wlzboy
6 天以前 09e6dc3fb7266620fafb5e341808a8eb36e080a1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
package com.ruoyi.system.service.impl;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.config.TiandituMapConfig;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.system.domain.VehicleGps;
import com.ruoyi.system.domain.VehicleGpsSegmentMileage;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.mapper.VehicleGpsMapper;
import com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper;
import com.ruoyi.system.mapper.SysTaskMapper;
import com.ruoyi.system.mapper.VehicleInfoMapper;
import com.ruoyi.system.service.IVehicleGpsSegmentMileageService;
import com.ruoyi.system.service.IVehicleMileageStatsService;
import com.ruoyi.system.service.ISysConfigService;
 
/**
 * 车辆GPS分段里程Service业务层处理
 */
@Service
public class VehicleGpsSegmentMileageServiceImpl implements IVehicleGpsSegmentMileageService {
    
    private static final Logger logger = LoggerFactory.getLogger(VehicleGpsSegmentMileageServiceImpl.class);
    
    /** 地球半径(公里) */
    private static final double EARTH_RADIUS_KM = 6371.0;
    
    /** 天地图批量路径规划API */
    private static final String TIANDITU_ROUTE_API = "http://api.tianditu.gov.cn/drive";
    
    @Autowired
    private VehicleGpsSegmentMileageMapper segmentMileageMapper;
    
    @Autowired
    private VehicleGpsMapper vehicleGpsMapper;
    
    @Autowired
    private SysTaskMapper sysTaskMapper;
    
    @Autowired
    private TiandituMapConfig tiandituMapConfig;
    
    @Autowired
    private ISysConfigService configService;
    
    @Autowired
    private RedisCache redisCache;
    
    @Autowired
    private IVehicleMileageStatsService mileageStatsService;
    
    @Autowired
    private VehicleInfoMapper vehicleInfoMapper;
 
    @Override
    public VehicleGpsSegmentMileage selectVehicleGpsSegmentMileageById(Long segmentId) {
        return segmentMileageMapper.selectVehicleGpsSegmentMileageById(segmentId);
    }
 
    @Override
    public List<VehicleGpsSegmentMileage> selectVehicleGpsSegmentMileageList(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
        return segmentMileageMapper.selectVehicleGpsSegmentMileageList(vehicleGpsSegmentMileage);
    }
 
