做过企业云盘权限系统的人都清楚,这玩意儿比表面复杂得多。私有化部署场景下,客户对权限颗粒度的要求远超"读/写/管理"三级。巴别鸟在 32 维权限模型上跑了 3 年,本文把设计思路、核心代码、踩坑实录全部展开,实操性拉满。

一个真实故事:从"研发能看设计稿"说起

泡泡玛特的业务有个有意思的场景:潮玩设计稿在上市前属于高度机密,但外包设计公司需要参与共创。怎么让外包在工作时间、从公司 IP 才能看,而且设计稿只能预览不能下载?

这个问题拆开来看,每句话都是一个权限约束:谁(外包公司)、能干什么(预览)、什么时候(工作时间)、从哪来(公司 IP)、不能干什么(下载)。传统的"角色+操作"二维模型根本兜不住。RBAC 只告诉你"用户 A 是角色 X,所以能操作 Y",但它回答不了"什么时候、从哪来、用什么设备"这类上下文问题。

后来航天五院又提了一个更极端的:涉密版本只有管理员能删,研发部只能看,商务部可以下载。同一份文件,三组人看到的东西完全不同,而且还跟版本挂钩——历史版本权限跟当前版本独立。

这种多维约束叠加的场景,逼着我们去重新思考权限模型的设计哲学。

权限模型的根本问题:RBAC 解决不了的到底是什么

RBAC 跑得好好的,为什么还要搞 32 维?

核心矛盾在于:企业权限决策依赖的上下文,远不止"谁+做什么"。真实场景里,约束条件天然就是多维的:

  • 主体:个人、部门、项目组、角色、外部协作人、服务账号、AI Agent
  • 资源:文件、文件夹、项目空间、组织单元
  • 动作:预览、下载、编辑、删除、分享、评论、版本管理、移动、权限委托
  • 环境:时间窗口 × IP段 × 设备类型 × 网络环境

7 × 4 × 9 × 环境维度 = 32 维权限空间。环境维度不是固定的 N,是动态枚举,所以叫"32 维"是口语说法,实际上是个开放空间。

把这个模型搭起来之后,之前那些"说不清道不明"的权限需求,突然就变得可以配置了。

数据库表设计:三张表把策略存清楚

先说表结构。踩过一个关键坑:别用布尔值存操作类型,用位掩码(bit mask)压缩。 一条策略同时覆盖下载+编辑,位掩码一条记录搞定,查询效率完全不一样。

