# External 系统 → 启航ERP（拼多多）商品上架网关：裁剪与改造方案

> 目标：在 `qihang-ecom-erp-open` 基础上，**仅保留**“商品上架/商品同步（仅商品域）/接口鉴权”能力，作为单体服务提供给外部系统调用。  
> 首期平台：**拼多多**。  
> 调用方：外部系统（传入商品数据 + `shopId` + `platform`）。  
> 店铺授权：由本服务维护（外部系统不传 appKey/appSecret/accessToken）。  
> 鉴权：**AK/SK 签名鉴权**（system-to-system）。

## 1. 范围与非目标

### 1.1 In Scope（首期必须）
- **商品数据接入**：外部系统推送商品（SPU + SKU）到本服务（创建/更新）。
- **拼多多上架全链路**：
  - **A** 创建商品（首次发布）
  - **B** 更新商品信息（标题/主图/详情/属性等）
  - **C** 更新 SKU（规格、价格等）
  - **D** 更新库存
  - **E** 上架/下架
  - **F** 接收“已准备好的商品数据”（图片/详情为可访问 URL 或可上传的素材）并发起平台发布
- **最小商品同步**：
  - 从拼多多拉取商品（用于对账/状态回写/增量同步），保留 `pull_goods` 能力即可
  - 同步结果落库，供外部系统查询/校验
- **接口鉴权**：
  - 调用方 → 本服务 API 必须签名验证（AK/SK）
  - 失败拒绝（401/403），并记录审计日志

### 1.2 Out of Scope（首期明确不做/可后置）
- 订单/售后/发货/电子面单/仓库/采购等业务域
- 多平台（淘宝/京东/抖店/快手/小红书/微信小店等）
- 前端管理后台（Vue）与多租户/多商户能力
- AI-agent 相关能力

## 2. 现状评估（基于当前开源代码）

### 2.1 已具备的基础能力
- **商品库接入入口**：`erp-api` 下存在 `POST /goods/add`，并且模型存在 `outerErpGoodsId` 等外部标识字段，适合承接调用方商品数据。
- **拼多多商品拉取（同步）**：`oms-api` 下存在 `POST /pdd/goods/pull_goods`，当前实现是“拉取列表并落库”。
- **安全模块线索**：`core/security` 已存在并在 `erp-api`/`oms-api` 引用（但对调用方的 S2S 鉴权需要单独设计）。

### 2.2 关键缺口（必须改造/补齐）
- 当前拼多多侧 `open-sdk-2.1.12.jar` 中公开的 `PddGoodsApiHelper` 仅暴露 `pullGoodsList`（拉取），未暴露“创建/更新/上下架/库存更新”等发布能力。
- 因此，**商品上架全链路需要新增拼多多发布适配层**（可能复用已有签名/HTTP 基础设施，但要补齐 API）。

## 3. 目标架构（单体部署）

### 3.1 进程形态
单体 Spring Boot 应用（Java 17），对外提供 REST API：
- `external-api`：外部系统调用入口（推送商品/触发上架/查询结果）
- `admin-api`（可选，首期可不暴露）：用于运维/店铺授权维护/密钥配置

### 3.2 逻辑分层（建议）
- **API 层**：只做参数校验、鉴权、幂等键处理、调用 Service、统一响应
- **Service 层**：
  - 统一商品模型校验与映射
  - 上架编排（创建→更新→库存→上架状态）
  - 与拼多多适配层交互
  - 任务化（可选）：异步发布/重试/死信
- **Adapter 层（PDD）**：
  - HTTP + 签名 + 请求/响应模型
  - 平台错误码映射

## 4. 对外接口设计（External/外部系统 → 本服务）

> 说明：下面是建议接口。落地时以外部系统现有调用规范为准，可在后续命令下达后再对齐路径/DTO 命名。

### 4.0 已确认口径（冻结）
- `shopId`：**全平台唯一**，可直接作为本服务店铺主键，不考虑跨平台撞号。
- `platform`：由外部系统显式传入，取值采用 **`PDD`/`TAO`/`JD`/`DOU`/`WEI`/`KWAI`/`XHS`**（后续平台新增再扩展枚举）。
- AK/SK：**一套即可**（外部系统作为单一调用方）。
- `X-Timestamp`：**毫秒**（ms）。
- `X-Signature`：**Base64** 编码（HMAC-SHA256 输出 bytes → Base64）。
- 签名 body：使用**原始请求体 raw bytes** 计算 SHA-256（不做 JSON 重排/字段排序）。
- 参数形态：**统一走 body**（尽量避免 query；GET 若必须使用 query，则改为 POST 查询接口更一致）。
- 幂等：**完全由本服务内部实现**，不依赖调用方提供 `X-Request-Id`。
- 商品素材：首期按**公网可访问 URL**（不做素材代上传）。
- 接口形态：首期按**同步接口**（直接返回发布结果或明确错误）。

