拆分大文件

This commit is contained in:
2026-04-23 23:38:00 +08:00
parent 53a9cdd791
commit 8df502b2a7
506 changed files with 11312 additions and 13069 deletions

View File

@@ -0,0 +1,260 @@
# 后台管理服务设计
日期:`2026-04-23`
## 1. 目标
为当前 Rust `api-server` 增加一套同源后台管理服务,满足以下首版目标:
1. 支持管理员用户名密码登录。
2. 支持独立的管理员鉴权,不允许普通玩家 JWT 越权访问。
3. 支持在后台查看当前服务与数据库概览信息。
4. 支持在后台测试当前 `api-server` 已挂载接口。
5. 保持首版工程足够轻量,不新建额外独立服务进程,不引入第二套前端工程。
## 2. 背景与约束
当前仓库已具备:
1. Rust `api-server` 主链。
2. 基于 JWT + refresh session 的普通用户登录体系。
3. `SpacetimeDB + spacetime-client` 的主数据面。
本次后台管理服务必须继续遵守:
1. 后端统一落在 `server-rs`,不回退到 `server-node`
2. 不额外新起独立管理服务进程。
3. 首版以“一个受保护管理域 + 一个同源后台页面”为落地形态。
4. 数据库信息必须尽量读取真实数据库侧信息,不能只展示硬编码假数据。
## 3. 首版范围
### 3.1 包含
1. `GET /admin`:后台管理页面入口。
2. `POST /admin/api/login`:管理员用户名密码登录。
3. `GET /admin/api/me`:当前管理员会话信息。
4. `GET /admin/api/overview`:服务与数据库概览。
5. `POST /admin/api/debug/http`:受控 HTTP 接口调试。
6. 基于 Bearer JWT 的管理员鉴权中间件。
### 3.2 不包含
1. 多角色管理员体系。
2. 管理员 refresh cookie / 多端会话管理。
3. 后台直接写库、删库、执行 reducer。
4. 任意 SQL 执行器。
5. 新建独立 React/Vite 管理端工程。
## 4. 总体方案
### 4.1 部署形态
后台管理服务直接挂载在现有 `server-rs/crates/api-server` 内,作为同一个 Axum 进程的一部分。
原因:
1. 当前 `api-server` 已具备配置、JWT、错误包裹、日志与同源路由能力。
2. 后台本质上是服务运维与调试面,不值得单独再起一个网关或 BFF。
3. 同源可以避免开发期额外 CORS 和 cookie 域问题。
### 4.2 页面形态
后台管理页面采用 `api-server` 直接返回一份内嵌 HTML/CSS/JS 的管理页。
原因:
1. 首版目标是“可用的后台能力”,不是新建一套复杂前端基建。
2. 管理页面交互相对简单,直接内嵌更易随服务端一起部署。
3. 可以避免新增构建链和静态资源发布路径。
### 4.3 数据库信息来源
数据库概览不走本地 CLI shell也不依赖前端直接访问数据库。
首版采用两类信息源:
1. 服务端配置与连接信息:来自 `api-server` 当前 `AppConfig`
2. SpacetimeDB 真正的数据库元信息与表行数:由 `api-server` 通过 SpacetimeDB 官方 HTTP API 读取。
读取口径:
1. `/v1/database/{database}`:读取数据库基础信息。
2. `/v1/database/{database}/schema`:读取 schema 信息。
3. `/v1/database/{database}/sql`:对受控表执行 `SELECT COUNT(*)` 统计。
说明:
1. 首版只做只读概览,不暴露任意 SQL 输入。
2. 表清单由后端显式维护,避免用户在后台拼接任意查询。
## 5. 管理员鉴权设计
### 5.1 管理员账号来源
首版不复用普通玩家账号仓储,不把管理员账号混进 `module-auth` 用户表。
管理员账号来自环境变量:
1. `GENARRATIVE_ADMIN_USERNAME`
2. `GENARRATIVE_ADMIN_PASSWORD`
原因:
1. 管理员是平台运维身份,不等于玩家账号。
2. 首版目标是尽快落地可靠后台,不引入额外管理员表迁移。
3. 环境变量方案最适合当前阶段的单后台入口。
### 5.2 管理员 JWT
后台登录成功后签发独立管理员 Bearer JWT。
claims 设计:
1. 继续复用 `platform-auth::AccessTokenClaims`
2. `roles` 固定包含 `admin`
3. `sub` 使用稳定管理员主体,例如 `admin:<username>`
4. `sid` 使用后台会话 ID。
5. 不写 refresh cookie。
### 5.3 权限校验
新增 `require_admin_auth` 中间件,校验规则如下:
1. Bearer token 必须可被当前 JWT 配置正确验签。
2. `roles` 中必须包含 `admin`
3. `sub` 必须匹配当前管理员配置主体。
普通用户 token 即使同样由本服务签发,只要不带 `admin` 角色,也一律拒绝访问后台接口。
## 6. 后台页面设计
首版页面包含三个主区域:
1. 登录卡片。
2. 数据库概览面板。
3. API 调试面板。
交互原则:
1. 页面简洁,不默认塞说明性长文案。
2. 移动端优先,窄屏下卡片改纵向堆叠。
3. API 调试结果在独立结果面板展示,不在按钮下方临时插一段文本。
## 7. 数据库概览设计
`GET /admin/api/overview` 返回以下信息:
1. 当前服务监听信息。
2. 当前 `SpacetimeDB server/database` 配置。
3. `SpacetimeDB` 数据库基础信息。
4. 当前 schema 表清单。
5. 首批关键表的行数统计。
首批关键表固定覆盖:
1. `runtime_setting`
2. `runtime_snapshot`
3. `user_browse_history`
4. `profile_dashboard_state`
5. `profile_wallet_ledger`
6. `profile_played_world`
7. `profile_save_archive`
8. `story_session`
9. `story_event`
10. `battle_state`
11. `inventory_slot`
12. `quest_record`
13. `quest_log`
14. `treasure_record`
15. `npc_state`
16. `custom_world_profile`
17. `custom_world_gallery_entry`
18. `custom_world_agent_session`
19. `custom_world_agent_message`
20. `custom_world_agent_operation`
21. `custom_world_draft_card`
22. `big_fish_creation_session`
23. `big_fish_agent_message`
24. `big_fish_asset_slot`
25. `big_fish_runtime_run`
26. `puzzle_work_profile`
27. `puzzle_agent_session`
28. `puzzle_agent_message`
29. `puzzle_runtime_run`
30. `ai_task`
31. `ai_task_stage`
32. `ai_text_chunk`
33. `ai_result_reference`
34. `asset_object`
35. `asset_entity_binding`
返回中的计数失败项必须带错误信息,不能静默吞掉。
## 8. API 调试设计
`POST /admin/api/debug/http` 提供一个受控 HTTP 调试代理。
请求参数:
1. `method`
2. `path`
3. `headers`
4. `body`
限制:
1. 只允许访问当前服务同源相对路径。
2. 调试回环地址由服务端按当前 `bind_host` 解析;若服务监听在 `0.0.0.0``::`,后台自动改走 loopback避免把通配监听地址直接当成调试目标。
2. 禁止调 `/admin/api/login` 本身,避免自套娃。
3. 禁止覆盖 `host``content-length` 等危险头。
4. 请求超时固定收口。
5. 返回调试结果时回显状态码、响应头、响应文本预览。
该能力用于验证当前服务端接口,不等价于通用代理工具。
## 9. 配置项
新增以下环境变量:
1. `GENARRATIVE_ADMIN_USERNAME`
2. `GENARRATIVE_ADMIN_PASSWORD`
3. `GENARRATIVE_ADMIN_TOKEN_TTL_SECONDS`
默认策略:
1. 若未配置用户名或密码,则后台登录接口返回 `503`,后台页面显示“后台未启用”。
2. 默认管理员 token TTL 为 `4` 小时。
## 10. 测试要求
至少覆盖:
1. 管理员登录成功。
2. 管理员密码错误返回 `401`
3. 普通用户 token 访问后台接口返回 `403`
4. 未登录访问后台接口返回 `401`
5. 后台概览接口在未启用管理员配置时返回 `503`
6. API 调试接口能成功访问 `/healthz`
7. API 调试接口拒绝绝对 URL 和后台自身登录接口。
## 11. 路由清单
首版新增路由:
1. `GET /admin`
2. `POST /admin/api/login`
3. `GET /admin/api/me`
4. `GET /admin/api/overview`
5. `POST /admin/api/debug/http`
## 12. 完成定义
满足以下条件时,本任务视为完成:
1. `api-server` 内存在受保护后台管理域。
2. 管理员用户名密码可登录。
3. 普通用户 token 无法访问后台接口。
4. 后台能看到服务和数据库真实概览。
5. 后台能调试当前服务 HTTP 接口。
6. 路由索引与技术文档已同步更新。

