---
module_id: module_usr
date: 2026-04-30
git_range: master..HEAD (31 commits)
---

# 模块完成报告 — module_usr 用户管理

## ① 模块信息
- 模块 ID: module_usr
- 模块名: 用户管理
- 开发区间: 31 commits，~4149 行新增
- 分支: `module-module_usr`
- **里程碑**：USR-004 完成 stub 闭环——整个项目 SecurityConfig 收紧到 `登录接口外全部 authenticated()`

## ② REQ 完成清单

- [x] REQ-USR-001 — 用户新增
  - spec: docs/superpowers/specs/2026-04-30-REQ-USR-001.md
  - plan: docs/superpowers/plans/2026-04-30-REQ-USR-001.md
  - review: docs/superpowers/reviews/2026-04-30-REQ-USR-001.md
- [x] REQ-USR-002 — 用户修改
  - spec/plan/review: 同名 2026-04-30-REQ-USR-002 三件套
- [x] REQ-USR-003 — 用户查询
  - spec/plan/review: 同名 2026-04-30-REQ-USR-003 三件套
- [x] REQ-USR-004 — 用户登录（含 Stub 闭环）
  - spec/plan/review: 同名 2026-04-30-REQ-USR-004 三件套

## ③ 文件变更表

| 文件 | 操作 | 说明 |
|---|---|---|
| backend/src/main/java/com/xly/erp/module/usr/entity/User.java | 新建 | 17 字段 1:1 映射 tUser |
| backend/src/main/java/com/xly/erp/module/usr/entity/UserPermission.java | 新建 | 8 字段映射 tUserPermission |
| backend/src/main/java/com/xly/erp/module/usr/dto/CreateUserDTO.java | 新建 | USR-001 入参 |
| backend/src/main/java/com/xly/erp/module/usr/dto/UpdateUserDTO.java | 新建 | USR-002 入参（无 sPasswordHash） |
| backend/src/main/java/com/xly/erp/module/usr/dto/LoginDTO.java | 新建 | USR-004 登录入参 |
| backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java | 新建 | USR-003 列表 VO（11 字段，含 LEFT JOIN tStaff） |
| backend/src/main/java/com/xly/erp/module/usr/vo/UserBriefVO.java | 新建 | USR-004 用户简表 |
| backend/src/main/java/com/xly/erp/module/usr/vo/LoginVO.java | 新建 | USR-004 登录响应 |
| backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java | 新建 + 多次扩展 | BaseMapper + 自定义查询 + 登录用方法 |
| backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java | 新建 + deleteByUserId | USR-001 + USR-002 |
| backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java | 新建 | 最小 existsActiveById |
| backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java | 新建 | 批量校验 countActiveByIds |
| backend/src/main/resources/mapper/usr/UserMapper.xml | 新建 | USR-003 动态 SQL（pageWithFilter / countWithFilter） |
| backend/src/main/java/com/xly/erp/module/usr/security/LoginAttemptStore.java | 新建 | USR-004 内存失败计数 + 锁定 |
| backend/src/main/java/com/xly/erp/module/usr/service/UserService.java | 新建 + 4 次扩展 | create/update/list/login |
| backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java | 新建 + 4 次扩展 | 全部业务逻辑 |
| backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java | 新建 | POST/PUT/GET /api/usr/users |
| backend/src/main/java/com/xly/erp/module/usr/controller/AuthController.java | 新建 | POST /api/usr/auth/login |
| backend/src/main/java/com/xly/erp/common/config/PasswordEncoderConfig.java | 新建 | BCryptPasswordEncoder bean（独立于 SecurityConfig，避免 NONE 上下文丢失） |
| backend/src/main/java/com/xly/erp/common/security/JwtAuthenticationEntryPoint.java | 新建 | USR-004 未认证返回 code=20001 |
| backend/src/main/java/com/xly/erp/common/security/SecurityConfig.java | 修改 | USR-001 加 /api/usr/** permitAll → USR-004 收紧到仅 /api/usr/auth/login permitAll + entryPoint |
| backend/src/main/java/com/xly/erp/common/security/JwtUtil.java | 修改 | 加 signRefresh + 通用 sign(userNo, ttl) |
| backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java | 修改 | USR-004 移除 stub.getStubUserNo() fallback |
| backend/src/test/java/com/xly/erp/module/usr/**/* (5 文件) | 新建 | UserMapperIT(7) / StaffMapperIT(1) / PermissionCategoryMapperIT(1) / UserServiceImplTest(35) / UserControllerIT(27) / LoginAttemptStoreTest(5) / AuthControllerIT(5) |
| backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java | 修改 | USR-004 闭环：4 处 stub IT 期望从 200 改 20001 |
| backend/src/test/java/com/xly/erp/common/security/JwtUtilTest.java | 修改 | 加 signRefresh 验证 |
| docs/superpowers/specs|plans|reviews/2026-04-30-REQ-USR-{001..004}.md | 新建 | 12 份文档 |
| docs/superpowers/module-reports/module_usr-test-gate.md | 新建 | 本模块 test-gate 证据 |
| docs/08-模块任务管理.md | 修改 | 4 个 REQ checkbox 勾上 |

