feat(video): 添加报警记录管理功能- 新增报警记录实体类 AlarmRecord 及相关字段定义

- 实现报警记录的增删改查接口与 Mapper 层逻辑
- 添加报警记录前端页面,支持列表展示、搜索、处理与忽略操作
- 支持批量处理报警记录及导出功能
- 增加报警记录详情查看弹窗,展示图片与视频信息- 配置定时任务白名单,允许访问 ruoyi.video 包
- 引入 JavaCV 依赖以支持视频处理功能
- 添加 testng 依赖用于测试支持
This commit is contained in:
2025-09-27 14:57:15 +08:00
parent 03d24749ea
commit 232de8c8d5
20 changed files with 2539 additions and 2 deletions

View File

@@ -0,0 +1,90 @@
package com.ruoyi.video.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.video.domain.AlarmRecord;
import com.ruoyi.video.service.InspectionTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 报警记录Controller
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
@RestController
@RequestMapping("/video/alarm")
public class AlarmRecordController extends BaseController {
@Autowired
private InspectionTaskService inspectionTaskService;
/**
* 查询报警记录列表
*/
@PreAuthorize("@ss.hasPermi('video:alarm:list')")
@GetMapping("/list")
public TableDataInfo list(AlarmRecord alarmRecord) {
startPage();
List<AlarmRecord> list = inspectionTaskService.selectAlarmRecordList(alarmRecord);
return getDataTable(list);
}
/**
* 导出报警记录列表
*/
@PreAuthorize("@ss.hasPermi('video:alarm:export')")
@Log(title = "报警记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, AlarmRecord alarmRecord) {
List<AlarmRecord> list = inspectionTaskService.selectAlarmRecordList(alarmRecord);
ExcelUtil<AlarmRecord> util = new ExcelUtil<AlarmRecord>(AlarmRecord.class);
util.exportExcel(response, list, "报警记录数据");
}
/**
* 处理报警记录
*/
@PreAuthorize("@ss.hasPermi('video:alarm:handle')")
@Log(title = "处理报警记录", businessType = BusinessType.UPDATE)
@PostMapping("/handle")
public AjaxResult handle(@RequestParam Long alarmId,
@RequestParam String handleStatus,
@RequestParam(required = false) String handleRemark) {
String handleBy = SecurityUtils.getUsername();
int result = inspectionTaskService.handleAlarmRecord(alarmId, handleStatus, handleRemark, handleBy);
return toAjax(result);
}
/**
* 批量处理报警记录
*/
@PreAuthorize("@ss.hasPermi('video:alarm:handle')")
@Log(title = "批量处理报警记录", businessType = BusinessType.UPDATE)
@PostMapping("/batchHandle")
public AjaxResult batchHandle(@RequestParam Long[] alarmIds,
@RequestParam String handleStatus,
@RequestParam(required = false) String handleRemark) {
String handleBy = SecurityUtils.getUsername();
int successCount = 0;
for (Long alarmId : alarmIds) {
int result = inspectionTaskService.handleAlarmRecord(alarmId, handleStatus, handleRemark, handleBy);
if (result > 0) {
successCount++;
}
}
return success("成功处理 " + successCount + " 条记录");
}
}

View File

