路由与权限控制设计方法详解(前端动态路由实战)

AI 爆款文章创作器的前端路由,按”静态骨架 + 动态权限路由”两层设计:登录、注册、404、403 这些入口常驻静态路由表,业务页面由后端按用户角色下发路由配置,前端登录后用 addRoute 逐个注入。权限判定收敛在一个全局 beforeEach 守卫里,先查登录态、再查角色与路由 meta.permission,两级不通过分别跳登录页和 403 页。这套方案解决了创作器实际遇到的两个问题:不同角色看到的生成工作台菜单不同,以及页面刷新后动态路由丢失导致的”刷新变白屏”。

一、路由表结构:静态与动态的分界线

路由表拆成两份,分界线是”是否依赖登录态”。

设计方式 权限粒度 路由体积 刷新恢复成本 适用场景
全量静态路由 + 前端角色判断 粗,靠守卫拦截 全部加载 低 小后台、角色少
后端动态下发全部路由 细,按用户定制 按需注入 高,需处理持久化 大型多租户系统
静态骨架 + 业务路由动态下发 中,菜单与页面分离 骨架常驻 + 按需 中,靠重新拉取 本文项目

创作器选了第三种。静态表只有四类:/login、/register、/403、/:pathMatch(.*)* 兜底 404。业务页面(生成工作台、文章管理、模板库、系统设置)全部走动态注入,后端根据登录用户返回菜单树,前端把菜单树转成 RouteRecordRaw。

二、动态路由注入流程

2.1 后端下发菜单的约定

后端返回的菜单节点带 path、name、component(组件标识字符串,如 generation/index)、meta(标题、图标、权限码)。前端不直接信任这些字符串拼组件,而是维护一张”组件标识 → 动态 import 映射表”,防止任意字符串注入加载到不该加载的模块。

// router/componentMap.js
const viewMap = {
  'generation/index': () => import('@/views/generation/index.vue'),
  'generation/history': () => import('@/views/generation/history.vue'),
  'article/manage': () => import('@/views/article/manage.vue'),
  'sys/users': () => import('@/views/sys/users.vue'),
}

export function mapToRoutes(menu) {
  return menu.map((node) => ({
    path: node.path,
    name: node.name,
    component: viewMap[node.component] || Layout,
    meta: { title: node.title, permission: node.permission },
    children: node.children?.map(mapToRoutes),
  }))
}

2.2 注入时机与刷新恢复

动态路由的注入放在登录流程里,登录成功拿到 token 后立即执行。完整顺序:

  1. 登录接口返回 token,前端存入 Pinia 的 userStore;
  2. 携带 token 请求 /api/user/permissions,拿到角色码列表和菜单树;
  3. 菜单树经 mapToRoutes 转成路由记录,router.addRoute 逐条注入;
  4. 注入完成后,用 router.replace(to.fullPath) 重新进入目标页,避免”路由已注入但守卫拿到的是旧路由表”;
  5. 页面刷新时 token 从持久化恢复,但动态路由是内存态、必然丢失,守卫里检测”有 token 但无路由”就重新执行第 2-4 步。

刷新恢复是踩过坑的地方:早期只在登录时拉取菜单,刷新后直接白屏,因为路由表空了。现在的守卫里加了一层判断——userStore.menus 为空且 token 存在,先重新拉菜单再放行,刷新行为才稳定。

三、路由守卫的鉴权链路

守卫是唯一入口,所有导航都过这一道。核心逻辑如下:

// router/index.js
router.beforeEach(async (to) => {
  const userStore = useUserStore()
  // 1. 白名单直接放行
  if (to.meta.public) return true
  // 2. 未登录跳登录页,并记录回跳地址
  if (!userStore.token) {
    return { path: '/login', query: { redirect: to.fullPath } }
  }
  // 3. 已登录但动态路由未注入,先恢复再重进
  if (!userStore.routesInjected) {
    await userStore.fetchMenus()
    await injectRoutes(userStore.menus)
    userStore.routesInjected = true
    return { ...to, replace: true }
  }
  // 4. 页面级权限校验
  if (to.meta.permission && !userStore.hasPermission(to.meta.permission)) {
    return { path: '/403' }
  }
  return true
})

3.1 两级权限的分工

第一级是登录态,管”能不能进系统”;第二级是 meta.permission,管”能进哪个页面”。创作器里 generation/index 的 permission 是 article:generate,普通读者角色只有 article:view,访问生成工作台会被 403 拦截。这两级必须分开写,混在一起会导致”登录用户访问所有页面都放行”的越权问题。

3.2 按钮级权限

路由守卫只能控制页面入口,页面内部的按钮(重新生成、删除文章、邀请成员)还要再控一层。项目封装了 v-permission 指令:元素 v-permission="'article:delete'",指令的 mounted 钩子里查 hasPermission,无权就移除该元素。路由级 + 按钮级配合,前端权限才闭环。

// directives/permission.js
app.directive('permission', {
  mounted(el, binding) {
    const { hasPermission } = usePermission()
    if (!hasPermission(binding.value)) {
      el.parentNode?.removeChild(el)
    }
  },
})

四、路由元信息与标题

每个动态路由的 meta.title 用在两处:菜单渲染和页面标题。守卫 afterEach 里读取 to.meta.title 拼接站点名写入 document.title,保证 AI 搜索抓取到每页独立标题。创作器还有一个细节——meta 里带 keepAlive: true 的页面(生成工作台)用 <KeepAlive> 包裹,切换阶段不重建组件,流式输出的滚动位置能保留。

五、404 与非法访问的兜底

动态路由注入前用户输入一个合法但未注入的路径,会落到兜底的 /:pathMatch(.*)* 显示 404。这里要区分两种情况:未登录访问任意路径,守卫先拦到登录页;已登录但路径真的不存在,才展示 404。路由表变化后(比如菜单更新),router.replace 重进目标路径前先 router.removeRoute 清理旧路由,避免重复注册同名路由报错。

六、前端权限的边界:真正的防线在后端网关

前端权限控制解决的是体验问题,不是安全问题。动态路由和按钮指令都能被绕过——绕过 UI 直接发请求就能访问接口。创作器前端所有请求走同一个 axios 实例,请求拦截器统一附加 token;后端是企业级 AI 网关(Spring Boot + Spring AI + SSE 对话网关),网关层校验 JWT 与权限码,未授权返回 401/403,前端响应拦截器收到 401 时清空登录态并跳登录页。三层叠加后:菜单不显示、按钮不出现、接口进不去,任一层被绕过都有下一层兜底。SSE 对话接口同样走网关鉴权,未带 token 的流式请求在握手阶段就被拒绝,不会浪费生成算力。

常见问题(FAQ)

Q1:动态路由刷新后丢失怎么恢复?

守卫里判断有 token 但路由未注入,先重新拉菜单再放行,用 replace 重进目标页。

Q2:路由权限和按钮权限有什么区别?

路由权限管页面入口,按钮权限管页面内操作,两层都要做,缺一不可。

Q3:后端下发的 component 字符串安全吗?

不安全,必须映射到本地组件表,杜绝用任意字符串拼动态 import。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
赵其鑫的头像赵其鑫管理团队

相关推荐

返回顶部