feat(external): 拼多多 POP 与 upsert 全链路日志(成功/失败均记录)

Made-with: Cursor
This commit is contained in:
huangyujie 2026-03-24 17:20:21 +08:00
parent dbc5f96768
commit d1cf874ab8
4 changed files with 97 additions and 5 deletions

View File

@ -92,6 +92,12 @@ public class ExternalGoodsController extends BaseController {
} }
ExternalGoodsUpsertResultVo vo = externalGoodsAppService.upsertGoods(req); ExternalGoodsUpsertResultVo vo = externalGoodsAppService.upsertGoods(req);
log.info("[external/goods/upsert] response shopId={} outGoodsId={} erpGoodsId={} platform={}",
req.getShopId(), req.getOutGoodsId(), vo.getGoodsId(), req.getPlatform());
if (EnumShopType.PDD.equals(platform)) {
log.info("[external/goods/upsert] pddPublish attempted={} success={}",
vo.getPddPublishAttempted(), vo.getPddPublishSuccess());
}
return AjaxResult.success(vo); return AjaxResult.success(vo);
} }

View File

@ -16,6 +16,7 @@ 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;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -32,6 +33,7 @@ import java.util.Objects;
/** /**
* @author guochengyu * @author guochengyu
*/ */
@Slf4j
@AllArgsConstructor @AllArgsConstructor
@Service @Service
public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService { public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
@ -142,7 +144,30 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
out.pddAutoResolveDetail(lane.getAutoResolveDetail()); out.pddAutoResolveDetail(lane.getAutoResolveDetail());
} }
return out.build(); 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={}",
req.getShopId(),
req.getOutGoodsId(),
vo.getGoodsId(),
vo.getPddPublishAttempted(),
vo.getPddPublishSuccess(),
vo.getShopCredentialSource(),
truncateForLog(vo.getPddPublishMessage(), 600),
truncateForLog(vo.getPddResponseSnippet(), 500),
vo.getPddCatRuleFetched(),
truncateForLog(vo.getPddCatRuleSnippet(), 300),
vo.getPddSpecAutoResolved(),
truncateForLog(vo.getPddAutoResolveDetail(), 300));
}
return vo;
}
private static String truncateForLog(String s, int max) {
if (s == null) {
return null;
}
return s.length() <= max ? s : s.substring(0, max) + "...";
} }
@Override @Override

View File