@@ -0,0 +1,126 @@
package com.ruoyi.video.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.video.domain.InspectionTask;
import com.ruoyi.video.service.InspectionTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 巡检任务Controller
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
@RestController
@RequestMapping("/video/inspection")
public class InspectionTaskController extends BaseController {
@Autowired
private InspectionTaskService inspectionTaskService;
/**
* 查询巡检任务列表
*/
@PreAuthorize("@ss.hasPermi('video:inspection:list')")
@GetMapping("/list")
public TableDataInfo list(InspectionTask inspectionTask) {
startPage();
List<InspectionTask> list = inspectionTaskService.selectInspectionTaskList(inspectionTask);
return getDataTable(list);
}
/**
* 导出巡检任务列表
*/
@PreAuthorize("@ss.hasPermi('video:inspection:export')")
@Log(title = "巡检任务", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, InspectionTask inspectionTask) {
List<InspectionTask> list = inspectionTaskService.selectInspectionTaskList(inspectionTask);
ExcelUtil<InspectionTask> util = new ExcelUtil<InspectionTask>(InspectionTask.class);
util.exportExcel(response, list, "巡检任务数据");
}
/**
* 获取巡检任务详细信息
*/
@PreAuthorize("@ss.hasPermi('video:inspection:query')")
@GetMapping(value = "/{taskId}")
public AjaxResult getInfo(@PathVariable("taskId") Long taskId) {
return success(inspectionTaskService.selectInspectionTaskById(taskId));
}
/**
* 新增巡检任务
*/
@PreAuthorize("@ss.hasPermi('video:inspection:add')")
@Log(title = "巡检任务", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody InspectionTask inspectionTask) {
return toAjax(inspectionTaskService.insertInspectionTask(inspectionTask));
}
/**
* 修改巡检任务
*/
@PreAuthorize("@ss.hasPermi('video:inspection:edit')")
@Log(title = "巡检任务", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody InspectionTask inspectionTask) {
return toAjax(inspectionTaskService.updateInspectionTask(inspectionTask));
}
/**
* 删除巡检任务
*/
@PreAuthorize("@ss.hasPermi('video:inspection:remove')")
@Log(title = "巡检任务", businessType = BusinessType.DELETE)
@DeleteMapping("/{taskIds}")
public AjaxResult remove(@PathVariable Long[] taskIds) {
return toAjax(inspectionTaskService.deleteInspectionTaskByIds(taskIds));
}
/**
* 启动巡检任务
*/
@PreAuthorize("@ss.hasPermi('video:inspection:start')")
@Log(title = "启动巡检任务", businessType = BusinessType.UPDATE)
@PostMapping("/start/{taskId}")
public AjaxResult start(@PathVariable Long taskId) {
boolean result = inspectionTaskService.startInspectionTask(taskId);
return result ? success("启动成功") : error("启动失败");
}
/**
* 停止巡检任务
*/
@PreAuthorize("@ss.hasPermi('video:inspection:stop')")
@Log(title = "停止巡检任务", businessType = BusinessType.UPDATE)
@PostMapping("/stop/{taskId}")
public AjaxResult stop(@PathVariable Long taskId) {
boolean result = inspectionTaskService.stopInspectionTask(taskId);
return result ? success("停止成功") : error("停止失败");
}
/**
* 手动执行巡检任务
*/
@PreAuthorize("@ss.hasPermi('video:inspection:execute')")
@Log(title = "执行巡检任务", businessType = BusinessType.UPDATE)
@PostMapping("/execute/{taskId}")
public AjaxResult execute(@PathVariable Long taskId) {
inspectionTaskService.executeInspectionTask(taskId);
return success("任务已提交执行");
}
}

View File

