摘要:本文完整复盘了校园集市后端注册、登录模块的开发过程,从 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,由全局异常处理器统一提示,避免暴露账号是否存在,兼顾安全与代码整洁。
十、今日项目整体架构总结
今日完整搭建:
Maven 依赖版本统一管理
DTO 参数校验 + Service 业务双层校验
BCrypt 密码加密安全机制
自定义异常 + 全局异常处理器
MyBatis 注解 CRUD + 主键回填
JWT 工具类封装
BeanUtils 对象转换规范
十一、个人复盘感悟
今天解决的全部是新手最容易踩的隐形坑:版本适配、依赖scope、校验分层、异常体系、密码安全、MyBatis细节。