D02 租户与组织域 · 设计文档

本域总览见《账号计费中心 · 结构大纲》。本文为该域完整设计:定位与边界 / 角色与依赖 / 数据模型 / 核心流程 / 接口契约 / 关键约束与验收标准。


1. 定位与边界

定位:工作区(租户)与成员归属的管理者。回答三个问题——「资源与费用归属谁」(工作区)、「谁可以操作」(成员与角色)、「用量如何切分」(组)。

核心概念

边界

kind 语义(本域最关键的口径)

kind 语义 可变操作
personal 只读身份锚点:kind 不可变、不可改名、不可邀请、不可退出、不可注销、不可转让;唯一成员 = 本人 Owner 无
company 可变租户 改名 / 联系方式 / 邀请 / 成员管理 / 组管理 / 转让 / 余额转回 / 注销

2. 角色与依赖

组织角色(三级):owner > admin > member。组织恒有主——Owner 唯一,且不可被移除、不可主动退出。

组内角色(两级):member / admin(组管理员),与组织角色相互独立:组织 Member 可以是某组的管理员;组管理员不因组内身份获得组织级权限。

能力矩阵

操作 Owner Admin Member
查看工作区列表 / 组织信息 ✓ ✓ ✓
编辑企业资料 / 邀请成员 ✓ ✓ ✗
普通成员角色升降 ✓ ✓ ✗
Admin 角色变更 / 移除 Admin ✓ ✗ ✗
建组 / 停用启用 / 设组额度 ✓ ✓ ✗
本组组管理员 ✗(除自身组) ✗(除自身组) 仅可调本组普通成员
转让 Owner / 余额转回 / 注销企业 ✓ ✗ ✗
退出组织 ✗(须先转让) ✓ ✓
组织审计查询 ✓ ✓ ✗

上游依赖

下游被依赖(谁引用本域)

引用方 引用内容 语义
钱包与账务域 工作区 → 钱包 1:1 工作区是计费与资金归属主体
订购与支付域 工作区、组 → 账户访问密钥 密钥归属某工作区,消耗归因落到某组
计量与计费域 工作区、组、成员(弱引用) 用量归属三层切分:工作区 / 组 / 成员
发票域 工作区 发票主体与抬头归属
风控与合规域 工作区 企业实名核验与风险事件归属
账户状态与准入域 工作区状态 工作区关闭触发对应钱包的准入状态发布
运营管理 组织投影(只读) 运营面按工作区视角查看成员与组,仅读不写

3. 数据模型

3.1 域内关联总览

用户的成员关系属主工作区邀请(成员关系前置态)工作区下的组组织审计自建组成员

users

organization_memberships

uuid

id

PK

uuid

user_id

FK

uuid

organization_id

FK

string

org_kind

冗余 kind,随写入同步

string

role

owner / admin / member

timestamp

last_active_at

切换器排序依据

organizations

uuid

id

PK

string

kind

personal / company,不可变

string

name

在营态唯一,closed 释放

string

credit_code

仅企业,在营态唯一

string

status

active / closed

string

real_name_status

none / pending / verified

string

identity_level

L0 / L1 / L2

uuid

created_by

FK

organization_invitations

uuid

id

PK

uuid

organization_id

FK

string

contact_type

email / phone / link

string

contact

链接邀请为空串

string

role

入组后的角色

string

code_hash

库内不存明文

string

status

pending / accepted / revoked / expired

uuid

invited_by

FK

uuid

accepted_user_id

FK

timestamp

expires_at

text

code_encrypted

加密回显,仅待接受行

groups

uuid

id

PK

uuid

organization_id

FK

string

name

工作区内唯一

string

description

boolean

is_default

每工作区至多一个

string

status

active / disabled

jsonb

quotas

多周期额度,缺省周期=不限

int

rpm_limit

阶段三启用

int

tpm_limit

阶段三启用

smallint

alert_threshold

organization_audit_logs

bigint

id

PK

uuid

organization_id

FK

uuid

actor_user_id

不留外键,保审计不被级联删

string

action

group.create / member.assign 等

string

target_type

uuid

target_id

jsonb

detail

string

ip

group_members

uuid

id

PK

uuid

group_id

FK

uuid

user_id

FK

string

role

member / admin

uuid

added_by

FK

图 1

默认组的成员是派生视图(= 工作区全体成员),不落 group_members 行——从模型层消灭「移出唯一组」的边界问题。

3.2 域间引用

弱引用:仅归属列,无外键弱引用:仅归属列,无外键

organizations
工作区

钱包与账务域
customers(工作区 1:1 钱包)

发票域
customer_invoices、invoice_applications

