# 资产对象业务实体绑定 reducer 设计 日期:`2026-04-21` ## 1. 文档目的 这份文档用于冻结 `M6` 中“对象绑定业务实体 reducer”的首版落地方案。 当前已经完成: 1. 浏览器可通过 `PostObject` 把文件直传到私有 OSS。 2. `POST /api/assets/objects/confirm` 已能确认对象存在。 3. `asset_object` 已按 `bucket + object_key` 写入 SpacetimeDB。 下一步要补上的最小闭环是: 1. 已确认的 `asset_object` 能绑定到某个业务实体。 2. 绑定关系由 SpacetimeDB 持久化。 3. Axum 提供最小 HTTP facade,避免前端直接拼 SpacetimeDB reducer 参数。 ## 2. 当前阶段不直接创建强业务表的原因 当前先落通用 `asset_entity_binding`,不直接创建 `character_visual_asset / scene_image_asset / sprite_sheet_asset`。 原因固定如下: 1. 角色、场景、精灵等强业务表的完整字段还没有冻结。 2. 当前最紧急的工程闭环是“确认后的对象能被实体引用”,不是完整发布模型。 3. 通用绑定表可以先承接旧接口迁移中的 `entityId + slot` 关系,后续再由强业务表逐步替换或派生。 ## 3. 表设计 首版新增 private table: 1. `asset_entity_binding` 字段如下: | 字段名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `binding_id` | `String` | 是 | 主键,固定 `assetbind_` 前缀。 | | `asset_object_id` | `String` | 是 | 被绑定的 `asset_object.asset_object_id`。 | | `entity_kind` | `String` | 是 | 业务实体类型,例如 `character`、`scene`、`profile`。 | | `entity_id` | `String` | 是 | 业务实体 ID。 | | `slot` | `String` | 是 | 实体上的资产槽位,例如 `primary_visual`、`cover`、`sprite_sheet`。 | | `asset_kind` | `String` | 是 | 资产类型,例如 `character_visual`。 | | `owner_user_id` | `Option` | 否 | 归属用户,当前仅作为服务端传入的记录字段。 | | `profile_id` | `Option` | 否 | 归属 profile。 | | `created_at` | `Timestamp` | 是 | 首次绑定时间。 | | `updated_at` | `Timestamp` | 是 | 最近绑定更新时间。 | 索引如下: 1. `entity_kind + entity_id + slot` 用于按实体槽位查当前绑定。 2. `asset_object_id` 用于按对象反查被哪些业务实体引用。 ## 4. 幂等规则 绑定写入按以下规则执行: 1. `asset_object_id` 必须已存在于 `asset_object`。 2. `entity_kind + entity_id + slot` 作为首版幂等定位键。 3. 同一实体槽位重复绑定时,不新增第二行。 4. 重复绑定会复用原 `binding_id` 与 `created_at`,更新 `asset_object_id / asset_kind / owner_user_id / profile_id / updated_at`。 5. 不同槽位可以绑定同一个 `asset_object_id`。 ## 5. reducer / procedure 设计 SpacetimeDB 新增: 1. `bind_asset_object_to_entity` reducer,只返回 `Result<(), String>`,供后续模块内部复用。 2. `bind_asset_object_to_entity_and_return` procedure,面向 Axum 同步接口返回最终绑定快照。 procedure 返回结构采用: 1. `ok` 2. `record` 3. `error_message` 与 `asset_object` 确认 procedure 保持一致,便于 `spacetime-client` 做统一错误映射。 ## 6. Axum HTTP facade 首版新增接口: `POST /api/assets/objects/bind` 请求体: | 字段名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `assetObjectId` | `String` | 是 | 已确认对象 ID。 | | `entityKind` | `String` | 是 | 业务实体类型。 | | `entityId` | `String` | 是 | 业务实体 ID。 | | `slot` | `String` | 是 | 资产槽位。 | | `assetKind` | `String` | 是 | 资产类型。 | | `ownerUserId` | `String` | 否 | 当前阶段由后端调用方显式传入。 | | `profileId` | `String` | 否 | 当前阶段由后端调用方显式传入。 | 响应体核心字段: 1. `bindingId` 2. `assetObjectId` 3. `entityKind` 4. `entityId` 5. `slot` 6. `assetKind` 7. `ownerUserId` 8. `profileId` 9. `createdAt` 10. `updatedAt` ## 7. 当前阶段安全边界 当前接口是 Axum facade,不是前端直接调用 SpacetimeDB reducer 的最终权限模型。 约束如下: 1. 当前不把长期 OSS AK/SK 下发给客户端。 2. 当前不让客户端直接写 private table。 3. `owner_user_id` 当前只作为记录字段,不作为可信授权依据。 4. 后续接入 SpacetimeDB 身份透传后,绑定 reducer 的授权必须改为基于可信身份,不信任客户端传入的用户 ID。 ## 8. 完成定义 首版完成条件: 1. `module-assets` 提供绑定输入、快照、结果结构与字段校验。 2. `spacetime-module` 新增 `asset_entity_binding` 表与绑定 reducer/procedure。 3. `spacetime-client` 生成最新 Rust bindings 并封装绑定 procedure。 4. `api-server` 暴露 `POST /api/assets/objects/bind`。 5. 本地测试覆盖字段错误与 “asset_object 不存在不能绑定”。 ## 9. 一句话结论 当前阶段先用通用 `asset_entity_binding` 把已确认 OSS 对象绑定到业务实体槽位,强业务资产表等字段稳定后再继续拆分。