|
|
@@ -1,12 +1,14 @@
|
|
|
package zs.payment.service.commodity.impl;
|
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.service.additional.update.impl.UpdateChainWrapper;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.context.annotation.Lazy;
|
|
|
@@ -50,10 +52,21 @@ import zs.payment.mapper.flymall.B2cGoodsStatusMapper;
|
|
|
import zs.payment.messages.KafkaProducer;
|
|
|
import zs.payment.utils.RedisUtils;
|
|
|
|
|
|
+import java.io.File;
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.io.OutputStreamWriter;
|
|
|
+import java.io.Writer;
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.net.URL;
|
|
|
+import java.net.URLConnection;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.sql.Timestamp;
|
|
|
import java.math.RoundingMode;
|
|
|
import java.util.*;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@Service
|
|
|
@@ -103,6 +116,20 @@ public class CommodityProcureServiceImpl implements CommodityProcureService {
|
|
|
@Autowired
|
|
|
private B2cGoodsStatusMapper b2cGoodsStatusMapper;
|
|
|
|
|
|
+ /** NFS 商品详情文件根目录(对应 PHP WEB_ROOT/nfs) */
|
|
|
+ @Value("${mall.nfs.base-path:/data01/cloudmall/nfs}")
|
|
|
+ private String nfsBasePath;
|
|
|
+ /** 商品详情文件对外访问 URL 前缀(对应 PHP SERVER_NAME + /nfs) */
|
|
|
+ @Value("${mall.nfs.url-prefix:http://mall.zhongsou.com/nfs}")
|
|
|
+ private String nfsUrlPrefix;
|
|
|
+ /** 详情图片相对路径转全路径的附件域名前缀(对应 PHP tomedia,需以 / 结尾) */
|
|
|
+ @Value("${mall.attachment-host:http://qiyun.zhongsou.com/attachment/}")
|
|
|
+ private String attachmentHost;
|
|
|
+
|
|
|
+ /** 提取 <img> 标签 src 的正则(对应 PHP html_to_images 的 preg_match) */
|
|
|
+ private static final Pattern IMG_SRC_PATTERN =
|
|
|
+ Pattern.compile("<img[^>]*?src\\s*=\\s*[\"']([^\"']+)[\"']", Pattern.CASE_INSENSITIVE);
|
|
|
+
|
|
|
private static final BigDecimal HUNDRED = new BigDecimal("100");
|
|
|
private static final BigDecimal COMMISSION_DEFAULT_RATE = new BigDecimal("0.01");
|
|
|
private static final Integer JD_SHOP_ID = 11507;
|
|
|
@@ -273,7 +300,22 @@ public class CommodityProcureServiceImpl implements CommodityProcureService {
|
|
|
yunGoods.setGd_name(spu.getGdName());
|
|
|
yunGoods.setUnit(spu.getGdUnit());
|
|
|
yunGoods.setWeight(spu.getGdWeight() != null ? BigDecimal.valueOf(spu.getGdWeight()) : BigDecimal.ZERO);
|
|
|
- yunGoods.setDesc(spu.getGdDesc());
|
|
|
+ // gd_desc 存的是 NFS 文件 URL 的 JSON 数组 ["…/1.txt","…/2.txt","…/3.txt"](对应 PHP _writeText)
|
|
|
+ // 取索引0(商品介绍)对应的 NFS 文件,回读富文本内容作为 desc(对齐 Groovy goodsDetail 逻辑)
|
|
|
+ if (StringUtils.isNotBlank(spu.getGdDesc())) {
|
|
|
+ try {
|
|
|
+ JSONArray descUrls = JSON.parseArray(spu.getGdDesc());
|
|
|
+ if (descUrls != null && !descUrls.isEmpty()) {
|
|
|
+ String descPath = descUrls.getString(0); // 0-商品介绍 1-规格参数 2-包装售后
|
|
|
+ if (StringUtils.isNotBlank(descPath)) {
|
|
|
+ yunGoods.setDesc(readRemoteFile(descPath));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析gd_desc读取NFS详情失败 gdId={}, gdDesc={}", spu.getGdId(), spu.getGdDesc(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
yunGoods.setStatement_rate(spu.getCommissionRatio());
|
|
|
yunGoods.setTg_statement_rate(spu.getTgCommissionRatio());
|
|
|
|
|
|
@@ -918,11 +960,8 @@ public class CommodityProcureServiceImpl implements CommodityProcureService {
|
|
|
spu.setGdWeight(item.getWeight() != null ? item.getWeight().doubleValue() : 0.0);
|
|
|
spu.setGdColor("#000000");
|
|
|
spu.setGdNum("QY" + System.currentTimeMillis() + String.format("%04d", (int) (Math.random() * 10000)));
|
|
|
- // 商品详情描述(对应PHP _saveDetailedDescription → _writeText)
|
|
|
- // PHP原始逻辑:将content写入NFS文件(1.txt),gd_desc存储文件URL数组
|
|
|
- // Java直连DB:无法访问PHP NFS,直接存储HTML内容到gd_desc字段
|
|
|
- // 原始Java代码仅传递msg1=content,未传msg2/msg3
|
|
|
- spu.setGdDesc(item.getContent());
|
|
|
+ // gd_desc 不在 insert 时设置:PHP 先插入商品拿到 gd_id,再由 _saveDetailedDescription 回填
|
|
|
+ // (gd_desc 存的是 NFS 文件 URL 数组,依赖 gd_id,故必须 insert 之后处理)
|
|
|
spu.setUniacid(uniacid);
|
|
|
spu.setMerchid(0);
|
|
|
spu.setStoreid(0);
|
|
|
@@ -984,7 +1023,149 @@ public class CommodityProcureServiceImpl implements CommodityProcureService {
|
|
|
if (!b2cGoodsSpuInOnlineService.save(spu)) {
|
|
|
throw new ApiException("保存商品基础信息失败");
|
|
|
}
|
|
|
- return spu.getGdId();
|
|
|
+ Integer gdId = spu.getGdId();
|
|
|
+
|
|
|
+ // 保存商品详细描述:写 NFS 文件 + 回填 gd_desc(对应 PHP _saveDetailedDescription → _writeText)
|
|
|
+ saveDetailedDescription(gdId, storeid, item.getContent());
|
|
|
+
|
|
|
+ return gdId;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存商品详细描述(严格对应 PHP _saveDetailedDescription → addDescriptionByYunyue → _writeText)
|
|
|
+ *
|
|
|
+ * PHP 真实逻辑:
|
|
|
+ * 1. msg1 = handleDescription(html_to_images(content));发送端只传 msg1,msg2/msg3 恒为空
|
|
|
+ * 2. _writeText 中 if($msg && ...):msg 为空直接跳过,故 2.txt/3.txt 从不生成
|
|
|
+ * 3. type==1 时先 updateGdDesc:gd_desc = 3 个文件 URL 的 JSON 数组,并置 is_on_sale=2
|
|
|
+ * 4. 再把 msg1 写入物理文件 {WEB_ROOT}/nfs/{shop_id}/{gd_id}/1.txt
|
|
|
+ *
|
|
|
+ * @param gdId 云商城商品ID(insert 后生成)
|
|
|
+ * @param shopId 供货商店铺ID(对应 PHP shop_id)
|
|
|
+ * @param content 商品详情原始HTML(来自463源商品 content 字段,对应 PHP $item['content'])
|
|
|
+ */
|
|
|
+ private void saveDetailedDescription(Integer gdId, Integer shopId, String content) {
|
|
|
+ // msg1 = html_to_images(content) 再经 handleDescription 清洗(对齐 PHP 发送端 + 接收端)
|
|
|
+ String msg1 = handleDescription(htmlToImages(content));
|
|
|
+ // 对应 PHP _writeText 的 if($msg && $shop_id && $gd_id && $type):msg 为空则什么都不做
|
|
|
+ if (msg1 == null || msg1.isEmpty() || shopId == null || gdId == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1) 回填 gd_desc = 3 个 NFS 文件 URL 的 JSON 数组(对应 PHP updateGdDesc,先于文件写入)
|
|
|
+ // 即便 2.txt/3.txt 不存在也照拼,保证前端解析逻辑与 PHP 完全一致
|
|
|
+ List<String> urls = new ArrayList<>();
|
|
|
+ urls.add(nfsUrlPrefix + "/" + shopId + "/" + gdId + "/1.txt");
|
|
|
+ urls.add(nfsUrlPrefix + "/" + shopId + "/" + gdId + "/2.txt");
|
|
|
+ urls.add(nfsUrlPrefix + "/" + shopId + "/" + gdId + "/3.txt");
|
|
|
+ B2cGoodsSpuInOnline update = new B2cGoodsSpuInOnline();
|
|
|
+ update.setGdId(gdId);
|
|
|
+ update.setGdDesc(JSON.toJSONString(urls));
|
|
|
+ update.setIsOnSale(2); // 对应 PHP updateGdDesc 同时置 is_on_sale=2(后续 Step4 会再置为1上架)
|
|
|
+ b2cGoodsSpuInOnlineService.updateById(update);
|
|
|
+
|
|
|
+ // 2) 把 msg1 写入 NFS 物理文件 1.txt(对应 PHP _writeText 文件写入;msg2/msg3 恒空不生成)
|
|
|
+ writeNfsText(shopId, gdId, 1, msg1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 写商品详情文件到 NFS(对应 PHP _writeText 的文件写入部分)
|
|
|
+ * 路径 {nfsBasePath}/{shopId}/{gdId}/{type}.txt,内容末尾追加 \r\n(与 PHP fwrite 一致)。
|
|
|
+ * 文件写入失败仅记录日志、不中断供货流程——与 PHP 一致(PHP 中 gd_desc 已先落库,
|
|
|
+ * 且 _writeText 失败时上层仍视为成功)。
|
|
|
+ */
|
|
|
+ private void writeNfsText(Integer shopId, Integer gdId, int type, String msg) {
|
|
|
+ try {
|
|
|
+ File dir = new File(nfsBasePath + File.separator + shopId + File.separator + gdId);
|
|
|
+ if (!dir.exists() && !dir.mkdirs() && !dir.exists()) {
|
|
|
+ log.error("创建NFS目录失败 dir={}", dir.getAbsolutePath());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ File file = new File(dir, type + ".txt");
|
|
|
+ try (Writer w = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
|
|
|
+ w.write(msg + "\r\n");
|
|
|
+ }
|
|
|
+ // 保证 web 服务(uid 96)可读:文件 0644、目录 0755
|
|
|
+ file.setReadable(true, false);
|
|
|
+ dir.setReadable(true, false);
|
|
|
+ dir.setExecutable(true, false);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("写NFS商品详情文件失败 shopId={}, gdId={}, type={}", shopId, gdId, type, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 读取远程文件内容(对齐 Groovy FileUtil.readRemoteFile)。
|
|
|
+ * 通过 URL 读取 NFS 商品详情 txt 全部文本;异常时返回空串不中断主流程。
|
|
|
+ */
|
|
|
+ private String readRemoteFile(String url) {
|
|
|
+ if (StringUtils.isBlank(url)) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ StringBuilder content = new StringBuilder();
|
|
|
+ try {
|
|
|
+ URLConnection conn = new URL(url).openConnection();
|
|
|
+ conn.setConnectTimeout(5000);
|
|
|
+ conn.setReadTimeout(10000);
|
|
|
+ try (BufferedReader reader = new BufferedReader(
|
|
|
+ new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
|
|
+ char[] buffer = new char[4096];
|
|
|
+ int len;
|
|
|
+ while ((len = reader.read(buffer)) != -1) {
|
|
|
+ content.append(buffer, 0, len);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("读取远程NFS文件失败 url={}", url, e);
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return content.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将详情HTML中 <img src> 的相对图片路径转为全路径(对应 PHP common->html_to_images / tomedia)。
|
|
|
+ * 已是 http(s):// 或 // 开头的绝对地址保持不变。
|
|
|
+ */
|
|
|
+ private String htmlToImages(String detail) {
|
|
|
+ if (detail == null || detail.isEmpty()) {
|
|
|
+ return detail == null ? "" : detail;
|
|
|
+ }
|
|
|
+ Matcher m = IMG_SRC_PATTERN.matcher(detail);
|
|
|
+ StringBuffer sb = new StringBuffer();
|
|
|
+ while (m.find()) {
|
|
|
+ String src = m.group(1);
|
|
|
+ String full = toMedia(src);
|
|
|
+ // 仅替换整段 img 标签内的 src,避免误伤其它同名字符串
|
|
|
+ String replaced = m.group(0).replace(src, full);
|
|
|
+ m.appendReplacement(sb, Matcher.quoteReplacement(replaced));
|
|
|
+ }
|
|
|
+ m.appendTail(sb);
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 相对图片路径转全路径(对应 PHP tomedia):绝对地址原样返回,相对路径拼接附件域名。
|
|
|
+ */
|
|
|
+ private String toMedia(String src) {
|
|
|
+ if (src == null || src.isEmpty()) {
|
|
|
+ return src;
|
|
|
+ }
|
|
|
+ String s = src.trim();
|
|
|
+ if (s.startsWith("http://") || s.startsWith("https://") || s.startsWith("//")) {
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+ String path = s.startsWith("/") ? s.substring(1) : s;
|
|
|
+ return attachmentHost + path;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清洗富文本(对应 PHP _handleDescription):去除空段落标签 <p><br/></p> 和 <p></p>。
|
|
|
+ */
|
|
|
+ private String handleDescription(String contents) {
|
|
|
+ if (contents == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return contents.replace("<p><br/></p>", "").replace("<p></p>", "");
|
|
|
}
|
|
|
|
|
|
/**
|