FastGPT 新资源权限接入实施指南:从权限位定义、鉴权函数到协作者管理与 Owner 转移的完整落地清单
本文以 FastGPT 仓库内《权限接入实施清单》(.agents/skills/support/permission/add-permission/checklist.md)为核心骨架,系统讲解如何为一个新资源类型(如 App、知识库 Dataset、技能 Skill)接入 FastGPT 的细粒度权限体系。文章涵盖设计判断、权限定义、资源 Schema、鉴权函数、API 权限校验、继承逻辑、协作者管理、Owner 转移、审计日志、前端接入与测试验证全流程,并结合
packages/global与packages/service中的源码实现逐项印证,帮助开发者在开发阶段就对照核对,避免上线后出现越权、误删或继承错乱等问题。
一、权限系统核心设计:位域(Bit Field)与角色映射
FastGPT 的权限模型受 Linux 文件权限启发,采用**位域(bit field)**表示权限值。在 packages/global/support/permission/type.ts 的类型注释中有明确说明:
PermissionValueType is a number, which is a bit field actually. The lowest 3 bits present the permission of reading, writing and managing. The higher bits are advanced permissions or extended permissions, which could be customized.
通用权限位定义在 packages/global/support/permission/constant.ts:
export const CommonPerList: PermissionListType = {
[CommonPerKeyEnum.owner]: OwnerRoleVal, // ~0 >>> 0,即 0xFFFFFFFF
[CommonPerKeyEnum.read]: 0b100, // 第 3 位:读
[CommonPerKeyEnum.write]: 0b010, // 第 2 位:写
[CommonPerKeyEnum.manage]: 0b001 // 第 1 位:管理
} as const;
对应的角色(Role)每个也是单一位的二进制数,而**角色到权限的映射(RolePerMap)**负责把角色值展开为实际权限位集合:
export const CommonRolePerMap: RolePerMapType = new Map([
[CommonRoleList['read'].value, CommonPerList.read], // read → read
[CommonRoleList['write'].value, sumPer(CommonPerList.write, CommonPerList.read)], // write → write + read
[CommonRoleList['manage'].value,
sumPer(CommonPerList.manage, CommonPerList.write, CommonPerList.read)] // manage → manage + write + read
]);
这里的关键点在于:
- read(0b100)、write(0b010)、manage(0b001)按位包含,与角色值一一对应;
- Owner 是特殊值:
OwnerRoleVal = OwnerPermissionVal = ~0 >>> 0(全 1),不是普通权限位,因此checkPer/checkRole中需要对 Owner 做全等判断(见 controller.ts); - 高 4 位起为资源扩展权限位,例如应用资源自定义了
readChatLog: 0b1000(见下文示例),可按需扩展而不影响通用低 3 位。
Permission 工具类(packages/global/support/permission/controller.ts)是权限判断的核心实现,它接收 role、isOwner、roleList、perList、rolePerMap,内部通过 calculatePer() 的"Binary Magic"循环把角色位逐步拆解为权限位:
private calculatePer() {
if (this.role === OwnerRoleVal) {
this.permission = OwnerPermissionVal;
return;
}
let role = this.role;
this.permission = 0;
while (role > 0) {
// Binary Magic
this.permission |= this.rolePerMap.get(role & -role) ?? 0;
role &= role - 1;
}
}
role & -role 取出最低的置位位,role &= role - 1 清除该位,循环累积出最终权限。实例化后会同步计算出 hasManagePer / hasWritePer / hasReadPer 与 hasManageRole / hasWriteRole / hasReadRole 等便捷布尔字段,供 API 层直接判断。
接入新资源的第一步,就是理解并复用这套位域模型:新资源的权限定义必须基于 CommonPerList / CommonRoleList 扩展,而不是另起炉灶。
二、设计判断:接入前先回答的六个问题
清单开篇要求开发者在上线前完成一组设计判断,这决定了后续所有实现路径:
| 判断项 | 含义 | 影响 |
|---|---|---|
| 资源是否有 owner | 是否存在资源所有者(tmbId) |
决定是否需要 Owner 转移逻辑、删除是否要求 Owner 权限 |
| 资源是否属于 team | 是否挂靠在团队下 | 决定是否需要 teamId 归属校验与团队级权限 |
| 资源是否需要协作者 | 是否允许多人对同一资源协作 | 决定是否接入 collaborator/list、collaborator/update |
| 是否有 folder / parent-child 结构 | 是否存在目录/父子层级 | 决定是否引入 parentId、子树权限同步 |
是否支持 inheritPermission |
子资源是否继承父级权限 | 决定是否实现继承合并与恢复继承逻辑 |
| 是否需要 owner 转移 | 资源能否更换所有者 | 决定是否实现 changeOwner 接口与转移日志 |
这些问题的答案直接映射到第三~八节的实现清单。确认资源是否有 owner、是否属于 team 是必选项,其余按资源形态按需勾选。
三、权限定义落地:枚举、常量文件与 Permission 子类
清单在"FastGPT 主仓库 → 权限定义"中要求三件事,逐一说明:
3.1 在 PerResourceTypeEnum 中注册新资源类型
枚举定义在 packages/global/support/permission/constant.ts:
export enum PerResourceTypeEnum {
team = 'team',
app = 'app',
dataset = 'dataset',
model = 'model',
agentSkill = 'agentSkill'
}
新增资源类型时需在此追加一个枚举成员(例如 xxx = 'xxx'),该值会被持久化到权限记录中,用于区分 ResourcePermissionType(见 type.ts 中的 resourceType 字段)。
3.2 创建 {resource}/constant.ts,声明四项内容
清单要求在 packages/global/support/permission/{resource}/constant.ts 中定义:
{Resource}RoleList:角色列表,继承自CommonRoleList,可覆盖名称与描述;{Resource}RolePerMap:角色→权限映射,通常以CommonRolePerMap为基础追加扩展;{Resource}PerList:权限位列表,{ ...CommonPerList, 扩展位 };{Resource}DefaultRoleVal:默认角色值,一般取NullRoleVal(0)。
以知识库为例,packages/global/support/permission/dataset/constant.ts 是最简形态——直接复用通用三件套:
export const DatasetRoleList = { read: {...}, write: {...}, manage: {...} };
export const DatasetRolePerMap: RolePerMapType = CommonRolePerMap;
export const DatasetPerList = CommonPerList;
export const DataSetDefaultRoleVal = NullRoleVal;
以应用(App)为例,packages/global/support/permission/app/constant.ts 展示了扩展权限位的写法——App 额外定义了 readChatLog(查看聊天日志)这一高阶权限,占用第 4 位:
export enum AppPermissionKeyEnum {
ReadChatLog = 'readChatLog'
}
export const AppPerList: PermissionListType<AppPermissionKeyEnum> = {
...CommonPerList,
readChatLog: 0b1000
};
export const AppRoleList: RoleListType<AppPermissionKeyEnum> = {
[CommonPerKeyEnum.read]: { ...CommonRoleList[CommonPerKeyEnum.read], name: ..., description: ... },
...
[AppPermissionKeyEnum.ReadChatLog]: { value: 0b1000, checkBoxType: 'multiple', name: ..., description: '' }
};
export const AppRolePerMap: RolePerMapType = new Map([
...CommonRolePerMap,
[CommonRoleList.manage.value, sumPer(read, write, manage, readChatLog)!], // 管理员自动含读聊天日志
[AppRoleList.ReadChatLog.value, sumPer(read, readChatLog)!] // 只读+读聊天日志
]);
export const AppDefaultRoleVal = NullRoleVal;
注意 checkBoxType 有 'single' | 'multiple' | 'hidden' 三种(type.ts):通用低 3 位角色是单选,高阶扩展权限通常为 'multiple' 多选,'hidden' 用于不可见角色。
3.3 创建 {resource}/controller.ts,实现 {Resource}Permission 子类
{Resource}Permission 继承通用 Permission 类,注入该资源的角色/权限定义,并可注册权限更新回调。以 packages/global/support/permission/app/controller.ts 为例:
export class AppPermission extends Permission {
hasReadChatLogPer: boolean = false;
hasReadChatLogRole: boolean = false;
constructor(props?: PerConstructPros) {
if (!props) { props = { role: AppDefaultRoleVal }; }
else if (!props?.role) { props.role = AppDefaultRoleVal; }
props.roleList = AppRoleList;
props.rolePerMap = AppRolePerMap;
props.perList = AppPerList;
super(props);
this.setUpdatePermissionCallback(() => {
this.hasReadChatLogPer = this.checkPer(AppPerList.readChatLog);
this.hasReadChatLogRole = this.checkRole(AppRoleList.readChatLog.value);
});
}
}
这样在鉴权层实例化 new AppPermission({ role, isOwner }) 后,即可通过 Per.hasReadPer / Per.hasWritePer / Per.hasManagePer 等布尔属性统一判断。同时 controller.ts 提供了 PermissionSchema(zod),用于 OpenAPI 文档声明权限对象序列化后的 JSON 字段(role、isOwner、has*Per、has*Role),前端实际依赖这些字段。
四、资源 Schema:四个关键字段
清单要求资源模型(Mongo Schema)按需包含:
| 字段 | 类型 | 用途 | 必需性 |
|---|---|---|---|
teamId |
string | 资源所属团队,鉴权时校验归属(String(app.teamId) !== teamId 即拒绝) |
必需 |
tmbId |
string | 资源 owner(团队成员),判断 `isOwner = tmbPer.isOwner | |
parentId |
string | 父资源 ID,用于 folder / parent-child 层级与继承 | 有层级时必需 |
inheritPermission |
boolean | 是否允许从父级继承协作者权限 | 支持继承时必需 |
在 packages/service/support/permission/app/auth.ts 中可以看到 teamId 归属与 tmbId owner 判断的实际用法:团队不匹配直接拒绝,isOwner 由"团队所有者"或"资源创建者本人"共同决定。
五、鉴权函数 auth{Resource}:统一入口与父级权限合并
清单要求创建 packages/service/support/permission/{resource}/auth.ts,实现 auth{Resource} 函数。仓库中已有 App、Dataset、Skill、MCP、Evaluation、User 等实现(见 packages/service/support/permission/app/auth.ts、packages/service/support/permission/dataset/auth.ts、packages/service/support/permission/skill/auth.ts 等)。
以 authAppByTmbId(auth.ts)为例,其核心流程是:
- 通过
getTmbInfoByTmbId获取调用者的teamId与tmbPer(团队内成员权限); - 加载资源,校验存在性与
teamId归属; - 处理 hidden 类型资源的特殊策略(仅读权限或读聊天日志权限);
- 继承合并:若资源支持继承(
shouldInheritResourcePermission(app.inheritPermission))且不是 folder 本身、且存在parentId,则并行获取父级权限folderPer与自身权限myPer,用sumPer(folderPer, myPer)合并后构造AppPermission:const Per = new AppPermission({ role: sumPer(folderPer, myPer), isOwner }); - 收藏/快捷资源自动附加读角色;最后校验目标权限位是否满足。
这就是清单中"如有继承,已实现父级权限合并"的源码落点——子资源的最终生效权限 = 父级权限 ∪ 自身显式权限。
sumPer 的实现细节(utils.ts)值得一提:空参数返回 undefined(以便回退到默认值),结果溢出(res < 0)时返回 OwnerRoleVal。
六、API 权限校验:Read / Write / Owner 的边界
清单对各类接口的权限要求是硬性规范,整理为矩阵:
| 接口类型 | 校验权限值 | 说明 |
|---|---|---|
| 列表接口 | ReadPermissionVal(0b100) |
能读到资源即可出现在列表 |
| 详情接口 | ReadPermissionVal |
同上 |
| 创建接口 | WritePermissionVal 或 team 级创建权限 |
新资源创建者自动成为 owner |
| 更新接口 | WritePermissionVal |
注意:写权限即可更新,不需要 Manage |
| 删除接口 | OwnerPermissionVal(不是 Manage!) |
最易出错:删除是 Owner 专属动作 |
| Folder 创建接口(如有) | 按父级权限 | 父目录有写入权限才能建子资源 |
| 恢复继承接口(如有) | 按资源类型 | 通常要求 Write/Manage 级 |
其中"删除要求 owner,而不是 manage"被清单在"最终检查"一节再次强调,这是 FastGPT 权限模型的一个重要设计决策:销毁资源是高风险动作,仅资源所有者可执行。实现时 checkPer(OwnerPermissionVal) 走的是全等分支(controller.ts),即只有 permission === OwnerPermissionVal 才为 true,普通角色叠加永远无法达到。
ReadPermissionVal / WritePermissionVal / ManagePermissionVal 三个导出常量定义在 constant.ts,鉴权函数按 per 参数接收它们,例如 authAppByTmbId({ tmbId, appId, per: ReadPermissionVal })。
七、继承(inheritPermission):folder 类型、复制、移动与恢复
如果资源支持继承,清单要求实现四件事:
- 明确 folder 类型列表:区分目录型资源与叶子资源。App 侧通过
AppFolderTypeList判断——shouldInheritResourcePermission(app.inheritPermission) && !AppFolderTypeList.includes(app.type) && !!app.parentId才做父级合并(见 auth.ts),folder 本身不向上继承。 - 资源创建时复制父协作者:新子资源继承父级的协作者配置作为初始状态。
- 资源移动时同步子树权限:移动资源到新的父目录后,需要重新计算整棵子树的生效权限。
resumeInheritPermission逻辑:恢复继承时,需基于当前生效协作者(父级合并后的快照)生成显式协作者记录,即清单"最终检查"中的"继承断开后生成正确的显式协作者快照"。
在 packages/global/support/permission/utils.ts 中还有两个与继承/协作者强相关的工具函数:
mergeCollaboratorList({ parentClbs, childClbs }):将父级协作者与子资源自身协作者按协作者 ID 合并,同一协作者在两处出现时做权限位按位或(sumPer);父级为 Owner 的协作者在子级降级为 Manage(permission: ManageRoleVal),避免全 1 权限污染子树。isPrivateResourceByCollaborators(...):判断资源在给定协作者集合下是否仍为私有(realClbs.length <= 1),继承型资源会先合并父级与自身协作者,避免重复计数。checkRoleUpdateConflict({ parentClbs, newChildClbs }):检测协作者更新是否与父级产生冲突(见第八节)。
八、fastgpt-pro(企业版):协作者管理、Owner 转移与审计
清单第二部分面向 fastgpt-pro 仓库(本开源仓库的 pro 目录对应独立的企业版内容)。其核心是**协作者(Collaborator)**体系:
8.1 协作者类型与数据结构
协作者 ID 是"三选一"的联合类型(packages/global/support/permission/collaborator.ts):
export type CollaboratorIdType = RequireOnlyOne<{
tmbId: string; // 团队成员
groupId: string; // 成员组
orgId: string; // 组织
}>;
getCollaboratorId(utils.ts)按 tmbId || groupId || orgId 取出唯一标识。协作者列表接口返回的 CollaboratorListType 包含两类数据(collaborator.ts):
clbs:最终生效协作者(子资源自身 + 继承合并后的结果);parentClbs:父级协作者(用于前端展示"来自父级"的继承态提示)。
8.2 collaborator/list 与 collaborator/update
collaborator/list:返回clbs(最终生效协作者)与parentClbs(父级协作者),前端据此展示最终权限与继承来源。collaborator/update:更新协作者,清单列出的校验规则:- 需要
ManagePermissionVal(管理权限才能管理协作者,比更新资源本身的WritePermissionVal更严格); - 不能修改自己的权限(防自抬权限);
- 非 owner 不能修改管理员权限(保护管理角色边界);
- 继承冲突时自动断开继承:当更新的协作者同时也是父级协作者、且修改会与父级权限冲突时,需断开
inheritPermission,转为显式协作者快照。对应工具函数即checkRoleUpdateConflict(utils.ts),其判定逻辑是:遍历发生变化的协作者(getChangedCollaborators输出),若某协作者同时存在于父级且其变更位与父级权限有交集((changedClb.changedRole & parent.permission) !== 0)或被删除,则判定为冲突。
- 需要
8.3 Owner 转移(changeOwner)
清单要求(如适用)实现 changeOwner 接口,且:
- 需要
OwnerPermissionVal(只有现任 owner 能发起转移); - 更新资源表的
tmbId为新 owner; - 根资源断开继承(转移后资源脱离父级继承关系,避免权限来源混乱);
- 修正权限记录(旧 owner 的权限记录与新 owner 的权限记录需同步修正)。
清单"最终检查"中对应的验证点是:"Owner 转移后旧/新 owner 权限记录正确"——转移后旧 owner 不应再保留 Owner 级权限,新 owner 应获得完整 Owner 权限。
8.4 审计日志
企业版要求为以下动作记录审计日志:更新协作者、删除协作者、Owner 转移、恢复继承(如有)、移动资源(如有)。这些日志是权限问题排查与合规审计的基础。
九、前端接入:组件、API 与继承态提示
清单对前端的要求如下:
| 项目 | 说明 |
|---|---|
| 协作者列表 API 调用 | 对接 collaborator/list,渲染 clbs 与 parentClbs |
| 协作者更新 API 调用 | 对接 collaborator/update,处理冲突回退 |
| Owner 转移 API 调用(如有) | 对接 changeOwner,转移前二次确认 |
| 权限配置弹窗 / 协作者管理组件 | 复用或实现资源权限配置 UI |
| 继承态提示 UI(如有) | 展示"权限来自父级"的标识,区分显式协作者与继承协作者 |
| 恢复继承入口(如有) | 提供断开继承后的恢复操作入口 |
前端展示的"最终权限"必须与后端实际鉴权一致(清单"最终检查"最后一条),这也是 PermissionSchema 序列化字段存在的意义——前后端共用同一套 hasReadPer / hasWritePer / hasManagePer / isOwner 语义。
十、测试:单元测试与集成测试
清单对测试提出了明确要求,分为两层:
10.1 单元测试
- Permission 类与角色映射:验证
RolePerMap展开正确,如 read 角色 → 仅 read 权限、write 角色 → write+read、manage 角色 → 全低 3 位; getTmbPermission优先级逻辑:验证团队内成员默认权限、资源显式权限的合并优先级;- 继承型资源的父子权限合并:验证
sumPer(folderPer, myPer)的结果与mergeCollaboratorList的位或合并行为(含父级 Owner 降级为 Manage 的特殊分支)。
10.2 集成测试
- 主要 API 的权限边界(读/写/管理分别能访问哪些接口);
- 删除是否要求 owner(而非 manage);
- 移动与继承恢复逻辑;
- 协作者更新冲突处理(继承冲突自动断开);
- Owner 转移后权限记录正确性。
仓库的测试基建位于 packages/service/test、packages/global/test 与 test,采用 vitest,可通过 pnpm vitest 系列命令运行(见根目录 package.json 与各包的 vitest.config.ts)。
十一、最终检查(上线前核对)
清单末尾的最终检查项是上线前的验收清单,每一项都对应一个易错点:
- 删除要求 owner,而不是 manage:务必在删除接口使用
OwnerPermissionVal,并编写对应集成测试锁定行为; - group / org 协作者按预期生效:验证
groupId、orgId两类协作者与tmbId一样参与权限合并与展示; - 继承断开后生成正确的显式协作者快照:断开继承时基于当前生效协作者固化快照,避免权限丢失;
- 移动资源后子树权限同步:父目录变更后子树内所有资源的生效权限即时更新;
- Owner 转移后旧/新 owner 权限记录正确:旧 owner 不再持有 Owner 权限,新 owner 完整接管;
- 前后端展示的"最终权限"与后端实际鉴权一致:以
clbs为准,前端仅作展示,杜绝"显示有权限、实际无权限"或反向的界面误导。
十二、接入新资源的推荐实施顺序
综合清单与源码,一个典型的新资源权限接入流程可归纳为:
- 设计判断:回答六个问题,明确 owner / team / 协作者 / 层级 / 继承 / 转移的需求范围;
- 权限定义:注册
PerResourceTypeEnum,创建constant.ts(RoleList、RolePerMap、PerList、DefaultRoleVal)与controller.ts(Permission 子类); - 资源 Schema:补齐
teamId、tmbId、parentId、inheritPermission字段; - 鉴权函数:实现
auth{Resource},含继承场景的父级权限合并; - API 接入:按"读/写/Owner"矩阵为各接口挂接鉴权;
- 继承逻辑(如适用):folder 列表、创建复制、移动同步、
resumeInheritPermission; - 协作者与企业版能力(fastgpt-pro):
collaborator/list、collaborator/update、changeOwner、审计日志; - 前端:协作者组件、继承态提示、恢复继承入口;
- 测试与验收:单元 + 集成测试覆盖权限边界,对照最终检查清单逐项核销后上线。
该清单本身可直接打印作为上线核对卡(checklist.md 开头注明"上线前核对清单,可打印使用"),配合本仓库的源码(权限核心、权限常量、鉴权实现)即可完成一次完整、可验证的资源权限接入。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0632
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00