前言
在实现动态路由和权限控制之前,可先了解一下以下三个概念:
什么是动态路由?
现在的大部分项目都是前后端分离的,这就导致了之前后端SpringMVC做的路由跳转交给了前端来控制。动态路由就是前端根据后端返回的路由信息生成权限路由列表
什么是权限控制?
权限控制就是指在用户发起调用接口的请求后,判断该用户是否有相应的权限,然后选择放行或拦截
动态路由和权限控制有什么关系?
用户发起的请求都是从某个路由指向的的页面中发起的,所以权限和路由是一个多对一的关系,即一个路由下面会有多个权限。
例如列表页 /list
中有 查询
、编辑
两种操作,当用户A发起查询列表的请求时,后端就可以判断用户A是否拥有列表页 /list
中的查询权限。
往期链接
一、思路分析
实现的话主要分为分为两个部分:
- 后端生成根据用户的角色生成路由权限信息
- 前端根据返回的路由权限信息生成路由信息表
简账中后端返回的路由权限信息如下所示:
因为前端需要根据树形结构的数据来构建权限路由表,所以在后端需要将取到的列表数据转为树形结构返回给前端
tips:修改AntDesginPro中的路由守卫我真的时改了好久!
二、实现方案
数据表设计
路由权限表主要就是要包含两个信息:路由地址、权限字符
简账中的路由权限表设计如下所示(tb_menu):
后端生成路由权限信息
Controller层
@LoginRequired
@ApiOperation(value = "获取用户的角色及菜单")
@GetMapping("/roleMenus")
public Result<?> getRoleMenus() {
// 用户角色
List<RoleDO> roleDOS = roleService.getByUserId(LocalUserId.get());
// 用户菜单权限
List<MenuDO> menuDOS = menuService.getUserMenus(LocalUserId.get());
List<MenuBO> menuBOS = menuService.copyFromMenuDos(menuDOS);
List<MenuBO> tree = menuService.generatorMenuTree(menuBOS);
RoleMenuVO roleMenuVO = new RoleMenuVO(LocalUser.get(), roleDOS, tree);
return Result.success(roleMenuVO);
}
Service层中将列表转为Tree
@Override
public List<MenuBO> generatorMenuTree(List<MenuBO> boList) {
return generatorTree(boList);
}
private List<MenuBO> generatorTree(List<MenuBO> voList) {
List<MenuBO> tree = list2Tree(voList, null);
sortTree(tree);
return tree;
}
/**
* list转为树结构
*/
private List<MenuBO> list2Tree(List<MenuBO> list, Integer pId) {
List<MenuBO> tree = new ArrayList<>();
Iterator<MenuBO> it = list.iterator();
while (it.hasNext()) {
MenuBO m = it.next();
if (m.getParentId() == pId) {
tree.add(m);
// 已添加的元素删除掉
it.remove();
}
}
// 寻找子元素
tree.forEach(n -> n.setChildren(list2Tree(list, n.getId())));
return tree;
}
前端生成权限路由列表
生成动态路由的主要代码
注意:在这里权限的标识是放到路由的meta中
/**
* 动态生成菜单
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = (ret) => {
return new Promise((resolve, reject) => {
const menuNav = []
rootRouter.children = ret
menuNav.push(rootRouter)
const routers = generator(menuNav)
routers.push(notFoundRouter)
resolve(routers)
})
}
/**
* 格式化树形结构数据 生成 vue-router 层级路由表
*
* @param routerMap
* @param parent
* @returns {*}
*/
export const generator = (routerMap, parent) => {
const children = []
routerMap.forEach(item => {
// 如果为按钮,则终止此次循环
if (item.menuType !== 'F') {
// 如果为菜单,则找到孩子节点的所有权限,并组成数组
if (item.menuType === 'C' && item.children) {
const arr = findMenuPermissions(item.children)
if (arr.length > 0) {
item.permissionSign = arr
}
}
const path = item.outerChain ? `${item.path}` : `${parent && parent.path !== '/' && parent.path || ''}/${item.path === '/' ? '' : item.path}`
// 判断是否为目录
if (item.menuType === 'M') {
item.component = 'RouteView'
}
const currentRouter = {
// 动态拼接路由地址
path: path,
// 路由名称
name: item.menuName,
// 动态加载该路由对应页面的组件
component: item.outerChain ? undefined : (constantRouterComponents[item.component]) || (() => import(`@/views${parent && parent.path !== '/' && parent.path || ''}/${item.component}`)),
meta: {
title: item.menuTitle,
icon: item.iconName || undefined,
hiddenHeaderContent: false,
target: item.outerChain ? '_blank' : undefined,
permission: item.permissionSign
}
}
// 重定向
// item.redirect && (currentRouter.redirect = item.redirect)
// 如当前节点为home则修改父节点的redirect
if (currentRouter.path === '/home') {
parent.redirect = currentRouter.path
}
if (item.outerChain === 1) {
currentRouter.redirect = item.path
}
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
const t = generator(item.children, currentRouter)
// 如果没有孩子,则不赋值
if (t.length > 0) currentRouter.children = t
}
children.push(currentRouter)
}
})
return children
}
前端判断某个按钮是否有权限
/**
* Action 权限指令
* 指令用法:
* - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下:
* <i-button v-action:add >添加用户</a-button>
* <a-button v-action:delete>删除用户</a-button>
* <a v-action:edit @click="edit(record)">修改</a>
*
* - 当前用户没有权限时,组件上使用了该指令则会被隐藏
* - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可
*
* tips:此处已根据后台返回值做了修改
*/
const action = Vue.directive('action', {
inserted: function (el, binding, vnode) {
const actionName = binding.arg
// 当前用户菜单列表,含权限
const permissions = vnode.context.$route.meta.permission
let hasPermission = false
for (let i = 0; i < permissions.length; i++) {
if (permissions[i] && permissions[i] === actionName) {
hasPermission = true
break
}
}
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none')
}
}
})
结果
管理员用户的菜单路由如下所示:
普通用户的菜单路由如下所示:
测试
这里假设前端忘记通过权限字符来隐藏编辑的按钮了(正常情况下,如无编辑权限此按钮不会展示)
这里测试人员是没有新建用户的权限的
当点击确认添加用户后会提示权限不足,说明此功能正常
三、总结
感谢看到最后,非常荣幸能够帮助到你~❤❤❤❤