企业官网后台几乎都离不开权限管理——谁能看文章列表、谁能发布内容、谁能管理用户。直接在每个接口里写 if (user.role == 'admin') 看似简单,但角色一多就难以维护。业界通用的解决方案是 RBAC(基于角色的访问控制),本篇带你从模型到落地完整理解它。
一、RBAC 权限模型
RBAC 的核心思想是"用户不直接拥有权限,而是通过角色间接获得权限"。一个用户可以被赋予多个角色,一个角色可以拥有多个权限,权限对应具体的操作(如"文章-发布""用户-删除")。这样用户与权限解耦,新增角色或调整权限时不必修改用户数据。
相比"用户直接绑权限"的扁平模型,RBAC 的优势在于:第一,权限调整集中发生在角色上,批量生效;第二,角色名(如"编辑""审核员")贴合业务语义,配置直观;第三,便于审计——想知道某用户能做什么,看他的角色即可。对于企业官网这种角色相对固定(管理员、编辑、运营、访客)的场景,RBAC 是最合适的选择。
- 用户(User):系统登录主体,如 admin、editor。
- 角色(Role):权限的集合,如"内容编辑""超级管理员"。
- 权限(Permission):最小操作单元,如"article:publish"。
- 分配关系:用户-角色多对多,角色-权限多对多。
二、用户-角色-权限表设计
经典的 RBAC 需要五张表:用户表、角色表、权限表,以及两张关联表。关联表用复合主键保证不重复分配,并加时间戳记录分配时机。
-- 用户表
CREATE TABLE sys_user (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) NOT NULL UNIQUE,
password VARCHAR(128) NOT NULL COMMENT '加密后密码',
status TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1启用0禁用',
create_time DATETIME NOT NULL
);
-- 角色表
CREATE TABLE sys_role (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
role_name VARCHAR(32) NOT NULL UNIQUE,
role_code VARCHAR(32) NOT NULL COMMENT '角色编码',
description VARCHAR(120)
);
-- 权限表
CREATE TABLE sys_permission (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
perm_code VARCHAR(64) NOT NULL UNIQUE COMMENT '如 article:publish',
perm_name VARCHAR(64) NOT NULL,
module VARCHAR(32) NOT NULL COMMENT '所属模块'
);
-- 用户-角色关联
CREATE TABLE sys_user_role (
user_id BIGINT UNSIGNED NOT NULL,
role_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (user_id, role_id)
);
-- 角色-权限关联
CREATE TABLE sys_role_permission (
role_id BIGINT UNSIGNED NOT NULL,
perm_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (role_id, perm_id)
);
三、权限校验落地
表结构建好后,登录时把该用户的所有权限码查出并缓存(建议放 Redis,避免每次请求查库)。校验时只需判断目标权限码是否在用户权限集合里。下面是伪代码示例:
// 登录时加载权限并缓存
function loadUserPermissions(userId) {
const perms = db.query(`
SELECT p.perm_code
FROM sys_user_role ur
JOIN sys_role_permission rp ON ur.role_id = rp.role_id
JOIN sys_permission p ON rp.perm_id = p.id
WHERE ur.user_id = ?`, userId);
cache.set(`perms:${userId}`, perms.map(p => p.perm_code));
}
// 接口入口校验
function checkPermission(userId, requiredCode) {
const perms = cache.get(`perms:${userId}`);
if (!perms.includes(requiredCode)) {
throw new Error('无操作权限');
}
}
两个细节值得注意:第一,权限码命名建议用"模块:操作"格式(如 article:delete),既可精确校验单操作,也可按模块前缀批量校验;第二,角色调整后必须刷新缓存,否则用户权限不生效。可以在分配/取消角色时主动清除对应用户的权限缓存,保证实时性。这套机制在尧图多个企业后台项目中稳定运行,足以应对绝大多数权限场景。