一、前言:本学期《Web 应用项目开发》课程,我从零完成了赛克能源管理系统的全栈开发、接口联调与云服务器部署。项目采用 SpringBoot 2.7 + MyBatis-Plus + MySQL 8.0 + Redis 5.0 技术栈,实现了用户管理、登录认证、JWT 令牌校验、能耗统计等核心功能。开发过程中,我踩遍了 Web 开发的经典坑:接口 400/404/500 错误、数据库字段映射失败、参数绑定异常、跨域问题、云服务器部署失败等。本文将完整记录我的开发流程、踩坑经历、解决方案与学习心得,不仅是课程作业的总结,也希望能为其他 Java Web 初学者提供一份可落地的实战指南。

二、项目整体架构与技术栈

2.1 技术选型

本项目采用企业级主流的三层架构 + MVC 设计模式,前后端分离开发,技术栈如下:

表格

层级 技术 / 工具 版本 作用
前端 原生 HTML + CSS + JavaScript - 页面渲染与用户交互
后端 SpringBoot 2.7.6 快速搭建 Web 应用
ORM 框架 MyBatis-Plus 3.5.5 简化数据库操作
数据库 MySQL 8.0.31 持久化存储业务数据
缓存 Redis 5.0.14 存储 JWT 令牌,实现会话管理
部署 阿里云轻量应用服务器 + 宝塔面板 - 线上部署与运维
开发工具 IDEA 2023 + Navicat 16 + Postman - 代码编写、数据库管理、接口测试

2.2 项目目录结构

plaintext

ems/
├── src/main/java/com/lzpu/
│   ├── base/          # 基础类(R返回结果、User实体类)
│   ├── config/        # 配置类(跨域配置、MyBatis配置)
│   ├── controller/    # 控制层(接收请求,返回响应)
│   ├── service/       # 业务层(处理业务逻辑)
│   │   └── impl/      # 业务实现类
│   ├── mapper/        # 数据访问层(操作数据库)
│   └── utils/         # 工具类(MD5加密、JWT工具)
├── src/main/resources/
│   ├── mapper/        # MyBatis XML映射文件
│   ├── static/        # 静态资源(前端测试页面)
│   └── application.yml # 项目配置文件
└── pom.xml            # Maven依赖配置

三、核心功能开发与经典踩坑解决

3.1 用户模块 CRUD 开发

用户模块是项目的基础,包含新增、删除、修改、查询单个用户、查询所有用户五大接口。这部分开发我踩了 3 个最典型的坑,也是初学者最容易遇到的问题。

坑 1:参数绑定异常(400 错误)

问题描述:一开始我在addUser方法上加上了@RequestBody注解,前端用普通表单提交时,直接抛出Required request body is missing 400 错误。

java

运行

// 错误写法
@PostMapping("/add")
public R addUser(@RequestBody User user) { ... }

错误分析@RequestBody只能接收 JSON 格式的请求体,而普通表单提交的Content-Typeapplication/x-www-form-urlencoded,Spring 无法解析。

正确写法:表单提交直接用实体类接收,Spring 会自动根据表单名与实体属性完成绑定

java

运行

// 正确写法
@PostMapping("/add")
public R addUser(User user) { ... }
坑 2:数据库字段非空错误(500 错误)

问题描述:新增用户时,后端日志报错Field 'nick_name' doesn't have a default value,前端返回 500 错误。

plaintext

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

错误分析:数据库user表中nick_name字段设置为NOT NULL且无默认值,但我在新增时只传递了用户名和密码,导致数据库插入失败。

解决方案

  1. 后端对必填字段做非空校验
  2. 对未传的必填字段设置合理的默认值

java

运行

@PostMapping("/add")
public R addUser(
        @RequestParam("username") String username,
        @RequestParam("password") String password
) {
    // 1. 非空校验
    if (username == null || username.isEmpty()) {
        return R.fail("用户名不能为空");
    }
    if (password == null || password.isEmpty()) {
        return R.fail("密码不能为空");
    }

    // 2. 手动封装对象,设置默认值
    User user = new User();
    user.setUsername(username);
    user.setPassword(Md5Util.md5(password));
    user.setNickName("默认昵称"); // 给nick_name设置默认值

    int result = userService.addUser(user);
    if (result > 0) {
        return R.ok("新增成功!用户名:" + username);
    }
    return R.fail("新增失败");
}

坑 3:数据库字段与实体属性映射失败

问题描述:查询用户时,username字段始终为null,但数据库里明明有数据。

错误分析:数据库字段是user_name(下划线命名),而实体类属性是username(全小写),虽然 MyBatis 开启了下划线转驼峰,但全小写的username无法与user_name匹配。

解决方案:使用@TableField注解显式映射数据库字段

java

运行

@Data
@TableName(value="user")
public class User {
    @TableId(value="user_id", type= IdType.AUTO)
    private Integer userId;
    
    @TableField(value="user_name") // 显式映射数据库字段
    private String username;
    
    @TableField(value="nick_name")
    private String nickName;
    
    // 其他字段...
}

3.2 登录认证模块(MD5 + JWT + Redis)

登录认证是本项目的核心,也是企业级 Web 应用的标配。我采用了MD5 密码加密 + JWT 令牌 + Redis 缓存的组合方案,三者分工明确:

  • MD5:对密码进行不可逆加密,数据库只存密文,防止密码泄露
  • JWT:生成包含用户信息的身份令牌,作为登录凭证
  • Redis:缓存 JWT 令牌并设置过期时间,实现登录状态管理
