抄完 Day 6 的 JWT 登录代码,我改了 6 个地方,其中一个是把 7 天抄成了 2.8 小时

📅 发布时间:2026/10/13 15:05:01
抄完 Day 6 的 JWT 登录代码,我改了 6 个地方,其中一个是把 7 天抄成了 2.8 小时
抄完 Day 6 的 JWT 登录代码我改了 6 个地方其中一个是把 7 天抄成了 2.8 小时Day 6 的答案纸说要新增 7 个文件、改 1 个一共 8 个。抄写顺序也给好了先在 pom 里加 jjwt 依赖再写工具类再写拦截器最后写 Controller。我就按这个顺序抄的。8 个文件抄完习惯性跑了一遍mvn -o compile然后项目直接编译不过。剩下的一整天基本都耗在这上面。下面按我踩的顺序记一遍。先放一张总表是我最后复盘出来的 6 个地方一、第一版我抄错在哪四处都在代码里躺着。第一个SysUserMapper.java注解写上去了import 没抄。packagecom.first.book.mapper;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.first.book.entity.SysUser;MapperpublicinterfaceSysUserMapperextendsBaseMapperSysUser{}答案纸里import com.first.book.entity.SysUser;下面还有一行import org.apache.ibatis.annotations.Mapper;。我抄的时候眼睛跳过去了Mapper却写上去了。第二个SysUserController.java泛型后面的括号少了一对。MapString,ObjectdatanewHashMap{};正确写法是new HashMap()。我当时脑子里想的是匿名内部类那套写法结果只写了{}前面的()忘了。第三个还是SysUserController.java第 11 行importorg.apache.catalina.User;我打字打到User的时候 IDEA 弹了补全框我顺手按了 Tab。它补进来的是 Tomcat 的org.apache.catalina.User跟我自己写的SysUser一点关系都没有。这个 import 从头到尾没用过。第四个WebMvcConfig.java的白名单.excludePathPatterns(/user/login,/user/regist,/hello,.error);最后一行应该是/error我抄成了.error。二、编译错误原文第一条报错来自mvn -o compile[ERROR] /D:/mywebapps/book-system-boot/src/main/java/com/first/book/mapper/SysUserMapper.java:[6,2] 找不到符号 符号: 类 Mapper [ERROR] 1 error只有一个错报的位置就是Mapper那一行。补上 import 之后再跑编译就过了。new HashMap{}这个我当时以为能过因为项目里那次编译没报它。后来我单独拿一个最小文件试了一下javac 的原文是这样T1.java:5: 错误: 需要(或[ MapString,Object d new HashMap{}; ^ 1 个错误需要(或[—— javac 就是想要那对括号。加上()就好了。这里有个细节值得记一下new HashMap(){}带括号又带花括号是合法的那是匿名内部类new HashMap{}少了括号就只剩语法错误。两个只差一对括号一字之差。三、两个我当时没想通的点先上这张图是一个请求从进来到被拦、或者被放行中间要过的几道关卡白名单里为什么要留/error答案纸的注释写的是/error// Spring Boot 内部错误转发路径拦了会把错误页也变成未登录我第一眼觉得这条多余 —— 我压根没写过/error这个接口放行它干什么。后来想明白了。Spring Boot 在 Controller 抛异常、又没走到你的ExceptionHandler时会内部 forward 到/error。forward 在容器里也算一次请求会再过一遍拦截器。这一步手上是没有 token 的于是拦截器抛NotLoginException你看到的就是未登录请先登录。真正的报错原因被这句未登录盖住了。答案纸的报错对照表里也写了同一件事/book/list 报 404 → /error 被拦了真实错误被 401 覆盖。而我抄成.error这个路径根本匹配不上任何东西等于白名单里这一条不存在。EXPIRE_MS为什么要连着乘四个数答案纸是privatestaticfinallongEXPIRE_MS7*24*60*60*1000L;我抄成了7 * 24 * 60 * 1000L少了一个* 60。看的时候真没觉得有问题一眼扫过去全是乘号和数字两个写法长得几乎一样。我后来写了个小类把两版都签一次 token再解析出来算exp - iatstaticvoidmake(longms,Stringtag){DatenownewDate();StringtJwts.builder().subject(wangzi).claim(uid,1).issuedAt(now).expiration(newDate(now.getTime()ms)).signWith(KEY).compact();ClaimscJwts.parser().verifyWith(KEY).build().parseSignedClaims(t).getPayload();longdiffc.getExpiration().getTime()-c.getIssuedAt().getTime();System.out.println(tag 实际有效期 diff ms (diff/1000) 秒 String.format(%.2f,diff/3600000.0) 小时);}输出原文[A] 少一个 *60 —— 项目里我抄错的那版 EXPIRE_MS 10080000 实际有效期 10080000 ms 10080 秒 2.80 小时 0.12 天 token 长度 143 字符, 段数 3 [B] 答案纸那版 EXPIRE_MS 604800000 实际有效期 604800000 ms 604800 秒 168.00 小时 7.00 天 token 长度 143 字符, 段数 310080000 毫秒 10080 秒 2.8 小时。我本来想发 7 天的票实际发出去的是 2.8 小时的。这种错编译期不会说话运行起来也不会报错表现就是用着用着忽然要重新登录。假如是在生产上大概要等到第二天有人来报 bug 才发现。顺带一提143 个字符、3 段这个和答案纸里说的结构对得上token 里没有密码只有 uid 和用户名。四、两处错别字验收的时候会咬人这两处不影响运行但会影响我对答案。LoginInterceptor.java里我写的是登陆已过期请重新登录答案纸是「登录已过期」。SysUserController.java里我写的是用户或密码错误答案纸是「用户名或密码错误」少一个「名」。因为验收是靠肉眼拿返回的 message 跟期望值一个字一个字对对不上我会先怀疑自己逻辑写错了白绕一圈。五、改完之后的几个关键片段SysUserMapper.java把漏掉的那行补回来。importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.first.book.entity.SysUser;importorg.apache.ibatis.annotations.Mapper;MapperpublicinterfaceSysUserMapperextendsBaseMapperSysUser{}JwtUtil.java补上那个* 60。privatestaticfinalStringSECRETbook-system-boot-jwt-secret-key-2026-fs1one;// 7 天单位毫秒。少一个 * 60 就变成 2.8 小时privatestaticfinallongEXPIRE_MS7*24*60*60*1000L;WebMvcConfig.java白名单最后一个改成/error。registry.addInterceptor(loginInterceptor).addPathPatterns(/**).excludePathPatterns(/user/login,/user/regist,/hello,/error);LoginInterceptor.java改掉错别字。UserContext.set(uid)保持放在 try-catch 之后顺序不乱。Stringtokenheader.substring(PREFIX.length());Claimsclaims;try{claimsJwtUtil.parseToken(token);}catch(Exceptione){log.warn(token 解析失败{},e.getMessage());thrownewNotLoginException(登录已过期请重新登录);}Integeruidclaims.get(uid,Integer.class);UserContext.set(uid);SysUserController.javanew HashMap()补上括号删掉 IDEA 那个org.apache.catalina.User文案改成「用户名或密码错误」。MapString,ObjectdatanewHashMap();data.put(token,token);data.put(uid,user.getUid());data.put(username,user.getUsername());六、怎么验证答案纸给了一套请求我抄进了工程里的api/day6.httpIDEA 里点每条左边的绿色 ▶ 就能跑。顺序是先 ① 再往下。期望值我列在这儿跑完一条一条对#请求期望的返回①POST /user/login{username:stu31551,userPwd:test123456}{code:1,data:{token:eyJhbGciOiJIUzI1NiJ9...,uid:1,username:stu31551},message:success}②POST /user/login{username:stu31551,userPwd:wrongpassword}{code:0,data:null,message:用户名或密码错误}③GET /book/list不带任何请求头{code:0,data:null,message:未登录请先登录}④GET /book/listAuthorization: Bearer ①的token{code:1,data:[...7 条图书...],message:success}⑤GET /book/list 签名被改过的 token{code:0,data:null,message:登录已过期请重新登录}⑥GET /user/infoAuthorization: Bearer ①的token{code:1,data:{uid:1,message:token 有效已通过拦截器},message:success}⑦GET /hello不带 token正常返回不该被拦③ 是核心那条。Day 5 的时候浏览器直接敲/book/list能看到全部图书现在应该被拦住。③ 过了接口不再裸奔这句话才算成立。④ 里Bearer后面那个空格最容易翻车少了它前缀就匹配不上会当成没带 token。⑤ 是验证签名真的在干活内容改一个字签名就对不上。⑥ 这个接口内部一行取 token 的代码都没写uid 是拦截器提前塞进 ThreadLocal 的。它能返回uid1说明「拦截器写入 ThreadLocal → Controller 读出」这条链通了。七、报错对照表我这几处加上答案纸里列的合在一张表里现象 / 报错原文原因找不到符号 符号: 类 Mapper用了Mapper但没import org.apache.ibatis.annotations.Mapper;需要(或[new HashMap{}少了构造方法的()带了 token 还返回未登录请先登录白名单写成.error之类路径匹配不上或Bearer后面少空格请求/book/list报 404但页面显示未登录/error没进白名单真实错误被未登录覆盖了token 用几小时就要重新登录EXPIRE_MS漏乘一个607 天写成 2.8 小时期望「用户名或密码错误」但返回对不上少抄了「名」字之类的文案差异WeakKeyException: The signing keys size is ... bits密钥短于 32 字节HS256 要求cannot find symbol: method setSubject抄成 jjwt 0.11 的老 API 了0.12 里是subject()八、下一篇写什么JwtUtil 里那串 SECRET 现在还是写死在代码里的privatestaticfinalStringSECRETbook-system-boot-jwt-secret-key-2026-fs1one;答案纸自己也点明白了密钥进 Git 仓库是要还的债。下一篇想把它挪到application.yaml里用Value或者ConfigurationProperties读进来顺便试试故意不配这个值看启动会怎么报错。还有 Day 7 的全接口贯通以及把/user/login里那几行手写的空值 if 判断换成 Day 5 学过的ValidatedNotBlank。