View File

@@ -23,7 +23,9 @@ RPG 创作结果页已经能看到完整草稿内容,但页面底部仍然持
2. 场景章节主链字段为 `sceneChapterBlueprints`
3. `settingText` 也会承载世界总体一句话设定
`server-rs/crates/spacetime-module/src/custom_world/mod.rs``summarize_publish_gate_from_json(...)` 仍只检查旧字段:
问题最初在拆分后的 `server-rs/crates/spacetime-module/src/custom_world/mod.rs`被修过一版,但当前线上实际执行入口仍保留在 `server-rs/crates/spacetime-module/src/lib.rs`
也就是说,真正参与 Agent session snapshot、结果页 publish gate 刷新和 `publish_world` 动作校验的,仍然是 `lib.rs` 里的历史实现;而它还只检查旧字段:
1. `worldHook`
2. `playerPremise`
@@ -36,11 +38,13 @@ RPG 创作结果页已经能看到完整草稿内容,但页面底部仍然持
2. 发布门槛检查读的是旧字段
3. 同一个草稿在 UI 看起来“已经有内容”,但 gate 仍然误判为缺失
此外,正式发布编译在把 session draft 编译成发布 profile 时,也只把 `sceneChapters` 映射为 `sceneChapterBlueprints`,没有兼容当前更常见的 `sceneChapterBlueprints` 输入
因此会出现“拆分模块里的代码已经对齐,但页面实际 blocker 仍然不消失”的假象
此外,`lib.rs` 里的最小草稿兜底结构也没有补上 `sceneChapterBlueprints` 默认槽位,导致部分恢复、回滚和草稿兜底链路继续偏向旧 schema。
## 3. 修复策略
本轮统一把发布门槛与发布编译对齐到当前前端主链 schema
本轮统一把实际入口 `server-rs/crates/spacetime-module/src/lib.rs` 的发布门槛与最小草稿结构对齐到当前前端主链 schema
1. `world hook` 检查同时兼容:
- `worldHook`
@@ -60,11 +64,12 @@ RPG 创作结果页已经能看到完整草稿内容,但页面底部仍然持
4. 主线第一幕检查优先读取:
- `sceneChapterBlueprints[*].acts`
- `sceneChapters[*].acts`
5. 发布编译时,`sceneChapterBlueprints` 与旧 `sceneChapters` 都能写入最终 profile
5. 最小草稿兜底结构同时补上 `sceneChapterBlueprints` 空数组,避免恢复链路重新回落到旧字段集合
## 4. 验收标准
1. 结果页已包含 `anchorContent / creatorIntent / sceneChapterBlueprints` 的草稿,不再被旧 blocker 误判。
2. `publishReady` 会随当前 session 最新 preview 正确刷新。
3. “发布并进入世界”在 blocker 清空后恢复可点击。
4. 正式发布后的 compiled profile 仍保留 `sceneChapterBlueprints`
4. `ensure_minimal_draft_profile(...)` 生成的兜底草稿也包含 `sceneChapterBlueprints`
5. 新增 Rust 单测,覆盖“当前 Agent 结果 schema 不应再误报 blocker”与“最小草稿必须保留 `sceneChapterBlueprints` 默认槽位”。

View File

