赛克能源管理系统全栈开发复盘:从零完成 SpringBoot + MySQL + Redis 项目,踩遍 Web 经典坑后我总结了这些教训
2026/6/12 22:14:48 网站建设 项目流程

文章速读
本文是一篇完整的 Java Web 全栈项目实战复盘。从项目构思、技术选型、用户模块开发、登录认证(MD5 + JWT + Redis)、接口联调,到最终部署在阿里云服务器,我完整走了一遍企业级 Web 应用的开发流程。
文中真实记录了 5 个最常见的“劝退级”错误:400 参数绑定异常、数据库字段非空导致的 500、MyBatis 映射失败、context-path 引起的 404、云服务器部署端口与进程问题。每个问题都附带了错误日志 → 原因分析 → 解决方案 → 验证方法,并提炼成一张【排错速查表】。
如果你是正在学习 SpringBoot 的初学者,这篇文章至少能帮你少踩 10 个坑。


📌 目录

  • 一、项目背景与开发心路

  • 二、技术选型全景解读

    • 2.1 技术栈表格

    • 2.2 为什么选这套组合

  • 三、项目结构与分层设计

    • 3.1 代码目录树

    • 3.2 核心数据库表设计

  • 四、用户模块 CRUD:三个“新手必炸”的坑

    • 坑 1:@RequestBody 用错导致 400

    • 坑 2:数据库非空字段引发的 500

    • 坑 3:MyBatis-Plus 驼峰映射失效

  • 五、登录认证模块(MD5 + JWT + Redis)深度实现

    • 5.1 为什么不只用 JWT?

    • 5.2 完整流程图(Mermaid)

    • 5.3 核心代码与关键注释

  • 六、接口联调中的两个“隐藏杀手”

    • 6.1 context-path 导致的 404

    • 6.2 跨域问题(CORS)及 Nginx 解决方案

  • 七、阿里云部署实战:从 0 到上线

    • 7.1 环境准备与端口矩阵

    • 7.2 数据库与 Redis 配置

    • 7.3 Jar 包部署与后台启动

    • 7.4 Nginx 反向代理配置

    • 7.5 部署中踩的 2 个真实坑

  • 八、面向高分的排错方法论总结

    • 8.1 排错五步法

    • 8.2 常见 Web 错误速查表

  • 九、后续优化计划(含 Docker 方向)

  • 十、致谢与互动


一、项目背景与开发心路

本学期《Web 应用项目开发》课程要求独立完成一个具有实际业务场景的全栈项目。我选择了“赛克能源管理系统”——一个面向企业内部能源数据管理的小型系统,核心模块包括:

  • 用户管理(增删改查)

  • 登录认证(JWT + Redis)

  • 能耗数据填报与统计(接口预留,后续扩展)

整个项目耗时约20 天,从最初连@Autowired@Resource都分不清,到最后在阿里云上稳定运行。这期间我经历了无数次 400、404、500,也曾在凌晨两点盯着Field 'nick_name' doesn't have a default value怀疑人生。

但正是这些错误,让我真正理解了 Web 开发的本质。本文不会只展示“最终正确代码”,而是完整还原每一次踩坑、定位、解决问题的全过程。


二、技术选型全景解读

2.1 技术栈表格

层级技术版本核心作用
前端HTML5 + CSS3 + ES6基础页面与交互(后续可升级为 Vue)
后端框架SpringBoot2.7.6简化配置、内嵌 Tomcat、自动装配
ORMMyBatis-Plus3.5.5单表操作零 SQL,分页插件友好
数据库MySQL8.0.31持久化存储用户、能耗数据
缓存Redis5.0.14存储 JWT 令牌,实现主动失效
安全JWT + MD5无状态认证 + 密码不可逆加密
部署阿里云轻量 + 宝塔 + NginxCentOS 7.9生产环境部署与反向代理
工具IDEA + Navicat + Postman + Git2023 / 16开发、调试、版本控制

2.2 为什么选这套组合

  • SpringBoot 2.7:稳定版本,文档丰富,社区活跃,同时兼容我后续想整合的 Spring Security。

  • MyBatis-Plus:相比原生 MyBatis 省去大量 XML 配置,且自带乐观锁、分页、代码生成器。

  • Redis + JWT:纯 JWT 无法做到服务端主动失效(比如踢用户下线),结合 Redis 缓存 token 后,登出时删除 Redis key 即可完美解决。

  • 宝塔面板:对新手最友好的 Linux 运维工具,文件管理、端口放行、Nginx 配置都可可视化操作。


三、项目结构与分层设计

3.1 代码目录树

text

