视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制
2020-11-27 21:51:08 责编:小采
文档


思路 :

动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用Vuex保存,通过  router.addRoutes  动态挂载到  router  上,按钮级别的权限控制,则需使用自定义指令去实现。

实现:

导航守卫代码:

router.beforeEach((to, from, next) => {
 NProgress.start() // start progress bar
 to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
 if (getStore('ACCESS_TOKEN')) {
 /* has token */
 if (to.path === '/user/login') {
 next({ path: '/other/list/user-list' })
 NProgress.done()
 } else {
 if (store.getters.roles.length === 0) {
 store
 .dispatch('GetInfo')
 .then(res => {
 const username = res.principal.username
 store.dispatch('GenerateRoutes', { username }).then(() => {
 // 根据roles生成可访问的路由表
 // 动态添加可访问路由表
 router.addRoutes(store.getters.addRouters)
 const redirect = decodeURIComponent(from.query.redirect || to.path)
 if (to.path === redirect) {
 // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
 next({ ...to, replace: true })
 } else {
 // 跳转到目的路由
 next({ path: redirect })
 }
 })
 })
 .catch(() => {
 notification.error({
 message: '错误',
 description: '请求用户信息失败,请重试'
 })
 store.dispatch('Logout').then(() => {
 next({ path: '/user/login', query: { redirect: to.fullPath } })
 })
 })
 } else {
 next()
 }
 }
 } else {
 if (whiteList.includes(to.name)) {
 // 在免登录白名单,直接进入
 next()
 } else {
 next({ path: '/user/login', query: { redirect: to.fullPath } })
 NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
 }
 }
})

Vuex保存routers

const permission = {
 state: {
 routers: constantRouterMap,
 addRouters: []
 },
 mutations: {
 SET_ROUTERS: (state, routers) => {
 state.addRouters = routers
 state.routers = constantRouterMap.concat(routers)
 }
 },
 actions: {
 GenerateRoutes ({ commit }, data) {
 return new Promise(resolve => {
 generatorDynamicRouter(data).then(routers => {
 commit('SET_ROUTERS', routers)
 resolve()
 })
 })
 }
 }
}

路由工具,访问后端接口获得菜单树,然后对菜单树进行处理,把菜单树的组件字符串进行转换为前端的组件如:

userlist: () => import('@/views/other/UserList'),这样生成的路由就是我们所要的了。

import { axios } from '@/utils/request'
import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
// 前端路由表
const constantRouterComponents = {
 // 基础页面 layout 必须引入
 BasicLayout: BasicLayout,
 BlankLayout: BlankLayout,
 RouteView: RouteView,
 PageView: PageView,
 // 需要动态引入的页面组件
 analysis: () => import('@/views/dashboard/Analysis'),
 workplace: () => import('@/views/dashboard/Workplace'),
 monitor: () => import('@/views/dashboard/Monitor'),
 userlist: () => import('@/views/other/UserList')
 // ...more
}
// 前端未找到页面路由(固定不用改)
const notFoundRouter = {
 path: '*', redirect: '/404', hidden: true
}
/**
 * 获取后端路由信息的 axios API
 * @returns {Promise}
 */
export const getRouterByUser = (parameter) => {
 return axios({
 url: '/menu/' + parameter.username,
 method: 'get'
 })
}
/**
 * 获取路由菜单信息
 *
 * 1. 调用 getRouterByUser() 访问后端接口获得路由结构数组
 * 2. 调用
 * @returns {Promise<any>}
 */
export const generatorDynamicRouter = (data) => {
 return new Promise((resolve, reject) => {
 // ajax
 getRouterByUser(data).then(res => {
 // const result = res.result
 const routers = generator(res)
 routers.push(notFoundRouter)
 resolve(routers)
 }).catch(err => {
 reject(err)
 })
 })
}
/**
 * 格式化 后端 结构信息并递归生成层级路由表
 *
 * @param routerMap
 * @param parent
 * @returns {*}
 */
