fix(weixin): require classify category and disable fallback cat

Use classify result as mandatory cats_v2 source. If classify fails or no authorized category chain is returned, fail fast with explicit error instead of falling back to fixed leaf category.

Made-with: Cursor
This commit is contained in:
huangyujie 2026-04-21 16:44:03 +08:00
parent 864fd47907
commit 0c4fb05074
1 changed files with 47 additions and 66 deletions

View File

@ -235,11 +235,7 @@ public class ExternalSphPublishService {
root.put("head_imgs", head); root.put("head_imgs", head);
root.put("deliver_method", 0); root.put("deliver_method", 0);
root.put("deliver_acct_type", new JSONArray()); root.put("deliver_acct_type", new JSONArray());
JSONArray catsV2 = new JSONArray(); JSONArray catsV2 = resolveCatsV2OrThrow(req, imgs.headImgUrls(), accessToken);
JSONObject leaf = new JSONObject();
long leafCatId = resolveLeafCatId(req, imgs.headImgUrls(), accessToken);
leaf.put("cat_id", String.valueOf(leafCatId));
catsV2.add(leaf);
root.put("cats_v2", catsV2); root.put("cats_v2", catsV2);
JSONObject extra = new JSONObject(); JSONObject extra = new JSONObject();
extra.put("seven_day_return", 0); extra.put("seven_day_return", 0);
@ -269,13 +265,15 @@ public class ExternalSphPublishService {
return root; return root;
} }
private long resolveLeafCatId(ExternalGoodsUpsertRequest req, List<String> headImgUrls, String accessToken) { private JSONArray resolveCatsV2OrThrow(ExternalGoodsUpsertRequest req, List<String> headImgUrls, String accessToken) {
long fixedCat = props.getFixedLeafCatIdV2();
if (!StringUtils.hasText(props.getProductClassifyUrl())) { if (!StringUtils.hasText(props.getProductClassifyUrl())) {
return fixedCat; throw new IllegalStateException("external.sph.product-classify-url 未配置,无法获取推荐类目");
} }
if (!StringUtils.hasText(accessToken) || CollectionUtils.isEmpty(headImgUrls)) { if (!StringUtils.hasText(accessToken)) {
return fixedCat; throw new IllegalStateException("缺少 accessToken无法调用类目推荐接口");
}
if (CollectionUtils.isEmpty(headImgUrls)) {
throw new IllegalStateException("缺少主图,无法调用类目推荐接口");
} }
try { try {
JSONObject body = new JSONObject(); JSONObject body = new JSONObject();
@ -289,9 +287,6 @@ public class ExternalSphPublishService {
head.add(u); head.add(u);
} }
} }
if (head.isEmpty()) {
return fixedCat;
}
body.put("head_imgs", head); body.put("head_imgs", head);
String url = props.getProductClassifyUrl(); String url = props.getProductClassifyUrl();
String fullUrl = url + (url.contains("?") ? "&" : "?") String fullUrl = url + (url.contains("?") ? "&" : "?")
@ -299,49 +294,66 @@ public class ExternalSphPublishService {
String raw = httpClient.postJson(fullUrl, body.toJSONString(), String raw = httpClient.postJson(fullUrl, body.toJSONString(),
Duration.ofSeconds(Math.max(5, props.getHttpReadTimeoutSeconds()))); Duration.ofSeconds(Math.max(5, props.getHttpReadTimeoutSeconds())));
if (!WeixinShopEcHttpClient.isBizOk(raw)) { if (!WeixinShopEcHttpClient.isBizOk(raw)) {
log.warn("[SPH] classify failed shopId={} outGoodsId={} err={} snippet={}", throw new IllegalStateException("类目推荐失败: " + WeixinShopEcHttpClient.formatWxError(raw));
req.getShopId(), req.getOutGoodsId(), WeixinShopEcHttpClient.formatWxError(raw),
WeixinShopEcLogSupport.snippet(raw, 500));
return fixedCat;
} }
Long recommended = parseRecommendedLeafCatId(raw); JSONArray catsV2 = parseRecommendedCatsV2(raw);
if (recommended == null || recommended <= 0) { if (catsV2 == null || catsV2.isEmpty()) {
return fixedCat; throw new IllegalStateException("类目推荐成功但未返回可用类目链");
} }
log.info("[SPH] classify ok shopId={} outGoodsId={} recommendedLeafCatId={} fixedLeafCatId={}", String leafCat = catsV2.getJSONObject(catsV2.size() - 1).getString("cat_id");
req.getShopId(), req.getOutGoodsId(), recommended, fixedCat); log.info("[SPH] classify ok shopId={} outGoodsId={} catsV2Size={} leafCatId={} catsV2={}",
return recommended; req.getShopId(), req.getOutGoodsId(), catsV2.size(), leafCat, catsV2);
return catsV2;
} catch (IllegalStateException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
log.warn("[SPH] classify exception shopId={} outGoodsId={} err={}", throw new IllegalStateException("类目推荐异常: " + e.getMessage(), e);
req.getShopId(), req.getOutGoodsId(), e.getMessage());
return fixedCat;
} }
} }
private static Long parseRecommendedLeafCatId(String raw) { private static JSONArray parseRecommendedCatsV2(String raw) {
JSONObject root = JSON.parseObject(raw); JSONObject root = JSON.parseObject(raw);
JSONArray categories = root.getJSONArray("categories"); JSONArray categories = root.getJSONArray("categories");
if (categories == null || categories.isEmpty()) { if (categories == null || categories.isEmpty()) {
return null; return null;
} }
Long fallback = null;
for (Object obj : categories) { for (Object obj : categories) {
if (!(obj instanceof JSONObject item)) { if (!(obj instanceof JSONObject item)) {
continue; continue;
} }
JSONObject cats = item.getJSONObject("cats"); JSONObject cats = item.getJSONObject("cats");
Long leaf = extractLeafCatId(cats); JSONArray catInfo = cats == null ? null : cats.getJSONArray("cat_info");
if (leaf == null || leaf <= 0) { if (catInfo == null || catInfo.isEmpty()) {
continue; continue;
} }
if (fallback == null) {
fallback = leaf;
}
if (hasCategoryAuth(item) || hasCategoryAuth(cats)) { if (hasCategoryAuth(item) || hasCategoryAuth(cats)) {
return leaf; JSONArray out = new JSONArray();
for (Object c : catInfo) {
if (!(c instanceof JSONObject cObj)) {
continue;
}
String catId = cObj.getString("cat_id");
if (!StringUtils.hasText(catId) && cObj.containsKey("cat_id")) {
Object n = cObj.get("cat_id");
catId = n == null ? null : String.valueOf(n);
}
if (!StringUtils.hasText(catId)) {
continue;
}
JSONObject one = new JSONObject();
String catIdTrim = catId == null ? "" : catId.trim();
if (!StringUtils.hasText(catIdTrim)) {
continue;
}
one.put("cat_id", catIdTrim);
out.add(one);
}
if (!out.isEmpty()) {
return out;
} }
} }
return fallback; }
return null;
} }
private static boolean hasCategoryAuth(JSONObject o) { private static boolean hasCategoryAuth(JSONObject o) {
@ -360,37 +372,6 @@ public class ExternalSphPublishService {
return false; return false;
} }
private static Long extractLeafCatId(JSONObject cats) {
if (cats == null) {
return null;
}
JSONArray catInfo = cats.getJSONArray("cat_info");
if (catInfo == null || catInfo.isEmpty()) {
return null;
}
for (int i = catInfo.size() - 1; i >= 0; i--) {
Object c = catInfo.get(i);
if (c instanceof JSONObject cObj) {
String catId = cObj.getString("cat_id");
if (StringUtils.hasText(catId)) {
try {
return Long.parseLong(catId.trim());
} catch (NumberFormatException ignore) {
// ignore and continue
}
}
if (cObj.containsKey("cat_id")) {
try {
return cObj.getLong("cat_id");
} catch (Exception ignore) {
// ignore and continue
}
}
}
}
return null;
}
private JSONArray buildSkus(List<OGoodsSku> skuRows, ExternalGoodsUpsertRequest req, String defaultThumbWxUrl, private JSONArray buildSkus(List<OGoodsSku> skuRows, ExternalGoodsUpsertRequest req, String defaultThumbWxUrl,
String accessToken) { String accessToken) {
JSONArray arr = new JSONArray(); JSONArray arr = new JSONArray();