From 3c44408f4fa10230b9f0329ec5acafbaf02d5ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=92?= Date: Thu, 30 Oct 2025 16:25:36 +0800 Subject: [PATCH] 1 --- .../service/impl/BatchPublishServiceImpl.java | 151 +++++++++++++++--- 1 file changed, 126 insertions(+), 25 deletions(-) diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java index 5c22dd3..9534997 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/BatchPublishServiceImpl.java @@ -163,53 +163,108 @@ public class BatchPublishServiceImpl implements IBatchPublishService log.info("提取的URLs: {}", urls); log.info("提取的SKUIDs: {}", skuids); - // 查询商品详情 + // 查询商品详情(保证与输入URL数量一致且顺序一致;失败使用占位) List> products = new ArrayList<>(); Set processedSkuids = new HashSet<>(); - // 优先处理URL(更准确) for (int i = 0; i < urls.size(); i++) { String url = urls.get(i); try { log.info("正在处理第 {}/{} 个URL: {}", i + 1, urls.size(), url); - Map productInfo = queryProductInfo(url); - + Map productInfo = queryProductInfoWithRetry(url, 2, 200); + if (productInfo == null) { - log.warn("URL解析返回空结果: {}", url); + log.warn("URL解析失败,使用占位返回: {}", url); + Map placeholder = new HashMap<>(); + placeholder.put("skuid", null); + placeholder.put("productName", "未解析:" + url); + placeholder.put("price", null); + placeholder.put("productImage", null); + placeholder.put("shopName", null); + placeholder.put("shopId", null); + placeholder.put("commission", null); + placeholder.put("commissionShare", null); + placeholder.put("commissionInfo", null); + placeholder.put("materialUrl", url); + placeholder.put("images", Collections.emptyList()); + placeholder.put("wenan", Collections.emptyList()); + placeholder.put("_raw", Collections.singletonMap("sourceUrl", url)); + placeholder.put("parseFailed", true); + products.add(placeholder); continue; } - - String skuid = (String) productInfo.get("skuid"); - if (skuid == null || skuid.trim().isEmpty()) { - log.warn("商品SKUID为空,URL: {}, 返回数据: {}", url, productInfo); - continue; - } - - if (processedSkuids.contains(skuid)) { - log.info("SKUID已存在,跳过: {}", skuid); - continue; - } - + + // 不对URL阶段做去重,保持与输入一致(允许同款多条) products.add(productInfo); - processedSkuids.add(skuid); - log.info("成功添加商品: SKUID={}, 名称={}", skuid, productInfo.get("productName")); - + Object skuObj = productInfo.get("skuid"); + if (skuObj != null) { + processedSkuids.add(String.valueOf(skuObj)); + } } catch (Exception e) { - log.error("查询商品信息失败,URL: {}", url, e); + log.error("查询商品信息异常(占位返回),URL: {}", url, e); + Map placeholder = new HashMap<>(); + placeholder.put("skuid", null); + placeholder.put("productName", "未解析:" + url); + placeholder.put("price", null); + placeholder.put("productImage", null); + placeholder.put("shopName", null); + placeholder.put("shopId", null); + placeholder.put("commission", null); + placeholder.put("commissionShare", null); + placeholder.put("commissionInfo", null); + placeholder.put("materialUrl", url); + placeholder.put("images", Collections.emptyList()); + placeholder.put("wenan", Collections.emptyList()); + placeholder.put("_raw", Collections.singletonMap("sourceUrl", url)); + placeholder.put("parseFailed", true); + products.add(placeholder); } } - // 处理剩余的SKUID + // 额外处理文本中单独出现的SKUID(避免重复) for (String skuid : skuids) { if (!processedSkuids.contains(skuid)) { try { - Map productInfo = queryProductInfo(skuid); + Map productInfo = queryProductInfoWithRetry(skuid, 2, 200); if (productInfo != null) { products.add(productInfo); processedSkuids.add(skuid); + } else { + Map placeholder = new HashMap<>(); + placeholder.put("skuid", skuid); + placeholder.put("productName", "未解析:" + skuid); + placeholder.put("price", null); + placeholder.put("productImage", null); + placeholder.put("shopName", null); + placeholder.put("shopId", null); + placeholder.put("commission", null); + placeholder.put("commissionShare", null); + placeholder.put("commissionInfo", null); + placeholder.put("materialUrl", null); + placeholder.put("images", Collections.emptyList()); + placeholder.put("wenan", Collections.emptyList()); + placeholder.put("_raw", Collections.singletonMap("skuid", skuid)); + placeholder.put("parseFailed", true); + products.add(placeholder); } } catch (Exception e) { - log.error("查询商品信息失败,SKUID: {}", skuid, e); + log.error("查询商品信息失败(占位返回),SKUID: {}", skuid, e); + Map placeholder = new HashMap<>(); + placeholder.put("skuid", skuid); + placeholder.put("productName", "未解析:" + skuid); + placeholder.put("price", null); + placeholder.put("productImage", null); + placeholder.put("shopName", null); + placeholder.put("shopId", null); + placeholder.put("commission", null); + placeholder.put("commissionShare", null); + placeholder.put("commissionInfo", null); + placeholder.put("materialUrl", null); + placeholder.put("images", Collections.emptyList()); + placeholder.put("wenan", Collections.emptyList()); + placeholder.put("_raw", Collections.singletonMap("skuid", skuid)); + placeholder.put("parseFailed", true); + products.add(placeholder); } } } @@ -255,6 +310,32 @@ public class BatchPublishServiceImpl implements IBatchPublishService } } + /** + * 带重试的商品信息查询 + * @param urlOrSkuid 传入URL或SKUID + * @param retries 重试次数 + * @param backoffMillis 两次尝试之间等待毫秒 + */ + private Map queryProductInfoWithRetry(String urlOrSkuid, int retries, long backoffMillis) { + int attempts = 0; + while (attempts <= retries) { + Map result = queryProductInfo(urlOrSkuid); + if (result != null) { + return result; + } + attempts++; + if (attempts <= retries) { + try { + Thread.sleep(backoffMillis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + return null; + } + /** * 规范化商品信息 */ @@ -529,7 +610,9 @@ public class BatchPublishServiceImpl implements IBatchPublishService shop.setProvince(commonParams.getProvince()); shop.setCity(commonParams.getCity()); shop.setDistrict(commonParams.getDistrict()); - shop.setTitle(item.getProductName()); // 使用商品名称 + // 标题长度限制:最多60个字符,做安全截断(按code point防止截断表情) + String title = item.getProductName(); + shop.setTitle(truncateByCodePoints(title, 60)); // 【修改】使用用户选择的文案 String content = getSelectedWenanContent(productConfig); @@ -643,6 +726,24 @@ public class BatchPublishServiceImpl implements IBatchPublishService return wenanItem != null ? wenanItem.getContent() : null; } + /** + * 安全按字符数截断字符串(按Unicode code point,避免截断表情导致乱码) + */ + private String truncateByCodePoints(String text, int maxChars) { + if (text == null) { + return null; + } + if (maxChars <= 0) { + return ""; + } + final int length = text.codePointCount(0, text.length()); + if (length <= maxChars) { + return text; + } + int endIndex = text.offsetByCodePoints(0, maxChars); + return text.substring(0, endIndex); + } + /** * 延迟上架商品