风控与合规域
org_real_name_verifications、risk_events

订购与支付域
account_api_keys(工作区归属)

groups
组

订购与支付域
account_api_keys.group_id(消耗归因落组)

计量与计费域
usage_daily 的工作区维度

图 2

3.3 表清单(6 表)

表 职责 关键字段
organizations 工作区(租户)主体,个人 / 企业同构 kind / name / credit_code / status / real_name_status / identity_level / created_by
organization_memberships 用户 × 工作区的成员关系与角色 user_id / organization_id / org_kind / role / last_active_at
organization_invitations 邀请(成员关系前置态) contact_type / contact / role / code_hash / status / expires_at / accepted_user_id / code_encrypted
groups 额度归因与准入执行锚点 organization_id / name / description / is_default / status / quotas / rpm_limit / tpm_limit / alert_threshold
group_members 自建组成员(默认组不落行) group_id / user_id / role / added_by
organization_audit_logs 组织视角留痕,Owner / Admin 可查 organization_id / actor_user_id / action / target_type / target_id / detail / ip

3.4 设计约束

  1. kind 不可变:不存在改写 kind 的路径,个人 ↔ 企业之间没有翻转操作。
  2. 在营态唯一:名称仅对在营工作区唯一、统一社会信用代码仅对在营企业唯一;已关闭工作区释放名称与执照,同一执照可重新注册。预检与落库双段口径一致(并发兜底)。
  3. 组织恒有主:DB 层以「在营 owner 唯一索引」兜底,任何并发路径都至多一行 owner。
  4. 不重复加入:(用户, 工作区) 唯一,作为并发邀请竞态的兜底。
  5. 邀请码零明文:库内只存哈希用于比对,另存加密副本仅用于待接受行的列表回显;明文只在创建响应中出现一次。
  6. 默认组不可破:每工作区至多一个默认组,不可停用、成员不可直接设置。
  7. 组名工作区内唯一;组数上限 50(含默认组)。
  8. 审计只增:审计行与业务变更同事务写入,不提供改写或删除路径。

4. 核心流程

4.1 个人注册即建立工作区

验证码票据一次性消费

建用户 + 联系方式凭据

建个人工作区(kind = personal)

落 Owner 成员关系

建钱包:余额从零,不发赠金

建默认组(成员派生自全体成员)

协议同意留痕:只增历史表 + 用户行当前快照

调度账户状态发布(新账号准入语义)

图 3

4.2 创建企业

无有占用通过占用通过是(并发注册竞态)否

发起创建企业

存在个人工作区

404 无个人工作区

统一社会信用代码是否被在营企业占用

409 执照已被占用

名称是否被在营工作区占用

409 名称已被占用

建企业工作区(kind = company)

落 Owner 成员关系

建企业钱包:从零开始,不迁移个人余额、不发赠金

建默认组,写组织审计

落库是否撞在营唯一索引

就地转 409,不抛未捕获异常

创建成功,上下文切到该企业工作区

图 4

4.3 邀请与入组

受邀人通知通道账号中心组织管理者受邀人通知通道账号中心组织管理者邀请链接不绑定联系方式——凭令牌本身入组alt[抢占失败(并发用码 / 已撤销 / 已过期)][抢占成功]alt[已在该工作区][未加入]发起邀请(批量邀请 或 邀请链接)建邀请:7 天有效、一次性;库内仅存哈希 + 加密回显副本发送邀请(best-effort,发送失败不回滚邀请)邮件 / 短信 / 链接凭码入组(已有账号)或 注册即入组(新账号)校验:工作区须在营;联系方式须与邀请目标一致409 重复加入原子抢占:待接受 → 已接受,判定与置位收敛在同一条更新里失效语义(过期、已撤销、已接受分别可辨)建立成员关系,角色随邀请
图 5

4.4 成员角色变更与移除

是否OwnerAdmin是否(Admin / Owner)是否

发起角色变更 / 移除成员

目标是否本人

400 不能对自己操作

操作者角色

可对任意成员升降 Admin ↔ Member

目标是否普通成员

403 角色不足

目标是否 Owner

拒绝:组织恒有主,须先转让

生效,写组织审计

图 6

4.5 组与组配额

是否占用通过是否组织 Owner / Admin本组组管理员

建组(Owner / Admin)

组数是否达上限 50(含默认组)

400 组数超限

组名(工作区内)是否已被占用

400 组名已占用

建组:默认无额度 = 不限;创建即生效

初始成员仅普通成员,且必须已是本工作区成员

设置组成员:全量替换

是否默认组

400 默认组成员为派生视图,不可设置

操作者

