2026-04-30-REQ-USR-001.md 4.5 KB

req_id: REQ-USR-001 date: 2026-04-30 round: 1

reviewer: superpower-code-reviewer

Review: REQ-USR-001 — round 1

结论

approve

Must-fix

(无)

Nice-to-have

  • docs/superpowers/specs/2026-04-30-REQ-USR-001.md § 实现范围与边界抉择 #3 + § 验收标准 工程验收 #2 写「BCryptPasswordEncoder bean 在 SecurityConfig 注册」,但实现已移到 common/config/PasswordEncoderConfig.java(理由:SecurityConfig 上 @ConditionalOnWebApplication(SERVLET)webEnvironment=NONE 的 mapperIT/serviceTest 上下文不加载会让 encoder 缺失)。正向设计修正——密码编码器是领域基础设施而非 Web 安全基础设施。建议把 spec 这两处改为「BCryptPasswordEncoder 在 common/config/PasswordEncoderConfig 注册」,并在 PasswordEncoderConfig.java 类注释里写一行可追溯说明。
  • backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java:104 — 循环里每次 LocalDateTime.now() 让 N 行 tUserPermission 的 tCreateDate 出现微秒级偏差。建议在 create 入口取一次 LocalDateTime now,user + 全部 permission 共享,更贴合「同一事务一次创建」的语义。
  • backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java:69-75 — permissionCategoryIds 含重复 id(如 [1,1,2] 全部有效)时 SQL IN 隐式去重 → countActiveByIds=2 ≠ ids.size=3 → 误抛 40023 假阴性。spec 没要求去重;建议 service 层先 ids = ids.stream().distinct().toList(),4 行修复。
  • backend/src/test/java/com/xly/erp/module/usr/controller/UserControllerIT.java:73-75 — postValidBody_with_jwt_returns200_andPersists 没断言响应 data 中不含 sPasswordHash。spec § 验收 工程 #5 明言「不返回 sPasswordHash」。建议加 assertThat(jb.get("data").has("sPasswordHash")).isFalse() 锁住该不变量,未来若有人改 Map.of 加字段不会无声泄漏。
  • backend/src/test/java/com/xly/erp/module/usr/controller/UserControllerIT.java:170-198 — postWithoutJwt_permitAllStub_returns200_andCreatedBySTUBADMIN / postTamperedJwt_returns20001 沿袭 MOD 模块 stub 路径仍缺 // REQ-MOD-001 stub: see USR-004 follow-up 锚点。建议在 module-report 阶段统一一次性补齐(与 MOD-004 review 同处理)。
  • backend/src/main/java/com/xly/erp/common/security/SecurityConfig.java:26 — stub 锚点放在 /api/mod/**/api/usr/** 共同上方。USR-004 实际收紧时需要分两条 requestMatchers 各自指向 hasAuthority/anyRequest,建议在 USR-004 plan 阶段显式记一笔回填动作。
  • backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java:59 — create 方法体缺 // REQ-USR-001: 用户新增 行内锚点(与 MOD 模块同源遗漏)。建议在 module-report 阶段一次性把 USR / MOD 两个模块的 REQ 行内锚点统一补齐。

反例 / 测试覆盖缺口

Spec 验收清单(service 8 + mapperIT 4 + controllerIT 9 + 工程 5)100% 落地,sourcing .env.localmvn -B test 全量 89/89 全绿。

  • BCryptPasswordEncoder 重定位是必要正向修正,被 mapperIT × 5(NONE 环境)+ serviceTest × 8 + controllerIT × 9(RANDOM_PORT 环境)双向覆盖。
  • 校验顺序(type/lang 枚举 → staff 存在 → permission 存在 → INSERT user catch DuplicateKey → INSERT permission × N)严格匹配 spec § 业务规则 1–6,@Transactional(rollbackFor=Exception.class) 包整个流程。
  • permissionCategoryMapper.countActiveByIds 由 service 短路空 list 保护,避免 SQL IN () 错误。
  • N+1 INSERT tUserPermission 是 spec § 业务规则 #7 显式 YAGNI 取舍;docs/04 § 3.4「循环中禁止执行 DB 查询」语义针对 SELECT N+1 不是 INSERT。
  • sPasswordHash 由 service 返回 Map 仅 put 两个 key 不会泄漏(构造保证),但缺直接断言锁不变量。
  • BCrypt 每次 salt 不同——单测用真实 BCryptPasswordEncoder + startsWith("$2a$") 断言(非 mock),正确。
  • IT cleanup 顺序 tUserPermission → tUser → tStaff → tPermissionCategory 满足 FK(iUserId CASCADE / iStaffId SET NULL / iCategoryId RESTRICT),无外键孤儿。
  • SecurityContextHelper 匿名处理与 MOD 同款,stub 锚点缺失沿袭 MOD-004 已点出的范畴。

非阻塞缺口:spec 文字与实现 BCryptPasswordEncoder 位置不一致(见 nice-to-have #1);sPasswordHash regression 锁断言(#4);permissionCategoryIds 重复值假阴性(#3);行内 REQ 锚点 + stub 锚点统一补齐(留 module-report)。