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.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<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) {
|
// 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<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; // 通知用户列表
|
}
|
}
|