Skip to content
On this page

路由权限方案

codingmore-admin-web

https://github.com/itwanger/codingmore-admin-web

"vue": "^2.5.2"

1 登录后获取token

image-20230503031851980

2 获取菜单列表

image-20230503031959408

请求拦截

httpRequest.interceptors.request.use(
  config => {
    if (getToken()) {
      // 设置请求头token
      config.headers["Authorization"] = getToken();
    }
})

返回数据

image-20230503032155377

json
{
    "code": 0,
    "message": "操作成功",
    "result": {
        "userDetail": {
            "usersId": 14,
            "userLogin": "admin",
            "userPass": null,
            "userNicename": "沉默王二的狗腿子",
            "userEmail": "",
            "userUrl": "https://cdn.tobebetterjavaer.com/codingmore/images/2022-05-15/9c6cb39d-6f5a-4e4a-92f7-9fe46b19a64c.png",
            "userRegistered": "2022-05-15 11:06:29",
            "userActivationKey": null,
            "userStatus": 0,
            "displayName": "",
            "userType": 0,
            "openId": null,
            "attribute": null
        },
        "roles": [
            "体验者"
        ],
        "icon": "",
        "menus": [
            {
                "menuId": 3,
                "parentId": 0,
                "createTime": "2022-04-03 10:34:03",
                "title": "内容管理",
                "level": 0,
                "sort": 1,
                "name": "content-management",
                "icon": "el-icon-s-help",
                "hidden": 0
            },
            {
                "menuId": 9,
                "parentId": 3,
                "createTime": "2022-04-03 22:29:02",
                "title": "文章列表",
                "level": 1,
                "sort": 0,
                "name": "article-management",
                "icon": "iconpark-list-two",
                "hidden": 0
            },
            {
                "menuId": 21,
                "parentId": 3,
                "createTime": "2022-04-20 00:57:35",
                "title": "标签管理",
                "level": 1,
                "sort": 0,
                "name": "tag-management",
                "icon": "iconfont-tag-management",
                "hidden": 0
            }
        ],
        "username": "admin"
    }
}

未使用addRoute

export default new Router({
  routes: [...pageRouters, ...systemRouters]
  // routes: systemRouters
})

权限控制思路

javascript
router.beforeEach(async (to, from, next) => {
  console.log('router.beforeEach参数:', 'to=', to, 'from=', from)
  console.log('router=', router)
  // 开启进度条
  NProgress.start()

  // 判断是否系统中支持跳转的路由
  if (!to.name) {
    next({
      name: 'error-page404',
      replace: true
    })
  }

  /*
   * 权限控制思路:
   * 1.判断是否有token
   * 2.没有token直接跳转登录页
   * 3.有token查看是否已经初始化用户权限,如果没有,则异步调用接口获得用户信息和权限信息
   * 4.判断用户是否有业务菜单权限,如果没有,直接跳转到没有任何权限页面
   * 5.判断特殊路由情况,login和刷新页面情况,两者都跳转到第一个有权限的路由名称即可
   * 6.判断当前要跳转的路由名称是否名称在用户允许访问的路由名称列表内
   * 7.不在可访问列表内,跳转无权限页面
   * 8.在可访问列表内,直接next()跳转目标路由
   */

  const tokenValue = getToken()
  if (!tokenValue && to.name !== 'login') {
    // 关闭进度条
    NProgress.done()
    console.log('next:::0')
    next({
      name: 'login',
      replace: true
    })
  }

  if (tokenValue && !store.state.userInfo) {
    // 通过路由跳转动态请求并渲染可用菜单
    await initSysPower()
  }

  // 核心:根据目标路径 查找权限 命中放行
  console.log('to.name=', to.name)
  const sysFind = systemRouters.find(x => x.name === to.name)
  const businessFind = pageRouters.find(x => {
    if (x.name === to.name) {
      console.log('返回x', x)
      return x
    }
    if (x.children) {
      const child = x.children.find(y => y.name === to.name)
      if (child) {
        console.log('返回x的child', child)
        return child
      }
    }
    return null
  })
  // 有toekn有用户信息
  if (tokenValue && store.state.userInfo) {
    console.log('store.state.userMenus=', store.state.userMenus)
    if (store.state.userMenus.length == 0 && to.name != 'error-no-any-power') {
      console.log('next:::1')
      NProgress.done()
      next({
        name: 'error-no-any-power',
        replace: true
      })
    }
    const powerFind = store.state.userMenus ? store.state.userMenus.find(x => x === to.name) : null
    // 业务路由匹配,权限没匹配,跳转无权限页面
    if (store.state.userMenus.length > 0 && businessFind && !powerFind && from.path !== '/') {
      console.log('next:::2, businessFind=', businessFind)
      NProgress.done()
      next({
        name: 'error-no-power'
      })
    }
    // 当有token还要跳转login的时候,或者直接跳转 / 的时候,跳转第一个有权限的菜单
    if (store.state.userMenus.length > 0 && !powerFind && (to.path === '/' || to.name === 'login' || from.path === '/')) {
      let targetName = store.state.userMenus[0]
      console.log('next:::3 targetName=', targetName)
      NProgress.done()
      next({
        name: targetName,
        replace: true
      })
    }
  }

  // 所有路由都没匹配,跳转404
  if (!sysFind && !businessFind) {
    console.log('next:::4')
    NProgress.done()
    next({
      name: 'error-page404',
      replace: true
    })
  }

  // 其他所有情况,直接跳转
  console.log('next:::5')
  next()
})

