## ADDED Requirements ### Requirement: 租户识别 mjava-pro SHALL 在请求入口识别租户上下文。Controller 入口 MUST 通过 `TenantInterceptor` 读 HTTP Header `X-Tenant-Id`。 #### Scenario: Header 缺失 - **WHEN** 请求缺少 `X-Tenant-Id` - **THEN** 返回 `401 { code: "TENANT_REQUIRED" }` #### Scenario: 租户不存在 - **WHEN** Header 存在但租户不在注册表 - **THEN** 返回 `403 { code: "TENANT_NOT_FOUND" }` #### Scenario: 识别成功 - **WHEN** tenantId 匹配注册表记录且启用 - **THEN** `TenantContext.set(profile)` 写入 ThreadLocal - **AND** 请求结束时在 finally 块 `TenantContext.clear()` ### Requirement: 异步任务传递 TenantContext MUST 在 @Async 线程池与 CompletableFuture 切换时正确传递,避免子线程拿不到租户凭据。 #### Scenario: @Async 线程池 - **WHEN** 通过 `@Async` 提交任务 - **THEN** 线程池 TaskDecorator 必须复制 TenantContext 到子线程 - **AND** 子线程开始执行时 `TenantContext.current()` 返回发起请求的租户 #### Scenario: 手动线程切换 - **WHEN** 代码显式切换线程(如 `CompletableFuture.supplyAsync(executor, ...)`) - **THEN** 必须使用 `TenantContext.propagate(ctx, runnable)` 包装 ### Requirement: Token 隔离 mjava-pro SHALL 使用带租户前缀的 UtilToken key,避免多租户间 token 互相覆盖。 #### Scenario: key 格式 - **WHEN** 调用 UtilToken 存取 - **THEN** key 格式统一为 `{tenantId}:{vendor}:{appKey}` - **AND** mjava-pro 代码 MUST 不使用无 tenant 前缀的 UtilToken 接口 #### Scenario: 无租户上下文场景 - **WHEN** 定时任务或启动时发起调用 - **THEN** 必须显式传 tenantId,或声明为 `SYSTEM` 伪租户 ### Requirement: 审计日志扩展 mjava-pro SHALL 在 `mjava-baseline §3.5` 字段基础上追加 tenantId,日志按租户分片。 #### Scenario: 按租户分片输出 - **WHEN** 请求进入 - **THEN** 日志输出到 `./log/{日期}/pro-{tenantId}.log` - **AND** MDC 含 `tenantId` 字段