SELECTED PROJECTS

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 的代码,都会想起那个什么都不懂、但什么都不怕的自己。

几个真实的感悟:

  1. 完成比完美重要。 LiYa 的代码现在看全是"坏味道"——没有异常处理、没有日志、密码明文存数据库。但它完成了,跑起来了,这就够了。

  2. 先跑起来,再跑得好。 不要一上来就追求"最佳实践"。先让功能 work,再回头重构。架构是在迭代中演进出来的,不是一开始就能设计出来的。

  3. 记录踩坑比记录成功更重要。 这篇文章里,最有价值的不是"我做了什么",而是"我踩了什么坑"。因为这些坑,下一个项目不会再踩。

  4. 学技术最好的方式就是做一个项目。 看十遍教程不如自己写一遍。报错、debug、修复——这个过程才是真正学习发生的地方。


写在最后

LiYa 现在静静躺在 GitHub 的仓库列表里,star 数为 0,issues 也为 0。它不会帮助我拿到 offer,也不会有人给我提 PR。

但它是我编程之路的起点。

如果一年前的我能看到现在的我,大概会惊讶于自己走了多远。而现在回头看 LiYa,那个歪歪扭扭的验证码、那个写错路径的表单、那个查了一下午的 null——都是路上最真实的脚印。

谢谢你,LiYa。


如果你也在刚开始学 Java Web,别怕写丑的代码。重要的是,动手去做。

—— 菜牙爱吃菜

从练习走向正式应用

明文密码属于待修复缺陷,应使用密码哈希;登录还需考虑会话固定攻击、CSRF、验证码有效期与重试限制。验证码绘图只是学习示例,不代表可靠的人机识别能力。

JavaSpring BootMyBatisMySQL