qihang-ecom-erp-open/docs/yunxi-pdd-goods-listing-gat...

274 lines
12 KiB
Markdown
Raw Normal View History

# 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**(尽量避免 queryGET 若必须使用 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-256hex
拼接方式:
```
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 + 回调/轮询)。首期建议同步,但需要设定超时时间与重试策略。