| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | |
| | | import com.ruoyi.common.utils.DateUtils; |
| | |
| | | |
| | | /** 天地图批量路径规划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; |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | |
| | | String endTimeStr=DateUtils.formatDate(endTime, DateUtils.YYYY_MM_DD_HH_MM_SS); |
| | | for (Long vehicleId : vehicleIds) { |
| | | try { |
| | | // 查询该车辆未被计算的GPS数据 |
| | | List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTimeStr, endTimeStr); |
| | | // 分批处理车辆,避免内存溢出 |
| | | 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); |
| | |
| | | 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()) { |
| | | System.gc(); |
| | | logger.debug("补偿计算批次 {}-{} 完成,已建议JVM回收内存", batchStart + 1, batchEnd); |
| | | } |
| | | } |
| | | |
| | |
| | | * 收集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) { |
| | |
| | | |
| | | /** |
| | | * 解析日期时间字符串 |
| | | * 使用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); |
| | | } |