--- req_id: REQ-USR-002 date: 2026-04-30 spec_ref: docs/superpowers/specs/2026-04-30-REQ-USR-002.md --- # REQ-USR-002 用户修改 Implementation Plan > **Execution:** Parent skill `feature-tdd` executes this plan task-by-task. **Goal:** 在 USR-001 已建工程基础上增量实现 `PUT /api/usr/users/{id}`:更新可编辑字段 + 重建权限组(删旧 + 插新);保留 `sPasswordHash` / `sCreatedBy` / 标准列。 **Architecture:** 复用 `UserService` / `UserServiceImpl` / `UserController` / 全部 mappers;新增 `UpdateUserDTO`、`UserService#update`、`UserPermissionMapper#deleteByUserId`。SecurityConfig 已对 `/api/usr/**` permitAll,无需改。 **Tech Stack:** 沿用(Spring Boot 3.3.5 / MyBatis-Plus / JJWT)。 --- ## Schema 改动 无(仅 UPDATE / DELETE / INSERT)。 ## 文件变更清单 ### 新增 - `backend/src/main/java/com/xly/erp/module/usr/dto/UpdateUserDTO.java` ### 修改 - `backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java` — 追加 `deleteByUserId(Integer)` - `backend/src/main/java/com/xly/erp/module/usr/service/UserService.java` — 追加 `Integer update(Integer id, UpdateUserDTO dto)` - `backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java` — 实现 update(含 5 类校验 + UPDATE + DELETE + INSERT × N) - `backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java` — 追加 PUT 端点 - `backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java` — 追加 10 单测 - `backend/src/test/java/com/xly/erp/module/usr/mapper/UserMapperIT.java` — 追加 1 用例(deleteByUserId) - `backend/src/test/java/com/xly/erp/module/usr/controller/UserControllerIT.java` — 追加 10 IT ## 任务步骤 ### Task 1: UserPermissionMapper#deleteByUserId + IT **Files:** - Modify: `backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java` - Modify: `backend/src/test/java/com/xly/erp/module/usr/mapper/UserMapperIT.java` **API shape:** - `@Delete("DELETE FROM tUserPermission WHERE iUserId = #{userId}")` `int deleteByUserId(@Param("userId") Integer userId)` - [ ] **Step 1: 写失败测试 `userPermissionMapper#deleteByUserId_removesAllRowsForGivenUser`** - 准备:插 user1 + user2;user1 关联 cat1+cat2,user2 关联 cat1 - 调 `deleteByUserId(user1.id)` → 返回 2,user1 行数 = 0;user2 行数 = 1(不受影响) - [ ] **Step 2: 实现 mapper 方法** - [ ] **Step 3: 子会话验证 PASS**:`mvn -B test -Dtest=UserMapperIT` - [ ] **Step 4: Commit**:`feat(usr): mapper#deleteByUserId for permission rebuild REQ-USR-002` ### Task 2: UpdateUserDTO + UserService.update 主流程(合法 + 目标存在性 + bShowPerm null→false) **Files:** - Create: `backend/src/main/java/com/xly/erp/module/usr/dto/UpdateUserDTO.java` - Modify: `backend/src/main/java/com/xly/erp/module/usr/service/UserService.java` - Modify: `backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java` - Modify: `backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java` **API shape:** - `UpdateUserDTO` 字段(与 CreateUserDTO 平行,去掉 `permissionCategoryIds` 之外字段语义不变;剔除 `sPasswordHash`,**仍包含** `permissionCategoryIds` 用于重建权限组) - `UserService#update(Integer id, UpdateUserDTO dto) : Integer` - 实现: 1. `selectById(id)` → null 或 bDeleted=true → 40400 2. 枚举校验 sUserType / sLanguage(同 create)→ 40001 3. iStaffId 校验(同 create)→ 40022 4. permissionCategoryIds 校验(仅当非空 list;null/空 list 跳过校验直接清空)→ 40023 5. 构造 entity 仅 set iIncrement + 6 个可编辑字段(其余 null);`bCanModifyDocs` null → false 6. `userMapper.updateById(entity)` try/catch DuplicateKeyException → 40020 7. `userPermissionMapper.deleteByUserId(id)` 8. 若 permissionCategoryIds 非空:for-loop 插 UserPermission - [ ] **Step 1: 追加 4 单测** - `updateWithValidDto_invokesUpdateById_andRebuildsPermissions` - `updateWithTargetNotFound_throws40400` - `updateWithTargetAlreadyDeleted_throws40400` - `updateWithBCanModifyDocsNull_setsFalseInEntity` - [ ] **Step 2: 实现 DTO + service 主流程** - [ ] **Step 3: 子会话验证 PASS**:`mvn -B test -Dtest=UserServiceImplTest`(期望 8+4=12) - [ ] **Step 4: Commit**:`feat(usr): user update dto + service happy path REQ-USR-002` ### Task 3: Service 异常分支补全(枚举 / staff / permission / 唯一冲突 / 清空权限) **Files:** - Modify: `backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java` - Modify: `backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java` **API shape:** 不变 - [ ] **Step 1: 追加 6 单测** - `updateWithInvalidUserType_throws40001` - `updateWithInvalidLanguage_throws40001` - `updateWithStaffNotFound_throws40022` - `updateWithSomeInvalidPermissionIds_throws40023` - `updateWithDuplicateUserNo_throws40020` - `updateWithEmptyPermissionIds_clearsExisting` — permissionCategoryIds=null;deleteByUserId 调一次、insert 永不调 - [ ] **Step 2: 实现校验分支** - [ ] **Step 3: 子会话验证 PASS**:`mvn -B test -Dtest=UserServiceImplTest`(期望 12+6=18) - [ ] **Step 4: Commit**:`feat(usr): user update error branches REQ-USR-002` ### Task 4: Controller PUT + IT(10 用例)+ 全量回归 **Files:** - Modify: `backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java` - Modify: `backend/src/test/java/com/xly/erp/module/usr/controller/UserControllerIT.java` **API shape:** - `@PutMapping("/users/{id}") public Result> update(@PathVariable Integer id, @Valid @RequestBody UpdateUserDTO dto)` - 返回 `Result.ok(Map.of("iIncrement", userService.update(id, dto)))` - [ ] **Step 1: 追加 10 IT**(参 spec 验收清单) - [ ] **Step 2: 实现 controller PUT** - [ ] **Step 3: 子会话跑全量回归**:`mvn -B test`(期望 89 + 1+10+10=20 = 109+ 用例全绿) - [ ] **Step 4: Commit**:`test(usr): user update integration coverage REQ-USR-002` ## 提交计划 | commit | 覆盖 | |---|---| | `feat(usr): mapper#deleteByUserId for permission rebuild REQ-USR-002` | Task 1 | | `feat(usr): user update dto + service happy path REQ-USR-002` | Task 2 | | `feat(usr): user update error branches REQ-USR-002` | Task 3 | | `test(usr): user update integration coverage REQ-USR-002` | Task 4 |