فهرست منبع

Merge remote-tracking branch 'origin/xiaolu' into zl

# Conflicts:
#	ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductBaseService.java
#	ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java
林小张 3 ماه پیش
والد
کامیت
fd25d40ff3
91فایلهای تغییر یافته به همراه6088 افزوده شده و 12 حذف شده
  1. 1 0
      ruoyi-api/pom.xml
  2. 29 0
      ruoyi-api/ruoyi-api-auth/pom.xml
  3. 12 0
      ruoyi-api/ruoyi-api-auth/src/main/java/org/dromara/auth/api/RemoteAuthService.java
  4. 57 0
      ruoyi-api/ruoyi-api-auth/src/main/java/org/dromara/auth/api/domain/RemoteLoginVo.java
  5. 6 0
      ruoyi-api/ruoyi-api-bom/pom.xml
  6. 38 0
      ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/RemoteProductService.java
  7. 47 0
      ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/domain/ProductChangeLogVo.java
  8. 366 0
      ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/domain/ProductVo.java
  9. 4 0
      ruoyi-auth/pom.xml
  10. 4 0
      ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java
  11. 21 0
      ruoyi-auth/src/main/java/org/dromara/auth/dubbo/RemoteAuthServiceImpl.java
  12. 7 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java
  13. 5 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java
  14. 43 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java
  15. 5 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java
  16. 5 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java
  17. 5 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java
  18. 1 0
      ruoyi-modules/pom.xml
  19. 27 0
      ruoyi-modules/ruoyi-external/Dockerfile
  20. 126 0
      ruoyi-modules/ruoyi-external/pom.xml
  21. 17 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/RuoyiExternalApplication.java
  22. 106 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalItemController.java
  23. 106 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalProductBrandController.java
  24. 106 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalProductCategoryController.java
  25. 106 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalPushPoolLogController.java
  26. 246 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/ZhongZhiPullController.java
  27. 389 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/ZhongZhiPushController.java
  28. 12 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/Attribute.java
  29. 12 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/AttributeValue.java
  30. 62 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/Result.java
  31. 12 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/DelMessagePoolBo.java
  32. 40 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/GetMessagePoolBo.java
  33. 28 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/GetProductDetailBo.java
  34. 38 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/OrderNoticeBo.java
  35. 69 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/OrderPushBo.java
  36. 17 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/ZZTokenBo.java
  37. 35 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ExternalProductVo.java
  38. 39 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/OrderPushVo.java
  39. 27 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductImageVo.java
  40. 22 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductMessageInfo.java
  41. 29 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductPriceVo.java
  42. 28 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductPromiseVo.java
  43. 19 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductShelfStateVo.java
  44. 72 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalItem.java
  45. 99 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalProductBrand.java
  46. 97 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalProductCategory.java
  47. 72 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalPushPoolLog.java
  48. 71 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalItemBo.java
  49. 102 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalProductBrandBo.java
  50. 100 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalProductCategoryBo.java
  51. 69 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalPushPoolLogBo.java
  52. 81 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalItemVo.java
  53. 122 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalProductBrandVo.java
  54. 113 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalProductCategoryVo.java
  55. 79 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalPushPoolLogVo.java
  56. 15 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalItemMapper.java
  57. 15 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalProductBrandMapper.java
  58. 15 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalProductCategoryMapper.java
  59. 15 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalPushPoolLogMapper.java
  60. 70 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalItemService.java
  61. 70 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalProductBrandService.java
  62. 70 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalProductCategoryService.java
  63. 70 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalPushPoolLogService.java
  64. 139 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalItemServiceImpl.java
  65. 144 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalProductBrandServiceImpl.java
  66. 144 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalProductCategoryServiceImpl.java
  67. 137 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalPushPoolLogServiceImpl.java
  68. 885 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/util/DataMigration.java
  69. 36 0
      ruoyi-modules/ruoyi-external/src/main/resources/application.yml
  70. 2 0
      ruoyi-modules/ruoyi-external/src/main/resources/banner.txt
  71. 28 0
      ruoyi-modules/ruoyi-external/src/main/resources/logback-plus.xml
  72. 7 0
      ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalItemMapper.xml
  73. 7 0
      ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalProductBrandMapper.xml
  74. 7 0
      ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalProductCategoryMapper.xml
  75. 7 0
      ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalPushPoolLogMapper.xml
  76. 5 0
      ruoyi-modules/ruoyi-product/pom.xml
  77. 106 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductPhotosController.java
  78. 1 1
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductBase.java
  79. 77 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductPhotos.java
  80. 19 1
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductBaseBo.java
  81. 77 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductPhotosBo.java
  82. 25 7
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBaseVo.java
  83. 90 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductPhotosVo.java
  84. 88 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/dubbo/RemoteProductServiceImpl.java
  85. 15 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductPhotosMapper.java
  86. 7 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductBaseService.java
  87. 70 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductPhotosService.java
  88. 26 2
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java
  89. 140 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductPhotosServiceImpl.java
  90. 1 1
      ruoyi-modules/ruoyi-product/src/main/resources/application.yml
  91. 7 0
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductPhotosMapper.xml

+ 1 - 0
ruoyi-api/pom.xml

@@ -14,6 +14,7 @@
         <module>ruoyi-api-resource</module>
         <module>ruoyi-api-workflow</module>
         <module>ruoyi-api-product</module>
+	    <module>ruoyi-api-auth</module>
     </modules>
 
     <artifactId>ruoyi-api</artifactId>

+ 29 - 0
ruoyi-api/ruoyi-api-auth/pom.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-api</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-api-auth</artifactId>
+
+    <description>
+        ruoyi-api-auth
+    </description>
+
+    <dependencies>
+
+        <!-- RuoYi Common Core-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+</project>

+ 12 - 0
ruoyi-api/ruoyi-api-auth/src/main/java/org/dromara/auth/api/RemoteAuthService.java

@@ -0,0 +1,12 @@
+package org.dromara.auth.api;
+
+import org.dromara.auth.api.domain.RemoteLoginVo;
+
+/**
+ * @author
+ * @date 2025/12/24 下午2:31
+ */
+public interface RemoteAuthService {
+
+    RemoteLoginVo getAccessToken(String username, String password);
+}

+ 57 - 0
ruoyi-api/ruoyi-api-auth/src/main/java/org/dromara/auth/api/domain/RemoteLoginVo.java

@@ -0,0 +1,57 @@
+package org.dromara.auth.api.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 登录验证信息
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class RemoteLoginVo implements Serializable {
+
+    private final static long serialVersionUID = 1L;
+
+    /**
+     * 授权令牌
+     */
+    private String accessToken;
+
+    /**
+     * 刷新令牌
+     */
+    private String refreshToken;
+
+    /**
+     * 授权令牌 access_token 的有效期
+     */
+    private Long expireIn;
+
+    /**
+     * 刷新令牌 refresh_token 的有效期
+     */
+
+    private Long refreshExpireIn;
+
+    /**
+     * 应用id
+     */
+    private String clientId;
+
+    /**
+     * 令牌权限
+     */
+    private String scope;
+
+    /**
+     * 用户 openid
+     */
+    private String openid;
+
+    private Integer code;
+
+    private String msg;
+
+}

+ 6 - 0
ruoyi-api/ruoyi-api-bom/pom.xml

@@ -20,6 +20,12 @@
 
     <dependencyManagement>
         <dependencies>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-api-auth</artifactId>
+                <version>${revision}</version>
+            </dependency>
             <!-- 系统接口 -->
             <dependency>
                 <groupId>org.dromara</groupId>

+ 38 - 0
ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/RemoteProductService.java

@@ -0,0 +1,38 @@
+package org.dromara.product.api;
+
+import org.dromara.product.api.domain.ProductChangeLogVo;
+import org.dromara.product.api.domain.ProductVo;
+
+import java.util.List;
+
+/**
+ *
+ * 商品服务接口
+ * @author
+ * @date 2025/12/29 下午5:08
+ */
+public interface RemoteProductService {
+
+    /**
+     * 获取商品详情
+    * */
+    ProductVo  getProductDetail(Long productId);
+
+    /**
+     * 商品上下架
+     * @param productId 商品id
+    * */
+    ProductVo updateProductShelfState(Long productId);
+
+    /**
+    * 获取多个商品详情
+    * */
+    List<ProductVo> getProductDetails(String productIds);
+
+    /**
+    * 获取商品变更记录
+    * */
+    List<ProductChangeLogVo> getProductChangeLogs(Long itemId,String type);
+
+    void delMessagePool(Long messageId);
+}

+ 47 - 0
ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/domain/ProductChangeLogVo.java

@@ -0,0 +1,47 @@
+package org.dromara.product.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+
+/**
+ * 商品变更消息记录对象
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+public class ProductChangeLogVo {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    private Long id;
+
+    /**
+     * 项目id
+     */
+    private Long itemId;
+
+    /**
+     * 商品id
+     */
+    private Long productId;
+
+    private String type;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 366 - 0
ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/domain/ProductVo.java

@@ -0,0 +1,366 @@
+package org.dromara.product.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+
+/**
+ * 产品基础信息视图对象 product_base
+ *
+ * @author LionLi
+ * @date 2025-12-11
+ */
+@Data
+public class ProductVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键,自增ID
+     */
+    private Long id;
+
+    /**
+     * 产品编号
+     */
+    private String productNo;
+
+    /**
+     * 产品名称
+     */
+    private String itemName;
+
+    /**
+     * 品牌id
+     */
+    private Long brandId;
+
+    /**
+     * 品牌名称
+     */
+    private String brandName;
+
+    /**
+     * 顶级分类id
+     */
+    private Long topCategoryId;
+
+    /**
+     * 中级分类id
+     */
+    private Long mediumCategoryId;
+
+    /**
+     * 底层分类id
+     */
+    private Long bottomCategoryId;
+
+    /**
+    * 分类名称
+    * */
+    private String categoryName;
+
+    /**
+     * 单位id
+     */
+    private String unitId;
+
+    /**
+    * 单位名称
+    * */
+    private String unitName;
+
+    /**
+     * 产品图片URL
+     */
+    private String productImage;
+
+    /**
+     * 产品图片URLUrl
+     */
+    private String productImageUrl;
+    /**
+     * 是否自营(1=是,0=否)
+     */
+    private String isSelf;
+
+    /**
+     * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+     */
+
+    private String productReviewStatus;
+
+    /**
+     * 首页推荐:1=推荐,0=不推荐
+     */
+    private String homeRecommended;
+
+    /**
+     * 分类推荐:1=推荐,0=不推荐
+     */
+    private String categoryRecommendation;
+
+    /**
+     * 购物车推荐:1=推荐,0=不推荐
+     */
+    private String cartRecommendation;
+
+    /**
+     * 推荐产品顺序
+     */
+    private Long recommendedProductOrder;
+
+    /**
+     * 是否热门:1=是,0=否
+     */
+    private String isPopular;
+
+    /**
+     * 是否新品:1=是,0=否
+     */
+    private String isNew;
+
+    /**
+     * 商品状态:1=上架,0=下架等
+     */
+    private String productStatus;
+
+    /**
+     * 数据来源
+     */
+    private String dataSource;
+
+    /**
+    * 市场价
+    * */
+    private BigDecimal marketPrice;
+
+    /**
+     * 会员价格
+     */
+    private BigDecimal memberPrice;
+
+    /**
+     * 最低销售价格
+     */
+    private BigDecimal minSellingPrice;
+
+    /**
+     * 采购价格
+     */
+    private BigDecimal purchasingPrice;
+
+
+
+    /**
+    * 暂估毛利率
+    * */
+    private BigDecimal tempGrossMargin;
+
+    /**
+     * 总库存
+     * */
+    private Long totalInventory;
+
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * A10产品名称
+     */
+    private String a10ProductName;
+
+    /**
+     * 规格型号
+     */
+    private String specification;
+
+    /**
+     * UPC(S)条码
+     */
+    private String upcBarcode;
+
+    /**
+     * 发票名称
+     */
+    private String invoiceName;
+
+    /**
+     * 发票规格
+     */
+    private String invoiceSpec;
+
+    /**
+     * 包装规格
+     */
+    private String packagingSpec;
+
+    /**
+     * 参考链接
+     */
+    private String referenceLink;
+
+    /**
+     * 商品重量
+     */
+    private String weight;
+
+    /**
+     * 重量单位
+     */
+    private String weightUnit;
+
+    /**
+     * 商品体积
+     */
+    private String volume;
+
+    /**
+     * 体积单位
+     */
+    private String volumeUnit;
+
+    /**
+     * 主库简介
+     */
+    private String mainLibraryIntro;
+
+    /**
+     * 售后服务
+     */
+    private String afterSalesService;
+
+    /**
+     * 服务保证 - 无忧退货
+     */
+    private String worryFreeReturn;
+
+    /**
+     * 服务保证 - 快速退款
+     */
+    private String quickRefund;
+
+    /**
+     * 服务保证 - 免费包邮
+     */
+    private String freeShipping;
+
+    /**
+     * 服务保证 - 正品保障
+     */
+    private String genuineGuarantee;
+
+    /**
+     * 安装服务 - 免费安装
+     */
+    private String freeInstallation;
+
+    /**
+     * 中档价
+     */
+    private BigDecimal midRangePrice;
+
+    /**
+     * 平档价
+     */
+
+    private BigDecimal standardPrice;
+
+    /**
+     * 套证价
+     */
+    private BigDecimal certificatePrice;
+
+    /**
+     * 采购价
+     */
+    private BigDecimal purchasePrice;
+
+    /**
+     * 暂估采购价
+     */
+    private BigDecimal estimatedPurchasePrice;
+
+    /**
+     * 产品性质
+     */
+    private String productNature;
+
+    /**
+     * 采购人员
+     */
+    private String purchasingPersonnel;
+
+    /**
+     * 商品详情 - 电脑端
+     */
+    private String pcDetail;
+
+    /**
+     * 商品详情 - 移动端
+     */
+    private String mobileDetail;
+
+    /**
+     * 税率
+     */
+    private BigDecimal taxRate;
+
+    /**
+     * 币种
+     */
+    private String currency;
+
+    /**
+     * 最低起订量
+     */
+    private Long minOrderQuantity;
+
+    /**
+     * 是否可定制
+     */
+    private Boolean customizable;
+
+    /**
+     * 定制说明
+     */
+    private String customDescription;
+
+    /**
+     * 定制详情列表(JSON字符串)
+     */
+    private String customDetailsJson;
+
+    /**
+     * 服务保障(逗号分隔的ID列表)
+     */
+    private String serviceGuarantee;
+
+    /**
+     * 商品属性值(JSON字符串)
+     */
+    private String attributesList;
+
+    /**
+     * 销售量/销量人气
+     */
+    private Long salesVolume;
+
+    /**
+     * 定制方式(逗号分隔)
+     */
+    private String customizedStyle;
+
+    /**
+     * 定制工艺(逗号分隔)
+     */
+    private String customizedCraft;
+
+}

+ 4 - 0
ruoyi-auth/pom.xml

@@ -91,6 +91,10 @@
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-api-resource</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-auth</artifactId>
+        </dependency>
 
         <!-- 自定义负载均衡(多团队开发使用) -->
 <!--        <dependency>-->

+ 4 - 0
ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java

@@ -51,4 +51,8 @@ public class LoginVo {
      */
     private String openid;
 
+    private Integer code;
+
+    private String msg;
+
 }

+ 21 - 0
ruoyi-auth/src/main/java/org/dromara/auth/dubbo/RemoteAuthServiceImpl.java

@@ -0,0 +1,21 @@
+package org.dromara.auth.dubbo;
+
+import cn.hutool.core.bean.BeanUtil;
+import org.dromara.auth.api.RemoteAuthService;
+import org.dromara.auth.api.domain.RemoteLoginVo;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.service.IAuthStrategy;
+
+/**
+ * @author
+ * @date 2025/12/24 下午2:35
+ */
+public class RemoteAuthServiceImpl implements RemoteAuthService {
+
+
+    @Override
+    public RemoteLoginVo getAccessToken(String username, String password) {
+        LoginVo loginVo = IAuthStrategy.getAccessToken(username, password);
+        return BeanUtil.toBean(loginVo, RemoteLoginVo.class);
+    }
+}

+ 7 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java

@@ -32,6 +32,11 @@ public interface IAuthStrategy {
         return instance.login(body, client);
     }
 
+    static LoginVo getAccessToken(String username, String password) {
+        IAuthStrategy instance = SpringUtils.getBean("password"+BASE_NAME);
+        return instance.getToken(username,password);
+    }
+
     /**
      * 登录
      *
@@ -41,4 +46,6 @@ public interface IAuthStrategy {
      */
     LoginVo login(String body, RemoteClientVo client);
 
+    LoginVo getToken(String username, String password);
+
 }

+ 5 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java

@@ -71,6 +71,11 @@ public class EmailAuthStrategy implements IAuthStrategy {
         return loginVo;
     }
 
+    @Override
+    public LoginVo getToken(String username, String password) {
+        return null;
+    }
+
     /**
      * 校验邮箱验证码
      */

+ 43 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java

@@ -1,8 +1,10 @@
 package org.dromara.auth.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.crypto.digest.BCrypt;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
@@ -13,6 +15,7 @@ import org.dromara.auth.service.IAuthStrategy;
 import org.dromara.auth.service.SysLoginService;
 import org.dromara.common.core.constant.Constants;
 import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.constant.SystemConstants;
 import org.dromara.common.core.enums.LoginType;
 import org.dromara.common.core.exception.user.CaptchaException;
 import org.dromara.common.core.exception.user.CaptchaExpireException;
@@ -23,6 +26,7 @@ import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.api.RemoteClientService;
 import org.dromara.system.api.RemoteUserService;
 import org.dromara.system.api.domain.vo.RemoteClientVo;
 import org.dromara.system.api.model.LoginUser;
@@ -45,6 +49,9 @@ public class PasswordAuthStrategy implements IAuthStrategy {
     @DubboReference
     private RemoteUserService remoteUserService;
 
+    @DubboReference
+    private RemoteClientService remoteClientService;
+
     @Override
     public LoginVo login(String body, RemoteClientVo client) {
         PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
@@ -104,4 +111,40 @@ public class PasswordAuthStrategy implements IAuthStrategy {
         }
     }
 
+    @Override
+    public LoginVo getToken(String username, String password) {
+        String tenantId = "000000";
+        LoginVo loginVo = new LoginVo();
+        LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
+            LoginUser user = remoteUserService.getUserInfo(username, tenantId);
+            loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+            return user;
+        });
+        if(ObjectUtil.isEmpty(loginUser)){
+            loginVo.setCode(5004);
+            loginVo.setMsg("用户名或密码错误");
+            return loginVo;
+        }
+        RemoteClientVo client = remoteClientService.queryByClientId("e5cd7e4891bf95d1d19206ce24a7b32e");
+        if(ObjectUtil.isEmpty(client)){
+            return null;
+        }
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(86400L);
+        model.setActiveTimeout(86400L);
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
 }

+ 5 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java

@@ -71,6 +71,11 @@ public class SmsAuthStrategy implements IAuthStrategy {
         return loginVo;
     }
 
+    @Override
+    public LoginVo getToken(String username, String password) {
+        return null;
+    }
+
     /**
      * 校验短信验证码
      */

+ 5 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java

@@ -99,4 +99,9 @@ public class SocialAuthStrategy implements IAuthStrategy {
         return loginVo;
     }
 
+    @Override
+    public LoginVo getToken(String username, String password) {
+        return null;
+    }
+
 }

+ 5 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java

