| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | |
| | | 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; |
| | | |
| | | /** |
| | |
| | | |
| | | @Autowired |
| | | private VehicleGpsMapper vehicleGpsMapper; |
| | | |
| | | @Autowired |
| | | private VehicleGpsSegmentMileageMapper segmentMileageMapper; |
| | | |
| | | @Autowired |
| | | private VehicleInfoMapper vehicleInfoMapper; |
| | | |
| | | /** |
| | | * 查询车辆里程统计 |
| | |
| | | stats.setVehicleId(vehicleId); |
| | | stats.setStatDate(statDate); |
| | | |
| | | // 获取车牌号 |
| | | // 获取车牌号:优先从GPS数据,如果没有则从车辆表查询 |
| | | String vehicleNo = null; |
| | | if (!gpsList.isEmpty() && gpsList.get(0).getVehicleNo() != null) { |
| | | stats.setVehicleNo(gpsList.get(0).getVehicleNo()); |
| | | 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. 设置统计数据 |
| | |
| | | @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(); |
| | | |
| | | // 查询所有活跃车辆 |
| | | List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(); |
| | | List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime); |
| | | |
| | | if (vehicleIds == null || vehicleIds.isEmpty()) { |
| | | logger.info("没有找到活跃车辆"); |
| | |
| | | ); |
| | | |
| | | // 获取这段距离的时间区间 |
| | | Date segmentStart = p1.getCollectTime(); |
| | | Date segmentEnd = p2.getCollectTime(); |
| | | Date segmentStart = parseDateTime(p1.getCollectTime()); |
| | | Date segmentEnd = parseDateTime(p2.getCollectTime()); |
| | | |
| | | // 计算这段距离在任务时段的占比 |
| | | double taskRatio = calculateTaskOverlapRatio(segmentStart, segmentEnd, taskIntervals); |
| | |
| | | // 计算任务里程占比 |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | // 保留两位小数 |
| | |
| | | * 使用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); |
| | |
| | | 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<VehicleGpsSegmentMileage> segments = segmentMileageMapper.selectSegmentsByDateRange(vehicleId, dayStart, dayEnd); |
| | | |
| | | if (segments == null || segments.isEmpty()) { |
| | | logger.info("车辆ID: {} 在日期: {} 无分段里程数据", vehicleId, statDate); |
| | | return null; |
| | | } |
| | | |
| | | // 3. 汇总里程数据 |
| | | BigDecimal totalMileage = BigDecimal.ZERO; |
| | | int totalGpsPoints = 0; |
| | | |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | if (segment.getSegmentDistance() != null) { |
| | | totalMileage = totalMileage.add(segment.getSegmentDistance()); |
| | | } |
| | | if (segment.getGpsPointCount() != null) { |
| | | totalGpsPoints += segment.getGpsPointCount(); |
| | | } |
| | | } |
| | | |
| | | // 4. 查询该日期的任务时间区间,计算任务里程和非任务里程 |
| | | List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); |
| | | |
| | | BigDecimal taskMileage = BigDecimal.ZERO; |
| | | BigDecimal nonTaskMileage = BigDecimal.ZERO; |
| | | |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | Date segStart = segment.getSegmentStartTime(); |
| | | Date segEnd = segment.getSegmentEndTime(); |
| | | BigDecimal segDistance = segment.getSegmentDistance() != null ? segment.getSegmentDistance() : BigDecimal.ZERO; |
| | | |
| | | // 计算该分段与任务时段的重叠比例 |
| | | double taskRatio = calculateTaskOverlapRatio(segStart, segEnd, taskIntervals); |
| | | |
| | | // 分摇里程 |
| | | BigDecimal taskDist = segDistance.multiply(BigDecimal.valueOf(taskRatio)); |
| | | BigDecimal nonTaskDist = segDistance.multiply(BigDecimal.valueOf(1 - taskRatio)); |
| | | |
| | | taskMileage = taskMileage.add(taskDist); |
| | | nonTaskMileage = nonTaskMileage.add(nonTaskDist); |
| | | } |
| | | |
| | | // 计算任务里程占比 |
| | | BigDecimal taskRatio = BigDecimal.ZERO; |
| | | if (totalMileage.compareTo(BigDecimal.ZERO) > 0) { |
| | | taskRatio = taskMileage.divide(totalMileage, 4, RoundingMode.HALF_UP); |
| | | |
| | | // 数据校验:占比应在0-1之间,如果超出说明数据异常 |
| | | if (taskRatio.compareTo(BigDecimal.ONE) > 0) { |
| | | logger.warn("车辆ID: {} 日期: {} 任务里程占比异常: {} (任务里程:{}, 总里程:{}), 强制设为1.0", |
| | | vehicleId, statDate, taskRatio, taskMileage, totalMileage); |
| | | taskRatio = BigDecimal.ONE; |
| | | } else if (taskRatio.compareTo(BigDecimal.ZERO) < 0) { |
| | | logger.warn("车辆ID: {} 日期: {} 任务里程占比为负: {}, 强制设为0", |
| | | vehicleId, statDate, taskRatio); |
| | | taskRatio = BigDecimal.ZERO; |
| | | } |
| | | } |
| | | |
| | | // 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(totalMileage.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setTaskMileage(taskMileage.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setNonTaskMileage(nonTaskMileage.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setTaskRatio(taskRatio); |
| | | stats.setGpsPointCount(totalGpsPoints); |
| | | stats.setTaskCount(taskIntervals == null ? 0 : taskIntervals.size()); |
| | | 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(); |
| | | |
| | | // 查询所有活跃车辆 |
| | | List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime); |
| | | |
| | | 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()); |
| | | } |
| | | } |
| | | } |