    @Override
    public int insertVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
        return segmentMileageMapper.insertVehicleGpsSegmentMileage(vehicleGpsSegmentMileage);
    }
 
    @Override
    public int updateVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
        return segmentMileageMapper.updateVehicleGpsSegmentMileage(vehicleGpsSegmentMileage);
    }
 
    @Override
    public int deleteVehicleGpsSegmentMileageByIds(Long[] segmentIds) {
        return segmentMileageMapper.deleteVehicleGpsSegmentMileageByIds(segmentIds);
    }
 
    @Override
    public int deleteVehicleGpsSegmentMileageById(Long segmentId) {
        return segmentMileageMapper.deleteVehicleGpsSegmentMileageById(segmentId);
    }
 
    @Override
    public int batchCalculateSegmentMileage(Date startTime, Date endTime) {
        try {
//            logger.info("开始批量计算GPS分段里程 - 时间范围: {} 到 {}", startTime, endTime);
            
            // 查询在指定时间范围内有GPS数据的所有车辆(添加慢SQL监控)
            long startQueryTime = System.currentTimeMillis();
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime);
            long queryTime = System.currentTimeMillis() - startQueryTime;
            
            // 慢查询警告(超过1秒)
            if (queryTime > 1000) {
                logger.warn("查询活跃车辆ID耗时过长: {}ms, 开始时间: {}, 建议检查 tb_vehicle_gps 表的索引(需要 vehicle_id 和 collect_time 组合索引)", 
                    queryTime, startTime);
            }
            logger.info("查询到 {} 辆活跃车辆,查询耗时: {}ms", vehicleIds != null ? vehicleIds.size() : 0, queryTime);
            
            if (vehicleIds == null || vehicleIds.isEmpty()) {
                logger.info("没有找到活跃车辆");
                return 0;
            }
            
            logger.info("找到 {} 辆活跃车辆,开始逐辆计算...", vehicleIds.size());
            
            int successCount = 0;
            int failedCount = 0;
            
            // 逐辆计算,包含错误处理和重试机制
            for (int i = 0; i < vehicleIds.size(); i++) {
                Long vehicleId = vehicleIds.get(i);
                try {
//                    logger.info("正在处理车辆 {} ({}/{})", vehicleId, i + 1, vehicleIds.size());
                    
                    int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
                    if (segmentCount > 0) {
                        successCount++;
//                        logger.info("车辆 {} 计算成功,生成 {} 个分段记录", vehicleId, segmentCount);
                    } else {
//                        logger.debug("车辆 {} 无有新的GPS分段数据", vehicleId);
                    }
                } catch (Exception e) {
                    failedCount++;
                    logger.error("计算车辆 {} 的分段里程失败 ({}/{})", vehicleId, i + 1, vehicleIds.size(), e);
                    
                    // 不中断整个批处理,继续处理下一辆车
                }
                
                // 每处理10辆车输出一次进度
                if ((i + 1) % 10 == 0) {
                    logger.info("批量计算进度: {}/{}, 成功: {}, 失败: {}",
                               i + 1, vehicleIds.size(), successCount, failedCount);
                }
            }
            
            logger.info("批量分段里程计算完成 - 时间范围: {} 到 {}, 总车辆数: {}, 成功: {}, 失败: {}",
                       startTime, endTime, vehicleIds.size(), successCount, failedCount);
            return successCount;
            
        } catch (Exception e) {
            logger.error("批量计算分段里程失败 - 时间范围: {} 到 {}", startTime, endTime, e);
            throw new RuntimeException("批量计算失败: " + e.getMessage(), e);
        }
    }
 
    @Override
    public int compensateCalculation(int lookbackDays) {
        try {
            // 计算时间范围(回溯指定天数)
            Calendar cal = Calendar.getInstance();
            Date endTime = cal.getTime();
            cal.add(Calendar.DAY_OF_MONTH, -lookbackDays);
            Date startTime = cal.getTime();
            
            logger.info("开始补偿计算 - 回溯天数: {}, 时间范围: {} 到 {}", lookbackDays, startTime, endTime);
            
            // 查询所有活跃车辆
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime);
            
            if (vehicleIds == null || vehicleIds.isEmpty()) {
                logger.info("没有找到活跃车辆");
                return 0;
            }
            
            int successCount = 0;
            int totalUncalculated = 0;
            
            for (Long vehicleId : vehicleIds) {
                try {
                    // 查询该车辆未被计算的GPS数据
                    List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTime, endTime);
                    
                    if (uncalculatedGps == null || uncalculatedGps.isEmpty()) {
                        logger.debug("车辆 {} 没有未计算的GPS数据", vehicleId);
                        continue;
                    }
                    
                    totalUncalculated += uncalculatedGps.size();
                    logger.info("车辆 {} 发现 {} 个未计算的GPS点,开始补偿计算...", 
                               vehicleId, uncalculatedGps.size());
                    
                    // 获取未计算GPS数据的时间范围
                    Date uncalculatedStartTime = parseDateTime(uncalculatedGps.get(0).getCollectTime());
                    Date uncalculatedEndTime = parseDateTime(uncalculatedGps.get(uncalculatedGps.size() - 1).getCollectTime());
                    
                    // 查找该时间段之前最后一个已处理的GPS坐标ID
                    Long lastCalculatedGpsId = segmentMileageMapper.selectLastCalculatedGpsId(vehicleId, uncalculatedStartTime);
                    
                    if (lastCalculatedGpsId != null) {
                        logger.info("车辆 {} 找到最后一个已处理的GPS点ID: {},将与未处理数据一起计算", vehicleId, lastCalculatedGpsId);
                        
                        // 将最后一个已处理的GPS点加入列表前面,作为前置点
                        VehicleGps lastCalculatedGps = vehicleGpsMapper.selectVehicleGpsById(lastCalculatedGpsId);
                        if (lastCalculatedGps != null) {
                            uncalculatedGps.add(0, lastCalculatedGps); // 插入到列表最前面
                            logger.info("已将GPS点 {} 作为前置点加入计算列表", lastCalculatedGpsId);
                        }
                    } else {
                        logger.info("车辆 {} 没有找到已处理的前置 GPS点,从第一个未处理点开始计算", vehicleId);
                    }
                    
                    // 重新计算该车辆在该时间范围的分段里程
                    // 注意:这里会重新计算整个时间范围,确保边缘节点被正确处理
                    int segmentCount = calculateVehicleSegmentMileageWithGpsList(
                        vehicleId, uncalculatedGps, uncalculatedStartTime, uncalculatedEndTime);
                    
                    if (segmentCount > 0) {
                        successCount++;
                        logger.info("车辆 {} 补偿计算完成,生成 {} 个分段记录", vehicleId, segmentCount);
                    }
                } catch (Exception e) {
                    logger.error("车辆 {} 补偿计算失败", vehicleId, e);
                }
            }
            
            logger.info("补偿计算完成 - 总车辆数: {}, 未计算GPS点数: {}, 成功车辆数: {}", 
                       vehicleIds.size(), totalUncalculated, successCount);
            return successCount;
            
        } catch (Exception e) {
            logger.error("补偿计算失败", e);
            throw new RuntimeException("补偿计算失败: " + e.getMessage());
        }
    }
 
    @Override
    public int calculateVehicleSegmentMileage(Long vehicleId, Date startTime, Date endTime) {
        try {
            // 查询车辆在时间范围内的GPS数据
            List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, startTime, endTime);
            
            if (gpsList == null || gpsList.isEmpty()) {
                logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime);
                return 0;
            }
            
            logger.info("车辆ID: {} 查询到 {} 条GPS数据 startTime:{},endTime:{}", vehicleId, gpsList.size(),startTime,endTime);
            
            return calculateVehicleSegmentMileageWithGpsList(vehicleId, gpsList, startTime, endTime);
            
        } catch (Exception e) {
            logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
            throw new RuntimeException("计算分段里程失败: " + e.getMessage());
        }
    }
    
    /**
     * 根据提供的GPS列表计算车辆分段里程
     * @param vehicleId 车辆ID
     * @param gpsList GPS列表(已按时间排序)
     * @param startTime 起始时间
     * @param endTime 结束时间
     * @return 生成的分段数量
     */
    private int calculateVehicleSegmentMileageWithGpsList(Long vehicleId, List<VehicleGps> gpsList, 
                                                           Date startTime, Date endTime) {
        try {
            // 验证输入数据
            if (gpsList == null || gpsList.isEmpty()) {
                logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime);
                return 0;
            }
            
            // 加载配置参数
            MileageCalculationConfig config = loadMileageCalculationConfig();
            
            // 按时间段分组GPS数据
            Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, config.segmentMinutes);
            
            // 处理每个时间段并计算里程
            int savedCount = processSegmentedGpsData(vehicleId, segmentedData, config);
            
            logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
            
            // 自动触发每日统计汇总
            if (savedCount > 0) {
                triggerDailyMileageAggregation(vehicleId, segmentedData);
            }
            
            return savedCount;
            
        } catch (Exception e) {
            logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
            throw new RuntimeException("计算分段里程失败: " + e.getMessage());
        }
    }
    
    /**
     * 加载里程计算配置参数
     */
    private MileageCalculationConfig loadMileageCalculationConfig() {
        MileageCalculationConfig config = new MileageCalculationConfig();
        
        // 获取时间间隔配置(分钟)
        config.segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null 
            ? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes")) 
            : 5;
        
        // 获取计算方式配置
        config.calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
        if (config.calculateMethod == null || config.calculateMethod.isEmpty()) {
            config.calculateMethod = "tianditu";
        }
        
        // 获取是否跳过已计算GPS点的配置
        String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
        config.skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
        logger.info("控制跳过重复计算标识: {}", config.skipCalculated);
        
        return config;
    }
    
    /**
     * 处理分段后的GPS数据并计算里程
     * @param vehicleId 车辆ID
     * @param segmentedData 分段后的GPS数据
     * @param config 计算配置
     * @return 成功保存的分段数量
     */
    private int processSegmentedGpsData(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData, 
                                         MileageCalculationConfig config) {
        int savedCount = 0;
        VehicleGps previousSegmentLastPoint = null; // 记录上一个时间段的最后一个点
        
        // 遍历每个时间段,计算里程
        for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
            Date segmentStartTime = entry.getKey();
            List<VehicleGps> segmentGpsList = entry.getValue();
            
            // 校验当前时间段数据
            if (!isSegmentValidForCalculation(segmentGpsList, previousSegmentLastPoint, vehicleId, segmentStartTime)) {
                // 保留当前点作为下一段的前置点(如果有的话)
                if (!segmentGpsList.isEmpty()) {
                    previousSegmentLastPoint = segmentGpsList.get(0);
                }
                continue;
            }
            
            // 检查是否已存在该时间段的记录
            if (isSegmentAlreadyCalculated(vehicleId, segmentStartTime, segmentGpsList)) {
                previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                continue;
            }
            
            // 计算并保存分段里程
            boolean success = calculateAndSaveSegment(vehicleId, segmentStartTime, segmentGpsList, 
                                                       previousSegmentLastPoint, config);
            if (success) {
                savedCount++;
            }
            
            // 更新上一段最后一个点,供下一段使用
            previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
        }
        
        return savedCount;
    }
    
    /**
     * 校验时间段数据是否有效
     */
    private boolean isSegmentValidForCalculation(List<VehicleGps> segmentGpsList, 
                                                  VehicleGps previousSegmentLastPoint,
                                                  Long vehicleId, Date segmentStartTime) {
        // 如果当前段没有GPS点,跳过
        if (segmentGpsList == null || segmentGpsList.isEmpty()) {
            return false;
        }
        
        // 如果本段只有1个点,且没有上一段的最后一个点,无法计算距离
        if (segmentGpsList.size() == 1 && previousSegmentLastPoint == null) {
            logger.debug("车辆 {} 时间段 {} 只有1个GPS点且无前置点,暂存待下一段计算", vehicleId, segmentStartTime);
            return false;
        }
        
        return true;
    }
    
    /**
     * 检查时间段是否已被计算
     */
    private boolean isSegmentAlreadyCalculated(Long vehicleId, Date segmentStartTime, List<VehicleGps> segmentGpsList) {
        VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
        if (existing != null) {
            logger.debug("车辆 {} 时间段 {} 的分段里程已存在,跳过", vehicleId, segmentStartTime);
            return true;
        }
        return false;
    }
    
    /**
     * 计算并保存单个时间段的里程
     */
    private boolean calculateAndSaveSegment(Long vehicleId, Date segmentStartTime, 
                                             List<VehicleGps> segmentGpsList,
                                             VehicleGps previousSegmentLastPoint,
                                             MileageCalculationConfig config) {
        try {
            // 计算时间段的结束时间
            Date segmentEndTime = calculateSegmentEndTime(segmentStartTime, config.segmentMinutes);
            
            // 计算该时间段的里程(包括跨段距离)
            BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, config.calculateMethod, previousSegmentLastPoint);
            
            // 收集GPS ID列表
            List<Long> gpsIdList = collectGpsIds(segmentGpsList, previousSegmentLastPoint);
            String gpsIds = gpsIdList.stream()
                .map(String::valueOf)
                .collect(java.util.stream.Collectors.joining(","));
            
            // 创建分段里程记录
            VehicleGpsSegmentMileage segment = buildSegmentMileageRecord(
                vehicleId, segmentStartTime, segmentEndTime, segmentGpsList, 
                distance, gpsIdList, gpsIds, config.calculateMethod);
            
            // 保存到数据库
            logger.info("保存车辆分时段里程到数据库中,车辆ID: {}, 时间段: {} 到 {}", vehicleId, segmentStartTime, segmentEndTime);
            segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
            
            // 记录已计算的GPS点(如果开启了重复计算控制)
            if (config.skipCalculated && segment.getSegmentId() != null) {
                recordCalculatedGpsPoints(gpsIdList, segment.getSegmentId(), vehicleId);
            }
            
            logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}", 
                       vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(), 
                       gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
            
            return true;
            
        } catch (Exception e) {
            logger.error("保存车辆 {} 时间段 {} 的里程记录失败", vehicleId, segmentStartTime, e);
            return false;
        }
    }
    
    /**
     * 计算时间段结束时间
     */
    private Date calculateSegmentEndTime(Date segmentStartTime, int segmentMinutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(segmentStartTime);
        cal.add(Calendar.MINUTE, segmentMinutes);
        return cal.getTime();
    }
    
    /**
     * 收集GPS ID列表(包括前置点)
     */
    private List<Long> collectGpsIds(List<VehicleGps> segmentGpsList, VehicleGps previousSegmentLastPoint) {
        List<Long> gpsIdList = new ArrayList<>();
        
        // 如果有上一段的最后一个点,先添加它的ID(用于计算跨段距离)
        if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
            gpsIdList.add(previousSegmentLastPoint.getGpsId());
        }
        
        // 再添加当前段的所有GPS点ID
        for (VehicleGps gps : segmentGpsList) {
            if (gps.getGpsId() != null) {
                gpsIdList.add(gps.getGpsId());
            }
        }
        
        return gpsIdList;
    }
    
    /**
     * 构建分段里程记录对象
     */
    private VehicleGpsSegmentMileage buildSegmentMileageRecord(Long vehicleId, Date segmentStartTime, 
                                                                 Date segmentEndTime, List<VehicleGps> segmentGpsList,
                                                                 BigDecimal distance, List<Long> gpsIdList, 
                                                                 String gpsIds, String calculateMethod) {
        VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
        segment.setVehicleId(vehicleId);
        
        // 获取车牌号
        String vehicleNo = getVehicleNo(vehicleId, segmentGpsList.get(0));
        segment.setVehicleNo(vehicleNo);
        
        // 设置时间范围
        segment.setSegmentStartTime(segmentStartTime);
        segment.setSegmentEndTime(segmentEndTime);
        
        // 设置起点坐标
        VehicleGps firstPoint = segmentGpsList.get(0);
        segment.setStartLongitude(BigDecimal.valueOf(firstPoint.getLongitude()));
        segment.setStartLatitude(BigDecimal.valueOf(firstPoint.getLatitude()));
        
        // 设置终点坐标
        VehicleGps lastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
        segment.setEndLongitude(BigDecimal.valueOf(lastPoint.getLongitude()));
        segment.setEndLatitude(BigDecimal.valueOf(lastPoint.getLatitude()));
        
        // 设置里程数据
        segment.setSegmentDistance(distance);
        segment.setGpsPointCount(gpsIdList.size());
        segment.setGpsIds(gpsIds);
        segment.setCalculateMethod(calculateMethod);
        
        // 查询并关联正在执行的任务
        associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
        
        return segment;
    }
    
    /**
     * 获取车牌号
     */
    private String getVehicleNo(Long vehicleId, VehicleGps firstGps) {
        String vehicleNo = firstGps.getVehicleNo();
        if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
            // GPS数据中没有车牌号,从车辆表查询
            VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
            if (vehicleInfo != null) {
                vehicleNo = vehicleInfo.getVehicleNo();
            }
        }
        return vehicleNo;
    }
    
    /**
     * 记录已计算的GPS点到状态表
     */
    private void recordCalculatedGpsPoints(List<Long> gpsIdList, Long segmentId, Long vehicleId) {
        for (Long gpsId : gpsIdList) {
            try {
                segmentMileageMapper.insertGpsCalculated(gpsId, segmentId, vehicleId);
            } catch (Exception e) {
                // 忽略重复键异常,继续处理
                logger.debug("记录GPS计算状态失败,可能已存在: gpsId={}", gpsId);
            }
        }
    }
    
    /**
     * 触发每日里程统计汇总
     */
    private void triggerDailyMileageAggregation(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData) {
        try {
            // 获取涉及的日期范围
            Set<Date> affectedDates = extractAffectedDates(segmentedData);
            
            // 对每个涉及的日期,触发汇总
            for (Date statDate : affectedDates) {
                try {
                    mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
                    logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
                } catch (Exception e) {
                    logger.error("车辆 {} 日期 {} 自动汇总统计失败", vehicleId, statDate, e);
                }
            }
        } catch (Exception e) {
            logger.error("触发自动汇总失败", e);
        }
    }
    
    /**
     * 提取受影响的日期列表(用于汇总统计)
     */
    private Set<Date> extractAffectedDates(Map<Date, List<VehicleGps>> segmentedData) {
        Set<Date> affectedDates = new HashSet<>();
        Calendar cal = Calendar.getInstance();
        
        for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
            cal.setTime(entry.getKey());
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            affectedDates.add(cal.getTime());
        }
        
        return affectedDates;
    }
    
    /**
     * 里程计算配置类
     */
    private static class MileageCalculationConfig {
        int segmentMinutes;          // 时间段间隔(分钟)
        String calculateMethod;      // 计算方式
        boolean skipCalculated;      // 是否跳过已计算的GPS点
    }
 
    /**
     * 将GPS数据按时间段分组
     */
    private Map<Date, List<VehicleGps>> segmentGpsDataByTime(List<VehicleGps> gpsList, int segmentMinutes) {
        Map<Date, List<VehicleGps>> segmentedData = new LinkedHashMap<>();
        
        for (VehicleGps gps : gpsList) {
            // 解析GPS采集时间
            Date collectTime = parseDateTime(gps.getCollectTime());
            
            // 计算该GPS点所属的时间段起始时间(向下取整到最近的时间段)
            Date segmentStart = getSegmentStartTime(collectTime, segmentMinutes);
            
            // 添加到对应时间段
            segmentedData.computeIfAbsent(segmentStart, k -> new ArrayList<>()).add(gps);
        }
        
        return segmentedData;
    }
 
    /**
     * 获取时间段的起始时间(向下取整)
     */
    private Date getSegmentStartTime(Date time, int segmentMinutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(time);
        
        // 将分钟数向下取整到最近的分段
        int minute = cal.get(Calendar.MINUTE);
        int segmentIndex = minute / segmentMinutes;
        int alignedMinute = segmentIndex * segmentMinutes;
        
        cal.set(Calendar.MINUTE, alignedMinute);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        
        return cal.getTime();
    }
 
    /**
     * 计算一个时间段内的总里程(包括与上一段的间隙距离)
     * @param gpsList 当前时间段的GPS点列表(至少1个点)
     * @param calculateMethod 计算方式
     * @param previousLastPoint 上一个时间段的最后一个点(可为null)
     * @return 总里程(公里),保留3位小数
     */
    private BigDecimal calculateSegmentDistanceWithGap(List<VehicleGps> gpsList, String calculateMethod, VehicleGps previousLastPoint) {
        if (gpsList == null || gpsList.isEmpty()) {
            return BigDecimal.ZERO;
        }
        
        BigDecimal totalDistance = BigDecimal.ZERO;
        
        // 1. 先计算跨段间隙距离(上一段最后一个点 -> 当前段第一个点)
        if (previousLastPoint != null) {
            VehicleGps currentFirstPoint = gpsList.get(0);
            double gapDistance = calculateHaversineDistance(
                previousLastPoint.getLatitude().doubleValue(),
                previousLastPoint.getLongitude().doubleValue(),
                currentFirstPoint.getLatitude().doubleValue(),
                currentFirstPoint.getLongitude().doubleValue()
            );
            totalDistance = totalDistance.add(BigDecimal.valueOf(gapDistance));
            
            logger.debug("跨段间隙距离: {}km (上一段末点 -> 当前段首点)", 
                String.format("%.3f", gapDistance));
        }
        
        // 2. 再计算当前段内部的距离(如果有2个或以上GPS点)
        if (gpsList.size() >= 2) {
            BigDecimal segmentInternalDistance;
            if ("tianditu".equalsIgnoreCase(calculateMethod)) {
                segmentInternalDistance = calculateDistanceByTianditu(gpsList);
            } else {
                segmentInternalDistance = calculateDistanceByHaversine(gpsList);
            }
            totalDistance = totalDistance.add(segmentInternalDistance);
        }
        // 如果只有1个点,段内距离为0,只计算跨段距离
        
        return totalDistance.setScale(3, RoundingMode.HALF_UP);
    }
    
    /**
     * 计算一个时间段内的总里程(仅段内距离)
     */
    private BigDecimal calculateSegmentDistance(List<VehicleGps> gpsList, String calculateMethod) {
        if (gpsList == null || gpsList.size() < 2) {
            return BigDecimal.ZERO;
        }
        
        BigDecimal totalDistance = BigDecimal.ZERO;
        
        if ("tianditu".equalsIgnoreCase(calculateMethod)) {
            // 使用天地图API计算(批量计算更精确)
            totalDistance = calculateDistanceByTianditu(gpsList);
        } else {
            // 使用Haversine公式计算(直线距离,更快但不够精确)
            totalDistance = calculateDistanceByHaversine(gpsList);
        }
        
        return totalDistance.setScale(3, RoundingMode.HALF_UP);
    }
 
    /**
     * 使用天地图API计算距离
     */
    private BigDecimal calculateDistanceByTianditu(List<VehicleGps> gpsList) {
        try {
            // 天地图路径规划API有点数限制,如果点太多需要分批处理
            int maxPointsPerRequest = 50; // 天地图API建议不超过50个点
            BigDecimal totalDistance = BigDecimal.ZERO;
            
            // 如果GPS点数较少,直接使用Haversine公式(避免频繁调用API)
            if (gpsList.size() <= 3) {
                return calculateDistanceByHaversine(gpsList);
            }
            
            // 分批处理
            for (int i = 0; i < gpsList.size() - 1; i += maxPointsPerRequest) {
                int endIndex = Math.min(i + maxPointsPerRequest, gpsList.size());
                List<VehicleGps> batchList = gpsList.subList(i, endIndex);
                
                BigDecimal batchDistance = calculateBatchDistanceByTianditu(batchList);
                totalDistance = totalDistance.add(batchDistance);
            }
            
            return totalDistance;
            
        } catch (Exception e) {
            logger.warn("天地图API计算距离失败,降级使用Haversine公式: {}", e.getMessage());
            return calculateDistanceByHaversine(gpsList);
        }
    }
 
    /**
     * 使用天地图API计算一批GPS点的距离
     */
    private BigDecimal calculateBatchDistanceByTianditu(List<VehicleGps> gpsList) {
        try {
            // 简化处理:计算相邻点之间的直线距离总和
            // 注:天地图的路径规划API主要用于导航,这里用简化的距离计算
            BigDecimal totalDistance = BigDecimal.ZERO;
            
            for (int i = 0; i < gpsList.size() - 1; i++) {
                VehicleGps p1 = gpsList.get(i);
                VehicleGps p2 = gpsList.get(i + 1);
                
                double distance = calculateHaversineDistance(
                    p1.getLatitude().doubleValue(), 
                    p1.getLongitude().doubleValue(),
                    p2.getLatitude().doubleValue(), 
                    p2.getLongitude().doubleValue()
                );
                
                totalDistance = totalDistance.add(BigDecimal.valueOf(distance));
            }
            
            return totalDistance;
            
        } catch (Exception e) {
            logger.error("天地图批量距离计算失败", e);
            throw e;
        }
    }
 
    /**
     * 使用Haversine公式计算距离
     */
    private BigDecimal calculateDistanceByHaversine(List<VehicleGps> gpsList) {
        BigDecimal totalDistance = BigDecimal.ZERO;
        
        for (int i = 0; i < gpsList.size() - 1; i++) {
            VehicleGps p1 = gpsList.get(i);
            VehicleGps p2 = gpsList.get(i + 1);
            
            double distance = calculateHaversineDistance(
                p1.getLatitude().doubleValue(), 
                p1.getLongitude().doubleValue(),
                p2.getLatitude().doubleValue(), 
                p2.getLongitude().doubleValue()
            );
            
            totalDistance = totalDistance.add(BigDecimal.valueOf(distance));
        }
        
        return totalDistance;
    }
 
    /**
     * 使用Haversine公式计算两点之间的距离(公里)
     */
    private double calculateHaversineDistance(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 Date parseDateTime(String dateTimeStr) {
        if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
            throw new RuntimeException("日期时间字符串不能为空");
        }
        
        try {
            java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setLenient(false);
            return sdf.parse(dateTimeStr.trim());
        } catch (Exception e) {
            throw new RuntimeException("日期时间格式错误: " + dateTimeStr + ", 应为 yyyy-MM-dd HH:mm:ss", e);
        }
    }
    
    /**
     * 查询并关联车辆正在执行的任务
     * @param segment 分段里程记录
     * @param vehicleId 车辆ID
     * @param segmentStartTime 时间段开始时间
     * @param segmentEndTime 时间段结束时间
     */
    private void associateActiveTask(VehicleGpsSegmentMileage segment, Long vehicleId, 
                                      Date segmentStartTime, Date segmentEndTime) {
        try {
            // 查询该车辆正在执行的任务列表
            List<SysTask> activeTasks = sysTaskMapper.selectTaskByVehicleIdAndDate(vehicleId,segmentStartTime,segmentEndTime);
            
            if (activeTasks == null || activeTasks.isEmpty()) {
                logger.debug("车辆 {} 在时间段 {} - {} 没有正在执行的任务", vehicleId, segmentStartTime, segmentEndTime);
                return;
            }
            
            // 遍历任务,查找与当前时间段有重叠的任务
            for (SysTask task : activeTasks) {
                // 获取任务的实际执行时间,如果没有实际时间则使用计划时间
                Date taskStart = task.getActualStartTime() != null ? task.getActualStartTime() : task.getPlannedStartTime();
                Date taskEnd = task.getActualEndTime() != null ? task.getActualEndTime() : task.getPlannedEndTime();
                
                // 判断时间段是否有重叠
                if (isTimeOverlap(segmentStartTime, segmentEndTime, taskStart, taskEnd)) {
                    // 关联任务ID和任务编号
                    segment.setTaskId(task.getTaskId());
                    segment.setTaskCode(task.getTaskCode());
                    
                    logger.debug("车辆 {} 时间段 {} - {} 关联任务: taskId={}, taskCode={}", 
                               vehicleId, segmentStartTime, segmentEndTime, task.getTaskId(), task.getTaskCode());
                    break; // 找到一个匹配的任务即可
                }
            }
            
        } catch (Exception e) {
            // 关联任务失败不影响主流程,只记录日志
            logger.warn("关联车辆 {} 的任务信息失败", vehicleId, e);
        }
    }
    
    /**
     * 判断两个时间段是否有重叠
     * @param start1 时间段1开始
     * @param end1 时间段1结束
     * @param start2 时间段2开始
     * @param end2 时间段2结束
     * @return true-有重叠, false-无重叠
     */
    private boolean isTimeOverlap(Date start1, Date end1, Date start2, Date end2) {
        // 任何时间为null,返回false
        if (start1 == null || end1 == null || start2 == null || end2 == null) {
            return false;
        }
        
        // 两个时间段有重叠的条件:
        // start1 < end2 && end1 > start2
        return start1.before(end2) && end1.after(start2);
    }
}