| | |
| | | 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; |
| | |
| | | @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("没有找到活跃车辆"); |
| | |
| | | |
| | | 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); |
| | |
| | | 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++; |
| | |
| | | @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; |
| | |
| | | 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点 |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | /** |
| | | * 计算一个时间段内的总里程(包括与上一段的间隙距离) |
| | | * @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; |
| | | } |
| | | |
| | |
| | | ); |
| | | 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); |
| | | } |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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; // 找到一个匹配的任务即可 |
| | | } |
| | | } |