Browse Source

修复供货和进货的逻辑,供货会把商品详情放到nfs里,形成txt问题,进货的时候又进行解析

zhangwl 1 day ago
parent
commit
6e6adb506e

+ 1 - 1
src/main/java/zs/payment/controller/EweiShopGoodsController.java

@@ -66,7 +66,7 @@ public class EweiShopGoodsController {
     }
 
     /**
-     * 供货/停止供货:把本地商品推送到云商城,或停止供货
+     * 批量供货/停止供货:把本地商品推送到云商城,或停止供货
      * issupply=1: 供货  issupply=0: 停止供货
      * 加入Redis分布式锁防止用户多次点击重复提交
      */

+ 1 - 0
src/main/java/zs/payment/controller/YunMallGoodsController.java

@@ -23,6 +23,7 @@ public class YunMallGoodsController {
 
     /**
      * 根据op,进行创建或删除生态商品
+     * 提供给企云商城php-单个供货和取消供货
      */
     @PostMapping("/save")
     public Boolean saveGoods(@Valid  @RequestBody GoodsSaveReq req){

+ 188 - 7
src/main/java/zs/payment/service/commodity/impl/CommodityProcureServiceImpl.java

@@ -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>", "");
     }
 
     /**

+ 6 - 0
src/main/resources/application-pro.yml

@@ -69,6 +69,12 @@ supply:
 
 mall:
     host: http://mall.zhongsou.com/qiyueapi/api
+    # 商品详情 NFS 文件存储(对应 PHP _writeText)
+    nfs:
+        base-path: /data01/cloudmall/nfs          # NFS 挂载后的物理根目录(对应 PHP WEB_ROOT/nfs)
+        url-prefix: http://mall.zhongsou.com/nfs   # 对外访问 URL 前缀(对应 PHP SERVER_NAME + /nfs)
+    # 详情图片相对路径转全路径的附件域名(对应 PHP tomedia,需以 / 结尾)
+    attachment-host: http://qiyun.zhongsou.com/attachment/