设计一个通用的 Vue 权限管理系统,支持路由权限、按钮级权限、数据权限,并能与后端 API 动态集成。

各位观众老爷们,晚上好!今天咱们不聊风花雪月,来点硬核的——Vue 权限管理系统实战。保证听完,腰不酸了,腿不疼了,一口气能写十个权限控制模块!

一、开场白:为啥要有权限管理?

想象一下,你辛辛苦苦开发的网站,没做任何限制,谁都能进来瞎搞,把数据删了,把页面改了,甚至把服务器都弄崩溃了……这酸爽,想想都可怕!

所以,权限管理就像给你的网站装了一扇扇门,只有拥有对应钥匙的人才能进入,保证安全又可靠。

二、权限管理系统的核心要素

一个合格的权限管理系统,至少要考虑以下几个方面:

  • 用户(User): 谁在使用系统?
  • 角色(Role): 用户有什么身份?(比如管理员、普通用户、访客等)
  • 权限(Permission): 角色能干什么?(比如查看页面、修改数据、删除文件等)
  • 资源(Resource): 权限作用于什么地方?(比如某个路由、某个按钮、某个数据字段等)

它们之间的关系可以用一句话概括:用户属于角色,角色拥有权限,权限作用于资源。

三、Vue 权限管理系统架构设计

我们的目标是打造一个通用的权限管理系统,支持路由权限、按钮级权限、数据权限,并且能动态与后端 API 集成。因此,架构设计如下:

  1. 前端(Vue):
    • 使用 Vuex 管理用户角色和权限信息。
    • 利用 Vue Router 的 beforeEach 钩子进行路由权限控制。
    • 自定义指令实现按钮级权限控制。
    • 封装 API 请求,根据权限过滤数据。
  2. 后端(API):
    • 提供用户登录接口,返回用户角色和权限信息。
    • 提供动态权限配置接口,方便修改权限。
    • 提供数据接口,根据用户权限返回相应的数据。

四、实战:代码说话

废话不多说,直接上代码!

1. Vuex 管理权限信息

首先,我们需要一个 Vuex store 来存储用户的角色和权限信息。

// store/modules/permission.js
import { constantRoutes } from '@/router' // 引入静态路由
import { getPermissionList } from '@/api/user' // 引入获取权限列表的接口

const state = {
  routes: [], // 存储用户可访问的路由
  asyncRoutes: [] // 存储异步路由
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.asyncRoutes = routes
    state.routes = constantRoutes.concat(routes) // 将静态路由和异步路由合并
  }
}

const actions = {
  // 获取用户权限列表
  generateRoutes({ commit }, roles) {
    return new Promise((resolve, reject) => {
      getPermissionList(roles).then(response => {
        const accessedRoutes = filterAsyncRoutes(response.data.permissions, asyncRoutes) // 根据权限过滤异步路由
        commit('SET_ROUTES', accessedRoutes) // 将过滤后的路由设置到 state 中
        resolve(accessedRoutes)
      }).catch(error => {
        reject(error)
      })
    })
  }
}

// 过滤异步路由
function filterAsyncRoutes(permissions, routes) {
  const res = []
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(permissions, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(permissions, tmp.children)
      }
      res.push(tmp)
    }
  })
  return res
}

// 判断路由是否有权限
function hasPermission(permissions, route) {
  if (route.meta && route.meta.roles) {
    return permissions.some(permission => route.meta.roles.includes(permission)) // 判断用户的权限是否包含路由的角色
  } else {
    return true // 如果路由没有设置角色,则默认拥有权限
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

// asyncRoutes 异步路由配置 (示例)
export const asyncRoutes = [
  {
    path: '/permission',
    component: () => import('@/views/permission/index'),
    meta: { roles: ['admin', 'editor'] }, // 只有 admin 和 editor 角色才能访问
    children: [
      {
        path: 'page',
        component: () => import('@/views/permission/page'),
        meta: { roles: ['admin'] } // 只有 admin 角色才能访问
      },
      {
        path: 'directive',
        component: () => import('@/views/permission/directive'),
        meta: { roles: ['admin', 'editor'] } // 只有 admin 和 editor 角色才能访问
      }
    ]
  },
  // ... 其他路由
  { path: '*', redirect: '/404', hidden: true }
]
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import permission from './modules/permission'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    permission
  }
})

2. 路由权限控制

利用 Vue Router 的 beforeEach 钩子,判断用户是否拥有访问某个路由的权限。

// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store' // 引入 Vuex store
import { constantRoutes, asyncRoutes } from './routes'

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: constantRoutes // 初始加载静态路由
})

router.beforeEach(async (to, from, next) => {
  const hasToken = localStorage.getItem('token') // 假设 token 存在 localStorage 中

  if (hasToken) {
    if (to.path === '/login') {
      // 如果已登录,则重定向到首页
      next({ path: '/' })
    } else {
      const hasRoutes = store.getters.routes && store.getters.routes.length > 0

      if (hasRoutes) {
        next() // 如果已经动态添加过路由,则直接放行
      } else {
        try {
          // 获取用户角色信息 (这里假设角色信息存储在 localStorage 中,实际应从后端获取)
          const roles = JSON.parse(localStorage.getItem('userInfo')).roles;
          // 根据角色生成可访问的路由表
          await store.dispatch('permission/generateRoutes', roles)

          // 动态添加可访问路由
          router.addRoutes(store.getters.routes)

          // hack方法 确保addRoutes已完成
          // 设置replace: true,这样导航就不会留下历史记录
          next({ ...to, replace: true })
        } catch (error) {
          // 清除token
          localStorage.removeItem('token')
          localStorage.removeItem('userInfo');
          next(`/login?redirect=${to.path}`)
          console.log(error)
        }
      }
    }
  } else {
    // 如果没有token
    if (to.path === '/login') {
      next() // 如果是登录页,则直接放行
    } else {
      // 重定向到登录页
      next(`/login?redirect=${to.path}`)
    }
  }
})

export default router

3. 按钮级权限控制

通过自定义指令,判断用户是否拥有某个按钮的权限,如果没有,则隐藏按钮。

// directives/permission.js
import Vue from 'vue'
import store from '@/store'

Vue.directive('permission', {
  inserted(el, binding) {
    const { value } = binding
    const permissions = JSON.parse(localStorage.getItem('userInfo')).permissions; // 从 localStorage 中获取权限列表 (实际应从 Vuex store 中获取)

    if (value && value instanceof Array && value.length > 0) {
      const hasPermission = permissions.some(permission => value.includes(permission));

      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el) // 移除没有权限的元素
      }
    } else {
      throw new Error(`need roles! Like v-permission="['admin','editor']"`)
    }
  }
})

使用方法:

<template>
  <div>
    <button v-permission="['user:create']">创建用户</button>
    <button v-permission="['user:update']">修改用户</button>
    <button v-permission="['user:delete']">删除用户</button>
  </div>
</template>

4. 数据权限控制

数据权限控制是指,根据用户的角色和权限,返回不同的数据。这需要在后端 API 中实现。

例如,假设我们有一个用户列表接口,后端可以根据用户的角色,返回不同的用户数据:

  • 管理员(admin): 返回所有用户数据。
  • 普通用户(user): 只能返回自己的用户数据。

前端在调用 API 时,不需要做任何特殊处理,只需要拿到数据并展示即可。

5. 与后端 API 动态集成

为了实现动态权限配置,我们需要后端 API 提供以下接口:

  • 获取用户角色和权限信息: 在用户登录时调用,返回用户的角色和权限列表。
  • 修改权限: 管理员可以调用此接口,修改用户的角色和权限。
  • 获取权限列表: 用于前端展示权限列表,方便管理员配置权限。

前端在获取到权限信息后,需要更新 Vuex store 中的数据,并重新计算路由。

