131 lines
8.2 KiB
Markdown
131 lines
8.2 KiB
Markdown
# 实现前待决与待落地事项
|
||
|
||
> 本文记录 API 契约和数据库 DDL 评审后尚未闭环的事项。它不是新的产品需求;除明确标为“已决策”的条目外,实施对应模块前必须先确认或补齐。最后复核:2026-09-02。
|
||
|
||
## 使用规则
|
||
|
||
- **已决策,待落地**:产品或技术方向已确认,但 DDL、接口契约或实现规则尚未同步,编码前补齐即可。
|
||
- **待确认**:会改变用户可见行为或数据语义,不能由开发自行猜测。
|
||
- **实现约束**:已经确认,后续代码、测试和文档必须遵守。
|
||
|
||
## P0:开始实现前必须闭环
|
||
|
||
### 1. 幂等请求与 Transactional Outbox
|
||
|
||
**状态:已决策,DDL 与基础架构文档已落地;待 NestJS 实现和集成测试。**
|
||
|
||
所有可重试的关键写操作需要幂等保护;所有会触发异步工作的业务写入需要 Transactional Outbox。基础迁移已新增以下两张工作空间隔离表,并已纳入 RLS、清理策略和 Worker 设计:
|
||
|
||
- `idempotency_requests`:按用户、工作空间、规范化请求路径和 `Idempotency-Key` 保存请求摘要、处理状态、HTTP 响应摘要、关联资源及过期时间。至少保留 24 小时;建议 48 小时。相同键而请求摘要不同返回 `409 IDEMPOTENCY_KEY_REUSED`。
|
||
- `outbox_events`:在**同一数据库事务**中随业务数据和 `operations` 记录写入待投递事件;独立 Dispatcher 读取、投递 BullMQ 并重试。事件投递必须按“至少一次”设计,消费者必须可重入;不得将 API Key、Cookie、预签名 URL 等秘密写入事件载荷。
|
||
|
||
首批必须接入的场景:店铺创建/凭据验证、上传完成后的媒体处理、采集素材转存、首次上架发布、在线商品同步、AI 生成任务。普通草稿保存可在首期只使用乐观锁。完整设计和验收项见 [`../architecture/transactional-outbox-and-idempotency.md`](../architecture/transactional-outbox-and-idempotency.md)。
|
||
|
||
### 2. `offer_id`(Ozon 货号)策略
|
||
|
||
**状态:2026-09-22 修订决策;AI 填报尚未实现。**
|
||
|
||
- 采集进入采集箱时,型号(`model`)和 `offer_id` 都允许为空。
|
||
- `model` 是商品通用字段;`offer_id` 是当前平台下、每个 SKU 的字段。用户可手动填写,也可在进入编辑页后点击“AI 填报”,由规则与 AI 生成并直接填入表单。
|
||
- 采集入箱本身不填写货号;AI 生成以 Atlas 稳定商品标识、已确认类目和实际 SKU 规格为依据,可使用来源信息提炼语义片段,但不得直接复用来源 SKU 作为目标平台货号。
|
||
- 首次上架前,`model` 必填;每个被选中 SKU 的 `offer_id` 必填,且在目标门店内唯一。生成值和手填值接受相同的保存及发布校验。
|
||
- 导入成功后,该次平台 SKU 发布记录使用的 `offer_id` 作为历史事实,不允许通过“重新上架”改变;后续编辑在线商品属于另一套平台商品更新能力。
|
||
|
||
详细的主动生成、表单回填及冲突处理见 [`../product/ai-autofill-plan.md`](../product/ai-autofill-plan.md)。
|
||
|
||
## P1:实现对应模块前修正
|
||
|
||
### 1. SKU 与平台发布配置的同商品归属
|
||
|
||
**状态:M3.0 已落地数据库约束(2026-09-09);发布任务、在线镜像等后续表必须复用该归属链。**
|
||
|
||
`product_variant_platform_listings` 同时引用平台发布配置(profile)和通用 SKU(variant),但必须强制:`variant.product_id = profile.product_id`。同一条归属链也适用于属性、报价、发布任务项和在线镜像映射。
|
||
|
||
2026-09-28:迁移 `043`~`045` 已补齐报价的同商品、同 profile、同门店/平台归属,以及已选候选属于当前报价的复合外键;确认快照不可更新。跨 SKU 和跨租户测试已通过。
|
||
|
||
实施时优先选用可维护的复合外键/冗余 `product_id` 加一致性约束;如 PostgreSQL 外键表达能力不足,再使用约束触发器。不得只依赖服务层校验。
|
||
|
||
### 2. 类目切换后未映射属性的持久化
|
||
|
||
**状态:M3.0 已落地持久化表(2026-09-09);类目切换写入与恢复操作随编辑草稿保存实现。**
|
||
|
||
切换目标平台类目后,旧类目中无法映射的属性值必须作为 Atlas 草稿保留,供用户查看、复用或手动处理,不能只出现在一次 API response 的 warning 中。
|
||
|
||
可选落地方式:
|
||
|
||
- 新增 `product_unmapped_attributes`,保存原类目/属性标识、名称快照、原值、原因、保留时间;或
|
||
- 在 `product_platform_profiles` 增加有明确 JSON Schema 的 `unmapped_attributes_draft`。
|
||
|
||
选择后需要同步商品编辑 API 的读取、保存和类目切换响应。
|
||
|
||
### 3. 乐观锁的统一实施
|
||
|
||
**状态:M3.1 已使用产品/profile 双 `revision` 条件更新、事务回滚和集成测试落地;后续编辑资源不得绕过该规则。**
|
||
|
||
`revision` 不依赖数据库触发器自动递增。所有可编辑资源的更新必须由统一 Repository/数据访问层执行:
|
||
|
||
```sql
|
||
UPDATE ...
|
||
SET ..., revision = revision + 1
|
||
WHERE id = :id AND revision = :expected_revision;
|
||
```
|
||
|
||
受影响行数为 `0` 时,服务层读取当前版本摘要并返回 `409 VERSION_CONFLICT`。必须为并发更新和重复提交编写集成测试;禁止由各业务开发者手写、遗漏该规则。
|
||
|
||
### 4. 来源平台、发布目标平台与页头上下文
|
||
|
||
**状态:已决策,待平台能力模型落地。**
|
||
|
||
`platforms` 需要显式表达以下能力,不能由名称或零散 JSON 约定猜测:
|
||
|
||
- `can_be_source`:可作为采集来源,例如 1688、淘宝、天猫、Ozon;
|
||
- `can_be_target`:可作为商品发布目标,例如首期 Ozon;
|
||
- `can_be_header_context`:可出现在 ERP 页头平台切换器。
|
||
|
||
页头只展示已启用、且 `can_be_header_context = true` 的目标平台;来源信息仍可保存其平台标识。实现时可以使用三个布尔列,或统一的 `capabilities` JSONB,但后者必须在架构文档中给出固定 JSON Schema、默认值和校验规则。
|
||
|
||
### 5. 在线商品同步中的“未见”与归档
|
||
|
||
**状态:已确认(2026-09-26)。** 维护者选择以下规则,在线商品全量同步按此执行。
|
||
|
||
规则如下:
|
||
|
||
1. 一次**成功的全量同步**中未见某在线 SKU:仅首次写入 `missing_since`;不归档。
|
||
2. 之后连续第二次成功全量同步仍未见,或 `missing_since` 已超过 7 天:写入 `archived_at`,列表默认隐藏但可筛选查看。
|
||
3. 同步失败、部分同步或被取消:绝不修改 `missing_since`、`archived_at`。
|
||
4. 该 SKU 随后的成功同步再次出现:清空 `missing_since` 与 `archived_at`,恢复正常展示。
|
||
|
||
## P2:契约一致性优化
|
||
|
||
### 1. 删除的同步与异步响应
|
||
|
||
**状态:已决策,待统一约定更新。**
|
||
|
||
- 同步软删除成功返回 `204 No Content`。
|
||
- 涉及大量素材/衍生文件清理、需要异步任务的删除或清空操作允许返回 `202 Accepted` 与 `operation`。
|
||
|
||
应将此例外写入 `docs/api/conventions.md`,并在每个异步删除接口明确说明。
|
||
|
||
### 2. 平台市场、语言和币种能力的 JSON Schema
|
||
|
||
**状态:待技术设计确认。**
|
||
|
||
目前平台 API 返回的 `default_market_code`、`default_locale` 等字段可暂存于 `platforms.capabilities`,但必须定义统一 Schema,至少覆盖:默认市场、可用市场、默认语言、可用语言、可用币种,以及上述来源/目标/页头能力。
|
||
|
||
如平台能力会被频繁查询、筛选或由后台维护,后续可拆分为平台市场/语言/币种关联表;首期不得让各模块自行解释任意 JSON 字段。
|
||
|
||
### 3. 修改门店 Client ID 的边界
|
||
|
||
**状态:待产品确认。**
|
||
|
||
当前 API 契约禁止编辑 `Client ID`,这比最初“编辑店铺”需求更严格。二选一:
|
||
|
||
- 保持禁止:Client ID 变化视为新增授权,用户新建门店后停用/删除旧门店;
|
||
- 允许替换:要求同时提交新的 `Client ID` 与 API Key,服务端原子加密替换、清除验证状态并重新验证,验证失败不得使旧密钥与新 Client ID 形成混合状态。
|
||
|
||
无论选哪种,都不得只允许单独改 `Client ID`。
|
||
|
||
## 关闭记录
|
||
|
||
事项关闭时,在对应小节补充“确认日期、最终决策、影响的 SQL/API/测试文件”。不要直接删除历史结论,以保留设计决策轨迹。
|