客户项目上下文
正确消费 clientAssignments 与业务授权
clientAssignments 是 IWish Auth 对“当前内部员工正在服务哪些客户项目、承担什么职责”的统一表达。各业务 App 可以按需消费,不要求所有 App 都使用。
1. 数据来源
管理员独立创建客户项目,然后从 active 飞书员工目录选择成员,并为一个员工配置一个或多个项目职责、主责标记、平台范围和生效期。
创建客户项目不需要外部客户账号,也不需要先创建客户组织。
平台管理员通过 POST /v1/admin/client-projects 创建项目,通过 POST /v1/admin/client-team-assignments 维护员工职责。飞书身份就是统一内部身份,项目团队成员必须从已同步的 active 飞书员工目录选择。
2. Session 结构
{
"clientAssignments": [
{
"clientId": "00000000-0000-0000-0000-000000000401",
"clientCode": "acme",
"clientName": "Acme 出海项目",
"shortName": "Acme",
"roles": [
{
"assignmentId": "00000000-0000-0000-0000-000000000501",
"code": "ad_optimizer",
"name": "广告平台优化师",
"category": "advertising",
"isPrimary": false,
"platformScopes": ["google", "meta"],
"validFrom": "2026-07-01",
"validUntil": null
}
]
}
]
}
只返回项目和职责均为 active、当前日期在有效期内、员工仍为 active 的记录。同一项目的多个职责聚合到一个项目条目。
3. 推荐用法
- 为项目选择器提供默认可见项目。
- 在工作台展示当前员工的服务关系和职责。
- 缩小默认查询范围,减少误选客户。
- 根据
platformScopes预选 Google、Meta 等广告平台。 - 在创建任务或需求时自动填充项目负责人候选人。
4. 不能做什么
clientAssignments 不是权限凭证,不能替代 permission 或业务数据授权。下面的判断是错误的:
// 错误:绑定项目不代表拥有删除权限
if (session.clientAssignments.some((item) => item.clientId === clientId)) {
await deleteReport(reportId);
}
正确顺序:
const session = await auth.requirePermission(request, "reporting.report.delete");
const assignment = session.clientAssignments.find((item) => item.clientId === clientId);
await assertBusinessDataAccess({ user: session.user, assignment, clientId, reportId });
await deleteReport(reportId);
第一层是 IWish Auth App 权限,第二层是业务 App 自己的数据授权。两层都必须通过。
5. 空数据和外部账号
业务 App 必须把空数组视为正常状态:
- 某些内部岗位不参与任何客户项目。
- 新员工尚未分配项目。
- 外部邮箱账号的项目可见范围由业务 App 自身模型决定,IWish Auth 不会把外部账号自动变成内部项目团队成员。
不要因为 clientAssignments 为空就把用户当成未登录,也不要自动授予全部项目。
6. 缓存与变更
项目团队会动态调整。优先使用当前 App session 返回值;长任务应在执行关键写操作前重新解析 session。不要永久存储 clientAssignments 快照作为授权依据。