8.4 KiB
8.4 KiB
SpacetimeDB Server-Node 迁移实现说明
本次落地范围
本次已经把 server-node 里与运行时存档、用户认证状态、自定义世界资料库相关的数据库层迁移到 Rust SpacetimeDB 模块中,模块代码位于 spacetimedb/src/,按职责拆成以下文件:
lib.rs- 只保留模块入口和生命周期 reducer。
types.rs- 统一放表结构、枚举、view/procedure 的输入输出类型。
config.rsapp_config单例表默认值、读取与客户端配置 view。
auth.rs- 游客建档、JWT/Identity 建档、短信验证、验证提示事件、踢下线事件、认证审计。
runtime.rs- 快照、运行时设置、资料库、自定义世界会话、浏览历史、资料统计与相关 view/procedure。
当前模块设计
1. app_config 单例表
原先散落在 Node AppConfig 中、且与认证/验证流程相关的配置,已经迁移到 app_config 单例表。
当前策略:
initreducer 会自动插入一行默认配置,主键固定为id = 1- 本地环境通过
spacetime sql更新这一行 - 客户端只通过
client_app_configview 读取必要的公开配置,不直接读整张表
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_configmy_auth_statemy_auth_audit_logsmy_account_sessionsmy_auth_risk_blocksmy_snapshotmy_runtime_settingsmy_profile_dashboardmy_profile_wallet_ledgermy_profile_played_worldsmy_browse_historymy_custom_world_profilesmy_custom_world_sessionspublished_custom_world_gallery
当前保留为 public event table 的只有两类事件:
verification_prompt_eventkick_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. 构建 / 发布本地模块
spacetime start
spacetime publish genarrative-local --clear-database -y --module-path ./spacetimedb
2. 初始化单例配置
初始化 SQL 已放到:
scripts/spacetime/init_local_app_config.sql
执行方式:
spacetime sql genarrative-local "$(tr '\n' ' ' < scripts/spacetime/init_local_app_config.sql)"
如果你不想走命令替换,也可以直接把 SQL 内容整段复制到 spacetime sql genarrative-local "<SQL>" 里执行。
当前已验证状态
已完成:
cargo checkspacetime 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_profilesview
当前仍保留的旧链路
/api/runtime/story/*相关故事运行时接口- AI / 资源生成相关 Express 路由
- 账号弹窗中的“解除保护 / 移除设备 / 全部退出”都已经走真实 STDB procedure
runtimeStoryService.ts这条故事运行时链路仍未迁移,还是当前最大的遗留
前端环境变量
请在本地配置:
VITE_SPACETIME_URIVITE_SPACETIME_DATABASE_NAME
示例已补到 .env.example。
后续建议
下一步如果继续推进,建议按这个顺序:
- 前端接入
verification_prompt_event/kick_event - 补齐账号弹窗需要的 session / risk block view 与对应操作 procedure
- 把原
/api/runtime/story/*运行时动作接口继续向 Spacetime 迁移 - 再决定是否保留
server-node作为纯 HTTP/AI 代理层