vue-router 控制路由权限的实现


Posted in Javascript onSeptember 24, 2020

注意:vue-router是无法完全控制前端路由权限。

1、实现思路

使用vue-router实例函数addRoutes动态添加路由规则,不多废话直接上思维导图:

vue-router 控制路由权限的实现

2、实现步骤

2.1、路由匹配判断

// src/router.js

import Vue from 'vue';
import Store from '@/store';
import Router from 'vue-router';
import Cookie from 'js-cookie';
const routers = new Router({
  base : "/test",
  // 定义默认路由比如登录、404、401等
  routes : [{
    path : "/404",
    // ...
  },{
    path : "/401",
    // ...
  }]
})
// ...省略部分代码
routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配路由
  if(isMatched){
  
  }else{
  
  }
})

通过vue-router前置守卫beforeEach中参数to来简单的实现匹配结果

2.2、登录访问控制

在实际开发中路由常常存在是否登录访问和是否需要登录访问的情况,于是可以通过token和路由配置meta信息中定义isAuth字段来区分。

// ...省略部分重复代码

const openRouters = [];
const authRouters = [{
  path : "order/list",
  // ...
  meta : {
    // 是否身份验证(至于默认定义false还是true由开发者自定义)
    isAuth : true
  }
}];

routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配路由
  let isLogin = Cookie.get("token") || null;
  let { isAuth } = (meta || {});
  if(isMatched){
    // 匹配到路由
    if(isAuth){
      // 需要登录访问
      if(isLogin){
        // 已登录访问
        next(); // 调用钩子函数
      }else{
        // 未登录访问
        next("/login"); // 跳转登录
      }
    }else{
      // 不需要登录访问
      next(); // 调用钩子函数
    }
  }else{
    // 未匹配到路由
    if(isLogin){
      // 已登录访问
      
    }else{
      // 未登录访问
      next("/login"); // 跳转登录
    }
  }
})

2.3、动态添加路由规则

实现动态添加路由规则只需要使用vue-router实例方法router.addRoutes(routes: Array) 。
那么问题来了,我们怎么才能获取到需要动态添加的路由规则呢?

2.4、构建路由规则匹配函数

假如后台获取到的路由权限列表是这样的:

[{
 resourceUrl : "/order/list",
 childMenu : ...
}]

为了对比用户权限和路由是否匹配我们需要提取出权限路由数组

// 简单的通过递归获取到了所有权限url
export function getAuthRouters(authMenu) {
  let authRouters = [];
  (authMenu || []).forEach((item) => {
    const { resourceUrl, childMenu } = item;
    resourceUrl && authRouters.push(resourceUrl);
    if (childMenu && childMenu.length > 0) {
      // 合并子级菜单
      authRouters = [...authRouters, ...getAuthRouters(childMenu)];
    }
  });
  return authRouters;
}

通过getAuthRouters函数获取到了所有用户路由权限,接下来是要怎么和vue-router路由匹配呢?

这要和(我这里使用的是RBAC模型)系统配置权限关联上。vue-router路由规则要和权限配置保持一致。所以通过递归动态拼接vue-router路由规则和用户拥有的路由权限做对比。如果匹配就保留该路由;然后得到一份过滤后的vue-router路由规则配置。最后通过实例方法addRoutes添加路由规则。具体实现代码如下:

// src/utils/index.js
const { pathToRegexp } = require('path-to-regexp');

export function createAuthRouters(authRouters) {
  const isAuthUrl = (url) => {
    return (authRouters || []).some((cUrl) => {
      return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString();
    });
  };
  return function createRouters(routers, upperPath) {
    let nRouters = [];
    (routers || []).forEach((item) => {
      const { children, path, name } = item;
      let isMatched = false,
        nItem = { ...item },
        fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'),
        nChildren = null;
      children && (nChildren = createRouters(children, fullPath));
      // 1.当前路由匹配
      if (isAuthUrl(fullPath)) {
        isMatched = true;
      }
      // 2.存在子路由匹配
      if (nChildren && nChildren.length > 0) {
        nItem.children = nChildren;
        isMatched = true;
      }
      // 特殊处理(不需要可以删除)
      if(name === "home"){
        isMatched = true;
      }
      // nItem
      isMatched && nRouters.push(nItem);
    });
    return nRouters;
  };
}

