From caf56217dc2bf898b63b0e1f31a7098202c32825 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期六, 15 十一月 2025 16:50:17 +0800
Subject: [PATCH] Merge branch 'feature_gps'

---
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java |  296 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 296 insertions(+), 0 deletions(-)

diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
index e69de29..4bd7aa3 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
@@ -0,0 +1,296 @@
+package com.ruoyi.system.service.impl;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+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.TaskTimeInterval;
+import com.ruoyi.system.mapper.VehicleGpsMapper;
+import com.ruoyi.system.mapper.VehicleMileageStatsMapper;
+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;
+
+    /**
+     * 鏌ヨ杞﹁締閲岀▼缁熻
+     */
+    @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. 鏌ヨ杞﹁締鍦ㄨ鏃ユ湡鐨凣PS鏁版嵁锛堟寜鏃堕棿鎺掑簭锛�
+            List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, dayStart, dayEnd);
+            
+            if (gpsList == null || gpsList.isEmpty()) {
+                logger.info("杞﹁締ID: {} 鍦ㄦ棩鏈�: {} 鏃燝PS鏁版嵁", vehicleId, statDate);
+                return null;
+            }
+            
+            // 3. 鏌ヨ杞﹁締鍦ㄨ鏃ユ湡鐨勪换鍔℃椂闂村尯闂�
+            List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd);
+            
+            // 4. 璁$畻閲岀▼
+            MileageCalculation calculation = calculateMileage(gpsList, taskIntervals);
+            
+            // 5. 鏌ヨ鎴栧垱寤虹粺璁¤褰�
+            VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate);
+            boolean isNew = (stats == null);
+            
+            if (isNew) {
+                stats = new VehicleMileageStats();
+                stats.setVehicleId(vehicleId);
+                stats.setStatDate(statDate);
+                
+                // 鑾峰彇杞︾墝鍙�
+                if (!gpsList.isEmpty() && gpsList.get(0).getVehicleNo() != null) {
+                    stats.setVehicleNo(gpsList.get(0).getVehicleNo());
+                }
+            }
+            
+            // 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());
+            
+            // 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 {
+            // 鏌ヨ鎵�鏈夋椿璺冭溅杈�
+            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds();
+            
+            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 MileageCalculation calculateMileage(List<VehicleGps> gpsList, List<TaskTimeInterval> taskIntervals) {
+        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()
+            );
+            
+            // 鑾峰彇杩欐璺濈鐨勬椂闂村尯闂�
+            Date segmentStart = p1.getCollectTime();
+            Date segmentEnd = p2.getCollectTime();
+            
+            // 璁$畻杩欐璺濈鍦ㄤ换鍔℃椂娈电殑鍗犳瘮
+            double taskRatio = calculateTaskOverlapRatio(segmentStart, segmentEnd, taskIntervals);
+            
+            // 鍒嗘憡閲岀▼
+            double taskDistance = distance * taskRatio;
+            double nonTaskDistance = distance * (1 - taskRatio);
+            
+            result.totalMileage = result.totalMileage.add(BigDecimal.valueOf(distance));
+            result.taskMileage = result.taskMileage.add(BigDecimal.valueOf(taskDistance));
+            result.nonTaskMileage = result.nonTaskMileage.add(BigDecimal.valueOf(nonTaskDistance));
+        }
+        
+        // 璁$畻浠诲姟閲岀▼鍗犳瘮
+        if (result.totalMileage.compareTo(BigDecimal.ZERO) > 0) {
+            result.taskRatio = result.taskMileage.divide(result.totalMileage, 4, RoundingMode.HALF_UP);
+        }
+        
+        // 淇濈暀涓や綅灏忔暟
+        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) {
+        // 灏嗚搴﹁浆鎹负寮у害
+        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 double calculateTaskOverlapRatio(Date segmentStart, Date segmentEnd, List<TaskTimeInterval> taskIntervals) {
+        if (taskIntervals == null || taskIntervals.isEmpty()) {
+            return 0.0;
+        }
+        
+        long segmentDuration = segmentEnd.getTime() - segmentStart.getTime();
+        if (segmentDuration <= 0) {
+            return 0.0;
+        }
+        
+        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;
+    }
+
+    /**
+     * 閲岀▼璁$畻缁撴灉鍐呴儴绫�
+     */
+    private static class MileageCalculation {
+        BigDecimal totalMileage = BigDecimal.ZERO;
+        BigDecimal taskMileage = BigDecimal.ZERO;
+        BigDecimal nonTaskMileage = BigDecimal.ZERO;
+        BigDecimal taskRatio = BigDecimal.ZERO;
+    }
+}

--
Gitblit v1.9.1