一. 前言

本篇基于 有来商城 youlai-mall微服务项目,通过对vue-element-admin的权限菜单模块理解个性定制其后台接口,实现对vue-element-admin工程几乎不做改动的情况下,无缝接入后台接口实现动态权限菜单的加载。

在进行接下来的工作前,我们需要对原生的vue-element-admin项目改造,移除mock连通后台接口,具体可参考我这篇文章 vue-element-admin移除mock接入后台搭建youlai-mall管理平台,如果在过程中有遇到问题,欢迎下方留言。

二. 前端调整

至于上文提到的对vue-element-admin几乎不做改动便可实现我们此篇文章的目的是不是我在扯,决定权给各位,我把对vue-element-admin项目改动的地方通过比对工具比对截图放上来。

先声明vue-element-admin此次改动的地方除了一个获取权限菜单的接口之外,剩余的改动全在 src/store/modules/permission.js 文件中。

看到了吗,可以说仅对vue-element-admin做两处改动,再加上对getRoutes调用后台接口返回的菜单数据的分析,就可以理解和实现动态权限菜单的加载了。

改动代码片段 + 注释说明

 import {list as getRoutes} from '@/api/admin/menu'
import Layout from '@/layout' generateRoutes({commit}, roles) {
return new Promise(resolve => {
// 请求后台数据替换src/router/index.js的asyncRoutes异步路由
getRoutes({mode: 3}).then(response => {
// filterAsyncRoutes方法作权限过滤和数据转换,roles为登录用户角色ID集合,如:[1,2]
let accessedRoutes = filterAsyncRoutes(response.data, roles)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
})
} // 递归权限过滤和数据转换
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = {...route}
if (hasPermission(roles, tmp)) {
const component = tmp.component
if (route.component) {
if (component == 'Layout') {
tmp.component = Layout
} else {
// 接口组件字符串转换成组件对象
tmp.component = (resolve) => require([`@/views/${component}`], resolve)
}
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
}
res.push(tmp)
}
})
return res
}

三. 后端接口

1. 接口数据分析

接下来通过后台接口替换配置在src/router/index.js文件中asyncRoutes异步路由。

首先分析下异步路由的数据结构:

通过对上图异步路由的数据观察和了解,得出以下几点:

  1. path在子路由中前面没有反斜杠'/'
  2. 根菜单的alwaysShow为true
  3. component组件需通过import完成编译时导入,接口只能返回组件路径字符串,所以这里在接口请求完成后必须有一个组件路径字符串到组件对象的转换过程
  4. meta的roles属性对应的是有该路由访问权限角色唯一标识的集合,这里我使用的是角色ID

2. 接口实现

完成以上的数据分析,接下来就是接口的具体实现了,有关SQL和完整代码请点击 有来商城 youlai-mall 拉取即可,以下仅仅贴出关键代码和分析。

SysMenuMapper

获取菜单列表和对应访问权限的角色ID的集合

SysMenuServiceImpl

将菜单转换成路由,递归生成父子结构树

SysMenuController

REST对外提供接口

3. 接口测试

使用接口测试工具测试, http://localhost:9999/youlai-admin/menus?mode=3 ,这里通过网关方式访问,详情请知悉有来商城 youlai-mall项目。

完整返回数据,看看是不是很匹配asyncRoutes异步路由的数据格式了

{
"code": "00000",
"data": [{
"path": "/admin",
"component": "Layout",
"alwaysShow": true,
"name": "系统管理",
"meta": {
"title": "系统管理",
"icon": "documentation",
"roles": [2, 1]
},
"children": [{
"path": "user",
"component": "admin/user",
"alwaysShow": false,
"name": "用户管理",
"meta": {
"title": "用户管理",
"icon": "user",
"roles": [1]
}
}, {
"path": "role",
"component": "admin/role",
"alwaysShow": false,
"name": "角色管理",
"meta": {
"title": "角色管理",
"icon": "peoples",
"roles": [2, 1]
}
}, {
"path": "dept",
"component": "admin/dept",
"alwaysShow": false,
"name": "部门管理",
"meta": {
"title": "部门管理",
"icon": "tree",
"roles": [1, 2]
}
}, {
"path": "menu",
"component": "admin/menu",
"alwaysShow": false,
"name": "菜单管理",
"meta": {
"title": "菜单管理",
"icon": "tree-table",
"roles": [1, 2]
}
}, {
"path": "dict",
"component": "admin/dict",
"alwaysShow": false,
"name": "字典管理",
"meta": {
"title": "字典管理",
"icon": "education",
"roles": [1, 2]
}
}]
}],
"msg": "一切ok"
}

