wlzboy
2026-02-05 57e98ac3f59e9ca12d3fdbc6f89c9c0b1f86be4d
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
package com.ruoyi.quartz.task;
 
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.mapper.*;
import com.ruoyi.system.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
 
/**
 * 车辆异常运行监控定时任务
 * 
 * @author ruoyi
 */
@Component("vehicleAbnormalAlertTask")
public class VehicleAbnormalAlertTask {
    
    private static final Logger log = LoggerFactory.getLogger(VehicleAbnormalAlertTask.class);
 
    @Autowired
    private ISysConfigService configService;
 
    @Autowired
    private VehicleGpsSegmentMileageMapper segmentMileageMapper;
 
    @Autowired
    private VehicleInfoMapper vehicleInfoMapper;
 
    @Autowired
    private SysTaskMapper sysTaskMapper;
 
    @Autowired
    private VehicleAbnormalAlertMapper alertMapper;
 
    @Autowired
    private IQyWechatService qyWechatService;
 
    @Autowired
    private ISysDeptService deptService;
 
    @Autowired
    private IVehicleAbnormalAlertService alertService;
 
    @Autowired
    private IVehicleAlertConfigService alertConfigService;
 
    @Autowired
    private ISysUserService userService;
 
    /**
     * 监控车辆异常运行情况
     */
    public void monitorVehicleAbnormalRunning() {
        try {
            // 检查功能开关
            if (!isAlertEnabled()) {
                log.debug("车辆异常告警功能未启用,跳过监控");
                return;
            }
 
            log.info("开始执行车辆异常运行监控任务");
            
            // 加载配置参数
            AlertConfig config = loadAlertConfig();
            
            // 获取监控时间窗口
            Date endTime = new Date();
            Calendar cal = Calendar.getInstance();
            cal.setTime(endTime);
            cal.add(Calendar.MINUTE, -config.timeWindow);
            Date startTime = cal.getTime();
            
            // 查询所有活跃车辆
            List<VehicleInfo> vehicles = vehicleInfoMapper.selectVehicleInfoList(new VehicleInfo());
            if (vehicles == null || vehicles.isEmpty()) {
                log.debug("没有找到需要监控的车辆");
                return;
            }
            
            log.info("开始监控 {} 辆车辆,时间窗口: {} 到 {}", vehicles.size(), 
                    DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, startTime),
                    DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, endTime));
            
            int alertCount = 0;
            for (VehicleInfo vehicle : vehicles) {
                try {
                    if (checkVehicleAbnormalRunning(vehicle, startTime, endTime, config)) {
                        alertCount++;
                    }
                } catch (Exception e) {
                    log.error("检查车辆 {} 异常运行失败", vehicle.getVehicleNo(), e);
                }
            }
            
