## multi-tenant-runtime ### REQ-MT-001 租户识别 - Controller 入口通过 `TenantInterceptor` 读 HTTP Header `X-Tenant-Id` - Header 缺失:返回 `401 { code: "TENANT_REQUIRED" }` - Header 存在但租户不在注册表:返回 `403 { code: "TENANT_NOT_FOUND" }` - 识别成功:写入 `TenantContext.set(profile)`;请求结束时 `TenantContext.clear()`(务必在 finally) ### REQ-MT-002 异步任务传递 - `@Async` 线程池必须通过 `TaskDecorator` 复制 TenantContext 到子线程(参考现有 `MdcTaskDecorator`) - `CompletableFuture` 手动切换线程时需显式 `TenantContext.propagate(ctx, () -> ...)` ### REQ-MT-003 Token 隔离 - `UtilToken` key 格式统一为 `{tenantId}:{vendor}:{appKey}` - `mjava-pro` 不得使用无 tenant 前缀的 UtilToken 接口 - 无租户上下文(如定时任务)显式传 tenantId,或声明为 `SYSTEM` 伪租户 ### REQ-MT-004 审计日志扩展 - `UtilHttp` 审计日志在 `mjava-baseline §3.5` 字段基础上追加 `tenantId` - 日志输出目标 `./log/{日期}/pro-{tenantId}.log`(按租户分片,方便定责) ## tenant-registry ### REQ-TR-001 注册源 - 租户配置存放于宜搭"应用表"(formUuid 配置在 `tenant.registry.formUuid`) - 字段约定见 `design.md` 表格 - 访问宜搭本身使用 `application-{profile}.yml` 配置的 `aliwork.appType` / `aliwork.systemToken`(这是 mjava-pro 自身的入口凭据,非租户凭据) ### REQ-TR-002 加载与缓存 - 启动时全量拉取一次,写入 `Map` - TTL:默认 600 秒(`tenant.registry.ttlSeconds` 可覆盖) - 过期后首次访问触发异步刷新,旧值继续可用直到新值就绪(stale-while-revalidate) - 热刷入口:`POST /api/pro/_admin/reloadTenant`(仅 dev profile) ### REQ-TR-003 敏感字段处理 - appSecret / systemToken 等字段在日志输出时必须脱敏(`***`) - 内存中允许保留明文(TenantProfile 字段直接 String,不引入加密存储以保持简单) ### REQ-TR-004 失效与禁用 - 宜搭应用表 `radioField_enabled=off` → 注册表移除该 tenantId - 请求携带已禁用 tenantId → 返回 `403 TENANT_DISABLED` ## 非目标明示 - 不定义 tenantId 命名规范(客户代号 / UUID / 数字 ID 均可,由运营决定) - 不定义 tenant-level feature flag(有需要另外提案) - 不定义多 tenant 同库的 row-level security(共享库方案下由业务 service 自行按 tenantId 过滤)