Compare commits

..

3 Commits

26 changed files with 1019 additions and 11 deletions

View File

@ -23,6 +23,7 @@ public interface ErrorCodeConstants {
ErrorCode MENU_NOT_EXISTS = new ErrorCode(1_002_001_003, "菜单不存在");
ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1_002_001_004, "存在子菜单,无法删除");
ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1_002_001_005, "父菜单的类型必须是目录或者菜单");
ErrorCode MENU_APP_NOT_EXISTS = new ErrorCode(1_002_001_006, "APP菜单权限不存在");
// ========== 角色模块 1-002-002-000 ==========
ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在");

View File

@ -0,0 +1,85 @@
package com.inspur.module.system.controller.admin.permission;
import com.inspur.framework.common.enums.CommonStatusEnum;
import com.inspur.framework.common.pojo.CommonResult;
import com.inspur.framework.common.util.object.BeanUtils;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.APPMenuSimpleRespVO;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppPageReqVO;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppRespVO;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppSaveReqVO;
import com.inspur.module.system.dal.dataobject.menuapp.MenuAppDO;
import com.inspur.module.system.service.permission.MenuAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Comparator;
import java.util.List;
import static com.inspur.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 菜单权限")
@RestController
@RequestMapping("/system/menu-app")
@Validated
public class MenuAppController {
@Resource
private MenuAppService menuAppService;
@PostMapping("/create")
@Operation(summary = "创建APP菜单权限")
public CommonResult<Long> createMenuApp(@Valid @RequestBody MenuAppSaveReqVO createReqVO) {
Long menuId = menuAppService.createMenuApp(createReqVO);
return success(menuId);
}
@PutMapping("/update")
@Operation(summary = "更新APP菜单权限")
public CommonResult<Boolean> updateMenuApp(@Valid @RequestBody MenuAppSaveReqVO updateReqVO) {
menuAppService.updateMenuApp(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除APP菜单权限")
@Parameter(name = "id", description = "编号", required = true)
public CommonResult<Boolean> deleteMenuApp(@RequestParam("id") Long id) {
menuAppService.deleteMenuApp(id);
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得APP菜单权限分页")
public CommonResult<List<MenuAppRespVO>> getMenuAppPage(@Valid MenuAppPageReqVO pageReqVO) {
List<MenuAppDO> list = menuAppService.getMenuAppPage(pageReqVO);
list.sort(Comparator.comparing(MenuAppDO::getSort));
return success(BeanUtils.toBean(list, MenuAppRespVO.class));
}
@GetMapping({"/list-all-simple", "simple-list"})
@Operation(summary = "获取菜单精简信息列表", description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" +
"在多租户的场景下,会只返回租户所在套餐有的菜单")
public CommonResult<List<APPMenuSimpleRespVO>> getSimpleMenuList() {
MenuAppPageReqVO reqVO = new MenuAppPageReqVO();
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
List<MenuAppDO> list = menuAppService.getMenuListByTenant(reqVO);
list = menuAppService.filterDisableMenus(list);
list.sort(Comparator.comparing(MenuAppDO::getSort));
return success(BeanUtils.toBean(list, APPMenuSimpleRespVO.class));
}
@GetMapping("/get")
@Operation(summary = "获得APP菜单权限")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<MenuAppRespVO> getMenuApp(@RequestParam("id") Long id) {
MenuAppDO menuApp = menuAppService.getMenuApp(id);
return success(BeanUtils.toBean(menuApp, MenuAppRespVO.class));
}
}

View File

@ -70,10 +70,12 @@ public class MenuController {
@Operation(summary = "获取菜单精简信息列表", description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" +
"在多租户的场景下,会只返回租户所在套餐有的菜单")
public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList() {
List<MenuDO> list = menuService.getMenuListByTenant(
new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
MenuListReqVO reqVO = new MenuListReqVO();
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
List<MenuDO> list = menuService.getMenuListByTenant(reqVO);
list = menuService.filterDisableMenus(list);
list.sort(Comparator.comparing(MenuDO::getSort));
return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
}

View File

@ -43,6 +43,14 @@ public class PermissionController {
return success(permissionService.getRoleMenuListByRoleId(roleId));
}
@Operation(summary = "获得角色拥有的APP菜单编号")
@Parameter(name = "roleId", description = "角色编号", required = true)
@GetMapping("/list-role-APPmenus")
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-appmenu')")
public CommonResult<Set<Long>> getRoleAPPMenuList(Long roleId) {
return success(permissionService.getRoleAPPMenuListByRoleId(roleId));
}
@PostMapping("/assign-role-menu")
@Operation(summary = "赋予角色菜单")
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
@ -55,6 +63,17 @@ public class PermissionController {
return success(true);
}
@PostMapping("/assign-role-appmenu")
@Operation(summary = "赋予角色APP菜单")
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-appmenu')")
public CommonResult<Boolean> assignRoleAPPMenu(@Validated @RequestBody PermissionAssignRoleMenuReqVO reqVO) {
// 开启多租户的情况下需要过滤掉未开通的菜单
tenantService.handleTenantAPPMenu(menuIds -> reqVO.getMenuIds().removeIf(menuId -> !CollUtil.contains(menuIds, menuId)));
// 执行菜单的分配
permissionService.assignRoleAPPMenu(reqVO.getRoleId(), reqVO.getMenuIds());
return success(true);
}
@PostMapping("/assign-role-data-scope")
@Operation(summary = "赋予角色数据权限")
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-data-scope')")

View File

@ -0,0 +1,26 @@
package com.inspur.module.system.controller.admin.permission.vo.appmenu;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 菜单精简信息 Response VO")
@Data
public class APPMenuSimpleRespVO {
@Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
private String name;
@Schema(description = "父菜单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long parentId;
@Schema(description = "类型,参见 MenuTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer type;
}

View File

@ -0,0 +1,13 @@
package com.inspur.module.system.controller.admin.permission.vo.appmenu;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - APP菜单列表 Request VO")
@Data
public class MenuAppListReqVO {
@Schema(description = "APP菜单名称模糊匹配", example = "芋道")
private String name;
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
private Integer status;
}

View File

@ -0,0 +1,61 @@
package com.inspur.module.system.controller.admin.permission.vo.appmenu;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import com.inspur.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.inspur.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 菜单权限分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MenuAppPageReqVO extends PageParam {
@Schema(description = "菜单名称", example = "芋艿")
private String name;
@Schema(description = "权限标识")
private String permission;
@Schema(description = "菜单类型", example = "1")
private Integer type;
@Schema(description = "显示顺序")
private Integer sort;
@Schema(description = "父菜单ID", example = "6433")
private Long parentId;
@Schema(description = "路由地址")
private String path;
@Schema(description = "菜单图标")
private String icon;
@Schema(description = "组件路径")
private String component;
@Schema(description = "组件名", example = "芋艿")
private String componentName;
@Schema(description = "菜单状态", example = "1")
private Integer status;
@Schema(description = "是否可见")
private Boolean visible;
@Schema(description = "是否缓存")
private Boolean keepAlive;
@Schema(description = "是否总是显示")
private Boolean alwaysShow;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,75 @@
package com.inspur.module.system.controller.admin.permission.vo.appmenu;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - 菜单权限 Response VO")
@Data
@ExcelIgnoreUnannotated
public class MenuAppRespVO {
@Schema(description = "菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "22580")
@ExcelProperty("菜单ID")
private Long id;
@Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@ExcelProperty("菜单名称")
private String name;
@Schema(description = "权限标识", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("权限标识")
private String permission;
@Schema(description = "菜单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("菜单类型")
private Integer type;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("显示顺序")
private Integer sort;
@Schema(description = "父菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "6433")
@ExcelProperty("父菜单ID")
private Long parentId;
@Schema(description = "路由地址")
@ExcelProperty("路由地址")
private String path;
@Schema(description = "菜单图标")
@ExcelProperty("菜单图标")
private String icon;
@Schema(description = "组件路径")
@ExcelProperty("组件路径")
private String component;
@Schema(description = "组件名", example = "芋艿")
@ExcelProperty("组件名")
private String componentName;
@Schema(description = "菜单状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("菜单状态")
private Integer status;
@Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("是否可见")
private Boolean visible;
@Schema(description = "是否缓存", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("是否缓存")
private Boolean keepAlive;
@Schema(description = "是否总是显示", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("是否总是显示")
private Boolean alwaysShow;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,64 @@
package com.inspur.module.system.controller.admin.permission.vo.appmenu;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 菜单权限新增/修改 Request VO")
@Data
public class MenuAppSaveReqVO {
@Schema(description = "菜单编号", example = "1024")
private Long id;
@Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
@NotBlank(message = "菜单名称不能为空")
@Size(max = 50, message = "菜单名称长度不能超过50个字符")
private String name;
@Schema(description = "权限标识,仅菜单类型为按钮时,才需要传递", example = "sys:menu:add")
@Size(max = 100)
private String permission;
@Schema(description = "类型,参见 MenuTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "菜单类型不能为空")
private Integer type;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空")
private Integer sort;
@Schema(description = "父菜单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "父菜单 ID 不能为空")
private Long parentId;
@Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
@Size(max = 200, message = "路由地址不能超过200个字符")
private String path;
@Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
private String icon;
@Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
@Size(max = 200, message = "组件路径不能超过255个字符")
private String component;
@Schema(description = "组件名", example = "SystemUser")
private String componentName;
@Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空")
private Integer status;
@Schema(description = "是否可见", example = "false")
private Boolean visible;
@Schema(description = "是否缓存", example = "false")
private Boolean keepAlive;
@Schema(description = "是否总是显示", example = "false")
private Boolean alwaysShow;
}

View File

@ -25,6 +25,9 @@ public class TenantPackageRespVO {
@Schema(description = "关联的菜单编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Set<Long> menuIds;
@Schema(description = "关联的APP菜单编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Set<Long> appMenuIds;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@ -32,4 +32,8 @@ public class TenantPackageSaveReqVO {
@NotNull(message = "关联的菜单编号不能为空")
private Set<Long> menuIds;
@Schema(description = "关联的菜单编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "关联的菜单编号不能为空")
private Set<Long> appMenuIds;
}

View File

@ -0,0 +1,83 @@
package com.inspur.module.system.dal.dataobject.menuapp;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import com.inspur.framework.mybatis.core.dataobject.BaseDO;
/**
* 菜单权限 DO
*
* @author 管理员
*/
@TableName("system_menu_app")
@KeySequence("system_menu_app_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuAppDO extends BaseDO {
/**
* 菜单ID
*/
@TableId
private Long id;
/**
* 菜单名称
*/
private String name;
/**
* 权限标识
*/
private String permission;
/**
* 菜单类型
*/
private Integer type;
/**
* 显示顺序
*/
private Integer sort;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 路由地址
*/
private String path;
/**
* 菜单图标
*/
private String icon;
/**
* 组件路径
*/
private String component;
/**
* 组件名
*/
private String componentName;
/**
* 菜单状态
*/
private Integer status;
/**
* 是否可见
*/
private Boolean visible;
/**
* 是否缓存
*/
private Boolean keepAlive;
/**
* 是否总是显示
*/
private Boolean alwaysShow;
}

View File

@ -0,0 +1,35 @@
package com.inspur.module.system.dal.dataobject.permission;
import com.inspur.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 角色和菜单关联
*
* @author ruoyi
*/
@TableName("system_role_menu_app")
@KeySequence("system_role_menu_app_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
public class RoleMenuAppDO extends TenantBaseDO {
/**
* 自增主键
*/
@TableId
private Long id;
/**
* 角色ID
*/
private Long roleId;
/**
* 菜单ID
*/
private Long menuId;
}

View File

@ -49,4 +49,10 @@ public class TenantPackageDO extends BaseDO {
@TableField(typeHandler = JacksonTypeHandler.class)
private Set<Long> menuIds;
/**
* 关联的菜单编号
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Set<Long> appMenuIds;
}

View File

@ -0,0 +1,35 @@
package com.inspur.module.system.dal.mysql.permission;
import com.inspur.framework.mybatis.core.mapper.BaseMapperX;
import com.inspur.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppPageReqVO;
import com.inspur.module.system.dal.dataobject.menuapp.MenuAppDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* APP菜单权限 Mapper
*
* @author 管理员
*/
@Mapper
public interface MenuAppMapper extends BaseMapperX<MenuAppDO> {
default MenuAppDO selectByParentIdAndName(Long parentId, String name) {
return selectOne(MenuAppDO::getParentId, parentId, MenuAppDO::getName, name);
}
default Long selectCountByParentId(Long parentId) {
return selectCount(MenuAppDO::getParentId, parentId);
}
default List<MenuAppDO> selectPage(MenuAppPageReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<MenuAppDO>()
.likeIfPresent(MenuAppDO::getName, reqVO.getName())
.eqIfPresent(MenuAppDO::getStatus, reqVO.getStatus()));
}
}

View File

@ -0,0 +1,40 @@
package com.inspur.module.system.dal.mysql.permission;
import com.inspur.framework.mybatis.core.mapper.BaseMapperX;
import com.inspur.module.system.dal.dataobject.permission.RoleMenuAppDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface RoleMenuAppMapper extends BaseMapperX<RoleMenuAppDO> {
default List<RoleMenuAppDO> selectListByRoleId(Long roleId) {
return selectList(RoleMenuAppDO::getRoleId, roleId);
}
default List<RoleMenuAppDO> selectListByRoleId(Collection<Long> roleIds) {
return selectList(RoleMenuAppDO::getRoleId, roleIds);
}
default List<RoleMenuAppDO> selectListByMenuId(Long menuId) {
return selectList(RoleMenuAppDO::getMenuId, menuId);
}
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
delete(new LambdaQueryWrapper<RoleMenuAppDO>()
.eq(RoleMenuAppDO::getRoleId, roleId)
.in(RoleMenuAppDO::getMenuId, menuIds));
}
default void deleteListByMenuId(Long menuId) {
delete(new LambdaQueryWrapper<RoleMenuAppDO>().eq(RoleMenuAppDO::getMenuId, menuId));
}
default void deleteListByRoleId(Long roleId) {
delete(new LambdaQueryWrapper<RoleMenuAppDO>().eq(RoleMenuAppDO::getRoleId, roleId));
}
}

View File

@ -0,0 +1,64 @@
package com.inspur.module.system.service.permission;
import com.inspur.framework.common.pojo.PageResult;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppListReqVO;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppPageReqVO;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppSaveReqVO;
import com.inspur.module.system.dal.dataobject.menuapp.MenuAppDO;
import javax.validation.Valid;
import java.util.List;
/**
* 菜单权限 Service 接口
*
* @author 管理员
*/
public interface MenuAppService {
/**
* 创建菜单权限
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createMenuApp(@Valid MenuAppSaveReqVO createReqVO);
/**
* 更新菜单权限
*
* @param updateReqVO 更新信息
*/
void updateMenuApp(@Valid MenuAppSaveReqVO updateReqVO);
/**
* 删除菜单权限
*
* @param id 编号
*/
void deleteMenuApp(Long id);
/**
* 获得菜单权限
*
* @param id 编号
* @return 菜单权限
*/
MenuAppDO getMenuApp(Long id);
/**
* 获得菜单权限分页
*
* @param pageReqVO 分页查询
* @return 菜单权限分页
*/
List<MenuAppDO> getMenuAppPage(MenuAppPageReqVO pageReqVO);
List<MenuAppDO> getMenuListByTenant(MenuAppPageReqVO reqVO);
List<MenuAppDO> filterDisableMenus(List<MenuAppDO> list);
public List<MenuAppDO> getMenuList(MenuAppPageReqVO reqVO);
public List<MenuAppDO> getAPPMenuList();
}

View File

@ -0,0 +1,227 @@
package com.inspur.module.system.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.google.common.annotations.VisibleForTesting;
import com.inspur.framework.common.enums.CommonStatusEnum;
import com.inspur.framework.common.exception.util.ServiceExceptionUtil;
import com.inspur.framework.common.pojo.PageResult;
import com.inspur.framework.common.util.object.BeanUtils;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppListReqVO;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppPageReqVO;
import com.inspur.module.system.controller.admin.permission.vo.appmenu.MenuAppSaveReqVO;
import com.inspur.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
import com.inspur.module.system.dal.dataobject.menuapp.MenuAppDO;
import com.inspur.module.system.dal.dataobject.permission.MenuDO;
import com.inspur.module.system.dal.mysql.permission.MenuAppMapper;
import com.inspur.module.system.enums.ErrorCodeConstants;
import com.inspur.module.system.enums.permission.MenuTypeEnum;
import com.inspur.module.system.service.tenant.TenantService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static com.inspur.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.inspur.framework.common.util.collection.CollectionUtils.convertMap;
import static com.inspur.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
import static com.inspur.module.system.enums.ErrorCodeConstants.MENU_APP_NOT_EXISTS;
/**
* 菜单权限 Service 实现类
*
* @author 管理员
*/
@Service
@Validated
public class MenuAppServiceImpl implements MenuAppService {
@Resource
private MenuAppMapper menuAppMapper;
@Resource
private PermissionService permissionService;
@Resource
@Lazy // 延迟避免循环依赖报错
private TenantService tenantService;
@Override
public Long createMenuApp(MenuAppSaveReqVO createReqVO) {
// 校验父菜单存在
validateParentMenu(createReqVO.getParentId(), null);
// 校验菜单自己
validateMenu(createReqVO.getParentId(), createReqVO.getName(), null);
// 插入
MenuAppDO menuApp = BeanUtils.toBean(createReqVO, MenuAppDO.class);
menuAppMapper.insert(menuApp);
// 返回
return menuApp.getId();
}
@Override
public void updateMenuApp(MenuAppSaveReqVO updateReqVO) {
// 校验存在
validateMenuAppExists(updateReqVO.getId());
// 校验父菜单存在
validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId());
// 校验菜单自己
validateMenu(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId());
// 更新
MenuAppDO updateObj = BeanUtils.toBean(updateReqVO, MenuAppDO.class);
menuAppMapper.updateById(updateObj);
}
@Override
public void deleteMenuApp(Long id) {
// 校验存在
validateMenuAppExists(id);
// 校验是否还有子菜单
if (menuAppMapper.selectCountByParentId(id) > 0) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.MENU_EXISTS_CHILDREN);
}
// 删除
menuAppMapper.deleteById(id);
// 删除授予给角色的权限
//TODO permissionService.processMenuDeleted(id);
permissionService.processMenuDeleted(id);
}
private void validateMenuAppExists(Long id) {
if (menuAppMapper.selectById(id) == null) {
throw exception(MENU_APP_NOT_EXISTS);
}
}
@Override
public MenuAppDO getMenuApp(Long id) {
return menuAppMapper.selectById(id);
}
@Override
public List<MenuAppDO> getMenuAppPage(MenuAppPageReqVO pageReqVO) {
return menuAppMapper.selectPage(pageReqVO);
}
@Override
public List<MenuAppDO> getMenuListByTenant(MenuAppPageReqVO reqVO) {
// 查询所有菜单并过滤掉关闭的节点
List<MenuAppDO> menus = getMenuList(reqVO);
// 开启多租户的情况下需要过滤掉未开通的菜单
tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
return menus;
}
@Override
public List<MenuAppDO> filterDisableMenus(List<MenuAppDO> menuList) {
if (CollUtil.isEmpty(menuList)){
return Collections.emptyList();
}
Map<Long, MenuAppDO> menuMap = convertMap(menuList, MenuAppDO::getId);
// 遍历 menu 菜单查找不是禁用的菜单添加到 enabledMenus 结果
List<MenuAppDO> enabledMenus = new ArrayList<>();
Set<Long> disabledMenuCache = new HashSet<>(); // 存下递归搜索过被禁用的菜单防止重复的搜索
for (MenuAppDO menu : menuList) {
if (isMenuDisabled(menu, menuMap, disabledMenuCache)) {
continue;
}
enabledMenus.add(menu);
}
return enabledMenus;
}
private boolean isMenuDisabled(MenuAppDO node, Map<Long, MenuAppDO> menuMap, Set<Long> disabledMenuCache) {
// 如果已经判定是禁用的节点直接结束
if (disabledMenuCache.contains(node.getId())) {
return true;
}
// 1. 遍历到 parentId 为根节点则无需判断
Long parentId = node.getParentId();
if (ObjUtil.equal(parentId, ID_ROOT)) {
if (CommonStatusEnum.isDisable(node.getStatus())) {
disabledMenuCache.add(node.getId());
return true;
}
return false;
}
// 2. 继续遍历 parent 节点
MenuAppDO parent = menuMap.get(parentId);
if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) {
disabledMenuCache.add(node.getId());
return true;
}
return false;
}
@Override
public List<MenuAppDO> getMenuList(MenuAppPageReqVO reqVO) {
return menuAppMapper.selectPage(reqVO);
}
@Override
public List<MenuAppDO> getAPPMenuList() {
return menuAppMapper.selectList();
}
/**
* 校验父菜单是否合法
* <p>
* 1. 不能设置自己为父菜单
* 2. 父菜单不存在
* 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
*
* @param parentId 父菜单编号
* @param childId 当前菜单编号
*/
@VisibleForTesting
void validateParentMenu(Long parentId, Long childId) {
if (parentId == null || ID_ROOT.equals(parentId)) {
return;
}
// 不能设置自己为父菜单
if (parentId.equals(childId)) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.MENU_PARENT_ERROR);
}
MenuAppDO menu = menuAppMapper.selectById(parentId);
// 父菜单不存在
if (menu == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.MENU_PARENT_NOT_EXISTS);
}
// 父菜单必须是目录或者菜单类型
if (!MenuTypeEnum.DIR.getType().equals(menu.getType())
&& !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.MENU_PARENT_NOT_DIR_OR_MENU);
}
}
/**
* 校验菜单是否合法
* <p>
* 1. 校验相同父菜单编号下是否存在相同的菜单名
*
* @param name 菜单名字
* @param parentId 父菜单编号
* @param id 菜单编号
*/
@VisibleForTesting
void validateMenu(Long parentId, String name, Long id) {
MenuAppDO menu = menuAppMapper.selectByParentIdAndName(parentId, name);
if (menu == null) {
return;
}
// 如果 id 为空说明不用比较是否为相同 id 的菜单
if (id == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.MENU_NAME_DUPLICATE);
}
if (!menu.getId().equals(id)) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.MENU_NAME_DUPLICATE);
}
}
}

View File

@ -42,6 +42,15 @@ public interface PermissionService {
* @param menuIds 菜单编号集合
*/
void assignRoleMenu(Long roleId, Set<Long> menuIds);
// ========== 角色-菜单的相关方法 ==========
/**
* 设置角色菜单
*
* @param roleId 角色编号
* @param menuIds 菜单编号集合
*/
void assignRoleAPPMenu(Long roleId, Set<Long> menuIds);
/**
* 处理角色删除时删除关联授权数据
@ -57,6 +66,13 @@ public interface PermissionService {
*/
void processMenuDeleted(Long menuId);
/**
* 处理菜单删除时删除关联授权数据
*
* @param menuId 菜单编号
*/
void processAPPMenuDeleted(Long menuId);
/**
* 获得角色拥有的菜单编号集合
*
@ -66,7 +82,15 @@ public interface PermissionService {
default Set<Long> getRoleMenuListByRoleId(Long roleId) {
return getRoleMenuListByRoleId(singleton(roleId));
}
/**
* 获得角色拥有的APP菜单编号集合
*
* @param roleId 角色编号
* @return APP菜单编号集合
*/
default Set<Long> getRoleAPPMenuListByRoleId(Long roleId){
return getRoleAPPMenuListByRoleId(singleton(roleId));
};
/**
* 获得角色们拥有的菜单编号集合
*
@ -75,6 +99,14 @@ public interface PermissionService {
*/
Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
/**
* 获得角色们拥有的APP菜单编号集合
*
* @param roleIds 角色编号数组
* @return APP菜单编号集合
*/
Set<Long> getRoleAPPMenuListByRoleId(Collection<Long> roleIds);
/**
* 获得拥有指定菜单的角色编号数组从缓存中获取
*
@ -143,4 +175,5 @@ public interface PermissionService {
*/
DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
}

View File

@ -8,10 +8,9 @@ import com.inspur.framework.common.enums.CommonStatusEnum;
import com.inspur.framework.common.util.collection.CollectionUtils;
import com.inspur.framework.datapermission.core.annotation.DataPermission;
import com.inspur.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import com.inspur.module.system.dal.dataobject.permission.MenuDO;
import com.inspur.module.system.dal.dataobject.permission.RoleDO;
import com.inspur.module.system.dal.dataobject.permission.RoleMenuDO;
import com.inspur.module.system.dal.dataobject.permission.UserRoleDO;
import com.inspur.module.system.dal.dataobject.menuapp.MenuAppDO;
import com.inspur.module.system.dal.dataobject.permission.*;
import com.inspur.module.system.dal.mysql.permission.RoleMenuAppMapper;
import com.inspur.module.system.dal.mysql.permission.RoleMenuMapper;
import com.inspur.module.system.dal.mysql.permission.UserRoleMapper;
import com.inspur.module.system.dal.redis.RedisKeyConstants;
@ -48,6 +47,8 @@ public class PermissionServiceImpl implements PermissionService {
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
private RoleMenuAppMapper roleMenuAppMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
@ -55,6 +56,8 @@ public class PermissionServiceImpl implements PermissionService {
@Resource
private MenuService menuService;
@Resource
private MenuAppService menuAppService;
@Resource
private DeptService deptService;
@Resource
private AdminUserService userService;
@ -155,6 +158,36 @@ public class PermissionServiceImpl implements PermissionService {
}
}
// ========== 角色-菜单的相关方法 ==========
@Override
@DSTransactional // 多数据源使用 @DSTransactional 保证本地事务以及数据源的切换
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
allEntries = true) // allEntries 清空所有缓存主要一次更新涉及到的 menuIds 较多反倒批量会更快
public void assignRoleAPPMenu(Long roleId, Set<Long> appmenuIds) {
// 获得角色拥有菜单编号
Set<Long> dbAPPMenuIds = convertSet(roleMenuAppMapper.selectListByRoleId(roleId), RoleMenuAppDO::getMenuId);
// 计算新增和删除的菜单编号
Set<Long> appmenuIdList = CollUtil.emptyIfNull(appmenuIds);
Collection<Long> createMenuIds = CollUtil.subtract(appmenuIdList, dbAPPMenuIds);
Collection<Long> deleteMenuIds = CollUtil.subtract(dbAPPMenuIds, appmenuIdList);
// 执行新增和删除对于已经授权的菜单不用做任何处理
if (CollUtil.isNotEmpty(createMenuIds)) {
roleMenuAppMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
RoleMenuAppDO entity = new RoleMenuAppDO();
entity.setRoleId(roleId);
entity.setMenuId(menuId);
return entity;
}));
}
if (CollUtil.isNotEmpty(deleteMenuIds)) {
roleMenuAppMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
@Caching(evict = {
@ -176,6 +209,12 @@ public class PermissionServiceImpl implements PermissionService {
roleMenuMapper.deleteListByMenuId(menuId);
}
@Override
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
public void processAPPMenuDeleted(Long menuId) {
roleMenuAppMapper.deleteListByMenuId(menuId);
}
@Override
public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
if (CollUtil.isEmpty(roleIds)) {
@ -190,6 +229,21 @@ public class PermissionServiceImpl implements PermissionService {
return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
}
@Override
public Set<Long> getRoleAPPMenuListByRoleId(Collection<Long> roleIds) {
if (CollUtil.isEmpty(roleIds)) {
return Collections.emptySet();
}
// 如果是管理员的情况下获取全部菜单编号
if (roleService.hasAnySuperAdmin(roleIds)) {
return convertSet(menuAppService.getAPPMenuList(), MenuAppDO::getId);
}
// 如果是非管理员的情况下获得拥有的菜单编号
return convertSet(roleMenuAppMapper.selectListByRoleId(roleIds), RoleMenuAppDO::getMenuId);
}
@Override
@Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {

View File

@ -113,6 +113,14 @@ public interface TenantService {
*/
void handleTenantMenu(TenantMenuHandler handler);
/**
* 进行租户的APP菜单处理逻辑
* 其中租户编号从 {@link TenantContextHolder} 上下文中获取
*
* @param handler 处理器
*/
void handleTenantAPPMenu(TenantMenuHandler handler);
/**
* 获得所有租户
*

View File

@ -17,6 +17,7 @@ import com.inspur.module.system.controller.admin.permission.vo.role.RoleSaveReqV
import com.inspur.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import com.inspur.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
import com.inspur.module.system.convert.tenant.TenantConvert;
import com.inspur.module.system.dal.dataobject.menuapp.MenuAppDO;
import com.inspur.module.system.dal.dataobject.permission.MenuDO;
import com.inspur.module.system.dal.dataobject.permission.RoleDO;
import com.inspur.module.system.dal.dataobject.tenant.TenantDO;
@ -24,6 +25,7 @@ import com.inspur.module.system.dal.dataobject.tenant.TenantPackageDO;
import com.inspur.module.system.dal.mysql.tenant.TenantMapper;
import com.inspur.module.system.enums.permission.RoleCodeEnum;
import com.inspur.module.system.enums.permission.RoleTypeEnum;
import com.inspur.module.system.service.permission.MenuAppService;
import com.inspur.module.system.service.permission.MenuService;
import com.inspur.module.system.service.permission.PermissionService;
import com.inspur.module.system.service.permission.RoleService;
@ -73,6 +75,8 @@ public class TenantServiceImpl implements TenantService {
@Resource
private MenuService menuService;
@Resource
private MenuAppService menuAppService;
@Resource
private PermissionService permissionService;
@Override
@ -135,8 +139,10 @@ public class TenantServiceImpl implements TenantService {
private Long createRole(TenantPackageDO tenantPackage) {
// 创建角色
RoleSaveReqVO reqVO = new RoleSaveReqVO();
reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode())
.setSort(0).setRemark("系统自动生成");
reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName());
reqVO.setCode(RoleCodeEnum.TENANT_ADMIN.getCode());
reqVO.setSort(0);
reqVO.setRemark("系统自动生成");
Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());
// 分配权限
permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds());
@ -300,6 +306,24 @@ public class TenantServiceImpl implements TenantService {
handler.handle(menuIds);
}
@Override
public void handleTenantAPPMenu(TenantMenuHandler handler) {
// 如果禁用则不执行逻辑
if (isTenantDisable()) {
return;
}
// 获得租户然后获得菜单
TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId());
Set<Long> menuIds;
if (isSystemTenant(tenant)) { // 系统租户菜单是全量的
menuIds = CollectionUtils.convertSet(menuAppService.getAPPMenuList(), MenuAppDO::getId);
} else {
menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getAppMenuIds();
}
// 执行处理器
handler.handle(menuIds);
}
private static boolean isSystemTenant(TenantDO tenant) {
return Objects.equals(tenant.getPackageId(), TenantDO.PACKAGE_ID_SYSTEM);
}

View File

@ -252,6 +252,7 @@ imt:
- system_dict_type
- system_error_code
- system_menu
- system_menu_app
- system_sms_channel
- system_sms_template
- system_sms_log

View File

View File

@ -71,6 +71,12 @@
<el-tree class="tree-border" :data="menuOptions" show-checkbox ref="menu" node-key="id"
:check-strictly="menuCheckStrictly" empty-text="加载中,请稍后" :props="defaultProps"></el-tree>
</el-form-item>
<el-form-item label="APP菜单权限">
<el-checkbox v-model="APPmenuExpand" @change="handleCheckedAPPTreeExpand($event)">展开/折叠</el-checkbox>
<el-checkbox v-model="APPmenuNodeAll" @change="handleCheckedAPPTreeNodeAll($event)">全选/全不选</el-checkbox>
<el-tree class="tree-border" :data="APPmenuOptions" show-checkbox ref="APPmenu" node-key="id"
:check-strictly="APPmenuCheckStrictly" empty-text="加载中,请稍后" :props="APPdefaultProps"></el-tree>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
@ -93,6 +99,7 @@
import { createTenantPackage, updateTenantPackage, deleteTenantPackage, getTenantPackage, getTenantPackagePage} from "@/api/system/tenantPackage";
import {CommonStatusEnum} from "@/utils/constants";
import {listSimpleMenus} from "@/api/system/menu";
import {listSimpleAPPMenus} from "@/api/system/appMenu";
export default {
name: "SystemTenantPackage",
@ -124,13 +131,21 @@ export default {
//
form: {},
menuExpand: false,
APPmenuExpand: false,
menuNodeAll: false,
APPmenuNodeAll: false,
menuCheckStrictly: true,
APPmenuCheckStrictly: true,
defaultProps: {
label: "name",
children: "children"
},
APPdefaultProps: {
label: "name",
children: "children"
},
menuOptions: [], //
APPmenuOptions: [], //
//
rules: {
name: [{ required: true, message: "套餐名不能为空", trigger: "blur" }],
@ -165,9 +180,15 @@ export default {
if (this.$refs.menu !== undefined) {
this.$refs.menu.setCheckedKeys([]);
}
if (this.$refs.APPmenu !== undefined) {
this.$refs.APPmenu.setCheckedKeys([]);
}
this.menuExpand = false;
this.APPmenuExpand = false;
this.menuNodeAll = false;
this.APPmenuNodeAll = false;
this.menuCheckStrictly = true;
this.APPmenuCheckStrictly = true;
//
this.form = {
id: undefined,
@ -175,6 +196,7 @@ export default {
status: CommonStatusEnum.ENABLE,
remark: undefined,
menuIds: undefined,
appMenuIds: undefined,
};
this.resetForm("form");
},
@ -196,6 +218,7 @@ export default {
this.title = "添加租户套餐";
// 使
this.menuCheckStrictly = false;
this.APPmenuCheckStrictly = false;
},
/** 修改按钮操作 */
handleUpdate(row) {
@ -209,10 +232,13 @@ export default {
//
//
this.menuCheckStrictly = true
this.APPmenuCheckStrictly = true
//
this.$refs.menu.setCheckedKeys(response.data.menuIds);
this.$refs.APPmenu.setCheckedKeys(response.data.appMenuIds);
// 使
this.menuCheckStrictly = false
this.APPmenuCheckStrictly = false
});
},
/** 获得菜单 */
@ -223,6 +249,12 @@ export default {
//
this.menuOptions.push(...this.handleTree(response.data, "id"));
});
listSimpleAPPMenus().then(response => {
// APPmenuOptions
this.APPmenuOptions = [];
//
this.APPmenuOptions.push(...this.handleTree(response.data, "id"));
});
},
/** 提交按钮 */
submitForm() {
@ -234,7 +266,8 @@ export default {
if (this.form.id != null) {
updateTenantPackage({
...this.form,
menuIds: [...this.$refs.menu.getCheckedKeys(), ...this.$refs.menu.getHalfCheckedKeys()]
menuIds: [...this.$refs.menu.getCheckedKeys(), ...this.$refs.menu.getHalfCheckedKeys()],
appMenuIds: [...this.$refs.APPmenu.getCheckedKeys(), ...this.$refs.APPmenu.getHalfCheckedKeys()]
}).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
@ -245,7 +278,8 @@ export default {
//
createTenantPackage({
...this.form,
menuIds: [...this.$refs.menu.getCheckedKeys(), ...this.$refs.menu.getHalfCheckedKeys()]
menuIds: [...this.$refs.menu.getCheckedKeys(), ...this.$refs.menu.getHalfCheckedKeys()],
appMenuIds: [...this.$refs.APPmenu.getCheckedKeys(), ...this.$refs.APPmenu.getHalfCheckedKeys()]
}).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
@ -274,6 +308,17 @@ export default {
handleCheckedTreeNodeAll(value) {
this.$refs.menu.setCheckedNodes(value ? this.menuOptions: []);
},
// /
handleCheckedAPPTreeExpand(value, type) {
let treeList = this.APPmenuOptions;
for (let i = 0; i < treeList.length; i++) {
this.$refs.APPmenu.store.nodesMap[treeList[i].id].expanded = value;
}
},
// /
handleCheckedAPPTreeNodeAll(value) {
this.$refs.APPmenu.setCheckedNodes(value ? this.APPmenuOptions: []);
},
//
handleCheckedTreeConnect(value) {
this.form.menuCheckStrictly = value;