package com.ruoyi.system.service.impl; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import com.ruoyi.common.utils.DateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.system.domain.VehicleGps; import com.ruoyi.system.domain.VehicleMileageStats; import com.ruoyi.system.domain.VehicleGpsSegmentMileage; import com.ruoyi.system.domain.TaskTimeInterval; import com.ruoyi.system.domain.VehicleInfo; import com.ruoyi.system.mapper.VehicleGpsMapper; import com.ruoyi.system.mapper.VehicleMileageStatsMapper; import com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper; import com.ruoyi.system.mapper.VehicleInfoMapper; import com.ruoyi.system.service.IVehicleMileageStatsService; /** * 车辆里程统计Service业务层处理 */ @Service public class VehicleMileageStatsServiceImpl implements IVehicleMileageStatsService { private static final Logger logger = LoggerFactory.getLogger(VehicleMileageStatsServiceImpl.class); /** 地球半径(公里) */ private static final double EARTH_RADIUS_KM = 6371.0; @Autowired private VehicleMileageStatsMapper vehicleMileageStatsMapper; @Autowired private VehicleGpsMapper vehicleGpsMapper; @Autowired private VehicleGpsSegmentMileageMapper segmentMileageMapper; @Autowired private VehicleInfoMapper vehicleInfoMapper; /** * 查询车辆里程统计 */ @Override public VehicleMileageStats selectVehicleMileageStatsById(Long statsId) { return vehicleMileageStatsMapper.selectVehicleMileageStatsById(statsId); } /** * 查询车辆里程统计列表 */ @Override public List selectVehicleMileageStatsList(VehicleMileageStats vehicleMileageStats) { return vehicleMileageStatsMapper.selectVehicleMileageStatsList(vehicleMileageStats); } /** * 新增车辆里程统计 */ @Override public int insertVehicleMileageStats(VehicleMileageStats vehicleMileageStats) { return vehicleMileageStatsMapper.insertVehicleMileageStats(vehicleMileageStats); } /** * 修改车辆里程统计 */ @Override public int updateVehicleMileageStats(VehicleMileageStats vehicleMileageStats) { return vehicleMileageStatsMapper.updateVehicleMileageStats(vehicleMileageStats); } /** * 批量删除车辆里程统计 */ @Override public int deleteVehicleMileageStatsByIds(Long[] statsIds) { return vehicleMileageStatsMapper.deleteVehicleMileageStatsByIds(statsIds); } /** * 删除车辆里程统计信息 */ @Override public int deleteVehicleMileageStatsById(Long statsId) { return vehicleMileageStatsMapper.deleteVehicleMileageStatsById(statsId); } /** * 计算并保存指定车辆指定日期的里程统计 */ @Override public VehicleMileageStats calculateAndSaveMileageStats(Long vehicleId, Date statDate) { try { // 1. 获取统计日期的开始和结束时间 Calendar calendar = Calendar.getInstance(); calendar.setTime(statDate); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); Date dayStart = calendar.getTime(); calendar.add(Calendar.DAY_OF_MONTH, 1); Date dayEnd = calendar.getTime(); String dayStartStr=DateUtils.formatDate(dayStart, DateUtils.YYYY_MM_DD_HH_MM_SS); String dayEndStr=DateUtils.formatDate(dayEnd, DateUtils.YYYY_MM_DD_HH_MM_SS); // 2. 查询车辆在该日期的GPS数据(按时间排序) List gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, dayStartStr, dayEndStr); if (gpsList == null || gpsList.isEmpty()) { logger.info("---> 车辆ID:{} 在日期:{} 无GPS数据", vehicleId, statDate); return null; } // logger.info("---> 车辆ID:{} GPS数据条数:{}", vehicleId, gpsList.size()); // 3. 查询车辆在该日期的任务时间区间 List taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); // logger.info("---> 车辆ID:{} 任务时间区间数:{}", vehicleId, taskIntervals.size()); List mileages = this.getTaskDistanceMileage(vehicleId, dayStart, dayEnd).stream().filter(e -> e.getSegmentDistance() != null && e.getSegmentDistance().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList()); int totalGpsPoints = mileages.stream() .filter(segment -> segment.getGpsPointCount() != null) .mapToInt(VehicleGpsSegmentMileage::getGpsPointCount) .sum(); // logger.info("---> 车辆ID:{} 任务时间:{} 里程时间:{}", vehicleId, // taskIntervals.stream().map(e->"开始时间:"+DateUtils.formatDate(e.getStartTime())+",结束时间:"+DateUtils.formatDate(e.getEndTime())).collect(Collectors.joining()), // mileages.stream().map(e->"开始时间:"+DateUtils.formatDate(e.getSegmentStartTime())+",结束时间:"+DateUtils.formatDate(e.getSegmentEndTime())).collect(Collectors.joining())); BigDecimal taskDistance = getTaskDistance(taskIntervals,mileages); // logger.info("---> 车辆ID:{} 任务总里程:{}", vehicleId, taskDistance); MileageCalculation calculation = calculateMileage(gpsList, taskDistance); // logger.info("---> 计算出车辆当天总里程,车辆ID:{},总里程:{},任务里程:{}",vehicleId,calculation.totalMileage,calculation.taskMileage); // 5. 查询或创建统计记录 VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate); boolean isNew = (stats == null); if (isNew) { stats = new VehicleMileageStats(); stats.setVehicleId(vehicleId); stats.setStatDate(statDate); // 获取车牌号:优先从GPS数据,如果没有则从车辆表查询 String vehicleNo = null; if (!gpsList.isEmpty() && gpsList.get(0).getVehicleNo() != null) { vehicleNo = gpsList.get(0).getVehicleNo(); } if (vehicleNo == null || vehicleNo.trim().isEmpty()) { VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId); if (vehicleInfo != null) { vehicleNo = vehicleInfo.getVehicleNo(); } } stats.setVehicleNo(vehicleNo); } // 6. 设置统计数据 stats.setTotalMileage(calculation.totalMileage); stats.setTaskMileage(calculation.taskMileage); stats.setNonTaskMileage(calculation.nonTaskMileage); stats.setTaskRatio(calculation.taskRatio); stats.setGpsPointCount(totalGpsPoints); stats.setTaskCount(taskIntervals == null ? 0 : taskIntervals.size()); // logger.info("车辆ID: {} 日期: {} 里程统计完成 - 总里程: {}km, 任务里程: {}km, 非任务里程: {}km, 占比: {}", // vehicleId, statDate, calculation.totalMileage, taskDistance, calculation.nonTaskMileage, calculation.taskRatio); // 7. 保存到数据库 if (isNew) { vehicleMileageStatsMapper.insertVehicleMileageStats(stats); } else { vehicleMileageStatsMapper.updateVehicleMileageStats(stats); } // if (taskIntervals != null) { // logger.info("---> 同步里程完成,车辆ID: {} 日期: {} 里程统计完成 - 总里程: {}km,任务数量:{} 任务里程: {}km, 非任务里程: {}km, 占比: {}", // vehicleId, statDate, calculation.totalMileage,taskIntervals.size(), calculation.taskMileage, // calculation.nonTaskMileage, calculation.taskRatio); // } return stats; } catch (Exception e) { logger.error("---> 计算车辆里程统计失败 - 车辆ID: {}, 日期: {}", vehicleId, statDate, e); throw new RuntimeException("计算里程统计失败: " + e.getMessage()); } } /** * 批量计算所有车辆指定日期的里程统计 */ @Override public int batchCalculateMileageStats(Date statDate) { try { // 计算查询开始时间(7天前) Calendar calendar = Calendar.getInstance(); calendar.setTime(statDate); calendar.add(Calendar.DAY_OF_MONTH, -7); Date startTime = calendar.getTime(); String startTimeStr = DateUtils.formatDate(startTime,DateUtils.YYYY_MM_DD_HH_MM_SS); // 查询所有活跃车辆 List vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTimeStr); if (vehicleIds == null || vehicleIds.isEmpty()) { logger.info("没有找到活跃车辆"); return 0; } int successCount = 0; for (Long vehicleId : vehicleIds) { try { calculateAndSaveMileageStats(vehicleId, statDate); successCount++; } catch (Exception e) { logger.error("计算车辆 {} 的里程统计失败", vehicleId, e); } } // logger.info("批量里程统计完成 - 日期: {}, 总车辆数: {}, 成功: {}", statDate, vehicleIds.size(), successCount); return successCount; } catch (Exception e) { logger.error("批量计算里程统计失败 - 日期: {}", statDate, e); throw new RuntimeException("批量计算失败: " + e.getMessage()); } } private BigDecimal calculateTotalMileage(List mileages) { if (mileages == null || mileages.isEmpty()) { return BigDecimal.ZERO; } return mileages.stream() .filter(mileage -> mileage.getSegmentDistance() != null) // 过滤掉距离为null的分段 .map(mileage -> mileage.getSegmentDistance()) .reduce(BigDecimal.ZERO, BigDecimal::add); } /** * 计算里程的内部方法 */ private MileageCalculation calculateMileage(List gpsList, BigDecimal taskDistance) { MileageCalculation result = new MileageCalculation(); // 遍历GPS点,计算相邻点之间的距离 for (int i = 0; i < gpsList.size() - 1; i++) { VehicleGps p1 = gpsList.get(i); VehicleGps p2 = gpsList.get(i + 1); // 计算两点间距离(使用Haversine公式) double distance = calculateDistance( p1.getLatitude().doubleValue(), p1.getLongitude().doubleValue(), p2.getLatitude().doubleValue(), p2.getLongitude().doubleValue() ); result.totalMileage = result.totalMileage.add(BigDecimal.valueOf(distance)); } result.taskMileage=taskDistance; result.nonTaskMileage=result.totalMileage.subtract(result.taskMileage); // 计算任务里程占比 if (result.totalMileage.compareTo(BigDecimal.ZERO) > 0) { result.taskRatio = result.taskMileage.divide(result.totalMileage, 4, RoundingMode.HALF_UP); // 数据校验:占比应在0-1之间 if (result.taskRatio.compareTo(BigDecimal.ONE) > 0) { logger.warn("任务里程占比异常: {} (任务里程:{}, 总里程:{}), 强制设为1.0", result.taskRatio, result.taskMileage, result.totalMileage); result.taskRatio = BigDecimal.ONE; } else if (result.taskRatio.compareTo(BigDecimal.ZERO) < 0) { logger.warn("任务里程占比为负: {}, 强制设为0", result.taskRatio); result.taskRatio = BigDecimal.ZERO; } } // 保留两位小数 result.totalMileage = result.totalMileage.setScale(2, RoundingMode.HALF_UP); result.taskMileage = result.taskMileage.setScale(2, RoundingMode.HALF_UP); result.nonTaskMileage = result.nonTaskMileage.setScale(2, RoundingMode.HALF_UP); return result; } /** * 使用Haversine公式计算两个GPS坐标之间的距离(公里) */ private double calculateDistance(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 List getTaskDistanceMileage(Long vehicleId, Date segmentStart, Date segmentEnd) { List mileages = segmentMileageMapper.selectSegmentsByDateRange(vehicleId, segmentStart, segmentEnd); return mileages != null ? mileages : new ArrayList<>(); } /** * 计算在任务时间段内的实际任务里程 * 通过检查分段里程数据是否与任务时间段重叠,累加这些重叠分段的实际里程 * * @param taskTimeIntervals 任务时间段列表 * @param segmentMileages 分段里程数据列表 * @return 在任务时间段内的总里程 */ private BigDecimal getTaskDistance(List taskTimeIntervals, List segmentMileages) { if (taskTimeIntervals == null || taskTimeIntervals.isEmpty() || segmentMileages == null || segmentMileages.isEmpty()) { return BigDecimal.ZERO; } BigDecimal totalTaskDistance = BigDecimal.ZERO; // 遍历所有分段里程数据 for (VehicleGpsSegmentMileage segment : segmentMileages) { // 只处理有关联任务ID且有距离数据的分段 // if (segment.getTaskId() == null || segment.getSegmentDistance() == null) { // continue; // } // 检查该分段是否与任何任务时间段重叠 Date segmentStart = segment.getSegmentStartTime(); Date segmentEnd = segment.getSegmentEndTime(); boolean isInTaskPeriod = false; for (TaskTimeInterval taskInterval : taskTimeIntervals) { // 计算时间重叠 --任务时间段 long overlapStart = Math.max(segmentStart.getTime(), taskInterval.getStartTime().getTime()); long overlapEnd = Math.min(segmentEnd.getTime(), taskInterval.getEndTime().getTime()); // 如果有时间重叠,则该分段属于任务里程 if (overlapEnd > overlapStart) { isInTaskPeriod = true; break; } } // 如果分段在任务时间段内,则累加其里程 if (isInTaskPeriod) { totalTaskDistance = totalTaskDistance.add(segment.getSegmentDistance()); } } return totalTaskDistance; } /** * 计算指定时间段内的实际任务里程 * 通过查找与该时间段重叠的任务,并累加这些任务在该时间段内的实际里程 */ private double calculateActualTaskMileage(Date segmentStart, Date segmentEnd, List segmentMileages) { if (segmentMileages == null || segmentMileages.isEmpty()) { return 0.0; } double totalTaskMileage = 0.0; // 遍历所有分段里程数据,找出与指定时间段重叠且有关联任务的分段 for (VehicleGpsSegmentMileage segment : segmentMileages) { // 只处理有关联任务的分段 if (segment.getTaskId() == null) { continue; } // 检查分段时间与指定时间段是否有重叠 Date segStart = segment.getSegmentStartTime(); Date segEnd = segment.getSegmentEndTime(); // 计算重叠时间 long overlapStart = Math.max(segmentStart.getTime(), segStart.getTime()); long overlapEnd = Math.min(segmentEnd.getTime(), segEnd.getTime()); // 如果有时间重叠,则将该分段的距离加入任务里程 if (overlapEnd > overlapStart) { if (segment.getSegmentDistance() != null) { totalTaskMileage += segment.getSegmentDistance().doubleValue(); } } } return totalTaskMileage; } /** * 里程计算结果内部类 */ private static class MileageCalculation { BigDecimal totalMileage = BigDecimal.ZERO; BigDecimal taskMileage = BigDecimal.ZERO; BigDecimal nonTaskMileage = BigDecimal.ZERO; BigDecimal taskRatio = BigDecimal.ZERO; } /** * 解析日期时间字符串 * * @param dateTimeStr 日期时间字符串,格式:yyyy-MM-dd HH:mm:ss * @return Date对象 * @throws RuntimeException 如果解析失败 */ private Date parseDateTime(String dateTimeStr) { if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) { throw new RuntimeException("日期时间字符串不能为空"); } try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setLenient(false); return sdf.parse(dateTimeStr.trim()); } catch (ParseException e) { throw new RuntimeException("日期时间格式错误: " + dateTimeStr + ", 应为 yyyy-MM-dd HH:mm:ss", e); } } /** * 从分段里程数据汇总生成按日统计 */ @Override public VehicleMileageStats aggregateFromSegmentMileage(Long vehicleId, Date statDate) { try { // 1. 获取统计日期的开始和结束时间 Calendar calendar = Calendar.getInstance(); calendar.setTime(statDate); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); Date dayStart = calendar.getTime(); calendar.add(Calendar.DAY_OF_MONTH, 1); Date dayEnd = calendar.getTime(); // 2. 查询该日期范围内的所有分段里程数据 List segments = segmentMileageMapper.selectSegmentsByDateRange(vehicleId, dayStart, dayEnd); if (segments == null || segments.isEmpty()) { logger.info("车辆ID: {} 在日期: {} 无分段里程数据", vehicleId, statDate); return null; } List taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); Integer taskCount = taskIntervals != null ? taskIntervals.size() : 0; logger.info("车辆ID: {} 在日期: {} 有 {} 个任务", vehicleId, statDate, taskCount); List mileages = this.getTaskDistanceMileage(vehicleId, dayStart, dayEnd).stream().filter(e -> e.getSegmentDistance() != null && e.getSegmentDistance().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList()); logger.info("车辆ID: {} 在日期: {} 有 {} 个分段里程数据", vehicleId, statDate, mileages.size()); Integer totalGpsPoints = mileages != null ? mileages.stream() .filter(segment -> segment.getGpsPointCount() != null) .mapToInt(VehicleGpsSegmentMileage::getGpsPointCount) .sum() : 0; BigDecimal taskDistance = getTaskDistance(taskIntervals, mileages); BigDecimal totalDistance = calculateTotalMileage(segments); BigDecimal nonTaskDistance = totalDistance.subtract(taskDistance); // 防止除零错误 BigDecimal taskRatio = BigDecimal.ZERO; if (totalDistance != null && totalDistance.compareTo(BigDecimal.ZERO) > 0) { taskRatio = taskDistance.divide(totalDistance, 4, RoundingMode.HALF_UP); } // 3. 汇总里程数据 // 5. 查询或创建统计记录 VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate); boolean isNew = (stats == null); if (isNew) { stats = new VehicleMileageStats(); stats.setVehicleId(vehicleId); stats.setStatDate(statDate); // 获取车牌号:优先从分段数据,如果没有则从车辆表查询 String vehicleNo = null; if (!segments.isEmpty() && segments.get(0).getVehicleNo() != null) { vehicleNo = segments.get(0).getVehicleNo(); } if (vehicleNo == null || vehicleNo.trim().isEmpty()) { VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId); if (vehicleInfo != null) { vehicleNo = vehicleInfo.getVehicleNo(); } } stats.setVehicleNo(vehicleNo); } // 6. 设置统计数据 stats.setTotalMileage(totalDistance.setScale(2, RoundingMode.HALF_UP)); stats.setTaskMileage(taskDistance.setScale(2, RoundingMode.HALF_UP)); stats.setNonTaskMileage(nonTaskDistance.setScale(2, RoundingMode.HALF_UP)); stats.setTaskRatio(taskRatio); stats.setGpsPointCount(totalGpsPoints); stats.setTaskCount(taskCount); stats.setSegmentCount(segments.size()); stats.setDataSource("segment"); // 标记数据来源为分段汇总 // 7. 保存到数据库 if (isNew) { vehicleMileageStatsMapper.insertVehicleMileageStats(stats); } else { vehicleMileageStatsMapper.updateVehicleMileageStats(stats); } // logger.info("车辆ID: {} 日期: {} 从分段汇总完成 - 总里程: {}km, 任务里程: {}km, 非任务里程: {}km, 分段数: {}", // vehicleId, statDate, totalMileage, taskMileage, nonTaskMileage, segments.size()); return stats; } catch (Exception e) { logger.error("从分段汇总里程统计失败 - 车辆ID: {}, 日期: {}", vehicleId, statDate, e); throw new RuntimeException("汇总里程统计失败: " + e.getMessage()); } } /** * 批量从分段里程汇总生成按日统计 */ @Override public int batchAggregateFromSegmentMileage(Date statDate) { try { // 计算查询开始时间(7天前) Calendar calendar = Calendar.getInstance(); calendar.setTime(statDate); calendar.add(Calendar.DAY_OF_MONTH, -7); Date startTime = calendar.getTime(); String startTimeStr = DateUtils.formatDate(startTime, DateUtils.YYYY_MM_DD_HH_MM_SS); // 查询所有活跃车辆 List vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTimeStr); if (vehicleIds == null || vehicleIds.isEmpty()) { logger.info("没有找到活跃车辆"); return 0; } int successCount = 0; for (Long vehicleId : vehicleIds) { try { aggregateFromSegmentMileage(vehicleId, statDate); successCount++; } catch (Exception e) { logger.error("汇总车辆 {} 的里程统计失败", vehicleId, e); } } logger.info("批量里程汇总完成 - 日期: {}, 总车辆数: {}, 成功: {}", statDate, vehicleIds.size(), successCount); return successCount; } catch (Exception e) { logger.error("批量汇总里程统计失败 - 日期: {}", statDate, e); throw new RuntimeException("批量汇总失败: " + e.getMessage()); } } }