CREATE TABLE permission_policy (
    id            BIGINT PRIMARY KEY AUTO_INCREMENT,
    policy_name   VARCHAR(128) NOT NULL COMMENT '策略名称',
    resource_type TINYINT NOT NULL COMMENT '1=文件 2=文件夹 3=项目空间 4=组织单元',
    resource_id   BIGINT NOT NULL COMMENT '资源ID',
    action        SMALLINT NOT NULL COMMENT '位掩码: 1=预览 2=下载 4=编辑 8=删除 16=分享 32=评论 64=版本管理 128=移动 256=权限委托',
    effect        TINYINT NOT NULL DEFAULT 1 COMMENT '1=允许 0=拒绝',
    priority      INT NOT NULL DEFAULT 0 COMMENT '数字越大优先级越高',
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_resource (resource_type, resource_id),
    INDEX idx_action (action)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE policy_subject (
    id           BIGINT PRIMARY KEY AUTO_INCREMENT,
    policy_id    BIGINT NOT NULL,
    subject_type TINYINT NOT NULL COMMENT '1=个人 2=部门 3=项目组 4=角色 5=外部协作人 6=服务账号 7=AI Agent',
    subject_id   BIGINT NOT NULL,
    INDEX idx_policy (policy_id),
    INDEX idx_subject (subject_type, subject_id),
    FOREIGN KEY (policy_id) REFERENCES permission_policy(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE policy_environment (
    id              BIGINT PRIMARY KEY AUTO_INCREMENT,
    policy_id       BIGINT NOT NULL,
    constraint_type TINYINT NOT NULL COMMENT '1=时间窗口 2=IP段 3=设备类型 4=网络环境',
    constraint_value JSON NOT NULL COMMENT '如 {"start":"09:00","end":"18:00","timezone":"Asia/Shanghai"}',
    INDEX idx_policy_env (policy_id),
    FOREIGN KEY (policy_id) REFERENCES permission_policy(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

重点说一下 action 字段的位掩码设计:action=6(二进制 110)表示同时覆盖下载+编辑,一条记录顶过去两条 JOIN。MySQL 5.7 以上直接用 action & 2 = 2 判断是否包含某个操作,不用拆字段查。

权限判定核心代码

数据库只做存储和初步过滤,权限判定逻辑在应用层。以下是 Node.js 实现,实测可以直接用:

const ACTIONS = {
  PREVIEW:  1,   // 000000001
  DOWNLOAD: 2,   // 000000010
  EDIT:     4,   // 000000100
  DELETE:   8,   // 000001000
  SHARE:    16,  // 000010000
  COMMENT:  32,  // 000100000
  VERSION:  64,  // 001000000
  MOVE:     128, // 010000000
  DELEGATE: 256  // 100000000
};

async function checkPermission(userId, resourceType, resourceId, actionBit, context = {}) {
  // 收集用户所有主体身份
  const identities = await resolveUserIdentities(userId);
  // identities: [{type:1, id:10086}, {type:2, id:301}, {type:4, id:5}]

  // 查询所有匹配的策略
  const policies = await queryMatchingPolicies(identities, resourceType, resourceId, actionBit);
  if (policies.length === 0) return { allowed: false, reason: 'NO_POLICY' };

  // 按优先级排序,逐条检查环境约束
  policies.sort((a, b) => b.priority - a.priority);
  for (const policy of policies) {
    const envOk = await evaluateEnvironmentConstraints(policy.id, context);
    if (!envOk) continue;
    return {
      allowed: policy.effect === 1,
      reason: policy.effect === 1 ? 'GRANTED' : 'DENIED',
      policyId: policy.id
    };
  }
  return { allowed: false, reason: 'NO_ENV_MATCH' };
}

async function evaluateEnvironmentConstraints(policyId, context) {
  const constraints = await db.query(
    'SELECT * FROM policy_environment WHERE policy_id = ?', [policyId]
  );
  if (constraints.length === 0) return true;

  for (const c of constraints) {
    const val = JSON.parse(c.constraint_value);
    switch (c.constraint_type) {
      case 1: {
        const now = timeInZone(val.timezone || 'Asia/Shanghai');
        if (now < val.start || now > val.end) return false;
        break;
      }
      case 2: if (!ipInRange(context.clientIp, val.cidr)) return false; break;
      case 3: if (!val.allowedDevices.includes(context.deviceType)) return false; break;
      case 4: if (val.requireSecure && !context.isTls) return false; break;
    }
  }
  return true;
}

策略继承也很关键:子资源默认继承父级策略,只有例外才单独配置。用以下命令可以查看当前策略总数:

mysql -h127.0.0.1 -uroot -p -D babelloop \
  -e "SELECT resource_type, COUNT(*) as cnt \
      FROM permission_policy GROUP BY resource_type \
      ORDER BY cnt DESC;"

三个真实坑点

坑 1:策略爆炸问题

7 × 4 × 9 看起来组合很多,但实际根本不需要那么多策略。靠权限继承,子资源默认继承父级策略,只有例外情况才单独配置。绝大部分企业 200-800 条策略就够用。泡泡玛特跑 3 年,策略从 150 条增长到 400 条,权限判定平均响应 < 5ms。

坑 2:环境约束性能

每次权限判定都要查 policy_environment 表,高并发下成了性能瓶颈。解决方案:热点策略的环境约束缓存到 Redis,TTL 60 秒。

# Redis key 设计
perm:env:{policy_id} -> JSON(constraints)
# 查询顺序: Redis → MySQL
# 命中率实测: 92%(无环境约束的策略返回空标记,不穿透 DB)

中建南洋在海外工程场景下,跨部门文件协同有大量临时时间窗口约束,Redis 缓存把这块的 DB 压力降了 90%。

坑 3:智巢 AI Agent 的权限边界

智巢 AI(DeepSeek 私有化 RAG)是巴别鸟的协同智能模块,AI 作为第 7 类主体,权限判定有特殊处理:

  • AI 只能访问被明确授权的知识库范围
  • AI 生成的文件自动继承创建者的权限
  • AI 读取文件时,环境约束中的"设备类型"替换为"Agent 版本号"
-- 智巢 AI 专用只读策略
INSERT INTO permission_policy (policy_name, resource_type, resource_id, action, effect, priority)
VALUES ('智巢AI只读知识库', 3, 5001, 1, 1, 100);
-- action=1 仅允许预览,下载和编辑均拒绝

INSERT INTO policy_subject (policy_id, subject_type, subject_id)
VALUES (LAST_INSERT_ID(), 7, 200);  -- subject_type=7 = AI Agent

航天五院踩过一个更极端的坑:VPN 用户来源 IP 不稳定,纯 IP 判断导致内网用户被误判。后来改为 IP + 设备指纹双重校验才解决。

权限变更怎么同步到审计

权限系统不仅是访问控制,还要支撑版本协同场景下的权限联动。审计日志同步写入是关键:

# 查看某文件的权限策略列表
mysql -h127.0.0.1 -uroot -p -D babelloop \
  -e "SELECT pp.policy_name, pp.action, pp.effect, pp.priority, \
             ps.subject_type, pe.constraint_type \
      FROM permission_policy pp \
      JOIN policy_subject ps ON pp.id = ps.policy_id \
      LEFT JOIN policy_environment pe ON pp.id = pe.policy_id \
      WHERE pp.resource_type = 1 AND pp.resource_id = ? \
      ORDER BY pp.priority DESC;"

每次策略变更通过消息队列同步到审计系统,审计日志与策略版本保持一致,支持回滚查证。

32 维权限 vs 传统 RBAC:实际场景对比

权限场景 传统 RBAC(角色+操作) 巴别鸟 32 维(多维上下文) 实际收益
研发部+设计稿预览 角色=研发/操作=读 主体=研发/资源=设计稿/操作=预览/IP=公司网段/时间=工作日/水印=研发工号 离职自动失效,外包无法下载
涉密版本管理 角色=管理员/操作=删 主体=管理员/资源=涉密/操作=删/版本=未发布/审计=实时推送 关键操作有迹可循
外包协作 角色=外部/操作=只读 主体=外包公司/资源=项目/操作=只读/时间=9-18点/IP=指定段/水印=公司名 不用拉防火墙,权限即边界
离职交接 主体=离职人/资源=全部/操作=转交/触发=离职流程/对象=直属上级 1 步完成,不用手动移交
AI Agent 协作 主体=AI 服务账号/资源=训练数据/操作=写入/审计=可追溯 AI 写入行为有责任人

总结

32 维权限模型的核心是把环境约束纳入决策链路,位掩码+优先级排序+缓存保证性能。实操中最大两个坑是策略爆炸和环境约束性能,用继承和 Redis 缓存可以有效规避。这套方案在泡泡玛特、中建南洋、航天五院均稳定运行 3 年以上,策略数量分别约为 400、550、800 条,场景各有侧重。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