JavaScript

超轻量级php框架startmvc

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

更新时间:2020-09-11 00:24:02 作者:startmvc
思路:动态路由实现:在导航守卫中判断用户是否有用户信息,通过调用接口,拿到后台根

思路 :

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

vue 动态路由 Springsecurity 按钮级别 vue 权限控制