Files
Genarrative/docs/technical/M3_RUNTIME_SETTINGS_AXUM_SPACETIMEDB_DESIGN_2026-04-21.md
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

8.2 KiB
Raw Blame History

M3runtime settings Axum + SpacetimeDB 落地设计

日期:2026-04-21

关联任务:

关联现状:

1. 文档目的

02_M3_RUNTIME_PROFILE.md 已经冻结了 M3 的任务范围但还没有把首批可编码切片细化到表字段、procedure、Axum facade、兼容错误格式和测试策略。

本文件只解决 M3 第一批最小纵向切片:

  1. GET /api/runtime/settings
  2. PUT /api/runtime/settings

以及其在 Rust 重写中的完整落位:

  1. module-runtime 的字段约束与 DTO
  2. crates/spacetime-moduleruntime_setting 表与 procedure
  3. crates/spacetime-client 的 procedure 调用封装
  4. crates/api-server 的兼容 facade 与响应 contract

本文件不新增 checklist不替代 ../../backend-rewrite-tasklist/02_M3_RUNTIME_PROFILE.md,只补足可以直接编码的技术口径。

2. 为什么先做 runtime settings

在 M3 范围内,runtime settings 是当前最适合先迁移的纵向切片:

  1. 读写模型最小,只依赖 user_id + music_volume + platform_theme
  2. 旧 Node 逻辑没有跨表聚合、副作用和复杂 projection。
  3. 前端 contract 清晰,兼容路径只有一条,不涉及 /api/profile/* 双路径。
  4. 可以先把 Axum -> JWT -> SpacetimeDB procedure -> 标准 envelope 主链跑通,为后续 browse history / snapshot / save archive / dashboard 复用。

3. 旧实现冻结口径

当前 Node 侧 runtime settings 行为来自:

  • server-node/src/routes/rpg-profile/rpgProfileRoutes.ts
  • server-node/src/repositories/runtimeRepository.ts

冻结行为如下:

3.1 路由

  • GET /api/runtime/settings
  • PUT /api/runtime/settings

两条接口都要求 JWT。

3.2 请求体

PUT /api/runtime/settings 请求体:

{
  "musicVolume": 0.42,
  "platformTheme": "light"
}

校验规则:

  1. musicVolume 必须在 0 ~ 1
  2. platformTheme 只接受 light | dark

3.3 默认值

默认值来自 packages/shared/src/contracts/runtime.ts

  1. DEFAULT_MUSIC_VOLUME = 0.42
  2. DEFAULT_PLATFORM_THEME = "light"

当用户从未写入过设置时,读取接口必须返回默认值,而不是 404null

3.4 归一化规则

旧 Node 写入时会做以下归一化:

  1. musicVolume 强制 clamp 到 0 ~ 1
  2. platformTheme 如果不是 dark,统一回退到 light

Rust 重写阶段仍保持同样语义,避免前端产生行为漂移。

4. Rust 落位决议

4.1 crate 分工

本切片固定按以下边界落位:

  1. crates/module-runtime
    • 定义 RuntimeSettings 领域 DTO、默认值、字段校验与归一化规则。
  2. crates/spacetime-module
    • 定义 runtime_setting 表。
    • 提供 upsert_runtime_setting_and_return procedure。
  3. crates/spacetime-client
    • 提供 get_runtime_settingsput_runtime_settings 调用封装。
  4. crates/api-server
    • 提供 GET/PUT /api/runtime/settings
    • 保持当前 envelope / 错误格式 / 请求头兼容。

4.2 身份边界

当前阶段前端仍只访问 Axum不直连 SpacetimeDB。

因此:

  1. 用户身份仍由 Axum 侧 JWT middleware 校验。
  2. Axum 从已校验的 access token claims 中取 user_id
  3. user_id 作为 procedure 入参写入 runtime_setting

注意:

  1. 这不是最终的 SpacetimeDB 原生身份透传形态。
  2. 在 M3 首批切片里,先以 Axum 作为唯一鉴权边界,保证与当前前端 contract 一致。

5. SpacetimeDB 表设计

5.1 表名

runtime_setting

5.2 字段

字段 类型 说明
user_id String 主键,绑定平台用户
music_volume f32 音量,持久化归一化后的值
platform_theme RuntimePlatformTheme 平台主题枚举
created_at Timestamp 首次创建时间
updated_at Timestamp 最近更新时间

5.3 设计决议

  1. 每个用户只保留一行设置,不做历史版本表。
  2. user_id 直接作为主键,避免再引入无业务价值的自增 ID。
  3. platform_theme 固定为枚举,不把 light/dark 继续散落成字符串字面量。
  4. 首批阶段不把设置拆成多行 KV 表,避免简单需求被过度抽象。

6. Procedure 设计

6.1 不单独暴露 reducer 给 Axum

本切片优先提供 procedure而不是让 Axum 直接调 reducer + 再查询表。

原因:

  1. 当前 spacetime-client 已经以 procedure 返回结果的模式承接资产链。
  2. 设置接口需要同步返回最终写入结果procedure 可减少一次额外查询。
  3. 当前 runtime_setting 不需要客户端订阅private table + procedure 更直接。

6.2 Procedure 列表

  1. get_runtime_setting_or_default
  2. upsert_runtime_setting_and_return

返回 DTO 固定为:

RuntimeSettingSnapshot {
  user_id
  music_volume
  platform_theme
  created_at_micros
  updated_at_micros
}

如果用户还没有设置记录:

  1. get_runtime_setting_or_default 返回默认值快照。
  2. 但不强制立即插入表,避免纯读取请求制造无意义写入。

7. Axum facade 设计

7.1 GET /api/runtime/settings

行为:

  1. require_bearer_auth
  2. claims.user_id 取用户 ID。
  3. spacetime_client.get_runtime_settings(user_id)
  4. 返回:
{
  "musicVolume": 0.42,
  "platformTheme": "light"
}

7.2 PUT /api/runtime/settings

行为:

  1. require_bearer_auth
  2. 使用 Axum Json + serde 解析请求。
  3. module-runtime 内做归一化。
  4. spacetime_client.put_runtime_settings(user_id, payload)
  5. 返回归一化后的最终值。

7.3 错误映射

  1. 请求体解析失败:400 BAD_REQUEST
  2. 字段校验失败:400 BAD_REQUEST
  3. SpacetimeDB 调用失败:502 BAD_GATEWAY
  4. JWT 缺失或失效:沿用现有 401 UNAUTHORIZED

错误 details.provider 固定为:

  1. runtime-settings:本地字段归一化或 DTO 构建失败
  2. spacetimedbprocedure 调用失败

8. 首批测试策略

本切片测试分两层:

8.1 必跑测试

  1. module-runtime
    • 默认值
    • clamp 规则
    • theme 归一化
  2. api-server
    • 未登录返回 401
    • 请求 envelope 打开时返回标准 ok/data/error/meta
    • JSON 结构与字段名兼容

8.2 可选联调测试

补一条 #[ignore] 的集成测试:

  1. 需要本地 SpacetimeDB 已启动
  2. 需要当前 spacetime-module 已发布
  3. 验证 PUT -> GET 能往返一致

原因:

  1. 当前仓库已有资产链的 #[ignore] 集成测试模式。
  2. 在未稳定建立测试 harness 前,不强制把 SpacetimeDB 作为默认单测前置条件。

9. 后续扩展顺序

runtime settings 完成后M3 后续能力按以下顺序推进:

  1. user_browse_history
  2. runtime_snapshot
  3. profile_save_archive
  4. profile_dashboard_state + profile_wallet_ledger + profile_played_world

顺序原因:

  1. browse_history 仍是单表为主,只带去重与排序规则。
  2. snapshotsave_archive 依赖兼容聚合策略,复杂度更高。
  3. dashboard / play-stats / wallet-ledger 依赖 projection更适合放在 snapshot 规则固定后收口。

10. 本文完成定义

当以下条件成立时,本设计文档视为完成:

  1. runtime settings 的字段、默认值、归一化规则、procedure 与 Axum facade 已书面冻结。
  2. 后续编码无需再猜测:
    • 表字段名
    • 主键策略
    • 默认值来源
    • Axum 与 SpacetimeDB 的职责边界
  3. 可以直接据此开始 module-runtimespacetime-modulespacetime-clientapi-server 编码。