日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

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

發布時間:2025/3/16 vue 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vue 动态路由的实现以及 Springsecurity 按钮级别的权限控制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

思路

動態路由實現:在導航守衛中判斷用戶是否有用戶信息,通過調用接口,拿到后臺根據用戶角色生成的菜單樹,格式化菜單樹結構信息并遞歸生成層級路由表并使用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 按钮级别的权限控制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。