package com.ruoyi.system.service.impl; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.common.config.TiandituMapConfig; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.system.domain.VehicleGps; import com.ruoyi.system.domain.VehicleGpsSegmentMileage; import com.ruoyi.system.domain.SysTask; import com.ruoyi.system.domain.VehicleInfo; import com.ruoyi.system.mapper.VehicleGpsMapper; import com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper; import com.ruoyi.system.mapper.SysTaskMapper; import com.ruoyi.system.mapper.VehicleInfoMapper; import com.ruoyi.system.service.IVehicleGpsSegmentMileageService; import com.ruoyi.system.service.IVehicleMileageStatsService; import com.ruoyi.system.service.ISysConfigService; /** * 车辆GPS分段里程Service业务层处理 */ @Service public class VehicleGpsSegmentMileageServiceImpl implements IVehicleGpsSegmentMileageService { private static final Logger logger = LoggerFactory.getLogger(VehicleGpsSegmentMileageServiceImpl.class); /** 地球半径(公里) */ private static final double EARTH_RADIUS_KM = 6371.0; /** 天地图批量路径规划API */ private static final String TIANDITU_ROUTE_API = "http://api.tianditu.gov.cn/drive"; @Autowired private VehicleGpsSegmentMileageMapper segmentMileageMapper; @Autowired private VehicleGpsMapper vehicleGpsMapper; @Autowired private SysTaskMapper sysTaskMapper; @Autowired private TiandituMapConfig tiandituMapConfig; @Autowired private ISysConfigService configService; @Autowired private RedisCache redisCache; @Autowired private IVehicleMileageStatsService mileageStatsService; @Autowired private VehicleInfoMapper vehicleInfoMapper; @Override public VehicleGpsSegmentMileage selectVehicleGpsSegmentMileageById(Long segmentId) { return segmentMileageMapper.selectVehicleGpsSegmentMileageById(segmentId); } @Override public List selectVehicleGpsSegmentMileageList(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) { return segmentMileageMapper.selectVehicleGpsSegmentMileageList(vehicleGpsSegmentMileage); } @Override public int insertVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) { return segmentMileageMapper.insertVehicleGpsSegmentMileage(vehicleGpsSegmentMileage); } @Override public int updateVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) { return segmentMileageMapper.updateVehicleGpsSegmentMileage(vehicleGpsSegmentMileage); } @Override public int deleteVehicleGpsSegmentMileageByIds(Long[] segmentIds) { return segmentMileageMapper.deleteVehicleGpsSegmentMileageByIds(segmentIds); } @Override public int deleteVehicleGpsSegmentMileageById(Long segmentId) { return segmentMileageMapper.deleteVehicleGpsSegmentMileageById(segmentId); } @Override public int batchCalculateSegmentMileage(Date startTime, Date endTime) { try { // 查询所有活跃车辆 List vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(); if (vehicleIds == null || vehicleIds.isEmpty()) { logger.info("没有找到活跃车辆"); return 0; } int successCount = 0; for (Long vehicleId : vehicleIds) { try { int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime); if (segmentCount > 0) { successCount++; } } catch (Exception e) { logger.error("计算车辆 {} 的分段里程失败", vehicleId, e); } } logger.info("批量分段里程计算完成 - 时间范围: {} 到 {}, 总车辆数: {}, 成功: {}", startTime, endTime, vehicleIds.size(), successCount); return successCount; } catch (Exception e) { logger.error("批量计算分段里程失败", e); throw new RuntimeException("批量计算失败: " + e.getMessage()); } } @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); // 查询所有活跃车辆 List vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(); if (vehicleIds == null || vehicleIds.isEmpty()) { logger.info("没有找到活跃车辆"); return 0; } int successCount = 0; int totalUncalculated = 0; for (Long vehicleId : vehicleIds) { try { // 查询该车辆未被计算的GPS数据 List uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTime, endTime); if (uncalculatedGps == null || uncalculatedGps.isEmpty()) { logger.debug("车辆 {} 没有未计算的GPS数据", vehicleId); continue; } totalUncalculated += uncalculatedGps.size(); logger.info("车辆 {} 发现 {} 个未计算的GPS点,开始补偿计算...", vehicleId, uncalculatedGps.size()); // 重新计算该车辆在该时间范围的分段里程 // 注意:这里会重新计算整个时间范围,确保边缘节点被正确处理 int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime); if (segmentCount > 0) { successCount++; logger.info("车辆 {} 补偿计算完成,生成 {} 个分段记录", vehicleId, segmentCount); } } catch (Exception e) { logger.error("车辆 {} 补偿计算失败", vehicleId, e); } } logger.info("补偿计算完成 - 总车辆数: {}, 未计算GPS点数: {}, 成功车辆数: {}", vehicleIds.size(), totalUncalculated, successCount); return successCount; } catch (Exception e) { logger.error("补偿计算失败", e); throw new RuntimeException("补偿计算失败: " + e.getMessage()); } } @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 gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, startTime, endTime); if (gpsList == null || gpsList.isEmpty()) { logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime); return 0; } logger.info("车辆ID: {} 查询到 {} 条GPS数据", vehicleId, gpsList.size()); // 按时间段分组GPS数据 Map> segmentedData = segmentGpsDataByTime(gpsList, segmentMinutes); int savedCount = 0; VehicleGps previousSegmentLastPoint = null; // 记录上一个时间段的最后一个点 // 遍历每个时间段,计算里程 for (Map.Entry> entry : segmentedData.entrySet()) { Date segmentStartTime = entry.getKey(); List 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 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); } logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount); // 自动触发汇总生成每日统计(如果有数据被保存) if (savedCount > 0) { try { // 获取涉及的日期范围,触发汇总 Set affectedDates = new HashSet<>(); Calendar cal = Calendar.getInstance(); for (Map.Entry> 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); } } return savedCount; } catch (Exception e) { logger.error("计算车辆 {} 分段里程失败", vehicleId, e); throw new RuntimeException("计算分段里程失败: " + e.getMessage()); } } /** * 将GPS数据按时间段分组 */ private Map> segmentGpsDataByTime(List gpsList, int segmentMinutes) { Map> segmentedData = new LinkedHashMap<>(); for (VehicleGps gps : gpsList) { // 解析GPS采集时间 Date collectTime = parseDateTime(gps.getCollectTime()); // 计算该GPS点所属的时间段起始时间(向下取整到最近的时间段) Date segmentStart = getSegmentStartTime(collectTime, segmentMinutes); // 添加到对应时间段 segmentedData.computeIfAbsent(segmentStart, k -> new ArrayList<>()).add(gps); } return segmentedData; } /** * 获取时间段的起始时间(向下取整) */ private Date getSegmentStartTime(Date time, int segmentMinutes) { Calendar cal = Calendar.getInstance(); cal.setTime(time); // 将分钟数向下取整到最近的分段 int minute = cal.get(Calendar.MINUTE); int segmentIndex = minute / segmentMinutes; int alignedMinute = segmentIndex * segmentMinutes; cal.set(Calendar.MINUTE, alignedMinute); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); } /** * 计算一个时间段内的总里程(包括与上一段的间隙距离) * @param gpsList 当前时间段的GPS点列表 * @param calculateMethod 计算方式 * @param previousLastPoint 上一个时间段的最后一个点(可为null) */ private BigDecimal calculateSegmentDistanceWithGap(List gpsList, String calculateMethod, VehicleGps previousLastPoint) { if (gpsList == null || gpsList.size() < 2) { return BigDecimal.ZERO; } BigDecimal totalDistance = BigDecimal.ZERO; // 1. 先计算跨段间隙距离(上一段最后一个点 -> 当前段第一个点) if (previousLastPoint != null) { VehicleGps currentFirstPoint = gpsList.get(0); double gapDistance = calculateHaversineDistance( previousLastPoint.getLatitude().doubleValue(), previousLastPoint.getLongitude().doubleValue(), currentFirstPoint.getLatitude().doubleValue(), currentFirstPoint.getLongitude().doubleValue() ); totalDistance = totalDistance.add(BigDecimal.valueOf(gapDistance)); logger.debug("跨段间隙距离: {}km (上一段末点 -> 当前段首点)", String.format("%.3f", gapDistance)); } // 2. 再计算当前段内部的距离 BigDecimal segmentInternalDistance; if ("tianditu".equalsIgnoreCase(calculateMethod)) { segmentInternalDistance = calculateDistanceByTianditu(gpsList); } else { segmentInternalDistance = calculateDistanceByHaversine(gpsList); } totalDistance = totalDistance.add(segmentInternalDistance); return totalDistance.setScale(3, RoundingMode.HALF_UP); } /** * 计算一个时间段内的总里程(仅段内距离) */ private BigDecimal calculateSegmentDistance(List gpsList, String calculateMethod) { if (gpsList == null || gpsList.size() < 2) { return BigDecimal.ZERO; } BigDecimal totalDistance = BigDecimal.ZERO; if ("tianditu".equalsIgnoreCase(calculateMethod)) { // 使用天地图API计算(批量计算更精确) totalDistance = calculateDistanceByTianditu(gpsList); } else { // 使用Haversine公式计算(直线距离,更快但不够精确) totalDistance = calculateDistanceByHaversine(gpsList); } return totalDistance.setScale(3, RoundingMode.HALF_UP); } /** * 使用天地图API计算距离 */ private BigDecimal calculateDistanceByTianditu(List gpsList) { try { // 天地图路径规划API有点数限制,如果点太多需要分批处理 int maxPointsPerRequest = 50; // 天地图API建议不超过50个点 BigDecimal totalDistance = BigDecimal.ZERO; // 如果GPS点数较少,直接使用Haversine公式(避免频繁调用API) if (gpsList.size() <= 3) { return calculateDistanceByHaversine(gpsList); } // 分批处理 for (int i = 0; i < gpsList.size() - 1; i += maxPointsPerRequest) { int endIndex = Math.min(i + maxPointsPerRequest, gpsList.size()); List batchList = gpsList.subList(i, endIndex); BigDecimal batchDistance = calculateBatchDistanceByTianditu(batchList); totalDistance = totalDistance.add(batchDistance); } return totalDistance; } catch (Exception e) { logger.warn("天地图API计算距离失败,降级使用Haversine公式: {}", e.getMessage()); return calculateDistanceByHaversine(gpsList); } } /** * 使用天地图API计算一批GPS点的距离 */ private BigDecimal calculateBatchDistanceByTianditu(List gpsList) { try { // 简化处理:计算相邻点之间的直线距离总和 // 注:天地图的路径规划API主要用于导航,这里用简化的距离计算 BigDecimal totalDistance = BigDecimal.ZERO; for (int i = 0; i < gpsList.size() - 1; i++) { VehicleGps p1 = gpsList.get(i); VehicleGps p2 = gpsList.get(i + 1); double distance = calculateHaversineDistance( p1.getLatitude().doubleValue(), p1.getLongitude().doubleValue(), p2.getLatitude().doubleValue(), p2.getLongitude().doubleValue() ); totalDistance = totalDistance.add(BigDecimal.valueOf(distance)); } return totalDistance; } catch (Exception e) { logger.error("天地图批量距离计算失败", e); throw e; } } /** * 使用Haversine公式计算距离 */ private BigDecimal calculateDistanceByHaversine(List gpsList) { BigDecimal totalDistance = BigDecimal.ZERO; for (int i = 0; i < gpsList.size() - 1; i++) { VehicleGps p1 = gpsList.get(i); VehicleGps p2 = gpsList.get(i + 1); double distance = calculateHaversineDistance( p1.getLatitude().doubleValue(), p1.getLongitude().doubleValue(), p2.getLatitude().doubleValue(), p2.getLongitude().doubleValue() ); totalDistance = totalDistance.add(BigDecimal.valueOf(distance)); } return totalDistance; } /** * 使用Haversine公式计算两点之间的距离(公里) */ private double calculateHaversineDistance(double lat1, double lon1, double lat2, double lon2) { // 如果起点和终点经纬度相同,直接返回0,避免不必要的计算 if (lat1 == lat2 && lon1 == lon2) { return 0.0; } // 将角度转换为弧度 double dLat = Math.toRadians(lat2 - lat1); double dLon = Math.toRadians(lon2 - lon1); double rLat1 = Math.toRadians(lat1); double rLat2 = Math.toRadians(lat2); // Haversine公式 double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(rLat1) * Math.cos(rLat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS_KM * c; } /** * 解析日期时间字符串 */ 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()); } catch (Exception e) { throw new RuntimeException("日期时间格式错误: " + dateTimeStr + ", 应为 yyyy-MM-dd HH:mm:ss", e); } } /** * 查询并关联车辆正在执行的任务 * @param segment 分段里程记录 * @param vehicleId 车辆ID * @param segmentStartTime 时间段开始时间 * @param segmentEndTime 时间段结束时间 */ private void associateActiveTask(VehicleGpsSegmentMileage segment, Long vehicleId, Date segmentStartTime, Date segmentEndTime) { try { // 查询该车辆正在执行的任务列表 List activeTasks = sysTaskMapper.selectActiveTasksByVehicleId(vehicleId); if (activeTasks == null || activeTasks.isEmpty()) { logger.debug("车辆 {} 在时间段 {} - {} 没有正在执行的任务", vehicleId, segmentStartTime, segmentEndTime); return; } // 遍历任务,查找与当前时间段有重叠的任务 for (SysTask task : activeTasks) { // 获取任务的实际执行时间,如果没有实际时间则使用计划时间 Date taskStart = task.getActualStartTime() != null ? task.getActualStartTime() : task.getPlannedStartTime(); Date taskEnd = task.getActualEndTime() != null ? task.getActualEndTime() : task.getPlannedEndTime(); // 判断时间段是否有重叠 if (isTimeOverlap(segmentStartTime, segmentEndTime, taskStart, taskEnd)) { // 关联任务ID和任务编号 segment.setTaskId(task.getTaskId()); segment.setTaskCode(task.getTaskCode()); logger.debug("车辆 {} 时间段 {} - {} 关联任务: taskId={}, taskCode={}", vehicleId, segmentStartTime, segmentEndTime, task.getTaskId(), task.getTaskCode()); break; // 找到一个匹配的任务即可 } } } catch (Exception e) { // 关联任务失败不影响主流程,只记录日志 logger.warn("关联车辆 {} 的任务信息失败", vehicleId, e); } } /** * 判断两个时间段是否有重叠 * @param start1 时间段1开始 * @param end1 时间段1结束 * @param start2 时间段2开始 * @param end2 时间段2结束 * @return true-有重叠, false-无重叠 */ private boolean isTimeOverlap(Date start1, Date end1, Date start2, Date end2) { // 任何时间为null,返回false if (start1 == null || end1 == null || start2 == null || end2 == null) { return false; } // 两个时间段有重叠的条件: // start1 < end2 && end1 > start2 return start1.before(end2) && end1.after(start2); } }