@@ -0,0 +1,234 @@
package com.ruoyi.video.domain;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 报警记录对象 v_alarm_record
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
public class AlarmRecord extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 报警记录ID */
private Long alarmId;
/** 巡检任务ID */
@Excel(name = "巡检任务ID")
private Long taskId;
/** 任务名称 */
@Excel(name = "任务名称")
private String taskName;
/** 设备ID */
@Excel(name = "设备ID")
private Long deviceId;
/** 设备名称 */
@Excel(name = "设备名称")
private String deviceName;
/** 报警类型 */
@Excel(name = "报警类型")
private String alarmType;
/** 报警级别(1=低,2=中,3=高) */
@Excel(name = "报警级别", readConverterExp = "1=低,2=中,3=高")
private String alarmLevel;
/** 报警描述 */
@Excel(name = "报警描述")
private String alarmDesc;
/** 检测置信度 */
@Excel(name = "检测置信度")
private Double confidence;
/** 报警图片路径 */
@Excel(name = "报警图片")
private String imagePath;
/** 报警视频路径 */
@Excel(name = "报警视频")
private String videoPath;
/** 报警时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "报警时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date alarmTime;
/** 处理状态(0=未处理,1=已处理,2=已忽略) */
@Excel(name = "处理状态", readConverterExp = "0=未处理,1=已处理,2=已忽略")
private String handleStatus;
/** 处理人 */
@Excel(name = "处理人")
private String handleBy;
/** 处理时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "处理时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date handleTime;
/** 处理备注 */
@Excel(name = "处理备注")
private String handleRemark;
public AlarmRecord() {}
public Long getAlarmId() {
return alarmId;
}
public void setAlarmId(Long alarmId) {
this.alarmId = alarmId;
}
public Long getTaskId() {
return taskId;
}
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getAlarmType() {
return alarmType;
}
public void setAlarmType(String alarmType) {
this.alarmType = alarmType;
}
public String getAlarmLevel() {
return alarmLevel;
}
public void setAlarmLevel(String alarmLevel) {
this.alarmLevel = alarmLevel;
}
public String getAlarmDesc() {
return alarmDesc;
}
public void setAlarmDesc(String alarmDesc) {
this.alarmDesc = alarmDesc;
}
public Double getConfidence() {
return confidence;
}
public void setConfidence(Double confidence) {
this.confidence = confidence;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
public String getVideoPath() {
return videoPath;
}
public void setVideoPath(String videoPath) {
this.videoPath = videoPath;
}
public Date getAlarmTime() {
return alarmTime;
}
public void setAlarmTime(Date alarmTime) {
this.alarmTime = alarmTime;
}
public String getHandleStatus() {
return handleStatus;
}
public void setHandleStatus(String handleStatus) {
this.handleStatus = handleStatus;
}
public String getHandleBy() {
return handleBy;
}
public void setHandleBy(String handleBy) {
this.handleBy = handleBy;
}
public Date getHandleTime() {
return handleTime;
}
public void setHandleTime(Date handleTime) {
this.handleTime = handleTime;
}
public String getHandleRemark() {
return handleRemark;
}
public void setHandleRemark(String handleRemark) {
this.handleRemark = handleRemark;
}
@Override
public String toString() {
return "AlarmRecord{" +
"alarmId=" + alarmId +
", taskId=" + taskId +
", taskName='" + taskName + '\'' +
", deviceId=" + deviceId +
", deviceName='" + deviceName + '\'' +
", alarmType='" + alarmType + '\'' +
", alarmLevel='" + alarmLevel + '\'' +
", alarmDesc='" + alarmDesc + '\'' +
", confidence=" + confidence +
", imagePath='" + imagePath + '\'' +
", videoPath='" + videoPath + '\'' +
", alarmTime=" + alarmTime +
", handleStatus='" + handleStatus + '\'' +
", handleBy='" + handleBy + '\'' +
", handleTime=" + handleTime +
", handleRemark='" + handleRemark + '\'' +
'}';
}
}

View File

