wlzboy
2 天以前 8cb5d3440208a3be3e772e65f1bd0ec63031ba62
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java
@@ -3,6 +3,8 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import com.ruoyi.common.utils.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -96,49 +98,82 @@
    @Override
    public int batchCalculateSegmentMileage(Date startTime, Date endTime) {
        try {
            // 查询所有活跃车辆
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds();
//            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(startTimeStr);
            long queryTime = System.currentTimeMillis() - startQueryTime;
            // 慢查询警告(超过1秒)
            if (queryTime > 1000) {
                logger.warn("查询活跃车辆ID耗时过长: {}ms, 开始时间: {}, 建议检查 tb_vehicle_gps 表的索引(需要 vehicle_id 和 collect_time 组合索引)",
                    queryTime, startTime);
            }
            logger.info("查询到 {} 辆活跃车辆,查询耗时: {}ms", vehicleIds != null ? vehicleIds.size() : 0, queryTime);
            
            if (vehicleIds == null || vehicleIds.isEmpty()) {
                logger.info("没有找到活跃车辆");
                return 0;
            }
            
            logger.info("找到 {} 辆活跃车辆,开始逐辆计算...", vehicleIds.size());
            int successCount = 0;
            for (Long vehicleId : vehicleIds) {
            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);
                    }
                } catch (Exception e) {
                    logger.error("计算车辆 {} 的分段里程失败", vehicleId, e);
                    failedCount++;
                    logger.error("计算车辆 {} 的分段里程失败 ({}/{})", vehicleId, i + 1, vehicleIds.size(), e);
                    // 不中断整个批处理,继续处理下一辆车
                }
                // 每处理10辆车输出一次进度
                if ((i + 1) % 10 == 0) {
                    logger.info("批量计算进度: {}/{}, 成功: {}, 失败: {}",
                               i + 1, vehicleIds.size(), successCount, failedCount);
                }
            }
            
            logger.info("批量分段里程计算完成 - 时间范围: {} 到 {}, 总车辆数: {}, 成功: {}",
                       startTime, endTime, vehicleIds.size(), successCount);
            logger.info("批量分段里程计算完成 - 时间范围: {} 到 {}, 总车辆数: {}, 成功: {}, 失败: {}",
                       startTime, endTime, vehicleIds.size(), successCount, failedCount);
            return successCount;
            
        } catch (Exception e) {
            logger.error("批量计算分段里程失败", e);
            throw new RuntimeException("批量计算失败: " + e.getMessage());
            logger.error("批量计算分段里程失败 - 时间范围: {} 到 {}", startTime, endTime, e);
            throw new RuntimeException("批量计算失败: " + e.getMessage(), e);
        }
    }
    @Override
    public int compensateCalculation(int lookbackDays) {
        try {
            // 计算时间范围
            // 计算时间范围(回溯指定天数)
            Calendar cal = Calendar.getInstance();
            Date endTime = cal.getTime();
            cal.add(Calendar.DAY_OF_MONTH, -lookbackDays);
            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();
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTimeStr);
            
            if (vehicleIds == null || vehicleIds.isEmpty()) {
                logger.info("没有找到活跃车辆");
@@ -147,11 +182,13 @@
            
            int successCount = 0;
            int totalUncalculated = 0;
            String endTimeStr=DateUtils.formatDate(endTime, DateUtils.YYYY_MM_DD_HH_MM_SS);
            for (Long vehicleId : vehicleIds) {
                try {
                    // 查询该车辆未被计算的GPS数据
                    List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTime, endTime);
                    List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTimeStr, endTimeStr);
                    
                    if (uncalculatedGps == null || uncalculatedGps.isEmpty()) {
                        logger.debug("车辆 {} 没有未计算的GPS数据", vehicleId);
@@ -162,9 +199,30 @@
                    logger.info("车辆 {} 发现 {} 个未计算的GPS点,开始补偿计算...", 
                               vehicleId, uncalculatedGps.size());
                    
                    // 获取未计算GPS数据的时间范围
                    Date uncalculatedStartTime = parseDateTime(uncalculatedGps.get(0).getCollectTime());
                    Date uncalculatedEndTime = parseDateTime(uncalculatedGps.get(uncalculatedGps.size() - 1).getCollectTime());
                    // 查找该时间段之前最后一个已处理的GPS坐标ID
                    Long lastCalculatedGpsId = segmentMileageMapper.selectLastCalculatedGpsId(vehicleId, uncalculatedStartTime);
                    if (lastCalculatedGpsId != null) {
                        logger.info("车辆 {} 找到最后一个已处理的GPS点ID: {},将与未处理数据一起计算", vehicleId, lastCalculatedGpsId);
                        // 将最后一个已处理的GPS点加入列表前面,作为前置点
                        VehicleGps lastCalculatedGps = vehicleGpsMapper.selectVehicleGpsById(lastCalculatedGpsId);
                        if (lastCalculatedGps != null) {
                            uncalculatedGps.add(0, lastCalculatedGps); // 插入到列表最前面
                            logger.info("已将GPS点 {} 作为前置点加入计算列表", lastCalculatedGpsId);
                        }
                    } else {
                        logger.info("车辆 {} 没有找到已处理的前置 GPS点,从第一个未处理点开始计算", vehicleId);
                    }
                    // 重新计算该车辆在该时间范围的分段里程
                    // 注意:这里会重新计算整个时间范围,确保边缘节点被正确处理
                    int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
                    int segmentCount = calculateVehicleSegmentMileageWithGpsList(
                        vehicleId, uncalculatedGps, uncalculatedStartTime, uncalculatedEndTime);
                    
                    if (segmentCount > 0) {
                        successCount++;
@@ -188,176 +246,57 @@
    @Override
    public int calculateVehicleSegmentMileage(Long vehicleId, Date startTime, Date endTime) {
        try {
            // 获取配置的时间间隔(分钟)
            int segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null
                ? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes"))
                : 5;
            // 获取计算方式配置
            String calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
            if (calculateMethod == null || calculateMethod.isEmpty()) {
                calculateMethod = "tianditu";
            }
            // 获取是否跳过已计算GPS点的配置
            String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
            boolean skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
            // 查询车辆在时间范围内的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数据", vehicleId, gpsList.size());
//            logger.info("车辆ID: {} 查询到 {} 条GPS数据 startTime:{},endTime:{}", vehicleId, gpsList.size(),startTime,endTime);
            
            // 按时间段分组GPS数据
            Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, segmentMinutes);
            return calculateVehicleSegmentMileageWithGpsList(vehicleId, gpsList, startTime, endTime);
            
            int savedCount = 0;
            VehicleGps previousSegmentLastPoint = null; // 记录上一个时间段的最后一个点
            // 遍历每个时间段,计算里程
            for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
                Date segmentStartTime = entry.getKey();
                List<VehicleGps> segmentGpsList = entry.getValue();
                if (segmentGpsList.size() < 2) {
                    // 如果本段只有1个点,但有上一段的最后一个点,仍可计算跨段距离
                    if (segmentGpsList.size() == 1 && previousSegmentLastPoint != null) {
                        // 保留当前点作为下一段的前置点,但不创建记录
                        previousSegmentLastPoint = segmentGpsList.get(0);
                    }
                    continue; // 至少需要2个点才能计算距离
                }
                // 检查是否已存在该时间段的记录
                VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
                if (existing != null) {
                    logger.debug("车辆 {} 时间段 {} 的分段里程已存在,跳过", vehicleId, segmentStartTime);
                    // 更新上一段最后一个点
                    previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                    continue;
                }
                // 计算时间段的结束时间
                Calendar cal = Calendar.getInstance();
                cal.setTime(segmentStartTime);
                cal.add(Calendar.MINUTE, segmentMinutes);
                Date segmentEndTime = cal.getTime();
                // 计算该时间段的里程(包括跨段距离)
                BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, calculateMethod, previousSegmentLastPoint);
                // 收集GPS ID列表(包括上一段的最后一个点,因为跨段间隙距离也用到了它)
                List<Long> gpsIdList = new ArrayList<>();
                // 如果有上一段的最后一个点,先添加它的ID
                if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
                    gpsIdList.add(previousSegmentLastPoint.getGpsId());
                }
                // 再添加当前段的所有GPS点ID
                for (VehicleGps gps : segmentGpsList) {
                    if (gps.getGpsId() != null) {
                        gpsIdList.add(gps.getGpsId());
                    }
                }
                String gpsIds = gpsIdList.stream()
                    .map(String::valueOf)
                    .collect(java.util.stream.Collectors.joining(","));
                // 创建分段里程记录
                VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
                segment.setVehicleId(vehicleId);
                // 从GPS数据或车辆表获取车牌号
                String vehicleNo = segmentGpsList.get(0).getVehicleNo();
                if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
                    // GPS数据中没有车牌号,从车辆表查询
                    VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
                    if (vehicleInfo != null) {
                        vehicleNo = vehicleInfo.getVehicleNo();
                    }
                }
                segment.setVehicleNo(vehicleNo);
                segment.setSegmentStartTime(segmentStartTime);
                segment.setSegmentEndTime(segmentEndTime);
                // 起点坐标
                VehicleGps firstPoint = segmentGpsList.get(0);
                segment.setStartLongitude(BigDecimal.valueOf(firstPoint.getLongitude()));
                segment.setStartLatitude(BigDecimal.valueOf(firstPoint.getLatitude()));
                // 终点坐标
                VehicleGps lastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                segment.setEndLongitude(BigDecimal.valueOf(lastPoint.getLongitude()));
                segment.setEndLatitude(BigDecimal.valueOf(lastPoint.getLatitude()));
                segment.setSegmentDistance(distance);
                segment.setGpsPointCount(gpsIdList.size()); // GPS点数:包括边缘点 + 当前段的点
                segment.setGpsIds(gpsIds); // 设置GPS ID列表
                segment.setCalculateMethod(calculateMethod);
                // 查询并关联正在执行的任务
                associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
                // 保存到数据库
                segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
                // 更新上一段最后一个点,供下一段使用
                previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                // 记录已计算的GPS点到状态表(如果开启了重复计算控制)
                if (skipCalculated && segment.getSegmentId() != null) {
                    for (Long gpsId : gpsIdList) {
                        try {
                            segmentMileageMapper.insertGpsCalculated(gpsId, segment.getSegmentId(), vehicleId);
                        } catch (Exception e) {
                            // 忽略重复键异常,继续处理
                            logger.debug("记录GPS计算状态失败,可能已存在: gpsId={}", gpsId);
                        }
                    }
                }
                savedCount++;
                logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}",
                           vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(),
                           gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
        } catch (Exception e) {
            logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
            throw new RuntimeException("计算分段里程失败: " + e.getMessage());
        }
    }
    /**
     * 根据提供的GPS列表计算车辆分段里程
     * @param vehicleId 车辆ID
     * @param gpsList GPS列表(已按时间排序)
     * @param startTime 起始时间
     * @param endTime 结束时间
     * @return 生成的分段数量
     */
    private int calculateVehicleSegmentMileageWithGpsList(Long vehicleId, List<VehicleGps> gpsList,
                                                           Date startTime, Date endTime) {
        try {
            // 验证输入数据
            if (gpsList == null || gpsList.isEmpty()) {
                logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime);
                return 0;
            }
            
            logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
            // 加载配置参数
            MileageCalculationConfig config = loadMileageCalculationConfig();
            
            // 自动触发汇总生成每日统计(如果有数据被保存)
            // 按时间段分组GPS数据
            Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, config.segmentMinutes);
            // 处理每个时间段并计算里程
            int savedCount = processSegmentedGpsData(vehicleId, segmentedData, config);