### 4.1 统一返回
- `code`: 0 成功；非 0 失败
- `msg`: 错误信息
- `data`: 业务数据

### 4.2 鉴权（AK/SK 签名）
#### 请求头
- `X-Api-Key`: 分配给调用方的 AK
- `X-Timestamp`: 毫秒时间戳（例如 `1710000000000`）
- `X-Nonce`: 随机串（建议 16~32）
- `X-Signature`: 签名串（Base64）
- `Content-Type: application/json`

#### 签名串构造（推荐）
canonical string：
1. `HTTP_METHOD`（大写）
2. `PATH`（不含域名，不含 query）
3. `timestamp`
4. `nonce`
5. `bodySha256Hex`（请求体 raw bytes 的 SHA-256，hex）

拼接方式：
```
METHOD + "\n" +
PATH + "\n" +
timestamp + "\n" +
nonce + "\n" +
bodySha256Hex
```

签名算法：
- `signature = HMAC-SHA256(secret, canonicalString)`  
- 输出：对 `signature` 的 bytes 做 `Base64`。

#### 校验规则
- `timestamp` 与服务端时间偏差 ≤ 300 秒（按 ms 计算）
- `nonce` 在时间窗内不可重复（需要缓存，例如 Redis/本地 Caffeine，首期可先用 Redis）
- `X-Api-Key` 必须存在且有效（数据库/配置中）
- `X-Signature` 校验失败直接拒绝

#### 错误码建议
- 401：缺少必要头/签名参数
- 403：签名错误、AK 无效、nonce 重放
- 429：频控（可选）

### 4.3 商品接入与上架接口（建议）
#### 4.3.1 推送/更新商品（SPU + SKU）
`POST /external/goods/upsert`

入参：
- `shopId`（必填）
- `platform`（必填，枚举，例如 `PDD`/`TAO`/`JD`…；首期仅允许 `PDD`）
- `outGoodsId`：调用方侧商品ID（用于幂等与映射）
- `title`、`mainImages[]`、`detail`（富文本/图片 URL 列表等）
- `skus[]`：`outSkuId`、`spec`、`price`、`stock`、`barCode`…
- `category`、`attrs`、`brand`、`weight`、`shipping`…

行为：
- 本地保存/更新商品库（以 `shopId + outGoodsId` 幂等）
- 返回本地 `goodsId`（内部ID）与当前状态

#### 4.3.2 触发上架（编排）
`POST /external/goods/listing/submit`

入参：
- `shopId`
- `platform`（必填，首期仅允许 `PDD`）
- `outGoodsId`
- `mode`: `CREATE_OR_UPDATE`（默认）

行为（同步/异步二选一，建议首期同步 + 超时控制，后续演进异步）：
- 若平台商品不存在：创建商品 → 创建/更新 SKU → 设置库存 → 上架
- 若已存在：更新商品 → 更新 SKU → 库存 → 上架/保持

返回：
- `listingTaskId`（若异步）
- 或直接返回平台侧 `goodsId`/状态

#### 4.3.3 上下架
`POST /external/goods/listing/status`

入参：
- `shopId`
- `platform`（必填，首期仅允许 `PDD`）
- `outGoodsId`
- `status`: `ON`/`OFF`

#### 4.3.4 库存更新（可被 submit 内部调用，也可单独暴露）
`POST /external/goods/stock/update`

入参：
- `shopId`
- `platform`（必填，首期仅允许 `PDD`）
- `outGoodsId`
- `skus[]`: `outSkuId` + `stock`

#### 4.3.5 查询发布状态/映射
`GET /external/goods/listing/status?shopId=...&platform=...&outGoodsId=...`

返回：
- 平台商品ID、上下架状态、最后一次错误、最后同步时间

## 5. 领域模型（最小集合）

### 5.1 本地核心表（建议最小）
- `shop`：店铺基础信息（含平台类型=PDD）
- `shop_auth`：拼多多 appKey/appSecret/accessToken/refreshToken/过期时间
- `goods`：统一商品（承接调用方）
- `goods_sku`：统一 SKU（承接调用方）
- `goods_platform_mapping`：统一商品与平台商品映射（平台 goodsId、skuId 映射）
- `listing_task`（可选）：异步发布任务、重试次数、状态机
- `api_client`：AK/SK、状态、权限范围、IP 白名单（可选）
- `api_request_log`：审计（签名校验结果、请求摘要、耗时）

