formatting and comments
This commit is contained in:
@@ -1,17 +1,36 @@
|
||||
# 洛星瓷直播组件服务
|
||||
|
||||
这是一个多用户 Rust/Axum 直播组件后端。目前内建 `danmaku_overlay` 弹幕姬,后端已经按“直播源 → 强类型事件 → 组件实例”的方式拆分,后续可以继续增加礼物展示、点歌姬等组件。镜像构建阶段会编译 React 前端,运行时由 Rust 同域托管控制台和 OBS 页面。
|
||||
这是一个多用户 Rust/Axum 直播组件后端。目前内建 `danmaku_overlay`
|
||||
弹幕姬,后端已经按“直播源 → 强类型事件 → 组件实例”的方式拆分,后续可以继续增加礼物展示、点歌姬等组件。镜像构建阶段会编译 React 前端,运行时由 Rust 同域托管控制台和 OBS 页面。
|
||||
|
||||
直播连接使用 [`blivedm_rs`](https://github.com/isomoes/blivedm_rs) 的 `blivedm` crate。项目在 `vendor/blivedm` 固定了保留原始 JSON 的小补丁,避免 UID、礼物价格和上游事件 ID 被简化消息结构丢弃。
|
||||
直播连接使用 [`blivedm_rs`](https://github.com/isomoes/blivedm_rs) 的 `blivedm` crate。项目在
|
||||
`vendor/blivedm` 固定了保留原始 JSON 的小补丁,避免 UID、礼物价格和上游事件 ID 被简化消息结构丢弃。
|
||||
|
||||
## 文档索引
|
||||
|
||||
- [总体架构与事件流](docs/architecture.md)
|
||||
- [Rust 后端模块](apps/server-rust/README.md)
|
||||
- [React 控制台、OBS 与 PWA](apps/overlay/README.md)
|
||||
- [组件开发指南](docs/components/README.md)
|
||||
- [`danmaku_overlay` 弹幕姬](docs/components/danmaku-overlay.md)
|
||||
- [WebSocket 实时协议](docs/protocol.md)
|
||||
- [租户、Secret 与部署安全](docs/security.md)
|
||||
- [完整配置注释](config.toml.example)
|
||||
|
||||
## 后端结构
|
||||
|
||||
`src/main.rs` 只负责配置、组装、监听和优雅退出,可复用核心由 `src/lib.rs` 导出:
|
||||
|
||||
- `live/` 中的 provider adapter 把 Bilibili 消息归一化为 `domain.rs` 的强类型事件;`SourceSupervisor` 保证每个用户的固定直播源只有一个 listener。
|
||||
- `components.rs` 注册组件定义、设置 schema/迁移、事件订阅、纯投影和独立副作用 handler;新礼物展示或点歌姬不需要修改弹幕姬队列核心。
|
||||
- `realtime.rs` 按 component UUID 建立独立广播通道,路由时同时校验 owner 与 source;副作用 handler 不依赖 OBS WebSocket 是否在线。
|
||||
- `auth.rs`、`repository.rs` 和 PostgreSQL RLS 共同实现身份、凭据、直播源、组件与 token 的用户隔离;`http_api.rs` 从服务端会话推导 owner,不接受客户端自报 owner ID。
|
||||
- `live/` 中的 provider adapter 把 Bilibili 消息归一化为 `domain.rs`
|
||||
的强类型事件;`SourceSupervisor` 保证每个用户的固定直播源只有一个 listener。
|
||||
- `components.rs`
|
||||
注册组件定义、设置 schema/迁移、事件订阅、纯投影和独立副作用 handler;新礼物展示或点歌姬不需要修改弹幕姬队列核心。
|
||||
- `realtime.rs` 按 component
|
||||
UUID 建立独立广播通道,路由时同时校验 owner 与 source;副作用 handler 不依赖 OBS
|
||||
WebSocket 是否在线。
|
||||
- `auth.rs`、`repository.rs` 和 PostgreSQL
|
||||
RLS 共同实现身份、凭据、直播源、组件与 token 的用户隔离;`http_api.rs`
|
||||
从服务端会话推导 owner,不接受客户端自报 owner ID。
|
||||
|
||||
## 部署
|
||||
|
||||
@@ -25,13 +44,43 @@ openssl rand -hex 32 # 填入兼容字段 admin.session_secret
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Compose 将 `config.toml` 只读挂载到 `/app/config.toml`,应用通过 `--config` 读取它。容器使用 host 网络,默认只在 `127.0.0.1:9719` 监听,供同机 Nginx 访问;不要把 `9719` 直接暴露到公网。
|
||||
Compose 将 `config.toml` 只读挂载到 `/app/config.toml`,应用通过 `--config`
|
||||
读取它。容器使用 host 网络,默认只在 `127.0.0.1:9719` 监听,供同机 Nginx 访问;不要把 `9719`
|
||||
直接暴露到公网。
|
||||
|
||||
容器默认以 `1000:1000` 非 root 身份、只读根文件系统运行,并丢弃全部 Linux capabilities。请将 `config.toml` 设为 `chmod 600`,并确保容器用户可读;如宿主机用户不是 `1000:1000`,启动前设置 `APP_UID` 与 `APP_GID`。
|
||||
容器默认以 `1000:1000` 非 root 身份、只读根文件系统运行,并丢弃全部 Linux capabilities。请将
|
||||
`config.toml` 设为 `chmod 600`,并确保容器用户可读;如宿主机用户不是 `1000:1000`,启动前设置
|
||||
`APP_UID` 与 `APP_GID`。
|
||||
|
||||
`[database].url` 指向 PostgreSQL。应用启动时自动执行版本化迁移,数据库保存账户、一次性邀请码、TOTP 注册状态、可撤销会话、恢复码摘要、用户直播源、组件设置、OBS 令牌摘要和审计记录。租户表同时使用 owner 复合外键与 PostgreSQL RLS 约束,HTTP API 也始终从当前会话取得 owner,客户端不能自行指定其他用户。
|
||||
`[database].url`
|
||||
指向 PostgreSQL。应用启动时自动执行版本化迁移,数据库保存账户、一次性邀请码、TOTP 注册状态、可撤销会话、恢复码摘要、用户直播源、组件设置、OBS 令牌摘要和审计记录。租户表同时使用 owner 复合外键与 PostgreSQL
|
||||
RLS 约束,HTTP API 也始终从当前会话取得 owner,客户端不能自行指定其他用户。
|
||||
|
||||
`security.data_encryption_key` 必须是独立生成并妥善备份的 32 字节 Base64 密钥。TOTP Secret 与每个用户的 CookieCloud Key/密码会在写入 PostgreSQL 前用它加密;丢失或擅自更换该密钥会导致已有账户和 CookieCloud 凭据无法解密。
|
||||
`security.data_encryption_key` 必须是独立生成并妥善备份的 32 字节 Base64 密钥。TOTP
|
||||
Secret 与每个用户的 CookieCloud
|
||||
Key/密码会在写入 PostgreSQL 前用它加密;丢失或擅自更换该密钥会导致已有账户和 CookieCloud 凭据无法解密。
|
||||
|
||||
## 开发与代码质量
|
||||
|
||||
Rust 使用仓库内 `rustfmt.toml`,前端和文档使用固定版本 Prettier。推荐提交前执行:
|
||||
|
||||
```sh
|
||||
cargo fmt --manifest-path apps/server-rust/Cargo.toml
|
||||
cargo clippy --manifest-path apps/server-rust/Cargo.toml --all-targets --no-deps -- -D warnings
|
||||
cargo test --manifest-path apps/server-rust/Cargo.toml --all-targets
|
||||
|
||||
npm --prefix apps/overlay run format
|
||||
npm --prefix apps/overlay run format:check
|
||||
npm --prefix apps/overlay run docs:check
|
||||
npm --prefix apps/overlay run build
|
||||
|
||||
docker compose config --quiet
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`.editorconfig` 统一换行、缩进和文件末尾规则;`.prettierignore` 与 Docker/Git
|
||||
ignore 会排除依赖、构建产物、第三方 vendor、PNG/SVG 和包含真实 Secret 的 `config.toml`。不要对
|
||||
`vendor/blivedm` 做无关的批量风格改写,以便继续审查上游补丁。
|
||||
|
||||
## 首次初始化与登录
|
||||
|
||||
@@ -41,7 +90,8 @@ Compose 将 `config.toml` 只读挂载到 `/app/config.toml`,应用通过 `--c
|
||||
https://danmaku.luoxingci.com/control/setup
|
||||
```
|
||||
|
||||
填写系统管理员用户名和 `config.toml` 中的 `admin.password`,扫描页面生成的 TOTP 二维码,再输入验证器中的 6 位动态码完成初始化。页面只显示一次恢复码,请立即离线保存。
|
||||
填写系统管理员用户名和 `config.toml` 中的
|
||||
`admin.password`,扫描页面生成的 TOTP 二维码,再输入验证器中的 6 位动态码完成初始化。页面只显示一次恢复码,请立即离线保存。
|
||||
|
||||
`admin.password` 只是“允许创建第一个系统管理员”的一次性 bootstrap proof:
|
||||
|
||||
@@ -50,9 +100,14 @@ https://danmaku.luoxingci.com/control/setup
|
||||
- 所有账户都是 passwordless 账户,以“用户名 + TOTP”登录;丢失验证器时可使用一次性恢复码。
|
||||
- 会话使用随机令牌,数据库只保存摘要,可在服务端到期或撤销。
|
||||
|
||||
系统管理员可在 `/control/invitations` 创建和撤销邀请码。每个邀请码只能使用一次,并在创建时固定绑定一个尚未占用的 Bilibili `room_id`;注册者不能修改该房间,账户创建后房间绑定也不可更改。受邀用户打开注册链接,选择用户名、扫描自己的 TOTP 二维码并确认动态码即可完成注册。普通用户不能创建邀请码。
|
||||
系统管理员可在 `/control/invitations`
|
||||
创建和撤销邀请码。每个邀请码只能使用一次,并在创建时固定绑定一个尚未占用的 Bilibili
|
||||
`room_id`;注册者不能修改该房间,账户创建后房间绑定也不可更改。受邀用户打开注册链接,选择用户名、扫描自己的 TOTP 二维码并确认动态码即可完成注册。普通用户不能创建邀请码。
|
||||
|
||||
每个用户在 `/control/` 配置自己的 CookieCloud 同步 UUID/Key 和密码,地址必须位于部署管理员配置的 `security.cookiecloud_allowed_hosts` 白名单。服务禁止 HTTP 重定向并安全编码 Key 路径,避免用户凭据导致服务端任意请求。服务会先验证凭据,再将敏感字段按用户独立加密保存;Cookie、Key 和明文密码不会返回浏览器,也不会与其他账户共享。CookieCloud 中需要存在 Bilibili `SESSDATA`。
|
||||
每个用户在 `/control/` 配置自己的 CookieCloud 同步 UUID/Key 和密码,地址必须位于部署管理员配置的
|
||||
`security.cookiecloud_allowed_hosts`
|
||||
白名单。服务禁止 HTTP 重定向并安全编码 Key 路径,避免用户凭据导致服务端任意请求。服务会先验证凭据,再将敏感字段按用户独立加密保存;Cookie、Key 和明文密码不会返回浏览器,也不会与其他账户共享。CookieCloud 中需要存在 Bilibili
|
||||
`SESSDATA`。
|
||||
|
||||
### 旧单用户配置迁移
|
||||
|
||||
@@ -67,7 +122,8 @@ https://danmaku.luoxingci.com/control/setup
|
||||
|
||||
## 必须使用 HTTPS
|
||||
|
||||
公开部署账号、TOTP 和会话功能时必须在 Nginx(或可信反向代理)终止 HTTPS,并保持 `security.secure_cookies = true`。下面是核心反代设置;证书路径按实际 Certbot 配置填写:
|
||||
公开部署账号、TOTP 和会话功能时必须在 Nginx(或可信反向代理)终止 HTTPS,并保持
|
||||
`security.secure_cookies = true`。下面是核心反代设置;证书路径按实际 Certbot 配置填写:
|
||||
|
||||
```nginx
|
||||
map $http_upgrade $connection_upgrade {
|
||||
@@ -103,36 +159,54 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
只有在本机、无敏感数据的纯 HTTP 开发环境中才能临时设置 `secure_cookies = false`。不要用该选项把登录页面直接发布到公网。
|
||||
只有在本机、无敏感数据的纯 HTTP 开发环境中才能临时设置
|
||||
`secure_cookies = false`。不要用该选项把登录页面直接发布到公网。
|
||||
|
||||
## 控制台 PWA
|
||||
|
||||
通过 HTTPS 打开 `/control/` 后,受支持的浏览器会在控制台顶部显示“安装到设备”。`/control` 会由服务端永久重定向到这个规范地址。安装后的应用使用独立窗口,并继续采用“用户名 + TOTP”登录;登录、注册与首次初始化在 PWA 内分别使用 `/control/login`、`/control/register` 和 `/control/setup`。原有 `/login`、`/register`、`/setup` 地址仍兼容普通浏览器书签。
|
||||
通过 HTTPS 打开 `/control/` 后,受支持的浏览器会在控制台顶部显示“安装到设备”。`/control`
|
||||
会由服务端永久重定向到这个规范地址。安装后的应用使用独立窗口,并继续采用“用户名 +
|
||||
TOTP”登录;登录、注册与首次初始化在 PWA 内分别使用 `/control/login`、`/control/register` 和
|
||||
`/control/setup`。原有 `/login`、`/register`、`/setup` 地址仍兼容普通浏览器书签。
|
||||
|
||||
PWA 只控制 `/control/`,不会控制、缓存或刷新 `/obs/*` 浏览器源。离线缓存仅包含 React 应用外壳、本地图标和构建后带哈希的静态资源;账户、TOTP、邀请码、CookieCloud、组件设置、OBS 令牌、直播事件和所有 `/api/*` 响应始终在线直连且由服务端返回 `Cache-Control: no-store`。离线时写操作会在浏览器端直接拒绝,不会排队或在恢复网络后重放。
|
||||
PWA 只控制 `/control/`,不会控制、缓存或刷新 `/obs/*`
|
||||
浏览器源。离线缓存仅包含 React 应用外壳、本地图标和构建后带哈希的静态资源;账户、TOTP、邀请码、CookieCloud、组件设置、OBS 令牌、直播事件和所有
|
||||
`/api/*` 响应始终在线直连且由服务端返回
|
||||
`Cache-Control: no-store`。离线时写操作会在浏览器端直接拒绝,不会排队或在恢复网络后重放。
|
||||
|
||||
部署新版本后,已打开的控制台会显示“更新可用”。更新不会自动接管或刷新页面,必须由用户点击确认;确认前请先保存设置以及只显示一次的邀请码、恢复码或刚轮换的 OBS 令牌。
|
||||
|
||||
## 组件与 OBS 地址
|
||||
|
||||
登录 `/control/` 后可以配置当前账户的直播源、管理组件、测试事件、调整弹幕样式,以及为每个组件单独生成或轮换只读 OBS 令牌。新的浏览器源地址格式是:
|
||||
登录 `/control/`
|
||||
后可以配置当前账户的直播源、管理组件、测试事件、调整弹幕样式,以及为每个组件单独生成或轮换只读 OBS 令牌。新的浏览器源地址格式是:
|
||||
|
||||
```text
|
||||
https://danmaku.luoxingci.com/obs/<publicId>#token=<component-token>
|
||||
```
|
||||
|
||||
`publicId` 是组件 UUID。`#token=...` 位于 URL fragment,不会随最初的 HTTP 请求发送到 Nginx;OBS 页面随后通过 WebSocket 的第一帧向 `/api/v1/components/<publicId>/stream` 完成认证。令牌只带 `events:subscribe` 权限,不能调用管理或写入接口;轮换后旧令牌立即失效。
|
||||
`publicId` 是组件 UUID。`#token=...` 位于 URL
|
||||
fragment,不会随最初的 HTTP 请求发送到 Nginx;OBS 页面随后通过 WebSocket 的第一帧向
|
||||
`/api/v1/components/<publicId>/stream` 完成认证。令牌只带 `events:subscribe`
|
||||
权限,不能调用管理或写入接口;轮换后旧令牌立即失效。
|
||||
|
||||
旧格式 `/obs?token=...` 与 `/ws?token=...` 已移除,避免 bearer token 进入 Nginx 访问日志。升级后请在控制台为组件轮换令牌,并把旧 OBS 源替换为上述新地址;旧令牌一旦轮换便立即失效。
|
||||
旧格式 `/obs?token=...` 与 `/ws?token=...` 已移除,避免 bearer
|
||||
token 进入 Nginx 访问日志。升级后请在控制台为组件轮换令牌,并把旧 OBS 源替换为上述新地址;旧令牌一旦轮换便立即失效。
|
||||
|
||||
OBS 页面保持透明根背景,不使用固定 1920×1080 画布,并根据浏览器源的实际宽高自动适配。常用尺寸可从 `360×600`、`440×760` 或 `600×1080` 开始,也可以在 OBS 中自由拖拽缩放。
|
||||
OBS 页面保持透明根背景,不使用固定 1920×1080 画布,并根据浏览器源的实际宽高自动适配。常用尺寸可从
|
||||
`360×600`、`440×760` 或 `600×1080` 开始,也可以在 OBS 中自由拖拽缩放。
|
||||
|
||||
## 弹幕姬展示
|
||||
|
||||
每张消息卡片会从六套花纹组合中稳定选择一套,轮换使用对称花枝、横向自然藤纹和雏菊花簇,并改变上下、左右、镜像、配色与局部背景。连续的新卡避免使用相同款式,礼物连击更新保持原样式;透明消息墙本身不铺设全局装饰背景。粒子数量与速度、字号、事件类别、最大条数、自动收缩、卷轴展开时长、动效强度、低性能模式和礼物高亮阈值均可按组件在控制台调整,并实时推送给对应 OBS 源。
|
||||
|
||||
花边 SVG 已本地打包,来源及公版/CC0 许可记录在 `apps/overlay/public/assets/NOTICE.md`,OBS 运行时不会访问素材站点。
|
||||
花边 SVG 已本地打包,来源及公版/CC0 许可记录在
|
||||
`apps/overlay/public/assets/NOTICE.md`,OBS 运行时不会访问素材站点。
|
||||
|
||||
后端会为每个活动直播源缓存 Bilibili 礼物图片、GIF、币种和价格。目录刷新失败时保留上一次成功缓存;缺失条目会降级为直播事件自带的名称与价格。刷新间隔和请求超时可通过 `[gifts]` 调整。
|
||||
后端会为每个活动直播源缓存 Bilibili 礼物图片、GIF、币种和价格。目录刷新失败时保留上一次成功缓存;缺失条目会降级为直播事件自带的名称与价格。刷新间隔和请求超时可通过
|
||||
`[gifts]` 调整。
|
||||
|
||||
普通混排表情和整条大表情会作为安全的文字/图片分段推送。消息自带图片地址优先;服务还会使用该用户 CookieCloud 中的 `SESSDATA` 刷新对应直播间的表情目录,在消息只提供唯一标识时补齐图片。加载失败时前端退回原始表情文字,刷新参数可通过 `[emoticons]` 调整。
|
||||
普通混排表情和整条大表情会作为安全的文字/图片分段推送。消息自带图片地址优先;服务还会使用该用户 CookieCloud 中的
|
||||
`SESSDATA`
|
||||
刷新对应直播间的表情目录,在消息只提供唯一标识时补齐图片。加载失败时前端退回原始表情文字,刷新参数可通过
|
||||
`[emoticons]` 调整。
|
||||
|
||||
Reference in New Issue
Block a user