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