可设成员,并可授予 / 撤销组管理员

仅可调本组普通成员,不可授予 / 撤销组管理员

设置组额度:限额必须带周期;显式清空 = 不限

图 7

4.6 所有权转让

成员关系数据账号中心现任 Owner成员关系数据账号中心现任 Owneralt[已易主 /自己已降级(并发双击的第二次请求)][重验通过]两步顺序不可颠倒——反过来会在同一事务内先撞「在营 owner 唯一」占用发起转让,指定受让成员行锁锁定当前 owner 行以库内现值重验操作者仍为 owner403 角色不足校验受让方:须为同工作区现有成员且非本人先落库:操作者降为 Admin再落库:受让方升为 Owner转让成功,组织恒有主
图 8

4.7 企业余额转回个人钱包

钱包锁域账号中心Owner钱包锁域账号中心Owneralt[负余额 / 欠费标记][无债务]发起余额转回(注销企业前的资产出口)校验:仅 Owner、须为企业工作区、工作区须在营按标识升序锁定双方钱包(与扣费共用同一锁域,升序防死锁)持锁后重读双方余额(避免旧快照污染流水余额位)409 存在未清偿债务,先清偿充值类额度整行迁回个人钱包(剩余口径与有效期不变)赠金类额度就地作废(不留可回收资产)现金:个人侧原子自增入账,企业侧清零两侧各落一行对偶流水,共用同一 ref完成;重复点击或重试自然空跑,不产生二次入账
图 9

4.8 注销企业

否是是否否是

发起注销(仅 Owner)

kind = company 且在营

拒绝:个人工作区不走此路径

存在未清偿债务

409 先清偿债务或运营核销

额度剩余与现金余额均为零

409 先做余额转回

成员名下组织内账户访问密钥全部失效

向全体成员发站内信(告知工作区已关闭)

解除全部成员关系

工作区置 closed、钱包置 closed —— 软关闭,流水与审计保留

提交后同步发布账户状态(封锁)

名称与执照释放,可凭同一执照重新注册

图 10

4.9 工作区上下文解析(切换器与请求鉴权)

未携带标识携带标识否(已关闭 / 不可用)是否是

请求携带工作区标识

能否解析出成员关系

回退:只取企业成员关系(个人工作区无工作台能力)

工作区是否在营

自愈语义:前端清选择、回退其它工作区

角色是否满足该接口的最低要求

角色不足语义

计费与数据对象 = 该工作区

切换时刷新最近活跃时间;切换器按最近活跃降序排列

图 11

5. 接口契约

5.1 组织与工作区(/api/account)

方法 路径 语义
GET /organizations 我的工作区列表:个人工作区居首 + 已加入企业,按最近活跃降序
POST /organizations 创建企业工作区(Owner 由创建人自动获得)
GET /organization 组织信息 + 成员列表(Member 可读,联系方式脱敏;响应带 kind 供前端分流)
PUT /organization 编辑企业资料(Owner / Admin;个人工作区拒绝)
PATCH /organization/touch 工作区活跃心跳(切换时调用,更新成员级最近活跃时间)
DELETE /organization 注销企业(仅 Owner;软关闭)
POST /organization/leave 退出组织(Admin / Member,可指定工作区)
POST /organization/ownership-transfer 转让 Owner(受让方须为同工作区成员)
POST /organization/balance/transfer 企业余额转回个人钱包(仅 Owner)

5.2 成员管理(/api/account)

方法 路径 语义
PUT /organization/members 成员角色变更(Owner 可调任意;Admin 仅可升降普通成员)
DELETE /organization/members 移除成员(Admin 仅可移除普通成员;不能移除自己 / Owner)
PUT /members/{membership_id}/role 角色变更(按成员关系定位的历史入口,内部换算后走同一业务校验)
DELETE /members/{membership_id} 移除成员(同上历史入口)

5.3 组管理(/api/account)

方法 路径 语义
GET /groups 组列表:Owner / Admin 见全部,普通成员见默认组 + 所在组
POST /groups 建组(组数上限 50、组名工作区内唯一、初始成员可选)
PATCH /groups/{group_id} 改名 / 描述 / 限速(显式 null = 清空不限)
POST /groups/{group_id}/disable 停用组(默认组不可停用;幂等)
POST /groups/{group_id}/enable 恢复启用
GET /groups/{group_id}/members 组成员:默认组 = 全体成员派生,自建组 = 组内成员(工作区内只读)
PUT /groups/{group_id}/members 全量替换组成员(默认组拒绝;组管理员不可授予组管理员)
GET /groups/{group_id}/quota 组额度与限速查看
PUT /groups/{group_id}/quota 组额度设置(限额必须带周期;显式清空 = 不限)

