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(Listroutes){
if(routes==null){
returnnull;
}
ListtopRoutes=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;
}
}
});
ArrayListlist=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)
privateListpermissionList;
@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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。