### 5.2 幂等策略（必须）
- `shopId + outGoodsId`：商品幂等键
- `shopId + outSkuId`：SKU 幂等键
- **不依赖调用方幂等键**：本服务内部需实现“请求去重/防重复上架”。
  - 推荐：落库 `idempotency_key = sha256Hex(platform + shopId + outGoodsId + operation + normalizedBodyHash)`  
  - 同一 `idempotency_key` 在有效期内重复请求：直接返回第一次执行结果
  - 对“提交上架”类接口：至少保证不会重复创建平台商品

## 6. 拼多多适配（PDD Adapter）设计要点

### 6.1 适配层职责
- 拼多多 API 签名、请求发送、响应解析
- 平台错误码归一化
- “创建/更新/上下架/库存”接口封装为内部方法：
  - `createGoods(...)`
  - `updateGoods(...)`
  - `updateSku(...)`
  - `updateStock(...)`
  - `setGoodsOnSale(...)` / `setGoodsOffSale(...)`

### 6.2 依赖策略（重要）
当前 `oms-api` 通过 `systemPath` 引入 `open-sdk-2.1.12.jar`。

首期有两条路径（二选一）：
- **路径 1（优先）**：在本仓库内新增 `pdd-adapter`（源码方式），不再依赖 `open-sdk-2.1.12.jar` 做发布能力（只保留 pull 同步可继续复用或也迁移）。
- **路径 2（备选）**：升级/替换 `open-sdk`，确保其暴露发布相关 API（需要确认是否可获得源码/维护成本）。

> 推荐路径 1：可控、可审计、便于后续扩展多平台。

## 7. 裁剪方案（保留/删除建议）

> 原项目是微服务结构（gateway/sys-api/erp-api/oms-api 等）。你的目标是单体优先，因此裁剪思路是：
> 1) **保留商品域 + 拼多多域 + 鉴权**  
> 2) 其它域模块不编译/不启动/不暴露接口

### 7.1 建议保留（首期）
- `core/common`（通用工具、AjaxResult 等）
- `core/security`（复用登录/权限基础设施的同时，新增 S2S AK/SK 鉴权）
- `model`（实体/BO/VO）
- `mapper`（DB 访问）
- `service`、`serviceImpl` 中与商品/店铺/拼多多相关的最小集合
- `api/erp-api`（商品库接入：`/goods/add` 等入口可重构为 `/external/goods/*`）
- `api/oms-api` 中 **拼多多商品拉取**（同步）相关接口（可改为内部任务或保留对外）

### 7.2 建议禁用/后置
- `api/ai-agent`
- `api/sys-api`（如仅对调用方开放，不需要后台用户体系，可后置）
- `api/gateway`（若单体直连，不需要网关；若保留网关需把 TokenFilter 改为 AK/SK 校验）
- `oms-api` 中非拼多多平台（dou/jd/tao/wei/kwai…）
- 订单/售后/发货/库存出入库/采购等 Controller 与 Service

## 8. 安全与审计
- 所有外部系统接口强制 AK/SK
- **平台一致性校验（强制）**：
  - 调用方传入 `platform`
  - 服务端根据 `shopId` 查询数据库中的 `shopType/platformType`
  - 若两者不一致：直接拒绝（建议 400 或 403），记录审计日志
  - 目的：避免“串平台/串店铺”导致误上架
- `shopId` 必须存在且平台类型为 PDD
- 店铺 accessToken 过期：返回明确错误码（例如 `SHOP_TOKEN_EXPIRED`），并支持后台刷新/重新授权流程（首期可人工）
- 全链路日志：
  - `requestId`、`shopId`、`outGoodsId`、平台错误码、耗时
- 敏感信息保护：
  - appSecret/accessToken 加密存储（KMS/本地对称加密；首期可用配置密钥 + AES）
  - 严禁打印明文 token/secret

## 9. 验证与验收清单（首期）
- [ ] 外部系统推送商品 → 本地入库（幂等生效）
- [ ] 触发上架 → 拼多多创建/更新/库存/上下架成功（全链路）
- [ ] 上架失败可返回可读错误（含平台原始信息可追踪）
- [ ] `pull_goods` 同步可用（用于对账/状态回写）
- [ ] AK/SK：
  - [ ] 缺参拒绝
  - [ ] 签名错误拒绝
  - [ ] 超时拒绝
  - [ ] nonce 重放拒绝
  - [ ] 审计日志完整

## 10. 待你确认/后续落地前置
- 拼多多商品发布所需字段“最小集”（类目、属性、物流、售后承诺、资质等）需要你提供调用方侧现有字段映射或样例 payload。
- 是否需要“异步上架”（返回 taskId + 回调/轮询）。首期建议同步，但需要设定超时时间与重试策略。

