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获取菜单列表,把所有菜单查询出来并且筛选出父子菜单以及排序后,构建成前端需要的菜单树。

    /**
     * 构建权限菜单树
     * @param req
     * @return com.lyx.autoperm.utils.R
     * @author 黎勇炫
     * @create 2022/6/21
     * @email 1677685900@qq.com
     */
    @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<Permission> permissions = permissionService.queryPermissionsByRoles(id);
        // 构建菜单树
        List<MenuVO> menus = permissionService.buildMenus(permissions);

        return R.ok().setData(menus);
    }

查询权限列表

    <select id="queryPermissionsDetail" resultType="com.lyx.autoperm.entity.Permission">
        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
            <where>
                ur.role_id = r.id
                <if test="id != null and id !=''">and ur.user_id = #{id}</if>
            </where>)
    </select>

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

    /**
     * 根据用户查询所有的权限菜单详情
     * @param id
     * @return java.util.Set<com.lyx.autoperm.entity.Permission>
     * @author 黎勇炫
     * @create 2022/6/13
     * @email 1677685900@qq.com
     */
    @Override
    public List<Permission> queryPermissionsByRoles(String id) {
        // 查询权限列表
        List<Permission> permissions = permissionMapper.queryPermissionsDetail(id);
        // 遍历权限列表,筛选出一级权限
        List<Permission> 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;
    }

    /**
     *
     * @param permissions 权限列表
     * @param l1 父权限
     * @return java.util.List<com.lyx.autoperm.entity.Permission>
     * @author 黎勇炫
     * @create 2022/6/25
     * @email 1677685900@qq.com
     */
    private List<Permission> findChildren(List<Permission> permissions, Permission l1) {

        List<Permission> 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;
    }

构建菜单树

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

    /**
     * 构建前端菜单树
     *
     * @param permissions
     * @return java.util.List<com.lyx.autoperm.entity.vo.MenuVO>
     * @author 黎勇炫
     * @create 2022/6/20
     * @email 1677685900@qq.com
     */
    @Override
    public List<MenuVO> buildMenus(List<Permission> permissions) {
        List<MenuVO> menus = new LinkedList<MenuVO>();
        // 遍历权限列表,构建菜单
        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<Permission> 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<MenuVO> childrenList = new ArrayList<MenuVO>();
                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 {
    /* has no token*/
    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实现动态权限(二)

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot+SpringSecurity+Vue实现动态路由的过程如下: 1. 在后端(SpringBoot)中,首先需要定义一个权限表,用于存储所有的权限信息,包括权限名称、权限标识等。 2. 在前端(Vue)中,需要定义一个路由表,用于存储所有的路由信息,包括路由路径、组件名称等。 3. 后端需要提供一个接口,用于获取当前用户的权限列表。该接口会根据用户的角色查询对应的权限,并返回给前端。 4. 前端在登录成功后,会调用后端接口获取当前用户的权限列表,并将权限列表存储到本地(如localStorage或vuex)中。 5. 前端在路由跳转时,会根据当前用户的权限列表动态生成路由。可以通过遍历权限列表,根据权限标识匹配路由表中的路由信息,将匹配到的路由添加到路由表中。 6. 前端在生成路由后,需要使用Vue Router的addRoutes方法将动态生成的路由添加到路由表中。 7. 前端在路由跳转时,会根据用户的权限判断是否有权限访问该路由。可以通过导航守卫的beforeEach方法,在路由跳转前进行权限判断。 8. 后端可以使用Spring Security的注解对接口进行权限控制。可以通过在接口上添加注解,指定需要的权限才能访问该接口。 9. 后端在接口调用时,可以通过从redis中获取当前用户的权限列表,并进行权限判断。 10. 前端和后端通过接口交互,实现动态路由的权限控制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值