From 20f0481f3afb352af7d4db864324b31f36d486df Mon Sep 17 00:00:00 2001 From: 86156 <823267011@qq.com> Date: Wed, 1 Oct 2025 22:19:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B7=A5=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/video/device/component/flv.vue | 3 +- .../com/ruoyi/video/domain/AlarmRecord.java | 246 +++++++++--------- .../video/service/VideoAnalysisService.java | 215 +++------------ 3 files changed, 159 insertions(+), 305 deletions(-) diff --git a/rtsp-vue/src/views/video/device/component/flv.vue b/rtsp-vue/src/views/video/device/component/flv.vue index 152da8f..6e6e57c 100644 --- a/rtsp-vue/src/views/video/device/component/flv.vue +++ b/rtsp-vue/src/views/video/device/component/flv.vue @@ -69,7 +69,8 @@ const getDetails = async (deviceId) => { const res = await getDevice(deviceId); tableData.value = res.data; // 使用当前访问的域名和端口,通过Nginx代理访问视频流 - playUrl.value = `http://49.232.154.205:10083/live?url=${tableData.value.url}`; + // 添加ffmpeg=true参数,强制使用FFmpeg转换为FLV格式 + playUrl.value = `${window.location.origin}/live?url=${tableData.value.url}&&&ffmpeg=true`; handlePlay(); } diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/domain/AlarmRecord.java b/ruoyi-video/src/main/java/com/ruoyi/video/domain/AlarmRecord.java index 5ae5d4a..7c0db86 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/domain/AlarmRecord.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/domain/AlarmRecord.java @@ -1,40 +1,56 @@ package com.ruoyi.video.domain; +import com.ruoyi.common.core.domain.BaseEntity; +import java.math.BigDecimal; import java.util.Date; /** - * 告警记录实体类 + * 报警记录对象 v_alarm_record + * + * @author ruoyi + * @date 2025-09-27 */ -public class AlarmRecord { - /** 告警ID */ +public class AlarmRecord extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 报警记录ID */ private Long alarmId; - /** 设备ID */ - private Long deviceId; - - /** 告警类型 */ - private String alarmType; - - /** 告警级别 */ - private String alarmLevel; - - /** 告警内容 */ - private String alarmContent; - /** 巡检任务ID */ private Long taskId; - /** 图像对象存储ID */ - private Long imageOssId; + /** 任务名称 */ + private String taskName; - /** 帧位置 */ - private long framePosition; + /** 设备ID */ + private Long deviceId; - /** 置信度 */ - private double confidence; + /** 设备名称 */ + private String deviceName; - /** 处理状态(0-未处理,1-已处理) */ - private Integer status; + /** 报警类型 */ + private String alarmType; + + /** 报警级别(1=低,2=中,3=高) */ + private String alarmLevel; + + /** 报警描述 */ + private String alarmDesc; + + /** 检测置信度 */ + private BigDecimal confidence; + + /** 报警图片路径 */ + private String imagePath; + + /** 报警视频路径 */ + private String videoPath; + + /** 报警时间 */ + private Date alarmTime; + + /** 处理状态(0=未处理,1=已处理,2=已忽略) */ + private String handleStatus; /** 处理人 */ private String handleBy; @@ -44,154 +60,132 @@ public class AlarmRecord { /** 处理备注 */ private String handleRemark; - - /** 创建时间 */ - private Date createTime; - - /** 备注 */ - private String remark; - - public Long getAlarmId() { - return alarmId; - } public void setAlarmId(Long alarmId) { this.alarmId = alarmId; } - public Long getDeviceId() { - return deviceId; - } - - public void setDeviceId(Long deviceId) { - this.deviceId = deviceId; - } - - public String getAlarmType() { - return alarmType; - } - - public void setAlarmType(String alarmType) { - this.alarmType = alarmType; - } - - public String getAlarmContent() { - return alarmContent; - } - - public void setAlarmContent(String alarmContent) { - this.alarmContent = alarmContent; - } - - public Long getTaskId() { - return taskId; + public Long getAlarmId() { + return alarmId; } public void setTaskId(Long taskId) { this.taskId = taskId; } - public Long getImageOssId() { - return imageOssId; + public Long getTaskId() { + return taskId; } - public void setImageOssId(Long imageOssId) { - this.imageOssId = imageOssId; + public void setTaskName(String taskName) { + this.taskName = taskName; } - public long getFramePosition() { - return framePosition; + public String getTaskName() { + return taskName; } - public void setFramePosition(long framePosition) { - this.framePosition = framePosition; + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; } - public double getConfidence() { - return confidence; + public Long getDeviceId() { + return deviceId; } - public void setConfidence(double confidence) { + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getDeviceName() { + return deviceName; + } + + public void setAlarmType(String alarmType) { + this.alarmType = alarmType; + } + + public String getAlarmType() { + return alarmType; + } + + public void setAlarmLevel(String alarmLevel) { + this.alarmLevel = alarmLevel; + } + + public String getAlarmLevel() { + return alarmLevel; + } + + public void setAlarmDesc(String alarmDesc) { + this.alarmDesc = alarmDesc; + } + + public String getAlarmDesc() { + return alarmDesc; + } + + public void setConfidence(BigDecimal confidence) { this.confidence = confidence; } - public Integer getStatus() { - return status; + public BigDecimal getConfidence() { + return confidence; } - public void setStatus(Integer status) { - this.status = status; + public void setImagePath(String imagePath) { + this.imagePath = imagePath; } - public String getHandleBy() { - return handleBy; + public String getImagePath() { + return imagePath; + } + + public void setVideoPath(String videoPath) { + this.videoPath = videoPath; + } + + public String getVideoPath() { + return videoPath; + } + + public void setAlarmTime(Date alarmTime) { + this.alarmTime = alarmTime; + } + + public Date getAlarmTime() { + return alarmTime; + } + + public void setHandleStatus(String handleStatus) { + this.handleStatus = handleStatus; + } + + public String getHandleStatus() { + return handleStatus; } public void setHandleBy(String handleBy) { this.handleBy = handleBy; } - public Date getHandleTime() { - return handleTime; + public String getHandleBy() { + return handleBy; } public void setHandleTime(Date handleTime) { this.handleTime = handleTime; } - public String getHandleRemark() { - return handleRemark; + public Date getHandleTime() { + return handleTime; } public void setHandleRemark(String handleRemark) { this.handleRemark = handleRemark; } - public Date getCreateTime() { - return createTime; - } - - public void setCreateTime(Date createTime) { - this.createTime = createTime; - } - - public String getRemark() { - return remark; - } - - public void setRemark(String remark) { - this.remark = remark; - } - - /** - * 获取ID的兼容方法,保持与原有代码兼容 - */ - public Long getId() { - return alarmId; - } - - /** - * 设置ID的兼容方法,保持与原有代码兼容 - */ - public void setId(Long id) { - this.alarmId = id; - } - - /** - * 设置告警级别 - * - * @param alarmLevel 告警级别 - */ - public void setAlarmLevel(String alarmLevel) { - this.alarmLevel = alarmLevel; - } - - /** - * 获取告警级别 - * - * @return 告警级别 - */ - public String getAlarmLevel() { - return alarmLevel; + public String getHandleRemark() { + return handleRemark; } } \ No newline at end of file diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/service/VideoAnalysisService.java b/ruoyi-video/src/main/java/com/ruoyi/video/service/VideoAnalysisService.java index 5cfe633..0515d96 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/service/VideoAnalysisService.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/service/VideoAnalysisService.java @@ -241,27 +241,23 @@ public class VideoAnalysisService { String bucketName = "alarm-images"; CustomMultipartFile multipartFile = new CustomMultipartFile(alarmImageFile, fileName, "image/jpeg"); - String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream()); + String imagePath = minioService.putObject(bucketName, fileName, multipartFile.getInputStream()); - VMinioObject minioObject = new VMinioObject(); - minioObject.setBucketName(bucketName); - minioObject.setObjectName(fileName); - minioObject.setUrl(objectUrl); - minioObject.setCreateBy("system"); - minioObject.setCreateTime(new Date()); - Long objectId = vMinioObjectService.insertVMinioObject(minioObject); - - // 创建告警记录 + // 创建告警记录(严格按照DDL) AlarmRecord alarmRecord = new AlarmRecord(); - alarmRecord.setDeviceId(task.getDeviceId()); - alarmRecord.setAlarmType("detection"); - alarmRecord.setAlarmContent(detection.getLabel() + " - 置信度: " + String.format("%.2f", detection.getConfidence())); alarmRecord.setTaskId(task.getTaskId()); - alarmRecord.setImageOssId(objectId); - alarmRecord.setFramePosition(frameCount); - alarmRecord.setConfidence(detection.getConfidence()); + alarmRecord.setTaskName(task.getTaskName()); + alarmRecord.setDeviceId(task.getDeviceId()); + alarmRecord.setDeviceName(task.getDeviceName()); + alarmRecord.setAlarmType("detection"); + alarmRecord.setAlarmLevel("2"); // 2=中级 + alarmRecord.setAlarmDesc(detection.getLabel() + " - 置信度: " + String.format("%.2f", detection.getConfidence())); + alarmRecord.setConfidence(java.math.BigDecimal.valueOf(detection.getConfidence())); + alarmRecord.setImagePath(imagePath); + alarmRecord.setAlarmTime(new Date()); + alarmRecord.setHandleStatus("0"); // 0=未处理 + alarmRecord.setCreateBy("system"); alarmRecord.setCreateTime(new Date()); - alarmRecord.setStatus(0); // 未处理 alarmRecordMapper.insertAlarmRecord(alarmRecord); @@ -293,89 +289,31 @@ public class VideoAnalysisService { minioObject.setCreateTime(new Date()); Long objectId = vMinioObjectService.insertVMinioObject(minioObject); - // 更新任务的处理后视频ID - task.setProcessedVideoOssId(objectId); - task.setVideoStatus(2); // 2: 已分析 - taskMapper.updateInspectionTask(task); - // 更新记录的附件信息,添加处理后的视频URL String currentAccessory = record.getAccessory(); - record.setAccessory(currentAccessory + ";" + objectUrl); + if (currentAccessory == null || currentAccessory.isEmpty()) { + record.setAccessory(objectUrl); + } else { + record.setAccessory(currentAccessory + ";" + objectUrl); + } inspectionTaskRecordMapper.updateInspectionTaskRecord(record); - log.info("处理后视频已上传: 任务ID={}, 记录ID={}, MinIO对象ID={}", - task.getTaskId(), record.getRecordId(), objectId); + log.info("处理后视频已上传: 任务ID={}, 记录ID={}, MinIO对象ID={}, URL={}", + task.getTaskId(), record.getRecordId(), objectId, objectUrl); } - /** - * 异步分析视频 - * @param taskId 巡检任务ID - */ - @Async - public void analyzeVideo(Long taskId) { - log.info("开始异步分析视频: 任务ID={}", taskId); - - try { - // 1. 获取任务信息 - InspectionTask task = taskMapper.selectInspectionTaskById(taskId); - if (task == null) { - log.error("任务不存在: {}", taskId); - return; - } - // 2. 获取原始视频信息 - Long videoOssId = task.getVideoOssId(); - if (videoOssId == null) { - log.error("任务视频不存在: {}", taskId); - return; - } - - VMinioObject videoObject = vMinioObjectService.selectVMinioObjectById(videoOssId); - if (videoObject == null) { - log.error("MinIO对象不存在: {}", videoOssId); - return; - } - - // 3. 下载原始视频到临时文件 - File videoTempFile = File.createTempFile("analysis_input_", ".mp4"); - try (InputStream inputStream = minioService.getObject(videoObject.getBucketName(), videoObject.getObjectName()); - FileOutputStream fileOutputStream = new FileOutputStream(videoTempFile)) { - byte[] buffer = new byte[8192]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - fileOutputStream.write(buffer, 0, bytesRead); - } - } - - // 4. 创建输出视频文件 - File outputVideoFile = File.createTempFile("analysis_output_", ".mp4"); - - // 5. 创建检测器 - HttpYoloDetector detector = new HttpYoloDetector("garbage", PYTHON_API_URL, MODEL_NAME, 0x00FF00); - - // 6. 处理视频 - processVideo(videoTempFile, outputVideoFile, detector, task); - - // 7. 清理临时文件 - if (videoTempFile.exists()) { - videoTempFile.delete(); - } - - log.info("视频分析完成: 任务ID={}", taskId); - } catch (Exception e) { - log.error("视频分析失败: 任务ID={}, 错误={}", taskId, e.getMessage()); - e.printStackTrace(); - } - } /** - * 处理视频 + * 处理视频记录 * @param inputFile 输入视频文件 * @param outputFile 输出视频文件 * @param detector 检测器 * @param task 巡检任务 + * @param record 巡检记录 */ - private void processVideo(File inputFile, File outputFile, HttpYoloDetector detector, InspectionTask task) throws Exception { + private void processVideoForRecord(File inputFile, File outputFile, HttpYoloDetector detector, + InspectionTask task, com.ruoyi.video.domain.InspectionTaskRecord record) throws Exception { FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile); grabber.start(); @@ -386,10 +324,12 @@ public class VideoAnalysisService { recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); recorder.setFrameRate(grabber.getFrameRate()); recorder.setVideoBitrate(grabber.getVideoBitrate()); - recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); - recorder.setAudioBitrate(grabber.getAudioBitrate()); - recorder.setAudioChannels(grabber.getAudioChannels()); - recorder.setSampleRate(grabber.getSampleRate()); + if (grabber.getAudioChannels() > 0) { + recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); + recorder.setAudioBitrate(grabber.getAudioBitrate()); + recorder.setAudioChannels(grabber.getAudioChannels()); + recorder.setSampleRate(grabber.getSampleRate()); + } recorder.start(); OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat(); @@ -397,7 +337,7 @@ public class VideoAnalysisService { // 用于去重的垃圾检测结果缓存 Map detectedGarbageCache = new HashMap<>(); // 跟踪检测到的垃圾ID - final Long[] detectionId = {1L}; // 使用数组实现effectively final + final Long[] detectionId = {1L}; // 帧计数 long frameCount = 0; @@ -425,8 +365,8 @@ public class VideoAnalysisService { // 这是新检测到的垃圾 detectedGarbageCache.put(detectionKey, detectionId[0]++); - // 创建告警记录 - createAlarmRecord(task, detection, mat, frameCount); + // 创建告警记录(关联到记录) + createAlarmRecordForRecord(task, record, detection, mat, frameCount); } else { // 更新上次检测时间 detectedGarbageCache.put(detectionKey, detectionId[0]++); @@ -458,40 +398,11 @@ public class VideoAnalysisService { grabber.stop(); grabber.close(); - // 上传处理后的视频 - uploadProcessedVideo(outputFile, task); + // 上传处理后的视频(更新记录) + uploadProcessedVideoForRecord(outputFile, task, record); } - /** - * 上传处理后的视频到MinIO - */ - private void uploadProcessedVideo(File videoFile, InspectionTask task) throws Exception { - String fileName = "processed_" + System.currentTimeMillis() + ".mp4"; - String bucketName = "inspection-videos"; - - CustomMultipartFile multipartFile = new CustomMultipartFile(videoFile, fileName, "video/mp4"); - String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream()); - - VMinioObject minioObject = new VMinioObject(); - minioObject.setBucketName(bucketName); - minioObject.setObjectName(fileName); - minioObject.setUrl(objectUrl); - minioObject.setCreateBy("system"); - minioObject.setCreateTime(new Date()); - Long objectId = vMinioObjectService.insertVMinioObject(minioObject); - - // 更新任务的处理后视频ID - task.setProcessedVideoOssId(objectId); - task.setVideoStatus(2); // 2: 已分析 - taskMapper.updateInspectionTask(task); - - log.info("处理后视频已上传: 任务ID={}, MinIO对象ID={}", task.getTaskId(), objectId); - - // 删除临时文件 - if (videoFile.exists()) { - videoFile.delete(); - } - } + /** * 生成检测结果的唯一键,用于检测结果去重 @@ -507,59 +418,7 @@ public class VideoAnalysisService { return String.format("%s_%d_%d_%d_%d", detection.getLabel(), x, y, w, h); } - /** - * 创建告警记录 - */ - private void createAlarmRecord(InspectionTask task, Detection detection, Mat frame, long frameCount) throws Exception { - // 创建告警图像临时文件 - File alarmImageFile = File.createTempFile("alarm_", ".jpg"); - - // 裁剪检测区域,略微扩大区域 - Rect rect = detection.getRect(); - int x = Math.max(0, rect.x() - 10); - int y = Math.max(0, rect.y() - 10); - int w = Math.min(frame.cols() - x, rect.width() + 20); - int h = Math.min(frame.rows() - y, rect.height() + 20); - - // 使用OpenCV保存告警图片 - Mat roi = new Mat(frame, new Rect(x, y, w, h)); - org.bytedeco.opencv.global.opencv_imgcodecs.imwrite(alarmImageFile.getAbsolutePath(), roi); - - // 上传告警图片到MinIO - String fileName = "alarm_" + System.currentTimeMillis() + ".jpg"; - String bucketName = "alarm-images"; - - CustomMultipartFile multipartFile = new CustomMultipartFile(alarmImageFile, fileName, "image/jpeg"); - String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream()); - - VMinioObject minioObject = new VMinioObject(); - minioObject.setBucketName(bucketName); - minioObject.setObjectName(fileName); - minioObject.setUrl(objectUrl); - minioObject.setCreateBy("system"); - minioObject.setCreateTime(new Date()); - Long objectId = vMinioObjectService.insertVMinioObject(minioObject); - - // 创建告警记录 - AlarmRecord alarmRecord = new AlarmRecord(); - alarmRecord.setDeviceId(task.getDeviceId()); - alarmRecord.setAlarmType("garbage"); - alarmRecord.setAlarmContent(detection.getLabel() + " - 置信度: " + String.format("%.2f", detection.getConfidence())); - alarmRecord.setTaskId(task.getTaskId()); - alarmRecord.setImageOssId(objectId); - alarmRecord.setFramePosition(frameCount); - alarmRecord.setConfidence(detection.getConfidence()); - alarmRecord.setCreateTime(new Date()); - - alarmRecordMapper.insertAlarmRecord(alarmRecord); - - log.info("创建告警记录: 类型={}, 任务ID={}, 告警ID={}", detection.getLabel(), task.getTaskId(), alarmRecord.getAlarmId()); - - // 删除临时文件 - if (alarmImageFile.exists()) { - alarmImageFile.delete(); - } - } + /** * 在图像上绘制检测框