2026-06-01-REQ-USR-002.md 5.63 KB

REQ-USR-002 修改用户 — AI 自审报告(review, round 1)

阶段:后端(backend)。spec:docs/superpowers/specs/2026-06-01-REQ-USR-002.md。 分支:module-usr;审查范围 = 本 REQ diff(09db895..8a1b01b)。 维度:通用后端代码审查(plan-alignment / 正确性 / 边界 / 错误处理 / 一致性 / 架构 / 文档)。


裁决

verdict = approve(issues = 空数组)。

实现与 spec / docs/05 契约 / docs/04 技术规范一致,8 条验收标准均有对应实现与测试覆盖,无客观可验证的 must-fix 缺陷。


1. 变更清单

文件 性质
backend/.../usr/dto/UpdateUserDTO.java 新增入参 DTO(匈牙利前缀 + @JsonProperty 锁键名 + Bean Validation)
backend/.../usr/service/UsrUserService.java 接口新增 updateUser(Integer, UpdateUserDTO)
backend/.../usr/service/impl/UsrUserServiceImpl.java 新增 updateUser 实现(存在性校验 / 部分更新 / 权限全量覆盖 / 单事务)
backend/.../usr/controller/UsrUserController.java 新增 PUT /api/usr/users/{id} + 管理员前置
backend/.../usr/dto/UpdateUserDTOValidationTest.java DTO 校验单测(5)
backend/.../usr/service/UsrUserServiceImplTest.java Service 单测续写(T2 3 + T3 5)
backend/.../usr/controller/UsrUserControllerTest.java Controller 单测续写(4)
backend/.../usr/UsrUserUpdateIT.java 端到端验收回归(6,AC1/2/3/6/7/8)

无 SQL migration 变更(符合 D5:所需表均在 V1 建好);无跨模块改动。


2. plan-alignment(与 spec / 契约对照)

  • 端点 / 方法 / 路径与 docs/05 § REQ-USR-002PUT /api/usr/users/{id})一致;响应 Result<{id}>code=0 一致。
  • DTO 字段集与契约请求体逐项吻合:sUserNo / iEmployeeId / sUserType / sLanguage / iCanModifyBill / iIsVoid / permissionIds;不接收 sUserName / sPassword / 审计列 / 租户列(spec § 2.1 注)。
  • 决策落实:D1(不碰密码)、D2(iIsVoid 承载禁用)、D3(可选列 null = 不更新)、D4(permissionIds 全量覆盖 / [] 清空 / null 不改)、D5(不新增 migration)、D6(管理员 = 超级管理员)。
  • 错误码:40401(不存在)/ 40001(关联职员或权限元素不存在、枚举越界)/ 40301(非管理员)/ 0(成功)——与 spec § 6 一致。

3. 正确性 / 边界 / 错误处理

  • 目标用户存在性校验置于所有写入之前,命中即 40401 且不落库(AC2)。
  • 关联职员、权限元素存在性校验置于主记录更新前,非法即抛 40001,配合 @Transactional(rollbackFor=Exception.class) 整体回滚、无副作用(AC3)。
  • 权限全量覆盖:先删该用户全部旧授权再按去重集合逐行插入;[] → 只删不插(清空);null → 不进入分支(不改);distinct() + Objects::nonNull 过滤去重去空(AC6)。
  • 主记录部分更新:仅 set 主键 + 必填两列 + 非 null 可选列;密码 / sUserName / 审计列 / 租户列全不 set,依赖 MP updateById 默认 NOT_NULL 字段策略(已核对 MybatisPlusConfigapplication.yml 无策略覆盖)从 SET 子句剔除,保持原值(AC1 / AC8)。
  • 响应仅 {id}sPassword 实体侧 @JsonIgnore,IT 断言响应体不含 password 子串(AC8)。

4. 架构 / 一致性

  • 分层正确:Controller 仅 @Valid + 管理员前置 + 委派,未直接调 Mapper;业务在 Service;数据访问走 LambdaQueryWrapper / MP 内置(docs/04 § 1.2 / § 3.4)。
  • 复用 REQ-USR-001 已建 controller / service / mapper / entity,未另起类,未跨 modules/usr/** 之外(spec § 4)。
  • 统一响应 / 全局异常 / 错误码枚举沿用既有公共设施,风格一致。

5. 文档

  • 类 / 方法 Javadoc 完整,含 REQ-USR-002 追溯标记与决策引用,符合项目注释约定。

6. 非阻塞建议(口头,非 must-fix)

  1. 路径参数非正整数口径:spec § 2.1 / § 6 期望非正整数 id 返回 40001,当前 Controller 未加 @Positive0 / 负数 id 会落到 selectById 返回 null → 40401。因 0 / 负数主键实际不可能命中记录,行为安全(仍被拒绝、不产生数据污染),但与契约文字口径略有出入;后续可在 @PathVariable@Positive 并开启 @Validated 使其归一为 40001
  2. 注释 tag 笔误UsrUserController 中管理员判定注释引用 spec § 8 D2,实际管理员口径决策为 D6(D2 是 iIsVoid 状态决策);纯注释问题,不影响行为。
  3. 并发健壮性(理论):权限"先删后插"在高并发同一用户授权场景下存在间隙锁 / 死锁的理论可能;当前管理员低频写场景可接受,不构成缺陷。
  4. 非整数 iIsVoid / iCanModifyBill 反序列化:传入非数字会触发 Jackson HttpMessageNotReadableException,落到兜底 Exception 处理器返回 50000 而非 40001;此为既有全局行为(与 REQ-USR-001 一致),不在本 REQ 作用域单独修。

7. 测试与闸门

  • verify 证据(2026-06-01-REQ-USR-002-verify.md):mvn checkstyle:check exit 0、mvn test 36 通过 / 0 失败,BUILD SUCCESS
  • 单测覆盖:DTO 校验、Service 存在性 / 部分更新 / 关联职员 / 权限全量覆盖(含去重、清空、不改)、Controller 管理员前置 / 路由 / 404 转译。
  • IT 覆盖 AC1/2/3/6/7/8;AC4(禁用实时生效)/ AC5(角色变更实时生效)依赖 REQ-USR-004 / REQ-USR-003,本 REQ 以"落库层读回等价验证"覆盖,符合 spec § 7 边界说明。