把单人工具升级成团队协作平台,绕不开的不是代码,而是三件事:谁可以干什么、状态如何同步、出问题怎么回滚。个人版所有数据归一个人,加入成员之后,权限、任务分配、审校流程、计费归属、审计留痕全要重新设计。我先后对比过按仓库隔离和按组织隔离两种模型,最终选了组织维度——同一个团队经常要共享术语表和模型配置,按仓库隔离会把公共资产复制好几份,同步起来没完没了。下面把设计拆成组织成员、角色、任务协作、审校、计费、审计几个模块,逐个说明取舍。
一、目标
目标不说清楚,后面每个模块都会跑偏。我们立了四条硬约束:多人共用一个工作区、角色清晰、任务可分配可追溯、计费按组织审计按人。这四条里难的是”审计按人”——它要求每个操作都记录操作者,团队一大,日志量涨得很快,存储和查询都要提前设计。
- 多人共用一个工作区;
- 角色清晰:管理员、翻译、审校、只读;
- 任务可分配、可协作、可追溯;
- 计费按组织维度,审计按人。
二、组织与成员
组织模型是后面所有权限判断的根基。一个用户可能同时属于两个团队,一个组织也可能挂多个仓库安装。我对比过”用户直接挂在仓库下”和”用户挂在组织下”两种模型:前者看着简单,但术语表、提示词这些公共配置没有归属,每个仓库各存一份,改起来到处同步。最终选了 User—Membership—Organization 三层模型,公共资产统一放到组织层。数据关系用一张小图就能说清楚:
User ──*── Membership ──*── Organization
Organization 1—* Installation 1—* Repository
Membership 是关联表,存 userId、orgId、role 三个字段,权限判断全靠读它。一个成员可以同时属于多个组织,所以是多对多关系,别做成一对多,否则第二个团队根本加不进去。
三、角色模型
角色定得过粗,权限收不拢;定得过细,成员自己都记不住。我调研了一圈协作工具后定了五档:owner 管钱和仓库,admin 管成员和任务,translator 干活,reviewer 把关,viewer 只读。下面从权限范围对比这五档:
| 角色 | 权限 |
|---|---|
| owner | 管理组织、成员、计费、仓库 |
| admin | 管理成员、术语表、任务 |
| translator | 创建任务、翻译片段、提交 PR |
| reviewer | 审核 PR、强制合并、回退 |
| viewer | 只读 |
角色可以叠加——一个人同时是 translator 和 reviewer 很常见,他翻译别人的片段,也审校别人的片段,两条链路互不冲突。实现时把 role 存在 membership 表里,而不是用户表,因为同一用户在不同组织的角色可能不一样。
四、任务协作
任务从”个人独占”变为”可分配”后,怕的是两个人同时改一个片段。我们在片段维度加了认领机制:谁先认领谁改,未认领的自动排队;翻译中的片段加锁,避免并发写;完成片段后自动通知审校,审校在 PR 上评论会触发任务回到翻译者手里。整条链路用状态机驱动,每个片段的状态变更都记日志,出问题可以倒查。
- 任务可指派给一个或多个成员;
- 片段维度可认领,未认领自动排队;
- 翻译中加锁,避免两人同时改;
- 完成片段后自动通知审校;
- 审校可在 PR 上评论,触发修改。
五、协作审校
审校环节直接复用 GitHub 的 PR Review 流程,这是当初比较省事的一个决定——不需要自己再造一套评论系统。强制要求至少 1 名 reviewer 通过才能合并,reviewer 可以拒绝并附理由,任务自动回到 translator。自动合并仍受分支保护限制,团队可以自定义规则,比如大仓库要求两人通过。
- PR Review 流程与 GitHub 同步;
- 强制要求至少 1 名 reviewer 通过;
- reviewer 可拒绝并附理由,任务回到 translator;
- 自动合并仍受分支保护限制,团队可自定义。
六、共享术语表
术语表从个人级升到组织级后,麻烦的是变更控制。改一个术语,可能影响所有正在翻译的任务。我们走了审批流程:修改 → 提议 → 多人 review → 通过。旧术语保留 30 天回滚窗口,发现改错可以立刻还原。这套流程一开始有人嫌重,但真出过一次术语误改导致整批翻译返工后,没人再提了。
- 组织级术语表覆盖个人级;
- 术语变更走审批:修改 → 提议 → 多人 review → 通过;
- 旧术语保留 30 天回滚窗口。
七、计费
计费按组织还是按个人,差别很大。个人版每个成员各买各的额度,团队要凑单、要分摊,对账麻烦。改成组织计费后,配额等于个人配额之和加公共池,团队统一采购;用量看板按成员、按任务类型展示,谁用得多一目了然,也为后续的成本分摊提供了依据。
- 按组织计费而非个人;
- 配额共享:组织配额 = 个人配额之和 + 公共池;
- 用量看板:按成员、按任务类型展示。
八、审计
审计从第一天就要有,后面补会漏掉一堆历史操作。所有操作记入 AuditLog,字段就四个:who、when、what、target。删除术语、移除成员这类关键操作要求二次确认,避免误触。审计日志不可删,定期归档到冷存储,按合规要求至少保留一年。
- 所有操作记入
AuditLog:who、when、what、target; - 关键操作(删除术语、移除成员)需二次确认;
- 审计日志不可删,定期归档到冷存储。
九、通知
通知容易做过头。我最初想做全量实时通知,结果成员被消息轰炸,纷纷关掉提醒。后来收敛成:只有任务关键节点才通知——分配、完成、失败、合并——通知频率可配置,实时、每日摘要、关闭三档。邮件、Slack、Discord Webhook 都接上,但默认关闭,由团队自己决定用哪个渠道。
- 邮件、Slack、Discord Webhook 集成;
- 任务关键节点通知:分配、完成、失败、合并;
- 通知频率可配置:实时、每日摘要、关闭。
十、UI 变化
前端改动不大,细节不少。顶部导航加”组织切换”下拉,任务列表支持按成员过滤,仪表盘加团队指标:完成量、平均审校时长、术语命中率。这三个指标选了很久,因为它们能回答”团队这周干得怎么样”,别的指标要么看不懂,要么看了不行动。
- 顶部导航增加”组织切换”下拉;
- 任务列表支持”按成员过滤”;
- 仪表盘展示团队指标:完成量、平均审校时长、术语命中率。
十一、数据模型调整
数据模型是这次改动量大的部分。新增 Organization、Membership、Assignment、Review、AuditLog 等实体。迁移用 prisma migrate,先建表再改代码,保持向后兼容——老用户的数据不受影响,新用户走新流程,灰度一段时间再切全量。
新增实体:
- Organization
- Membership(user × org × role)
- Team(可选,用于大团队)
- Assignment(task × user × role)
- Review(task × reviewer × status)
- AuditLog
迁移用 prisma migrate,保持向后兼容。
十二、权限实现
权限校验集中在服务端中间件,前端只做展示,避免按钮藏了但接口没拦住。这段代码解决的核心问题是”这个人在这个组织里有没有这个角色”,每层接口进来先过 requireRole,不通过直接抛 403。
function requireRole(orgId: string, userId: string, roles: Role[]) {
const m = await prisma.membership.findFirst({
where: { orgId, userId },
});
if (!m || !roles.includes(m.role)) {
throw new ApiError(403, "forbidden");
}
}
这里有个易错点:中间件每次进接口都查库,任务列表这种高频接口扛不住。我们后来给 membership 表加了 (orgId, userId) 联合索引,并把角色读进请求上下文,问题就消失了。权限规则宁可写死也不要做成前端可配置,否则很容易绕过。
十三、回归与监控
功能多了,回归测试不能少。e2e 覆盖核心链路:组织创建 → 邀请成员 → 任务分配 → 审校合并。性能上,成员一多,列表查询必须分页加索引。告警盯着审计写入失败、配额耗尽、合并失败率上升——这三类问题出现任何一个,都说明团队协作流程在出问题。
- e2e:组织创建 → 邀请成员 → 任务分配 → 审校合并;
- 性能:成员多时列表查询要分页 + 索引;
- 告警:审计写入失败、配额耗尽、合并失败率上升。
十四、给同类项目的建议
几条经验。角色与权限是地基,先做对再做别的;计费按组织而非个人,省掉大量对账工作;审计从第一天就有,后面补成本高;通知频率要克制,否则成员会关掉所有提醒。设计文档写清楚每个模块的取舍,后面接手的同事才不会又踩一遍坑。
- 角色与权限是基础,先做对;
- 计费按组织而非个人;
- 审计从第一天就要有;
- 通知频率要克制,避免疲劳。
常见问题(FAQ)
Q1:团队协作要不要做实时多人编辑?
不建议。任务颗粒度大、状态机清晰,认领 + 异步协作已够。
Q2:组织创建是否自动绑定 GitHub Org?
不自动。让用户手动绑定,避免误绑。
Q3:审计日志保留多久?
按合规要求,建议至少 1 年。