## ④ 数据库使用表

- 读：`tUser` / `tStaff` / `tPermissionCategory`（各 REQ 的存在性校验 / JOIN 显示）
- 写：
  - `tUser`：INSERT (USR-001) / UPDATE 可编辑列 (USR-002) / UPDATE tLastLoginDate (USR-004)
  - `tUserPermission`：INSERT (USR-001) / DELETE+INSERT 重建 (USR-002)

## ⑤ 测试结果

- `scripts/test.sh` 最终：**GREEN**
- 通过: 149 / 失败: 0 / 跳过: 0（前端段因 frontend/ 未初始化而 skip）
- 详见: docs/superpowers/module-reports/module_usr-test-gate.md
- 测试分布：
  - SmokeTest: 1
  - 全局基础设施: GlobalExceptionHandlerTest 4 / JwtUtilTest 4 / JwtAuthenticationFilterTest 3
  - MOD 模块: ModuleMapperIT 5 / ModuleServiceImplTest 25 / ModuleControllerIT 26
  - USR 模块: UserMapperIT 7 / StaffMapperIT 1 / PermissionCategoryMapperIT 1 / LoginAttemptStoreTest 5 / UserServiceImplTest 35 / UserControllerIT 27 / AuthControllerIT 5

## ⑥ 本模块新增 Migration

—（无 schema 改动；本模块 4 张表均在 V1 就位）

## ⑦ 跨模块改动清单（软规则 S2）

**1 处跨模块改动**（USR-004 stub 闭环必需，spec 已声明）：

- `backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java:create()/delete()` — 移除 `stub.getStubUserNo()` fallback
- `backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java` — 4 处 `*WithoutJwt_permitAllStub_*` 用例改名为 `*WithoutJwt_returns20001`，期望 200 → 20001
- `backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java` — 2 处 sCreatedBy 期望从 "STUB_ADMIN" 改为 null

**原因 / 影响评估**：
- 原因：USR-004 是模块循环最后一个 REQ，按 docs/02 § 二 顺序正好在收尾。spec 把 stub 闭环作为 Phase B 纳入本 REQ；项目首次具备真正的 JWT 鉴权能力，必须把 MOD 模块 stub-friendly 的测试期望同步收紧
- 影响评估：MOD 模块业务逻辑未变（仅 fallback 一行被删，行为等价于"sCreatedBy 必须由 SecurityContextHelper 提供"——authenticated 守卫保证非 null）；MOD 模块的 IT 测试在改写后行为更接近生产实际（无 token 必拒），增强了回归质量

## ⑧ 偏离 spec 清单

- **REQ-USR-001 & spec § 实现范围 #3**：spec 写 BCryptPasswordEncoder bean 在 SecurityConfig 注册，实际移到独立 `PasswordEncoderConfig.java`。原因：SecurityConfig `@ConditionalOnWebApplication(SERVLET)` 在 webEnvironment=NONE 的 mapperIT 上下文不加载。良性设计修正——密码编码器应是领域基础设施而非 Web 安全基础设施。
- **REQ-USR-002 & spec/plan § "controller 先 trim"**：实际实现 trim 在 service。功能等价；spec 文字与实现微差。
- **REQ-USR-003 & plan § 文件变更清单**：未创建 `UserListQuery` DTO，5 参直接传 service。简化合理。
- **REQ-USR-002 错误码 40022**：docs/05 § USR-002 错误码列表未列 40022；本实现复用 USR-001 已建立的语义（同字段两个接口错误码一致更优）。
- **REQ-USR-004 LoginDTO.version `@NotBlank`**：spec § 输入说"仅记录, 不参与校验"，实际加了 @NotBlank。微差。

