# SpacetimeDB 资产对象存储设计 日期:`2026-04-21` ## 1. 文档目的 这份文档用于冻结当前 `assets / OSS` 前置落地后的两个关键口径: 1. 阿里云 OSS 当前按**私有 bucket** 模式接入。 2. 后续 `SpacetimeDB` 的资产对象引用统一按 `bucket` 与 `object_key` **两列存储**,而不是拼成单个路径字符串。 这份文档直接服务于以下工程收口动作: 1. `platform-oss` 的输出 contract 收敛。 2. `api-server` 的直传票据接口收敛。 3. 后续 `asset_object`、`asset_manifest`、业务绑定表的字段设计收敛。 ## 2. 当前已确认事实 ### 2.1 Bucket 访问策略 当前阿里云 OSS bucket: - `bucket`:`xushi-dev` - `endpoint`:`oss-cn-beijing.aliyuncs.com` 已确认: 1. bucket 当前为**私有读写**。 2. 服务端可通过 `AccessKeyId / AccessKeySecret` 正常访问 bucket。 3. 浏览器可通过服务端签发的 `PostObject` 票据完成上传。 4. 未签名的对象公开 URL 返回 `403`,不能当成正式读取链路。 ### 2.2 当前工程能力状态 当前 `server-rs` 已落地: 1. `platform-oss`:浏览器 `PostObject` 直传签名 2. `api-server`:`POST /api/assets/direct-upload-tickets` 3. `platform-oss`:私有对象短期签名读取 URL 4. `api-server`:`GET /api/assets/read-url` 5. `server-rs/scripts/oss-smoke.ps1`:真实 OSS 联调脚本 6. `platform-oss`:私有 `HEAD Object` 探测 7. `api-server`:`POST /api/assets/objects/confirm` 8. `module-assets`:进程内 `asset_object` 确认服务 9. `platform-oss`:服务端 `PutObject` 上传 helper 10. `api-server`:`POST /api/assets/sts-upload-credentials` 禁用式 contract 当前仍未落地: 1. `STS` 真实临时授权下发 2. multipart 分片上传 3. 内容 hash 自动计算与版本字段细化 当前上传完成确认接口的详细请求、校验顺序与写入规则见: 1. [ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](./ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md) 服务端上传与 STS 禁用式 contract 的详细设计见: 1. [M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md](./M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md) ## 3. 正式存储口径 ### 3.1 SpacetimeDB 中的对象引用格式 后续 `SpacetimeDB` 中凡是需要引用 OSS 对象的地方,统一按以下两列存储: 1. `bucket` 2. `object_key` 不采用以下做法作为正式真相: 1. 不存完整公开 URL 2. 不存 `bucket/object_key` 拼接后的单字符串 3. 不存依赖 bucket 权限假设的匿名可读 URL ### 3.2 为什么必须拆成两列 原因固定如下: 1. bucket 后续可能按环境、业务线或冷热分层拆桶。 2. `object_key` 本身才是对象路径主键;`bucket` 是对象所属仓。 3. 后续签名下载 URL、服务端代理读取、对象迁移都需要分别拿到 `bucket` 与 `object_key`。 4. 若只存拼接字符串,后续查询、迁移、批量回填和索引都更差。 ## 4. 推荐表字段 ### 4.1 `asset_object` 后续 `asset_object` 至少应包含: 1. `id` 2. `bucket` 3. `object_key` 4. `access_policy` 5. `content_type` 6. `content_length` 7. `content_hash` 8. `version` 9. `source_job_id` 10. `owner_user_id` 11. `profile_id` 12. `entity_id` 13. `asset_kind` 14. `created_at` 15. `updated_at` 当前已落到可编码级别的字段类型、索引和访问级别见: 1. [SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md) ### 4.2 业务资产表 如: 1. `character_visual_asset` 2. `character_animation_asset` 3. `scene_image_asset` 4. `sprite_sheet_asset` 这些表不直接重复存 URL,而是引用: 1. `asset_object_id` 2. 或明确的 `bucket + object_key` 优先建议: 1. 强业务关系表引用 `asset_object_id` 2. `asset_object` 再统一承载 `bucket + object_key` ## 5. API 输出口径 ### 5.1 直传票据接口 `POST /api/assets/direct-upload-tickets` 的正式输出应以以下字段为核心: 1. `bucket` 2. `objectKey` 3. `legacyPublicPath` 4. `access` 5. `formFields` 6. `expiresAt` 补充说明: 1. `publicUrl` 在私有 bucket 模式下不是正式读取真相。 2. 若当前为了调试保留 `publicUrl`,也只能视为“候选直连 URL”,不能代表对象一定可匿名读取。 ### 5.2 私有读取链路 后续正式读取私有对象时,应采用以下方案之一: 1. `api-server` 输出短期签名 URL 2. `api-server` 做下载代理 3. CDN 私有回源 + 服务端签名 当前仓库已实现第一种最小闭环: 1. 服务端落库保存 `bucket + object_key` 2. Web 端请求 `GET /api/assets/read-url` 3. `api-server` 返回短期 `signedUrl` 4. 浏览器再使用该 `signedUrl` 执行展示或下载 当前阶段不允许: 1. 把长期 `AccessKeyId / AccessKeySecret` 下发给客户端 2. 让浏览器直接持有长期主凭证读取私有对象 ## 6. `platform-oss` 的实现约束 从当前版本开始,`platform-oss` 需要遵守以下约束: 1. 默认按私有对象思维设计,不假设对象可匿名读取。 2. `PostObject` 直传输出必须显式带 `bucket` 与 `object_key`。 3. `publicUrl` 若存在,只能作为派生信息,不能替代 `bucket/object_key`。 4. 后续新增签名下载能力时,输入必须是 `bucket + object_key`。 ## 7. `SpacetimeDB` 表设计约束 结合 `SpacetimeDB` 的 schema 演进约束,当前先冻结: 1. 资产对象表必须尽早固定 `bucket` 与 `object_key` 两列。 2. 不要先落单列字符串,后续再拆列,这会放大 schema 迁移成本。 3. 对象 URL、签名 URL、CDN URL 都属于派生读模型,不是主存储字段。 ## 8. 一句话结论 当前 `assets / OSS` 的正式真相应当是: **阿里云 OSS 用私有 bucket 持有二进制对象,`SpacetimeDB` 用 `bucket + object_key` 两列持有对象引用,任何 URL 都只能是运行时派生结果。**