将 list-menus 合并到 get-permission-info 接口,接口职责更统一

This commit is contained in:
YunaiV 2023-02-25 14:13:29 +08:00
parent 2976675331
commit b371225292
18 changed files with 152 additions and 143 deletions

View File

@ -21,7 +21,7 @@ public class PermissionApiImpl implements PermissionApi {
@Override
public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
return permissionService.getUserRoleIdListByRoleIds(roleIds);
return permissionService.getUserRoleIdListByRoleId(roleIds);
}
@Override

View File

@ -24,9 +24,3 @@ tenant-id: {{adminTenentId}}
GET {{baseUrl}}/system/auth/get-permission-info
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /list-menus 接口 => 成功
GET {{baseUrl}}/system/list-menus
Authorization: Bearer {{token}}
#Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a
tenant-id: {{adminTenentId}}

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.controller.admin.auth;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
@ -12,16 +11,16 @@ 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.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
import cn.iocoder.yudao.module.system.service.permission.MenuService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -34,9 +33,9 @@ import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
import static java.util.Collections.singleton;
@Tag(name = "管理后台 - 认证")
@RestController
@ -52,6 +51,8 @@ public class AuthController {
@Resource
private RoleService roleService;
@Resource
private MenuService menuService;
@Resource
private PermissionService permissionService;
@Resource
private SocialUserService socialUserService;
@ -90,34 +91,24 @@ public class AuthController {
@GetMapping("/get-permission-info")
@Operation(summary = "获取登录用户的权限信息")
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
// 获得用户信息
// 1.1 获得用户信息
AdminUserDO user = userService.getUser(getLoginUserId());
if (user == null) {
return null;
}
// 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(),
singleton(CommonStatusEnum.ENABLE.getStatus()));
List<RoleDO> roleList = roleService.getRoleList(roleIds);
// 获得菜单列表
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
// 拼接结果返回
return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
}
@GetMapping("/list-menus")
@Operation(summary = "获得登录用户的菜单列表")
public CommonResult<List<AuthMenuRespVO>> getMenuList() {
// 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
// 获得用户拥有的菜单列表
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
// 转换成 Tree 结构返回
return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
// 1.2 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
List<RoleDO> roles = roleService.getRoleList(roleIds);
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
// 1.3 获得菜单列表
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
List<MenuDO> menuList = menuService.getMenuList(menuIds);
menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单
// 2. 拼接结果返回
return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
}
// ========== 短信登录相关 ==========

View File

@ -1,53 +0,0 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthMenuRespVO {
@Schema(description = "菜单名称", required = true, example = "芋道")
private Long id;
@Schema(description = "父菜单 ID", required = true, example = "1024")
private Long parentId;
@Schema(description = "菜单名称", required = true, example = "芋道")
private String name;
@Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
private String path;
@Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
private String component;
@Schema(description = "组件名", example = "SystemUser")
private String componentName;
@Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
private String icon;
@Schema(description = "是否可见", required = true, example = "false")
private Boolean visible;
@Schema(description = "是否缓存", required = true, example = "false")
private Boolean keepAlive;
@Schema(description = "是否总是显示", example = "false")
private Boolean alwaysShow;
/**
* 子路由
*/
private List<AuthMenuRespVO> children;
}

View File

@ -6,6 +6,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Set;
@Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表")
@ -24,6 +25,9 @@ public class AuthPermissionInfoRespVO {
@Schema(description = "操作权限数组", required = true)
private Set<String> permissions;
@Schema(description = "菜单树", required = true)
private List<MenuVO> menus;
@Schema(description = "用户信息 VO")
@Data
@NoArgsConstructor
@ -37,9 +41,53 @@ public class AuthPermissionInfoRespVO {
@Schema(description = "用户昵称", required = true, example = "芋道源码")
private String nickname;
@Schema(description = "用户头像", required = true, example = "http://www.iocoder.cn/xx.jpg")
@Schema(description = "用户头像", required = true, example = "https://www.iocoder.cn/xx.jpg")
private String avatar;
}
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class MenuVO {
@Schema(description = "菜单名称", required = true, example = "芋道")
private Long id;
@Schema(description = "父菜单 ID", required = true, example = "1024")
private Long parentId;
@Schema(description = "菜单名称", required = true, example = "芋道")
private String name;
@Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
private String path;
@Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
private String component;
@Schema(description = "组件名", example = "SystemUser")
private String componentName;
@Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
private String icon;
@Schema(description = "是否可见", required = true, example = "false")
private Boolean visible;
@Schema(description = "是否缓存", required = true, example = "false")
private Boolean keepAlive;
@Schema(description = "是否总是显示", example = "false")
private Boolean alwaysShow;
/**
* 子路由
*/
private List<MenuVO> children;
}
}

