啊鑫
2025-06-05 e47d11017af9eff6581591d5d73c1e55676b0955
优化执行内存
已添加3个文件
已修改9个文件
603 ■■■■■ 文件已修改
.gitignore 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/config/AsyncConfig.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/service/DeliveryNoticeService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/service/Impl/DeliveryNoticeServiceImpl.java 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/service/PurchaseService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/service/XkyService.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/task/PurchaseOrderSyncTask.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/task/ScheduledTasks.java 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/gs/xky/XkyApplicationTests.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
start.bat 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
start.sh 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -4,6 +4,8 @@
!**/src/main/**/target/
!**/src/test/**/target/
logs
### STS ###
.apt_generated
.classpath
README.md
@@ -155,3 +155,179 @@
1. ä¿®æ”¹å®žä½“类文件中的字段定义
2. ä¿®æ”¹å¯¹åº”çš„SQL文件中的表结构定义
3. æ‰§è¡ŒSQL语句更新数据库表结构 
## æ€§èƒ½ä¼˜åŒ–
系统在处理大量数据时可能会遇到内存占用过高的问题,特别是在执行以下操作时:
1. é‡‡è´­è®¢å•数据同步(`syncPurchaseOrderDetails`方法)
2. é€è´§å•数据处理
### å·²å®žæ–½çš„优化措施
1. **分批处理数据**:将大量数据分成小批次进行处理,减少一次性内存占用
2. **及时释放对象引用**:处理完毕后将不再使用的对象引用设为null,帮助GC回收内存
3. **优化SQL查询**:避免一次性加载大量数据到内存
### JVM调优建议
在启动应用时,可以通过以下JVM参数优化内存使用:
```bash
java -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar XkyCollection.jar
```
参数说明:
- `-Xms512m`:初始堆内存大小为512MB
- `-Xmx1024m`:最大堆内存大小为1024MB(根据服务器实际可用内存调整)
- `-XX:MetaspaceSize=128m`:初始元空间大小为128MB
- `-XX:MaxMetaspaceSize=256m`:最大元空间大小为256MB
- `-XX:+UseG1GC`:使用G1垃圾收集器,适合大内存应用
- `-XX:MaxGCPauseMillis=200`:最大GC暂停时间目标为200毫秒
### ç›‘控建议
1. ä½¿ç”¨JConsole或VisualVM等工具监控应用内存使用情况
2. å…³æ³¨GC日志,分析内存使用模式
3. åœ¨ç”Ÿäº§çŽ¯å¢ƒä¸­ï¼Œå¯ä»¥æ·»åŠ ä»¥ä¸‹å‚æ•°å¼€å¯GC日志:
   ```
   -Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
   ```
## å¸¸è§é—®é¢˜æŽ’查
### å†…存溢出(OutOfMemoryError)
如果遇到内存溢出问题:
1. æ£€æŸ¥æ˜¯å¦æœ‰å¤§é‡æ•°æ®ä¸€æ¬¡æ€§åŠ è½½åˆ°å†…å­˜
2. ç¡®è®¤æ˜¯å¦æœ‰å†…存泄漏(对象创建后未被释放)
3. å¢žåŠ JVM堆内存大小
4. è€ƒè™‘使用分页查询或流式处理大数据量
### æœåŠ¡å¡é¡¿
如果服务出现卡顿:
1. æ£€æŸ¥æ˜¯å¦æœ‰é•¿æ—¶é—´è¿è¡Œçš„事务
2. ç¡®è®¤æ•°æ®åº“连接是否正常释放
3. æŸ¥çœ‹GC日志,确认是否频繁发生Full GC
4. ä¼˜åŒ–数据库查询,添加适当的索引
## æœ€ä½³å®žè·µ
1. é¿å…åœ¨é«˜å³°æœŸæ‰§è¡Œå¤§é‡æ•°æ®åŒæ­¥æ“ä½œ
2. å¯¹äºŽå®šæ—¶ä»»åŠ¡ï¼Œé€‰æ‹©åœ¨ç³»ç»Ÿè´Ÿè½½è¾ƒä½Žçš„æ—¶æ®µæ‰§è¡Œ
3. ä½¿ç”¨é€‚当的批处理大小(建议100-500条记录)
4. å®šæœŸæ¸…理不再需要的历史数据
## å¼‚步任务处理
系统使用异步任务处理机制来执行耗时操作,避免阻塞主线程和定时任务调度线程。主要包括以下几个部分:
### çº¿ç¨‹æ± é…ç½®
系统配置了两个专用线程池:
1. **采购订单同步线程池 (purchaseTaskExecutor)**
    - æ ¸å¿ƒçº¿ç¨‹æ•°ï¼š1(确保同一时间只有一个采购同步任务在执行)
    - æœ€å¤§çº¿ç¨‹æ•°ï¼š2
    - é˜Ÿåˆ—容量:5
    - æ‹’绝策略:CallerRunsPolicy(调用者线程执行)
