feat(pdd): 二次上架走 goods.information.update,下架走 sale.status.set
- 请求带 pddGoodsId>0 时使用顶层表单调用 pdd.goods.information.update(与 goods.add 同形态) - 移除 information-update-reshelf-enabled 配置与开关 - 业务成功以 goods_update_response.is_success 为准 - 扩展 ExternalGoodsUpsert/Delist 请求与发布/下架链路 Made-with: Cursor
This commit is contained in:
parent
22152216dd
commit
4eac7824fe
|
|
@ -26,7 +26,7 @@ import java.util.List;
|
|||
* <p>不查询 {@code o_shop}:{@code shopId} 仅作业务侧店铺维度标识写入 {@code o_goods}/{@code o_goods_sku}。</p>
|
||||
* <ul>
|
||||
* <li>{@code POST /external/goods/upsert} — 商品上架/同步(新建默认 {@code o_goods.status=1})</li>
|
||||
* <li>{@code POST /external/goods/delist} — 本地下架({@code status=2}),不调各平台下架 API</li>
|
||||
* <li>{@code POST /external/goods/delist} — 本地下架({@code status=2});可选 {@code pddGoodsId}+{@code pddPopAuth} 调拼多多 {@code pdd.goods.sale.status.set}({@code is_onsale=0})</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 商品标题
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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} 无关 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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}(去重上传)。
|
||||
|
|
|
|||
|
|
@ -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<String, String> 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()));
|
||||
|
|
|
|||
|
|
@ -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} 相同方式展开为<strong>表单顶层</strong>提交。
|
||||
*/
|
||||
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} 为<strong>字符串</strong>形式的 JSON 数组,无规格为 {@code "[]"}(有规格如 {@code "[25]"});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import java.util.Map;
|
|||
* <p>每次请求无论成功失败均打日志(含 HTTP 状态、耗时、响应片段;client_id 脱敏)。</p>
|
||||
* <p><b>入参形态(与官方 POP curl / Java SDK 一致):</b></p>
|
||||
* <ul>
|
||||
* <li>{@link #invokeGoodsAdd} — {@code pdd.goods.add}:业务字段全部在表单顶层({@code sku_list}、{@code carousel_gallery} 等为 JSON 字符串),
|
||||
* <b>无</b> {@code param_json}、<b>无</b> {@code version}</li>
|
||||
* <li>{@link #invokeGoodsAdd} / {@link #invokeGoodsInformationUpdate} — {@code pdd.goods.add}、{@code pdd.goods.information.update}:
|
||||
* 根 JSON 展开为表单顶层(与官方 curl / Java SDK 一致),<b>无</b> {@code param_json}、<b>无</b> {@code version}</li>
|
||||
* <li>{@link #invokeTopLevelBiz} — 如 {@code pdd.goods.cat.rule.get} 的 {@code cat_id}/{@code goods_id} 等顶层字段</li>
|
||||
* <li>{@link #invokeGoodsImageUpload} — {@code pdd.goods.image.upload}:urlencoded,{@code image} 为 {@code data:image/*;base64,} + Base64</li>
|
||||
* <li>{@link #invoke} — 仅当接口要求整包 {@code param_json} 时使用(少数场景)</li>
|
||||
|
|
@ -58,8 +58,28 @@ public class PddPopClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* 业务参数整包放入 {@code param_json}(并带 {@code version=V1})。仅适用于仍要求该形态的接口。
|
||||
* {@code pdd.goods.information.update}:与 {@link #invokeGoodsAdd} 相同,将根 JSON 展开为顶层表单(官方示例 {@code sku_list}、{@code goods_id} 等与 {@code type} 同级)。
|
||||
*/
|
||||
public String invokeGoodsInformationUpdate(String gatewayUrl, String clientId, String clientSecret, String accessToken,
|
||||
String informationUpdateRootJson) throws Exception {
|
||||
Map<String, String> biz = PddOpenApiSupport.flattenPopTopLevelFromRootJson(informationUpdateRootJson);
|
||||
String logPayload = PddOpenApiSupport.snippet(informationUpdateRootJson, 400);
|
||||
if (!StringUtils.hasText(gatewayUrl)) {
|
||||
throw new IllegalArgumentException("gatewayUrl 不能为空");
|
||||
}
|
||||
Map<String, String> params = buildBaseParams(clientId, accessToken, "pdd.goods.information.update", false);
|
||||
if (biz != null) {
|
||||
for (Map.Entry<String, String> e : biz.entrySet()) {
|
||||
if (e.getKey() == null || e.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
params.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
return postSignedAndLog(gatewayUrl, clientId, clientSecret, "pdd.goods.information.update", params, logPayload,
|
||||
Duration.ofSeconds(60));
|
||||
}
|
||||
|
||||
public String invoke(String gatewayUrl, String clientId, String clientSecret, String accessToken,
|
||||
String type, String paramJson) throws Exception {
|
||||
if (!StringUtils.hasText(gatewayUrl)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue