diff --git a/README.md b/README.md index bbe1975e..1116581f 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,7 +43,7 @@ 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] 接入设备 @@ -97,10 +97,10 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] GPS订阅与通知(直播推流) - [X] 语音对讲 - [X] 支持同时级联到多个上级平台 -- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; +- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; - [X] 多流媒体节点,自动选择负载最低的节点使用。 - [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能; -- [X] 支持公网部署; +- [X] 支持公网部署; - [X] 支持wvp与zlm分开部署,提升平台并发能力 - [X] 支持拉流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台 - [X] 支持推流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台 @@ -110,6 +110,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 支持打包可执行jar和war - [X] 支持跨域请求,支持前后端分离部署 - [X] 支持Mysql,Postgresql,金仓等数据库 +- [X] 支持录制计划, 根据设定的时间对通道进行录制. 暂不支持将录制的内容转发到国标上级 - [X] 支持Onvif, 目前付费提供, 永久免费试用包在知识星球获取 - [X] 支持国标28181-2022协议, 目前付费提供, 永久免费试用包在知识星球获取 @@ -122,7 +123,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git # 授权协议 本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议 -# 技术支持 +# 技术支持 [知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:, - [使用入门系列一:WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp) @@ -134,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/gb28181/bean/CatalogData.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CatalogData.java index 25407ed4..fb687b35 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 @@ -20,6 +20,7 @@ public class CatalogData { 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 6327688b..ea606692 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/controller/CommonChannelController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/CommonChannelController.java index 44d11b0b..3db52a43 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, 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 664329e4..64f7dad8 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 a5263235..99bb6e36 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); 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 092c90dc..8a941d3c 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 @@ -17,6 +17,7 @@ public class ChannelProvider { " stream_proxy_id,\n" + " create_time,\n" + " update_time,\n" + + " record_plan_id,\n" + " coalesce(gb_device_id, device_id) as gb_device_id,\n" + " coalesce(gb_name, name) as gb_name,\n" + " coalesce(gb_manufacturer, manufacturer) as gb_manufacturer,\n" + @@ -182,6 +183,37 @@ public class ChannelProvider { return sqlBuild.toString(); } + public String queryList(Map params ){ + StringBuilder sqlBuild = new StringBuilder(); + sqlBuild.append(BASE_SQL); + sqlBuild.append(" where channel_type = 0 "); + if (params.get("query") != null) { + sqlBuild.append(" AND (coalesce(gb_device_id, device_id) LIKE concat('%',#{query},'%') escape '/'" + + " OR coalesce(gb_name, name) LIKE concat('%',#{query},'%') escape '/' )") + ; + } + if (params.get("online") != null && (Boolean)params.get("online")) { + sqlBuild.append(" AND coalesce(gb_status, status) = 'ON'"); + } + if (params.get("online") != null && !(Boolean)params.get("online")) { + sqlBuild.append(" AND coalesce(gb_status, status) = 'OFF'"); + } + if (params.get("hasRecordPlan") != null && (Boolean)params.get("hasRecordPlan")) { + sqlBuild.append(" AND record_plan_id > 0"); + } + + if (params.get("channelType") != null) { + if ((Integer)params.get("channelType") == 0) { + sqlBuild.append(" AND device_db_id is not null"); + }else if ((Integer)params.get("channelType") == 1) { + sqlBuild.append(" AND stream_push_id is not null"); + }else if ((Integer)params.get("channelType") == 2) { + sqlBuild.append(" AND stream_proxy_id is not null"); + } + } + return sqlBuild.toString(); + } + public String queryInListByStatus(Map params ){ StringBuilder sqlBuild = new StringBuilder(); sqlBuild.append(BASE_SQL); 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 81d9f9af..bb21e321 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 @@ -122,4 +122,7 @@ public interface IDeviceChannelService { DeviceChannel getOneBySourceId(int deviceDbId, String channelId); + List queryChaneListByDeviceDbId(Integer deviceDbId); + + List queryChaneIdListByDeviceDbIds(List deviceDbId); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java index 2d8f9531..8dc67df2 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java @@ -84,4 +84,7 @@ public interface IGbChannelService { List queryListByStreamPushList(List streamPushList); void updateGpsByDeviceIdForStreamPush(List channels); + + PageInfo queryList(int page, int count, String query, Boolean online, Boolean hasRecordPlan, Integer channelType); + } 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 4536c1ec..c18510c3 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 @@ -348,6 +348,16 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { return channelMapper.queryChannelsByDeviceDbId(device.getId()); } + @Override + public List queryChaneListByDeviceDbId(Integer deviceDbId) { + return channelMapper.queryChannelsByDeviceDbId(deviceDbId); + } + + @Override + public List queryChaneIdListByDeviceDbIds(List deviceDbIds) { + return channelMapper.queryChaneIdListByDeviceDbIds(deviceDbIds); + } + @Override public void updateChannelGPS(Device device, DeviceChannel deviceChannel, MobilePosition mobilePosition) { if (userSetting.getSavePositionHistory()) { 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 f19f4f9c..792fd88b 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 @@ -714,4 +714,16 @@ public class GbChannelServiceImpl implements IGbChannelService { public void updateGpsByDeviceIdForStreamPush(List channels) { commonGBChannelMapper.updateGpsByDeviceIdForStreamPush(channels); } + + @Override + public PageInfo queryList(int page, int count, String query, Boolean online, Boolean hasRecordPlan, Integer channelType) { + PageHelper.startPage(page, count); + if (query != null) { + query = query.replaceAll("/", "//") + .replaceAll("%", "/%") + .replaceAll("_", "/_"); + } + List all = commonGBChannelMapper.queryList(query, online, hasRecordPlan, channelType); + return new PageInfo<>(all); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java index 62125e7c..6b51054b 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java @@ -52,6 +52,11 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService { @Override public PageInfo queryChannelList(int page, int count, String query, Integer channelType, Boolean online, Integer platformId, Boolean hasShare) { PageHelper.startPage(page, count); + if (query != null) { + query = query.replaceAll("/", "//") + .replaceAll("%", "/%") + .replaceAll("_", "/_"); + } List all = platformChannelMapper.queryForPlatformForWebList(platformId, query, channelType, online, hasShare); return new PageInfo<>(all); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataManager.java index 049fc00e..9be9a519 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataManager.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataManager.java @@ -283,7 +283,7 @@ public class CatalogDataManager implements CommandLineRunner { if (catalogData == null) { return 0; } - return catalogData.getRedisKeysForChannel().size(); + return catalogData.getRedisKeysForChannel().size() + catalogData.getErrorChannel().size(); } public int sumNum(String deviceId, int sn) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java index 97748222..2567b762 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java @@ -94,4 +94,6 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) { } + + } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java index 33f9856f..3777e19e 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java @@ -1,7 +1,11 @@ package com.genersoft.iot.vmp.media.zlm.dto.hook; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; +import lombok.Getter; +import lombok.Setter; +@Setter +@Getter public class HookResultForOnPublish extends HookResult{ private boolean enable_audio; @@ -34,54 +38,6 @@ public class HookResultForOnPublish extends HookResult{ setMsg(msg); } - public boolean isEnable_audio() { - return enable_audio; - } - - public void setEnable_audio(boolean enable_audio) { - this.enable_audio = enable_audio; - } - - public boolean isEnable_mp4() { - return enable_mp4; - } - - public void setEnable_mp4(boolean enable_mp4) { - this.enable_mp4 = enable_mp4; - } - - public int getMp4_max_second() { - return mp4_max_second; - } - - public void setMp4_max_second(int mp4_max_second) { - this.mp4_max_second = mp4_max_second; - } - - public String getMp4_save_path() { - return mp4_save_path; - } - - public void setMp4_save_path(String mp4_save_path) { - this.mp4_save_path = mp4_save_path; - } - - public String getStream_replace() { - return stream_replace; - } - - public void setStream_replace(String stream_replace) { - this.stream_replace = stream_replace; - } - - public Integer getModify_stamp() { - return modify_stamp; - } - - public void setModify_stamp(Integer modify_stamp) { - this.modify_stamp = modify_stamp; - } - @Override public String toString() { return "HookResultForOnPublish{" + diff --git a/src/main/java/com/genersoft/iot/vmp/service/IRecordPlanService.java b/src/main/java/com/genersoft/iot/vmp/service/IRecordPlanService.java new file mode 100644 index 00000000..f3b34912 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/IRecordPlanService.java @@ -0,0 +1,31 @@ +package com.genersoft.iot.vmp.service; + +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.service.bean.RecordPlan; +import com.github.pagehelper.PageInfo; + +import java.util.List; + +public interface IRecordPlanService { + + + RecordPlan get(Integer planId); + + void update(RecordPlan plan); + + void delete(Integer planId); + + PageInfo query(Integer page, Integer count, String query); + + void add(RecordPlan plan); + + void link(List channelIds, Integer planId); + + PageInfo queryChannelList(int page, int count, String query, Integer channelType, Boolean online, Integer planId, Boolean hasLink); + + void linkAll(Integer planId); + + void cleanAll(Integer planId); + + Integer recording(String app, String stream); +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlan.java b/src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlan.java new file mode 100644 index 00000000..5333b2c3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlan.java @@ -0,0 +1,32 @@ +package com.genersoft.iot.vmp.service.bean; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "录制计划") +public class RecordPlan { + + @Schema(description = "计划数据库ID") + private int id; + + @Schema(description = "计划名称") + private String name; + + @Schema(description = "计划关联通道数量") + private int channelCount; + + @Schema(description = "是否开启定时截图") + private Boolean snap; + + @Schema(description = "创建时间") + private String createTime; + + @Schema(description = "更新时间") + private String updateTime; + + @Schema(description = "计划内容") + private List planItemList; +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlanItem.java b/src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlanItem.java new file mode 100644 index 00000000..31fa3214 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/RecordPlanItem.java @@ -0,0 +1,25 @@ +package com.genersoft.iot.vmp.service.bean; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "录制计划项") +public class RecordPlanItem { + + @Schema(description = "计划项数据库ID") + private int id; + + @Schema(description = "计划开始时间的序号, 从0点开始,每半个小时增加1") + private Integer start; + + @Schema(description = "计划结束时间的序号, 从0点开始,每半个小时增加1") + private Integer stop; + + @Schema(description = "计划周几执行") + private Integer weekDay; + + @Schema(description = "所属计划ID") + private Integer planId; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java index ad575042..fcb7570f 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java @@ -15,6 +15,7 @@ import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.bean.ResultForOnPublish; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.IMediaService; +import com.genersoft.iot.vmp.service.IRecordPlanService; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy; @@ -59,6 +60,9 @@ public class MediaServiceImpl implements IMediaService { @Autowired private SipInviteSessionManager sessionManager; + @Autowired + private IRecordPlanService recordPlanService; + @Override public boolean authenticatePlay(String app, String stream, String callId) { if (app == null || stream == null) { @@ -205,6 +209,9 @@ public class MediaServiceImpl implements IMediaService { @Override public boolean closeStreamOnNoneReader(String mediaServerId, String app, String stream, String schema) { boolean result = false; + if (recordPlanService.recording(app, stream) != null) { + return false; + } // 国标类型的流 if ("rtp".equals(app)) { result = userSetting.getStreamOnDemand(); diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/RecordPlanServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/RecordPlanServiceImpl.java new file mode 100644 index 00000000..ac146e86 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/RecordPlanServiceImpl.java @@ -0,0 +1,293 @@ +package com.genersoft.iot.vmp.service.impl; + +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService; +import com.genersoft.iot.vmp.media.bean.MediaInfo; +import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent; +import com.genersoft.iot.vmp.media.service.IMediaServerService; +import com.genersoft.iot.vmp.service.IRecordPlanService; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; +import com.genersoft.iot.vmp.service.bean.RecordPlan; +import com.genersoft.iot.vmp.service.bean.RecordPlanItem; +import com.genersoft.iot.vmp.storager.dao.RecordPlanMapper; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.google.common.base.Joiner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Service +@Slf4j +public class RecordPlanServiceImpl implements IRecordPlanService { + + @Autowired + private RecordPlanMapper recordPlanMapper; + + @Autowired + private CommonGBChannelMapper channelMapper; + + @Autowired + private IGbChannelPlayService channelPlayService; + + @Autowired + private IMediaServerService mediaServerService; + + + + /** + * 流离开的处理 + */ + @Async("taskExecutor") + @EventListener + public void onApplicationEvent(MediaDepartureEvent event) { + // 流断开,检查是否还处于录像状态, 如果是则继续录像 + Integer channelId = recording(event.getApp(), event.getStream()); + if(channelId == null) { + return; + } + // 重新拉起 + CommonGBChannel channel = channelMapper.queryById(channelId); + if (channel == null) { + log.warn("[录制计划] 流离开时拉起需要录像的流时, 发现通道不存在, id: {}", channelId); + return; + } + // 开启点播, + channelPlayService.play(channel, null, ((code, msg, streamInfo) -> { + if (code == InviteErrorCode.SUCCESS.getCode() && streamInfo != null) { + log.info("[录像] 流离开时拉起需要录像的流, 开启成功, 通道ID: {}", channel.getGbId()); + recordStreamMap.put(channel.getGbId(), streamInfo); + } else { + recordStreamMap.remove(channelId); + log.info("[录像] 流离开时拉起需要录像的流, 开启失败, 十分钟后重试, 通道ID: {}", channel.getGbId()); + } + })); + } + + Map recordStreamMap = new HashMap<>(); + +// @Scheduled(cron = "0 */30 * * * *") + @Scheduled(fixedRate = 10, timeUnit = TimeUnit.MINUTES) + public void execution() { + log.info("[录制计划] 执行"); + // 查询现在需要录像的通道Id + List startChannelIdList = queryCurrentChannelRecord(); + + if (startChannelIdList.isEmpty()) { + // 当前没有录像任务, 如果存在旧的正在录像的就移除 + if(!recordStreamMap.isEmpty()) { + stopStreams(recordStreamMap.keySet(), recordStreamMap); + recordStreamMap.clear(); + } + }else { + // 当前存在录像任务, 获取正在录像中存在但是当前录制列表不存在的内容,进行停止; 获取正在录像中没有但是当前需录制的列表中存在的进行开启. + Set recordStreamSet = new HashSet<>(recordStreamMap.keySet()); + startChannelIdList.forEach(recordStreamSet::remove); + if (!recordStreamSet.isEmpty()) { + // 正在录像中存在但是当前录制列表不存在的内容,进行停止; + stopStreams(recordStreamSet, recordStreamMap); + } + + // 移除startChannelIdList中已经在录像的部分, 剩下的都是需要新添加的(正在录像中没有但是当前需录制的列表中存在的进行开启) + recordStreamMap.keySet().forEach(startChannelIdList::remove); + if (!startChannelIdList.isEmpty()) { + // 获取所有的关联的通道 + List channelList = channelMapper.queryByIds(startChannelIdList); + if (!channelList.isEmpty()) { + // 查找是否已经开启录像, 如果没有则开启录像 + for (CommonGBChannel channel : channelList) { + // 开启点播, + channelPlayService.play(channel, null, ((code, msg, streamInfo) -> { + if (code == InviteErrorCode.SUCCESS.getCode() && streamInfo != null) { + log.info("[录像] 开启成功, 通道ID: {}", channel.getGbId()); + recordStreamMap.put(channel.getGbId(), streamInfo); + } else { + log.info("[录像] 开启失败, 十分钟后重试, 通道ID: {}", channel.getGbId()); + } + })); + } + } else { + log.error("[录制计划] 数据异常, 这些关联的通道已经不存在了: {}", Joiner.on(",").join(startChannelIdList)); + } + } + } + } + + /** + * 获取当前时间段应该录像的通道Id列表 + */ + private List queryCurrentChannelRecord(){ + // 获取当前时间在一周内的序号, 数据库存储的从第几个30分钟开始, 0-47, 包括首尾 + LocalDateTime now = LocalDateTime.now(); + int week = now.getDayOfWeek().getValue(); + int index = now.getHour() * 2 + (now.getMinute() > 30?1:0); + + // 查询现在需要录像的通道Id + return recordPlanMapper.queryRecordIng(week, index); + } + + private void stopStreams(Collection channelIds, Map recordStreamMap) { + for (Integer channelId : channelIds) { + try { + StreamInfo streamInfo = recordStreamMap.get(channelId); + if (streamInfo == null) { + continue; + } + // 查看是否有人观看,存在则不做处理,等待后续自然处理,如果无人观看,则关闭该流 + MediaInfo mediaInfo = mediaServerService.getMediaInfo(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); + if (mediaInfo.getReaderCount() == null || mediaInfo.getReaderCount() == 0) { + mediaServerService.closeStreams(streamInfo.getMediaServer(), streamInfo.getApp(), streamInfo.getStream()); + log.info("[录制计划] 停止, 通道ID: {}", channelId); + } + }catch (Exception e) { + log.error("[录制计划] 停止时异常", e); + }finally { + recordStreamMap.remove(channelId); + } + } + } + + @Override + public Integer recording(String app, String stream) { + for (Integer channelId : recordStreamMap.keySet()) { + StreamInfo streamInfo = recordStreamMap.get(channelId); + if (streamInfo != null && streamInfo.getApp().equals(app) && streamInfo.getStream().equals(stream)) { + return channelId; + } + } + return null; + } + + @Override + @Transactional + public void add(RecordPlan plan) { + plan.setCreateTime(DateUtil.getNow()); + plan.setUpdateTime(DateUtil.getNow()); + recordPlanMapper.add(plan); + if (plan.getId() > 0 && !plan.getPlanItemList().isEmpty()) { + for (RecordPlanItem recordPlanItem : plan.getPlanItemList()) { + recordPlanItem.setPlanId(plan.getId()); + } + recordPlanMapper.batchAddItem(plan.getId(), plan.getPlanItemList()); + } + // TODO 更新录像队列 + } + + @Override + public RecordPlan get(Integer planId) { + RecordPlan recordPlan = recordPlanMapper.get(planId); + if (recordPlan == null) { + return null; + } + List recordPlanItemList = recordPlanMapper.getItemList(planId); + if (!recordPlanItemList.isEmpty()) { + recordPlan.setPlanItemList(recordPlanItemList); + } + return recordPlan; + } + + @Override + @Transactional + public void update(RecordPlan plan) { + plan.setUpdateTime(DateUtil.getNow()); + recordPlanMapper.update(plan); + recordPlanMapper.cleanItems(plan.getId()); + if (plan.getPlanItemList() != null && !plan.getPlanItemList().isEmpty()){ + List planItemList = new ArrayList<>(); + for (RecordPlanItem recordPlanItem : plan.getPlanItemList()) { + if (recordPlanItem.getStart() == null || recordPlanItem.getStop() == null || recordPlanItem.getWeekDay() == null){ + continue; + } + if (recordPlanItem.getPlanId() == null) { + recordPlanItem.setPlanId(plan.getId()); + } + planItemList.add(recordPlanItem); + } + if(!planItemList.isEmpty()) { + recordPlanMapper.batchAddItem(plan.getId(), planItemList); + } + } + // TODO 更新录像队列 + + } + + @Override + @Transactional + public void delete(Integer planId) { + RecordPlan recordPlan = recordPlanMapper.get(planId); + if (recordPlan == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "录制计划不存在"); + } + // 清理关联的通道 + channelMapper.removeRecordPlanByPlanId(recordPlan.getId()); + recordPlanMapper.cleanItems(planId); + recordPlanMapper.delete(planId); + // TODO 更新录像队列 + } + + @Override + public PageInfo query(Integer page, Integer count, String query) { + PageHelper.startPage(page, count); + if (query != null) { + query = query.replaceAll("/", "//") + .replaceAll("%", "/%") + .replaceAll("_", "/_"); + } + List all = recordPlanMapper.query(query); + return new PageInfo<>(all); + } + + @Override + public void link(List channelIds, Integer planId) { + if (channelIds == null || channelIds.isEmpty()) { + log.info("[录制计划] 关联/移除关联时, 通道编号必须存在"); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "通道编号必须存在"); + } + if (planId == null) { + channelMapper.removeRecordPlan(channelIds); + }else { + channelMapper.addRecordPlan(channelIds, planId); + } + // 查看当前的待录制列表是否变化,如果变化,则调用录制计划马上开始录制 + List currentChannelRecord = queryCurrentChannelRecord(); + recordStreamMap.keySet().forEach(currentChannelRecord::remove); + if (!currentChannelRecord.isEmpty()) { + execution(); + } + } + + @Override + public PageInfo queryChannelList(int page, int count, String query, Integer channelType, Boolean online, Integer planId, Boolean hasLink) { + PageHelper.startPage(page, count); + if (query != null) { + query = query.replaceAll("/", "//") + .replaceAll("%", "/%") + .replaceAll("_", "/_"); + } + List all = channelMapper.queryForRecordPlanForWebList(planId, query, channelType, online, hasLink); + return new PageInfo<>(all); + } + + @Override + public void linkAll(Integer planId) { + channelMapper.addRecordPlanForAll(planId); + } + + @Override + public void cleanAll(Integer planId) { + channelMapper.removeRecordPlanByPlanId(planId); + } +} 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 00000000..ae0649aa --- /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/vmanager/recordPlan/RecordPlanController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/recordPlan/RecordPlanController.java new file mode 100644 index 00000000..f01c11e5 --- /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 00000000..9f56a0fb --- /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 00000000..9d2fdca2 --- /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 c314b9ba..78582b87 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/RecordPLan.vue b/web_src/src/components/RecordPLan.vue new file mode 100755 index 00000000..4e649fa5 --- /dev/null +++ b/web_src/src/components/RecordPLan.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/web_src/src/components/dialog/editRecordPlan.vue b/web_src/src/components/dialog/editRecordPlan.vue new file mode 100644 index 00000000..4d425779 --- /dev/null +++ b/web_src/src/components/dialog/editRecordPlan.vue @@ -0,0 +1,228 @@ + + + diff --git a/web_src/src/components/dialog/linkChannelRecord.vue b/web_src/src/components/dialog/linkChannelRecord.vue new file mode 100755 index 00000000..2046c1eb --- /dev/null +++ b/web_src/src/components/dialog/linkChannelRecord.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/web_src/src/layout/UiHeader.vue b/web_src/src/layout/UiHeader.vue index 0561bea7..dda1a8ea 100755 --- a/web_src/src/layout/UiHeader.vue +++ b/web_src/src/layout/UiHeader.vue @@ -15,6 +15,7 @@ 行政区划 业务分组 + 录制计划 云端录像 节点管理 国标级联 diff --git a/web_src/src/router/index.js b/web_src/src/router/index.js index f85c5a5a..5d609c4e 100755 --- a/web_src/src/router/index.js +++ b/web_src/src/router/index.js @@ -26,6 +26,7 @@ import rtcPlayer from '../components/dialog/rtcPlayer.vue' import region from '../components/region.vue' import group from '../components/group.vue' import operations from '../components/operations.vue' +import recordPLan from '../components/RecordPLan.vue' const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { @@ -148,6 +149,10 @@ export default new VueRouter({ path: '/operations', component: operations, }, + { + path: '/recordPLan', + component: recordPLan, + }, ] }, { diff --git a/数据库/2.7.3/初始化-mysql-2.7.3.sql b/数据库/2.7.3/初始化-mysql-2.7.3.sql index d572843d..e12677da 100644 --- a/数据库/2.7.3/初始化-mysql-2.7.3.sql +++ b/数据库/2.7.3/初始化-mysql-2.7.3.sql @@ -147,6 +147,7 @@ create table wvp_device_channel gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, + record_plan_id integer, stream_push_id integer, stream_proxy_id integer, constraint uk_wvp_device_channel_unique_device_channel unique (device_db_id, device_id), @@ -427,3 +428,23 @@ CREATE TABLE wvp_common_region constraint uk_common_region_device_id unique (device_id) ); +create table wvp_record_plan +( + id serial primary key, + snap bool default false, + name varchar(255) NOT NULL, + create_time character varying(50), + update_time character varying(50) +); + +create table wvp_record_plan_item +( + id serial primary key, + start int, + stop int, + week_day int, + plan_id int, + create_time character varying(50), + update_time character varying(50) +); + diff --git a/数据库/2.7.3/初始化-postgresql-kingbase-2.7.3.sql b/数据库/2.7.3/初始化-postgresql-kingbase-2.7.3.sql index cb06d2ee..c632f9a8 100644 --- a/数据库/2.7.3/初始化-postgresql-kingbase-2.7.3.sql +++ b/数据库/2.7.3/初始化-postgresql-kingbase-2.7.3.sql @@ -163,6 +163,7 @@ create table wvp_device_channel gb_download_speed character varying(255), gb_svc_space_support_mod integer, gb_svc_time_support_mode integer, + record_plan_id integer, stream_push_id integer, stream_proxy_id integer, constraint uk_wvp_device_channel_unique_device_channel unique (device_db_id, device_id), @@ -444,3 +445,23 @@ CREATE TABLE wvp_common_region constraint uk_common_region_device_id unique (device_id) ); +create table wvp_record_plan +( + id serial primary key, + snap bool default false, + name varchar(255) NOT NULL, + create_time character varying(50), + update_time character varying(50) +); + +create table wvp_record_plan_item +( + id serial primary key, + start int, + stop int, + week_day int, + plan_id int, + create_time character varying(50), + update_time character varying(50) +); +