2. **通用异步任务线程池 (taskExecutor)**
    - æ ¸å¿ƒçº¿ç¨‹æ•°ï¼š5
    - æœ€å¤§çº¿ç¨‹æ•°ï¼š10
    - é˜Ÿåˆ—容量:25
    - æ‹’绝策略:CallerRunsPolicy(调用者线程执行)
### å®šæ—¶ä»»åŠ¡ä¼˜åŒ–
所有定时任务都使用异步执行方式,防止互相阻塞:
1. **采购订单同步任务**:每天12:05执行一次,使用专用线程池
2. **设备实时数据获取**:每5分钟执行一次,使用通用线程池
3. **补偿逻辑**:每5分钟执行多次,使用通用线程池
4. **钉钉数据同步**:每53分钟执行一次,使用通用线程池
### ä»»åŠ¡æ‰§è¡ŒçŠ¶æ€ç®¡ç†
使用 AtomicBoolean æ ‡è®°ä»»åŠ¡æ‰§è¡ŒçŠ¶æ€ï¼Œé¿å…åŒä¸€ä»»åŠ¡é‡å¤æ‰§è¡Œï¼š
```java
private final AtomicBoolean isRunning = new AtomicBoolean(false);
// ä»»åŠ¡å¼€å§‹å‰æ£€æŸ¥
if(!isRunning.
compareAndSet(false,true)){
        log.
info("上一次任务还在执行中,跳过本次执行");
    return;
            }
// ä»»åŠ¡ç»“æŸåŽé‡ç½®çŠ¶æ€
            finally{
            isRunning.
set(false);
}
```
### å¼‚步任务执行流程
1. å®šæ—¶å™¨è§¦å‘任务
2. æ£€æŸ¥ä»»åŠ¡æ˜¯å¦å·²åœ¨è¿è¡Œï¼Œå¦‚å·²è¿è¡Œåˆ™è·³è¿‡
3. å°†ä»»åŠ¡æäº¤åˆ°ç›¸åº”çš„çº¿ç¨‹æ± å¼‚æ­¥æ‰§è¡Œ
4. å®šæ—¶å™¨ç«‹å³è¿”回,不等待任务完成
5. ä»»åŠ¡åœ¨çº¿ç¨‹æ± ä¸­æ‰§è¡Œå®Œæ¯•åŽé‡ç½®çŠ¶æ€æ ‡è®°
这种机制确保了即使某个任务执行时间较长,也不会影响其他定时任务的正常执行。
## æœ€æ–°ä¼˜åŒ–æ›´æ–°
在最近的优化中,我们进一步改进了系统性能和稳定性:
1. **送货单数据处理优化**
    - å®žçŽ°äº† XkyService.GetSaveDetail() æ–¹æ³•的分批处理
    - æ¯æ‰¹å¤„理10条送货单数据,减少内存占用
    - å¢žå¼ºäº†å¼‚常处理,单条数据异常不会影响整批处理
2. **异步任务处理增强**
    - ä¸º DeliveryNoticeService æ·»åŠ äº† @Async æ³¨è§£çš„异步处理方法
    - å®žçŽ°äº† processAsyncBatch æ–¹æ³•,支持并行处理多批数据
    - ä¼˜åŒ–了日志记录,便于问题排查
3. **错误处理改进**
    - æ‰€æœ‰å…³é”®æ–¹æ³•都添加了详细的日志记录
    - å®žçŽ°äº†æ›´ç»†ç²’åº¦çš„å¼‚å¸¸æ•èŽ·å’Œå¤„ç†
    - é˜²æ­¢å•个任务失败导致整个流程中断
