wlzboy
2026-01-12 45d90d1e7ba86286e998d1ac4d2cba8e98cd059b
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java
@@ -2,7 +2,10 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.format.DateTimeFormatter;
import java.util.*;
import com.ruoyi.common.utils.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,6 +41,12 @@
    
    /** 天地图批量路径规划API */
    private static final String TIANDITU_ROUTE_API = "http://api.tianditu.gov.cn/drive";
    /** 线程安全的日期格式化器 */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /** 分批处理大小,避免一次性加载过多数据 */
    private static final int BATCH_SIZE = 10;
    
    @Autowired
    private VehicleGpsSegmentMileageMapper segmentMileageMapper;
@@ -97,10 +106,11 @@
    public int batchCalculateSegmentMileage(Date startTime, Date endTime) {
        try {
//            logger.info("开始批量计算GPS分段里程 - 时间范围: {} 到 {}", startTime, endTime);
            String startTimeStr = DateUtils.formatDate(startTime,DateUtils.YYYY_MM_DD_HH_MM_SS);
            String endTimeStr = DateUtils.formatDate(endTime,DateUtils.YYYY_MM_DD_HH_MM_SS);
            // 查询在指定时间范围内有GPS数据的所有车辆(添加慢SQL监控)
            long startQueryTime = System.currentTimeMillis();
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime);
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTimeStr);
            long queryTime = System.currentTimeMillis() - startQueryTime;
            
            // 慢查询警告(超过1秒)