@@ -88,4 +88,9 @@ public class XcxAuthStrategy implements IAuthStrategy {
         return loginVo;
     }
 
+    @Override
+    public LoginVo getToken(String username, String password) {
+        return null;
+    }
+
 }

+ 1 - 0
ruoyi-modules/pom.xml

@@ -17,6 +17,7 @@
         <module>ruoyi-order</module>
 	    <module>ruoyi-product</module>
         <module>ruoyi-customer</module>
+	    <module>ruoyi-external</module>
     </modules>
 
     <artifactId>ruoyi-modules</artifactId>

+ 27 - 0
ruoyi-modules/ruoyi-external/Dockerfile

@@ -0,0 +1,27 @@
+# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
+FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
+#FROM findepi/graalvm:java17-native
+
+LABEL maintainer="Lion Li"
+
+RUN mkdir -p /ruoyi/external/logs \
+    /ruoyi/external/temp \
+    /ruoyi/skywalking/agent
+
+WORKDIR /ruoyi/external
+
+ENV SERVER_PORT=9618 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+
+EXPOSE ${SERVER_PORT}
+
+ADD ./target/ruoyi-external.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
+           #-Dskywalking.agent.service_name=ruoyi-external \
+           #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
+           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+           -jar app.jar
+

+ 126 - 0
ruoyi-modules/ruoyi-external/pom.xml

@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-modules</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-external</artifactId>
+
+    <description>
+        ruoyi-product产品模块
+    </description>
+
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-nacos</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sentinel</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common Log -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-log</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-service-impl</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-dubbo</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-seata</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-idempotent</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-tenant</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-translation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sensitive</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-encrypt</artifactId>
+        </dependency>
+
+        <!-- RuoYi Api System -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-system</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-auth</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-resource</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-product</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+            <version>9.4.0.jre11</version>
+        </dependency>
+    </dependencies>
+
+
+</project>

+ 17 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/RuoyiExternalApplication.java

@@ -0,0 +1,17 @@
+package org.dromara.external;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+
+@SpringBootApplication
+public class RuoyiExternalApplication {
+
+    public static void main(String[] args) {
+        SpringApplication application = new SpringApplication(RuoyiExternalApplication.class);
+        application.setApplicationStartup(new BufferingApplicationStartup(2048));
+        application.run(args);
+        System.out.println("(♥◠‿◠)ノ゙  外部Api模块启动成功   ლ(´ڡ`ლ)゙  ");
+    }
+
+}

+ 106 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalItemController.java

