Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制
思路:
动态路由实现:在导航守卫中判断用户是否有用户信息,通过调用接口,拿到后台根据用户角色生成的菜单树,格式化菜单树结构信息并递归生成层级路由表并使用Vuex保存,通过 router.addRoutes 动态挂载到 router 上,按钮级别的权限控制,则需使用自定义指令去实现。
实现:
导航守卫代码:
router.beforeEach((to,from,next)=>{ NProgress.start()//startprogressbar to.meta&&(typeofto.meta.title!=='undefined'&&setDocumentTitle(`${to.meta.title}-${domTitle}`)) if(getStore('ACCESS_TOKEN')){ /*hastoken*/ 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=>{ constusername=res.principal.username store.dispatch('GenerateRoutes',{username}).then(()=>{ //根据roles生成可访问的路由表 //动态添加可访问路由表 router.addRoutes(store.getters.addRouters) constredirect=decodeURIComponent(from.query.redirect||to.path) if(to.path===redirect){ //hack方法确保addRoutes已完成,setthereplace:truesothenavigationwillnotleaveahistoryrecord 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()//ifcurrentpageisloginwillnottriggerafterEachhook,somanuallyhandleit } } })
Vuex保存routers
constpermission={ state:{ routers:constantRouterMap, addRouters:[] }, mutations:{ SET_ROUTERS:(state,routers)=>{ state.addRouters=routers state.routers=constantRouterMap.concat(routers) } }, actions:{ GenerateRoutes({commit},data){ returnnewPromise(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' //前端路由表 constconstantRouterComponents={ //基础页面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 } //前端未找到页面路由(固定不用改) constnotFoundRouter={ path:'*',redirect:'/404',hidden:true } /** *获取后端路由信息的axiosAPI *@returns{Promise} */ exportconstgetRouterByUser=(parameter)=>{ returnaxios({ url:'/menu/'+parameter.username, method:'get' }) } /** *获取路由菜单信息 * *1.调用getRouterByUser()访问后端接口获得路由结构数组 *2.调用 *@returns{Promise} */ exportconstgeneratorDynamicRouter=(data)=>{ returnnewPromise((resolve,reject)=>{ //ajax getRouterByUser(data).then(res=>{ //constresult=res.result constrouters=generator(res) routers.push(notFoundRouter) resolve(routers) }).catch(err=>{ reject(err) }) }) } /** *格式化后端结构信息并递归生成层级路由表 * *@paramrouterMap *@paramparent *@returns{*} */ exportconstgenerator=(routerMap,parent)=>{ returnrouterMap.map(item=>{ constcurrentRouter={ //路由地址动态拼接生成如/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) } returncurrentRouter }) }
后端菜单树生成工具类
/** *构造菜单树工具类 *@authordang * */ publicclassTreeUtil{ protectedTreeUtil(){ } privatefinalstaticLongTOP_NODE_ID=(long)1; /** *构造前端路由 *@paramroutes *@return */ publicstaticArrayListbuildVueRouter(List routes){ if(routes==null){ returnnull; } List topRoutes=newArrayList<>(); routes.forEach(route->{ LongparentId=route.getParentId(); if(TOP_NODE_ID.equals(parentId)){ topRoutes.add(route); return; } for(MenuEntityparent:routes){ Longid=parent.getId(); if(id!=null&&id.equals(parentId)){ if(parent.getChildren()==null){ parent.initChildren(); } parent.getChildren().add(route); return; } } }); ArrayList list=newArrayList<>(); MenuEntityroot=newMenuEntity(); root.setName("首页"); root.setComponent("BasicLayout"); root.setPath("/"); root.setRedirect("/other/list/user-list"); root.setChildren(topRoutes); list.add(root); returnlist; } }
菜单实体(使用了lombok插件)
/** *菜单实体 *@authordang * */ publicclassMenuEntityextendsCoreEntity{ privatestaticfinallongserialVersionUID=1L; @TableField("FParentId") privateLongparentId; @TableField("FNumber") privateStringnumber; @TableField("FName") privateStringname; @TableField("FPerms") privateStringperms; @TableField("FType") privateinttype; @TableField("FLongNumber") privateStringlongNumber; @TableField("FPath") privateStringpath; @TableField("FComponent") privateStringcomponent; @TableField("FRedirect") privateStringredirect; @TableField(exist=false) privateListchildren; @TableField(exist=false) privateMenuMetameta; @TableField(exist=false) privateList permissionList; @Override publicinthashCode(){ returnnumber.hashCode(); } @Override publicbooleanequals(Objectobj){ returnsuper.equals(obj(obj); } publicvoidinitChildren(){ this.children=newArrayList<>(); } }
路由菜单是根据用户的角色去获得的,一个用户具有多个角色,一个角色具有多个菜单
思路:
说下按钮权限控制的实现:前端vue主要用自定义指令实现控制按钮的显示与隐藏,后端我用的是SpringSecurity框架,所以使用的是 @PreAuthorize注解,在菜单实体的perms属性记录权限的标识,如:sys:user:add,记录有权限标识的菜单其parentId应为上级菜单,然后获取用户的perms集合,在用户登录的时候传给前端并用Vuex保存,在自定义指令中去比较用户是否含有按钮所需要的权限。
实现:
获取用户信息的时候,把权限存到Vuex中 commit('SET_PERMISSIONS',result.authorities)
//获取用户信息 GetInfo({commit}){ returnnewPromise((resolve,reject)=>{ getInfo().then(response=>{ constresult=response if(result.authorities){ commit('SET_PERMISSIONS',result.authorities) commit('SET_ROLES',result.principal.roles) commit('SET_INFO',result) }else{ reject(newError('getInfo:rolesmustbeanon-nullarray!')) } commit('SET_NAME',{name:result.principal.displayName,welcome:welcome()}) commit('SET_AVATAR',result.principal.avatar) resolve(response) }).catch(error=>{ reject(error) }) }) }
前端自定义指令
//定义一些和权限有关的Vue指令 //必须包含列出的所有权限,元素才显示 exportconsthasPermission={ install(Vue){ Vue.directive('hasPermission',{ bind(el,binding,vnode){ constpermissions=vnode.context.$store.state.user.permissions constper=[] for(constvofpermissions){ per.push(v.authority) } constvalue=binding.value letflag=true for(constvofvalue){ if(!per.includes(v)){ flag=false } } if(!flag){ if(!el.parentNode){ el.style.display='none' }else{ el.parentNode.removeChild(el) } } } }) } } //当不包含列出的权限时,渲染该元素 exportconsthasNoPermission={ install(Vue){ Vue.directive('hasNoPermission',{ bind(el,binding,vnode){ constpermissions=vnode.context.$store.state.user.permissions constper=[] for(constvofpermissions){ per.push(v.authority) } constvalue=binding.value letflag=true for(constvofvalue){ if(per.includes(v)){ flag=false } } if(!flag){ if(!el.parentNode){ el.style.display='none' }else{ el.parentNode.removeChild(el) } } } }) } } //只要包含列出的任意一个权限,元素就会显示 exportconsthasAnyPermission={ install(Vue){ Vue.directive('hasAnyPermission',{ bind(el,binding,vnode){ constpermissions=vnode.context.$store.state.user.permissions constper=[] for(constvofpermissions){ per.push(v.authority) } constvalue=binding.value letflag=false for(constvofvalue){ if(per.includes(v)){ flag=true } } if(!flag){ if(!el.parentNode){ el.style.display='none' }else{ el.parentNode.removeChild(el) } } } }) } } //必须包含列出的所有角色,元素才显示 exportconsthasRole={ install(Vue){ Vue.directive('hasRole',{ bind(el,binding,vnode){ constpermissions=vnode.context.$store.state.user.roles constper=[] for(constvofpermissions){ per.push(v.authority) } constvalue=binding.value letflag=true for(constvofvalue){ if(!per.includes(v)){ flag=false } } if(!flag){ if(!el.parentNode){ el.style.display='none' }else{ el.parentNode.removeChild(el) } } } }) } } //只要包含列出的任意一个角色,元素就会显示 exportconsthasAnyRole={ install(Vue){ Vue.directive('hasAnyRole',{ bind(el,binding,vnode){ constpermissions=vnode.context.$store.state.user.roles constper=[] for(constvofpermissions){ per.push(v.authority) } constvalue=binding.value letflag=false for(constvofvalue){ if(per.includes(v)){ flag=true } } if(!flag){ if(!el.parentNode){ el.style.display='none' }else{ el.parentNode.removeChild(el) } } } }) } }
在main.js中引入自定义指令
importVuefrom'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工具测试也会拒绝访问
总结
以上所述是小编给大家介绍的Vue动态路由的实现以及Vue动态路由的实现及Springsecurity按钮级别的权限控制Springsecurity按钮级别的权限控制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。