### å¼‚步执行流程示例
以下是一个典型的异步执行流程:
```
主线程: å¼€å§‹å¤„理100条数据
主线程: å°†æ•°æ®åˆ†ä¸º5批,每批20条
主线程: æäº¤æ‰¹æ¬¡1到异步线程池
主线程: æäº¤æ‰¹æ¬¡2到异步线程池
主线程: æäº¤æ‰¹æ¬¡3到异步线程池
主线程: æäº¤æ‰¹æ¬¡4到异步线程池
主线程: æäº¤æ‰¹æ¬¡5到异步线程池
主线程: å…¨éƒ¨æ•°æ®å¤„理提交完成
异步线程1: å¼€å§‹å¤„理批次1
异步线程2: å¼€å§‹å¤„理批次2
异步线程3: å¼€å§‹å¤„理批次3
异步线程1: æ‰¹æ¬¡1处理完成
异步线程1: å¼€å§‹å¤„理批次4
异步线程2: æ‰¹æ¬¡2处理完成
异步线程2: å¼€å§‹å¤„理批次5
异步线程3: æ‰¹æ¬¡3处理完成
异步线程1: æ‰¹æ¬¡4处理完成
异步线程2: æ‰¹æ¬¡5处理完成
```
这种方式确保了主线程不会被长时间阻塞,同时充分利用了系统资源进行并行处理。
src/main/java/com/gs/xky/config/AsyncConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
package com.gs.xky.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
 * å¼‚步任务配置类
 * é…ç½®ä¸“用线程池处理异步任务,避免阻塞Spring默认线程池
 */
