diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index c89a5dcdb2..459f3953de 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -46,6 +46,8 @@ public interface ErrorCodeConstants { ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空"); ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册"); ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭"); + ErrorCode USER_ADMIN_NOT_ALLOW_DISABLE = new ErrorCode(1_002_003_012, "超级管理员或租户管理员账号不能禁用"); + ErrorCode USER_ADMIN_NOT_ALLOW_DELETE = new ErrorCode(1_002_003_013, "超级管理员或租户管理员账号不能删除"); // ========== 部门模块 1-002-004-000 ========== ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门"); diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index 3899a32fce..15e37118b7 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -6,10 +6,12 @@ import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; @@ -22,14 +24,17 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqV import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; import cn.iocoder.yudao.module.system.service.permission.PermissionService; +import cn.iocoder.yudao.module.system.service.permission.RoleService; import cn.iocoder.yudao.module.system.service.tenant.TenantService; import com.google.common.annotations.VisibleForTesting; import com.mzt.logapi.context.LogRecordContext; @@ -75,6 +80,8 @@ public class AdminUserServiceImpl implements AdminUserService { @Resource private PermissionService permissionService; @Resource + private RoleService roleService; + @Resource private PasswordEncoder passwordEncoder; @Resource @Lazy // 延迟,避免循环依赖报错 @@ -250,15 +257,20 @@ public class AdminUserServiceImpl implements AdminUserService { @Override public void updateUserStatus(Long id, Integer status) { - // 校验用户存在 + // 1.1 校验用户存在 validateUserExists(id); - // 更新状态 + // 1.2 管理员不能禁用 + if (CommonStatusEnum.isDisable(status)) { + validateUserNotAdmin(id, USER_ADMIN_NOT_ALLOW_DISABLE); + } + + // 2. 更新状态 AdminUserDO updateObj = new AdminUserDO(); updateObj.setId(id); updateObj.setStatus(status); userMapper.updateById(updateObj); - // 如果是禁用用户,则删除其 Token 信息 + // 3. 如果是禁用用户,则删除其 Token 信息 if (CommonStatusEnum.isDisable(status)) { oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue()); } @@ -269,8 +281,10 @@ public class AdminUserServiceImpl implements AdminUserService { @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = SYSTEM_USER_DELETE_SUCCESS) public void deleteUser(Long id) { - // 1. 校验用户存在 + // 1.1 校验用户存在 AdminUserDO user = validateUserExists(id); + // 1.2 管理员不能删除 + validateUserNotAdmin(id, USER_ADMIN_NOT_ALLOW_DELETE); // 2.1 删除用户 userMapper.deleteById(id); @@ -286,16 +300,32 @@ public class AdminUserServiceImpl implements AdminUserService { @Override @Transactional(rollbackFor = Exception.class) public void deleteUserList(List ids) { - // 1. 批量删除用户 + // 1. 校验不能删除管理员 + ids.forEach(id -> validateUserNotAdmin(id, USER_ADMIN_NOT_ALLOW_DELETE)); + + // 2. 批量删除用户 userMapper.deleteByIds(ids); - // 2. 批量删除用户关联数据 + // 3. 批量删除用户关联数据 ids.forEach(id -> { permissionService.processUserDeleted(id); userPostMapper.deleteByUserId(id); }); } + private void validateUserNotAdmin(Long id, ErrorCode errorCode) { + Set roleIds = permissionService.getUserRoleIdListByUserId(id); + if (CollUtil.isEmpty(roleIds)) { + return; + } + for (RoleDO role : roleService.getRoleList(roleIds)) { + if (ObjectUtils.equalsAny(role.getCode(), + RoleCodeEnum.SUPER_ADMIN.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) { + throw exception(errorCode); + } + } + } + @Override public AdminUserDO getUserByUsername(String username) { return userMapper.selectByUsername(username); @@ -562,6 +592,16 @@ public class AdminUserServiceImpl implements AdminUserService { respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg()); return; } + // 2.2.3 校验管理员不能禁用 + if (CommonStatusEnum.isDisable(importUser.getStatus())) { + try { + validateUserNotAdmin(existUser.getId(), USER_ADMIN_NOT_ALLOW_DISABLE); + } catch (ServiceException ex) { + respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); + return; + } + } + // 2.2.4 更新用户 AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class); updateUser.setId(existUser.getId()); userMapper.updateById(updateUser); diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java index b9f0dec463..55aadc8b39 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java @@ -19,16 +19,19 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqV import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; import cn.iocoder.yudao.module.system.enums.common.SexEnum; +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; import cn.iocoder.yudao.module.system.service.permission.PermissionService; +import cn.iocoder.yudao.module.system.service.permission.RoleService; import cn.iocoder.yudao.module.system.service.tenant.TenantService; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; @@ -78,6 +81,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { @MockitoBean private PermissionService permissionService; @MockitoBean + private RoleService roleService; + @MockitoBean private PasswordEncoder passwordEncoder; @MockitoBean private TenantService tenantService; @@ -289,6 +294,35 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { verify(oauth2TokenService).removeAccessToken(userId, UserTypeEnum.ADMIN.getValue()); } + @Test + public void testUpdateUserStatus_adminCannotDisable() { + for (RoleCodeEnum roleCode : new RoleCodeEnum[]{RoleCodeEnum.SUPER_ADMIN, RoleCodeEnum.TENANT_ADMIN}) { + // mock 数据 + AdminUserDO dbUser = randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + userMapper.insert(dbUser); + mockUserRole(dbUser.getId(), roleCode); + + // 调用,并断言异常 + assertServiceException(() -> userService.updateUserStatus(dbUser.getId(), CommonStatusEnum.DISABLE.getStatus()), + USER_ADMIN_NOT_ALLOW_DISABLE); + assertEquals(CommonStatusEnum.ENABLE.getStatus(), userMapper.selectById(dbUser.getId()).getStatus()); + } + verify(oauth2TokenService, never()).removeAccessToken(anyLong(), anyInt()); + } + + @Test + public void testUpdateUserStatus_adminCanEnable() { + // mock 数据 + AdminUserDO dbUser = randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())); + userMapper.insert(dbUser); + mockUserRole(dbUser.getId(), RoleCodeEnum.TENANT_ADMIN); + + // 调用 + userService.updateUserStatus(dbUser.getId(), CommonStatusEnum.ENABLE.getStatus()); + // 断言 + assertEquals(CommonStatusEnum.ENABLE.getStatus(), userMapper.selectById(dbUser.getId()).getStatus()); + } + @Test public void testDeleteUser_success(){ // mock 数据 @@ -305,6 +339,43 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { verify(permissionService, times(1)).processUserDeleted(eq(userId)); } + @Test + public void testDeleteUser_adminCannotDelete() { + for (RoleCodeEnum roleCode : new RoleCodeEnum[]{RoleCodeEnum.SUPER_ADMIN, RoleCodeEnum.TENANT_ADMIN}) { + // mock 数据 + AdminUserDO dbUser = randomAdminUserDO(); + userMapper.insert(dbUser); + mockUserRole(dbUser.getId(), roleCode); + + // 调用,并断言异常 + assertServiceException(() -> userService.deleteUser(dbUser.getId()), USER_ADMIN_NOT_ALLOW_DELETE); + assertNotNull(userMapper.selectById(dbUser.getId())); + } + } + + @Test + public void testDeleteUserList_adminCannotDelete() { + // mock 数据 + AdminUserDO ordinaryUser = randomAdminUserDO(); + userMapper.insert(ordinaryUser); + AdminUserDO adminUser = randomAdminUserDO(); + userMapper.insert(adminUser); + mockUserRole(adminUser.getId(), RoleCodeEnum.TENANT_ADMIN); + + // 调用,并断言整批未删除 + assertServiceException(() -> userService.deleteUserList(List.of(ordinaryUser.getId(), adminUser.getId())), + USER_ADMIN_NOT_ALLOW_DELETE); + assertNotNull(userMapper.selectById(ordinaryUser.getId())); + assertNotNull(userMapper.selectById(adminUser.getId())); + } + + private void mockUserRole(Long userId, RoleCodeEnum roleCode) { + Long roleId = 100L; + when(permissionService.getUserRoleIdListByUserId(userId)).thenReturn(asSet(roleId)); + when(roleService.getRoleList(asSet(roleId))).thenReturn(singletonList( + randomPojo(RoleDO.class, role -> role.setCode(roleCode.getCode())))); + } + @Test public void testGetUserByUsername() { // mock 数据 @@ -532,6 +603,29 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { assertEquals(0, respVO.getFailureUsernames().size()); } + @Test + public void testImportUserList_adminCannotDisable() { + // mock 数据 + AdminUserDO dbUser = randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + userMapper.insert(dbUser); + mockUserRole(dbUser.getId(), RoleCodeEnum.TENANT_ADMIN); + // 准备参数 + UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { + o.setUsername(dbUser.getUsername()); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + o.setSex(randomEle(SexEnum.values()).getSex()); + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); + }); + + // 调用 + UserImportRespVO respVO = userService.importUserList(singletonList(importUser), true); + // 断言 + assertEquals(USER_ADMIN_NOT_ALLOW_DISABLE.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername())); + assertTrue(respVO.getUpdateUsernames().isEmpty()); + assertEquals(CommonStatusEnum.ENABLE.getStatus(), userMapper.selectById(dbUser.getId()).getStatus()); + } + @Test public void testValidateUserExists_notExists() { assertServiceException(() -> userService.validateUserExists(randomLongId()), USER_NOT_EXISTS);