## ⑨ AI reviewer 报告汇总

- REQ-USR-001: round 1 — approve
- REQ-USR-002: round 1 — approve
- REQ-USR-003: round 1 — approve
- REQ-USR-004: round 1 — approve（含 Phase B stub 闭环）

4/4 round 1 一次性通过；4 份 review 共约 28 条 nice-to-have，无 must-fix。

## ⑩ 已知问题

整合自 4 份 review 的非阻塞 nice-to-have，按主题分组：

**架构遗留（建议下一模块开始前 chore commit 一次性收口）**
1. `ModuleServiceImpl` / `UserServiceImpl` 移除 stub fallback 后 `private final StubSecurityProperties stub` 字段已无引用（但构造器仍注入）；按 surgical-changes 原则建议删字段 + 构造器参数 + 测试 setUp
2. `JwtAuthenticationFilter` 仍带 `@Component`，与 SecurityConfig `addFilterBefore` 重叠注册；可改 @Bean + FilterRegistrationBean disable
3. `JwtUtil` 暴露两个 public 构造器；`JwtUtil(String secret)` 实质 test-only
4. 业务方法 (create/update/delete/listTree/login 5 处) 缺行内 `// REQ-USR-XXX` 锚点
5. entity `Module` 类名与 `java.lang.Module` 冲突
6. `application-dev.yml` 缺失（docs/09 § 二 列出）

**测试覆盖增强（非阻塞）**
7. `LoginAttemptStoreTest.lockExpiresAfterDuration` 未驱动同 userName 跨过 lockExpireAt 验证 records.remove 自动清除
8. PUT-stub-regression IT 只断单列 unchanged，建议同时断 sCreatedBy / tCreateDate
9. USR-001/002 permissionCategoryIds 重复 id 假阴性（IN 隐式去重，建议 service `distinct`）
10. USR-003 响应 records 缺直接 `has("sPasswordHash") == false` 断言
11. USR-003 ORDER BY iIncrement DESC / tLastLoginDate JSON ISO 格式无显式断言
12. `tCreateDate` / `sBrandsId` / `sSubsidiaryId` 在 update / delete IT 端未端到端断言保留
13. `iParentId` 指向 `bDeleted=1` 旧记录是否回 40021 未单独验证
14. AuthControllerIT 字段级负例（仅缺 sUserName）未单独覆盖

**配置 / 校验**
15. application.yml `${JWT_SECRET}` 缺 fail-fast 默认值
16. GlobalExceptionHandler `handleAny(Exception)` 把 4xx 框架异常转 50000
17. DTO `iParentId` / `iSortOrder` 缺 `@PositiveOrZero` / `@Min` 约束
18. `LoginDTO.version` `@NotBlank` 与 spec "仅记录" 不一致

**信息泄漏**
19. UserListVO 设计上不暴露敏感字段，但 IT 缺 regression-locking 断言

## ⑪ 下一模块预览

**项目所有 REQ 已完成**：docs/02 § 二 共 8 个 REQ（MOD 4 + USR 4），全部 review approve 完毕。

USR 模块合并到 master 后，整个项目处于：
- 后端工程：149 用例全绿
- 鉴权：仅 `POST /api/usr/auth/login` 公开，其余 `authenticated()`
- 数据库：tUser / tStaff / tPermissionCategory / tUserPermission / tModule 全部就位

**后续工作（不属于本期 REQ 范围，建议作为新 epic）**：
- 引入 frontend/（React + Vite）
- 引入 Redis 替换 LoginAttemptStore 内存实现
- jacoco 覆盖率
- application-dev.yml + dev profile
- `ModuleEntity` 重命名（Module 与 java.lang.Module 冲突）
- 4 处行内 `// REQ-XXX` 锚点补齐

## ⑫ MR 链接

- !2 — http://git.xlyprint.cn/zhuzc/test/merge_requests/2
