多租户

i18n 覆盖率 Dashboard

AuraBoot 在设计上就是多租户的。同一份部署能承载多个租户,并让它们的数据、配置与凭据保持隔离。多租户由平台的每一层强制落实,不是事后才加上去的。

租户模型

概念范围
Tenant最顶层的隔离边界;有自己的 users、roles、models 与文件
User属于且仅属于一个 tenant;token 中编码了 tenantId
Model / page / command在 tenant 范围内编写;platform 范围是一个特殊的 "system" tenant
Record每张动态表上都带 tenant_id

跨租户访问在设计上不可能发生。一个解析到 tenant A 的请求无法读写 tenant B 的记录,连动态查询 API 都不行。

何时使用

  • 承载多个客户组织的 SaaS 部署。
  • 想要在业务单元之间强隔离的单一组织。
  • Demo 与试用环境,不能让数据泄漏到生产 tenant。

工作原理

平台为每个请求从 JWT 解析出一个 MetaContextMetaContext 携带 tenantIduserIduserPidusernameroleIds;环境、成员与数据权限旁路等扩展状态(environmentIdmemberId、各类 bypass 标志)存放在独立的 ThreadLocal 中。所有下游代码——包括 DynamicDataMapper、Kafka producer 与文件存储网关——都从 MetaContext 读取,并在缺失时拒绝工作。

后台任务(Kafka 消费者、定时任务)必须显式构造一个 MetaContext,system user id 用 0L;不这么做会让 ab_data_change_log 的 insert 违反 NOT NULL 约束并回滚事务。

数据隔离

强制方式
数据库每张动态表都有 tenant_id 列;所有查询按它过滤
RepositoryDynamicDataMapper 拒绝缺少 tenantId 参数的查询
行策略与行级 permission 复合(见 安全加固
缓存Redis key 加前缀 aura:t:<tenantId>:
文件MinIO 对象 key 加前缀 t/<tenantId>/...

模型命名空间

Model 按 tenant 划分命名空间。物理表名遵循 mt_<modelCode> 的模式,跨 tenant 共享,由 tenant_id 列强制隔离。一个 tenant 无法枚举或查询另一个 tenant 声明的 model。system tenant 拥有平台级表,例如审计日志 ab_audit_trail

文件分区

auraboot-files/
  t/tenant_001/
    uploads/2026/05/28/<ulid>.pdf
    exports/qualified-leads-2026-05.csv
  t/tenant_002/
    uploads/...

一个 tenant 的预签名 URL 由只能解析其前缀下对象的 key 签出。即便 URL 泄漏,也不能读到别的 tenant 的文件。

凭据范围

通过 ConnectorCredentialResolvercr_csp_connector_pid 解析的 connector 凭据是按 tenant 划分范围的。tenant A 调用外部 CRM 的 API key 绝不进入 tenant B 的请求上下文。不同 tenant 使用同一个 cr_csp_connector_pid 值会解析到不同的 secret。

Kafka topic 命名

<env>.<tenant-or-shared>.<domain>.<event>
模式示例范围
共享 topic,tenant 在 payload 里prod.shared.record.updated默认;tenant id 是 payload 字段
每租户独立 topicprod.t.tenant_001.export.requested仅当顺序或吞吐要求按 tenant 分区时使用

优先用共享 topic 模式。每租户 topic 会成倍放大 broker 资源开销;只为那些负载值得这份开销的 tenant 保留它。

配置

aura:
  tenancy:
    default-tenant: "tenant_default"
    enforce-context: true        # 拒绝任何缺 tenant 的请求
    background-system-user-id: 0
  storage:
    file-prefix: "t/${tenantId}/"
  cache:
    key-prefix: "aura:t:${tenantId}:"
  kafka:
    topic-pattern: "${env}.shared.${domain}.${event}"
    per-tenant-topics: []        # 显式 opt-in 列表

新 tenant 由已登录用户的租户选择 / 注册流程创建:前端调用 TenantSelectionController/process 端点提交一个 TenantSelectionRequest,服务层 TenantApplicationService.createTenantForUser(request, user) 在当前用户身份下落地新 tenant(最终走 TenantService.createTenant)。平台会创建 tenant 行,并把该用户接入新 tenant(建立 member、撒入默认角色)。

验证

  • 用 tenant A 的 token 调 /api/<pageKey>/list,tenant B 的数据返回 0 行。
  • 限定为 tenant A 的 MinIO 预签名 URL,对 tenant B 的前缀返回 403。
  • 后台任务在每个动作上都打印 tenant.id;缺值时 fail-fast 报错。
  • 删除某个 tenant 会清理其记录、文件与凭据,其他 tenant 不受影响。
  • 任何按 tenant 维度的指标序列都是有界的(每个活跃 tenant 至多一条时间序列),不存在无界 label 基数。

相关