栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot+SpringSecurity+Vue实现动态权限(三)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot+SpringSecurity+Vue实现动态权限(三)

前言

在上一篇完成了Security登录认证和授权过滤器的编写,后端的登录等功能已经实现。这一篇整合前端Vue实现动态菜单功能。前端Vue项目使用脚手架vue-admin-template

搭建前端
# 克隆项目
git clone https://github.com/PanJiaChen/vue-admin-template.git

# 进入项目目录
cd vue-admin-template

# 安装依赖
npm install

# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org

# 启动服务
npm run dev

安装完成后在router.js文件中删除脚手架原来的路由信息,只留下一些基本的路由。

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: '主页', icon: 'dashboard' }
    }]
  },
  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]
修改登录逻辑

我们需要将原来的登录逻辑和登录接口url的地址修改。首先是在vue.config.js文件中加上代理,使前端项目能够访问到我们的后端接口。
接着找到api/user.js文件中的loginf方法将路径改为我们后台的登录接口,比如我在Security中设置的路径就是**/autoperm/user/login**,请求登录接口后,登录校验成功后台会返回一个token,我们要在邓秋完成后将返回的token存储起来,并且在之后的每次请求都携带。


在request.js文件中修改请求拦截器,每次请求前都在请求头中带上token。

测试登录功能

输入错误密码

输入正确密码成功进入主页

构建菜单列表

后端在请求头中拿到token后获取id,再根据用户id获取菜单列表,把所有菜单查询出来并且筛选出父子菜单以及排序后,构建成前端需要的菜单树。

    
    @GetMapping("/createRouter")
    public R getRouter(HttpServletRequest req){
        // 获取用户id
        String id = JwtUtils.getMemberIdByJwtToken(req);
        if(StringUtils.isEmpty(id)){
            throw new UserException(UserCodeEnum.TOKEN_NOT_FOUND);
        }
        // 根据角色查询所有的菜单
        List permissions = permissionService.queryPermissionsByRoles(id);
        // 构建菜单树
        List menus = permissionService.buildMenus(permissions);

        return R.ok().setData(menus);
    }
查询权限列表
    
        select DISTINCT p.* from L_PERMISSION p,L_ROLE_PERMISSION rp where rp.permission_id = p.id and rp.role_id
            in
        (select r.id from L_USER_ROLE ur,L_ROLE r
            
                ur.role_id = r.id
                and ur.user_id = #{id}
            )
    

业务层使用java8的stream流快速处理权限集合,只用十几行代码就能筛选出子菜单列表并排序。

    
    @Override
    public List queryPermissionsByRoles(String id) {
        // 查询权限列表
        List permissions = permissionMapper.queryPermissionsDetail(id);
        // 遍历权限列表,筛选出一级权限
        List perm = permissions.stream().filter(item -> {
            return item.getParentId() == 0;
        }).map(l1 -> {
            l1.setChildren(findChildren(permissions, l1));
            return l1;
        }).sorted((Comparator.comparingInt(o -> (o.getSort() == null ? 0 : o.getSort())))).collect(Collectors.toList());

        return perm;
    }

    
    private List findChildren(List permissions, Permission l1) {

        List children = permissions.stream().filter(perm -> {
            return perm.getParentId().toString().equals(l1.getId().toString());
        }).sorted((Comparator.comparingInt(o -> (o.getSort() == null ? 0 : o.getSort())))).collect(Collectors.toList());

        return children;
    }
构建菜单树

拿到正确的菜单列表后,将菜单列表构建成前端需要的菜单树的结构。

    
    @Override
    public List buildMenus(List permissions) {
        List menus = new LinkedList();
        // 遍历权限列表,构建菜单
        for (Permission item : permissions) {
            MenuVO menu = new MenuVO();
            menu.setHidden(false);
            menu.setPath(item.getPath());
            menu.setComponent(buildComponent(item));item.getType().toString().equals(MenuType.MENU.getCode().toString());
            menu.setMeta(new MetaVO(item.getPermName(), item.getIcon()));
            List cMenus = item.getChildren();
            // 如果有子菜单
            if (!CollectionUtils.isEmpty(cMenus) && cMenus.size() > 0 )
            {
                menu.setAlwaysShow(true);
                menu.setRedirect("noRedirect");
                // 递归调用构建子菜单
                menu.setChildren(buildMenus(cMenus));
            }
            else if (item.getParentId().equals(0))
            {
                menu.setMeta(null);
                List childrenList = new ArrayList();
                MenuVO children = new MenuVO();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                menu.setComponent(ComponentConstant.LAYOUT);
                children.setName(StringUtils.capitalize(menu.getPath()));
                children.setMeta(new MetaVO(item.getPermName(), item.getIcon()));
                childrenList.add(children);
                menu.setChildren(childrenList);
            }
            menus.add(menu);
        }

        return menus;
    }
