ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot4 + MyBatis 实战踩坑复盘

SpringBoot4 + MyBatis 实战踩坑复盘

摘要:本文完整复盘了校园集市后端注册、登录模块的开发过程,从 SpringBoot4 版本适配、Maven Scope 区别、DTO 参数校验、Service 层业务校验、BCrypt 密码加密、自定义异常体系、MyBatis 注解开发、BeanUtils 对象拷贝到 JWT 令牌生成。

SpringBoot4 + MyBatis 实战踩坑

今天独立开发校园集市后端注册、登录模块,全程从零踩坑、逐个解决,涵盖Maven 依赖适配、JWT 整合、参数校验、密码加密、MyBatis 主键回填、自定义异常、对象拷贝等核心知识点。

一、SpringBoot4 版本适配坑(最开始的大问题)

1. 问题描述

项目使用 SpringBoot 4.x,导入 mybatis-spring-boot-starter 4.0.1 一直爆红、下载失败。

2. 原因

阿里云镜像滞后同步 4.0.1 小版本,导致拉取不到依赖。SpringBoot4 必须搭配对应高版本 starter,不能沿用 SpringBoot3、2 的依赖。

3. 最终解决方案

降级为镜像稳定同步的4.0.0(功能完全一致,无任何业务影响)。

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>4.0.0</version> </dependency>

同时统一采用properties 统一版本管理,全局只改一处即可升级依赖,规范项目结构。

二、Maven Scope 区别(runtime 踩坑)

JWT 依赖一直不明白为什么要写 runtime,今天彻底搞懂了:

  • compile(默认):编译、运行都需要
  • runtime:编译不需要,仅运行、打包需要(如 jjwt-impl、mysql 驱动)
  • provided:编译需要,运行容器提供,不打进包(lombok)

JWT 的 api 是编译用,impl、jackson 是运行实现,所以必须 runtime。

三、DTO 参数校验规范(彻底解决 IDEA 报红)

错误写法

在 Controller 参数上加 @NotNull,导致 IDEA 误以为需要注入 Bean,报红警告,且校验不生效。

正确规范(企业标准)

校验注解全部写在 DTO 字段上,Controller 参数仅加 @Valid 开启校验。

  • @NotBlank:非空、非空格(用户名、密码专用)

  • @Size(min = 6):限制密码最少6位

简单注解做基础校验,复杂业务校验全部放 Service 层。

四、Service 层业务校验(密码长度、账号重复)

前端注解校验只能防普通参数,业务规则必须写在 Service:

  • 密码长度不能小于6位

  • 查询用户名是否存在,存在则抛出注册异常

不再用普通 RuntimeException,自定义业务异常,分类清晰、前端提示精准。

五、密码加密(BCrypt 企业标准方案)

痛点

明文密码绝对不能入库,MD5 不安全、无盐可破解。

解决方案

使用 SpringSecurity 自带 BCrypt 加密:

  • 自动随机盐,无需手动存盐

  • 相同密码多次加密结果不同

  • 登录使用 matches() 比对,不比对密文字符串

// 加密(注册) String pwd = passwordEncoder.encode(dto.getPassword()); // 比对(登录) boolean ok = passwordEncoder.matches(明文, 数据库密文);

六、自定义异常体系(账号已存在 / 账号密码错误)

今天搭建了项目基础异常架构:

  • AccountExistException:注册重复

  • AccountOrPwdErrorException:登录失败统一异常

配合 @RestControllerAdvice 全局异常统一拦截,Controller 零 try-catch,代码极度干净。

安全规范:登录失败不区分“账号不存在/密码错误”,统一提示,防止暴力遍历用户名。

七、MyBatis 注解开发踩坑

1. SQL 多余单引号报错

SQL 语句末尾多写一个 ',编译不报红,运行直接报错,属于隐形大坑。

2. Mapper 参数取值错误

Mapper 方法参数是实体,取值直接写 #{username},不能写 #{user.username}。

3. 自增主键返回

通过 @Options 实现主键回填,无需返回值,直接回填到原实体对象:

@Insert("insert into users(username,password,nickname) values(#{username},#{password},#{nickname})") @Options(useGeneratedKeys = true, keyProperty = "id") void addUser(User user);

八、对象属性拷贝 BeanUtils.copyProperties

解决 DTO、Entity 快速转换问题:

  • 同名属性自动复制

  • 目标不存在的字段自动忽略

  • 可忽略指定字段(密码、id)

核心口诀:copyProperties(源对象, 目标对象),顺序绝对不能颠倒。

九、JWT 令牌生成架构搭建

适配 SpringBoot4 + JDK17,使用 jjwt 0.12.6 最新版本,摒弃旧版废弃 API:

  • 统一签发过期时间

  • 存储用户ID、用户名载荷

  • 统一解析、校验、过期判断

为后续登录返回 Token、拦截器鉴权打下基础。

登录模块完整时序图

下面用 Mermaid 时序图串联登录模块的完整调用链:Controller 接收请求、Service 业务校验、BCrypt 密码比对、JWT 生成,最终把 Token 返回给前端。

流程要点:Controller 只负责接收参数和返回结果,业务校验、密码比对、Token 生成全部下沉到 Service 层;登录失败统一抛出 AccountOrPwdErrorException,由全局异常处理器统一提示,避免暴露账号是否存在,兼顾安全与代码整洁。

十、今日项目整体架构总结

今日完整搭建:

  1. Maven 依赖版本统一管理

  2. DTO 参数校验 + Service 业务双层校验

  3. BCrypt 密码加密安全机制

  4. 自定义异常 + 全局异常处理器

  5. MyBatis 注解 CRUD + 主键回填

  6. JWT 工具类封装

  7. BeanUtils 对象转换规范

十一、个人复盘感悟

今天解决的全部是新手最容易踩的隐形坑:版本适配、依赖scope、校验分层、异常体系、密码安全、MyBatis细节。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进