src/main/java/com/saike/ems/ ├── common │ ├── R.java // 统一响应结果封装 │ └── ResultCode.java // 业务状态码枚举 ├── config │ ├── CorsConfig.java // 跨域配置 │ ├── RedisConfig.java // Redis 序列化配置 │ └── MybatisPlusConfig.java // 分页插件配置 ├── controller │ └── UserController.java ├── service │ ├── IUserService.java │ └── impl │ └── UserServiceImpl.java ├── mapper │ └── UserMapper.java ├── entity │ └── User.java ├── dto │ └── LoginDTO.java ├── interceptor │ └── JwtInterceptor.java // 全局令牌校验 └── utils ├── Md5Util.java └── JwtUtil.java

3.2 核心数据库表设计

sql

CREATE TABLE `user` ( `user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID', `user_name` varchar(50) NOT NULL COMMENT '用户名', `password` char(32) NOT NULL COMMENT 'MD5加密后的密码', `nick_name` varchar(50) DEFAULT '赛克用户' COMMENT '昵称', `status` tinyint DEFAULT '1' COMMENT '状态:1正常 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`user_id`), UNIQUE KEY `uk_user_name` (`user_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

设计说明

  • password固定 32 位(MD5 输出长度)

  • nick_name设置了默认值,避免新增时因非空约束失败(这就是坑 2 的根源)

  • 用户名唯一索引,防止重复注册


四、用户模块 CRUD:三个“新手必炸”的坑

坑 1:@RequestBody 用错导致 400

现象
前端用 Postman 以x-www-form-urlencoded方式提交,后端报错:

text

org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing

错误代码

java

@PostMapping("/add") public R addUser(@RequestBody User user) { // ❌ 错误 return userService.save(user) ? R.ok() : R.fail(); }

分析
@RequestBody要求请求Content-Type: application/json,并且 body 中是 JSON 字符串。而表单提交的Content-Typeapplication/x-www-form-urlencoded,参数以key1=value1&key2=value2形式放在 body 中,两者完全不兼容。

正确写法

java

@PostMapping("/add") public R addUser(User user) { // ✅ 直接使用实体接收 return userService.save(user) ? R.ok() : R.fail(); }

或者显式接收参数

java

public R addUser(@RequestParam String userName, @RequestParam String password) { User user = new User(); user.setUserName(userName); user.setPassword(Md5Util.md5(password)); // ... }

✅ 验证:Postman 中选择x-www-form-urlencoded,输入字段后请求返回 200。


坑 2:数据库非空字段引发的 500

现象
新增用户时控制台报错:

text

### SQL: INSERT INTO user (user_name, password) VALUES ( ?, ? ) ### Cause: java.sql.SQLException: Field 'nick_name' doesn't have a default value

分析
数据库nick_name字段定义为NOT NULL,且建表时未设置DEFAULT值。而实体类中nickName默认为null,MyBatis-Plus 生成的 INSERT 语句只包含user_namepassword,导致数据库拒绝插入。

解决方案

方案一(推荐):修改数据库,增加默认值

sql

ALTER TABLE `user` MODIFY COLUMN `nick_name` varchar(50) DEFAULT '默认昵称' NOT NULL;

方案二(代码兜底):在业务层判断并设置默认值

java

if (user.getNickName() == null || user.getNickName().isEmpty()) { user.setNickName("赛克用户"); }

✅ 验证:再次执行 INSERT,日志显示插入成功,数据库中出现新增记录。


坑 3:MyBatis-Plus 驼峰映射失效

现象
查询用户列表,返回的username字段全是null,但数据库中user_name有值。

分析
MyBatis-Plus 默认开启mapUnderscoreToCamelCase,会将user_name映射为userName
而我实体类中属性名是username(全小写),无法匹配。

错误实体类

java

@TableName("user") public class User { private Integer userId; private String username; // ❌ 数据库是 user_name }

正确实体类

java

@TableName("user") public class User { @TableId("user_id") private Integer userId; @TableField("user_name") // ✅ 显式映射 private String username; @TableField("nick_name") private String nickName; }

或者统一命名风格
将实体类属性改为userName,利用默认驼峰转换(推荐)。

✅ 验证:查询接口返回数据中 username 正常显示。


五、登录认证模块(MD5 + JWT + Redis)深度实现

5.1 为什么不只用 JWT?

纯 JWT 的痛点:无法主动失效。用户修改密码或退出登录后,旧 token 在过期前仍然有效。
解决方案:服务端将 JWT 存入 Redis,key 为用户唯一标识(如 userId),value 为 token。
每次请求携带 token 时,不仅要校验 JWT 签名,还要校验 Redis 中的 token 是否存在且一致。

5.2 完整流程图(Mermaid)

5.3 核心代码与关键注释

java

@PostMapping("/login") public R<String> login(@RequestBody LoginDTO loginDTO) { // 1. 查询用户(已写在service中) User user = userService.getByUserName(loginDTO.getUserName()); if (user == null) { return R.fail("用户名不存在"); } // 2. 密码校验(MD5) String encrypted = Md5Util.md5(loginDTO.getPassword()); if (!encrypted.equals(user.getPassword())) { return R.fail("密码错误"); } // 3. 生成 JWT Map<String, Object> claims = new HashMap<>(); claims.put("userId", user.getUserId()); claims.put("userName", user.getUserName()); String token = JwtUtil.generate(claims); // 4. 存储到 Redis(1小时过期) String redisKey = "token:user:" + user.getUserId(); stringRedisTemplate.opsForValue().set(redisKey, token, 1, TimeUnit.HOURS); return R.ok(token); }

登出逻辑:删除 Redis key 即可实现主动失效。

java

@PostMapping("/logout") public R<String> logout(@RequestHeader("Authorization") String token) { // 解析token获取userId Integer userId = JwtUtil.getUserId(token); stringRedisTemplate.delete("token:user:" + userId); return R.ok("已退出"); }

六、接口联调中的两个“隐藏杀手”

6.1 context-path 导致的 404

现象
前端请求/user/list返回 404,但后端明明写了@GetMapping("/user/list")

分析
application.yml中配置了server.servlet.context-path: /api,导致后端实际路径为/api/user/list。前端请求没有加/api

解决
统一规范:前端 axios 配置baseURL: '/api',所有请求自动加上前缀。

6.2 跨域问题(CORS)及 Nginx 解决方案

开发阶段直接在 SpringBoot 中配置跨域过滤器:

java

@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

生产环境更推荐用 Nginx 反向代理解决(见第七部分)。


七、阿里云部署实战:从 0 到上线

7.1 环境准备与端口矩阵

端口服务宝塔放行阿里云安全组放行
80Nginx
9763SpringBoot Jar
3306MySQL❌ 仅本地
6379Redis❌ 仅本地
8888宝塔面板

7.2 数据库与 Redis 配置

  • 在宝塔中创建数据库ems,导入 SQL 文件。

  • 修改application-prod.yml

yaml

spring: datasource: url: jdbc:mysql://localhost:3306/ems?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你的密码 redis: host: localhost port: 6379

7.3 Jar 包部署与后台启动

bash

# 上传jar包到 /www/wwwroot/ems cd /www/wwwroot/ems # 停止旧进程(如果有) pkill -f ems-0.0.1-SNAPSHOT.jar # 后台启动 nohup java -jar ems-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > logs/ems.log 2>&1 & # 查看日志 tail -f logs/ems.log

7.4 Nginx 反向代理配置

nginx

server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /www/wwwroot/ems-front; index index.html; } # 后端API代理(解决跨域) location /api/ { proxy_pass http://127.0.0.1:9763/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

7.5 部署中踩的 2 个真实坑

坑 1:阿里云安全组开了 9763,但宝塔防火墙没开 → 仍然无法访问 → 两边都要放行。
坑 2:多次部署后出现Address already in uselsof -i:9763找到进程kill -9再重启。


八、面向高分的排错方法论总结

8.1 排错五步法

步骤行动产出
1. 复现用相同参数再次请求,记录完整错误信息错误现象确定
2. 看日志后端控制台 /tail -f server.log异常堆栈 + 错误行号
3. 定位根据堆栈找到对应代码行问题代码位置
4. 查证搜索引擎 + 官方文档 + Stack Overflow2~3 种候选方案
5. 验证每次只改一个变量,测试通过后复盘永久解决问题

8.2 常见 Web 错误速查表

HTTP 状态常见原因排查方向
400参数类型不匹配 /@RequestBody用错检查请求头 Content-Type,对比后端参数注解
401未认证或 Token 失效查看 Redis 中 token 是否存在,检查 JWT 过期时间
404路径错误 / context-path 遗漏对比后端@RequestMapping和前端的完整 URL
500数据库字段缺失 / 空指针查看完整 SQL 日志,检查数据库非空约束
502后端服务未启动 / 端口不通ps -ef | grep java,检查防火墙

九、后续优化计划(含 Docker 方向)

  • 统一异常处理@RestControllerAdvice接管所有异常,返回规范格式。

  • 参数校验:引入spring-boot-starter-validation,使用@NotNull等注解。

  • Redis 验证优化:在拦截器中实现 token 校验,避免每个方法重复写。

  • 容器化部署:编写Dockerfile+docker-compose.yml,一键启动 MySQL、Redis、后端。

dockerfile

# 示例 Dockerfile FROM openjdk:11-jre COPY target/ems-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"]

十、致谢与互动

这门课程让我从“纸上谈兵”走向真正的工程实践。
感谢老师每节课对 Web 底层原理的剖析,感谢室友在我通宵调 Bug 时给予的精神支持。

如果本文帮你解决了一个实际 Bug,欢迎点赞 / 收藏 / 评论。
你在开发中遇到过哪些“离谱”的错误?评论区一起交流,我会定期回复。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询