From bda4302e59fb314f0b3ad1a993aac01a760cf4a1 Mon Sep 17 00:00:00 2001 From: huangyujie <27665451@qq.com> Date: Mon, 30 Mar 2026 14:46:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(erp-open):=20=E6=8B=BC=E5=A4=9A=E5=A4=9A?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E6=8E=A5=E5=8F=A3=E6=94=AF=E6=8C=81=20pdd.go?= =?UTF-8?q?ods.quantity.update=20=E5=BA=93=E5=AD=98=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 POST /external/pdd/goods/quantity/update,封装 POP 调用与请求/响应模型;Controller 与日志脱敏沿用 goods 外部接口约定;补充 PddOpenApiSupport 对 quantity.update 响应解析。 Made-with: Cursor --- .../ExternalPddGoodsController.java | 35 +++++ .../ExternalGoodsRequestLogSupport.java | 25 +++- ...ExternalPddGoodsQuantityUpdateRequest.java | 35 +++++ ...xternalPddGoodsQuantityUpdateResultVo.java | 31 +++++ ...ernalPddGoodsQuantityUpdateAppService.java | 12 ++ ...lPddGoodsQuantityUpdateAppServiceImpl.java | 121 ++++++++++++++++++ .../external/pdd/PddOpenApiSupport.java | 55 ++++++++ .../service/external/pdd/PddPopClient.java | 16 ++- 8 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 model/src/main/java/cn/qihangerp/model/request/ExternalPddGoodsQuantityUpdateRequest.java create mode 100644 model/src/main/java/cn/qihangerp/model/vo/ExternalPddGoodsQuantityUpdateResultVo.java create mode 100644 service/src/main/java/cn/qihangerp/service/external/ExternalPddGoodsQuantityUpdateAppService.java create mode 100644 service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddGoodsQuantityUpdateAppServiceImpl.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 728fa2d7..ee33ded7 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 @@ -2,11 +2,14 @@ package cn.qihangerp.erp.controller; import cn.qihangerp.common.AjaxResult; import cn.qihangerp.model.request.ExternalPddGoodsDetailRequest; +import cn.qihangerp.model.request.ExternalPddGoodsQuantityUpdateRequest; import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo; +import cn.qihangerp.model.vo.ExternalPddGoodsQuantityUpdateResultVo; 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.ExternalPddGoodsQuantityUpdateAppService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -19,6 +22,7 @@ import org.springframework.web.bind.annotation.RestController; * 拼多多专用外部接口(与 {@code /external/goods/upsert} 编排解耦)。 *
凭证形态与 {@link ExternalGoodsUpsertRequest#getPddPopAuth()} 一致。
+ */ +@Data +public class ExternalPddGoodsQuantityUpdateRequest { + + /** 拼多多商品 {@code goods_id} */ + private Long goodsId; + + /** 拼多多 {@code sku_id}(与 POP 文档一致) */ + private Long skuId; + + /** 可选,商家外部编码 {@code outer_id} */ + private String outerId; + + /** 库存数量 */ + private Long quantity; + + /** + * 更新类型,参见拼多多开放平台;常见 {@code 1}(与官方示例一致)。 + */ + private Integer updateType; + + /** + * 是否使用 {@code force_update}(默认 {@code true},与官方示例一致)。 + */ + private Boolean forceUpdate; + + private ExternalGoodsUpsertRequest.PddPopAuth pddPopAuth; +} diff --git a/model/src/main/java/cn/qihangerp/model/vo/ExternalPddGoodsQuantityUpdateResultVo.java b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddGoodsQuantityUpdateResultVo.java new file mode 100644 index 00000000..8fb491cb --- /dev/null +++ b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddGoodsQuantityUpdateResultVo.java @@ -0,0 +1,31 @@ +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/quantity/update} 返回:含 POP 网关原始 JSON 与业务 {@code is_success} 解析结果。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ExternalPddGoodsQuantityUpdateResultVo implements Serializable { + + /** + * 整体是否成功:无 {@code error_response} 且 {@code goods_quantity_update_response.is_success=true}。 + */ + private Boolean popBizSuccess; + + /** 与响应体 {@code goods_quantity_update_response.is_success} 一致(解析失败时为 false) */ + private Boolean quantityUpdateSuccess; + + private String message; + + /** 拼多多网关响应全文 */ + private String popResponseBody; +} diff --git a/service/src/main/java/cn/qihangerp/service/external/ExternalPddGoodsQuantityUpdateAppService.java b/service/src/main/java/cn/qihangerp/service/external/ExternalPddGoodsQuantityUpdateAppService.java new file mode 100644 index 00000000..8401b441 --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/ExternalPddGoodsQuantityUpdateAppService.java @@ -0,0 +1,12 @@ +package cn.qihangerp.service.external; + +import cn.qihangerp.model.request.ExternalPddGoodsQuantityUpdateRequest; +import cn.qihangerp.model.vo.ExternalPddGoodsQuantityUpdateResultVo; + +/** + * 拼多多库存更新({@code pdd.goods.quantity.update})。 + */ +public interface ExternalPddGoodsQuantityUpdateAppService { + + ExternalPddGoodsQuantityUpdateResultVo updateQuantity(ExternalPddGoodsQuantityUpdateRequest req); +} diff --git a/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddGoodsQuantityUpdateAppServiceImpl.java b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddGoodsQuantityUpdateAppServiceImpl.java new file mode 100644 index 00000000..4f575fbe --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddGoodsQuantityUpdateAppServiceImpl.java @@ -0,0 +1,121 @@ +package cn.qihangerp.service.external.impl; + +import cn.qihangerp.model.request.ExternalGoodsUpsertRequest; +import cn.qihangerp.model.request.ExternalPddGoodsQuantityUpdateRequest; +import cn.qihangerp.model.vo.ExternalPddGoodsQuantityUpdateResultVo; +import cn.qihangerp.service.external.ExternalPddGoodsQuantityUpdateAppService; +import cn.qihangerp.service.external.pdd.ExternalPddProperties; +import cn.qihangerp.service.external.pdd.PddOpenApiSupport; +import cn.qihangerp.service.external.pdd.PddPopClient; +import cn.qihangerp.service.external.shop.PddShopCredential; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +/** + * @author guochengyu + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ExternalPddGoodsQuantityUpdateAppServiceImpl implements ExternalPddGoodsQuantityUpdateAppService { + + private final ExternalPddProperties props; + private final PddPopClient pddPopClient; + + @Override + public ExternalPddGoodsQuantityUpdateResultVo updateQuantity(ExternalPddGoodsQuantityUpdateRequest req) { + if (req == null || req.getGoodsId() == null || req.getGoodsId() <= 0) { + return fail("goodsId 无效", null, false); + } + if (req.getSkuId() == null || req.getSkuId() <= 0) { + return fail("skuId 无效:须传拼多多 sku_id", null, false); + } + if (req.getQuantity() == null || req.getQuantity() < 0) { + return fail("quantity 无效:不能为空且不能为负", null, false); + } + int updateType = req.getUpdateType() != null ? req.getUpdateType() : 1; + boolean forceUpdate = req.getForceUpdate() == null || Boolean.TRUE.equals(req.getForceUpdate()); + + PddShopCredential cred = resolveCredential(req); + if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret()) + || !StringUtils.hasText(cred.getAccessToken())) { + return fail("pddPopAuth 不完整:需 appKey、appSecret、accessToken", null, false); + } + String gateway = StringUtils.hasText(cred.getGatewayUrl()) ? cred.getGatewayUrl() : props.getGatewayUrl(); + if (!StringUtils.hasText(gateway)) { + return fail("未配置 POP gatewayUrl(请求体或 external.pdd.gateway-url)", null, false); + } + + try { + var biz = PddOpenApiSupport.goodsQuantityUpdateTopLevelParams( + req.getGoodsId(), + req.getSkuId(), + req.getOuterId(), + req.getQuantity(), + updateType, + forceUpdate); + String raw = pddPopClient.invokeTopLevelBiz( + gateway, + cred.getAppKey(), + cred.getAppSecret(), + cred.getAccessToken(), + "pdd.goods.quantity.update", + biz); + boolean noPopError = !PddOpenApiSupport.isError(raw); + boolean innerOk = PddOpenApiSupport.parseGoodsQuantityUpdateIsSuccess(raw); + boolean ok = noPopError && innerOk; + String msg; + if (!noPopError) { + msg = "pdd.goods.quantity.update POP错误: " + PddOpenApiSupport.formatError(raw); + } else if (!innerOk) { + msg = "pdd.goods.quantity.update 业务失败: goods_quantity_update_response.is_success=false 或响应缺字段"; + } else { + msg = "pdd.goods.quantity.update 成功"; + } + return ExternalPddGoodsQuantityUpdateResultVo.builder() + .popBizSuccess(ok) + .quantityUpdateSuccess(innerOk) + .message(msg) + .popResponseBody(raw) + .build(); + } catch (Exception e) { + log.warn("[PDD] pdd.goods.quantity.update exception goodsId={} skuId={} err={}", + req.getGoodsId(), req.getSkuId(), e.getMessage(), e); + return fail(e.getMessage(), null, false); + } + } + + private static ExternalPddGoodsQuantityUpdateResultVo fail(String message, String raw, boolean innerOk) { + return ExternalPddGoodsQuantityUpdateResultVo.builder() + .popBizSuccess(false) + .quantityUpdateSuccess(innerOk) + .message(message) + .popResponseBody(raw) + .build(); + } + + private static PddShopCredential resolveCredential(ExternalPddGoodsQuantityUpdateRequest req) { + if (req == null || req.getPddPopAuth() == null) { + return null; + } + ExternalGoodsUpsertRequest.PddPopAuth a = req.getPddPopAuth(); + PddShopCredential c = new PddShopCredential(); + c.setAppKey(trimToNull(a.getAppKey())); + c.setAppSecret(trimToNull(a.getAppSecret())); + c.setAccessToken(trimToNull(a.getAccessToken())); + if (StringUtils.hasText(a.getGatewayUrl())) { + c.setGatewayUrl(a.getGatewayUrl().trim()); + } + c.setSource("REQUEST"); + return c; + } + + private static String trimToNull(String s) { + if (!StringUtils.hasText(s)) { + return null; + } + return s.trim(); + } +} 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 9beab40b..81f111ff 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 @@ -51,6 +51,61 @@ public final class PddOpenApiSupport { return m; } + /** + * {@code pdd.goods.quantity.update} 表单顶层参数(与 POP 官方 curl / Java SDK 一致:{@code goods_id}、{@code sku_id}、{@code quantity}、 + * {@code update_type}、{@code force_update}、可选 {@code outer_id})。 + */ + public static Map