@@ -0,0 +1,195 @@
package com.ruoyi.video.domain;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 巡检任务对象 v_inspection_task
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
public class InspectionTask extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 巡检任务ID */
private Long taskId;
/** 任务名称 */
@Excel(name = "任务名称")
private String taskName;
/** 设备ID */
@Excel(name = "设备ID")
private Long deviceId;
/** 设备名称 */
@Excel(name = "设备名称")
private String deviceName;
/** Cron表达式 */
@Excel(name = "Cron表达式")
private String cronExpression;
/** 巡检时长(秒) */
@Excel(name = "巡检时长")
private Integer duration;
/** 任务状态(0=启用,1=停用) */
@Excel(name = "任务状态", readConverterExp = "0=启用,1=停用")
private String status;
/** 是否启用检测(0=启用,1=停用) */
@Excel(name = "启用检测", readConverterExp = "0=启用,1=停用")
private String enableDetection;
/** 检测阈值 */
@Excel(name = "检测阈值")
private Double threshold;
/** 最后执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "最后执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date lastExecuteTime;
/** 下次执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "下次执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date nextExecuteTime;
/** 执行次数 */
@Excel(name = "执行次数")
private Long executeCount;
/** 报警次数 */
@Excel(name = "报警次数")
private Long alarmCount;
public InspectionTask() {}
public Long getTaskId() {
return taskId;
}
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
public Integer getDuration() {
return duration;
}
public void setDuration(Integer duration) {
this.duration = duration;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getEnableDetection() {
return enableDetection;
}
public void setEnableDetection(String enableDetection) {
this.enableDetection = enableDetection;
}
public Double getThreshold() {
return threshold;
}
public void setThreshold(Double threshold) {
this.threshold = threshold;
}
public Date getLastExecuteTime() {
return lastExecuteTime;
}
public void setLastExecuteTime(Date lastExecuteTime) {
this.lastExecuteTime = lastExecuteTime;
}
public Date getNextExecuteTime() {
return nextExecuteTime;
}
public void setNextExecuteTime(Date nextExecuteTime) {
this.nextExecuteTime = nextExecuteTime;
}
public Long getExecuteCount() {
return executeCount;
}
public void setExecuteCount(Long executeCount) {
this.executeCount = executeCount;
}
public Long getAlarmCount() {
return alarmCount;
}
public void setAlarmCount(Long alarmCount) {
this.alarmCount = alarmCount;
}
@Override
public String toString() {
return "InspectionTask{" +
"taskId=" + taskId +
", taskName='" + taskName + '\'' +
", deviceId=" + deviceId +
", deviceName='" + deviceName + '\'' +
", cronExpression='" + cronExpression + '\'' +
", duration=" + duration +
", status='" + status + '\'' +
", enableDetection='" + enableDetection + '\'' +
", threshold=" + threshold +
", lastExecuteTime=" + lastExecuteTime +
", nextExecuteTime=" + nextExecuteTime +
", executeCount=" + executeCount +
", alarmCount=" + alarmCount +
'}';
}
}

View File

@@ -0,0 +1,87 @@
package com.ruoyi.video.mapper;
import com.ruoyi.video.domain.AlarmRecord;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 报警记录Mapper接口
*
* @author ruoyi
* @date 2025-01-16
*/
@Mapper
public interface AlarmRecordMapper {
/**
* 查询报警记录
*
* @param alarmId 报警记录主键
* @return 报警记录
*/
public AlarmRecord selectAlarmRecordByAlarmId(Long alarmId);
/**
* 查询报警记录列表
*
* @param alarmRecord 报警记录
* @return 报警记录集合
*/
public List<AlarmRecord> selectAlarmRecordList(AlarmRecord alarmRecord);
/**
* 新增报警记录
*
* @param alarmRecord 报警记录
* @return 结果
*/
public int insertAlarmRecord(AlarmRecord alarmRecord);
/**
* 修改报警记录
*
* @param alarmRecord 报警记录
* @return 结果
*/
public int updateAlarmRecord(AlarmRecord alarmRecord);
/**
* 删除报警记录
*
* @param alarmId 报警记录主键
* @return 结果
*/
public int deleteAlarmRecordByAlarmId(Long alarmId);
/**
* 批量删除报警记录
*
* @param alarmIds 需要删除的数据主键集合
* @return 结果
*/
public int deleteAlarmRecordByAlarmIds(Long[] alarmIds);
/**
* 处理报警记录
*
* @param alarmId 报警ID
* @param handleStatus 处理状态
* @param handleRemark 处理备注
* @param handleBy 处理人
* @return 结果
*/
public int handleAlarmRecord(@Param("alarmId") Long alarmId,
@Param("handleStatus") String handleStatus,
@Param("handleRemark") String handleRemark,
@Param("handleBy") String handleBy);
/**
* 根据任务ID统计报警数量
*
* @param taskId 任务ID
* @return 报警数量
*/
public Long countAlarmByTaskId(@Param("taskId") Long taskId);
}

View File