登录流程
  1. 前端提交用户名和密码
  2. 后端根据用户名查询数据库
  3. 将前端传入的密码进行 MD5 加密,与数据库中的密文对比
  4. 密码正确则生成 JWT 令牌
  5. 将令牌存入 Redis,设置 1 小时过期
  6. 返回令牌给前端
核心代码实现

java

运行

@PostMapping("/login")
public R<String> login(@RequestBody LoginInfo loginInfo) {
    // 1. 根据用户名查询用户
    User loginUser = userService.getUserByUsername(loginInfo.getUsername());
    
    // 2. 校验用户名
    if (loginUser == null) {
        return R.fail("用户名不存在");
    }
    
    // 3. 校验密码(MD5加密对比)
    if (!Md5Util.md5(loginInfo.getPassword()).equals(loginUser.getPassword())) {
        return R.fail("密码错误");
    }
    
    // 4. 生成JWT令牌
    Map<String, Object> claims = new HashMap<>();
    claims.put("userId", loginUser.getUserId());
    claims.put("username", loginUser.getUsername());
    String token = JWTUtil.genToken(claims);
    
    // 5. 将令牌存入Redis,过期时间1小时
    stringRedisTemplate.opsForValue().set(
            loginInfo.getUsername(), 
            token, 
            1, 
            TimeUnit.HOURS
    );
    
    // 6. 返回令牌给前端
    return R.ok(token);
}

3.3 接口路径 404 问题

问题描述:前端请求接口时,频繁出现 404 错误,但后端 Controller 明明已经写好了对应的接口。

错误分析:我在application.yml中配置了server.servlet.context-path: /home,给所有接口加了统一前缀/home,但前端请求时没有加上这个前缀。

yaml

server:
  port: 9763
  servlet:
    context-path: /home # 统一接口前缀

解决方案:前端所有请求地址都加上/home前缀

javascript

运行

// 错误写法
fetch(`/user/add?username=测试用户&password=123456`, {method: 'POST'})

// 正确写法
fetch(`/home/user/add?username=测试用户&password=123456`, {method: 'POST'})

四、云服务器部署实战(宝塔面板)

本地功能调试完成后,我将项目部署到了阿里云轻量应用服务器,使用宝塔面板进行管理,部署步骤如下:

4.1 环境准备

  1. 阿里云服务器开放端口:80(网页)、9763(后端)、3306(MySQL)、6379(Redis)、8888(宝塔面板)
  2. 宝塔面板安装软件:Nginx 1.20、MySQL 8.0、Redis 6.2、OpenJDK 11

4.2 数据库部署

  1. 宝塔面板创建数据库ems,设置用户名和密码
  2. 使用 Navicat 连接云数据库,执行建表语句,导入基础数据

4.3 后端部署

  1. 修改后端application.yml配置,将数据库和 Redis 地址改为云服务器地址
  2. Maven 打包:mvn clean package -DskipTests
  3. 上传 Jar 包到服务器/www/wwwroot/backend目录
  4. 后台启动后端服务:

bash

运行

cd /www/wwwroot/backend
pkill -f java # 停止旧进程
nohup java -jar ems-0.0.1-SNAPSHOT.jar > server.log 2>&1 &
  1. 查看运行日志:tail -f server.log   

4.4 前端部署

  1. 前端打包:npm run build(如果是 Vue 项目)
  2. 宝塔面板创建站点,端口 8081
  3. 上传前端打包文件到站点根目录
  4. 配置 Nginx 反向代理,解决跨域问题

五、学习心得与总结

经过一个学期的学习和实践,我不仅掌握了 SpringBoot 全栈开发技术,更重要的是培养了独立解决问题的能力和工程化思维。以下是我的几点深刻感悟:

5.1 规范大于技巧

编码规范是避免 80% 低级错误的关键。在开发中,我深刻体会到:

  • 数据库字段使用下划线命名,Java 实体类使用驼峰命名
  • 前后端参数名保持一致
  • 接口路径遵循 RESTful 规范
  • 代码分层清晰,Controller 只接收请求,Service 处理业务,Mapper 只操作数据库

5.2 报错优先看日志

遇到问题不要盲目改代码,后端日志是最好的老师。无论是 400、404 还是 500 错误,日志都会明确告诉你错误原因和位置。学会看日志、分析日志,是程序员的必备技能。

5.3 理论结合实操

课堂上学到的 UML 建模、HTTP 协议、TCP/IP、三层架构等理论知识,在实战中都有了具体的体现。只有将理论与实操结合起来,才能真正理解技术的本质,做到举一反三。

5.4 排错能力是核心竞争力

开发过程就是不断踩坑、不断排错的过程。每解决一个问题,都是对知识点的一次巩固。慢慢的,我形成了一套自己的排错思路:

  1. 复现问题,记录错误现象
  2. 查看后端日志,定位错误位置
  3. 分析错误原因,查阅资料
  4. 尝试解决方案,验证效果
  5. 总结经验,避免再次踩坑

六、后续计划

本次课程项目虽然完成了核心功能,但还有很多可以优化的地方:

  1. 增加统一异常处理,优化错误提示
  2. 实现接口参数校验,使用 Validation 框架
  3. 完善权限管理,实现不同角色的权限控制
  4. 优化前端页面,使用 Vue + Element UI 重构
  5. 学习 Docker 容器化部署,简化运维流程

七、结语

《Web 应用项目开发》这门课程让我从一个只会写 Hello World 的小白,成长为能够独立完成一个小型 Web 项目全流程开发的开发者。感谢老师的悉心指导,也感谢自己在无数个熬夜调试的夜晚没有放弃。

编程之路没有捷径,唯有不断学习、不断实践、不断踩坑,才能不断进步。希望这篇文章能对正在学习 Java Web 的同学有所帮助,也欢迎大家在评论区交流讨论。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