最后要做的就是将组件(component)路径字符串转换成组件对象即可,再次贴出在上文permission.js改造的代码:

 if (component == 'Layout') {
tmp.component = Layout
} else {
// 接口组件字符串转换成组件对象
tmp.component = (resolve) => require([`@/views/${component}`], resolve)
}

有一点需要注意的是上面组件动态导入不能使用import,如下:

 tmp.component = () => import(`@/views/${component}`)

不然会报以下错误

Error: Cannot find module '@/views/***'

原因是webpack不支持import导入了,但是之前确实是可以的,所以有些让人想不通,这里使用require动态导入组件即可。

4. 菜单权限测试

参看当前用户拥有的角色ID为2

用户管理要求只有角色ID为1才能有其访问权限

测试下用户登录控制台能否看到“用户管理”菜单

怎么样,只拥有角色ID为2的角色是看不到需要角色ID为1才能访问的“用户管理”菜单的,也就证明了我们成功通过接入后台接口实现了权限菜单的动态加载的目的。

四. 结语

其实有个问题值得去思考下的。为什么我们在从后台获取全部菜单列表的时候需要关联查询出有对应访问权限的角色ID的集合,而不是说动态传入当前登录用户的信息去查询该用户拥有的菜单集合呢?这其实是两种实现方式,区别在于第一种每个用户都要获取全部菜单数据,而第二种方式针对每个用户加载其拥有权限的菜单数据。单看的话第二种方式肯定优于第一种,但这里我们选用的是第一种加载全部菜单数据。如果你结合缓存去看的话自然就想的通了,所有用户共享缓存的同一份菜单数据,还是说缓存针对每个用户存放一份菜单缓存又或者用户每次都去请求数据库。后面计划把菜单数据存放到缓存Redis中然后做一个分析比较。

附完整代码:

有来商城后端:youlai-mall

有来商城前端:youlai-mall-admin-web

写了这么多其实也是想给自己的项目打个广告,更希望对大家有所帮助,对于技术人来说少走弯路真的很重要,喜欢的朋友给个star,对我来说这份帮助更是自己继续下去的动力,所以谢谢了。有啥问题下方留言,或直接联系我(微信号:haoxianrui)。

随机推荐

  1. DedeCMS全版本通杀SQL注入漏洞利用代码

    EXP: Exp:plus/recommend.php?action=&aid=1&_FILES[type][tmp_name]=\'   or mid=@`\'` /*!50000u ...

  2. 织梦dedecms自定义字段在首页列表页文章页的调用

      1.首页调用. {dede:arclist addfields='字段英文名' channelid='模型ID' row='条数' type='栏目ID'}       [field:字段英文名/ ...

  3. 在MySQL数据库建立多对多的数据表关系

    在数据库中,如果两个表的之间的关系为,多对多的关系,如:“学生表和课程表”,一个学生的可以选多门课,一门课也可以被多门学习选;根据数据库的设计原则,应当形成第三张关联表 步骤1:创建三张数据表Stud ...

  4. CSS 背景 background 讲解

    背景语法:background: background-color || background-image || background-repeat || background-attachment ...

  5. Struts2--Global Result全局结果集

    如果有很多action,有共同的result指向, 而且属于不同的包,那么可以继承上面的包, 然后写一个<global-results> 1. jsp显示文件: <ol> &l ...

  6. Phpcms 前台页面实现分页

    phpcms开发就是模仿里面原有的方法进行扩展,前台要实现分页,就去找后台页面的分页实现. 如后台 扩展->后台操作日志,就有分页展示. 1.先去添加自己的分页方法(千万不要在原来的方法上修改, ...

  7. mqtt异步publish方法

    Python基于mqtt异步编程主要用到asyncio及第三方库hbmqtt,这里主要介绍mqtt的异步发布及遇到的一些问题. hbmqtt安装很简单,pip hbmqtt install. mqtt ...

  8. Android SimpleAdapter ViewBinder

  9. python 基础总结1

    1.python简介特点:    是简单义学,有功能强大,高性能.面向对象,对动态输入的支持.解释性语言的本质,是大多数平台上理想的脚本语言. 简单,义学                    免费, ...

  10. iOS AOP框架Aspects实现原理

    总结: Aspects 是对 类的继承结构isa.mataclass结构的调整和维护:相当于链表的节点插入和删除: 同时使用method Swizzling 对方法统一重定向: 同时使用类似代理的机制 ...