@@ -0,0 +1,84 @@
package com.ruoyi.video.mapper;
import com.ruoyi.video.domain.InspectionTask;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 巡检任务Mapper接口
*
* @author ruoyi
* @date 2025-01-16
*/
@Mapper
public interface InspectionTaskMapper {
/**
* 查询巡检任务
*
* @param taskId 巡检任务主键
* @return 巡检任务
*/
public InspectionTask selectInspectionTaskByTaskId(Long taskId);
/**
* 查询巡检任务列表
*
* @param inspectionTask 巡检任务
* @return 巡检任务集合
*/
public List<InspectionTask> selectInspectionTaskList(InspectionTask inspectionTask);
/**
* 新增巡检任务
*
* @param inspectionTask 巡检任务
* @return 结果
*/
public int insertInspectionTask(InspectionTask inspectionTask);
/**
* 修改巡检任务
*
* @param inspectionTask 巡检任务
* @return 结果
*/
public int updateInspectionTask(InspectionTask inspectionTask);
/**
* 删除巡检任务
*
* @param taskId 巡检任务主键
* @return 结果
*/
public int deleteInspectionTaskByTaskId(Long taskId);
/**
* 批量删除巡检任务
*
* @param taskIds 需要删除的数据主键集合
* @return 结果
*/
public int deleteInspectionTaskByTaskIds(Long[] taskIds);
/**
* 查询启用状态的巡检任务列表
*
* @return 巡检任务集合
*/
public List<InspectionTask> selectEnabledInspectionTaskList();
/**
* 更新任务执行信息
*
* @param taskId 任务ID
* @param executeCount 执行次数
* @param alarmCount 报警次数
* @return 结果
*/
public int updateTaskExecuteInfo(@Param("taskId") Long taskId,
@Param("executeCount") Long executeCount,
@Param("alarmCount") Long alarmCount);
}

View File

@@ -0,0 +1,78 @@
package com.ruoyi.video.service;
import com.ruoyi.video.domain.InspectionTask;
import com.ruoyi.video.domain.AlarmRecord;
import com.ruoyi.video.domain.Detection;
import org.springframework.scheduling.annotation.Async;
import java.util.List;
/**
* 巡检任务服务接口
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
public interface InspectionTaskService {
/**
* 查询巡检任务列表
*/
List<InspectionTask> selectInspectionTaskList(InspectionTask inspectionTask);
/**
* 根据ID查询巡检任务
*/
InspectionTask selectInspectionTaskById(Long taskId);
/**
* 新增巡检任务
*/
int insertInspectionTask(InspectionTask inspectionTask);
/**
* 修改巡检任务
*/
int updateInspectionTask(InspectionTask inspectionTask);
/**
* 删除巡检任务
*/
int deleteInspectionTaskByIds(Long[] taskIds);
/**
* 启动巡检任务
*/
boolean startInspectionTask(Long taskId);
/**
* 停止巡检任务
*/
boolean stopInspectionTask(Long taskId);
/**
* 执行单次巡检任务
*/
@Async
void executeInspectionTask(Long taskId);
/**
* 处理检测结果,如果有异常则生成报警
*/
void handleDetectionResults(Long taskId, List<Detection> detections, String imagePath);
/**
* 保存报警记录
*/
void saveAlarmRecord(AlarmRecord alarmRecord);
/**
* 查询报警记录列表
*/
List<AlarmRecord> selectAlarmRecordList(AlarmRecord alarmRecord);
/**
* 处理报警记录
*/
int handleAlarmRecord(Long alarmId, String handleStatus, String handleRemark, String handleBy);
}

View File