值得注意的是createAuthRouters方法通过变量isMatched控制是否保留,之所以通过变量来决定是因为嵌套路由中父路由可能无法匹配,但是子路由能匹配所以父路由规则也需要子路参与是否保留。比如:

// 路由规则
const routers = new Router({
  base : "/test",
  // 定义默认路由比如登录、404、401等
  routes : [{
    path : "/",
    ...
    children : [{
      path : "login",
      ...
    },{
      path : "about",
      ...
    },{
      path : "order",
      ...
      children : [{
        path : "id"
      }]
    }]
  }]
})

// 用户权限
["/order/id"]; // 在匹配的过程中 "/" 不等于 "/order/id" 、"/" 不等于 "/order" 但是子路由 "/order/id" == "/order/id" 所以不但要保留 path : "/",还得保留 path : "order" 嵌套层。

2.5、动态注册

// ...省略部分重复代码

const openRouters = [];
const authRouters = [{
  path : "order/list",
  // ...
  meta : {
    // 是否身份验证(至于默认定义false还是true由开发者自定义)
    isAuth : true
  }
}];

/* 动态注册路由 */
async function AddRoutes() {
  // 获取用户路由权限
  let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU);
  try {
    const { code, data } = res || {};
    if (code === '000') {
      let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base);
      // 注册路由
      routes.addRoutes([].concat(newAuthRoutes, openRouters));
      // 设置已注册
      Store.commit('UPDATE_IS_ADD_ROUTERS', true);
      // 保存菜单信息
      Store.commit('UPDATE_MENU_INFO', data);
    }
  } catch (error) {
    console.error('>>> AddRoutes() - error:', error);
  }
}

routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配路由
  let isLogin = Cookie.get("token") || null;
  let { isAuth } = (meta || {});
  if(isMatched){
    // 匹配到路由
    if(isAuth){
      // 需要登录访问
      if(isLogin){
        // 已登录访问
        next(); // 调用钩子函数
      }else{
        // 未登录访问
        next("/login"); // 跳转登录
      }
    }else{
      // 不需要登录访问
      next(); // 调用钩子函数
    }
  }else{
    // 未匹配到路由
    if(isLogin){
      // 已登录访问
      AddRoutes();
      next();
    }else{
      // 未登录访问
      next("/login"); // 跳转登录
    }
  }
})

2.6、归类整理

/* 路由前置 */
let { origin } = window.location || {};
routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配
  let isAuth = (meta || {}).isAuth; // 是否授权访问
  let { isAddRoutes } = Store.state; // 注册路由
  let isLogin = Cookie.get('token') || null; // 是否登录
  if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) {
    // next()
    // 1.匹配路由 && 未登录访问
    // 2.匹配路由 && 登录访问 && 登录
    next();
  } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) {
    // 登录
    // 1.匹配路由 && 登录访问 && 未登录
    // 2.未匹配路由 && 未登录
    next(`/login?r=${origin}/e-lottery${path}`);
  } else if (!isMatched && isLogin && isAddRoutes) {
    // 404
    // 1.未匹配路由 && 登录 && 动态注册路由
    next('/404');
  } else if (!isMatched && isLogin && !isAddRoutes) {
    // 注册路由
    // 1.未匹配路由 && 登录 && 未动态注册路由
    AddRoutes();
    next();
  }
});

嗯! 这下看起来舒服多了。

3、完整实现代码

// src/utils/index.js
const { pathToRegexp } = require('path-to-regexp');

