# 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_` 生成,避免再依赖原先 Node 自签 access token / refresh token 流程 ### 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_user_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_user_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 ""` 里执行。 ## 当前已验证状态 已完成: - `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_user_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 代理层