feat(pdd): 按 cat.rule 必填补全 goods_properties,拉取类目规则与 attributes/refPid 支持
Made-with: Cursor
This commit is contained in:
parent
89c239149c
commit
412be97158
|
|
@ -49,6 +49,10 @@ external:
|
|||
group-buy-discount-percent: 75
|
||||
# 单买价(分)= 拼单价(分)+ 本值,默认 200=2 元
|
||||
single-buy-add-fen: 200
|
||||
# 图书 ISBN 在 goods_properties 的 ref_pid(与 cat.rule 一致;0=不自动拼 ext.isbn)
|
||||
isbn-goods-property-ref-pid: 425
|
||||
# 按 pdd.goods.cat.rule.get 中 goods_properties_rule 必填项自动补全 goods_properties
|
||||
fill-goods-properties-from-cat-rule: true
|
||||
# categoryCode(请求体)-> 拼多多叶子类目 cat_id(与 maindata default-category-code=DEFAULT 对齐)
|
||||
category-map:
|
||||
DEFAULT: "15693"
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ external:
|
|||
two-pieces-discount: 99
|
||||
group-buy-discount-percent: 75
|
||||
single-buy-add-fen: 200
|
||||
isbn-goods-property-ref-pid: 425
|
||||
fill-goods-properties-from-cat-rule: true
|
||||
category-map:
|
||||
DEFAULT: "15693"
|
||||
cost-template-map:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package cn.qihangerp.model.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
|
@ -161,6 +162,12 @@ public class ExternalGoodsUpsertRequest {
|
|||
private String name;
|
||||
private String value;
|
||||
private String unit;
|
||||
/** 拼多多类目属性 {@code ref_pid},与 {@code value} 一并写入 {@code goods_properties} */
|
||||
@JsonProperty("refPid")
|
||||
@JsonAlias({"ref_pid", "refPid"})
|
||||
private Long refPid;
|
||||
@JsonAlias({"vid"})
|
||||
private Long vid;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
|
|
|||
|
|
@ -61,6 +61,17 @@ public class ExternalPddProperties {
|
|||
*/
|
||||
private long singleBuyAddFen = 200L;
|
||||
|
||||
/**
|
||||
* 图书等类目「ISBN编号」在 {@code goods_properties} 中的 {@code ref_pid}(以 {@code pdd.goods.cat.rule.get} 为准,常见 425)。
|
||||
* 当请求 {@code ext.isbn} 有值且本值 > 0 时自动拼接一条属性;为 0 则关闭自动拼接。
|
||||
*/
|
||||
private long isbnGoodsPropertyRefPid = 425L;
|
||||
|
||||
/**
|
||||
* 是否根据 {@code pdd.goods.cat.rule.get} 中 {@code goods_properties_rule} 的必填项自动补全 {@code goods_properties}。
|
||||
*/
|
||||
private boolean fillGoodsPropertiesFromCatRule = true;
|
||||
|
||||
/**
|
||||
* 与单次请求 {@code skus} 列表顺序一一对应(过滤掉 null 后的顺序),
|
||||
* 每条对应 PDD sku_list 一项的 spec_id_list 与 sku_properties。
|
||||
|
|
|
|||
|
|
@ -96,21 +96,22 @@ public class ExternalPddPublishService {
|
|||
boolean noSpecBookPublish = false;
|
||||
|
||||
List<ExternalPddProperties.PddSkuOverride> effectiveOverrides = null;
|
||||
PddCatRuleSpecAutoResolver.AutoResolveResult resolveResult = null;
|
||||
if (!CollectionUtils.isEmpty(props.getSkuOverrides())) {
|
||||
effectiveOverrides = props.getSkuOverrides();
|
||||
} else if (props.isAutoResolveSpecIdsWhenSkuOverridesEmpty()) {
|
||||
PddCatRuleSpecAutoResolver.AutoResolveResult ar = catRuleSpecAutoResolver.resolve(cred, gateway, req, skuRows, catId);
|
||||
catRuleSnippet = ar.getCatRuleSnippet();
|
||||
catFetched = ar.isCatRuleFetched();
|
||||
autoDetail = ar.getDetail();
|
||||
if (ar.isResolved() && ar.getOverrides() != null && ar.getOverrides().size() == skuRows.size()) {
|
||||
effectiveOverrides = ar.getOverrides();
|
||||
resolveResult = catRuleSpecAutoResolver.resolve(cred, gateway, req, skuRows, catId);
|
||||
catRuleSnippet = resolveResult.getCatRuleSnippet();
|
||||
catFetched = resolveResult.isCatRuleFetched();
|
||||
autoDetail = resolveResult.getDetail();
|
||||
if (resolveResult.isResolved() && resolveResult.getOverrides() != null && resolveResult.getOverrides().size() == skuRows.size()) {
|
||||
effectiveOverrides = resolveResult.getOverrides();
|
||||
specAuto = true;
|
||||
} else if (PddOpenApiSupport.isNoSpecBookCatRule(ar.getCatRuleRaw()) && skuRows.size() == 1) {
|
||||
} else if (PddOpenApiSupport.isNoSpecBookCatRule(resolveResult.getCatRuleRaw()) && skuRows.size() == 1) {
|
||||
noSpecBookPublish = true;
|
||||
autoDetail = "类目 input_max_spec_num=0:拼多多无规格发品(单 sku_list,spec_id_list=\"[]\" 字符串,price/multi_price 分数字符串,未调用 spec.id.get)";
|
||||
log.info("[PDD] no-spec book publish shopId={} outGoodsId={}", req.getShopId(), req.getOutGoodsId());
|
||||
} else if (PddOpenApiSupport.isNoSpecBookCatRule(ar.getCatRuleRaw())) {
|
||||
} else if (PddOpenApiSupport.isNoSpecBookCatRule(resolveResult.getCatRuleRaw())) {
|
||||
log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=no-spec-requires-single-sku skuCount={}",
|
||||
req.getShopId(), req.getOutGoodsId(), skuRows.size());
|
||||
return PddPublishLaneResultVo.builder()
|
||||
|
|
@ -140,11 +141,12 @@ public class ExternalPddPublishService {
|
|||
}
|
||||
}
|
||||
|
||||
String autoFetchedCatRuleRaw = null;
|
||||
if (props.isAutoFetchCatRule() && !catFetched) {
|
||||
try {
|
||||
String raw = pddPopClient.invokeTopLevelBiz(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
|
||||
autoFetchedCatRuleRaw = pddPopClient.invokeTopLevelBiz(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
|
||||
"pdd.goods.cat.rule.get", PddOpenApiSupport.catRuleGetTopLevelParams(catId));
|
||||
catRuleSnippet = PddOpenApiSupport.snippet(raw, 2000);
|
||||
catRuleSnippet = PddOpenApiSupport.snippet(autoFetchedCatRuleRaw, 2000);
|
||||
catFetched = true;
|
||||
} catch (Exception e) {
|
||||
log.warn("pdd.goods.cat.rule.get shopId={} outGoodsId={} failed: {}",
|
||||
|
|
@ -154,12 +156,15 @@ public class ExternalPddPublishService {
|
|||
}
|
||||
}
|
||||
|
||||
String catRuleRawForGoodsProps = resolveCatRuleRawForGoodsProperties(
|
||||
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)
|
||||
: paramBuilder.buildParamJson(goods, skus, req, props, effectiveOverrides);
|
||||
? paramBuilder.buildParamJsonNoSpecBook(goods, skus, req, props, catRuleRawForGoodsProps)
|
||||
: paramBuilder.buildParamJson(goods, skus, req, props, effectiveOverrides, catRuleRawForGoodsProps);
|
||||
String raw = pddPopClient.invokeGoodsAdd(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
|
||||
goodsAddRootJson);
|
||||
boolean ok = !PddOpenApiSupport.isError(raw);
|
||||
|
|
@ -222,4 +227,40 @@ public class ExternalPddPublishService {
|
|||
}
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 供 {@code goods_properties} 自动补全:优先规格解析链路上的 cat.rule,其次 {@code auto-fetch-cat-rule} 拉取结果,最后再请求一次。
|
||||
*/
|
||||
private String resolveCatRuleRawForGoodsProperties(PddShopCredential cred, String gateway, long catId,
|
||||
PddCatRuleSpecAutoResolver.AutoResolveResult resolveResult,
|
||||
String autoFetchedCatRuleRaw,
|
||||
ExternalPddProperties props,
|
||||
ExternalGoodsUpsertRequest req) {
|
||||
if (!props.isFillGoodsPropertiesFromCatRule()) {
|
||||
return null;
|
||||
}
|
||||
String raw = resolveResult != null ? resolveResult.getCatRuleRaw() : null;
|
||||
if (usableCatRuleJson(raw)) {
|
||||
return raw;
|
||||
}
|
||||
if (usableCatRuleJson(autoFetchedCatRuleRaw)) {
|
||||
return autoFetchedCatRuleRaw;
|
||||
}
|
||||
try {
|
||||
return catRuleSpecAutoResolver.fetchCatRuleJson(cred, gateway, catId);
|
||||
} catch (Exception e) {
|
||||
log.warn("pdd.goods.cat.rule.get (goods_properties) shopId={} outGoodsId={} catId={} err={}",
|
||||
req != null ? req.getShopId() : null,
|
||||
req != null ? req.getOutGoodsId() : null,
|
||||
catId,
|
||||
e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean usableCatRuleJson(String raw) {
|
||||
return StringUtils.hasText(raw)
|
||||
&& !PddOpenApiSupport.isError(raw)
|
||||
&& raw.contains("goods_properties_rule");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ import org.springframework.util.StringUtils;
|
|||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 将 Canonical 落库结果组装为 {@code pdd.goods.add} 的<strong>单根业务 JSON</strong>(字段名为 POP 下划线形式);
|
||||
|
|
@ -35,7 +38,8 @@ public class PddGoodsAddParamBuilder {
|
|||
}
|
||||
|
||||
public String buildParamJson(OGoods goods, List<OGoodsSku> skus, ExternalGoodsUpsertRequest req,
|
||||
ExternalPddProperties props, List<ExternalPddProperties.PddSkuOverride> effectiveSkuOverrides) {
|
||||
ExternalPddProperties props, List<ExternalPddProperties.PddSkuOverride> effectiveSkuOverrides,
|
||||
String catRuleRaw) {
|
||||
Long catId = resolveCatId(req.getCategoryCode(), props);
|
||||
Long costTpl = resolveCostTemplate(req.getLogisticsTemplateCode(), props);
|
||||
if (catId == null || catId <= 0) {
|
||||
|
|
@ -142,7 +146,7 @@ public class PddGoodsAddParamBuilder {
|
|||
long marketFen = resolvePddMarketPriceFen(maxSuggestedFen, maxSingleFen);
|
||||
root.put("market_price", marketFen);
|
||||
root.put("sku_list", skuList);
|
||||
root.put("goods_properties", new JSONArray());
|
||||
root.put("goods_properties", buildGoodsProperties(goods, req, props, catRuleRaw));
|
||||
|
||||
return JSON.toJSONString(root);
|
||||
}
|
||||
|
|
@ -154,7 +158,7 @@ public class PddGoodsAddParamBuilder {
|
|||
* <p>调用方须保证过滤后仅 1 条有效 SKU。</p>
|
||||
*/
|
||||
public String buildParamJsonNoSpecBook(OGoods goods, List<OGoodsSku> skus, ExternalGoodsUpsertRequest req,
|
||||
ExternalPddProperties props) {
|
||||
ExternalPddProperties props, String catRuleRaw) {
|
||||
Long catId = resolveCatId(req.getCategoryCode(), props);
|
||||
Long costTpl = resolveCostTemplate(req.getLogisticsTemplateCode(), props);
|
||||
if (catId == null || catId <= 0) {
|
||||
|
|
@ -236,11 +240,292 @@ public class PddGoodsAddParamBuilder {
|
|||
|
||||
root.put("market_price", marketFen);
|
||||
root.put("sku_list", skuList);
|
||||
root.put("goods_properties", new JSONArray());
|
||||
root.put("goods_properties", buildGoodsProperties(goods, req, props, catRuleRaw));
|
||||
|
||||
return JSON.toJSONString(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code pdd.goods.add} 根级 {@code goods_properties}:
|
||||
* <ol>
|
||||
* <li>请求 {@code attributes}({@code refPid} + {@code value} 或 {@code vid})</li>
|
||||
* <li>{@code ext.isbn}(及配置的 {@link ExternalPddProperties#getIsbnGoodsPropertyRefPid()})兼容旧逻辑</li>
|
||||
* <li>若开启 {@link ExternalPddProperties#isFillGoodsPropertiesFromCatRule()} 且传入 {@code pdd.goods.cat.rule.get} 成功体:
|
||||
* 解析 {@code goods_properties_rule.properties} 中 {@code required=true} 且非 {@code is_sku} 的项并自动补全(书名、ISBN、枚举单选等)</li>
|
||||
* </ol>
|
||||
*/
|
||||
private static JSONArray buildGoodsProperties(OGoods goods, ExternalGoodsUpsertRequest req, ExternalPddProperties props,
|
||||
String catRuleRaw) {
|
||||
JSONArray arr = new JSONArray();
|
||||
Set<Long> seenRefPid = new HashSet<>();
|
||||
if (req != null && !CollectionUtils.isEmpty(req.getAttributes())) {
|
||||
for (ExternalGoodsUpsertRequest.AttributeItem a : req.getAttributes()) {
|
||||
if (a == null || a.getRefPid() == null) {
|
||||
continue;
|
||||
}
|
||||
if (!StringUtils.hasText(a.getValue()) && a.getVid() == null) {
|
||||
continue;
|
||||
}
|
||||
long rp = a.getRefPid();
|
||||
if (!seenRefPid.add(rp)) {
|
||||
continue;
|
||||
}
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("ref_pid", rp);
|
||||
if (a.getVid() != null) {
|
||||
o.put("vid", a.getVid());
|
||||
}
|
||||
o.put("value", a.getValue() == null ? "" : a.getValue().trim());
|
||||
o.put("punit", StringUtils.hasText(a.getUnit()) ? a.getUnit().trim() : "");
|
||||
arr.add(o);
|
||||
}
|
||||
}
|
||||
String isbn = resolveIsbnFromExt(req, goods);
|
||||
long isbnPid = props.getIsbnGoodsPropertyRefPid();
|
||||
if (StringUtils.hasText(isbn) && isbnPid > 0 && seenRefPid.add(isbnPid)) {
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("ref_pid", isbnPid);
|
||||
o.put("value", isbn.trim());
|
||||
o.put("punit", "");
|
||||
arr.add(o);
|
||||
}
|
||||
|
||||
boolean usableRule = props.isFillGoodsPropertiesFromCatRule()
|
||||
&& StringUtils.hasText(catRuleRaw)
|
||||
&& !PddOpenApiSupport.isError(catRuleRaw)
|
||||
&& catRuleRaw.contains("goods_properties_rule");
|
||||
List<PddOpenApiSupport.CatGoodsPropertyRuleRow> requiredRows = usableRule
|
||||
? PddOpenApiSupport.listRequiredGoodsLevelPropertyRules(catRuleRaw)
|
||||
: List.of();
|
||||
if (!requiredRows.isEmpty()) {
|
||||
for (PddOpenApiSupport.CatGoodsPropertyRuleRow row : requiredRows) {
|
||||
if (seenRefPid.contains(row.getRefPid())) {
|
||||
continue;
|
||||
}
|
||||
ResolvedGoodsPropValue rv = resolveGoodsPropValueForCatRule(row, goods, req, props);
|
||||
if (rv == null || (rv.vid() == null && !StringUtils.hasText(rv.value()))) {
|
||||
continue;
|
||||
}
|
||||
appendGoodsPropertyEntry(arr, seenRefPid, row.getRefPid(), rv.vid(), rv.value(), "");
|
||||
}
|
||||
List<String> missing = new ArrayList<>();
|
||||
for (PddOpenApiSupport.CatGoodsPropertyRuleRow row : requiredRows) {
|
||||
if (!seenRefPid.contains(row.getRefPid())) {
|
||||
missing.add(row.getRefPid() + "[" + (row.getName() == null ? "" : row.getName()) + "]");
|
||||
}
|
||||
}
|
||||
if (!missing.isEmpty()) {
|
||||
throw new IllegalStateException("当前类目 goods_properties 必填未补齐(见 pdd.goods.cat.rule.get):"
|
||||
+ String.join(", ", missing)
|
||||
+ ";请传 attributes(refPid+value 或 vid)、ext.pdd_prop_<refPid>、ext.isbn(ISBN)等");
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static void appendGoodsPropertyEntry(JSONArray arr, Set<Long> seenRefPid, long refPid, Long vid, String value,
|
||||
String punit) {
|
||||
if (!seenRefPid.add(refPid)) {
|
||||
return;
|
||||
}
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("ref_pid", refPid);
|
||||
if (vid != null) {
|
||||
o.put("vid", vid);
|
||||
}
|
||||
o.put("value", value == null ? "" : value);
|
||||
o.put("punit", punit == null ? "" : punit);
|
||||
arr.add(o);
|
||||
}
|
||||
|
||||
private record ResolvedGoodsPropValue(Long vid, String value) {
|
||||
}
|
||||
|
||||
private static ResolvedGoodsPropValue resolveGoodsPropValueForCatRule(PddOpenApiSupport.CatGoodsPropertyRuleRow rule,
|
||||
OGoods goods, ExternalGoodsUpsertRequest req,
|
||||
ExternalPddProperties props) {
|
||||
if (req != null && !CollectionUtils.isEmpty(req.getAttributes())) {
|
||||
for (ExternalGoodsUpsertRequest.AttributeItem a : req.getAttributes()) {
|
||||
if (a == null || a.getRefPid() == null || a.getRefPid() != rule.getRefPid()) {
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.hasText(a.getValue()) || a.getVid() != null) {
|
||||
return new ResolvedGoodsPropValue(a.getVid(),
|
||||
a.getValue() == null ? "" : a.getValue().trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
JSONArray opts = rule.getValueOptions();
|
||||
if (opts != null && !opts.isEmpty()) {
|
||||
if (opts.size() == 1) {
|
||||
return pickEnumOption(opts.getJSONObject(0));
|
||||
}
|
||||
String hint = goodsPropertyExtHint(rule, req, goods);
|
||||
if (StringUtils.hasText(hint)) {
|
||||
ResolvedGoodsPropValue m = matchEnumOption(opts, hint.trim());
|
||||
if (m != null) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
if (props.getIsbnGoodsPropertyRefPid() == rule.getRefPid()) {
|
||||
String isbn = resolveIsbnFromExt(req, goods);
|
||||
if (StringUtils.hasText(isbn)) {
|
||||
ResolvedGoodsPropValue m = matchEnumOption(opts, isbn.trim());
|
||||
if (m != null) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
String v = goodsPropertyExtHint(rule, req, goods);
|
||||
if (!StringUtils.hasText(v)) {
|
||||
v = heuristicGoodsPropertyText(rule, goods, req, props);
|
||||
}
|
||||
if (!StringUtils.hasText(v)) {
|
||||
return null;
|
||||
}
|
||||
v = applyMaxValueLen(v, rule.getMaxValueHint());
|
||||
return new ResolvedGoodsPropValue(null, v);
|
||||
}
|
||||
|
||||
private static ResolvedGoodsPropValue pickEnumOption(JSONObject opt) {
|
||||
if (opt == null) {
|
||||
return null;
|
||||
}
|
||||
Long vid = opt.getLong("vid");
|
||||
if (vid == null || vid <= 0) {
|
||||
vid = opt.getLong("id");
|
||||
}
|
||||
String val = opt.getString("value");
|
||||
if (!StringUtils.hasText(val)) {
|
||||
val = opt.getString("name");
|
||||
}
|
||||
if (vid == null || vid <= 0) {
|
||||
vid = null;
|
||||
}
|
||||
return new ResolvedGoodsPropValue(vid, val == null ? "" : val);
|
||||
}
|
||||
|
||||
private static ResolvedGoodsPropValue matchEnumOption(JSONArray opts, String hint) {
|
||||
if (opts == null || opts.isEmpty() || !StringUtils.hasText(hint)) {
|
||||
return null;
|
||||
}
|
||||
String h = hint.trim().toLowerCase(Locale.ROOT);
|
||||
for (int i = 0; i < opts.size(); i++) {
|
||||
JSONObject opt = opts.getJSONObject(i);
|
||||
if (opt == null) {
|
||||
continue;
|
||||
}
|
||||
String val = opt.getString("value");
|
||||
if (!StringUtils.hasText(val)) {
|
||||
val = opt.getString("name");
|
||||
}
|
||||
if (!StringUtils.hasText(val)) {
|
||||
continue;
|
||||
}
|
||||
String lv = val.trim().toLowerCase(Locale.ROOT);
|
||||
if (lv.equals(h) || lv.contains(h) || h.contains(lv)) {
|
||||
return pickEnumOption(opt);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String goodsPropertyExtHint(PddOpenApiSupport.CatGoodsPropertyRuleRow rule,
|
||||
ExternalGoodsUpsertRequest req, OGoods goods) {
|
||||
String k1 = "pdd_prop_" + rule.getRefPid();
|
||||
String v = extFromCanonicalOrReq(req, goods, k1);
|
||||
if (StringUtils.hasText(v)) {
|
||||
return v;
|
||||
}
|
||||
if (StringUtils.hasText(rule.getName())) {
|
||||
v = extFromCanonicalOrReq(req, goods, compactExtKey(rule.getName()));
|
||||
if (StringUtils.hasText(v)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String compactExtKey(String name) {
|
||||
return name.replaceAll("\\s+", "").toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static String heuristicGoodsPropertyText(PddOpenApiSupport.CatGoodsPropertyRuleRow rule, OGoods goods,
|
||||
ExternalGoodsUpsertRequest req, ExternalPddProperties props) {
|
||||
String name = rule.getName();
|
||||
if (StringUtils.hasText(name)) {
|
||||
if (name.contains("ISBN") || name.contains("isbn")) {
|
||||
return resolveIsbnFromExt(req, goods);
|
||||
}
|
||||
if (name.contains("书名")) {
|
||||
if (goods != null && StringUtils.hasText(goods.getName())) {
|
||||
return goods.getName().trim();
|
||||
}
|
||||
if (req != null && StringUtils.hasText(req.getTitle())) {
|
||||
return req.getTitle().trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props.getIsbnGoodsPropertyRefPid() == rule.getRefPid()) {
|
||||
return resolveIsbnFromExt(req, goods);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String applyMaxValueLen(String value, String maxValueHint) {
|
||||
if (!StringUtils.hasText(value) || !StringUtils.hasText(maxValueHint)) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
int max = Integer.parseInt(maxValueHint.trim());
|
||||
if (max > 0 && value.length() > max) {
|
||||
return value.substring(0, max);
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String extFromCanonicalOrReq(ExternalGoodsUpsertRequest req, OGoods goods, String key) {
|
||||
if (!StringUtils.hasText(key)) {
|
||||
return null;
|
||||
}
|
||||
if (req != null && req.getExt() != null) {
|
||||
String v = req.getExt().get(key);
|
||||
if (StringUtils.hasText(v)) {
|
||||
return v.trim();
|
||||
}
|
||||
}
|
||||
if (goods == null || !StringUtils.hasText(goods.getCanonicalExt())) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject c = JSON.parseObject(goods.getCanonicalExt());
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject ext = c.getJSONObject("ext");
|
||||
if (ext != null) {
|
||||
String v = ext.getString(key);
|
||||
if (StringUtils.hasText(v)) {
|
||||
return v.trim();
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String resolveIsbnFromExt(ExternalGoodsUpsertRequest req, OGoods goods) {
|
||||
String a = extFromCanonicalOrReq(req, goods, "isbn");
|
||||
if (StringUtils.hasText(a)) {
|
||||
return a;
|
||||
}
|
||||
return extFromCanonicalOrReq(req, goods, "ISBN");
|
||||
}
|
||||
|
||||
private static String resolveGoodsDescForPdd(OGoods goods, ExternalGoodsUpsertRequest req) {
|
||||
if (goods != null && StringUtils.hasText(goods.getCanonicalExt())) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ import org.springframework.util.StringUtils;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
|
@ -508,4 +511,114 @@ public final class PddOpenApiSupport {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code goods_properties_rule.properties} 中「商品级」必填项({@code required} 为真且非 {@code is_sku}),
|
||||
* 用于 {@code pdd.goods.add} 根级 {@code goods_properties} 自动补全。
|
||||
*/
|
||||
public static List<CatGoodsPropertyRuleRow> listRequiredGoodsLevelPropertyRules(String catRuleBody) {
|
||||
if (!StringUtils.hasText(catRuleBody) || isError(catRuleBody)) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
JSONObject root = JSON.parseObject(catRuleBody);
|
||||
if (root == null) {
|
||||
return List.of();
|
||||
}
|
||||
JSONObject inner = unwrapCatRulePayload(root);
|
||||
if (inner == null) {
|
||||
return List.of();
|
||||
}
|
||||
JSONObject gpr = inner.getJSONObject("goods_properties_rule");
|
||||
if (gpr == null) {
|
||||
return List.of();
|
||||
}
|
||||
JSONArray props = gpr.getJSONArray("properties");
|
||||
if (props == null || props.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<CatGoodsPropertyRuleRow> out = new ArrayList<>();
|
||||
for (int i = 0; i < props.size(); i++) {
|
||||
Object el = props.get(i);
|
||||
if (!(el instanceof JSONObject p)) {
|
||||
continue;
|
||||
}
|
||||
if (!isTruthyRequired(p.get("required"))) {
|
||||
continue;
|
||||
}
|
||||
if (p.getBooleanValue("is_sku") || p.getBooleanValue("isSku")) {
|
||||
continue;
|
||||
}
|
||||
long refPid = p.getLongValue("ref_pid");
|
||||
if (refPid <= 0) {
|
||||
refPid = p.getLongValue("refPid");
|
||||
}
|
||||
if (refPid <= 0) {
|
||||
continue;
|
||||
}
|
||||
String name = p.getString("name");
|
||||
JSONArray values = p.getJSONArray("values");
|
||||
if (values == null || values.isEmpty()) {
|
||||
values = p.getJSONArray("property_values");
|
||||
}
|
||||
String maxVal = p.getString("max_value");
|
||||
if (!StringUtils.hasText(maxVal)) {
|
||||
maxVal = p.getString("maxValue");
|
||||
}
|
||||
out.add(new CatGoodsPropertyRuleRow(refPid, name, values, maxVal));
|
||||
}
|
||||
return out.isEmpty() ? List.of() : Collections.unmodifiableList(out);
|
||||
} catch (Exception e) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isTruthyRequired(Object v) {
|
||||
if (v == null) {
|
||||
return false;
|
||||
}
|
||||
if (v instanceof Boolean b) {
|
||||
return b;
|
||||
}
|
||||
if (v instanceof Number n) {
|
||||
return n.intValue() == 1;
|
||||
}
|
||||
String s = String.valueOf(v).trim();
|
||||
return "1".equals(s) || "true".equalsIgnoreCase(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一条商品级类目属性规则(来自 {@code pdd.goods.cat.rule.get})。
|
||||
*/
|
||||
public static final class CatGoodsPropertyRuleRow {
|
||||
private final long refPid;
|
||||
private final String name;
|
||||
/** 可选值列表(非空时表示需从枚举中选,元素多为含 {@code vid}/{@code value} 的对象) */
|
||||
private final JSONArray valueOptions;
|
||||
/** 文本最大长度提示(平台返回的字符串,多为数字) */
|
||||
private final String maxValueHint;
|
||||
|
||||
public CatGoodsPropertyRuleRow(long refPid, String name, JSONArray valueOptions, String maxValueHint) {
|
||||
this.refPid = refPid;
|
||||
this.name = name;
|
||||
this.valueOptions = valueOptions;
|
||||
this.maxValueHint = maxValueHint;
|
||||
}
|
||||
|
||||
public long getRefPid() {
|
||||
return refPid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public JSONArray getValueOptions() {
|
||||
return valueOptions;
|
||||
}
|
||||
|
||||
public String getMaxValueHint() {
|
||||
return maxValueHint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue