IMS系统权限系统设计完全指南

权限系统是企业级应用的基础模块,决定了用户能看什么、能做什么。本文将详细介绍 IMS 系统中基于 RBAC 模型的权限系统设计,包括菜单权限、数据权限与按钮级别权限。

RBAC 权限模型

RBAC(Role-Based Access Control)基于角色的访问控制是业界最通用的权限模型:

  • 用户(User) - 系统的实际使用者
  • 角色(Role) - 一组权限的集合,代表某个岗位或职能
  • 权限(Permission) - 对系统资源的访问许可
  • 资源(Resource) - 菜单、按钮、API、数据等

数据库设计

权限系统的核心表结构:

permission-tables.sql
-- 用户表
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(128) NOT NULL,
    real_name VARCHAR(50),
    dept_id BIGINT,
    status TINYINT DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 角色表
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_code VARCHAR(50) NOT NULL UNIQUE,
    role_name VARCHAR(100) NOT NULL,
    description VARCHAR(255),
    status TINYINT DEFAULT 1
);

-- 用户-角色关联表
CREATE TABLE sys_user_role (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY (user_id, role_id)
);

-- 权限表
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    perm_code VARCHAR(100) NOT NULL UNIQUE,
    perm_name VARCHAR(100) NOT NULL,
    perm_type TINYINT,  -- 1:菜单 2:按钮 3:API
    parent_id BIGINT DEFAULT 0,
    path VARCHAR(255),
    component VARCHAR(255),
    icon VARCHAR(50),
    sort_order INT DEFAULT 0
);

-- 角色-权限关联表
CREATE TABLE sys_role_permission (
    role_id BIGINT,
    perm_id BIGINT,
    PRIMARY KEY (role_id, perm_id)
);

权限服务实现

权限核心服务类:

permission-service.js
class PermissionService {
  constructor(repository) {
    this.repository = repository;
  }

  // 获取用户所有权限
  async getUserPermissions(userId) {
    // 1. 获取用户所有角色
    const roles = await this.repository.getUserRoles(userId);

    // 2. 获取角色对应的权限
    const roleIds = roles.map(r => r.id);
    const permissions = await this.repository.getPermissionsByRoles(roleIds);

    // 3. 构建权限树
    return this.buildPermissionTree(permissions);
  }

  // 获取用户菜单
  async getUserMenus(userId) {
    const permissions = await this.getUserPermissions(userId);

    // 只保留菜单类型权限
    return permissions.filter(p => p.permType === 1);
  }

  // 权限检查
  async checkPermission(userId, permCode) {
    const userPerms = await this.repository.getUserPermCodes(userId);
    return userPerms.includes(permCode);
  }

  // 构建权限树
  buildPermissionTree(permissions) {
    const map = new Map();
    const roots = [];

    // 先将所有权限转为节点
    permissions.forEach(p => {
      map.set(p.id, { ...p, children: [] });
    });

    // 构建树形结构
    map.forEach(node => {
      if (node.parentId === 0 || !map.has(node.parentId)) {
        roots.push(node);
      } else {
        map.get(node.parentId).children.push(node);
      }
    });

    return roots;
  }
}

认证与授权中间件

API 级别的权限控制:

auth-middleware.js
// 权限认证中间件
const permissionMiddleware = async (ctx, next) => {
  // 1. 检查是否登录
  const userId = ctx.session.userId;
  if (!userId) {
    return ctx.throw(401, '请先登录');
  }

  // 2. 检查是否为超级管理员
  const isAdmin = await redis.sismember(`admin:users`, userId);
  if (isAdmin) {
    return next();
  }

  // 3. 检查接口权限
  const permCode = getPermCodeFromRouter(ctx.path, ctx.method);
  if (permCode) {
    const hasPerm = await permissionService.checkPermission(userId, permCode);
    if (!hasPerm) {
      return ctx.throw(403, '无此接口权限');
    }
  }

  await next();
};

// 按钮级别权限控制
const buttonPermission = async (userId, buttonCode) => {
  return await permissionService.checkPermission(userId, buttonCode);
};

前端权限控制

前端通过权限指令控制页面元素显示:

vue-permission.js
// 权限指令
Vue.directive('permission', {
  inserted: (el, binding) => {
    const permCode = binding.value;
    const userPerms = store.state.user.permissions;

    // 没有权限则移除元素
    if (!userPerms.includes(permCode)) {
      el.parentNode && el.parentNode.removeChild(el);
    }
  }
});

// 使用示例
/*

*/

// 路由守卫中动态加载菜单
router.beforeEach(async (to, from, next) => {
  // 获取用户菜单
  const menus = await api.getUserMenus();

  // 动态添加路由
  const accessedRoutes = filterAsyncRoutes(menus, to.matched);
  accessedRoutes.forEach(route => router.addRoute(route));

  next({ ...to, replace: true });
});

数据权限控制

在查询时加入数据权限过滤:

data-permission.js
class DataPermissionFilter {
  // 获取用户数据权限范围
  async getDataScope(userId) {
    const user = await userRepository.findById(userId);
    const roles = await roleRepository.findByUserId(userId);

    for (const role of roles) {
      if (role.dataScope === 1) {
        return { type: 'all' };  // 全部数据
      }
      if (role.dataScope === 2) {
        return { type: 'dept', deptId: user.deptId };  // 本部门
      }
      if (role.dataScope === 3) {
        return { type: 'deptBelow', deptId: user.deptId };  // 本部门及下属
      }
      if (role.dataScope === 4) {
        return { type: 'self', userId };  // 仅自己
      }
    }

    return { type: 'self', userId };  // 默认仅自己
  }

  // 应用数据权限到查询
  async applyDataScope(queryBuilder, userId) {
    const scope = await this.getDataScope(userId);

    switch (scope.type) {
      case 'all':
        break;  // 不加限制
      case 'dept':
        queryBuilder.where('dept_id', scope.deptId);
        break;
      case 'deptBelow':
        const deptIds = await getDeptAndChildrenIds(scope.deptId);
        queryBuilder.whereIn('dept_id', deptIds);
        break;
      case 'self':
        queryBuilder.where('create_by', scope.userId);
        break;
    }

    return queryBuilder;
  }
}

权限缓存策略

为提高性能,需要对权限进行缓存:

  • 用户权限缓存 - 用户登录时加载,存入 Redis,设置过期时间
  • 菜单缓存 - 菜单变更时主动清除缓存
  • 角色缓存 - 角色变更时清除相关用户缓存

总结

一个完善的权限系统需要考虑菜单权限、按钮权限、数据权限三个层次。通过 RBAC 模型管理角色和权限,配合前端指令和后端中间件实现多层控制。同时需要注意权限变更的实时性,使用缓存平衡性能。