# 拼图生成图片读取链路修复
日期:`2026-04-27`
更新:`2026-05-08`
## 背景
拼图结果页的“生成或更换图片”会在 `api-server` 中调用 DashScope 生成图片,再把候选图上传到 OSS,最终以 `/generated-puzzle-assets/...` 旧兼容路径写回 `PuzzleGeneratedImageCandidate.image_src` 与草稿封面字段。
历史排查曾发现拼图图片写入路径已经进入 `platform-oss::LegacyAssetPrefix::PuzzleAssets`,但后端 Axum 旧资源代理和 Vite 本地代理没有挂载 `/generated-puzzle-assets`。当时的处理口径是补旧资源代理。
当前 `WP-DEL` 后,旧 `/generated-*` 直读代理已经物理删除;`/generated-puzzle-assets/...` 只允许作为 `legacyPublicPath` / OSS object key 标识。浏览器不能再直接请求该路径,必须通过 `/api/assets/read-url?legacyPublicPath=...` 换取短期签名 URL 后预览。
## 修复口径
1. `platform-oss::LEGACY_PUBLIC_PREFIXES` 必须包含 `generated-puzzle-assets`,保持直传票据、服务端上传、read-url 支持列表和错误提示同一口径。
2. `src/services/assetReadUrlService.ts` 的 `isGeneratedLegacyPath(...)` 需要同时识别 `/generated-puzzle-assets/...` 和 `generated-puzzle-assets/...`。后者可能来自 object key 形态的历史字段。
3. `ResolvedAssetImage` / `useResolvedAssetReadUrl` 在签名 URL 返回前不能把裸 `/generated-*` 或 `generated-*` 写进 `
`。
4. `refreshKey` 只能用于跳过前端签名 URL 缓存并重新请求 `/api/assets/read-url`,不能再给 OSS V4 签名 URL 追加 `_v` 等额外 query;OSS 会把 query 纳入签名,额外参数会让新生成图变成 403/破图。
5. 历史素材被选为参考图后,参考图小预览也必须走 `ResolvedAssetImage`,不能使用裸 `
`。
6. 禁止恢复 `/generated-puzzle-assets/{*path}` Axum 路由或 Vite 直读代理;正式读取统一走 `/api/assets/read-url`。
## 后续约束
1. 任何新增 `LegacyAssetPrefix` 都必须同时检查:
- `platform-oss` 前缀枚举
- `platform-oss::LEGACY_PUBLIC_PREFIXES`
- `/api/assets/read-url` 入参解析
- 前端 `isGeneratedLegacyPath(...)` 是否能识别
2. 拼图候选图 JSON 仍保持 SpacetimeDB 持久化结构 `PuzzleGeneratedImageCandidate` 的 snake_case 字段,不把 HTTP camelCase 响应结构写入 `draft_json`。
3. 图片生成、OSS 读写和外部参考图解析继续留在 `api-server`,不能下沉到 SpacetimeDB reducer。
4. 如果图片组件需要刷新 generated 私有资源,优先让 `refreshKey` 触发重新换签;不要修改已返回的 `signedUrl`。
## 验收
1. `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`
2. `cargo test -p platform-oss --manifest-path server-rs\Cargo.toml`
3. `npm run check:encoding`
4. `npm run api-server` 重启后检查 `/healthz`,再点击拼图结果页“生成或更换图片”,候选图应能写回并正常展示。