@@ -115,35 +125,44 @@
                return 0;
            }
            
            logger.info("找到 {} 辆活跃车辆,开始逐辆计算...", vehicleIds.size());
            logger.info("找到 {} 辆活跃车辆,开始分批逐辆计算...", vehicleIds.size());
            
            int successCount = 0;
            int failedCount = 0;
            
            // 逐辆计算,包含错误处理和重试机制
            for (int i = 0; i < vehicleIds.size(); i++) {
                Long vehicleId = vehicleIds.get(i);
                try {
//                    logger.info("正在处理车辆 {} ({}/{})", vehicleId, i + 1, vehicleIds.size());
                    int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
                    if (segmentCount > 0) {
                        successCount++;
//                        logger.info("车辆 {} 计算成功,生成 {} 个分段记录", vehicleId, segmentCount);
                    } else {
//                        logger.debug("车辆 {} 无有新的GPS分段数据", vehicleId);
            // 分批处理,避免一次性处理过多数据导致内存溢出
            for (int batchStart = 0; batchStart < vehicleIds.size(); batchStart += BATCH_SIZE) {
                int batchEnd = Math.min(batchStart + BATCH_SIZE, vehicleIds.size());
                List<Long> batchVehicleIds = vehicleIds.subList(batchStart, batchEnd);
                logger.info("处理批次 {}-{}/{}", batchStart + 1, batchEnd, vehicleIds.size());
                // 逐辆计算,包含错误处理和重试机制
                for (int i = 0; i < batchVehicleIds.size(); i++) {
                    Long vehicleId = batchVehicleIds.get(i);
                    int overallIndex = batchStart + i;
                    try {
                        int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
                        if (segmentCount > 0) {
                            successCount++;
                        }
                    } catch (Exception e) {
                        failedCount++;
                        logger.error("计算车辆 {} 的分段里程失败 ({}/{})", vehicleId, overallIndex + 1, vehicleIds.size(), e);
                        // 不中断整个批处理,继续处理下一辆车
                    }
                } catch (Exception e) {
                    failedCount++;
                    logger.error("计算车辆 {} 的分段里程失败 ({}/{})", vehicleId, i + 1, vehicleIds.size(), e);
                    
                    // 不中断整个批处理,继续处理下一辆车
                    // 每处理10辆车输出一次进度
                    if ((overallIndex + 1) % 10 == 0) {
                        logger.info("批量计算进度: {}/{}, 成功: {}, 失败: {}",
                                   overallIndex + 1, vehicleIds.size(), successCount, failedCount);
                    }
                }
                
                // 每处理10辆车输出一次进度
                if ((i + 1) % 10 == 0) {
                    logger.info("批量计算进度: {}/{}, 成功: {}, 失败: {}",
                               i + 1, vehicleIds.size(), successCount, failedCount);
                // 批次结束后,主动触发GC建议(不强制)
                if (batchEnd < vehicleIds.size()) {
                    System.gc();
                    logger.debug("批次 {}-{} 处理完成,已建议JVM回收内存", batchStart + 1, batchEnd);
                }
            }
            
@@ -167,9 +186,10 @@
            Date startTime = cal.getTime();
            
            logger.info("开始补偿计算 - 回溯天数: {}, 时间范围: {} 到 {}", lookbackDays, startTime, endTime);
            String startTimeStr=DateUtils.formatDate(startTime, DateUtils.YYYY_MM_DD_HH_MM_SS);
            // 查询所有活跃车辆
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime);
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTimeStr);
            
            if (vehicleIds == null || vehicleIds.isEmpty()) {
                logger.info("没有找到活跃车辆");
@@ -178,11 +198,18 @@
            
            int successCount = 0;
            int totalUncalculated = 0;
            for (Long vehicleId : vehicleIds) {
                try {
                    // 查询该车辆未被计算的GPS数据
                    List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTime, endTime);
            String endTimeStr=DateUtils.formatDate(endTime, DateUtils.YYYY_MM_DD_HH_MM_SS);
            // 分批处理车辆,避免内存溢出
            for (int batchStart = 0; batchStart < vehicleIds.size(); batchStart += BATCH_SIZE) {
                int batchEnd = Math.min(batchStart + BATCH_SIZE, vehicleIds.size());
                List<Long> batchVehicleIds = vehicleIds.subList(batchStart, batchEnd);
                for (Long vehicleId : batchVehicleIds) {
                    try {
                        // 查询该车辆未被计算的GPS数据
                        List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTimeStr, endTimeStr);
                    
                    if (uncalculatedGps == null || uncalculatedGps.isEmpty()) {
                        logger.debug("车辆 {} 没有未计算的GPS数据", vehicleId);
@@ -218,12 +245,20 @@
                    int segmentCount = calculateVehicleSegmentMileageWithGpsList(
                        vehicleId, uncalculatedGps, uncalculatedStartTime, uncalculatedEndTime);
                    
                    if (segmentCount > 0) {
                        successCount++;
                        logger.info("车辆 {} 补偿计算完成,生成 {} 个分段记录", vehicleId, segmentCount);
                        if (segmentCount > 0) {
                            successCount++;
                            logger.info("车辆 {} 补偿计算完成,生成 {} 个分段记录", vehicleId, segmentCount);
                        }
                    } catch (Exception e) {
                        logger.error("车辆 {} 补偿计算失败", vehicleId, e);
                    }
                } catch (Exception e) {
                    logger.error("车辆 {} 补偿计算失败", vehicleId, e);
                }
                // 每批次结束后,主动建议GC
                if (batchEnd < vehicleIds.size()) {
                    uncalculatedGps = null; // 显式清空引用
                    System.gc();
                    logger.debug("补偿计算批次 {}-{} 完成,已建议JVM回收内存", batchStart + 1, batchEnd);
                }
            }
            
@@ -241,14 +276,16 @@
    public int calculateVehicleSegmentMileage(Long vehicleId, Date startTime, Date endTime) {
        try {
            // 查询车辆在时间范围内的GPS数据
            List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, startTime, endTime);
            String startTimeStr=DateUtils.formatDate(startTime, DateUtils.YYYY_MM_DD_HH_MM_SS);
            String endTimeStr=DateUtils.formatDate(endTime, DateUtils.YYYY_MM_DD_HH_MM_SS);
            List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, startTimeStr, endTimeStr);
            
            if (gpsList == null || gpsList.isEmpty()) {
                logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime);
                return 0;
            }
            
            logger.info("车辆ID: {} 查询到 {} 条GPS数据 startTime:{},endTime:{}", vehicleId, gpsList.size(),startTime,endTime);
//            logger.info("车辆ID: {} 查询到 {} 条GPS数据 startTime:{},endTime:{}", vehicleId, gpsList.size(),startTime,endTime);
            
            return calculateVehicleSegmentMileageWithGpsList(vehicleId, gpsList, startTime, endTime);
            
@@ -284,7 +321,7 @@
            // 处理每个时间段并计算里程
            int savedCount = processSegmentedGpsData(vehicleId, segmentedData, config);
            
            logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
//            logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
            
            // 自动触发每日统计汇总
            if (savedCount > 0) {
@@ -319,7 +356,7 @@
        // 获取是否跳过已计算GPS点的配置
        String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
        config.skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
        logger.info("控制跳过重复计算标识: {}", config.skipCalculated);
//        logger.info("控制跳过重复计算标识: {}", config.skipCalculated);
        
        return config;
    }
@@ -383,7 +420,7 @@
        
        // 如果本段只有1个点,且没有上一段的最后一个点,无法计算距离
        if (segmentGpsList.size() == 1 && previousSegmentLastPoint == null) {
            logger.debug("车辆 {} 时间段 {} 只有1个GPS点且无前置点,暂存待下一段计算", vehicleId, segmentStartTime);
//            logger.debug("车辆 {} 时间段 {} 只有1个GPS点且无前置点,暂存待下一段计算", vehicleId, segmentStartTime);
            return false;
        }
        
@@ -421,14 +458,14 @@
            String gpsIds = gpsIdList.stream()
                .map(String::valueOf)
                .collect(java.util.stream.Collectors.joining(","));
            // 创建分段里程记录
            VehicleGpsSegmentMileage segment = buildSegmentMileageRecord(
                vehicleId, segmentStartTime, segmentEndTime, segmentGpsList, 
                distance, gpsIdList, gpsIds, config.calculateMethod);
            
            // 保存到数据库
            logger.info("保存车辆分时段里程到数据库中,车辆ID: {}, 时间段: {} 到 {}", vehicleId, segmentStartTime, segmentEndTime);
//            logger.info("保存车辆分时段里程到数据库中,车辆ID: {}, 时间段: {} 到 {}", vehicleId, segmentStartTime, segmentEndTime);
            segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
            
            // 记录已计算的GPS点(如果开启了重复计算控制)
@@ -436,9 +473,9 @@
                recordCalculatedGpsPoints(gpsIdList, segment.getSegmentId(), vehicleId);
            }
            
            logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}",
                       vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(),
                       gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
//            logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}",
//                       vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(),
//                       gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
            
            return true;
            
@@ -462,7 +499,8 @@
     * 收集GPS ID列表(包括前置点)
     */
    private List<Long> collectGpsIds(List<VehicleGps> segmentGpsList, VehicleGps previousSegmentLastPoint) {
        List<Long> gpsIdList = new ArrayList<>();
        // 预分配合理容量,减少扩容开销
        List<Long> gpsIdList = new ArrayList<>(segmentGpsList.size() + 1);
        
        // 如果有上一段的最后一个点,先添加它的ID(用于计算跨段距离)
        if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
@@ -560,7 +598,7 @@
            for (Date statDate : affectedDates) {
                try {
                    mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
                    logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
//                    logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
                } catch (Exception e) {
                    logger.error("车辆 {} 日期 {} 自动汇总统计失败", vehicleId, statDate, e);
                }
@@ -662,8 +700,8 @@
            );
            totalDistance = totalDistance.add(BigDecimal.valueOf(gapDistance));
            
            logger.debug("跨段间隙距离: {}km (上一段末点 -> 当前段首点)",
                String.format("%.3f", gapDistance));
//            logger.debug("跨段间隙距离: {}km (上一段末点 -> 当前段首点)",
//                String.format("%.3f", gapDistance));
        }
        
        // 2. 再计算当前段内部的距离(如果有2个或以上GPS点)
@@ -814,16 +852,22 @@
    /**
     * 解析日期时间字符串
     * 使用ThreadLocal的SimpleDateFormat,避免每次创建新对象
     */
    private static final ThreadLocal<java.text.SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL =
        ThreadLocal.withInitial(() -> {
            java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setLenient(false);
            return sdf;
        });
    private Date parseDateTime(String dateTimeStr) {
        if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
            throw new RuntimeException("日期时间字符串不能为空");
        }
        
        try {
            java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setLenient(false);
            return sdf.parse(dateTimeStr.trim());
            return DATE_FORMAT_THREAD_LOCAL.get().parse(dateTimeStr.trim());
        } catch (Exception e) {
            throw new RuntimeException("日期时间格式错误: " + dateTimeStr + ", 应为 yyyy-MM-dd HH:mm:ss", e);
        }
@@ -840,10 +884,12 @@
                                      Date segmentStartTime, Date segmentEndTime) {
        try {
            // 查询该车辆正在执行的任务列表
            List<SysTask> activeTasks = sysTaskMapper.selectTaskByVehicleIdAndDate(vehicleId,segmentStartTime,segmentEndTime);
          String segmentStartTimeStr =  DateUtils.formatDate(segmentStartTime, "yyyy-MM-dd HH:mm:ss");
          String segmentEndTimeStr =  DateUtils.formatDate(segmentEndTime, "yyyy-MM-dd HH:mm:ss");
            List<SysTask> activeTasks = sysTaskMapper.selectTaskByVehicleIdAndDate(vehicleId,segmentStartTimeStr,segmentEndTimeStr);
            
            if (activeTasks == null || activeTasks.isEmpty()) {
                logger.debug("车辆 {} 在时间段 {} - {} 没有正在执行的任务", vehicleId, segmentStartTime, segmentEndTime);
                logger.info("车辆 {} 在时间段 {} - {} 没有正在执行的任务", vehicleId, segmentStartTime, segmentEndTime);
                return;
            }
            
@@ -859,8 +905,8 @@
                    segment.setTaskId(task.getTaskId());
                    segment.setTaskCode(task.getTaskCode());
                    
                    logger.debug("车辆 {} 时间段 {} - {} 关联任务: taskId={}, taskCode={}",
                               vehicleId, segmentStartTime, segmentEndTime, task.getTaskId(), task.getTaskCode());
//                    logger.debug("车辆 {} 时间段 {} - {} 关联任务: taskId={}, taskCode={}",
//                               vehicleId, segmentStartTime, segmentEndTime, task.getTaskId(), task.getTaskCode());
                    break; // 找到一个匹配的任务即可
                }
            }