export const generator = (routerMap, parent) => {
 return routerMap.map(item => {
 const currentRouter = {
 // 路由地址 动态拼接生成如 /dashboard/workplace
 path: `${item && item.path || ''}`,
 // 路由名称,建议唯一
 name: item.name || item.key || '',
 // 该路由对应页面的 组件
 component: constantRouterComponents[item.component || item.key],
 // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
 meta: { title: item.name, icon: item.icon || undefined, permission: item.key && [ item.key ] || null }
 }
 // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
 currentRouter.path = currentRouter.path.replace('//', '/')
 // 重定向
 item.redirect && (currentRouter.redirect = item.redirect)
 // 是否有子菜单,并递归处理
 if (item.children && item.children.length > 0) {
 // Recursion
 currentRouter.children = generator(item.children, currentRouter)
 }
 return currentRouter
 })
}

后端菜单树生成工具类

/**
 * 构造菜单树工具类
 * @author dang
 *
 */
public class TreeUtil {

 protected TreeUtil() {

 }

 private final static Long TOP_NODE_ID = (long) 1;
 /**
 * 构造前端路由
 * @param routes
 * @return
 */
 public static ArrayList<MenuEntity> buildVueRouter(List<MenuEntity> routes) {
 if (routes == null) {
 return null;
 }
 List<MenuEntity> topRoutes = new ArrayList<>();
 routes.forEach(route -> {
 Long parentId = route.getParentId();
 if (TOP_NODE_ID.equals(parentId)) {
 topRoutes.add(route);
 return;
 }
 for (MenuEntity parent : routes) {
 Long id = parent.getId();
 if (id != null && id.equals(parentId)) {
 if (parent.getChildren() == null) {
 parent.initChildren();
 }
 parent.getChildren().add(route);
 return;
 }
 }
 });

 ArrayList<MenuEntity> list = new ArrayList<>();
 MenuEntity root = new MenuEntity();
 root.setName("首页");
 root.setComponent("BasicLayout");
 root.setPath("/");
 root.setRedirect("/other/list/user-list");
 root.setChildren(topRoutes);
 list.add(root);
 return list;
 }
}

菜单实体 (使用了lombok插件)

/**
 * 菜单实体
 * @author dang
 *
 */
public class MenuEntity extends CoreEntity {
 private static final long serialVersionUID = 1L;
 @TableField("FParentId")
 private Long parentId;
 @TableField("FNumber")
 private String number;
 @TableField("FName")
 private String name;
 @TableField("FPerms")
 private String perms;
 @TableField("FType")
 private int type;
 @TableField("FLongNumber")
 private String longNumber;
 @TableField("FPath")
 private String path;
 @TableField("FComponent")
 private String component;
 @TableField("FRedirect")
 private String redirect;
 @TableField(exist = false)
 private List<MenuEntity> children;
 @TableField(exist = false)
 private MenuMeta meta;
 @TableField(exist = false)
 private List<PermissionEntity> permissionList;
 @Override
 public int hashCode() {
 return number.hashCode();
 }
 @Override
 public boolean equals(Object obj) {
 return super.equals(obj(obj);
 }
 public void initChildren() {
 this.children = new ArrayList<>();
 }
}

路由菜单是根据用户的角色去获得的,一个用户具有多个角色,一个角色具有多个菜单

思路:

说下按钮权限控制的实现:前端vue主要用自定义指令实现控制按钮的显示与隐藏,后端我用的是SpringSecurity框架,所以使用的是 @PreAuthorize注解, 在菜单实体的 perms属性记录权限的标识,如:sys:user:add,记录有权限标识的菜单其 parentId 应为上级菜单,然后获取用户的perms集合,在用户登录的时候传给前端并用Vuex保存,在自定义指令中去比较用户是否含有按钮所需要的权限。

实现:

获取用户信息的时候,把权限存到Vuex中   commit('SET_PERMISSIONS', result.authorities)

