c8029aebc94ee3e521b0d6f2a5ea00a81f7a4c42..f9b26d13f0a43f0bb5c8140a2de3ebd8d9c9c8d8
4 天以前 gongchunyi
fix: 产品导入去重
f9b26d 对比 | 目录
4 天以前 gongchunyi
chore: 隆盛环保配置文件
89e314 对比 | 目录
已添加1个文件
已修改1个文件
344 ■■■■■ 文件已修改
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-lshb.yml 267 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -28,6 +28,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -145,6 +146,9 @@
            return AjaxResult.error("未找到 [原料/半成品/成品] å·¥ä½œè¡¨ï¼Œæˆ–文件中没有数据");
        }
        // åŽ»é‡é”®ï¼šäº§å“åç§° + è§„格型号 + ç¼–码(空编码按空串参与比较)
        Set<String> seenKeys = loadExistingProductKeys(sheetMap);
        int successCount = 0;
        int skipCount = 0;
        Set<String> generatedCodes = new HashSet<>();
@@ -155,7 +159,6 @@
            }
            Long topId = findOrCreateTopProduct(sheetName);
            Map<String, Long> nodeCache = new HashMap<>();
            Map<Long, Set<String>> codeCache = new HashMap<>();
            for (int i = 0; i < rows.size(); i++) {
                ProductImportRowDto row = rows.get(i);
                int rowNum = i + 2;
@@ -164,6 +167,16 @@
                String productName = trimToNull(row.getProductName());
                if (productName == null) {
                    throw new ServiceException("第 " + rowNum + " è¡Œ [产品名称] ä¸èƒ½ä¸ºç©º");
                }
                String model = trimToNull(row.getModel());
                if (model == null) {
                    model = "/";
                }
                String code = trimToNull(row.getProductCode());
                if (!seenKeys.add(dedupKey(productName, model, code))) {
                    skipCount++;
                    continue;
                }
                String path = String.valueOf(topId);
@@ -179,20 +192,14 @@
                path = path + ">" + productName;
                Long leafId = ensureChildNode(nodeCache, path, currentParentId, productName);
                String code = trimToNull(row.getProductCode());
                if (code == null) {
                    code = generateUniqueCode(generatedCodes);
                }
                Set<String> codes = codeCache.computeIfAbsent(leafId, this::loadProductCodes);
                if (!codes.add(code)) {
                    skipCount++;
                    continue;
                }
                ProductModel productModel = new ProductModel();
                productModel.setProductId(leafId);
                productModel.setProductCode(code);
                productModel.setModel(StringUtils.isEmpty(row.getModel()) ? "/" : row.getModel().trim());
                productModel.setModel(model);
                productModel.setUnit(trimToNull(row.getUnit()));
                productModelMapper.insert(productModel);
                successCount++;
@@ -240,16 +247,54 @@
        return product.getId();
    }
    private Set<String> loadProductCodes(Long productId) {
        List<ProductModel> list = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
                .eq(ProductModel::getProductId, productId));
        Set<String> codes = new HashSet<>();
        for (ProductModel productModel : list) {
            if (productModel.getProductCode() != null) {
                codes.add(productModel.getProductCode().trim());
    /**
     * é¢„加载库中已存在的去重键(产品名称 + è§„格型号 + ç¼–码),并作为本次导入的初始集合。
     */
    private Set<String> loadExistingProductKeys(Map<String, List<ProductImportRowDto>> sheetMap) {
        Set<String> productNames = new HashSet<>();
        for (List<ProductImportRowDto> rows : sheetMap.values()) {
            if (CollectionUtils.isEmpty(rows)) {
                continue;
            }
            for (ProductImportRowDto row : rows) {
                String productName = trimToNull(row.getProductName());
                if (productName != null) {
                    productNames.add(productName);
                }
            }
        }
        return codes;
        Set<String> keys = new HashSet<>();
        if (productNames.isEmpty()) {
            return keys;
        }
        List<Product> products = productMapper.selectList(new LambdaQueryWrapper<Product>()
                .in(Product::getProductName, productNames));
        if (CollectionUtils.isEmpty(products)) {
            return keys;
        }
        Map<Long, String> nameById = new HashMap<>();
        List<Long> productIds = new ArrayList<>();
        for (Product product : products) {
            nameById.put(product.getId(), product.getProductName());
            productIds.add(product.getId());
        }
        List<ProductModel> productModels = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
                .in(ProductModel::getProductId, productIds));
        for (ProductModel productModel : productModels) {
            String productName = nameById.get(productModel.getProductId());
            if (productName != null) {
                keys.add(dedupKey(productName, productModel.getModel(), productModel.getProductCode()));
            }
        }
        return keys;
    }
    private static String dedupKey(String productName, String model, String productCode) {
        String normalizedModel = trimToNull(model) == null ? "/" : model.trim();
        String normalizedCode = productCode == null ? "" : productCode.trim();
        return productName.trim() + "\u0001" + normalizedModel + "\u0001" + normalizedCode;
    }
    private String generateUniqueCode(Set<String> generatedCodes) {
src/main/resources/application-lshb.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,267 @@
# é¡¹ç›®ç›¸å…³é…ç½®
ruoyi:
  # åç§°
  name: RuoYi
  # ç‰ˆæœ¬
  version: 3.8.9
  # ç‰ˆæƒå¹´ä»½
  copyrightYear: 2025
  # æ–‡ä»¶è·¯å¾„ ç¤ºä¾‹ï¼ˆ Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: /javaWork/product-inventory-management/file
  # èŽ·å–ip地址开关
  addressEnabled: false
  # éªŒè¯ç ç±»åž‹ math æ•°å­—计算 char å­—符验证
  captchaType: math
  # ååŒå®¡æ‰¹ç¼–号前缀(配置文件后缀命名)
  approvalNumberPrefix: NEW
  # ä¸ªæŽ¨ Unipush é…ç½®
  getui:
    appId: PfjyAAE0FK64FaO1w2CMb1
    appKey: zTMb831OEL6J4GK1uE3Ob4
    masterSecret: K1GFtsv42v61tXGnF7SGE5
    domain: https://restapi.getui.cn/v2/
    # ç¦»çº¿æŽ¨é€ä½¿ç”¨çš„包名/组件名
    intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry
# å¼€å‘环境配置
server:
  # æœåŠ¡å™¨çš„HTTP端口,默认为8080
  port: 9002
  servlet:
    # åº”用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
    # è¿žæŽ¥æ•°æ»¡åŽçš„æŽ’队数,默认为100
    accept-count: 1000
    max-swallow-size: -1          # -1 è¡¨ç¤ºä¸é™åˆ¶
    max-http-form-post-size: -1   # POST è¯·æ±‚体不限制
    connection-timeout: 60000     # è¿žæŽ¥è¶…æ—¶æ—¶é—´(毫秒)
    threads:
      # tomcat最大线程数,默认为200
      max: 800
      # Tomcat启动初始化的线程数,默认值10
      min-spare: 100
# æ—¥å¿—配置
logging:
  level:
    com.ruoyi: warn
    org.springframework: warn
minio:
  endpoint: http://114.132.189.42/
  port: 7019
  secure: false
  accessKey: admin
  secretKey: 12345678
  preview-expiry: 24 # é¢„览地址默认24小时
  default-bucket: uploadPath
# ç”¨æˆ·é…ç½®
user:
  password:
    # å¯†ç æœ€å¤§é”™è¯¯æ¬¡æ•°
    maxRetryCount: 5
    # å¯†ç é”å®šæ—¶é—´ï¼ˆé»˜è®¤10分钟)
    lockTime: 10
# Spring配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # ä¸»åº“数据源
      master:
        url: jdbc:mysql://36.137.158.5:83/product-inventory-management-lshb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: lshb@123456..
      # ä»Žåº“数据源
      slave:
        # ä»Žæ•°æ®æºå¼€å…³/默认关闭
        enabled: false
        url:
        username:
        password:
      # åˆå§‹è¿žæŽ¥æ•°
      initialSize: 5
      # æœ€å°è¿žæŽ¥æ± æ•°é‡
      minIdle: 10
      # æœ€å¤§è¿žæŽ¥æ± æ•°é‡
      maxActive: 20
      # é…ç½®èŽ·å–è¿žæŽ¥ç­‰å¾…è¶…æ—¶çš„æ—¶é—´
      maxWait: 60000
      # é…ç½®è¿žæŽ¥è¶…æ—¶æ—¶é—´
      connectTimeout: 30000
      # é…ç½®ç½‘络超时时间
      socketTimeout: 60000
      # é…ç½®é—´éš”多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å°ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位是毫秒
      minEvictableIdleTimeMillis: 300000
      # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å¤§ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # é…ç½®æ£€æµ‹è¿žæŽ¥æ˜¯å¦æœ‰æ•ˆ
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # è®¾ç½®ç™½åå•,不填则允许所有访问
        allow:
        url-pattern: /druid/*
        # æŽ§åˆ¶å°ç®¡ç†ç”¨æˆ·åå’Œå¯†ç 
        login-username: ruoyi
        login-password: 123456
      filter:
        stat:
          enabled: true
          # æ…¢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
  # èµ„源信息
  messages:
    # å›½é™…化资源文件路径
    basename: i18n/messages
  # æ–‡ä»¶ä¸Šä¼ 
  servlet:
    multipart:
      # å•个文件大小
      max-file-size: 1GB
      # è®¾ç½®æ€»ä¸Šä¼ çš„æ–‡ä»¶å¤§å°
      max-request-size: 2GB
  # æœåŠ¡æ¨¡å—
  devtools:
    restart:
      # çƒ­éƒ¨ç½²å¼€å…³
      enabled: false
  data:
    # redis é…ç½®
    redis:
      # åœ°å€
      #      host: localhost
      host: 172.17.0.1
      # ç«¯å£ï¼Œé»˜è®¤ä¸º6379
      port: 6379
      # æ•°æ®åº“索引
      database: 0
      # å¯†ç 
      #    password: root2022!
      password:
      # è¿žæŽ¥è¶…æ—¶æ—¶é—´
      timeout: 10s
      lettuce:
        pool:
          # è¿žæŽ¥æ± ä¸­çš„æœ€å°ç©ºé—²è¿žæŽ¥
          min-idle: 0
          # è¿žæŽ¥æ± ä¸­çš„æœ€å¤§ç©ºé—²è¿žæŽ¥
          max-idle: 8
          # è¿žæŽ¥æ± çš„æœ€å¤§æ•°æ®åº“连接数
          max-active: 8
          # #连接池最大阻塞等待时间(使用负值表示没有限制)
          max-wait: -1ms
  # Quartz定时任务配置(新增部分)
  quartz:
    job-store-type: jdbc  # ä½¿ç”¨æ•°æ®åº“存储
    jdbc:
      initialize-schema: never  # é¦–次运行时自动创建表结构,成功后改为never
      schema: classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql  # MySQL表结构脚本
    properties:
      org:
        quartz:
          scheduler:
            instanceName: RuoYiScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  # MySQL适配
            tablePrefix: qrtz_  # è¡¨åå‰ç¼€ï¼Œä¸Žè„šæœ¬ä¸€è‡´
            isClustered: false  # å•节点模式(集群需改为true)
            clusterCheckinInterval: 10000
            txIsolationLevelSerializable: true
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10  # çº¿ç¨‹æ± å¤§å°
            threadPriority: 5
            makeThreadsDaemons: true
          updateCheck: false  # å…³é—­ç‰ˆæœ¬æ£€æŸ¥
# token配置
token:
  # ä»¤ç‰Œè‡ªå®šä¹‰æ ‡è¯†
  header: Authorization
  # ä»¤ç‰Œå¯†é’¥
  secret: xpAVjhCjQDaDB7mjPAzMDSbQWXNu2zYkTdDNUsPMS5Xx8QMmQVYN7n74eZrYJxDJ
  # ä»¤ç‰Œæœ‰æ•ˆæœŸï¼ˆé»˜è®¤30分钟)
  expireTime: 450
# MyBatis Plus配置
mybatis-plus:
  # æœç´¢æŒ‡å®šåŒ…别名   æ ¹æ®è‡ªå·±çš„项目来
  typeAliasesPackage: com.ruoyi.**.pojo
  # é…ç½®mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # åŠ è½½å…¨å±€çš„é…ç½®æ–‡ä»¶
  configLocation: classpath:mybatis/mybatis-config.xml
  global-config:
    enable-sql-runner: true
    db-config:
      id-type: auto
# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  supportMethodsArguments: true
  params: count=countSql
# Swagger配置
swagger:
  # æ˜¯å¦å¼€å¯swagger
  enabled: true
  # è¯·æ±‚前缀
  pathMapping: /dev-api
# é˜²æ­¢XSS攻击
xss:
  # è¿‡æ»¤å¼€å…³
  enabled: true
  # æŽ’除链接(多个用逗号分隔)
  excludes: /system/notice
  # åŒ¹é…é“¾æŽ¥
  urlPatterns: /system/*,/monitor/*,/tool/*
# ä»£ç ç”Ÿæˆ
gen:
  # ä½œè€…
  author: ruoyi
  # é»˜è®¤ç”ŸæˆåŒ…路径 system éœ€æ”¹æˆè‡ªå·±çš„æ¨¡å—名称 å¦‚ system monitor tool
  packageName: com.ruoyi.project.system
  # è‡ªåŠ¨åŽ»é™¤è¡¨å‰ç¼€ï¼Œé»˜è®¤æ˜¯true
  autoRemovePre: false
  # è¡¨å‰ç¼€ï¼ˆç”Ÿæˆç±»åä¸ä¼šåŒ…含表前缀,多个用逗号分隔)
  tablePrefix: sys_
  # æ˜¯å¦å…è®¸ç”Ÿæˆæ–‡ä»¶è¦†ç›–到本地(自定义路径),默认不允许
  allowOverwrite: false
# æ–‡ä»¶ä¸Šä¼ é…ç½®
file:
  temp-dir: /javaWork/product-inventory-management/file/temp/uploads   # ä¸´æ—¶ç›®å½• åŽæœŸåˆ é™¤
  upload-dir: /javaWork/product-inventory-management/file/prod/uploads # æ­£å¼ç›®å½• åŽæœŸåˆ é™¤
  path: /javaWork/product-inventory-management/file/path/uploads # ä¸Šä¼ ç›®å½•
  urlPrefix: /prod-api/common # é“¾æŽ¥å‰ç¼€
  domain: http://36.137.158.5:81 # åŸŸåå‰ç¼€
  expired: 120 # è¿‡æœŸæ—¶é—´(单位:分钟)
  useLimit: 10 # ä½¿ç”¨æ¬¡æ•°
  compress: true # æ˜¯å¦åŽ‹ç¼©
  needCompressSize: 10MB # åŽ‹ç¼©é˜ˆå€¼
  compressQuality: 0.5 # åŽ‹ç¼©è´¨é‡(0.0-1.0)