欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-28 20:54:34
思路 : 动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用vuex保...

思路 :

动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用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 按钮级别的权限控制,希望对大家有所帮助