@ -36,6 +36,9 @@ public class ExternalPddPublishService {
*/ */
public PddPublishLaneResultVo publish(OGoods goods, List<OGoodsSku> skus, ExternalGoodsUpsertRequest req) { public PddPublishLaneResultVo publish(OGoods goods, List<OGoodsSku> skus, ExternalGoodsUpsertRequest req) {
if (!props.isPublishEnabled()) { if (!props.isPublishEnabled()) {
log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=publish-disabled",
req != null ? req.getShopId() : null,
req != null ? req.getOutGoodsId() : null);
return PddPublishLaneResultVo.builder() return PddPublishLaneResultVo.builder()
.attempted(false) .attempted(false)
.success(null) .success(null)
@ -44,6 +47,7 @@ public class ExternalPddPublishService {
} }
if (req == null) { if (req == null) {
log.info("[PDD] publish skipped shopId=null outGoodsId=null reason=req-null");
return PddPublishLaneResultVo.builder() return PddPublishLaneResultVo.builder()
.attempted(false) .attempted(false)
.success(false) .success(false)
@ -55,6 +59,8 @@ public class ExternalPddPublishService {
PddShopCredential cred = resolveCredentialFromRequest(req); PddShopCredential cred = resolveCredentialFromRequest(req);
if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret()) if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret())
|| !StringUtils.hasText(cred.getAccessToken())) { || !StringUtils.hasText(cred.getAccessToken())) {
log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=pddPopAuth-incomplete",
req.getShopId(), req.getOutGoodsId());
return PddPublishLaneResultVo.builder() return PddPublishLaneResultVo.builder()
.attempted(false) .attempted(false)
.success(false) .success(false)
@ -75,6 +81,8 @@ public class ExternalPddPublishService {
Long catId = paramBuilder.resolveCatIdForPublish(req.getCategoryCode(), props); Long catId = paramBuilder.resolveCatIdForPublish(req.getCategoryCode(), props);
if (catId == null || catId <= 0) { if (catId == null || catId <= 0) {
log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=category-map-missing categoryCode={}",
req.getShopId(), req.getOutGoodsId(), req.getCategoryCode());
return PddPublishLaneResultVo.builder() return PddPublishLaneResultVo.builder()
.attempted(false) .attempted(false)
.success(false) .success(false)
@ -100,6 +108,8 @@ public class ExternalPddPublishService {
effectiveOverrides = ar.getOverrides(); effectiveOverrides = ar.getOverrides();
specAuto = true; specAuto = true;
} else { } else {
log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=spec-auto-resolve-fail detail={}",
req.getShopId(), req.getOutGoodsId(), autoDetail);
return PddPublishLaneResultVo.builder() return PddPublishLaneResultVo.builder()
.attempted(false) .attempted(false)
.success(false) .success(false)
@ -122,18 +132,29 @@ public class ExternalPddPublishService {
catRuleSnippet = PddOpenApiSupport.snippet(raw, 2000); catRuleSnippet = PddOpenApiSupport.snippet(raw, 2000);
catFetched = true; catFetched = true;
} catch (Exception e) { } catch (Exception e) {
log.warn("pdd.goods.cat.rule.get 失败: {}", e.getMessage()); log.warn("pdd.goods.cat.rule.get shopId={} outGoodsId={} failed: {}",
req.getShopId(), req.getOutGoodsId(), e.getMessage());
catRuleSnippet = "ERROR: " + e.getMessage(); catRuleSnippet = "ERROR: " + e.getMessage();
catFetched = true; catFetched = true;
} }
} }
try { try {
log.info("[PDD] pdd.goods.add begin shopId={} outGoodsId={} catId={} skuCount={}",
req.getShopId(), req.getOutGoodsId(), catId, skuRows.size());
String paramJson = paramBuilder.buildParamJson(goods, skus, req, props, effectiveOverrides); String paramJson = paramBuilder.buildParamJson(goods, skus, req, props, effectiveOverrides);
String raw = pddPopClient.invoke(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(), String raw = pddPopClient.invoke(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
"pdd.goods.add", paramJson); "pdd.goods.add", paramJson);
boolean ok = !PddOpenApiSupport.isError(raw); boolean ok = !PddOpenApiSupport.isError(raw);
String errMsg = ok ? null : PddOpenApiSupport.formatError(raw); String errMsg = ok ? null : PddOpenApiSupport.formatError(raw);
if (ok) {
log.info("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=true msg={} snippet={}",
req.getShopId(), req.getOutGoodsId(), "pdd.goods.add 调用成功",
PddOpenApiSupport.snippet(raw, 800));
} else {
log.warn("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=false err={} snippet={}",
req.getShopId(), req.getOutGoodsId(), errMsg, PddOpenApiSupport.snippet(raw, 800));
}
return PddPublishLaneResultVo.builder() return PddPublishLaneResultVo.builder()
.attempted(true) .attempted(true)
.success(ok) .success(ok)
@ -146,7 +167,7 @@ public class ExternalPddPublishService {
.autoResolveDetail(autoDetail) .autoResolveDetail(autoDetail)
.build(); .build();
} catch (Exception e) { } catch (Exception e) {
log.warn("拼多多发布失败: {}", e.getMessage()); log.warn("拼多多发布异常 shopId={} outGoodsId={} err={}", req.getShopId(), req.getOutGoodsId(), e.getMessage(), e);
return PddPublishLaneResultVo.builder() return PddPublishLaneResultVo.builder()
.attempted(true) .attempted(true)
.success(false) .success(false)

View File

@ -1,5 +1,6 @@
package cn.qihangerp.service.external.pdd; package cn.qihangerp.service.external.pdd;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -15,9 +16,11 @@ import java.util.Map;
/** /**
* 拼多多 POP HTTP 调用application/x-www-form-urlencoded * 拼多多 POP HTTP 调用application/x-www-form-urlencoded
* <p>每次请求无论成功失败均打日志 HTTP 状态耗时响应片段client_id 脱敏</p>
* *
* @author guochengyu * @author guochengyu
*/ */
@Slf4j
@Component @Component
public class PddPopClient { public class PddPopClient {
@ -57,8 +60,45 @@ public class PddPopClient {
.header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)) .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build(); .build();
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); String host = safeHostForLog(gatewayUrl);
return resp.body(); String clientMasked = PddSensitiveLogUtil.maskForLog(clientId);
long t0 = System.nanoTime();
try {
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
long durationMs = (System.nanoTime() - t0) / 1_000_000L;
int httpStatus = resp.statusCode();
String raw = resp.body();
boolean httpOk = httpStatus >= 200 && httpStatus < 300;
boolean popBizError = PddOpenApiSupport.isError(raw);
String snippet = PddOpenApiSupport.snippet(raw, 600);
if (!httpOk || popBizError) {
String errSummary = popBizError ? PddOpenApiSupport.formatError(raw) : "";
log.warn("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} popBizError={} errSummary={} bodySnippet={}",
type, host, clientMasked, httpStatus, durationMs, popBizError, errSummary, snippet);
} else {
log.info("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} bodySnippet={}",
type, host, clientMasked, httpStatus, durationMs, snippet);
}
return raw;
} catch (Exception e) {
long durationMs = (System.nanoTime() - t0) / 1_000_000L;
log.warn("PDD_POP api={} host={} clientId={} durationMs={} invokeFailed={}",
type, host, clientMasked, durationMs, e.toString(), e);
throw e;
}
}
private static String safeHostForLog(String gatewayUrl) {
if (!StringUtils.hasText(gatewayUrl)) {
return "";
}
try {
URI u = URI.create(gatewayUrl.trim());
String h = u.getHost();
return h != null ? h : gatewayUrl.trim();
} catch (Exception e) {
return gatewayUrl.trim();
}
} }
private static String formEncode(Map<String, String> params) { private static String formEncode(Map<String, String> params) {