x
我先设计common
因为队长接口有CreateTime,所以我添加个自动填充字段
里面设置「创建时间、更新时间、创建人、更新人」
用常量 SET_CREATE_TIME
拼写错误会直接提示「找不到该常量」(编译期报错),提前暴露问题;
IDE 会自动补全常量名,降低拼写错误概率。
常量加了 public static final:
public:保证其他包的类(比如上面的处理器)能访问;static:无需创建类的实例,直接通过类名.常量名调用;
直接AutoFillConstant.SET_CREATE_TIME
final:保证常量值不可被修改,符合「常量」的语义。
在纯 MyBatis 开发中,需要做这些事:
- 写 Mapper 接口 + XML 文件(或注解),哪怕是简单的「查询所有」「根据 ID 查询」也要写 SQL;
- 分页、排序、条件查询需要手动拼 SQL,容易出错;
- 公共字段(如创建时间、更新人)需要手动赋值,代码重复。
而 MyBatis-Plus 帮你解决了这些问题,核心特性包括:
1. 无侵入式增强
完全兼容 MyBatis,你可以在原有 MyBatis 项目中直接引入 MP,既保留 MyBatis 的灵活性,又享受 MP 的便捷。
2. 通用 CRUD 封装
内置
BaseMapper接口,只要你的 Mapper 继承它,就能直接使用「增删改查」方法,无需写任何 SQL:
1 | @TableId(type = IdType.AUTO) // 主键自增 |
那我现在借鉴的就是MS了
Bearer Token规范
令牌的传输位置:HTTP 请求头(Authorization)
令牌的格式:固定前缀 + 空格 + 令牌字符串
1 | // 3. 提取令牌(去掉Bearer和空格) |
然后我设计了jwt断言常量,是user数据里删去password
接下来是发送异常信息常量,暂时有登陆异常信息,假设一个队长登录页面,前端校验必填username和password,username查不到回显“用户不存在”,password回显“密码错误”,尝试爆破回显“账号被锁定”,否则回显未知错误
然后写死超级管理员的密码是123456
然后状态码是对应四种角色
防止多线程下id共享,设置BasrContext线程级别的上下文容器
然后加入枚举类,让aop自动填充
然后根据异常信息errmsg设置exception异常类
先设置baseException异常类继承
补充:业务异常是比如说用户密码错误,系统异常是程序员代码写错了
RuntimeException 是 Java 异常体系中 “非受检异常(Unchecked Exception)” 的父类
所以我理解runtimeexception就是让代码简化,抑制报错,和@一样
再复习一下,子类除了父类的构造方法不能继承之外,其他都能继承
子类的构造方法执行时,必须先调用父类的构造方法(初始化父类的成员),再执行子类自己的构造逻辑;
如果子类构造方法中没有显式写 super(...),Java 会自动在第一行插入 super()(调用父类的无参构造方法);
如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法(否则编译报错)。
传参时就加super(参数)
RuntimeException 不需要强制捕获,Exception 需要。
然后优化一下createtime展示
LocalDateTime是从数据库那里获得的
1 | Controller → Service → Mapper → 数据库 |
Mapper 是“数据库访问接口”。
写的是接口,没有实现类。
但运行时:
MyBatis 会动态生成实现类。****
MyBatis 里叫 DAO 或 Mapper
SimpleModule 是:
给 Jackson 增加“自定义规则”的插件模块。
然后配置jwtproperties和aliproperties
springbot里propeities指的是application.yml里的配置项
把配置文件里的属性,映射成 Java 对象。
1 | @ConfigurationProperties(prefix = "user.jwt") |
1 | application.yml |
Spring 绑定规则是:
1 | user.jwt.secret-key |
会绑定到:
1 | private String secretKey; |
再复习
@Data 是 Lombok 提供的一个注解,用来“自动生成一堆样板代码”。
然后设置统一返回结果result
再设计utils
先放个jwtutils
JavaDoc 注释
System.currentTimeMillis():Java 中获取当前系统时间的毫秒数(时间戳)
ttlMillis:全称是 Time-To-Live in Milliseconds,即「令牌的存活时长(毫秒)」,是你自定义的参数(比如设置 3600000 毫秒 = 1 小时)
两者相加:当前时间戳 + 存活时长 = 令牌过期的时间戳(expMillis)。
Date:这是 Java 核心库 java.util.Date 类,用来表示一个「具体的时间点」(精确到毫秒),是 Java 中处理日期和时间的基础类。
new Date(expMillis):调用 Date 类的构造方法,传入一个毫秒级时间戳(也就是你之前问的 expMillis),把这个数字转换成对应的日期对象。
返回值 Claims:JJWT 库封装的「声明集合」对象,本质是一个键值对容器(类似 Map);
然后开始设置dto和vo
中途把controller删除了,改了spring的配置,成功启动项目,圆梦2025ohhhhhhhhhhhhhhhhh
如果请求方式一样,四种角色可以合成一个logindto
再设计vo,vo对应的是data的返回值,如果直接返回实体的话
会把password返回给前端,所以专门设计个vo来把返回数据分离开
implements Serializable:让对象支持序列化,比如前端和后端通过 HTTP 传输 JSON 时,底层会用到序列化(或框架自动处理)。
然后这里我引入了一个依赖springfox
在
1 | http://localhost:8080/doc.html |
里面可以测试
@RestController是@Controller + @ResponseBody的组合注解,它的核心作用有两个:
- 标识控制器:告诉 Spring 这是一个处理 HTTP 请求的控制器类,能接收前端的请求并返回响应。
- 自动返回 JSON/XML 数据:默认将方法的返回值直接转换为 JSON(或 XML)格式的响应体,而不是跳转到视图页面(这是和
@Controller最核心的区别)。
然后我有个疑问
admin和judge和academy不需要登录吗,如果直接在后台的话如何进行身份验证
超级管理员和二级管理员可以看到所有的比赛,队长只能看到自己参加的比赛
问了ai说要把if判断加载service层
一开始想把它乱加在vo层的
VO 负责“展示数据”
Service 负责“业务逻辑”
权限判断属于 —— 业务逻辑
所以必须放在 Service 层
安全风险
假设:
- Service 查出所有比赛
- VO 再根据角色过滤
问题是:
👉 数据已经查出来了
如果后面某个开发忘记调用 VO 过滤方法
或者直接返回 entity
那队长就能看到所有比赛了。
权限必须在数据查询阶段就控制。
然后在mapper层写两个方法,再再xml里面写查询语句,之前写登录页面的时候有个注解式写法
service校验身份,mapper层传递usercode
mapper 根标签 + namespace
1 | <mapper namespace="com.julien.mapper.CompetitionListMapper"> |
<mapper>是 MyBatis 映射文件的根标签namespace必须写成 对应 Mapper 接口的全限定名- 这里对应你的接口:
com.julien.mapper.CompetitionListMapper
- 这里对应你的接口:
- 意思是:这个 XML 里的 SQL,是给这个接口用的
后面
<select id="...">的id就必须和接口方法名对应。
mapper调用的是xml里面的id,id前面对应查表动作“增删改查”
SQL 主体:
1 | SELECT DISTINCT c.* |
c.*:只取比赛表competition的字段DISTINCT:去重- 因为一个队长可能有多个队伍/或同一比赛可能连接出多行,DISTINCT 防止重复比赛出现
我要根据队长id查到team id
如果vo层返回的数据包含实体,就直接返回给vo
写到一半发现我的jwt拦截器Interceptor 还没装
1 | 请求 |
当你希望某个类的对象交给 Spring 容器管理,而非自己手动 new 时,就需要用 @Component
1 |
|
组合起来等价于手写
1 | import java.util.Date; |
添加完拦截器还要添加配置类
role 不是通过 return 传递
而是通过 ThreadLocal 在线程内共享
1 | 请求进来 |
什么时候用枚举?
满足以下情况建议用枚举:
- 状态(订单状态)
- 角色
- 性别
- 审核状态
- 支付状态
- 比赛状态
只要是:
固定可选项
就优先枚举。
什么时候用常量?
- HTTP 状态码
- 错误码
- 配置键
- 非“类型”的固定字符串
用 ThreadLocal(线程上下文)存用户信息更好,还是每次都查数据库更好?”(比如查 user 表拿 role/权限)。
结论先说:
- 绝大多数场景:线程里存(ThreadLocal)更好
- 但要保证 token 可信、权限变化要可控,否则需要查表/缓存校验
1)线程里存(ThreadLocal)的优缺点
✅ 优点
- 快:取值是内存读取 O(1),不需要 IO
- 少查库:同一次请求里 Controller/Service/Mapper 都能用同一份 userId/role
- 代码干净:不用每个方法都传 userId/role 参数
❌ 缺点(要注意)
- 只能在同一次请求的同一线程有效
- 必须
remove(),否则线程复用会“串号” - 如果你的 role/权限会频繁变更,token 里旧 role 可能过期(需要额外策略)
2)每次查表(数据库)的优缺点
✅ 优点
- 最实时:role/权限一改,马上生效
- 更安全:可以做黑名单、冻结用户、强制下线等
❌ 缺点
- 慢:每次查库是网络/磁盘 IO
- 压力大:并发一高数据库扛不住
- 代码也更繁琐
,resultType 需要使用完整的类名,而不是简单的类名
我测了,测了半天@@RequestBody没加,一直返回500
我测了,加了@RequestBody还是不行,这个时候要看控制台,再别看线程和变量了
现在我找到了问题所在!在 UserMapper.java 文件中,第9行的 SQL 查询使用了 UserCode 作为列名,但数据库中实际的列名可能是 user_code 或其他名称。
6666记不住变量名,红温了
parseClaimsJwt() 用于解析未签名的 JWT(JSON Web Token)
parseClaimsJws() 用于解析签名的 JWT(JSON Web Signature)
你的应用创建的是签名的 JWT(使用 HS256 算法),所以必须使用 parseClaimsJws() 方法来解析
这个错误很常见,特别是在使用 JWT 库时混淆了 JWT 和 JWS 的概念。修改后应该就能正常解析 JWT token 了。
一开始post‘传参传不进去,中间好了,现在又传不进去。力竭了
问题原因
你的 MyBatis XML 中使用的参数名是
#{com_id}(下划线命名),但 Java 方法参数是comId(驼峰命名),命名不一致导致参数绑定失败。
Service 方法还有问题:没有返回查询结果
1 | @Override |
与ai对话千遍终于得到有效返回结果,增加jwt身份校验在playercontroller里面,jwt白名单在webmvcconfoguration里面,识别和提取jwt在interceptor里面,把jwt信息提取到线程basecontext里面,要校验的时候再到impl里面做判断,然后提取信息我也是乱写,我在controller里面就提取出来了,然后传到implement里面
我在返回值上卡了好久,if判断完就直接调用mapper return,不要再赋值在return了,最后的return我也不会,最后return new ArrayList<>();,一直不返回信息是因为我comId == null,还有之前查找了没返回,所以控制台上看到查找成功但是没返回就是return错了,一直在踩坑,以后一定要学完整套课程再来写
1 | { |
如有错误,多多指教