@@ -0,0 +1,175 @@
# Custom World `draft_foundation` 迁移到 `api-server + platform-llm` 方案
日期:`2026-04-23`
## 1. 背景
当前 RPG 创作 Agent 的 `draft_foundation` 虽然已经能把会话推进到结果页,但真实执行位置仍在 `spacetime-module``execute_draft_foundation_action(...)`
这条链路的问题是:
1. `draft_foundation` 没有走 `platform-llm`
2. SpacetimeDB reducer 内部自己从 `seed_text / session.draft_profile_json` 兜底拼草稿,属于规则编译,不是“真实 LLM 生成”。
3. reducer 按 SpacetimeDB 约束不应承担外部网络副作用,因此“让 reducer 里直接调 LLM”本身也是错误方向。
验证清单第三项要求是:
1. 草稿编译需要真实走 LLM。
2. 不能再用本地占位 compile 去冒充真实生成。
因此这条链必须改成:
```text
前端 action
-> api-server 接收 draft_foundation
-> platform-llm 真实生成 foundation draft
-> spacetime-client 调用 SpacetimeDB action/procedure 写回 session / card / gate / preview
-> 前端继续通过 operation 轮询完成态
```
## 2. 本轮目标
本轮只解决第三项验证要求最核心的问题:
1. `draft_foundation` 的草稿生成必须在 `api-server` 中完成。
2. `api-server` 必须真实调用 `platform-llm`
3. `spacetime-module` 只负责:
- 校验 action 执行条件
- 落库 session / draft card / checkpoint / publish gate / result preview
4. 前端协议尽量不变,继续保留:
- `POST /api/runtime/custom-world/agent/sessions/:sessionId/actions`
- `GET /api/runtime/custom-world/agent/sessions/:sessionId/operations/:operationId`
本轮不做:
1. 把旧 Node 的 foundation draft 全量多阶段 pipeline 一次性 1:1 搬到 Rust。
2. 额外新增前端 action 接口。
3. 在 SpacetimeDB 内新增“可联网 procedure”去直接调 LLM。
4.`legacyResultProfile` 兼容双重编译一起迁回主链。
## 3. 迁移后的职责边界
### 3.1 `api-server`
负责:
1. 识别 `draft_foundation` action。
2. 读取当前 session snapshot。
3. 基于真实 `seed_text``anchor_content / creator_intent / anchor_pack / draft_profile` 组织 foundation draft prompt。
4. 调用 `platform-llm::LlmClient` 获取首版草稿 JSON。
5. 做最小字段归一化,保证至少满足当前 `publish gate / result preview` 所需字段。
6. 把生成结果作为 `payload_json.draftProfile` 传给 `spacetime-client.execute_custom_world_agent_action(...)`
### 3.2 `spacetime-module`
负责:
1. 校验 session 是否允许执行 `draft_foundation`
2. 校验 payload 中必须带有外部已生成的 `draftProfile`
3.`draftProfile` 写入:
- `custom_world_agent_session.draft_profile_json`
- `custom_world_draft_card`
- `publish_gate_json`
- `result_preview_json`
- `checkpoints_json`
- `custom_world_agent_message`
- `custom_world_agent_operation`
4. 不再自己从 `seed_text` 兜底编译 `draftProfile`
5. 不再对 `draft_foundation` 的外部 `draftProfile` 做二次补全编译,避免责任边界重新漂回 SpacetimeDB。
### 3.3 `platform-llm`
负责:
1. 提供统一文本模型网关。
2. 返回 foundation draft JSON 文本。
## 4. 最小实现策略
## 4.1 先保留当前 action / operation 协议
前端现在的行为是:
1. `POST /actions` 拿到一个 `operation`
2. 进入“世界草稿生成进度”页
3. 轮询 `GET /operations/:operationId`
4. operation 完成后拉最新 session
因此本轮不改协议,只改服务端编排。
## 4.2 `draft_foundation` 的执行口径
`api-server` 接收到 `draft_foundation` 时:
1. 先读取当前 session。
2. 必须使用 session 中真实的 `seed_text` 与当前锚点组织 prompt不能误把 `session_id` 当作 seed 传给 LLM。
3.`progressPercent < 100`,直接返回错误。
4.`platform-llm` 生成 `draftProfile`
5. 用当前时间戳作为 action 提交时间。
6.`spacetime-client.execute_custom_world_agent_action(...)`,把 `draftProfile` 放进 payload。
7. 返回 SpacetimeDB 已落库的 operation。
首版保持同步完成,不额外引入新的 action finalize procedure。
原因:
1. 这样改动范围最小。
2. 已满足“LLM 在 api-server、SpacetimeDB 只负责落库”的验证要求。
3. 前端没有全局短超时,本轮可先接受单次 action 等待 LLM 返回。
如果后续需要更强的可观测性和更长耗时容忍,再把这条链拆成 submit/finalize 两段式后台任务。
## 4.3 foundation draft 的最小字段要求
本轮生成结果至少保证以下字段存在:
1. `name`
2. `subtitle`
3. `summary`
4. `worldHook`
5. `playerPremise`
6. `coreConflicts`
7. `playableNpcs`
8. `storyNpcs`
9. `landmarks`
10. `chapters`
11. `sceneChapterBlueprints`
这样可以直接满足当前 Rust `publish gate` 的最小校验,不会再次出现:
1. 草稿明明生成了
2. 但结果页仍然提示缺少 world hook / player premise / 主线章节 / 第一幕
## 5. 与旧 Node foundation draft 服务的关系
旧 Node 版本已经证明下面几点是成立的:
1. foundation draft 必须由后端调用真实 LLM。
2. foundation draft 与 preview compiler 应该拆边界。
3. `legacyResultProfile` 不应继续主导草稿主字段。
Rust 首版沿用这些结论,但不要求一次性照搬旧 Node 的全部多阶段拆分。
本轮只迁移:
1. “真实 LLM 生成 draft 主字段”这条主要求。
2. “结果落库由 SpacetimeDB 负责”这条边界。
## 6. 验收标准
满足以下条件时,这次迁移视为完成:
1. `draft_foundation``api-server` 真实调用 `platform-llm`
2. `spacetime-module``draft_foundation` 不再允许无 `draftProfile` 自行兜底编译。
3. 点击“生成游戏设定草稿”后session 的 `draft_profile_json / publish_gate_json / result_preview_json` 正常写回。
4. 结果页不再因为缺少最小底稿字段而错误阻断。
5. 定向测试通过。
6. 编码检查通过。
## 7. 相关文件
1. `server-rs/crates/api-server/src/custom_world.rs`
2. `server-rs/crates/api-server/src/custom_world_foundation_draft.rs`
3. `server-rs/crates/spacetime-module/src/lib.rs`
4. `server-rs/crates/module-custom-world/src/lib.rs`
5. `docs/technical/SPACETIMEDB_CUSTOM_WORLD_WORKS_AND_AGENT_EXTENSION_STAGE9_DESIGN_2026-04-22.md`

View File

