Vue + mongodb 的后台权限分配

343 阅读3分钟

人嘛总会有很多借口去拖延一件事,我发现这种人真的是无敌的用那个词怎么讲?哦对就是神烦。你看看我一天天的距离上次更新博客很长时间了吧?一直在忙忙周一到周五不是在上班就是在上班路上哈哈哈,反正就是没有一天在闲着,然后就是嘛.....,hahhahh 就拖着 就那么硬拖,我不是也没找理由 哈哈哈哈,貌似上边已经找了,反正吧,爱咋咋吧你能把我咋着吧!不好意思,先容我跪好哈哈哈。还好我足智多谋,看了个电视剧写了个观后感,灵机一动直接更新到博客,不能只看技术不看精神世界是吧?嗯!可以的!理由都衔接的这么完美。话说最近记忆力不太好,睡不着觉的时候,我想数数羊吧,数着数着,突然一个小羊瞥了我一眼并且非常不屑的说:“你已经数过我一次了”,我天我真的是居然被一只羊嘲笑了。哎人生啊,真的是 “卧槽,无情!”。好了最近没有更新多说了一点还是言归正传吧,上回书说到使用mongodb + vue完成登陆,这次我们要来搞一下后台的页面权限控制。来吧展示。

  • 整体思路

    • 公共页面不需要用户登陆的路由肯定有一个静态路由表 constantRouterMap

    • 在前端肯定有一个带权限路由表 asyncRouterMap

    • 后端有一整套的动态路由表存储

    • 登陆时获取到用户的用户角色

    • 使用Vue Router 路由守卫进行是否有权限进入的或者说存在权限菜单项的判断

    • 发送用户角色到后台,返回可访问的路由相关信息,和前端存储的路由表进行匹配

    • 使用router.addRoutes 方法进行动态路由的添加

    • 思路图

  • 具体代码

    • Vue前端逻辑源码
      1. 路由守卫,守的就是底线

        //手动跳转的页面白名单
          const whiteList = [
            '/',
            '/home',
            '404'
          ];
          const constantRouterMap = [
            {
              path: "/home",
              name: "Home",
              component: Home,
            },
            {
              path: "/login",
              name: "Login",
              component: Login,
            }
          ];
          
          const router = new VueRouter({
            mode: "history",
            base: process.env.BASE_URL,
            routes:constantRouterMap
          });
          router.beforeEach(async (to, from, next) => {
            if (Cookie.getCookie('username')) { // 验证是否已经登陆过了
              if (to.path!=='/login') {
                if (store.state.menuList.length!==0) {
                  next();
                }else{
                  store.dispatch('getPermission').then(() => {
                    store.dispatch('getPermissionList').then((res)=>{
                      // .....进行路由合并
                      {  // 此处需特别注意置于最底部
                        path: "/404",
                        name: "notFound",
                        component: () => import('@/components/layout/404.vue')
                      },
                      {
                        path: "*", // 此处需特别注意置于最底部
                        redirect: "/404" //无匹配到的路径自动重定向到404页面
                      }
                      ]
                      router.addRoutes(accessRoutes) // 动态添加可访问路由表
                      next({ ...to, replace: true })
                    });
                  }).catch((err) => {
                    console.log(err);
                    router.replace('/login')
                  })
                }
              }else{
                alert('已经登陆过了');
                // 跳转默认的页面
              }
            } else {
              if (whiteList.indexOf(to.path) !== -1) {
                // 免登陆白名单 直接进入
                next();
              } else {
                if (to.path !== '/login') {
                  // 重定向到登录页面 不能这么写 因为假如之前的角色是 管理员页面 后又登陆了非管理员 重定向的页面就可能不存在,就会导致404
                  next(`/home?redirect=${to.path}`)
                  //next('/login');
                } else {
                  next();
                }
              }
            }
          })
        export default router;
        
      2. Vuex

        /**
           * 把后台返回的的路由和前端路由表中的路由进行
           * @param {*} routes 
           * @param {*} roles 
           */
          export function recursionRouter(userRouter, allRouter ) {
            var realRoutes = []
            allRouter.forEach((v) => {
                userRouter.forEach((item) => {
                    if (item.name === v.name) {
                        if (item.children && item.children.length > 0) {
                            v.children = recursionRouter(item.children, v.children)
                        }
                        realRoutes.push(v)
                    }
                })
            })
            return realRoutes
          }
          const token = JSON.parse(localStorage.getItem('USER_TOKEN')) || '' // 用户信息
          const userRoles = JSON.parse(localStorage.getItem('USER_ROLE'))|| '' // 用户角色
          export default new Vuex.Store({
            state: {
              token,
              userRoles,
              menuList:[]
            },
            strict:true, // 使用严格模式
            mutations: {
              setUserToken(state,token){
                //alert(token);
                state.token= token;
              },
              SaveLoginInfo(state,userRoles){
                state.userRoles = userRoles
              },
              setPermissList(state,data){
                state.menuList  = data;
              }
            },
            actions: {
              getPermission({commit,state}){
                return new Promise((resolve,reject)=>{
                  api.GetPermission({
                    userRole:state.userRoles
                  }).then(res=>{
                    commit('setPermissList',res.permission);
                    resolve(res.permission);
                  }).catch((err)=>{
                   reject(err)
                  })
                })
              },
              getPermissionList({ state }) {
                return new Promise((resolve) => {
                  let permissionList = []
                  permissionList = recursionRouter(state.menuList, asyncRouterMap);
                  resolve(permissionList)
                })
              }
          
            },
            modules: {}
          });
        
      3. 根据路由生成的菜单

        <template>
            <div>
              <el-aside width="201px"  height="100vh"
                class="app-side app-side-left"
                :class="isCollapse ? 'app-side-collapsed' : 'app-side-expanded'"
              >
                <div class="app-side-logo">
                  <img
                    src="@/assets/logo.png"
                    :width="isCollapse ? '60' : '60'"
                    height="60"
                  />
                </div>
                <div>
                  <!-- 我是样例菜单 -->
                  <el-menu style="height:calc(100vh - 62px);overflow-y:auto; min-width:201px"
                    :default-openeds= "['0','1']"
                    class="el-menu-vertical-demo"
                    :router="true"
                    :default-active="this.$route.path"
                    @open="handleOpen"
                    @close="handleClose"
                  >
                    <el-submenu  v-for="(item,index) in activeMenuList" :key="index" :index="index+''">
                        <template slot="title"  >
                          <i :class="item.type" ></i>
                          <span  slot="title">{{item.text}}</span>
                        </template>
                        <div v-for="(c,cindex) in item.children" :key="cindex" >
                            <el-menu-item :index="'/user/'+item.path+ '/'+ c.path" >{{c.text}}</el-menu-item>
                        </div>
                    </el-submenu>
                  </el-menu>
                </div>
              </el-aside>
            </div>
          </template>
          <script>
          //import asyncRouterMap from './../../router/asyncRouterMap'
          export default {
            data() {
              return {
                isCollapse: true,
                menuItemData:[],
                openList:[],
              };
            },
            methods: {
              handleOpen(key, keyPath) {
                console.log(key, keyPath);
              },
              handleClose(key, keyPath) {
                console.log(key, keyPath);
              }
            },
            mounted (){
              this.menuItemData = this.$store.state.menuList;
            },
            computed:{
              activeMenuList:function(){
                return this.menuItemData.filter(function(item){
                  return item.children.length&&item.children
                })
              }
            }
          };
          </script>
        
    • mongodb逻辑源码
      1. 主要是根据用户的角色返回对应角色的路由信息

        router.post('/getpermission',async function(ctx,next){
          let {userRole} = ctx.request.body;
          if (!userRole) return ctx.body = {code:4020,msg:'该用户没有任何的权限'};
          let args = {userRole:userRole};
          const userRouters  = await getRouters.query(args);
          //console.log(userRouters)
          ctx.body = (userRouters.code ===200) ? {code:200,msg:"获取权限成功",permission:userRouters.routerList} : userRouters
        })
        
      2. Models 模块下的父路由和子路由 (这里踩了一个坑,当时认为mongodb的表直接把父路由和子路由直接写在一个表里,这样造成了很复杂的循环嵌套,请教了当时网上的一个大神,当时不明白,后来还是问后端的朋友才理解。)

        const mongoose  =require('../db');
        const { model ,Schema} = require('mongoose');
        const PermissionSchema = new Schema({
          text:String,
          type:String,
          children:Array,
          userRoles:Array
        })
        const RouteModel  = model("parent_routers",PermissionSchema);
        module.exports = RouteModel
        
        const mongoose  =require('../db');
        const { model ,Schema} = require('mongoose');
        const PermissionSchema = new Schema({
          name:String,
          type:String,
          text:String,
          userRole:Array,
          parentRouterId:String
        })
        const childRouteModel  = model("children_routers",PermissionSchema);
        module.exports = childRouteModel
        
      3. controllers模块下的

        const PermissionModel  = require('./../models/routers');
        const PermissionChildModel =                 require('./../models/children_routers');
        class PermissionCtl {
          constructor(){
          }
          async query (obj) {  // 用户权限查询接口
            let { userRole } = obj ;
            if (!userRole) {
              return {code:403,msg:'用户权限丢失,请退出后重新登陆!'}
            }
            const routersDoc = await PermissionModel.find({userRoles:{$elemMatch:{$eq:userRole}}});
            const routersChildDoc = await PermissionChildModel.find({userRole:{$elemMatch:{$eq:userRole}}});
            routersDoc.forEach(element => {
              //console.log(typeof(element._id));
              routersChildDoc.forEach(item => {
                //console.log(typeof(item.parentRouterId));
                if (item.parentRouterId==element._id.toString()) {
                  element.children.push(item);
                }
              });
            });
            console.log(routersDoc);
            return !routersDoc ? {code:403 ,msg :'改用户没有任何权限'}:{code:200,routerList:routersDoc,}
          }
        }
        module.exports =  new PermissionCtl()
        
    • 效果图

    • 代码不全请访问github 获取全部源码

      Yuhior的GitHub地址欢迎点赞