feat(aiot): 告警图片代理 + 告警列表页面
后端: - IAiAlertService / AiAlertServiceImpl: 新增 proxyAlertImage() 支持 COS object key(通过 CosUtil 生成 presigned URL)和完整 URL - AiAlertController: 新增 GET /api/ai/alert/image 图片代理端点 - WebSecurityConfig: 白名单加 /api/ai/alert/image 前端: - 新建 aiAlert.js API(列表查询、删除、统计、图片 URL 构造) - 新建 alertList/index.vue 告警列表页面 · 分页表格 + 类型/时间筛选 · 缩略图通过 WVP 图片代理显示 · 详情弹窗 + 删除功能 - router/index.js: 添加告警记录路由 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -60,6 +62,19 @@ public class AiAlertController {
|
||||
return alertService.statistics(startTime);
|
||||
}
|
||||
|
||||
@Operation(summary = "告警图片代理(服务端从 COS 下载后返回)")
|
||||
@GetMapping("/image")
|
||||
public ResponseEntity<byte[]> getAlertImage(@RequestParam String imagePath) {
|
||||
byte[] image = alertService.proxyAlertImage(imagePath);
|
||||
if (image == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG)
|
||||
.header("Cache-Control", "public, max-age=3600")
|
||||
.body(image);
|
||||
}
|
||||
|
||||
// ==================== Edge 告警上报 ====================
|
||||
|
||||
@Operation(summary = "Edge 告警上报")
|
||||
|
||||
@@ -21,4 +21,12 @@ public interface IAiAlertService {
|
||||
Map<String, Object> statistics(String startTime);
|
||||
|
||||
void updateDuration(String alertId, double durationMinutes);
|
||||
|
||||
/**
|
||||
* 代理获取告警图片(通过 COS presigned URL 下载后返回字节)
|
||||
*
|
||||
* @param imagePath COS 对象路径
|
||||
* @return JPEG 图片字节,失败返回 null
|
||||
*/
|
||||
byte[] proxyAlertImage(String imagePath);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ package com.genersoft.iot.vmp.aiot.service.impl;
|
||||
import com.genersoft.iot.vmp.aiot.bean.AiAlert;
|
||||
import com.genersoft.iot.vmp.aiot.dao.AiAlertMapper;
|
||||
import com.genersoft.iot.vmp.aiot.service.IAiAlertService;
|
||||
import com.genersoft.iot.vmp.aiot.util.CosUtil;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
@@ -20,6 +23,9 @@ public class AiAlertServiceImpl implements IAiAlertService {
|
||||
@Autowired
|
||||
private AiAlertMapper alertMapper;
|
||||
|
||||
@Autowired
|
||||
private CosUtil cosUtil;
|
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Override
|
||||
@@ -72,4 +78,42 @@ public class AiAlertServiceImpl implements IAiAlertService {
|
||||
public void updateDuration(String alertId, double durationMinutes) {
|
||||
alertMapper.updateDuration(alertId, durationMinutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] proxyAlertImage(String imagePath) {
|
||||
if (imagePath == null || imagePath.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果是完整 URL(https://),直接下载
|
||||
if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
|
||||
try {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
return restTemplate.getForObject(URI.create(imagePath), byte[].class);
|
||||
} catch (Exception e) {
|
||||
log.error("[AiAlert] 直接下载告警图片失败: path={}, error={}", imagePath, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// COS object key → 生成 presigned URL → 下载
|
||||
if (!cosUtil.isAvailable()) {
|
||||
log.warn("[AiAlert] COS 客户端未初始化,无法代理告警图片");
|
||||
return null;
|
||||
}
|
||||
|
||||
String presignedUrl = cosUtil.generatePresignedUrl(imagePath);
|
||||
if (presignedUrl == null) {
|
||||
log.error("[AiAlert] 生成 presigned URL 失败: imagePath={}", imagePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
return restTemplate.getForObject(URI.create(presignedUrl), byte[].class);
|
||||
} catch (Exception e) {
|
||||
log.error("[AiAlert] 代理告警图片失败: path={}, error={}", imagePath, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ public class WebSecurityConfig {
|
||||
defaultExcludes.add("/api/ai/roi/snap/image");
|
||||
defaultExcludes.add("/api/ai/camera/get");
|
||||
defaultExcludes.add("/api/ai/alert/edge/**");
|
||||
defaultExcludes.add("/api/ai/alert/image");
|
||||
defaultExcludes.add("/api/ai/device/edge/**");
|
||||
|
||||
if (userSetting.getInterfaceAuthentication() && !userSetting.getInterfaceAuthenticationExcludes().isEmpty()) {
|
||||
|
||||
Reference in New Issue
Block a user