5.4 邀请(/api/account)

方法 路径 语义
POST /invitations 批量邀请(邮件 / 短信;7 天有效;通知 best-effort)
POST /invitations/link 创建邀请链接(一次性令牌,随响应返回由前端拼链)
GET /invitations 邀请列表(待接受 / 已接受 / 已撤销 / 已过期;过期项就地标记)
POST /invitations/{invitation_id}/revoke 撤销邀请
POST /invitations/accept 已有账号凭码入组
POST /register/organization 企业注册(注册即建企业工作区,创建人 Owner)
POST /register/invite 受邀注册(注册即入组,一次请求完成建号与入组)

5.5 内部组织投影(/internal/organizations)

方法 路径 语义
GET /internal/organizations 工作区列表:含成员标识与 Owner 联系方式摘要,供运营面按工作区投影
GET /internal/organizations/{org_id} 工作区详情:成员(联系方式脱敏)+ 组摘要 + 钱包锚点标识
GET /internal/organizations/{org_id}/groups 工作区下全部组的额度口径(与单组查询同字段)

6. 关键约束与验收标准

6.1 约束

  1. kind 不可变、锚点受保护:个人工作区上任何企业维度操作(改名 / 邀请 / 成员 / 组 / 转让 / 注销 / 余额转回)一律拒绝,且系统不提供 kind 翻转路径。
  2. 组织恒有主:Owner 唯一、不可被移除、不可退出;转让为唯一变更路径;并发转让由「行锁 + 在营 owner 唯一索引」双兜底。
  3. 单次消费原子化:邀请接受与受邀注册的抢占以条件更新完成,并发凭同一邀请至多一人成功,且失败方不产生半成品账号。
  4. 注销先清零:存在债务或余额未处理时拒绝注销;注销为软关闭,保留资金流水与审计。
  5. 资金迁移对偶留痕:额度迁出 / 迁入两侧各落一行只增流水并共用同一 ref;额度行改挂保留,不做物理合并。
  6. 并发互斥与锁序:钱包类变更(扣费 / 充值 / 余额转回)共用同一钱包锁域;多钱包场景按标识升序加锁,防死锁。
  7. 两类台账分离:组织审计按工作区聚合给 Owner / Admin;本人安全日志按用户聚合(归账号与身份域)。二者不混用。
  8. 投影只读且最小化:运营面组织投影只读、字段白名单、联系方式脱敏;不提供经投影写组织的路径。
  9. 同一邀请至多一次有效:邀请过期即失效,过期状态在查询时就地收敛,不留「看起来仍待接受」的假状态。

6.2 验收标准

# 验收口径
1 在营期间重复注册同一名称 / 同一执照 → 409;工作区注销后,同一名称与执照可重新注册成功
2 并发两次「接受同一邀请」:仅一人成功,另一人失败,且成员关系只有一行
3 同工作区并发两次转让:仅一次成功;任何时刻 owner 行数 ≤ 1
4 余额转回:企业侧现金清零、个人侧按原子自增入账;两侧各有且仅有一行流水且 ref 相同;重试不产生二次入账
5 存在负余额或欠费标记时,余额转回与注销均返回 409
6 工作区仍有余额时注销返回 409;清零后可注销,注销后成员关系全部解除、成员名下组织内密钥全部失效
7 个人工作区上调用改名 / 邀请 / 成员管理 / 转让 / 注销 / 余额转回 → 拒绝(非 5xx)
8 组数达上限再建组 → 400;组名重复 → 400;默认组不可停用、不可直接设置成员
9 组管理员无法授予或撤销组管理员角色
10 邀请过期后接受 → 明确失效语义;列表查询将过期待接受项就地标记为已过期
11 普通成员查询组列表,只能看到默认组与自身所在组
12 内部投影接口无内部令牌不可访问;响应中联系方式为脱敏值;关闭态工作区可被列表感知
13 请求携带已关闭工作区标识时返回自愈语义(清选择回退),而非角色不足语义
14 创建企业后,个人钱包余额不变(无隐式内部转账)

6.3 待后续设计细化的开放项

  1. 运营面组织投影的增量同步机制(现为按页惰性拉取,全量拉取需评估上限与一致性口径)。
  2. 组级限速与告警阈值的执行时机与执行方(本域只存配置,执行在计量侧)。
  3. 企业实名等级 L1 / L2 的正式划分——待资金权限认证通道接入后按新证据重新划分。
  4. 成员离开工作区后的资产与密钥交接流程:是否引入「密钥归属变更」路径,或统一按失效 + 重建处理。