From 4eac7824fea19203492bb81bfa8dfdde3913c43a Mon Sep 17 00:00:00 2001 From: huangyujie <27665451@qq.com> Date: Fri, 27 Mar 2026 11:19:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(pdd):=20=E4=BA=8C=E6=AC=A1=E4=B8=8A?= =?UTF-8?q?=E6=9E=B6=E8=B5=B0=20goods.information.update=EF=BC=8C=E4=B8=8B?= =?UTF-8?q?=E6=9E=B6=E8=B5=B0=20sale.status.set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 请求带 pddGoodsId>0 时使用顶层表单调用 pdd.goods.information.update(与 goods.add 同形态) - 移除 information-update-reshelf-enabled 配置与开关 - 业务成功以 goods_update_response.is_success 为准 - 扩展 ExternalGoodsUpsert/Delist 请求与发布/下架链路 Made-with: Cursor --- .../controller/ExternalGoodsController.java | 11 +- .../src/main/resources/application.yml | 2 + .../request/ExternalGoodsDelistRequest.java | 11 +- .../request/ExternalGoodsUpsertRequest.java | 9 ++ .../model/vo/ExternalGoodsUpsertResultVo.java | 3 + .../model/vo/PddPublishLaneResultVo.java | 5 + .../impl/ExternalGoodsAppServiceImpl.java | 13 +- .../external/pdd/ExternalPddProperties.java | 5 + .../pdd/ExternalPddPublishService.java | 143 ++++++++++++++++-- .../external/pdd/PddGoodsAddParamBuilder.java | 17 +++ .../external/pdd/PddOpenApiSupport.java | 46 ++++++ .../service/external/pdd/PddPopClient.java | 26 +++- 12 files changed, 271 insertions(+), 20 deletions(-) diff --git a/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalGoodsController.java b/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalGoodsController.java index 5028c835..b9a4daad 100644 --- a/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalGoodsController.java +++ b/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalGoodsController.java @@ -26,7 +26,7 @@ import java.util.List; *

不查询 {@code o_shop}:{@code shopId} 仅作业务侧店铺维度标识写入 {@code o_goods}/{@code o_goods_sku}。

