diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java index 795301584..d85fb45ae 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java @@ -658,7 +658,7 @@ public interface CommonGBChannelMapper { void saveLevel(List channels); @SelectProvider(type = ChannelProvider.class, method = "queryCameraChannelByIds") - List queryCameraChannelByIds(List ids); + List queryCameraChannelByIds(List channelList); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java index fd7976407..8a555174e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java @@ -833,13 +833,13 @@ public class ChannelProvider { sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE); sqlBuild.append(" where wdc.id in ( "); - List ids = (List)params.get("ids"); + List channelList = (List)params.get("channelList"); boolean first = true; - for (Integer id : ids) { + for (CommonGBChannel channel : channelList) { if (!first) { sqlBuild.append(","); } - sqlBuild.append(id); + sqlBuild.append(channel.getGbId()); first = false; } sqlBuild.append(" )"); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java index 058ac5d89..e7fa2fe7d 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java @@ -74,7 +74,6 @@ public class EventPublisher { applicationEventPublisher.publishEvent(channelEvent); } - public void catalogEventPublish(Platform platform, CommonGBChannel deviceChannel, String type) { catalogEventPublish(platform, Collections.singletonList(deviceChannel), type); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/channel/ChannelEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/channel/ChannelEvent.java index a591b5d72..534c6ce8f 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/channel/ChannelEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/channel/ChannelEvent.java @@ -32,7 +32,7 @@ public class ChannelEvent extends ApplicationEvent { public enum ChannelEventMessageType { - ADD, UPDATE, DELETE, ONLINE, OFFLINE, VLOST, DEFECT + ADD, UPDATE, DEL, ON, OFF, VLOST, DEFECT } public static ChannelEvent getInstance(Object source, ChannelEventMessageType messageType, List channelList) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java index 69b6d350c..ca0c27dfd 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java @@ -78,7 +78,7 @@ public interface IDeviceChannelService { void changeAudio(Integer channelId, Boolean audio); - void updateChannelStatus(DeviceChannel channel); + void updateChannelStatusForNotify(DeviceChannel channel); void addChannel(DeviceChannel channel); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java index 07336cba6..268ffa940 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java @@ -613,7 +613,7 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { } @Override - public void updateChannelStatus(DeviceChannel channel) { + public void updateChannelStatusForNotify(DeviceChannel channel) { channelMapper.updateStatus(channel); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java index 33879da3c..b386a41ce 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java @@ -98,7 +98,7 @@ public class GbChannelServiceImpl implements IGbChannelService { commonGBChannelMapper.delete(gbId); try { // 发送通知 - eventPublisher.channelEventPublish(channel, ChannelEvent.ChannelEventMessageType.DELETE); + eventPublisher.channelEventPublish(channel, ChannelEvent.ChannelEventMessageType.DEL); } catch (Exception e) { log.warn("[通道移除通知] 发送失败,{}", channel.getGbDeviceId(), e); } @@ -150,21 +150,6 @@ public class GbChannelServiceImpl implements IGbChannelService { } catch (Exception e) { log.warn("[更新通道通知] 发送失败,{}", commonGBChannel.getGbDeviceId(), e); } - MobilePosition mobilePosition = new MobilePosition(); - mobilePosition.setLongitude(commonGBChannel.getGbLongitude()); - mobilePosition.setLatitude(commonGBChannel.getGbLatitude()); - mobilePosition.setCreateTime(DateUtil.getNow()); - mobilePosition.setDeviceId(commonGBChannel.getGbDeviceId()); - mobilePosition.setTime(DateUtil.getNow()); - mobilePosition.setAltitude(0.0); - mobilePosition.setDirection(0.0); - mobilePosition.setSpeed(0.0); - mobilePosition.setChannelId(commonGBChannel.getGbId()); - try { - eventPublisher.mobilePositionEventPublish(mobilePosition); - }catch (Exception e) { - log.error("[向上级转发移动位置失败] ", e); - } } return result; } @@ -179,7 +164,7 @@ public class GbChannelServiceImpl implements IGbChannelService { if (result > 0) { try { // 发送通知 - eventPublisher.channelEventPublish(commonGBChannel, ChannelEvent.ChannelEventMessageType.OFFLINE); + eventPublisher.channelEventPublish(commonGBChannel, ChannelEvent.ChannelEventMessageType.OFF); } catch (Exception e) { log.warn("[通道离线通知] 发送失败,{}", commonGBChannel.getGbDeviceId(), e); } @@ -211,7 +196,7 @@ public class GbChannelServiceImpl implements IGbChannelService { if (result > 0) { try { // 发送catalog - eventPublisher.channelEventPublish(commonGBChannelList, ChannelEvent.ChannelEventMessageType.OFFLINE); + eventPublisher.channelEventPublish(commonGBChannelList, ChannelEvent.ChannelEventMessageType.OFF); } catch (Exception e) { log.warn("[多个通道离线] 发送失败,数量:{}", commonGBChannelList.size(), e); } @@ -229,7 +214,7 @@ public class GbChannelServiceImpl implements IGbChannelService { if (result > 0) { try { // 发送通知 - eventPublisher.channelEventPublish(commonGBChannel, ChannelEvent.ChannelEventMessageType.ONLINE); + eventPublisher.channelEventPublish(commonGBChannel, ChannelEvent.ChannelEventMessageType.ON); } catch (Exception e) { log.warn("[通道上线通知] 发送失败,{}", commonGBChannel.getGbDeviceId(), e); } @@ -260,7 +245,7 @@ public class GbChannelServiceImpl implements IGbChannelService { } try { // 发送catalog - eventPublisher.channelEventPublish(commonGBChannelList, ChannelEvent.ChannelEventMessageType.ONLINE); + eventPublisher.channelEventPublish(commonGBChannelList, ChannelEvent.ChannelEventMessageType.ON); } catch (Exception e) { log.warn("[多个通道上线] 发送失败,数量:{}", commonGBChannelList.size(), e); } @@ -417,7 +402,7 @@ public class GbChannelServiceImpl implements IGbChannelService { // 发送通过更新通知 try { // 发送通知 - eventPublisher.catalogEventPublish(null, channelNew, CatalogEvent.UPDATE); + eventPublisher.channelEventPublishForUpdate(channelNew, channel); } catch (Exception e) { log.warn("[通道移除通知] 发送失败,{}", channelNew.getGbDeviceId(), e); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GroupServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GroupServiceImpl.java index e78b37fdd..7ced4a140 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GroupServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GroupServiceImpl.java @@ -148,7 +148,7 @@ public class GroupServiceImpl implements IGroupService, CommandLineRunner { CommonGBChannel channel = CommonGBChannel.build(chjildGroup); try { // 发送catalog - eventPublisher.catalogEventPublish(null, channel, CatalogEvent.UPDATE); + eventPublisher.channelEventPublishForUpdate(channel, null); }catch (Exception e) { log.warn("[业务分组/虚拟组织变化] 发送失败,{}", group.getDeviceId(), e); } @@ -160,7 +160,7 @@ public class GroupServiceImpl implements IGroupService, CommandLineRunner { CommonGBChannel channel = CommonGBChannel.build(group); try { // 发送catalog - eventPublisher.catalogEventPublish(null, channel, CatalogEvent.UPDATE); + eventPublisher.channelEventPublishForUpdate(channel, null); }catch (Exception e) { log.warn("[业务分组/虚拟组织变化] 发送失败,{}", group.getDeviceId(), e); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/RegionServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/RegionServiceImpl.java index abc2fd6bf..f2091e9cd 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/RegionServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/RegionServiceImpl.java @@ -8,7 +8,6 @@ import com.genersoft.iot.vmp.gb28181.bean.RegionTree; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.RegionMapper; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.service.IRegionService; import com.genersoft.iot.vmp.utils.CivilCodeUtil; @@ -125,7 +124,7 @@ public class RegionServiceImpl implements IRegionService { // 发送变化通知 try { // 发送catalog - eventPublisher.catalogEventPublish(null, CommonGBChannel.build(region), CatalogEvent.UPDATE); + eventPublisher.channelEventPublishForUpdate(CommonGBChannel.build(region), null); }catch (Exception e) { log.warn("[行政区划变化] 发送失败,{}", region.getDeviceId(), e); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java index ce61535d6..adccfbd9a 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java @@ -49,7 +49,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent @Autowired private IDeviceChannelService deviceChannelService; - + // @Scheduled(fixedRate = 2000) //每400毫秒执行一次 // public void showSize(){ // log.warn("[notify-目录订阅] 待处理消息数量: {}", taskQueue.size() ); @@ -282,7 +282,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent try { switch (notifyCatalogChannel.getType()) { case STATUS_CHANGED: - deviceChannelService.updateChannelStatus(notifyCatalogChannel.getChannel()); + deviceChannelService.updateChannelStatusForNotify(notifyCatalogChannel.getChannel()); break; case ADD: deviceChannelService.addChannel(notifyCatalogChannel.getChannel()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java index a8c106281..587063068 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java @@ -81,7 +81,8 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement // 查询设备是否存在 Device device = redisCatchStorage.getDevice(deviceId); // 查询上级平台是否存在 - Platform parentPlatform = platformService.queryPlatformByServerGBId(deviceId); +// Platform parentPlatform = platformService.queryPlatformByServerGBId(deviceId); + Platform parentPlatform = null; try { if (device != null && parentPlatform != null) { String hostAddress = request.getRemoteAddress().getHostAddress(); diff --git a/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java b/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java index 0b61b2c58..b6216e0cb 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java @@ -5,14 +5,20 @@ import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; +import com.genersoft.iot.vmp.service.ICloudRecordService; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.streamPush.service.IStreamPushPlayService; +import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl; import com.genersoft.iot.vmp.web.custom.bean.*; import com.genersoft.iot.vmp.web.custom.service.CameraChannelService; import com.github.pagehelper.PageInfo; @@ -21,6 +27,7 @@ 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.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -29,9 +36,15 @@ import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; @Tag(name = "第三方接口") @Slf4j @@ -52,6 +65,12 @@ public class CameraChannelController { @Autowired private IMediaServerService mediaServerService; + @Autowired + private ICloudRecordService cloudRecordService; + + @Autowired + private IStreamPushPlayService streamPushPlayService; + @GetMapping(value = "/camera/list") @ResponseBody @@ -330,5 +349,228 @@ public class CameraChannelController { return new StreamContent(streamInfo); } + @ResponseBody + @GetMapping("/record/collect/add") + @Operation(summary = "添加收藏") + @Parameter(name = "app", description = "应用名", required = false) + @Parameter(name = "stream", description = "流ID", required = false) + @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) + @Parameter(name = "startTime", description = "鉴权ID", required = false) + @Parameter(name = "endTime", description = "鉴权ID", required = false) + @Parameter(name = "callId", description = "鉴权ID", required = false) + @Parameter(name = "recordId", description = "录像记录的ID,用于精准收藏一个视频文件", required = false) + public int addCollect(@RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String callId, @RequestParam(required = false) Integer recordId) { + log.info("[云端录像] 添加收藏,app={},stream={},mediaServerId={},startTime={},endTime={},callId={},recordId={}", app, stream, mediaServerId, startTime, endTime, callId, recordId); + if (recordId != null) { + return cloudRecordService.changeCollectById(recordId, true); + } else { + return cloudRecordService.changeCollect(true, app, stream, mediaServerId, startTime, endTime, callId); + } + } + @ResponseBody + @GetMapping("/record/collect/delete") + @Operation(summary = "移除收藏") + @Parameter(name = "app", description = "应用名", required = false) + @Parameter(name = "stream", description = "流ID", required = false) + @Parameter(name = "mediaServerId", description = "流媒体ID", required = false) + @Parameter(name = "startTime", description = "鉴权ID", required = false) + @Parameter(name = "endTime", description = "鉴权ID", required = false) + @Parameter(name = "callId", description = "鉴权ID", required = false) + @Parameter(name = "recordId", description = "录像记录的ID,用于精准精准移除一个视频文件的收藏", required = false) + public int deleteCollect(@RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String callId, @RequestParam(required = false) Integer recordId) { + log.info("[云端录像] 移除收藏,app={},stream={},mediaServerId={},startTime={},endTime={},callId={},recordId={}", app, stream, mediaServerId, startTime, endTime, callId, recordId); + if (recordId != null) { + return cloudRecordService.changeCollectById(recordId, false); + } else { + return cloudRecordService.changeCollect(false, app, stream, mediaServerId, startTime, endTime, callId); + } + } + + /************************* 以下这些接口只适合wvp和zlm部署在同一台服务器的情况,且wvp只有一个zlm节点的情况 ***************************************/ + + /** + * 下载指定录像文件的压缩包 + * @param query 检索内容 + * @param app 应用名 + * @param stream 流ID + * @param startTime 开始时间(yyyy-MM-dd HH:mm:ss) + * @param endTime 结束时间(yyyy-MM-dd HH:mm:ss) + * @param mediaServerId 流媒体ID,置空则查询全部流媒体 + * @param callId 每次录像的唯一标识,置空则查询全部流媒体 + * @param ids 指定的Id + */ + @ResponseBody + @GetMapping("/record/zip") + public void downloadZipFile(HttpServletResponse response, @RequestParam(required = false) String query, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId, @RequestParam(required = false) List ids + + ) { + log.info("[下载指定录像文件的压缩包] 查询 app->{}, stream->{}, mediaServerId->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, startTime, endTime, callId); + + List mediaServers; + if (!org.apache.commons.lang3.ObjectUtils.isEmpty(mediaServerId)) { + mediaServers = new ArrayList<>(); + MediaServer mediaServer = mediaServerService.getOne(mediaServerId); + if (mediaServer == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); + } + mediaServers.add(mediaServer); + } else { + mediaServers = mediaServerService.getAll(); + } + if (mediaServers.isEmpty()) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "当前无流媒体"); + } + if (query != null && org.apache.commons.lang3.ObjectUtils.isEmpty(query.trim())) { + query = null; + } + if (app != null && org.apache.commons.lang3.ObjectUtils.isEmpty(app.trim())) { + app = null; + } + if (stream != null && org.apache.commons.lang3.ObjectUtils.isEmpty(stream.trim())) { + stream = null; + } + if (startTime != null && org.apache.commons.lang3.ObjectUtils.isEmpty(startTime.trim())) { + startTime = null; + } + if (endTime != null && org.apache.commons.lang3.ObjectUtils.isEmpty(endTime.trim())) { + endTime = null; + } + if (callId != null && org.apache.commons.lang3.ObjectUtils.isEmpty(callId.trim())) { + callId = null; + } + if (stream != null && callId != null) { + response.addHeader("Content-Disposition", "attachment;filename=" + stream + "_" + callId + ".zip"); + } + List cloudRecordItemList = cloudRecordService.getAllList(query, app, stream, startTime, endTime, mediaServers, callId, ids); + if (org.apache.commons.lang3.ObjectUtils.isEmpty(cloudRecordItemList)) { + return; + } + try { + ZipOutputStream zos = new ZipOutputStream(response.getOutputStream()); + for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { + zos.putNextEntry(new ZipEntry(DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime()) + ".mp4")); + File file = new File(cloudRecordItem.getFilePath()); + if (!file.exists() || file.isDirectory()) { + continue; + } + FileInputStream fis = new FileInputStream(cloudRecordItem.getFilePath()); + byte[] buf = new byte[2 * 1024]; + int len; + while ((len = fis.read(buf)) != -1) { + zos.write(buf, 0, len); + } + zos.closeEntry(); + fis.close(); + } + zos.close(); + } catch (IOException e) { + log.error("[下载指定录像文件的压缩包] 失败: 查询 app->{}, stream->{}, mediaServerId->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, startTime, endTime, callId, e); + } + } + + /** + * + * @param query 检索内容 + * @param app 应用名 + * @param stream 流ID + * @param startTime 开始时间(yyyy-MM-dd HH:mm:ss) + * @param endTime 结束时间(yyyy-MM-dd HH:mm:ss) + * @param mediaServerId 流媒体ID,置空则查询全部流媒体 + * @param callId 每次录像的唯一标识,置空则查询全部流媒体 + * @param remoteHost 拼接播放地址时使用的远程地址 + */ + @ResponseBody + @GetMapping("/record/list-url") + @Operation(summary = "分页查询云端录像", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "query", description = "检索内容", required = false) + @Parameter(name = "app", description = "应用名", required = false) + @Parameter(name = "stream", description = "流ID", required = false) + @Parameter(name = "page", description = "当前页", required = true) + @Parameter(name = "count", description = "每页查询数量", required = true) + @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false) + @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false) + @Parameter(name = "mediaServerId", description = "流媒体ID,置空则查询全部流媒体", required = false) + @Parameter(name = "callId", description = "每次录像的唯一标识,置空则查询全部流媒体", required = false) + public PageInfo getListWithUrl(HttpServletRequest request, @RequestParam(required = false) String query, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam int page, @RequestParam int count, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId, @RequestParam(required = false) String remoteHost + + ) { + log.info("[云端录像] 查询URL app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, page, count, startTime, endTime, callId); + + List mediaServers; + if (!org.apache.commons.lang3.ObjectUtils.isEmpty(mediaServerId)) { + mediaServers = new ArrayList<>(); + MediaServer mediaServer = mediaServerService.getOne(mediaServerId); + if (mediaServer == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); + } + mediaServers.add(mediaServer); + } else { + mediaServers = null; + } + if (query != null && org.apache.commons.lang3.ObjectUtils.isEmpty(query.trim())) { + query = null; + } + if (app != null && org.apache.commons.lang3.ObjectUtils.isEmpty(app.trim())) { + app = null; + } + if (stream != null && org.apache.commons.lang3.ObjectUtils.isEmpty(stream.trim())) { + stream = null; + } + if (startTime != null && org.apache.commons.lang3.ObjectUtils.isEmpty(startTime.trim())) { + startTime = null; + } + if (endTime != null && org.apache.commons.lang3.ObjectUtils.isEmpty(endTime.trim())) { + endTime = null; + } + if (callId != null && org.apache.commons.lang3.ObjectUtils.isEmpty(callId.trim())) { + callId = null; + } + MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); + if (mediaServer == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体节点"); + } + if (remoteHost == null) { + remoteHost = request.getScheme() + "://" + request.getLocalAddr() + ":" + (request.getScheme().equals("https") ? mediaServer.getHttpSSlPort() : mediaServer.getHttpPort()); + } + PageInfo cloudRecordItemPageInfo = cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId, null); + PageInfo cloudRecordUrlPageInfo = new PageInfo<>(); + if (!org.apache.commons.lang3.ObjectUtils.isEmpty(cloudRecordItemPageInfo)) { + cloudRecordUrlPageInfo.setPageNum(cloudRecordItemPageInfo.getPageNum()); + cloudRecordUrlPageInfo.setPageSize(cloudRecordItemPageInfo.getPageSize()); + cloudRecordUrlPageInfo.setSize(cloudRecordItemPageInfo.getSize()); + cloudRecordUrlPageInfo.setEndRow(cloudRecordItemPageInfo.getEndRow()); + cloudRecordUrlPageInfo.setStartRow(cloudRecordItemPageInfo.getStartRow()); + cloudRecordUrlPageInfo.setPages(cloudRecordItemPageInfo.getPages()); + cloudRecordUrlPageInfo.setPrePage(cloudRecordItemPageInfo.getPrePage()); + cloudRecordUrlPageInfo.setNextPage(cloudRecordItemPageInfo.getNextPage()); + cloudRecordUrlPageInfo.setIsFirstPage(cloudRecordItemPageInfo.isIsFirstPage()); + cloudRecordUrlPageInfo.setIsLastPage(cloudRecordItemPageInfo.isIsLastPage()); + cloudRecordUrlPageInfo.setHasPreviousPage(cloudRecordItemPageInfo.isHasPreviousPage()); + cloudRecordUrlPageInfo.setHasNextPage(cloudRecordItemPageInfo.isHasNextPage()); + cloudRecordUrlPageInfo.setNavigatePages(cloudRecordItemPageInfo.getNavigatePages()); + cloudRecordUrlPageInfo.setNavigateFirstPage(cloudRecordItemPageInfo.getNavigateFirstPage()); + cloudRecordUrlPageInfo.setNavigateLastPage(cloudRecordItemPageInfo.getNavigateLastPage()); + cloudRecordUrlPageInfo.setNavigatepageNums(cloudRecordItemPageInfo.getNavigatepageNums()); + cloudRecordUrlPageInfo.setTotal(cloudRecordItemPageInfo.getTotal()); + List cloudRecordUrlList = new ArrayList<>(cloudRecordItemPageInfo.getList().size()); + List cloudRecordItemList = cloudRecordItemPageInfo.getList(); + for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { + CloudRecordUrl cloudRecordUrl = new CloudRecordUrl(); + cloudRecordUrl.setId(cloudRecordItem.getId()); + cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath() + "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime())); + cloudRecordUrl.setPlayUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath()); + cloudRecordUrlList.add(cloudRecordUrl); + } + cloudRecordUrlPageInfo.setList(cloudRecordUrlList); + } + return cloudRecordUrlPageInfo; + } + + @GetMapping(value = "/forceClose") + @ResponseBody + @Operation(summary = "强制停止推流", security = @SecurityRequirement(name = JwtUtils.HEADER)) + public void stop(String app, String stream){ + streamPushPlayService.stop(app, stream); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/web/custom/service/CameraChannelService.java b/src/main/java/com/genersoft/iot/vmp/web/custom/service/CameraChannelService.java index 7a3b41dc1..3aa89501b 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/custom/service/CameraChannelService.java +++ b/src/main/java/com/genersoft/iot/vmp/web/custom/service/CameraChannelService.java @@ -11,7 +11,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Group; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; import com.genersoft.iot.vmp.gb28181.dao.GroupMapper; -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; +import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition.MobilePositionEvent; import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; @@ -113,49 +113,120 @@ public class CameraChannelService implements CommandLineRunner { // 监听通道变化,如果是移动设备则发送redis消息 @EventListener - public void onApplicationEvent(CatalogEvent event) { + public void onApplicationEvent(ChannelEvent event) { List channels = event.getChannels(); if (channels.isEmpty()) { return; } + List resultListForAdd = new ArrayList<>(); + List resultListForDelete = new ArrayList<>(); + List resultListForUpdate = new ArrayList<>(); + List resultListForOnline = new ArrayList<>(); + List resultListForOffline = new ArrayList<>(); - List mobilechannelList = null; - if (event.getType().equals(CatalogEvent.DEL)) { - mobilechannelList = new ArrayList<>(); - for (CommonGBChannel channel : channels) { - if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { - CameraChannel cameraChannel = new CameraChannel(); - cameraChannel.setGbDeviceId(channel.getGbDeviceId()); - mobilechannelList.add(cameraChannel); + switch (event.getMessageType()) { + case UPDATE: + List oldChannelList = event.getOldChannels(); + List channelList = event.getChannels(); + // 更新操作 + if (oldChannelList == null || oldChannelList.isEmpty()) { + // 无旧设备则不需要判断, 目前只有分组或行政区划转换为通道信息时没有旧的通道信息,这两个类型也是不需要发送通知的,直接忽略即可 + break; } - } - }else { - List ids = new ArrayList<>(); - channels.forEach((channel -> { - if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { - ids.add(channel.getGbId()); + // 需要比对旧数据,看看是否是新增的移动设备或者取消的移动设备 + // 将 channelList 转为以 gbDeviceId 为 key 的 Map + Map oldChannelMap = new HashMap<>(); + for (CommonGBChannel channel : oldChannelList) { + if (channel != null && channel.getGbDeviceId() != null) { + oldChannelMap.put(channel.getGbDeviceId(), channel); + } } - })); - if (ids.isEmpty()) { - return; - } - mobilechannelList = channelMapper.queryCameraChannelByIds(ids); + for (CommonGBChannel channel : channelList) { + if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { + CommonGBChannel oldChannel = oldChannelMap.get(channel.getGbDeviceId()); + if (oldChannel != null) { + if (oldChannel.getGbPtzType() != null && oldChannel.getGbPtzType() == 99) { + resultListForUpdate.add(channel); + }else { + resultListForAdd.add(channel); + } + }else { + resultListForAdd.add(channel); + } + }else { + CommonGBChannel oldChannel = oldChannelMap.get(channel.getGbDeviceId()); + if (oldChannel != null && oldChannel.getGbPtzType() != null && oldChannel.getGbPtzType() == 99) { + CameraChannel cameraChannel = new CameraChannel(); + cameraChannel.setGbDeviceId(channel.getGbDeviceId()); + resultListForDelete.add(cameraChannel); + } + } + } + + break; + case DEL: + for (CommonGBChannel channel : channels) { + if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { + CameraChannel cameraChannel = new CameraChannel(); + cameraChannel.setGbDeviceId(channel.getGbDeviceId()); + resultListForDelete.add(cameraChannel); + } + } + break; + case ON: + case OFF: + case DEFECT: + case VLOST: + for (CommonGBChannel channel : channels) { + if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { + if (event.getMessageType() == ChannelEvent.ChannelEventMessageType.ON) { + resultListForOnline.add(channel); + }else { + resultListForOffline.add(channel); + } + + } + } + break; + case ADD: + for (CommonGBChannel channel : channels) { + if (channel.getGbPtzType() != null && channel.getGbPtzType() == 99) { + resultListForAdd.add(channel); + } + } + break; } - if (mobilechannelList == null || mobilechannelList.isEmpty()) { + if (!resultListForDelete.isEmpty()) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("type", ChannelEvent.ChannelEventMessageType.DEL); + jsonObject.put("list", resultListForDelete); + log.info("[SY-redis发送通知-DEL] 发送 通道信息变化 {}: {}", REDIS_CHANNEL_MESSAGE, jsonObject.toString()); + redisTemplate.convertAndSend(REDIS_CHANNEL_MESSAGE, jsonObject); + } + if (!resultListForAdd.isEmpty()) { + sendChannelMessage(resultListForAdd, ChannelEvent.ChannelEventMessageType.ADD); + } + if (!resultListForUpdate.isEmpty()) { + sendChannelMessage(resultListForAdd, ChannelEvent.ChannelEventMessageType.UPDATE); + } + if (!resultListForOnline.isEmpty()) { + sendChannelMessage(resultListForAdd, ChannelEvent.ChannelEventMessageType.ON); + } + if (!resultListForOffline.isEmpty()) { + sendChannelMessage(resultListForAdd, ChannelEvent.ChannelEventMessageType.OFF); + } + } + + private void sendChannelMessage(List channelList, ChannelEvent.ChannelEventMessageType type) { + if (channelList.isEmpty()) { return; } - String type = event.getType(); - if (type.equals(CatalogEvent.VLOST) || type.equals(CatalogEvent.DEFECT)) { - type = CatalogEvent.OFF; - } - + List cameraChannelList = channelMapper.queryCameraChannelByIds(channelList); JSONObject jsonObject = new JSONObject(); jsonObject.put("type", type); - jsonObject.put("list", mobilechannelList); - log.info("[SY-redis发送通知] 发送 通道信息变化 {}: {}", REDIS_CHANNEL_MESSAGE, jsonObject.toString()); + jsonObject.put("list", cameraChannelList); + log.info("[SY-redis发送通知-{}] 发送 通道信息变化 {}: {}", type, REDIS_CHANNEL_MESSAGE, jsonObject.toString()); redisTemplate.convertAndSend(REDIS_CHANNEL_MESSAGE, jsonObject); - - } // 监听GPS消息,如果是移动设备则发送redis消息 diff --git a/web/src/views/channel/group/index.vue b/web/src/views/channel/group/index.vue index 1484b6031..a61503a98 100755 --- a/web/src/views/channel/group/index.vue +++ b/web/src/views/channel/group/index.vue @@ -10,7 +10,7 @@ :add-channel-to-group="addChannelToGroup" />
- + {{ key }} @@ -74,7 +74,7 @@ ref="channelListTable" size="medium" :data="channelList" - height="calc(100vh - 190px)" + :height="tableHeight" style="width: 100%" header-row-class-name="table-header" @selection-change="handleSelectionChange" @@ -132,6 +132,7 @@ export default { data() { return { channelList: [], + tableHeight: `calc(100vh - ${this.$refs.queryForm.offsetHeight}px)`, searchStr: '', channelType: '', online: '',