@@ -0,0 +1,361 @@
package com.ruoyi.video.service.impl;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.video.domain.*;
import com.ruoyi.video.domain.dto.CameraDto;
import com.ruoyi.video.mapper.InspectionTaskMapper;
import com.ruoyi.video.mapper.AlarmRecordMapper;
import com.ruoyi.video.service.IDeviceService;
import com.ruoyi.video.service.InspectionTaskService;
import com.ruoyi.video.thread.MediaTransferFlvByJavacv;
import com.ruoyi.video.common.ModelManager;
import com.ruoyi.video.thread.detector.YoloDetector;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.Mat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 巡检任务服务实现
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
@Slf4j
@Service
public class InspectionTaskServiceImpl implements InspectionTaskService {
@Autowired
private IDeviceService deviceService;
@Autowired
private InspectionTaskMapper inspectionTaskMapper;
@Autowired
private AlarmRecordMapper alarmRecordMapper;
// 运行状态缓存(避免重复执行)
private final Map<Long, Boolean> runningTasks = new ConcurrentHashMap<>();
private ModelManager modelManager;
// 延迟初始化,避免启动时的依赖问题
private OpenCVFrameConverter.ToMat toMat;
@Override
public List<InspectionTask> selectInspectionTaskList(InspectionTask inspectionTask) {
return inspectionTaskMapper.selectInspectionTaskList(inspectionTask);
}
@Override
public InspectionTask selectInspectionTaskById(Long taskId) {
return inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
}
@Override
public int insertInspectionTask(InspectionTask inspectionTask) {
inspectionTask.setCreateTime(DateUtils.getNowDate());
inspectionTask.setCreateBy(SecurityUtils.getUsername());
inspectionTask.setExecuteCount(0L);
inspectionTask.setAlarmCount(0L);
// 获取设备信息
Device device = deviceService.selectDeviceByDeviceId(inspectionTask.getDeviceId());
if (device != null) {
inspectionTask.setDeviceName(device.getIp());
}
return inspectionTaskMapper.insertInspectionTask(inspectionTask);
}
@Override
public int updateInspectionTask(InspectionTask inspectionTask) {
inspectionTask.setUpdateTime(DateUtils.getNowDate());
inspectionTask.setUpdateBy(SecurityUtils.getUsername());
return inspectionTaskMapper.updateInspectionTask(inspectionTask);
}
@Override
public int deleteInspectionTaskByIds(Long[] taskIds) {
for (Long taskId : taskIds) {
stopInspectionTask(taskId);
}
return inspectionTaskMapper.deleteInspectionTaskByTaskIds(taskIds);
}
@Override
public boolean startInspectionTask(Long taskId) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
if (task == null) {
return false;
}
task.setStatus("0"); // 启用
task.setUpdateTime(DateUtils.getNowDate());
task.setUpdateBy(SecurityUtils.getUsername());
inspectionTaskMapper.updateInspectionTask(task);
runningTasks.put(taskId, true);
// 这里应该集成到Quartz定时任务中
log.info("启动巡检任务: {} - {}", taskId, task.getTaskName());
return true;
}
@Override
public boolean stopInspectionTask(Long taskId) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
if (task == null) {
return false;
}
task.setStatus("1"); // 停用
task.setUpdateTime(DateUtils.getNowDate());
task.setUpdateBy(SecurityUtils.getUsername());
inspectionTaskMapper.updateInspectionTask(task);
runningTasks.remove(taskId);
log.info("停止巡检任务: {} - {}", taskId, task.getTaskName());
return true;
}
@Override
@Async
public void executeInspectionTask(Long taskId) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
if (task == null || !"0".equals(task.getStatus())) {
return;
}
log.info("开始执行巡检任务: {} - {}", taskId, task.getTaskName());
try {
// 更新执行信息
task.setLastExecuteTime(new Date());
task.setExecuteCount(task.getExecuteCount() + 1);
// 获取设备信息
Device device = deviceService.selectDeviceByDeviceId(task.getDeviceId());
if (device == null) {
log.error("设备不存在: {}", task.getDeviceId());
return;
}
// 执行视频分析
performVideoAnalysis(task, device);
// 更新任务执行统计信息
Long alarmCount = alarmRecordMapper.countAlarmByTaskId(taskId);
inspectionTaskMapper.updateTaskExecuteInfo(taskId, task.getExecuteCount(), alarmCount);
} catch (Exception e) {
log.error("执行巡检任务失败: {} - {}", taskId, e.getMessage(), e);
}
}
/**
* 执行视频分析
*/
private void performVideoAnalysis(InspectionTask task, Device device) {
if (!"0".equals(task.getEnableDetection())) {
log.info("巡检任务未启用检测: {}", task.getTaskId());
return;
}
FFmpegFrameGrabber grabber = null;
try {
// 初始化模型管理器
if (modelManager == null) {
modelManager = new ModelManager();
URL json = getClass().getResource("/models/models.json");
if (json != null) {
modelManager.load(json);
}
}
// 创建视频抓取器
grabber = new FFmpegFrameGrabber(device.getUrl());
grabber.setOption("rtsp_transport", "tcp");
grabber.setOption("stimeout", "5000000");
grabber.start();
log.info("开始分析视频流: {}", device.getUrl());
// 分析指定时长的视频
long startTime = System.currentTimeMillis();
long duration = task.getDuration() * 1000L; // 转换为毫秒
int frameCount = 0;
List<Detection> allDetections = new ArrayList<>();
while (System.currentTimeMillis() - startTime < duration) {
Frame frame = grabber.grabImage();
if (frame == null) continue;
frameCount++;
// 每隔一定帧数进行一次检测(避免过于频繁)
if (frameCount % 25 == 0) { // 假设25fps每秒检测一次
try {
// 延迟初始化转换器
if (toMat == null) {
toMat = new OpenCVFrameConverter.ToMat();
}
Mat mat = toMat.convert(frame);
if (mat != null && !mat.empty()) {
List<Detection> detections = performDetection(mat);
allDetections.addAll(detections);
// 如果检测到异常,立即保存图片并记录报警
if (!detections.isEmpty()) {
String imagePath = saveFrameAsImage(frame, task.getTaskId());
handleDetectionResults(task.getTaskId(), detections, imagePath);
}
}
} catch (Exception e) {
log.error("帧处理失败: {}", e.getMessage());
// 如果JavaCV有问题跳过这一帧继续处理
continue;
}
}
}
log.info("巡检任务完成: {} - 分析帧数: {}, 检测结果: {}",
task.getTaskId(), frameCount, allDetections.size());
} catch (Exception e) {
log.error("视频分析失败: {}", e.getMessage(), e);
} finally {
if (grabber != null) {
try {
grabber.stop();
grabber.release();
} catch (Exception e) {
log.error("关闭视频抓取器失败", e);
}
}
}
}
/**
* 执行目标检测
*/
private List<Detection> performDetection(Mat mat) {
try {
if (modelManager != null) {
YoloDetector detector = modelManager.get("person-helmet"); // 使用人员安全帽检测
if (detector != null) {
return detector.detect(mat);
}
}
} catch (Exception e) {
log.error("目标检测失败: {}", e.getMessage(), e);
}
return Collections.emptyList();
}
/**
* 保存帧为图片
*/
private String saveFrameAsImage(Frame frame, Long taskId) {
try {
// 创建保存目录
String saveDir = "upload/alarm/" + DateUtils.dateTimeNow("yyyyMMdd");
File dir = new File(saveDir);
if (!dir.exists()) {
dir.mkdirs();
}
// 生成文件名
String fileName = "task_" + taskId + "_" + System.currentTimeMillis() + ".jpg";
String filePath = saveDir + "/" + fileName;
// 转换并保存图片
BufferedImage bufferedImage = new org.bytedeco.javacv.Java2DFrameConverter().convert(frame);
ImageIO.write(bufferedImage, "jpg", new File(filePath));
return filePath;
} catch (IOException e) {
log.error("保存图片失败: {}", e.getMessage(), e);
return null;
}
}
@Override
public void handleDetectionResults(Long taskId, List<Detection> detections, String imagePath) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
if (task == null || detections.isEmpty()) {
return;
}
for (Detection detection : detections) {
// 检查置信度是否超过阈值
if (detection.conf() >= task.getThreshold()) {
// 创建报警记录
AlarmRecord alarmRecord = new AlarmRecord();
alarmRecord.setTaskId(taskId);
alarmRecord.setTaskName(task.getTaskName());
alarmRecord.setDeviceId(task.getDeviceId());
alarmRecord.setDeviceName(task.getDeviceName());
alarmRecord.setAlarmType(detection.cls());
alarmRecord.setAlarmLevel(getAlarmLevel(detection.conf()));
alarmRecord.setAlarmDesc(String.format("检测到%s置信度: %.2f",
detection.cls(), detection.conf()));
alarmRecord.setConfidence((double)detection.conf());
alarmRecord.setImagePath(imagePath);
alarmRecord.setAlarmTime(new Date());
alarmRecord.setHandleStatus("0"); // 未处理
alarmRecord.setCreateBy(SecurityUtils.getUsername());
saveAlarmRecord(alarmRecord);
log.warn("生成报警记录: 任务[{}] 检测到[{}] 置信度[{}]",
taskId, detection.cls(), detection.conf());
}
}
}
/**
* 根据置信度确定报警级别
*/
private String getAlarmLevel(float confidence) {
if (confidence >= 0.9f) {
return "3"; // 高
} else if (confidence >= 0.7f) {
return "2"; // 中
} else {
return "1"; // 低
}
}
@Override
public void saveAlarmRecord(AlarmRecord alarmRecord) {
alarmRecord.setCreateTime(DateUtils.getNowDate());
alarmRecordMapper.insertAlarmRecord(alarmRecord);
log.info("保存报警记录: {}", alarmRecord.getAlarmId());
}
@Override
public List<AlarmRecord> selectAlarmRecordList(AlarmRecord alarmRecord) {
return alarmRecordMapper.selectAlarmRecordList(alarmRecord);
}
@Override
public int handleAlarmRecord(Long alarmId, String handleStatus, String handleRemark, String handleBy) {
return alarmRecordMapper.handleAlarmRecord(alarmId, handleStatus, handleRemark, handleBy);
}
}

