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 vehicles = vehicleInfoMapper.selectVehicleInfoList(new VehicleInfo()); if (vehicles == null || vehicles.isEmpty()) { log.info("没有找到需要监控的车辆"); 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.info("车辆 {} 未找到有效配置,跳过监控", vehicleNo); return false; } // 1. 查询车辆在时间窗口内的总运行里程 BigDecimal totalMileage = calculateVehicleMileage(vehicleId, startTime, endTime); if (totalMileage == null || totalMileage.compareTo(BigDecimal.ZERO) == 0) { // log.info("车辆 {} 在监控窗口内无运行里程", 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.info("车辆 {} 在监控窗口内无非任务里程", vehicleNo); return false; } // log.info("车辆 {} 总里程: {}km, 任务里程: {}km, 非任务里程: {}km", // vehicleNo, totalMileage, taskMileage, nonTaskMileage); // 4. 检查非任务里程是否超过公里数阈值 if (nonTaskMileage.compareTo(config.mileageThreshold) <= 0) { // log.info("车辆 {} 非任务运行里程 {}km 未超过阈值 {}km", // vehicleNo, nonTaskMileage, config.mileageThreshold); return false; } // 5. 检查告警频率限制 if (!checkAlertFrequency(vehicleId, config)) { // log.info("车辆 {} 已达到告警频率限制", 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 params = new HashMap<>(); params.put("vehicleId", vehicleId); params.put("startTime", startTime); params.put("endTime", endTime); List 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 taskParams = new HashMap<>(); taskParams.put("vehicleId", vehicleId); taskParams.put("startTime", startTime); taskParams.put("endTime", endTime); List tasks = sysTaskMapper.selectVehicleTasksInTimeRange(taskParams); if (tasks == null || tasks.isEmpty()) { return BigDecimal.ZERO; } // 排除已取消的任务 List activeTasks = tasks.stream() .filter(t -> !"CANCELLED".equals(t.getTaskStatus())) .collect(Collectors.toList()); if (activeTasks.isEmpty()) { return BigDecimal.ZERO; } // 2. 查询车辆的所有GPS分段里程 Map segmentParams = new HashMap<>(); segmentParams.put("vehicleId", vehicleId); segmentParams.put("startTime", startTime); segmentParams.put("endTime", endTime); List 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) { // log.info("车辆 {} 今日已存在相同类型的告警,不再创建", vehicleNo); 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 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 getNotifyUsers(Long deptId, AlertConfig config) { List 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; // 通知用户列表 } }