Commit 81a263b909a3f3c174838b87208cc6bba01b70be
1 parent
704d75a4
fix(usr): 修复 review round 1 must-fix REQ-USR-002
M2: UserUpdateReqDTO 添加 @NotBlank/@Pattern/@NotNull 字段校验 M3: UserFormDrawer InitialData 补 permGroupIds 字段并初始化 M4: 列表查询加 bCanEditDoc 返回字段,编辑抽屉传真实值而非 false M1: 补充 updateUser_selfAdminKeepType_doesNotThrow 测试
Showing
9 changed files
with
62 additions
and
5 deletions
backend/src/main/java/com/example/erp/module/usr/dto/UserUpdateReqDTO.java
| 1 | 1 | package com.example.erp.module.usr.dto; |
| 2 | 2 | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import jakarta.validation.constraints.Pattern; | |
| 3 | 6 | import lombok.Getter; |
| 4 | 7 | import lombok.Setter; |
| 5 | 8 | |
| ... | ... | @@ -9,10 +12,16 @@ import java.util.List; |
| 9 | 12 | @Setter |
| 10 | 13 | public class UserUpdateReqDTO { |
| 11 | 14 | |
| 15 | + @NotBlank(message = "用户类型不能为空") | |
| 16 | + @Pattern(regexp = "普通用户|超级管理员", message = "用户类型无效") | |
| 12 | 17 | private String userType; |
| 18 | + | |
| 19 | + @NotBlank(message = "语言不能为空") | |
| 20 | + @Pattern(regexp = "中文|英文|繁体", message = "语言无效") | |
| 13 | 21 | private String language; |
| 14 | 22 | private boolean canEditDoc; |
| 15 | 23 | private boolean isDisabled; |
| 16 | 24 | private String employeeId; |
| 25 | + @NotNull(message = "权限组列表不能为空") | |
| 17 | 26 | private List<String> permGroupIds; |
| 18 | 27 | } | ... | ... |
backend/src/main/java/com/example/erp/module/usr/vo/UserListItemVO.java
backend/src/main/resources/mapper/UsrUserMapper.xml
| ... | ... | @@ -6,7 +6,7 @@ |
| 6 | 6 | <!-- REQ-USR-003: 查询用户 --> |
| 7 | 7 | <select id="selectUserList" resultType="com.example.erp.module.usr.vo.UserListItemVO"> |
| 8 | 8 | SELECT u.sId, u.sUsername, u.sUserCode, u.sUserType, u.sLanguage, |
| 9 | - u.bIsDisabled, u.tLastLoginDate, u.sCreatorUsername, u.tCreateDate, | |
| 9 | + u.bCanEditDoc, u.bIsDisabled, u.tLastLoginDate, u.sCreatorUsername, u.tCreateDate, | |
| 10 | 10 | s.sStaffName, s.sDepartment |
| 11 | 11 | FROM usr_user u |
| 12 | 12 | LEFT JOIN tStaff s ON u.sEmployeeId = s.sId | ... | ... |
backend/src/test/java/com/example/erp/module/usr/UserServiceTest.java
| ... | ... | @@ -233,6 +233,22 @@ class UserServiceTest { |
| 233 | 233 | } |
| 234 | 234 | |
| 235 | 235 | @Test |
| 236 | + void updateUser_selfAdminKeepType_doesNotThrow() { | |
| 237 | + UserUpdateReqDTO dto = new UserUpdateReqDTO(); | |
| 238 | + dto.setUserType("超级管理员"); | |
| 239 | + dto.setLanguage("中文"); | |
| 240 | + dto.setPermGroupIds(List.of()); | |
| 241 | + UsrUserEntity existing = new UsrUserEntity(); | |
| 242 | + existing.setSId("u1"); | |
| 243 | + existing.setSUsername("admin"); | |
| 244 | + when(userMapper.selectOne(any())).thenReturn(existing); | |
| 245 | + when(userMapper.update(any(), any())).thenReturn(1); | |
| 246 | + | |
| 247 | + // 自己保持 超级管理员 角色 → 不应抛 40301 | |
| 248 | + assertDoesNotThrow(() -> userService.updateUser("u1", dto, superAdmin)); | |
| 249 | + } | |
| 250 | + | |
| 251 | + @Test | |
| 236 | 252 | void updateUser_invalidEmployee_throws40001() { |
| 237 | 253 | UserUpdateReqDTO dto = new UserUpdateReqDTO(); |
| 238 | 254 | dto.setUserType("普通用户"); | ... | ... |
docs/superpowers/reviews/2026-05-08-REQ-USR-002.md
0 → 100644
| 1 | +--- | |
| 2 | +req_id: REQ-USR-002 | |
| 3 | +date: 2026-05-08 | |
| 4 | +round: 1 | |
| 5 | +reviewer: superpower-code-reviewer | |
| 6 | +--- | |
| 7 | + | |
| 8 | +# Review: REQ-USR-002 — round 1 | |
| 9 | + | |
| 10 | +## 结论 | |
| 11 | +request-changes | |
| 12 | + | |
| 13 | +## Must-fix | |
| 14 | +- [HIGH] backend/src/main/java/com/example/erp/module/usr/dto/UserUpdateReqDTO.java:12-13 — userType / language 缺少 @NotBlank + @Pattern 校验,非法值直接写库(建议:参照 UserCreateReqDTO 添加 @NotBlank + @Pattern 注解) | |
| 15 | +- [HIGH] frontend/src/pages/usr/UserFormDrawer.tsx:8-14 — InitialData 接口缺少 permGroupIds 字段,导致有权限组的用户打开编辑抽屉时权限组无法被预填(建议:添加 permGroupIds?: string[],useEffect 改为 setSelectedPermIds(initialData.permGroupIds ?? [])) | |
| 16 | +- [HIGH] frontend/src/pages/usr/UserListPage.tsx:124 — canEditDoc 写死为 false,每次保存会将已有用户的 canEditDoc 覆盖为 false(建议:在列表查询中加入 bCanEditDoc,传入 initialData 时使用真实值) | |
| 17 | +- [MEDIUM] backend/src/test/java/com/example/erp/module/usr/UserServiceTest.java — 缺少测试 userId==自己 但 userType 仍为 "超级管理员" 时不抛出 40301(建议:补充 updateUser_selfAdminKeepType_doesNotThrow 测试,确认允许路径) | |
| 18 | + | |
| 19 | +## Nice-to-have | |
| 20 | +- backend/.../vo/UserUpdateRespVO.java — updatedAt 建议加 @JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss") 确保序列化格式显式 | |
| 21 | +- backend/.../dto/UserUpdateReqDTO.java — permGroupIds 建议加 @NotNull,防止调用方传 null 时静默跳过权限组删除 | |
| 22 | +- frontend/src/test/UserListPage.test.tsx:106 — editMode_submit_callsUpdateUser 只断言调用次数,建议补充 toHaveBeenCalledWith('u1', ...) 验证 userId 传值正确 | |
| 23 | + | |
| 24 | +## 反例 / 测试覆盖缺口 | |
| 25 | +- 缺 "自己修改自己 userType 但保持 超级管理员" 的无异常路径测试(G1) | |
| 26 | +- controller 层无 BizException → 错误码映射测试(G4,40300/40400/40301 未被 controller 测试覆盖) | |
| 27 | +- canEditDoc/isDisabled 写入 DB 的 int 映射值(0/1)未在 service happy-path 测试中验证 | ... | ... |
frontend/src/api/usr.ts
frontend/src/pages/usr/UserFormDrawer.tsx
| ... | ... | @@ -11,6 +11,7 @@ interface InitialData { |
| 11 | 11 | canEditDoc: boolean |
| 12 | 12 | isDisabled: boolean |
| 13 | 13 | employeeId?: string | null |
| 14 | + permGroupIds?: string[] | |
| 14 | 15 | } |
| 15 | 16 | |
| 16 | 17 | interface Props { |
| ... | ... | @@ -42,7 +43,7 @@ export default function UserFormDrawer({ open, onClose, onSuccess, userId, initi |
| 42 | 43 | isDisabled: initialData.isDisabled, |
| 43 | 44 | employeeId: initialData.employeeId ?? null, |
| 44 | 45 | }) |
| 45 | - setSelectedPermIds([]) | |
| 46 | + setSelectedPermIds(initialData.permGroupIds ?? []) | |
| 46 | 47 | } else { |
| 47 | 48 | form.resetFields() |
| 48 | 49 | setSelectedPermIds([]) | ... | ... |
frontend/src/pages/usr/UserListPage.tsx
| ... | ... | @@ -121,7 +121,7 @@ export default function UserListPage() { |
| 121 | 121 | initialData={editingUser ? { |
| 122 | 122 | userType: editingUser.sUserType, |
| 123 | 123 | language: editingUser.sLanguage, |
| 124 | - canEditDoc: false, | |
| 124 | + canEditDoc: editingUser.bCanEditDoc === 1, | |
| 125 | 125 | isDisabled: editingUser.bIsDisabled === 1, |
| 126 | 126 | employeeId: null, |
| 127 | 127 | } : undefined} | ... | ... |
frontend/src/test/UserListPage.test.tsx
| ... | ... | @@ -70,7 +70,7 @@ describe('UserListPage', () => { |
| 70 | 70 | total: 1, page: 1, pageSize: 20, |
| 71 | 71 | list: [{ |
| 72 | 72 | sId: 'u1', sUsername: 'alice', sUserCode: 'UC001', sUserType: '普通用户', |
| 73 | - sLanguage: '中文', bIsDisabled: 0, tLastLoginDate: null, | |
| 73 | + sLanguage: '中文', bCanEditDoc: 0, bIsDisabled: 0, tLastLoginDate: null, | |
| 74 | 74 | sCreatorUsername: 'admin', tCreateDate: '2026-01-01T00:00:00', |
| 75 | 75 | sStaffName: '张三', sDepartment: '研发部' |
| 76 | 76 | }] |
| ... | ... | @@ -93,7 +93,7 @@ describe('UserListPage', () => { |
| 93 | 93 | total: 1, page: 1, pageSize: 20, |
| 94 | 94 | list: [{ |
| 95 | 95 | sId: 'u1', sUsername: 'alice', sUserCode: 'UC001', sUserType: '普通用户', |
| 96 | - sLanguage: '中文', bIsDisabled: 0, tLastLoginDate: null, | |
| 96 | + sLanguage: '中文', bCanEditDoc: 0, bIsDisabled: 0, tLastLoginDate: null, | |
| 97 | 97 | sCreatorUsername: 'admin', tCreateDate: '2026-01-01T00:00:00', |
| 98 | 98 | sStaffName: null, sDepartment: null |
| 99 | 99 | }] | ... | ... |