五、代码示例:后端 API (Node.js + Express)

// server.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();
const port = 3000;

app.use(cors());
app.use(bodyParser.json());

// 模拟数据库
const users = [
  { id: 1, username: 'admin', password: 'password', roles: ['admin'], permissions: ['user:create', 'user:update', 'user:delete'] },
  { id: 2, username: 'editor', password: 'password', roles: ['editor'], permissions: ['user:update'] },
  { id: 3, username: 'user', password: 'password', roles: ['user'], permissions: [] },
];

const products = [
  { id: 1, name: 'Product A', price: 100 },
  { id: 2, name: 'Product B', price: 200 },
  { id: 3, name: 'Product C', price: 300 },
];

// 登录接口
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const user = users.find(u => u.username === username && u.password === password);

  if (user) {
    res.json({
      code: 200,
      data: {
        id: user.id,
        username: user.username,
        roles: user.roles,
        permissions: user.permissions,
        token: 'fake_token' // 实际项目中应生成 JWT
      }
    });
  } else {
    res.status(401).json({ code: 401, message: 'Invalid credentials' });
  }
});

// 获取用户权限列表接口
app.get('/permissionList', (req, res) => {
  // 实际项目中应从数据库中获取权限列表
  const permissions = ['user:create', 'user:update', 'user:delete', 'product:read', 'product:write'];
  res.json({ code: 200, data: { permissions } });
});

//  获取用户角色权限列表接口
app.get('/user/permissionList', (req, res) => {
  const roles = req.query.roles;
  const user = users.find(u => u.roles.includes(roles));
  if(user){
    res.json({ code: 200, data: { permissions:user.permissions } });
  }else{
    res.status(401).json({ code: 401, message: 'Invalid roles' });
  }

});

// 获取产品列表接口 (模拟数据权限)
app.get('/products', (req, res) => {
  const token = req.headers.authorization; // 实际项目中应验证 JWT
  // 这里简单模拟,根据 token (用户角色) 返回不同的数据
  // 在实际项目中,需要根据用户的角色和权限,查询数据库并返回相应的数据
  if (token === 'fake_token') {
    // 假设 admin 角色拥有所有权限
    res.json({ code: 200, data: products });
  } else {
    // 其他角色只能看到部分数据
    res.json({ code: 200, data: products.slice(0, 2) });
  }
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

六、权限管理策略

选择合适的权限管理策略至关重要。常见的策略有:

策略 优点 缺点 适用场景
基于角色的访问控制 (RBAC) 简单易用,易于理解和管理,适合大型系统,可以根据角色快速分配权限。 当角色数量较多时,权限管理可能会变得复杂。 适用于用户角色相对稳定,权限需求不经常变化的系统。
基于属性的访问控制 (ABAC) 灵活性强,可以根据各种属性(用户属性、资源属性、环境属性等)进行权限控制。 配置复杂,需要对各种属性进行定义和管理,性能可能受到影响。 适用于需要精细化权限控制,权限需求经常变化的系统。
访问控制列表 (ACL) 粒度细,可以对单个资源进行权限控制。 管理复杂,当资源数量较多时,权限管理会变得非常困难。 适用于资源数量较少,需要对单个资源进行精细化权限控制的系统。

选择哪种策略,需要根据你的实际情况进行考虑。

七、注意事项

  • 安全第一: 永远不要相信前端传来的数据,所有权限判断都应该在后端进行。
  • 缓存: 权限数据可以进行缓存,提高性能。但要注意缓存更新的问题。
  • 测试: 充分测试你的权限管理系统,确保没有漏洞。
  • 文档: 编写清晰的文档,方便其他开发人员使用和维护。

八、总结

一个好的权限管理系统,能够有效地保护你的网站安全,提高开发效率。希望今天的讲座对你有所帮助。记住,权限管理不是一蹴而就的,需要不断地学习和实践。

散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注