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

思路 :

动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用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}

*/

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 buildVueRouter(List routes) {

if (routes == null) {

return null;

}

List 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 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 children;

@TableField(exist = false)

private MenuMeta meta;

@TableField(exist = false)

private List 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工具测试也会拒绝访问

总结

以上所述是小编给大家介绍的Vue 动态路由的实现以及 Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制Springsecurity 按钮级别的权限控制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值