1. 修复回滚父子角色功能时错误的代码逻辑,补全单元测试用例

2. 新增支持切换后业务菜单查询需限定只查询该公司业务数据能力
This commit is contained in:
chenbowen
2025-07-10 19:05:58 +08:00
parent 92959efdc6
commit 7f0957d9c4
60 changed files with 1749 additions and 64 deletions

View File

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
* System 错误码枚举类
*
* system 系统,使用 1-002-000-000 段
* @author chenbowen
*/
public interface ErrorCodeConstants {
@@ -33,8 +34,9 @@ public interface ErrorCodeConstants {
ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, "不能操作类型为系统内置的角色");
ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, "名字为【{}】的角色已被禁用");
ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "标识【{}】不能使用");
ErrorCode ROLE_CAN_NOT_UPDATE_NORMAL_TYPE_ROLE = new ErrorCode(1_002_002_006, "不能操作类型为标准的角色,除非是管理员角色");
ErrorCode ROLE_CAN_NOT_DELETE_HAS_CHILDREN = new ErrorCode(1_002_002_007, " 角色【{}】存在子角色,不允许删除");
ErrorCode ROLE_PARENT_IS_CHILD = new ErrorCode(1_002_002_008, "不能设置自己的子角色为父角色");
// ========== 用户模块 1-002-003-000 ==========
ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, "用户账号已经存在");

View File

