vehicle_mileage_stats.sql - 创建里程统计表和明细表vehicle_mileage_stats_job.sql - 创建定时任务配置vehicle_mileage_stats_menu.sql - 创建菜单权限配置VehicleMileageStats.java - 里程统计实体TaskTimeInterval.java - 任务时间区间辅助类VehicleMileageStatsMapper.java - 里程统计Mapper接口VehicleMileageStatsMapper.xml - MyBatis映射配置VehicleGpsMapper.java - 扩展GPS查询方法(新增2个方法)VehicleGpsMapper.xml - 扩展GPS查询SQLIVehicleMileageStatsService.java - Service接口VehicleMileageStatsServiceImpl.java - Service实现(核心算法)VehicleMileageStatsController.java - REST API接口VehicleMileageStatsTask.java - 自动统计定时任务mileageStats.js - 前端接口封装车辆里程统计使用说明.md - 详细使用文档private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
double dLat = Math.toRadians(lat2 - lat1);
double dLon = Math.toRadians(lon2 - lon1);
double rLat1 = Math.toRadians(lat1);
double rLat2 = Math.toRadians(lat2);
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 double calculateTaskOverlapRatio(Date segmentStart, Date segmentEnd,
List<TaskTimeInterval> taskIntervals) {
long segmentDuration = segmentEnd.getTime() - segmentStart.getTime();
long totalOverlap = 0;
for (TaskTimeInterval task : taskIntervals) {
long overlapStart = Math.max(segmentStart.getTime(), task.getStartTime().getTime());
long overlapEnd = Math.min(segmentEnd.getTime(), task.getEndTime().getTime());
if (overlapEnd > overlapStart) {
totalOverlap += (overlapEnd - overlapStart);
}
}
return (double) totalOverlap / segmentDuration;
}
任务时段 = 从任务创建时间(sys_task.create_time)到任务完成时间(sys_task.actual_end_time)
SQL查询:sql select tv.task_id, t.create_time as start_time, IFNULL(t.actual_end_time, NOW()) as end_time from sys_task_vehicle tv inner join sys_task t on tv.task_id = t.task_id where tv.vehicle_id = #{vehicleId} and t.del_flag = '0' and t.actual_end_time is not null and t.create_time < #{endTime} and t.actual_end_time > #{startTime}
tb_vehicle_mileage_stats 表中uk_vehicle_date (vehicle_id, stat_date)GET /system/mileageStats/list
参数:
- vehicleId: 车辆ID(可选)
- vehicleNo: 车牌号(可选)
- statDate: 统计日期(可选)
- beginStatDate: 开始日期(可选)
- endStatDate: 结束日期(可选)
- pageNum: 页码
- pageSize: 每页数量
POST /system/mileageStats/calculate
参数:
- vehicleId: 车辆ID(必填)
- statDate: 统计日期,格式 yyyy-MM-dd(必填)
返回:VehicleMileageStats对象
POST /system/mileageStats/batchCalculate
参数:
- statDate: 统计日期,格式 yyyy-MM-dd(必填)
返回:成功统计的车辆数量
POST /system/mileageStats/export
参数:同查询列表接口
返回:Excel文件
0 30 1 * * ?(每天凌晨1:30执行)在系统管理 -> 定时任务中,可以手动执行:
统计昨日数据:
vehicleMileageStatsTask.calculateYesterdayMileage
统计指定日期:
vehicleMileageStatsTask.calculateMileageByDate('2025-11-09')
| 字段名 | 类型 | 说明 |
|---|---|---|
| stats_id | bigint(20) | 统计ID,主键 |
| vehicle_id | bigint(20) | 车辆ID |
| vehicle_no | varchar(20) | 车牌号 |
| stat_date | date | 统计日期 |
| total_mileage | decimal(10,2) | 总里程(公里) |
| task_mileage | decimal(10,2) | 任务时段里程(公里) |
| non_task_mileage | decimal(10,2) | 非任务时段里程(公里) |
| task_ratio | decimal(5,4) | 任务里程占比(0-1) |
| gps_point_count | int(11) | GPS点数量 |
| task_count | int(11) | 任务数量 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
索引:
- PRIMARY KEY: stats_id
- UNIQUE KEY: uk_vehicle_date (vehicle_id, stat_date)
- KEY: idx_vehicle_id (vehicle_id)
- KEY: idx_stat_date (stat_date)
用于存储里程计算明细,方便调试和追溯。
1. sql/vehicle_mileage_stats.sql
2. sql/vehicle_mileage_stats_job.sql
3. sql/vehicle_mileage_stats_menu.sql
代码文件已自动创建,重启应用即可生效。
import { calculateMileage } from '@/api/mileageStats'
calculateMileage(1001, '2025-11-09').then(response => {
console.log('统计结果:', response.data)
// 输出示例:
// {
// vehicleNo: '粤A12345',
// statDate: '2025-11-09',
// totalMileage: 285.67,
// taskMileage: 198.43,
// nonTaskMileage: 87.24,
// taskRatio: 0.6948,
// gpsPointCount: 1205,
// taskCount: 8
// }
})
import { batchCalculateMileage } from '@/api/mileageStats'
batchCalculateMileage('2025-11-09').then(response => {
console.log(response.msg) // 输出:批量里程统计完成,成功统计 45 辆车
})
import { listMileageStats } from '@/api/mileageStats'
const query = {
vehicleNo: '粤A12345',
beginStatDate: '2025-11-01',
endStatDate: '2025-11-09',
pageNum: 1,
pageSize: 10
}
listMileageStats(query).then(response => {
console.log('统计列表:', response.rows)
})
当前使用Haversine公式计算直线距离,可升级为:
- 将GPS轨迹点发送到天地图路径规划API
- 获取实际道路距离
- 提高里程统计精度
# Service层日志
grep "VehicleMileageStatsServiceImpl" logs/ruoyi-*.log
# 定时任务日志
grep "VehicleMileageStatsTask" logs/ruoyi-*.log
✅ 本功能已完整实现车辆GPS里程统计的所有需求:
- ✅ 每日自动统计车辆行驶里程
- ✅ 区分任务时段和非任务时段里程
- ✅ 计算任务里程占比
- ✅ 统计数据缓存到数据库表
- ✅ 支持手动触发和批量计算
- ✅ 提供完整的查询和导出功能
- ✅ 集成定时任务自动化执行
核心算法采用Haversine公式计算GPS点间距离,按时间重叠比例分摊里程到任务和非任务时段,确保统计准确性。所有数据缓存在专用统计表中,支持高效查询和分析。