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

@@ -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();

Some files were not shown because too many files have changed in this diff Show More