前端设置路由

在permission.js的router.beforeEach中拿到菜单树后,调用router.addRoutes方法添加路由

router.beforeEach(async(to, from, next) => {

  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // get user info
          await store.dispatch('user/getInfo').then(()=>{
            // 发起请求,构建路由和菜单
            store.dispatch('permission/createRoutes').then(menus=>{
              router.addRoutes(menus) // 动态添加可访问路由表
              next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
            })
          })
          next()
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

src/store/modules/permission.js

import { constantRoutes } from '@/router'
import { getRouters } from '@/api/perm'
import Layout from '@/layout/index'

const state = {
    routes: [],
    addRoutes: [],
    defaultRoutes: [],
    topbarRouters: [],
    sidebarRouters: []
  }

const mutations = {
    SET_ROUTES: (state, routes) => {
      state.addRoutes = routes
      state.routes = constantRoutes.concat(routes)
    },
    SET_DEFAULT_ROUTES: (state, routes) => {
      state.defaultRoutes = constantRoutes.concat(routes)
    },
    SET_TOPBAR_ROUTES: (state, routes) => {
      // 顶部导航菜单默认添加统计报表栏指向首页
      const index = [{
        path: 'index',
        meta: { title: '统计报表', icon: 'dashboard'}
      }]
      state.topbarRouters = routes.concat(index);
    },
    SET_SIDEBAR_ROUTERS: (state, routes) => {
      state.sidebarRouters = routes
    },
  }

const actions= {
    // 生成路由
  createRoutes({ commit }) {
      return new Promise(resolve => {
        // 向后端请求路由数据
        getRouters().then(res => {
          console.log(res)
          const sdata = JSON.parse(JSON.stringify(res.data))
          const rdata = JSON.parse(JSON.stringify(res.data))
          const sidebarRoutes = filterAsyncRouter(sdata)
          const rewriteRoutes = filterAsyncRouter(rdata, false, true)
          rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
          commit('SET_ROUTES', rewriteRoutes)
          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
          commit('SET_TOPBAR_ROUTES', sidebarRoutes)
          resolve(rewriteRoutes)
        })
      })
    }
  }


// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
  return asyncRouterMap.filter(route => {
    if (type && route.children) {
      route.children = filterChildren(route.children)
    }
    if (route.component) {
      // Layout ParentView 组件特殊处理
      if (route.component === 'Layout') {
        route.component = Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else if (route.component === 'InnerLink') {
        route.component = InnerLink
      } else {
        console.log(route.component)
        route.component = loadView(route.component)
      }
    }
    if (route.children != null && route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children, route, type)
    } else {
      delete route['children']
      delete route['redirect']
    }
    return true
  })
}

function filterChildren(childrenMap, lastRouter = false) {
  var children = []
  childrenMap.forEach((el, index) => {
    if (el.children && el.children.length) {
      if (el.component === 'ParentView') {
        el.children.forEach(c => {
          c.path = el.path + '/' + c.path
          if (c.children && c.children.length) {
            children = children.concat(filterChildren(c.children, c))
            return
          }
          children.push(c)
        })
        return
      }
    }
    if (lastRouter) {
      el.path = lastRouter.path + '/' + el.path
    }
    children = children.concat(el)
  })
  return children
}

export const loadView = (view) => { // 路由懒加载
  return (resolve) => require([`@/views${view}`], resolve)
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}


src/layout/components/Sidebar/index.vue

测试动态菜单

数据库中菜单列表以及和角色对应的菜单,我所登录的角色编号是2.


登录成功后的菜单列表,菜单是根据数据库的信息动态生成的。

相关内容

SpringBoot+SpringSecurity+Vue实现动态权限(一)
SpringBoot+SpringSecurity+Vue实现动态权限(二)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/991833.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号