diff --git a/README.md b/README.md index 6f2621deb..1116581f7 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,16 @@ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/xia-chu/ZLMediaKit/pulls) -WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将不带国标功能的摄像机/直播流/直播推流转发到其他国标平台。 +WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将不带国标功能的摄像机/直播流/直播推流转发到其他国标平台。 流媒体服务基于@夏楚 ZLMediaKit [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) 播放器使用@dexter jessibuca [https://github.com/langhuihui/jessibuca/tree/v3](https://github.com/langhuihui/jessibuca/tree/v3) -前端页面基于@Kyle MediaServerUI [https://gitee.com/kkkkk5G/MediaServerUI](https://gitee.com/kkkkk5G/MediaServerUI) 进行修改. +前端页面基于@Kyle MediaServerUI [https://gitee.com/kkkkk5G/MediaServerUI](https://gitee.com/kkkkk5G/MediaServerUI) 进行修改. # 应用场景: 支持浏览器无插件播放摄像头视频。 支持国标设备(摄像机、平台、NVR等)设备接入 -支持非国标(onvif, rtsp, rtmp,直播设备等等)设备接入,充分利旧。 +支持非国标(onvif, rtsp, rtmp,直播设备等等)设备接入,充分利旧。 支持国标级联。多平台级联。跨网视频预览。 支持跨网网闸平台互联。 @@ -43,10 +43,9 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git ![build_1](https://images.gitee.com/uploads/images/2022/0304/101919_ee5b8c79_1018729.png "2022-03-04_10-13.png") ![运维中心](doc/_media/log.jpg "log.jpg") -# 功能特性 +# 功能特性 - [X] 集成web界面 - [X] 兼容性良好 -- [X] 支持电子地图,支持接入WGS84和GCJ02两种坐标系,并且自动转化为合适的坐标系进行展示和分发 - [X] 接入设备 - [X] 视频预览 - [X] 支持主码流子码流切换 @@ -68,6 +67,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 支持播放H264和H265 - [X] 报警信息处理,支持向前端推送报警信息 - [X] 语音对讲 + - [X] 支持业务分组和行政区划树自定义展示以及级联推送 - [X] 支持订阅与通知方法 - [X] 移动位置订阅 - [X] 移动位置通知处理 @@ -84,6 +84,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 注册 - [X] 心跳保活 - [X] 通道选择 + - [X] 支持通道编号自定义, 支持每个平台使用不同的通道编号 - [X] 通道推送 - [X] 点播 - [X] 云台控制 @@ -95,10 +96,11 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 录像查看与播放 - [X] GPS订阅与通知(直播推流) - [X] 语音对讲 -- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; + - [X] 支持同时级联到多个上级平台 +- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; - [X] 多流媒体节点,自动选择负载最低的节点使用。 - [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能; -- [X] 支持公网部署; +- [X] 支持公网部署; - [X] 支持wvp与zlm分开部署,提升平台并发能力 - [X] 支持拉流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台 - [X] 支持推流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台 @@ -108,8 +110,9 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 支持打包可执行jar和war - [X] 支持跨域请求,支持前后端分离部署 - [X] 支持Mysql,Postgresql,金仓等数据库 -- [X] 支持Onvif(目前在onvif分支,需要安装onvif服务,服务请在知识星球获取) - +- [X] 支持录制计划, 根据设定的时间对通道进行录制. 暂不支持将录制的内容转发到国标上级 +- [X] 支持Onvif, 目前付费提供, 永久免费试用包在知识星球获取 +- [X] 支持国标28181-2022协议, 目前付费提供, 永久免费试用包在知识星球获取 # 非开源的内容 @@ -120,7 +123,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git # 授权协议 本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议 -# 技术支持 +# 技术支持 [知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:, - [使用入门系列一:WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp) @@ -132,7 +135,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git 感谢作者[dexter langhuihui](https://github.com/langhuihui) 开源这么好用的WEB播放器。 感谢作者[Kyle](https://gitee.com/kkkkk5G) 开源了好用的前端页面 感谢各位大佬的赞助以及对项目的指正与帮助。包括但不限于代码贡献、问题反馈、资金捐赠等各种方式的支持!以下排名不分先后: -[lawrencehj](https://github.com/lawrencehj) [Smallwhitepig](https://github.com/Smallwhitepig) [swwhaha](https://github.com/swwheihei) +[lawrencehj](https://github.com/lawrencehj) [Smallwhitepig](https://github.com/Smallwhitepig) [swwhaha](https://github.com/swwheihei) [hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen) [chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb) [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666) diff --git a/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java b/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java index 878e01c5d..28e68fb3d 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java @@ -15,6 +15,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; /** @@ -54,7 +55,7 @@ public class CivilCodeFileConf implements CommandLineRunner { inputStream = Files.newInputStream(civilCodeFile.toPath()); } - BufferedReader inputStreamReader = new BufferedReader(new InputStreamReader(inputStream)); + BufferedReader inputStreamReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); int index = -1; String line; while ((line = inputStreamReader.readLine()) != null) { diff --git a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java index a56d4090b..a020fcb94 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java @@ -34,6 +34,7 @@ public class DynamicTask { threadPoolTaskScheduler.setPoolSize(300); threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true); threadPoolTaskScheduler.setAwaitTerminationSeconds(10); + threadPoolTaskScheduler.setThreadNamePrefix("dynamicTask-"); threadPoolTaskScheduler.initialize(); } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java index 432fafbb0..df88bcf3a 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java @@ -1,13 +1,18 @@ package com.genersoft.iot.vmp.conf; import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; +import static com.genersoft.iot.vmp.conf.ThreadPoolTaskConfig.cpuNum; + /** * "@Scheduled"是Spring框架提供的一种定时任务执行机制,默认情况下它是单线程的,在同时执行多个定时任务时可能会出现阻塞和性能问题。 * 为了解决这种单线程瓶颈问题,可以将定时任务的执行机制改为支持多线程 @@ -15,16 +20,21 @@ import java.util.concurrent.ThreadPoolExecutor; @Configuration public class ScheduleConfig implements SchedulingConfigurer { - public static final int cpuNum = Runtime.getRuntime().availableProcessors(); + /** + * 核心线程数(默认线程数) + */ + private static final int corePoolSize = Math.max(cpuNum, 20); - private static final int corePoolSize = cpuNum; - - private static final String threadNamePrefix = "scheduled-task-pool-%d"; + /** + * 线程池名前缀 + */ + private static final String threadNamePrefix = "schedule"; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { - taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(corePoolSize, + ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern(threadNamePrefix).daemon(true).build(), - new ThreadPoolExecutor.CallerRunsPolicy())); + new ThreadPoolExecutor.CallerRunsPolicy()); + taskRegistrar.setScheduler(scheduledThreadPoolExecutor); } } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java index c7532e38e..a549a053a 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java @@ -28,11 +28,11 @@ public class ThreadPoolTaskConfig { /** * 核心线程数(默认线程数) */ - private static final int corePoolSize = cpuNum; + private static final int corePoolSize = Math.max(cpuNum * 2, 16); /** * 最大线程数 */ - private static final int maxPoolSize = cpuNum*2; + private static final int maxPoolSize = corePoolSize * 10; /** * 允许线程空闲时间(单位:默认为秒) */ @@ -45,12 +45,9 @@ public class ThreadPoolTaskConfig { /** * 线程池名前缀 */ - private static final String threadNamePrefix = "wvp-"; + private static final String threadNamePrefix = "async-"; + - /** - * - * @return - */ @Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名 public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java index b1eb792ad..c2457dc45 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java @@ -46,7 +46,7 @@ public class JwtUtils implements InitializingBean { /** * token过期时间(分钟) */ - public static final long EXPIRATION_TIME = 30 * 24 * 60; + public static final long EXPIRATION_TIME = 30; private static RsaJsonWebKey rsaJsonWebKey; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java index 2c37a1de5..fb687b355 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java @@ -1,14 +1,10 @@ package com.genersoft.iot.vmp.gb28181.bean; import lombok.Data; -import org.jetbrains.annotations.NotNull; import java.time.Instant; import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.concurrent.Delayed; -import java.util.concurrent.TimeUnit; /** * @author lin @@ -19,11 +15,12 @@ public class CatalogData { * 命令序列号 */ private int sn; - private int total; + private Integer total; private Instant time; private Device device; private String errorMsg; private Set redisKeysForChannel = new HashSet<>(); + private Set errorChannel = new HashSet<>(); private Set redisKeysForRegion = new HashSet<>(); private Set redisKeysForGroup = new HashSet<>(); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java index 6327688b2..ea6066920 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CommonGBChannel.java @@ -126,6 +126,9 @@ public class CommonGBChannel { @Schema(description = "关联的国标设备数据库ID") private Integer gbDeviceDbId; + @Schema(description = "二进制保存的录制计划, 每一位表示每个小时的前半个小时") + private Long recordPLan; + @Schema(description = "关联的推流Id(流来源是推流时有效)") private Integer streamPushId; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpInfo.java index 988c2d441..0fc612ac5 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpInfo.java @@ -175,15 +175,18 @@ public class SendRtpInfo { return sendRtpItem; } - public static SendRtpInfo getInstance(Integer localPort, MediaServer mediaServer, String ip, int port, String ssrc, - String deviceId, String platformId, Integer channelId, boolean isTcp, boolean rtcp, + public static SendRtpInfo getInstance(Integer localPort, MediaServer mediaServer, String ip, Integer port, String ssrc, + String deviceId, String platformId, Integer channelId, Boolean isTcp, Boolean rtcp, String serverId) { if (localPort == 0) { return null; } SendRtpInfo sendRtpItem = new SendRtpInfo(); sendRtpItem.setIp(ip); - sendRtpItem.setPort(port); + if(port != null) { + sendRtpItem.setPort(port); + } + sendRtpItem.setSsrc(ssrc); if (deviceId != null) { sendRtpItem.setTargetId(deviceId); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java index 373b971cc..074a7a126 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java @@ -1,51 +1,31 @@ package com.genersoft.iot.vmp.gb28181.bean; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.Instant; /** * 摄像机同步状态 * @author lin */ +@Data @Schema(description = "摄像机同步状态") public class SyncStatus { + @Schema(description = "总数") - private int total; + private Integer total; + @Schema(description = "当前更新多少") - private int current; + private Integer current; + @Schema(description = "错误描述") private String errorMsg; + @Schema(description = "是否同步中") - private boolean syncIng; + private Boolean syncIng; - public int getTotal() { - return total; - } + @Schema(description = "时间") + private Instant time; - public void setTotal(int total) { - this.total = total; - } - - public int getCurrent() { - return current; - } - - public void setCurrent(int current) { - this.current = current; - } - - public String getErrorMsg() { - return errorMsg; - } - - public void setErrorMsg(String errorMsg) { - this.errorMsg = errorMsg; - } - - public boolean isSyncIng() { - return syncIng; - } - - public void setSyncIng(boolean syncIng) { - this.syncIng = syncIng; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java index 6f5295daa..3db52a437 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java @@ -101,11 +101,31 @@ public class CommonChannelController { return channel; } + @Operation(summary = "获取通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "page", description = "当前页", required = true) + @Parameter(name = "count", description = "每页查询数量", required = true) + @Parameter(name = "query", description = "查询内容") + @Parameter(name = "online", description = "是否在线") + @Parameter(name = "hasRecordPlan", description = "是否已设置录制计划") + @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") + @GetMapping("/list") + public PageInfo queryList(int page, int count, + @RequestParam(required = false) String query, + @RequestParam(required = false) Boolean online, + @RequestParam(required = false) Boolean hasRecordPlan, + @RequestParam(required = false) Integer channelType){ + if (ObjectUtils.isEmpty(query)){ + query = null; + } + return channelService.queryList(page, count, query, online, hasRecordPlan, channelType); + } + @Operation(summary = "获取关联行政区划通道列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "page", description = "当前页", required = true) @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") + @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "civilCode", description = "行政区划") @GetMapping("/civilcode/list") public PageInfo queryListByCivilCode(int page, int count, @@ -124,6 +144,7 @@ public class CommonChannelController { @Parameter(name = "count", description = "每页查询数量", required = true) @Parameter(name = "query", description = "查询内容") @Parameter(name = "online", description = "是否在线") + @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") @Parameter(name = "groupDeviceId", description = "业务分组下的父节点ID") @GetMapping("/parent/list") public PageInfo queryListByParentId(int page, int count, @@ -240,21 +261,7 @@ public class CommonChannelController { result.setResult(WVPResult.fail(code, msg)); } }; - - if (channel.getGbDeviceDbId() != null) { - // 国标通道 - channelPlayService.playGbDeviceChannel(channel, callback); - } else if (channel.getStreamProxyId() != null) { - // 拉流代理 - channelPlayService.playProxy(channel, callback); - } else if (channel.getStreamPushId() != null) { - // 推流 - channelPlayService.playPush(channel, null, null, callback); - } else { - // 通道数据异常 - log.error("[点播通用通道] 通道数据异常,无法识别通道来源: {}({})", channel.getGbName(), channel.getGbDeviceId()); - throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error"); - } + channelPlayService.play(channel, null, callback); return result; } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java index f6febd83a..5e9ee17e7 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java @@ -144,9 +144,21 @@ public class DeviceQuery { Device device = deviceService.getDeviceByDeviceId(deviceId); boolean status = deviceService.isSyncRunning(deviceId); // 已存在则返回进度 - if (status) { + if (deviceService.isSyncRunning(deviceId)) { SyncStatus channelSyncStatus = deviceService.getChannelSyncStatus(deviceId); - return WVPResult.success(channelSyncStatus); + WVPResult wvpResult = new WVPResult(); + if (channelSyncStatus.getErrorMsg() != null) { + wvpResult.setCode(ErrorCode.ERROR100.getCode()); + wvpResult.setMsg(channelSyncStatus.getErrorMsg()); + }else if (channelSyncStatus.getTotal() == null || channelSyncStatus.getTotal() == 0){ + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); + wvpResult.setMsg("等待通道信息..."); + }else { + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); + wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); + wvpResult.setData(channelSyncStatus); + } + return wvpResult; } deviceService.sync(device); @@ -257,7 +269,7 @@ public class DeviceQuery { @Operation(summary = "修改数据流传输模式", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "streamMode", description = "数据流传输模式, 取值:" + - "UDP(udp传输),TCP-ACTIVE(tcp主动模式,暂不支持),TCP-PASSIVE(tcp被动模式)", required = true) + "UDP(udp传输),TCP-ACTIVE(tcp主动模式),TCP-PASSIVE(tcp被动模式)", required = true) @PostMapping("/transport/{deviceId}/{streamMode}") public void updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){ Device device = deviceService.getDeviceByDeviceId(deviceId); @@ -414,15 +426,18 @@ public class DeviceQuery { SyncStatus channelSyncStatus = deviceService.getChannelSyncStatus(deviceId); WVPResult wvpResult = new WVPResult<>(); if (channelSyncStatus == null) { - wvpResult.setCode(-1); - wvpResult.setMsg("同步尚未开始"); + wvpResult.setCode(ErrorCode.ERROR100.getCode()); + wvpResult.setMsg("同步不存在"); + }else if (channelSyncStatus.getErrorMsg() != null) { + wvpResult.setCode(ErrorCode.ERROR100.getCode()); + wvpResult.setMsg(channelSyncStatus.getErrorMsg()); + }else if (channelSyncStatus.getTotal() == null || channelSyncStatus.getTotal() == 0){ + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); + wvpResult.setMsg("等待通道信息..."); }else { wvpResult.setCode(ErrorCode.SUCCESS.getCode()); wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); wvpResult.setData(channelSyncStatus); - if (channelSyncStatus.getErrorMsg() != null) { - wvpResult.setMsg(channelSyncStatus.getErrorMsg()); - } } return wvpResult; } 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 664329e48..64f7dad85 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 @@ -457,4 +457,97 @@ public interface CommonGBChannelMapper { " "}) void updateGpsByDeviceIdForStreamPush(List channels); + @SelectProvider(type = ChannelProvider.class, method = "queryList") + List queryList(@Param("query") String query, @Param("online") Boolean online, @Param("hasRecordPlan") Boolean hasRecordPlan, @Param("channelType") Integer channelType); + + @Update(value = {" "}) + void removeRecordPlan(List channelIds); + + @Update(value = {" "}) + void addRecordPlan(List channelIds, @Param("planId") Integer planId); + + @Update(value = {" "}) + void addRecordPlanForAll(@Param("planId") Integer planId); + + @Update(value = {" "}) + void removeRecordPlanByPlanId( @Param("planId") Integer planId); + + + @Select("") + List queryForRecordPlanForWebList(@Param("planId") Integer planId, @Param("query") String query, + @Param("channelType") Integer channelType, @Param("online") Boolean online, + @Param("hasLink") Boolean hasLink); + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java index a52632354..068eb4b79 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceChannelMapper.java @@ -93,6 +93,12 @@ public interface DeviceChannelMapper { @SelectProvider(type = DeviceChannelProvider.class, method = "queryChannelsByDeviceDbId") List queryChannelsByDeviceDbId(@Param("deviceDbId") int deviceDbId); + @Select(value = {" "}) + List queryChaneIdListByDeviceDbIds(List deviceDbIds); + @Delete("DELETE FROM wvp_device_channel WHERE device_db_id=#{deviceId}") int cleanChannelsByDeviceId(@Param("deviceId") int deviceId); @@ -407,6 +413,10 @@ public interface DeviceChannelMapper { "") void updateChannelStreamIdentification(DeviceChannel channel); + @Update("") + void updateAllChannelStreamIdentification(@Param("streamIdentification") String streamIdentification); @Update({"") List getList(@Param("query") String query, @Param("app") String app, @Param("stream") String stream, @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp, diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/RecordPlanMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/RecordPlanMapper.java new file mode 100644 index 000000000..ae0649aa2 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/RecordPlanMapper.java @@ -0,0 +1,67 @@ +package com.genersoft.iot.vmp.storager.dao; + +import com.genersoft.iot.vmp.service.bean.RecordPlan; +import com.genersoft.iot.vmp.service.bean.RecordPlanItem; +import org.apache.ibatis.annotations.*; + +import java.util.List; + +@Mapper +public interface RecordPlanMapper { + + @Insert(" ") + @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") + void add(RecordPlan plan); + + @Insert(" ") + void batchAddItem(@Param("planId") int planId, List planItemList); + + @Select("select * from wvp_record_plan where id = #{planId}") + RecordPlan get(@Param("planId") Integer planId); + + @Select(" ") + List query(@Param("query") String query); + + @Update("UPDATE wvp_record_plan SET update_time=#{updateTime}, name=#{name}, snap=#{snap} WHERE id=#{id}") + void update(RecordPlan plan); + + @Delete("DELETE FROM wvp_record_plan WHERE id=#{planId}") + void delete(@Param("planId") Integer planId); + + @Select("select * from wvp_record_plan_item where plan_id = #{planId}") + List getItemList(@Param("planId") Integer planId); + + @Delete("DELETE FROM wvp_record_plan_item WHERE plan_id = #{planId}") + void cleanItems(@Param("planId") Integer planId); + + @Select(" ") + List queryRecordIng(@Param("week") int week, @Param("index") int index); +} diff --git a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java index 88f9454a9..cf1818d9a 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyServiceImpl.java @@ -230,16 +230,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService { gbChannelService.add(streamProxy.buildCommonGBChannel()); } } - // 判断是否需要重启代理 - if (!streamProxyInDb.getApp().equals(streamProxy.getApp()) - || !streamProxyInDb.getStream().equals(streamProxy.getStream()) - || (streamProxyInDb.getMediaServerId() != null && streamProxyInDb.getMediaServerId().equals(streamProxy.getMediaServerId())) - || (streamProxyInDb.getMediaServerId() == null && streamProxy.getMediaServerId() != null) - ) { - // 变化则重启代理 - playService.stopProxy(streamProxyInDb); - playService.startProxy(streamProxy); - } return true; } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/RecordPlanController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/RecordPlanController.java new file mode 100644 index 000000000..f01c11e5f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/RecordPlanController.java @@ -0,0 +1,150 @@ +package com.genersoft.iot.vmp.vmanager.recordPlan; + +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.gb28181.service.IDeviceChannelService; +import com.genersoft.iot.vmp.service.IRecordPlanService; +import com.genersoft.iot.vmp.service.bean.RecordPlan; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.recordPlan.bean.RecordPlanParam; +import com.github.pagehelper.PageInfo; +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 lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +@Tag(name = "录制计划") +@Slf4j +@RestController +@RequestMapping("/api/record/plan") +public class RecordPlanController { + + @Autowired + private IRecordPlanService recordPlanService; + + @Autowired + private IDeviceChannelService deviceChannelService; + + + @ResponseBody + @PostMapping("/add") + @Operation(summary = "添加录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "plan", description = "计划", required = true) + public void add(@RequestBody RecordPlan plan) { + if (plan.getPlanItemList() == null || plan.getPlanItemList().isEmpty()) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "添加录制计划时,录制计划不可为空"); + } + recordPlanService.add(plan); + } + + @ResponseBody + @PostMapping("/link") + @Operation(summary = "通道关联录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "param", description = "通道关联录制计划", required = true) + public void link(@RequestBody RecordPlanParam param) { + if (param.getAllLink() != null) { + if (param.getAllLink()) { + recordPlanService.linkAll(param.getPlanId()); + }else { + recordPlanService.cleanAll(param.getPlanId()); + } + return; + } + + if (param.getChannelIds() == null && param.getDeviceDbIds() == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道ID和国标设备ID不可都为NULL"); + } + + List channelIds = new ArrayList<>(); + if (param.getChannelIds() != null) { + channelIds.addAll(param.getChannelIds()); + }else { + List chanelIdList = deviceChannelService.queryChaneIdListByDeviceDbIds(param.getDeviceDbIds()); + if (chanelIdList != null && !chanelIdList.isEmpty()) { + channelIds = chanelIdList; + } + } + recordPlanService.link(channelIds, param.getPlanId()); + } + + @ResponseBody + @GetMapping("/get") + @Operation(summary = "查询录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "planId", description = "计划ID", required = true) + public RecordPlan get(Integer planId) { + if (planId == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "计划ID不可为NULL"); + } + return recordPlanService.get(planId); + } + + @ResponseBody + @GetMapping("/query") + @Operation(summary = "查询录制计划列表", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "query", description = "检索内容", required = false) + @Parameter(name = "page", description = "当前页", required = true) + @Parameter(name = "count", description = "每页查询数量", required = true) + public PageInfo query(@RequestParam(required = false) String query, @RequestParam Integer page, @RequestParam Integer count) { + if (query != null && ObjectUtils.isEmpty(query.trim())) { + query = null; + } + return recordPlanService.query(page, count, query); + } + + @Operation(summary = "分页查询级联平台的所有所有通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "page", description = "当前页", required = true) + @Parameter(name = "count", description = "每页条数", required = true) + @Parameter(name = "planId", description = "录制计划ID") + @Parameter(name = "channelType", description = "通道类型, 0:国标设备,1:推流设备,2:拉流代理") + @Parameter(name = "query", description = "查询内容") + @Parameter(name = "online", description = "是否在线") + @Parameter(name = "hasLink", description = "是否已经关联") + @GetMapping("/channel/list") + @ResponseBody + public PageInfo queryChannelList(int page, int count, + @RequestParam(required = false) Integer planId, + @RequestParam(required = false) String query, + @RequestParam(required = false) Integer channelType, + @RequestParam(required = false) Boolean online, + @RequestParam(required = false) Boolean hasLink) { + + Assert.notNull(planId, "录制计划ID不可为NULL"); + if (org.springframework.util.ObjectUtils.isEmpty(query)) { + query = null; + } + + return recordPlanService.queryChannelList(page, count, query, channelType, online, planId, hasLink); + } + + @ResponseBody + @PostMapping("/update") + @Operation(summary = "更新录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "plan", description = "计划", required = true) + public void update(@RequestBody RecordPlan plan) { + if (plan == null || plan.getId() == 0) { + throw new ControllerException(ErrorCode.ERROR400); + } + recordPlanService.update(plan); + } + + @ResponseBody + @DeleteMapping("/delete") + @Operation(summary = "删除录制计划", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "planId", description = "计划ID", required = true) + public void delete(Integer planId) { + if (planId == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "计划IDID不可为NULL"); + } + recordPlanService.delete(planId); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/bean/RecordPlanParam.java b/src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/bean/RecordPlanParam.java new file mode 100644 index 000000000..9f56a0fbb --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/bean/RecordPlanParam.java @@ -0,0 +1,23 @@ +package com.genersoft.iot.vmp.vmanager.recordPlan.bean; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "录制计划-添加/编辑参数") +public class RecordPlanParam { + + @Schema(description = "关联的通道ID") + private List channelIds; + + @Schema(description = "关联的设备ID,会为设备下的所有通道关联此录制计划,channelId存在是此项不生效,") + private List deviceDbIds; + + @Schema(description = "全部关联/全部取消关联") + private Boolean allLink; + + @Schema(description = "录制计划ID, ID为空是删除关联的计划") + private Integer planId; +} diff --git a/src/main/resources/index.html b/src/main/resources/index.html new file mode 100644 index 000000000..9d2fdca2a --- /dev/null +++ b/src/main/resources/index.html @@ -0,0 +1,10 @@ + + + + + Title + + +111 + + \ No newline at end of file diff --git a/web_src/package.json b/web_src/package.json index c314b9ba8..78582b871 100644 --- a/web_src/package.json +++ b/web_src/package.json @@ -15,6 +15,7 @@ "@liveqing/liveplayer": "^2.7.10", "@wchbrad/vue-easy-tree": "^1.0.12", "axios": "^0.24.0", + "byte-weektime-picker": "^1.1.1", "core-js": "^2.6.5", "echarts": "^4.9.0", "element-ui": "^2.15.14", diff --git a/web_src/src/components/CloudRecord.vue b/web_src/src/components/CloudRecord.vue index d9f512e42..def4c2631 100755 --- a/web_src/src/components/CloudRecord.vue +++ b/web_src/src/components/CloudRecord.vue @@ -7,26 +7,28 @@
搜索: - 开始时间: 结束时间: 节点选择: - + @click="initData()">
@@ -146,14 +148,13 @@ export default { computed: {}, mounted() { this.initData(); + this.getMediaServerList(); }, destroyed() { this.$destroy('recordVideoPlayer'); }, methods: { initData: function () { - // 获取媒体节点列表 - this.getMediaServerList(); this.getRecordList(); }, currentChange: function (val) { diff --git a/web_src/src/components/DeviceList.vue b/web_src/src/components/DeviceList.vue index c34c1e7bb..7525def58 100755 --- a/web_src/src/components/DeviceList.vue +++ b/web_src/src/components/DeviceList.vue @@ -246,6 +246,15 @@ export default { type: 'error' }); } else { + if (res.data.data && res.data.data.errorMsg) { + that.$message({ + showClose: true, + message: res.data.data.errorMsg, + type: 'error' + }); + return; + } + this.$refs.syncChannelProgress.openDialog(itemData.deviceId, ()=>{ console.log(32322) this.initData() diff --git a/web_src/src/components/RecordPLan.vue b/web_src/src/components/RecordPLan.vue new file mode 100755 index 000000000..4e649fa55 --- /dev/null +++ b/web_src/src/components/RecordPLan.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/web_src/src/components/common/GroupTree.vue b/web_src/src/components/common/GroupTree.vue index aded33161..9e0993552 100755 --- a/web_src/src/components/common/GroupTree.vue +++ b/web_src/src/components/common/GroupTree.vue @@ -14,6 +14,7 @@
+ { if (res.data.code === 0) { + if (res.data.data.length > 0) { + this.showAlert = false + } resolve(res.data.data); } diff --git a/web_src/src/components/common/RegionTree.vue b/web_src/src/components/common/RegionTree.vue index 6dd38fb3a..f45bc8ba9 100755 --- a/web_src/src/components/common/RegionTree.vue +++ b/web_src/src/components/common/RegionTree.vue @@ -12,7 +12,8 @@
-
+
+ { if (res.data.code === 0) { + if (res.data.data.length > 0) { + this.showAlert = false + } resolve(res.data.data); } diff --git a/web_src/src/components/dialog/GbChannelSelect.vue b/web_src/src/components/dialog/GbChannelSelect.vue index 62a09066f..258e313ec 100644 --- a/web_src/src/components/dialog/GbChannelSelect.vue +++ b/web_src/src/components/dialog/GbChannelSelect.vue @@ -64,16 +64,22 @@ - - +
+
+ 未找到通道,可在国标设备/通道中选择编辑按钮, 选择{{dataType === 'civilCode'?'行政区划':'父节点编码'}} +
+ + +
+
diff --git a/web_src/src/components/dialog/SyncChannelProgress.vue b/web_src/src/components/dialog/SyncChannelProgress.vue index b623d249f..f94d9743a 100755 --- a/web_src/src/components/dialog/SyncChannelProgress.vue +++ b/web_src/src/components/dialog/SyncChannelProgress.vue @@ -60,16 +60,14 @@ export default { url:`/api/device/query/${this.deviceId}/sync_status/`, }).then((res) => { if (res.data.code === 0) { - if (!this.syncFlag) { - this.syncFlag = true; - } if (res.data.data != null) { if (res.data.data.syncIng) { - if (res.data.data.total == 0) { + if (res.data.data.total === 0) { this.msg = `等待同步中`; this.timmer = setTimeout(this.getProgress, 300) }else { + this.syncFlag = true; this.total = res.data.data.total; this.current = res.data.data.current; this.percentage = Math.floor(Number(res.data.data.current)/Number(res.data.data.total)* 10000)/100; @@ -89,6 +87,9 @@ export default { }, 3000) } } + }else { + this.msg = res.data.msg; + this.timmer = setTimeout(this.getProgress, 300) } }else { if (this.syncFlag) { diff --git a/web_src/src/components/dialog/devicePlayer.vue b/web_src/src/components/dialog/devicePlayer.vue index 97162b5f4..9644d1c81 100755 --- a/web_src/src/components/dialog/devicePlayer.vue +++ b/web_src/src/components/dialog/devicePlayer.vue @@ -501,35 +501,6 @@ export default { videoError: function (e) { console.log("播放器错误:" + JSON.stringify(e)); }, - presetPosition: function (cmdCode, presetPos) { - console.log('预置位控制:' + this.presetPos + ' : 0x' + cmdCode.toString(16)); - let that = this; - this.$axios({ - method: 'post', - url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=0¶meter2=' + presetPos + '&combindCode2=0' - }).then(function (res) { - }); - }, - setSpeedOrTime: function (cmdCode, groupNum, parameter) { - let that = this; - let parameter2 = parameter % 256; - let combindCode2 = Math.floor(parameter / 256) * 16; - console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter2.toString(16) + ' 0x' + combindCode2.toString(16)); - this.$axios({ - method: 'post', - url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter2 + '&combindCode2=' + combindCode2 - }).then(function (res) { - }); - }, - setCommand: function (cmdCode, groupNum, parameter) { - let that = this; - console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter.toString(16) + ' 0x0'); - this.$axios({ - method: 'post', - url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter + '&combindCode2=0' - }).then(function (res) { - }); - }, copyUrl: function (dropdownItem) { console.log(dropdownItem) this.$copyText(dropdownItem).then((e) => { @@ -613,17 +584,6 @@ export default { recvOnly: false, }) - // webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放 - // console.error('播放成功',e.streams) - // this.broadcastStatus = 1; - // }); - // - // webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,(s)=>{// 获取到了本地流 - // this.broadcastStatus = 1; - // // document.getElementById('selfVideo').srcObject=s; - // // this.eventcallbacK("LOCAL STREAM", "获取到了本地流") - // }); - this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, (e) => {// 获取到了本地流 console.error('不支持webrtc', e) this.$message({ diff --git a/web_src/src/components/dialog/editRecordPlan.vue b/web_src/src/components/dialog/editRecordPlan.vue new file mode 100644 index 000000000..4d425779a --- /dev/null +++ b/web_src/src/components/dialog/editRecordPlan.vue @@ -0,0 +1,228 @@ + + + diff --git a/web_src/src/components/dialog/groupEdit.vue b/web_src/src/components/dialog/groupEdit.vue index 72795cb4d..1c77bc841 100755 --- a/web_src/src/components/dialog/groupEdit.vue +++ b/web_src/src/components/dialog/groupEdit.vue @@ -22,7 +22,7 @@ - 生成 + 选择 @@ -37,17 +37,17 @@
- + + + diff --git a/web_src/src/components/group.vue b/web_src/src/components/group.vue index bce8d9567..85fb134e0 100755 --- a/web_src/src/components/group.vue +++ b/web_src/src/components/group.vue @@ -8,9 +8,10 @@