# Multi App Tenant Architecture 本文定义 `hyapp-server` 同时服务多个 App 时的租户边界。当前后端不再默认只服务单个 App;`popoo`、`lalu`、`siya` 这类产品都必须先解析成内部 `app_code`,再进入用户、房间、钱包、活动和 host 业务链路。 ## Core Rule `app_code` 是后端内部租户键。客户端包名、Bundle ID、渠道名和展示 App 名都不能直接作为业务表隔离键。 首个内置映射: | package_name | platform | app_code | status | | --- | --- | --- | --- | | `com.org.laluparty` | 任意或空 | `lalu` | `active` | | `com.app.huwaa` | 任意或空 | `huwaa` | `active` | | `com.chat.fami` | 任意或空 | `fami` | `active` | 新增 App 时,只新增 `apps` 注册表记录和对应 App 的基础配置/种子数据,不新建一套服务,也不复制业务代码。 ## App Registry `apps` 表由 `user-service` 持有,放在 `hyapp_user` 库: ```sql apps( app_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, app_code VARCHAR(32) NOT NULL, app_name VARCHAR(128) NOT NULL, package_name VARCHAR(191) NOT NULL, platform VARCHAR(32) NOT NULL DEFAULT '', status VARCHAR(32) NOT NULL, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, UNIQUE KEY uk_apps_code (app_code), UNIQUE KEY uk_apps_package_platform (package_name, platform) ); ``` `gateway-service` 可以通过包名和平台解析 `app_code`,也可以校验显式传入的 `app_code`。解析入口是 `user-service.AppRegistryService.ResolveApp`。 ## Request Flow ```mermaid sequenceDiagram participant C as Client participant G as gateway-service participant U as user-service participant R as room-service / wallet-service / activity-service C->>G: HTTP request + package/platform/app_code header G->>U: ResolveApp(package_name, platform, app_code) U-->>G: active app_code G->>G: write request context and JWT claims G->>R: gRPC RequestMeta.app_code R->>R: all reads and writes are scoped by app_code ``` Header 约定: | Header | Meaning | | --- | --- | | `X-App-Code` / `X-HY-App-Code` / `App-Code` | 已知内部 `app_code` | | `X-App-Package` / `X-Package-Name` / `X-App-Bundle-ID` / `X-Bundle-ID` | 客户端包名或 Bundle ID | | `X-App-Platform` / `X-Platform` | `android` 或 `ios` | 已登录请求以 JWT 里的 `app_code` 为准。未登录登录/注册请求允许通过包名解析;解析成功后,`user-service` 签发的 token 必须带同一个 `app_code`。 ## App Scoped IDs 对客户端暴露的用户长 ID 和房间长 ID 都带 App 前缀,格式为: ```text _ ``` 例如 `com.org.laluparty` 解析到 `lalu` 后,新用户的 `user_id` 是数值型主键,新房间的 `room_id` 仍按 App 生成 `lalu_`。服务间主关联使用 `users.user_id`,JWT 只携带数值型 `user_id`,gateway 直接把该 ID 透传给后端 RPC。 用户短号 `display_user_id` 按 App 独立发号,首个号段从 `163000` 开始。创建房间时 `room_short_id` 等于创建者当前短号,用于客户端搜索、分享和展示。 ## Data Boundary 以下业务库表都必须有 `app_code`,并且查询、唯一键和幂等键都必须带 `app_code`: | Service | Database | Scope | | --- | --- | --- | | `user-service` | `hyapp_user` | `users`、登录身份、session、国家、区域、短号、host/Agency/BD 事实 | | `room-service` | `hyapp_room` | 房间元数据、房间列表、快照、命令日志、room outbox | | `wallet-service` | `hyapp_wallet` | 账户、交易、分录、wallet outbox、礼物价格 | | `activity-service` | `hyapp_activity` | 活动状态、事件消费幂等、activity outbox | 规则: - 跨 App 可以存在相同 `display_user_id`、相同三方 `provider_subject`、相同钱包 `command_id`;对外长 ID 自带 `app_code` 前缀,仍然要求落库唯一键带 `app_code`。 - 业务查询不能只用 `user_id`、`room_id` 或 `command_id`;必须同时带 `app_code`。 - outbox envelope 必须带 `app_code`,消费者按事件所属 App 落库和幂等。 - 默认 `lalu` 只用于本地开发和历史测试兜底;真实客户端请求必须能解析出明确 App。 ## Admin Boundary 后台管理后端使用独立 `hyapp_admin` 库,不把后台审计表写进 App 业务库。后台操作某个 App 的用户、区域、host、钱包或房间时,HTTP 请求必须显式携带或由后台上下文选择 `app_code`,再调用对应业务能力。 `hyapp_admin.admin_operation_logs` 可以在 `detail` 中记录 `app_code`、目标对象和变更摘要;不要为了审计把 admin 表复制到各 App 业务库。 ## Adding A New App 以新增 `popoo` 为例: 1. 在 `apps` 表插入 `app_code=popoo`、包名、平台和状态。 2. 为 `popoo` 初始化国家、区域、礼物价格、登录 provider/Firebase 配置。 3. 客户端请求携带包名和平台,gateway 解析到 `popoo`。 4. 注册、房间、钱包、活动、host 数据自动按 `app_code=popoo` 写入现有表。 新增 App 不需要拆新微服务;只有当业务规则、部署生命周期或数据合规要求必须强隔离时,才考虑单独部署一套服务集群。