@@ -0,0 +1,106 @@
+package org.dromara.external.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.external.domain.vo.ExternalItemVo;
+import org.dromara.external.domain.bo.ExternalItemBo;
+import org.dromara.external.service.IExternalItemService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 第三方对接项目管理
+ * 前端访问路由地址为:/external/item
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/item")
+public class ExternalItemController extends BaseController {
+
+    private final IExternalItemService externalItemService;
+
+    /**
+     * 查询第三方对接项目管理列表
+     */
+    @SaCheckPermission("external:item:list")
+    @GetMapping("/list")
+    public TableDataInfo<ExternalItemVo> list(ExternalItemBo bo, PageQuery pageQuery) {
+        return externalItemService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出第三方对接项目管理列表
+     */
+    @SaCheckPermission("external:item:export")
+    @Log(title = "第三方对接项目管理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ExternalItemBo bo, HttpServletResponse response) {
+        List<ExternalItemVo> list = externalItemService.queryList(bo);
+        ExcelUtil.exportExcel(list, "第三方对接项目管理", ExternalItemVo.class, response);
+    }
+
+    /**
+     * 获取第三方对接项目管理详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("external:item:query")
+    @GetMapping("/{id}")
+    public R<ExternalItemVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(externalItemService.queryById(id));
+    }
+
+    /**
+     * 新增第三方对接项目管理
+     */
+    @SaCheckPermission("external:item:add")
+    @Log(title = "第三方对接项目管理", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ExternalItemBo bo) {
+        return toAjax(externalItemService.insertByBo(bo));
+    }
+
+    /**
+     * 修改第三方对接项目管理
+     */
+    @SaCheckPermission("external:item:edit")
+    @Log(title = "第三方对接项目管理", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ExternalItemBo bo) {
+        return toAjax(externalItemService.updateByBo(bo));
+    }
+
+    /**
+     * 删除第三方对接项目管理
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("external:item:remove")
+    @Log(title = "第三方对接项目管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(externalItemService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalProductBrandController.java

@@ -0,0 +1,106 @@
+package org.dromara.external.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.external.domain.vo.ExternalProductBrandVo;
+import org.dromara.external.domain.bo.ExternalProductBrandBo;
+import org.dromara.external.service.IExternalProductBrandService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 第三方产品品牌信息
+ * 前端访问路由地址为:/external/productBrand
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/productBrand")
+public class ExternalProductBrandController extends BaseController {
+
+    private final IExternalProductBrandService externalProductBrandService;
+
+    /**
+     * 查询第三方产品品牌信息列表
+     */
+    @SaCheckPermission("external:productBrand:list")
+    @GetMapping("/list")
+    public TableDataInfo<ExternalProductBrandVo> list(ExternalProductBrandBo bo, PageQuery pageQuery) {
+        return externalProductBrandService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出第三方产品品牌信息列表
+     */
+    @SaCheckPermission("external:productBrand:export")
+    @Log(title = "第三方产品品牌信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ExternalProductBrandBo bo, HttpServletResponse response) {
+        List<ExternalProductBrandVo> list = externalProductBrandService.queryList(bo);
+        ExcelUtil.exportExcel(list, "第三方产品品牌信息", ExternalProductBrandVo.class, response);
+    }
+
+    /**
+     * 获取第三方产品品牌信息详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("external:productBrand:query")
+    @GetMapping("/{id}")
+    public R<ExternalProductBrandVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(externalProductBrandService.queryById(id));
+    }
+
+    /**
+     * 新增第三方产品品牌信息
+     */
+    @SaCheckPermission("external:productBrand:add")
+    @Log(title = "第三方产品品牌信息", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ExternalProductBrandBo bo) {
+        return toAjax(externalProductBrandService.insertByBo(bo));
+    }
+
+    /**
+     * 修改第三方产品品牌信息
+     */
+    @SaCheckPermission("external:productBrand:edit")
+    @Log(title = "第三方产品品牌信息", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ExternalProductBrandBo bo) {
+        return toAjax(externalProductBrandService.updateByBo(bo));
+    }
+
+    /**
+     * 删除第三方产品品牌信息
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("external:productBrand:remove")
+    @Log(title = "第三方产品品牌信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(externalProductBrandService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalProductCategoryController.java

@@ -0,0 +1,106 @@
+package org.dromara.external.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.external.domain.vo.ExternalProductCategoryVo;
+import org.dromara.external.domain.bo.ExternalProductCategoryBo;
+import org.dromara.external.service.IExternalProductCategoryService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 第三方产品分类
+ * 前端访问路由地址为:/external/productCategory
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/productCategory")
+public class ExternalProductCategoryController extends BaseController {
+
+    private final IExternalProductCategoryService externalProductCategoryService;
+
+    /**
+     * 查询第三方产品分类列表
+     */
+    @SaCheckPermission("external:productCategory:list")
+    @GetMapping("/list")
+    public TableDataInfo<ExternalProductCategoryVo> list(ExternalProductCategoryBo bo, PageQuery pageQuery) {
+        return externalProductCategoryService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出第三方产品分类列表
+     */
+    @SaCheckPermission("external:productCategory:export")
+    @Log(title = "第三方产品分类", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ExternalProductCategoryBo bo, HttpServletResponse response) {
+        List<ExternalProductCategoryVo> list = externalProductCategoryService.queryList(bo);
+        ExcelUtil.exportExcel(list, "第三方产品分类", ExternalProductCategoryVo.class, response);
+    }
+
+    /**
+     * 获取第三方产品分类详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("external:productCategory:query")
+    @GetMapping("/{id}")
+    public R<ExternalProductCategoryVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(externalProductCategoryService.queryById(id));
+    }
+
+    /**
+     * 新增第三方产品分类
+     */
+    @SaCheckPermission("external:productCategory:add")
+    @Log(title = "第三方产品分类", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ExternalProductCategoryBo bo) {
+        return toAjax(externalProductCategoryService.insertByBo(bo));
+    }
+
+    /**
+     * 修改第三方产品分类
+     */
+    @SaCheckPermission("external:productCategory:edit")
+    @Log(title = "第三方产品分类", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ExternalProductCategoryBo bo) {
+        return toAjax(externalProductCategoryService.updateByBo(bo));
+    }
+
+    /**
+     * 删除第三方产品分类
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("external:productCategory:remove")
+    @Log(title = "第三方产品分类", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(externalProductCategoryService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/ExternalPushPoolLogController.java

@@ -0,0 +1,106 @@
+package org.dromara.external.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.external.domain.vo.ExternalPushPoolLogVo;
+import org.dromara.external.domain.bo.ExternalPushPoolLogBo;
+import org.dromara.external.service.IExternalPushPoolLogService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 商品池推送记录
+ * 前端访问路由地址为:/external/pushPoolLog
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pushPoolLog")
+public class ExternalPushPoolLogController extends BaseController {
+
+    private final IExternalPushPoolLogService externalPushPoolLogService;
+
+    /**
+     * 查询商品池推送记录列表
+     */
+    @SaCheckPermission("external:pushPoolLog:list")
+    @GetMapping("/list")
+    public TableDataInfo<ExternalPushPoolLogVo> list(ExternalPushPoolLogBo bo, PageQuery pageQuery) {
+        return externalPushPoolLogService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出商品池推送记录列表
+     */
+    @SaCheckPermission("external:pushPoolLog:export")
+    @Log(title = "商品池推送记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ExternalPushPoolLogBo bo, HttpServletResponse response) {
+        List<ExternalPushPoolLogVo> list = externalPushPoolLogService.queryList(bo);
+        ExcelUtil.exportExcel(list, "商品池推送记录", ExternalPushPoolLogVo.class, response);
+    }
+
+    /**
+     * 获取商品池推送记录详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("external:pushPoolLog:query")
+    @GetMapping("/{id}")
+    public R<ExternalPushPoolLogVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(externalPushPoolLogService.queryById(id));
+    }
+
+    /**
+     * 新增商品池推送记录
+     */
+    @SaCheckPermission("external:pushPoolLog:add")
+    @Log(title = "商品池推送记录", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ExternalPushPoolLogBo bo) {
+        return toAjax(externalPushPoolLogService.insertByBo(bo));
+    }
+
+    /**
+     * 修改商品池推送记录
+     */
+    @SaCheckPermission("external:pushPoolLog:edit")
+    @Log(title = "商品池推送记录", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ExternalPushPoolLogBo bo) {
+        return toAjax(externalPushPoolLogService.updateByBo(bo));
+    }
+
+    /**
+     * 删除商品池推送记录
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("external:pushPoolLog:remove")
+    @Log(title = "商品池推送记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(externalPushPoolLogService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 246 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/ZhongZhiPullController.java

@@ -0,0 +1,246 @@
+package org.dromara.external.controller.zhongzhi;
+
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.external.controller.ExternalItemController;
+import org.dromara.external.controller.zhongzhi.domain.Result;
+import org.dromara.external.domain.ExternalProductBrand;
+import org.dromara.external.domain.ExternalProductCategory;
+import org.dromara.external.service.IExternalProductBrandService;
+import org.dromara.external.service.IExternalProductCategoryService;
+import org.dromara.external.service.IExternalPushPoolLogService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Duration;
+import java.util.Map;
+
+import static org.dromara.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
+
+/**
+ * 中直项目拉取
+ * 前端访问路由地址为:/external/zhongzhi/api
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/zhongzhi/pull")
+public class ZhongZhiPullController {
+    private final String url = "http://gcytapi.gcycloud.cn/";
+
+    private final String key = GLOBAL_REDIS_KEY+"external:zhongzhi:token:";
+
+    private final String username = "yoe365";
+
+    private final String password = "Yoe4001110027@";
+
+    private final String code = "ZF00030";
+
+    private final ExternalItemController externalItemController;
+
+    private final IExternalProductBrandService externalProductBrandService;
+
+    private final IExternalProductCategoryService externalProductCategoryService;
+
+    private final IExternalPushPoolLogService externalPushPoolLogService;
+
+
+    public static void main(String[] args) {
+//        HttpResponse response = HttpUtil.createPost("http://127.0.0.1:8080/external/zhongzhi/pull/gyssc/token")
+//            .execute();
+        HttpResponse response2 = HttpUtil.createPost("http://127.0.0.1:8080/external/zhongzhi/pull/gyssc/platform/info")
+            .execute();
+        System.out.println(response2.body());
+    }
+
+    /**
+    * 获取电子卖场接口交互凭证
+    * */
+    @PostMapping("/gyssc/token")
+    public String getAccessToken() {
+        String accessToken = RedisUtils.getCacheObject(key);
+        if (accessToken != null) {
+            return accessToken;
+        }
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/token");
+        String timestamp = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss") ;
+        String signStr = username + password + timestamp + password;
+        String sign = SecureUtil.md5(signStr).toLowerCase();
+        post.form("username", username)
+                .form("password", password)
+                .form("timestamp", timestamp)
+                .form("sign", sign);
+        String body = post.execute().body();
+        Result<Map<String,String>> result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            throw new RuntimeException(result.getDesc());
+        }
+        accessToken = result.getResult().get("access_token");
+        String expiresAt = result.getResult().get("expires_at");
+        long expiresIn = DateUtil.between(DateUtil.date(), DateUtil.parse(expiresAt), DateUnit.SECOND);
+        RedisUtils.setCacheObject(key, accessToken, Duration.ofSeconds(expiresIn));
+        return accessToken;
+    }
+
+    /**
+    * 获取平台信息接口
+    * */
+    @PostMapping("/gyssc/platform/info")
+    public Result getPlatformInfo() {
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/platform/info");
+        String accessToken = this.getAccessToken();
+        post.form("token", accessToken);
+        String body = post.execute().body();
+        Result result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        return Result.ok(result.getResult());
+    }
+
+    /**
+    * 获取平台品目信息接口
+    * */
+    @PostMapping("/gyssc/platform/catalogs")
+    public Result getPlatformCatalogs(String access_token) {
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/platform/catalogs");
+        post.header("Authorization", "Bearer " + access_token);
+        String body = post.execute().body();
+        Result<Map<String,String>> result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        result.getResult().forEach((k,v)->{
+            boolean exists = externalProductCategoryService.exists(Wrappers.lambdaQuery(ExternalProductCategory.class)
+                .eq(ExternalProductCategory::getCategoryNo, k));
+
+            ExternalProductCategory externalProductCategory = new ExternalProductCategory();
+            externalProductCategory.setPlatformCode(code);
+            externalProductCategory.setCategoryName(v);
+            externalProductCategory.setCategoryNo(k);
+            if(!exists){
+                externalProductCategoryService.save(externalProductCategory);
+            }else {
+                externalProductCategoryService.update(externalProductCategory,Wrappers.lambdaUpdate(ExternalProductCategory.class)
+                    .eq(ExternalProductCategory::getCategoryNo, k));
+            }
+
+        });
+        return Result.ok(result.getResult());
+    }
+    /**
+     * 获取商品承诺/产品认证信息/七天无理由退货接口
+     * */
+    @PostMapping("/gyssc/platform/getRegionPromise")
+    public Result getRegionPromise(String access_token) {
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/platform/getRegionPromise");
+        post.header("Authorization", "Bearer " + access_token);
+        String body = post.execute().body();
+        Result result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        return Result.ok(result.getResult());
+    }
+
+    /**
+    * 获取标准品目标准参数项信息接口
+    * */
+    @PostMapping("/gyssc/platform/catalogsParam")
+    public Result getStandardCatalogs(String access_token) {
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/platform/catalogsParam");
+        post.header("Authorization", "Bearer " + access_token);
+        String body = post.execute().body();
+        Result result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        return Result.ok(result.getResult());
+    }
+
+    /**
+    * 获取品牌信息接口
+    * */
+    @PostMapping("/brands")
+    public Result getBrands(String access_token) {
+        HttpRequest post = HttpUtil.createPost(url + "brands");
+        post.header("Authorization", "Bearer " + access_token);
+        String body = post.execute().body();
+        Result<Map<String,String>> result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        result.getResult().forEach((k,v)->{
+            boolean exists = externalProductBrandService.exists(Wrappers.lambdaQuery(ExternalProductBrand.class)
+                .eq(ExternalProductBrand::getBrandNo, k));
+                ExternalProductBrand externalProductBrand = new ExternalProductBrand();
+                externalProductBrand.setPlatformCode(code);
+                externalProductBrand.setBrandName(v);
+                externalProductBrand.setBrandNo(k);
+                if(!exists){
+                    externalProductBrandService.save(externalProductBrand);
+                }else {
+                    externalProductBrandService.update(externalProductBrand,Wrappers.lambdaUpdate(ExternalProductBrand.class)
+                        .eq(ExternalProductBrand::getBrandNo, k));
+                }
+        });
+        return Result.ok(result.getResult());
+    }
+    /**
+    * 获取大数据链接接口
+    * */
+    @PostMapping("/gyssc/platform/getBigdataUrl")
+    public Result getBigdataUrl(String access_token) {
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/platform/getBigdataUrl");
+        post.header("Authorization", "Bearer " + access_token);
+        String body = post.execute().body();
+        Result result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        return Result.ok(result.getResult());
+    }
+    /**
+    * 日志查询接口
+    * */
+    @PostMapping("/gyssc/platform/getPushLog")
+    public Result getPushLog(String access_token) {
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/platform/getPushLog");
+        post.header("Authorization", "Bearer " + access_token);
+        String body = post.execute().body();
+        Result result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        return Result.ok(result.getResult());
+    }
+
+    /**
+    * 价格来源选项接口
+    * */
+    @PostMapping("/gyssc/platform/goodsPriceSource")
+    public Result getGoodsPriceSource(String access_token) {
+        HttpRequest post = HttpUtil.createPost(url + "gyssc/platform/goodsPriceSource");
+        post.header("Authorization", "Bearer " + access_token);
+        String body = post.execute().body();
+        Result result = JSONUtil.toBean(body, Result.class);
+        if (!result.getSuccess()) {
+            return Result.fail(result.getCode(), result.getDesc());
+        }
+        return Result.ok(result.getResult());
+    }
+
+}

+ 389 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/ZhongZhiPushController.java

@@ -0,0 +1,389 @@
+package org.dromara.external.controller.zhongzhi;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.SecureUtil;
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.api.RemoteAuthService;
+import org.dromara.auth.api.domain.RemoteLoginVo;
+import org.dromara.external.controller.zhongzhi.domain.Attribute;
+import org.dromara.external.controller.zhongzhi.domain.Result;
+import org.dromara.external.controller.zhongzhi.domain.bo.*;
+import org.dromara.external.controller.zhongzhi.domain.vo.*;
+import org.dromara.product.api.RemoteProductService;
+import org.dromara.product.api.domain.ProductChangeLogVo;
+import org.dromara.product.api.domain.ProductVo;
+import org.dromara.system.api.RemoteUserService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 商品池推送记录
+ * 前端访问路由地址为:/external/zhongzhi/api
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/zhongzhi/api")
+public class ZhongZhiPushController {
+
+    @DubboReference
+    private final RemoteAuthService remoteAuthService;
+
+    @DubboReference
+    private final RemoteProductService remoteProductService;
+
+    /**
+     * 校验token
+     * */
+    private Result checkToken(String token,String platformCode){
+        if(ObjectUtil.isEmpty(token)){
+            return Result.fail(5006,"token不能为空");
+        }
+        if(StpUtil.getTokenTimeout(token) == -2l){
+            return Result.fail(5005,"token_expired");
+        }
+        if(ObjectUtil.isEmpty(platformCode)){
+            return Result.fail(5006,"平台标识不能为空");
+        }
+        return null;
+    }
+
+
+    /**
+     * 获取 Access Token
+     * @param username 用户名
+     * @param password 密码(明文,生产环境建议加密后再传)
+     * @return 包含 access_token 和 expires_at 的 Result,失败则 success=false
+     */
+    @PostMapping("/auth2/access_token")
+    public Result getAccessToken(String timestamp, String username, String password, String sign) {
+        // 2. 生成 sign = MD5(username + password + timestamp + password).toLowerCase()
+        String signStr = username + password + timestamp + password;
+        String sign1 = SecureUtil.md5(signStr).toLowerCase();
+
+        if(Objects.equals(sign1,sign)){
+            RemoteLoginVo loginVo= remoteAuthService.getAccessToken(username, password);
+            if(ObjectUtil.isEmpty(loginVo)){
+                return Result.fail(5002,"授权失败");
+            }
+            if(ObjectUtil.isNotEmpty(loginVo.getMsg())){
+                return Result.fail(loginVo.getCode(),loginVo.getMsg());
+            }
+            DateTime date = DateUtil.offsetSecond(DateUtil.date(), (int) StpUtil.getTokenTimeout(loginVo.getAccessToken()));
+            String format = DateUtil.format(date, "yyyy-MM-dd HH:mm:ss");
+            return  Result.tokenOk(loginVo.getAccessToken(), format);
+        } else  {
+            return Result.fail(5004,"签名错误,请检查后重试");
+        }
+    }
+
+    /**
+    * 获取电商商品变动消息
+    * */
+    @PostMapping("/product/getMessagePool")
+    public Result<List<ProductMessageInfo>> getMessagePool(GetMessagePoolBo bo) {
+        Result result = checkToken(bo.getToken(),bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getType())){
+            return Result.fail(5006,"消息类型不能为空");
+        }
+        List<ProductChangeLogVo> productChangeLogs = remoteProductService.getProductChangeLogs(null, String.valueOf(bo.getType()));
+        List<ProductMessageInfo> productMessageInfos = productChangeLogs.stream().map(productChangeLogVo -> {
+            ProductMessageInfo productMessageInfo = new ProductMessageInfo();
+            ProductVo productDetail = remoteProductService.getProductDetail(productChangeLogVo.getProductId());
+            productMessageInfo.setId(productChangeLogVo.getId());
+            productMessageInfo.setTime(DateUtil.now());
+            productMessageInfo.setType(Integer.parseInt(productChangeLogVo.getType()));
+            productMessageInfo.setResult(new ProductMessageInfo.Result());
+            productMessageInfo.getResult().setSkuId(String.valueOf(productChangeLogVo.getProductId()));
+            productMessageInfo.getResult().setState(Integer.parseInt(productChangeLogVo.getStatus()));
+            productMessageInfo.getResult().setPrice(productDetail.getMarketPrice());
+            productMessageInfo.getResult().setMarket_price(productDetail.getMarketPrice());
+            return productMessageInfo;
+        }).toList();
+
+        return Result.ok(productMessageInfos);
+    }
+    /**
+    * 获取商品详情
+    * */
+    @PostMapping("/product/detail_standard")
+    public Result<ExternalProductVo> detailStandard(GetProductDetailBo bo) {
+        Result result = checkToken(bo.getToken(), bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getSku())){
+            return Result.fail(5006,"商品编号不能为空");
+        }
+
+        ProductVo productDetail = remoteProductService.getProductDetail(Long.valueOf(bo.getSku()));
+        ExternalProductVo externalProductVo = ExternalProductVo.builder()
+            .sku(String.valueOf(productDetail.getId()))
+            .url(productDetail.getProductImage())
+            .model(productDetail.getItemName())
+            .weight(productDetail.getWeight())
+            .image_path(productDetail.getProductImageUrl())
+            .state(productDetail.getProductStatus())
+            .brand_name(productDetail.getBrandName())
+            .name(productDetail.getItemName())
+            .product_area(null)
+            .upc(productDetail.getUpcBarcode())
+            .unit(productDetail.getUnitName())
+            .category(productDetail.getCategoryName())
+            .service(productDetail.getAfterSalesService())
+            .code_69(null)
+            .introduction(productDetail.getMainLibraryIntro())
+            .build();
+        return Result.ok(externalProductVo);
+    }
+
+    /**
+    * 获取商品参数信息
+    * */
+    @PostMapping("/product/ product_attributes_standard")
+    public Result<Attribute> productAttributesStandard(GetProductDetailBo bo) {
+        Result result = checkToken(bo.getToken(), bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getSku())){
+            return Result.fail(5006,"商品编号不能为空");
+        }
+        Attribute attribute = new Attribute();
+        return Result.ok(attribute);
+    }
+
+    /**
+    * 获取商品承诺函/产品认证信息/七天无理由退货(由电商提供)
+    * */
+    @PostMapping("/product/product_promise")
+    public Result<List<ProductPromiseVo>> productPromise(GetProductDetailBo bo) {
+        Result result = checkToken(bo.getToken(), bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getSku())){
+            return Result.fail(5006,"商品编号不能为空");
+        }
+        List<ProductPromiseVo> productPromises = new ArrayList<>();
+        return Result.ok(productPromises);
+    }
+
+    /**
+    * 商品上下架状态接口
+    * */
+    @PostMapping("/product/shelf_states")
+    public Result<List<ProductShelfStateVo>> shelfStates(GetProductDetailBo bo) {
+        Result result = checkToken(bo.getToken(), bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getSku())){
+            return Result.fail(5006,"商品编号不能为空");
+        }
+        String[] skuIds = bo.getSku().split(",");
+        List<ProductShelfStateVo> shelfState = new ArrayList<>();
+        for (String skuId : skuIds) {
+            ProductVo productVo = remoteProductService.updateProductShelfState(Long.valueOf(skuId));
+            ProductShelfStateVo productShelfStateVo = new ProductShelfStateVo();
+            productShelfStateVo.setSku(skuId);
+            productShelfStateVo.setState(productVo.getProductStatus());
+            shelfState.add(productShelfStateVo);
+        }
+        return Result.ok(shelfState);
+    }
+
+    /**
+    * 商品图片
+    * */
+    @PostMapping("/product/images")
+    public Result<List<ProductImageVo>> images(GetProductDetailBo bo) {
+        Result result = checkToken(bo.getToken(), bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getSku())){
+            return Result.fail(5006,"商品编号不能为空");
+        }
+        List<ProductVo> productDetails = remoteProductService.getProductDetails(bo.getSku());
+        List<ProductImageVo> imageVos = productDetails.stream().map(productVo -> {
+            ProductImageVo productImageVo = new ProductImageVo();
+            productImageVo.setSku(String.valueOf(productVo.getId()));
+            if (ObjectUtil.isNotEmpty(productVo.getProductImage())) {
+                List<ProductImageVo.Image> images = new ArrayList<>();
+                String[] split = productVo.getProductImage().split(",");
+                for (int i = 0; i < split.length; i++) {
+                    ProductImageVo.Image image = new ProductImageVo.Image();
+                    image.setPath(split[i]);
+                    image.setOrder(i);
+                    images.add(image);
+                }
+                productImageVo.setImages(images);
+            }
+            return productImageVo;
+        }).toList();
+        return Result.ok(imageVos);
+    }
+    /**
+    * 商品价格接口
+    * */
+    @PostMapping("/product/prices")
+    public Result<List<ProductPriceVo>> prices(GetProductDetailBo bo) {
+        Result result = checkToken(bo.getToken(), bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getSku())){
+            return Result.fail(5006,"商品编号不能为空");
+        }
+        List<ProductVo> productDetails = remoteProductService.getProductDetails(bo.getSku());
+        List<ProductPriceVo> prices = productDetails.stream().map(productVo -> {
+            ProductPriceVo productPriceVo = new ProductPriceVo();
+            productPriceVo.setSku(String.valueOf(productVo.getId()));
+            productPriceVo.setPrice(productVo.getMarketPrice());
+            productPriceVo.setMall_price(productVo.getMemberPrice());
+            productPriceVo.setStock(String.valueOf(productVo.getTotalInventory()));
+            return productPriceVo;
+        }).toList();
+        return Result.ok(prices);
+    }
+
+    /**
+    * 删除消息接口
+    * */
+    @PostMapping("/product/delMessagePool")
+    public Result delMessagePool(DelMessagePoolBo bo) {
+        Result result = checkToken(bo.getToken(),bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getMessageId())){
+            return Result.fail(5006,"消息编号不能为空");
+        }
+        remoteProductService.delMessagePool(Long.valueOf(bo.getMessageId()));
+        return Result.ok("消息"+bo.getMessageId()+"删除成功", null);
+    }
+
+    /**
+    * 推送订单
+    * */
+    @PostMapping("/order/pushOrder")
+    public Result<OrderPushVo> pushOrder(OrderPushBo bo) {
+        Result result = checkToken(bo.getToken(),bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        //校验必填字段
+        if(ObjectUtil.isEmpty(bo.getYggc_order())){
+            return Result.fail(5006,"订单编号不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getSku())){
+            return Result.fail(5006,"订单商品不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getName())){
+            return Result.fail(5006,"收货人名称不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getProvince())){
+            return Result.fail(5006,"一级地址不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getCity())){
+            return Result.fail(5006,"二级地址不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getCounty())){
+            return Result.fail(5006,"三级地址不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getAddress())){
+            return Result.fail(5006,"详细地址不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getZip())){
+            return Result.fail(5006,"邮编不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getPhone()) && ObjectUtil.isEmpty(bo.getMobile())){
+            return Result.fail(5006,"手机号或座机号不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getInvoice_title())){
+            return Result.fail(5006,"发票抬头不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getInvoice_org_code())){
+            return Result.fail(5006,"纳税人识别号不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getInvoice_type())){
+            return Result.fail(5006,"发票类型不能为空");
+        }else if(bo.getInvoice_type().equals("2")){
+            if(ObjectUtil.isEmpty(bo.getInvoice_name())){
+                return Result.fail(5006,"增值税发票抬头不能为空");
+            }
+            if(ObjectUtil.isEmpty(bo.getInvoice_phone())){
+                return Result.fail(5006,"发票电话不能为空");
+            }
+            if(ObjectUtil.isEmpty(bo.getInvoice_bank())){
+                return Result.fail(5006,"发票开户银行不能为空");
+            }
+            if(ObjectUtil.isEmpty(bo.getInvoice_bank_code())){
+                return Result.fail(5006,"发票开户行账号不能为空");
+            }
+            if(ObjectUtil.isEmpty(bo.getInvoice_address())){
+                return Result.fail(5006,"注册地址不能为空");
+            }
+        }
+
+        if(ObjectUtil.isEmpty(bo.getPayment())){
+            return Result.fail(5006,"付款方式不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getOrder_price())){
+            return Result.fail(5006,"订单金额不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getFreight())){
+            return Result.fail(5006,"运费不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getMode())){
+            return Result.fail(5006,"订单模式不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getOrderCode())){
+            return Result.fail(5006,"卖场订单编号不能为空");
+        }
+        OrderPushVo orderPushVo = new OrderPushVo();
+        return Result.ok(orderPushVo);
+    }
+
+    /**
+    * 订单状态通知接口
+    * */
+    @PostMapping("/order/notice")
+    public Result notice(OrderNoticeBo bo) {
+        Result result = checkToken(bo.getToken(),bo.getPlatformCode());
+        if(ObjectUtil.isNotEmpty(result)){
+            return result;
+        }
+        if(ObjectUtil.isEmpty(bo.getOrderId())){
+            return Result.fail(5006,"订单编号不能为空");
+        }
+        if(ObjectUtil.isEmpty(bo.getOrderState())){
+            return Result.fail(5006,"订单状态不能为空");
+        }
+        return Result.ok("成功收到通知",null);
+    }
+
+
+
+
+
+
+
+}

+ 12 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/Attribute.java

@@ -0,0 +1,12 @@
+package org.dromara.external.controller.zhongzhi.domain;
+
+import lombok.Data;
+
+@Data
+public class Attribute {
+    private Object attributeID; // null in your data
+    private String attributeName;
+    private String valueID;
+    private String value;
+    private int selectFlag;
+}

+ 12 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/AttributeValue.java

@@ -0,0 +1,12 @@
+package org.dromara.external.controller.zhongzhi.domain;
+
+import lombok.Data;
+
+@Data
+public class AttributeValue {
+    private String attributeID;
+    private String attributeName;
+    private String valueID;
+    private String value;
+
+}

+ 62 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/Result.java

@@ -0,0 +1,62 @@
+package org.dromara.external.controller.zhongzhi.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.dubbo.common.logger.FluentLogger;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@NoArgsConstructor
+public class Result<T> implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 成功
+     */
+    private static final boolean SUCCESS = true;
+
+    /**
+     * 失败
+     */
+    private static final boolean FAIL = false;
+
+    private Boolean success;
+    private int code;
+    private String access_token;
+    private String expires_at;
+    private String desc;
+    private T result;
+
+    public static <T> Result<T> tokenOk(String accessToken,String expires_at) {
+        return restResult(SUCCESS, 200,accessToken,expires_at, "",null);
+    }
+
+
+    public static <T> Result<T> ok(T result) {
+        return restResult(SUCCESS, 200, "", "", "",result);
+    }
+
+    public static <T> Result<T> ok(String msg, T result) {
+        return restResult(SUCCESS, 200, "", "", msg,result);
+    }
+
+    public static <T> Result<T> fail(int code,String msg) {
+        return restResult(FAIL, code, "","",msg,null);
+    }
+
+
+
+    private static <T>Result<T> restResult(boolean success,int code, String accessToken, String expires_at,String desc,T result) {
+        Result Result = new Result();
+        Result.setResult( result);
+        Result.setSuccess(success);
+        Result.setCode(code);
+        Result.setAccess_token(accessToken);
+        Result.setExpires_at(expires_at);
+        Result.setDesc(desc);
+        return Result;
+    }
+}

+ 12 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/DelMessagePoolBo.java

@@ -0,0 +1,12 @@
+package org.dromara.external.controller.zhongzhi.domain.bo;
+
+import lombok.Data;
+
+/**
+ * @author
+ * @date 2025/12/24 下午5:33
+ */
+@Data
+public class DelMessagePoolBo extends ZZTokenBo{
+    private String messageId;
+}

+ 40 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/GetMessagePoolBo.java

@@ -0,0 +1,40 @@
+package org.dromara.external.controller.zhongzhi.domain.bo;
+
+import lombok.Data;
+
+/**
+ *  获取电商商品变动消息
+ * @author
+ * @date 2025/12/24 下午3:25
+ */
+@Data
+public class GetMessagePoolBo extends ZZTokenBo {
+
+    /**
+    * 消息类型
+     * type=2商品
+     * 价格变更,后
+     * 续会调用价格
+     * 接口。
+     * type=4代表
+     * 商品上下架变
+     * 更消息,后续
+     * 会调用上下架
+     * 状态接口。
+     * type=6代表
+     * 添加、删除商
+     * 品池内的商
+     * 品,触发保存
+     * 商品流程,依
+     * 次调用商品详
+     * 情等接口获取
+     * 商品信息。
+     * type=16商品
+     * 介绍及规格参
+     * 数变更消息,
+     * 调用商品详情
+     * 等接口更新商
+     * 品信息。
+    * */
+    private  Integer type;
+}

+ 28 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/GetProductDetailBo.java

@@ -0,0 +1,28 @@
+package org.dromara.external.controller.zhongzhi.domain.bo;
+
+import lombok.Data;
+
+/**
+ * 获取商品详情
+ *
+ * @author
+ * @date 2025/12/24 下午3:43
+ */
+@Data
+public class GetProductDetailBo extends ZZTokenBo {
+
+    /**
+    * 商品编号
+    * */
+    private String sku;
+    /**
+    * 获取除基
+     * 本商品信
+     * 息外的其
+     * 他商品信
+     * 息
+    * */
+    private String product_extend_attributes;
+
+
+}

+ 38 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/OrderNoticeBo.java

@@ -0,0 +1,38 @@
+package org.dromara.external.controller.zhongzhi.domain.bo;
+
+import lombok.Data;
+
+/**
+ * @author
+ * @date 2025/12/24 下午6:08
+ */
+@Data
+public class OrderNoticeBo extends ZZTokenBo{
+    /**
+    * 订单编号
+    * */
+    private String orderId;
+    /**
+    * 订单状态
+     * (100, "待付预付款"),
+     *
+     * (120, "待供应商发货"),
+     *
+     * (131, "供应商已发货(待供应商收货)"),
+     *
+     * (132, "已收货(待供应商发起验收)"),
+     *
+     * (149, "退款完成"),
+     *
+     * (150, "待采购人验收"),
+     *
+     * (151, "已验收"),
+     *
+     * (153, "待付款,(线下支付)待采购人确认付款(待上传支付凭证)"),
+     *
+     * (154, "已付款,(线下支付)采购人已上传正式支付凭证,待供应商确认收款"),
+     *
+     * (159, "已结算")
+    * */
+    private String orderState;
+}

+ 69 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/OrderPushBo.java

@@ -0,0 +1,69 @@
+package org.dromara.external.controller.zhongzhi.domain.bo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class OrderPushBo extends ZZTokenBo{
+
+    // 3. 订单单号 Y
+    private String yggc_order;
+
+    // 4. 订单商品信息 Y
+//    private List<SkuItem> sku;
+    private String sku;
+
+    // 5-14. 收货与地址信息
+    private String name;           // 收货人名称 Y
+    private Integer province;      // 一级地址(省级编码) Y
+    private Integer city;          // 二级地址(市级编码) Y
+    private Integer county;        // 三级地址(区级编码) Y
+    private String address;        // 详细地址 Y
+    private String zip;            // 邮编 Y
+    private String phone;          // 座机号(与 mobile 二选一) Y
+    private String mobile;         // 手机号(与 phone 二选一) Y
+    private String email;          // 邮箱(可选)
+    private String remark;         // 备注(<100字)
+
+    // 15. 采购单位名称
+    private String dep_name;
+
+    // 16-25. 发票信息
+    private String invoice_title;           // 发票抬头 Y
+    private String invoice_type;            // 发票类型:1普通, 2增值税, 3电子 Y
+    private String invoice_org_code;         // 纳税人识别号 Y
+    private String invoice_name;            // 增值税收票人(invoiceType=2 时必填)
+    private String invoice_phone;           // 注册电话(invoiceType=2 时必填)
+    private String invoice_bank;            // 开户银行(invoiceType=2 时必填)
+    private String invoice_bank_code;        // 开户行账号(invoiceType=2 时必填)
+    private String invoice_address;         // 注册地址(invoiceType=2 时必填)
+    private String invoice_mobile;          // 收票联系电话(可选)
+    private String invoice_receipt_address;  // 收票地址(可选)
+
+    // 26-30. 其他订单信息
+    private String payment;        // 付款方式1:货到付款,2:集中支付, 3:在线支付, 4:支票 5:账期支付 6:先款后货 Y
+    private String order_price;     // 订单金额(含运费)Y
+    private String freight;        // 运费 Y
+    private String mode;           // 订单模式:1-协议价(默认), 2-团购, 3-特惠, 4-阶梯价(可选) Y
+    private String orderCode;      // 卖场订单编号   Y
+
+
+    // ===== 内部静态类:商品项(含延保) =====
+    @Data
+    public static class SkuItem {
+        private String sku;                    // 商品编号
+        private Integer num;                   // 商品数量
+        private Integer price;                 // 商品价格
+        private Integer mode;                  // 价格模式(同订单 mode 含义)
+        private List<YanbaoItem> yanbao;       // 延保服务(可选)
+
+        @Data
+        public static class YanbaoItem {
+            private String sku;                // 延保编号
+            private Integer price;             // 延保价格
+            private Integer num;               // 延保数量
+        }
+    }
+
+}

+ 17 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/bo/ZZTokenBo.java

@@ -0,0 +1,17 @@
+package org.dromara.external.controller.zhongzhi.domain.bo;
+
+import lombok.Data;
+
+/**
+ * @author
+ * @date 2025/8/12 下午5:55
+ */
+@Data
+public class ZZTokenBo {
+    private String token;
+
+    /**
+     * 平台标识
+     * */
+    private  String platformCode;
+}

+ 35 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ExternalProductVo.java

@@ -0,0 +1,35 @@
+package org.dromara.external.controller.zhongzhi.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+import org.dromara.external.controller.zhongzhi.domain.Attribute;
+import org.dromara.external.controller.zhongzhi.domain.AttributeValue;
+
+import java.util.List;
+
+@Data
+@Builder
+public class ExternalProductVo {
+    private String sku;
+    private String url;
+    private String model;
+    private String weight;
+    private String image_path;
+    private String state;
+    private String brand_name;
+    private String name;
+    private String product_area;
+    private String upc;
+    private String unit;
+    private String category;
+    private String service;
+    private String code_69;      // null
+    private List<AttributeValue> attributes;
+    private String introduction;
+    private List<Attribute> param;
+    private String ware;
+    private int saleActives;
+
+
+
+}

+ 39 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/OrderPushVo.java

@@ -0,0 +1,39 @@
+package org.dromara.external.controller.zhongzhi.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author
+ * @date 2025/12/24 下午6:02
+ */
+@Data
+public class OrderPushVo {
+    /**
+    * 订单编号
+    * */
+    private String mall_order_id;
+
+    /**
+    * 订单价格
+    * */
+    private String orderPrice;
+
+    /**
+    * 运费
+    * */
+    private String freight;
+
+    /**
+    * 订单商品
+    * */
+    private List<Sku> sku;
+
+    @Data
+    public static class Sku {
+        private String sku;
+        private String num;
+        private String price;
+    }
+}

+ 27 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductImageVo.java

@@ -0,0 +1,27 @@
+package org.dromara.external.controller.zhongzhi.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author
+ * @date 2025/12/24 下午4:48
+ */
+@Data
+public class ProductImageVo {
+    /**
+    * 商品编号
+    */
+    private String sku;
+    /**
+    * 图片路径
+    */
+    private List<Image> images;
+
+    @Data
+    public static class Image {
+        private String path;
+        private Integer order;
+    }
+}

+ 22 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductMessageInfo.java

@@ -0,0 +1,22 @@
+package org.dromara.external.controller.zhongzhi.domain.vo;
+
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class ProductMessageInfo {
+    private Result result;
+    private Long id;
+    private String time;
+    private Integer type;
+
+    @Data
+    public static class Result {
+        private BigDecimal price;
+        private BigDecimal market_price; // 注意:JSON 中是下划线命名,Java 推荐驼峰命名
+        private Integer state;
+        private String skuId;
+    }
+}

+ 29 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductPriceVo.java

@@ -0,0 +1,29 @@
+package org.dromara.external.controller.zhongzhi.domain.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author
+ * @date 2025/12/24 下午4:55
+ */
+@Data
+public class ProductPriceVo {
+    /**
+    * 商品编号
+    * */
+    private String sku;
+    /**
+    * 价格
+    * */
+    private BigDecimal price;
+    /**
+    * 商城价格
+    * */
+    private BigDecimal mall_price;
+    /**
+    * 库存
+    * */
+    private String stock;
+}

+ 28 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductPromiseVo.java

@@ -0,0 +1,28 @@
+package org.dromara.external.controller.zhongzhi.domain.vo;
+
+import lombok.Data;
+
+/**
+ *
+ * 获取商品承诺函/产品认证信息/七天无理由退货
+ * @author
+ * @date 2025/12/24 下午4:31
+ */
+@Data
+public class ProductPromiseVo {
+
+    /**
+    * 商品承诺/产品认证唯一识别码
+    * */
+    private String regionpromiseguid;
+
+    /**
+    * 商品承诺/产品认证文件路径
+    * */
+    private String promisefilepath;
+
+    /**
+    * 备注
+    * */
+    private String remark;
+}

+ 19 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/zhongzhi/domain/vo/ProductShelfStateVo.java

@@ -0,0 +1,19 @@
+package org.dromara.external.controller.zhongzhi.domain.vo;
+
+import lombok.Data;
+
+/**
+ * @author
+ * @date 2025/12/24 下午4:35
+ */
+@Data
+public class ProductShelfStateVo {
+    /**
+    * 商品编号
+    * */
+    private String sku;
+    /**
+    * 状态(0.下架 1.上架)
+    * */
+    private String state;
+}

+ 72 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalItem.java

@@ -0,0 +1,72 @@
+package org.dromara.external.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 第三方对接项目管理对象 external_item
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("external_item")
+public class ExternalItem extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 项目id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 项目表
+     */
+    private String itemName;
+
+    /**
+     * 项目key
+     */
+    private String itemKey;
+
+    /**
+     * 项目用户名
+     */
+    private String userName;
+
+    /**
+     * 项目密码
+     */
+    private String password;
+
+    /**
+     * 项目url
+     */
+    private String url;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 99 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalProductBrand.java

@@ -0,0 +1,99 @@
+package org.dromara.external.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+import java.io.Serial;
+
+/**
+ * 第三方产品品牌信息对象 external_product_brand
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("external_product_brand")
+public class ExternalProductBrand extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 第三方品牌id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 原系统品牌id
+     */
+    private Long productBrandId;
+
+    /**
+     * 项目id
+     */
+    private Long itemId;
+
+    /**
+     * 品牌编号(唯一标识)
+     */
+    private String brandNo;
+
+    /**
+     * 品牌中文名称
+     */
+    private String brandName;
+
+    /**
+     * 品牌首字母缩写(如拼音首字母)
+     */
+    private String brandInitials;
+
+    /**
+     * 品牌英文名称
+     */
+    private String brandEnglishName;
+
+    /**
+     * 品牌Logo图片路径或URL
+     */
+    private String brandLogo;
+
+    /**
+     * 品牌标题(用于展示)
+     */
+    private String brandTitle;
+
+    /**
+     * 品牌大图(横幅/封面图)
+     */
+    private String brandBigImage;
+
+    /**
+     * 品牌故事(简介文本)
+     */
+    private String brandStory;
+
+    /**
+     * 数据来源
+     */
+    private String dataSource;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 97 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalProductCategory.java

@@ -0,0 +1,97 @@
+package org.dromara.external.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 第三方产品分类对象 external_product_category
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("external_product_category")
+public class ExternalProductCategory extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 第三方系统id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 项目id
+     */
+    private Long itemId;
+
+    /**
+     * 原系统分类id
+     */
+    private Long productCategoryId;
+
+    /**
+     * 分类编号
+     */
+    private String categoryNo;
+
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+
+    /**
+     * 父级分类ID
+     */
+    private Long parentId;
+
+    /**
+     * 祖籍列表
+     */
+    private String ancestors;
+
+    /**
+     * 分类层级(1=一级,2=二级, 3三级)
+     */
+    private Long classLevel;
+
+    /**
+     * 拼音码(用于快速检索)
+     */
+    private String pyCode;
+
+    /**
+     * 分类描述
+     */
+    private String classDescription;
+
+    /**
+     * 数据来源
+     */
+    private String dataSource;
+
+    /**
+     * 所属平台(0=Web, 1=小程序)
+     */
+    private Long platform;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 72 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/ExternalPushPoolLog.java

@@ -0,0 +1,72 @@
+package org.dromara.external.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 商品池推送记录对象 external_push_pool_log
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("external_push_pool_log")
+public class ExternalPushPoolLog extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 项目id
+     * */
+    private Long itemId;
+
+    /**
+     * 商品池id
+     */
+    private Long poolId;
+
+    /**
+     * 操作人id
+     */
+    private Long operatorId;
+
+    /**
+     * 推送状态 0成功 1失败
+     */
+    private String pushStatus;
+
+    /**
+     * 失败原因
+     * */
+    private String reason;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 71 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalItemBo.java

@@ -0,0 +1,71 @@
+package org.dromara.external.domain.bo;
+
+import org.dromara.external.domain.ExternalItem;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 第三方对接项目管理业务对象 external_item
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ExternalItem.class, reverseConvertGenerate = false)
+public class ExternalItemBo extends BaseEntity {
+
+    /**
+     * 项目id
+     */
+    private Long id;
+
+    /**
+     * 项目表
+     */
+    @NotBlank(message = "项目表不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String itemName;
+
+    /**
+     * 项目key
+     */
+    @NotBlank(message = "项目key不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String itemKey;
+
+    /**
+     * 项目用户名
+     */
+    @NotBlank(message = "项目用户名不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String userName;
+
+    /**
+     * 项目密码
+     */
+    @NotBlank(message = "项目密码不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String password;
+
+    /**
+     * 项目url
+     */
+    @NotBlank(message = "项目url不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String url;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 102 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalProductBrandBo.java

@@ -0,0 +1,102 @@
+package org.dromara.external.domain.bo;
+
+import org.dromara.external.domain.ExternalProductBrand;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+/**
+ * 第三方产品品牌信息业务对象 external_product_brand
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ExternalProductBrand.class, reverseConvertGenerate = false)
+public class ExternalProductBrandBo extends BaseEntity {
+
+    /**
+     * 第三方品牌id
+     */
+    private Long id;
+
+    /**
+     * 原系统品牌id
+     */
+    @NotNull(message = "原系统品牌id不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long productBrandId;
+
+    /**
+     * 项目id
+     */
+    @NotNull(message = "项目id不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long itemId;
+
+    /**
+     * 品牌编号(唯一标识)
+     */
+    private String brandNo;
+
+    /**
+     * 品牌中文名称
+     */
+    @NotBlank(message = "品牌中文名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String brandName;
+
+    /**
+     * 品牌首字母缩写(如拼音首字母)
+     */
+    @NotBlank(message = "品牌首字母缩写(如拼音首字母)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String brandInitials;
+
+    /**
+     * 品牌英文名称
+     */
+    @NotBlank(message = "品牌英文名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String brandEnglishName;
+
+    /**
+     * 品牌Logo图片路径或URL
+     */
+    @NotBlank(message = "品牌Logo图片路径或URL不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String brandLogo;
+
+    /**
+     * 品牌标题(用于展示)
+     */
+    @NotBlank(message = "品牌标题(用于展示)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String brandTitle;
+
+    /**
+     * 品牌大图(横幅/封面图)
+     */
+    @NotBlank(message = "品牌大图(横幅/封面图)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String brandBigImage;
+
+    /**
+     * 品牌故事(简介文本)
+     */
+    @NotBlank(message = "品牌故事(简介文本)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String brandStory;
+
+    /**
+     * 数据来源
+     */
+    @NotBlank(message = "数据来源不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String dataSource;
+
+    /**
+     * 备注
+     */
+    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 100 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalProductCategoryBo.java

@@ -0,0 +1,100 @@
+package org.dromara.external.domain.bo;
+
+import org.dromara.external.domain.ExternalProductCategory;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 第三方产品分类业务对象 external_product_category
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ExternalProductCategory.class, reverseConvertGenerate = false)
+public class ExternalProductCategoryBo extends BaseEntity {
+
+    /**
+     * 第三方系统id
+     */
+    private Long id;
+
+    /**
+     * 项目id
+     */
+    @NotNull(message = "项目id不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long itemId;
+
+    /**
+     * 原系统分类id
+     */
+    @NotNull(message = "原系统分类id不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long productCategoryId;
+
+    /**
+     * 分类编号
+     */
+    @NotBlank(message = "分类编号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String categoryNo;
+
+    /**
+     * 分类名称
+     */
+    @NotBlank(message = "分类名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String categoryName;
+
+    /**
+     * 父级分类ID
+     */
+    @NotNull(message = "父级分类ID不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long parentId;
+
+    /**
+     * 祖籍列表
+     */
+    @NotBlank(message = "祖籍列表不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String ancestors;
+
+    /**
+     * 分类层级(1=一级,2=二级, 3三级)
+     */
+    private Long classLevel;
+
+    /**
+     * 拼音码(用于快速检索)
+     */
+    @NotBlank(message = "拼音码(用于快速检索)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String pyCode;
+
+    /**
+     * 分类描述
+     */
+    @NotBlank(message = "分类描述不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String classDescription;
+
+    /**
+     * 数据来源
+     */
+    @NotBlank(message = "数据来源不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String dataSource;
+
+    /**
+     * 所属平台(0=Web, 1=小程序)
+     */
+    @NotNull(message = "所属平台(0=Web, 1=小程序)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long platform;
+
+    /**
+     * 备注
+     */
+    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 69 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/bo/ExternalPushPoolLogBo.java

@@ -0,0 +1,69 @@
+package org.dromara.external.domain.bo;
+
+import org.dromara.external.domain.ExternalPushPoolLog;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 商品池推送记录业务对象 external_push_pool_log
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ExternalPushPoolLog.class, reverseConvertGenerate = false)
+public class ExternalPushPoolLogBo extends BaseEntity {
+
+    /**
+     *
+     */
+    private Long id;
+
+    /**
+    * 项目id
+    * */
+    private Long itemId;
+
+    /**
+     * 商品池id
+     */
+    @NotNull(message = "商品池id不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long poolId;
+
+    /**
+     * 操作人id
+     */
+    @NotNull(message = "操作人id不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long operatorId;
+
+    /**
+     * 推送状态 0成功 1失败
+     */
+    @NotBlank(message = "推送状态 0成功 1失败不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String pushStatus;
+
+    /**
+    * 失败原因
+    * */
+    private String reason;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 81 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalItemVo.java

@@ -0,0 +1,81 @@
+package org.dromara.external.domain.vo;
+
+import org.dromara.external.domain.ExternalItem;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 第三方对接项目管理视图对象 external_item
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ExternalItem.class)
+public class ExternalItemVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 项目id
+     */
+    @ExcelProperty(value = "项目id")
+    private Long id;
+
+    /**
+     * 项目表
+     */
+    @ExcelProperty(value = "项目表")
+    private String itemName;
+
+    /**
+     * 项目key
+     */
+    @ExcelProperty(value = "项目key")
+    private String itemKey;
+
+    /**
+     * 项目用户名
+     */
+    @ExcelProperty(value = "项目用户名")
+    private String userName;
+
+    /**
+     * 项目密码
+     */
+    @ExcelProperty(value = "项目密码")
+    private String password;
+
+    /**
+     * 项目url
+     */
+    @ExcelProperty(value = "项目url")
+    private String url;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 122 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalProductBrandVo.java

@@ -0,0 +1,122 @@
+package org.dromara.external.domain.vo;
+
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.external.domain.ExternalProductBrand;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 第三方产品品牌信息视图对象 external_product_brand
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ExternalProductBrand.class)
+public class ExternalProductBrandVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 第三方品牌id
+     */
+    @ExcelProperty(value = "第三方品牌id")
+    private Long id;
+
+    /**
+     * 原系统品牌id
+     */
+    @ExcelProperty(value = "原系统品牌id")
+    private Long productBrandId;
+
+    /**
+     * 项目id
+     */
+    @ExcelProperty(value = "项目id")
+    private Long itemId;
+
+    /**
+     * 品牌编号(唯一标识)
+     */
+    @ExcelProperty(value = "品牌编号", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "唯=一标识")
+    private String brandNo;
+
+    /**
+     * 品牌中文名称
+     */
+    @ExcelProperty(value = "品牌中文名称")
+    private String brandName;
+
+    /**
+     * 品牌首字母缩写(如拼音首字母)
+     */
+    @ExcelProperty(value = "品牌首字母缩写", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "如=拼音首字母")
+    private String brandInitials;
+
+    /**
+     * 品牌英文名称
+     */
+    @ExcelProperty(value = "品牌英文名称")
+    private String brandEnglishName;
+
+    /**
+     * 品牌Logo图片路径或URL
+     */
+    @ExcelProperty(value = "品牌Logo图片路径或URL")
+    private String brandLogo;
+
+    /**
+     * 品牌标题(用于展示)
+     */
+    @ExcelProperty(value = "品牌标题", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "用=于展示")
+    private String brandTitle;
+
+    /**
+     * 品牌大图(横幅/封面图)
+     */
+    @ExcelProperty(value = "品牌大图", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "横=幅/封面图")
+    private String brandBigImage;
+
+    /**
+     * 品牌大图(横幅/封面图)Url
+     */
+    @Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "brandBigImage")
+    private String brandBigImageUrl;
+    /**
+     * 品牌故事(简介文本)
+     */
+    @ExcelProperty(value = "品牌故事", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "简=介文本")
+    private String brandStory;
+
+    /**
+     * 数据来源
+     */
+    @ExcelProperty(value = "数据来源")
+    private String dataSource;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 113 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalProductCategoryVo.java

@@ -0,0 +1,113 @@
+package org.dromara.external.domain.vo;
+
+import org.dromara.external.domain.ExternalProductCategory;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 第三方产品分类视图对象 external_product_category
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ExternalProductCategory.class)
+public class ExternalProductCategoryVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 第三方系统id
+     */
+    @ExcelProperty(value = "第三方系统id")
+    private Long id;
+
+    /**
+     * 项目id
+     */
+    @ExcelProperty(value = "项目id")
+    private Long itemId;
+
+    /**
+     * 原系统分类id
+     */
+    @ExcelProperty(value = "原系统分类id")
+    private Long productCategoryId;
+
+    /**
+     * 分类编号
+     */
+    @ExcelProperty(value = "分类编号")
+    private String categoryNo;
+
+    /**
+     * 分类名称
+     */
+    @ExcelProperty(value = "分类名称")
+    private String categoryName;
+
+    /**
+     * 父级分类ID
+     */
+    @ExcelProperty(value = "父级分类ID")
+    private Long parentId;
+
+    /**
+     * 祖籍列表
+     */
+    @ExcelProperty(value = "祖籍列表")
+    private String ancestors;
+
+    /**
+     * 分类层级(1=一级,2=二级, 3三级)
+     */
+    @ExcelProperty(value = "分类层级", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "1==一级,2=二级,,3=三级")
+    private Long classLevel;
+
+    /**
+     * 拼音码(用于快速检索)
+     */
+    @ExcelProperty(value = "拼音码", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "用=于快速检索")
+    private String pyCode;
+
+    /**
+     * 分类描述
+     */
+    @ExcelProperty(value = "分类描述")
+    private String classDescription;
+
+    /**
+     * 数据来源
+     */
+    @ExcelProperty(value = "数据来源")
+    private String dataSource;
+
+    /**
+     * 所属平台(0=Web, 1=小程序)
+     */
+    @ExcelProperty(value = "所属平台", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0==Web,,1==小程序")
+    private Long platform;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 79 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/domain/vo/ExternalPushPoolLogVo.java

@@ -0,0 +1,79 @@
+package org.dromara.external.domain.vo;
+
+import org.dromara.external.domain.ExternalPushPoolLog;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 商品池推送记录视图对象 external_push_pool_log
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ExternalPushPoolLog.class)
+public class ExternalPushPoolLogVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @ExcelProperty(value = "")
+    private Long id;
+
+    /**
+     * 项目id
+     * */
+    private Long itemId;
+
+    /**
+     * 商品池id
+     */
+    @ExcelProperty(value = "商品池id")
+    private Long poolId;
+
+    /**
+     * 操作人id
+     */
+    @ExcelProperty(value = "操作人id")
+    private Long operatorId;
+
+    /**
+     * 推送状态 0成功 1失败
+     */
+    @ExcelProperty(value = "推送状态 0成功 1失败")
+    private String pushStatus;
+
+    /**
+     * 失败原因
+     * */
+    private String reason;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalItemMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.external.mapper;
+
+import org.dromara.external.domain.ExternalItem;
+import org.dromara.external.domain.vo.ExternalItemVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 第三方对接项目管理Mapper接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface ExternalItemMapper extends BaseMapperPlus<ExternalItem, ExternalItemVo> {
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalProductBrandMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.external.mapper;
+
+import org.dromara.external.domain.ExternalProductBrand;
+import org.dromara.external.domain.vo.ExternalProductBrandVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 第三方产品品牌信息Mapper接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface ExternalProductBrandMapper extends BaseMapperPlus<ExternalProductBrand, ExternalProductBrandVo> {
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalProductCategoryMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.external.mapper;
+
+import org.dromara.external.domain.ExternalProductCategory;
+import org.dromara.external.domain.vo.ExternalProductCategoryVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 第三方产品分类Mapper接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface ExternalProductCategoryMapper extends BaseMapperPlus<ExternalProductCategory, ExternalProductCategoryVo> {
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/mapper/ExternalPushPoolLogMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.external.mapper;
+
+import org.dromara.external.domain.ExternalPushPoolLog;
+import org.dromara.external.domain.vo.ExternalPushPoolLogVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 商品池推送记录Mapper接口
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+public interface ExternalPushPoolLogMapper extends BaseMapperPlus<ExternalPushPoolLog, ExternalPushPoolLogVo> {
+
+}

+ 70 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalItemService.java

@@ -0,0 +1,70 @@
+package org.dromara.external.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.external.domain.ExternalItem;
+import org.dromara.external.domain.vo.ExternalItemVo;
+import org.dromara.external.domain.bo.ExternalItemBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 第三方对接项目管理Service接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface IExternalItemService extends IService<ExternalItem>{
+
+    /**
+     * 查询第三方对接项目管理
+     *
+     * @param id 主键
+     * @return 第三方对接项目管理
+     */
+    ExternalItemVo queryById(Long id);
+
+    /**
+     * 分页查询第三方对接项目管理列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 第三方对接项目管理分页列表
+     */
+    TableDataInfo<ExternalItemVo> queryPageList(ExternalItemBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的第三方对接项目管理列表
+     *
+     * @param bo 查询条件
+     * @return 第三方对接项目管理列表
+     */
+    List<ExternalItemVo> queryList(ExternalItemBo bo);
+
+    /**
+     * 新增第三方对接项目管理
+     *
+     * @param bo 第三方对接项目管理
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ExternalItemBo bo);
+
+    /**
+     * 修改第三方对接项目管理
+     *
+     * @param bo 第三方对接项目管理
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ExternalItemBo bo);
+
+    /**
+     * 校验并批量删除第三方对接项目管理信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 70 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalProductBrandService.java

@@ -0,0 +1,70 @@
+package org.dromara.external.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.external.domain.ExternalProductBrand;
+import org.dromara.external.domain.vo.ExternalProductBrandVo;
+import org.dromara.external.domain.bo.ExternalProductBrandBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 第三方产品品牌信息Service接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface IExternalProductBrandService extends IService<ExternalProductBrand>{
+
+    /**
+     * 查询第三方产品品牌信息
+     *
+     * @param id 主键
+     * @return 第三方产品品牌信息
+     */
+    ExternalProductBrandVo queryById(Long id);
+
+    /**
+     * 分页查询第三方产品品牌信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 第三方产品品牌信息分页列表
+     */
+    TableDataInfo<ExternalProductBrandVo> queryPageList(ExternalProductBrandBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的第三方产品品牌信息列表
+     *
+     * @param bo 查询条件
+     * @return 第三方产品品牌信息列表
+     */
+    List<ExternalProductBrandVo> queryList(ExternalProductBrandBo bo);
+
+    /**
+     * 新增第三方产品品牌信息
+     *
+     * @param bo 第三方产品品牌信息
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ExternalProductBrandBo bo);
+
+    /**
+     * 修改第三方产品品牌信息
+     *
+     * @param bo 第三方产品品牌信息
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ExternalProductBrandBo bo);
+
+    /**
+     * 校验并批量删除第三方产品品牌信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 70 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalProductCategoryService.java

@@ -0,0 +1,70 @@
+package org.dromara.external.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.external.domain.ExternalProductCategory;
+import org.dromara.external.domain.vo.ExternalProductCategoryVo;
+import org.dromara.external.domain.bo.ExternalProductCategoryBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 第三方产品分类Service接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface IExternalProductCategoryService extends IService<ExternalProductCategory>{
+
+    /**
+     * 查询第三方产品分类
+     *
+     * @param id 主键
+     * @return 第三方产品分类
+     */
+    ExternalProductCategoryVo queryById(Long id);
+
+    /**
+     * 分页查询第三方产品分类列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 第三方产品分类分页列表
+     */
+    TableDataInfo<ExternalProductCategoryVo> queryPageList(ExternalProductCategoryBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的第三方产品分类列表
+     *
+     * @param bo 查询条件
+     * @return 第三方产品分类列表
+     */
+    List<ExternalProductCategoryVo> queryList(ExternalProductCategoryBo bo);
+
+    /**
+     * 新增第三方产品分类
+     *
+     * @param bo 第三方产品分类
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ExternalProductCategoryBo bo);
+
+    /**
+     * 修改第三方产品分类
+     *
+     * @param bo 第三方产品分类
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ExternalProductCategoryBo bo);
+
+    /**
+     * 校验并批量删除第三方产品分类信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 70 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/IExternalPushPoolLogService.java

@@ -0,0 +1,70 @@
+package org.dromara.external.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.external.domain.ExternalPushPoolLog;
+import org.dromara.external.domain.vo.ExternalPushPoolLogVo;
+import org.dromara.external.domain.bo.ExternalPushPoolLogBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 商品池推送记录Service接口
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+public interface IExternalPushPoolLogService extends IService<ExternalPushPoolLog>{
+
+    /**
+     * 查询商品池推送记录
+     *
+     * @param id 主键
+     * @return 商品池推送记录
+     */
+    ExternalPushPoolLogVo queryById(Long id);
+
+    /**
+     * 分页查询商品池推送记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品池推送记录分页列表
+     */
+    TableDataInfo<ExternalPushPoolLogVo> queryPageList(ExternalPushPoolLogBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的商品池推送记录列表
+     *
+     * @param bo 查询条件
+     * @return 商品池推送记录列表
+     */
+    List<ExternalPushPoolLogVo> queryList(ExternalPushPoolLogBo bo);
+
+    /**
+     * 新增商品池推送记录
+     *
+     * @param bo 商品池推送记录
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ExternalPushPoolLogBo bo);
+
+    /**
+     * 修改商品池推送记录
+     *
+     * @param bo 商品池推送记录
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ExternalPushPoolLogBo bo);
+
+    /**
+     * 校验并批量删除商品池推送记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 139 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalItemServiceImpl.java

@@ -0,0 +1,139 @@
+package org.dromara.external.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.external.domain.bo.ExternalItemBo;
+import org.dromara.external.domain.vo.ExternalItemVo;
+import org.dromara.external.domain.ExternalItem;
+import org.dromara.external.mapper.ExternalItemMapper;
+import org.dromara.external.service.IExternalItemService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 第三方对接项目管理Service业务层处理
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ExternalItemServiceImpl  extends ServiceImpl<ExternalItemMapper, ExternalItem> implements IExternalItemService {
+
+    private final ExternalItemMapper baseMapper;
+
+    /**
+     * 查询第三方对接项目管理
+     *
+     * @param id 主键
+     * @return 第三方对接项目管理
+     */
+    @Override
+    public ExternalItemVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询第三方对接项目管理列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 第三方对接项目管理分页列表
+     */
+    @Override
+    public TableDataInfo<ExternalItemVo> queryPageList(ExternalItemBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ExternalItem> lqw = buildQueryWrapper(bo);
+        Page<ExternalItemVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的第三方对接项目管理列表
+     *
+     * @param bo 查询条件
+     * @return 第三方对接项目管理列表
+     */
+    @Override
+    public List<ExternalItemVo> queryList(ExternalItemBo bo) {
+        LambdaQueryWrapper<ExternalItem> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ExternalItem> buildQueryWrapper(ExternalItemBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ExternalItem> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ExternalItem::getId);
+        lqw.like(StringUtils.isNotBlank(bo.getItemName()), ExternalItem::getItemName, bo.getItemName());
+        lqw.eq(StringUtils.isNotBlank(bo.getItemKey()), ExternalItem::getItemKey, bo.getItemKey());
+        lqw.like(StringUtils.isNotBlank(bo.getUserName()), ExternalItem::getUserName, bo.getUserName());
+        lqw.eq(StringUtils.isNotBlank(bo.getPassword()), ExternalItem::getPassword, bo.getPassword());
+        lqw.eq(StringUtils.isNotBlank(bo.getUrl()), ExternalItem::getUrl, bo.getUrl());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ExternalItem::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ExternalItem::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增第三方对接项目管理
+     *
+     * @param bo 第三方对接项目管理
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ExternalItemBo bo) {
+        ExternalItem add = MapstructUtils.convert(bo, ExternalItem.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改第三方对接项目管理
+     *
+     * @param bo 第三方对接项目管理
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ExternalItemBo bo) {
+        ExternalItem update = MapstructUtils.convert(bo, ExternalItem.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ExternalItem entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除第三方对接项目管理信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 144 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalProductBrandServiceImpl.java

@@ -0,0 +1,144 @@
+package org.dromara.external.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.external.domain.bo.ExternalProductBrandBo;
+import org.dromara.external.domain.vo.ExternalProductBrandVo;
+import org.dromara.external.domain.ExternalProductBrand;
+import org.dromara.external.mapper.ExternalProductBrandMapper;
+import org.dromara.external.service.IExternalProductBrandService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 第三方产品品牌信息Service业务层处理
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ExternalProductBrandServiceImpl  extends ServiceImpl<ExternalProductBrandMapper, ExternalProductBrand> implements IExternalProductBrandService {
+
+    private final ExternalProductBrandMapper baseMapper;
+
+    /**
+     * 查询第三方产品品牌信息
+     *
+     * @param id 主键
+     * @return 第三方产品品牌信息
+     */
+    @Override
+    public ExternalProductBrandVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询第三方产品品牌信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 第三方产品品牌信息分页列表
+     */
+    @Override
+    public TableDataInfo<ExternalProductBrandVo> queryPageList(ExternalProductBrandBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ExternalProductBrand> lqw = buildQueryWrapper(bo);
+        Page<ExternalProductBrandVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的第三方产品品牌信息列表
+     *
+     * @param bo 查询条件
+     * @return 第三方产品品牌信息列表
+     */
+    @Override
+    public List<ExternalProductBrandVo> queryList(ExternalProductBrandBo bo) {
+        LambdaQueryWrapper<ExternalProductBrand> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ExternalProductBrand> buildQueryWrapper(ExternalProductBrandBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ExternalProductBrand> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ExternalProductBrand::getId);
+        lqw.eq(bo.getProductBrandId() != null, ExternalProductBrand::getProductBrandId, bo.getProductBrandId());
+        lqw.eq(bo.getItemId() != null, ExternalProductBrand::getItemId, bo.getItemId());
+        lqw.eq(StringUtils.isNotBlank(bo.getBrandNo()), ExternalProductBrand::getBrandNo, bo.getBrandNo());
+        lqw.like(StringUtils.isNotBlank(bo.getBrandName()), ExternalProductBrand::getBrandName, bo.getBrandName());
+        lqw.eq(StringUtils.isNotBlank(bo.getBrandInitials()), ExternalProductBrand::getBrandInitials, bo.getBrandInitials());
+        lqw.like(StringUtils.isNotBlank(bo.getBrandEnglishName()), ExternalProductBrand::getBrandEnglishName, bo.getBrandEnglishName());
+        lqw.eq(StringUtils.isNotBlank(bo.getBrandLogo()), ExternalProductBrand::getBrandLogo, bo.getBrandLogo());
+        lqw.eq(StringUtils.isNotBlank(bo.getBrandTitle()), ExternalProductBrand::getBrandTitle, bo.getBrandTitle());
+        lqw.eq(StringUtils.isNotBlank(bo.getBrandBigImage()), ExternalProductBrand::getBrandBigImage, bo.getBrandBigImage());
+        lqw.eq(StringUtils.isNotBlank(bo.getBrandStory()), ExternalProductBrand::getBrandStory, bo.getBrandStory());
+        lqw.eq(StringUtils.isNotBlank(bo.getDataSource()), ExternalProductBrand::getDataSource, bo.getDataSource());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ExternalProductBrand::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增第三方产品品牌信息
+     *
+     * @param bo 第三方产品品牌信息
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ExternalProductBrandBo bo) {
+        ExternalProductBrand add = MapstructUtils.convert(bo, ExternalProductBrand.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改第三方产品品牌信息
+     *
+     * @param bo 第三方产品品牌信息
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ExternalProductBrandBo bo) {
+        ExternalProductBrand update = MapstructUtils.convert(bo, ExternalProductBrand.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ExternalProductBrand entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除第三方产品品牌信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 144 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalProductCategoryServiceImpl.java

@@ -0,0 +1,144 @@
+package org.dromara.external.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.external.domain.bo.ExternalProductCategoryBo;
+import org.dromara.external.domain.vo.ExternalProductCategoryVo;
+import org.dromara.external.domain.ExternalProductCategory;
+import org.dromara.external.mapper.ExternalProductCategoryMapper;
+import org.dromara.external.service.IExternalProductCategoryService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 第三方产品分类Service业务层处理
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ExternalProductCategoryServiceImpl  extends ServiceImpl<ExternalProductCategoryMapper, ExternalProductCategory> implements IExternalProductCategoryService {
+
+    private final ExternalProductCategoryMapper baseMapper;
+
+    /**
+     * 查询第三方产品分类
+     *
+     * @param id 主键
+     * @return 第三方产品分类
+     */
+    @Override
+    public ExternalProductCategoryVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询第三方产品分类列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 第三方产品分类分页列表
+     */
+    @Override
+    public TableDataInfo<ExternalProductCategoryVo> queryPageList(ExternalProductCategoryBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ExternalProductCategory> lqw = buildQueryWrapper(bo);
+        Page<ExternalProductCategoryVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的第三方产品分类列表
+     *
+     * @param bo 查询条件
+     * @return 第三方产品分类列表
+     */
+    @Override
+    public List<ExternalProductCategoryVo> queryList(ExternalProductCategoryBo bo) {
+        LambdaQueryWrapper<ExternalProductCategory> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ExternalProductCategory> buildQueryWrapper(ExternalProductCategoryBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ExternalProductCategory> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ExternalProductCategory::getId);
+        lqw.eq(bo.getItemId() != null, ExternalProductCategory::getItemId, bo.getItemId());
+        lqw.eq(bo.getProductCategoryId() != null, ExternalProductCategory::getProductCategoryId, bo.getProductCategoryId());
+        lqw.eq(StringUtils.isNotBlank(bo.getCategoryNo()), ExternalProductCategory::getCategoryNo, bo.getCategoryNo());
+        lqw.like(StringUtils.isNotBlank(bo.getCategoryName()), ExternalProductCategory::getCategoryName, bo.getCategoryName());
+        lqw.eq(bo.getParentId() != null, ExternalProductCategory::getParentId, bo.getParentId());
+        lqw.eq(StringUtils.isNotBlank(bo.getAncestors()), ExternalProductCategory::getAncestors, bo.getAncestors());
+        lqw.eq(bo.getClassLevel() != null, ExternalProductCategory::getClassLevel, bo.getClassLevel());
+        lqw.eq(StringUtils.isNotBlank(bo.getPyCode()), ExternalProductCategory::getPyCode, bo.getPyCode());
+        lqw.eq(StringUtils.isNotBlank(bo.getClassDescription()), ExternalProductCategory::getClassDescription, bo.getClassDescription());
+        lqw.eq(StringUtils.isNotBlank(bo.getDataSource()), ExternalProductCategory::getDataSource, bo.getDataSource());
+        lqw.eq(bo.getPlatform() != null, ExternalProductCategory::getPlatform, bo.getPlatform());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ExternalProductCategory::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增第三方产品分类
+     *
+     * @param bo 第三方产品分类
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ExternalProductCategoryBo bo) {
+        ExternalProductCategory add = MapstructUtils.convert(bo, ExternalProductCategory.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改第三方产品分类
+     *
+     * @param bo 第三方产品分类
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ExternalProductCategoryBo bo) {
+        ExternalProductCategory update = MapstructUtils.convert(bo, ExternalProductCategory.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ExternalProductCategory entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除第三方产品分类信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 137 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/service/impl/ExternalPushPoolLogServiceImpl.java

@@ -0,0 +1,137 @@
+package org.dromara.external.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.external.domain.bo.ExternalPushPoolLogBo;
+import org.dromara.external.domain.vo.ExternalPushPoolLogVo;
+import org.dromara.external.domain.ExternalPushPoolLog;
+import org.dromara.external.mapper.ExternalPushPoolLogMapper;
+import org.dromara.external.service.IExternalPushPoolLogService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 商品池推送记录Service业务层处理
+ *
+ * @author LionLi
+ * @date 2025-12-24
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ExternalPushPoolLogServiceImpl  extends ServiceImpl<ExternalPushPoolLogMapper, ExternalPushPoolLog> implements IExternalPushPoolLogService {
+
+    private final ExternalPushPoolLogMapper baseMapper;
+
+    /**
+     * 查询商品池推送记录
+     *
+     * @param id 主键
+     * @return 商品池推送记录
+     */
+    @Override
+    public ExternalPushPoolLogVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询商品池推送记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品池推送记录分页列表
+     */
+    @Override
+    public TableDataInfo<ExternalPushPoolLogVo> queryPageList(ExternalPushPoolLogBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ExternalPushPoolLog> lqw = buildQueryWrapper(bo);
+        Page<ExternalPushPoolLogVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的商品池推送记录列表
+     *
+     * @param bo 查询条件
+     * @return 商品池推送记录列表
+     */
+    @Override
+    public List<ExternalPushPoolLogVo> queryList(ExternalPushPoolLogBo bo) {
+        LambdaQueryWrapper<ExternalPushPoolLog> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ExternalPushPoolLog> buildQueryWrapper(ExternalPushPoolLogBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ExternalPushPoolLog> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ExternalPushPoolLog::getId);
+        lqw.eq(bo.getPoolId() != null, ExternalPushPoolLog::getPoolId, bo.getPoolId());
+        lqw.eq(bo.getOperatorId() != null, ExternalPushPoolLog::getOperatorId, bo.getOperatorId());
+        lqw.eq(StringUtils.isNotBlank(bo.getPushStatus()), ExternalPushPoolLog::getPushStatus, bo.getPushStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ExternalPushPoolLog::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ExternalPushPoolLog::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增商品池推送记录
+     *
+     * @param bo 商品池推送记录
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ExternalPushPoolLogBo bo) {
+        ExternalPushPoolLog add = MapstructUtils.convert(bo, ExternalPushPoolLog.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改商品池推送记录
+     *
+     * @param bo 商品池推送记录
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ExternalPushPoolLogBo bo) {
+        ExternalPushPoolLog update = MapstructUtils.convert(bo, ExternalPushPoolLog.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ExternalPushPoolLog entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除商品池推送记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 885 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/util/DataMigration.java

@@ -0,0 +1,885 @@
+package org.dromara.external.util;
+
+import java.math.BigDecimal;
+import java.sql.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+public class DataMigration {
+
+
+
+
+    // SQL Server 连接信息
+    private static final String SQL_SERVER_URL = "jdbc:sqlserver://192.168.1.123:1433;databaseName=yoe_db";
+    private static final String SQL_SERVER_USER = "sa";
+    private static final String SQL_SERVER_PASS = "Sql123456";
+
+    // MySQL 连接信息
+    private static final String MYSQL_URL = "jdbc:mysql://127.0.0.1:3306/yoe_product_db?useSSL=false&serverTimezone=UTC";
+    private static final String MYSQL_USER = "root";
+    private static final String MYSQL_PASS = "root";
+
+    // 分页参数
+    private static final int BATCH_SIZE = 1000;
+
+
+    // === 全局 product_no -> product_id 映射(只读)===
+    private static volatile Map<String, Long> productNoToId;
+
+    // === 配置 ===
+    private static final int THREAD_COUNT = 8;
+    private static final int PAGE_SIZE = 10_000;
+
+    public static void main(String[] args) {
+//        try (Connection sqlConn = DriverManager.getConnection(SQL_SERVER_URL, SQL_SERVER_USER, SQL_SERVER_PASS)) {
+//            // 先迁移依赖表(品牌、分类、单位、售后)
+//            try (Connection mysqlConn = DriverManager.getConnection(MYSQL_URL, MYSQL_USER, MYSQL_PASS)) {
+//                loadMappings(mysqlConn);
+//            }
+//            // 再多线程迁移主表 product_info
+//            migrateData(sqlConn); // 注意:现在只传 sqlConn(但内部线程会新建连接)
+//            System.out.println("✅ 所有数据迁移完成!");
+//
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+
+        try {
+            // 1. 加载 product_no -> id 映射(从已迁移的 product_base 表)
+            loadProductNoMapping();
+            System.out.println("✅ 已加载 " + productNoToId.size() + " 个 product_no 映射");
+
+            // 2. 获取 ID 范围
+            RangeInfo range = getMssqlIdRange();
+            System.out.println("📊 总记录数: " + range.totalCount + ", ID 范围: [" + range.minId + ", " + range.maxId + "]");
+
+            // 3. 划分任务
+            List<TaskRange> tasks = splitTasks(range.minId, range.maxId, PAGE_SIZE);
+            System.out.println("🧩 共划分 " + tasks.size() + " 个任务");
+
+            // 4. 并行执行
+            ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
+            List<Future<Long>> futures = new ArrayList<>();
+
+            for (TaskRange task : tasks) {
+                futures.add(executor.submit(new PhotoMigrationTask(task)));
+            }
+
+            long total = 0;
+            for (Future<Long> f : futures) total += f.get();
+            executor.shutdown();
+
+            System.out.println("🎉 图片表迁移完成!共 " + total + " 条记录。");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+    * 售后服务迁移
+    * */
+    private static void migrateProductAfterSales(Connection source, Connection target) throws SQLException {
+
+        try (Connection sqlServerConn = source;
+             Connection mysqlConn = target) {
+
+            // Fetch data from SQL Server
+            String fetchSql = "SELECT id, after_sales_items, data_source, created, modify FROM product_after_sales";
+            try (Statement stmt = sqlServerConn.createStatement(); ResultSet rs = stmt.executeQuery(fetchSql)) {
+
+                // Insert data into MySQL
+                String insertSql = "INSERT INTO product_after_sales (id, after_sales_items, data_source, tenant_id, del_flag, create_time, update_time) VALUES (?, ?, ?, '000000', '0', ?, ?)";
+                try (PreparedStatement pstmt = mysqlConn.prepareStatement(insertSql)) {
+                    while (rs.next()) {
+                        int id = rs.getInt("id");
+                        String afterSalesItems = rs.getString("after_sales_items");
+                        String dataSource = rs.getString("data_source");
+                        Timestamp createTime = rs.getTimestamp("created");
+                        Timestamp updateTime = rs.getTimestamp("modify");
+
+                        pstmt.setInt(1, id);
+                        pstmt.setString(2, afterSalesItems);
+                        pstmt.setString(3, dataSource);
+                        pstmt.setTimestamp(4, createTime);
+                        pstmt.setTimestamp(5, updateTime);
+
+                        pstmt.addBatch();
+                    }
+                    pstmt.executeBatch();
+                }
+            }
+
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /**
+    * 分类迁移
+    * */
+    private static void migrateProductCategory(Connection source, Connection target) throws SQLException {
+        String selectSql = """
+            SELECT
+                id, category_no, category_name, parent_id, class_level,
+                is_show, is_show_gps, discount_rate, py_code, class_description,
+                data_source, created, modify,
+                one_lable1, one_lable2, one_link1, one_link2,
+                sort, color, purchase_no, purchase_name,
+                purchase_manager_no, purchase_manager_name, platform
+            FROM product_category
+            where data_source = 'youyi'
+            ORDER BY id
+            """;
+        String selectSql1 = "SELECT id,parent_id FROM product_category where data_source = 'youyi'and category_no =";
+
+        String insertSql = """
+            INSERT INTO product_category (
+                id, category_no, category_name, parent_id, ancestors,
+                class_level, is_show, is_show_gps, discount_rate, py_code,
+                class_description, data_source,
+                one_lable1, one_lable2, one_link1, one_link2,
+                sort, color, purchase_no, purchase_name,
+                purchase_manager_no, purchase_manager_name, platform,
+                tenant_id, del_flag, create_time, update_time
+            ) VALUES (
+                ?, ?, ?, ?, NULL,
+                ?, ?, ?, ?, ?,
+                ?, ?,
+                ?, ?, ?, ?,
+                ?, ?, ?, ?,
+                ?, ?, ?,
+                '000000', '0', ?, ?
+            )
+            """;
+        String updateSql1 =
+            "UPDATE product_category SET ancestors = 0 WHERE parent_id = 0;";
+        String updateSql2 = "UPDATE product_category AS child\n" +
+            "JOIN product_category AS parent \n" +
+            "  ON child.parent_id = parent.id\n" +
+            "SET child.ancestors = CONCAT(parent.ancestors, ',', parent.id)\n" +
+            "WHERE child.parent_id != 0;";
+
+        try (PreparedStatement selectStmt = source.prepareStatement(selectSql);
+             ResultSet rs = selectStmt.executeQuery();
+             PreparedStatement insertStmt = target.prepareStatement(insertSql)) {
+
+            int batchCount = 0;
+            while (rs.next()) {
+                // id
+                insertStmt.setLong(1, rs.getInt("id"));
+
+                // strings
+                insertStmt.setString(2, rs.getString("category_no"));
+                insertStmt.setString(3, rs.getString("category_name"));
+
+
+                PreparedStatement selectStmt1 = source.prepareStatement(selectSql1 +"'" +rs.getString("parent_id") +"'");
+                ResultSet rs1 = selectStmt1.executeQuery();
+                Long parentId = 0L;
+                // 检查是否有结果行
+                if (rs1.next()) {
+                    // parent_id: 尝试转为 Long
+                    String parentIdS = rs.getString("parent_id");
+                    String parentIdStr = rs1.getString("id");
+                    System.out.println("parentId="+parentIdS);
+                    System.out.println("parentIdStr="+parentIdStr);
+                    if (parentIdStr != null && !parentIdStr.trim().isEmpty()) {
+                        try {
+                            parentId = Long.parseLong(parentIdStr.trim());
+                        } catch (NumberFormatException ignored) {
+                            System.err.println("⚠️ 无法转换 parent_id: " + parentIdStr + ",设为 NULL");
+                        }
+                    }
+                }
+                insertStmt.setObject(4, parentId, Types.BIGINT);
+
+                // class_level
+                insertStmt.setInt(5, rs.getInt("class_level"));
+
+                // is_show (nullable)
+                Integer isShow = rs.getObject("is_show", Integer.class);
+                insertStmt.setObject(6, isShow, Types.INTEGER);
+
+                // is_show_gps (not null in source)
+                insertStmt.setInt(7, rs.getInt("is_show_gps"));
+
+                // discount_rate: 尝试转为 BigDecimal
+                String discStr = rs.getString("discount_rate");
+                BigDecimal discountRate = null;
+                if (discStr != null && !discStr.trim().isEmpty()) {
+                    try {
+                        // 支持 "0.85", "85%", "85" 等格式(按需调整)
+                        String clean = discStr.trim().replace("%", "");
+                        if (clean.contains(".")) {
+                            discountRate = new BigDecimal(clean);
+                        } else {
+                            // 如果是整数,比如 "85",视为 85.00
+                            discountRate = new BigDecimal(clean).setScale(2);
+                        }
+                    } catch (Exception ex) {
+                        System.err.println("⚠️ 无法解析 discount_rate: " + discStr + ",设为 NULL");
+                    }
+                }
+                insertStmt.setObject(8, discountRate, Types.DECIMAL);
+
+                // 其他 varchar/text 字段
+                insertStmt.setString(9, rs.getString("py_code"));
+                insertStmt.setString(10, rs.getString("class_description"));
+                insertStmt.setString(11, rs.getString("data_source"));
+
+                insertStmt.setString(12, rs.getString("one_lable1"));
+                insertStmt.setString(13, rs.getString("one_lable2"));
+                insertStmt.setString(14, rs.getString("one_link1"));
+                insertStmt.setString(15, rs.getString("one_link2"));
+
+                insertStmt.setInt(16, rs.getInt("sort"));
+                insertStmt.setString(17, rs.getString("color"));
+                insertStmt.setString(18, rs.getString("purchase_no"));
+                insertStmt.setString(19, rs.getString("purchase_name"));
+                insertStmt.setString(20, rs.getString("purchase_manager_no"));
+                insertStmt.setString(21, rs.getString("purchase_manager_name"));
+
+                Integer platform = rs.getObject("platform", Integer.class);
+                insertStmt.setObject(22, platform, Types.INTEGER);
+
+                // 时间字段:datetime2(7) -> LocalDateTime -> Timestamp
+                Timestamp created = rs.getTimestamp("created");
+                Timestamp modified = rs.getTimestamp("modify");
+
+                insertStmt.setTimestamp(23, created);
+                insertStmt.setTimestamp(24, modified);
+
+                insertStmt.addBatch();
+
+                if (++batchCount % 1000 == 0) {
+                    insertStmt.executeBatch();
+                    System.out.println("已迁移 " + batchCount + " 条记录...");
+                }
+            }
+
+            // 执行剩余批次
+            insertStmt.executeBatch();
+        }
+    }
+
+    /**
+    * 品牌迁移
+    * */
+    private static void migrateProductBrand(Connection source, Connection target) throws SQLException {
+        String selectSql = """
+            SELECT
+                id, brand_no, brand_name, brand_initials, brand_english_name,
+                recommend_value, brand_logo, brand_title, brand_big_image, brand_story,
+                is_show, brand_registrant, license, registration_certificate, expire_time,
+                created, modify, brand_describe, position, care, data_source
+            FROM product_brand
+            ORDER BY id
+            """;
+
+        String insertSql = """
+            INSERT INTO product_brand (
+                id, brand_no, brand_name, brand_initials, brand_english_name,
+                recommend_value, brand_logo, brand_title, brand_big_image, brand_story,
+                is_show, brand_registrant, license, registration_certificate, expire_time,
+                brand_describe, position, care, data_source,
+                tenant_id, del_flag, create_time, update_time
+            ) VALUES (
+                ?, ?, ?, ?, ?,
+                ?, ?, ?, ?, ?,
+                ?, ?, ?, ?, ?,
+                ?, ?, ?, ?,
+                '000000', '0', ?, ?
+            )
+            """;
+
+        try (PreparedStatement selectStmt = source.prepareStatement(selectSql);
+             ResultSet rs = selectStmt.executeQuery();
+             PreparedStatement insertStmt = target.prepareStatement(insertSql)) {
+
+            int count = 0;
+            while (rs.next()) {
+                insertStmt.setLong(1, rs.getInt("id"));
+                insertStmt.setString(2, rs.getString("brand_no"));
+                insertStmt.setString(3, rs.getString("brand_name"));
+                insertStmt.setString(4, rs.getString("brand_initials"));
+                insertStmt.setString(5, rs.getString("brand_english_name"));
+
+                insertStmt.setObject(6, rs.getObject("recommend_value"), Types.INTEGER);
+                insertStmt.setString(7, rs.getString("brand_logo"));
+                insertStmt.setString(8, rs.getString("brand_title"));
+                insertStmt.setString(9, rs.getString("brand_big_image"));
+                insertStmt.setString(10, rs.getString("brand_story"));
+
+                // is_show: 若为 NULL,则设为 0(符合 MySQL 默认逻辑)
+                Integer isShow = rs.getObject("is_show", Integer.class);
+                insertStmt.setInt(11, isShow != null ? isShow : 0);
+
+                insertStmt.setString(12, rs.getString("brand_registrant"));
+                insertStmt.setString(13, rs.getString("license"));
+                insertStmt.setString(14, rs.getString("registration_certificate"));
+
+                // 时间字段:datetime2(7) -> Timestamp
+                insertStmt.setTimestamp(15, rs.getTimestamp("expire_time"));
+                insertStmt.setString(16, rs.getString("brand_describe"));
+                insertStmt.setString(17, rs.getString("position"));
+
+                // care 是 NOT NULL,默认 0,但源表也有默认值,直接取
+                insertStmt.setInt(18, rs.getInt("care"));
+
+                insertStmt.setString(19, rs.getString("data_source"));
+
+                // create_time 和 update_time
+                insertStmt.setTimestamp(20, rs.getTimestamp("created"));
+                insertStmt.setTimestamp(21, rs.getTimestamp("modify"));
+
+                insertStmt.addBatch();
+
+                if (++count % 1000 == 0) {
+                    insertStmt.executeBatch();
+                    System.out.println("已迁移 " + count + " 条品牌记录...");
+                }
+            }
+
+            insertStmt.executeBatch();
+            System.out.println("总计迁移 " + count + " 条记录。");
+        }
+    }
+
+    private static void migrateProductUnit(Connection source, Connection target) throws SQLException {
+        String selectSql = "SELECT id, unit_no, unit_name, data_source, is_thow, created, modify FROM product_unit ORDER BY id";
+        String insertSql = "INSERT INTO product_unit (id, unit_no, unit_name, data_source, is_show, tenant_id, del_flag, create_time, update_time) VALUES (?, ?, ?, ?, ?, '000000', '0', ?, ?)";
+
+        try (PreparedStatement selectStmt = source.prepareStatement(selectSql);
+             PreparedStatement insertStmt = target.prepareStatement(insertSql)) {
+
+            ResultSet rs = selectStmt.executeQuery();
+
+            while (rs.next()) {
+                insertStmt.setLong(1, rs.getInt("id"));
+                insertStmt.setString(2, rs.getString("unit_no"));
+                insertStmt.setString(3, rs.getString("unit_name"));
+                insertStmt.setString(4, rs.getString("data_source"));
+
+                // 假设 is_thow 为 1 表示显示,否则不显示
+                int isThow = rs.getInt("is_thow");
+                insertStmt.setString(5, isThow == 1 ? "1" : "0");
+
+                // 时间字段
+                insertStmt.setTimestamp(6, rs.getTimestamp("created"));
+                insertStmt.setTimestamp(7, rs.getTimestamp("modify") != null ? rs.getTimestamp("modify") : null);
+
+                insertStmt.addBatch();
+
+                if (rs.getRow() % 1000 == 0) { // 每1000条执行一次批量插入
+                    insertStmt.executeBatch();
+                }
+            }
+            insertStmt.executeBatch(); // 执行剩余记录
+        }
+    }
+
+    // 在类顶部添加线程池常量(可配置)
+    private static final ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
+
+    public static void migrateData(Connection mssqlConn) throws SQLException, InterruptedException {
+        // 先获取总记录数(用于计算分页)
+        long totalRecords = getTotalRecordCount(mssqlConn);
+        System.out.println("📊 总记录数: " + totalRecords);
+
+        long offset = 0;
+        List<Future<?>> futures = new ArrayList<>();
+
+        while (offset < totalRecords) {
+            long currentOffset = offset; // 必须是 final 或 effectively final
+            Future<?> future = executor.submit(() -> {
+                try {
+                    processBatch(currentOffset, BATCH_SIZE);
+                } catch (Exception e) {
+                    System.err.println("❌ 批次 " + currentOffset + " 处理失败: " + e.getMessage());
+                    e.printStackTrace();
+                }
+            });
+            futures.add(future);
+            offset += BATCH_SIZE;
+        }
+
+        // 等待所有任务完成
+        for (Future<?> f : futures) {
+            try {
+                f.get(); // 抛出异常会中断
+            } catch (ExecutionException e) {
+                System.err.println("⚠️ 任务执行异常: " + e.getCause());
+            }
+        }
+
+        executor.shutdown();
+        System.out.println("✅ 所有 product_info 数据迁移完成!");
+    }
+
+    // 获取总记录数
+    private static long getTotalRecordCount(Connection conn) throws SQLException {
+        String countSql = "SELECT COUNT(*) FROM product_info";
+        try (Statement stmt = conn.createStatement();
+             ResultSet rs = stmt.executeQuery(countSql)) {
+            if (rs.next()) {
+                return rs.getLong(1);
+            }
+        }
+        return 0;
+    }
+
+    // 单个批次处理逻辑(每个线程执行)
+    private static void processBatch(long offset, int batchSize) throws SQLException {
+        // 每个线程独立连接 MySQL
+        try (Connection mysqlConn = DriverManager.getConnection(MYSQL_URL, MYSQL_USER, MYSQL_PASS)) {
+            mysqlConn.setAutoCommit(false);
+
+            // 准备语句
+            PreparedStatement baseStmt = prepareBaseInsert(mysqlConn);
+            PreparedStatement priceStmt = preparePriceInsert(mysqlConn);
+            PreparedStatement extendStmt = prepareExtendInsert(mysqlConn);
+
+            // 从 SQL Server 读取当前批次(注意:这里需要重新建立连接 or 传入?)
+            // 由于原 mssqlConn 是主线程的,不能共享,所以每个线程也需独立连接 SQL Server
+            try (Connection sqlConn = DriverManager.getConnection(SQL_SERVER_URL, SQL_SERVER_USER, SQL_SERVER_PASS)) {
+                String sql = "SELECT * FROM product_info ORDER BY id OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
+                try (PreparedStatement selectStmt = sqlConn.prepareStatement(sql)) {
+                    selectStmt.setLong(1, offset);
+                    selectStmt.setInt(2, batchSize);
+
+                    try (ResultSet rs = selectStmt.executeQuery()) {
+                        boolean hasData = false;
+                        while (rs.next()) {
+                            hasData = true;
+                            setBaseValues(baseStmt, rs);
+                            baseStmt.addBatch();
+
+                            setPriceValues(priceStmt, rs);
+                            priceStmt.addBatch();
+
+                            setExtendValues(extendStmt, rs);
+                            extendStmt.addBatch();
+                        }
+
+                        if (hasData) {
+                            baseStmt.executeBatch();
+                            priceStmt.executeBatch();
+                            extendStmt.executeBatch();
+                            mysqlConn.commit();
+                            System.out.println("✅ 线程 " + Thread.currentThread().getName() +
+                                " 完成批次 offset=" + offset + ",迁移 " + batchSize + " 条");
+                        }
+                    }
+                }
+            } finally {
+                // 关闭所有 PreparedStatement
+                try { baseStmt.close(); } catch (Exception ignored) {}
+                try { priceStmt.close(); } catch (Exception ignored) {}
+                try { extendStmt.close(); } catch (Exception ignored) {}
+            }
+        }
+    }
+
+    // === product_base 插入准备 ===
+    private static PreparedStatement prepareBaseInsert(Connection conn) throws SQLException {
+        String sql = """
+            INSERT INTO product_base (
+                id, product_no, item_name,
+                brand_id, top_category_id, medium_category_id, bottom_category_id,
+                unit_id, product_image,
+                is_self, product_review_status,
+                home_recommended, category_recommendation, cart_recommendation,
+                recommended_product_order, is_popular, is_new, product_status,
+                data_source, tenant_id, del_flag,
+                create_time, update_time
+            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+            """;
+        return conn.prepareStatement(sql);
+    }
+
+    private static void setBaseValues(PreparedStatement stmt, ResultSet rs) throws SQLException {
+        long newId = rs.getLong("id"); // 注意:MySQL 用 BIGINT,SQL Server 是 INT,但值兼容
+        stmt.setLong(1, newId);
+        stmt.setString(2, rs.getString("product_no"));
+        stmt.setString(3, rs.getString("item_name"));
+
+        // 注意:brand_no / cate_no 是字符串,但目标表是 bigint(ID)。这里假设你后续会通过字典映射。
+        // 如果没有映射关系,暂时设为 NULL 或 0。你可以根据实际业务替换为 lookupBrandId(rs.getString("brand_no")) 等。
+// 改为:
+        String brandNo = rs.getString("brand_no");
+        stmt.setObject(4, brandNoToId.get(brandNo), Types.BIGINT);
+
+        String topCateNo = rs.getString("top_cate_no");
+        stmt.setObject(5, categoryNoToId.get(topCateNo), Types.BIGINT);
+
+        String mediumCateNo = rs.getString("medium_cate_no");
+        stmt.setObject(6, categoryNoToId.get(mediumCateNo), Types.BIGINT);
+
+        String bottomCateNo = rs.getString("bottom_cate_no");
+        stmt.setObject(7, categoryNoToId.get(bottomCateNo), Types.BIGINT);
+
+        String unitNo = rs.getString("unit_no");
+        Long unitId = unitNoToId.get(unitNo);
+        stmt.setObject(8, unitId, Types.BIGINT);
+        stmt.setString(9, rs.getString("product_image"));
+
+        stmt.setString(10, intToFlag(rs.getInt("IsSelf")));
+        stmt.setString(11, String.valueOf(rs.getInt("product_review_status")));
+
+        stmt.setString(12, intToFlag(rs.getInt("home_recommended")));
+        stmt.setString(13, intToFlag(rs.getInt("category_recommendation")));
+        stmt.setString(14, intToFlag(rs.getInt("cart_recommendation")));
+
+        stmt.setInt(15, rs.getInt("recommended_product_order"));
+        stmt.setString(16, intToFlag(rs.getInt("is_popular")));
+        stmt.setString(17, intToFlag(rs.getObject("is_new") == null ? 0 : rs.getInt("is_new")));
+        stmt.setString(18, rs.getObject("product_status") == null ? "0" : String.valueOf(rs.getInt("product_status")));
+
+        stmt.setString(19, rs.getString("data_source") == null ? "youyi" : rs.getString("data_source"));
+        stmt.setString(20, "000000"); // tenant_id
+        stmt.setString(21, "0");      // del_flag
+
+        stmt.setTimestamp(22, rs.getTimestamp("created"));
+        stmt.setTimestamp(23, rs.getTimestamp("modify"));
+    }
+
+    // === product_price_inventory 插入准备 ===
+    private static PreparedStatement preparePriceInsert(Connection conn) throws SQLException {
+        String sql = """
+            INSERT INTO product_price_inventory (
+                product_id,
+                market_price, member_price, min_selling_price,
+                purchasing_price, max_purchase_price,
+                total_inventory, now_inventory, virtual_inventory,
+                min_order_quantity, tax_rate, currency,
+                tenant_id, del_flag, create_time, update_time
+            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+            """;
+        return conn.prepareStatement(sql);
+    }
+
+    private static void setPriceValues(PreparedStatement stmt, ResultSet rs) throws SQLException {
+        long productId = rs.getLong("id");
+        stmt.setLong(1, productId);
+
+        stmt.setBigDecimal(2, toDecimal(rs.getString("market_price")));
+        stmt.setBigDecimal(3, toDecimal(rs.getString("member_price")));
+        stmt.setBigDecimal(4, toDecimal(rs.getString("min_selling_price")));
+        stmt.setBigDecimal(5, toDecimal(rs.getString("purchasing_price")));
+        stmt.setBigDecimal(6, toDecimal(rs.getString("max_purchase_price")));
+
+        stmt.setInt(7, rs.getInt("total_inventory"));
+        stmt.setInt(8, rs.getInt("now_inventory"));
+        stmt.setInt(9, rs.getInt("virtual_inventory"));
+
+        stmt.setInt(10, safeInt(rs.getString("min_order_quantity"), 1));
+        stmt.setBigDecimal(11, toDecimalPercent(rs.getString("tax_rate"))); // 如 "13" → 13.00%
+        stmt.setString(12, rs.getString("currency") == null ? "CNY" : rs.getString("currency"));
+
+        stmt.setString(13, "000000");
+        stmt.setString(14, "0");
+        stmt.setTimestamp(15, rs.getTimestamp("created"));
+        stmt.setTimestamp(16, rs.getTimestamp("modify"));
+    }
+
+    // === product_extend 插入准备 ===
+    private static PreparedStatement prepareExtendInsert(Connection conn) throws SQLException {
+        String sql = """
+            INSERT INTO product_extend (
+                product_id,
+                promotion_title, invoice_name, invoice_type, specifications_code,
+                bar_coding, product_description,
+                product_weight, weight_unit, product_volume, volume_unit,
+                after_sales_service, service_guarantee,
+                is_install_service, install_amount, distribution_price,
+                standard_sizes, gram_weight, opacity,
+                is_customize, custom_description,
+                product_profit, report_require, review_comments,
+                supplier_no, push_status, invoice_specs,
+                increment, purchase_no, purchase_name, supplier_name,
+                purchase_manager_no, purchase_manager_name,
+                reference_link, sales_volume, delivery_time, pit_time,
+                data_source, create_supplier, other_info,
+                tenant_id, del_flag, create_time, update_time
+            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+            """;
+        return conn.prepareStatement(sql);
+    }
+
+    private static void setExtendValues(PreparedStatement stmt, ResultSet rs) throws SQLException {
+        long productId = rs.getLong("id");
+        stmt.setLong(1, productId);
+
+        stmt.setString(2, rs.getString("promotion_title"));
+        stmt.setString(3, rs.getString("invoice_name"));
+        stmt.setString(4, rs.getString("invoice_type"));
+        stmt.setString(5, rs.getString("specifications_code"));
+        stmt.setString(6, rs.getString("bar_coding"));
+        stmt.setString(7, rs.getString("product_description"));
+        stmt.setString(8, rs.getString("product_weight"));
+        stmt.setString(9, rs.getString("weight_unit"));
+        stmt.setString(10, rs.getString("product_volume"));
+        stmt.setString(11, rs.getString("volume_unit"));
+        stmt.setString(12, rs.getString("After_sales_service")); // 注意大小写
+        stmt.setString(13, rs.getString("service_guarantee"));
+
+        stmt.setString(14, intToFlag(rs.getInt("is_install_service")));
+        stmt.setString(15, rs.getString("install_amount"));
+        stmt.setString(16, rs.getString("distribution_price"));
+
+        stmt.setString(17, rs.getString("standard_sizes"));
+        stmt.setString(18, rs.getString("gram_weight"));
+        stmt.setString(19, rs.getString("opacity"));
+
+        stmt.setString(20, intToFlag(rs.getInt("is_customize")));
+        stmt.setString(21, rs.getString("custom_description"));
+
+        stmt.setString(22, rs.getString("product_profit"));
+        stmt.setString(23, rs.getString("report_require"));
+        stmt.setString(24, rs.getString("review_comments"));
+
+        stmt.setString(25, rs.getString("supplier_no"));
+        stmt.setString(26, rs.getObject("push_status") == null ? null : String.valueOf(rs.getInt("push_status")));
+        stmt.setString(27, rs.getString("invoice_specs"));
+
+        stmt.setObject(28, rs.getObject("increment"));
+        stmt.setString(29, rs.getString("purchase_no"));
+        stmt.setString(30, rs.getString("purchase_name"));
+        stmt.setString(31, rs.getString("supplier_name"));
+        stmt.setString(32, rs.getString("purchase_manager_no"));
+        stmt.setString(33, rs.getString("purchase_manager_name"));
+        stmt.setString(34, rs.getString("ReferenceLink")); // 注意大小写
+        stmt.setObject(35, rs.getObject("SalesVolume"));   // 可能为 null
+        stmt.setString(36, rs.getString("DeliveryTime"));
+        stmt.setTimestamp(37, rs.getTimestamp("PitTime"));
+
+        stmt.setString(38, rs.getString("data_source"));
+        stmt.setString(39, rs.getString("CreateSupplier"));
+        stmt.setString(40, rs.getString("Other"));
+
+        stmt.setString(41, "000000");
+        stmt.setString(42, "0");
+        stmt.setTimestamp(43, rs.getTimestamp("created"));
+        stmt.setTimestamp(44, rs.getTimestamp("modify"));
+    }
+
+    // === 工具方法 ===
+
+    private static String intToFlag(int value) {
+        return value == 1 ? "1" : "0";
+    }
+
+    private static BigDecimal toDecimal(String str) {
+        if (str == null || str.trim().isEmpty()) return null;
+        try {
+            return new BigDecimal(str.trim());
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    private static BigDecimal toDecimalPercent(String str) {
+        // 假设 tax_rate 是 "13" 表示 13%,存为 13.00
+        BigDecimal bd = toDecimal(str);
+        return bd; // 如果需要百分比小数(如 0.13),则除以 100
+    }
+
+    private static int safeInt(String str, int defaultValue) {
+        if (str == null || str.trim().isEmpty()) return defaultValue;
+        try {
+            return Integer.parseInt(str.trim());
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    // 在 ProductMigration 类中添加以下字段
+    private static Map<String, Long> brandNoToId = new HashMap<>();
+    private static Map<String, Long> categoryNoToId = new HashMap<>();
+    private static Map<String, Long> unitNoToId = new HashMap<>();
+
+    private static void loadMappings(Connection mysqlConn) throws SQLException {
+        System.out.println("🔄 正在加载品牌、分类、单位映射...");
+
+        // 加载品牌
+        try (PreparedStatement stmt = mysqlConn.prepareStatement("SELECT brand_no, id FROM product_brand WHERE del_flag = '0'")) {
+            ResultSet rs = stmt.executeQuery();
+            while (rs.next()) {
+                brandNoToId.put(rs.getString("brand_no"), rs.getLong("id"));
+            }
+        }
+
+        // 加载分类(所有层级)
+        try (PreparedStatement stmt = mysqlConn.prepareStatement("SELECT category_no, id FROM product_category WHERE del_flag = '0'")) {
+            ResultSet rs = stmt.executeQuery();
+            while (rs.next()) {
+                categoryNoToId.put(rs.getString("category_no"), rs.getLong("id"));
+            }
+        }
+
+        // 加载单位
+        try (PreparedStatement stmt = mysqlConn.prepareStatement("SELECT unit_no, id FROM product_unit WHERE del_flag = '0'")) {
+            ResultSet rs = stmt.executeQuery();
+            while (rs.next()) {
+                unitNoToId.put(rs.getString("unit_no"), rs.getLong("id"));
+            }
+        }
+
+        System.out.println("✅ 品牌映射数量: " + brandNoToId.size());
+        System.out.println("✅ 分类映射数量: " + categoryNoToId.size());
+        System.out.println("✅ 单位映射数量: " + unitNoToId.size());
+    }
+
+
+    // 加载 product_no -> product_base.id 映射
+    private static void loadProductNoMapping() throws SQLException {
+        try (Connection mysqlConn = DriverManager.getConnection(MYSQL_URL, MYSQL_USER, MYSQL_PASS)) {
+            productNoToId = new ConcurrentHashMap<>();
+            String sql = "SELECT product_no, id FROM product_base WHERE del_flag = '0'";
+            try (PreparedStatement stmt = mysqlConn.prepareStatement(sql);
+                 ResultSet rs = stmt.executeQuery()) {
+                while (rs.next()) {
+                    productNoToId.put(rs.getString("product_no"), rs.getLong("id"));
+                }
+            }
+        }
+    }
+
+    private static RangeInfo getMssqlIdRange() throws SQLException {
+        try (Connection conn = DriverManager.getConnection(SQL_SERVER_URL, SQL_SERVER_USER, SQL_SERVER_PASS);
+             Statement stmt = conn.createStatement();
+             ResultSet rs = stmt.executeQuery("SELECT MIN(id) min_id, MAX(id) max_id, COUNT(*) total FROM product_photos")) {
+            if (rs.next()) {
+                return new RangeInfo(rs.getLong("min_id"), rs.getLong("max_id"), rs.getLong("total"));
+            }
+            throw new RuntimeException("无法获取 ID 范围");
+        }
+    }
+
+    private static List<TaskRange> splitTasks(long minId, long maxId, int pageSize) {
+        List<TaskRange> tasks = new ArrayList<>();
+        for (long start = minId; start <= maxId; start += pageSize) {
+            long end = Math.min(start + pageSize - 1, maxId);
+            tasks.add(new TaskRange(start, end));
+        }
+        return tasks;
+    }
+
+    // --- 内部类 ---
+    static class RangeInfo {
+        final long minId, maxId, totalCount;
+        RangeInfo(long minId, long maxId, long totalCount) {
+            this.minId = minId;
+            this.maxId = maxId;
+            this.totalCount = totalCount;
+        }
+    }
+
+    static class TaskRange {
+        final long startId, endId;
+        TaskRange(long startId, long endId) {
+            this.startId = startId;
+            this.endId = endId;
+        }
+    }
+
+    // --- 迁移任务 ---
+    static class PhotoMigrationTask implements Callable<Long> {
+        private final TaskRange range;
+
+        PhotoMigrationTask(TaskRange range) {
+            this.range = range;
+        }
+
+        @Override
+        public Long call() throws Exception {
+            Thread.currentThread().setName("PhotoThread-" + range.startId);
+            long count = 0;
+
+            try (
+                Connection mssql = DriverManager.getConnection(SQL_SERVER_URL, SQL_SERVER_USER, SQL_SERVER_PASS);
+                Connection mysql = DriverManager.getConnection(MYSQL_URL, MYSQL_USER, MYSQL_PASS)
+            ) {
+                mysql.setAutoCommit(false);
+
+                String selectSql = """
+                    SELECT id, image_no, product_no, image_url,
+                           product_details_pc, product_details_app, created, modify, spec
+                    FROM product_photos
+                    WHERE id BETWEEN ? AND ?
+                    ORDER BY id
+                    """;
+
+                try (PreparedStatement sel = mssql.prepareStatement(selectSql)) {
+                    sel.setLong(1, range.startId);
+                    sel.setLong(2, range.endId);
+                    ResultSet rs = sel.executeQuery();
+
+                    String insertSql = """
+                        INSERT INTO product_photos (
+                            product_id,image_no , product_no, image_url,
+                            product_details_pc, product_details_app, spec,
+                            tenant_id, status, del_flag,
+                            create_time, update_time, platform_code
+                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                        """;
+
+                    PreparedStatement ins = mysql.prepareStatement(insertSql);
+
+                    while (rs.next()) {
+                        int idx = 1;
+                        String productNo = rs.getString("product_no");
+                        Long productId = productNoToId.get(productNo);
+                        ins.setObject(idx++, productId, Types.BIGINT); // 可能为 null
+                        ins.setString(idx++, rs.getString("image_no"));
+                        // 关键:通过 product_no 查 product_id
+                        ins.setString(idx++, productNo);
+                        ins.setString(idx++, rs.getString("image_url"));
+                        ins.setString(idx++, rs.getString("product_details_pc"));
+                        ins.setString(idx++, rs.getString("product_details_app"));
+                        ins.setString(idx++, rs.getString("spec"));
+
+                        // 新增字段设默认值
+                        ins.setString(idx++, "000000"); // tenant_id
+                        ins.setString(idx++, "0");      // status
+                        ins.setString(idx++, "0");      // del_flag
+
+                        Timestamp createdTime = rs.getTimestamp("created");
+                        if (createdTime == null) {
+                            createdTime = new Timestamp(System.currentTimeMillis());
+                        }
+                        ins.setTimestamp(idx++, createdTime);
+                        
+                        Timestamp modifyTime = rs.getTimestamp("modify");
+                        if (modifyTime == null) {
+                            modifyTime = new Timestamp(System.currentTimeMillis());
+                        }
+                        ins.setTimestamp(idx++, modifyTime);
+                        
+                        ins.setString(idx++, null);     // platform_code(若源表无,可设 null 或固定值)
+
+                        ins.addBatch();
+                        count++;
+
+                        if (count % BATCH_SIZE == 0) {
+                            ins.executeBatch();
+                            mysql.commit();
+                            ins.clearBatch();
+                        }
+                    }
+
+                    if (count % BATCH_SIZE != 0) {
+                        ins.executeBatch();
+                        mysql.commit();
+                    }
+                }
+            }
+            System.out.println(Thread.currentThread().getName() + " 完成,迁移 " + count + " 条图片记录");
+            return count;
+        }
+    }
+
+
+}

+ 36 - 0
ruoyi-modules/ruoyi-external/src/main/resources/application.yml

@@ -0,0 +1,36 @@
+# Tomcat
+server:
+  port: 9618
+
+# Spring
+spring:
+  application:
+    # 应用名称
+    name: ruoyi-external
+  profiles:
+    # 环境配置
+    active: @profiles.active@
+# 👇 新增:允许 Bean 定义覆盖(解决 Filter/Bean 冲突)
+  main:
+    allow-bean-definition-overriding: true
+--- # nacos 配置
+spring:
+  cloud:
+    nacos:
+      # nacos 服务地址
+      server-addr: @nacos.server@
+      username: @nacos.username@
+      password: @nacos.password@
+      discovery:
+        # 注册组
+        group: @nacos.discovery.group@
+        namespace: ${spring.profiles.active}
+      config:
+        # 配置组
+        group: @nacos.config.group@
+        namespace: ${spring.profiles.active}
+  config:
+    import:
+      - optional:nacos:application-common.yml
+      - optional:nacos:datasource.yml
+      - optional:nacos:${spring.application.name}.yml

+ 2 - 0
ruoyi-modules/ruoyi-external/src/main/resources/banner.txt

@@ -0,0 +1,2 @@
+Spring Boot Version: ${spring-boot.version}
+Spring Application Name: ${spring.application.name}

+ 28 - 0
ruoyi-modules/ruoyi-external/src/main/resources/logback-plus.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="logs/${project.artifactId}" />
+   <!-- 日志输出格式 -->
+    <property name="console.log.pattern"
+              value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${console.log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+    </appender>
+
+    <include resource="logback-common.xml" />
+
+    <include resource="logback-logstash.xml" />
+
+    <!-- 开启 skywalking 日志收集 -->
+    <include resource="logback-skylog.xml" />
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="console" />
+    </root>
+</configuration>

+ 7 - 0
ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalItemMapper.xml

@@ -0,0 +1,7 @@
+<?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">
+<mapper namespace="org.dromara.external.mapper.ExternalItemMapper">
+
+</mapper>

+ 7 - 0
ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalProductBrandMapper.xml

@@ -0,0 +1,7 @@
+<?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">
+<mapper namespace="org.dromara.external.mapper.ExternalProductBrandMapper">
+
+</mapper>

+ 7 - 0
ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalProductCategoryMapper.xml

@@ -0,0 +1,7 @@
+<?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">
+<mapper namespace="org.dromara.external.mapper.ExternalProductCategoryMapper">
+
+</mapper>

+ 7 - 0
ruoyi-modules/ruoyi-external/src/main/resources/mapper/external/ExternalPushPoolLogMapper.xml

@@ -0,0 +1,7 @@
+<?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">
+<mapper namespace="org.dromara.external.mapper.ExternalPushPoolLogMapper">
+
+</mapper>

+ 5 - 0
ruoyi-modules/ruoyi-product/pom.xml

@@ -104,6 +104,11 @@
             <artifactId>ruoyi-api-resource</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-product</artifactId>
+        </dependency>
+
 
     </dependencies>
 

+ 106 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductPhotosController.java

@@ -0,0 +1,106 @@
+package org.dromara.product.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.product.domain.vo.ProductPhotosVo;
+import org.dromara.product.domain.bo.ProductPhotosBo;
+import org.dromara.product.service.IProductPhotosService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 产品图片及详情
+ * 前端访问路由地址为:/product/photos
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/photos")
+public class ProductPhotosController extends BaseController {
+
+    private final IProductPhotosService productPhotosService;
+
+    /**
+     * 查询产品图片及详情列表
+     */
+    @SaCheckPermission("product:photos:list")
+    @GetMapping("/list")
+    public TableDataInfo<ProductPhotosVo> list(ProductPhotosBo bo, PageQuery pageQuery) {
+        return productPhotosService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出产品图片及详情列表
+     */
+    @SaCheckPermission("product:photos:export")
+    @Log(title = "产品图片及详情", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ProductPhotosBo bo, HttpServletResponse response) {
+        List<ProductPhotosVo> list = productPhotosService.queryList(bo);
+        ExcelUtil.exportExcel(list, "产品图片及详情", ProductPhotosVo.class, response);
+    }
+
+    /**
+     * 获取产品图片及详情详细信息
+     *
+     * @param productId 主键
+     */
+    @SaCheckPermission("product:photos:query")
+    @GetMapping("/{productId}")
+    public R<ProductPhotosVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("productId") Long productId) {
+        return R.ok(productPhotosService.queryById(productId));
+    }
+
+    /**
+     * 新增产品图片及详情
+     */
+    @SaCheckPermission("product:photos:add")
+    @Log(title = "产品图片及详情", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ProductPhotosBo bo) {
+        return toAjax(productPhotosService.insertByBo(bo));
+    }
+
+    /**
+     * 修改产品图片及详情
+     */
+    @SaCheckPermission("product:photos:edit")
+    @Log(title = "产品图片及详情", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ProductPhotosBo bo) {
+        return toAjax(productPhotosService.updateByBo(bo));
+    }
+
+    /**
+     * 删除产品图片及详情
+     *
+     * @param productIds 主键串
+     */
+    @SaCheckPermission("product:photos:remove")
+    @Log(title = "产品图片及详情", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{productIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("productIds") Long[] productIds) {
+        return toAjax(productPhotosService.deleteWithValidByIds(List.of(productIds), true));
+    }
+}

+ 1 - 1
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductBase.java

@@ -75,7 +75,7 @@ public class ProductBase extends TenantEntity {
     private String isSelf;
 
     /**
-     * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+     * 产品审核状态 0=待采购审核,1=审核通过,2=驳回,3=待营销审核
      */
     private String productReviewStatus;
 

+ 77 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductPhotos.java

@@ -0,0 +1,77 @@
+package org.dromara.product.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 产品图片及详情对象 product_photos
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("product_photos")
+public class ProductPhotos extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "product_id")
+    private Long productId;
+
+    /**
+     * 图片编号
+     */
+    private String imageNo;
+
+    /**
+     * 产品编号
+     */
+    private String productNo;
+
+    /**
+     * 图片URL地址
+     */
+    private String imageUrl;
+
+    /**
+     * PC端产品详情(HTML或富文本)
+     */
+    private String productDetailsPc;
+
+    /**
+     * APP端产品详情(HTML或富文本)
+     */
+    private String productDetailsApp;
+
+    /**
+     * 规格标识(如颜色、尺寸等)
+     */
+    private String spec;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 19 - 1
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductBaseBo.java

@@ -29,6 +29,11 @@ public class ProductBaseBo extends BaseEntity {
      */
     private Long id;
 
+    /**
+    * 多个商品
+    * */
+    private String productIds;
+
     /**
      * 产品编号
      */
@@ -81,7 +86,7 @@ public class ProductBaseBo extends BaseEntity {
     private String isSelf;
 
     /**
-     * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
+     * 产品审核状态 0=待采购审核,1=审核通过,2=驳回,3=待营销审核
      */
     private String productReviewStatus;
 
@@ -244,6 +249,19 @@ public class ProductBaseBo extends BaseEntity {
      * 暂估采购价
      */
     private java.math.BigDecimal estimatedPurchasePrice;
+    /**
+    * 总库存
+    * */
+    private Long totalInventory;
+    /**
+     * 当前可用库存
+     * */
+    private Long nowInventory;
+
+    /**
+    * 虚拟库存
+    * */
+    private Long virtualInventory;
 
     /**
      * 产品性质

+ 77 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductPhotosBo.java

@@ -0,0 +1,77 @@
+package org.dromara.product.domain.bo;
+
+import org.dromara.product.domain.ProductPhotos;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 产品图片及详情业务对象 product_photos
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProductPhotos.class, reverseConvertGenerate = false)
+public class ProductPhotosBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long productId;
+
+    /**
+     * 图片编号
+     */
+    @NotBlank(message = "图片编号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String imageNo;
+
+    /**
+     * 产品编号
+     */
+    @NotBlank(message = "产品编号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String productNo;
+
+    /**
+     * 图片URL地址
+     */
+    @NotBlank(message = "图片URL地址不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String imageUrl;
+
+    /**
+     * PC端产品详情(HTML或富文本)
+     */
+    @NotBlank(message = "PC端产品详情(HTML或富文本)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String productDetailsPc;
+
+    /**
+     * APP端产品详情(HTML或富文本)
+     */
+    @NotBlank(message = "APP端产品详情(HTML或富文本)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String productDetailsApp;
+
+    /**
+     * 规格标识(如颜色、尺寸等)
+     */
+    @NotBlank(message = "规格标识(如颜色、尺寸等)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String spec;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 25 - 7
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBaseVo.java

@@ -57,7 +57,6 @@ public class ProductBaseVo implements Serializable {
     /**
      * 品牌名称
      */
-    @Translation(type = "brand_id_to_name", mapper = "brandId")
     private String brandName;
 
     /**
@@ -78,23 +77,28 @@ public class ProductBaseVo implements Serializable {
     @ExcelProperty(value = "底层分类id")
     private Long bottomCategoryId;
 
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+
     /**
      * 单位id
      */
     @ExcelProperty(value = "单位id")
     private String unitId;
 
+    /**
+     * 单位名称(如:件、箱、千克等)
+     */
+    private String unitName;
+
     /**
      * 产品图片URL
      */
     @ExcelProperty(value = "产品图片URL")
     private String productImage;
 
-    /**
-     * 产品图片URLUrl
-     */
-    @Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "productImage")
-    private String productImageUrl;
     /**
      * 是否自营(1=是,0=否)
      */
@@ -105,7 +109,7 @@ public class ProductBaseVo implements Serializable {
     /**
      * 产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回
      */
-    @ExcelProperty(value = "产品审核状态 0=待提交,1=待审核,2=审核通过,3=审核驳回")
+    @ExcelProperty(value = "产品审核状态 0=待采购审核,1=审核通过,2=驳回,3=待营销审核")
     private String productReviewStatus;
 
     /**
@@ -374,6 +378,20 @@ public class ProductBaseVo implements Serializable {
     @ExcelProperty(value = "最低起订量")
     private Long minOrderQuantity;
 
+    /**
+     * 总库存
+     * */
+    private Long totalInventory;
+    /**
+     * 当前可用库存
+     * */
+    private Long nowInventory;
+
+    /**
+     * 虚拟库存
+     * */
+    private Long virtualInventory;
+
     /**
      * 是否可定制
      */

+ 90 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductPhotosVo.java

@@ -0,0 +1,90 @@
+package org.dromara.product.domain.vo;
+
+import org.dromara.product.domain.ProductPhotos;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 产品图片及详情视图对象 product_photos
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProductPhotos.class)
+public class ProductPhotosVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long productId;
+
+    /**
+     * 图片编号
+     */
+    @ExcelProperty(value = "图片编号")
+    private String imageNo;
+
+    /**
+     * 产品编号
+     */
+    @ExcelProperty(value = "产品编号")
+    private String productNo;
+
+    /**
+     * 图片URL地址
+     */
+    @ExcelProperty(value = "图片URL地址")
+    private String imageUrl;
+
+    /**
+     * PC端产品详情(HTML或富文本)
+     */
+    @ExcelProperty(value = "PC端产品详情", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "H=TML或富文本")
+    private String productDetailsPc;
+
+    /**
+     * APP端产品详情(HTML或富文本)
+     */
+    @ExcelProperty(value = "APP端产品详情", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "H=TML或富文本")
+    private String productDetailsApp;
+
+    /**
+     * 规格标识(如颜色、尺寸等)
+     */
+    @ExcelProperty(value = "规格标识", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "如=颜色、尺寸等")
+    private String spec;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 88 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/dubbo/RemoteProductServiceImpl.java

@@ -0,0 +1,88 @@
+package org.dromara.product.dubbo;
+
+import cn.hutool.core.bean.BeanUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.dromara.product.api.RemoteProductService;
+import org.dromara.product.api.domain.ProductChangeLogVo;
+import org.dromara.product.api.domain.ProductVo;
+import org.dromara.product.domain.bo.ProductBaseBo;
+import org.dromara.product.domain.bo.ProductChangeLogBo;
+import org.dromara.product.domain.vo.ProductBaseVo;
+import org.dromara.product.service.IProductBaseService;
+import org.dromara.product.service.IProductChangeLogService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 产品基础信息服务
+ * @author
+ * @date 2025/12/29 下午5:20
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@DubboService
+public class RemoteProductServiceImpl implements RemoteProductService {
+
+    private final IProductBaseService productBaseService;
+
+    private final IProductChangeLogService productChangeLogService;
+
+    /**
+     * 获取商品详情
+     *
+     * @param productId
+     */
+    @Override
+    public ProductVo getProductDetail(Long productId) {
+        ProductBaseVo productBaseVo = productBaseService.queryById(productId);
+        return BeanUtil.toBean(productBaseVo, ProductVo.class);
+    }
+
+    /**
+     * 商品上下架
+     *
+     * @param productId  商品id
+     */
+    @Override
+    public ProductVo updateProductShelfState(Long productId) {
+        ProductBaseVo productBaseVo = productBaseService.updateProductShelfState(productId);
+        return BeanUtil.toBean(productBaseVo, ProductVo.class);
+    }
+
+    /**
+     * 获取多个商品详情
+     *
+     * @param productIds
+     */
+    @Override
+    public List<ProductVo> getProductDetails(String productIds) {
+        ProductBaseBo productBaseBo = new ProductBaseBo();
+        productBaseBo.setProductIds(productIds);
+        List<ProductBaseVo> productBaseVos = productBaseService.queryList(productBaseBo);
+        return BeanUtil.copyToList(productBaseVos, ProductVo.class);
+    }
+
+    /**
+     * 获取商品变更记录
+     *
+     * @param itemId
+     * @param type
+     */
+    @Override
+    public List<ProductChangeLogVo> getProductChangeLogs(Long itemId, String type) {
+        ProductChangeLogBo productChangeLogBo = new ProductChangeLogBo();
+        productChangeLogBo.setItemId(itemId);
+        productChangeLogBo.setType(type);
+        List<org.dromara.product.domain.vo.ProductChangeLogVo> productChangeLogVos = productChangeLogService.queryList(productChangeLogBo);
+        return BeanUtil.copyToList(productChangeLogVos, ProductChangeLogVo.class);
+    }
+
+    @Override
+    public void delMessagePool(Long messageId) {
+        productBaseService.removeById(messageId);
+    }
+}

+ 15 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductPhotosMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.product.mapper;
+
+import org.dromara.product.domain.ProductPhotos;
+import org.dromara.product.domain.vo.ProductPhotosVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 产品图片及详情Mapper接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface ProductPhotosMapper extends BaseMapperPlus<ProductPhotos, ProductPhotosVo> {
+
+}

+ 7 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductBaseService.java

@@ -78,4 +78,11 @@ public interface IProductBaseService extends IService<ProductBase>{
      * @return 站点产品分页列表
      */
     TableDataInfo<SiteProductVo> querySiteProductPageList(SiteProductBo bo, PageQuery pageQuery);
+
+    /**
+     * 修改商品上架状态
+     *
+     * @param productId 商品id
+     */
+    ProductBaseVo updateProductShelfState(Long productId);
 }

+ 70 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductPhotosService.java

@@ -0,0 +1,70 @@
+package org.dromara.product.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.product.domain.ProductPhotos;
+import org.dromara.product.domain.vo.ProductPhotosVo;
+import org.dromara.product.domain.bo.ProductPhotosBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 产品图片及详情Service接口
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+public interface IProductPhotosService extends IService<ProductPhotos>{
+
+    /**
+     * 查询产品图片及详情
+     *
+     * @param productId 主键
+     * @return 产品图片及详情
+     */
+    ProductPhotosVo queryById(Long productId);
+
+    /**
+     * 分页查询产品图片及详情列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品图片及详情分页列表
+     */
+    TableDataInfo<ProductPhotosVo> queryPageList(ProductPhotosBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的产品图片及详情列表
+     *
+     * @param bo 查询条件
+     * @return 产品图片及详情列表
+     */
+    List<ProductPhotosVo> queryList(ProductPhotosBo bo);
+
+    /**
+     * 新增产品图片及详情
+     *
+     * @param bo 产品图片及详情
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ProductPhotosBo bo);
+
+    /**
+     * 修改产品图片及详情
+     *
+     * @param bo 产品图片及详情
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ProductPhotosBo bo);
+
+    /**
+     * 校验并批量删除产品图片及详情信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 26 - 2
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java

@@ -1,6 +1,8 @@
 package org.dromara.product.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.apache.dubbo.common.logger.FluentLogger;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -10,7 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.dromara.product.domain.ProductPriceInventory;
+import org.dromara.product.api.domain.ProductVo;
+import org.dromara.product.domain.*;
 import org.dromara.product.mapper.*;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -134,7 +137,7 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
             }
             vo.setCustomizedStyle(styles.toString());
             vo.setCustomizedCraft(crafts.toString());
-            
+
             // 将定制详情列表转换为JSON字符串
             try {
                 ObjectMapper objectMapper = new ObjectMapper();
@@ -656,4 +659,25 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
         Page<SiteProductVo> page = baseMapper.selectSiteProductPage(pageQuery.build(), bo);
         return TableDataInfo.build(page);
     }
+
+    /**
+     * 修改商品上架状态
+     *
+     * @param productId  商品id
+     */
+    @Override
+    public ProductBaseVo updateProductShelfState(Long productId) {
+        ProductBaseVo productBase = baseMapper.selectVoById(productId);
+
+        if(productBase.getProductStatus().equals("1")){
+            productBase.setProductStatus("0");
+        }else{
+            productBase.setProductStatus("1");
+        }
+        baseMapper.update(Wrappers.lambdaUpdate(ProductBase.class)
+            .set(ProductBase::getProductStatus, productBase.getProductStatus())
+            .eq(ProductBase::getId, productId)
+        );
+        return productBase;
+    }
 }

+ 140 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductPhotosServiceImpl.java

@@ -0,0 +1,140 @@
+package org.dromara.product.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.product.domain.bo.ProductPhotosBo;
+import org.dromara.product.domain.vo.ProductPhotosVo;
+import org.dromara.product.domain.ProductPhotos;
+import org.dromara.product.mapper.ProductPhotosMapper;
+import org.dromara.product.service.IProductPhotosService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 产品图片及详情Service业务层处理
+ *
+ * @author LionLi
+ * @date 2025-12-29
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProductPhotosServiceImpl  extends ServiceImpl<ProductPhotosMapper, ProductPhotos> implements IProductPhotosService {
+
+    private final ProductPhotosMapper baseMapper;
+
+    /**
+     * 查询产品图片及详情
+     *
+     * @param productId 主键
+     * @return 产品图片及详情
+     */
+    @Override
+    public ProductPhotosVo queryById(Long productId){
+        return baseMapper.selectVoById(productId);
+    }
+
+    /**
+     * 分页查询产品图片及详情列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品图片及详情分页列表
+     */
+    @Override
+    public TableDataInfo<ProductPhotosVo> queryPageList(ProductPhotosBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ProductPhotos> lqw = buildQueryWrapper(bo);
+        Page<ProductPhotosVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的产品图片及详情列表
+     *
+     * @param bo 查询条件
+     * @return 产品图片及详情列表
+     */
+    @Override
+    public List<ProductPhotosVo> queryList(ProductPhotosBo bo) {
+        LambdaQueryWrapper<ProductPhotos> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ProductPhotos> buildQueryWrapper(ProductPhotosBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProductPhotos> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProductPhotos::getProductId);
+        lqw.eq(StringUtils.isNotBlank(bo.getImageNo()), ProductPhotos::getImageNo, bo.getImageNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductNo()), ProductPhotos::getProductNo, bo.getProductNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getImageUrl()), ProductPhotos::getImageUrl, bo.getImageUrl());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductDetailsPc()), ProductPhotos::getProductDetailsPc, bo.getProductDetailsPc());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductDetailsApp()), ProductPhotos::getProductDetailsApp, bo.getProductDetailsApp());
+        lqw.eq(StringUtils.isNotBlank(bo.getSpec()), ProductPhotos::getSpec, bo.getSpec());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProductPhotos::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ProductPhotos::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增产品图片及详情
+     *
+     * @param bo 产品图片及详情
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ProductPhotosBo bo) {
+        ProductPhotos add = MapstructUtils.convert(bo, ProductPhotos.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setProductId(add.getProductId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改产品图片及详情
+     *
+     * @param bo 产品图片及详情
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProductPhotosBo bo) {
+        ProductPhotos update = MapstructUtils.convert(bo, ProductPhotos.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProductPhotos entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除产品图片及详情信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 1 - 1
ruoyi-modules/ruoyi-product/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 # Tomcat
 server:
-  port: 9608
+  port: 9618
 
 # Spring
 spring:

+ 7 - 0
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductPhotosMapper.xml

@@ -0,0 +1,7 @@
+<?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">
+<mapper namespace="org.dromara.product.mapper.ProductPhotosMapper">
+
+</mapper>