router.afterEach((to, from) => {
  // 关闭进度条
  NProgress.done()
})

async function initSysPower() {
  return store.dispatch('refleshUserInfo').then(menus => {
    console.log('menus=', menus)
    if (menus.length > 0) {
      // 存储最终要添加到客户端的菜单变量
      let userMenus = menus.map(item => item.name)
      store.dispatch('setUserPowers', userMenus)
    }
  })
}

vben-admin

路由获取

登录,调用pinia的userStore的login

javascript
if (!permissionStore.isDynamicAddedRoute) {
    const routes = await permissionStore.buildRoutesAction();
    routes.forEach((route) => {
        router.addRoute(route as unknown as RouteRecordRaw);
    });
    router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
    permissionStore.setDynamicAddedRoute(true);
}
goHome && (await router.replace(userInfo?.homePath || PageEnum.BASE_HOME));

permissionStore.buildRoutesAction

javascript
let routes: AppRouteRecordRaw[] = [];

const roleList = toRaw(userStore.getRoleList) || [];
/**
 * 权限模式
 */
export enum PermissionModeEnum {
  // role
  // 角色权限
  ROLE = 'ROLE',
  // black
  // 后端
  BACK = 'BACK',
  // route mapping
  // 路由映射
  ROUTE_MAPPING = 'ROUTE_MAPPING',
}
// 根据不同模式生成routes  开发模式下走ROUTE_MAPPING
 switch (permissionMode) {
        // 角色权限
        case PermissionModeEnum.ROLE:
          // 对非一级路由进行过滤
          routes = filter(asyncRoutes, routeFilter);
          // 对一级路由根据角色权限过滤
          routes = routes.filter(routeFilter);
          // Convert multi-level routing to level 2 routing
          // 将多级路由转换为 2 级路由
          routes = flatMultiLevelRoutes(routes);
          break;

        // 路由映射, 默认进入该case
        case PermissionModeEnum.ROUTE_MAPPING:
          // 对非一级路由进行过滤
          routes = filter(asyncRoutes, routeFilter);
          // 对一级路由再次根据角色权限过滤
          routes = routes.filter(routeFilter);
          // 将路由转换成菜单
          const menuList = transformRouteToMenu(routes, true);
          // 移除掉 ignoreRoute: true 的路由 非一级路由
          routes = filter(routes, routeRemoveIgnoreFilter);
          // 移除掉 ignoreRoute: true 的路由 一级路由;
          routes = routes.filter(routeRemoveIgnoreFilter);
          // 对菜单进行排序
          menuList.sort((a, b) => {
            return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0);
          });

          // 设置菜单列表
          this.setFrontMenuList(menuList);

          // Convert multi-level routing to level 2 routing
          // 将多级路由转换为 2 级路由
          routes = flatMultiLevelRoutes(routes);
          break;

        //  If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below
        //  如果确定不需要做后台动态权限,请在下方注释整个判断
        case PermissionModeEnum.BACK:
          const { createMessage } = useMessage();

          createMessage.loading({
            content: t('sys.app.menuLoading'),
            duration: 1,
          });

          // !Simulate to obtain permission codes from the background,
          // 模拟从后台获取权限码,
          // this function may only need to be executed once, and the actual project can be put at the right time by itself
          // 这个功能可能只需要执行一次,实际项目可以自己放在合适的时间
          let routeList: AppRouteRecordRaw[] = [];
          try {
            await this.changePermissionCode();
            routeList = (await getMenuList()) as AppRouteRecordRaw[];
          } catch (error) {
            console.error(error);
          }

          // Dynamically introduce components
          // 动态引入组件
          routeList = transformObjToRoute(routeList);

          //  Background routing to menu structure
          //  后台路由到菜单结构
          const backMenuList = transformRouteToMenu(routeList);
          this.setBackMenuList(backMenuList);

          // remove meta.ignoreRoute item
          // 删除 meta.ignoreRoute 项
          routeList = filter(routeList, routeRemoveIgnoreFilter);
          routeList = routeList.filter(routeRemoveIgnoreFilter);

          routeList = flatMultiLevelRoutes(routeList);
          routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
          break;
      }

routes.push(ERROR_LOG_ROUTE);
patchHomeAffix(routes);
return routes;

路由校验

setupRouterGuard

刷新页面触发

export function setupRouterGuard(router: Router) {
  createPageGuard(router);
  createPageLoadingGuard(router);
  createHttpGuard(router);
  createScrollGuard(router);
  createMessageGuard(router);
  createProgressGuard(router);
  createPermissionGuard(router);
  createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.)
  createStateGuard(router);
}

// createPermissionGuard
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
      router.addRoute(route as unknown as RouteRecordRaw);
});
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);

缓存

手动实现pinia缓存

粤ICP备2024285819号