Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制


Posted in Javascript onSeptember 05, 2019

思路 :

动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用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 按钮级别的权限控制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
jquery 可排列的表实现代码
Nov 13 Javascript
JQuery 选择器、过滤器介绍
Feb 14 Javascript
JS、CSS加载中的小问题探讨
Nov 26 Javascript
Jquery实现自定义弹窗示例
Mar 12 Javascript
Jquery操作cookie记住用户名
Mar 29 Javascript
AngularJS入门教程之AngularJS 模板
Aug 18 Javascript
javascript中的try catch异常捕获机制用法分析
Dec 14 Javascript
jQuery表格(Table)基本操作实例分析
Mar 10 Javascript
微信小程序移动拖拽视图-movable-view实例详解
Aug 17 Javascript
解决vue 子组件修改父组件传来的props值报错问题
Nov 09 Javascript
vue-cli在 history模式下的配置详解
Nov 26 Javascript
浅谈javascript如何获取文件后缀名
Aug 07 Javascript
layui 对弹窗 form表单赋值的实现方法
Sep 04 #Javascript
layui-table对返回的数据进行转变显示的实例
Sep 04 #Javascript
layui table数据修改的回显方法
Sep 04 #Javascript
Vue实现商品详情页的评价列表功能
Sep 04 #Javascript
Layui 数据表格批量删除和多条件搜索的实例
Sep 04 #Javascript
解决layui表格的表头不滚动的问题
Sep 04 #Javascript
解决layui数据表格table的横向滚动条显示问题
Sep 04 #Javascript
You might like
php关于array_multisort多维数组排序的使用说明
2011/01/04 PHP
CI框架装载器Loader.php源码分析
2014/11/04 PHP
laravel通过创建自定义artisan make命令来新建类文件详解
2017/08/17 PHP
PHP实现数组向任意位置插入,删除,替换数据操作示例
2019/04/05 PHP
javascript最常用与实用的创建类的代码
2010/08/12 Javascript
方便实用的jQuery checkbox复选框全选功能简单实例
2013/10/09 Javascript
js中关于一个分号的崩溃示例
2013/11/11 Javascript
javascript如何判断输入的url是否正确
2014/04/11 Javascript
JavaScript加入收藏夹功能(兼容IE、firefox、chrome)
2014/05/05 Javascript
jQuery 动态云标签插件
2014/11/11 Javascript
node.js中的fs.fchownSync方法使用说明
2014/12/16 Javascript
jquery实现聚光灯效果的方法
2015/02/06 Javascript
举例讲解Node.js中的Writable对象
2015/07/29 Javascript
javascript针对cookie的基本操作实例详解
2015/11/30 Javascript
AngularJS入门教程之路由与多视图详解
2016/08/19 Javascript
javascript 网页进度条简单实例
2017/02/22 Javascript
AngularJS ionic手势事件的使用总结
2017/08/09 Javascript
ActiveX控件的使用-js实现打印超市小票功能代码详解
2017/11/22 Javascript
基于iScroll实现内容滚动效果
2018/03/21 Javascript
iview同时验证多个表单问题总结
2018/09/29 Javascript
ES7之Async/await的使用详解
2019/03/28 Javascript
js函数柯里化的方法和作用实例分析
2020/04/11 Javascript
微信小程序开发打开另一个小程序的实现方法
2020/05/17 Javascript
[40:50]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS LGD第四场
2014/05/24 DOTA
[00:37]DOTA2上海特级锦标赛 OG战队宣传片
2016/03/03 DOTA
机器学习python实战之手写数字识别
2017/11/01 Python
python实现图书管理系统
2018/03/12 Python
Python实现结构体代码实例
2020/02/10 Python
python uuid生成唯一id或str的最简单案例
2021/01/13 Python
欧尚俄罗斯网上超市:Auchan俄罗斯
2018/05/03 全球购物
加热夹克:RAVEAN
2018/10/19 全球购物
教师的实习鉴定
2013/12/15 职场文书
餐厅服务员岗位职责
2015/02/09 职场文书
2016入党积极分子党校培训心得体会
2016/01/06 职场文书
2016年班主任培训心得体会
2016/01/07 职场文书
《跨越海峡的生命桥》教学反思
2016/02/18 职场文书