export function getAuthRouters(authMenu) {
  let authRouters = [];
  (authMenu || []).forEach((item) => {
    const { resourceUrl, childMenu } = item;
    resourceUrl && authRouters.push(resourceUrl);
    if (childMenu && childMenu.length > 0) {
      // 合并子级菜单
      authRouters = [...authRouters, ...getAuthRouters(childMenu)];
    }
  });
  return authRouters;
}
/**
 *
 * @param { Array } authRouters
 */
export function createAuthRouters(authRouters) {
  const isAuthUrl = (url) => {
    return (authRouters || []).some((cUrl) => {
      return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString();
    });
  };
  return function createRouters(routers, upperPath) {
    let nRouters = [];
    (routers || []).forEach((item) => {
      const { children, path, name } = item;
      let isMatched = false,
        nItem = { ...item },
        fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'),
        nChildren = null;
      children && (nChildren = createRouters(children, fullPath));
      // 1.当前路由匹配
      if (isAuthUrl(fullPath)) {
        isMatched = true;
      }
      // 2.存在子路由匹配
      if (nChildren && nChildren.length > 0) {
        nItem.children = nChildren;
        isMatched = true;
      }
      // 特殊处理
      if(name === "home"){
        isMatched = true;
      }
      // nItem
      isMatched && nRouters.push(nItem);
    });
    return nRouters;
  };
}

 
// src/router.js

import Vue from 'vue';
import Store from '@/store';
import Router from 'vue-router';
import Cookie from 'js-cookie';

const openRouters = [];
const authRouters = [{
  path : "order/list",
  // ...
  meta : {
    // 是否身份验证(至于默认定义false还是true由开发者自定义)
    isAuth : true
  }
}];

/* 动态注册路由 */
async function AddRoutes() {
  // 获取用户路由权限
  let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU);
  try {
    const { code, data } = res || {};
    if (code === '000') {
      let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base);
      // 注册路由
      routes.addRoutes([].concat(newAuthRoutes, openRouters));
      // 设置已注册
      Store.commit('UPDATE_IS_ADD_ROUTERS', true);
      // 保存菜单信息
      Store.commit('UPDATE_MENU_INFO', data);
    }
  } catch (error) {
    console.error('>>> AddRoutes() - error:', error);
  }
}


/* 路由前置 */
let { origin } = window.location || {};
routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配
  let isAuth = (meta || {}).isAuth; // 是否授权访问
  let { isAddRoutes } = Store.state; // 注册路由
  let isLogin = Cookie.get('token') || null; // 是否登录
  if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) {
    // next()
    // 1.匹配路由 && 未登录访问
    // 2.匹配路由 && 登录访问 && 登录
    next();
  } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) {
    // 登录
    // 1.匹配路由 && 登录访问 && 未登录
    // 2.未匹配路由 && 未登录
    next(`/login?r=${origin}/e-lottery${path}`);
  } else if (!isMatched && isLogin && isAddRoutes) {
    // 404
    // 1.未匹配路由 && 登录 && 动态注册路由
    next('/404');
  } else if (!isMatched && isLogin && !isAddRoutes) {
    // 注册路由
    // 1.未匹配路由 && 登录 && 未动态注册路由
    AddRoutes();
    next();
  }
});

虽然前端能够通过vue-router实现对路由权限的控制,但是实际是伪权限控制,无法达到完全控制;强烈建议对于需要控制路由权限的系统采用后端控制。

到此这篇关于vue-router 控制路由权限的实现的文章就介绍到这了,更多相关vue-router 控制路由权限内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木! 

