--- req_id: REQ-USR-004 date: 2026-05-06 round: 2 reviewer: superpower-code-reviewer --- # Review: REQ-USR-004 — round 2 ## 结论 approve ## Must-fix (无) Round 1 三条 must_fix 处理结果(commit d439c0d): 1. **CRITICAL JwtTokenProviderTest 硬编码生产 JWT_SECRET** — RESOLVED。test SECRET 改为 fake 32-byte hex(line 21);`.env.local` JWT_SECRET 已旋转为新随机值。**遗留运维项**:旧值已入 commit b7ed804 git history,所有已部署环境必须同步轮换 JWT_SECRET。 2. **HIGH InMemoryLoginAttemptStore 锁定到期不重置** — RESOLVED。cooldownSeconds 过期路径 `store.remove`,recordFailure compute 入口检测 prev.lockUntil 过期重建 FailRecord;spec § 业务规则 4 第 4 条达成。 3. **MEDIUM 验收 #9 无测试** — RESOLVED。补 `cooldown_afterExpiry_resetsCount` 单测(含 reset 后再 4 次未锁、第 5 次再锁的完整往返)+ `login_afterLockExpiry_returns200` IT;`expireLockForTest` 改 public 解决跨包可见性。 ## Nice-to-have - backend/src/main/java/com/xly/erp/module/usr/controller/LoginController.java + service — spec § 业务规则 7 客户端 IP 审计未实施;service 未拿 `HttpServletRequest.getRemoteAddr()`。**需在模块完成报告 § ⑩ 登记技术债**。 - backend/src/test/java/com/xly/erp/module/usr/service/LoginServiceImplTest.java — `login_successUpdatesTLastLoginDate_viaSetClause` 仍只 `verify(...).update(isNull(), any(Wrapper.class))`,没断言 LambdaUpdateWrapper 的 set 子句包含 tLastLoginDate(plan Step 4.1 要求)。 - backend/src/main/java/com/xly/erp/config/SecurityConfig.java — plan Step 5.2 要求 `requestMatchers("/api/auth/login").permitAll()` 显式白名单;当前 anyRequest().permitAll() 等价覆盖但未做 plan 偏离登记。 - backend/src/main/java/com/xly/erp/common/exception/GlobalExceptionHandler.java:17 — `java.util.Map` fully-qualified;下次 sweep 加 import。 - backend/src/main/java/com/xly/erp/module/usr/security/InMemoryLoginAttemptStore.java:51 — `expireLockForTest` 已 public 但住在生产包;下一 sweep 可挪到 src/test 的 testutil 包。 ## 反例 / 测试覆盖缺口 Round 1 三项 must_fix 全部解决: 1. JwtTokenProviderTest SECRET 改 fake + .env.local 已旋转;运维侧需轮换部署环境。 2. cooldownSeconds + recordFailure 双路径处理过期重置;business rule #4 完整。 3. 验收 #9 单测 + IT 双层覆盖;reset 后能重新触发新一轮锁定。 `mvn -B test` 经 .env.local 注入后 172/172 全绿;`scripts/test.sh` GREEN;无新高危。 **核心结论**:critical + high + medium 三项关键修复已落地。剩余 5 条 nice-to-have(IP 审计 / SET 子句捕获 / SecurityConfig 显式白名单 / Map import / testutil 移植)在模块完成报告 § ⑩ 登记后留给后续 sweep。verdict: approve。