4. 权限数据初始化脚本

当 RBAC 实体表创建完成后,必须注入一组默认角色(如超级管理员、普通用户)、预定义权限(如创建文章、编辑用户)以及角色‑权限关联,作为系统启动时的最小权限基准。本章聚焦于如何编写可重复执行、数据一致且支持事务回滚的 SQL 初始化脚本,并给出典型测试数据。

4.1 核心原理:数据一致性策略

策略说明RBAC 场景示例
事务封装将全部 DML 放入一个事务,任何一步失败则整体回滚角色表和角色‑权限表必须同时提交
外键约束FK 保证关联记录存在,防止悬挂引用role_id 在 role_permission 中必须存在于 role 表
幂等插入使用唯一键 + ON CONFLICT / 预检查避免重复角色编码 code 设 Unique 约束
审计字段created_at / updated_at 记录变更时间方便回滚或数据追溯

4.2 实现流程

  1. 取消 autocommit,开启事务
  2. 插入 role 表:super_admin, admin, user, guest
  3. 插入 permission 表:增删改查资源对应的权限标识
  4. 插入 role_permission 关联表:每角色至少 1 条权限
  5. 插入测试用户(user 表)并分配角色
  6. 提交事务;异常则 ROLLBACK

关键设计: 采用自然主键(如角色编码 code)作为业务唯一标识,便于编写幂等 INSERT;后续迁移时可稳定关联。

4.3 SQL 初始化脚本

-- 4.3.1 插入角色(幂等写法,基于 code 冲突时忽略)
BEGIN;

INSERT INTO role (code, name, description, created_at)
VALUES
    ('super_admin', '超级管理员', '拥有系统全部权限,不可删除', NOW()),
    ('admin',       '管理员',       '可管理用户与内容', NOW()),
    ('user',        '普通用户',     '仅可访问个人资源', NOW()),
    ('guest',       '访客',         '只读权限', NOW())
ON CONFLICT (code) DO NOTHING;

-- 4.3.2 插入权限(标识格式: resource:action)
INSERT INTO permission (code, name, resource, action, description, created_at)
VALUES
    ('article:create', '创建文章', 'article', 'create', '允许新增文章', NOW()),
    ('article:edit',   '编辑文章', 'article', 'edit',   '允许修改任意文章', NOW()),
    ('article:delete', '删除文章', 'article', 'delete', '允许删除文章', NOW()),
    ('article:view',   '查看文章', 'article', 'view',   '允许查看文章列表及详情', NOW()),
    ('user:manage',    '用户管理', 'user',    'manage', '允许创建/编辑/删除用户', NOW()),
    ('system:config',  '系统配置', 'system', 'config',  '允许修改系统参数', NOW())
ON CONFLICT (code) DO NOTHING;

-- 4.3.3 角色-权限关联(使用角色code和权限code简化)
-- 超级管理员拥有所有权限
INSERT INTO role_permission (role_code, permission_code, created_at)
SELECT r.code, p.code, NOW()
FROM role r, permission p
WHERE r.code = 'super_admin'
ON CONFLICT DO NOTHING;

-- 管理员:文章增改删 + 用户管理(不含系统配置)
INSERT INTO role_permission (role_code, permission_code, created_at)
SELECT r.code, p.code, NOW()
FROM role r, permission p
WHERE r.code = 'admin'
  AND p.code IN ('article:create','article:edit','article:delete','article:view','user:manage')
ON CONFLICT DO NOTHING;

-- 普通用户:仅查看+创建自己的文章
INSERT INTO role_permission (role_code, permission_code, created_at)
SELECT r.code, p.code, NOW()
FROM role r, permission p
WHERE r.code = 'user'
  AND p.code = 'article:view'
ON CONFLICT DO NOTHING;

-- 访客:仅查看文章
INSERT INTO role_permission (role_code, permission_code, created_at)
SELECT r.code, p.code, NOW()
FROM role r, permission p
WHERE r.code = 'guest'
  AND p.code = 'article:view'
ON CONFLICT DO NOTHING;

-- 4.3.4 插入测试用户(假设user表有role_code外键)
INSERT INTO "user" (username, password_hash, role_code, email, created_at)
VALUES
    ('root',     'hashed_pwd_1', 'super_admin', 'root@example.com', NOW()),
    ('admin01',  'hashed_pwd_2', 'admin',       'admin01@example.com', NOW()),
    ('alice',    'hashed_pwd_3', 'user',        'alice@example.com', NOW()),
    ('bob',      'hashed_pwd_4', 'user',        'bob@example.com', NOW()),
    ('guest01',  'hashed_pwd_5', 'guest',       'guest@example.com', NOW())
ON CONFLICT (username) DO NOTHING;

COMMIT;

4.4 调试要点

  • 执行后查询各表行数:SELECT count(*) FROM role; 应返回 4 行。
  • 验证角色‑权限关联总数:超级管理员应含所有 6 条权限,管理员 5 条,普通用户和访客各 1 条。
  • 检查外键:插入不存在的 role_code 时应被 FK 拒绝并报错。

调试命令片段:

-- 查看每个角色的权限数量
SELECT r.code, count(rp.permission_code) as perm_count
FROM role r
LEFT JOIN role_permission rp ON r.code = rp.role_code
GROUP BY r.code;

4.5 常见错误

错误现象原因解决方案
INSERT 时违反唯一约束code 重复但未用 ON CONFLICT添加 ON CONFLICT (code) DO NOTHING
role_permission 插入 0 行子查询中 IN 列表为空或 code 不匹配先用 SELECT 验证子查询结果
外键失败插入 user 表的 role_code 不在 role 表中检查 role 表是否已提交(事务未提交时不可见)
测试用户无法登录密码哈希仅占位,未使用真实 hash将 password_hash 替换为 bcrypt 或 scrypt 输出

4.6 性能与稳定性优化

  • 使用批量 INSERT:多条 VALUES 合并在一条语句中,减少网络往返。
  • 索引覆盖:code 列已有唯一索引,关联查询无须额外扫描。
  • 大事务拆分:如果预置权限超过几百条,可拆分为多次事务,但需保证角色表先提交。
  • 初始化脚本版本化:将 SQL 文件纳入版本控制(如 Git),配合 Flyway/Liquibase 管理变更。

4.7 数据初始化流程结构图

权限数据初始化流程与依赖关系 开启事务 BEGIN 1. INSERT INTO role (code, name, description) 4 条 2. INSERT INTO permission (code, resource, action) 6 条 3. INSERT INTO role_permission (role_code, permission_code) 使用子查询 4. INSERT INTO "user" (username, role_code) 5 条测试数据 COMMIT 异常 → ROLLBACK 关键约束:角色与权限必须先存在,关联表才能插入;所有 DML 在同一个事务中保证原子性。

4.8 验证脚本的一致性

具备数据一致性的初始化脚本应通过以下验收标准:

  • 任何时候重复执行,数据行数不变(幂等性)
  • 删除 super_admin 角色前,必须先删除其关联的 role_permission,否则外键报错
  • 事务中途失败(如网络断开),数据库自动回滚,不会出现“只有角色没有权限”的中间状态

生产建议: 切勿在业务高峰期执行大型初始化脚本;建议将初始化脚本封装为独立迁移文件,使用 SELECT set_config('app.init_executed', 'true', false) 标记是否已运行,避免重复执行导致数据膨胀。