@@ -25,11 +25,15 @@ import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static java.util.Collections.singleton;
/**
* @author chenbowen
*/
@Tag(name = "管理后台 - 角色")
@RestController
@RequestMapping("/system/role")
@@ -43,7 +47,7 @@ public class RoleController {
@Operation(summary = "创建角色")
@PreAuthorize("@ss.hasPermission('system:role:create')")
public CommonResult<Long> createRole(@Valid @RequestBody RoleSaveReqVO createReqVO) {
return success(roleService.createRole(createReqVO, null));
return success(roleService.createRole(createReqVO, createReqVO.getType() == null ? null : Integer.valueOf(createReqVO.getType())));
}
@PutMapping("/update")
@@ -76,6 +80,20 @@ public class RoleController {
@PreAuthorize("@ss.hasPermission('system:role:query')")
public CommonResult<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) {
PageResult<RoleDO> pageResult = roleService.getRolePage(pageReqVO);
// 获取所有父级角色信息
List<Long> parentIds = pageResult.getList().stream().filter(role -> role.getParentId() != null && role.getParentId() > 0)
.map(RoleDO::getParentId)
.distinct()
.toList();
List<RoleDO> parentRoles = roleService.getRoleList(parentIds);
// 将父级角色信息转换为 id 与 name 的 Map
var parentRoleMap = parentRoles.stream().collect(Collectors.toMap(RoleDO::getId, RoleDO::getName, (v1, v2) -> v1));
// 补全父级角色名称
pageResult.getList().forEach(role -> {
if (role.getParentId() != null && role.getParentId() > 0) {
role.setParentName(parentRoleMap.get(role.getParentId()));
}
});
return success(BeanUtils.toBean(pageResult, RoleRespVO.class));
}
@@ -87,6 +105,16 @@ public class RoleController {
return success(BeanUtils.toBean(list, RoleRespVO.class));
}
@GetMapping({"/list-all-extend-simple", "/simple-extend-list"})
@Operation(summary = "获取所有可继承角色精简信息列表", description = "只包含被开启的角色,主要用于前端的下拉选项")
public CommonResult<List<RoleRespVO>> getParentSimpleRoleList() {
List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus()));
// 过滤掉系统内置角色(如有需要)
list.removeIf(role -> role.getType() != null && role.getType().equals(1));
list.sort(Comparator.comparing(RoleDO::getSort));
return success(BeanUtils.toBean(list, RoleRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出角色 Excel")
@ApiAccessLog(operateType = EXPORT)

View File

@@ -12,6 +12,9 @@ import lombok.Data;
import java.time.LocalDateTime;
import java.util.Set;
/**
* @author chenbowen
*/
@Schema(description = "管理后台 - 角色信息 Response VO")
@Data
@ExcelIgnoreUnannotated
@@ -56,4 +59,11 @@ public class RoleRespVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
private LocalDateTime createTime;
@Schema(description = "父级角色名称", example = "1")
@ExcelProperty("父级角色名称")
private String parentName;
@Schema(description = "父级角色 Id", example = "1")
@ExcelProperty("父级角色 Id")
private Long parentId;
}

View File

@@ -33,7 +33,6 @@ public class RoleDO extends TenantBaseDO {
private String name;
/**
* 角色标识
*
* 枚举
*/
private String code;
@@ -43,13 +42,11 @@ public class RoleDO extends TenantBaseDO {
private Integer sort;
/**
* 角色状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 角色类型
*
* 枚举 {@link RoleTypeEnum}
*/
private Integer type;
@@ -60,16 +57,27 @@ public class RoleDO extends TenantBaseDO {
/**
* 数据范围
*
* 枚举 {@link DataScopeEnum}
*/
private Integer dataScope;
/**
* 数据范围(指定部门数组)
*
* 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#DEPT_CUSTOM} 时
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Set<Long> dataScopeDeptIds;
/**
* 父级标准角色 Id : 继承的标准角色Id系统角色为 -1、标准角色为 0
*/
private Long parentId;
/**
* 父级角色名称
* 仅用于前端角色界面展示
*/
@TableField(exist = false)
private String parentName;
}

View File

@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 角色菜单剔除 DO
*
* @author 管理员
*/
@TableName("system_role_menu_exclusion")
@KeySequence("system_role_menu_exclusion_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleMenuExclusionDO extends BaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 角色ID
*/
private Long roleId;
/**
* 菜单ID
*/
private Long menuId;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.dal.dataobject.user;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import com.baomidou.mybatisplus.annotation.*;
@@ -53,6 +54,16 @@ public class AdminUserDO extends TenantBaseDO {
*/
@TableField(exist = false, typeHandler = JacksonTypeHandler.class )
private Set<Long> deptIds;
/**
* 公司 ID 列表
*/
@TableField(exist = false, typeHandler = JacksonTypeHandler.class )
private Set<Long> companyIds;
/**
* 公司与部门关系列表
*/
@TableField(exist = false)
private Set<CompanyDeptInfo> companyDeptInfos;
/**
* 岗位编号数组
*/

View File

@@ -12,6 +12,9 @@ import org.springframework.lang.Nullable;
import java.util.Collection;
import java.util.List;
/**
* @author chenbowen
*/
@Mapper
public interface RoleMapper extends BaseMapperX<RoleDO> {
@@ -36,4 +39,8 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
return selectList(RoleDO::getStatus, statuses);
}
default long selectCountByParentId(Long parentId) {
return selectCount(RoleDO::getParentId, parentId);
}
}

View File

@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.system.dal.mysql.rolemenuexclusion;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion.RoleMenuExclusionDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 角色菜单剔除 Mapper
*
* @author 管理员
*/
@Mapper
public interface RoleMenuExclusionMapper extends BaseMapperX<RoleMenuExclusionDO> {
/**
* 根据角色编号,查询角色菜单剔除列表
*
* @param roleIds 角色编号
*/
default List<RoleMenuExclusionDO> selectMenuIdListByRoleId(Collection<Long> roleIds) {
return selectList(RoleMenuExclusionDO::getRoleId, roleIds);
}
/**
* 根据角色编号,菜单编号,删除角色菜单剔除列表
*
* @param roleId 角色编号
* @param menuIds 菜单编号
*/
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
delete(new LambdaQueryWrapper<RoleMenuExclusionDO>()
.eq(RoleMenuExclusionDO::getRoleId, roleId)
.in(RoleMenuExclusionDO::getMenuId, menuIds));
}
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.service.dept;
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
@@ -115,4 +116,6 @@ public interface DeptService {
void validateDeptList(Collection<Long> ids);
List<DeptDO> getUserCompanyList();
Set<CompanyDeptInfo> getCompanyDeptInfoListByUserId(Long userId);
}

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
@@ -263,4 +264,43 @@ public class DeptServiceImpl implements DeptService {
return getDeptList(companyIds);
}
/**
* 根据用户ID查询其归属公司及直属部门关系列表不递归下级公司
*/
@Override
public Set<CompanyDeptInfo> getCompanyDeptInfoListByUserId(Long userId) {
// 查询用户所属部门
Set<Long> deptIds = userDeptMapper.selectValidListByUserIds(singleton(userId))
.stream()
.map(UserDeptDO::getDeptId)
.collect(Collectors.toSet());
if (CollUtil.isEmpty(deptIds)) {
return Collections.emptySet();
}
// 查询所有部门信息
Map<Long, DeptDO> deptMap = getDeptList(deptIds).stream()
.collect(Collectors.toMap(DeptDO::getId, d -> d));
Set<CompanyDeptInfo> result = new HashSet<>();
for (Long deptId : deptIds) {
DeptDO dept = deptMap.get(deptId);
if (dept == null) continue;
// 向上查找公司如果到达顶层parentId为PARENT_ID_ROOT还没找到公司则用顶层部门作为公司
DeptDO company = dept;
while (company != null && !Boolean.TRUE.equals(company.getIsCompany())) {
if (company.getParentId() == null || DeptDO.PARENT_ID_ROOT.equals(company.getParentId())) {
break;
}
company = getDept(company.getParentId());
}
if (company == null) continue;
CompanyDeptInfo info = new CompanyDeptInfo();
info.setCompanyId(company.getId());
info.setCompanyName(company.getName());
info.setDeptId(dept.getId());
info.setDeptName(dept.getName());
result.add(info);
}
return result;
}
}

View File

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
@@ -199,7 +200,12 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
if (userType.equals(UserTypeEnum.ADMIN.getValue())) {
AdminUserDO user = adminUserService.getUser(userId);
return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname())
.put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString()).build();
.put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString())
.put(LoginUser.INFO_KEY_COMPANY_IDS, CollUtil.isEmpty(user.getCompanyIds()) ? "[]" : JsonUtils.toJsonString(user.getCompanyIds()))
.put(LoginUser.INFO_KEY_DEPT_IDS, CollUtil.isEmpty(user.getDeptIds()) ? "[]" : JsonUtils.toJsonString(user.getDeptIds()))
.put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, CollUtil.isEmpty(user.getCompanyDeptInfos()) ? "[]" : JsonUtils.toJsonString(user.getCompanyDeptInfos()))
.put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds()))
.build();
} else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {
// 注意:目前 Member 暂时不读取,可以按需实现
return Collections.emptyMap();

View File

@@ -12,9 +12,11 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion.RoleMenuExclusionDO;
import cn.iocoder.yudao.module.system.dal.dataobject.userdept.UserDeptDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
@@ -69,6 +71,8 @@ public class PermissionServiceImpl implements PermissionService {
@Resource
private AdminUserService userService;
@Resource
private RoleMenuExclusionMapper roleMenuExclusionMapper;
@Resource
private UserDeptService userDeptService;
@Autowired
private PermissionService permissionService;
@@ -210,8 +214,14 @@ public class PermissionServiceImpl implements PermissionService {
if (roleService.hasAnySuperAdmin(roleIds)) {
return convertSet(menuService.getMenuList(), MenuDO::getId);
}
// 如果是非管理员的情况下,获得拥有的菜单编号
return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
// 递归获取所有父角色id
Set<Long> allRoleIds = roleService.getAllParentAndSelfRoleIds(roleIds);
// 如果是非管理员的情况下,获得拥有的菜单编号(含父角色,需要剔除当前角色排除的菜单)
Set<Long> menuIds = convertSet(roleMenuMapper.selectListByRoleId(allRoleIds), RoleMenuDO::getMenuId);
// 排除当前角色排除的菜单编号
Set<Long> excludeMenuIds = convertSet(roleMenuExclusionMapper.selectMenuIdListByRoleId(allRoleIds), RoleMenuExclusionDO::getMenuId);
menuIds.removeAll(excludeMenuIds);
return menuIds;
}
@Override

View File

@@ -123,4 +123,12 @@ public interface RoleService {
*/
void validateRoleList(Collection<Long> ids);
/**
* 获取所有父角色id递归
* @param roleIds 当前角色id集合
* @return 包含自身和所有父级的id集合
*/
Set<Long> getAllParentAndSelfRoleIds(Collection<Long> roleIds);
}

View File

@@ -24,6 +24,7 @@ import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
@@ -33,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -69,8 +71,11 @@ public class RoleServiceImpl implements RoleService {
// 2. 插入到数据库
RoleDO role = BeanUtils.toBean(createReqVO, RoleDO.class)
.setType(ObjectUtil.defaultIfNull(type, RoleTypeEnum.CUSTOM.getType()))
// 如果类型不为公司角色则设置 parentId 为 0
.setParentId(!ObjectUtil.equal(RoleTypeEnum.CUSTOM.getType(), type) ? 0L : createReqVO.getParentId())
.setStatus(ObjUtil.defaultIfNull(createReqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus()))
.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
// 默认可查看所有数据。原因是,可能一些项目不需要项目权限
.setDataScope(DataScopeEnum.ALL.getScope());
roleMapper.insert(role);
// 3. 记录操作日志上下文
@@ -87,6 +92,11 @@ public class RoleServiceImpl implements RoleService {
RoleDO role = validateRoleForUpdate(updateReqVO.getId());
// 1.2 校验角色的唯一字段是否重复
validateRoleDuplicate(updateReqVO.getName(), updateReqVO.getCode(), updateReqVO.getId());
// 1.3 校验角色当前修改的父角色是否为当前角色的子角色
if (updateReqVO.getParentId() != null && !updateReqVO.getParentId().equals(0L) && isChildRole(updateReqVO.getId(), updateReqVO.getParentId())) {
throw exception(ROLE_PARENT_IS_CHILD, updateReqVO.getName());
}
// 2. 更新到数据库
RoleDO updateObj = BeanUtils.toBean(updateReqVO, RoleDO.class);
@@ -112,6 +122,7 @@ public class RoleServiceImpl implements RoleService {
}
@Override
@SneakyThrows
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
@LogRecord(type = SYSTEM_ROLE_TYPE, subType = SYSTEM_ROLE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
@@ -120,6 +131,11 @@ public class RoleServiceImpl implements RoleService {
// 1. 校验是否可以更新
RoleDO role = validateRoleForUpdate(id);
// 1.1 校验角色是否存在子角色,如果存在,则不允许删除
if (roleMapper.selectCountByParentId(id) > 0) {
throw exception(ROLE_CAN_NOT_DELETE_HAS_CHILDREN , role.getName());
}
// 2.1 标记删除
roleMapper.deleteById(id);
// 2.2 删除相关数据
@@ -214,7 +230,7 @@ public class RoleServiceImpl implements RoleService {
if (CollectionUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return roleMapper.selectBatchIds(ids);
return roleMapper.selectByIds(ids);
}
@Override
@@ -262,7 +278,7 @@ public class RoleServiceImpl implements RoleService {
return;
}
// 获得角色信息
List<RoleDO> roles = roleMapper.selectBatchIds(ids);
List<RoleDO> roles = roleMapper.selectByIds(ids);
Map<Long, RoleDO> roleMap = convertMap(roles, RoleDO::getId);
// 校验
ids.forEach(id -> {
@@ -276,6 +292,29 @@ public class RoleServiceImpl implements RoleService {
});
}
@Override
public Set<Long> getAllParentAndSelfRoleIds(Collection<Long> roleIds) {
// 递归获取所有父角色id最多递归5层防止环
if (CollUtil.isEmpty(roleIds)) {
return Collections.emptySet();
}
RoleServiceImpl self = getSelf();
return roleIds.stream()
.flatMap(id -> {
Set<Long> chain = new LinkedHashSet<>();
Long current = id;
for (int depth = 0; current != null && current > 0 && depth < 5 && chain.add(current); depth++) {
RoleDO role = self.getRoleFromCache(current);
if (role == null || role.getParentId() == null || role.getParentId() <= 0) {
break;
}
current = role.getParentId();
}
return chain.stream();
})
.collect(Collectors.toSet());
}
/**
* 获得自身的代理对象,解决 AOP 生效问题
*
@@ -285,4 +324,24 @@ public class RoleServiceImpl implements RoleService {
return SpringUtil.getBean(getClass());
}
/**
* 判断 parentId 是否为 roleId 的子孙节点递归最多5次
*/
public boolean isChildRole(Long parentId, Long id) {
return isChildRole(parentId, id, 0);
}
public boolean isChildRole(Long parentId, Long id, int depth) {
if (parentId.equals(id)) {
return true;
}
if (depth >= 5) {
return false;
}
RoleDO parent = roleMapper.selectById(id);
if (parent == null || parent.getParentId() == null || parent.getParentId().equals(0L) || parent.getParentId().equals(-1L)) {
return false;
}
return isChildRole(parentId, parent.getParentId(), depth + 1);
}
}

View File

@@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollectionUtil;
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.pojo.CompanyDeptInfo;
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;
@@ -39,6 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -284,9 +286,11 @@ public class AdminUserServiceImpl implements AdminUserService {
@Override
public AdminUserDO getUser(Long id) {
AdminUserDO adminUserDO = userMapper.selectListByIds(singleton(id)).stream().findFirst().orElseThrow(() -> exception(USER_NOT_EXISTS));
// 查询用户关联的部门编号
List<UserDeptDO> userDeptList = userDeptService.getValidUserDeptListByUserIds(singleton(id));
adminUserDO.setDeptIds(convertSet(userDeptList, UserDeptDO::getDeptId));
Set<CompanyDeptInfo> companyDeptInfoListByUserId = deptService.getCompanyDeptInfoListByUserId(id);
adminUserDO.setDeptIds(companyDeptInfoListByUserId.stream().map(CompanyDeptInfo::getDeptId).collect(Collectors.toSet()));
adminUserDO.setCompanyIds(companyDeptInfoListByUserId.stream().map(CompanyDeptInfo::getCompanyId).collect(Collectors.toSet()));
adminUserDO.setCompanyDeptInfos(companyDeptInfoListByUserId);
// 设置用户的部门名称集合
return adminUserDO;
}

View File

@@ -86,7 +86,7 @@ xxl:
job:
enabled: false # 是否开启调度中心,默认为 true 开启
admin:
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
--- #################### 服务保障相关配置 ####################

View File

@@ -81,7 +81,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
assertPojoEquals(accessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted");
assertEquals(userId, accessTokenDO.getUserId());
assertEquals(userType, accessTokenDO.getUserType());
assertEquals(2, accessTokenDO.getUserInfo().size());
assertEquals(6, accessTokenDO.getUserInfo().size());
assertEquals(user.getNickname(), accessTokenDO.getUserInfo().get("nickname"));
assertEquals(clientId, accessTokenDO.getClientId());
assertEquals(scopes, accessTokenDO.getScopes());

View File

@@ -1,12 +1,16 @@
package cn.iocoder.yudao.module.system.service.permission;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion.RoleMenuExclusionDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper;
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
@@ -17,6 +21,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -25,6 +30,9 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEq
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Import({PermissionServiceImpl.class, RoleServiceImpl.class})
public class PermissionServiceTest extends BaseDbUnitTest {
@@ -35,6 +43,8 @@ public class PermissionServiceTest extends BaseDbUnitTest {
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
private RoleMenuExclusionMapper roleMenuExclusionMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
@@ -263,5 +273,114 @@ public class PermissionServiceTest extends BaseDbUnitTest {
// ========== 用户-部门的相关方法 ==========
// ========== 父子角色的相关方法 ==========
@Test
public void testGetAllParentAndSelfRoleIds() {
// mock 3层父子关系 A->B->C
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(roleA);
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
roleMapper.insert(roleB);
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
roleMapper.insert(roleC);
// 调用 PermissionService 的父子角色链路能力
Set<Long> ids = roleService.getAllParentAndSelfRoleIds(asSet(roleC.getId()));
// 断言递归能拿到所有父节点和自身
assertEquals(asSet(roleA.getId(), roleB.getId(), roleC.getId()), ids);
}
@Test
public void testIsChildRole_trueAndFalse() {
// mock 3层父子关系 A->B->C
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(roleA);
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
roleMapper.insert(roleB);
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
roleMapper.insert(roleC);
// 断言 C 是 A 的子孙节点
assertTrue(roleService.isChildRole(roleA.getId(), roleC.getId()));
// 断言 A 不是 C 的子孙节点
assertFalse(roleService.isChildRole(roleC.getId(), roleA.getId()));
}
@Test
public void testUpdateRole_parentIsChildException() {
// mock 2层父子关系 A->B
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(roleA);
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
roleMapper.insert(roleB);
// 尝试把A的父节点改为B形成环
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(roleA.getId()).setParentId(roleB.getId()));
assertThrows(ServiceException.class, () -> roleService.updateRole(reqVO));
}
@Test
public void testChildRoleInheritParentRoleMenus() {
// mock 父子关系 A->B
RoleDO parentRole = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(parentRole);
RoleDO childRole = randomPojo(RoleDO.class, o -> o.setParentId(parentRole.getId()));
roleMapper.insert(childRole);
// 给父角色分配菜单
RoleMenuDO parentMenu1 = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(101L);
roleMenuMapper.insert(parentMenu1);
RoleMenuDO parentMenu2 = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(102L);
roleMenuMapper.insert(parentMenu2);
// 给子角色分配部分菜单
RoleMenuDO childMenu = randomPojo(RoleMenuDO.class).setRoleId(childRole.getId()).setMenuId(201L);
roleMenuMapper.insert(childMenu);
// 调用:获取子角色的所有菜单(应包含父角色的菜单)
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(childRole.getId());
// 断言:包含父角色和子角色自己的菜单
assertEquals(asSet(101L, 102L, 201L), menuIds);
}
@Test
public void testChildRoleExcludeParentMenu() {
// mock 父子关系 A->B
RoleDO parentRole = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(parentRole);
RoleDO childRole = randomPojo(RoleDO.class, o -> o.setParentId(parentRole.getId()));
roleMapper.insert(childRole);
// 父角色分配菜单
RoleMenuDO parentMenu = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(101L);
roleMenuMapper.insert(parentMenu);
// 子角色排除父菜单(模拟排除表)
RoleMenuExclusionDO exclusion = new RoleMenuExclusionDO();
exclusion.setRoleId(childRole.getId());
exclusion.setMenuId(101L);
roleMenuExclusionMapper.insert(exclusion);
// 调用:获取子角色菜单(应不包含父菜单)
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(childRole.getId());
assertFalse(menuIds.contains(101L));
}
@Test
public void testChildRoleRemoveExclusionThenInheritMenu() {
// mock 父子关系 A->B
RoleDO parentRole = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(parentRole);
RoleDO childRole = randomPojo(RoleDO.class, o -> o.setParentId(parentRole.getId()));
roleMapper.insert(childRole);
// 父角色分配菜单
RoleMenuDO parentMenu = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(101L);
roleMenuMapper.insert(parentMenu);
// 子角色排除父菜单
RoleMenuExclusionDO exclusion = new RoleMenuExclusionDO();
exclusion.setRoleId(childRole.getId());
exclusion.setMenuId(101L);
roleMenuExclusionMapper.insert(exclusion);
// 先断言排除
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(childRole.getId());
assertFalse(menuIds.contains(101L));
// 取消排除
roleMenuExclusionMapper.deleteListByRoleIdAndMenuIds(childRole.getId(), Collections.singleton(101L));
// 再次获取,应能继承
Set<Long> menuIds2 = permissionService.getRoleMenuListByRoleId(childRole.getId());
assertTrue(menuIds2.contains(101L));
}
}

View File

@@ -390,4 +390,122 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
// 调用, 并断言异常
assertServiceException(() -> roleService.validateRoleList(ids), ROLE_IS_DISABLE, RoleDO.getName());
}
@Test
public void testIsChildRole_trueAndFalse() {
// mock 3层父子关系 A->B->C
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(roleA);
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
roleMapper.insert(roleB);
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
roleMapper.insert(roleC);
// 断言 C 的 parentId 是 A 的子孙节点
assertTrue(roleService.getClass().cast(roleService).isChildRole(roleA.getId(), roleC.getId()));
// 断言 A 不是 C 的子孙节点
assertFalse(roleService.getClass().cast(roleService).isChildRole(roleC.getId(), roleA.getId()));
}
@Test
public void testGetAllParentAndSelfRoleIds_multiLevelAndLoop() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
.thenReturn(roleService);
// mock 3层父子关系 A->B->C
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(roleA);
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
roleMapper.insert(roleB);
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
roleMapper.insert(roleC);
// 断言递归能拿到所有父节点
Set<Long> ids = roleService.getAllParentAndSelfRoleIds(singleton(roleC.getId()));
assertTrue(ids.contains(roleA.getId()));
assertTrue(ids.contains(roleB.getId()));
assertTrue(ids.contains(roleC.getId()));
// mock 环路C->A
roleC.setParentId(roleA.getId());
roleMapper.updateById(roleC);
// 不会死循环
Set<Long> idsLoop = roleService.getAllParentAndSelfRoleIds(singleton(roleC.getId()));
assertTrue(idsLoop.contains(roleA.getId()));
}
}
@Test
public void testHasAnyAdmin_trueAndFalse() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
.thenReturn(roleService);
// mock admin
RoleDO adminRole = randomPojo(RoleDO.class).setCode("super_admin");
roleMapper.insert(adminRole);
assertTrue(roleService.hasAnyAdmin(singletonList(adminRole.getId())));
// mock 普通
RoleDO normalRole = randomPojo(RoleDO.class).setCode("user");
roleMapper.insert(normalRole);
assertFalse(roleService.hasAnyAdmin(singletonList(normalRole.getId())));
}
}
@Test
public void testUpdateRole_parentIsChildException() {
// mock 2层父子关系 A->B
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(roleA);
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
roleMapper.insert(roleB);
// 尝试把A的父节点改为B形成环
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(roleA.getId()).setParentId(roleB.getId()));
assertServiceException(() -> roleService.updateRole(reqVO), ROLE_PARENT_IS_CHILD, reqVO.getName());
}
@Test
public void testGetRoleListFromCache_empty() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
.thenReturn(roleService);
// 空集合
List<RoleDO> list = roleService.getRoleListFromCache(List.of());
assertTrue(list.isEmpty());
}
}
@Test
public void testValidateRoleList_empty() {
// 空集合
roleService.validateRoleList(List.of()); // 不抛异常
}
@Test
public void testUpdateRoleDataScope_roleNotExist() {
assertServiceException(() -> roleService.updateRoleDataScope(randomLongId(), 1, Set.of(1L)), ROLE_NOT_EXISTS);
}
@Test
public void testDeleteRole_hasChildrenException() {
// mock 父子关系
RoleDO parent = randomPojo(RoleDO.class, o -> o.setParentId(0L));
roleMapper.insert(parent);
RoleDO child = randomPojo(RoleDO.class, o -> o.setParentId(parent.getId()));
roleMapper.insert(child);
assertServiceException(() -> roleService.deleteRole(parent.getId()), ROLE_CAN_NOT_DELETE_HAS_CHILDREN, parent.getName());
}
@Test
public void testDeleteRole_roleNotExist() {
assertServiceException(() -> roleService.deleteRole(randomLongId()), ROLE_NOT_EXISTS);
}
@Test
public void testCreateRole_superAdminCodeError() {
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class).setCode("super_admin");
assertServiceException(() -> roleService.createRole(reqVO, null), ROLE_ADMIN_CODE_ERROR, "super_admin");
}
@Test
public void testValidateRoleDuplicate_codeEmpty() {
// name 不重复code 为空
roleService.validateRoleDuplicate(randomString(), "", null); // 不抛异常
}
}

