Commit e765fc2972e3b65b0309f4a68412ffd114b10ee0
1 parent
15d5257b
feat(usr): UserService.updateUser 三重校验 + 权限组 replace REQ-USR-002
REQ-USR-002
Showing
3 changed files
with
177 additions
and
0 deletions
backend/src/main/java/com/example/erp/module/usr/service/UserService.java
| ... | ... | @@ -4,10 +4,12 @@ import com.example.erp.common.vo.PageVO; |
| 4 | 4 | import com.example.erp.config.UserPrincipal; |
| 5 | 5 | import com.example.erp.module.usr.dto.UserCreateReqDTO; |
| 6 | 6 | import com.example.erp.module.usr.dto.UserListQueryDTO; |
| 7 | +import com.example.erp.module.usr.dto.UserUpdateReqDTO; | |
| 7 | 8 | import com.example.erp.module.usr.vo.PermissionGroupVO; |
| 8 | 9 | import com.example.erp.module.usr.vo.StaffVO; |
| 9 | 10 | import com.example.erp.module.usr.vo.UserCreateRespVO; |
| 10 | 11 | import com.example.erp.module.usr.vo.UserListItemVO; |
| 12 | +import com.example.erp.module.usr.vo.UserUpdateRespVO; | |
| 11 | 13 | |
| 12 | 14 | import java.util.List; |
| 13 | 15 | |
| ... | ... | @@ -20,4 +22,6 @@ public interface UserService { |
| 20 | 22 | List<PermissionGroupVO> getPermissionGroups(String brandId); |
| 21 | 23 | |
| 22 | 24 | PageVO<UserListItemVO> getUserList(UserListQueryDTO query, String brandId); |
| 25 | + | |
| 26 | + UserUpdateRespVO updateUser(String userId, UserUpdateReqDTO req, UserPrincipal principal); | |
| 23 | 27 | } | ... | ... |
backend/src/main/java/com/example/erp/module/usr/service/impl/UserServiceImpl.java
| 1 | 1 | package com.example.erp.module.usr.service.impl; |
| 2 | 2 | |
| 3 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 4 | 5 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 5 | 6 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 6 | 7 | import com.example.erp.common.constants.UsrErrorCode; |
| ... | ... | @@ -9,6 +10,7 @@ import com.example.erp.common.vo.PageVO; |
| 9 | 10 | import com.example.erp.config.UserPrincipal; |
| 10 | 11 | import com.example.erp.module.usr.dto.UserCreateReqDTO; |
| 11 | 12 | import com.example.erp.module.usr.dto.UserListQueryDTO; |
| 13 | +import com.example.erp.module.usr.dto.UserUpdateReqDTO; | |
| 12 | 14 | import com.example.erp.module.usr.entity.PermissionGroupEntity; |
| 13 | 15 | import com.example.erp.module.usr.entity.StaffEntity; |
| 14 | 16 | import com.example.erp.module.usr.entity.UserPermissionEntity; |
| ... | ... | @@ -22,6 +24,7 @@ import com.example.erp.module.usr.vo.PermissionGroupVO; |
| 22 | 24 | import com.example.erp.module.usr.vo.StaffVO; |
| 23 | 25 | import com.example.erp.module.usr.vo.UserCreateRespVO; |
| 24 | 26 | import com.example.erp.module.usr.vo.UserListItemVO; |
| 27 | +import com.example.erp.module.usr.vo.UserUpdateRespVO; | |
| 25 | 28 | import lombok.RequiredArgsConstructor; |
| 26 | 29 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
| 27 | 30 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -146,4 +149,67 @@ public class UserServiceImpl implements UserService { |
| 146 | 149 | return vo; |
| 147 | 150 | }).collect(Collectors.toList()); |
| 148 | 151 | } |
| 152 | + | |
| 153 | + // REQ-USR-002: 修改用户 | |
| 154 | + @Override | |
| 155 | + @Transactional | |
| 156 | + public UserUpdateRespVO updateUser(String userId, UserUpdateReqDTO req, UserPrincipal principal) { | |
| 157 | + if (!"超级管理员".equals(principal.userType())) { | |
| 158 | + throw new BizException(UsrErrorCode.PERMISSION_DENIED, "权限不足"); | |
| 159 | + } | |
| 160 | + | |
| 161 | + UsrUserEntity existing = userMapper.selectOne(new LambdaQueryWrapper<UsrUserEntity>() | |
| 162 | + .eq(UsrUserEntity::getSId, userId) | |
| 163 | + .eq(UsrUserEntity::getSBrandsId, principal.brandId())); | |
| 164 | + if (existing == null) { | |
| 165 | + throw new BizException(UsrErrorCode.USER_NOT_FOUND, "用户不存在"); | |
| 166 | + } | |
| 167 | + | |
| 168 | + if (principal.userId().equals(userId) && !"超级管理员".equals(req.getUserType())) { | |
| 169 | + throw new BizException(UsrErrorCode.SELF_ADMIN_CHANGE, "禁止修改自己的管理员角色"); | |
| 170 | + } | |
| 171 | + | |
| 172 | + if (req.getEmployeeId() != null) { | |
| 173 | + StaffEntity staff = staffMapper.selectOne(new LambdaQueryWrapper<StaffEntity>() | |
| 174 | + .eq(StaffEntity::getSId, req.getEmployeeId()) | |
| 175 | + .eq(StaffEntity::getSBrandsId, principal.brandId())); | |
| 176 | + if (staff == null) { | |
| 177 | + throw new BizException(UsrErrorCode.EMPLOYEE_NOT_FOUND, "员工不存在"); | |
| 178 | + } | |
| 179 | + } | |
| 180 | + | |
| 181 | + userMapper.update(null, new LambdaUpdateWrapper<UsrUserEntity>() | |
| 182 | + .eq(UsrUserEntity::getSId, userId) | |
| 183 | + .eq(UsrUserEntity::getSBrandsId, principal.brandId()) | |
| 184 | + .set(UsrUserEntity::getSUserType, req.getUserType()) | |
| 185 | + .set(UsrUserEntity::getSLanguage, req.getLanguage()) | |
| 186 | + .set(UsrUserEntity::getBCanEditDoc, req.isCanEditDoc() ? 1 : 0) | |
| 187 | + .set(UsrUserEntity::getBIsDisabled, req.isDisabled() ? 1 : 0) | |
| 188 | + .set(UsrUserEntity::getSEmployeeId, req.getEmployeeId())); | |
| 189 | + | |
| 190 | + userPermissionMapper.delete(new LambdaQueryWrapper<UserPermissionEntity>() | |
| 191 | + .eq(UserPermissionEntity::getSUserId, userId) | |
| 192 | + .eq(UserPermissionEntity::getSBrandsId, principal.brandId())); | |
| 193 | + | |
| 194 | + if (req.getPermGroupIds() != null && !req.getPermGroupIds().isEmpty()) { | |
| 195 | + List<UserPermissionEntity> perms = req.getPermGroupIds().stream() | |
| 196 | + .map(groupId -> { | |
| 197 | + UserPermissionEntity perm = new UserPermissionEntity(); | |
| 198 | + perm.setSId(UUID.randomUUID().toString()); | |
| 199 | + perm.setSBrandsId(principal.brandId()); | |
| 200 | + perm.setTCreateDate(LocalDateTime.now()); | |
| 201 | + perm.setSUserId(userId); | |
| 202 | + perm.setSPermGroupId(groupId); | |
| 203 | + return perm; | |
| 204 | + }) | |
| 205 | + .collect(Collectors.toList()); | |
| 206 | + userPermissionMapper.insert(perms); | |
| 207 | + } | |
| 208 | + | |
| 209 | + UserUpdateRespVO vo = new UserUpdateRespVO(); | |
| 210 | + vo.setUserId(userId); | |
| 211 | + vo.setUsername(existing.getSUsername()); | |
| 212 | + vo.setUpdatedAt(LocalDateTime.now()); | |
| 213 | + return vo; | |
| 214 | + } | |
| 149 | 215 | } | ... | ... |
backend/src/test/java/com/example/erp/module/usr/UserServiceTest.java
| ... | ... | @@ -7,7 +7,9 @@ import com.example.erp.common.vo.PageVO; |
| 7 | 7 | import com.example.erp.config.UserPrincipal; |
| 8 | 8 | import com.example.erp.module.usr.dto.UserCreateReqDTO; |
| 9 | 9 | import com.example.erp.module.usr.dto.UserListQueryDTO; |
| 10 | +import com.example.erp.module.usr.dto.UserUpdateReqDTO; | |
| 10 | 11 | import com.example.erp.module.usr.vo.UserListItemVO; |
| 12 | +import com.example.erp.module.usr.vo.UserUpdateRespVO; | |
| 11 | 13 | import com.example.erp.module.usr.entity.StaffEntity; |
| 12 | 14 | import com.example.erp.module.usr.entity.UsrUserEntity; |
| 13 | 15 | import com.example.erp.module.usr.entity.UserPermissionEntity; |
| ... | ... | @@ -187,4 +189,109 @@ class UserServiceTest { |
| 187 | 189 | assertEquals(40400, UsrErrorCode.USER_NOT_FOUND); |
| 188 | 190 | assertEquals(40301, UsrErrorCode.SELF_ADMIN_CHANGE); |
| 189 | 191 | } |
| 192 | + | |
| 193 | + @Test | |
| 194 | + void updateUser_nonAdmin_throws40300() { | |
| 195 | + UserUpdateReqDTO dto = new UserUpdateReqDTO(); | |
| 196 | + dto.setUserType("普通用户"); | |
| 197 | + dto.setLanguage("中文"); | |
| 198 | + dto.setPermGroupIds(List.of()); | |
| 199 | + | |
| 200 | + BizException ex = assertThrows(BizException.class, | |
| 201 | + () -> userService.updateUser("u-target", dto, normalUser)); | |
| 202 | + assertEquals(UsrErrorCode.PERMISSION_DENIED, ex.getCode()); | |
| 203 | + } | |
| 204 | + | |
| 205 | + @Test | |
| 206 | + void updateUser_userNotFound_throws40400() { | |
| 207 | + UserUpdateReqDTO dto = new UserUpdateReqDTO(); | |
| 208 | + dto.setUserType("普通用户"); | |
| 209 | + dto.setLanguage("中文"); | |
| 210 | + dto.setPermGroupIds(List.of()); | |
| 211 | + when(userMapper.selectOne(any())).thenReturn(null); | |
| 212 | + | |
| 213 | + BizException ex = assertThrows(BizException.class, | |
| 214 | + () -> userService.updateUser("u-target", dto, superAdmin)); | |
| 215 | + assertEquals(UsrErrorCode.USER_NOT_FOUND, ex.getCode()); | |
| 216 | + } | |
| 217 | + | |
| 218 | + @Test | |
| 219 | + void updateUser_selfAdminChange_throws40301() { | |
| 220 | + UserUpdateReqDTO dto = new UserUpdateReqDTO(); | |
| 221 | + dto.setUserType("普通用户"); | |
| 222 | + dto.setLanguage("中文"); | |
| 223 | + dto.setPermGroupIds(List.of()); | |
| 224 | + UsrUserEntity existing = new UsrUserEntity(); | |
| 225 | + existing.setSId("u1"); | |
| 226 | + existing.setSUsername("admin"); | |
| 227 | + when(userMapper.selectOne(any())).thenReturn(existing); | |
| 228 | + | |
| 229 | + // superAdmin.userId() == "u1", target userId == "u1" → self-admin change | |
| 230 | + BizException ex = assertThrows(BizException.class, | |
| 231 | + () -> userService.updateUser("u1", dto, superAdmin)); | |
| 232 | + assertEquals(UsrErrorCode.SELF_ADMIN_CHANGE, ex.getCode()); | |
| 233 | + } | |
| 234 | + | |
| 235 | + @Test | |
| 236 | + void updateUser_invalidEmployee_throws40001() { | |
| 237 | + UserUpdateReqDTO dto = new UserUpdateReqDTO(); | |
| 238 | + dto.setUserType("普通用户"); | |
| 239 | + dto.setLanguage("中文"); | |
| 240 | + dto.setEmployeeId("bad-emp"); | |
| 241 | + dto.setPermGroupIds(List.of()); | |
| 242 | + UsrUserEntity existing = new UsrUserEntity(); | |
| 243 | + existing.setSId("u-target"); | |
| 244 | + existing.setSUsername("alice"); | |
| 245 | + when(userMapper.selectOne(any())).thenReturn(existing); | |
| 246 | + when(staffMapper.selectOne(any())).thenReturn(null); | |
| 247 | + | |
| 248 | + BizException ex = assertThrows(BizException.class, | |
| 249 | + () -> userService.updateUser("u-target", dto, superAdmin)); | |
| 250 | + assertEquals(UsrErrorCode.EMPLOYEE_NOT_FOUND, ex.getCode()); | |
| 251 | + } | |
| 252 | + | |
| 253 | + @Test | |
| 254 | + void updateUser_happyPath_updatesAndReturnsResp() { | |
| 255 | + UserUpdateReqDTO dto = new UserUpdateReqDTO(); | |
| 256 | + dto.setUserType("超级管理员"); | |
| 257 | + dto.setLanguage("英文"); | |
| 258 | + dto.setCanEditDoc(true); | |
| 259 | + dto.setDisabled(false); | |
| 260 | + dto.setPermGroupIds(List.of("g1", "g2")); | |
| 261 | + UsrUserEntity existing = new UsrUserEntity(); | |
| 262 | + existing.setSId("u-target"); | |
| 263 | + existing.setSUsername("alice"); | |
| 264 | + when(userMapper.selectOne(any())).thenReturn(existing); | |
| 265 | + when(userMapper.update(any(), any())).thenReturn(1); | |
| 266 | + when(userPermissionMapper.delete(any())).thenReturn(0); | |
| 267 | + | |
| 268 | + UserUpdateRespVO resp = userService.updateUser("u-target", dto, superAdmin); | |
| 269 | + | |
| 270 | + assertNotNull(resp); | |
| 271 | + assertEquals("u-target", resp.getUserId()); | |
| 272 | + assertEquals("alice", resp.getUsername()); | |
| 273 | + assertNotNull(resp.getUpdatedAt()); | |
| 274 | + verify(userMapper).update(any(), any()); | |
| 275 | + verify(userPermissionMapper).delete(any()); | |
| 276 | + verify(userPermissionMapper).insert(argThat((java.util.Collection<UserPermissionEntity> c) -> c.size() == 2)); | |
| 277 | + } | |
| 278 | + | |
| 279 | + @Test | |
| 280 | + void updateUser_emptyPermGroupIds_onlyDeletes() { | |
| 281 | + UserUpdateReqDTO dto = new UserUpdateReqDTO(); | |
| 282 | + dto.setUserType("普通用户"); | |
| 283 | + dto.setLanguage("中文"); | |
| 284 | + dto.setPermGroupIds(List.of()); | |
| 285 | + UsrUserEntity existing = new UsrUserEntity(); | |
| 286 | + existing.setSId("u-target"); | |
| 287 | + existing.setSUsername("bob"); | |
| 288 | + when(userMapper.selectOne(any())).thenReturn(existing); | |
| 289 | + when(userMapper.update(any(), any())).thenReturn(1); | |
| 290 | + when(userPermissionMapper.delete(any())).thenReturn(0); | |
| 291 | + | |
| 292 | + userService.updateUser("u-target", dto, superAdmin); | |
| 293 | + | |
| 294 | + verify(userPermissionMapper).delete(any()); | |
| 295 | + verify(userPermissionMapper, never()).insert(anyList()); | |
| 296 | + } | |
| 190 | 297 | } | ... | ... |