@@ -0,0 +1,331 @@
# 公开编号用户搜索与广场作品搜索设计
## 1. 背景
当前前端展示的“叙世号”由前端基于 `AuthUser.id` 临时拼装:
- 前缀固定为 `SY-`
-`user.id``username` 去除非字母数字字符后的末 8 位
- 不足 8 位左侧补零
该方案只适合展示,不适合作为正式检索键,主要问题:
1. 它不是后端持久化字段,前后端对同一编号没有统一语义。
2. 它依赖 `user.id` 当前格式,一旦账号 ID 生成规则调整,展示号会漂移。
3. 只截取末 8 位存在潜在碰撞风险,不适合作为用户搜索主键。
4. 广场作品当前仅能通过 `ownerUserId + profileId` 读取详情,不利于做公开搜索和分享。
本次目标是把“公开编号”升级为后端一等字段,并同时支持:
1. 按用户公开编号搜索用户
2. 按作品公开编号搜索广场作品
3. 前端统一展示后端返回的公开编号,不再本地拼接
## 2. 目标与非目标
### 2.1 目标
1. 为用户增加稳定唯一的公开编号 `public_user_code`
2. 为发布到广场的作品增加稳定唯一的公开编号 `public_work_code`
3. 提供匿名可读的公开搜索接口
4. 平台首页 / 广场搜索框支持输入公开编号直达用户或作品
5. 搜索兼容用户输入的不同格式,如大小写、带不带前缀、是否包含空格
### 2.2 非目标
1. 本期不实现复杂全文搜索排序系统
2. 本期不做“用户主页”完整社交系统,只返回搜索命中的公开资料摘要
3. 本期不把所有广场列表改造成关键词后端分页搜索
4. 本期不修改既有业务主键 `user.id / profile_id`
## 3. 核心设计原则
1. **公开编号必须后端生成并持久化**
2. **公开编号只做公开检索键,不替代内部主键**
3. **前端只展示和透传公开编号,不自行拼装**
4. **公开接口只暴露最小必要公开信息**
5. **SpacetimeDB 查询走唯一索引或明确索引,不做无界扫描**
## 4. 数据模型设计
## 4.1 用户公开编号
在认证用户模型中新增字段:
- `public_user_code: String`
格式定义:
- 标准展示格式:`SY-00000001`
- 前缀固定:`SY-`
- 数字部分固定 8 位,左侧补零
生成规则:
1. 新建账号时,从认证存储中的 `next_user_id` 派生
2. 若用户内部 ID 为 `user_{:08}`,则公开编号同步使用同一序号生成
3. 一旦生成后永久不变
示例:
- `user_00000001` -> `SY-00000001`
- `user_00001234` -> `SY-00001234`
原因:
1. 与当前展示习惯兼容,用户认知成本最低
2. 不再依赖前端截断逻辑
3. 可在后端保证唯一性与稳定性
## 4.2 广场作品公开编号
在作品真相模型与广场快照中分别新增字段:
- `CustomWorldProfile.public_work_code: String`
- `CustomWorldProfile.author_public_user_code: String`
在广场作品快照模型 `CustomWorldGalleryEntry` 中新增字段:
- `public_work_code: String`
- `author_public_user_code: String`
格式定义:
- 标准展示格式:`CW-00000001`
- 前缀固定:`CW-`
- 数字部分固定 8 位,左侧补零
生成规则:
1. 作品第一次发布到广场时分配
2. 编号先写入 `CustomWorldProfile` 真相表,再同步到 `CustomWorldGalleryEntry`
3. 同一 `profile_id` 重复发布、更新、重新上架时沿用原编号
4. 删除后不回收编号
原因:
1. 作品公开检索和分享应使用作品级稳定编号,而不是 `ownerUserId + profileId`
2. 同一作品反复编辑发布不应导致公开编号变化
## 4.3 归一化规则
用户与作品公开编号搜索都应在后端统一做归一化,前端仅做轻提示,不做最终判定。
### 用户编号归一化
输入样例:
- `SY-00000001`
- `sy00000001`
- `00000001`
- ` sy-00000001 `
归一化步骤:
1. 去掉首尾空白
2. 转大写
3. 去掉所有非字母数字字符
4. 若结果以 `SY` 开头,则去掉前缀
5. 剩余部分必须为 1~8 位数字
6. 左侧补零到 8 位
7. 最终重建为标准格式 `SY-XXXXXXXX`
### 作品编号归一化
输入样例:
- `CW-00000001`
- `cw00000001`
- `00000001`
归一化步骤:
1. 去掉首尾空白
2. 转大写
3. 去掉所有非字母数字字符
4. 若结果以 `CW` 开头,则去掉前缀
5. 剩余部分必须为 1~8 位数字
6. 左侧补零到 8 位
7. 最终重建为标准格式 `CW-XXXXXXXX`
## 5. 后端接口设计
## 5.1 用户公开编号搜索
新增匿名可读接口:
- `GET /api/auth/public-users/by-code/{code}`
- `GET /api/auth/public-users/by-id/{userId}`
响应:
```json
{
"user": {
"id": "user_00000001",
"publicUserCode": "SY-00000001",
"displayName": "旅人一号"
}
}
```
说明:
1. `id` 返回内部 ID 仅供当前工程内部跳转与资源读取使用,不在 UI 上直接暴露为文案
2. 不返回手机号、登录方式、绑定状态、tokenVersion 等敏感字段
3. 未命中返回 `404`
4. `by-id` 仅接受内部 `user_XXXXXXXX` 这类用户 ID用于工程内跳转、运营排查或已有资源引用不替代公开叙世号主搜索语义
## 5.2 广场作品公开编号搜索
新增匿名可读接口:
- `GET /api/runtime/custom-world-gallery/by-code/{code}`
响应结构与现有广场详情接口一致:
```json
{
"entry": {
"ownerUserId": "user_00000001",
"profileId": "world-public-1",
"publicWorkCode": "CW-00000001",
"publicUserCode": "SY-00000001",
"authorDisplayName": "旅人一号",
"worldName": "雾港旧梦"
}
}
```
说明:
1. 作品搜索命中后仍使用现有详情页承载
2. 详情返回里补入 `publicWorkCode` 和作者 `publicUserCode`
## 6. SpacetimeDB 与认证存储改造
## 6.1 认证域
认证域目前是 `module-auth` 内存存储,不是 SpacetimeDB 表。
需要改造:
1. `AuthUser` 增加 `public_user_code`
2. `create_user / create_phone_user / create_pending_wechat_user` 统一生成公开编号
3. `AuthUserPayload` / 前端 `AuthUser` 合约增加 `publicUserCode`
4. `auth/me`、密码登录、手机登录、微信登录、绑手机返回都补齐该字段
## 6.2 广场作品表
`CustomWorldProfile` 增加:
1. `public_work_code: String`
2. `author_public_user_code: String`
`CustomWorldGalleryEntry` 增加:
1. `public_work_code: String`
2. `author_public_user_code: String`
索引建议:
1. `public_work_code` 唯一索引
2. 保留现有 `owner_user_id` 索引
3. `profile_id` 仍作为主键 / 唯一查找键之一
其中 `author_public_user_code` 本期可先不建索引,除非后续明确需要“按用户公开号列出该作者作品”。
## 6.3 作品公开编号分配策略
需要在模块内新增稳定计数状态,例如:
- `gallery_public_work_counter`
要求:
1. 每次首次发布作品时分配一次
2. 若作品已存在公开编号,则从 `CustomWorldProfile` 直接复用并同步到广场快照
3. 不因下架、删除、重新发布而重新编号
## 7. 前端交互设计
## 7.1 账号展示
当前首页资料卡和桌面顶部都展示前端拼装叙世号,改为:
1. 直接展示 `authUi.user.publicUserCode`
2. 复制按钮复制后端返回值
3. 若字段缺失才进入兼容兜底逻辑,但兼容逻辑仅作过渡
## 7.2 广场作品卡
广场作品卡和详情页增加:
1. 作品号 `CW-XXXXXXXX`
2. 作者叙世号 `SY-XXXXXXXX`
展示要求:
1. 以轻量辅助信息形式出现
2. 不在卡片默认堆过多说明文字
3. 移动端优先,避免挤压主要标题和摘要
## 7.3 搜索入口
平台首页顶部搜索框当前只是静态文案,需要接成真实输入与行为:
1. 当输入命中 `SY` 格式时,优先走用户公开号搜索
2. 当输入命中 `CW` 格式时,优先走作品公开号搜索
3. 当输入纯 1~8 位数字时:
- 先尝试作品号
- 未命中再尝试用户号
4. 暂不命中公开编号格式时,保持当前占位,不在本期强做全文关键词搜索
用户搜索命中后的最小行为:
1. 打开独立用户搜索结果面板或对话框
2. 展示头像字母、显示名、叙世号
3. 提供“查看该作者作品”入口
作品搜索命中后的行为:
1. 直接打开广场作品详情
## 8. 安全与边界
1. 用户公开搜索接口只允许返回公开资料摘要
2. 不允许通过公开接口枚举登录方式、绑定状态、设备信息、手机号掩码
3. 作品公开搜索接口只返回已公开发布的作品
4. 对不存在的编号统一返回未找到,避免泄露更多状态差异
## 9. 实现步骤
1.`module-auth` 用户模型与返回合约,补 `public_user_code`
2.`shared-contracts / packages/shared`,前后端统一 `publicUserCode`
3. 改首页展示,去掉前端本地拼装依赖
4. 改 SpacetimeDB `CustomWorldGalleryEntry` 表与快照,补 `public_work_code / author_public_user_code`
5. 新增广场按公开作品号读取 procedure / api-server 路由
6. 新增认证域按公开用户号读取接口
7. 改平台首页搜索框,支持按公开编号跳转
8. 补测试:
- 公开编号归一化
- 用户搜索命中/未命中
- 作品搜索命中/未命中
- 前端展示与复制
## 10. 验收标准
1. 新注册、游客自动登录、手机号登录、微信登录用户都能拿到稳定 `publicUserCode`
2. 首页与账户入口展示统一使用后端 `publicUserCode`
3. 新发布广场作品自动获得稳定 `publicWorkCode`
4. 输入 `SY-00000001` 可命中对应用户
5. 输入 `CW-00000001` 可命中对应广场作品并打开详情
6. 输入 `00000001` 时仍能归一化识别并命中
7. 既有广场列表、详情和发布流程不回归
## 11. 当前落地说明
1. 首页叙世号展示已优先读取后端 `publicUserCode`,原本基于 `AuthUser.id/username` 的前端拼装仅保留为兼容兜底,避免老会话未刷新时界面直接空白。
2. 用户公开搜索与广场作品公开搜索均已改为调用后端匿名接口,前端只负责输入、展示与跳转,不再自行决定最终编号格式。
3. 自定义世界发布链路已改为从认证服务读取真实 `public_user_code` 写入作品真相与广场读模型,不再从内部 `user_id` 临时反推 `SY-XXXXXXXX`
4. 当前作品号 `public_work_code` 仍采用基于 `profile_id` 的稳定 fallback 方案生成 `CW-XXXXXXXX`;若后续补独立计数表,需要在不改变读写接口的前提下替换生成来源。

View File

