This commit is contained in:
2026-04-21 19:17:31 +08:00
parent d234d27cc0
commit 89129ef1f4
83 changed files with 13329 additions and 176 deletions

View File

@@ -0,0 +1,191 @@
# 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 都只能是运行时派生结果。**