fix(system): 禁止禁用或删除超级管理员和租户管理员账号

This commit is contained in:
YunaiV
2026-09-26 17:48:28 +08:00
parent 459986bfca
commit 223a4d816a
3 changed files with 142 additions and 6 deletions
@@ -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, "已经存在该名字的部门");
@@ -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<Long> 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<Long> 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);
@@ -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);