LiYa:第一个 Java Web 项目复盘
回顾登录、注册、验证码和 MVC 分层的练手项目,整理初学时的问题与改进方向。
项目文稿说明:本文按现有笔记整理,代码是节选或设计示意;仓库实现、线上地址和测试结果尚未逐项核验。后续计划不代表已经完成。
莉雅(LiYa)——我的第一个 Java Web 项目复盘
博客:菜牙爱吃菜 写于 2026 年 10 月
前言:那个春天写下的第一行 Java
2025 年 3 月,我在 GitHub 上创建了人生第一个正经的后端项目——LiYa(莉雅网页)。
说实话,当时连 Spring Boot 的依赖注入都没完全搞懂,就这么稀里糊涂地开干了。代码写得很丑,命名很随便,但那种"我居然真的把网站跑起来了"的兴奋感,到现在都记得清清楚楚。
这篇文章不是教程,更像是一封写给一年前的自己的信。聊聊这个项目做了什么、踩了哪些坑、以及它在我学习路上扮演了什么角色。
项目介绍:LiYa 是什么?
LiYa 是一个简单的个人网页应用,核心功能就两个:
- 用户登录
- 用户注册
听起来对吧?就是那种你在 B 站教程里看到的"练手项目"。但对我来说,它意味着从零到一——从 pom.xml 到能看到浏览器里跳出一个登录页面。
项目结构非常标准:
liya/
├── src/main/java/com/liya/
│ ├── controller/ # LoginController, RegisterController, ViewController
│ ├── service/ # UserService
│ ├── mapper/ # MyBatis Mapper
│ ├── entity/ # User 实体类
│ ├── interceptor/ # LoginInterceptor
│ └── utils/ # VerifyCode(验证码生成)
├── src/main/resources/
│ ├── static/ # 图片等静态资源
│ └── templates/ # HTML 页面
└── pom.xml
技术栈:当时我会什么
| 层面 | 技术 |
|---|---|
| 语言 | Java 17 |
| 框架 | Spring Boot 2.x |
| 构建 | Maven |
| 数据库 | MySQL |
| ORM | MyBatis |
| 前端 | 原生 HTML + CSS |
| 部署 | 本地 / Aliyun ECS |
没有 Redis、没有 Spring Security、没有 JWT。登录状态靠 Session,验证码靠自己手写的 VerifyCode 工具类生成图片。现在回头看,简直"原始人写法",但那时候觉得能生成一个歪歪扭扭的验证码图片就已经很了不起了。
代码片段:登录拦截器的初体验
让我印象最深的是 LoginInterceptor。当时第一次接触"拦截器"这个概念,感觉像发现了新大陆:
// 项目源码 src/main/java/com/liya/interceptor/LoginInterceptor.java
// preHandle 方法:每次请求到达 Controller 前执行
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
if (user == null) {
response.sendRedirect("/login");
return false; // 拦截!不许进!
}
return true; // 放行
就这么几行代码,实现了"没登录就不让看主页"。当时我反复测试:登录前访问首页 → 跳转到登录页 → 登录成功 → 正常进入。那种正反馈让我连续高兴了好几天。
验证码生成
VerifyCode 是我从各种博客里东拼西凑出来的:
// 项目源码 src/main/java/com/liya/utils/VerifyCode.java
// 用 Java 2D API 手绘验证码图片
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.setColor(new Color(240, 240, 240));
g.fillRect(0, 0, width, height);
// 画干扰线防止机器识别
java.security.SecureRandom random = new java.security.SecureRandom();
g.setColor(Color.LIGHT_GRAY);
for (int i = 0; i < 10; i++) {
g.drawLine(random.nextInt(width), random.nextInt(height),
random.nextInt(width), random.nextInt(height));
}
// 生成随机字符并画到图片上
String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
StringBuilder code = new StringBuilder();
for (int i = 0; i < codeLength; i++) {
char c = chars.charAt(random.nextInt(chars.length()));
code.append(c);
g.drawString(String.valueOf(c), 20 + i * 25, 30);
}
// 小写后存入 Session,和用户输入比对
session.setAttribute("verifyCode", code.toString().toLowerCase());
g.dispose();
那时候不知道什么 Captcha 库,也不知道 Kaptcha,就是这么硬画。图片模糊得像打了码,但功能确实 work。
踩过的坑(血泪史)
坑一:MyBatis 的 resultType 和 resultMap 分不清
<!-- src/main/resources/mapper/UserMapper.xml -->
<!-- 踩坑时写的:user_name 和 Java 的 userName 对不上 -->
<select id="findByUsername" resultType="com.liya.entity.User">
SELECT user_name, password FROM user WHERE user_name = #{username}
</select>
<!-- 修复:用别名让列名和实体属性对齐 -->
<select id="findByUsername" resultType="com.liya.entity.User">
SELECT user_name AS userName, password FROM user WHERE user_name = #{username}
</select>
数据库里叫 user_name,Java 里叫 userName。结果查出来的 User 对象全是 null,debug 了一整个下午才发现。后来学会了用 resultMap 做映射,或者直接在 SQL 里用别名 SELECT user_name AS userName。
坑二:Session 丢失之谜
登录成功后跳转页面,发现 Session 里的 user 不见了。查了半天才发现是前端表单的 action 路径写错,请求打到了另一个控制器上,还需核对 Cookie、域名、Session 创建/失效与控制器行为;路径变化本身不会自动清除 Session。
教训: 遇到问题先别怀疑框架,先看自己的代码。
坑三:中文乱码
数据库里存的中文全变成 ???。需要同时核对数据库/表/连接字符集与驱动版本,不能只用是否存在某个 URL 参数判断:
## 错误
spring.datasource.url=jdbc:mysql://localhost:3306/liya
## 正确
spring.datasource.url=jdbc:mysql://localhost:3306/liya?characterEncoding=utf8&useUnicode=true
学到的东西
虽然 LiYa 功能很简单,但它让我真正理解了:
1. MVC 架构到底在干什么
以前看理论觉得"Controller 接收请求 → Service 处理逻辑 → Mapper 操作数据库"很抽象。写完这个项目之后,这个流程终于变成了肌肉记忆。
2. HTTP 请求-响应模型
浏览器发请求 → 服务器处理 → 返回页面/数据。这个最基础的循环,在 Liya 里被重复了几十遍,终于刻进了脑子里。
3. 依赖注入(DI)的"啊哈"时刻
一开始觉得 @Autowired 很神奇——我没 new 啊,这个对象哪来的?后来理解了 IoC 容器,才明白是 Spring 帮我们创建并管理了对象。那种"原来如此"的感觉,比学会一个 API 重要得多。
4. 分层架构的意义
Controller、Service、Mapper 分开写,一开始觉得麻烦。但当业务逻辑变复杂时(比如注册时要校验用户名、检查验证码、加密密码),才发现分层是真的香。
成长反思:从 LiYa 到现在
写完 LiYa 之后,我又做了好几个项目,学了更多框架和工具。但每次回头看 LiYa 的代码,都会想起那个什么都不懂、但什么都不怕的自己。
几个真实的感悟:
-
完成比完美重要。 LiYa 的代码现在看全是"坏味道"——没有异常处理、没有日志、密码明文存数据库。但它完成了,跑起来了,这就够了。
-
先跑起来,再跑得好。 不要一上来就追求"最佳实践"。先让功能 work,再回头重构。架构是在迭代中演进出来的,不是一开始就能设计出来的。
-
记录踩坑比记录成功更重要。 这篇文章里,最有价值的不是"我做了什么",而是"我踩了什么坑"。因为这些坑,下一个项目不会再踩。
-
学技术最好的方式就是做一个项目。 看十遍教程不如自己写一遍。报错、debug、修复——这个过程才是真正学习发生的地方。
写在最后
LiYa 现在静静躺在 GitHub 的仓库列表里,star 数为 0,issues 也为 0。它不会帮助我拿到 offer,也不会有人给我提 PR。
但它是我编程之路的起点。
如果一年前的我能看到现在的我,大概会惊讶于自己走了多远。而现在回头看 LiYa,那个歪歪扭扭的验证码、那个写错路径的表单、那个查了一下午的 null——都是路上最真实的脚印。
谢谢你,LiYa。
如果你也在刚开始学 Java Web,别怕写丑的代码。重要的是,动手去做。
—— 菜牙爱吃菜
从练习走向正式应用
明文密码属于待修复缺陷,应使用密码哈希;登录还需考虑会话固定攻击、CSRF、验证码有效期与重试限制。验证码绘图只是学习示例,不代表可靠的人机识别能力。