feat: 方案设计、数据库和接口设计基本完成
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 云端基础模块:数据模型与数据库边界
|
||||
|
||||
> 状态:设计稿。数据库为 PostgreSQL;配套可执行 DDL 见 [`sql/001_foundation.sql`](./sql/001_foundation.sql)。本阶段只产出设计和 SQL,不连接、不创建实际数据库。
|
||||
> 状态:设计稿。数据库为 PostgreSQL;基础 DDL 见 [`sql/001_foundation.sql`](./sql/001_foundation.sql),采集箱、商品编辑、发布与在线商品 DDL 见 [`sql/002_product-domain.sql`](./sql/002_product-domain.sql)。本阶段只产出设计和 SQL,不连接、不创建实际数据库。
|
||||
|
||||
## 1. 核心边界
|
||||
|
||||
@@ -17,7 +17,7 @@ Logistics provider(全局) ── Rate card ── Product type × transport
|
||||
```
|
||||
|
||||
- **工作空间即租户**:新账户创建一个工作空间,创建者是 `owner`。用户、工作空间、成员关系分离,以支持未来多人协作和一个用户管理多个工作空间。
|
||||
- **平台与当前店铺是全局上下文**:店铺属于“工作空间 + 平台”;每个平台最多一个默认店铺。所有门店业务数据均携带 `workspace_id`。
|
||||
- **平台、市场与当前店铺是全局上下文**:店铺属于“工作空间 + 平台”,且一个门店只能绑定一个平台;每个平台最多一个默认店铺。市场/国家、默认语言、支持语言和支持币种属于平台能力,不属于门店。首期 Ozon 使用平台默认市场 `RU`;未来多市场时由顶部全局上下文选择或使用平台默认值。所有门店业务数据均携带 `workspace_id`。
|
||||
- **两套类目均强绑定平台**:平台商品类目来自 Ozon 等平台的商品分类;平台佣金类目来自该平台的佣金规则树。它们在同一平台内独立维护,并由版本化映射关联;SQL 使用 `platform_id + category_id` 的复合外键阻止跨平台映射,绝不按名称或 ID 猜测。
|
||||
- **CEL 价表是全局规则数据**:由后台维护版本和生效期;报价只引用确定版本。首期仅 Ozon-rFBS + CEL,香港空运不录入首期规则。
|
||||
- **报价是快照**:定价方案可以变,CEL/佣金规则可以更新,但已确认 SKU 报价始终保留当时输入、费率、规则版本和费用拆分。
|
||||
@@ -26,32 +26,39 @@ Logistics provider(全局) ── Rate card ── Product type × transport
|
||||
|
||||
| 领域 | 表 | 说明 |
|
||||
|---|---|---|
|
||||
| 账户与租户 | `users`、`workspaces`、`workspace_memberships`、`user_sessions` | 手机号密码登录;短信验证/风控字段预留但一期不启用。 |
|
||||
| 平台与门店 | `platforms`、`currencies`、`platform_currency_support`、`stores`、`store_secrets`、`store_warehouses` | API Key 单独加密保存;密钥过期时间、商品限额无数据时使用 `NULL`,界面显示 `—`。 |
|
||||
| 水印 | `media_assets`、`watermark_fonts`、`watermark_templates`、`watermark_template_versions`、`store_watermark_bindings` | 店铺绑定一个当前模板;发布任务未来固化模板版本,而不修改原图。 |
|
||||
| 账户与租户 | `users`、`workspaces`、`workspace_memberships`、`user_sessions` | 邮箱密码登录;首期不校验邮箱,`email_verified_at` 为后续验证预留。手机号、短信验证和风控后续以独立 migration 扩展。 |
|
||||
| 可靠写入与异步投递 | `idempotency_requests`、`outbox_events`、`operations` | 关键写操作按幂等键安全重放;异步业务事务同时写入 Outbox,由 Dispatcher 补投 BullMQ。`operations` 是用户可见的任务状态,不以 Redis 队列作为唯一事实。 |
|
||||
| 平台与门店 | `platforms`、`currencies`、`platform_currency_support`、`stores`、`store_secrets`、`store_warehouses` | 平台维护支持币种;门店只绑定一个平台,并保存默认计价币种和连接凭据。市场/国家与语言属于待补充的平台能力配置;密钥过期时间、商品限额无数据时使用 `NULL`,界面显示 `—`。 |
|
||||
| 水印与素材 | `media_assets`、`media_upload_intents`、`watermark_fonts`、`watermark_templates`、`watermark_template_versions`、`store_watermark_bindings` | 素材保存 `storage_provider + bucket + object_key` 及元数据,不保存长期访问 URL;浏览器直传先建立持久化上传意图,再校验并落为素材。店铺绑定一个当前模板,发布任务未来固化模板版本,而不修改原图。 |
|
||||
| 类目佣金 | `platform_product_categories`、`commission_categories`、`commission_category_rules`、`platform_product_category_commission_mappings` | 平台商品类目和平台佣金类目都强绑定同一平台;佣金按销售价 RUB 阶梯计费,抓取数据以 `competitor_capture` 来源和待审核版本录入。 |
|
||||
| 跨境物流 | `logistics_providers`、`logistics_rate_cards`、`logistics_product_types`、`logistics_transport_methods`、`logistics_rate_rules` | CEL 产品类型、运输方式、货值/重量/尺寸/抛重规则、费率和版本分开保存。 |
|
||||
| 定价 | `pricing_policies`、`pricing_policy_transport_methods`、`pricing_quotes`、`pricing_quote_options` | 店铺默认方案、SKU 单次临时选择、所有候选运输方式结果与最终确认结果。 |
|
||||
| 定价 | `pricing_policies`、`pricing_policy_transport_methods`、`pricing_quotes`、`pricing_quote_options` | 店铺默认方案、SKU 单次临时选择、所有候选运输方式结果与最终确认结果。方案可明确控制“缺少正式佣金规则时是否允许本次临时覆盖”。 |
|
||||
| 汇率 | `exchange_rates` | 全局不可变汇率历史;首期系统汇率为 CBR 的 CNY→RUB 官方参考汇率。固定汇率和本次覆盖仅固化到报价快照。 |
|
||||
|
||||
## 3. 必须由服务端保证的规则
|
||||
|
||||
1. 在每一个请求事务开始后,先验证当前用户的工作空间成员身份,再执行 `SET LOCAL app.workspace_id = '<uuid>'`;PostgreSQL RLS 作为第二道隔离。
|
||||
2. `store_secrets.secret_ciphertext` 只保存信封加密后的密文、nonce 和密钥版本。不得写日志、返回 API 响应或发往浏览器缓存。
|
||||
3. 一个工作空间在同一平台只有一个活动默认店铺;切换默认店铺必须在同一事务内完成。
|
||||
4. 定价引擎枚举“佣金档位 × CEL 产品类型 × 运输方式”,回验最终销售价的 RUB 佣金档和 CNY 商品货值档;不能沿用旧项目的固定 12% / 18% 佣金。
|
||||
5. `pricing_quotes.product_variant_id` 暂为逻辑外键:商品/SKU DDL 落地时会补为指向 `product_variants` 的真实外键。报价本身可在没有 SKU 的“即时试算”中存在。
|
||||
1. 每个请求事务先验证当前用户的工作空间成员身份,再执行 `SET LOCAL app.workspace_id = '<uuid>'`;PostgreSQL RLS 是第二道隔离,不能替代业务授权。
|
||||
2. API 和 Worker 必须使用无表所有权、无 `BYPASSRLS` 属性的专用应用登录角色;迁移只能由表所有者(迁移角色)执行。应用绝不能连接为建表/迁移角色。
|
||||
3. 所有带 `workspace_id` 的业务表同时 `ENABLE` 和 `FORCE ROW LEVEL SECURITY`。这使表所有者的普通数据访问也受策略约束;PostgreSQL 超级用户及显式拥有 `BYPASSRLS` 的角色仍可绕过,不能用于应用连接。
|
||||
4. `store_secrets.secret_ciphertext` 只保存信封加密后的密文、nonce 和密钥版本。不得写日志、返回 API 响应或发往浏览器缓存。
|
||||
5. 一个工作空间在同一平台只有一个活动默认店铺;切换默认店铺必须在同一事务内完成。
|
||||
6. 定价引擎枚举“佣金档位 × CEL 产品类型 × 运输方式”,回验最终销售价的 RUB 佣金档和 CNY 商品货值档;不能沿用旧项目的固定 12% / 18% 佣金。
|
||||
7. `pricing_quotes.product_variant_id` 暂为逻辑外键:商品/SKU DDL 落地时会补为指向 `product_variants` 的真实外键。报价本身可在没有 SKU 的“即时试算”中存在。
|
||||
8. 关键可重试写操作使用 `idempotency_requests`;启动异步工作的业务事务同时写入 `outbox_events`,由 Dispatcher 至少一次投递到 BullMQ。完整规则见 [`transactional-outbox-and-idempotency.md`](./transactional-outbox-and-idempotency.md)。
|
||||
|
||||
## 4. 初始数据与迁移顺序
|
||||
|
||||
1. 执行 `001_foundation.sql`。
|
||||
2. 初始化 `OZON` 平台、`CNY` / `RUB` 币种和平台币种支持关系。
|
||||
3. 建立 Ozon 平台商品类目和 Ozon 平台佣金类目树,再人工审核并导入同平台映射与阶梯佣金规则。
|
||||
4. 将已 Review 的 CEL Ozon-rFBS 规则录入一个 `draft` rate card,用真实 CEL 报价复核后发布为 `active`。
|
||||
5. 商品/SKU 模块落地后,增加 `product_variants` 外键并把 SKU “定价”按钮接到 `pricing_quotes`。
|
||||
4. 配置 CBR CNY→RUB 汇率 Worker,成功写入第一条 `exchange_rates` 后才启用“系统汇率”报价;详见 [`exchange-rates.md`](./exchange-rates.md)。
|
||||
5. 将已 Review 的 CEL Ozon-rFBS 规则录入一个 `draft` rate card,用真实 CEL 报价复核后发布为 `active`。
|
||||
6. 执行 `002_product-domain.sql`,它会补上 `pricing_quotes.product_variant_id` 的真实外键和 `product_platform_profile_id` 关联。
|
||||
7. 启动 Outbox Dispatcher 与过期幂等记录/已投递事件的清理 Worker;在 staging 按文档模拟投递故障并验证可恢复性。
|
||||
|
||||
## 5. 仍不在此 DDL 内的内容
|
||||
|
||||
- 短信发送、验证码、风控供应商和密码找回流程;
|
||||
- Ozon API 的实际同步 Worker、密钥轮换 Worker 和对象存储上传;
|
||||
- 商品/SPU/SKU、媒体处理任务与真正的上架任务表;
|
||||
- 商品编辑和在线商品页面的具体列表字段、筛选与交互(表结构已预留,需求仍可继续细化);
|
||||
- 平台/物流/佣金规则的后台管理 UI 与导入程序。
|
||||
|
||||
Reference in New Issue
Block a user