wlzboy
2025-12-02 d294abb765e4ed349907c92ce313689c6299ba7d
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
@@ -2,6 +2,8 @@
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;
@@ -11,9 +13,13 @@
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;
/**
@@ -32,6 +38,12 @@
    
    @Autowired
    private VehicleGpsMapper vehicleGpsMapper;
    @Autowired
    private VehicleGpsSegmentMileageMapper segmentMileageMapper;
    @Autowired
    private VehicleInfoMapper vehicleInfoMapper;
    /**
     * 查询车辆里程统计
@@ -122,10 +134,18 @@
                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. 设置统计数据
@@ -161,8 +181,14 @@
    @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("没有找到活跃车辆");
@@ -208,8 +234,8 @@
            );
            
            // 获取这段距离的时间区间
            Date segmentStart = p1.getCollectTime();
            Date segmentEnd = p2.getCollectTime();
            Date segmentStart = parseDateTime(p1.getCollectTime());
            Date segmentEnd = parseDateTime(p2.getCollectTime());
            
            // 计算这段距离在任务时段的占比
            double taskRatio = calculateTaskOverlapRatio(segmentStart, segmentEnd, taskIntervals);
@@ -226,6 +252,16 @@
        // 计算任务里程占比
        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;
            }
        }
        
        // 保留两位小数
@@ -240,6 +276,11 @@
     * 使用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);
@@ -293,4 +334,193 @@
        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());
        }
    }
}