diff --git a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java index 0d1e2e47c..9703c828e 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java @@ -235,6 +235,10 @@ public class UserSetting { */ private long alarmCatchSize = 10000; + /** + * 是否使用拉流的方式获取快照,默认false,避免流量大规模消耗,开启后则使用拉流的方式获取快照 + */ + private boolean snapByPullStream = false; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java index 709933719..a3065f0f1 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java @@ -92,6 +92,7 @@ public class WebSecurityConfig { defaultExcludes.add("/js/**"); defaultExcludes.add("/api/device/query/snap/**"); + defaultExcludes.add("/api/alarm/snap/**"); defaultExcludes.add("/record_proxy/*/**"); defaultExcludes.add("/api/emit"); defaultExcludes.add("/favicon.ico"); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelPlayService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelPlayService.java index b0653e7ed..3ffade223 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelPlayService.java @@ -38,4 +38,7 @@ public interface IGbChannelPlayService { void playbackSpeed(CommonGBChannel channel, String stream, Double speed); void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback> callback); + + + void getSnap(CommonGBChannel channel, ErrorCallback callback); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlayService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlayService.java index a1f234fd4..55fc701b3 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlayService.java @@ -66,6 +66,8 @@ public interface IPlayService { void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); + void getSnap(CommonGBChannel channel, ErrorCallback errorCallback); + void stop(InviteSessionType type, Device device, DeviceChannel channel, String stream); void stop(InviteInfo inviteInfo); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourcePlayService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourcePlayService.java index 5c125b7c3..47fe7f634 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourcePlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/ISourcePlayService.java @@ -14,4 +14,6 @@ public interface ISourcePlayService { void stopPlay(CommonGBChannel channel); + void getSnap(CommonGBChannel channel, ErrorCallback callback); + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelPlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelPlayServiceImpl.java index 1afef8ec3..8e887dbf1 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelPlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelPlayServiceImpl.java @@ -10,7 +10,6 @@ import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; import com.genersoft.iot.vmp.gb28181.service.ISourceDownloadService; import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService; import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService; -import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import lombok.extern.slf4j.Slf4j; @@ -34,9 +33,6 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService { @Autowired private Map sourcePlayServiceMap; - @Autowired - private Ijt1078PlayService jt1078PlayService; - @Autowired private Map sourcePlaybackServiceMap; @@ -238,4 +234,17 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService { } playbackService.queryRecord(channel, startTime, endTime, callback); } + + @Override + public void getSnap(CommonGBChannel channel, ErrorCallback callback) { + log.info("[通用通道] 获取快照, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId()); + Integer dataType = channel.getDataType(); + ISourcePlayService sourceChannelPlayService = sourcePlayServiceMap.get(ChannelDataType.PLAY_SERVICE + dataType); + if (sourceChannelPlayService == null) { + // 通道数据异常 + log.error("[通用通道] 获取快照 类型编号: {} 不支持实时流预览相关服务", ChannelDataType.getDateTypeDesc(channel.getDataType())); + throw new PlayException(Response.BUSY_HERE, "channel not support"); + } + sourceChannelPlayService.getSnap(channel, callback); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java index d5cb646bc..1c102d187 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java @@ -1630,6 +1630,57 @@ public class PlayServiceImpl implements IPlayService { }); } + @Override + public void getSnap(CommonGBChannel channel, ErrorCallback errorCallback) { + // 2016协议不支持直接获取国标通道的抓图, 只能通过点播的方式获取 + Device device = deviceService.getDevice(channel.getDataDeviceId()); + if (device == null) { + log.warn("[快照] 未找到通道{}的设备信息", channel); + errorCallback.run(InviteErrorCode.FAIL.getCode(), "未找到设备信息", null); + return; + } + DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); + if (deviceChannel == null) { + log.warn("[快照] 未找到通道{}的设备信息", channel); + errorCallback.run(InviteErrorCode.FAIL.getCode(), "未找到原始通道", null); + return; + } + + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getGbId()); + if (inviteInfo != null) { + if (inviteInfo.getStreamInfo() != null) { + // 已存在线直接截图 + MediaServer mediaServer = inviteInfo.getStreamInfo().getMediaServer(); + String path = "snap"; + // 请求截图 + log.info("[请求截图]: 返回byte数组" ); + byte[] snapByteArray = mediaServerService.getSnap(mediaServer, MediaApp.GB28181, inviteInfo.getStreamInfo().getStream(), 15, 1, path, null); + if (snapByteArray != null) { + errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapByteArray); + }else { + errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); + } + return; + } + } + + play(device, deviceChannel, (code, msg, data)->{ + if (code == InviteErrorCode.SUCCESS.getCode()) { + InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getGbId()); + if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { + byte[] snapByteArray = mediaServerService.getSnap(data.getMediaServer(), MediaApp.GB28181, data.getStream(), 15, 1, null, null); + errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapByteArray); + }else { + errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); + } + }else { + errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); + } + }); + } + + + @Override public void stop(InviteSessionType type, Device device, DeviceChannel channel, String stream) { if (!userSetting.getServerId().equals(device.getServerId())) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourcePlayServiceForGbImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourcePlayServiceForGbImpl.java index 8dde900a1..81c8f1ca7 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourcePlayServiceForGbImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/SourcePlayServiceForGbImpl.java @@ -48,4 +48,19 @@ public class SourcePlayServiceForGbImpl implements ISourcePlayService { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } + + @Override + public void getSnap(CommonGBChannel channel, ErrorCallback callback) { + try { + deviceChannelPlayService.getSnap(channel, callback); + } catch (PlayException e) { + callback.run(e.getCode(), e.getMsg(), null); + } catch (ControllerException e) { + log.error("[获取抓图失败] {}({}), {}", channel.getGbName(), channel.getGbDeviceId(), e.getMsg()); + callback.run(Response.BUSY_HERE, "busy here", null); + } catch (Exception e) { + log.error("[获取抓图失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); + callback.run(Response.BUSY_HERE, "busy here", null); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java index 8432a88a6..48c86d915 100644 --- a/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/service/impl/SourcePlayServiceForJTImpl.java @@ -44,4 +44,9 @@ public class SourcePlayServiceForJTImpl implements ISourcePlayService { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } + + @Override + public void getSnap(CommonGBChannel channel, ErrorCallback callback) { + + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java index 79084a1a8..71d963e79 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLMediaNodeServerService.java @@ -264,8 +264,8 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService { } @Override - public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { - ablresTfulUtils.getSnap(mediaServer, app, stream, timeoutSec, path, fileName); + public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { + return ablresTfulUtils.getSnap(mediaServer, app, stream, timeoutSec, path, fileName); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java index 9c019bd67..02977244b 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/abl/ABLRESTfulUtils.java @@ -203,11 +203,11 @@ public class ABLRESTfulUtils { return result; } - public void sendGetForImg(MediaServer mediaServerItem, String api, Map params, String targetPath, String fileName) { + public byte[] sendGetForImg(MediaServer mediaServerItem, String api, Map params, String targetPath, String fileName) { String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); HttpUrl parseUrl = HttpUrl.parse(url); if (parseUrl == null) { - return; + return null; } HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); @@ -239,9 +239,8 @@ public class ABLRESTfulUtils { outStream.write(Objects.requireNonNull(response.body()).bytes()); outStream.flush(); outStream.close(); - } else { - logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } + return Objects.requireNonNull(response.body()).bytes(); } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } @@ -252,6 +251,7 @@ public class ABLRESTfulUtils { } catch (IOException e) { logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); } + return null; } public void sendGetForImgForUrl(String url, String targetPath, String fileName) { @@ -414,7 +414,7 @@ public class ABLRESTfulUtils { } } - public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, String path, String fileName) { + public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, String path, String fileName) { Map param = new HashMap<>(); param.put("app", app); param.put("stream", stream); @@ -425,8 +425,7 @@ public class ABLRESTfulUtils { // String url = jsonObject.getString("url"); // sendGetForImgForUrl(url, path, fileName); // } - sendGetForImg(mediaServer, "getSnap", param, path, fileName); - + return sendGetForImg(mediaServer, "getSnap", param, path, fileName); } public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java index 49aa5e1a2..9a3af07b1 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaNodeServerService.java @@ -46,7 +46,7 @@ public interface IMediaNodeServerService { Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String app, String stream); - void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); + byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream); diff --git a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java index 9f7320a7e..a65a91961 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/service/IMediaServerService.java @@ -83,7 +83,7 @@ public interface IMediaServerService { Boolean connectRtpServer(MediaServer mediaServerItem, String address, int port, String app, String stream); - void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); + byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName); MediaInfo getMediaInfo(MediaServer mediaServerItem, String app, String stream); diff --git a/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java index a2652d293..84fadd2df 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java @@ -634,13 +634,13 @@ public class MediaServerServiceImpl implements IMediaServerService { } @Override - public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { + public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType()); if (mediaNodeServerService == null) { log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType()); - return; + return null; } - mediaNodeServerService.getSnap(mediaServer, app, stream, timeoutSec, expireSec, path, fileName); + return mediaNodeServerService.getSnap(mediaServer, app, stream, timeoutSec, expireSec, path, fileName); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java index 5400b0d12..c4ac17e9b 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java @@ -217,14 +217,14 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { } @Override - public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { + public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) { String streamUrl; if (mediaServer.getRtspPort() != 0) { streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServer.getRtspPort(), app, stream); } else { streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServer.getHttpPort(), app, stream); } - zlmresTfulUtils.getSnap(mediaServer, streamUrl, timeoutSec, expireSec, path, fileName); + return zlmresTfulUtils.getSnap(mediaServer, streamUrl, timeoutSec, expireSec, path, fileName); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java index 8fd650d7f..fe09277c5 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -167,11 +167,11 @@ public class ZLMRESTfulUtils { return result; } - public void sendGetForImg(MediaServer mediaServer, String api, Map params, String targetPath, String fileName) { + public byte[] sendGetForImg(MediaServer mediaServer, String api, Map params, String targetPath, String fileName) { String url = String.format("http://%s:%s/index/api/%s", mediaServer.getIp(), mediaServer.getHttpPort(), api); HttpUrl parseUrl = HttpUrl.parse(url); if (parseUrl == null) { - return; + return null; } HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); @@ -188,6 +188,7 @@ public class ZLMRESTfulUtils { if (log.isDebugEnabled()){ log.debug(request.toString()); } + byte[] result = null; try { OkHttpClient client = getClient(); Response response = client.newCall(request).execute(); @@ -198,27 +199,24 @@ public class ZLMRESTfulUtils { if (!snapFolder.mkdirs()) { log.warn("{}路径创建失败", snapFolder.getAbsolutePath()); } - } File snapFile = new File(targetPath + File.separator + fileName); FileOutputStream outStream = new FileOutputStream(snapFile); - - outStream.write(Objects.requireNonNull(response.body()).bytes()); + result = Objects.requireNonNull(response.body()).bytes(); + outStream.write(result); outStream.flush(); outStream.close(); - } else { - log.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } } else { - log.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); + log.error("[ {} ]请求失败: {} {}", url, response.code(), response.message()); } - Objects.requireNonNull(response.body()).close(); } catch (ConnectException e) { - log.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + log.error("连接ZLM失败: {}, {}", e.getCause().getMessage(), e.getMessage()); log.info("请检查media配置并确认ZLM已启动..."); } catch (IOException e) { - log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + log.error("[ {} ]请求失败: {}", url, e.getMessage()); } + return result; } public ZLMResult isMediaOnline(MediaServer mediaServer, String app, String stream, String schema){ @@ -657,13 +655,13 @@ public class ZLMRESTfulUtils { sendPost(mediaServer, "kick_sessions",param, null); } - public void getSnap(MediaServer mediaServer, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) { + public byte[] getSnap(MediaServer mediaServer, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) { Map param = new HashMap<>(3); param.put("url", streamUrl); param.put("timeout_sec", timeout_sec); param.put("expire_sec", expire_sec); param.put("async", 1); - sendGetForImg(mediaServer, "getSnap", param, targetPath, fileName); + return sendGetForImg(mediaServer, "getSnap", param, targetPath, fileName); } public ZLMResult pauseRtpCheck(MediaServer mediaServer, String streamId) { diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/AlarmServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/AlarmServiceImpl.java index 4a275b98b..40b6b177c 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/AlarmServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/AlarmServiceImpl.java @@ -3,10 +3,13 @@ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarmNotify; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.event.alarm.DeviceAlarmEvent; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.service.IAlarmService; import com.genersoft.iot.vmp.service.bean.Alarm; import com.genersoft.iot.vmp.service.bean.AlarmType; @@ -18,16 +21,20 @@ import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +@Slf4j @Service @RequiredArgsConstructor public class AlarmServiceImpl implements IAlarmService { @@ -40,6 +47,10 @@ public class AlarmServiceImpl implements IAlarmService { private final IDeviceChannelService deviceChannelService; + private final IGbChannelPlayService gbChannelPlayService; + + private final IGbChannelService gbChannelService; + // 使用Caffeine缓存设备通道信息,避免频繁查询数据库,提升性能 private Cache channelCache = null; @@ -63,6 +74,7 @@ public class AlarmServiceImpl implements IAlarmService { if (event.getDeviceAlarmList().isEmpty()) { return; } + log.info("收到设备报警事件,数量:{}", event.getDeviceAlarmList().size()); for (DeviceAlarmNotify notify : event.getDeviceAlarmList()) { Alarm alarm = Alarm.buildFromDeviceAlarmNotify(notify); String key = notify.getDeviceId() + notify.getChannelId(); @@ -100,7 +112,31 @@ public class AlarmServiceImpl implements IAlarmService { List batchList = handlerCatchDataList.subList(i, end); alarmMapper.insertAlarms(batchList); } + // 按照通道ID分组,去补充快照文件 + handlerCatchDataList.forEach(this::getSnapByAlarm); + } + @Async + public void getSnapByAlarm(Alarm alarm) { + CommonGBChannel channel = gbChannelService.getOne(alarm.getChannelId()); + if (channel == null) { + log.warn("未找到报警关联的通道信息,alarmId:{},channelId:{}", alarm.getId(), alarm.getChannelId()); + return; + } + gbChannelPlayService.getSnap(channel, (code, msg, data) -> { + if (data == null) { + return; + } + File file = new File(alarm.getSnapPath()); + if(file.exists()) { + file.delete(); + } + try { + FileUtils.writeByteArrayToFile(file, data); + } catch (Exception e) { + log.warn("保存报警快照失败,alarmId:{},channelId:{}", alarm.getId(), alarm.getChannelId(), e); + } + }); } @Override @@ -132,7 +168,7 @@ public class AlarmServiceImpl implements IAlarmService { @Override public String getAlarmSnapById(Long id) { - return ""; + return alarmMapper.getSnapPathById(id); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/AlarmMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/AlarmMapper.java index eff0c47fa..23e183bf0 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/AlarmMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/AlarmMapper.java @@ -48,4 +48,7 @@ public interface AlarmMapper { "" + "") void insertAlarms(List handlerCatchDataList); + + @Select("SELECT snap_path FROM wvp_alarm WHERE id = #{id}") + String getSnapPathById(@Param("id") Long id); } diff --git a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/SourcePlayServiceForStreamProxyImpl.java b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/SourcePlayServiceForStreamProxyImpl.java index 984d565e4..7cca0e39a 100644 --- a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/SourcePlayServiceForStreamProxyImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/SourcePlayServiceForStreamProxyImpl.java @@ -39,4 +39,9 @@ public class SourcePlayServiceForStreamProxyImpl implements ISourcePlayService { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } + + @Override + public void getSnap(CommonGBChannel channel, ErrorCallback callback) { + + } } diff --git a/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java index 5197687e4..0303cec31 100644 --- a/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/SourcePlayServiceForStreamPushImpl.java @@ -50,4 +50,9 @@ public class SourcePlayServiceForStreamPushImpl implements ISourcePlayService { log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e); } } + + @Override + public void getSnap(CommonGBChannel channel, ErrorCallback callback) { + + } } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/alarm/AlarmController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/alarm/AlarmController.java index a817c56a7..038e1645d 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/alarm/AlarmController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/alarm/AlarmController.java @@ -9,10 +9,17 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; import java.util.List; @Tag(name = "报警管理接口") @@ -47,4 +54,28 @@ public class AlarmController { public void delete(@RequestBody List ids) { alarmService.deleteAlarmInfo(ids); } + + @GetMapping("/snap/{id}") + @Operation(summary = "获取报警快照图片") + @Parameter(name = "id", description = "报警ID", required = true) + public void snap(HttpServletResponse resp, @PathVariable Long id) { + String snapPath = alarmService.getAlarmSnapById(id); + if (snapPath == null || snapPath.isEmpty()) { + resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + return; + } + try { + File file = new File(snapPath); + if (!file.exists()) { + resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + return; + } + try (InputStream in = Files.newInputStream(file.toPath())) { + resp.setContentType(MediaType.IMAGE_JPEG_VALUE); + IOUtils.copy(in, resp.getOutputStream()); + } + } catch (IOException e) { + resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + } } diff --git a/src/main/resources/配置详情.yml b/src/main/resources/配置详情.yml index 03ca320c7..8ad5f7432 100644 --- a/src/main/resources/配置详情.yml +++ b/src/main/resources/配置详情.yml @@ -268,6 +268,8 @@ user-settings: # 处理报警消息时,会缓存通道数据,如果超出则丢弃低热度消息,被丢弃的通道下次使用就需要重新查询数据库,默认10000, # 建议根据实际情况调整,过大可能会占用较多内存,过小可能会增加数据库查询压力, alarm-catch-size: 10000 + # 是否使用拉流的方式获取快照,默认false,避免流量大规模消耗,开启后则使用拉流的方式获取快照 + snap-by-pull-stream: true # 关闭在线文档(生产环境建议关闭) springdoc: diff --git a/web/src/views/alarm/index.vue b/web/src/views/alarm/index.vue index 2995810f3..fd1c015d6 100644 --- a/web/src/views/alarm/index.vue +++ b/web/src/views/alarm/index.vue @@ -77,6 +77,23 @@ + + + @@ -87,8 +104,14 @@ {{ formatTime(scope.row.alarmTime) }} - +