Javascript 相关文章推荐
经典的解除许多网站无法复制文字的绝招
Dec 31 Javascript
JavaScript关于select的相关操作说明
Jan 13 Javascript
复制小说文本时出现的随机乱码的去除方法
Sep 07 Javascript
js生成随机数的过程解析
Nov 24 Javascript
谈谈JavaScript的New关键字
Aug 26 Javascript
JS搜狐面试题分析
Dec 16 Javascript
jQuery获取单选按钮radio选中值与去除所有radio选中状态的方法
May 20 jQuery
Node.js+jade+mongodb+mongoose实现爬虫分离入库与生成静态文件的方法
Sep 20 Javascript
jquery使用iscorll实现上拉、下拉加载刷新
Oct 26 jQuery
Vue.js分页组件实现:diVuePagination的使用详解
Jan 10 Javascript
jquery在启动页面时,自动加载数据的实例
Jan 22 jQuery
vux-scroller实现移动端上拉加载功能过程解析
Oct 08 Javascript
vue+elementUI实现简单日历功能
Sep 24 #Javascript
JavaScript获取时区实现过程解析
Sep 24 #Javascript
小程序点餐界面添加购物车左右摆动动画
Sep 23 #Javascript
原生js实现购物车功能
Sep 23 #Javascript
详解微信小程序动画Animation执行过程
Sep 23 #Javascript
原生js实现分页效果
Sep 23 #Javascript
原生js实现购物车
Sep 23 #Javascript
You might like
全国FM电台频率大全 - 14 江西省
2020/03/11 无线电
使用zend studio for eclipse不能激活代码提示功能的解决办法
2009/10/11 PHP
PHP长连接实现与使用方法详解
2018/02/11 PHP
PHP结合Ffmpeg快速搭建流媒体服务的实践记录
2018/10/31 PHP
让whoops帮我们告别ThinkPHP6的异常页面
2020/03/02 PHP
PHP如何使用cURL实现Get和Post请求
2020/07/11 PHP
JavaScript 异步调用框架 (Part 2 - 用例设计)
2009/08/03 Javascript
Dreamweaver jQuery智能提示插件,支持版本提示,支持1.6api
2011/07/31 Javascript
JavaScript高级程序设计阅读笔记(六) ECMAScript中的运算符(二)
2012/02/27 Javascript
JavaScript 产生不重复的随机数三种实现思路
2012/12/13 Javascript
jQuery中对节点进行操作的相关介绍
2013/04/16 Javascript
JS实现鼠标经过好友列表中的好友头像时显示资料卡的效果
2014/07/02 Javascript
jQuery原型属性和原型方法详解
2015/07/07 Javascript
jQuery简单实现验证邮箱格式
2015/07/15 Javascript
理解javascript定时器中的单线程
2016/02/23 Javascript
举例说明JavaScript中的实例对象与原型对象
2016/03/11 Javascript
JS日程管理插件FullCalendar中文说明文档
2017/02/06 Javascript
jQuery插件FusionCharts绘制的3D双柱状图效果示例【附demo源码】
2017/04/20 jQuery
纯JavaScript实现实时反馈系统时间
2017/10/26 Javascript
react实现一个优雅的图片占位模块组件详解
2017/10/30 Javascript
一个基于react的图片裁剪组件示例
2018/04/18 Javascript
vue实现登录页面的验证码以及验证过程解析(面向新手)
2019/08/02 Javascript
使用layui实现树形结构的方法
2019/09/20 Javascript
layui--select使用以及下拉框实现键盘选择的例子
2019/09/24 Javascript
python3中int(整型)的使用教程
2017/03/23 Python
Python中with及contextlib的用法详解
2017/06/08 Python
Python内置函数reversed()用法分析
2018/03/20 Python
pycharm访问mysql数据库的方法步骤
2019/06/18 Python
解决Python正则表达式匹配反斜杠''\''问题
2019/07/17 Python
超实用的 30 段 Python 案例
2019/10/10 Python
有关Tensorflow梯度下降常用的优化方法分享
2020/02/04 Python
python os模块常用的29种方法使用详解
2020/06/02 Python
网上卖盒饭创业计划书
2014/01/26 职场文书
幼儿园教师获奖感言
2014/03/11 职场文书
怎样写观后感
2015/06/19 职场文书
移除Selenium中window.navigator.webdriver值
2022/06/10 Python