View File

@@ -18,11 +18,13 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
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.dataobject.userdept.UserDeptDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
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.dal.mysql.userdept.UserDeptMapper;
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.dept.DeptServiceImpl;
import cn.iocoder.yudao.module.system.service.dept.PostService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
@@ -56,7 +58,7 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@Import({AdminUserServiceImpl.class,UserDeptServiceImpl.class})
@Import({AdminUserServiceImpl.class,UserDeptServiceImpl.class,DeptServiceImpl.class})
public class AdminUserServiceImplTest extends BaseDbUnitTest {
@Resource
@@ -69,10 +71,11 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
@Resource
private UserDeptMapper userDeptMapper;
@Resource
private DeptMapper deptMapper;
@Resource
private UserDeptServiceImpl userDeptService;
@MockBean
private DeptService deptService;
@Resource
private DeptServiceImpl deptService;
@MockBean
private PostService postService;
@MockBean
@@ -99,7 +102,12 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
o.setMobile(randomString());
o.setPostIds(asSet(1L, 2L));
o.setDeptIds(asSet(1L, 2L));
}).setId(null); // 避免 id 被赋值
// 新建对应的部门
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(1L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(2L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
// mock 账户额度充足
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(1));
doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {
@@ -147,16 +155,20 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
@Test
public void testUpdateUser_success() {
// mock 数据
AdminUserDO dbUser = randomAdminUserDO(o -> o.setPostIds(asSet(1L, 2L)));
AdminUserDO dbUser = randomAdminUserDO(o -> o.setPostIds(asSet(1L, 2L)).setDeptIds(asSet(1L, 2L)));
userMapper.insert(dbUser);
userPostMapper.insert(new UserPostDO().setUserId(dbUser.getId()).setPostId(1L));
userPostMapper.insert(new UserPostDO().setUserId(dbUser.getId()).setPostId(2L));
// 新增对应的部门
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(1L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(2L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
// 准备参数
UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {
o.setId(dbUser.getId());
o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
o.setMobile(randomString());
o.setPostIds(asSet(2L, 3L));
o.setDeptIds(asSet(1L, 2L));
});
// mock postService 的方法
List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
@@ -299,7 +311,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
// 调用
AdminUserDO user = userService.getUserByUsername(username);
// 断言
assertPojoEquals(dbUser, user,"deptIds");
assertPojoEquals(dbUser, user,"deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -313,7 +325,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
// 调用
AdminUserDO user = userService.getUserByMobile(mobile);
// 断言
assertPojoEquals(dbUser, user,"deptIds");
assertPojoEquals(dbUser, user,"deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -330,7 +342,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
// reqVO.setDeptId(1L); // 其中1L 是 2L 的父部门
// mock 方法
List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
deptService.getChildDeptList(reqVO.getDeptId());
// 新增 1L 和用户关联关系
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(1L));
// 调用
@@ -338,7 +350,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbUser, pageResult.getList().get(0), "deptIds");
assertPojoEquals(dbUser, pageResult.getList().get(0), "deptIds", "companyIds", "companyDeptInfos");
}
/**
@@ -376,7 +388,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
// 调用
AdminUserDO user = userService.getUser(userId);
// 断言
assertPojoEquals(dbUser, user, "deptIds");
assertPojoEquals(dbUser, user, "deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -397,7 +409,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
List<AdminUserDO> list = userService.getUserListByDeptIds(deptIds);
// 断言
assertEquals(1, list.size());
assertPojoEquals(dbUser, list.get(0), "deptIds");
assertPojoEquals(dbUser, list.get(0), "deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -520,7 +532,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
List<AdminUserDO> result = userService.getUserListByPostIds(postIds);
// 断言
assertEquals(1, result.size());
assertPojoEquals(user1, result.get(0), "deptIds");
assertPojoEquals(user1, result.get(0), "deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -539,7 +551,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
List<AdminUserDO> result = userService.getUserList(ids);
// 断言
assertEquals(1, result.size());
assertPojoEquals(user, result.get(0), "deptIds");
assertPojoEquals(user, result.get(0), "deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -558,7 +570,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
Map<Long, AdminUserDO> result = userService.getUserMap(ids);
// 断言
assertEquals(1, result.size());
assertPojoEquals(user, result.get(user.getId()), "deptIds");
assertPojoEquals(user, result.get(user.getId()), "deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -575,7 +587,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
List<AdminUserDO> result = userService.getUserListByNickname(nickname);
// 断言
assertEquals(1, result.size());
assertPojoEquals(user, result.get(0),"deptIds");
assertPojoEquals(user, result.get(0),"deptIds", "companyIds", "companyDeptInfos");
}
@Test
@@ -588,15 +600,12 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
userMapper.insert(user2);
// 准备参数
Integer status = CommonStatusEnum.DISABLE.getStatus();
// 新增 1L 和用户关联关系 未关联 部门的用户无法被正确查询
userDeptMapper.insert(new UserDeptDO().setUserId(user2.getId()).setDeptId(1L));
userDeptMapper.insert(new UserDeptDO().setUserId(user.getId()).setDeptId(1L));
// 调用
List<AdminUserDO> result = userService.getUserListByStatus(status);
// 断言
assertEquals(1, result.size());
AdminUserDO user1 = userService.getUser(result.get(0).getId());
assertPojoEquals(user, user1, "deptIds");
assertPojoEquals(user, user1, "deptIds","companyIds","companyDeptInfos");
}
@Test
@@ -640,7 +649,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
Consumer<AdminUserDO> consumer = (o) -> {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
o.setSex(randomEle(SexEnum.values()).getSex());
o.setDeptIds(new HashSet<>(asSet(1L, 2L))); // 保证 deptIds 的范围
o.setDeptIds(new HashSet<>(asSet(1L, 2L)));
o.setCompanyDeptInfos(null);// 保证 deptIds 的范围
};
return randomPojo(AdminUserDO.class, ArrayUtils.append(consumer, consumers));
}
@@ -651,4 +661,88 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
return randomPojo(UserDeptDO.class, ArrayUtils.append(consumer, consumers));
}
@Test
public void testCreateUser_withMultipleDepts() {
// 构造带多个部门的用户
UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {
o.setDeptIds(asSet(1L, 2L, 3L));
o.setSex(1);
}).setId(null);
// 新建对应的部门
deptMapper.insert(new DeptDO().setId(1L).setName("部门1").setStatus(CommonStatusEnum.ENABLE.getStatus()));
deptMapper.insert(new DeptDO().setId(2L).setName("部门2").setStatus(CommonStatusEnum.ENABLE.getStatus()));
deptMapper.insert(new DeptDO().setId(3L).setName("部门3").setStatus(CommonStatusEnum.ENABLE.getStatus()));
// mock 账户额度充足、部门可用
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(1));
doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {
handler.handle(tenant);
return true;
}));
List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
randomPojo(PostDO.class, o -> {
o.setId(postId);
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
}));
when(postService.getPostList(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
when(passwordEncoder.encode(eq(reqVO.getPassword()))).thenReturn("yudaoyuanma");
// 调用
Long userId = userService.createUser(reqVO);
// 校验 user_dept 表有3条数据
List<UserDeptDO> userDepts = userDeptMapper.selectValidListByUserIds(singleton(userId));
assertEquals(3, userDepts.size());
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(1L)));
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(2L)));
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(3L)));
}
@Test
public void testUpdateUser_changeDepts() {
// 先插入用户和2个部门
AdminUserDO dbUser = randomAdminUserDO(o -> o.setDeptIds(asSet(1L, 2L)));
userMapper.insert(dbUser);
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(1L));
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(2L));
// 新建对应的部门
deptMapper.insert(new DeptDO().setId(1L).setName("部门1").setStatus(CommonStatusEnum.ENABLE.getStatus()));
deptMapper.insert(new DeptDO().setId(2L).setName("部门2").setStatus(CommonStatusEnum.ENABLE.getStatus()));
// 更新为3个新部门
UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {
o.setId(dbUser.getId());
o.setSex(1);
o.setDeptIds(asSet(1L, 2L));
});
// mock postService 的方法
List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
randomPojo(PostDO.class, o -> {
o.setId(postId);
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
}));
when(postService.getPostList(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
// 调用
userService.updateUser(reqVO);
// 校验 user_dept 表
List<UserDeptDO> userDepts = userDeptMapper.selectValidListByUserIds(singleton(dbUser.getId()));
assertEquals(2, userDepts.size());
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(2L)));
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(1L)));
}
@Test
public void testGetUser_withMultipleDepts() {
// 插入用户和多个部门
AdminUserDO dbUser = randomAdminUserDO();
userMapper.insert(dbUser);
// 插入用户部门关系
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(1L));
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(2L));
// 插入部门
deptMapper.insert(new DeptDO().setId(1L).setName("部门1").setStatus(CommonStatusEnum.ENABLE.getStatus()));
deptMapper.insert(new DeptDO().setId(2L).setName("部门2").setStatus(CommonStatusEnum.ENABLE.getStatus()));
AdminUserDO user = userService.getUser(dbUser.getId());
assertTrue(user.getDeptIds().contains(1L));
assertTrue(user.getDeptIds().contains(2L));
}
}