View File

@ -40,7 +40,7 @@ public class PermissionController {
@GetMapping("/list-role-resources")
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
return success(permissionService.getRoleMenuIds(roleId));
return success(permissionService.getRoleMenuListByRoleId(roleId));
}
@PostMapping("/assign-role-menu")

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.convert.auth;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
@ -9,12 +8,14 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
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.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import org.slf4j.LoggerFactory;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
@ -27,13 +28,19 @@ public interface AuthConvert {
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
return AuthPermissionInfoRespVO.builder()
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
.roles(CollectionUtils.convertSet(roleList, RoleDO::getCode))
.permissions(CollectionUtils.convertSet(menuList, MenuDO::getPermission))
.build();
// 用户信息
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId())
.nickname(user.getNickname()).avatar(user.getAvatar()).build())
// 角色信息
.roles(convertSet(roleList, RoleDO::getCode))
// 权限标识信息
.permissions(convertSet(menuList, MenuDO::getPermission))
// 菜单树
.menus(buildMenuTree(menuList))
.build();
}
AuthMenuRespVO convertTreeNode(MenuDO menu);
AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);
/**
* 将菜单列表构建成菜单树
@ -41,17 +48,20 @@ public interface AuthConvert {
* @param menuList 菜单列表
* @return 菜单树
*/
default List<AuthMenuRespVO> buildMenuTree(List<MenuDO> menuList) {
default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList) {
// 移除按钮
menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
// 排序保证菜单的有序性
menuList.sort(Comparator.comparing(MenuDO::getSort));
// 构建菜单树
// 使用 LinkedHashMap 的原因是为了排序 实际也可以用 Stream API 就是太丑了
Map<Long, AuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
// 处理父子关系
treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
// 获得父节点
AuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
if (parentNode == null) {
LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
childNode.getId(), childNode.getParentId());

View File

@ -14,14 +14,14 @@ import java.util.List;
// TODO 芋艿@TenantDS
public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
@Repository
class BatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> {
}
default List<RoleMenuDO> selectListByRoleId(Long roleId) {
return selectList(RoleMenuDO::getRoleId, roleId);
}
default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {
return selectList(RoleMenuDO::getRoleId, roleIds);
}
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
delete(new LambdaQueryWrapper<RoleMenuDO>()
.eq(RoleMenuDO::getRoleId, roleId)

View File

@ -106,4 +106,12 @@ public interface MenuService {
*/
MenuDO getMenu(Long id);
/**
* 获得菜单数组
*
* @param ids 菜单编号数组
* @return 菜单数组
*/
List<MenuDO> getMenuList(Collection<Long> ids);
}

View File

@ -212,6 +212,11 @@ public class MenuServiceImpl implements MenuService {
return menuMapper.selectById(id);
}
@Override
public List<MenuDO> getMenuList(Collection<Long> ids) {
return menuMapper.selectBatchIds(ids);
}
/**
* 校验父菜单是否合法
*

View File

@ -5,9 +5,12 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import org.springframework.lang.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static java.util.Collections.singleton;
/**
* 权限 Service 接口
*
@ -50,7 +53,17 @@ public interface PermissionService {
* @param roleId 角色编号
* @return 菜单编号集合
*/
Set<Long> getRoleMenuIds(Long roleId);
default Set<Long> getRoleMenuListByRoleId(Long roleId) {
return getRoleMenuListByRoleId(singleton(roleId));
}
/**
* 获得角色们拥有的菜单编号集合
*
* @param roleIds 角色编号数组
* @return 菜单编号集合
*/
Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
/**
* 获得拥有多个角色的用户编号集合
@ -58,7 +71,7 @@ public interface PermissionService {
* @param roleIds 角色编号集合
* @return 用户编号集合
*/
Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds);
/**
* 设置角色菜单

View File

@ -186,21 +186,20 @@ public class PermissionServiceImpl implements PermissionService {
}
@Override
public Set<Long> getRoleMenuIds(Long roleId) {
public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
// 如果是管理员的情况下获取全部菜单编号
if (roleService.hasAnySuperAdmin(Collections.singleton(roleId))) {
if (roleService.hasAnySuperAdmin(roleIds)) {
return convertSet(menuService.getMenuList(), MenuDO::getId);
}
// 如果是非管理员的情况下获得拥有的菜单编号
return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
// 获得角色拥有菜单编号
Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId),
RoleMenuDO::getMenuId);
Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
// 计算新增和删除的菜单编号
Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds);
Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
@ -234,7 +233,7 @@ public class PermissionServiceImpl implements PermissionService {
}
@Override
public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
return convertSet(userRoleMapper.selectListByRoleIds(roleIds),
UserRoleDO::getUserId);
}

View File

@ -168,7 +168,7 @@ public class TenantServiceImpl implements TenantService {
return;
}
// 如果是其他角色则去掉超过套餐的权限
Set<Long> roleMenuIds = permissionService.getRoleMenuIds(role.getId());
Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());
roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
permissionService.assignRoleMenu(role.getId(), roleMenuIds);
log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);

View File

@ -166,7 +166,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
when(menuService.getMenuList()).thenReturn(menuList);
// 调用
Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
// 断言
assertEquals(singleton(1L), menuIds);
}
@ -182,7 +182,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
roleMenuMapper.insert(roleMenu02);
// 调用
Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
// 断言
assertEquals(asSet(1L, 2L), menuIds);
}
@ -260,7 +260,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
userRoleMapper.insert(roleMenuDO02);
// 调用
Set<Long> result = permissionService.getUserRoleIdListByRoleIds(roleIds);
Set<Long> result = permissionService.getUserRoleIdListByRoleId(roleIds);
// 断言
assertEquals(asSet(1L, 2L), result);
}

View File

@ -199,7 +199,7 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
role101.setTenantId(dbTenant.getId());
when(roleService.getRoleListByStatus(isNull())).thenReturn(asList(role100, role101));
// mock 每个角色的权限
when(permissionService.getRoleMenuIds(eq(101L))).thenReturn(asSet(201L, 202L));
when(permissionService.getRoleMenuListByRoleId(eq(101L))).thenReturn(asSet(201L, 202L));
// 调用
tenantService.updateTenant(reqVO);

View File

@ -1,9 +0,0 @@
import request from '@/utils/request'
// 获取路由
export const getRouters = () => {
return request({
url: '/system/auth/list-menus',
method: 'get'
})
}

View File

@ -25,9 +25,10 @@ router.beforeEach((to, from, next) => {
// 获取字典数据 add by 芋艿
store.dispatch('dict/loadDictDatas')
// 判断当前用户是否已拉取完 user_info 信息
store.dispatch('GetInfo').then(() => {
store.dispatch('GetInfo').then(userInfo => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 触发 GenerateRoutes 事件时,将 menus 菜单树传递进去
store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
// 根据 roles 权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成

View File

@ -1,5 +1,4 @@
import {constantRoutes} from '@/router'
import {getRouters} from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView';
import {toCamelCase} from "@/utils";
@ -27,22 +26,25 @@ const permission = {
},
},
actions: {
// 生成路由
GenerateRoutes({commit}) {
/**
* 生成路由
*
* @param commit commit 函数
* @param menus 路由参数
*/
GenerateRoutes({commit}, menus) {
return new Promise(resolve => {
// 向后端请求路由数据(菜单)
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data)) // 【重要】用于菜单中的数据
const rdata = JSON.parse(JSON.stringify(res.data)) // 用于最后添加到 Router 中的数据
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
rewriteRoutes.push({path: '*', redirect: '/404', hidden: true})
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
})
// 将 menus 菜单,转换为 route 路由数组
const sdata = JSON.parse(JSON.stringify(menus)) // 【重要】用于菜单中的数据
const rdata = JSON.parse(JSON.stringify(menus)) // 用于最后添加到 Router 中的数据
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
rewriteRoutes.push({path: '*', redirect: '/404', hidden: true})
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
})
}
}