//            logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
            // 自动触发每日统计汇总
            if (savedCount > 0) {
                try {
                    // 获取涉及的日期范围,触发汇总
                    Set<Date> affectedDates = new HashSet<>();
                    Calendar cal = Calendar.getInstance();
                    for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
                        cal.setTime(entry.getKey());
                        cal.set(Calendar.HOUR_OF_DAY, 0);
                        cal.set(Calendar.MINUTE, 0);
                        cal.set(Calendar.SECOND, 0);
                        cal.set(Calendar.MILLISECOND, 0);
                        affectedDates.add(cal.getTime());
                    }
                    // 对每个涉及的日期,触发汇总
                    for (Date statDate : affectedDates) {
                        try {
                            mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
                            logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
                        } catch (Exception e) {
                            logger.error("车辆 {} 日期 {} 自动汇总统计失败", vehicleId, statDate, e);
                        }
                    }
                } catch (Exception e) {
                    logger.error("触发自动汇总失败", e);
                }
                triggerDailyMileageAggregation(vehicleId, segmentedData);
            }
            
            return savedCount;
@@ -366,6 +305,305 @@
            logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
            throw new RuntimeException("计算分段里程失败: " + e.getMessage());
        }
    }
    /**
     * 加载里程计算配置参数
     */
    private MileageCalculationConfig loadMileageCalculationConfig() {
        MileageCalculationConfig config = new MileageCalculationConfig();
        // 获取时间间隔配置(分钟)
        config.segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null
            ? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes"))
            : 5;
        // 获取计算方式配置
        config.calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
        if (config.calculateMethod == null || config.calculateMethod.isEmpty()) {
            config.calculateMethod = "tianditu";
        }
        // 获取是否跳过已计算GPS点的配置
        String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
        config.skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
//        logger.info("控制跳过重复计算标识: {}", config.skipCalculated);
        return config;
    }
    /**
     * 处理分段后的GPS数据并计算里程
     * @param vehicleId 车辆ID
     * @param segmentedData 分段后的GPS数据
     * @param config 计算配置
     * @return 成功保存的分段数量
     */
    private int processSegmentedGpsData(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData,
                                         MileageCalculationConfig config) {
        int savedCount = 0;
        VehicleGps previousSegmentLastPoint = null; // 记录上一个时间段的最后一个点
        // 遍历每个时间段,计算里程
        for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
            Date segmentStartTime = entry.getKey();
            List<VehicleGps> segmentGpsList = entry.getValue();
            // 校验当前时间段数据
            if (!isSegmentValidForCalculation(segmentGpsList, previousSegmentLastPoint, vehicleId, segmentStartTime)) {
                // 保留当前点作为下一段的前置点(如果有的话)
                if (!segmentGpsList.isEmpty()) {
                    previousSegmentLastPoint = segmentGpsList.get(0);
                }
                continue;
            }
            // 检查是否已存在该时间段的记录
            if (isSegmentAlreadyCalculated(vehicleId, segmentStartTime, segmentGpsList)) {
                previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                continue;
            }
            // 计算并保存分段里程
            boolean success = calculateAndSaveSegment(vehicleId, segmentStartTime, segmentGpsList,
                                                       previousSegmentLastPoint, config);
            if (success) {
                savedCount++;
            }
            // 更新上一段最后一个点,供下一段使用
            previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
        }
        return savedCount;
    }
    /**
     * 校验时间段数据是否有效
     */
    private boolean isSegmentValidForCalculation(List<VehicleGps> segmentGpsList,
                                                  VehicleGps previousSegmentLastPoint,
                                                  Long vehicleId, Date segmentStartTime) {
        // 如果当前段没有GPS点,跳过
        if (segmentGpsList == null || segmentGpsList.isEmpty()) {
            return false;
        }
        // 如果本段只有1个点,且没有上一段的最后一个点,无法计算距离
        if (segmentGpsList.size() == 1 && previousSegmentLastPoint == null) {
//            logger.debug("车辆 {} 时间段 {} 只有1个GPS点且无前置点,暂存待下一段计算", vehicleId, segmentStartTime);
            return false;
        }
        return true;
    }
    /**
     * 检查时间段是否已被计算
     */
    private boolean isSegmentAlreadyCalculated(Long vehicleId, Date segmentStartTime, List<VehicleGps> segmentGpsList) {
        VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
        if (existing != null) {
            logger.debug("车辆 {} 时间段 {} 的分段里程已存在,跳过", vehicleId, segmentStartTime);
            return true;
        }
        return false;
    }
    /**
     * 计算并保存单个时间段的里程
     */
    private boolean calculateAndSaveSegment(Long vehicleId, Date segmentStartTime,
                                             List<VehicleGps> segmentGpsList,
                                             VehicleGps previousSegmentLastPoint,
                                             MileageCalculationConfig config) {
        try {
            // 计算时间段的结束时间
            Date segmentEndTime = calculateSegmentEndTime(segmentStartTime, config.segmentMinutes);
            // 计算该时间段的里程(包括跨段距离)
            BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, config.calculateMethod, previousSegmentLastPoint);
            // 收集GPS ID列表
            List<Long> gpsIdList = collectGpsIds(segmentGpsList, previousSegmentLastPoint);
            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);
            segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
            // 记录已计算的GPS点(如果开启了重复计算控制)
            if (config.skipCalculated && segment.getSegmentId() != null) {
                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);
            return true;
        } catch (Exception e) {
            logger.error("保存车辆 {} 时间段 {} 的里程记录失败", vehicleId, segmentStartTime, e);
            return false;
        }
    }
    /**
     * 计算时间段结束时间
     */
    private Date calculateSegmentEndTime(Date segmentStartTime, int segmentMinutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(segmentStartTime);
        cal.add(Calendar.MINUTE, segmentMinutes);
        return cal.getTime();
    }
    /**
     * 收集GPS ID列表(包括前置点)
     */
    private List<Long> collectGpsIds(List<VehicleGps> segmentGpsList, VehicleGps previousSegmentLastPoint) {
        List<Long> gpsIdList = new ArrayList<>();
        // 如果有上一段的最后一个点,先添加它的ID(用于计算跨段距离)
        if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
            gpsIdList.add(previousSegmentLastPoint.getGpsId());
        }
        // 再添加当前段的所有GPS点ID
        for (VehicleGps gps : segmentGpsList) {
            if (gps.getGpsId() != null) {
                gpsIdList.add(gps.getGpsId());
            }
        }
        return gpsIdList;
    }
    /**
     * 构建分段里程记录对象
     */
    private VehicleGpsSegmentMileage buildSegmentMileageRecord(Long vehicleId, Date segmentStartTime,
                                                                 Date segmentEndTime, List<VehicleGps> segmentGpsList,
                                                                 BigDecimal distance, List<Long> gpsIdList,
                                                                 String gpsIds, String calculateMethod) {
        VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
        segment.setVehicleId(vehicleId);
        // 获取车牌号
        String vehicleNo = getVehicleNo(vehicleId, segmentGpsList.get(0));
        segment.setVehicleNo(vehicleNo);
        // 设置时间范围
        segment.setSegmentStartTime(segmentStartTime);
        segment.setSegmentEndTime(segmentEndTime);
        // 设置起点坐标
        VehicleGps firstPoint = segmentGpsList.get(0);
        segment.setStartLongitude(BigDecimal.valueOf(firstPoint.getLongitude()));
        segment.setStartLatitude(BigDecimal.valueOf(firstPoint.getLatitude()));
        // 设置终点坐标
        VehicleGps lastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
        segment.setEndLongitude(BigDecimal.valueOf(lastPoint.getLongitude()));
        segment.setEndLatitude(BigDecimal.valueOf(lastPoint.getLatitude()));
        // 设置里程数据
        segment.setSegmentDistance(distance);
        segment.setGpsPointCount(gpsIdList.size());
        segment.setGpsIds(gpsIds);
        segment.setCalculateMethod(calculateMethod);
        // 查询并关联正在执行的任务
        associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
        return segment;
    }
    /**
     * 获取车牌号
     */
    private String getVehicleNo(Long vehicleId, VehicleGps firstGps) {
        String vehicleNo = firstGps.getVehicleNo();
        if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
            // GPS数据中没有车牌号,从车辆表查询
            VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
            if (vehicleInfo != null) {
                vehicleNo = vehicleInfo.getVehicleNo();
            }
        }
        return vehicleNo;
    }
    /**
     * 记录已计算的GPS点到状态表
     */
    private void recordCalculatedGpsPoints(List<Long> gpsIdList, Long segmentId, Long vehicleId) {
        for (Long gpsId : gpsIdList) {
            try {
                segmentMileageMapper.insertGpsCalculated(gpsId, segmentId, vehicleId);
            } catch (Exception e) {
                // 忽略重复键异常,继续处理
                logger.debug("记录GPS计算状态失败,可能已存在: gpsId={}", gpsId);
            }
        }
    }
    /**
     * 触发每日里程统计汇总
     */
    private void triggerDailyMileageAggregation(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData) {
        try {
            // 获取涉及的日期范围
            Set<Date> affectedDates = extractAffectedDates(segmentedData);
            // 对每个涉及的日期,触发汇总
            for (Date statDate : affectedDates) {
                try {
                    mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
//                    logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
                } catch (Exception e) {
                    logger.error("车辆 {} 日期 {} 自动汇总统计失败", vehicleId, statDate, e);
                }
            }
        } catch (Exception e) {
            logger.error("触发自动汇总失败", e);
        }
    }
    /**
     * 提取受影响的日期列表(用于汇总统计)
     */
    private Set<Date> extractAffectedDates(Map<Date, List<VehicleGps>> segmentedData) {
        Set<Date> affectedDates = new HashSet<>();
        Calendar cal = Calendar.getInstance();
        for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
            cal.setTime(entry.getKey());
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            affectedDates.add(cal.getTime());
        }
        return affectedDates;
    }
    /**
     * 里程计算配置类
     */
    private static class MileageCalculationConfig {
        int segmentMinutes;          // 时间段间隔(分钟)
        String calculateMethod;      // 计算方式
        boolean skipCalculated;      // 是否跳过已计算的GPS点
    }
    /**
@@ -409,12 +647,13 @@
    /**
     * 计算一个时间段内的总里程(包括与上一段的间隙距离)
     * @param gpsList 当前时间段的GPS点列表
     * @param gpsList 当前时间段的GPS点列表(至少1个点)
     * @param calculateMethod 计算方式
     * @param previousLastPoint 上一个时间段的最后一个点(可为null)
     * @return 总里程(公里),保留3位小数
     */
    private BigDecimal calculateSegmentDistanceWithGap(List<VehicleGps> gpsList, String calculateMethod, VehicleGps previousLastPoint) {
        if (gpsList == null || gpsList.size() < 2) {
        if (gpsList == null || gpsList.isEmpty()) {
            return BigDecimal.ZERO;
        }
        
@@ -431,18 +670,21 @@
            );
            totalDistance = totalDistance.add(BigDecimal.valueOf(gapDistance));
            
            logger.debug("跨段间隙距离: {}km (上一段末点 -> 当前段首点)",
                String.format("%.3f", gapDistance));
//            logger.debug("跨段间隙距离: {}km (上一段末点 -> 当前段首点)",
//                String.format("%.3f", gapDistance));
        }
        
        // 2. 再计算当前段内部的距离
        BigDecimal segmentInternalDistance;
        if ("tianditu".equalsIgnoreCase(calculateMethod)) {
            segmentInternalDistance = calculateDistanceByTianditu(gpsList);
        } else {
            segmentInternalDistance = calculateDistanceByHaversine(gpsList);
        // 2. 再计算当前段内部的距离(如果有2个或以上GPS点)
        if (gpsList.size() >= 2) {
            BigDecimal segmentInternalDistance;
            if ("tianditu".equalsIgnoreCase(calculateMethod)) {
                segmentInternalDistance = calculateDistanceByTianditu(gpsList);
            } else {
                segmentInternalDistance = calculateDistanceByHaversine(gpsList);
            }
            totalDistance = totalDistance.add(segmentInternalDistance);
        }
        totalDistance = totalDistance.add(segmentInternalDistance);
        // 如果只有1个点,段内距离为0,只计算跨段距离
        
        return totalDistance.setScale(3, RoundingMode.HALF_UP);
    }
@@ -606,10 +848,12 @@
                                      Date segmentStartTime, Date segmentEndTime) {
        try {
            // 查询该车辆正在执行的任务列表
            List<SysTask> activeTasks = sysTaskMapper.selectActiveTasksByVehicleId(vehicleId);
          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;
            }
            
@@ -625,8 +869,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; // 找到一个匹配的任务即可
                }
            }