diff --git a/.gitignore b/.gitignore index e55eb64b..a63c02e2 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,6 @@ npm-debug.log* yarn-error.log /coverage -.idea yarn.lock package-lock.json *bak @@ -73,3 +72,4 @@ functions/mock screenshot .firebase sessionStore +/http/ diff --git a/sql/dm/角色权限剔除表.sql b/sql/dm/角色权限剔除表.sql new file mode 100644 index 00000000..d25aa493 --- /dev/null +++ b/sql/dm/角色权限剔除表.sql @@ -0,0 +1,33 @@ +create table "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION +( + ID BIGINT, + ROLE_ID BIGINT not null, + MENU_ID BIGINT not null, + REMARK VARCHAR(2000), + CREATOR VARCHAR(256) default '', + CREATE_TIME TIMESTAMP default CURRENT_TIMESTAMP not null, + UPDATER VARCHAR(256) default '', + UPDATE_TIME TIMESTAMP default CURRENT_TIMESTAMP not null, + DELETED TINYINT default 0 not null, + TENANT_ID BIGINT default 0 not null +); + +comment on table "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION is '角色菜单剔除表'; + +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.ID is '主键ID'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.ROLE_ID is '角色ID'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.MENU_ID is '菜单ID'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.REMARK is '备注'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.CREATOR is '创建者'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.CREATE_TIME is '创建时间'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.UPDATER is '更新者'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.UPDATE_TIME is '更新时间'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.DELETED is '是否删除'; +comment on column "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION.TENANT_ID is '租户编号'; + +create unique index "RUOYI-VUE-PRO".IDX_ROLE_MENU_EXCLUSION_ID + on "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION (ID); + +alter table "RUOYI-VUE-PRO".SYSTEM_ROLE_MENU_EXCLUSION + add constraint PK_ROLE_MENU_EXCLUSION_ID + primary key (ID); \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml b/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml index c51b2e48..ef1b047a 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml @@ -3,12 +3,12 @@ spring: cloud: nacos: -# server-addr: 172.16.46.63:30848 # Nacos 服务器地址 - server-addr: 120.26.250.247:8848 # Nacos 服务器地址 + server-addr: 172.16.46.63:30848 # Nacos 服务器地址 +# server-addr: 120.26.250.247:8848 # Nacos 服务器地址 username: # Nacos 账号 password: # Nacos 密码 discovery: # 【配置中心】配置项 - namespace: dev # 命名空间。这里使用 dev 开发环境 + namespace: local # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP metadata: version: 1.0.0 # 服务实例的版本号,可用于灰度发布 diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index c89a5dcd..376ac3e1 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode; * System 错误码枚举类 * * system 系统,使用 1-002-000-000 段 + * @author chenbowen */ public interface ErrorCodeConstants { @@ -33,6 +34,8 @@ 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_DELETE_HAS_CHILDREN = new ErrorCode(1_002_002_006, " 角色【{}】存在子角色,不允许删除"); + ErrorCode ROLE_PARENT_IS_CHILD = new ErrorCode(1_002_002_007, "不能设置自己的子角色为父角色"); // ========== 用户模块 1-002-003-000 ========== ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, "用户账号已经存在"); diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginResultEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginResultEnum.java index afb92e42..836b9c28 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginResultEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginResultEnum.java @@ -5,16 +5,22 @@ import lombok.Getter; /** * 登录结果的枚举类 + * @author chenbowen */ @Getter @AllArgsConstructor public enum LoginResultEnum { - SUCCESS(0), // 成功 - BAD_CREDENTIALS(10), // 账号或密码不正确 - USER_DISABLED(20), // 用户被禁用 - CAPTCHA_NOT_FOUND(30), // 图片验证码不存在 - CAPTCHA_CODE_ERROR(31), // 图片验证码不正确 + // 成功 + SUCCESS(0), + // 账号或密码不正确 + BAD_CREDENTIALS(10), + // 用户被禁用 + USER_DISABLED(20), + // 图片验证码不存在 + CAPTCHA_NOT_FOUND(30), + // 图片验证码不正确 + CAPTCHA_CODE_ERROR(31), ; diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleTypeEnum.java index 1607b20b..098251fc 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleTypeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleTypeEnum.java @@ -3,6 +3,10 @@ package cn.iocoder.yudao.module.system.enums.permission; import lombok.AllArgsConstructor; import lombok.Getter; +/** + * @author chenbowen + */ + @Getter @AllArgsConstructor public enum RoleTypeEnum { @@ -11,10 +15,14 @@ public enum RoleTypeEnum { * 内置角色 */ SYSTEM(1), + /** + * 标准角色 + */ + NORMAL(2), /** * 自定义角色 */ - CUSTOM(2); + CUSTOM(3); private final Integer type; diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java index c7bec77d..1301a599 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java @@ -10,12 +10,12 @@ import cn.iocoder.yudao.module.system.service.tenant.TenantService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; import java.util.Set; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/RoleController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/RoleController.java index cf226875..15e138e0 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/RoleController.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/RoleController.java @@ -7,7 +7,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; -import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.*; +import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleRespVO; +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.service.permission.RoleService; import io.swagger.v3.oas.annotations.Operation; @@ -23,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") @@ -41,7 +47,7 @@ public class RoleController { @Operation(summary = "创建角色") @PreAuthorize("@ss.hasPermission('system:role:create')") public CommonResult 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") @@ -74,6 +80,20 @@ public class RoleController { @PreAuthorize("@ss.hasPermission('system:role:query')") public CommonResult> getRolePage(RolePageReqVO pageReqVO) { PageResult pageResult = roleService.getRolePage(pageReqVO); + // 获取所有父级角色信息 + List parentIds = pageResult.getList().stream().filter(role -> role.getParentId() != null && role.getParentId() > 0) + .map(RoleDO::getParentId) + .distinct() + .toList(); + List 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)); } @@ -85,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> getParentSimpleRoleList() { + List 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) diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java index bc97dcbf..146be829 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java @@ -1,12 +1,15 @@ package cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Data; -import jakarta.validation.constraints.NotNull; import java.util.Collections; import java.util.Set; +/** + * @author chenbowen + */ @Schema(description = "管理后台 - 赋予用户角色 Request VO") @Data public class PermissionAssignUserRoleReqVO { @@ -16,6 +19,6 @@ public class PermissionAssignUserRoleReqVO { private Long userId; @Schema(description = "角色编号列表", example = "1,3,5") - private Set roleIds = Collections.emptySet(); // 兜底 + private Set roleIds = Collections.emptySet(); } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java index 89f80c67..74c87aa9 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java @@ -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; } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java index 2d273f36..1aa0658d 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java @@ -9,6 +9,9 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; +/** + * @author chenbowen + */ @Schema(description = "管理后台 - 角色创建/更新 Request VO") @Data public class RoleSaveReqVO { @@ -39,6 +42,12 @@ public class RoleSaveReqVO { @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") private Integer status; + @Schema(description = "角色类型", example = "1 内置角色,2 标准角色,3 租户自定义角色") + private String type; + + @Schema(description = "父级角色Id", example = "-1 系统角色,0 顶级角色") + private Long parentId; + @Schema(description = "备注", example = "我是一个角色") @Size(max = 500, message = "备注长度不能超过 500 个字符") @DiffLogField(name = "备注") diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java index 7cb2824a..2f7696b0 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java @@ -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 dataScopeDeptIds; + /** + * 父级标准角色 Id : 继承的标准角色Id,系统角色为 -1、标准角色为 0 + */ + private Long parentId; + + /** + * 父级角色名称 + * 仅用于前端角色界面展示 + */ + @TableField(exist = false) + private String parentName; + + } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/rolemenuexclusion/RoleMenuExclusionDO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/rolemenuexclusion/RoleMenuExclusionDO.java new file mode 100644 index 00000000..cf0285a6 --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/rolemenuexclusion/RoleMenuExclusionDO.java @@ -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; + + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java index 2e4da2bb..91c0d90b 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java @@ -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 { @@ -36,4 +39,8 @@ public interface RoleMapper extends BaseMapperX { return selectList(RoleDO::getStatus, statuses); } + default long selectCountByParentId(Long parentId) { + return selectCount(RoleDO::getParentId, parentId); + } + } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/rolemenuexclusion/RoleMenuExclusionMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/rolemenuexclusion/RoleMenuExclusionMapper.java new file mode 100644 index 00000000..409542cc --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/rolemenuexclusion/RoleMenuExclusionMapper.java @@ -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 { + + /** + * 根据角色编号,查询角色菜单剔除列表 + * + * @param roleIds 角色编号 + */ + default List selectMenuIdListByRoleId(Collection roleIds) { + return selectList(RoleMenuExclusionDO::getRoleId, roleIds); + } + + /** + * 根据角色编号,菜单编号,删除角色菜单剔除列表 + * + * @param roleId 角色编号 + * @param menuIds 菜单编号 + */ + default void deleteListByRoleIdAndMenuIds(Long roleId, Collection menuIds) { + delete(new LambdaQueryWrapper() + .eq(RoleMenuExclusionDO::getRoleId, roleId) + .in(RoleMenuExclusionDO::getMenuId, menuIds)); + } +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java index 8eb5b096..01f25ada 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java @@ -4,16 +4,18 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; -import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; 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.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.service.dept.DeptService; @@ -22,6 +24,7 @@ import com.baomidou.dynamic.datasource.annotation.DSTransactional; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Suppliers; import com.google.common.collect.Sets; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @@ -29,11 +32,11 @@ import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import jakarta.annotation.Resource; import java.util.*; import java.util.function.Supplier; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; /** @@ -58,6 +61,8 @@ public class PermissionServiceImpl implements PermissionService { private DeptService deptService; @Resource private AdminUserService userService; + @Resource + private RoleMenuExclusionMapper roleMenuExclusionMapper; @Override public boolean hasAnyPermissions(Long userId, String... permissions) { @@ -140,22 +145,49 @@ public class PermissionServiceImpl implements PermissionService { }) public void assignRoleMenu(Long roleId, Set menuIds) { // 获得角色拥有菜单编号 - Set dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId); + Set dbMenuIds = convertSet(getRoleMenuListByRoleId(roleId)); + // 获取父级角色拥有的菜单编号 + Set parentRoleIds = roleService.getAllParentAndSelfRoleIds(singleton(roleId)); + // 移除自身角色编号 + parentRoleIds.remove(roleId); + Set dbInheritedMenuIds = convertSet(roleMenuMapper.selectListByRoleId(parentRoleIds), RoleMenuDO::getMenuId); // 计算新增和删除的菜单编号 Set menuIdList = CollUtil.emptyIfNull(menuIds); Collection createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds); Collection deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList); - // 执行新增和删除。对于已经授权的菜单,不用做任何处理 + // 执行新增和删除。对于已经授权的菜单,不用进行新增和删除,处理排除关系即可 if (CollUtil.isNotEmpty(createMenuIds)) { - roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> { - RoleMenuDO entity = new RoleMenuDO(); - entity.setRoleId(roleId); - entity.setMenuId(menuId); - return entity; - })); + Set inheritedCreateMenuIds = new HashSet<>(dbInheritedMenuIds); + inheritedCreateMenuIds.retainAll(createMenuIds); + if (CollUtil.isNotEmpty(inheritedCreateMenuIds)) { + // 不需要新增,只需要检查是否存在排除关系,如果存在,则标记排除关系失效 + roleMenuExclusionMapper.deleteListByRoleIdAndMenuIds(roleId, inheritedCreateMenuIds); + createMenuIds.removeAll(inheritedCreateMenuIds); + } + if (CollUtil.isNotEmpty(createMenuIds)) { + roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> { + RoleMenuDO entity = new RoleMenuDO(); + entity.setRoleId(roleId); + entity.setMenuId(menuId); + return entity; + })); + } } if (CollUtil.isNotEmpty(deleteMenuIds)) { - roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); + Set inheritedDeleteMenuIds = new HashSet<>(dbInheritedMenuIds); + inheritedDeleteMenuIds.retainAll(deleteMenuIds); + if (CollUtil.isNotEmpty(inheritedDeleteMenuIds)) { + // 标记排除 + roleMenuExclusionMapper.insertBatch(CollectionUtils.convertList(inheritedDeleteMenuIds, menuId -> { + RoleMenuExclusionDO entity = new RoleMenuExclusionDO(); + entity.setRoleId(roleId); + entity.setMenuId(menuId); + return entity; + })); + } + if (CollUtil.isNotEmpty(deleteMenuIds)) { + roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); + } } } @@ -190,8 +222,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 allRoleIds = roleService.getAllParentAndSelfRoleIds(roleIds); + // 如果是非管理员的情况下,获得拥有的菜单编号(含父角色,需要剔除当前角色排除的菜单) + Set menuIds = convertSet(roleMenuMapper.selectListByRoleId(allRoleIds), RoleMenuDO::getMenuId); + // 排除当前角色排除的菜单编号 + Set excludeMenuIds = convertSet(roleMenuExclusionMapper.selectMenuIdListByRoleId(allRoleIds), RoleMenuExclusionDO::getMenuId); + menuIds.removeAll(excludeMenuIds); + return menuIds; } @Override diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java index 6de8b515..bea26e63 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; - import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; import java.util.Set; @@ -121,4 +121,11 @@ public interface RoleService { */ void validateRoleList(Collection ids); + /** + * 获取所有父角色id(递归) + * @param roleIds 当前角色id集合 + * @return 包含自身和所有父级的id集合 + */ + Set getAllParentAndSelfRoleIds(Collection roleIds); + } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java index c18ef31c..adbd3549 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java @@ -22,6 +22,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.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @@ -30,6 +31,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; @@ -62,8 +64,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. 记录操作日志上下文 @@ -80,6 +85,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); @@ -105,6 +115,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}}", @@ -113,6 +124,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 删除相关数据 @@ -124,7 +140,6 @@ public class RoleServiceImpl implements RoleService { /** * 校验角色的唯一字段是否重复 - * * 1. 是否存在相同名字的角色 * 2. 是否存在相同编码的角色 * @@ -200,7 +215,7 @@ public class RoleServiceImpl implements RoleService { if (CollectionUtil.isEmpty(ids)) { return Collections.emptyList(); } - return roleMapper.selectBatchIds(ids); + return roleMapper.selectByIds(ids); } @Override @@ -236,7 +251,7 @@ public class RoleServiceImpl implements RoleService { return; } // 获得角色信息 - List roles = roleMapper.selectBatchIds(ids); + List roles = roleMapper.selectByIds(ids); Map roleMap = convertMap(roles, RoleDO::getId); // 校验 ids.forEach(id -> { @@ -250,6 +265,29 @@ public class RoleServiceImpl implements RoleService { }); } + @Override + public Set getAllParentAndSelfRoleIds(Collection roleIds) { + // 递归获取所有父角色id,最多递归5层,防止环 + if (CollUtil.isEmpty(roleIds)) { + return Collections.emptySet(); + } + RoleServiceImpl self = getSelf(); + return roleIds.stream() + .flatMap(id -> { + Set 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 生效问题 * @@ -259,4 +297,24 @@ public class RoleServiceImpl implements RoleService { return SpringUtil.getBean(getClass()); } + /** + * 判断 parentId 是否为 roleId 的子孙节点,递归最多5次 + */ + private boolean isChildRole(Long roleId, Long parentId) { + return isChildRole(roleId, parentId, 0); + } + private boolean isChildRole(Long roleId, Long parentId, int depth) { + if (roleId.equals(parentId)) { + return true; + } + if (depth >= 5) { + return false; + } + RoleDO parent = roleMapper.selectById(parentId); + if (parent == null || parent.getParentId() == null || parent.getParentId().equals(0L) || parent.getParentId().equals(-1L)) { + return false; + } + return isChildRole(roleId, parent.getParentId(), depth + 1); + } + }