如何在前端项目中设计一个灵活可扩展的权限管理机制

195 阅读2分钟

一、问题背景:权限系统做得好,项目少一半 bug

广告平台的典型权限场景:

  • 多角色(广告主、运营、审核员、管理员)
  • 页面权限 + 按钮权限 + 接口权限 + 数据权限
  • 权限频繁变更,要求灵活配置 + 实时生效

初期项目权限系统写得“够用”,后期越补越乱。

我在重构阶段主导设计了一套可配置、可插拔、可回溯的权限机制。


二、权限系统的核心结构设计

抽象三类权限:

interface PermissionData {
  menus: string[];       // 页面级权限
  actions: string[];     // 操作级(按钮、接口)
  scopes: string[];      // 数据作用域权限
}

后台返回如下结构:

{
  "menus": ["/ad-plan", "/material"],
  "actions": ["ad:create", "ad:delete"],
  "scopes": ["city:guangzhou"]
}

三、路由动态挂载逻辑

权限匹配 + 动态注册:

const allRoutes = [...]

const registerRoutesByPermission = (menus: string[]) => {
  const filtered = allRoutes.filter(r => menus.includes(r.path))
  filtered.forEach(r => router.addRoute(r))
}

四、按钮级权限控制(两种方式)

方法1:自定义指令 v-permission

app.directive('permission', {
  mounted(el, binding) {
    const actions = userStore.actions
    if (!actions.includes(binding.value)) {
      el.parentNode?.removeChild(el)
    }
  }
})

使用方式:

<n-button v-permission="'ad:delete'">删除</n-button>

方法2:封装组件 PermissionButton

<PermissionButton code="ad:create">新增计划</PermissionButton>

五、数据权限(作用域)逻辑

接口调用时注入作用域参数:

const getAdList = () => {
  return request.get('/api/ads', {
    params: {
      ...filters,
      city: userStore.scope.city
    }
  })
}

六、权限失效后的动态刷新

权限变化后,触发:

await userStore.fetchPermissions()
resetRoutes()
registerRoutesByPermission(userStore.menus)

七、权限配置管理策略

权限码统一维护在一处,导出使用:

export const PermissionCodes = {
  CREATE_AD: 'ad:create',
  DELETE_AD: 'ad:delete',
  VIEW_REPORT: 'report:view'
}

方便查找、配置、后端同步维护。


八、扩展能力设计

  • 插件式注入权限判断函数
export const hasPermission = (code: string) => {
  return userStore.actions.includes(code)
}
  • 对外暴露 API 判断逻辑,供组件自由调用
if (hasPermission('report:view')) showReportCard()

九、总结:权限系统的三个关键词

  • 灵活:菜单、组件、接口都能动态控制
  • 统一:所有权限点集中管理,命名规范
  • 可组合:与 router、组件、接口天然融合

权限系统是大前端工程架构中最容易“沦为脆弱拼图”的部分,但一旦设计清晰,就能极大提升系统安全性与可维护性。

这是我在广告平台架构工作中的关键模块之一。