Kaynağa Gözat

feat(external): 新增同济推送接口并优化外部商品服务

- 新增 TongJiController 提供同济推送相关接口
- 修改 ExternalProductServiceImpl 中的商品详情回填逻辑
- 调整外部商品图片更新策略的调用方式
- 优化商品上下架操作的方法实现
- 在 ProductBaseBo 中新增查询类型字段
- 更新 MyBatis XML 映射文件中的 SQL 查询语句
- 重构 ProductBaseServiceImpl 中的商品搜索条件构建逻辑
- 完善商品审核和库存管理的相关功能
- 优化采购程序组商品列表查询功能
- 修复产品池审核服务中的协议产品处理逻辑
- 增加对供应商和合作伙伴查询类型的区分支持
- 改进商品分类推荐和购物车商品查询功能
肖路 9 saat önce
ebeveyn
işleme
605e295826

+ 919 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/tongji/TongJiController.java

@@ -0,0 +1,919 @@
+package org.dromara.external.controller.tongji;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.zhongche.domain.Goods;
+import org.dromara.common.core.domain.zhongche.domain.Prices;
+import org.dromara.common.core.domain.zhongche.vo.PricesVo;
+import org.dromara.common.core.domain.zhongche.vo.ZhongCheTrackVo;
+import org.dromara.external.api.zhongche.domain.Catalog;
+import org.dromara.external.api.zhongche.domain.StandardCatalog;
+import org.dromara.external.api.zhongche.domain.Stocks;
+import org.dromara.external.api.zhongche.domain.ZCR;
+import org.dromara.external.api.zhongche.domain.bo.*;
+import org.dromara.external.api.zhongche.domain.vo.*;
+import org.dromara.external.controller.zhongche.handle.MallMessageDispatcher;
+import org.dromara.external.domain.ExternalItem;
+import org.dromara.external.domain.ExternalProduct;
+import org.dromara.external.domain.ExternalProductCategory;
+import org.dromara.external.service.IExternalItemService;
+import org.dromara.external.service.IExternalProductCategoryService;
+import org.dromara.external.service.IExternalProductService;
+import org.dromara.external.util.SM2SignatureUtils;
+import org.dromara.external.util.SignParamUtils;
+import org.dromara.product.api.RemoteExternalOrderService;
+import org.dromara.product.api.RemoteProductService;
+import org.dromara.product.api.domain.ProductCategoryRemoteVo;
+import org.dromara.product.api.domain.zhongche.dto.StocksResult;
+import org.dromara.product.api.domain.zhongche.dto.StocksResultDto;
+import org.springframework.beans.BeanUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.dromara.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
+
+/**
+ * 同济推送接口
+ * 时间:2026/1/5,19:03
+ */
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/tongji/api/mall")
+public class TongJiController {
+    private final String key = GLOBAL_REDIS_KEY+"external:zhongche:token:";
+    private final String CLIENT_ID = "KFZnKGiDsJ7";
+    private final String VERSION = "1.0.0";
+
+    @DubboReference
+    private final RemoteProductService remoteProductService;
+
+    @DubboReference
+    private final RemoteExternalOrderService remoteOrderService;
+
+    private final MallMessageDispatcher mallMessageDispatcher;
+
+    private final IExternalProductCategoryService externalProductCategoryService;
+
+    private final IExternalProductService externalProductService;
+
+    private final IExternalItemService externalItemService;
+
+    //电商私钥
+    private final static String DEVELOPER_PRIVATE_KEY = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgQu0H97EPqkgz1YS5LkzZNmkG3mS5Er8rJ2LSoJtuOlGgCgYIKoEcz1UBgi2hRANCAARP6NYwTHpW2QTL8A2f2hpgunEpDVkJBhErBQPLqNS/Si5Q+9I9wUpCYdk1EvB5Hw6yzkE4bYk5IZM1j+/SnNFn";
+    //企采公钥
+    private final static String DEVELOPER_PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE9ITEKJdH9o1K9AeQYY7zNMo/q5/cdce+9jbawURTPEpBKAx4VkB+lRkb5e5YL+Be4pPM464rPvLyfqGNJvL6uQ==";
+
+
+    //测试环境
+//    private static final String DEVELOPER_PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1YybOl0QDE2e9humlm4AgI3wJ1tI+UfVRZx8kk4hfPtZjorHN8Tjq/cP07t4Yscy+R9oFci8xw0VpBbcnlaq1w=="; // 电商提供的私钥
+//
+//    private static final String DEVELOPER_PRIVATE_KEY = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgpQdXwMi21Mg1FhWad2AQLOwfNiDHgwhootau0YerQbagCgYIKoEcz1UBgi2hRANCAATVjJs6XRAMTZ72G6aWbgCAjfAnW0j5R9VFnHySTiF8+1mOisc3xOOr9w/Tu3hixzL5H2gVyLzHDRWkFtyeVqrX";  // 电商提供的公钥
+
+
+
+
+    //登录
+    @PostMapping("/login")
+    public ZCR login(@RequestBody ZCTokenBo zcTokenBo) {
+        //业务参数
+        UserLoginBo zcUserLoginBo = new UserLoginBo();
+
+        return null;
+    }
+
+    // 获取品目列表
+    @PostMapping("/auth/catalog/query")
+    public ZCR catelogQuery(@RequestBody ZCTokenBo zcTokenBo) {
+        //  1. 公共请求参数校验
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            // 校验失败,直接返回错误响应
+            return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
+        }
+        // 2.业务参数校验
+        String bizJson = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+        if (!"{}".equals(bizJson.trim())){
+            return ZCR.fail("5007", "业务参数data格式错误,需为空JSON的Base64编码");
+        }
+        //3. AccessToken有效性校验
+        ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!zcr.getRespCode().equals("0")){
+            return zcr;
+        }
+        // 4. 核心业务:查询品目列表并转换为文档要求的结构  TODO这里标准品目没有做
+        List<ExternalProductCategory> externalProductCategoryList = externalProductCategoryService.lambdaQuery()
+            .select(ExternalProductCategory::getProductCategoryId)
+            .list();
+        List<Long> collect = externalProductCategoryList.stream()
+            .map(ExternalProductCategory::getProductCategoryId)
+            .collect(Collectors.toList());
+        List<Catalog> catalogVoList = remoteProductService.queryListByIds(collect)
+            .stream()
+            .map(this::convertToCatalogVo)
+            .collect(Collectors.toList());
+        StandardCatalog standardCatalog = new StandardCatalog();
+        standardCatalog.setStandardCatalogId("1374013891398471680");
+        standardCatalog.setStandardCatalogName("福利套餐");
+        catalogVoList.forEach(catalog -> catalog.setStandardCatalog(standardCatalog));
+        // 5. 构造响应业务参数并Base64编码(放入ZCR的data字段)
+        CatalogsVo catalogVo = new CatalogsVo();
+        catalogVo.setCatalogs(catalogVoList);
+        // 业务参数→JSON→Base64编码
+        String respBizJson = JSONUtil.toJsonStr(catalogVo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+        // 6. 生成响应签名(核心:复用工具类,完成SM2withSM3签名,补全)
+        String respSign = "";
+        try {
+            // 步骤6.1:封装空签名的成功响应实体(用于生成待签名字符串)
+            ZCR successZcr = ZCR.ok(respDataBase64, "");
+            // 步骤6.2:生成待签名字符串(筛选、排序、拼接JSON,严格贴合文档)
+            String respSignContent = SignParamUtils.getSignContent(successZcr);
+            // 步骤6.3:用电商私钥生成SM2签名(Base64编码,直接赋值给sign)
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (JsonProcessingException e) {
+            log.error("同济电商品目查询接口 - 待签名字符串生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5009", "接口响应签名生成异常(JSON转换失败)");
+        } catch (Exception e) {
+            log.error("同济电商品目查询接口 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5010", "接口响应签名生成失败,请稍后重试");
+        }
+        // 7. 封装最终响应(严格复用你的ZCR.resetR)
+        return ZCR.ok(respDataBase64,respSign);
+    }
+
+    /**
+     * 数据库实体 → 同济品目VO(ProductCategory字段)
+     */
+    private Catalog convertToCatalogVo(ProductCategoryRemoteVo category) {
+        Catalog catalog = new Catalog();
+        // 以下字段映射需根据你的实体实际字段调整
+        catalog.setId(category.getId().toString());          // 品目id
+        catalog.setPid(category.getParentId().toString());   // 父id
+        catalog.setName(category.getCategoryName());      // 品目名称
+        catalog.setLevel(category.getClassLevel().toString()); // 品目级次
+        catalog.setState(category.getIsShow() == 1 ? 1 : 0); // 状态:1有效 0无效
+        // 标准品目(仅最细级填充)
+        /*if (StrUtil.isNotBlank(category.getStandardCatalogId())) {
+            StandardCatalogVo standardVo = new StandardCatalogVo();
+            standardVo.setStandardCatalogId(category.getStandardCatalogId());
+            standardVo.setStandardCatalogName(category.getStandardCatalogName());
+            vo.setStandardCatalog(standardVo);
+        }*/
+        catalog.setStandardCatalog(null);
+        return catalog;
+    }
+
+
+
+
+    //查询商品的库存情况
+
+    /**
+     * 请求业务参数
+     * AreaStockBo areaStock = new AreaStockBo();
+     * Goods goods = new Goods();
+     *
+     * 响应业务参数
+     * Stocks stocks = new Stocks();
+     * @param zcTokenBo
+     * @return
+     */
+    @PostMapping("/auth/egoods/stock/query")
+    public ZCR egoodsStockQuery(@RequestBody ZCTokenBo zcTokenBo) {
+
+        //1. 公共请求参数校验
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            return checkResult;
+        }
+        //2. AccessToken有效性校验
+        ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!zcr.getRespCode().equals("0")){
+            return zcr;
+        }
+        //  3. 业务参数解析
+        AreaStockBo areaStockBo;
+        try {
+            // 解码data字段(Base64→JSON→业务BO)
+            String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+            if (StrUtil.isBlank(decodedData)) {
+                return ZCR.fail("5011", "业务参数data不能为空");
+            }
+            areaStockBo = JSONUtil.toBean(decodedData, AreaStockBo.class);
+            if (areaStockBo == null) {
+                return ZCR.fail("5012", "业务参数data格式错误,无法解析");
+            }
+            // 3.3 业务参数必填项校验
+            if (areaStockBo.getGoods() == null || areaStockBo.getGoods().isEmpty()) {
+                return ZCR.fail("5013", "商品信息列表goods不能为空");
+            }
+            if (areaStockBo.getGoods().size() > 50) {
+                return ZCR.fail("5014", "商品信息列表goods最多支持50条");
+            }
+            // 3.4 单个商品信息校验(避免无效数据)
+            for (Goods goods : areaStockBo.getGoods()) {
+                if (StrUtil.isBlank(goods.getGoodsId()) || goods.getGoodsNum() == null || goods.getGoodsNum() <= 0) {
+                    return ZCR.fail("5015", "商品sku(goodsId)不能为空,所需库存(goodsNum)必须为正整数");
+                }
+            }
+        } catch (Exception e) {
+            return ZCR.fail("5007", "业务参数data解析失败:" + e.getMessage());
+        }
+
+        GoodsStockVo goodsStockVo = new GoodsStockVo();
+        // 4. 核心业务:查询商品库存(封装业务逻辑,映射文档返回格式)
+        List<Stocks> stockRespList = queryGoodsStock(areaStockBo);
+        goodsStockVo.setStocks(stockRespList);
+        // 7. 业务参数→JSON→Base64编码(响应data字段要求)
+        String respBizJson = JSONUtil.toJsonStr(goodsStockVo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+
+        // 8. 生成响应签名(贴合签名机制,复用工具类)
+        String respSign = "";
+        try {
+            ZCR successZcr = ZCR.ok(respDataBase64, "");
+            String respSignContent = SignParamUtils.getSignContent(successZcr);
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("响应签名生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5017", "接口响应签名生成失败");
+        }
+
+        // 9. 封装最终响应(符合文档要求)
+        return ZCR.ok(respDataBase64, respSign);
+    }
+
+    @GetMapping("testQueryGoodsStock")
+    public R<List<Stocks>> testQueryGoodsStock() {
+        AreaStockBo areaStockBo = new AreaStockBo();
+        areaStockBo.setAreaId("1");
+        List<Goods> goods = new ArrayList<>();
+        Goods goods1 = new Goods();
+        goods1.setGoodsId("002171765");
+        goods1.setGoodsNum(2);
+        goods.add(goods1);
+        Goods goods2 = new Goods();
+        goods2.setGoodsId("002171772");
+        goods2.setGoodsNum(2);
+        goods.add(goods2);
+        areaStockBo.setGoods(goods);
+        List<Stocks> stocks = queryGoodsStock(areaStockBo);
+        return R.ok(stocks);
+    }
+
+    /**
+     * 核心业务:查询商品库存,映射文档返回格式(包含库存状态、剩余数量等规则)
+     */
+    private List<Stocks> queryGoodsStock(AreaStockBo bo) {
+        String areaId = bo.getAreaId() == null ? "" : bo.getAreaId();
+
+        Map<String, Integer> goodsMap = new HashMap<>();
+        for (Goods goodsReq : bo.getGoods()) {
+            String goodsId = goodsReq.getGoodsId();
+            Integer goodsNum = goodsReq.getGoodsNum();
+            goodsMap.put(goodsId, goodsNum); // 对应Dubbo服务要求:key=商品ID,value=所需库存
+        }
+
+
+        // 3. 调用Dubbo服务,获取库存结果(你的remoteProductService)
+        StocksResultDto stocksResultDto = new StocksResultDto();
+        stocksResultDto.setStocks(new ArrayList<>());
+
+        // 2. 提取商品ID列表,准备查询库存
+        List<String> goodsIds = goodsMap.keySet().stream().collect(Collectors.toList());
+        ExternalItem one = externalItemService.getOne(Wrappers.lambdaQuery(ExternalItem.class).eq(ExternalItem::getItemKey, "zhongche").last("LIMIT 1"));
+        List<ExternalProduct> externalProducts = externalProductService.queryProductStock(one.getId(),goodsIds);
+        // 5. 遍历查询结果,封装返回数据
+        externalProducts.forEach(product -> {
+            // 5.1 提取商品核心信息
+            String productIdStr = product.getProductNo();
+            Integer nowInventory = product.getAvailableInventory();
+            Integer goodsNum = goodsMap.get(productIdStr); // 入参的所需库存数量
+            //可用库存
+
+
+            // 5.2 初始化单个商品库存结果
+            StocksResult stocksResult = new StocksResult();
+            stocksResult.setGoodsId(productIdStr);
+            stocksResult.setAreaId(areaId == null ? "" : areaId); // 地区id为空时赋值空字符串,保证格式统一
+
+            if (nowInventory > 0){
+                //有货
+                stocksResult.setStockState("1");
+                stocksResult.setStockStateDesc("下单立即发货");
+                if (goodsNum < 50){
+                    stocksResult.setRemainNum(nowInventory.intValue());
+                }else if (goodsNum <= 100){
+                    stocksResult.setRemainNum(-1);
+                }else {
+                    stocksResult.setRemainNum(goodsNum);
+                }
+            }else {
+                //无货
+                stocksResult.setStockState("5");
+                stocksResult.setStockStateDesc("无货");
+                stocksResult.setRemainNum(-999);
+            }
+            // 5.6 添加到结果列表(已提前初始化,无需判断null)
+            stocksResultDto.getStocks().add(stocksResult);
+        });
+
+        // 6. 补充:处理「未查询到库存」的商品(即 goods 中有,但 productPriceInventories 中没有的商品)
+        // 提取已查询到的商品ID
+        Set<String> queriedGoodsIds = externalProducts.stream()
+            .map(p -> p.getProductNo())
+            .collect(Collectors.toSet());
+        // 遍历入参goods,补充未查询到的商品,返回 remainNum=-1
+        goodsMap.keySet().stream()
+            .filter(goodsId -> !queriedGoodsIds.contains(goodsId))
+            .forEach(goodsId -> {
+                StocksResult noStockResult = new StocksResult();
+                noStockResult.setGoodsId(goodsId);
+                noStockResult.setAreaId(areaId == null ? "" : areaId);
+                noStockResult.setStockState(""); // 未查询到,库存状态为空
+                noStockResult.setStockStateDesc("未查询到商品库存");
+                noStockResult.setRemainNum(-1); // 文档规定:未查询到返回 -1
+                stocksResultDto.getStocks().add(noStockResult);
+            });
+
+        List<Stocks> goodsStockRespList = new ArrayList<>();
+        if (stocksResultDto != null || stocksResultDto.getStocks() != null || !stocksResultDto.getStocks().isEmpty()) {
+            // 无库存数据返回,遍历入参商品,按「未查询到」处理
+            for (StocksResult stocksResult : stocksResultDto.getStocks()) {
+                Stocks stocks =new Stocks();
+                BeanUtils.copyProperties(stocksResult, stocks);
+                goodsStockRespList.add(stocks);
+            }
+        }
+
+        return goodsStockRespList;
+    }
+
+
+    //查询商品价格
+    /*
+        //请求业务参数
+        Goods goods = new Goods();
+
+        //响应业务参数
+        PricesVo pricesVo = new PricesVo();
+     */
+    @PostMapping("/auth/egoods/price/query")
+    public ZCR egoodsPriceQuery(@RequestBody ZCTokenBo zcTokenBo) {
+        //1. 公共请求参数校验
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
+        }
+        //2. AccessToken有效性校验
+        ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!zcr.getRespCode().equals("0")){
+            return zcr;
+        }
+        // 3. 业务参数解析
+        GoodsPrieceBo goods;
+        List<String> goodsIdList;
+        // 解码data字段(Base64→JSON→业务BO)
+        String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+        if (StrUtil.isBlank(decodedData)) {
+            return ZCR.fail("5021", "业务参数data不能为空");
+        }
+        goods = JSONUtil.toBean(decodedData, GoodsPrieceBo.class);
+        if (goods == null || StrUtil.isBlank(goods.getGoodsIds())) {
+            return ZCR.fail("5022", "商品sku(goodsIds)不能为空");
+        }
+        // 3.3 分割商品sku,转换为列表(多个用,分隔)
+        goodsIdList = Arrays.stream(goods.getGoodsIds().split(","))
+            .map(String::trim)
+            .filter(StrUtil::isNotBlank)
+            .collect(Collectors.toList());
+        if (goodsIdList.isEmpty()) {
+            return ZCR.fail("5023", "商品sku(goodsIds)格式错误,有效sku不能为空");
+        }
+        // 4. 核心业务:调用Dubbo服务查询商品价格(适配你的业务逻辑)
+        PricesVo pricesVo = queryGoodsPrice(goodsIdList);
+
+        // 5. 构造响应业务参数并Base64编码(符合文档data字段要求)
+        String respBizJson = JSONUtil.toJsonStr(pricesVo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+
+        // 6. 生成响应签名(复用工具类,贴合签名机制)
+        String respSign = "";
+        try {
+            ZCR successZcr = ZCR.ok(respDataBase64, "");
+            String respSignContent = SignParamUtils.getSignContent(successZcr);
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("商品价格查询 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5025", "接口响应签名生成失败");
+        }
+        // 7. 封装最终响应(符合文档要求)
+        return ZCR.ok(respDataBase64, respSign);
+    }
+
+    /**
+     * 核心业务:查询商品价格,映射文档返回格式
+     */
+    public PricesVo queryGoodsPrice(List<String> goodsIdList) {
+        // 1. 初始化响应结果
+        PricesVo pricesVo = new PricesVo();
+        pricesVo.setPrices(new ArrayList<>());
+
+        // 2. 调用Dubbo服务查询商品价格(替换为你的真实Dubbo服务方法)
+        // 示例:假设你的Dubbo服务提供了查询商品价格的方法
+        Map<String, Prices> priceMap = remoteProductService.queryProductPrice(goodsIdList);
+
+        // 3. 遍历商品sku,封装价格信息(贴合文档规则)
+        for (String goodsId : goodsIdList) {
+            Prices priceResp = new Prices();
+            priceResp.setGoodsId(goodsId);
+
+            // 3.1 获取Dubbo查询结果,判断是否查询到
+            Prices productPrice = priceMap.get(goodsId);
+            if (productPrice == null) {
+                // 未查询到,按文档规则赋值-1(BigDecimal类型)
+                priceResp.setDsPrice(new BigDecimal(-1));
+                priceResp.setPrice(new BigDecimal(-1));
+                priceResp.setTaxFreePrice(null);
+                priceResp.setTax(BigDecimal.valueOf(0.13));
+                priceResp.setTaxCode("107022301");
+                pricesVo.getPrices().add(priceResp);
+                continue;
+            }
+
+            // 3.2 填充查询到的价格信息(保证精度,最多精确到分)
+            // 电商价格(dsPrice)
+            BigDecimal dsPrice = productPrice.getDsPrice() == null ? new BigDecimal(-1) : productPrice.getDsPrice();
+            priceResp.setDsPrice(dsPrice); // 保留2位小数,四舍五入
+
+            // 协议价格(price)
+            ExternalProduct one = externalProductService.getOne(new LambdaQueryWrapper<>(ExternalProduct.class)
+                .eq(ExternalProduct::getProductNo, goodsId)
+                .last("LIMIT 1")
+            );
+            if (one == null) {
+                // 未找到协议价格,使用 Dubbo 返回的价格
+                BigDecimal protocolPrice = productPrice.getPrice() == null ? new BigDecimal(-1) : productPrice.getPrice();
+                priceResp.setPrice(protocolPrice);
+            } else {
+                // 找到协议价格,使用数据库中的协议价格
+                priceResp.setPrice(one.getExternalPrice());
+            }
+            priceResp.setTaxFreePrice(null);
+            priceResp.setTax(BigDecimal.valueOf(0.13));
+            // 税收编码(非必填)
+            priceResp.setTaxCode("107022301");
+            // 3.3 添加到响应列表
+            pricesVo.getPrices().add(priceResp);
+        }
+        return pricesVo;
+    }
+    @GetMapping("/get/queryGoodsPrice")
+    public R getMallOrderNo() {
+
+        PricesVo pricesVo = queryGoodsPrice(List.of("002171604"));
+        return R.ok(pricesVo);
+    }
+
+
+    // 4.5查询物流信息(物流信息尚未实现)
+    @PostMapping("/get/track")
+    public ZCR getTrack(@RequestBody ZCTokenBo zcTokenBo) {
+        // 1. 公共请求参数校验(含签名、版本、clientId等,复用已有逻辑)
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            return checkResult;
+        }
+
+        //2. AccessToken有效性校验
+        ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!zcr.getRespCode().equals("0")){
+            return zcr;
+        }
+
+        // 3. 业务参数解析与校验(贴合文档要求)
+        DeliveryTrackBo bizReq;
+        try {
+            // 3.1 Base64解码data字段
+            String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+            if (StrUtil.isBlank(decodedData)) {
+                return ZCR.fail("5031", "业务参数data不能为空");
+            }
+
+            // 3.2 JSON转业务请求实体
+            bizReq = JSONUtil.toBean(decodedData, DeliveryTrackBo.class);
+            if (bizReq == null) {
+                return ZCR.fail("5032", "业务参数data格式错误,无法解析");
+            }
+            // 3.3 业务参数必填项校验
+            if (StrUtil.isBlank(bizReq.getOutgoingCode()) || bizReq.getOutgoingCode().length() > 20) {
+                return ZCR.fail("5033", "发货单编号(outgoingCode)不能为空,且长度不超过20");
+            }
+            if (StrUtil.isBlank(bizReq.getWaybillType()) || !List.of("0", "1").contains(bizReq.getWaybillType())) {
+                return ZCR.fail("5034", "运单类型(waybillType)必填,且仅支持0(订单)、1(换新单)");
+            }
+        } catch (Exception e) {
+            log.error("物流查询 - 业务参数解析/校验失败,data:{}", zcTokenBo.getData(), e);
+            return ZCR.fail("5035", "业务参数data解析失败:" + e.getMessage());
+        }
+
+        //******4. 核心业务:调用Dubbo服务查询物流信息   TODO我们没有换新单 所以第二个参数先抛弃掉
+        ZhongCheTrackVo trackVo = remoteOrderService.queryLogisticsTrack(bizReq.getOutgoingCode(), bizReq.getWaybillType());
+        if (trackVo == null){
+            return ZCR.fail("5035", "物流信息为空");
+        }
+        // 6. 业务参数→JSON→Base64编码(符合文档data字段要求)
+        String respBizJson = JSONUtil.toJsonStr(trackVo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+
+        // 7. 生成响应签名(复用工具类,贴合签名机制)
+        String respSign = "";
+        try {
+            ZCR successZcr = ZCR.ok(respDataBase64, "");
+            String respSignContent = SignParamUtils.getSignContent(successZcr);
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("物流查询 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5036", "接口响应签名生成失败");
+        }
+
+        // 8. 封装最终响应(符合文档要求)
+        return ZCR.ok(respDataBase64, respSign);
+    }
+
+    //4.6 查询电商平台订单号
+    @PostMapping("/get/mallOrderNo")
+    public ZCR getMallOrderNo(@RequestBody ZCTokenBo zcTokenBo) {
+        //1. 公共请求参数校验
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
+        }
+        // 2. 请求签名校验(必须)
+        boolean verifyResult;
+        try {
+            verifyResult = SignParamUtils.verifyRequestSign(
+                zcTokenBo,
+                DEVELOPER_PUBLIC_KEY   // 同济给你的公钥
+            );
+        } catch (Exception e) {
+            log.error("查询电商订单号 - 请求验签异常", e);
+            return ZCR.fail("5005", "请求签名校验异常");
+        }
+
+        if (!verifyResult) {
+            return ZCR.fail("5006", "请求签名校验失败");
+        }
+        //3. AccessToken 有效性校验
+        ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!zcr.getRespCode().equals("0")){
+            return zcr;
+        }
+        // 4. 业务参数解析(Base64 → JSON → BO)
+        MallOrderNoQueryBo orderNoQueryBo;
+        try {
+            String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+            orderNoQueryBo = JSONUtil.toBean(decodedData, MallOrderNoQueryBo.class);
+        } catch (Exception e) {
+            log.error("查询电商订单号 - 业务参数解析失败,data={}", zcTokenBo.getData(), e);
+            return ZCR.fail("5008", "业务参数解析失败");
+        }
+
+        // 5. 查询电商平台订单号(你的内部逻辑)
+        String orderNo = orderNoQueryBo.getOrderNo();
+        String mallOrderNo = remoteOrderService.getOrderNo(orderNo);
+        if (!ObjectUtil.isNotEmpty(mallOrderNo)){
+            return ZCR.fail("5010", "查询电商订单号失败");
+        }
+        // 6. 构造业务响应 data(VO → JSON → Base64)
+        MallOrderNoVo orderNoVo = new MallOrderNoVo();
+        orderNoVo.setMallOrderNo(mallOrderNo);
+
+        String respBizJson = JSONUtil.toJsonStr(orderNoVo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+
+        // 7. 生成响应签名
+        ZCR resp = ZCR.ok(respDataBase64);
+        String respSign;
+        try {
+            String respSignContent = SignParamUtils.getSignContent(resp);
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("查询电商订单号 - 响应签名生成失败,业务JSON={}", respBizJson, e);
+            return ZCR.fail("5036", "接口响应签名生成失败");
+        }
+
+        // 9. 回填 sign 并返回
+        resp.setSign(respSign);
+        return resp;
+    }
+
+    //4.7 查询电商平台售后单号
+    @PostMapping("/get/mallAfterSaleNo")
+    public ZCR getMallAfterSaleNo(@RequestBody ZCTokenBo zcTokenBo) {
+        // 1. 公共请求参数校验
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
+        }
+
+        //2. AccessToken 有效性校验
+        ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!zcr.getRespCode().equals("0")){
+            return zcr;
+        }
+
+        // 3. 业务参数解析(Base64 → JSON → BO)
+        MallAfterSaleNoQueryBo queryBo;
+        try {
+            String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+            queryBo = JSONUtil.toBean(decodedData, MallAfterSaleNoQueryBo.class);
+        } catch (Exception e) {
+            log.error("售后单号查询 - 业务参数解析失败", e);
+            return ZCR.fail("5007", "业务参数解析失败");
+        }
+
+        // 4. 业务参数校验
+        if (queryBo == null || StrUtil.isBlank(queryBo.getAfterSaleNo())) {
+            return ZCR.fail("5007", "afterSaleNo不能为空");
+        }
+
+        // 5. 查询电商平台售后单号(内部逻辑你自己实现)
+        String afterSaleNo = queryBo.getAfterSaleNo();
+        String returnOrderNo = remoteOrderService.getReturnOrderNo(afterSaleNo);
+        if (StrUtil.isBlank(returnOrderNo)) {
+            return ZCR.fail("5004", "未查询到对应的电商平台售后单号");
+        }
+
+        // 6. 构造响应业务参数
+        MallAfterSaleNoVo vo = new MallAfterSaleNoVo();
+        vo.setMallAfterSaleNo(returnOrderNo);
+
+        String respBizJson = JSONUtil.toJsonStr(vo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+
+        // 7. 生成响应签名(关键补全点)
+        String respSign;
+        try {
+            // 先构造不带 sign 的响应对象
+            ZCR successZcr = ZCR.ok(respDataBase64);
+
+            // 按平台规则生成待签名字符串
+            String respSignContent = SignParamUtils.getSignContent(successZcr);
+
+            // 使用【电商私钥】进行 SM2 签名
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("售后单号查询 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5036", "接口响应签名生成失败");
+        }
+
+        // 8. 返回最终响应
+        return ZCR.ok(respDataBase64, respSign);
+    }
+
+    //4.8 消息监听
+    @PostMapping("/message/listening")
+    public ZCR listener(@RequestBody ZCTokenBo zcTokenBo) {
+        // 1. 公共请求参数校验
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
+        }
+
+        // 2. accessToken 校验
+        ZCR tokenCheck = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!"0".equals(tokenCheck.getRespCode())) {
+            return tokenCheck;
+        }
+        // 3. 业务参数解析
+        MessageBo messageBo;
+        try {
+            String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+            messageBo = JSONUtil.toBean(decodedData, MessageBo.class);
+        } catch (Exception e) {
+            log.error("消息监听 - 业务参数解析失败", e);
+            return ZCR.fail("5007", "业务参数解析失败");
+        }
+        // 4. 业务参数必填校验
+        if (messageBo == null
+            || StrUtil.isBlank(messageBo.getId())
+            || StrUtil.isBlank(messageBo.getType())
+            || messageBo.getContent() == null
+            || StrUtil.isBlank(messageBo.getTime())) {
+            return ZCR.fail("5007", "消息业务参数不完整");
+        }
+
+        // 5. 按消息类型处理业务
+        MessageVo messageVo;
+        try {
+            messageVo = mallMessageDispatcher.dispatch(messageBo);
+        } catch (Exception e) {
+            log.error("消息处理异常,messageId={}", messageBo.getId(), e);
+            return ZCR.fail("5007", "消息处理失败");
+        }
+        // 防御式兜底
+        if (messageVo == null) {
+            messageVo = new MessageVo("0", "消息处理无返回结果");
+        }
+
+        String respBizJson = JSONUtil.toJsonStr(messageVo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+
+        // 7. 生成响应签名(和 4.7 一模一样)
+        String respSign;
+        try {
+            ZCR successZcr = ZCR.ok(respDataBase64);
+            String respSignContent = SignParamUtils.getSignContent(successZcr);
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("消息监听 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5036", "接口响应签名生成失败");
+        }
+
+        // 8. 返回响应
+        return ZCR.ok(respDataBase64, respSign);
+    }
+
+    //4.9 非必接模块接入校验
+    @PostMapping("/notRequireApi/check")
+    public ZCR notRequireApiCheck(@RequestBody ZCTokenBo zcTokenBo) {
+        // 1. 公共请求参数校验
+        ZCR checkResult = checkPublicParams(zcTokenBo);
+        if (!"0".equals(checkResult.getRespCode())) {
+            return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
+        }
+
+        // 2. accessToken 校验
+        ZCR tokenCheck = checkAccessTokenValid(zcTokenBo.getAccessToken());
+        if (!"0".equals(tokenCheck.getRespCode())) {
+            return tokenCheck;
+        }
+
+        // 3. 业务参数解析
+        ModuleCheckBo moduleCheckBo;
+        try {
+            String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+            moduleCheckBo = JSONUtil.toBean(decodedData, ModuleCheckBo.class);
+        } catch (Exception e) {
+            log.error("非必接模块校验 - 业务参数解析失败", e);
+            return ZCR.fail("5007", "业务参数解析失败");
+        }
+        // 4. 业务参数必填校验
+        if (moduleCheckBo == null
+            || StrUtil.isBlank(moduleCheckBo.getAccount())
+            || StrUtil.isBlank(moduleCheckBo.getModuleType())) {
+            return ZCR.fail("5007", "模块校验业务参数不完整");
+        }
+
+        // 5. 核心业务逻辑
+        // 当前阶段:所有非必接模块【均未接入】
+        CheckResultVo checkResultVo = new CheckResultVo();
+        checkResultVo.setCheckResult("1"); // 0 = 未接入
+
+        // 6. 业务响应转 Base64
+        String respBizJson = JSONUtil.toJsonStr(checkResultVo);
+        String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
+
+        // 7. 生成响应签名
+        String respSign;
+        try {
+            ZCR successZcr = ZCR.ok(respDataBase64);
+            String respSignContent = SignParamUtils.getSignContent(successZcr);
+            respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("非必接模块校验 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
+            return ZCR.fail("5036", "接口响应签名生成失败");
+        }
+
+        // 8. 返回成功响应
+        return ZCR.ok(respDataBase64, respSign);
+    }
+
+    //TODO 4.10 	查询运费
+    @PostMapping("/auth/egoods/getfreight")
+    public ZCR getFreight(@RequestBody ZCTokenBo zcTokenBo) {
+        //请求业务参数
+        AreaStockBo areaStock = new AreaStockBo();
+
+        //响应业务参数
+        FreightVo freightVo = new FreightVo();
+
+        return null;
+    }
+
+
+    /**
+     * 校验公共请求参数(基于你的ZCTokenBo)
+     */
+    private ZCR checkPublicParams(ZCTokenBo zcTokenBo) {
+        // 1. 必填项非空校验(严格按文档4.2.4)
+        if (StrUtil.isBlank(zcTokenBo.getVersion())) {
+            return ZCR.fail("5007", "接口版本version不能为空");
+        }
+        if (StrUtil.isBlank(zcTokenBo.getTimestamp())) {
+            return ZCR.fail("5007", "时间戳timestamp不能为空(格式:YYYYMMDDHHMMSS)");
+        }
+        if (StrUtil.isBlank(zcTokenBo.getClientId())) {
+            return ZCR.fail("5007", "开发者id clientId不能为空");
+        }
+        if (StrUtil.isBlank(zcTokenBo.getData())) {
+            return ZCR.fail("5007", "业务参数data不能为空");
+        }
+        if (StrUtil.isBlank(zcTokenBo.getSign())) {
+            return ZCR.fail("5007", "签名sign不能为空");
+        }
+
+        // 2.版本校验(固定1.0.0)
+        if (!VERSION.equals(zcTokenBo.getVersion())) {
+            return ZCR.fail("1008", "仅支持接口版本1.0.0");
+        }
+
+        // 3. 时间戳格式校验(14位)
+        if (!ReUtil.isMatch("\\d{14}", zcTokenBo.getTimestamp())) {
+            return ZCR.fail("5007", "时间戳格式错误,需为14位数字YYYYMMDDHHMMSS");
+        }
+        // 4. clientId 校验
+        if (!CLIENT_ID.equals(zcTokenBo.getClientId())){
+            return ZCR.fail("5007", "clientId错误");
+        }
+
+        //
+        // 5. 签名校验
+        try {
+            // 防御性 Base64 校验
+//            Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
+            // 调用 SignParamUtils 验证请求签名,直接复用之前的逻辑
+            boolean signValid = SignParamUtils.verifyRequestSign(zcTokenBo, DEVELOPER_PUBLIC_KEY);
+            if (!signValid) {
+                // 验签失败,返回错误响应
+                return ZCR.fail("5007", "接口签名错误(验签失败)");
+            }
+        } catch (Exception e) {
+            // 捕获验签过程中的异常(如JSON转换、Base64解码、SM2算法异常等)
+            log.error("同济电商接口签名校验异常,请求参数:{}", JSONUtil.toJsonStr(zcTokenBo), e);
+            return ZCR.fail("5007", "接口签名校验异常,请稍后重试");
+        }
+
+        // 校验通过
+        return ZCR.ok(null);
+    }
+
+
+
+    /**
+     * 校验AccessToken有效性
+     */
+    private ZCR checkAccessTokenValid(String accessToken) {
+        if(ObjectUtil.isEmpty(accessToken)){
+            return ZCR.fail("5006","token不能为空");
+        }
+        System.out.println(StpUtil.getTokenTimeout(accessToken));
+        if(StpUtil.getTokenTimeout(accessToken) == -2l){
+            return ZCR.fail("5005","token_expired");
+        }
+        return ZCR.ok( null);
+    }
+
+    public static void main(String[] args) throws JsonProcessingException {
+        // 1️⃣ 构造请求对象(和你 controller 里的一模一样)
+        ZCTokenBo bo = new ZCTokenBo();
+        bo.setVersion("1.0.0");
+        bo.setTimestamp("20260326160413");
+        bo.setClientId("KFZAVuIyC56");
+        bo.setData("eyJpZCI6IjM1ODdjOGRhLTAxMWItNGUyOS1iYmQzLTIzODA0MDYyZDc1NyIsInR5cGUiOiIyMjAxIiwiY29udGVudCI6eyJ1c2VybmFtZSI6IjIwMjQwMzE2MDAxIn0sInRpbWUiOiIyMDI2MDMyNjE2MDQxMzU3NyJ9");
+        bo.setAccessToken( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoyMDM3MDc2OTc3MjQ2MzE0NDk3Iiwicm5TdHIiOiJVTm84azZISzFLQXZ0eXJZOG9KZUtZV0dQbHd1MnlMZCIsImNsaWVudGlkIjoiZTVjZDdlNDg5MWJmOTVkMWQxOTIwNmNlMjRhN2IzMmUiLCJ0ZW5hbnRJZCI6IjAwMDAwMCIsInVzZXJJZCI6MjAzNzA3Njk3NzI0NjMxNDQ5NywidXNlck5hbWUiOiIyMDI0MDMxNjAwMSIsImRlcHRJZCI6MTk5NjgyODcyMjQwMDg5OTA3NCwiZGVwdE5hbWUiOiJBUEnlr7nmjqXnrqHnkIblubPlj7AiLCJkZXB0Q2F0ZWdvcnkiOiIifQ.BSrQtYQQIcy2chvcza4qVjj7x6wXw2OA5zAJb0vI-rk");
+        // 注意:此时不要 setSign
+
+        // 2️⃣ 打印待签名内容(用于和同济排查)
+        String signContent = SignParamUtils.getSignContent(bo);
+        System.out.println("待签名字符串:");
+        System.out.println(signContent);
+
+        // 3️⃣ 生成 SM2 签名
+        String sign = SignParamUtils.generateRequestSign(bo, DEVELOPER_PRIVATE_KEY);
+
+        // 4️⃣ 输出 sign
+        System.out.println("生成的 sign:");
+        System.out.println(sign);
+    }
+
+}

+ 772 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/tongji/TongJiPullController.java

@@ -0,0 +1,772 @@
+package org.dromara.external.controller.tongji;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.zhongche.aftersale.bo.*;
+import org.dromara.common.core.domain.zhongche.aftersale.vo.*;
+import org.dromara.common.core.domain.zhongche.vo.OrderDetailVo;
+import org.dromara.common.core.domain.zhongche.vo.PrepareOrderDetailRespVo;
+import org.dromara.common.core.exception.api.ZhongcheException;
+import org.dromara.external.api.zhongche.domain.ZCR;
+import org.dromara.external.api.zhongche.domain.bo.*;
+import org.dromara.external.api.zhongche.domain.invoice.bo.*;
+import org.dromara.external.api.zhongche.domain.invoice.vo.InvoiceApplyDetailVo;
+import org.dromara.external.api.zhongche.domain.invoice.vo.InvoiceApplyOrdersVo;
+import org.dromara.external.api.zhongche.domain.invoice.vo.InvoiceApplyWaitlistVo;
+import org.dromara.external.api.zhongche.domain.settlement.bo.*;
+import org.dromara.external.api.zhongche.domain.settlement.vo.SettlementApplyOrdersVo;
+import org.dromara.external.api.zhongche.domain.settlement.vo.SettlementDetailVo;
+import org.dromara.external.api.zhongche.domain.settlement.vo.SettlementPaymentDetailVo;
+import org.dromara.external.api.zhongche.domain.vo.*;
+import org.dromara.external.util.SM2SignatureUtils;
+import org.dromara.external.util.SignParamUtils;
+import org.dromara.external.util.ZCApiUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * 同济拉取接口
+ * 时间:2026/1/5,19:03
+ */
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/tongji/api")
+public class TongJiPullController {
+
+    //正式环境
+    // 同济地区查询接口地址(替换为真实域名)
+    private static final String AREA_QUERY_URL = "https://supply.crrcgo.cc/mallapi";
+    // 同济提供的配置(替换为真实值)
+    private static final String CLIENT_ID = "KFZnKGiDsJ7";
+    private static final String PRIVATE_KEY = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgQu0H97EPqkgz1YS5LkzZNmkG3mS5Er8rJ2LSoJtuOlGgCgYIKoEcz1UBgi2hRANCAARP6NYwTHpW2QTL8A2f2hpgunEpDVkJBhErBQPLqNS/Si5Q+9I9wUpCYdk1EvB5Hw6yzkE4bYk5IZM1j+/SnNFn"; // 电商平台私钥
+    private static final String ZC_PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE9ITEKJdH9o1K9AeQYY7zNMo/q5/cdce+9jbawURTPEpBKAx4VkB+lRkb5e5YL+Be4pPM464rPvLyfqGNJvL6uQ=="; // 同济公钥
+    //测试环境
+    // 同济地区查询接口地址(替换为真实域名)
+//    private static final String AREA_QUERY_URL = "https://supply-test.crrcgo.cc/mallapi/";
+//    // 同济提供的配置(替换为真实值)
+//    private static final String CLIENT_ID = "KFZAVuIyC56";
+//    private static final String PRIVATE_KEY = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgpQdXwMi21Mg1FhWad2AQLOwfNiDHgwhootau0YerQbagCgYIKoEcz1UBgi2hRANCAATVjJs6XRAMTZ72G6aWbgCAjfAnW0j5R9VFnHySTiF8+1mOisc3xOOr9w/Tu3hixzL5H2gVyLzHDRWkFtyeVqrX"; // 电商平台私钥
+//    private static final String ZC_PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1YybOl0QDE2e9humlm4AgI3wJ1tI+UfVRZx8kk4hfPtZjorHN8Tjq/cP07t4Yscy+R9oFci8xw0VpBbcnlaq1w=="; // 同济公钥
+    //企采公钥
+    private static final String VERSION = "1.0.0";
+
+
+    //5	同济电子商城提供服务
+    //5.1	基础数据服务
+
+    //5.1.1	地区查询
+    @PostMapping("/area/query")
+    public AreaVo areaQuery(@RequestBody AreaQueryBo bo) {
+        // 1. 校验业务请求参数(自身先校验,避免无效调用电商平台)
+        //1 省级,2市级,3县级, 4区级
+        //父级地址id	当查询省级时填0
+        if (bo.getLevel() == null || !List.of(1, 2, 3, 4).contains(bo.getLevel())) {
+            throw new RuntimeException("级次(level)必填,且仅支持1/2/3/4");
+        }
+        if (StrUtil.isBlank(bo.getPid())) {
+            throw new RuntimeException("父级地址id(pid)不能为空");
+        }
+        //获取response.body
+        ZCR responseDto = doZcPost("/api/area/query", bo);
+        // 4. 复用工具类解析响应
+        return parseZcResponse(responseDto, AreaVo.class);
+    }
+
+    private Boolean verifyResponseSign(ZCR responseDto) {
+        // 1. 空值防护
+        if (responseDto == null || responseDto.getSign() == null || responseDto.getSign().trim().isEmpty()) {
+            log.warn("通用签名校验 - 电商平台响应签名为空");
+            return false;
+        }
+
+        try {
+            // 2. 生成响应签名原文(复用 SignParamUtils.getSignContent,剔除 sign 字段)
+            String signContent = SignParamUtils.getSignContent(responseDto);
+
+            // 3. SM2 验签(复用 SM2SignatureUtils.verify,传入电商平台公钥)
+            return SM2SignatureUtils.verify(
+                signContent,
+                responseDto.getSign(),
+                ZC_PUBLIC_KEY
+            );
+        } catch (Exception e) {
+            log.error("通用签名校验 - 校验响应签名异常", e);
+            return false;
+        }
+    }
+
+
+    private <B> ZCR doZcPost(String apiPath, B bo) {
+        // 1.BO → JSON
+        String bizJson;
+        try {
+            bizJson = ZCApiUtils.objectToJson(bo);
+        } catch (JsonProcessingException e) {
+            return ZCR.fail("5000", "业务参数转换失败:" + e.getMessage());
+        }
+
+        // 2. JSON→ Base64
+        String dataBase64;
+        try {
+            // 注意:你的 ZCApiUtils.base64Encode 内部用 getBytes(),默认是系统编码,补充 UTF-8 编码保证一致性
+            dataBase64 = ZCApiUtils.base64Encode(bizJson);
+        } catch (IllegalArgumentException e) {
+            log.error("通用请求 - 业务 JSON Base64 编码失败", e);
+            return ZCR.fail("5000", "业务参数编码失败:" + e.getMessage());
+        }
+
+        //3. 构建 ZCTokenBo 请求体
+        ZCTokenBo zcTokenBo = getZcTokenBo(dataBase64);
+        // 4. 生成请求签名
+        String requestSign;
+        try {
+            requestSign = SignParamUtils.generateRequestSign(zcTokenBo, PRIVATE_KEY);
+        } catch (Exception e) {
+            log.error("通用请求 - 生成请求签名失败", e);
+            return ZCR.fail("5000", "生成请求签名失败:" + e.getMessage());
+        }
+        zcTokenBo.setSign(requestSign);
+        // 5. ZCTokenBo → JSON 字符串
+        String requestJson;
+        try {
+            requestJson = ZCApiUtils.objectToJson(zcTokenBo);
+        } catch (JsonProcessingException e) {
+            log.error("通用请求 - ZCTokenBo 转换 JSON 失败", e);
+            return ZCR.fail("5000", "请求体转换失败:" + e.getMessage());
+        }
+
+        // 6. 发送 HTTP POST 请求(保持 hutool HTTP 工具,保证稳定性)
+        String fullUrl = AREA_QUERY_URL + apiPath;
+        HttpResponse httpResponse = HttpRequest
+            .post(fullUrl)
+            .charset(StandardCharsets.UTF_8)
+            .contentType("application/json")
+            .body(requestJson)
+            .timeout(60000)
+            .execute();
+
+        // 7. 校验 HTTP 响应状态
+        if (!httpResponse.isOk()) {
+            log.error("通用请求 - 电商平台接口调用失败,接口路径:{},HTTP 状态码:{}", apiPath, httpResponse.getStatus());
+            return ZCR.fail("5000", "电商平台接口调用失败,HTTP 响应异常");
+        }
+
+        // 8. 响应体 → ZCR(复用 ZCApiUtils 转换 JSON)
+        String responseBody = httpResponse.body();
+        ZCR responseDto;
+        try {
+            responseDto = ZCApiUtils.jsonToObject(responseBody, ZCR.class);
+        } catch (JsonProcessingException e) {
+            log.error("通用请求 - 响应体转换 ZCR 失败", e);
+            return ZCR.fail("5000", "响应体解析失败:" + e.getMessage());
+        }
+
+        // 9. 响应码校验
+        if (!"0".equals(responseDto.getRespCode())) {
+            return ZCR.fail(responseDto.getRespCode(), responseDto.getRespMsg());
+        }
+
+        // 10. 响应签名校验(同济强要求)
+        if (!verifyResponseSign(responseDto)) {
+            log.error("通用请求 - 响应签名校验失败,apiPath={}", apiPath);
+            return ZCR.fail("5001", "响应签名验证失败");
+        }
+
+        return responseDto;
+    }
+    private ZCTokenBo getZcTokenBo(String data) {
+        ZCTokenBo zcTokenBo = new ZCTokenBo();
+        zcTokenBo.setVersion(VERSION);
+        zcTokenBo.setTimestamp(DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"));
+        zcTokenBo.setClientId(CLIENT_ID);
+        zcTokenBo.setData(data);
+        zcTokenBo.setSign(null);
+        return zcTokenBo;
+    }
+
+
+    //ZhongCheOrderDeliverVo(outgoingCode=1471093529601249280)
+    //查询发货单详情
+    /*public static void main(String[] args) {
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        OutgoingQueryBo queryBo = new OutgoingQueryBo();
+        queryBo.setOrderNo("20260210110174131");
+        OutgoingVo outgoingVo = zhongChePullController.mallOrderQueryOutgoing(queryBo);
+        System.out.println(outgoingVo);
+        //OutgoingVo(outgoingList=[OutgoingList(orderNo=20260210110174131, outgoingCode=1470843870811131904, outgoingStatus=0, deliveryType=1, expressCode=123456, expressCompanyName=韵达, outgoingGoods=[OutgoingGoods(goodsId=362032, num=7.0000)])])
+    }*/
+
+//OrderDetailVo(orderNo=20260211110174132, purchaserOrderNo=2026021110452506, preOrder=0, mallOrderNo=null, orderStatus=1, orderAmount=205.24, freight=0.00, orderGoods=[OrderGoodsItem(goodsId=362032, num=7.0000, price=29.3200, subAmount=205.24, wishDeliveryDate=null)], name=111, provinceId=1, cityId=2800, countyId=55835, townId=0, address=同济智程, zip=null, mobile=13381115928, phone=null, email=service_yg@bosssoft.com.cn, memo=, orderTime=2026-02-11 10:17:40, buyer=常恒瑀, buyerMobile=18801195315, purchaserName=中国同济, purchaserDeptName=中国同济-同济科技园发展有限公司-同济智程文化科技(北京)有限公司, paymentType=01, orderInvoice=ZhongCheOrderInvoice(invoiceType=2, selectedInvoiceTitle=5, companyName=同济智程文化科技(北京), invoiceContent=1, invoiceName=王琳, invoicePhone=15249101907, invoiceFullAddress=北京海淀区羊坊店街道北京市海淀区羊坊店路11号, regCompanyName=同济智程文化科技(北京), regCode=91110108MAC4385K6J, regAddr=北京市海淀区羊坊店路11号四层428室测试, regPhone=010-51891021, regBank=中国工商银行股份有限公司北京玉渊潭支行测试, regBankAccount=111111111), voucher=0, payClient=0, paySerialNum=null, payAccount=null, payMoney=null, payPrestore=null, files=null)
+
+    //他们售后发货了
+//{"id":"d0094ec3-e032-43df-b95a-7cfb4ac91261","type":"2103","content":{"afterSaleNo":"1471137739549315072","expressCode":"21312321","expressCompanyName":"顺丰快递","freight":0,"deliverTime":"20260211135600"},"time":"20260211135601149","protocolId":null}
+//{"id":"d0094ec3-e032-43df-b95a-7cfb4ac91261","type":"2001","content":{"orderNo":"20260210110174130"},"time":"20260211135601149"}
+//{"id":"d0094ec3-e032-43df-b95a-7cfb4ac91261","type":"2101","content":{"afterSaleNo":"1471137739549315072"},"time":"20260211135601149"}
+//{"id":"d0094ec3-e032-43df-b95a-7cfb4ac91261","type":"2002","content":{"orderNo":"20260210110174130","cancelReason":"不想要了"},"time":"20260211135601149"}
+//{"id":"d0094ec3-e032-43df-b95a-7cfb4ac91261","type":"2003","content":{"orderNo":"20260210110174131","outgoingCode":"123456789"},"time":"20260211135601149"}
+//{"outgoingCode":"123456789","waybillType":"1"}
+
+    //查询订单详情
+    /*public static void main(String[] args) {
+        OrderDetailBo bo = new OrderDetailBo();
+        bo.setOrderNo("20260210110174131");
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        OrderDetailVo orderDetailVo =  zhongChePullController.mallOrderDetail(bo);
+        System.out.println(orderDetailVo);
+        //待签名字符串:{"clientId":"KFZAVuIyC56","data":"eyJvcmRlck5vIjoiMjAyNjAyMTAxMTAxNzQxMzAifQ==","timestamp":"20260210174806","version":"1.0.0"}
+        //OrderDetailVo(orderNo=20260210110174130, purchaserOrderNo=2026021010452504, preOrder=0, mallOrderNo=null, orderStatus=1, orderAmount=205.24, freight=0.00, orderGoods=[OrderGoodsItem(goodsId=362032, num=7.0000, price=29.3200, subAmount=205.24, wishDeliveryDate=null)], name=111, provinceId=1, cityId=2800, countyId=55835, townId=0, address=同济智程, zip=null, mobile=13381115928, phone=null, email=service_yg@bosssoft.com.cn, memo=, orderTime=2026-02-10 17:25:18, buyer=常恒瑀, buyerMobile=18801195315, purchaserName=中国同济, purchaserDeptName=中国同济-同济科技园发展有限公司-同济智程文化科技(北京)有限公司, paymentType=01, orderInvoice=ZhongCheOrderInvoice(invoiceType=2, selectedInvoiceTitle=5, companyName=同济智程文化科技(北京), invoiceContent=1, invoiceName=王琳, invoicePhone=15249101907, invoiceFullAddress=北京海淀区羊坊店街道北京市海淀区羊坊店路11号, regCompanyName=同济智程文化科技(北京), regCode=91110108MAC4385K6J, regAddr=北京市海淀区羊坊店路11号四层428室测试, regPhone=010-51891021, regBank=中国工商银行股份有限公司北京玉渊潭支行测试, regBankAccount=111111111), voucher=0, payClient=0, paySerialNum=null, payAccount=null, payMoney=null, payPrestore=null, files=null)
+    }*/
+
+    //导入商品
+    /*public static void main(String[] args) {
+        String username = "admin";
+        List<GoodsImportItem> batchGoods = new ArrayList<>();
+        GoodsImportItem item = new GoodsImportItem();
+        item.setGoodsId("362031");
+        item.setCatalogId("13012");
+        item.setCatalogName("原装墨盒");
+        item.setStandardCatalogId("1750717233748832257");
+        item.setStandardCatalogName("铁圈装订机耗材");
+        item.setBrandName("得力");
+        item.setName("得力");
+        item.setDsPrice(new BigDecimal(41));
+        item.setPrice(new BigDecimal(34));
+        item.setUnit("个");
+        item.setStock(100);
+        item.setBarImgUrls("[\"https://img1.com\",\"https://img2.com\"]");
+        item.setDescription("得力商品");
+        item.setProperties("{\"颜色\":\"红色\",\"尺寸\":\"XL\",\"材质\":\"棉|涤纶\"}");
+        item.setIsSelfOperated(1);
+        item.setTax(new BigDecimal(0.05));
+        item.setTaxCode("123456");
+        batchGoods.add(item);
+        GoodsImportBo bo = new GoodsImportBo();
+        bo.setAccount(username);
+        bo.setGoods(batchGoods);
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        GoodsImportVo resp = zhongChePullController.egoodsImport(bo);
+        System.out.println(resp);
+    }*/
+
+    //发货
+    /*public static void main(String[] args) {
+        ZhongCheOrderDeliverBo bo = new ZhongCheOrderDeliverBo();
+        bo.setOrderNo("20260211110174132");
+        bo.setDeliveryType("1");
+        bo.setExpressCode("434848895994511");
+        bo.setExpressCompanyName("韵达");
+        List<OrderDeliverGoods> orderDeliverGoods1 = new ArrayList<>();
+        OrderDeliverGoods orderDeliverGoods = new OrderDeliverGoods();
+        orderDeliverGoods.setGoodsId("362032");
+        orderDeliverGoods.setNum(new BigDecimal(7.0000));
+        orderDeliverGoods.setVoucher("[\"imgUrl1\",\"imgUrl2\"]");
+        orderDeliverGoods1.add(orderDeliverGoods);
+        bo.setOrderDeliverGoods(orderDeliverGoods1);
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        ZhongCheOrderDeliverVo vo =  zhongChePullController.mallOrderDeliverGoods(bo);
+        System.out.println(vo);
+    }*/
+
+    //接单
+    /*public static void main(String[] args) {
+        OrderConfirmBo bo = new OrderConfirmBo();
+        bo.setOrderNo("20260211110174132");
+        bo.setMallOrderNo("RS20260002");
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        GoodsUpdateVo goodsUpdateVo =  zhongChePullController.mallOrderConfirm(bo);
+        System.out.println(goodsUpdateVo);
+        //待签名字符串:{"clientId":"KFZAVuIyC56","data":"eyJvcmRlck5vIjoiMjAyNjAyMTAxMTAxNzQxMzEiLCJtYWxsT3JkZXJObyI6IlJTMjAyNjAwMDEifQ==","timestamp":"20260210175937","version":"1.0.0"}
+        //GoodsUpdateVo(result=1, message=null)
+    }*/
+
+    //查询售后单详情
+    /*public static void main(String[] args) {
+        AfterSaleDetailBo bo = new AfterSaleDetailBo();
+        bo.setAfterSaleNo("1471151366025121792");
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        AfterSaleDetailVo vo =zhongChePullController.mallAftersaleDetail(bo);
+        System.out.println(vo);
+        //仅退款
+        //AfterSaleDetailVo(afterSaleNo=1471151366025121792, purchaserAfterSaleNo=1471151366243225600, mallAfterSaleNo=null, orderNo=20260211110174132, afterSaleType=4, memo=null, afterSaleStatus=10, goodsReturnStatus=1, applyTime=20260211143018, afterSaleReason=AfterSaleReason(reasonTypeName=收货商品与商品描述不符, reasonTypeCode=1, reason=2312321, imageList=[]), afterSaleGoods=AfterSaleGoods(goodsId=362032, num=1.0000, needDetectionReport=null, hasPackage=null, packageDesc=null), afterSalePick=null, afterSaleReturn=null, refundAccount=null, refundMoney=null, refundGoodsStatus=1, refundPrestore=null)
+        //换货
+        //AfterSaleDetailVo(afterSaleNo=1471137739549315072, purchaserAfterSaleNo=1471137739675144192, mallAfterSaleNo=null, orderNo=20260211110174132, afterSaleType=1, memo=null, afterSaleStatus=10, goodsReturnStatus=1, applyTime=20260211133610, afterSaleReason=AfterSaleReason(reasonTypeName=错发, reasonTypeCode=1, reason=1111, imageList=[]), afterSaleGoods=AfterSaleGoods(goodsId=362032, num=1.0000, needDetectionReport=0, hasPackage=1, packageDesc=10), afterSalePick=AfterSalePick(pickType=3, name=null, provinceId=null, cityId=null, countyId=null, townId=0, address=null, zip=null, mobile=null, phone=null, email=null), afterSaleReturn=null, refundAccount=null, refundMoney=null, refundGoodsStatus=null, refundPrestore=null)
+    }*/
+
+    //同意售后
+    /*public static void main(String[] args) {
+        AfterSaleConfirmBo bo = new AfterSaleConfirmBo();
+        bo.setAfterSaleNo("1471151366025121792");
+        bo.setMallAfterSaleNo("RT202602110003");
+        //bo.setName("李景阳");
+        //bo.setProvinceId("1");
+        //bo.setCityId("2800");
+        //bo.setCountyId("55835");
+        //bo.setAddress("湖北省武汉市武昌区中南国际城C12207");
+        //bo.setZip("430000");
+        //bo.setMobile("18327041620");
+        //bo.setPhone("18327041620");
+        //bo.setEmail("18327041620@163.com");
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        GoodsUpdateVo vo =zhongChePullController.mallAftersaleConfirm(bo);
+        System.out.println(vo);
+    }*/
+
+    //换货
+    //待签名字符串:{"clientId":"KFZAVuIyC56","data":"eyJhZnRlclNhbGVObyI6IjE0NzExNDUwNTAwMjgyNDkwODgifQ==","timestamp":"20260211140847","version":"1.0.0"}
+    //AfterSaleDetailVo(afterSaleNo=1471145050028249088, purchaserAfterSaleNo=1471145050934218752, mallAfterSaleNo=null, orderNo=20260211110174132, afterSaleType=2, memo=null, afterSaleStatus=10, goodsReturnStatus=1, applyTime=20260211140512, afterSaleReason=AfterSaleReason(reasonTypeName=错发, reasonTypeCode=1, reason=123123, imageList=[]), afterSaleGoods=AfterSaleGoods(goodsId=362032, num=1.0000, needDetectionReport=0, hasPackage=1, packageDesc=10), afterSalePick=AfterSalePick(pickType=1, name=111, provinceId=1, cityId=2800, countyId=55835, townId=0, address=同济智程, zip=null, mobile=13381115928, phone=null, email=null), afterSaleReturn=AfterSaleReturn(name=111, provinceId=1, cityId=2800, countyId=55835, townId=null, address=同济智程, zip=null, mobile=13381115928, phone=null, email=null), refundAccount=null, refundMoney=null, refundGoodsStatus=null, refundPrestore=null)
+
+    //收到商品确认
+    /*public static void main(String[] args) {
+        AfterSaleReturnReceivedBo bo = new AfterSaleReturnReceivedBo();
+        bo.setAfterSaleNo("1471145050028249088");
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        GoodsUpdateVo vo =zhongChePullController. mallAftersaleReturnGoodsReceived(bo);
+        System.out.println(vo);
+    }*/
+
+    //换货新品发货
+    /*public static void main(String[] args) {
+        AfterSaleDeliverGoodsBo bo = new AfterSaleDeliverGoodsBo();
+        bo.setAfterSaleNo("1471145050028249088");
+        bo.setDeliveryType("1");
+        bo.setExpressCode("434848895994511");
+        bo.setExpressCompanyName("韵达");
+        List<AfterSaleDeliverGoodsItem> afterSaleDeliverGoods = new ArrayList<>();
+        AfterSaleDeliverGoodsItem afterSaleDeliverGoodsItem = new AfterSaleDeliverGoodsItem();
+        afterSaleDeliverGoodsItem.setGoodsId("362032");
+        afterSaleDeliverGoodsItem.setNum(new BigDecimal(1.0000));
+        afterSaleDeliverGoods.add(afterSaleDeliverGoodsItem);
+        bo.setAfterSaleDeliverGoods(afterSaleDeliverGoods);
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        AfterSaleDeliverGoodsVo vo =  zhongChePullController.mallAftersaleDeliverGoods(bo);
+        System.out.println(vo);
+        //待签名字符串:{"clientId":"KFZAVuIyC56","data":"eyJhZnRlclNhbGVObyI6IjE0NzExNDUwNTAwMjgyNDkwODgiLCJkZWxpdmVyeVR5cGUiOiIxIiwiZXhwcmVzc0NvZGUiOiI0MzQ4NDg4OTU5OTQ1MTEiLCJleHByZXNzQ29tcGFueU5hbWUiOiLpn7Xovr4iLCJhZnRlclNhbGVEZWxpdmVyR29vZHMiOlt7Imdvb2RzSWQiOiIzNjIwMzIiLCJudW0iOjF9XX0=","timestamp":"20260211142325","version":"1.0.0"}
+        //AfterSaleDeliverGoodsVo(outgoingCode=1471149642388475904)
+    }*/
+
+    //确认退款
+    /*public static void main(String[] args) {
+        AfterSaleRefundBo bo = new AfterSaleRefundBo();
+        bo.setAfterSaleNo("1471151366025121792");
+        ZhongChePullController zhongChePullController = new ZhongChePullController();
+        GoodsUpdateVo vo = zhongChePullController.mallAftersaleRefund(bo);
+        System.out.println(vo);
+    }*/
+    //5.2.1	商品导入
+    @PostMapping("/egoods/import")
+    public GoodsImportVo egoodsImport(@RequestBody GoodsImportBo bo) throws ZhongcheException {
+        ZCR responseDto = doZcPost("/api/egoods/import", bo);
+        log.info(JSONUtil.toJsonStr(bo));
+        //解析业务响应参数
+        GoodsImportVo zcr = parseZcResponse(responseDto, GoodsImportVo.class);
+        return zcr;
+    }
+    private <V> V parseZcResponse(ZCR responseDto, Class<V> voClass) throws ZhongcheException {
+        // 1. 基础校验
+        if (responseDto == null) {
+            throw new ZhongcheException("同济响应为空");
+        }
+
+        if (!"0".equals(responseDto.getRespCode())) {
+            throw new ZhongcheException("同济接口返回失败:" + responseDto.getRespCode() + " - " + responseDto.getRespMsg());
+        }
+
+        if (StrUtil.isBlank(responseDto.getData())) {
+            throw new ZhongcheException("同济响应 data 为空");
+        }
+        try {
+            // 2. Base64 → JSON
+            String bizJson = ZCApiUtils.base64Decode(responseDto.getData());
+
+            // 3. JSON → VO(Jackson 自动处理嵌套对象 / List)
+//            return ZCApiUtils.jsonToObject(bizJson, voClass);
+            return JSONUtil.toBean(bizJson, voClass);
+        } catch (Exception e) {
+            log.error("解析同济响应失败,response={}", responseDto, e);
+            throw new ZhongcheException("同济响应解析失败 : " + e.getMessage());
+        }
+    }
+
+
+    //TODO 5.2.2	商品价格变更
+    @PostMapping("/egoods/price/update")
+    public GoodsPriceUpdateVo egoodsPriceUpdate(@RequestBody GoodsImportBo bo) {
+        ZCR responseDto = doZcPost("/api/egoods/price/update", bo);
+        log.info("商品价格变更参数:{}",JSONUtil.toJsonStr(bo));
+        GoodsPriceUpdateVo zcr = parseZcResponse(responseDto, GoodsPriceUpdateVo.class);
+        log.info("商品价格变更结果:{}", zcr);
+        return zcr;
+    }
+
+    // 5.2.3	商品上下架状态变更
+    @PostMapping("/egoods/status/update")
+    public GoodsStatusUpdateVo egoodsStatusUpdate(@RequestBody GoodsStatusUpdateBo bo) {
+
+        ZCR responseDto = doZcPost("/api/egoods/status/update", bo);
+        log.info("商品上下架状态变更参数:{}",JSONUtil.toJsonStr(bo));
+        GoodsStatusUpdateVo zcr = parseZcResponse(responseDto, GoodsStatusUpdateVo.class);
+        log.info("商品上下架状态变更结果:{}", zcr);
+        return zcr;
+    }
+
+    // 5.2.4	商品图片变更
+    @PostMapping("/egoods/imgs/update")
+    public GoodsImageUpdateVo egoodsImgsUpdate(@RequestBody GoodsImageUpdateBo bo) {
+        ZCR responseDto = doZcPost("/api/egoods/imgs/update", bo);
+        log.info("商品图片变更参数:{}",JSONUtil.toJsonStr(bo));
+        GoodsImageUpdateVo zcr = parseZcResponse(responseDto, GoodsImageUpdateVo.class);
+        log.info("商品图片变更结果:{}", zcr);
+        return zcr;
+    }
+
+    // 5.2.5	商品规格信息变更
+    @PostMapping("/egoods/properties/update")
+    public GoodsUpdateVo egoodsPropertiesUpdate(@RequestBody GoodsPropertiesUpdateBo bo) {
+        ZCR responseDto = doZcPost("/api/egoods/properties/update", bo);
+        log.info("商品规格信息变更参数:{}",JSONUtil.toJsonStr(bo));
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        log.info("商品规格信息变更结果:{}", zcr);
+        return zcr;
+    }
+
+    //TODO 5.2.6	商品详情信息变更
+    @PostMapping("/egoods/detail/update")
+    public GoodsUpdateVo egoodsDetailUpdate(@RequestBody GoodsDetailBo bo) {
+        ZCR responseDto = doZcPost("/api/egoods/detail/update", bo);
+        log.info("商品详情信息变更参数:{}",JSONUtil.toJsonStr(bo));
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        log.info("商品详情信息变更结果:{}", zcr);
+        return zcr;
+    }
+
+    //5.3	订单服务
+
+    // 5.3.1	查询订单详情
+    @PostMapping("/mall/order/detail")
+    public OrderDetailVo mallOrderDetail(@RequestBody OrderDetailBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/order/detail", bo);
+        OrderDetailVo orderDetailVo = parseZcResponse(responseDto, OrderDetailVo.class);
+        return orderDetailVo;
+    }
+
+    //TODO 5.3.2	接单
+    @PostMapping("/mall/order/confirm")
+    public GoodsUpdateVo mallOrderConfirm(@RequestBody OrderConfirmBo bo) throws ZhongcheException {
+        ZCR responseDto = doZcPost("/api/mall/order/confirm", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //TODO 5.3.3	同步商品协商发货时间
+    @PostMapping("/mall/order/goods/append")
+    public GoodsUpdateVo mallOrderGoodsAppend(@RequestBody OrderGoodsAppendBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/order/goods/append", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //TODO 5.3.4	拒单
+    @PostMapping("/mall/order/reject")
+    public GoodsUpdateVo mallOrderReject(@RequestBody OrderRejectBo bo) throws ZhongcheException {
+        ZCR responseDto = doZcPost("/api/mall/order/reject", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //TODO 5.3.5	发货
+    @PostMapping("/mall/order/deliver/goods")
+    public ZhongCheOrderDeliverVo mallOrderDeliverGoods(@RequestBody ZhongCheOrderDeliverBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/order/deliver/goods", bo);
+        ZhongCheOrderDeliverVo zcr = parseZcResponse(responseDto, ZhongCheOrderDeliverVo.class);
+        return zcr;
+    }
+
+    //TODO 5.3.6	查询待处理订单列表
+    @PostMapping("/mall/order/pending/list")
+    public PendingOrderListVo mallOrderPendingList(@RequestBody ZCTokenBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/order/pending/list", bo);
+        PendingOrderListVo zcr = parseZcResponse(responseDto, PendingOrderListVo.class);
+        return zcr;
+    }
+
+    //TODO 5.3.7	妥投通知
+    @PostMapping("/mall/order/delivered/notice")
+    public GoodsUpdateVo mallOrderDeliveredNotice(@RequestBody OrderDeliveredNoticeBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/order/delivered/notice", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //TODO 5.3.8	查询发货单信息
+    @PostMapping("/mall/order/query/outgoing")
+    public OutgoingVo mallOrderQueryOutgoing(@RequestBody OutgoingQueryBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/order/query/outgoing", bo);
+        OutgoingVo zcr = parseZcResponse(responseDto, OutgoingVo.class);
+        return zcr;
+    }
+
+    //5.3.9	查询备货单详情
+    @PostMapping("/mall/prepare/order/detail")
+    public PrepareOrderDetailRespVo mallPrepareOrderDetail(@RequestBody PrepareOrderDetailReqBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/prepare/order/detail", bo);
+        PrepareOrderDetailRespVo zcr = parseZcResponse(responseDto, PrepareOrderDetailRespVo.class);
+        return zcr;
+    }
+
+    //5.3.10	备货
+    @PostMapping("/mall/prepare/order/goods")
+    public GoodsUpdateVo mallPrepareOrderGoods(@RequestBody PrepareOrderDataBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/prepare/order/goods", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+
+    //5.3.11	确认取消备货单
+    @PostMapping("/mall/prepare/order/confirm/cancel")
+    public GoodsUpdateVo mallPrepareOrderConfirmCancel(@RequestBody PrepareOrderDetailReqBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/prepare/order/confirm/cancel", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+
+
+    //TODO 5.3.12	审核订单取消
+    /*
+        //请求业务参数
+        OrderCancelAuditBo orderCancelAuditBo = new OrderCancelAuditBo();
+        //响应业务参数
+        GoodsUpdateVo goodsUpdateVo = new GoodsUpdateVo();
+     */
+    @PostMapping("/mall/order/cancel/audit/judge")
+    public GoodsUpdateVo mallOrderCancelAuditJudge(@RequestBody OrderCancelAuditBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/order/cancel/audit/judge", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+    //5.4.1	查询售后单详情
+    /*
+        //请求业务参数
+        AfterSaleDetailBo afterSaleDetailBo = new AfterSaleDetailBo();
+        //响应业务参数
+        AfterSaleDetailVo afterSaleDetailVo = new AfterSaleDetailVo();
+        AfterSaleReason afterSaleReason = new AfterSaleReason();
+        AfterSaleGoods afterSaleGoods = new AfterSaleGoods();
+        AfterSalePick afterSalePick = new AfterSalePick();
+        ImageList imageList = new ImageList();
+        AfterSaleReturn afterSaleReturn = new AfterSaleReturn();
+     */
+    @PostMapping("/mall/aftersale/detail")
+    public AfterSaleDetailVo mallAftersaleDetail(@RequestBody AfterSaleDetailBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/aftersale/detail", bo);
+        AfterSaleDetailVo zcr = parseZcResponse(responseDto, AfterSaleDetailVo.class);
+        return zcr;
+    }
+
+    //5.4.2	接受售后
+    @PostMapping("/mall/aftersale/confirm")
+    public GoodsUpdateVo mallAftersaleConfirm(@RequestBody AfterSaleConfirmBo bo) throws ZhongcheException{
+        ZCR responseDto = doZcPost("/api/mall/aftersale/confirm", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.4.3	拒绝售后
+    @PostMapping("/mall/aftersale/reject")
+    public GoodsUpdateVo mallAftersaleReject(@RequestBody AfterSaleRejectBo bo) throws ZhongcheException{
+        ZCR responseDto = doZcPost("/api/mall/aftersale/reject", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.4.4	确认收到退货
+    @PostMapping("/mall/aftersale/return/goods/received")
+    public GoodsUpdateVo mallAftersaleReturnGoodsReceived(@RequestBody AfterSaleReturnReceivedBo bo) throws ZhongcheException{
+        ZCR responseDto = doZcPost("/api/mall/aftersale/return/goods/received", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.4.5	换货新品发货
+    @PostMapping("/mall/aftersale/deliver/goods")
+    public AfterSaleDeliverGoodsVo mallAftersaleDeliverGoods(@RequestBody AfterSaleDeliverGoodsBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/aftersale/deliver/goods", bo);
+        AfterSaleDeliverGoodsVo zcr = parseZcResponse(responseDto, AfterSaleDeliverGoodsVo.class);
+        return zcr;
+    }
+
+    //TODO 5.4.6	查询待处理售后单列表
+    @PostMapping("/mall/aftersale/pending/list")
+    public AfterSalePendingListVo mallAftersalePendingList(@RequestBody AfterSalePendingListBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/aftersale/pending/list", bo);
+        AfterSalePendingListVo zcr = parseZcResponse(responseDto, AfterSalePendingListVo.class);
+        return zcr;
+    }
+
+    //5.4.7	查询换货新品发货单信息
+    @PostMapping("/mall/aftersale/query/outgoing")
+    public AfterSaleQueryOutgoingVo mallAftersaleQueryOutgoing(@RequestBody AfterSaleQueryOutgoingBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/aftersale/query/outgoing", bo);
+        AfterSaleQueryOutgoingVo zcr = parseZcResponse(responseDto, AfterSaleQueryOutgoingVo.class);
+        return zcr;
+    }
+
+    //5.4.8	线下售后
+    @PostMapping("/mall/aftersale/offline/create")
+    public AfterSaleOfflineCreateVo mallAftersaleOfflineCreate(@RequestBody AfterSaleOfflineCreateBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/aftersale/offline/create", bo);
+        AfterSaleOfflineCreateVo zcr = parseZcResponse(responseDto, AfterSaleOfflineCreateVo.class);
+        return zcr;
+    }
+
+    //5.4.9	确认退款
+    @PostMapping("/mall/aftersale/refund")
+    public GoodsUpdateVo mallAftersaleRefund(@RequestBody AfterSaleRefundBo bo) throws ZhongcheException{
+        ZCR responseDto = doZcPost("/api/mall/aftersale/refund", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+
+    //5.7.1	查询开票信息
+    @PostMapping("/mall/invoice/apply/detail")
+    public InvoiceApplyDetailVo mallInvoiceApplyDetail(@RequestBody InvoiceApplyDetailBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/apply/detail", bo);
+        InvoiceApplyDetailVo zcr = parseZcResponse(responseDto, InvoiceApplyDetailVo.class);
+        return zcr;
+
+    }
+
+    //5.7.2	查询开票申请订单列表
+    @PostMapping("/mall/invoice/apply/orders")
+    public InvoiceApplyOrdersVo mallInvoiceApplyOrders(@RequestBody InvoiceApplyOrdersBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/apply/orders", bo);
+        InvoiceApplyOrdersVo zcr = parseZcResponse(responseDto, InvoiceApplyOrdersVo.class);
+        return zcr;
+
+    }
+
+    //5.7.3    拒绝开票
+    @PostMapping("/mall/invoice/apply/reject")
+    public GoodsUpdateVo mallInvoiceApplyReject(@RequestBody InvoiceApplyRejectBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/apply/reject", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.7.4	电商同步开票信息
+    @PostMapping("/mall/invoice/sync/invoiceinfos")
+    public GoodsUpdateVo mallInvoiceSyncInvoiceInfos(@RequestBody InvoiceSyncInvoiceInfosBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/sync/invoiceinfos", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.7.5 同步纸质发票邮寄信息
+
+    @PostMapping("/mall/invoice/sync/waybill")
+    public GoodsUpdateVo mallInvoiceSyncWaybill(@RequestBody InvoiceSyncWaybillBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/sync/waybill", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.7.6 发票妥投通知
+    @PostMapping("/mall/invoice/delivered/notice")
+    public GoodsUpdateVo mallInvoiceDeliveredNotice(@RequestBody InvoiceDeliveredNoticeBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/delivered/notice", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.7.7	查询待开票申请单列表
+    @PostMapping("/mall/invoice/apply/waitlist")
+    public InvoiceApplyWaitlistVo mallInvoiceApplyWaitList(@RequestBody InvoiceApplyWaitlistBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/apply/waitlist", bo);
+        InvoiceApplyWaitlistVo zcr = parseZcResponse(responseDto, InvoiceApplyWaitlistVo.class);
+        return zcr;
+    }
+
+    //5.7.8	电商同意退票
+    @PostMapping("/mall/invoice/refund/agree")
+    public GoodsUpdateVo mallInvoiceRefundAgree(@RequestBody InvoiceRefundAgreeBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/refund/agree", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.7.9	电商确认完成退票
+
+    @PostMapping("/mall/invoice/refund/finish")
+    public GoodsUpdateVo mallInvoiceRefundFinish(@RequestBody InvoiceRefundFinishBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/refund/finish", bo);
+        GoodsUpdateVo zcr = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return zcr;
+    }
+
+    //5.7.10 电商拒绝退票申请
+    @PostMapping("/mall/invoice/refund/reject")
+    public GoodsUpdateVo mallInvoiceRefundReject(@RequestBody InvoiceRefundRejectBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/invoice/refund/reject", bo);
+        GoodsUpdateVo goodsUpdateVo = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return goodsUpdateVo;
+    }
+
+    //5.9.1	查询结算单详情
+    @PostMapping("/mall/settlement/detail")
+    public SettlementDetailVo mallSettlementDetail(@RequestBody SettlementDetailBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/settlement/detail", bo);
+        SettlementDetailVo settlementDetailVo = parseZcResponse(responseDto, SettlementDetailVo.class);
+        return settlementDetailVo;
+    }
+
+    //5.9.2	查询结算单订单列表
+    @PostMapping("/mall/settlement/apply/orders")
+    public SettlementApplyOrdersVo mallSettlementApplyOrders(@RequestBody SettlementApplyOrdersBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/settlement/apply/orders", bo);
+        SettlementApplyOrdersVo settlementApplyOrdersVo = parseZcResponse(responseDto, SettlementApplyOrdersVo.class);
+        return settlementApplyOrdersVo;
+    }
+
+
+    //5.9.3	结算单确认结算
+    @PostMapping("/mall/settlement/confirm")
+    public GoodsUpdateVo mallSettlementConfirm(@RequestBody SettlementConfirmBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/settlement/confirm", bo);
+        GoodsUpdateVo goodsUpdateVo = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return goodsUpdateVo;
+    }
+
+    //5.9.4	结算单付款详情
+    @PostMapping("/mall/settlement/payment/detail")
+    public SettlementPaymentDetailVo mallSettlementPaymentDetail( @RequestBody SettlementPaymentDetailBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/settlement/payment/detail", bo);
+        SettlementPaymentDetailVo settlementPaymentDetailVo = parseZcResponse(responseDto, SettlementPaymentDetailVo.class);
+        return settlementPaymentDetailVo;
+
+    }
+
+    //5.9.5	结算单确认收款
+    @PostMapping("/mall/settlement/confirm/payment")
+    public GoodsUpdateVo mallSettlementConfirmPayment( @RequestBody SettlementConfirmPaymentBo bo) {
+        ZCR responseDto = doZcPost("/api/mall/settlement/confirm/payment", bo);
+        GoodsUpdateVo goodsUpdateVo = parseZcResponse(responseDto, GoodsUpdateVo.class);
+        return goodsUpdateVo;
+    }
+
+}

+ 89 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/handler/impl/TongjiPushStrategy.java

@@ -0,0 +1,89 @@
+package org.dromara.external.handler.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.external.api.zhongche.domain.vo.GoodsImageUpdateVo;
+import org.dromara.external.api.zhongche.domain.vo.GoodsPriceUpdateVo;
+import org.dromara.external.api.zhongche.domain.vo.GoodsStatusUpdateVo;
+import org.dromara.external.api.zhongche.domain.vo.GoodsUpdateVo;
+import org.dromara.external.domain.ExternalProduct;
+import org.dromara.external.handler.ProductPushStrategy;
+import org.dromara.product.api.domain.ProductVo;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author
+ * @date 2026/4/21 下午5:20
+ */
+@Component("tongjiPushStrategy")
+@RequiredArgsConstructor
+@Slf4j
+public class TongjiPushStrategy  implements ProductPushStrategy {
+    /**
+     * 推送商品
+     *
+     * @param itemId
+     * @param products
+     */
+    @Override
+    public void push(Long itemId, List<ExternalProduct> products) {
+
+    }
+
+    /**
+     * 商品价格变更
+     *
+     * @param itemId
+     * @param products
+     */
+    @Override
+    public GoodsPriceUpdateVo updatePrice(Long itemId, List<ProductVo> products) {
+        return null;
+    }
+
+    /**
+     * 商品上下架状态变更
+     *
+     * @param productNos
+     * @param status
+     */
+    @Override
+    public GoodsStatusUpdateVo updateStatus(List<String> productNos, Integer status) {
+        return null;
+    }
+
+    /**
+     * 商品图片变更
+     *
+     * @param itemId
+     * @param products
+     */
+    @Override
+    public GoodsImageUpdateVo updateImages(Long itemId, List<ProductVo> products) {
+        return null;
+    }
+
+    /**
+     * 商品规格信息变更
+     *
+     * @param itemId
+     * @param products
+     */
+    @Override
+    public GoodsUpdateVo updateProperties(Long itemId, List<ProductVo> products) {
+        return null;
+    }
+
+    /**
+     * 商品详情信息变更
+     *
+     * @param itemId
+     * @param products
+     */
+    @Override
+    public GoodsUpdateVo updateDetail(Long itemId, List<ProductVo> products) {
+        return null;
+    }
+}

+ 18 - 18
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalProductServiceImpl.java

@@ -191,20 +191,20 @@ public class ExternalProductServiceImpl  extends ServiceImpl<ExternalProductMapp
                     ExternalProductCategory::getCategoryName
                 ));
         }
-            // 回填分类名称
+        // 回填分类名称
         Map<Long, String> finalCategoryMap = categoryMap;
         result.getRecords().forEach(item -> {
-                ExternalProductVo externalProductVo = BeanUtil.toBean(item, ExternalProductVo.class);
-                ProductVo productDetail = remoteProductService.getProductDetail(item.getProductId());
-                BeanUtil.copyProperties(productDetail, item);
-                item.setId(externalProductVo.getId());
-                item.setProductStatus(externalProductVo.getProductStatus());
-                item.setTotalInventory(externalProductVo.getTotalInventory());
-                if(ObjectUtil.isNotEmpty(finalCategoryMap)){
-                    String categoryName = finalCategoryMap.get(item.getExternalCategoryId());
-                    item.setStandardCatalogName(categoryName);
-                }
-            });
+            ExternalProductVo externalProductVo = BeanUtil.toBean(item, ExternalProductVo.class);
+            ProductVo productDetail = remoteProductService.getProductDetail(item.getProductId());
+            BeanUtil.copyProperties(productDetail, item);
+            item.setId(externalProductVo.getId());
+            item.setProductStatus(externalProductVo.getProductStatus());
+            item.setTotalInventory(externalProductVo.getTotalInventory());
+            if(ObjectUtil.isNotEmpty(finalCategoryMap)){
+                String categoryName = finalCategoryMap.get(item.getExternalCategoryId());
+                item.setStandardCatalogName(categoryName);
+            }
+        });
         return TableDataInfo.build( result);
     }
 
@@ -256,10 +256,10 @@ public class ExternalProductServiceImpl  extends ServiceImpl<ExternalProductMapp
         if (ObjectUtil.isNotEmpty(productIds)) {
             //获取需要推送的商品数据
             List<ProductVo> productDetails = remoteProductService.getProductDetails(productIds);
-            strategy.updateDetail(itemId,productDetails);
+//            strategy.updateDetail(itemId,productDetails);
 //            strategy.updatePrice(itemId,productDetails);
 //            strategy.updateProperties(itemId,productDetails);
-//            strategy.updateImages(itemId,productDetails);
+            strategy.updateImages(itemId,productDetails);
         }
         return baseMapper.update(Wrappers.lambdaUpdate(ExternalProduct.class)
             .set(ExternalProduct::getPushStatus, 1)
@@ -287,8 +287,8 @@ public class ExternalProductServiceImpl  extends ServiceImpl<ExternalProductMapp
             strategyFactory.getStrategy(itemKey);
 
         // 5️⃣ 执行上下架
-       strategy.updateStatus(List.of(externalProductVo.getProductNo()), bo.getProductStatus());
-       return baseMapper.update(Wrappers.lambdaUpdate(ExternalProduct.class)
+        strategy.updateStatus(List.of(externalProductVo.getProductNo()), bo.getProductStatus());
+        return baseMapper.update(Wrappers.lambdaUpdate(ExternalProduct.class)
             .set(ExternalProduct::getProductStatus, bo.getProductStatus())
             .eq(ExternalProduct::getId, bo.getId())
         )>0;
@@ -331,8 +331,8 @@ public class ExternalProductServiceImpl  extends ServiceImpl<ExternalProductMapp
     }
 
     /**
-    *测试批量上下架
-    * */
+     *测试批量上下架
+     * */
     @Override
     public Boolean batchShelfReviewTest(String[] productNos, Integer status) {
         // 4️⃣ 获取策略

+ 9 - 4
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductBaseBo.java

@@ -448,15 +448,20 @@ public class ProductBaseBo extends BaseEntity {
     private Long poolAuditId;
 
     /**
-    * 池类型
-    * */
+     * 池类型
+     * */
     private String poolType;
 
     /**
-    * 客户id
-    * */
+     * 客户id
+     * */
     private Long customerId;
 
+    /**
+     * 查询类型  1=供应商查询,2=伙伴商查询
+     * */
+    private Integer queryType;
+
 
 
 }

+ 17 - 17
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductListVo.java

@@ -28,8 +28,8 @@ public class ProductListVo implements Serializable {
     private static final long serialVersionUID = 1L;
 
     /**
-    * id
-    * */
+     * id
+     * */
     private Long id;
 
 
@@ -45,8 +45,8 @@ public class ProductListVo implements Serializable {
     //@NotNull(message = "审核池id不能为空", groups = { AddGroup.class, EditGroup.class })
     private Long poolAuditId;
     /**
-    * 商品id
-    * */
+     * 商品id
+     * */
     @ExcelProperty(value = "商品id")
     private Long productId;
 
@@ -159,8 +159,8 @@ public class ProductListVo implements Serializable {
     private BigDecimal purchasingPrice;
 
     /*
-    * 协议价
-    * */
+     * 协议价
+     * */
     @ExcelProperty(value = "协议价")
     private BigDecimal agreementPrice;
 
@@ -210,30 +210,30 @@ public class ProductListVo implements Serializable {
     private Integer type;
 
     /**
-    * 产品池类型 0普通产品池,1精选产品池,2协议产品池,3项目产品池,4营销产品池
-    * */
+     * 产品池类型 0普通产品池,1精选产品池,2协议产品池,3项目产品池,4营销产品池
+     * */
     @ExcelProperty(value = "产品池类型")
     private Integer poolType;
 
 
     /**
-    * 池id
-    * */
+     * 池id
+     * */
     @ExcelProperty(value = "池id")
     private Long poolId;
     /**
-    * 协议id
-    * */
+     * 协议id
+     * */
     @ExcelProperty(value = "协议id")
-    private Long agreementId;
+    private Long protocolId;
     /**
-    * 客户Id
-    * */
+     * 客户Id
+     * */
     @ExcelProperty(value = "客户Id")
     private Long customerId;
     /**
-    * 项目id
-    * */
+     * 项目id
+     * */
     @ExcelProperty(value = "项目id")
     private Long itemId;
 }

+ 64 - 60
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java

@@ -452,6 +452,10 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
         if (ObjectUtil.isNotEmpty(bo.getCategoryIds())){
             productBaseVoLambdaEsQueryWrapper.in(ProductBaseVo::getBottomCategoryId, bo.getCategoryIds().split(","));
         }
+        productBaseVoLambdaEsQueryWrapper.eq(bo.getQueryType()==1,ProductBaseVo::getCreateByType,1);
+        productBaseVoLambdaEsQueryWrapper.eq(bo.getQueryType()==1,ProductBaseVo::getCreateById,LoginHelper.getLoginUser().getSupplierId());
+        productBaseVoLambdaEsQueryWrapper.eq(bo.getQueryType()==2,ProductBaseVo::getCreateByType,2);
+        productBaseVoLambdaEsQueryWrapper.eq(bo.getQueryType()==2,ProductBaseVo::getCreateById,LoginHelper.getLoginUser().getPartnerId());
         return productBaseVoLambdaEsQueryWrapper;
     }
 
@@ -893,7 +897,7 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
         if (bo.getMarketPrice() == null && bo.getMemberPrice() == null &&
             bo.getMinSellingPrice() == null && bo.getPurchasingPrice() == null &&
             bo.getEstimatedPurchasePrice() == null && bo.getTotalInventory() == null &&
-             bo.getNowInventory() == null && bo.getVirtualInventory() == null) {
+            bo.getNowInventory() == null && bo.getVirtualInventory() == null) {
             return;
         }
 
@@ -1309,8 +1313,8 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     }
 
     /**
-    * 商品审核
-    * */
+     * 商品审核
+     * */
     @Override
     public void review(ProductBaseBo bo) {
         baseMapper.update(Wrappers.lambdaUpdate(ProductBase.class)
@@ -1510,10 +1514,10 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
         lqw.and(ObjectUtil.isNotEmpty(bo.getSearchKeyword())
             , (queryWrapper) -> queryWrapper.and(qw ->
                 qw.like("b.item_name", bo.getSearchKeyword())
-                .or().like("tc.category_name", bo.getSearchKeyword())
-                .or().like("mc.category_name", bo.getSearchKeyword())
-                .or().like("bc.category_name", bo.getSearchKeyword())
-                .or().like("br.brand_name", bo.getSearchKeyword())
+                    .or().like("tc.category_name", bo.getSearchKeyword())
+                    .or().like("mc.category_name", bo.getSearchKeyword())
+                    .or().like("bc.category_name", bo.getSearchKeyword())
+                    .or().like("br.brand_name", bo.getSearchKeyword())
             )
         );
         lqw.like(StringUtils.isNotBlank(bo.getProductNo()), "b.product_no", bo.getProductNo());
@@ -1655,9 +1659,9 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     public TableDataInfo<PcProductVo> getCategoryRecommendProductPage(Long categoryId, PageQuery pageQuery) {
         //获取推荐的商品id
         List<ProductCategoryRecommendedLinkVo> productCategoryRecommendedLinkVos = productCategoryRecommendedLinkMapper.selectVoList(
-                Wrappers.lambdaQuery(ProductCategoryRecommendedLink.class)
-                    .eq(ProductCategoryRecommendedLink::getCategoryId, categoryId)
-            );
+            Wrappers.lambdaQuery(ProductCategoryRecommendedLink.class)
+                .eq(ProductCategoryRecommendedLink::getCategoryId, categoryId)
+        );
         if (CollUtil.isNotEmpty(productCategoryRecommendedLinkVos)) {
             List<Long> productIds = productCategoryRecommendedLinkVos.stream().map(ProductCategoryRecommendedLinkVo::getProductId).toList();
             QueryWrapper<ProductBase> lqw = Wrappers.query(ProductBase.class);
@@ -1684,9 +1688,9 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     public TableDataInfo<PcProductVo> getProductShoppingCartPage(String id,Long userId, PageQuery pageQuery) {
         LambdaQueryWrapper<ProductShoppingCart> lqw = Wrappers.lambdaQuery(ProductShoppingCart.class);
         lqw.eq(ProductShoppingCart::getUserId, userId);
-            if (ObjectUtil.isNotEmpty(id)) {
-                lqw.in(ProductShoppingCart::getId, id.split(","));
-            }
+        if (ObjectUtil.isNotEmpty(id)) {
+            lqw.in(ProductShoppingCart::getId, id.split(","));
+        }
         Page<ProductShoppingCart> productShoppingCartPage = productShoppingCartMapper.selectPage(pageQuery.build(), lqw);
         if (CollUtil.isNotEmpty(productShoppingCartPage.getRecords())) {
             List<PcProductVo> productVos = new ArrayList<>();
@@ -1749,9 +1753,9 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     @Override
     public List<PcProductVo> getCarouselDisplayProductList(Long recommendId) {
         List<ProductRecommendLinkVo> productRecommendLinkVos = productRecommendLinkMapper.selectVoList(
-                Wrappers.lambdaQuery(ProductRecommendLink.class)
-                    .eq(ProductRecommendLink::getRecommendId, recommendId)
-            );
+            Wrappers.lambdaQuery(ProductRecommendLink.class)
+                .eq(ProductRecommendLink::getRecommendId, recommendId)
+        );
         if (CollUtil.isNotEmpty(productRecommendLinkVos)) {
             List<Long> productIds = productRecommendLinkVos.stream().map(ProductRecommendLinkVo::getProductId).toList();
             QueryWrapper<ProductBase> lqw = Wrappers.query(ProductBase.class);
@@ -1773,19 +1777,19 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     @Override
     public List<PcProductVo> getRecommendedCategoryProductList(Long categoryId) {
         List<ProductCategoryRecommendedLinkVo> productCategoryRecommendedLinkVos = productCategoryRecommendedLinkMapper.selectVoList(
-                Wrappers.lambdaQuery(ProductCategoryRecommendedLink.class)
-                    .eq(ObjectUtil.isNotEmpty(categoryId),ProductCategoryRecommendedLink::getCategoryId, categoryId)
-            );
-            if (CollUtil.isNotEmpty(productCategoryRecommendedLinkVos)) {
-                List<Long> productIds = productCategoryRecommendedLinkVos.stream().map(ProductCategoryRecommendedLinkVo::getProductId).toList();
-                QueryWrapper<ProductBase> lqw = Wrappers.query(ProductBase.class);
-                lqw.in("b.id", productIds);
+            Wrappers.lambdaQuery(ProductCategoryRecommendedLink.class)
+                .eq(ObjectUtil.isNotEmpty(categoryId),ProductCategoryRecommendedLink::getCategoryId, categoryId)
+        );
+        if (CollUtil.isNotEmpty(productCategoryRecommendedLinkVos)) {
+            List<Long> productIds = productCategoryRecommendedLinkVos.stream().map(ProductCategoryRecommendedLinkVo::getProductId).toList();
+            QueryWrapper<ProductBase> lqw = Wrappers.query(ProductBase.class);
+            lqw.in("b.id", productIds);
 //                lqw.eq("b.product_status", 1);
-                List<ProductBaseVo> productBaseVos = baseMapper.selectAllList(lqw);
-                if (CollUtil.isNotEmpty(productBaseVos)) {
-                    return BeanUtil.copyToList(productBaseVos, PcProductVo.class);
-                }
+            List<ProductBaseVo> productBaseVos = baseMapper.selectAllList(lqw);
+            if (CollUtil.isNotEmpty(productBaseVos)) {
+                return BeanUtil.copyToList(productBaseVos, PcProductVo.class);
             }
+        }
         return List.of();
     }
 
@@ -1797,9 +1801,9 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     @Override
     public List<PcProductVo> getIndustrialFloorProductList(Long floorId) {
         List<ProductIndustrialFloorLinkVo> productIndustrialFloorLinkVos = industrialFloorLinkMapper.selectVoList(
-                Wrappers.lambdaQuery(ProductIndustrialFloorLink.class)
-                    .eq(ProductIndustrialFloorLink::getFloorId, floorId)
-            );
+            Wrappers.lambdaQuery(ProductIndustrialFloorLink.class)
+                .eq(ProductIndustrialFloorLink::getFloorId, floorId)
+        );
         if (CollUtil.isNotEmpty(productIndustrialFloorLinkVos)) {
             List<Long> productIds = productIndustrialFloorLinkVos.stream().map(ProductIndustrialFloorLinkVo::getProductId).toList();
             QueryWrapper<ProductBase> lqw = Wrappers.query(ProductBase.class);
@@ -1846,42 +1850,42 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     @Override
     public List<PcProductVo> getGiftFloorLinkProductList(Long floorId) {
         List<ProductGiftFloorLinkVo> productGiftFloorLinkVos = productGiftFloorLinkMapper.selectVoList(
-                Wrappers.lambdaQuery(ProductGiftFloorLink.class)
-                    .eq(ProductGiftFloorLink::getFloorId, floorId)
-            );
-            if (CollUtil.isNotEmpty(productGiftFloorLinkVos)) {
-                List<Long> productIds = productGiftFloorLinkVos.stream().map(ProductGiftFloorLinkVo::getProductId).toList();
+            Wrappers.lambdaQuery(ProductGiftFloorLink.class)
+                .eq(ProductGiftFloorLink::getFloorId, floorId)
+        );
+        if (CollUtil.isNotEmpty(productGiftFloorLinkVos)) {
+            List<Long> productIds = productGiftFloorLinkVos.stream().map(ProductGiftFloorLinkVo::getProductId).toList();
 
-                // 使用ES查询
-                try {
-                    LambdaEsQueryWrapper<ProductBaseVo> esQueryWrapper = new LambdaEsQueryWrapper<ProductBaseVo>()
-                        .in(ProductBaseVo::getId, productIds)
+            // 使用ES查询
+            try {
+                LambdaEsQueryWrapper<ProductBaseVo> esQueryWrapper = new LambdaEsQueryWrapper<ProductBaseVo>()
+                    .in(ProductBaseVo::getId, productIds)
 //                        .eq(ProductBaseVo::getProductStatus, 1)
-                        ;
-
-                    // 检查ES索引是否存在
-                    if (!esMapper.existsIndex("productbasevo")) {
-                        log.warn("ES索引 [productbasevo] 不存在,降级到数据库查询");
-                        List<ProductBaseVo> productBaseVos = baseMapper.selectAllList(Wrappers.query(ProductBase.class)
-                            .in("b.id", productIds));
-                        if (CollUtil.isNotEmpty(productBaseVos)) {
-                            return BeanUtil.copyToList(productBaseVos, PcProductVo.class);
-                        }
-                    } else {
-                        List<ProductBaseVo> productBaseVos = esMapper.selectList(esQueryWrapper);
-                        if (CollUtil.isNotEmpty(productBaseVos)) {
-                            return BeanUtil.copyToList(productBaseVos, PcProductVo.class);
-                        }
-                    }
-                } catch (Exception e) {
-                    log.error("ES查询福利楼层商品失败,降级到数据库查询: {}", e.getMessage(), e);
+                    ;
+
+                // 检查ES索引是否存在
+                if (!esMapper.existsIndex("productbasevo")) {
+                    log.warn("ES索引 [productbasevo] 不存在,降级到数据库查询");
                     List<ProductBaseVo> productBaseVos = baseMapper.selectAllList(Wrappers.query(ProductBase.class)
                         .in("b.id", productIds));
                     if (CollUtil.isNotEmpty(productBaseVos)) {
                         return BeanUtil.copyToList(productBaseVos, PcProductVo.class);
                     }
+                } else {
+                    List<ProductBaseVo> productBaseVos = esMapper.selectList(esQueryWrapper);
+                    if (CollUtil.isNotEmpty(productBaseVos)) {
+                        return BeanUtil.copyToList(productBaseVos, PcProductVo.class);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("ES查询福利楼层商品失败,降级到数据库查询: {}", e.getMessage(), e);
+                List<ProductBaseVo> productBaseVos = baseMapper.selectAllList(Wrappers.query(ProductBase.class)
+                    .in("b.id", productIds));
+                if (CollUtil.isNotEmpty(productBaseVos)) {
+                    return BeanUtil.copyToList(productBaseVos, PcProductVo.class);
                 }
             }
+        }
         return List.of();
     }
 
@@ -1893,9 +1897,9 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     @Override
     public TableDataInfo<PcProductVo> getProcurementProgramGroupProductList(Long groupId,PageQuery pageQuery) {
         List<ProcurementProgramProductVo> productRecommendLinkVos = procurementProgramProductMapper.selectVoList(
-                Wrappers.lambdaQuery(ProcurementProgramProduct.class)
-                    .eq(ProcurementProgramProduct::getGroupId, groupId)
-            );
+            Wrappers.lambdaQuery(ProcurementProgramProduct.class)
+                .eq(ProcurementProgramProduct::getGroupId, groupId)
+        );
         if (CollUtil.isNotEmpty(productRecommendLinkVos)) {
             LambdaEsQueryWrapper<ProductBaseVo> wrapper = new LambdaEsQueryWrapper<ProductBaseVo>();
 //            wrapper.eq(ProductBaseVo::getProductStatus, 1);

+ 27 - 21
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductPoolAuditServiceImpl.java

@@ -517,7 +517,7 @@ public class ProductPoolAuditServiceImpl  extends ServiceImpl<ProductPoolAuditMa
                     protocolProductsMapper.updateById(protocolProducts);
                 }
 
-        });
+            });
 
     }
 
@@ -546,14 +546,14 @@ public class ProductPoolAuditServiceImpl  extends ServiceImpl<ProductPoolAuditMa
 
         // 调用远程服务添加到外部项目商品池
         if (remoteExternalProductService != null) {
-                try {
-                    remoteExternalProductService.syncExternalProduct( externalProductDtos);
-                    // 暂时直接记录日志,实际应该调用远程服务
-                    log.info("添加产品到项目池:, ItemId={}", audit.getItemId());
-                } catch (Exception e) {
-                    log.error("添加产品到外部项目池失败", e);
-                    throw new ServiceException("添加产品到项目池失败");
-                }
+            try {
+                remoteExternalProductService.syncExternalProduct( externalProductDtos);
+                // 暂时直接记录日志,实际应该调用远程服务
+                log.info("添加产品到项目池:, ItemId={}", audit.getItemId());
+            } catch (Exception e) {
+                log.error("添加产品到外部项目池失败", e);
+                throw new ServiceException("添加产品到项目池失败");
+            }
 
         }
     }
@@ -620,14 +620,14 @@ public class ProductPoolAuditServiceImpl  extends ServiceImpl<ProductPoolAuditMa
     public TableDataInfo<ProductListVo> selectPoolAuditProductPage(ProductBaseBo bo, PageQuery pageQuery) {
         QueryWrapper<ProductBase> wrapper = Wrappers.query();
         wrapper.eq("a.product_review_status", 1)
-               .eq(ObjectUtil.isNotEmpty(bo.getPoolType()),"d.type",bo.getPoolType())
-               .eq(ObjectUtil.isNotEmpty(bo.getItemId()),"d.item_id", bo.getItemId())
-               .eq(ObjectUtil.isNotEmpty(bo.getProductNo()),"b.product_no", bo.getProductNo())
-               .like(ObjectUtil.isNotEmpty(bo.getItemName()),"b.item_name", bo.getItemName())
-               .eq(ObjectUtil.isNotEmpty(bo.getBrandId()),"b.brand_id", bo.getBrandId())
-               .eq(ObjectUtil.isNotEmpty(bo.getProductStatus()),"b.product_status", bo.getProductStatus())
-               .eq(ObjectUtil.isNotEmpty(bo.getBottomCategoryId()),"b.bottom_category_id", bo.getBottomCategoryId())
-               .orderByDesc("a.create_time")
+            .eq(ObjectUtil.isNotEmpty(bo.getPoolType()),"d.type",bo.getPoolType())
+            .eq(ObjectUtil.isNotEmpty(bo.getItemId()),"d.item_id", bo.getItemId())
+            .eq(ObjectUtil.isNotEmpty(bo.getProductNo()),"b.product_no", bo.getProductNo())
+            .like(ObjectUtil.isNotEmpty(bo.getItemName()),"b.item_name", bo.getItemName())
+            .eq(ObjectUtil.isNotEmpty(bo.getBrandId()),"b.brand_id", bo.getBrandId())
+            .eq(ObjectUtil.isNotEmpty(bo.getProductStatus()),"b.product_status", bo.getProductStatus())
+            .eq(ObjectUtil.isNotEmpty(bo.getBottomCategoryId()),"b.bottom_category_id", bo.getBottomCategoryId())
+            .orderByDesc("a.create_time")
         ;
         Page<ProductListVo> page = productBaseMapper.selectPoolAuditProductPage(pageQuery.build(), wrapper);
         page.getRecords().forEach(product -> {
@@ -637,10 +637,16 @@ public class ProductPoolAuditServiceImpl  extends ServiceImpl<ProductPoolAuditMa
             }else if(Objects.equals(product.getPoolType(),1)){
                 name = "标准产品池";
             }else if(Objects.equals(product.getPoolType(),2)){
-                //获取客户信息
-                CustomerInfoDTO customerInfoDTO = remoteCustomerService.selectCustomerInfoById(product.getCustomerId());
-                if(customerInfoDTO != null){
-                    name = customerInfoDTO.getCustomerName();
+                //先获取协议信息
+                ProtocolInfo  protocolInfo= protocolInfoMapper.selectById(product.getProtocolId());
+                if (ObjectUtil.isNotEmpty(protocolInfo)){
+                    //获取客户信息
+                    CustomerInfoDTO customerInfoDTO = remoteCustomerService.selectCustomerInfoById(protocolInfo.getCustomerId());
+                    if(customerInfoDTO != null){
+                        name = customerInfoDTO.getCustomerName();
+                    }else{
+                        name = "未知协议产品池";
+                    }
                 }else{
                     name = "未知协议产品池";
                 }

+ 101 - 101
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductBaseMapper.xml

@@ -1,38 +1,38 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.dromara.product.mapper.ProductBaseMapper">
 
     <!-- 站点产品列表查询(联表查询) -->
     <select id="selectSiteProductPage" resultType="org.dromara.product.domain.vo.SiteProductVo">
         SELECT
-            b.id,
-            b.product_no AS productNo,
-            b.item_name AS productName,
-            b.product_image AS productImage,
-            b.is_self AS isSelf,
-            b.product_review_status AS productReviewStatus,
-            b.product_status AS productStatus,
-            p.market_price AS marketPrice,
-            p.member_price AS memberPrice,
-            p.min_selling_price AS minSellingPrice,
-            p.purchasing_price AS purchasingPrice,
-            p.max_purchase_price AS maxPurchasePrice,
-            p.total_inventory AS totalInventory,
-            p.now_inventory AS nowInventory,
-            p.virtual_inventory AS virtualInventory,
-            p.min_order_quantity AS minOrderQuantity,
-            b.brand_id AS brandId,
-            br.brand_name AS brandName,
-            b.top_category_id AS topCategoryId,
-            tc.category_name AS topCategoryName,
-            b.medium_category_id AS mediumCategoryId,
-            mc.category_name AS mediumCategoryName,
-            b.bottom_category_id AS bottomCategoryId,
-            bc.category_name AS bottomCategoryName,
-            GROUP_CONCAT(DISTINCT gcl.category_name) AS giftCategoryName,
-            b.remark
+        b.id,
+        b.product_no AS productNo,
+        b.item_name AS productName,
+        b.product_image AS productImage,
+        b.is_self AS isSelf,
+        b.product_review_status AS productReviewStatus,
+        b.product_status AS productStatus,
+        p.market_price AS marketPrice,
+        p.member_price AS memberPrice,
+        p.min_selling_price AS minSellingPrice,
+        p.purchasing_price AS purchasingPrice,
+        p.max_purchase_price AS maxPurchasePrice,
+        p.total_inventory AS totalInventory,
+        p.now_inventory AS nowInventory,
+        p.virtual_inventory AS virtualInventory,
+        p.min_order_quantity AS minOrderQuantity,
+        b.brand_id AS brandId,
+        br.brand_name AS brandName,
+        b.top_category_id AS topCategoryId,
+        tc.category_name AS topCategoryName,
+        b.medium_category_id AS mediumCategoryId,
+        mc.category_name AS mediumCategoryName,
+        b.bottom_category_id AS bottomCategoryId,
+        bc.category_name AS bottomCategoryName,
+        GROUP_CONCAT(DISTINCT gcl.category_name) AS giftCategoryName,
+        b.remark
         FROM product_base b
         LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
         LEFT JOIN product_brand br ON b.brand_id = br.id AND br.del_flag = '0'
@@ -68,32 +68,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
         </where>
         GROUP BY b.id, b.product_no, b.item_name, b.product_image, b.is_self,
-                 b.product_review_status, b.product_status, b.brand_id,
-                 b.top_category_id, b.medium_category_id, b.bottom_category_id, b.remark,
-                 p.market_price, p.member_price, p.min_selling_price, p.purchasing_price,
-                 p.max_purchase_price, p.total_inventory, p.now_inventory, p.virtual_inventory,
-                 p.min_order_quantity, br.brand_name, tc.category_name, mc.category_name, bc.category_name
+        b.product_review_status, b.product_status, b.brand_id,
+        b.top_category_id, b.medium_category_id, b.bottom_category_id, b.remark,
+        p.market_price, p.member_price, p.min_selling_price, p.purchasing_price,
+        p.max_purchase_price, p.total_inventory, p.now_inventory, p.virtual_inventory,
+        p.min_order_quantity, br.brand_name, tc.category_name, mc.category_name, bc.category_name
         ORDER BY b.create_time DESC
     </select>
 
     <!-- 推荐商品列表查询(联表查询分类名称) -->
     <select id="selectRecommendProductPage" resultType="org.dromara.product.domain.vo.RecommendProductVo">
         SELECT
-            b.id,
-            b.product_no AS productNo,
-            b.item_name AS itemName,
-            b.product_image AS productImage,
-            tc.category_name AS topCategoryName,
-            mc.category_name AS mediumCategoryName,
-            bc.category_name AS bottomCategoryName,
-            b.product_status AS productStatus,
-            b.home_recommended AS homeRecommended,
-            b.category_recommendation AS categoryRecommendation,
-            b.cart_recommendation AS cartRecommendation,
-            b.is_popular AS isPopular,
-            b.recommended_product_order AS recommendedProductOrder,
-            p.market_price AS marketPrice,
-            p.min_selling_price AS minSellingPrice
+        b.id,
+        b.product_no AS productNo,
+        b.item_name AS itemName,
+        b.product_image AS productImage,
+        tc.category_name AS topCategoryName,
+        mc.category_name AS mediumCategoryName,
+        bc.category_name AS bottomCategoryName,
+        b.product_status AS productStatus,
+        b.home_recommended AS homeRecommended,
+        b.category_recommendation AS categoryRecommendation,
+        b.cart_recommendation AS cartRecommendation,
+        b.is_popular AS isPopular,
+        b.recommended_product_order AS recommendedProductOrder,
+        p.market_price AS marketPrice,
+        p.min_selling_price AS minSellingPrice
         FROM product_base b
         LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
         LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
@@ -138,27 +138,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <!-- 商品运营列表查询(联表查询) -->
     <select id="selectProductOperationPage" resultType="org.dromara.product.domain.vo.ProductOperationVo">
         SELECT
-            b.id,
-            b.product_no AS productNo,
-            b.item_name AS itemName,
-            b.product_image AS productImage,
-            b.brand_id AS brandId,
-            br.brand_name AS brandName,
-            b.top_category_id AS topCategoryId,
-            tc.category_name AS topCategoryName,
-            b.medium_category_id AS mediumCategoryId,
-            mc.category_name AS mediumCategoryName,
-            b.bottom_category_id AS bottomCategoryId,
-            bc.category_name AS bottomCategoryName,
-            u.unit_name AS unitName,
-            p.min_order_quantity AS minOrderQuantity,
-            p.market_price AS marketPrice,
-            p.member_price AS memberPrice,
-            p.min_selling_price AS minSellingPrice,
-            p.total_inventory AS totalInventory,
-            p.now_inventory AS nowInventory,
-            p.virtual_inventory AS virtualInventory,
-            b.product_status AS productStatus
+        b.id,
+        b.product_no AS productNo,
+        b.item_name AS itemName,
+        b.product_image AS productImage,
+        b.brand_id AS brandId,
+        br.brand_name AS brandName,
+        b.top_category_id AS topCategoryId,
+        tc.category_name AS topCategoryName,
+        b.medium_category_id AS mediumCategoryId,
+        mc.category_name AS mediumCategoryName,
+        b.bottom_category_id AS bottomCategoryId,
+        bc.category_name AS bottomCategoryName,
+        u.unit_name AS unitName,
+        p.min_order_quantity AS minOrderQuantity,
+        p.market_price AS marketPrice,
+        p.member_price AS memberPrice,
+        p.min_selling_price AS minSellingPrice,
+        p.total_inventory AS totalInventory,
+        p.now_inventory AS nowInventory,
+        p.virtual_inventory AS virtualInventory,
+        b.product_status AS productStatus
         FROM product_base b
         LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
         LEFT JOIN product_brand br ON b.brand_id = br.id AND br.del_flag = '0'
@@ -196,25 +196,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <!-- 商品简化列表查询(用于选择弹窗) -->
     <select id="selectSimplePage" resultType="org.dromara.product.domain.vo.ProductBaseSimpleVo">
         SELECT
-            b.id,
-            b.product_no AS productNo,
-            b.item_name AS itemName,
-            b.product_image AS productImage,
-            p.market_price AS marketPrice,
-            p.member_price AS memberPrice,
-            e.specifications_code AS specification,
-            b.top_category_id AS topCategoryId,
-            tc.category_name AS topCategoryName,
-            b.medium_category_id AS mediumCategoryId,
-            mc.category_name AS mediumCategoryName,
-            b.bottom_category_id AS bottomCategoryId,
-            bc.category_name AS bottomCategoryName,
-            b.home_recommended AS homeRecommended,
-            b.category_recommendation AS categoryRecommendation,
-            b.cart_recommendation AS cartRecommendation,
-            b.recommended_product_order AS recommendedProductOrder,
-            b.is_popular AS isPopular,
-            b.product_status AS productStatus
+        b.id,
+        b.product_no AS productNo,
+        b.item_name AS itemName,
+        b.product_image AS productImage,
+        p.market_price AS marketPrice,
+        p.member_price AS memberPrice,
+        e.specifications_code AS specification,
+        b.top_category_id AS topCategoryId,
+        tc.category_name AS topCategoryName,
+        b.medium_category_id AS mediumCategoryId,
+        mc.category_name AS mediumCategoryName,
+        b.bottom_category_id AS bottomCategoryId,
+        bc.category_name AS bottomCategoryName,
+        b.home_recommended AS homeRecommended,
+        b.category_recommendation AS categoryRecommendation,
+        b.cart_recommendation AS cartRecommendation,
+        b.recommended_product_order AS recommendedProductOrder,
+        b.is_popular AS isPopular,
+        b.product_status AS productStatus
         FROM product_base b
         LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
         LEFT JOIN product_extend e ON b.id = e.product_id AND e.del_flag = '0'
@@ -311,15 +311,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             -- 属性分类表字段
             c.attributes_list AS attributesList
         FROM product_base b
-        LEFT JOIN product_extend e ON b.id = e.product_id AND e.del_flag = '0'
-        LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
-        LEFT JOIN product_classification c ON b.id = c.product_id AND c.del_flag = '0'
-        LEFT JOIN product_brand br ON b.brand_id = br.id AND br.del_flag = '0'
-        LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
-        LEFT JOIN product_category mc ON b.medium_category_id = mc.id AND mc.del_flag = '0'
-        LEFT JOIN product_category bc ON b.bottom_category_id = bc.id AND bc.del_flag = '0'
-        LEFT JOIN product_unit u ON b.unit_id = u.id AND u.del_flag = '0'
-        ${ew.customSqlSegment}
+                 LEFT JOIN product_extend e ON b.id = e.product_id AND e.del_flag = '0'
+                 LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
+                 LEFT JOIN product_classification c ON b.id = c.product_id AND c.del_flag = '0'
+                 LEFT JOIN product_brand br ON b.brand_id = br.id AND br.del_flag = '0'
+                 LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
+                 LEFT JOIN product_category mc ON b.medium_category_id = mc.id AND mc.del_flag = '0'
+                 LEFT JOIN product_category bc ON b.bottom_category_id = bc.id AND bc.del_flag = '0'
+                 LEFT JOIN product_unit u ON b.unit_id = u.id AND u.del_flag = '0'
+            ${ew.customSqlSegment}
     </select>
 
     <!-- 查询商品状态统计信息(优化版) -->
@@ -335,7 +335,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
     <select id="selectBrandIdsByCategory" resultType="java.lang.Long">
         SELECT
-            product.brand_id
+        product.brand_id
         FROM product_base product
         INNER JOIN product_category category ON product.top_category_id = category.id
         <where>
@@ -348,7 +348,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
 
     </select>
-	<select id="selectPoolAuditProductPage" resultType="org.dromara.product.domain.vo.ProductListVo">
+    <select id="selectPoolAuditProductPage" resultType="org.dromara.product.domain.vo.ProductListVo">
         SELECT
             a.id,
             a.pool_audit_id AS poolAuditId,
@@ -374,9 +374,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             c.now_inventory AS nowInventory,
             c.virtual_inventory AS virtualInventory,
             c.min_order_quantity AS minOrderQuantity,
-            c.purchasing_price AS purchasingPrice,
             d.pool_id AS poolId,
             d.item_id AS itemId,
+            d.protocol_id AS protocol_id,
             d.type AS poolType,
             d.protocol_id AS protocolId,
             e.brand_name AS brandName,
@@ -394,7 +394,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 LEFT JOIN product_category g ON b.top_category_id = g.id
                 LEFT JOIN product_category h ON b.medium_category_id = h.id
                 LEFT JOIN product_category i ON b.bottom_category_id = i.id
-        ${ew.customSqlSegment}
+            ${ew.customSqlSegment}
     </select>