            log.info("车辆异常运行监控任务完成,共产生 {} 个告警", alertCount);
            
        } catch (Exception e) {
            log.error("车辆异常运行监控任务执行失败", e);
        }
    }
 
    /**
     * 检查单个车辆是否异常运行
     */
    private boolean checkVehicleAbnormalRunning(VehicleInfo vehicle, Date startTime, Date endTime, AlertConfig globalConfig) {
        Long vehicleId = vehicle.getVehicleId();
        String vehicleNo = vehicle.getVehicleNo();
        Long deptId = vehicle.getDeptId();
        
        // 获取该车辆的配置(优先级:车辆 > 部门 > 全局)
        AlertConfig config = getVehicleAlertConfig(vehicleId, deptId, globalConfig);
        if (config == null) {
            log.debug("车辆 {} 未找到有效配置,跳过监控", vehicleNo);
            return false;
        }
        
        // 1. 查询车辆在时间窗口内的总运行里程
        BigDecimal totalMileage = calculateVehicleMileage(vehicleId, startTime, endTime);
        if (totalMileage == null || totalMileage.compareTo(BigDecimal.ZERO) == 0) {
            log.debug("车辆 {} 在监控窗口内无运行里程", vehicleNo);
            return false;
        }
        
        // 2. 查询车辆在时间窗口内有任务时的里程
        BigDecimal taskMileage = calculateTaskMileage(vehicleId, startTime, endTime);
        if (taskMileage == null) {
            taskMileage = BigDecimal.ZERO;
        }
        
        // 3. 计算非任务状态下的运行里程
        BigDecimal nonTaskMileage = totalMileage.subtract(taskMileage);
        if (nonTaskMileage.compareTo(BigDecimal.ZERO) <= 0) {
            log.debug("车辆 {} 在监控窗口内无非任务里程", vehicleNo);
            return false;
        }
        
        log.debug("车辆 {} 总里程: {}km, 任务里程: {}km, 非任务里程: {}km", 
                vehicleNo, totalMileage, taskMileage, nonTaskMileage);
        
        // 4. 检查非任务里程是否超过公里数阈值
        if (nonTaskMileage.compareTo(config.mileageThreshold) <= 0) {
            log.debug("车辆 {} 非任务运行里程 {}km 未超过阈值 {}km", 
                    vehicleNo, nonTaskMileage, config.mileageThreshold);
            return false;
        }
        
        // 5. 检查告警频率限制
        if (!checkAlertFrequency(vehicleId, config)) {
            log.debug("车辆 {} 已达到告警频率限制", vehicleNo);
            return false;
        }
        
        // 6. 创建告警记录
        return createAlertAndNotify(vehicle, nonTaskMileage, startTime, endTime, config);
    }
 
    /**
     * 计算车辆运行里程
     */
    private BigDecimal calculateVehicleMileage(Long vehicleId, Date startTime, Date endTime) {
        try {
            // 查询车辆在时间窗口内的分段里程记录
            VehicleGpsSegmentMileage query = new VehicleGpsSegmentMileage();
            query.setVehicleId(vehicleId);
            
            Map<String, Object> params = new HashMap<>();
            params.put("vehicleId", vehicleId);
            params.put("startTime", startTime);
            params.put("endTime", endTime);
            
            List<VehicleGpsSegmentMileage> segments = segmentMileageMapper.selectSegmentsByTimeRange(params);
            
            if (segments == null || segments.isEmpty()) {
                return BigDecimal.ZERO;
            }
            
            // 累加所有分段里程
            BigDecimal totalMileage = segments.stream()
                    .map(VehicleGpsSegmentMileage::getSegmentDistance)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            
            return totalMileage;
            
        } catch (Exception e) {
            log.error("计算车辆里程失败,vehicleId={}", vehicleId, e);
            return BigDecimal.ZERO;
        }
    }
 
    /**
     * 计算车辆在有任务时的运行里程
     */
    private BigDecimal calculateTaskMileage(Long vehicleId, Date startTime, Date endTime) {
        try {
            // 1. 查询车辆在时间窗口内的任务
            Map<String, Object> taskParams = new HashMap<>();
            taskParams.put("vehicleId", vehicleId);
            taskParams.put("startTime", startTime);
            taskParams.put("endTime", endTime);
            
            List<SysTask> tasks = sysTaskMapper.selectVehicleTasksInTimeRange(taskParams);
            
            if (tasks == null || tasks.isEmpty()) {
                return BigDecimal.ZERO;
            }
            
            // 排除已取消的任务
            List<SysTask> activeTasks = tasks.stream()
                    .filter(t -> !"CANCELLED".equals(t.getTaskStatus()))
                    .collect(Collectors.toList());
            
            if (activeTasks.isEmpty()) {
                return BigDecimal.ZERO;
            }
            
            // 2. 查询车辆的所有GPS分段里程
            Map<String, Object> segmentParams = new HashMap<>();
            segmentParams.put("vehicleId", vehicleId);
            segmentParams.put("startTime", startTime);
            segmentParams.put("endTime", endTime);
            
            List<VehicleGpsSegmentMileage> segments = segmentMileageMapper.selectSegmentsByTimeRange(segmentParams);
            
            if (segments == null || segments.isEmpty()) {
                return BigDecimal.ZERO;
            }
            
            // 3. 筛选出在任务时间范围内的分段里程
            BigDecimal taskMileage = BigDecimal.ZERO;
            
            for (VehicleGpsSegmentMileage segment : segments) {
                Date segmentStart = segment.getSegmentStartTime();
                Date segmentEnd = segment.getSegmentEndTime();
                
                if (segmentStart == null || segmentEnd == null) {
                    continue;
                }
                
                // 检查该分段是否在任意任务的时间范围内
                for (SysTask task : activeTasks) {
                    Date taskStart = task.getPlannedStartTime();
                    Date taskEnd = task.getActualEndTime();
                    
                    // 如果任务还没完成,使用当前时间作为结束时间
                    if (taskEnd == null) {
                        taskEnd = endTime;
                    }
                    
                    if (taskStart == null) {
                        continue;
                    }
                    
                    // 判断分段时间是否与任务时间有重叠
                    if (isTimeOverlap(segmentStart, segmentEnd, taskStart, taskEnd)) {
                        if (segment.getSegmentDistance() != null) {
                            taskMileage = taskMileage.add(segment.getSegmentDistance());
                        }
                        break; // 该分段已计入任务里程,不重复计算
                    }
                }
            }
            
            return taskMileage;
            
        } catch (Exception e) {
            log.error("计算任务里程失败,vehicleId={}", vehicleId, e);
            return BigDecimal.ZERO;
        }
    }
    
    /**
     * 判断两个时间段是否有重叠
     */
    private boolean isTimeOverlap(Date start1, Date end1, Date start2, Date end2) {
        // 时间段1: [start1, end1]
        // 时间段2: [start2, end2]
        // 有重叠的条件:start1 < end2 && start2 < end1
        return start1.before(end2) && start2.before(end1);
    }
 
    /**
     * 检查告警频率限制
     */
    private boolean checkAlertFrequency(Long vehicleId, AlertConfig config) {
        try {
            // 1. 检查当日告警次数
            Date today = DateUtils.parseDate(DateUtils.getDate());
            int dailyCount = alertMapper.selectDailyAlertCount(vehicleId, today);
            if (dailyCount >= config.dailyLimit) {
                log.debug("车辆 {} 今日告警次数 {} 已达上限 {}", vehicleId, dailyCount, config.dailyLimit);
                return false;
            }
            
            // 2. 检查告警间隔
            Date lastAlertTime = alertMapper.selectLastAlertTime(vehicleId);
            if (lastAlertTime != null) {
                long minutesDiff = (new Date().getTime() - lastAlertTime.getTime()) / (1000 * 60);
                if (minutesDiff < config.alertInterval) {
                    log.debug("车辆 {} 距离上次告警仅 {} 分钟,未达到间隔 {} 分钟", 
                            vehicleId, minutesDiff, config.alertInterval);
                    return false;
                }
            }
            
            return true;
            
        } catch (Exception e) {
            log.error("检查告警频率失败,vehicleId={}", vehicleId, e);
            // 出错时谨慎处理,允许告警
            return true;
        }
    }
 
    /**
     * 创建告警并发送通知
     */
    private boolean createAlertAndNotify(VehicleInfo vehicle, BigDecimal mileage, 
                                         Date startTime, Date endTime, AlertConfig config) {
        try {
            Long vehicleId = vehicle.getVehicleId();
            String vehicleNo = vehicle.getVehicleNo();
            
            // 获取车辆归属部门信息
            Long deptId = vehicle.getDeptId();
            String deptName = vehicle.getDeptName();
            
            // 创建告警记录
            boolean created = alertService.checkAndCreateAlert(
                    vehicleId, vehicleNo, mileage, startTime, endTime, deptId, deptName);
            
            if (!created) {
                return false;
            }
            
            log.info("车辆 {} 产生异常告警:无任务运行 {}km,时间 {} 至 {}", 
                    vehicleNo, mileage,
                    DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, startTime),
                    DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, endTime));
            
            // 发送通知
            sendAlertNotification(vehicle, mileage, deptId, config);
            
            return true;
            
        } catch (Exception e) {
            log.error("创建告警失败,vehicleNo={}", vehicle.getVehicleNo(), e);
            return false;
        }
    }
 
    /**
     * 发送告警通知
     */
    private void sendAlertNotification(VehicleInfo vehicle, BigDecimal mileage, 
                                       Long deptId, AlertConfig config) {
        try {
            // 获取通知用户列表
            List<Long> notifyUserIds = getNotifyUsers(deptId, config);
            if (notifyUserIds.isEmpty()) {
                log.warn("车辆 {} 告警无通知用户,跳过发送", vehicle.getVehicleNo());
                return;
            }
            
            // 构造通知内容
            String title = "车辆异常运行告警";
            String content = String.format("车辆 %s 在无任务状态下运行了 %.2f 公里,请及时录入任务单,点击去录单。",
                    vehicle.getVehicleNo(), mileage);
            
            // 通过企业微信发送通知
            if (qyWechatService != null && qyWechatService.isEnabled()) {
                for (Long userId : notifyUserIds) {
                    try {
                        // 这里可以根据实际需求指定跳转链接
                        String notifyUrl = "/pagesTask/create-emergency"; // 实际链接需根据业务调整
                        qyWechatService.sendNotifyMessageWithDefaultAppId(userId, title, content, notifyUrl);
                        log.info("已向用户 {} 发送车辆 {} 异常告警通知", userId, vehicle.getVehicleNo());
                    } catch (Exception e) {
                        log.error("向用户 {} 发送告警通知失败", userId, e);
                    }
                }
            } else {
                log.warn("企业微信服务未启用,无法发送告警通知");
            }
            
        } catch (Exception e) {
            log.error("发送告警通知失败,vehicleNo={}", vehicle.getVehicleNo(), e);
        }
    }
 
    /**
     * 获取通知用户列表
     */
    private List<Long> getNotifyUsers(Long deptId, AlertConfig config) {
        List<Long> userIds = new ArrayList<>();
        
        try {
            // 1. 优先使用配置的用户列表
            if (StringUtils.isNotEmpty(config.notifyUsers)) {
                String[] userIdStrs = config.notifyUsers.split(",");
                for (String userIdStr : userIdStrs) {
                    try {
                        userIds.add(Long.parseLong(userIdStr.trim()));
                    } catch (NumberFormatException e) {
                        log.warn("无效的用户ID: {}", userIdStr);
                    }
                }
            }
            
            // 2. 如果没有配置用户,查询车辆所属部门的负责人
            if ( deptId != null) {
                SysDept dept = deptService.selectDeptById(deptId);
                if (dept != null && StringUtils.isNotEmpty(dept.getLeader())) {
                    // leader是用户名,通过用户名查询用户ID
                    com.ruoyi.common.core.domain.entity.SysUser leaderUser = 
                            userService.selectUserByUserName(dept.getLeader());
                    if (leaderUser != null) {
                        userIds.add(leaderUser.getUserId());
                        log.info("使用部门 {} 负责人: {} (ID: {})", 
                                dept.getDeptName(), dept.getLeader(), leaderUser.getUserId());
                    } else {
                        log.warn("部门 {} 负责人 {} 未找到对应用户", 
                                dept.getDeptName(), dept.getLeader());
                    }
                }
            }
            
            // 3. 如果还是没有用户,使用系统默认配置的用户列表(总公司负责人)
            if (userIds.isEmpty()) {
                String defaultUsers = configService.selectConfigByKey("vehicle.alert.default.users");
                if (StringUtils.isNotEmpty(defaultUsers)) {
                    String[] defaultUserIds = defaultUsers.split(",");
                    for (String userId : defaultUserIds) {
                        try {
                            userIds.add(Long.parseLong(userId.trim()));
                            log.info("使用系统默认通知用户: {}", userId);
                        } catch (NumberFormatException e) {
                            log.warn("无效的默认用户ID: {}", userId);
                        }
                    }
                }
            }
            
        } catch (Exception e) {
            log.error("获取通知用户列表失败", e);
        }
        
        return userIds;
    }
 
    /**
     * 检查告警功能是否启用
     */
    private boolean isAlertEnabled() {
        try {
            String enabled = configService.selectConfigByKey("vehicle.alert.enabled");
            return "true".equalsIgnoreCase(enabled);
        } catch (Exception e) {
            log.warn("获取告警开关配置失败,使用默认值(false)", e);
            return false;
        }
    }
 
    /**
     * 获取车辆的告警配置(优先级:车辆 > 部门 > 全局)
     */
    private AlertConfig getVehicleAlertConfig(Long vehicleId, Long deptId, AlertConfig globalConfig) {
        try {
            // 从数据库查询配置
            VehicleAlertConfig dbConfig = alertConfigService.getConfigByVehicle(vehicleId, deptId);
            
            if (dbConfig != null) {
                // 将数据库配置转换为AlertConfig
                AlertConfig config = new AlertConfig();
                config.mileageThreshold = dbConfig.getMileageThreshold();
                config.dailyLimit = dbConfig.getDailyAlertLimit();
                config.alertInterval = dbConfig.getAlertInterval();
                config.timeWindow = globalConfig.timeWindow; // 时间窗口使用全局配置
                config.notifyUsers = dbConfig.getNotifyUserIds();
                return config;
            }
            
            // 如果没有数据库配置,使用全局配置
            return globalConfig;
            
        } catch (Exception e) {
            log.error("获取车辆配置失败,vehicleId={}", vehicleId, e);
            return globalConfig;
        }
    }
 
    /**
     * 加载告警配置参数
     */
    private AlertConfig loadAlertConfig() {
        AlertConfig config = new AlertConfig();
        
        try {
            // 公里数阈值
            String thresholdStr = configService.selectConfigByKey("vehicle.alert.mileage.threshold");
            config.mileageThreshold = StringUtils.isNotEmpty(thresholdStr) 
                    ? new BigDecimal(thresholdStr) : new BigDecimal("10");
            
            // 每日告警次数限制
            String limitStr = configService.selectConfigByKey("vehicle.alert.daily.limit");
            config.dailyLimit = StringUtils.isNotEmpty(limitStr) ? Integer.parseInt(limitStr) : 5;
            
            // 告警间隔时间
            String intervalStr = configService.selectConfigByKey("vehicle.alert.interval.minutes");
            config.alertInterval = StringUtils.isNotEmpty(intervalStr) ? Integer.parseInt(intervalStr) : 5;
            
            // 监控时间窗口
            String windowStr = configService.selectConfigByKey("vehicle.alert.time.window");
            config.timeWindow = StringUtils.isNotEmpty(windowStr) ? Integer.parseInt(windowStr) : 10;
            
            // 通知用户列表
            config.notifyUsers = configService.selectConfigByKey("vehicle.alert.notify.users");
            
            log.debug("告警配置: 阈值={}km, 每日限制={}次, 间隔={}分钟, 时间窗口={}分钟", 
                    config.mileageThreshold, config.dailyLimit, config.alertInterval, config.timeWindow);
            
        } catch (Exception e) {
            log.error("加载告警配置失败,使用默认值", e);
        }
        
        return config;
    }
 
    /**
     * 告警配置内部类
     */
    private static class AlertConfig {
        BigDecimal mileageThreshold = new BigDecimal("10");  // 公里数阈值
        int dailyLimit = 5;                                   // 每日告警次数限制
        int alertInterval = 5;                                // 告警间隔(分钟)
        int timeWindow = 10;                                  // 监控时间窗口(分钟)
        String notifyUsers;                                   // 通知用户列表
    }
}