223 lines
8.4 KiB
Markdown
223 lines
8.4 KiB
Markdown
# SpacetimeDB Server-Node 迁移实现说明
|
||
|
||
## 本次落地范围
|
||
|
||
本次已经把 `server-node` 里与运行时存档、用户认证状态、自定义世界资料库相关的数据库层迁移到 Rust SpacetimeDB 模块中,模块代码位于 `spacetimedb/src/`,按职责拆成以下文件:
|
||
|
||
- `lib.rs`
|
||
- 只保留模块入口和生命周期 reducer。
|
||
- `types.rs`
|
||
- 统一放表结构、枚举、view/procedure 的输入输出类型。
|
||
- `config.rs`
|
||
- `app_config` 单例表默认值、读取与客户端配置 view。
|
||
- `auth.rs`
|
||
- 游客建档、JWT/Identity 建档、短信验证、验证提示事件、踢下线事件、认证审计。
|
||
- `runtime.rs`
|
||
- 快照、运行时设置、资料库、自定义世界会话、浏览历史、资料统计与相关 view/procedure。
|
||
|
||
## 当前模块设计
|
||
|
||
### 1. `app_config` 单例表
|
||
|
||
原先散落在 Node `AppConfig` 中、且与认证/验证流程相关的配置,已经迁移到 `app_config` 单例表。
|
||
|
||
当前策略:
|
||
|
||
- `init` reducer 会自动插入一行默认配置,主键固定为 `id = 1`
|
||
- 本地环境通过 `spacetime sql` 更新这一行
|
||
- 客户端只通过 `client_app_config` view 读取必要的公开配置,不直接读整张表
|
||
|
||
### 2. 默认游客登录
|
||
|
||
连接进入模块时:
|
||
|
||
- `client_connected` 会按 `ctx.sender` 自动建档
|
||
- 无 JWT 时默认为 `guest`
|
||
- 有 JWT 时使用 SpacetimeDB 自带 `sender_auth().jwt()` 建档,`login_provider` 标记为 `jwt`
|
||
- 用户主键按 `user_<identity_hex>` 生成,避免再依赖原先 Node 自签 access token / refresh token 流程
|
||
|
||
### 2.2 内部账户模型状态
|
||
|
||
当前 STDB 私有实现已经开始显式转向账户语义:
|
||
|
||
- 私有 Rust struct 已经切到 `Account / AccountIdentity / AccountSession`
|
||
- `auth.rs / runtime.rs / lib.rs` 内部 helper 和生命周期接线也开始改用 `account` 语义命名
|
||
- 公开 view / procedure 名称暂时保持不变,但当前 schema 字段已经切到 `account_id / owner_account_id`
|
||
- 前端 TypeScript bindings 已经重新生成并同步适配了 `account_id` 语义
|
||
|
||
### 2.1 当前统一账户策略
|
||
|
||
当前已经开始按“统一账户,多设备会话”方向调整:
|
||
|
||
- 新建账户时,账户主键已经不再直接使用连接 identity,而是生成独立 `acct_*` 账户 id
|
||
- 当前设备在短信验证时,如果手机号已命中已有用户,不再直接报“手机号已绑定其他账号”
|
||
- 当前连接的 identity / session 会被归并到这个已有手机号用户
|
||
- 当前游客账户下的快照、设置、自定义世界、游玩统计、浏览历史等运行时数据也会一起迁移到目标账户
|
||
- 这样同一个手机号可以在多个设备上同时建立会话,并归到同一个用户主体下
|
||
|
||
当前限制:
|
||
|
||
- 归并的是“当前连接身份”和“当前会话”
|
||
- 当前的账户数据迁移是规则式合并,不是全量业务语义级合并
|
||
- 例如看板/游玩统计用了保守合并策略,自定义世界同名冲突按更新时间取新
|
||
- 也就是说,统一账户主语义已经开始生效,但后续仍值得补一轮更细的并档策略
|
||
|
||
### 3. 短信验证门禁
|
||
|
||
当前行为已经按你的要求落地:
|
||
|
||
- 是否需要短信验证由 `app_config.sms_verification_required` 决定
|
||
- 除短信发送 / 短信校验 procedure 外,其余 runtime procedure 统一走门禁
|
||
- 若用户未完成短信验证:
|
||
- 先发 `verification_prompt_event`
|
||
- 再发 `kick_event`
|
||
- procedure 直接返回 `kicked = true`
|
||
|
||
### 4. 登录即弹验证窗
|
||
|
||
`client_connected` 中新增了这条行为:
|
||
|
||
- 如果命中“已存在用户,且未完成短信验证”
|
||
- 立即发 `verification_prompt_event`
|
||
- 前端订阅到事件后即可弹出手机号验证窗口
|
||
|
||
### 5. 客户端同步方式
|
||
|
||
遵循“尽量不公开表”的要求,当前数据同步面以 `view` 为主:
|
||
|
||
- `client_app_config`
|
||
- `my_auth_state`
|
||
- `my_auth_audit_logs`
|
||
- `my_account_sessions`
|
||
- `my_auth_risk_blocks`
|
||
- `my_snapshot`
|
||
- `my_runtime_settings`
|
||
- `my_profile_dashboard`
|
||
- `my_profile_wallet_ledger`
|
||
- `my_profile_played_worlds`
|
||
- `my_browse_history`
|
||
- `my_custom_world_profiles`
|
||
- `my_custom_world_sessions`
|
||
- `published_custom_world_gallery`
|
||
|
||
当前保留为 `public event table` 的只有两类事件:
|
||
|
||
- `verification_prompt_event`
|
||
- `kick_event`
|
||
|
||
这是因为客户端要订阅并即时响应事件,而 event table 不能被 view 读取。
|
||
|
||
### 6. 账户弹窗同步面
|
||
|
||
为了承接客户端账户弹窗,本轮补了:
|
||
|
||
- `my_account_sessions`
|
||
- 用于读取当前账号关联的会话列表
|
||
- `my_auth_risk_blocks`
|
||
- 用于读取当前账号手机号/IP 对应的保护记录
|
||
- `lift_my_risk_block`
|
||
- 用于当前账号自助解除手机号或当前连接 IP 的保护
|
||
- `revoke_user_session`
|
||
- 用于撤销指定会话,并通过事件通知目标连接断开
|
||
- `logout_all_user_sessions`
|
||
- 用于撤销当前账号全部会话,并广播撤销事件
|
||
- `session_revocation_event`
|
||
- 用于通知目标连接当前 session 已失效
|
||
|
||
说明:
|
||
|
||
- 当前 `session` 已按连接维度追踪,不再只是 identity 级别的审计记录
|
||
- 被撤销的连接在再次调用受保护 procedure 时会被踢下线
|
||
- 前端也会订阅 `session_revocation_event`,在目标 session 被撤销时主动断开当前连接
|
||
|
||
## 本地初始化
|
||
|
||
### 1. 构建 / 发布本地模块
|
||
|
||
```bash
|
||
spacetime start
|
||
spacetime publish genarrative-local --clear-database -y --module-path ./spacetimedb
|
||
```
|
||
|
||
### 2. 初始化单例配置
|
||
|
||
初始化 SQL 已放到:
|
||
|
||
- `scripts/spacetime/init_local_app_config.sql`
|
||
|
||
执行方式:
|
||
|
||
```bash
|
||
spacetime sql genarrative-local "$(tr '\n' ' ' < scripts/spacetime/init_local_app_config.sql)"
|
||
```
|
||
|
||
如果你不想走命令替换,也可以直接把 SQL 内容整段复制到 `spacetime sql genarrative-local "<SQL>"` 里执行。
|
||
|
||
## 当前已验证状态
|
||
|
||
已完成:
|
||
|
||
- `cargo check`
|
||
- `spacetime build`
|
||
|
||
说明:
|
||
|
||
- 当前模块可以通过 Rust 编译和 Spacetime 模块构建
|
||
- `spacetime.json` 已切到 TypeScript 绑定输出目录 `src/spacetime/generated`
|
||
- 前端已开始接入 Spacetime 连接与绑定生成代码
|
||
|
||
## 当前客户端改造
|
||
|
||
本轮已经把前端里最核心的账号与运行时存储链路切到 Spacetime:
|
||
|
||
- `src/spacetime/client.ts`
|
||
- 统一维护浏览器端 Spacetime 连接、订阅和事件桥。
|
||
- `src/spacetime/mappers.ts`
|
||
- 负责把生成绑定里的 view/event 数据映射回现有前端契约类型。
|
||
- `src/services/authService.ts`
|
||
- 已从 `/api/auth/*` 切到 Spacetime 连接、view、procedure。
|
||
- `src/services/storageService.ts`
|
||
- 已从 `/api/runtime/*` 的存档/设置/资料库接口切到 Spacetime。
|
||
- `src/services/authService.ts`
|
||
- 现在也会读取 `my_account_sessions` / `my_auth_risk_blocks`,并调用 `lift_my_risk_block`。
|
||
- `src/components/auth/AuthGate.tsx`
|
||
- 已改成默认游客建连,并监听 `verification_prompt_event` / `kick_event`。
|
||
- `src/components/auth/PhoneVerificationModal.tsx`
|
||
- 新增短信验证弹窗,收到验证提示事件后直接弹出。
|
||
|
||
### 当前客户端行为
|
||
|
||
- 页面启动后会直接连到 SpacetimeDB,并复用/写回 Spacetime token
|
||
- 若账号未完成短信验证:
|
||
- 连接阶段收到 `verification_prompt_event` 会弹出验证窗
|
||
- 调用受保护 procedure 后收到 `kick_event` 也会重新弹出验证窗
|
||
- 存档、设置、个人看板、浏览历史、自定义世界资料库与作品广场已经改走 Spacetime
|
||
- 账户弹窗里的“当前安全状态 / 会话列表”已经开始读取真实 STDB view
|
||
- 账户弹窗里的“移除设备 / 全部退出”已经开始调用真实 STDB procedure
|
||
- 作品广场详情为支持客户端恢复完整 profile,新补了 `published_custom_world_profiles` view
|
||
|
||
### 当前仍保留的旧链路
|
||
|
||
- `/api/runtime/story/*` 相关故事运行时接口
|
||
- AI / 资源生成相关 Express 路由
|
||
- 账号弹窗中的“解除保护 / 移除设备 / 全部退出”都已经走真实 STDB procedure
|
||
- `runtimeStoryService.ts` 这条故事运行时链路仍未迁移,还是当前最大的遗留
|
||
|
||
### 前端环境变量
|
||
|
||
请在本地配置:
|
||
|
||
- `VITE_SPACETIME_URI`
|
||
- `VITE_SPACETIME_DATABASE_NAME`
|
||
|
||
示例已补到 `.env.example`。
|
||
|
||
## 后续建议
|
||
|
||
下一步如果继续推进,建议按这个顺序:
|
||
|
||
1. 前端接入 `verification_prompt_event` / `kick_event`
|
||
2. 补齐账号弹窗需要的 session / risk block view 与对应操作 procedure
|
||
3. 把原 `/api/runtime/story/*` 运行时动作接口继续向 Spacetime 迁移
|
||
4. 再决定是否保留 `server-node` 作为纯 HTTP/AI 代理层
|