add account localization and switchable rooms

Centralize control and OBS copy in a shared TOML catalog, persist the selected locale per account, and broadcast language changes to component streams. Allow account owners to atomically switch their Bilibili room and restart the shared listener without changing component URLs.
This commit is contained in:
2026-07-18 23:28:05 -07:00
parent 53ae90ec13
commit f79852d8e6
35 changed files with 1946 additions and 472 deletions
+4 -2
View File
@@ -5,7 +5,7 @@
## 核心目标
- 每个账户固定绑定一个 Bilibili 直播间、一个 CookieCloud 来源和一条独立监听连接。
- 每个账户拥有一个可切换的 Bilibili 直播间、一个 CookieCloud 来源和一条独立监听连接。
- 组件不绑定或选择直播源;账户事件流会提供给该账户所有启用的组件实例。
- 平台原始命令先转换成稳定的领域事件,组件不直接依赖 Bilibili `CMD`。
- HTTP 会话、直播源、组件、OBS token 和实时通道均以租户为边界。
@@ -45,11 +45,12 @@ flowchart LR
| ---------------- | ------------ | ------------------------ | ----------------------------- |
| 用户、TOTP、会话 | PostgreSQL | 无 | Secret 加密,token 只保存摘要 |
| CookieCloud 凭据 | PostgreSQL | provider 构建期间解密 | 每账户一份,不返回浏览器 |
| 直播源和房间 | PostgreSQL | `SourceSupervisor` | 每账户固定一个房间和连接 |
| 直播源和房间 | PostgreSQL | `SourceSupervisor` | 每账户一个可切换房间和连接 |
| 组件实例与设置 | PostgreSQL | `InMemoryComponentStore` | 写入成功后刷新热路径缓存 |
| 礼物/表情目录 | Bilibili API | provider catalog | 刷新失败保留最近成功快照 |
| 实时消息 | provider | `EventHub` 有界广播 | 不作为业务持久化机制 |
| 点歌队列和评分 | PostgreSQL | OBS revision snapshot | handler 事务写入、RLS 隔离 |
| 账户语言偏好 | PostgreSQL | React/OBS runtime | TOML 目录验证、RLS 隔离 |
| PWA 静态壳层 | Docker 镜像 | Cache Storage | 不包含 API 或用户数据 |
## 启动顺序
@@ -74,6 +75,7 @@ flowchart LR
- Nginx 负责公网 TLS、域名和 WebSocket upgrade。
- CookieCloud 与 PostgreSQL 是外部服务,不由本项目 Compose 创建。
- `libilibili` 是源码树中的相邻 crate,由 Compose named build context 注入 Rust 构建阶段。
- `resources/i18n.toml` 同时输入 Vite 和 Rust 构建;缺少默认语言键的 locale 会使后端测试失败。
## 代码导航
+6 -1
View File
@@ -34,7 +34,8 @@ log。WebSocket 建立后,客户端必须在 8 秒内发送第一帧:
"version": 1,
"type": "authenticated",
"componentId": "08d31d19-3e4b-4c11-9cf7-9bd786a39465",
"componentKind": "song_request"
"componentKind": "song_request",
"language": "zh-CN"
}
```
@@ -42,6 +43,9 @@ log。WebSocket 建立后,客户端必须在 8 秒内发送第一帧:
`overlay.settings.snapshot` 兼容帧。token 无效、被轮换或属于其他组件时,服务端以 policy
close 结束连接。
`language` 是组件所属账户的 locale。用户在控制台修改语言后,所有已连接组件会立即收到
`component.language.updated`,payload 为 `{ "language": "en-US" }`;新连接以认证帧为准。
设置快照中的 `themeId` 是稳定的主题标识,目前支持
`jade-scroll`。消费者应把未知主题降级为自身默认主题,不能因主题发布顺序不同而中断实时消息。
@@ -75,6 +79,7 @@ close 结束连接。
| ----------------------------- | --------------------------------------------- | ---------------------- |
| `component.settings.snapshot` | `settings` | 所有组件的设置快照 |
| `component.settings.updated` | `settings` | 所有组件的设置更新 |
| `component.language.updated` | `language` | 账户语言实时更新 |
| `overlay.settings.snapshot` | `settings` | 认证后当前设置快照 |
| `overlay.settings.updated` | `settings` | 控制台保存后的实时设置 |
| `live.danmaku` | `viewer`, `text`, `segments` | 普通文字和表情分段 |
+2
View File
@@ -8,7 +8,9 @@ token。以下规则是实现约束,而不是可选部署建议。
- HTTP handler 只从服务端会话解析 `owner_id`,不接受客户端声明的 owner。
- 组件查询始终限定 `owner_user_id`;组件表不保存直播源外键。
- 直播间、直播源和 CookieCloud 凭据分别以账户 ID 建立唯一约束与 RLS 边界。
- 用户切换直播间时,账户房间、直播源房间与加密 CookieCloud 凭据在同一事务中更新;目标房间仍保持账户间唯一。
- 数据库使用 owner 复合外键、RLS 和 `FORCE ROW LEVEL SECURITY`。
- 语言偏好使用强制 RLS 的账户行;修改时只向该 owner 的组件广播 locale。
- tenant 查询必须在事务中执行 `SET LOCAL app.user_id`,不能使用会泄漏到连接池的 session-level
`SET`。
- 实时广播按 `component_id` 建立独立 channel,不提供全局订阅。