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.Calendar;
|
import java.util.Date;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
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<VehicleMileageStats> 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();
|
|
// 2. 查询车辆在该日期的GPS数据(按时间排序)
|
List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, dayStart, dayEnd);
|
|
if (gpsList == null || gpsList.isEmpty()) {
|
// logger.info("车辆ID: {} 在日期: {} 无GPS数据", vehicleId, statDate);
|
return null;
|
}
|
// logger.info("车辆ID:{} GPS数据条数:{}", vehicleId, gpsList.size());
|
// 3. 查询车辆在该日期的任务时间区间
|
List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd);
|
|
|
List<VehicleGpsSegmentMileage> mileages = this.getTaskDistanceMileage(vehicleId, dayStart, dayEnd);
|
int totalGpsPoints = mileages.stream()
|
.filter(segment -> segment.getGpsPointCount() != null)
|
.mapToInt(VehicleGpsSegmentMileage::getGpsPointCount)
|
.sum();
|
BigDecimal taskDistance = getTaskDistance(taskIntervals,mileages);
|
|
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(gpsList.size());
|
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);
|
}
|
|
logger.info("车辆ID: {} 日期: {} 里程统计完成 - 总里程: {}km, 任务里程: {}km, 非任务里程: {}km, 占比: {}",
|
vehicleId, statDate, calculation.totalMileage, 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();
|
|
// 查询所有活跃车辆
|
List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime);
|
|
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<VehicleGpsSegmentMileage> mileages) {
|
return mileages.stream()
|
.map(mileage -> mileage.getSegmentDistance())
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
}
|
/**
|
* 计算里程的内部方法
|
*/
|
private MileageCalculation calculateMileage(List<VehicleGps> 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<VehicleGpsSegmentMileage> getTaskDistanceMileage(Long vehicleId, Date segmentStart, Date segmentEnd) {
|
return segmentMileageMapper.selectSegmentsByDateRange(vehicleId, segmentStart, segmentEnd);
|
}
|
|
/**
|
* 计算在任务时间段内的实际任务里程
|
* 通过检查分段里程数据是否与任务时间段重叠,累加这些重叠分段的实际里程
|
*
|
* @param taskTimeIntervals 任务时间段列表
|
* @param segmentMileages 分段里程数据列表
|
* @return 在任务时间段内的总里程
|
*/
|
private BigDecimal getTaskDistance(List<TaskTimeInterval> taskTimeIntervals, List<VehicleGpsSegmentMileage> segmentMileages) {
|
if (taskTimeIntervals == null || taskTimeIntervals.isEmpty() ||
|
segmentMileages == null || segmentMileages.isEmpty()) {
|
return BigDecimal.ZERO;
|
}
|
|
BigDecimal totalTaskDistance = BigDecimal.ZERO;
|
|
// 遍历所有分段里程数据
|
for (VehicleGpsSegmentMileage segment : segmentMileages) {
|
// 只处理有关联任务ID且有距离数据的分段
|
|
|
// 检查该分段是否与任何任务时间段重叠
|
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<VehicleGpsSegmentMileage> 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 {
|
calculateAndSaveMileageStats(vehicleId, statDate);
|
//TODO
|
// 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;
|
}
|
|
List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd);
|
Integer taskCount = taskIntervals.size();
|
|
|
List<VehicleGpsSegmentMileage> mileages = this.getTaskDistanceMileage(vehicleId, dayStart, dayEnd);
|
Integer totalGpsPoints = mileages.stream()
|
.filter(segment -> segment.getGpsPointCount() != null)
|
.mapToInt(VehicleGpsSegmentMileage::getGpsPointCount)
|
.sum();
|
BigDecimal taskDistance = getTaskDistance(taskIntervals,mileages);
|
BigDecimal totalDistance = calculateTotalMileage(segments);
|
BigDecimal nonTaskDistance = totalDistance.subtract(taskDistance);
|
BigDecimal 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();
|
|
// 查询所有活跃车辆
|
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());
|
}
|
}
|
}
|