2026-06-01-REQ-USR-003.md 6.4 KB

REQ-USR-003 查询用户 — AI 自审报告(review, round 1)

阶段:后端(backend)。spec:docs/superpowers/specs/2026-06-01-REQ-USR-003.md。 分支:module-usr;审阅范围 = 本 REQ 引入的 controller / service / mapper / dto / vo / 公共分页响应体 + 配套测试。 通用代码审查维度:plan-alignment / 正确性 / 边界 / 错误处理 / 一致性 / 架构 / 安全。


1. 裁决

verdict = approve(round 1)。未发现客观、可定位的 must-fix 缺陷;issues = []

下方记录亮点、观察与(非阻塞)建议,供后续治理参考。


2. plan-alignment(实现 ↔ 规格 / 契约)

spec 条款 实现位置 结论
单端点 GET /api/usr/usersResult<PageResult<UserVO>> UsrUserController.queryUsers(仅 @Valid + 委派) 一致
任意已认证用户可调用(D5,无管理员前置) Controller 查询方法ADMIN_USER_TYPE 前置;SecurityConfig 其余请求 authenticated() 一致
单条件(queryField + matchType + queryValue,D2) parseCondition 单分支解析 一致
字段→列白名单映射(§ 3.4) FIELD_COLUMN Map.of(...) 8 项,带表别名 一致
文本 包含/不包含/等于;枚举/布尔/日期精确 UserQueryCondition 四类 + XML <choose> 分支 一致
空 queryValue(trim 后空)= 不过滤全量分页(§ 3.2) parseCondition 首段 none() 一致
LEFT JOIN 取员工名/部门,未关联职员两列 null(§ 3.6) XML LEFT JOIN usr_employee + 显式 resultMap 一致
分页:参数非法 → 42201;数据越界 → 钳最后一页 code=0(D1/D8) Service 先判定范围抛 42201,再按 pages 钳位回查 一致
total=0 返回空 records、pageNum=1 Service 显式分支 一致
布尔/日期解析口径(D6),非法 → 40001 normalizeBool / parseDayStart 一致
密码/租户列绝不返回(§ 3.9 / AC13) 显式列清单查询、VO 无密码列、无 SELECT * 一致
API 契约(docs/05)端点/参数/响应体形状 完全吻合(含 pageSize 上限 100) 一致
错误码(ResultCode 0/40001/42201/401) 复用既有枚举(42201 此前已预留) 一致

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

  • 分页钳位:pages = (total + pageSize - 1) / pageSizepageNum > pages 时用钳后页号回查一次保证 records 为真实末页数据,PageResult.pageNum 回传钳后页号、total 回传真实总数 —— 符合 AC10,单测 dataPageOutOfRangeClampsToLastPage + IT ac10 双重覆盖。
  • 42201 与 40001 边界明确:UserQueryDTO 故意pageNum/pageSize@Min/@Max(否则被全局处理器统一转 40001,与要求的 42201 冲突),范围在 Service 入口显式判定 —— 设计与 D8 一致,IT ac11 三种非法值均回 42201。
  • 布尔归一化兼容 0/1是/否true/false(大小写无关),不可解析显式 40001;日期支持 yyyy-MM-ddyyyy-MM-dd HH:mm:ss,按「当日 [00:00, 次日 00:00)」半开区间匹配,非法 40001。
  • 「不包含」对 NULL(未关联职员)行:依赖 SQL 三值逻辑 NOT LIKE 对 NULL 返回 unknown 自然不命中(D7),未引入易错的隐式 OR col IS NULL

4. 安全

  • SQL 注入面已封闭:XML 中唯一的 ${}cond.column,其取值来自 FIELD_COLUMN 固定白名单 Map.of(...),用户输入(queryField 中文)仅作 key 查表、查不到即抛 40001,绝不拼接到列 token;所有值经 #{} 预编译占位。
  • LIKE 通配符 % _ \escapeLike 转义并配合 XML ESCAPE '\\',防止用户输入被当通配符(D3)。
  • 密码零泄露:查询 SQL 显式列清单不含 sPassword/租户列,VO 无对应属性;IT ac13 断言响应体不含 sPassword/password/$2a$

5. 架构 / 一致性

  • 分层正确(Controller 仅校验+委派 → Service 业务+分页装配 → Mapper),Controller 不直接碰 Mapper。
  • 复用 REQ-USR-001/002 既有 controller/service/mapper/entity,仅新增 UserQueryDTO / UserVO / UserQueryCondition / PageResult,未跨模块。
  • UserQueryCondition 中间载体把「中文枚举判断 + 类型解析」收敛在 Service,XML 只做结构化 <if>/<choose> 分支,关注点分离清晰。
  • PageResult<T> 作为通用分页契约(docs/04 § 1.4/§ 3.2)落地,后续分页 REQ 可复用。
  • VO 对匈牙利前缀字段用 @JsonProperty 锁小驼峰键名,与 docs/05 契约键一致,做法与 DTO 统一。

6. 观察与建议(非阻塞,不进 issues)

  • [建议 / 测试治理] UsrUserQueryIT 未进标准 unit 管线:docs/04 § 零 后端 unit = mvn -q -B teste2e = 无,Surefire 默认排除 **/*IT.java,且 pom 无 maven-failsafe-plugin,故本 REQ 的端到端验收回归(AC1-AC13)不在锁定闸门内执行(verify 报告 § 4 已如实记录)。
    • 此为项目既有测试装配现状(与 REQ-USR-001/002 一致),非本 REQ 代码引入的缺陷,故不构成 request-changes。
    • 审阅期间我手动执行 mvn -Dtest=UsrUserQueryIT test(连真实测试库,Flyway V1 已 apply),结果 Tests run: 13, Failures: 0, Errors: 0, Skipped: 0,10.19s 全绿;其中 ac8 按部门跨表过滤实跑了真实 LEFT JOIN + MyBatis-Plus 自动 COUNT 路径、ac7 实跑日期半开区间、ac6 实跑布尔归一化、ac4/ac8 实跑 ESCAPE LIKE,均通过——本 REQ SQL 路径经验性无误。
    • 后续治理建议(超出本 REQ 作用域):引入 maven-failsafe-plugin 并把 mvn verify 锁进 docs/04 § 零,使真实安全链 IT 进入 CI 标准管线。
  • [微 nit,不影响正确性] MybatisPlusConfig 未显式设置 optimizeJoin:3.5.7 默认 optimizeJoin=true,对「无 join 列过滤」的 COUNT 会去掉 LEFT JOIN(性能更优且语义正确,因为 iEmployeeId N:1 不放大行数),对「按 e.* 过滤」的 COUNT 会保留 JOIN——经 ac8 实跑验证 COUNT 行为正确。无需改动,仅记录。

7. 结论

代码实现与 spec / docs/05 契约 / docs/04 技术规范一致;正确性、边界、错误处理、安全(注入面、密码泄露)、架构分层、命名/响应/异常约定均达标;单测(Service/DTO/VO/Mapper)+ 手动实跑的 IT(AC1-AC13)双重佐证行为正确。

approve。issues = []。