* * * @author guochengyu @@ -114,7 +114,7 @@ public class ExternalGoodsController extends BaseController { } /** - * 本地下架:将 {@code o_goods.status} 置为 2(已下架)。不调用拼多多等平台的下架接口。 + * 本地下架:将 {@code o_goods.status} 置为 2;若传 {@code pddGoodsId} 则同步拼多多在售状态(见 {@code external.pdd.sale-status-set-enabled})。 */ @PostMapping("/delist") public AjaxResult delist(@RequestBody ExternalGoodsDelistRequest req) { @@ -127,6 +127,13 @@ public class ExternalGoodsController extends BaseController { if (!StringUtils.hasText(req.getOutGoodsId())) { return AjaxResult.error("参数错误:outGoodsId不能为空"); } + if (req.getPddGoodsId() != null && req.getPddGoodsId() > 0 && externalPddProperties.isSaleStatusSetEnabled()) { + var auth = req.getPddPopAuth(); + if (auth == null || !StringUtils.hasText(auth.getAppKey()) || !StringUtils.hasText(auth.getAppSecret()) + || !StringUtils.hasText(auth.getAccessToken())) { + return AjaxResult.error("参数错误:传 pddGoodsId 下架拼多多时,pddPopAuth 需提供 appKey、appSecret、accessToken"); + } + } try { externalGoodsAppService.delistGoods(req); return AjaxResult.success(); diff --git a/api/erp-api/src/main/resources/application.yml b/api/erp-api/src/main/resources/application.yml index edd0df87..2487ca96 100644 --- a/api/erp-api/src/main/resources/application.yml +++ b/api/erp-api/src/main/resources/application.yml @@ -38,6 +38,8 @@ external: # 拼多多 POP:upsert 落库后是否调用 pdd.goods.add(须与 maindata yundt.maindata.erp-open.default-category-code 等对齐) pdd: publish-enabled: true + # 下架:请求体带 pddGoodsId+pddPopAuth 时走 pdd.goods.sale.status.set(is_onsale=0) + sale-status-set-enabled: true gateway-url: https://gw-api.pinduoduo.com/api/router # 发布前可选拉类目规则(诊断用) auto-fetch-cat-rule: false diff --git a/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsDelistRequest.java b/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsDelistRequest.java index 385db40c..a71a9e4b 100644 --- a/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsDelistRequest.java +++ b/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsDelistRequest.java @@ -3,7 +3,7 @@ package cn.qihangerp.model.request; import lombok.Data; /** - * 外部系统商品下架(本地 {@code o_goods.status=2}),不调用各平台开放 API。 + * 外部系统商品下架:本地 {@code o_goods.status=2};可选同步拼多多 {@code pdd.goods.sale.status.set}({@code is_onsale=0})。 * * @author guochengyu */ @@ -15,4 +15,13 @@ public class ExternalGoodsDelistRequest { /** 外部商品 ID(幂等键),与 upsert 的 outGoodsId 一致 */ private String outGoodsId; + + /** + * 拼多多 {@code goods_id}。与 {@link #pddPopAuth} 同时传入且开启 {@code external.pdd.sale-status-set-enabled} 时, + * 先调 {@code pdd.goods.sale.status.set} 再落库下架。 + */ + private Long pddGoodsId; + + /** 拼多多 POP 凭证,与 upsert 的 {@code pddPopAuth} 结构一致 */ + private ExternalGoodsUpsertRequest.PddPopAuth pddPopAuth; } diff --git a/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsUpsertRequest.java b/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsUpsertRequest.java index 94011f57..b57de55d 100644 --- a/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsUpsertRequest.java +++ b/model/src/main/java/cn/qihangerp/model/request/ExternalGoodsUpsertRequest.java @@ -32,6 +32,15 @@ public class ExternalGoodsUpsertRequest { */ private String outGoodsId; + /** + * 拼多多侧商品 ID({@code goods_id})。调用方在「本店该商品货架已上架且 extension 已存 pddGoodsId」时传入, + * 服务端将走 {@code pdd.goods.information.update}(与 {@code pdd.goods.add} 相同的顶层表单字段形态),而不再 {@code pdd.goods.add} + * (需 {@code publish-enabled=true})。 + */ + @JsonProperty("pddGoodsId") + @JsonAlias({"pdd_goods_id"}) + private Long pddGoodsId; + /** * 商品标题 */ diff --git a/model/src/main/java/cn/qihangerp/model/vo/ExternalGoodsUpsertResultVo.java b/model/src/main/java/cn/qihangerp/model/vo/ExternalGoodsUpsertResultVo.java index 379e7ae0..670c9d51 100644 --- a/model/src/main/java/cn/qihangerp/model/vo/ExternalGoodsUpsertResultVo.java +++ b/model/src/main/java/cn/qihangerp/model/vo/ExternalGoodsUpsertResultVo.java @@ -37,6 +37,9 @@ public class ExternalGoodsUpsertResultVo implements Serializable { */ private Long pddGoodsId; + /** {@code GOODS_ADD} / {@code SALE_STATUS_SET}(与拼多多发布链路一致) */ + private String pddPublishLane; + // ---- 店铺凭证与自动拉取类目/spec(仅 platform=PDD 时有意义) ---- /** REQUEST(请求体 pddPopAuth)/ NONE;与 {@code o_shop} 无关 */ diff --git a/model/src/main/java/cn/qihangerp/model/vo/PddPublishLaneResultVo.java b/model/src/main/java/cn/qihangerp/model/vo/PddPublishLaneResultVo.java index e55ed0aa..664b7bd8 100644 --- a/model/src/main/java/cn/qihangerp/model/vo/PddPublishLaneResultVo.java +++ b/model/src/main/java/cn/qihangerp/model/vo/PddPublishLaneResultVo.java @@ -26,6 +26,11 @@ public class PddPublishLaneResultVo implements Serializable { /** 拼多多 {@code goods_add_response.goods_id};仅 add 成功且能解析时有值 */ private Long pddGoodsId; + /** + * {@code GOODS_ADD}:{@code pdd.goods.add};{@code SALE_STATUS_SET}:{@code pdd.goods.sale.status.set}(在售状态)。 + */ + private String publishLane; + /** REQUEST / NONE */ private String shopCredentialSource; diff --git a/service/src/main/java/cn/qihangerp/service/external/impl/ExternalGoodsAppServiceImpl.java b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalGoodsAppServiceImpl.java index 3914bfe9..c084f0c6 100644 --- a/service/src/main/java/cn/qihangerp/service/external/impl/ExternalGoodsAppServiceImpl.java +++ b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalGoodsAppServiceImpl.java @@ -108,7 +108,8 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService { .pddCatRuleFetched(null) .pddCatRuleSnippet(null) .pddSpecAutoResolved(null) - .pddAutoResolveDetail(null); + .pddAutoResolveDetail(null) + .pddPublishLane(null); if ("PDD".equalsIgnoreCase(req.getPlatform())) { OGoods g = goodsService.getById(goodsId); @@ -143,16 +144,18 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService { out.pddCatRuleSnippet(lane.getCatRuleSnippet()); out.pddSpecAutoResolved(lane.getSpecAutoResolved()); out.pddAutoResolveDetail(lane.getAutoResolveDetail()); + out.pddPublishLane(lane.getPublishLane()); } ExternalGoodsUpsertResultVo vo = out.build(); if ("PDD".equalsIgnoreCase(req.getPlatform())) { - log.info("[external/upsert] PDD summary shopId={} outGoodsId={} erpGoodsId={} attempted={} success={} credentialSource={} message={} goodsAddSnippet={} catRuleFetched={} catRuleSnippet={} specAutoResolved={} autoResolveDetail={}", + log.info("[external/upsert] PDD summary shopId={} outGoodsId={} erpGoodsId={} attempted={} success={} lane={} credentialSource={} message={} goodsAddSnippet={} catRuleFetched={} catRuleSnippet={} specAutoResolved={} autoResolveDetail={}", req.getShopId(), req.getOutGoodsId(), vo.getGoodsId(), vo.getPddPublishAttempted(), vo.getPddPublishSuccess(), + vo.getPddPublishLane(), vo.getShopCredentialSource(), truncateForLog(vo.getPddPublishMessage(), 600), truncateForLog(vo.getPddResponseSnippet(), 500), @@ -188,6 +191,12 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService { if (existing == null) { throw new IllegalArgumentException("商品不存在:shopId=" + req.getShopId() + ", outGoodsId=" + req.getOutGoodsId()); } + PddPublishLaneResultVo popDelist = externalPddPublishService.pddSaleStatusSetForDelist(req); + if (Boolean.TRUE.equals(popDelist.getAttempted()) && !Boolean.TRUE.equals(popDelist.getSuccess())) { + throw new IllegalArgumentException(StringUtils.hasText(popDelist.getMessage()) + ? popDelist.getMessage() + : "拼多多下架在售状态失败"); + } // 1 销售中 2 已下架(与 o_goods.status 注释一致) existing.setStatus(2); existing.setUpdateBy("external"); diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddProperties.java b/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddProperties.java index 68cb3e24..c8c46293 100644 --- a/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddProperties.java +++ b/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddProperties.java @@ -22,6 +22,11 @@ public class ExternalPddProperties { */ private boolean publishEnabled = false; + /** + * 是否允许调用 {@code pdd.goods.sale.status.set}(下架时 {@code is_onsale=0})。 + */ + private boolean saleStatusSetEnabled = true; + /** * 发品前是否将外链图片经 POP 图片上传接口上传到拼多多图床,并用返回 URL 替换 * {@code carousel_gallery}/{@code detail_gallery}/{@code sku_list[].thumb_url}(去重上传)。 diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddPublishService.java b/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddPublishService.java index de853685..d33cbdbb 100644 --- a/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddPublishService.java +++ b/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddPublishService.java @@ -2,6 +2,7 @@ package cn.qihangerp.service.external.pdd; import cn.qihangerp.model.entity.OGoods; import cn.qihangerp.model.entity.OGoodsSku; +import cn.qihangerp.model.request.ExternalGoodsDelistRequest; import cn.qihangerp.model.request.ExternalGoodsUpsertRequest; import cn.qihangerp.model.vo.PddPublishLaneResultVo; import cn.qihangerp.service.external.shop.PddShopCredential; @@ -11,7 +12,9 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * 拼多多发布:POP 凭证仅来自本次请求的 {@link ExternalGoodsUpsertRequest#getPddPopAuth()};不依赖 {@code o_shop}。 @@ -161,30 +164,63 @@ public class ExternalPddPublishService { cred, gateway, catId, resolveResult, autoFetchedCatRuleRaw, props, req); try { - log.info("[PDD] pdd.goods.add begin shopId={} outGoodsId={} catId={} skuCount={} noSpecBook={}", - req.getShopId(), req.getOutGoodsId(), catId, skuRows.size(), noSpecBookPublish); String goodsAddRootJson = noSpecBookPublish ? paramBuilder.buildParamJsonNoSpecBook(goods, skus, req, props, catRuleRawForGoodsProps) : paramBuilder.buildParamJson(goods, skus, req, props, effectiveOverrides, catRuleRawForGoodsProps); goodsAddRootJson = pddGoodsImageRehostService.rehostExternalImagesInGoodsAddJson( goodsAddRootJson, gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken()); - String raw = pddPopClient.invokeGoodsAdd(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(), - goodsAddRootJson); - boolean ok = !PddOpenApiSupport.isError(raw); - String errMsg = ok ? null : PddOpenApiSupport.formatError(raw); - Long pddGoodsId = ok ? PddOpenApiSupport.parseGoodsIdFromGoodsAddResponse(raw) : null; + boolean useInfoUpdate = req.getPddGoodsId() != null && req.getPddGoodsId() > 0; + String raw; + String lane; + String okMsg; + String failPrefix; + if (useInfoUpdate) { + log.info("[PDD] pdd.goods.information.update begin shopId={} outGoodsId={} pddGoodsId={} catId={} skuCount={} noSpecBook={}", + req.getShopId(), req.getOutGoodsId(), req.getPddGoodsId(), catId, skuRows.size(), noSpecBookPublish); + String updateJson = paramBuilder.toInformationUpdateParamJson(goodsAddRootJson, req.getPddGoodsId()); + raw = pddPopClient.invokeGoodsInformationUpdate(gateway, cred.getAppKey(), cred.getAppSecret(), + cred.getAccessToken(), updateJson); + lane = "INFORMATION_UPDATE"; + okMsg = "pdd.goods.information.update 成功"; + failPrefix = "pdd.goods.information.update 失败"; + } else { + log.info("[PDD] pdd.goods.add begin shopId={} outGoodsId={} catId={} skuCount={} noSpecBook={}", + req.getShopId(), req.getOutGoodsId(), catId, skuRows.size(), noSpecBookPublish); + raw = pddPopClient.invokeGoodsAdd(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(), + goodsAddRootJson); + lane = "GOODS_ADD"; + okMsg = "pdd.goods.add 调用成功"; + failPrefix = "pdd.goods.add 失败"; + } + boolean ok; + String errMsg; + if (useInfoUpdate) { + ok = PddOpenApiSupport.isInformationUpdateBizOk(raw); + errMsg = ok ? null + : (PddOpenApiSupport.isError(raw) + ? PddOpenApiSupport.formatError(raw) + : PddOpenApiSupport.summarizeInformationUpdateFailure(raw)); + } else { + ok = !PddOpenApiSupport.isError(raw); + errMsg = ok ? null : PddOpenApiSupport.formatError(raw); + } + Long pddGoodsId = ok + ? (useInfoUpdate ? req.getPddGoodsId() : PddOpenApiSupport.parseGoodsIdFromGoodsAddResponse(raw)) + : null; if (ok) { - log.info("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=true pddGoodsId={} msg={} snippet={}", - req.getShopId(), req.getOutGoodsId(), pddGoodsId, "pdd.goods.add 调用成功", + log.info("[PDD] {} end shopId={} outGoodsId={} success=true pddGoodsId={} snippet={}", + useInfoUpdate ? "information.update" : "pdd.goods.add", + req.getShopId(), req.getOutGoodsId(), pddGoodsId, PddOpenApiSupport.snippet(raw, 800)); } else { - log.warn("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=false err={} snippet={}", + log.warn("[PDD] {} end shopId={} outGoodsId={} success=false err={} snippet={}", + useInfoUpdate ? "information.update" : "pdd.goods.add", req.getShopId(), req.getOutGoodsId(), errMsg, PddOpenApiSupport.snippet(raw, 800)); } return PddPublishLaneResultVo.builder() .attempted(true) .success(ok) - .message(ok ? "pdd.goods.add 调用成功" : ("pdd.goods.add 失败: " + errMsg)) + .message(ok ? okMsg : (failPrefix + ": " + errMsg)) .goodsAddSnippet(PddOpenApiSupport.snippet(raw, 2000)) .pddGoodsId(pddGoodsId) .shopCredentialSource(cred.getSource()) @@ -192,6 +228,7 @@ public class ExternalPddPublishService { .catRuleSnippet(catRuleSnippet) .specAutoResolved(specAuto) .autoResolveDetail(autoDetail) + .publishLane(lane) .build(); } catch (Exception e) { log.warn("拼多多发布异常 shopId={} outGoodsId={} err={}", req.getShopId(), req.getOutGoodsId(), e.getMessage(), e); @@ -204,6 +241,82 @@ public class ExternalPddPublishService { .catRuleSnippet(catRuleSnippet) .specAutoResolved(specAuto) .autoResolveDetail(autoDetail) + .publishLane("GOODS_ADD") + .build(); + } + } + + /** + * 下架:{@code pdd.goods.sale.status.set},{@code is_onsale=0}(与开放平台表单顶层参数一致)。 + */ + public PddPublishLaneResultVo pddSaleStatusSetForDelist(ExternalGoodsDelistRequest req) { + if (req == null || req.getPddGoodsId() == null || req.getPddGoodsId() <= 0) { + return PddPublishLaneResultVo.builder() + .attempted(false) + .success(null) + .message("未传 pddGoodsId,跳过拼多多下架在售状态") + .build(); + } + if (!props.isSaleStatusSetEnabled()) { + return PddPublishLaneResultVo.builder() + .attempted(false) + .success(null) + .message("external.pdd.sale-status-set-enabled=false,未调用 pdd.goods.sale.status.set") + .build(); + } + PddShopCredential cred = credentialFromUpsertAuth(req.getPddPopAuth()); + if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret()) + || !StringUtils.hasText(cred.getAccessToken())) { + return PddPublishLaneResultVo.builder() + .attempted(false) + .success(false) + .message("下架同步拼多多需提供 pddPopAuth(appKey、appSecret、accessToken)") + .shopCredentialSource("NONE") + .build(); + } + String gateway = StringUtils.hasText(cred.getGatewayUrl()) ? cred.getGatewayUrl() : props.getGatewayUrl(); + return invokePddGoodsSaleStatusSet(req.getShopId(), req.getOutGoodsId(), cred, gateway, req.getPddGoodsId(), 0); + } + + private PddPublishLaneResultVo invokePddGoodsSaleStatusSet(Long shopId, String outGoodsId, PddShopCredential cred, + String gateway, long goodsId, int isOnsale) { + try { + log.info("[PDD] pdd.goods.sale.status.set begin shopId={} outGoodsId={} goodsId={} is_onsale={}", + shopId, outGoodsId, goodsId, isOnsale); + Map biz = new LinkedHashMap<>(2); + biz.put("goods_id", String.valueOf(goodsId)); + biz.put("is_onsale", String.valueOf(isOnsale)); + String raw = pddPopClient.invokeTopLevelBiz(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(), + "pdd.goods.sale.status.set", biz); + boolean ok = !PddOpenApiSupport.isError(raw); + String errMsg = ok ? null : PddOpenApiSupport.formatError(raw); + if (ok) { + log.info("[PDD] pdd.goods.sale.status.set end shopId={} outGoodsId={} goodsId={} is_onsale={} snippet={}", + shopId, outGoodsId, goodsId, isOnsale, PddOpenApiSupport.snippet(raw, 400)); + } else { + log.warn("[PDD] pdd.goods.sale.status.set end shopId={} outGoodsId={} goodsId={} fail err={} snippet={}", + shopId, outGoodsId, goodsId, errMsg, PddOpenApiSupport.snippet(raw, 800)); + } + return PddPublishLaneResultVo.builder() + .attempted(true) + .success(ok) + .message(ok ? "pdd.goods.sale.status.set 成功" : ("pdd.goods.sale.status.set 失败: " + errMsg)) + .goodsAddSnippet(PddOpenApiSupport.snippet(raw, 2000)) + .pddGoodsId(goodsId) + .shopCredentialSource(cred.getSource()) + .catRuleFetched(false) + .specAutoResolved(false) + .publishLane("SALE_STATUS_SET") + .build(); + } catch (Exception e) { + log.warn("pdd.goods.sale.status.set 异常 shopId={} outGoodsId={} goodsId={} err={}", + shopId, outGoodsId, goodsId, e.getMessage(), e); + return PddPublishLaneResultVo.builder() + .attempted(true) + .success(false) + .message("pdd.goods.sale.status.set 异常: " + e.getMessage()) + .shopCredentialSource(cred.getSource()) + .publishLane("SALE_STATUS_SET") .build(); } } @@ -212,7 +325,13 @@ public class ExternalPddPublishService { if (req == null || req.getPddPopAuth() == null) { return null; } - ExternalGoodsUpsertRequest.PddPopAuth a = req.getPddPopAuth(); + return credentialFromUpsertAuth(req.getPddPopAuth()); + } + + private PddShopCredential credentialFromUpsertAuth(ExternalGoodsUpsertRequest.PddPopAuth a) { + if (a == null) { + return null; + } PddShopCredential c = new PddShopCredential(); c.setAppKey(trimToNull(a.getAppKey())); c.setAppSecret(trimToNull(a.getAppSecret())); diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/PddGoodsAddParamBuilder.java b/service/src/main/java/cn/qihangerp/service/external/pdd/PddGoodsAddParamBuilder.java index 2b715f8a..68c85875 100644 --- a/service/src/main/java/cn/qihangerp/service/external/pdd/PddGoodsAddParamBuilder.java +++ b/service/src/main/java/cn/qihangerp/service/external/pdd/PddGoodsAddParamBuilder.java @@ -151,6 +151,23 @@ public class PddGoodsAddParamBuilder { return JSON.toJSONString(root); } + /** + * 在 {@code pdd.goods.add} 根 JSON 上注入拼多多 {@code goods_id},并移除 {@code out_goods_id}, + * 供 {@code pdd.goods.information.update} 与 {@code pdd.goods.add} 相同方式展开为表单顶层提交。 + */ + public String toInformationUpdateParamJson(String goodsAddRootJson, long pddGoodsId) { + if (!StringUtils.hasText(goodsAddRootJson)) { + throw new IllegalStateException("goodsAddRootJson 不能为空"); + } + JSONObject root = JSON.parseObject(goodsAddRootJson); + if (root == null) { + throw new IllegalStateException("无法解析为 JSON 对象"); + } + root.remove("out_goods_id"); + root.put("goods_id", pddGoodsId); + return root.toJSONString(); + } + /** * 拼多多「无规格」发品(如图书 {@code input_max_spec_num=0}):仅一条 {@code sku_list}。 * 与 POP 文档一致:{@code spec_id_list} 为字符串形式的 JSON 数组,无规格为 {@code "[]"}(有规格如 {@code "[25]"}); diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/PddOpenApiSupport.java b/service/src/main/java/cn/qihangerp/service/external/pdd/PddOpenApiSupport.java index 68d9c561..9beab40b 100644 --- a/service/src/main/java/cn/qihangerp/service/external/pdd/PddOpenApiSupport.java +++ b/service/src/main/java/cn/qihangerp/service/external/pdd/PddOpenApiSupport.java @@ -157,6 +157,52 @@ public final class PddOpenApiSupport { } } + /** + * {@code pdd.goods.information.update}:无 {@code error_response};若存在 {@code goods_update_response} 则须 {@code is_success=true}。 + */ + public static boolean isInformationUpdateBizOk(String body) { + if (!StringUtils.hasText(body)) { + return false; + } + try { + JSONObject root = JSON.parseObject(body); + if (root == null) { + return false; + } + if (root.containsKey("error_response")) { + return false; + } + JSONObject gr = root.getJSONObject("goods_update_response"); + if (gr != null && !gr.getBooleanValue("is_success")) { + return false; + } + return true; + } catch (Exception e) { + return false; + } + } + + /** 业务失败摘要(含 {@code goods_update_response} 未成功时) */ + public static String summarizeInformationUpdateFailure(String body) { + try { + JSONObject root = JSON.parseObject(body); + if (root == null) { + return body; + } + JSONObject gr = root.getJSONObject("goods_update_response"); + if (gr != null && !gr.getBooleanValue("is_success")) { + StringBuilder sb = new StringBuilder("goods_update_response.is_success=false"); + if (gr.containsKey("goods_commit_id")) { + sb.append(", goods_commit_id=").append(gr.get("goods_commit_id")); + } + return sb.toString(); + } + return formatError(body); + } catch (Exception e) { + return body; + } + } + public static String formatError(String body) { try { JSONObject o = JSON.parseObject(body); diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java b/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java index 84bcb1c9..0ae952e2 100644 --- a/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java +++ b/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java @@ -20,8 +20,8 @@ import java.util.Map; *

每次请求无论成功失败均打日志(含 HTTP 状态、耗时、响应片段;client_id 脱敏)。

*

入参形态(与官方 POP curl / Java SDK 一致):

*