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,185 @@
# 资产对象上传完成确认接口设计
日期:`2026-04-21`
## 1. 文档目的
这份文档用于把 `M6` 中“上传完成后的对象确认接口”冻结到可直接编码的级别。
当前要解决的不是完整资产发布链,而是最小闭环:
1. 浏览器先通过 `PostObject` 把文件上传到私有 OSS
2. 服务端确认对象真实存在
3. 服务端把对象元数据写入当前阶段的 `asset_object` 真相存储
4. 后续业务绑定与 SpacetimeDB reducer 再基于这条已确认对象继续扩展
## 2. 当前前提
已落地事实:
1. `POST /api/assets/direct-upload-tickets` 已能签发浏览器 `PostObject` 直传票据。
2. `platform-oss` 已能生成私有读签名 URL。
3. `asset_object` 已在 `spacetime-module` 中落下首版表骨架。
当前仍未落地:
1. 上传完成确认接口
2. 对象 HEAD 校验
3. `asset_object` 实际写入路径
4. 业务实体绑定
## 3. 接口职责
`POST /api/assets/objects/confirm` 当前阶段只负责三件事:
1. 校验请求给出的 `bucket + object_key` 是否合法
2. 调 OSS 做一次私有 `HEAD Object` 校验,确认对象真实存在
3. 把对象元数据写入当前阶段的 `asset_object` 进程内存储,并返回确认结果
当前阶段明确不做:
1. 不做业务实体绑定
2. 不做图片尺寸探测
3. 不做 hash 计算
4. 不做重复对象合并
5. 不直接调用 SpacetimeDB reducer
## 4. 请求体设计
请求路径:
`POST /api/assets/objects/confirm`
请求体:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `bucket` | `String` | 否 | 当前阶段允许不传;不传时默认回落到服务端 OSS bucket。 |
| `objectKey` | `String` | 是 | 正式对象路径真相字段。 |
| `contentType` | `String` | 否 | 客户端已知 MIME可回写到对象元数据。 |
| `contentLength` | `u64` | 否 | 客户端可传期望大小;当前仅用于一致性校验,不作为唯一真相来源。 |
| `contentHash` | `String` | 否 | 后续内容摘要预留字段。 |
| `assetKind` | `String` | 是 | 业务资产类型,例如 `character_visual`。 |
| `accessPolicy` | `String` | 否 | 默认 `private`。 |
| `sourceJobId` | `String` | 否 | 来源任务 ID。 |
| `ownerUserId` | `String` | 否 | 归属用户 ID。 |
| `profileId` | `String` | 否 | 归属 profile ID。 |
| `entityId` | `String` | 否 | 归属业务实体 ID。 |
补充约束:
1. `bucket` 当前若传入,必须与服务端已配置 bucket 一致。
2. `objectKey` 必须落在受支持的 `generated-*` 前缀下。
3. `assetKind` 当前不能为空。
## 5. 校验顺序
接口校验顺序固定如下:
1. 检查 OSS 配置是否存在
2. 校验请求参数基础合法性
3. 校验 `bucket` 与服务端配置 bucket 是否一致
4. 调用 OSS `HEAD Object`
5. 若客户端传了 `contentLength`,则与 OSS 返回的真实 `Content-Length` 做一致性校验
6. 通过后写入 `asset_object`
## 6. OSS 校验结果口径
OSS `HEAD Object` 当前至少回填:
1. `content_length`
2. `content_type`
3. `last_modified_at`
4. `etag`
当前阶段以 OSS 返回值为准:
1. `content_length` 真相取 OSS
2. `content_type` 优先取 OSSOSS 未返回时再回退请求体
3. `content_hash` 暂不强行等于 `etag`
原因:
1. `etag` 对 multipart 上传和不同上传模式并不总等价于内容 hash
2. 当前阶段先留出字段,不把错误假设固化进 schema
## 7. 写入规则
确认成功后写入 `asset_object`
1. `asset_object_id` 由服务端生成,固定 `assetobj_` 前缀
2. `bucket``object_key` 按正式真相写入
3. `access_policy` 当前默认 `private`
4. `content_length` 以 OSS HEAD 为准
5. `content_type` 优先 OSS HEAD
6. `version` 当前固定为 `1`
7. `created_at` / `updated_at` 在确认时写当前 UTC 时间
当前阶段重复确认同一 `bucket + object_key` 的行为固定为:
1. 若已存在,则返回已存在记录并更新 `updated_at`
2. 不生成第二条重复对象记录
## 8. 响应体设计
成功响应核心字段:
1. `assetObjectId`
2. `bucket`
3. `objectKey`
4. `accessPolicy`
5. `contentType`
6. `contentLength`
7. `contentHash`
8. `assetKind`
9. `sourceJobId`
10. `ownerUserId`
11. `profileId`
12. `entityId`
13. `version`
14. `createdAt`
15. `updatedAt`
## 9. 错误口径
### 9.1 请求参数错误
返回 `400`
1. `objectKey` 为空
2. `objectKey` 不在受支持前缀下
3. `assetKind` 为空
4. `bucket` 与当前配置 bucket 不一致
5. 客户端声明的 `contentLength` 与 OSS HEAD 不一致
### 9.2 OSS 未配置
返回 `503`
### 9.3 OSS 对象不存在
返回 `404`
### 9.4 OSS 探测失败
返回 `502`
## 10. 当前阶段实现边界
当前阶段实现固定为:
1. `platform-oss` 增加服务端 `HEAD Object` helper
2. `module-assets` 提供进程内 `asset_object` 确认服务
3. `api-server` 接入 `POST /api/assets/objects/confirm`
下一阶段再继续:
1. 对接真实 SpacetimeDB reducer
2. 业务实体绑定 reducer
3. 更细的元数据探测
## 11. 关联文档
1. [SPACETIMEDB_ASSET_OBJECT_STORAGE_DESIGN_2026-04-21.md](./SPACETIMEDB_ASSET_OBJECT_STORAGE_DESIGN_2026-04-21.md)
2. [SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md)
3. [SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md](./SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md)