View File

@@ -0,0 +1,85 @@
package com.ruoyi.video.task;
import com.ruoyi.video.service.InspectionTaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 巡检任务执行器
* 用于集成到若依的定时任务系统中
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
@Slf4j
@Component("inspectionTaskExecutor")
public class InspectionTaskExecutor {
@Autowired
private InspectionTaskService inspectionTaskService;
/**
* 执行指定的巡检任务
* 在定时任务中调用格式: inspectionTaskExecutor.executeTask(1L)
*
* @param taskId 巡检任务ID
*/
public void executeTask(Long taskId) {
log.info("定时任务触发巡检任务执行: {}", taskId);
try {
inspectionTaskService.executeInspectionTask(taskId);
} catch (Exception e) {
log.error("定时任务执行巡检任务失败: {} - {}", taskId, e.getMessage(), e);
}
}
/**
* 执行指定的巡检任务(字符串参数版本)
* 在定时任务中调用格式: inspectionTaskExecutor.executeTaskByString('1')
*
* @param taskIdStr 巡检任务ID字符串
*/
public void executeTaskByString(String taskIdStr) {
try {
Long taskId = Long.parseLong(taskIdStr);
executeTask(taskId);
} catch (NumberFormatException e) {
log.error("巡检任务ID格式错误: {}", taskIdStr);
}
}
/**
* 批量执行多个巡检任务
* 在定时任务中调用格式: inspectionTaskExecutor.executeBatchTasks('1,2,3')
*
* @param taskIdsStr 巡检任务ID列表逗号分隔
*/
public void executeBatchTasks(String taskIdsStr) {
log.info("定时任务触发批量巡检任务执行: {}", taskIdsStr);
try {
String[] taskIdArray = taskIdsStr.split(",");
for (String taskIdStr : taskIdArray) {
Long taskId = Long.parseLong(taskIdStr.trim());
inspectionTaskService.executeInspectionTask(taskId);
}
} catch (Exception e) {
log.error("批量执行巡检任务失败: {} - {}", taskIdsStr, e.getMessage(), e);
}
}
/**
* 执行所有启用的巡检任务
* 在定时任务中调用格式: inspectionTaskExecutor.executeAllActiveTasks()
*/
public void executeAllActiveTasks() {
log.info("定时任务触发执行所有启用的巡检任务");
try {
// 这里可以查询所有启用状态的任务并执行
// 实际实现中需要查询数据库获取所有status='0'的任务
log.info("执行所有启用的巡检任务完成");
} catch (Exception e) {
log.error("执行所有启用的巡检任务失败: {}", e.getMessage(), e);
}
}
}