 // 获取用户信息
 GetInfo ({ commit }) {
 return new Promise((resolve, reject) => {
 getInfo().then(response => {
 const result = response
 if (result.authorities) {
 commit('SET_PERMISSIONS', result.authorities)
 commit('SET_ROLES', result.principal.roles)
 commit('SET_INFO', result)
 } else {
 reject(new Error('getInfo: roles must be a non-null array !'))
 }
 commit('SET_NAME', { name: result.principal.displayName, welcome: welcome() })
 commit('SET_AVATAR', result.principal.avatar)
 resolve(response)
 }).catch(error => {
 reject(error)
 })
 })
 }

前端自定义指令

// 定义一些和权限有关的 Vue指令
// 必须包含列出的所有权限,元素才显示
export const hasPermission = {
 install (Vue) {
 Vue.directive('hasPermission', {
 bind (el, binding, vnode) {
 const permissions = vnode.context.$store.state.user.permissions
 const per = []
 for (const v of permissions) {
 per.push(v.authority)
 }
 const value = binding.value
 let flag = true
 for (const v of value) {
 if (!per.includes(v)) {
 flag = false
 }
 }
 if (!flag) {
 if (!el.parentNode) {
 el.style.display = 'none'
 } else {
 el.parentNode.removeChild(el)
 }
 }
 }
 })
 }
}
// 当不包含列出的权限时,渲染该元素
export const hasNoPermission = {
 install (Vue) {
 Vue.directive('hasNoPermission', {
 bind (el, binding, vnode) {
 const permissions = vnode.context.$store.state.user.permissions
 const per = []
 for (const v of permissions) {
 per.push(v.authority)
 }
 const value = binding.value
 let flag = true
 for (const v of value) {
 if (per.includes(v)) {
 flag = false
 }
 }
 if (!flag) {
 if (!el.parentNode) {
 el.style.display = 'none'
 } else {
 el.parentNode.removeChild(el)
 }
 }
 }
 })
 }
}
// 只要包含列出的任意一个权限,元素就会显示
export const hasAnyPermission = {
 install (Vue) {
 Vue.directive('hasAnyPermission', {
 bind (el, binding, vnode) {
 const permissions = vnode.context.$store.state.user.permissions
 const per = []
 for (const v of permissions) {
 per.push(v.authority)
 }
 const value = binding.value
 let flag = false
 for (const v of value) {
 if (per.includes(v)) {
 flag = true
 }
 }
 if (!flag) {
 if (!el.parentNode) {
 el.style.display = 'none'
 } else {
 el.parentNode.removeChild(el)
 }
 }
 }
 })
 }
}
// 必须包含列出的所有角色,元素才显示
export const hasRole = {
 install (Vue) {
 Vue.directive('hasRole', {
 bind (el, binding, vnode) {
 const permissions = vnode.context.$store.state.user.roles
 const per = []
 for (const v of permissions) {
 per.push(v.authority)
 }
 const value = binding.value
 let flag = true
 for (const v of value) {
 if (!per.includes(v)) {
 flag = false
 }
 }
 if (!flag) {
 if (!el.parentNode) {
 el.style.display = 'none'
 } else {
 el.parentNode.removeChild(el)
 }
 }
 }
 })
 }
}
// 只要包含列出的任意一个角色,元素就会显示
export const hasAnyRole = {
 install (Vue) {
 Vue.directive('hasAnyRole', {
 bind (el, binding, vnode) {
 const permissions = vnode.context.$store.state.user.roles
 const per = []
 for (const v of permissions) {
 per.push(v.authority)
 }
 const value = binding.value
 let flag = false
 for (const v of value) {
 if (per.includes(v)) {
 flag = true
 }
 }
 if (!flag) {
 if (!el.parentNode) {
 el.style.display = 'none'
 } else {
 el.parentNode.removeChild(el)
 }
 }
 }
 })
 }
}

在main.js中引入自定义指令

import Vue from 'vue'
import { hasPermission, hasNoPermission, hasAnyPermission, hasRole, hasAnyRole } from './utils/permissionDirect'

Vue.use(hasPermission)
Vue.use(hasNoPermission)
Vue.use(hasAnyPermission)
Vue.use(hasRole)
Vue.use(hasAnyRole)

这样就可以在按钮中使用自定义指令,没有权限时,按钮自动隐藏,使用Postman工具测试也会拒绝访问

 <a-button type="primary" @click="handleAddUser()" v-hasPermission="['sys:user:add']" icon="plus"

总结

以上所述是小编给大家介绍的Vue 动态路由的实现以及 Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制Springsecurity 按钮级别的权限控制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

下载本文
显示全文
专题