fix(pdd): upsert 成功后用 detail.get 回填 o_goods_sku 的 pddSkuId
- GOODS_ADD 仍从 goods_add_response.sku_list 解析;INFORMATION_UPDATE 等路径无 sku_list 时自动调 pdd.goods.detail.get - 解析 goods_detail_get_response.sku_list 写入 canonicalExt.pddSkuId,供 quantity/update 使用 - persistAfterPddPublishSuccess 增加 ExternalGoodsUpsertRequest 入参以携带 pddPopAuth - 类注释说明平台映射归 ERP-Open、canonicalExt 键可扩展多平台 Made-with: Cursor
This commit is contained in:
parent
d4f4c79104
commit
f271eb2300
|
|
@ -151,7 +151,7 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
|
|||
|
||||
ExternalGoodsUpsertResultVo vo = out.build();
|
||||
if ("PDD".equalsIgnoreCase(req.getPlatform())) {
|
||||
oGoodsPddMappingPersistence.persistAfterPddPublishSuccess(goodsId, vo);
|
||||
oGoodsPddMappingPersistence.persistAfterPddPublishSuccess(goodsId, req, vo);
|
||||
log.info("[external/upsert] PDD summary shopId={} outGoodsId={} erpGoodsId={} attempted={} success={} lane={} credentialSource={} message={} goodsAddSnippet={} catRuleFetched={} catRuleSnippet={} specAutoResolved={} autoResolveDetail={}",
|
||||
req.getShopId(),
|
||||
req.getOutGoodsId(),
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ package cn.qihangerp.service.external.pdd;
|
|||
|
||||
import cn.qihangerp.model.entity.OGoods;
|
||||
import cn.qihangerp.model.entity.OGoodsSku;
|
||||
import cn.qihangerp.model.request.ExternalGoodsUpsertRequest;
|
||||
import cn.qihangerp.model.request.ExternalPddGoodsDetailRequest;
|
||||
import cn.qihangerp.model.vo.ExternalGoodsUpsertResultVo;
|
||||
import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo;
|
||||
import cn.qihangerp.module.service.OGoodsService;
|
||||
import cn.qihangerp.module.service.OGoodsSkuService;
|
||||
import cn.qihangerp.service.external.ExternalPddGoodsDetailAppService;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
|
|
@ -14,12 +18,17 @@ import org.springframework.stereotype.Component;
|
|||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 拼多多发品成功后,将 POP {@code goods_id}/{@code sku_id} 写入 {@code o_goods}/{@code o_goods_sku}.{@code canonicalExt},
|
||||
* 供 {@code /external/goods/pdd/quantity/update} 按 {@code outGoodsId}+{@code outSkuId} 解析调用库存接口。
|
||||
* <p><b>平台商品映射由 ERP-Open 落库</b>:中台仅调用 {@code /external/goods/upsert}、改库存等 HTTP,不持久化拼多多/京东等平台侧 ID。
|
||||
* 各渠道在 {@code o_goods}/{@code o_goods_sku}.{@code canonicalExt} 使用<strong>独立键名</strong>(拼多多为 {@value #CANONICAL_PDD_GOODS_ID}、{@value #CANONICAL_PDD_SKU_ID}),
|
||||
* 未来京东/天猫可并行增加 {@code jdSkuId} 等,改库存接口按平台从本库解析后再调 POP。</p>
|
||||
*
|
||||
* <p>拼多多:{@code pdd.goods.add} 响应可解析 SKU 映射;{@code pdd.goods.information.update} 往往不带 {@code sku_list},
|
||||
* 因此在 upsert 成功后若仍有 SKU 未写入 {@value #CANONICAL_PDD_SKU_ID},将自动补调 {@code pdd.goods.detail.get} 回填。</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
|
|
@ -31,11 +40,15 @@ public class OGoodsPddMappingPersistence {
|
|||
|
||||
private final OGoodsService goodsService;
|
||||
private final OGoodsSkuService skuService;
|
||||
private final ExternalPddGoodsDetailAppService externalPddGoodsDetailAppService;
|
||||
|
||||
/**
|
||||
* 在 upsert 拼多多链路成功({@link ExternalGoodsUpsertResultVo#getPddPublishSuccess()}==true)后调用。
|
||||
*
|
||||
* @param req 用于携带 {@code pddPopAuth},在需补拉 {@code pdd.goods.detail.get} 时使用;可为 {@code null}(则无法补全 SKU 映射)
|
||||
*/
|
||||
public void persistAfterPddPublishSuccess(Long erpGoodsId, ExternalGoodsUpsertResultVo vo) {
|
||||
public void persistAfterPddPublishSuccess(Long erpGoodsId, ExternalGoodsUpsertRequest req,
|
||||
ExternalGoodsUpsertResultVo vo) {
|
||||
if (erpGoodsId == null || vo == null || !Boolean.TRUE.equals(vo.getPddPublishSuccess())) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -52,20 +65,63 @@ public class OGoodsPddMappingPersistence {
|
|||
g.setUpdateTime(new Date());
|
||||
goodsService.updateById(g);
|
||||
|
||||
Map<String, Long> outerToPddSku = Map.of();
|
||||
Map<String, Long> outerToPddSku = new LinkedHashMap<>();
|
||||
String lane = vo.getPddPublishLane();
|
||||
if (lane != null && "GOODS_ADD".equalsIgnoreCase(lane.trim())) {
|
||||
outerToPddSku = PddOpenApiSupport.parseOuterKeyToPddSkuIdFromGoodsAddResponse(vo.getPddResponseSnippet());
|
||||
outerToPddSku.putAll(PddOpenApiSupport.parseOuterKeyToPddSkuIdFromGoodsAddResponse(vo.getPddResponseSnippet()));
|
||||
}
|
||||
if (outerToPddSku.isEmpty()) {
|
||||
log.info("[PDD] persisted pddGoodsId on o_goods only (no sku_list parse) erpGoodsId={} lane={}",
|
||||
erpGoodsId, vo.getPddPublishLane());
|
||||
int fromAdd = applyPddSkuMappings(erpGoodsId, outerToPddSku);
|
||||
if (!outerToPddSku.isEmpty()) {
|
||||
log.info("[PDD] persisted pddGoodsId + sku from GOODS_ADD snippet erpGoodsId={} lane={} skuRowsUpdated={}",
|
||||
erpGoodsId, lane, fromAdd);
|
||||
} else {
|
||||
log.info("[PDD] persisted pddGoodsId on o_goods (no GOODS_ADD sku_list) erpGoodsId={} lane={}",
|
||||
erpGoodsId, lane);
|
||||
}
|
||||
|
||||
if (!anySkuMissingPddSkuId(erpGoodsId)) {
|
||||
return;
|
||||
}
|
||||
if (req == null || req.getPddPopAuth() == null) {
|
||||
log.warn("[PDD] skip detail.get backfill: missing pddPopAuth erpGoodsId={} lane={}",
|
||||
erpGoodsId, lane);
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalPddGoodsDetailRequest dreq = new ExternalPddGoodsDetailRequest();
|
||||
dreq.setPddGoodsId(pddGoodsId);
|
||||
dreq.setPddPopAuth(req.getPddPopAuth());
|
||||
ExternalPddGoodsDetailResultVo detailVo = externalPddGoodsDetailAppService.fetchGoodsDetail(dreq);
|
||||
if (!Boolean.TRUE.equals(detailVo.getPopBizSuccess()) || !StringUtils.hasText(detailVo.getPopResponseBody())) {
|
||||
log.warn("[PDD] detail.get backfill failed erpGoodsId={} pddGoodsId={} msg={}",
|
||||
erpGoodsId, pddGoodsId, detailVo.getMessage());
|
||||
return;
|
||||
}
|
||||
Map<String, Long> fromDetail =
|
||||
PddOpenApiSupport.parseOuterKeyToPddSkuIdFromGoodsDetailGetResponse(detailVo.getPopResponseBody());
|
||||
int fromDetailCount = applyPddSkuMappings(erpGoodsId, fromDetail);
|
||||
if (fromDetailCount > 0) {
|
||||
log.info("[PDD] detail.get backfill applied erpGoodsId={} pddGoodsId={} skuRowsUpdated={}",
|
||||
erpGoodsId, pddGoodsId, fromDetailCount);
|
||||
} else if (anySkuMissingPddSkuId(erpGoodsId)) {
|
||||
log.warn("[PDD] detail.get ok but no sku_id matched outer_erp_sku_id erpGoodsId={} pddGoodsId={}",
|
||||
erpGoodsId, pddGoodsId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 {@code outerKey -> 拼多多 sku_id} 合并写入 {@code o_goods_sku.canonicalExt}(键 {@value #CANONICAL_PDD_SKU_ID})。
|
||||
*
|
||||
* @return 实际更新行数
|
||||
*/
|
||||
private int applyPddSkuMappings(Long erpGoodsId, Map<String, Long> outerToPddSku) {
|
||||
if (outerToPddSku == null || outerToPddSku.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
List<OGoodsSku> rows = skuService.list(new LambdaQueryWrapper<OGoodsSku>()
|
||||
.eq(OGoodsSku::getGoodsId, erpGoodsId));
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
int updated = 0;
|
||||
for (OGoodsSku row : rows) {
|
||||
|
|
@ -81,8 +137,40 @@ public class OGoodsPddMappingPersistence {
|
|||
skuService.updateById(row);
|
||||
updated++;
|
||||
}
|
||||
log.info("[PDD] persisted pddGoodsId + pddSkuId mappings erpGoodsId={} skuRowsUpdated={}",
|
||||
erpGoodsId, updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean anySkuMissingPddSkuId(Long erpGoodsId) {
|
||||
List<OGoodsSku> rows = skuService.list(new LambdaQueryWrapper<OGoodsSku>()
|
||||
.eq(OGoodsSku::getGoodsId, erpGoodsId));
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (OGoodsSku row : rows) {
|
||||
if (row == null || !StringUtils.hasText(row.getOuterErpSkuId())) {
|
||||
continue;
|
||||
}
|
||||
Long v = readCanonicalLong(row.getCanonicalExt(), CANONICAL_PDD_SKU_ID);
|
||||
if (v == null || v <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Long readCanonicalLong(String json, String key) {
|
||||
if (!StringUtils.hasText(json) || !StringUtils.hasText(key)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject o = JSON.parseObject(json);
|
||||
if (o == null || !o.containsKey(key)) {
|
||||
return null;
|
||||
}
|
||||
return o.getLong(key);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String mergeJsonLongField(String existingJson, String field, long value) {
|
||||
|
|
|
|||
|
|
@ -175,6 +175,53 @@ public final class PddOpenApiSupport {
|
|||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 {@code pdd.goods.detail.get} 成功响应中解析 {@code goods_detail_get_response.sku_list[]},
|
||||
* 与 {@link #parseOuterKeyToPddSkuIdFromGoodsAddResponse} 对齐:按 {@code out_sku_sn}/{@code outer_id}/{@code out_sku_id} 映射拼多多 {@code sku_id}。
|
||||
* <p>用于 {@code INFORMATION_UPDATE} 等未返回 {@code goods_add_response.sku_list} 的发品路径之后,在 ERP 本地补全 {@code o_goods_sku.canonicalExt.pddSkuId}。</p>
|
||||
*/
|
||||
public static Map<String, Long> parseOuterKeyToPddSkuIdFromGoodsDetailGetResponse(String raw) {
|
||||
Map<String, Long> out = new LinkedHashMap<>();
|
||||
if (!StringUtils.hasText(raw)) {
|
||||
return out;
|
||||
}
|
||||
try {
|
||||
JSONObject root = JSON.parseObject(raw);
|
||||
if (root == null) {
|
||||
return out;
|
||||
}
|
||||
JSONObject detail = root.getJSONObject("goods_detail_get_response");
|
||||
if (detail == null) {
|
||||
return out;
|
||||
}
|
||||
JSONArray skuList = detail.getJSONArray("sku_list");
|
||||
if (skuList == null || skuList.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
for (int i = 0; i < skuList.size(); i++) {
|
||||
JSONObject sku = skuList.getJSONObject(i);
|
||||
if (sku == null) {
|
||||
continue;
|
||||
}
|
||||
Long skuId = sku.getLong("sku_id");
|
||||
if (skuId == null || skuId <= 0) {
|
||||
continue;
|
||||
}
|
||||
String outerKey = firstNonBlank(
|
||||
sku.getString("out_sku_sn"),
|
||||
sku.getString("outer_id"),
|
||||
sku.getString("out_sku_id"));
|
||||
if (!StringUtils.hasText(outerKey)) {
|
||||
continue;
|
||||
}
|
||||
out.putIfAbsent(outerKey.trim(), skuId);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// ignore
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String a, String b, String c) {
|
||||
if (StringUtils.hasText(a)) {
|
||||
return a.trim();
|
||||
|
|
|
|||
Loading…
Reference in New Issue