@@ -0,0 +1,230 @@
# 拼图 Agent 聊天接入大模型设计
日期:`2026-04-23`
## 1. 背景
当前拼图创作链已经具备:
1. `PuzzleAgentWorkspace` 前端聊天壳层
2. Rust `api-server``submit / stream / action` HTTP facade
3. `spacetime-module``puzzle_agent_session / puzzle_agent_message` 真相表
4. `module-puzzle` 的锚点推断、草稿编译、发布校验与运行态规则
`submit_puzzle_agent_message` 仍然是 deterministic 占位逻辑:
1. 用户发言后,`spacetime-module` 直接推断 `anchor_pack`
2. 同一事务里直接写入固定 assistant 总结文案
3. SSE `/messages/stream` 只是把 `last_assistant_reply` 一次性回放给前端
这导致拼图 Agent 看起来有聊天面板,但没有真实 LLM 共创能力。
## 2. 目标
本轮只恢复拼图 Agent 聊天主链的真实 LLM 接入,不扩到图片模型和复杂多阶段编排:
1. 用户发消息后assistant 回复必须来自 LLM
2. LLM 输出必须同时产出:
- `replyText`
- `progressPercent`
- `nextAnchorPack`
3. SSE `reply_delta` 必须来自真实流式增量解析
4. finalize 后一次性回写最新 session 真相
5. LLM 不可用或解析失败时,不再写固定 assistant 假回复
## 3. 分层边界
### 3.1 `module-puzzle`
只负责:
1. `PuzzleAnchorPack``PuzzleCreatorIntent``PuzzleResultDraft` 纯领域模型
2. 锚点清洗、草稿编译、发布校验
3. 运行态拼图规则
明确不负责:
1. 网络请求
2. LLM prompt 组织
3. SSE 推流
### 3.2 `spacetime-module`
只负责:
1. `submit_puzzle_agent_message`
- 校验 session / ownership / message id
- 只写入 user message
- 不再直接写 assistant message
- 不再直接推进 `current_turn / progress_percent / anchor_pack_json`
2. `finalize_puzzle_agent_message_turn`
- 追加 assistant message
- 覆盖 `anchor_pack_json`
- 回写 `current_turn / progress_percent / stage / last_assistant_reply / updated_at`
### 3.3 `api-server`
负责:
1. 读取拼图 session 快照
2. 组织 LLM prompt
3. 流式截取 `replyText`
4. 回合结束后组装 finalize 输入
5. 调用 SpacetimeDB finalize procedure
## 4. 目标链路
### 4.1 阶段 A提交消息
`submit_puzzle_agent_message`
职责:
1. 写入 user message
2. 返回 submit 后的 session 快照
3. 该快照中尚未新增 assistant message
4. 该快照中的 `anchorPack / progressPercent / currentTurn` 保持提交前真相
### 4.2 阶段 B完成单轮推理
`finalize_puzzle_agent_message_turn`
职责:
1. 追加 assistant message
2. 回写:
- `current_turn`
- `progress_percent`
- `stage`
- `anchor_pack_json`
- `last_assistant_reply`
- `updated_at`
3. 若已存在 `draft_json`,允许继续保留,不在聊天 finalize 中改写结果页草稿
## 5. LLM 输出契约
拼图聊天不需要复刻 Custom World 那么重的多层结构,本轮冻结为单个 JSON
```json
{
"replyText": "我已经收住画面方向了,接下来你更想强调夜雨反光,还是猫咪本身的奇幻感?",
"progressPercent": 46,
"nextAnchorPack": {
"themePromise": {
"key": "themePromise",
"label": "题材承诺",
"value": "雨夜中的奇幻探索",
"status": "confirmed"
},
"visualSubject": {
"key": "visualSubject",
"label": "画面主体",
"value": "发光猫咪站在遗迹台阶上",
"status": "confirmed"
},
"visualMood": {
"key": "visualMood",
"label": "视觉气质",
"value": "潮湿、梦幻、带轻微悬疑",
"status": "confirmed"
},
"compositionHooks": {
"key": "compositionHooks",
"label": "拼图记忆点",
"value": "台阶透视、倒影、远处遗迹门洞",
"status": "inferred"
},
"tagsAndForbidden": {
"key": "tagsAndForbidden",
"label": "标签与禁忌",
"value": "雨夜、猫咪、神庙遗迹;禁止文字水印",
"status": "inferred"
}
}
}
```
要求:
1. 只能输出 JSON
2. `progressPercent` 范围固定 `0..100`
3. `nextAnchorPack` 必须是完整对象,不允许只给增量 patch
## 6. Prompt 设计
本轮不追求“复杂创作状态识别器”,只做拼图场景最小可用 prompt
1. system prompt 明确模型角色是“拼图视觉共创策划”
2. 明确 5 个锚点:
- 题材承诺
- 画面主体
- 视觉气质
- 拼图记忆点
- 标签与禁忌
3. 明确回复必须:
- 自然中文
- 一次只推进一个最关键问题
- 不提“字段”“锚点”“JSON 结构”等内部词
4. user prompt 携带:
- 当前 turn / progress
- 当前 anchor pack
- 最近聊天记录
- 输出 JSON 契约
## 7. SSE 口径
`POST /api/runtime/puzzle/agent/sessions/:sessionId/messages/stream`
成功时按顺序输出:
1. 多个 `reply_delta`
2. 一个 `session`
3. 一个 `done`
失败时输出:
1. `error`
要求:
1. `reply_delta.text` 来自真实流式累计 `replyText`
2. `session` 必须来自 finalize 后重新读取的最新 session
## 8. 错误策略
1. 如果 `llm_client` 未配置:
- 普通接口返回 `502`
- SSE 返回 `error`
- 不写 assistant message
2. 如果 LLM 响应解析失败:
- 普通接口返回 `502`
- SSE 返回 `error`
- 只保留 user message
3. 如果 finalize 失败:
- 普通接口返回 `502`
- SSE 返回 `error`
- 以前端重新拉 session 为准
## 9. 实现清单
1. `module-puzzle`
- 新增 `PuzzleAgentMessageFinalizeInput`
2. `spacetime-module/src/puzzle.rs`
- 新增 `finalize_puzzle_agent_message_turn`
- 修改 `submit_puzzle_agent_message`
3. `spacetime-client`
- 刷新 Rust bindings
- 新增 `finalize_puzzle_agent_message(...)`
4. `api-server`
- 新增 `puzzle_agent_turn.rs`
- `puzzle.rs` 的普通消息与 SSE 改接 turn service
5. `docs/technical/README.md`
- 补入本文档索引
## 10. 验收
1. 拼图 Agent 普通发送接口不再返回固定 assistant 文案
2. 拼图 Agent SSE 可看到逐步增长的 `reply_delta`
3. finalize 后 `session.messages` 中新增 assistant chat 消息
4. `session.anchorPack / progressPercent / currentTurn / lastAssistantReply` 已真实更新
5. LLM 关闭时不会再写入伪造 assistant 回复

View File

@@ -4,7 +4,13 @@
## 文档列表
- [ADMIN_CONSOLE_SERVICE_DESIGN_2026-04-23.md](./ADMIN_CONSOLE_SERVICE_DESIGN_2026-04-23.md):冻结 Rust `api-server` 内后台管理服务首版方案,明确管理员用户名密码登录、管理员 JWT 鉴权、数据库概览、受控 API 调试台与同源管理页面的落地边界。
- [SPACETIME_MODULE_LIB_RS_SPLIT_EXECUTION_2026-04-23.md](./SPACETIME_MODULE_LIB_RS_SPLIT_EXECUTION_2026-04-23.md):冻结 `server-rs/crates/spacetime-module/src/lib.rs` 的模块地图、二级落位点与迁移顺序,要求后续 SpacetimeDB 主工程改动按对应模块落位,不再继续堆回单大文件。
- [CUSTOM_WORLD_DRAFT_FOUNDATION_API_SERVER_LLM_MIGRATION_2026-04-23.md](./CUSTOM_WORLD_DRAFT_FOUNDATION_API_SERVER_LLM_MIGRATION_2026-04-23.md):冻结 `draft_foundation` 从 SpacetimeDB 内部规则编译迁到 `api-server + platform-llm` 的边界,明确草稿必须由 `api-server` 真实调 LLM 生成SpacetimeDB 只负责落库。
- [CUSTOM_WORLD_AGENT_LLM_REPLY_RESTORE_2026-04-22.md](./CUSTOM_WORLD_AGENT_LLM_REPLY_RESTORE_2026-04-22.md):恢复 Custom World Agent 聊天必须走大模型推理的 Rust 落地方案,冻结 submit/finalize 两阶段职责、旧 Node 提示词原样搬运、SSE 流式回复与 session 回写边界。
- [PUZZLE_AGENT_LLM_REPLY_INTEGRATION_2026-04-23.md](./PUZZLE_AGENT_LLM_REPLY_INTEGRATION_2026-04-23.md):冻结拼图 Agent 聊天接入真实 LLM 的最小 Rust 落地方案,明确 submit 只写 user message、`api-server` 承接推理、SSE 流式回放与 finalize 回写 session 真相的边界。
- [UNIFIED_CREATION_DRAFT_SESSION_RESTORE_2026-04-23.md](./UNIFIED_CREATION_DRAFT_SESSION_RESTORE_2026-04-23.md):冻结创作中心全草稿恢复 Agent 会话的统一口径,覆盖 RPG、Big Fish、Puzzle 三类草稿的会话索引、结果页分流和 Big Fish works 最小投影边界。
- [PUZZLE_DRAFT_SESSION_RESTORE_2026-04-23.md](./PUZZLE_DRAFT_SESSION_RESTORE_2026-04-23.md):冻结拼图结果页草稿进入创作中心后的恢复口径,明确 draft 作品投影、`sourceSessionId` 反查 Agent session、编译时同步落草稿卡与发布复用同一作品记录。
- [RUST_LOCAL_DEV_SPACETIMEDB_PUBLISH_GUARD_AND_AGENT_LLM_FAILURE_POLICY_2026-04-23.md](./RUST_LOCAL_DEV_SPACETIMEDB_PUBLISH_GUARD_AND_AGENT_LLM_FAILURE_POLICY_2026-04-23.md):冻结 Rust 本地联调启动前必须 publish/generate 最新 `spacetime-module` 的守卫,以及 Custom World Agent 在 LLM 失败时禁止写固定 assistant 回复的 finalize 与 HTTP/SSE 错误策略。
- [CREATION_AGENT_CHAT_SCROLL_FOLLOW_POLICY_FIX_2026-04-23.md](./CREATION_AGENT_CHAT_SCROLL_FOLLOW_POLICY_FIX_2026-04-23.md):记录统一创作聊天工作区从“每次更新都强制滚到底”改为“仅在用户仍停留在底部附近时跟随”的滚动策略修复,避免流式回复持续抢走阅读位置。
- [CREATION_AGENT_STREAMING_MESSAGE_STABILITY_FIX_2026-04-23.md](./CREATION_AGENT_STREAMING_MESSAGE_STABILITY_FIX_2026-04-23.md):记录创作 Agent 聊天流式文本、玩家乐观消息、最终 session 回写和草稿切换的展示稳定性修复,避免乱码、闪消、插队和旧草稿闪烁。
@@ -14,7 +20,7 @@
- [CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md](./CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md):记录创作中心点击类别后长时间停留在“正在开启”的根因与修复口径,收口前端创建会话启动超时、中文错误提示以及 Big Fish / 拼图代理上游超时兜底。
- [JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md](./JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md):冻结 Jenkins `构建 / 部署 / 构建并部署` 三条流水线的职责、版本号传递、上游触发门禁、本地目录部署脚本与 `/home/ubuntu/Genarrative-deploy/` 覆盖策略。
- [RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md](./RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md):冻结 Rust 本地一键联调脚本与 Ubuntu 发布包构建脚本的执行口径,覆盖 `npm run dev:rust``npm run build:rust:ubuntu`、Vite release、Linux `api-server`、SpacetimeDB wasm、启动停止脚本、默认 scp 上传和安全清库开关。
- [RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md](./RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md):记录当前 Rust `api-server` 已挂载的 96 条 Axum 路由,按 auth、assets、runtime、custom world、story、generated path 等挂载面归类,用于对照 Node 能力基线与切流 smoke 清单。
- [RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md](./RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md):记录当前 Rust `api-server` 已挂载的 101 条 Axum 路由,并补充管理后台入口与管理接口索引,按 auth、assets、runtime、custom world、story、generated path 等挂载面归类,用于对照 Node 能力基线与切流 smoke 清单。
- [BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md](./BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md):冻结后端重写收口阶段的横向治理规则,覆盖 TypeScript contract 到 Rust DTO 映射、SpacetimeDB schema 演进、大对象 / workflow cache 存储边界和文档维护门禁。
- [PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md](./PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md)`platform-llm` 文本模型网关首版设计,冻结 OpenAI 兼容 `/chat/completions`、SSE 增量解析、错误模型与重试边界。
- [API_SERVER_PLATFORM_LLM_PROXY_DESIGN_2026-04-21.md](./API_SERVER_PLATFORM_LLM_PROXY_DESIGN_2026-04-21.md)`api-server` 接入 `platform-llm` 的最小代理设计,冻结 `/api/llm/chat/completions` 的配置、状态注入与首版非流式兼容边界。
@@ -30,6 +36,10 @@
- [PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md](./PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md):冻结手机号验证码发送链路的日志补强口径,确保 `api-server``module-auth``platform-auth` 能直接暴露发送前后与错误分类关键字段。
- [PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md](./PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md):冻结短信平台受理成功与最终送达状态的区分方式、追踪字段、送达回执接口和前端提示文案边界。
- [PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第一项“真实短信验证码链路”的本地启动、前端操作、日志观察点、通过标准与失败排查步骤。
- [ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第四项“图片、视频、动作真实外部生成”的人工联调口径,明确哪些入口已接真实外部图片服务、哪些入口仍是 Stage 1 占位链,以及前端点击路径、日志观察点和通过标准。
- [M6_CHARACTER_VISUAL_ASSET_EXTERNAL_GENERATION_STAGE2_2026-04-23.md](./M6_CHARACTER_VISUAL_ASSET_EXTERNAL_GENERATION_STAGE2_2026-04-23.md):冻结角色主形象从 Stage 1 SVG 占位草稿切到 DashScope 真实出图的 Stage 2 口径覆盖模型、参考图解析、去底处理、OSS 草稿存储和失败策略。
- [M6_CHARACTER_ANIMATION_ASSET_EXTERNAL_GENERATION_STAGE2_2026-04-23.md](./M6_CHARACTER_ANIMATION_ASSET_EXTERNAL_GENERATION_STAGE2_2026-04-23.md):冻结角色动作从 Stage 1 占位视频切到真实 Ark/DashScope 生成的 Stage 2 口径,优先明确 image-to-video 主链、Ark 固定参数、媒体上传与错误回退规则。
- [M6_CHARACTER_ANIMATION_BACKEND_FRAME_EXTRACTION_AND_PUBLISH_STAGE3_2026-04-23.md](./M6_CHARACTER_ANIMATION_BACKEND_FRAME_EXTRACTION_AND_PUBLISH_STAGE3_2026-04-23.md):冻结角色动作正式发布链从“前端抽帧回传”继续迁到“后端抽帧、去绿幕、上传 OSS、落库绑定”的 Stage 3 口径,明确 `ffmpeg/ffprobe` 依赖、contract 扩展与兼容策略。
- [WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md](./WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md)Rust `api-server` 微信登录实现设计,冻结微信 provider 接入、系统 JWT 签发边界、`wechat/start` / `wechat/callback` / `wechat/bind-phone` 闭环,以及与后续 `SpacetimeDB` claims 透传的关系。
- [WECHAT_LOGIN_REAL_INTEGRATION_RUNBOOK_2026-04-21.md](./WECHAT_LOGIN_REAL_INTEGRATION_RUNBOOK_2026-04-21.md):微信登录从本地 mock 到真实微信开放平台联调的执行手册,覆盖环境变量、回调域名、代理头要求、验证步骤与常见失败排查。
- [PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md](./PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md):密码登录与自动建号落地设计,冻结 `/api/auth/entry`、幂等兼容策略、模块边界以及与 JWT / refresh cookie 的衔接方式。

View File

@@ -1,6 +1,6 @@
# Rust API Server 路由索引2026-04-22
# Rust API Server 路由索引2026-04-23
更新时间:`2026-04-22`
更新时间:`2026-04-23`
## 1. 文档目标
@@ -10,27 +10,36 @@
## 2. 当前统计
当前 Rust `api-server``app.rs` 可抽取到 `96` 条路由:
当前 Rust `api-server``app.rs` 可抽取到 `101` 条路由:
1. 内部鉴权调试接口:`2` 条。
2. AI task 接口:`9` 条。
3. assets / OSS 接口:`15` 条。
4. auth 接口:`12` 条。
5. custom world / agent 接口:`23` 条。
6. llm proxy 接口:`1` 条。
7. profile / runtime profile 接口:`12` 条。
8. runtime story / story gameplay 接口:`15` 条。
9. legacy generated 静态路径兼容`6` 条。
10. health check`1` 条。
1. 管理后台接口:`5` 条。
2. 内部鉴权调试接口:`2` 条。
3. AI task 接口:`9` 条。
4. assets / OSS 接口:`15` 条。
5. auth 接口:`12` 条。
6. custom world / agent 接口:`23` 条。
7. llm proxy 接口:`1` 条。
8. profile / runtime profile 接口:`12` 条。
9. runtime story / story gameplay 接口`15` 条。
10. legacy generated 静态路径兼容`6` 条。
11. health check`1` 条。
## 3. 路由清单
### 3.1 内部鉴权调试
### 3.1 管理后台
1. `GET /admin`
2. `POST /admin/api/login`
3. `GET /admin/api/me`
4. `GET /admin/api/overview`
5. `POST /admin/api/debug/http`
### 3.2 内部鉴权调试
1. `GET /_internal/auth/claims`
2. `GET /_internal/auth/refresh-cookie`
### 3.2 AI Task
### 3.3 AI Task
1. `POST /api/ai/tasks`
2. `POST /api/ai/tasks/{task_id}/start`
@@ -42,7 +51,7 @@
8. `POST /api/ai/tasks/{task_id}/stages/{stage_kind}/start`
9. `POST /api/ai/tasks/{task_id}/stages/{stage_kind}/complete`
### 3.3 Assets / OSS
### 3.4 Assets / OSS
1. `POST /api/assets/direct-upload-tickets`
2. `POST /api/assets/sts-upload-credentials`
@@ -60,7 +69,7 @@
14. `GET /api/assets/character-workflow-cache/{character_id}`
15. `GET / POST /api/assets/character-workflow-cache`
### 3.4 Auth
### 3.5 Auth
1. `GET /api/auth/login-options`
2. `GET /api/auth/me`
@@ -75,7 +84,7 @@
11. `POST /api/auth/wechat/bind-phone`
12. `POST /api/auth/entry`
### 3.5 Custom World / Agent
### 3.6 Custom World / Agent
1. `GET /api/runtime/custom-world-library`
2. `GET /api/runtime/custom-world-library/{profile_id}`
@@ -101,11 +110,11 @@
22. `POST /api/runtime/custom-world/cover-image`
23. `POST /api/runtime/custom-world/cover-upload`
### 3.6 LLM Proxy
### 3.7 LLM Proxy
1. `POST /api/llm/chat/completions`
### 3.7 Profile / Runtime Profile
### 3.8 Profile / Runtime Profile
1. `GET /api/profile/dashboard`
2. `GET /api/runtime/profile/dashboard`
@@ -120,7 +129,7 @@
11. `POST /api/profile/save-archives/{world_key}`
12. `POST /api/runtime/profile/save-archives/{world_key}`
### 3.8 Runtime Story / Gameplay
### 3.9 Runtime Story / Gameplay
1. `POST /api/runtime/save/snapshot`
2. `GET /api/runtime/settings`
@@ -138,7 +147,7 @@
14. `POST /api/story/npc/battle`
15. `GET /api/runtime/sessions/{runtime_session_id}/inventory`
### 3.9 Legacy Generated 路径
### 3.10 Legacy Generated 路径
1. `GET /generated-character-drafts/{*path}`
2. `GET /generated-characters/{*path}`
@@ -147,7 +156,7 @@
5. `GET /generated-custom-world-covers/{*path}`
6. `GET /generated-qwen-sprites/{*path}`
### 3.10 Health
### 3.11 Health
1. `GET /healthz`

View File

@@ -0,0 +1,241 @@
# spacetime-module `lib.rs` 拆分执行方案
日期:`2026-04-23`
## 1. 背景
当前 `server-rs/crates/spacetime-module/src/lib.rs` 已经超过 `9000` 行,同时混合了承载:
1. SpacetimeDB 主工程入口
2. 跨域共享类型
3. Big Fish
4. Asset Metadata
5. Runtime
6. Gameplay
7. Custom World
8. AI
9. Puzzle
10. 测试
这会直接导致:
1. 任意一个领域改动都要在同一个超大文件里定位
2. 不同玩法与领域的 reducer / procedure / helper 互相缠绕
3. 结构上已经与 `M7` 冻结的“按业务与 SpacetimeDB 聚合层次拆分目录”目标不一致
## 2. 本轮约束
本轮拆分严格遵守以下规则:
1. 只做物理结构收口,不改 table 名、reducer 名、procedure 名。
2. 不改现有 schema 字段名、字段顺序语义与已有对外 contract。
3. 不把外部副作用搬进 SpacetimeDB reducer / procedure。
4. 允许在过渡期继续保留少量跨域 helper 在 `lib.rs`,但新增内容禁止继续直接堆回 `lib.rs`
## 3. 模块地图
### 3.1 根入口
`server-rs/crates/spacetime-module/src/lib.rs`
后续只允许保留:
1. `use` 聚合
2. `mod` 声明
3. 少量跨域共享 helper
4. 迁移过渡期测试
禁止继续新增某个具体业务域的 table / reducer / procedure / tx helper。
导入导出风格同步冻结为:
1. `src/lib.rs` 对外统一优先使用 `pub use xxx::*;` 重新导出主工程模块与外部模块 crate 内容。
2. 已拆出的业务模块内部统一优先使用 `use crate::*;`,避免每个文件重复维护大段显式 `use` 列表。
3. 子模块只有遇到命名冲突、宏限制或无法从 crate 根重导出的外部符号时,才允许补局部显式 `use`
4. 后续拆分时不再因为导入列表变长而把业务实现留在 `lib.rs`
### 3.2 已冻结的一级模块
1. `src/entry.rs`
- 模块初始化入口
- `#[spacetimedb::reducer(init)]`
2. `src/domain_types.rs`
- 跨域共享的 SpacetimeDB 类型
- 目前主要承载 NPC 开战桥接输入输出
3. `src/asset_metadata/mod.rs`
- `asset_object`
- `asset_entity_binding`
- 资产确认与绑定 reducer / procedure
4. `src/big_fish/mod.rs`
- Big Fish session / message / asset slot / runtime run
- Big Fish procedure 与 tx helper
5. `src/runtime/mod.rs`
- Runtime settings / snapshots / browse history / dashboard / wallet / save archive
6. `src/gameplay/mod.rs`
- `story / combat / inventory / npc / quest / runtime_item / progression`
7. `src/custom_world/mod.rs`
- profile / session / agent / publish / gallery / works
8. `src/ai/mod.rs`
- ai task / stage / chunk / reference
9. `src/puzzle.rs`
- 拼图玩法当前仍为单文件域模块,后续再决定是否继续拆目录
### 3.3 本轮先创建的二级落位点
#### `asset_metadata/`
1. `objects.rs`
2. `bindings.rs`
#### `big_fish/`
1. `tables.rs`
2. `session.rs`
3. `assets.rs`
4. `runtime.rs`
#### `runtime/`
1. `settings.rs`
2. `snapshots.rs`
3. `browse_history.rs`
4. `profile.rs`
#### `gameplay/`
1. `combat.rs`
2. `inventory.rs`
3. `npc.rs`
4. `progression.rs`
5. `quest.rs`
6. `runtime_item.rs`
7. `story.rs`
#### `custom_world/`
1. `profile.rs`
2. `session.rs`
3. `agent.rs`
4. `publishing.rs`
5. `gallery.rs`
6. `works.rs`
#### `ai/`
1. `tasks.rs`
2. `stages.rs`
3. `snapshots.rs`
## 4. 迁移顺序
为了降低脏工作区下的冲突风险,本轮按下面顺序推进:
1. 先冻结文档与 README 路由规则。
2. 先创建二级空模块文件,作为后续内容落位点。
3. 第一批先迁:
- `entry.rs`
- `domain_types.rs`
- `asset_metadata/mod.rs`
- `big_fish/mod.rs`
4. 第二批再迁:
- `runtime/mod.rs`
- `gameplay/mod.rs`
5. 第三批再迁:
- `custom_world/mod.rs`
- `ai/mod.rs`
6. `puzzle.rs` 暂时保持单文件,不在本轮强拆。
## 5. 本轮完成标准
1. `README.md` 明确声明后续新增逻辑的落位规则。
2. 一级模块与二级空模块文件创建完成。
3. `lib.rs` 不再承载 `init`、共享 domain types、asset metadata、big fish 的实现细节。
4. `cargo check -p spacetime-module --lib` 继续通过。
5. 中文文档与 Rust 文件经过编码检查,没有写坏。
## 6. `runtime` 域实拆记录
`2026-04-23` 追加执行 `runtime` 域真实内容拆分,目标是把根入口中的 runtime 表、procedure 与同域 tx helper 收口到 `src/runtime/`,同时保持对外 API 名称不变。
### 6.1 已落位文件
1. `server-rs/crates/spacetime-module/src/runtime/mod.rs`
- 仅保留二级模块声明与 `pub use xxx::*;` 聚合导出。
2. `server-rs/crates/spacetime-module/src/runtime/settings.rs`
- 承载 `RuntimeSetting`
- 承载 runtime setting 的读取、upsert procedure 与快照构建 helper。
3. `server-rs/crates/spacetime-module/src/runtime/snapshots.rs`
- 承载 `RuntimeSnapshotRow`
- 承载 runtime snapshot 的读取、upsert、delete 与 JSON 解析 helper。
4. `server-rs/crates/spacetime-module/src/runtime/browse_history.rs`
- 承载 `UserBrowseHistory`
- 承载平台浏览历史 list、upsert、clear procedure 与行转换 helper。
5. `server-rs/crates/spacetime-module/src/runtime/profile.rs`
- 承载 `ProfileDashboardState`
- 承载 `ProfileWalletLedger`
- 承载 `ProfilePlayedWorld`
- 承载 `ProfileSaveArchive`
- 承载 profile dashboard、wallet ledger、play stats、save archive 投影与 snapshot 同步 helper。
### 6.2 根入口调整
`server-rs/crates/spacetime-module/src/lib.rs` 当前只通过下面方式接入 runtime 域:
1. `mod runtime;`
2. `pub use runtime::*;`
原先留在 `lib.rs` 的 runtime setting、snapshot、profile、browse history 旧 helper 已删除,避免同名实现重复存在,也避免后续继续在根入口堆叠 runtime 业务逻辑。
### 6.3 后续维护规则
1. 对外导出继续通过 `pub use runtime::*;` 以及 `src/runtime/mod.rs` 中的 `pub use xxx::*;` 透出。
2. `runtime` 子文件内部优先使用 `use crate::*;`,只有 SpacetimeDB table accessor trait、命名冲突或宏限制需要时才补最小显式导入。
3. 新增 runtime 相关表、procedure、reducer 或 tx helper 时,必须先按 `settings / snapshots / browse_history / profile` 判断二级落位点;不匹配时先更新本文件与 README再新增二级模块。
4. `lib.rs` 只保留跨域共享 helper 和尚未迁出的过渡代码,不再接收 runtime 域新增实现。
## 7. `ai` 域实拆记录
`2026-04-23` 追加执行 `ai` 域真实内容拆分,目标是把根入口中的 AI 表、procedure 与同域 tx helper 收口到 `src/ai/`,同时保持 `module-ai` 对外输入输出 contract 与 SpacetimeDB reducer / procedure 名称不变。
### 7.1 已落位文件
1. `server-rs/crates/spacetime-module/src/ai/mod.rs`
- 仅保留二级模块声明与聚合导出。
- public API 通过 `pub use stages::*;``pub use tasks::*;` 透出。
- 内部转换 helper 通过 `pub(crate) use snapshots::*;` 供 AI 子模块共享。
2. `server-rs/crates/spacetime-module/src/ai/tasks.rs`
- 承载 `AiTask`
- 承载 `create_ai_task`
- 承载 `create_ai_task_and_return`
- 承载 `start_ai_task`
- 承载 `complete_ai_task_and_return`
- 承载 `fail_ai_task_and_return`
- 承载 `cancel_ai_task_and_return`
- 承载 task 状态迁移、读取与持久化 helper。
3. `server-rs/crates/spacetime-module/src/ai/stages.rs`
- 承载 `AiTaskStage`
- 承载 `AiTextChunk`
- 承载 `AiResultReference`
- 承载 `start_ai_task_stage`
- 承载 `append_ai_text_chunk_and_return`
- 承载 `complete_ai_stage_and_return`
- 承载 `attach_ai_result_reference_and_return`
- 承载阶段流式文本聚合、阶段替换与 result reference 写入 helper。
4. `server-rs/crates/spacetime-module/src/ai/snapshots.rs`
- 承载 `AiTask / AiTaskStage / AiTextChunk / AiResultReference` 的 row 与 snapshot 转换 helper。
### 7.2 根入口调整
`server-rs/crates/spacetime-module/src/lib.rs` 当前只通过下面方式接入 AI 域:
1. `mod ai;`
2. `pub use ai::*;`
原先留在 `lib.rs` 的 AI 表、procedure 与 helper 已删除,避免根入口继续堆叠 AI 业务实现。
### 7.3 后续维护规则
1. 对外导出继续通过 `pub use ai::*;` 以及 `src/ai/mod.rs` 中的 `pub use xxx::*;` 透出。
2. `ai` 子文件内部优先使用 `use crate::*;`,只有 `module_ai` 中的归一化函数、校验函数、ID 前缀常量等需要避免 glob 歧义的符号才补最小显式导入。
3. 新增 AI 相关表、procedure、reducer 或 tx helper 时,必须先按 `tasks / stages / snapshots` 判断二级落位点;不匹配时先更新本文件与 README再新增二级模块。
4. `lib.rs` 不再接收 AI 域新增实现。