思路:

动态路由实现:在导航守卫中判断用户是否有用户信息,通过调用接口,拿到后台根据用户角色生成的菜单树,格式化菜单树结构信息并递归生成层级路由表并使用Vuex保存,通过 router.addRoutes 动态挂载到 router 上,按钮级别的权限控制,则需使用自定义指令去实现。

实现:

导航守卫代码:

router.beforeEach((to, from, next) => {NProgress.start() // start progress barto.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.usernamestore.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 recordnext({ ...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 = routersstate.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) => {// ajaxgetRouterByUser(data).then(res => {// const result = res.resultconst 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/workplacepath: `${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) {// RecursioncurrentRouter.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;@Overridepublic int hashCode() {return number.hashCode();}@Overridepublic 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 = responseif (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.permissionsconst per = []for (const v of permissions) {per.push(v.authority)}const value = binding.valuelet flag = truefor (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.permissionsconst per = []for (const v of permissions) {per.push(v.authority)}const value = binding.valuelet flag = truefor (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.permissionsconst per = []for (const v of permissions) {per.push(v.authority)}const value = binding.valuelet flag = falsefor (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.rolesconst per = []for (const v of permissions) {per.push(v.authority)}const value = binding.valuelet flag = truefor (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.rolesconst per = []for (const v of permissions) {per.push(v.authority)}const value = binding.valuelet flag = falsefor (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">新建</a-button>

后端方法级别权限控制

@PreAuthorize注解使用需要在SpringSecurity的配置类里添加@EnableGlobalMethodSecurity(prePostEnabled = true)注解,开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认,这样就可以使用@PreAuthorize去控制访问方法的权限了

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter

控制层使用方法如下:

@GetMapping
@PreAuthorize("hasAuthority('sys:user:view')")
public Map<String, Object> listUser(QueryRequest queryRequest, UserEntity userEntity) {return getDataTable(userServiceImpl.findUserDetail(userEntity, queryRequest));
}

权限获取

把权限放在UserDetail的authorities属性中,登录后跟着用户信息传到前端

private Collection<? extends GrantedAuthority> getUserAuthorities(Long uId) {// 用户权限列表,根据用户拥有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口对比,决定是否可以调用接口Set<String> permissions = menuServiceImpl.findUserPermissions(uId).stream().map(MenuEntity::getPerms).collect(Collectors.toSet());Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(permissions.toArray(new String[0]));return authorities;}

在UserDetailsService中实现loadUserByUsername方法并设置authorities

@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserEntity u=userServiceImpl.getOne(new QueryWrapper<UserEntity>().eq("FUserName",username));if(u!=null) {//设置用户角色和权限List<RoleEntity> roles= (List<RoleEntity>) roleServiceImpl.listByIds((userRoleServiceImpl.list(new QueryWrapper<UserRoleEntity>().eq("FUserId",u.getId()))).stream().map(UserRoleEntity::getRoleId).collect(Collectors.toList()));u.setRoles(roles);Collection<? extends GrantedAuthority> authorities = getUserAuthorities(u.getId());u.setAuthorities(authorities);return u;}else {throw new AuthenticationCredentialsNotFoundException("当前用户不存在");}}

转载于:https://www.cnblogs.com/dang-/p/11460698.html

Vue 动态路由的实现以及 Springsecurity 按钮级别的权限控制相关推荐

  1. vue动态路由添加,vue-router的addRoute方法实现权限控制,添加根路由和子路由

    addRoute 路由分为静态路由和动态路由 静态路由和动态路由的优缺点 动态路由实现思路: 动态路由遇到的问题与解决方式 动态添加子路由 路由分为静态路由和动态路由 静态路由和动态路由的优缺点 1. ...

  2. Java代码生成器codeMan重磅更新——添加权限管理模块生成功能,实现动态菜单和按钮级别的权限控制

    前言 转眼又过去了两个多月,时间就像一个隐形的精灵,总是在不经意间从眼皮底下溜走,不知不觉已经8月底了,金九银十近在眼前,不知道小伙伴们有没有跳槽的打算呢?相信此时你的心中已经有了自己的答案.闲言少叙 ...

  3. ant design pro vue 动态路由 流程详解

    ant design pro vue 动态路由 流程详解 前言 流程图 流程1 src/permission.js 流程2 src/store/modules/user.js 流程3 src/perm ...

  4. Vue动态路由的前端实现

    1. 需求描述 最近开发的一个项目,涉及到这样一个需求:随着项目的不断推进,后台管理功能逐渐增多,与此同时,静态路由表也逐渐扩大,需要把静态路由方式转换为动态路由方式.要完成这样一个转换,有几个技术要 ...

  5. Vue动态路由传参和监听路由

    Vue动态路由传参 query传参 params传参 //定义Detail路由 {path: '/detail/:id',name: 'Detail'component: () => impor ...

  6. [vue] vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?

    [vue] vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做? 可以通过指令去做 Vue.directive('hasPermission', { bind(el, binding, vno ...

  7. SpringSecurity系列——其他的权限控制,基于access表达式的权限控制day6-2(源于官网5.7.2版本)

    SpringSecurity系列--其他的权限控制,基于access表达式的权限控制day6-2(源于官网5.7.2版本) 常见权限控制总表 基于access表达式的权限控制 实例1:改写hasRol ...

  8. 【VUE】Vue动态路由匹配路由

    [VUE] vue-router 动态路由匹配 我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件.例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染.那 ...

  9. 第6.1.3 vue动态路由初探

    从老A / AG-Admin这个里面,老A用的是 vue-element-admin的后台框架 我看到的动态路由的写法,但是我自己亲自尝试,连一个登陆页面也出不来. 在Vue2项目构建心得,我也看到了 ...

最新文章

  1. js基础篇——localStorage使用要点
  2. python搭建简单http文件服务器
  3. boost::test模块测试功能模板测试用例
  4. What's blocking my lock? 转载
  5. 164. Maximum Gap
  6. android 控件方向旋转90度,android – 旋转视图层次90度
  7. 【Java】Java 集合 可视化 在线演示 环境
  8. Visual Studio Code(VS Code)与Git Source Control集成
  9. 使用JQuery做一组复选框的功能。
  10. python列表求斐波那契数列_python3 求斐波那契数列(Fibonacci sequence)
  11. 计数排序(count sort)
  12. python 进位_Python中常见的数制转换的说明
  13. Memcached内存分配机制
  14. ASP.NET 备忘
  15. 屏幕颜色拾取器 (VC++)
  16. StorageManager
  17. 中国 各大银行bankCode 开户行代码和名称 高清银行图标
  18. 一款GaN HEMT内匹配功率放大器设计过程详解
  19. java切换浏览器_java – 如何切换到新的浏览器窗口,点击按钮后打开?
  20. 怎么精细化管理客户关系?企业微信CRM系统可以做到吗

热门文章

  1. 大型分布式C++框架《四:netio之请求包中转站 上》
  2. nuxt.js 本地开发跨域问题(Access-Control-Allow-Origin)及其解决方案
  3. 项目经理之项目经理的基本特征
  4. 使用树莓派和kali Linux打造便携式渗透套件
  5. HDOJ 2546饭卡(01背包问题)
  6. 【Android】【转】查看内存
  7. 物联网、云计算商用或从“家庭自动化”得以突破
  8. 《碟中谍4:幽灵协议》蓝光1080P 720P首发!!汤姆克鲁斯主演
  9. potential things for recommendation
  10. hs300 quant