@Configuration
@EnableAsync
public class AsyncConfig {
    private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);
    /**
     * é…ç½®é‡‡è´­è®¢å•同步任务专用线程池
     * ä½¿ç”¨æœ‰é™çš„线程数,避免资源竞争
     */
    @Bean(name = "purchaseTaskExecutor")
    public Executor purchaseTaskExecutor() {
        log.info("创建采购订单同步任务专用线程池");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // æ ¸å¿ƒçº¿ç¨‹æ•°è®¾ç½®ä¸º1,确保同一时间只有一个采购同步任务在执行
        executor.setCorePoolSize(1);
        // æœ€å¤§çº¿ç¨‹æ•°è®¾ç½®ä¸º2,允许有限的并发
        executor.setMaxPoolSize(2);
        // é˜Ÿåˆ—容量
        executor.setQueueCapacity(5);
        // çº¿ç¨‹åç§°å‰ç¼€
        executor.setThreadNamePrefix("purchase-task-");
        // æ‹’绝策略:当线程池已满时,调用者线程执行任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // ç­‰å¾…所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // ç­‰å¾…时间(秒)
        executor.setAwaitTerminationSeconds(60);
        // åˆå§‹åŒ–线程池
        executor.initialize();
        return executor;
    }
    /**
     * é…ç½®é€šç”¨å¼‚步任务线程池
     * ç”¨äºŽå¤„理其他轻量级异步任务
     */
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        log.info("创建通用异步任务线程池");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // æ ¸å¿ƒçº¿ç¨‹æ•°
        executor.setCorePoolSize(5);
        // æœ€å¤§çº¿ç¨‹æ•°
        executor.setMaxPoolSize(10);
        // é˜Ÿåˆ—容量
        executor.setQueueCapacity(25);
        // çº¿ç¨‹åç§°å‰ç¼€
        executor.setThreadNamePrefix("async-task-");
        // æ‹’绝策略:当线程池已满时,调用者线程执行任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // åˆå§‹åŒ–线程池
        executor.initialize();
        return executor;
    }
}
src/main/java/com/gs/xky/service/DeliveryNoticeService.java
@@ -22,4 +22,11 @@
    Integer processMesInvItemArnStatus(String factory, String company, String userCode, Long id);
    void processMesInvItemArnStatusAsync(List<MesInvItemArn> itemArnMinus);
    /**
     * å¼‚步处理一批MesInvItemArn数据
     *
     * @param batchItems å½“前批次的数据
     */
    void processAsyncBatch(List<MesInvItemArn> batchItems);
}
src/main/java/com/gs/xky/service/Impl/DeliveryNoticeServiceImpl.java
@@ -16,6 +16,9 @@
import com.gs.xky.service.DeliveryNoticeService;
import com.gs.xky.service.MesInvItemArnService;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -34,10 +37,9 @@
public class DeliveryNoticeServiceImpl extends ServiceImpl<DeliveryNoticeMapper, DeliveryNotice>
        implements DeliveryNoticeService {
    private static final Logger log = LoggerFactory.getLogger(DeliveryNoticeServiceImpl.class);
    private final DeliveryNoticeDetailService detailService;
    private final MesInvItemArnService invItemArnService;
    @Override
    public boolean saveDeliveryNotice(XkyDetail xkyDetail) {
@@ -114,15 +116,59 @@
    @Override
    public void processMesInvItemArnStatusAsync(List<MesInvItemArn> itemArnMinus) {
        if (itemArnMinus == null || itemArnMinus.isEmpty()) {
            return;
        }
        // è®°å½•开始处理的日志
        log.info("【processMesInvItemArnStatusAsync】开始处理{}条数据", itemArnMinus.size());
        // åˆ†æ‰¹å¤„理,每批最多处理20条数据
        int batchSize = 20;
        int totalSize = itemArnMinus.size();
        int batchCount = (totalSize + batchSize - 1) / batchSize;
        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min((i + 1) * batchSize, totalSize);
            List<MesInvItemArn> batchItems = itemArnMinus.subList(fromIndex, toIndex);
            log.info("【processMesInvItemArnStatusAsync】处理第{}批数据,范围:{}-{}", i + 1, fromIndex, toIndex);
            // å¼‚步处理每批数据
            processAsyncBatch(batchItems);
        }
        log.info("【processMesInvItemArnStatusAsync】全部数据处理提交完成");
    }
    /**
     * å¼‚步处理一批MesInvItemArn数据
     *
     * @param batchItems å½“前批次的数据
     */
    @Async("taskExecutor")
    public void processAsyncBatch(List<MesInvItemArn> batchItems) {
        log.info("【processAsyncBatch】异步处理{}条数据开始", batchItems.size());
        processBatch(batchItems);
        log.info("【processAsyncBatch】异步处理{}条数据完成", batchItems.size());
    }
    /**
     * æ‰¹é‡å¤„理MesInvItemArn数据
     *
     * @param batchItems å½“前批次的数据
     */
    private void processBatch(List<MesInvItemArn> batchItems) {
        // éåŽ†æ¯ä¸ª itemArn
        itemArnMinus.forEach(itemArn -> {
        batchItems.forEach(itemArn -> {
            try {
                // å¤„理每个 itemArn
                processMesInvItemArnStatus("1000", "1000", "PL017", itemArn.getId());
                Integer result = processMesInvItemArnStatus("1000", "1000", "PL017", itemArn.getId());
                log.info("【processBatch】处理itemArn: {}, ç»“æžœ: {}", itemArn.getId(), result);
            } catch (Exception e) {
                // å¤„理异常,例如记录日志
                System.err.println("Error processing itemArn: " + itemArn.getId());
                e.printStackTrace();
                // å¤„理异常,记录详细日志
                log.error("【processBatch】处理itemArn: {} å¼‚常: {}", itemArn.getId(), e.getMessage(), e);
            }
        });
    }
src/main/java/com/gs/xky/service/PurchaseService.java
@@ -79,8 +79,35 @@
        log.info("【syncPurchaseOrderDetails】获取到{}条采购订单数据", orderDetails.size());
        // å¤„理采购订单明细数据
        orderDetails.forEach(detail -> {
        // åˆ†æ‰¹å¤„理数据,减少内存占用
        int batchSize = 100; // æ¯æ‰¹å¤„理100条数据
        int totalSize = orderDetails.size();
        int batchCount = (totalSize + batchSize - 1) / batchSize; // å‘上取整计算批次数
        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min((i + 1) * batchSize, totalSize);
            log.info("【syncPurchaseOrderDetails】处理第{}批数据,范围:{}-{}", i + 1, fromIndex, toIndex);
            // èŽ·å–å½“å‰æ‰¹æ¬¡çš„æ•°æ®
            List<PurchaseOrderDetail> batchDetails = orderDetails.subList(fromIndex, toIndex);
            // å¤„理当前批次的数据
            processBatch(batchDetails);
            // æ‰‹åŠ¨è§¦å‘GC,释放内存(谨慎使用,仅在内存紧张时考虑)
            // System.gc();
        }
    }
    /**
     * æ‰¹é‡å¤„理采购订单明细数据
     *
     * @param batchDetails å½“前批次的采购订单明细数据
     */
    private void processBatch(List<PurchaseOrderDetail> batchDetails) {
        batchDetails.forEach(detail -> {
            try {
                // æ ¹æ®æœ‰æ•ˆæ ‡å¿—和订单状态处理不同的业务逻辑
                if (detail.getValidFlag() != null && detail.getValidFlag() == 0) {
@@ -113,6 +140,10 @@
                // ä¿å­˜SRM采购订单明细
                savePurchaseOrderDetail(detail);
                // å¸®åŠ©GC回收不再使用的对象
                wrapper = null;
                erpData = null;
            } catch (Exception e) {
                log.error("【syncPurchaseOrderDetails å¤„理异常】订单号: {}, é¡¹æ¬¡: {}, å¼‚常: {}",
                        detail.getPoErpNo(), detail.getLineNo(), e.getMessage(), e);
src/main/java/com/gs/xky/service/XkyService.java
@@ -42,7 +42,7 @@
    public void GetSaveDetail() throws IOException {
        long currentTimeMillis = System.currentTimeMillis();
        long startDate = currentTimeMillis - (20 * 60 * 1000); // è®¡ç®— 20 åˆ†é’Ÿå‰çš„æ—¶é—´æˆ³
        long startDate = currentTimeMillis - (30 * 60 * 1000); // è®¡ç®— 120 åˆ†é’Ÿå‰çš„æ—¶é—´æˆ³
        XkyCommonParam<BodyParam> param = XkyCommonParam.GetInit();
        BodyParam bodyParam = new BodyParam();
@@ -66,24 +66,58 @@
            return;
        }
        deliveryNoList.forEach(deliveryNo -> {
        log.info("【GetSaveDetail】获取到{}条送货单数据", deliveryNoList.size());
        // åˆ†æ‰¹å¤„理数据,减少内存占用
        int batchSize = 10; // æ¯æ‰¹å¤„理10条数据
        int totalSize = deliveryNoList.size();
        int batchCount = (totalSize + batchSize - 1) / batchSize; // å‘上取整计算批次数
        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min((i + 1) * batchSize, totalSize);
            log.info("【GetSaveDetail】处理第{}批送货单数据,范围:{}-{}", i + 1, fromIndex, toIndex);
            // èŽ·å–å½“å‰æ‰¹æ¬¡çš„æ•°æ®
            List<XkyEntity> batchDeliveries = deliveryNoList.subList(fromIndex, toIndex);
            // å¤„理当前批次的数据
            processBatchDeliveries(batchDeliveries);
        }
        log.info("【GetSaveDetail】所有送货单处理完成");
    }
    /**
     * æ‰¹é‡å¤„理送货单数据
     *
     * @param batchDeliveries å½“前批次的送货单数据
     */
    private void processBatchDeliveries(List<XkyEntity> batchDeliveries) {
        batchDeliveries.forEach(deliveryNo -> {
            try {
                if ("6".equals(deliveryNo.getStatus()) || "0".equals(deliveryNo.getLogisticsStatus())) {
                    log.info("【GetSaveDetail】移除送货单: {}", deliveryNo.getDeliveryNo());
                    log.info("【processBatchDeliveries】移除送货单: {}", deliveryNo.getDeliveryNo());
                    remove1(deliveryNo);
                } else if ("1".equals(deliveryNo.getStatus()) && ("2".equals(deliveryNo.getLogisticsStatus()) || "1".equals(deliveryNo.getLogisticsStatus()))) {
                    log.info("【processBatchDeliveries】处理送货单: {}, ç‰©æµçŠ¶æ€: {}", deliveryNo.getDeliveryNo(), deliveryNo.getLogisticsStatus());
                    XkyDetail detail = getDetail(deliveryNo.getDeliveryNo());
                    deliveryNoticeService.saveDeliveryNotice(detail);
                    List<BarcodeDeliveryNo> barcodeDeliveryNos = GetBarcodeInformation(deliveryNo.getDeliveryNo());
                    barcodeInformationService.SaveBarcodeInformation(barcodeDeliveryNos, deliveryNo.getDeliveryNo());
                    //已送达的才自动转换为MES到货单
                    if ("2".equals(deliveryNo.getLogisticsStatus())) {
                        log.info("【processBatchDeliveries】送货单已送达,执行签收: {}", deliveryNo.getDeliveryNo());
                        deliveryNoticeService.callPdaReceiptBtn("送货单签收[BTNOK[PL017[" + deliveryNo.getDeliveryNo(), "");
                    }
                }
            } catch (IOException e) {
                log.error("【GetSaveDetail å¤„理异常】送货单: {}, å¼‚常: {}", deliveryNo.getDeliveryNo(), e.getMessage(), e);
                throw new RuntimeException(e);
                log.error("【processBatchDeliveries】处理送货单异常: {}, å¼‚常: {}", deliveryNo.getDeliveryNo(), e.getMessage(), e);
                // ä¸æŠ›å‡ºå¼‚常,避免一个送货单的异常导致整个批次失败
            } catch (Exception e) {
                log.error("【processBatchDeliveries】处理送货单未预期异常: {}, å¼‚常: {}", deliveryNo.getDeliveryNo(), e.getMessage(), e);
                // ä¸æŠ›å‡ºå¼‚常,避免一个送货单的异常导致整个批次失败
            }
        });
    }
src/main/java/com/gs/xky/task/PurchaseOrderSyncTask.java
@@ -8,6 +8,9 @@
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
 * é‡‡è´­è®¢å•同步定时任务
@@ -18,20 +21,76 @@
    private static final Logger log = LoggerFactory.getLogger(PurchaseOrderSyncTask.class);
    private final PurchaseService purchaseService;
    private final Executor purchaseTaskExecutor; // æ³¨å…¥ä¸“用线程池
    // ç”¨äºŽæ ‡è®°ä»»åŠ¡æ˜¯å¦æ­£åœ¨æ‰§è¡Œ
    private final AtomicBoolean isRunning = new AtomicBoolean(false);
    /**
     * å®šæ—¶æ‰§è¡Œé‡‡è´­è®¢å•同步任务
     * æ¯å¤©12点整执行一次
     * è®¾ç½®ä¸º12点05分执行,避免与其他定时任务冲突
     * ä½¿ç”¨å¼‚步执行,防止阻塞其他定时任务
     */
    @Scheduled(cron = "0 5 12 * * ?")
    public void syncPurchaseOrders() {
        // å¦‚果任务已经在运行,则跳过本次执行
        if (!isRunning.compareAndSet(false, true)) {
            log.info("【syncPurchaseOrders】上一次任务还在执行中,跳过本次执行");
            return;
        }
        log.info("【syncPurchaseOrders】开始执行采购订单同步任务");
        // ä½¿ç”¨ä¸“用线程池执行异步任务
        CompletableFuture.runAsync(() -> {
        try {
                log.info("【syncPurchaseOrders】异步线程开始执行采购订单同步");
            purchaseService.syncPurchaseOrderDetails();
            log.info("【syncPurchaseOrders】采购订单同步任务执行成功");
        } catch (IOException e) {
            log.error("【syncPurchaseOrders】采购订单同步任务执行异常: {}", e.getMessage(), e);
            } finally {
                // æ— è®ºæˆåŠŸè¿˜æ˜¯å¤±è´¥ï¼Œéƒ½å°†è¿è¡ŒçŠ¶æ€é‡ç½®
                isRunning.set(false);
                log.info("【syncPurchaseOrders】任务状态已重置,可以接受新的任务");
        }
        }, purchaseTaskExecutor);
        // ä¸ç­‰å¾…任务完成,立即返回,避免阻塞调度线程
        log.info("【syncPurchaseOrders】采购订单同步任务已提交到异步线程执行");
    }
    /**
     * æ‰‹åŠ¨è§¦å‘é‡‡è´­è®¢å•åŒæ­¥ä»»åŠ¡
     * ç”¨äºŽç³»ç»Ÿç®¡ç†å‘˜æ‰‹åŠ¨è§¦å‘åŒæ­¥
     *
     * @return ä»»åŠ¡æ˜¯å¦å·²æäº¤æ‰§è¡Œ
     */
    public boolean manualSyncPurchaseOrders() {
        // å¦‚果任务已经在运行,则拒绝本次执行
        if (!isRunning.compareAndSet(false, true)) {
            log.info("【manualSyncPurchaseOrders】上一次任务还在执行中,拒绝本次执行");
            return false;
        }
        log.info("【manualSyncPurchaseOrders】手动触发采购订单同步任务");
        // ä½¿ç”¨ä¸“用线程池执行异步任务
        CompletableFuture.runAsync(() -> {
            try {
                log.info("【manualSyncPurchaseOrders】异步线程开始执行采购订单同步");
                purchaseService.syncPurchaseOrderDetails();
                log.info("【manualSyncPurchaseOrders】采购订单同步任务执行成功");
            } catch (IOException e) {
                log.error("【manualSyncPurchaseOrders】采购订单同步任务执行异常: {}", e.getMessage(), e);
            } finally {
                // æ— è®ºæˆåŠŸè¿˜æ˜¯å¤±è´¥ï¼Œéƒ½å°†è¿è¡ŒçŠ¶æ€é‡ç½®
                isRunning.set(false);
                log.info("【manualSyncPurchaseOrders】任务状态已重置,可以接受新的任务");
            }
        }, purchaseTaskExecutor);
        return true;
    }
src/main/java/com/gs/xky/task/ScheduledTasks.java
@@ -8,60 +8,126 @@
import com.gs.xky.entity.MesInvItemArn;
import com.gs.xky.service.*;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class ScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
    private final XkyService xkyService;
    private final ApiService apiService;
    private final MesStaffService staffService;
    private final DeliveryNoticeService deliveryNoticeService;
    private final MesInvItemArnService invItemArnService;
    private final Executor taskExecutor; // æ³¨å…¥é€šç”¨çº¿ç¨‹æ± 
    // ç”¨äºŽæ ‡è®°å„任务是否正在执行
    private final AtomicBoolean isDeviceDataRunning = new AtomicBoolean(false);
    private final AtomicBoolean isCompensateRunning = new AtomicBoolean(false);
    private final AtomicBoolean isDingTalkRunning = new AtomicBoolean(false);
    /**
     * æ¯äº”分钟执行一次
     * èŽ·å–è®¾å¤‡æœ€è¿‘çš„ä¸€æ¡è®°å½•
     *
     * @return void
     * @author tjx
     * @description TODO
     * @date 2024/9/27 21:48
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void getDeviceRealTimeData() throws IOException {
        xkyService.GetSaveDetail();
    public void getDeviceRealTimeData() {
        // å¦‚果任务已经在运行,则跳过本次执行
        if (!isDeviceDataRunning.compareAndSet(false, true)) {
            log.info("【getDeviceRealTimeData】上一次任务还在执行中,跳过本次执行");
            return;
    }
        log.info("【getDeviceRealTimeData】开始获取设备实时数据");
        // ä½¿ç”¨å¼‚步执行,避免阻塞调度线程
        CompletableFuture.runAsync(() -> {
            try {
                xkyService.GetSaveDetail();
                log.info("【getDeviceRealTimeData】获取设备实时数据成功");
            } catch (IOException e) {
                log.error("【getDeviceRealTimeData】获取设备实时数据异常: {}", e.getMessage(), e);
            } finally {
                isDeviceDataRunning.set(false);
            }
        }, taskExecutor);
    }
    /**
     * å®šæ—¶æ‰§è¡Œè¡¥å¿é€»è¾‘
     */
    @Scheduled(cron = "10 3,8,13,18,23,28,33,38,43,48,53,58 * * * ?")
    public void compensateMethod() throws IOException {
    public void compensateMethod() {
        // å¦‚果任务已经在运行,则跳过本次执行
        if (!isCompensateRunning.compareAndSet(false, true)) {
            log.info("【compensateMethod】上一次任务还在执行中,跳过本次执行");
            return;
        }
        log.info("【compensateMethod】开始执行补偿逻辑");
        // ä½¿ç”¨å¼‚步执行,避免阻塞调度线程
        CompletableFuture.runAsync(() -> {
            try {
        // è¡¥å¿é€»è¾‘
        List<MesInvItemArn> itemArnMinus = invItemArnService.getItemArnMinus();
        deliveryNoticeService.processMesInvItemArnStatusAsync(itemArnMinus);
                log.info("【compensateMethod】补偿逻辑执行成功");
            } catch (Exception e) {
                log.error("【compensateMethod】补偿逻辑执行异常: {}", e.getMessage(), e);
            } finally {
                isCompensateRunning.set(false);
            }
        }, taskExecutor);
    }
    /**
     * å®šæ—¶èŽ·å–é’‰é’‰æ•°æ®
     */
    @Scheduled(cron = "0 0/53 * * * ?")
    public void getDinTalkData() throws IOException {
    public void getDinTalkData() {
        // å¦‚果任务已经在运行,则跳过本次执行
        if (!isDingTalkRunning.compareAndSet(false, true)) {
            log.info("【getDinTalkData】上一次任务还在执行中,跳过本次执行");
            return;
        }
        log.info("【getDinTalkData】开始获取钉钉数据");
        // ä½¿ç”¨å¼‚步执行,避免阻塞调度线程
        CompletableFuture.runAsync(() -> {
            try {
        DingTalkParam dingTalkParam = new DingTalkParam(1);
                DingTalkResponse<EmployeeInfo> employeeInfoDingTalkResponse =
                        apiService.sendListRequest(dingTalkParam, EmployeeInfo.class,
                                "http://192.168.1.64/eHR/eHRExternalService/Service.ashx");
        DingTalkResponse<EmployeeInfo> employeeInfoDingTalkResponse = apiService.sendListRequest(dingTalkParam, EmployeeInfo.class, "http://192.168.1.64/eHR/eHRExternalService/Service.ashx");
                List<EmployeeInfo> collect = employeeInfoDingTalkResponse.getData().stream()
                        .filter(s -> "造梦者(浙江)科技有限公司".equals(s.getCUnitName()))
                        .collect(Collectors.toList());
        List<EmployeeInfo> collect = employeeInfoDingTalkResponse.getData().stream().filter(s -> "造梦者(浙江)科技有限公司".equals(s.getCUnitName())).collect(Collectors.toList());
        System.out.println(collect.size());
                log.info("【getDinTalkData】获取到{}条员工数据", collect.size());
        List<List<EmployeeInfo>> partition = ListUtil.partition(collect, 100);
        partition.forEach(staffService::UpdateStaff);
                log.info("【getDinTalkData】钉钉数据处理完成");
            } catch (IOException e) {
                log.error("【getDinTalkData】获取钉钉数据异常: {}", e.getMessage(), e);
            } finally {
                isDingTalkRunning.set(false);
            }
        }, taskExecutor);
    }
}
src/test/java/com/gs/xky/XkyApplicationTests.java
@@ -58,7 +58,7 @@
    @Test
    void cs() throws IOException {
        String str = "2225052002LD;222505200F75;22250520145U;2225052026EA;222505204CR8;222505204XY4;222505205WR8;222505208WCJ;222505209N46;22250520D8HK;22250520DM9W;22250520E8XU;22250520EMT7;22250520F01D;22250520FQ18;22250520J6J7;22250520JAWU;22250520JJTE;22250520JVUT;22250520KYN7;22250520QUQ2;22250520RXRY;22250520UR0E;22250520Y5W9;22250528Y85U;2225052918KK;";
        String str = "22250529195E;22250529EQHY;22250529HCM9;222505304W8Q;222505305XRK;22250530F6UC;22250530JQ31;22250531UF8T;222506013N09;222506015051;2225060152A2;2225060152EN;222506015AM7;222506015U07;2225060162K4;222506016C4C;222506017GEM;222506017YTU;222506019RWD;22250601A216;22250601C4C3;22250601GTT8;22250601GUGU;22250601H6Y7;22250601JN9X;22250601JVW5;22250601L6R5;22250601NANG;22250601NHK4;22250601NYAL;22250601Q669;22250601RGJX;22250601VCE2;22250601W6C6;22250601WFK1;22250601WH28;22250601XE2T;22250602M0MU;";
        String[] split = str.split(";");
@@ -77,6 +77,12 @@
            ApiResponse<XkyDetail> detail = apiService.sendListRequest(param, XkyDetail.class, "https://openapi.xiekeyun.com/delivery/getDetail.json");
            XkyDetail deliveryNo = detail.getData();
            if (deliveryNo == null) {
                System.out.println("为空的送货单号" + s);
                return;
            }
            if ("6".equals(deliveryNo.getStatus()) || "0".equals(deliveryNo.getLogisticsStatus())) {
                XkyEntity xkyEntity = new XkyEntity();
                xkyEntity.setDeliveryNo(deliveryNo.getDeliveryNo());
start.bat
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
@echo off
echo æ­£åœ¨å¯åŠ¨é‡‡è´­è®¢å•ç®¡ç†ç³»ç»Ÿ...
echo.
REM è®¾ç½®JVM参数以优化内存使用
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200
REM å¯åŠ¨åº”ç”¨
java %JAVA_OPTS% -jar XkyCollection.jar
REM å¦‚果启动失败,等待用户按键后退出
if %ERRORLEVEL% NEQ 0 (
    echo.
    echo åº”用启动失败,请检查错误信息。
    pause
    exit /b %ERRORLEVEL%
)
start.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
#!/bin/bash
echo "正在启动采购订单管理系统..."
echo
# è®¾ç½®JVM参数以优化内存使用
JAVA_OPTS="-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# å¯åŠ¨åº”ç”¨
java $JAVA_OPTS -jar XkyCollection.jar
# å¦‚果启动失败,显示错误信息
if [ $? -ne 0 ]; then
    echo
    echo "应用启动失败,请检查错误信息。"
    exit 1
fi