4、角色管理模块:角色创建、角色编辑、角色删除、角色继承关系

角色管理,说白了就是给「权限」找个容器。你想想看,如果每个用户都要单独配权限,那配方系统里有几百号人,光配权限就能配到崩溃。所以我们需要角色——把一堆权限打包成一个角色,再把角色分配给用户。

我在好几个配方管理项目里都踩过角色的坑。嗯,今天就把这些经验掰开揉碎了讲给你听。

4.1 角色创建:别小看这一步

角色创建看起来简单——填个名字,点个保存。但我在项目中遇到过一个问题:角色名称没做唯一性校验,结果生产环境里出现了两个「质检员」角色,权限还不一样,排查了整整两天。

我曾经踩过的坑:角色名称必须全局唯一,且建议用英文标识+中文描述的组合。比如 role_code = "qc_inspector",role_name = "质检员"。这样代码里引用时不会乱。

创建角色时,我建议至少包含以下字段:

字段 类型 说明
role_code 字符串 角色编码,全局唯一,用于代码判断
role_name 字符串 角色名称,显示给用户看
description 文本 角色描述,说明这个角色是干嘛的
parent_role_id 整数 父角色ID,用于继承关系
status 枚举 启用/禁用,禁用时该角色下所有用户都失效

代码实现上,我个人习惯用这样的结构:

// 角色创建接口示例
POST /api/roles
{
  "role_code": "qc_inspector",
  "role_name": "质检员",
  "description": "负责配方质检流程的操作员",
  "parent_role_id": null,
  "status": "enabled"
}
小技巧:创建角色时,建议默认勾选「启用」状态。但如果是继承自父角色的子角色,最好默认「禁用」,等权限配置完再手动启用。防止权限还没配好就被用户用上了。

4.2 角色编辑:权限变更的「安全阀」

角色编辑比创建复杂得多。为什么?因为角色一旦被分配给了用户,你改角色权限就会影响到所有关联用户。我在一个项目中遇到过:编辑角色时不小心去掉了「查看配方」权限,结果整个质检组都看不到配方了,生产线直接停摆。

所以我建议角色编辑时,必须做两件事:

  1. 变更记录:每次编辑都要记录谁改了、改了啥、什么时候改的
  2. 影响范围提示:保存前弹窗提示「此角色已分配给 23 个用户,确认修改?」

编辑界面的核心逻辑:

// 角色编辑时,权限变更的对比逻辑
const originalPermissions = getRolePermissions(roleId);
const newPermissions = getFormPermissions();

// 找出差异
const added = newPermissions.filter(p => !originalPermissions.includes(p));
const removed = originalPermissions.filter(p => !newPermissions.includes(p));

// 如果有差异,记录变更并提示用户
if (added.length > 0 || removed.length > 0) {
  showConfirmDialog({
    title: '权限变更确认',
    message: `将新增 ${added.length} 项权限,移除 ${removed.length} 项权限`,
    onConfirm: () => saveRoleChanges(roleId, newPermissions)
  });
}
核心原则:角色编辑是「批量操作」,一次修改影响多人。所以编辑操作必须比创建操作多一层确认、多一层记录、多一层回滚机制。

4.3 角色删除:最危险的操作

角色删除,嗯,这个我得好好说说。我曾经在生产环境里直接删了一个角色,结果那个角色下的 50 多个用户瞬间变成了「无角色」状态,所有权限都丢了。虽然数据没丢,但用户那边炸了锅。

所以我现在的做法是:角色删除必须走「软删除 + 转移」流程。

步骤 操作 说明
1 检查关联用户 统计该角色下有多少用户
2 选择转移角色 这些用户要转移到哪个角色下
3 确认删除 二次确认,输入角色名称才能删除
4 软删除 标记为 deleted,保留历史数据

代码实现上,我建议这样:

// 角色删除接口
DELETE /api/roles/{roleId}
// 请求体
{
  "transfer_to_role_id": 123,  // 用户转移到哪个角色
  "confirm_role_name": "质检员" // 输入角色名称确认
}

// 后端处理逻辑
function deleteRole(roleId, transferToRoleId, confirmName) {
  const role = getRoleById(roleId);
  if (role.role_name !== confirmName) {
    throw new Error('角色名称确认失败');
  }
  
  // 转移用户
  const users = getUsersByRoleId(roleId);
  users.forEach(user => {
    assignRoleToUser(user.id, transferToRoleId);
  });
  
  // 软删除角色
  softDeleteRole(roleId);
}
绝对不要做的事:不要直接物理删除角色!你永远不知道哪天需要回溯历史权限数据。软删除 + 保留操作日志,这是底线。

4.4 角色继承关系:权限复用的精髓

角色继承,说白了就是「子角色自动拥有父角色的权限」。这个设计在配方管理系统里特别实用。比如你有一个「配方管理员」角色,下面可以继承出「高级配方管理员」「初级配方管理员」等子角色。

我建议的继承规则:

  • 单向继承:子角色继承父角色的所有权限,但不能反向继承
  • 权限叠加:子角色可以额外添加权限,但不能移除父角色的权限
  • 层级限制:最多 3 层继承,太深了容易混乱

下面这张图展示了角色继承的核心逻辑:

角色继承关系结构图 角色继承关系结构图 配方管理员 高级配方管理员 初级配方管理员 资深质检员 实习质检员 配方审核员 说明: 子角色自动继承父角色的所有权限,并可额外添加专属权限。 例如「高级配方管理员」拥有「配方管理员」的全部权限,再加上高级操作权限。

实现继承关系时,我建议用这样的数据结构:

// 角色权限合并逻辑
function getEffectivePermissions(roleId) {
  const role = getRoleById(roleId);
  let permissions = [];
  
  // 递归获取父角色权限
  if (role.parent_role_id) {
    const parentPermissions = getEffectivePermissions(role.parent_role_id);
    permissions = permissions.concat(parentPermissions);
  }
  
  // 叠加当前角色自己的权限
  const selfPermissions = getRoleSelfPermissions(roleId);
  permissions = permissions.concat(selfPermissions);
  
  // 去重
  return [...new Set(permissions)];
}
我的经验:继承关系不要搞太深。3 层以内是清晰的,超过 3 层,你根本搞不清某个权限是从哪一层继承来的。我见过一个项目搞了 7 层继承,最后没人敢改角色权限,因为不知道会影响到谁。

4.5 角色管理的避坑总结

嗯,说了这么多,最后给你总结几个我踩过的坑:

  • 角色名称别用中文做唯一标识——我见过「质检员」和「质检员(带空格)」被当成两个角色
  • 删除角色前必须检查关联用户——不然用户权限会「凭空消失」
  • 继承关系要显示在界面上——用户需要一眼看出某个角色是从哪继承来的
  • 角色变更要有审批流程——特别是生产环境,改角色权限最好走审批
一句话总结:角色管理不是简单的增删改查,它是权限体系的骨架。骨架歪了,整个权限系统都会出问题。创建要规范、编辑要留痕、删除要转移、继承要清晰——这四件事做好了,角色管理就稳了。