From 3b5ba130d5ff393b37df81604af98e425cee3eb8 Mon Sep 17 00:00:00 2001 From: huangyujie <27665451@qq.com> Date: Fri, 10 Apr 2026 21:59:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(pdd):=20=E6=96=B0=E5=A2=9E=20sku-canonical?= =?UTF-8?q?-sync=20=E6=8E=A5=E5=8F=A3=E4=BE=9B=E4=B8=BB=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=9B=9E=E5=86=99=20pddSkuId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主数据在 detail.get 成功后回调,将 POP sku_list 映射合并写入 o_goods_sku.canonical_ext。 Made-with: Cursor --- .../ExternalPddGoodsController.java | 28 ++++++++++++++ .../ExternalPddSkuCanonicalSyncRequest.java | 21 +++++++++++ .../ExternalPddSkuCanonicalSyncResultVo.java | 25 +++++++++++++ ...ExternalPddSkuCanonicalSyncAppService.java | 12 ++++++ ...rnalPddSkuCanonicalSyncAppServiceImpl.java | 37 +++++++++++++++++++ .../pdd/OGoodsPddMappingPersistence.java | 18 ++++++++- 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 model/src/main/java/cn/qihangerp/model/request/ExternalPddSkuCanonicalSyncRequest.java create mode 100644 model/src/main/java/cn/qihangerp/model/vo/ExternalPddSkuCanonicalSyncResultVo.java create mode 100644 service/src/main/java/cn/qihangerp/service/external/ExternalPddSkuCanonicalSyncAppService.java create mode 100644 service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddSkuCanonicalSyncAppServiceImpl.java diff --git a/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java b/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java index 0b175add..9c27ac35 100644 --- a/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java +++ b/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java @@ -3,12 +3,15 @@ package cn.qihangerp.erp.controller; import cn.qihangerp.common.AjaxResult; import cn.qihangerp.model.request.ExternalPddGoodsDetailRequest; import cn.qihangerp.model.request.ExternalPddGoodsLatestCommitStatusRequest; +import cn.qihangerp.model.request.ExternalPddSkuCanonicalSyncRequest; import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo; +import cn.qihangerp.model.vo.ExternalPddSkuCanonicalSyncResultVo; import cn.qihangerp.security.common.BaseController; import cn.qihangerp.erp.config.ExternalGoodsApiLogProperties; import cn.qihangerp.erp.support.ExternalGoodsRequestLogSupport; import cn.qihangerp.service.external.ExternalPddGoodsDetailAppService; import cn.qihangerp.service.external.ExternalPddGoodsLatestCommitStatusAppService; +import cn.qihangerp.service.external.ExternalPddSkuCanonicalSyncAppService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -22,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController; *
改库存请使用 {@code POST /external/goods/pdd/quantity/update}(与 upsert 相同 out 键)。
*/ @@ -33,6 +37,7 @@ public class ExternalPddGoodsController extends BaseController { private final ExternalPddGoodsDetailAppService externalPddGoodsDetailAppService; private final ExternalPddGoodsLatestCommitStatusAppService externalPddGoodsLatestCommitStatusAppService; + private final ExternalPddSkuCanonicalSyncAppService externalPddSkuCanonicalSyncAppService; private final ExternalGoodsApiLogProperties goodsApiLogProperties; @PostMapping("/detail") @@ -76,9 +81,32 @@ public class ExternalPddGoodsController extends BaseController { return AjaxResult.error("参数错误:pddPopAuth 需提供 appKey、appSecret、accessToken"); } ExternalPddGoodsDetailResultVo vo = externalPddGoodsLatestCommitStatusAppService.fetchLatestCommitStatus(req); + int n = req.getPddGoodsIds().size(); + log.info( + "[external/pdd/goods/latest-commit-status] goodsIdCount={} popBizSuccess={} msg={}", + n, + vo.getPopBizSuccess(), + vo.getMessage()); if (!Boolean.TRUE.equals(vo.getPopBizSuccess())) { return AjaxResult.error(StringUtils.hasText(vo.getMessage()) ? vo.getMessage() : "pdd.goods.latest.commit.status.get 失败"); } return AjaxResult.success(vo); } + + @PostMapping("/sku-canonical-sync") + public AjaxResult skuCanonicalSync(@RequestBody ExternalPddSkuCanonicalSyncRequest req) { + if (goodsApiLogProperties.isLogFullRequest()) { + int rawLen = req != null && req.getGoodsDetailPopRaw() != null ? req.getGoodsDetailPopRaw().length() : 0; + log.info("[external/pdd/goods/sku-canonical-sync] erpGoodsId={} goodsDetailPopRawLen={}", + req != null ? req.getErpGoodsId() : null, rawLen); + } + if (req == null || req.getErpGoodsId() == null || req.getErpGoodsId() <= 0) { + return AjaxResult.error("参数错误:erpGoodsId 不能为空且须为正数"); + } + if (!StringUtils.hasText(req.getGoodsDetailPopRaw())) { + return AjaxResult.error("参数错误:goodsDetailPopRaw 不能为空"); + } + ExternalPddSkuCanonicalSyncResultVo vo = externalPddSkuCanonicalSyncAppService.syncFromGoodsDetailPop(req); + return AjaxResult.success(vo); + } } diff --git a/model/src/main/java/cn/qihangerp/model/request/ExternalPddSkuCanonicalSyncRequest.java b/model/src/main/java/cn/qihangerp/model/request/ExternalPddSkuCanonicalSyncRequest.java new file mode 100644 index 00000000..8ca6a4b7 --- /dev/null +++ b/model/src/main/java/cn/qihangerp/model/request/ExternalPddSkuCanonicalSyncRequest.java @@ -0,0 +1,21 @@ +package cn.qihangerp.model.request; + +import lombok.Data; + +/** + * {@code POST /external/pdd/goods/sku-canonical-sync}:主数据在 {@code detail.get} 成功后回写 + * {@code o_goods_sku.canonical_ext.pddSkuId}。 + *与 {@link ExternalPddGoodsDetailAppService#fetchGoodsDetail} 返回的 {@code popResponseBody} 同源(拼多多 POP 原始 JSON, + * 须含 {@code goods_detail_get_response.sku_list})。
+ */ +@Data +public class ExternalPddSkuCanonicalSyncRequest { + + /** ERP {@code o_goods.id} */ + private Long erpGoodsId; + + /** + * {@code pdd.goods.detail.get} 成功响应原始 JSON(顶层含 {@code goods_detail_get_response})。 + */ + private String goodsDetailPopRaw; +} diff --git a/model/src/main/java/cn/qihangerp/model/vo/ExternalPddSkuCanonicalSyncResultVo.java b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddSkuCanonicalSyncResultVo.java new file mode 100644 index 00000000..5fbfd413 --- /dev/null +++ b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddSkuCanonicalSyncResultVo.java @@ -0,0 +1,25 @@ +package cn.qihangerp.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * {@code /external/pdd/goods/sku-canonical-sync} 业务结果。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ExternalPddSkuCanonicalSyncResultVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 实际更新了 {@code canonical_ext} 的 SKU 行数 */ + private int updatedSkuRows; + + private String message; +} diff --git a/service/src/main/java/cn/qihangerp/service/external/ExternalPddSkuCanonicalSyncAppService.java b/service/src/main/java/cn/qihangerp/service/external/ExternalPddSkuCanonicalSyncAppService.java new file mode 100644 index 00000000..740eec5e --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/ExternalPddSkuCanonicalSyncAppService.java @@ -0,0 +1,12 @@ +package cn.qihangerp.service.external; + +import cn.qihangerp.model.request.ExternalPddSkuCanonicalSyncRequest; +import cn.qihangerp.model.vo.ExternalPddSkuCanonicalSyncResultVo; + +/** + * 主数据回调:将 {@code detail.get} 中的 SKU 映射写入 {@code o_goods_sku.canonical_ext}。 + */ +public interface ExternalPddSkuCanonicalSyncAppService { + + ExternalPddSkuCanonicalSyncResultVo syncFromGoodsDetailPop(ExternalPddSkuCanonicalSyncRequest req); +} diff --git a/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddSkuCanonicalSyncAppServiceImpl.java b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddSkuCanonicalSyncAppServiceImpl.java new file mode 100644 index 00000000..958ab52e --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddSkuCanonicalSyncAppServiceImpl.java @@ -0,0 +1,37 @@ +package cn.qihangerp.service.external.impl; + +import cn.qihangerp.model.entity.OGoods; +import cn.qihangerp.model.request.ExternalPddSkuCanonicalSyncRequest; +import cn.qihangerp.model.vo.ExternalPddSkuCanonicalSyncResultVo; +import cn.qihangerp.module.service.OGoodsService; +import cn.qihangerp.service.external.ExternalPddSkuCanonicalSyncAppService; +import cn.qihangerp.service.external.pdd.OGoodsPddMappingPersistence; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +/** + * @author guochengyu + */ +@Service +@RequiredArgsConstructor +public class ExternalPddSkuCanonicalSyncAppServiceImpl implements ExternalPddSkuCanonicalSyncAppService { + + private final OGoodsService goodsService; + private final OGoodsPddMappingPersistence oGoodsPddMappingPersistence; + + @Override + public ExternalPddSkuCanonicalSyncResultVo syncFromGoodsDetailPop(ExternalPddSkuCanonicalSyncRequest req) { + OGoods g = goodsService.getById(req.getErpGoodsId()); + if (g == null) { + return ExternalPddSkuCanonicalSyncResultVo.builder() + .updatedSkuRows(0) + .message("o_goods 不存在: " + req.getErpGoodsId()) + .build(); + } + int n = oGoodsPddMappingPersistence.mergePddSkuCanonicalFromGoodsDetailPop( + req.getErpGoodsId(), req.getGoodsDetailPopRaw().trim()); + return ExternalPddSkuCanonicalSyncResultVo.builder() + .updatedSkuRows(n) + .message(n > 0 ? "已合并 pddSkuId" : "无匹配 outerErpSkuId 或未解析到 sku_list") + .build(); + } +} diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java b/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java index f3438473..86363718 100644 --- a/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java +++ b/service/src/main/java/cn/qihangerp/service/external/pdd/OGoodsPddMappingPersistence.java @@ -26,7 +26,8 @@ import java.util.Map; * *拼多多:仅根据 {@code pdd.goods.add} 成功响应中的 {@code sku_list} 回填 {@value #CANONICAL_PDD_SKU_ID};不在本服务内调用 * {@code pdd.goods.detail.get}。{@code pdd.goods.information.update} 若未带 {@code sku_list},则 {@code pddSkuId} 须由主数据在适当时机 - * 调 {@code POST /external/pdd/goods/detail} 后自行落库或后续扩展同步接口写回 ERP(当前 detail 接口仅返回 JSON 给调用方)。
+ * 调 {@code POST /external/pdd/goods/detail} 成功后,由主数据再调 {@code POST /external/pdd/goods/sku-canonical-sync} 将 + * {@code sku_list} 映射合并写入 {@code o_goods_sku.canonical_ext}(键 {@value #CANONICAL_PDD_SKU_ID})。 */ @Slf4j @Component @@ -78,6 +79,21 @@ public class OGoodsPddMappingPersistence { } } + /** + * 从 {@code pdd.goods.detail.get} 的 POP 原始 JSON 解析 {@code sku_list},按 {@code outerErpSkuId} 合并写入 + * {@code o_goods_sku.canonicalExt.pddSkuId}(与 {@link #persistAfterPddPublishSuccess} 中 GOODS_ADD 片段逻辑一致)。 + * + * @return 实际更新行数 + */ + public int mergePddSkuCanonicalFromGoodsDetailPop(Long erpGoodsId, String goodsDetailPopRaw) { + if (erpGoodsId == null || erpGoodsId <= 0) { + return 0; + } + Map