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 new file mode 100644 index 00000000..728fa2d7 --- /dev/null +++ b/api/erp-api/src/main/java/cn/qihangerp/erp/controller/ExternalPddGoodsController.java @@ -0,0 +1,55 @@ +package cn.qihangerp.erp.controller; + +import cn.qihangerp.common.AjaxResult; +import cn.qihangerp.model.request.ExternalPddGoodsDetailRequest; +import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo; +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 lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 拼多多专用外部接口(与 {@code /external/goods/upsert} 编排解耦)。 + *
凭证形态与 {@link ExternalGoodsUpsertRequest#getPddPopAuth()} 一致。
+ */ +@Data +public class ExternalPddGoodsDetailRequest { + + /** 拼多多 {@code goods_add_response.goods_id} */ + private Long pddGoodsId; + + private ExternalGoodsUpsertRequest.PddPopAuth pddPopAuth; +} 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 5e4730f5..379e7ae0 100644 --- a/model/src/main/java/cn/qihangerp/model/vo/ExternalGoodsUpsertResultVo.java +++ b/model/src/main/java/cn/qihangerp/model/vo/ExternalGoodsUpsertResultVo.java @@ -32,6 +32,11 @@ public class ExternalGoodsUpsertResultVo implements Serializable { /** 拼多多网关响应片段(便于排错) */ private String pddResponseSnippet; + /** + * 拼多多侧商品 ID({@code goods_add_response.goods_id});与 {@link #goodsId}(ERP 本地 {@code o_goods.id})不同。 + */ + private Long pddGoodsId; + // ---- 店铺凭证与自动拉取类目/spec(仅 platform=PDD 时有意义) ---- /** REQUEST(请求体 pddPopAuth)/ NONE;与 {@code o_shop} 无关 */ diff --git a/model/src/main/java/cn/qihangerp/model/vo/ExternalPddGoodsDetailResultVo.java b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddGoodsDetailResultVo.java new file mode 100644 index 00000000..b1d5ef0f --- /dev/null +++ b/model/src/main/java/cn/qihangerp/model/vo/ExternalPddGoodsDetailResultVo.java @@ -0,0 +1,28 @@ +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/detail} 返回:拼多多网关原始 JSON(供主数据解析 {@code goods_detail_get_response})。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ExternalPddGoodsDetailResultVo implements Serializable { + + /** 是否 POP 业务成功(非 error_response) */ + private Boolean popBizSuccess; + + private String message; + + /** + * 拼多多网关响应全文(解密后的 body 字符串),与 {@link cn.qihangerp.service.external.pdd.PddPopClient} 日志一致。 + */ + private String popResponseBody; +} 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 028f584c..e55ed0aa 100644 --- a/model/src/main/java/cn/qihangerp/model/vo/PddPublishLaneResultVo.java +++ b/model/src/main/java/cn/qihangerp/model/vo/PddPublishLaneResultVo.java @@ -23,6 +23,9 @@ public class PddPublishLaneResultVo implements Serializable { private String message; private String goodsAddSnippet; + /** 拼多多 {@code goods_add_response.goods_id};仅 add 成功且能解析时有值 */ + private Long pddGoodsId; + /** REQUEST / NONE */ private String shopCredentialSource; diff --git a/service/src/main/java/cn/qihangerp/service/external/ExternalPddGoodsDetailAppService.java b/service/src/main/java/cn/qihangerp/service/external/ExternalPddGoodsDetailAppService.java new file mode 100644 index 00000000..30b5dfb6 --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/ExternalPddGoodsDetailAppService.java @@ -0,0 +1,12 @@ +package cn.qihangerp.service.external; + +import cn.qihangerp.model.request.ExternalPddGoodsDetailRequest; +import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo; + +/** + * 拼多多商品详情({@code pdd.goods.detail.get}),与 upsert/add 编排解耦。 + */ +public interface ExternalPddGoodsDetailAppService { + + ExternalPddGoodsDetailResultVo fetchGoodsDetail(ExternalPddGoodsDetailRequest req); +} 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 ade225a9..3914bfe9 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 @@ -137,6 +137,7 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService { out.pddPublishSuccess(lane.getSuccess()); out.pddPublishMessage(lane.getMessage()); out.pddResponseSnippet(lane.getGoodsAddSnippet()); + out.pddGoodsId(lane.getPddGoodsId()); out.shopCredentialSource(lane.getShopCredentialSource()); out.pddCatRuleFetched(lane.getCatRuleFetched()); out.pddCatRuleSnippet(lane.getCatRuleSnippet()); diff --git a/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddGoodsDetailAppServiceImpl.java b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddGoodsDetailAppServiceImpl.java new file mode 100644 index 00000000..a4be93cc --- /dev/null +++ b/service/src/main/java/cn/qihangerp/service/external/impl/ExternalPddGoodsDetailAppServiceImpl.java @@ -0,0 +1,99 @@ +package cn.qihangerp.service.external.impl; + +import cn.qihangerp.model.request.ExternalGoodsUpsertRequest; +import cn.qihangerp.model.request.ExternalPddGoodsDetailRequest; +import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo; +import cn.qihangerp.service.external.ExternalPddGoodsDetailAppService; +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 ExternalPddGoodsDetailAppServiceImpl implements ExternalPddGoodsDetailAppService { + + private final ExternalPddProperties props; + private final PddPopClient pddPopClient; + + @Override + public ExternalPddGoodsDetailResultVo fetchGoodsDetail(ExternalPddGoodsDetailRequest req) { + if (req == null || req.getPddGoodsId() == null || req.getPddGoodsId() <= 0) { + return ExternalPddGoodsDetailResultVo.builder() + .popBizSuccess(false) + .message("pddGoodsId 无效") + .popResponseBody(null) + .build(); + } + PddShopCredential cred = resolveCredential(req); + if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret()) + || !StringUtils.hasText(cred.getAccessToken())) { + return ExternalPddGoodsDetailResultVo.builder() + .popBizSuccess(false) + .message("pddPopAuth 不完整:需 appKey、appSecret、accessToken") + .popResponseBody(null) + .build(); + } + String gateway = StringUtils.hasText(cred.getGatewayUrl()) ? cred.getGatewayUrl() : props.getGatewayUrl(); + if (!StringUtils.hasText(gateway)) { + return ExternalPddGoodsDetailResultVo.builder() + .popBizSuccess(false) + .message("未配置 POP gatewayUrl(请求体或 external.pdd.gateway-url)") + .popResponseBody(null) + .build(); + } + try { + String raw = pddPopClient.invokeTopLevelBiz( + gateway, + cred.getAppKey(), + cred.getAppSecret(), + cred.getAccessToken(), + "pdd.goods.detail.get", + PddOpenApiSupport.goodsDetailGetTopLevelParams(req.getPddGoodsId())); + boolean ok = !PddOpenApiSupport.isError(raw); + return ExternalPddGoodsDetailResultVo.builder() + .popBizSuccess(ok) + .message(ok ? "pdd.goods.detail.get 调用成功" : ("pdd.goods.detail.get 失败: " + PddOpenApiSupport.formatError(raw))) + .popResponseBody(raw) + .build(); + } catch (Exception e) { + log.warn("[PDD] pdd.goods.detail.get exception pddGoodsId={} err={}", req.getPddGoodsId(), e.getMessage(), e); + return ExternalPddGoodsDetailResultVo.builder() + .popBizSuccess(false) + .message(e.getMessage()) + .popResponseBody(null) + .build(); + } + } + + private static PddShopCredential resolveCredential(ExternalPddGoodsDetailRequest 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/ExternalPddPublishService.java b/service/src/main/java/cn/qihangerp/service/external/pdd/ExternalPddPublishService.java index a9ab06e1..de853685 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 @@ -172,9 +172,10 @@ public class ExternalPddPublishService { goodsAddRootJson); boolean ok = !PddOpenApiSupport.isError(raw); String errMsg = ok ? null : PddOpenApiSupport.formatError(raw); + Long pddGoodsId = ok ? PddOpenApiSupport.parseGoodsIdFromGoodsAddResponse(raw) : null; if (ok) { - log.info("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=true msg={} snippet={}", - req.getShopId(), req.getOutGoodsId(), "pdd.goods.add 调用成功", + log.info("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=true pddGoodsId={} msg={} snippet={}", + req.getShopId(), req.getOutGoodsId(), pddGoodsId, "pdd.goods.add 调用成功", PddOpenApiSupport.snippet(raw, 800)); } else { log.warn("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=false err={} snippet={}", @@ -185,6 +186,7 @@ public class ExternalPddPublishService { .success(ok) .message(ok ? "pdd.goods.add 调用成功" : ("pdd.goods.add 失败: " + errMsg)) .goodsAddSnippet(PddOpenApiSupport.snippet(raw, 2000)) + .pddGoodsId(pddGoodsId) .shopCredentialSource(cred.getSource()) .catRuleFetched(catFetched) .catRuleSnippet(catRuleSnippet) 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 48c97928..68d9c561 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 @@ -39,6 +39,41 @@ public final class PddOpenApiSupport { return m; } + /** + * {@code pdd.goods.detail.get} 表单顶层参数(与 POP 文档一致:{@code goods_id})。 + */ + public static Map