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();
|
ExternalGoodsUpsertResultVo vo = out.build();
|
||||||
if ("PDD".equalsIgnoreCase(req.getPlatform())) {
|
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={}",
|
log.info("[external/upsert] PDD summary shopId={} outGoodsId={} erpGoodsId={} attempted={} success={} lane={} credentialSource={} message={} goodsAddSnippet={} catRuleFetched={} catRuleSnippet={} specAutoResolved={} autoResolveDetail={}",
|
||||||
req.getShopId(),
|
req.getShopId(),
|
||||||
req.getOutGoodsId(),
|
req.getOutGoodsId(),
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,13 @@ package cn.qihangerp.service.external.pdd;
|
||||||
|
|
||||||
import cn.qihangerp.model.entity.OGoods;
|
import cn.qihangerp.model.entity.OGoods;
|
||||||
import cn.qihangerp.model.entity.OGoodsSku;
|
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.ExternalGoodsUpsertResultVo;
|
||||||
|
import cn.qihangerp.model.vo.ExternalPddGoodsDetailResultVo;
|
||||||
import cn.qihangerp.module.service.OGoodsService;
|
import cn.qihangerp.module.service.OGoodsService;
|
||||||
import cn.qihangerp.module.service.OGoodsSkuService;
|
import cn.qihangerp.module.service.OGoodsSkuService;
|
||||||
|
import cn.qihangerp.service.external.ExternalPddGoodsDetailAppService;
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
|
@ -14,12 +18,17 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拼多多发品成功后,将 POP {@code goods_id}/{@code sku_id} 写入 {@code o_goods}/{@code o_goods_sku}.{@code canonicalExt},
|
* <p><b>平台商品映射由 ERP-Open 落库</b>:中台仅调用 {@code /external/goods/upsert}、改库存等 HTTP,不持久化拼多多/京东等平台侧 ID。
|
||||||
* 供 {@code /external/goods/pdd/quantity/update} 按 {@code outGoodsId}+{@code outSkuId} 解析调用库存接口。
|
* 各渠道在 {@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
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
|
|
@ -31,11 +40,15 @@ public class OGoodsPddMappingPersistence {
|
||||||
|
|
||||||
private final OGoodsService goodsService;
|
private final OGoodsService goodsService;
|
||||||
private final OGoodsSkuService skuService;
|
private final OGoodsSkuService skuService;
|
||||||
|
private final ExternalPddGoodsDetailAppService externalPddGoodsDetailAppService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 upsert 拼多多链路成功({@link ExternalGoodsUpsertResultVo#getPddPublishSuccess()}==true)后调用。
|
* 在 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())) {
|
if (erpGoodsId == null || vo == null || !Boolean.TRUE.equals(vo.getPddPublishSuccess())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -52,20 +65,63 @@ public class OGoodsPddMappingPersistence {
|
||||||
g.setUpdateTime(new Date());
|
g.setUpdateTime(new Date());
|
||||||
goodsService.updateById(g);
|
goodsService.updateById(g);
|
||||||
|
|
||||||
Map<String, Long> outerToPddSku = Map.of();
|
Map<String, Long> outerToPddSku = new LinkedHashMap<>();
|
||||||
String lane = vo.getPddPublishLane();
|
String lane = vo.getPddPublishLane();
|
||||||
if (lane != null && "GOODS_ADD".equalsIgnoreCase(lane.trim())) {
|
if (lane != null && "GOODS_ADD".equalsIgnoreCase(lane.trim())) {
|
||||||
outerToPddSku = PddOpenApiSupport.parseOuterKeyToPddSkuIdFromGoodsAddResponse(vo.getPddResponseSnippet());
|
outerToPddSku.putAll(PddOpenApiSupport.parseOuterKeyToPddSkuIdFromGoodsAddResponse(vo.getPddResponseSnippet()));
|
||||||
}
|
}
|
||||||
if (outerToPddSku.isEmpty()) {
|
int fromAdd = applyPddSkuMappings(erpGoodsId, outerToPddSku);
|
||||||
log.info("[PDD] persisted pddGoodsId on o_goods only (no sku_list parse) erpGoodsId={} lane={}",
|
if (!outerToPddSku.isEmpty()) {
|
||||||
erpGoodsId, vo.getPddPublishLane());
|
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;
|
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>()
|
List<OGoodsSku> rows = skuService.list(new LambdaQueryWrapper<OGoodsSku>()
|
||||||
.eq(OGoodsSku::getGoodsId, erpGoodsId));
|
.eq(OGoodsSku::getGoodsId, erpGoodsId));
|
||||||
if (rows == null || rows.isEmpty()) {
|
if (rows == null || rows.isEmpty()) {
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
int updated = 0;
|
int updated = 0;
|
||||||
for (OGoodsSku row : rows) {
|
for (OGoodsSku row : rows) {
|
||||||
|
|
@ -81,8 +137,40 @@ public class OGoodsPddMappingPersistence {
|
||||||
skuService.updateById(row);
|
skuService.updateById(row);
|
||||||
updated++;
|
updated++;
|
||||||
}
|
}
|
||||||
log.info("[PDD] persisted pddGoodsId + pddSkuId mappings erpGoodsId={} skuRowsUpdated={}",
|
return updated;
|
||||||
erpGoodsId, 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) {
|
private static String mergeJsonLongField(String existingJson, String field, long value) {
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,53 @@ public final class PddOpenApiSupport {
|
||||||
return out;
|
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) {
|
private static String firstNonBlank(String a, String b, String c) {
|
||||||
if (StringUtils.hasText(a)) {
|
if (StringUtils.hasText(a)) {
|
||||||
return a.trim();
|
return a.trim();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue