制度相关功能后端开发

This commit is contained in:
wangna0328 2025-07-28 16:33:21 +08:00
parent 3d7263248c
commit 8ab365fc8b
139 changed files with 34 additions and 8516 deletions

View File

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dite.znpt</groupId>
<artifactId>parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>sip</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.dite.znpt</groupId>
<artifactId>core</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- sip协议栈 -->
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-ri</artifactId>
<version>1.3.0-91</version>
</dependency>
<!-- xml解析库 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
</project>

View File

@ -1,30 +0,0 @@
package com.dite.znpt.monitor.config;
import cn.hutool.core.collection.CollUtil;
import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 媒体格式配置
*
* @author huise23
* @since 2023-07-31 09:08:05
*/
@Configuration
public class MediaFormatConfig {
@Bean
public static List<StreamMediaFormat> streamMediaFormatList() {
List<StreamMediaFormat> formatList = CollUtil.newArrayList();
formatList.add(new StreamMediaFormat("flv",null,"1"));
formatList.add(new StreamMediaFormat("mp4",null,"0"));
formatList.add(new StreamMediaFormat("hls",null,"0"));
formatList.add(new StreamMediaFormat("webrtc",null,"1"));
return formatList;
}
}

View File

@ -1,37 +0,0 @@
package com.dite.znpt.monitor.constant;
/**
* @author yunp
* @since 2022/7/14
*/
public class Constants {
/**
* UTF-8 字符集
*/
public static final String UTF8 = "UTF-8";
/**
* GBK 字符集
*/
public static final String GBK = "GBK";
/**
* 路径拼接符 /
*/
public static final String File_SEPARATOR = "/";
/**
* http请求
*/
public static final String HTTP = "http://";
/**
* https请求
*/
public static final String HTTPS = "https://";
/**
* 默认字符串分隔符
*/
public static final String DEFAULT_DELIMITER = ",";
}

View File

@ -1,37 +0,0 @@
package com.dite.znpt.monitor.constant;
/**
* @author yunp
* @since 2022/8/4
* @description 缓存key定义key全部以iot开头
*/
public class IotCacheConstants {
/**
* 参数管理 cache key
*/
public static final String SYS_CONFIG_KEY = "sys_config:";
/**
* 字典管理 cache key
*/
public static final String SYS_DICT_KEY = "sys_dict:";
/**
* 图标库 cache key
*/
public static final String SYS_ICON_KEY = "sys_icon";
private static final String IOT_DEVICE_VIDEO_PREFIX = "vs_device_video:";
public static String getIotDeviceVideoKey(String deviceCode){
return IOT_DEVICE_VIDEO_PREFIX + deviceCode;
}
private final static String CLIENT_TRANSACTION_CACHE_PREFIX = "IOT_CLIENT_TRANSACTION_CACHE:";
public static String getClientTransactionCacheKey(String ssrc){
return CLIENT_TRANSACTION_CACHE_PREFIX + ssrc;
}
}

View File

@ -1,20 +0,0 @@
package com.dite.znpt.monitor.constant;
/**
* @author yunp
* @since 2022/8/4
* @description 字典类型定义
*/
public class IotDictConstants {
/**
* 设备状态-在线
*/
public static final String IOT_DEVICE_STATUS_ONLINE = "2";
/**
* 设备状态-离线
*/
public static final String IOT_DEVICE_STATUS_OFFLINE = "3";
}

View File

@ -1,113 +0,0 @@
package com.dite.znpt.monitor.constant;
/**
* @author yunp
* @since 2022/8/4
* @description 响应文案定义
*/
public class IotRespMessage {
public static final String UNKNOWN_FAIL = "未知错误";
public static final String ID_NOT_FOUND = "数据不存在";
public static final String PARAMETER_ERROR = "参数有误";
public static final String NO_PERMISSION = "没有访问权限";
public static final String PARENT_AREA_NOT_FOUND = "上级分区不存在";
public static final String CAR_TYPE_HAS_CARS = "该车型下仍有车辆,无法删除";
public static final String CAR_BRAND_HAS_CARS = "该品牌下仍有车辆,无法删除";
public static final String CAR_PLATE_NUMBER_EXIST = "该车牌号车辆已存在";
public static final String CAR_PLATE_NUMBER_NOT_EXIST = "该车牌号车辆不存在";
public static final String CAR_HAS_BIND_TAG_OR_SPEED_MONITOR = "车辆已绑定车载标签或速度检测仪,禁止删除";
public static final String CAR_FREIGHT_NOT_EXIST = "货物不存在";
public static final String CAR_HAS_BIND_ALARM_TEMPLATE = "该车辆已绑定其他告警模板";
public static final String USER_HAS_BIND_ALARM_TEMPLATE = "该人员已绑定其他告警模板";
public static final String REPETITION_ALARM_FOR_AREA = "该厂区分区下已有同类型告警,无法重复添加";
public static final String REPETITION_ALARM_NOTIFY_CONFIG = "已存在相同部门层级的告警消息推送配置";
public static final String DEVICE_TERMINAL_HAS_BEEN_BIND = "该标签卡已被其他人绑定";
public static final String DEVICE_TERMINAL_TYPE_ERROR = "标签卡类型有误";
public static final String DEVICE_TERMINAL_NOT_FOUND_OR_STATUS_ERROR = "标签卡未找到或状态异常";
public static final String DEVICE_CANNOT_EDIT = "设备未启用或已停用才可编辑";
public static final String VIDEO_DEVICE_CANNOT_DELETE = "视频设备禁止删除";
public static final String DEVICE_CANNOT_DELETE = "未启用的设备才可删除";
public static final String DEVICE_HAS_BIND_TO_GROUP = "设备已经绑定至该分组";
public static final String DEVICE_VIDEO_CANNOT_DELETE = "禁止删除在线视频设备";
public static final String DEVICE_VIDEO_CANNOT_SYNC = "禁止更新离线视频设备";
public static final String DEVICE_INACTIVE = "设备未启用";
public static final String CODE_HAS_BEEN_USED = "编号已被占用";
public static final String NAME_HAS_BEEN_USED = "名称已被占用";
public static final String FLAG_HAS_BEEN_USED = "标识已被占用";
public static final String GROUP_HAS_CHILD_CANNOT_REMOVE = "分组有下级分组,无法删除";
public static final String GROUP_HAS_DEVICE_CANNOT_REMOVE = "分组下有绑定设备,无法删除";
public static final String PRODUCT_PUBLISH_CANNOT_DELETE = "该产品已发布,不可删除";
public static final String PRODUCT_HAS_DEVICE_CANNOT_REMOVE = "产品下存在设备关联,需删除设备后进行操作";
public static final String MSG_PROTOCOL_PUBLISH_CANNOT_DELETE = "消息协议未发布,发布协议后操作";
public static final String CATEGORY_CANNOT_DELETE = "该产品分类信息已被产品关联,不可删除";
public static final String SCENE_CANNOT_DELETE = "该场景信息已被产品关联,不可删除";
public static final String SWITCH_PARAM_HAS_BEEN_USED = "该参数在设备服务中只能定义一个";
public static final String CONFIG_CANNOT_DELETE = "该配置已被通知模板关联,不可删除, 请取消关联后重试。";
public static final String TEMPLATE_CANNOT_DELETE = "该通知模板已被场景联动关联,不可删除, 请取消关联后操作。";
public static final String PROTOCOL_CANNOT_DELETE_BY_PUBLISHED = "当前协议状态为已发布,不可删除.";
public static final String PROTOCOL_CANNOT_DELETE_WITH_PRODUCT = "该协议已被产品关联,不可删除,请删除关联后操作!";
public static final String PROTOCOL_CANNOT_UN_PUBLISH = "协议已被产品发布,不可取消!";
public static final String PROTOCOL_TYPE_CANNOT_NULL = "协议类型不能为空";
public static final String PROTOCOL_CLASS_NAME_CANNOT_EMPTY = "协议类型为Jar或者Local类型时必须指定协议类名.";
public static final String PROTOCOL_FILE_PATH_CANNOT_EMPTY = "协议类型为Jar或者Local类型时必须上传或者指定协议文件路径.";
public static final String DATA_ILLEGAL = "数据非法";
public static final String IMPORT_ERROR = "导入出错";
public static final String COMMON_DEVICE_ATTR_DUPLICATE_ERROR = "订阅的通用设备点位全局不唯一";
public static final String PROTOCOL_NOT_EXISTS = "协议不存在";
public static final String RULE_NOT_EXISTS = "上报规则不存在";
public static final String DEVICE_NOT_EXISTS = "设备不存在";
public static final String DATA_DATA_TREND_TIME_TYPE_NOT_EXISTS = "设备数据趋势时间类型不存在";
public static final String DATA_DATA_TREND_DATA_TYPE_NOT_EXISTS = "设备数据趋势数据类型不存在";
public static final String CRON_ERROR = "cron表达式输入错误:";
}

View File

@ -1,32 +0,0 @@
package com.dite.znpt.monitor.constant.dict;
/**
* 摄像头类型
*
* @author huise23
* @since 2023-07-28 15:30:10
*/
public enum CameraType implements ValueAndLabel {
UNKNOWN("0","未知"),
BALLHEAD("1","球机");
private final String value;
private final String label;
CameraType(String value, String label) {
this.value = value;
this.label = label;
}
@Override
public String getValue() {
return value;
}
@Override
public String getLabel() {
return label;
}
}

View File

@ -1,34 +0,0 @@
package com.dite.znpt.monitor.constant.dict;
/**
* 设备状态
*
* @author huise23
* @since 2023-07-28 15:30:10
*/
public enum DeviceStatus implements ValueAndLabel {
INACTIV("1", "未启用"),
ONLINE("2", "在线"),
OFFLINE("3", "离线"),
STOP("4", "停用");
private final String value;
private final String label;
DeviceStatus(String value, String label) {
this.value = value;
this.label = label;
}
@Override
public String getValue() {
return value;
}
@Override
public String getLabel() {
return label;
}
}

View File

@ -1,26 +0,0 @@
package com.dite.znpt.monitor.constant.dict;
public enum SipTransferMode implements ValueAndLabel {
UDP("UDP","UDP"),
TCP("TCP","TCP");
private final String value;
private final String label;
SipTransferMode(String value, String label) {
this.value = value;
this.label = label;
}
@Override
public String getValue() {
return value;
}
@Override
public String getLabel() {
return label;
}
}

View File

@ -1,27 +0,0 @@
package com.dite.znpt.monitor.constant.dict;
public enum StreamTransferMode implements ValueAndLabel {
UDP("UDP","UDP"),
TCP_ACTIVE("TCP-ACTIVE","TCP主动"),
TCP_PASSIVE("TCP-PASSIVE", "TCP被动");
private final String value;
private final String label;
StreamTransferMode(String value, String label) {
this.value = value;
this.label = label;
}
@Override
public String getValue() {
return value;
}
@Override
public String getLabel() {
return label;
}
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.constant.dict;
/**
* 字典常量接口
*
* @author huise23
* @since 2023-07-28 15:28:55
*/
public interface ValueAndLabel {
String getValue();
String getLabel();
}

View File

@ -1,32 +0,0 @@
package com.dite.znpt.monitor.constant.dict;
/**
* YesOrNo
*
* @author huise23
* @since 2023-07-28 15:30:10
*/
public enum YesOrNo implements ValueAndLabel {
YES("Y",""),
NO("N", "");
private final String value;
private final String label;
YesOrNo(String value, String label) {
this.value = value;
this.label = label;
}
@Override
public String getValue() {
return value;
}
@Override
public String getLabel() {
return label;
}
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.domain;
/**
* @Author: cuizhibin
* @Date: 2023/1/16 14:36:36
* @Description:
*/
public interface CustomFunction<T> {
/**
* 执行的方法
* @return
*/
T get();
}

View File

@ -1,124 +0,0 @@
package com.dite.znpt.monitor.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @Author: huise23
* @Date: 2022/8/11 17:29
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("vs_device_video_channel")
@ApiModel(value="DeviceVideoChannelEntity", description="视频通道表")
public class DeviceVideoChannelEntity implements Serializable {
private static final long serialVersionUID = -4175177624487756818L;
@ApiModelProperty(value = "主键id")
@TableId(value = "channel_id", type = IdType.AUTO)
private Long channelId;
@ApiModelProperty(value = "视频设备id")
private Long videoId;
@ApiModelProperty(value = "通道国标编号")
private String channelCode;
@ApiModelProperty(value = "通道名")
private String channelName;
@ApiModelProperty(value = "生产厂商")
private String manufacture;
@ApiModelProperty(value = "型号")
private String model;
@ApiModelProperty(value = "设备归属")
private String owner;
@ApiModelProperty(value = "行政区域")
private String civilCode;
@ApiModelProperty(value = "警区")
private String block;
@ApiModelProperty(value = "安装位置")
private String address;
@ApiModelProperty(value = "是否有子设备 1有, 0没有")
private int parental;
@ApiModelProperty(value = "父级id")
private String parentId;
@ApiModelProperty(value = "信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式")
private int safetyWay;
@ApiModelProperty(value = "注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式")
private int registerWay;
@ApiModelProperty("证书序列号")
private String certNum;
@ApiModelProperty("证书有效标识 缺省为0;证书有效标识:0:无效1: 有效")
private int certifiable;
@ApiModelProperty("证书无效原因码")
private int errCode;
@ApiModelProperty( "证书终止有效期")
private String endTime;
@ApiModelProperty("保密属性 缺省为0; 0:不涉密, 1:涉密")
private String secrecy;
@ApiModelProperty("IP地址")
private String ipAddress;
@ApiModelProperty("端口号")
private int port;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("摄像头类型")
private String cameraType;
@ApiModelProperty("云台控制")
private String ptzControl;
@ApiModelProperty(value = "状态")
private String status;
@ApiModelProperty("经度")
private double longitude;
@ApiModelProperty("纬度")
private double latitude;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("创建人id")
@TableField(fill = FieldFill.INSERT)
private Long createBy;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新人id")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
}

View File

@ -1,97 +0,0 @@
package com.dite.znpt.monitor.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @Author: huise23
* @Date: 2022/8/11 10:24
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("vs_device_video")
@ApiModel(value="DeviceVideoEntity", description="视频设备表")
public class DeviceVideoEntity implements Serializable {
private static final long serialVersionUID = -182441901641147882L;
@ApiModelProperty(value = "主键id")
@TableId(value = "video_id", type = IdType.AUTO)
private Long videoId;
@ApiModelProperty(value = "视频设备国标编码")
private String videoCode;
@ApiModelProperty(value = "视频设备名称")
private String videoName;
@ApiModelProperty(value = "生产厂商")
private String manufacturer;
@ApiModelProperty(value = "型号")
private String model;
@ApiModelProperty(value = "固件版本")
private String firmware;
@ApiModelProperty(value = "传输协议UDP/TCP默认UDP")
private String transport;
@ApiModelProperty(value = "数据流传输模式(默认UDP)")
private String streamMode;
@ApiModelProperty(value = "设备状态")
private String status;
@ApiModelProperty(value = "注册时间")
private LocalDateTime registerTime;
@ApiModelProperty(value = "心跳时间")
private LocalDateTime KeepaliveTime;
@ApiModelProperty("通道个数")
private int channelCount;
@ApiModelProperty(value = "ip")
private String ip;
@ApiModelProperty(value = "端口")
private Integer port;
@ApiModelProperty(value = "地址")
private String hostAddress;
@ApiModelProperty(value = "注册有效期")
private Integer expires;
@ApiModelProperty(value = "符集, 支持 UTF-8 与 GB2312")
private String charset;
@ApiModelProperty(value = "产品id")
private Long productId;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("创建人id")
@TableField(fill = FieldFill.INSERT)
private Long createBy;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新人id")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
}

View File

@ -1,35 +0,0 @@
package com.dite.znpt.monitor.domain.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Date: 2023/09/05 16:39
* @Description: 监控设备IP配置表实体类
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("vs_ip_config")
@ApiModel(value="VsIpConfigEntity对象", description="监控设备IP配置表")
public class IpConfigEntity implements Serializable {
@ApiModelProperty("${column.comment}")
@TableId(type = IdType.AUTO)
private Long configId;
@ApiModelProperty("ip地址")
private String ip;
@ApiModelProperty("ip地址前三位")
private String ipTopThree;
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.domain.req;
import lombok.Data;
import java.util.List;
/**
* @Date:2023/9/5 16:19
* @Description: 视频服务配置新增对象
*/
@Data
public class MonitorConfigAddReq {
private List<String> ipAddresses;
}

View File

@ -1,23 +0,0 @@
package com.dite.znpt.monitor.domain.req;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 查询视频信息参数
*
* @author huise23
* @since 2024-12-03 14:03:29
*/
@Data
public class VideoInfoReq implements Serializable {
@ApiModelProperty(value = "视频对接方式 1.摄像头直连 2.级联")
private Integer videoConnection;
@ApiModelProperty(value = "级联分隔符(默认/)")
private String cascadeSeparator = "/";
}

View File

@ -1,19 +0,0 @@
package com.dite.znpt.monitor.domain.resp;
import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Date:2023/9/7 10:26
* @Description:
*/
@Data
public class DeviceVideoResp extends DeviceVideoEntity {
@ApiModelProperty(value = "设备状态label")
private String statusLabel;
@ApiModelProperty(value = "流传输模式label")
private String streamModeLabel;
@ApiModelProperty(value = "传输模式label")
private String transportLabel;
}

View File

@ -1,58 +0,0 @@
package com.dite.znpt.monitor.domain.resp;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 查询视频信息返回信息
*
* @author huise23
* @since 2024-12-03 14:03:29
*/
@Data
public class VideoInfoResp implements Serializable {
@ApiModelProperty(value = "视频设备id")
private Long videoId;
@ApiModelProperty(value = "视频设备名称")
private String videoName;
@ApiModelProperty(value = "通道id")
private Long channelId;
@ApiModelProperty(value = "国标编码")
private String channelCode;
@ApiModelProperty(value = "通道名称")
private String channelName;
@ApiModelProperty(value = "生产厂商")
private String manufacture;
@ApiModelProperty(value = "型号")
private String model;
@ApiModelProperty(value = "安装位置")
private String address;
@ApiModelProperty("IP地址")
private String ipAddress;
@ApiModelProperty("端口号")
private int port;
@ApiModelProperty(value = "云台控制label")
private String ptzControl;
@ApiModelProperty("经度")
private double longitude;
@ApiModelProperty("纬度")
private double latitude;
@ApiModelProperty(value = "状态 DeviceStatus")
private String status;
}

View File

@ -1,34 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/11 18:12
* @Description:
*/
@Data
@ApiModel("视频通道编辑请求")
public class DeviceVideoChannelEditReq implements Serializable {
private static final long serialVersionUID = 719557164910393807L;
@ApiModelProperty(value = "通道名称")
private String channelName;
@ApiModelProperty(value = "安装位置")
private String address;
@ApiModelProperty(value = "摄像头类型")
private String cameraType;
@ApiModelProperty(value = "云台控制Y表示是,N表示否")
private String ptzControl;
@ApiModelProperty(value = "描述")
private String remark;
}

View File

@ -1,55 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/11 18:12
* @Description:
*/
@Data
@ApiModel("视频通道列表响应")
public class DeviceVideoChannelListResp implements Serializable {
private static final long serialVersionUID = -8053965410352257803L;
@ApiModelProperty(value = "主键id")
private Long channelId;
@ApiModelProperty(value = "所属产品id")
private Long productId;
@ApiModelProperty(value = "通道国标编码")
private String channelCode;
@ApiModelProperty(value = "通道名称")
private String channelName;
@ApiModelProperty(value = "安装位置")
private String address;
@ApiModelProperty(value = "摄像头类型label")
private String cameraType;
@ApiModelProperty(value = "摄像头类型")
private String cameraTypeLabel;
@ApiModelProperty(value = "云台控制label")
private String ptzControl;
@ApiModelProperty(value = "云台控制Y表示是,N表示否")
private String ptzControlLabel;
@ApiModelProperty(value = "状态")
private String status;
@ApiModelProperty(value = "状态label")
private String statusLabel;
@ApiModelProperty(value = "描述")
private String remark;
}

View File

@ -1,43 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/11 18:12
* @Description:
*/
@Data
@ApiModel("视频通道响应")
public class DeviceVideoChannelResp implements Serializable {
private static final long serialVersionUID = 1140851083577845760L;
@ApiModelProperty(value = "主键id")
private Long channelId;
@ApiModelProperty(value = "通道国标编码")
private String channelCode;
@ApiModelProperty(value = "通道名称")
private String channelName;
@ApiModelProperty(value = "安装位置")
private String address;
@ApiModelProperty(value = "摄像头类型")
private String cameraType;
@ApiModelProperty(value = "云台控制Y表示是,N表示否")
private String ptzControl;
@ApiModelProperty(value = "状态")
private String status;
@ApiModelProperty(value = "描述")
private String remark;
}

View File

@ -1,29 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/11 18:13
* @Description:
*/
@Data
@ApiModel("视频设备编辑请求参数")
public class DeviceVideoEditReq implements Serializable {
private static final long serialVersionUID = -3387666090991548317L;
@ApiModelProperty(value = "设备名称")
private String videoName;
@ApiModelProperty(value = "所属产品")
private Long productId;
@ApiModelProperty(value = "说明")
private String remark;
}

View File

@ -1,74 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @Author: huise23
* @Date: 2022/8/11 18:13
* @Description:
*/
@Data
@ApiModel("视频设备列表响应")
public class DeviceVideoListResp implements Serializable {
private static final long serialVersionUID = -5568664011265192343L;
@ApiModelProperty(value = "主键id")
private Long videoId;
@ApiModelProperty(value = "视频设备国标编码")
private String videoCode;
@ApiModelProperty(value = "视频设备名称")
private String videoName;
@ApiModelProperty(value = "传输模式")
private String transport;
@ApiModelProperty(value = "传输模式label")
private String transportLabel;
@ApiModelProperty(value = "流传输模式")
private String streamMode;
@ApiModelProperty(value = "流传输模式label")
private String streamModeLabel;
@ApiModelProperty(value = "通道数量")
private Integer channelCount;
@ApiModelProperty(value = "设备状态")
private String status;
@ApiModelProperty(value = "设备状态label")
private String statusLabel;
@ApiModelProperty(value = "设备ip")
private String ip;
@ApiModelProperty(value = "设备端口")
private String port;
@ApiModelProperty(value = "设备地址ip+端口)")
private String hostAddress;
@ApiModelProperty(value = "生产厂商")
private String manufacturer;
@ApiModelProperty(value = "备注")
private String remark;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "心跳时间")
private LocalDateTime keepAliveTime;
}

View File

@ -1,24 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Author: huise23
* @Date: 2022/8/16 9:34
* @Description:
*/
@Data
@ApiModel("视频设备数量响应")
public class DeviceVideoNumResp {
@ApiModelProperty(value = "设备总数量")
private Long allDevice;
@ApiModelProperty(value = "设备在线数量")
private Long onlineDevice;
@ApiModelProperty(value = "设备离线数量")
private Long offlineDevice;
}

View File

@ -1,77 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
import com.dite.znpt.monitor.media.zlm.enums.MediaFormatType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/11 10:25
* @Description:
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "StreamMediaFormat对象", description = "流媒体格式")
public class StreamMediaFormat implements Serializable {
private static final long serialVersionUID = -4177962876536716643L;
@ApiModelProperty(value = "流媒体格式")
private String mediaFormat;
@ApiModelProperty(value = "端口")
private Integer port;
@ApiModelProperty(value = "是否开启tls,1表示ture,0表示false")
private String openTls;
@TableField(exist = false)
@ApiModelProperty(value = "WebSocket播放地址")
private String wsUrl;
@TableField(exist = false)
@ApiModelProperty(value = "Http播放地址")
private String httpUrl;
@TableField(exist = false)
@ApiModelProperty(value = "WebSocket播放地址")
private String wssUrl;
@TableField(exist = false)
@ApiModelProperty(value = "Http播放地址")
private String httpsUrl;
@TableField(exist = false)
@ApiModelProperty(value = "相对播放地址")
private String relativePath;
public StreamMediaFormat(String mediaFormat,Integer port,String openTls){
this.mediaFormat = mediaFormat;
this.port = port;
this.openTls = openTls;
}
public void generateUrl(String host, String streamId, ServerConfig config, String mediaRouter) {
if("webrtc".equals(this.mediaFormat)){
this.httpUrl = StrUtil.format("http://{}:{}/index/api/webrtc?app=rtp&stream={}&type=play", host, config.getHttpPort(), streamId);
this.httpsUrl = StrUtil.format("https://{}:{}/index/api/webrtc?app=rtp&stream={}&type=play", host, config.getHttpSslPort(), streamId);
this.relativePath = StrUtil.format("{}/index/api/webrtc?app=rtp&stream={}&type=play", mediaRouter, streamId);
return;
}
String suffix = MediaFormatType.getSuffix(this.mediaFormat);
if (config.getHttpSslPort() != null && config.getHttpSslPort() > 0) {
this.wssUrl = StrUtil.format("wss://{}:{}/rtp/{}{}", host, config.getHttpSslPort(), streamId, suffix);
this.httpsUrl = StrUtil.format("https://{}:{}/rtp/{}{}", host, config.getHttpSslPort(), streamId, suffix);
}
this.wsUrl = StrUtil.format("ws://{}:{}/rtp/{}{}", host, config.getHttpPort(), streamId, suffix);
this.httpUrl = StrUtil.format("http://{}:{}/rtp/{}{}", host, config.getHttpPort(), streamId, suffix);
this.relativePath = StrUtil.format("{}/rtp/{}{}", mediaRouter, streamId, suffix);
}
}

View File

@ -1,28 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/11 14:40
* @Description:
*/
@Data
@ApiModel("流媒体格式请求")
public class StreamMediaFormatReq implements Serializable {
private static final long serialVersionUID = 6627383994019834279L;
@ApiModelProperty(value = "流媒体格式")
private String mediaFormat;
@ApiModelProperty(value = "端口")
private Integer port;
@ApiModelProperty(value = "是否开启TLS,1表示true,0表示false")
private String openTls;
}

View File

@ -1,31 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/11 14:40
* @Description:
*/
@Data
@ApiModel("流媒体格式响应")
public class StreamMediaFormatResp implements Serializable {
private static final long serialVersionUID = -5714327034173930078L;
@ApiModelProperty(value = "流媒体格式主键")
private Long formatId;
@ApiModelProperty(value = "流媒体格式")
private String mediaFormat;
@ApiModelProperty(value = "端口")
private Integer port;
@ApiModelProperty(value = "是否开启TLS,1表示true,0表示false")
private String openTls;
}

View File

@ -1,59 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/11 14:16
* @Description:
*/
@Data
@ApiModel("流媒体服务配置请求")
public class StreamMediaServerConfigReq implements Serializable {
private static final long serialVersionUID = -1228005085084886474L;
@NotNull(message = "流媒体名称不能为空")
@ApiModelProperty(value = "流媒体名称")
private String mediaName;
@ApiModelProperty(value = "流媒体服务")
private String mediaService;
@ApiModelProperty(value = "公网 HOST")
private String publicHost;
@ApiModelProperty(value = "API HOST")
private String apiHost;
@ApiModelProperty(value = "API 端口")
private Integer apiPort;
@ApiModelProperty(value = "密钥")
private Integer secretKey;
@ApiModelProperty(value = "流ID前缀")
private String streamPrefix;
@ApiModelProperty(value = "RTP IP")
private String rtpHost;
@ApiModelProperty(value = "RTP 端口")
private Integer rtpPort;
@ApiModelProperty(value = "动态端口起始值")
private Integer dynamicPortStart;
@ApiModelProperty(value = "动态端口结束值")
private Integer dynamicPortEnd;
@ApiModelProperty(value = "流媒体格式")
private List<StreamMediaFormatReq> streamMediaFormatReqList;
}

View File

@ -1,60 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/11 14:16
* @Description:
*/
@Data
@ApiModel("流媒体服务配置响应")
public class StreamMediaServerConfigResp implements Serializable {
private static final long serialVersionUID = 3464085768355214710L;
@ApiModelProperty(value = "'流媒体配置主键'")
private Long configId;
@ApiModelProperty(value = "'流媒体名称'")
private String mediaName;
@ApiModelProperty(value = "流媒体服务")
private String mediaService;
@ApiModelProperty(value = "公网 HOST")
private String publicHost;
@ApiModelProperty(value = "API HOST")
private String apiHost;
@ApiModelProperty(value = "API 端口")
private Integer apiPort;
@ApiModelProperty(value = "密钥")
private String secretKey;
@ApiModelProperty(value = "流ID前缀")
private String streamPrefix;
@ApiModelProperty(value = "RTP IP")
private String rtpHost;
@ApiModelProperty(value = "RTP 端口")
private Integer rtpPort;
@ApiModelProperty(value = "动态端口起始值")
private Integer dynamicPortStart;
@ApiModelProperty(value = "动态端口结束值")
private Integer dynamicPortEnd;
@ApiModelProperty(value = "流媒体格式")
private List<StreamMediaFormatResp> streamMediaFormatRespList;
}

View File

@ -1,24 +0,0 @@
package com.dite.znpt.monitor.domain.vo.video;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 视频播放响应
* @author huise23
* @since 2024-11-26 14:03:41
*/
@Data
@Builder
public class VideoPayResp {
@ApiModelProperty(value = "播放方式")
private String mediaType;
@ApiModelProperty(value = "流媒体播放地址")
private List<StreamMediaFormat> streamMediaFormatList;
}

View File

@ -1,55 +0,0 @@
package com.dite.znpt.monitor.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity;
import com.dite.znpt.monitor.domain.req.VideoInfoReq;
import com.dite.znpt.monitor.domain.resp.VideoInfoResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelResp;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/11 18:08
* @Description:
*/
public interface DeviceVideoChannelMapper extends BaseMapper<DeviceVideoChannelEntity> {
/**
* 查询视频通道列表
*
* @param videoId 视频id
* @param keyword 插叙条件
* @return {@link List< DeviceVideoChannelListResp>}
*/
List<DeviceVideoChannelListResp> selectDeviceVideoChannel(@Param("videoId") Long videoId, @Param("keyword") String keyword);
/**
* 查询所有视频通道列表
*
* @param keyword 插叙条件
* @return {@link List< DeviceVideoChannelListResp>}
*/
List<DeviceVideoChannelListResp> selectAllDeviceVideoChannel(@Param("keyword") String keyword);
/**
* 查询视频通道详情
*
* @param channelCode 通道code
* @return {@link DeviceVideoChannelResp}
*/
DeviceVideoChannelResp getDeviceVideoChannelDetail(@Param("channelCode") String channelCode);
/**
* 查询通道及视频信息
*
* @param videoInfoReq 查询参数
* @return {@link VideoInfoResp }
* @author huise23
* @since 2024-12-03 13:54:52
*/
List<VideoInfoResp> selectVideoInfoList(VideoInfoReq videoInfoReq);
}

View File

@ -1,24 +0,0 @@
package com.dite.znpt.monitor.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/11 18:09
* @Description:
*/
public interface DeviceVideoMapper extends BaseMapper<DeviceVideoEntity> {
/**
* 条件查询视频设备列表
* @param status 是否在线
* @param keyword 设备名称或者编码
* @return {@link List< DeviceVideoListResp>}
*/
List<DeviceVideoListResp> selectDeviceVideoList(@Param("status") String status, @Param("keyword") String keyword, @Param("hostAddress") String hostAddress);
}

View File

@ -1,13 +0,0 @@
package com.dite.znpt.monitor.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dite.znpt.monitor.domain.entity.IpConfigEntity;
/**
* @Date: 2023/09/05 16:39
* @Description: 监控设备IP配置表数据库访问层
*/
public interface IpConfigMapper extends BaseMapper<IpConfigEntity> {
}

View File

@ -1,12 +0,0 @@
package com.dite.znpt.monitor.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
/**
* @Author: huise23
* @Date: 2022/8/11 15:00
* @Description:
*/
public interface StreamMediaFormatMapper extends BaseMapper<StreamMediaFormat> {
}

File diff suppressed because one or more lines are too long

View File

@ -1,135 +0,0 @@
package com.dite.znpt.monitor.media.zlm;
import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.event.*;
import com.dite.znpt.monitor.media.zlm.impl.ZlmHookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: huise23
* @Date: 2022/8/29 10:22
* @Description:
*/
@Slf4j
@RestController
@RequestMapping("/index/hook")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ZlmHook {
private final ZlmHookService service;
/**
* 流量统计事件播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件
* 阈值通过配置文件general.flowThreshold配置此事件对回复不敏感
*/
@PostMapping(value = "/on_flow_report")
public BaseEventResp onFlowReport(@RequestBody FlowReportReq req) {
return service.onFlowReport(req);
}
/**
* 访问http文件服务器上hls之外的文件时触发
*/
@PostMapping(value = "/on_http_access")
public HttpAccessResp onHttpAccess(@RequestBody HttpAccessReq req) {
return service.onHttpAccess(req);
}
/**
* 播放器鉴权事件rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件
* 如果流不存在那么先触发on_play事件然后触发on_stream_not_found事件
* 播放rtsp流时如果该流启动了rtsp专属鉴权(on_rtsp_realm)那么将不再触发on_play事件
*/
@PostMapping(value = "/on_play")
public BaseEventResp onPlay(@RequestBody PlayReq req) {
return service.onPlay(req);
}
/**
* rtsp/rtmp/rtp推流鉴权事件
*/
@PostMapping(value = "/on_publish")
public PublishResp onPublish(@RequestBody PublishReq req) {
return service.onPublish(req);
}
/**
* 录制mp4完成后通知事件此事件对回复不敏感
*/
@PostMapping(value = "/on_record_mp4")
public BaseEventResp onRecordMp4(@RequestBody RecordMp4Req req) {
return service.onRecordMp4(req);
}
/**
* 该rtsp流是否开启rtsp专用方式的鉴权事件开启后才会触发on_rtsp_auth事件
* 需要指出的是rtsp也支持url参数鉴权它支持两种方式鉴权
*/
@PostMapping(value = "/on_rtsp_realm")
public BaseEventResp onRtspRealm(@RequestBody RtspRealmReq req) {
return service.onRtspRealm(req);
}
/**
* rtsp专用的鉴权事件先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件
*/
@PostMapping(value = "/on_rtsp_auth")
public RtspAuthResp onRtspAuth(@RequestBody RtspAuthReq req) {
return service.onRtspAuth(req);
}
/**
* shell登录鉴权ZLMediaKit提供简单的telnet调试方式
* 使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面
*/
@PostMapping(value = "/on_shell_login")
public BaseEventResp onShellLogin(@RequestBody ShellLoginReq req) {
return service.onShellLogin(req);
}
/**
* rtsp/rtmp流注册或注销时触发此事件此事件对回复不敏感
*/
@PostMapping(value = "/on_stream_changed")
public BaseEventResp onStreamChanged(@RequestBody StreamChangedReq req) {
return service.onStreamChanged(req);
}
/**
* 流无人观看时事件用户可以通过此事件选择是否关闭无人看的流
*/
@PostMapping(value = "/on_stream_none_reader")
public BaseEventResp onStreamNoneReader(@RequestBody StreamNoneReaderReq req) {
return service.onStreamNoneReader(req);
}
/**
* 流未找到事件用户可以在此事件触发时立即去拉流这样可以实现按需拉流此事件对回复不敏感
*/
@PostMapping(value = "/on_stream_not_found")
public BaseEventResp onStreamNotFound(@RequestBody StreamNotFoundReq req) {
return service.onStreamNotFound(req);
}
/**
* 服务器启动事件可以用于监听服务器崩溃重启此事件对回复不敏感
*/
@PostMapping(value = "/on_server_started")
public BaseEventResp onServerStarted(@RequestBody ServerConfig req) {
return service.onServerStarted(req);
}
/**
* 服务器定时上报时间上报间隔可配置默认10s上报一次
*/
@PostMapping(value = "/on_server_keepalive")
public BaseEventResp onServerKeepalive(@RequestBody ServerKeepaliveReq req) {
return service.onServerKeepalive(req);
}
}

View File

@ -1,42 +0,0 @@
package com.dite.znpt.monitor.media.zlm;
import com.dite.znpt.monitor.media.zlm.dto.MediaItem;
/**
* @Author: huise23
* @Date: 2022/8/30 10:39
* @Description: 流媒体服务管理主业务
*/
public interface ZlmService {
/**
* 点播视频
*
* @param deviceCode 设备编码
* @param channelCode 通道编码
* @return 流信息
*/
MediaItem play(String deviceCode, String channelCode);
/**
* 失败的时候释放流媒体资源
*
* @param deviceCode 设备编码
* @param channelCode 通道编码
*/
void release(String deviceCode, String channelCode);
/**
* 失败的时候释放流媒体资源
*
* @param media 流媒体信息
*/
void release(MediaItem media);
/**
* 停止点播
*
* @param mediaServerId 流媒体服务器id,通过配置文件设置
* @param streamId 流ID
*/
void display(String mediaServerId, String streamId);
}

View File

@ -1,38 +0,0 @@
package com.dite.znpt.monitor.media.zlm.cache;
import com.dite.znpt.monitor.media.zlm.dto.ServerItem;
import com.dite.znpt.service.impl.RedisService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @Author: huise23
* @Date: 2022/8/30 15:46
* @Description:
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class MediaServerCache {
private final RedisService redisService;
private final String zlm_key = "zlm_media_server";
public void putLoad(ServerItem serverItem) {
redisService.setCacheObject(zlm_key, serverItem);
}
/**
* 获取zlm节点
*/
public ServerItem getLoad() {
return redisService.getCacheObject(zlm_key);
}
public void releaseSsrc(String ssrc) {
ServerItem item = getLoad();
item.releaseSsrc(ssrc);
putLoad(item);
}
}

View File

@ -1,54 +0,0 @@
package com.dite.znpt.monitor.media.zlm.cache;
import cn.hutool.core.util.StrUtil;
import com.dite.znpt.monitor.media.zlm.dto.MediaItem;
import com.dite.znpt.service.impl.RedisService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @Author: huise23
* @Date: 2022/8/30 15:46
* @Description:
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class MediaServerChannelCache {
private final RedisService redisService;
private String getKey(String deviceCode, String channelCode) {
return StrUtil.format("zlm_media_server_channel:{}:{}", deviceCode, channelCode);
}
private String getStreamKey(String mediaServerId, String streamId) {
return StrUtil.format("zlm_media_server_channel_stream_key:{}:{}", mediaServerId, streamId);
}
public boolean has(String deviceCode, String channelCode) {
return redisService.hasKey(getKey(deviceCode, channelCode));
}
public MediaItem get(String deviceCode, String channelCode) {
return redisService.getCacheObject(getKey(deviceCode, channelCode));
}
public void put(String deviceCode, String channelCode, MediaItem media) {
String key = getKey(deviceCode, channelCode);
redisService.setCacheObject(key, media);
redisService.setCacheObject(getStreamKey(media.getConfig().getGeneralMediaServerId(), media.getStreamId()), key);
}
public void delete(MediaItem media) {
redisService.deleteObject(getKey(media.getDeviceCode(), media.getChannelCode()));
redisService.deleteObject(getStreamKey(media.getConfig().getGeneralMediaServerId(), media.getStreamId()));
}
public MediaItem getByStream(String mediaServerId, String streamId) {
String key = redisService.getCacheObject(getStreamKey(mediaServerId, streamId));
if (StrUtil.isNotBlank(key)) {
return redisService.getCacheObject(key);
}
return null;
}
}

View File

@ -1,46 +0,0 @@
package com.dite.znpt.monitor.media.zlm.config;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "zlm-config")
public class StreamMediaServerConfig {
@ApiModelProperty(value = "'流媒体名称'")
private String mediaName;
@ApiModelProperty(value = "流媒体服务商")
private String mediaService;
@ApiModelProperty(value = "公网ip")
private String publicHost;
@ApiModelProperty(value = "接口ip")
private String apiHost;
@ApiModelProperty(value = "接口端口")
private Integer apiPort;
@ApiModelProperty(value = "密钥")
private String secretKey;
@ApiModelProperty(value = "流id前缀")
private String streamPrefix;
@ApiModelProperty(value = "rtp ip")
private String rtpHost;
@ApiModelProperty(value = "rtp 端口")
private Integer rtpPort;
@ApiModelProperty(value = "动态端口起始值")
private String dynamicPortStart;
@ApiModelProperty(value = "动态端口结束值")
private String dynamicPortEnd;
}

View File

@ -1,130 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto;
import cn.hutool.core.util.StrUtil;
import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
import com.dite.znpt.monitor.media.zlm.dto.resp.RtpInfoResp;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/29 15:41
* @Description:
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class MediaItem implements Serializable {
private static final long serialVersionUID = -6679610697837602559L;
/**
* 设备编码
*/
private String deviceCode;
/**
* 通道编码
*/
private String channelCode;
/**
* 节点信息ID
*/
private String configId;
/**
* 节点信息ID
*/
private ServerInfo server;
/**
* 节点格式信息
*/
private List<StreamMediaFormat> formatList;
/**
* 节点配置信息
*/
private ServerConfig config;
/**
* 流ID
*/
private String streamId;
/**
* 播放流信息
*/
private RtpInfoResp rtp;
/**
* Rtp服务监听端口
*/
private Integer rtpPort;
/**
* SSRC源地址
*/
private String ssrc;
/**
* rtmp播放地址
*/
private String rtmpUrl;
/**
* rtmpSsl播放地址
*/
private String rtmpSslUrl;
/**
* rtsp播放地址
*/
private String rtspUrl;
/**
* rtspSsl播放地址
*/
private String rtspSslUrl;
/**
* rtc流地址
*/
private String rtc;
/**
* rtcs流地址
*/
private String rtcs;
/**
* 是否缓存
*/
private Boolean isCache;
public List<StreamMediaFormat> getFormatList(String mediaRouter) {
if (StrUtil.isNotBlank(streamId)) {
formatList.forEach(item -> item.generateUrl(server.getApiHost(), streamId, config, mediaRouter));
}
return formatList;
}
public String getRtmpUrl() {
if (StrUtil.isBlank(streamId)) {
return "";
}
return StrUtil.format("rtmp://{}:{}/rtp/{}", server.getApiHost(), config.getRtmpPort(), streamId);
}
public String getRtmpSslUrl() {
if (StrUtil.isBlank(streamId)) {
return "";
}
return config.getRtspSslPort() > 0 ? StrUtil.format("rtmps://{}:{}/rtp/{}", server.getApiHost(), config.getRtspSslPort(), streamId) : "";
}
public String getRtspUrl() {
if (StrUtil.isBlank(streamId)) {
return "";
}
return StrUtil.format("rtsp://{}:{}/rtp/{}", server.getApiHost(), config.getRtspPort(), streamId);
}
public String getRtspSslUrl() {
if (StrUtil.isBlank(streamId)) {
return "";
}
return config.getRtspSslPort() > 0 ? StrUtil.format("rtsps://{}:{}/rtp/{}", server.getApiHost(), config.getRtspSslPort(), streamId) : "";
}
}

View File

@ -1,852 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.annotation.JSONField;
import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.req.BaseReq;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Author: huise23
* @Date: 2022/8/29 10:54
* @Description: 服务器配置
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class ServerConfig extends BaseReq {
// ----------------------------------------------- api -----------------------------------------------
/**
* 是否调试http api,启用调试后会打印每次http请求的内容和回复
* apiDebug=1
*/
@JSONField(name = "api.apiDebug")
private Integer apiDebug;
/**
* 一些比较敏感的http api在访问时需要提供secret否则无权限调用
* 如果是通过127.0.0.1访问,那么可以不提供secret
* secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
*/
@JSONField(name = "api.secret")
private String apiSecret;
/**
* 截图保存路径根目录截图通过http api(/index/api/getSnap)生成和获取
* snapRoot=./www/snap/
*/
@JSONField(name = "api.snapRoot")
private String apiSnapRoot;
/**
* 默认截图图片在启动FFmpeg截图后但是截图还未生成时可以返回默认的预设图片
* defaultSnap=./www/logo.png
*/
@JSONField(name = "api.defaultSnap")
private String apiDefaultSnap;
// ----------------------------------------------- ffmpeg -----------------------------------------------
/**
* FFmpeg可执行程序路径,支持相对路径/绝对路径
* bin=/usr/bin/ffmpeg
*/
@JSONField(name = "ffmpeg.bin")
private String ffmpegBin;
/**
* FFmpeg拉流再推流的命令模板通过该模板可以设置再编码的一些参数
* cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
*/
@JSONField(name = "ffmpeg.cmd")
private String ffmpegCmd;
/**
* FFmpeg生成截图的命令可以通过修改该配置改变截图分辨率或质量
* snap=%s -i %s -y -f mjpeg -t 0.001 %s
*/
@JSONField(name = "ffmpeg.snap")
private String ffmpegSnap;
/**
* FFmpeg日志的路径如果置空则不生成FFmpeg日志
* 可以为相对(相对于本可执行程序目录)或绝对路径
* log=./ffmpeg/ffmpeg.log
*/
@JSONField(name = "ffmpeg.log")
private String ffmpegLog;
/**
* 自动重启的时间(), 默认为0, 也就是不自动重启. 主要是为了避免长时间ffmpeg拉流导致的不同步现象
* restart_sec=0
*/
@JSONField(name = "ffmpeg.restart_sec")
private String ffmpegRestartSec;
// ----------------------------------------------- general -----------------------------------------------
/**
* 是否启用虚拟主机
* enableVhost=0
*/
@JSONField(name = "general.enableVhost")
private Integer enableVhost;
/**
* 播放器或推流器在断开后会触发hook.on_flow_report事件(使用多少流量事件)
* flowThreshold参数控制触发hook.on_flow_report事件阈值使用流量超过该阈值后才触发单位KB
* flowThreshold=1024
*/
@JSONField(name = "general.flowThreshold")
private Integer generalFlowThreshold;
/**
* 播放最多等待时间单位毫秒
* 播放在播放某个流时如果该流不存在
* ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
* 如果在这个时间内该流注册成功那么会立即返回播放器播放成功
* 否则返回播放器未找到该流该机制的目的是可以先播放再推流
* maxStreamWaitMS=15000
*/
@JSONField(name = "general.maxStreamWaitMS")
private Integer generalMaxStreamWaitMs;
/**
* 某个流无人观看时触发hook.on_stream_none_reader事件的最大等待时间单位毫秒
* 在配合hook.on_stream_none_reader事件时可以做到无人观看自动停止拉流或停止接收推流
* streamNoneReaderDelayMS=20000
*/
@JSONField(name = "general.streamNoneReaderDelayMS")
private Integer generalStreamNoneReaderDelayMs;
/**
* 是否全局添加静音aac音频转协议时有效
* 有些播放器在打开单视频流时不能秒开添加静音音频可以加快秒开速度
* addMuteAudio=1
*/
@JSONField(name = "general.addMuteAudio")
private Integer generalAddMuteAudio;
/**
* 拉流代理时如果断流再重连成功是否删除前一次的媒体流数据如果删除将重新开始
* 如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
* resetWhenRePlay=1
*/
@JSONField(name = "general.resetWhenRePlay")
private Integer generalResetWhenRePlay;
/**
* 是否默认推流时转换成hlshook接口(on_publish)中可以覆盖该设置
* publishToHls=1
*/
@JSONField(name = "general.publishToHls")
private Integer generalPublishToHls;
/**
* 是否默认推流时mp4录像hook接口(on_publish)中可以覆盖该设置
* publishToMP4=0
*/
@JSONField(name = "general.publishToMP4")
private Integer generalPublishToMP4;
/**
* 合并写缓存大小(单位毫秒)合并写指服务器缓存一定的数据后才会一次性写入socket这样能提高性能但是会提高延时
* 开启后会同时关闭TCP_NODELAY并开启MSG_MORE
* mergeWriteMS=0
*/
@JSONField(name = "general.mergeWriteMS")
private Integer generalMergeWriteMS;
/**
* 全局的时间戳覆盖开关在转协议时对frame进行时间戳覆盖
* 该开关对rtsp/rtmp/rtp推流rtsp/rtmp/hls拉流代理转协议时生效
* 会直接影响rtsp/rtmp/hls/mp4/flv等协议的时间戳
* 同协议情况下不影响(例如rtsp/rtmp推流那么播放rtsp/rtmp时不会影响时间戳)
* modifyStamp=0
*/
@JSONField(name = "general.modifyStamp")
private Integer generalModifyStamp;
/**
* 服务器唯一id用于触发hook时区别是哪台服务器
* mediaServerId=your_server_id
*/
@JSONField(name = "general.mediaServerId")
private String generalMediaServerId;
/**
* 转协议是否全局开启或关闭音频
* enable_audio=1
*/
@JSONField(name = "general.enable_audio")
private Integer generalEnableAudio;
// ###### 以下是按需转协议的开关在测试ZLMediaKit的接收推流性能时请把下面开关置1
// ###### 如果某种协议你用不到你可以把以下开关置1以便节省资源(但是还是可以播放只是第一个播放者体验稍微差点)
// ###### 如果某种协议你想获取最好的用户体验请置0(第一个播放者可以秒开且不花屏)
/**
* hls协议是否按需生成如果hls.segNum配置为0(意味着hls录制)那么hls将一直生成(不管此开关)
* hls_demand=0
*/
@JSONField(name = "general.hls_demand")
private Integer generalHlsDemand;
/**
* rtsp[s]协议是否按需生成
* rtsp_demand=0
*/
@JSONField(name = "general.rtsp_demand")
private Integer generalRtspDemand;
/**
* rtmp[s]http[s]-flvws[s]-flv协议是否按需生成
* rtmp_demand=0
*/
@JSONField(name = "general.rtmp_demand")
private Integer generalRtmpDemand;
/**
* http[s]-ts协议是否按需生成
* ts_demand=0
*/
@JSONField(name = "general.ts_demand")
private Integer generalTsDemand;
/**
* http[s]-fmp4ws[s]-fmp4协议是否按需生成
* fmp4_demand=0
*/
@JSONField(name = "general.fmp4_demand")
private Integer generalFmp4Demand;
/**
* 最多等待未初始化的Track时间单位毫秒超时之后会忽略未初始化的Track
* wait_track_ready_ms=10000
*/
@JSONField(name = "general.wait_track_ready_ms")
private Integer generalWaitTrackReadyMs;
/**
* 如果流只有单Track最多等待若干毫秒超时后未收到其他Track的数据则认为是单Track
* 如果协议元数据有声明特定track数那么无此等待时间
* wait_add_track_ms=3000
*/
@JSONField(name = "general.wait_add_track_ms")
private Integer generalWaitAddTrackMs;
/**
* 如果track未就绪我们先缓存帧数据但是有最大个数限制防止内存溢出
* unready_frame_cache=100
*/
@JSONField(name = "general.unready_frame_cache")
private Integer generalUnreadyFrameCache;
/**
* 推流断开后可以在超时时间内重新连接上继续推流这样播放器会接着播放
* 置0关闭此特性(推流断开会导致立即断开播放器)
* 此参数不应大于播放器超时时间
* continue_push_ms=15000
*/
@JSONField(name = "general.continue_push_ms")
private Integer generalContinuePushMs;
// ----------------------------------------------- hls -----------------------------------------------
/**
* hls写文件的buf大小调整参数可以提高文件io性能
* fileBufSize=65536
*/
@JSONField(name = "hls.fileBufSize")
private Integer hlsFileBufSize;
/**
* hls保存文件路径
* 可以为相对(相对于本可执行程序目录)或绝对路径
* filePath=./www
*/
@JSONField(name = "hls.filePath")
private String hlsFilePath;
/**
* hls最大切片时间
* segDur=2
*/
@JSONField(name = "hls.segDur")
private Integer hlsSegDur;
/**
* m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个)
* 如果设置为0则不删除切片而是保存为点播
* segNum=3
*/
@JSONField(name = "hls.segNum")
private Integer hlsSegNum;
/**
* HLS切片从m3u8文件中移除后继续保留在磁盘上的个数
* segRetain=5
*/
@JSONField(name = "hls.segRetain")
private Integer hlsSegRetain;
/**
* 是否广播 ts 切片完成通知
* broadcastRecordTs=0
*/
@JSONField(name = "hls.broadcastRecordTs")
private Integer hlsBroadcastRecordTs;
/**
* 直播hls文件删除延时单位秒issue: #913
* deleteDelaySec=0
*/
@JSONField(name = "hls.deleteDelaySec")
private Integer hlsDeleteDelaySec;
/**
* 是否保留hls文件此功能部分等效于segNum=0的情况
* 不同的是这个保留不会在m3u8文件中体现
* 0为不保留不起作用
* 1为保留则不删除hls文件如果开启此功能注意磁盘大小或者定期手动清理hls文件
* segKeep=0
*/
@JSONField(name = "hls.segKeep")
private Integer hlsSegKeep;
// ----------------------------------------------- hook -----------------------------------------------
/**
* 在推流时如果url参数匹对admin_params那么可以不经过hook鉴权直接推流成功播放时亦然
* 该配置项的目的是为了开发者自己调试测试该参数暴露后会有泄露隐私的安全隐患
* admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
*/
@JSONField(name = "hook.admin_params")
private String hookAdminParams;
/**
* 是否启用hook事件启用后推拉流都将进行鉴权
* enable=0
*/
@JSONField(name = "hook.enable")
private Integer hookHookEnable;
/**
* 播放器或推流器使用流量事件置空则关闭
* on_flow_report=https://127.0.0.1/index/hook/on_flow_report
*/
@JSONField(name = "hook.on_flow_report")
private String hookOnFlowReport;
/**
* 访问http文件鉴权事件置空则关闭鉴权
* on_http_access=https://127.0.0.1/index/hook/on_http_access
*/
@JSONField(name = "hook.on_http_access")
private String hookOnHttpAccess;
/**
* 播放鉴权事件置空则关闭鉴权
* on_play=https://127.0.0.1/index/hook/on_play
*/
@JSONField(name = "hook.on_play")
private String hookOnPlay;
/**
* 推流鉴权事件置空则关闭鉴权
* on_publish=https://127.0.0.1/index/hook/on_publish
*/
@JSONField(name = "hook.on_publish")
private String hookOnPublish;
/**
* 录制mp4切片完成事件
* on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4
*/
@JSONField(name = "hook.on_record_mp4")
private String hookOnRecordMp4;
/**
* 录制 hls ts 切片完成事件
* on_record_ts=https://127.0.0.1/index/hook/on_record_ts
*/
@JSONField(name = "hook.on_record_ts")
private String hookOnRecordTs;
/**
* rtsp播放鉴权事件此事件中比对rtsp的用户名密码
* on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
*/
@JSONField(name = "hook.on_rtsp_auth")
private String hookOnRtspAuth;
/**
* rtsp播放是否开启专属鉴权事件置空则关闭rtsp鉴权rtsp播放鉴权还支持url方式鉴权
* 建议开发者统一采用url参数方式鉴权rtsp用户名密码鉴权一般在设备上用的比较多
* 开启rtsp专属鉴权后将不再触发on_play鉴权事件
* on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm
*/
@JSONField(name = "hook.on_rtsp_realm")
private String hookOnRtspRealm;
/**
* 远程telnet调试鉴权事件
* on_shell_login=https://127.0.0.1/index/hook/on_shell_login
*/
@JSONField(name = "hook.on_shell_login")
private String hookOnShellLogin;
/**
* 直播流注册或注销事件
* on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed
*/
@JSONField(name = "hook.on_stream_changed")
private String hookOnStreamChanged;
/**
* 服务器启动报告可以用于服务器的崩溃重启事件监听
* on_server_started=https://127.0.0.1/index/hook/on_server_started
*/
@JSONField(name = "hook.on_server_started")
private String hookOnServerStarted;
/**
* server保活上报
* on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive
*/
@JSONField(name = "hook.on_server_keepalive")
private String hookOnServerKeepalive;
/**
* 无人观看流事件通过该事件可以选择是否关闭无人观看的流配合general.streamNoneReaderDelayMS选项一起使用
* on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
*/
@JSONField(name = "hook.on_stream_none_reader")
private String hookOnStreamNoneReader;
/**
* 播放时未找到流事件通过配合hook.on_stream_none_reader事件可以完成按需拉流
* on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found
*/
@JSONField(name = "hook.on_stream_not_found")
private String hookOnStreamNotFound;
/**
* 发送rtp(startSendRtp)被动关闭时回调
* on_send_rtp_stopped=https://127.0.0.1/index/hook/on_send_rtp_stopped
*/
@JSONField(name = "hook.on_send_rtp_stopped")
private String hookOnSendRtpStopped;
/**
* hook api最大等待回复时间单位秒
* timeoutSec=10
*/
@JSONField(name = "hook.timeoutSec")
private Integer hookTimeoutSec;
/**
* keepalive hook触发间隔,单位秒float类型
* alive_interval=10.0
*/
@JSONField(name = "hook.alive_interval")
private Float hookAliveInterval;
/**
* hook通知失败重试次数,正整数为0不重试1时重试一次以此类推
* retry=1
*/
@JSONField(name = "hook.retry")
private Integer hookRetry;
/**
* hook通知失败重试延时单位秒float型
* retry_delay=3.0
*/
@JSONField(name = "hook.retry_delay")
private Float hookRetryDelay;
// ----------------------------------------------- cluster -----------------------------------------------
/**
* 设置源站拉流url模板, 格式跟printf类似第一个%s指定app,第二个%s指定stream_id,
* 开启集群模式后on_stream_not_found和on_stream_none_reader hook将无效.
* 溯源模式支持以下类型:
* rtmp方式: rtmp://127.0.0.1:1935/%s/%s
* rtsp方式: rtsp://127.0.0.1:554/%s/%s
* hls方式: http://127.0.0.1:80/%s/%s/hls.m3u8
* http-ts方式: http://127.0.0.1:80/%s/%s.live.ts
* 支持多个源站不同源站通过分号(;)分隔
* origin_url=
*/
@JSONField(name = "cluster.origin_url")
private String clusterOriginUrl;
/**
* 溯源总超时时长单位秒float型假如源站有3个那么单次溯源超时时间为timeout_sec除以3
* 单次溯源超时时间不要超过general.maxStreamWaitMS配置
* timeout_sec=15
*/
@JSONField(name = "cluster.timeout_sec")
private Integer clusterTimeoutSec;
/**
* 溯源失败尝试次数-1时永久尝试
* retry_count=3
*/
@JSONField(name = "cluster.retry_count")
private Integer clusterRetryCount;
// ----------------------------------------------- http -----------------------------------------------
/**
* http服务器字符编码windows上默认gb2312
* charSet=utf-8
*/
@JSONField(name = "http.charSet")
private String httpCharSet;
/**
* http链接超时时间
* keepAliveSecond=30
*/
@JSONField(name = "http.keepAliveSecond")
private Integer httpKeepAliveSecond;
/**
* http请求体最大字节数如果post的body太大则不适合缓存body在内存
* maxReqSize=40960
*/
@JSONField(name = "http.maxReqSize")
private Integer httpMaxReqSize;
/**
* 404网页内容用户可以自定义404网页
* notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><center><h1>您访问的资源不存在</h1></center><hr><center>ZLMediaKit-4.0</center></body></html>
*/
@JSONField(name = "http.notFound")
private String httpNotFound;
/**
* http服务器监听端口
* port=80
*/
@JSONField(name = "http.port")
private Integer httpPort;
/**
* http文件服务器根目录
* 可以为相对(相对于本可执行程序目录)或绝对路径
* rootPath=./www
*/
@JSONField(name = "http.rootPath")
private String httpRootPath;
/**
* http文件服务器读文件缓存大小单位BYTE调整该参数可以优化文件io性能
* sendBufSize=65536
*/
@JSONField(name = "http.sendBufSize")
private Integer httpSendBufSize;
/**
* https服务器监听端口
* sslport=443
*/
@JSONField(name = "http.sslport")
private Integer httpSslPort;
/**
* 是否显示文件夹菜单开启后可以浏览文件夹
* dirMenu=1
*/
@JSONField(name = "http.dirMenu")
private Integer httpDirMenu;
/**
* 虚拟目录, 虚拟目录名和文件路径使用","隔开多个配置路径间用";"隔开
* 例如赋值为 app_a,/path/to/a;app_b,/path/to/b 那么
* 访问 http://127.0.0.1/app_a/file_a 对应的文件路径为 /path/to/a/file_a
* 访问 http://127.0.0.1/app_b/file_b 对应的文件路径为 /path/to/b/file_b
* 访问其他http路径,对应的文件路径还是在rootPath内
* virtualPath=
*/
@JSONField(name = "http.virtualPath")
private String httpVirtualPath;
/**
* 禁止后缀的文件使用mmap缓存使用,隔开
* 例如赋值为 .mp4,.flv
* 那么访问后缀为.mp4与.flv 的文件不缓存
* forbidCacheSuffix=
*/
@JSONField(name = "http.forbidCacheSuffix")
private String httpForbidCacheSuffix;
/**
* 可以把http代理前真实客户端ip放在http头中https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
* 切勿暴露此key否则可能导致伪造客户端ip
* forwarded_ip_header=
*/
@JSONField(name = "http.forwarded_ip_header")
private String httpForwardedIpHeader;
// ----------------------------------------------- multicast -----------------------------------------------
/**
* rtp组播截止组播ip地址
* addrMax=239.255.255.255
*/
@JSONField(name = "multicast.addrMax")
private String multicastAddrMax;
/**
* rtp组播起始组播ip地址
* addrMin=239.0.0.0
*/
@JSONField(name = "multicast.addrMin")
private String multicastAddrMin;
/**
* 组播udp ttl
* udpTTL=64
*/
@JSONField(name = "multicast.udpTTL")
private Integer multicastUdpTtl;
// ----------------------------------------------- record -----------------------------------------------
/**
* mp4录制或mp4点播的应用名通过限制应用名可以防止随意点播
* 点播的文件必须放置在此文件夹下
* appName=record
*/
@JSONField(name = "record.appName")
private String recordAppName;
/**
* mp4录制写文件缓存单位BYTE,调整参数可以提高文件io性能
* fileBufSize=65536
*/
@JSONField(name = "record.fileBufSize")
private Integer recordFileBufSize;
/**
* mp4录制保存mp4点播根路径
* 可以为相对(相对于本可执行程序目录)或绝对路径
* filePath=./www
*/
@JSONField(name = "record.filePath")
private String recordFilePath;
/**
* mp4录制切片时间单位秒
* fileSecond=3600
*/
@JSONField(name = "record.fileSecond")
private Integer recordFileSecond;
/**
* mp4点播每次流化数据量单位毫秒
* 减少该值可以让点播数据发送量更平滑增大该值则更节省cpu资源
* sampleMS=500
*/
@JSONField(name = "record.sampleMS")
private Integer recordSampleMs;
/**
* mp4录制完成后是否进行二次关键帧索引写入头部
* fastStart=0
*/
@JSONField(name = "record.fastStart")
private Integer recordFastStart;
/**
* MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件
* fileRepeat=0
*/
@JSONField(name = "record.fileRepeat")
private Integer recordFileRepeat;
/**
* MP4录制是否当做播放器参与播放人数统计
* mp4_as_player=0
*/
@JSONField(name = "record.mp4_as_player")
private Integer recordMp4AsPlayer;
// ----------------------------------------------- rtmp -----------------------------------------------
/**
* rtmp必须在此时间内完成握手否则服务器会断开链接单位秒
* handshakeSecond=15
*/
@JSONField(name = "rtmp.handshakeSecond")
private Integer rtmpHandshakeSecond;
/**
* rtmp超时时间如果该时间内未收到客户端的数据
* 或者tcp发送缓存超过这个时间则会断开连接单位秒
* keepAliveSecond=15
*/
@JSONField(name = "rtmp.keepAliveSecond")
private Integer rtmpKeepAliveSecond;
/**
* 在接收rtmp推流时是否重新生成时间戳(很多推流器的时间戳着实很烂)
* modifyStamp=0
*/
@JSONField(name = "rtmp.modifyStamp")
private Integer rtmpModifyStamp;
/**
* rtmp服务器监听端口
* port=1935
*/
@JSONField(name = "rtmp.port")
private Integer rtmpPort = 0;
/**
* rtmps服务器监听地址
* sslport=0
*/
@JSONField(name = "rtmp.sslport")
private Integer rtmpSslPort = 0;
// ----------------------------------------------- rtp -----------------------------------------------
/**
* 音频mtu大小该参数限制rtp最大字节数推荐不要超过1400
* 加大该值会明显增加直播延时
* audioMtuSize=600
*/
@JSONField(name = "rtp.audioMtuSize")
private Integer rtpAudioMtuSize;
/**
* 视频mtu大小该参数限制rtp最大字节数推荐不要超过1400
* videoMtuSize=1400
*/
@JSONField(name = "rtp.videoMtuSize")
private Integer rtpVideoMtuSize;
/**
* rtp包最大长度限制单位KB,主要用于识别TCP上下文破坏时获取到错误的rtp
* rtpMaxSize=10
*/
@JSONField(name = "rtp.rtpMaxSize")
private Integer rtpMaxSize;
// ----------------------------------------------- rtp_proxy -----------------------------------------------
/**
* 导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
* dumpDir=
*/
@JSONField(name = "rtp_proxy.dumpDir")
private String proxyDumpDir;
/**
* udp和tcp代理服务器支持rtp(必须是ts或ps类型)代理
* port=10000
*/
@JSONField(name = "rtp_proxy.port")
private Integer proxyPort;
/**
* rtp超时时间单位秒
* timeoutSec=15
*/
@JSONField(name = "rtp_proxy.timeoutSec")
private Integer proxyTimeoutSec;
/**
* 随机端口范围最少确保36个端口
* 该范围同时限制rtsp服务器udp端口范围
* port_range=30000-35000
*/
@JSONField(name = "rtp_proxy.port_range")
private String proxyPortRange;
/**
* rtp h264 负载的pt
* h264_pt=98
*/
@JSONField(name = "rtp_proxy.h264_pt")
private Integer proxyH264Pt;
/**
* rtp h265 负载的pt
* h265_pt=99
*/
@JSONField(name = "rtp_proxy.h265_pt")
private Integer proxyH265Pt;
/**
* rtp ps 负载的pt
* ps_pt=96
*/
@JSONField(name = "rtp_proxy.ps_pt")
private Integer proxyPsPt;
/**
* rtp ts 负载的pt
* ts_pt=33
*/
@JSONField(name = "rtp_proxy.ts_pt")
private Integer proxyTsPt;
/**
* rtp opus 负载的pt
* opus_pt=100
*/
@JSONField(name = "rtp_proxy.opus_pt")
private Integer proxyOpusPt;
/**
* rtp g711u 负载的pt
* g711u_pt=0
*/
@JSONField(name = "rtp_proxy.g711u_pt")
private Integer proxyG711UPt;
/**
* rtp g711a 负载的pt
* g711a_pt=8
*/
@JSONField(name = "rtp_proxy.g711a_pt")
private Integer proxyG711APt;
// ----------------------------------------------- rtc -----------------------------------------------
/**
* rtc播放推流播放超时时间
* timeoutSec=15
*/
@JSONField(name = "rtc.timeoutSec")
private Integer rtcTimeoutSec;
/**
* 本机对rtc客户端的可见ip作为服务器时一般为公网ip可有多个','分开当置空时会自动获取网卡ip
* 同时支持环境变量以$开头"$EXTERN_IP"; 请参考https://github.com/ZLMediaKit/ZLMediaKit/pull/1786
* externIP=
*/
@JSONField(name = "rtc.externIP")
private String rtcExternIp;
/**
* rtc udp服务器监听端口号所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据
* 该端口是多线程的同时支持客户端网络切换导致的连接迁移
* 需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致
* port=8000
*/
@JSONField(name = "rtc.port")
private Integer rtcPort;
/**
* 设置remb比特率非0时关闭twcc并开启remb该设置在rtc推流时有效可以控制推流画质
* 目前已经实现twcc自动调整码率关闭remb根据真实网络状况调整码率
* rembBitRate=0
*/
@JSONField(name = "rtc.rembBitRate")
private Integer rtcRembBitRate;
/**
* rtc支持的音频codec类型,在前面的优先级更高
* 以下范例为所有支持的音频codec
* preferredCodecA=PCMU,PCMA,opus,mpeg4-generic
*/
@JSONField(name = "rtc.preferredCodecA")
private String rtcPreferredCodecA;
/**
* rtc支持的视频codec类型,在前面的优先级更高
* 以下范例为所有支持的视频codec
* preferredCodecV=H264,H265,AV1X,VP9,VP8
*/
@JSONField(name = "rtc.preferredCodecV")
private String rtcPreferredCodecV;
// ----------------------------------------------- srt -----------------------------------------------
/**
* srt播放推流播放超时时间,单位秒
* timeoutSec=5
*/
@JSONField(name = "srt.timeoutSec")
private Integer srtTimeoutSec;
/**
* srt udp服务器监听端口号所有srt客户端将通过该端口传输srt数据
* 该端口是多线程的同时支持客户端网络切换导致的连接迁移
* port=9000
*/
@JSONField(name = "srt.port")
private Integer srtPort;
/**
* srt 协议中延迟缓存的估算参数在握手阶段估算rtt ,然后latencyMul*rtt 为最大缓存时长此参数越大表示等待重传的时长就越大
* latencyMul=4
*/
@JSONField(name = "srt.latencyMul")
private Integer srtLatencyMul;
/**
* 包缓存的大小
* pktBufSize=8192
*/
@JSONField(name = "srt.pktBufSize")
private Integer srtPktBufSize;
// ----------------------------------------------- rtsp -----------------------------------------------
/**
* rtsp专有鉴权方式是采用base64还是md5方式
* authBasic=0
*/
@JSONField(name = "rtsp.authBasic")
private Integer rtspAuthBasic;
/**
* rtsp拉流推流代理是否是直接代理模式
* 直接代理后支持任意编码格式但是会导致GOP缓存无法定位到I帧可能会导致开播花屏
* 并且如果是tcp方式拉流如果rtp大于mtu会导致无法使用udp方式代理
* 假定您的拉流源地址不是264或265或AAC那么你可以使用直接代理的方式来支持rtsp代理
* 如果你是rtsp推拉流但是webrtc播放也建议关闭直接代理模式
* 因为直接代理时rtp中可能没有sps pps,会导致webrtc无法播放; 另外webrtc也不支持Single NAL Unit Packets类型rtp
* 默认开启rtsp直接代理rtmp由于没有这些问题是强制开启直接代理的
* directProxy=1
*/
@JSONField(name = "rtsp.directProxy")
private Integer rtspDirectProxy;
/**
* rtsp必须在此时间内完成握手否则服务器会断开链接单位秒
* handshakeSecond=15
*/
@JSONField(name = "rtsp.handshakeSecond")
private Integer rtspHandshakeSecond;
/**
* rtsp超时时间如果该时间内未收到客户端的数据
* 或者tcp发送缓存超过这个时间则会断开连接单位秒
* keepAliveSecond=15
*/
@JSONField(name = "rtsp.keepAliveSecond")
private Integer rtspKeepAliveSecond;
/**
* rtsp服务器监听地址
* port=554
*/
@JSONField(name = "rtsp.port")
private Integer rtspPort = 0;
/**
* rtsps服务器监听地址
* sslport=0
*/
@JSONField(name = "rtsp.sslport")
private Integer rtspSslPort = 0;
// ----------------------------------------------- shell -----------------------------------------------
/**
* 调试telnet服务器接受最大bufffer大小
* maxReqSize=1024
*/
@JSONField(name = "shell.maxReqSize")
private Integer shellMaxReqSize;
/**
* 调试telnet服务器监听端口
* port=0
*/
@JSONField(name = "shell.port")
private Integer shellPort;
public void refreshHook(String ip, String port, StreamMediaServerConfig server) {
String host = ip + ":" + port;
this.hookOnFlowReport = StrUtil.format("http://{}/index/hook/on_flow_report", host);
this.hookOnHttpAccess = StrUtil.format("http://{}/index/hook/on_http_access", host);
this.hookOnPlay = StrUtil.format("http://{}/index/hook/on_play", host);
this.hookOnPublish = StrUtil.format("http://{}/index/hook/on_publish", host);
this.hookOnRecordMp4 = StrUtil.format("http://{}/index/hook/on_record_mp4", host);
this.hookOnRecordTs = StrUtil.format("http://{}/index/hook/on_record_ts", host);
this.hookOnRtspAuth = StrUtil.format("http://{}/index/hook/on_rtsp_auth", host);
this.hookOnRtspRealm = StrUtil.format("http://{}/index/hook/on_rtsp_realm", host);
this.hookOnShellLogin = StrUtil.format("http://{}/index/hook/on_shell_login", host);
this.hookOnStreamChanged = StrUtil.format("http://{}/index/hook/on_stream_changed", host);
this.hookOnStreamNoneReader = StrUtil.format("http://{}/index/hook/on_stream_none_reader", host);
this.hookOnStreamNotFound = StrUtil.format("http://{}/index/hook/on_stream_not_found", host);
this.hookOnServerStarted = StrUtil.format("http://{}/index/hook/on_server_started", host);
this.hookOnServerKeepalive = StrUtil.format("http://{}/index/hook/on_server_keepalive", host);
// this.hookOnSendRtpStopped = StrUtil.format("http://{}/index/hook/on_send_rtp_stopped", host);
this.hookOnSendRtpStopped = "";
}
}

View File

@ -1,30 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/30 10:50
* @Description: 节点基础信息
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServerInfo implements Serializable {
/**
* 节点地址
*/
private String apiHost;
/**
* 节点端口
*/
private Integer apiPort;
/**
* 节点秘钥
*/
private String secretKey;
}

View File

@ -1,108 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.dite.znpt.exception.ServiceException;
import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @Author: huise23
* @Date: 2022/8/30 10:50
* @Description: 节点信息
*/
@Data
@NoArgsConstructor
public class ServerItem implements Serializable {
private static final long serialVersionUID = 2460404295026548536L;
/**
* 播流最大并发个数
*/
public static final Integer MAX_PLAY_COUNT = 10000;
/**
* 节点信息ID
*/
private String configId;
/**
* 流ID前缀
*/
private String streamPrefix;
/**
* 节点信息ID
*/
private ServerInfo server;
/**
* 节点格式信息
*/
private List<StreamMediaFormat> formatList;
/**
* 节点配置信息
*/
private ServerConfig config;
/**
* 当前流信息
*/
private List<MediaResp> media;
/**
* 节点状态是否正常
*/
private Boolean status;
/**
* 流媒体服务器已用的会话句柄
*/
private Set<Integer> usedSn;
public ServerItem(StreamMediaServerConfig server, List<StreamMediaFormat> formatList) {
this.streamPrefix = server.getStreamPrefix();
this.server = new ServerInfo(server.getApiHost(), server.getApiPort(), server.getSecretKey());
this.formatList = formatList;
this.status = false;
this.usedSn = new HashSet<>();
}
public String genPlaySsrc(String channelCode) {
if (this.usedSn.size() >= MAX_PLAY_COUNT) {
throw new ServiceException("ssrc已经用完!");
}
int sn;
for (sn = 0; sn < MAX_PLAY_COUNT; sn++) {
if (!this.usedSn.contains(sn)) {
this.usedSn.add(sn);
break;
}
}
//return StrUtil.format("0{}{}", StrUtil.blankToDefault(streamPrefix, channelCode.substring(3, 8)), NumberUtil.decimalFormat("0000", sn));
return channelCode;
}
public void releaseSsrc(String ssrc) {
try {
Integer sn = NumberUtil.parseInt(ssrc.substring(6));
usedSn.remove(sn);
} catch (Exception ignored) {
}
}
public void setMedia(List<MediaResp> media) {
this.media = media;
if (CollUtil.isNotEmpty(media)) {
media.forEach(item -> {
try {
Integer sn = NumberUtil.parseInt(StrUtil.isBlank(streamPrefix) ? item.getStream().substring(6) : item.getStream().replace("0" + streamPrefix, ""));
usedSn.add(sn);
} catch (Exception ignored) {
}
});
}
}
}

View File

@ -1,48 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
/**
* @Author: huise23
* @Date: 2022/8/30 9:24
* @Description:
*/
@Data
public class BaseEventReq {
/**
* 服务器id,通过配置文件设置
*/
private String mediaServerId;
/**
* 流应用名
*/
private String app;
/**
* TCP链接唯一ID
*/
private String id;
/**
* 播放器ip
*/
private String ip;
/**
* 播放url参数
*/
private String params;
/**
* 播放器端口号
*/
private Integer port;
/**
* 播放的协议可能是rtsprtmphttp
*/
private String schema;
/**
* 流ID
*/
private String stream;
/**
* 流虚拟主机
*/
private String vhost;
}

View File

@ -1,42 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @Author: huise23
* @Date: 2022/8/29 10:33
* @Description: 请求响应基类
*/
@Data
@Accessors(chain = true)
public class BaseEventResp {
/**
* code == 0时代表完全成功
*/
private Integer code;
/**
* 失败提示
*/
private String msg;
/**
* 失败具体原因
*/
private String err;
/**
* 该rtsp流是否需要rtsp专有鉴权空字符串代码不需要鉴权
*/
private String realm;
/**
* 是否关闭推流或拉流
*/
private Boolean close;
public BaseEventResp setSuccess(){
return this.setCode(0).setMsg("success").setErr("");
}
public static BaseEventResp success() {
return new BaseEventResp().setSuccess();
}
}

View File

@ -1,26 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:04
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class FlowReportReq extends BaseEventReq {
/**
* tcp链接维持时间单位秒
*/
private Integer duration;
/**
* true为播放器false为推流器
*/
private Boolean player;
/**
* 耗费上下行流量总和单位字节
*/
private Integer totalBytes;
}

View File

@ -1,29 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import cn.hutool.core.lang.Dict;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:10
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class HttpAccessReq extends BaseEventReq {
/**
* http客户端请求header
*/
private Dict header;
/**
* 访问路径是文件还是目录
*/
@JSONField(name = "is_dir")
private Boolean isDir;
/**
* 请求访问的文件或目录
*/
private String path;
}

View File

@ -1,34 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Author: huise23
* @Date: 2022/8/30 9:15
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class HttpAccessResp extends BaseEventResp {
/**
* 该客户端能访问或被禁止的顶端目录如果为空字符串则表述为当前目录
*/
private String path;
/**
* 本次授权结果的有效期单位秒
*/
private Integer second;
/**
* 服务器id,通过配置文件设置
*/
private String mediaServerId;
public static HttpAccessResp success() {
HttpAccessResp resp = new HttpAccessResp();
resp.setSuccess();
return resp.setSecond(600).setPath("");
}
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:19
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class PlayReq extends BaseEventReq {
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:57
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class PublishReq extends BaseEventReq{
}

View File

@ -1,81 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:27
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class PublishResp extends BaseEventResp {
/**
* 是否转换成hls协议
*/
@JSONField(name = "enable_hls")
private Boolean enableHls;
/**
* 是否允许mp4录制
*/
@JSONField(name = "enable_mp4")
private Boolean enableMp4;
/**
* 是否转rtsp协议
*/
@JSONField(name = "enable_rtsp")
private Boolean enableRtsp;
/**
* 是否转rtmp/flv协议
*/
@JSONField(name = "enable_rtmp")
private Boolean enableRtmp;
/**
* 是否转http-ts/ws-ts协议
*/
@JSONField(name = "enable_ts")
private Boolean enableTs;
/**
* 是否转http-fmp4/ws-fmp4协议
*/
@JSONField(name = "enable_fmp4")
private Boolean enableFmp4;
/**
* 转协议时是否开启音频
*/
@JSONField(name = "enable_audio")
private Boolean enableAudio;
/**
* 转协议时无音频是否添加静音aac音频
*/
@JSONField(name = "add_mute_audio")
private Boolean addMuteAudio;
/**
* mp4录制文件保存根目录置空使用默认
*/
@JSONField(name = "mp4_save_path")
private String mp4SavePath;
/**
* mp4录制切片大小单位秒
*/
@JSONField(name = "mp4_max_second")
private Integer mp4MaxSecond;
/**
* hls文件保存保存根目录置空使用默认
*/
@JSONField(name = "hls_save_path")
private String hlsSavePath;
/**
* 断连续推延时单位毫秒置空使用配置文件默认值
*/
@JSONField(name = "continue_push_ms")
private Long continuePushMs;
public static PublishResp success() {
PublishResp resp = new PublishResp();
resp.setSuccess();
return resp;
}
}

View File

@ -1,48 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:32
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RecordMp4Req extends BaseEventReq {
/**
* 文件名
*/
@JSONField(name = "file_name")
private String fileName;
/**
* 文件绝对路径
*/
@JSONField(name = "file_path")
private String filePath;
/**
* 文件大小单位字节
*/
@JSONField(name = "file_size")
private Integer fileSize;
/**
* 文件所在目录路径
*/
private String folder;
/**
* 开始录制时间戳
*/
@JSONField(name = "start_time")
private Integer startTime;
/**
* 录制时长单位秒
*/
@JSONField(name = "time_len")
private Integer timeLen;
/**
* http/rtsp/rtmp点播相对url路径
*/
private String url;
}

View File

@ -1,30 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:35
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RtspAuthReq extends BaseEventReq {
/**
* 请求的密码是否必须为明文(base64鉴权需要明文密码)
*/
@JSONField(name = "must_no_encrypt")
private Boolean mustNoEncrypt;
/**
* rtsp播放鉴权加密realm
*/
private String realm;
/**
* 播放用户名
*/
@JSONField(name = "user_name")
private String userName;
}

View File

@ -1,30 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Author: huise23
* @Date: 2022/8/30 9:35
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class RtspAuthResp extends BaseEventResp {
/**
* 用户密码
*/
private String passwd;
/**
* 用户密码是否已加密
*/
private Boolean encrypted;
public static RtspAuthResp success() {
RtspAuthResp resp = new RtspAuthResp();
resp.setSuccess();
return resp.setEncrypted(false);
}
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:33
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RtspRealmReq extends BaseEventReq {
}

View File

@ -1,16 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import com.dite.znpt.monitor.media.zlm.dto.resp.StatisticResp;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:43
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ServerKeepaliveReq extends BaseEventReq {
private StatisticResp data;
}

View File

@ -1,24 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:37
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ShellLoginReq extends BaseEventReq {
/**
* 终端登录用户密码
*/
private String passwd;
/**
* 终端登录用户名
*/
@JSONField(name = "user_name")
private String userName;
}

View File

@ -1,19 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:38
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class StreamChangedReq extends MediaResp {
/**
* 流注册或注销
*/
private Boolean regist;
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:40
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class StreamNoneReaderReq extends BaseEventReq {
}

View File

@ -1,14 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/30 9:42
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class StreamNotFoundReq extends BaseEventReq {
}

View File

@ -1,18 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/29 10:33
* @Description: 请求响应基类
*/
@Data
public class BaseReq implements Serializable {
/**
* api操作密钥(配置文件配置)如果操作ip是127.0.0.1则不需要此参数
*/
private String secret;
}

View File

@ -1,18 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 10:51
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CloseStreamReq extends StreamReq {
/**
* 是否强制关闭(有人在观看是否还关闭)
*/
private Integer force;
}

View File

@ -1,45 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 12:26
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class FFmpegSourceReq extends BaseReq {
/**
* FFmpeg拉流地址,支持任意协议或格式(只要FFmpeg支持即可)
*/
@JSONField(name = "src_url")
private String srcUrl;
/**
* FFmpeg rtmp推流地址一般都是推给自己例如rtmp://127.0.0.1/live/stream_form_ffmpeg
*/
@JSONField(name = "dst_url")
private String dstUrl;
/**
* FFmpeg推流成功超时时间
*/
@JSONField(name = "timeout_ms")
private Integer timeoutMs;
/**
* 是否开启hls录制
*/
@JSONField(name = "enable_hls")
private Integer enableHls;
/**
* 是否开启mp4录制
*/
@JSONField(name = "enable_mp4")
private Integer enableMp4;
/**
* 配置文件中FFmpeg命令参数模板key(非内容)置空则采用默认模板:ffmpeg.cmd
*/
@JSONField(name = "ffmpeg_cmd_key")
private String ffmpegCmdKey;
}

View File

@ -1,25 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 17:21
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class GetAllSessionReq extends BaseReq{
/**
*筛选本机端口例如筛选rtsp链接554
*/
@JSONField(name = "local_port")
private Integer localPort;
/**
*筛选客户端ip
*/
@JSONField(name = "peer_ip")
private String peerIp;
}

View File

@ -1,24 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 13:04
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class GetMp4RecordFileReq extends StreamReq {
/**
* 流的录像日期格式为2020-02-01,如果不是完整的日期那么是搜索录像文件夹列表否则搜索对应日期下的mp4文件列表
*/
private String period;
/**
* 自定义搜索路径与startRecord方法中的customized_path一样默认为配置文件的路径
*/
@JSONField(name = "customized_path")
private String customizedPath;
}

View File

@ -1,20 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 17:24
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
public class IdReq extends BaseReq {
/**
* 客户端唯一id可以通过getAllSession接口获取
*/
private Long id;
}

View File

@ -1,20 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 17:30
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
public class KeyReq extends BaseReq {
/**
* addStreamProxy接口返回的key
*/
private String key;
}

View File

@ -1,29 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 13:11
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RecordReq extends StreamReq {
/**
* 0为hls1为mp4
*/
private Integer type;
/**
* 录像保存目录
*/
@JSONField(name = "customized_path")
private String customizedPath;
/**
* mp4录像切片时间大小,单位秒置0则采用配置项
*/
@JSONField(name = "max_second")
private Integer maxSecond;
}

View File

@ -1,45 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @Author: huise23
* @Date: 2022/8/29 13:17
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
public class RtpServerReq extends BaseReq {
/**
* 接收端口0则为随机端口
*/
private Integer port;
/**
* 启用UDP监听的同时是否监听TCP端口
*/
@JSONField(name = "enable_tcp")
private Integer enableTcp;
/**
* 该端口绑定的流ID该端口只能创建这一个流(而不是根据ssrc创建多个)
*/
@JSONField(name = "stream_id")
private String streamId;
public RtpServerReq(Integer port, Integer enableTcp, String streamId) {
this.port = port;
this.enableTcp = enableTcp;
this.streamId = streamId;
}
public RtpServerReq(Integer enableTcp, String streamId) {
this(0, enableTcp, streamId);
}
public RtpServerReq(String streamId) {
this(1, streamId);
}
}

View File

@ -1,53 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 13:23
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class SendRtpReq extends StreamReq {
/**
* 推流的rtp的ssrc,指定不同的ssrc可以同时推流到多个服务器
*/
private String ssrc;
/**
* 目标ip或域名
*/
@JSONField(name = "dst_url")
private String dstUrl;
/**
* 目标端口
*/
@JSONField(name = "dst_port")
private Integer dstPort;
/**
* 是否为udp模式,否则为tcp模式
*/
@JSONField(name = "is_udp")
private Integer isUdp;
/**
* 使用的本机端口为0或不传时默认为随机端口
*/
@JSONField(name = "src_port")
private Integer srcPort;
/**
* 使用的本机端口为0或不传时默认为随机端口
*/
private Integer pt;
/**
* 发送时rtp的负载类型为1时负载为ps为0时为es不传时默认为1
*/
@JSONField(name = "use_ps")
private Integer usePs;
/**
* 当use_ps 为0时有效为1时发送音频为0时发送视频不传时默认为0
*/
@JSONField(name = "only_audio")
private Integer onlyAudio;
}

View File

@ -1,29 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 13:14
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class SnapReq extends BaseReq {
/**
* 需要截图的url可以是本机的也可以是远程主机的
*/
private String url;
/**
* 截图失败超时时间防止FFmpeg一直等待截图
*/
@JSONField(name = "timeout_sec")
private Integer timeoutSec;
/**
* 截图的过期时间该时间内产生的截图都会作为缓存返回
*/
@JSONField(name = "expire_sec")
private Integer expireSec;
}

View File

@ -1,22 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 17:30
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
public class StreamIdReq extends BaseReq {
/**
* RTP的ssrc16进制字符串或者是流的id(openRtpServer接口指定)
*/
@JSONField(name = "stream_id")
private String streamId;
}

View File

@ -1,89 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 12:19
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class StreamProxyReq extends StreamReq {
/**
* 拉流地址例如rtmp://live.hkstv.hk.lxdns.com/live/hks2 Y
*/
private String url;
/**
* 拉流重试次数默认为-1无限重试
*/
@JSONField(name = "retry_count")
private Integer retryCount;
/**
* rtsp拉流时拉流方式0tcp1udp2组播
*/
@JSONField(name = "rtp_type")
private Integer rtpType;
/**
* 拉流超时时间单位秒float类型
*/
@JSONField(name = "timeout_sec")
private Integer timeoutSec;
/**
* 是否转换成hls协议
*/
@JSONField(name = "enable_hls")
private Integer enableHls;
/**
* 是否允许mp4录制
*/
@JSONField(name = "enable_mp4")
private Integer enableMp4;
/**
* 是否转rtsp协议
*/
@JSONField(name = "enable_rtsp")
private Integer enableRtsp;
/**
* 是否转rtmp/flv协议
*/
@JSONField(name = "enable_rtmp")
private Integer enableRtmp;
/**
* 是否转http-ts/ws-ts协议
*/
@JSONField(name = "enable_ts")
private Integer enableTs;
/**
* 是否转http-fmp4/ws-fmp4协议
*/
@JSONField(name = "enable_fmp4")
private Integer enableFmp4;
/**
* 转协议时是否开启音频
*/
@JSONField(name = "enable_audio")
private Integer enableAudio;
/**
* 转协议时无音频是否添加静音aac音频
*/
@JSONField(name = "add_mute_audio")
private Integer addMuteAudio;
/**
* mp4录制文件保存根目录置空使用默认
*/
@JSONField(name = "mp4_save_path")
private String mp4SavePath;
/**
* mp4录制切片大小单位秒
*/
@JSONField(name = "mp4_max_second")
private Integer mp4MaxSecond;
/**
* hls文件保存保存根目录置空使用默认
*/
@JSONField(name = "hls_save_path")
private String hlsSavePath;
}

View File

@ -1,35 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 13:50
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class StreamPusherProxyReq extends StreamReq {
/**
* 目标转推url带参数需要自行url转义
*/
@JSONField(name = "dst_url")
private String dstUrl;
/**
* 转推失败重试次数默认无限重试
*/
@JSONField(name = "retry_count")
private Integer retryCount;
/**
* rtsp拉流时拉流方式0tcp1udp2组播
*/
@JSONField(name = "rtp_type")
private Integer rtpType;
/**
* 拉流超时时间单位秒float类型
*/
@JSONField(name = "timeout_sec")
private Float timeoutSec;
}

View File

@ -1,32 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.req;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Author: huise23
* @Date: 2022/8/29 13:05
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class StreamReq extends BaseReq {
/**
* 协议例如 rtsp或rtmp
*/
private String schema;
/**
* 筛选虚拟主机例如__defaultVhost__
*/
private String vhost;
/**
* 筛选应用名例如 live
*/
private String app;
/**
* 筛选流id例如 test
*/
private String stream;
}

View File

@ -1,64 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/29 10:33
* @Description: 请求响应基类
*/
@Data
public class BaseResp {
/**
* code == 0时代表完全成功
*/
private Integer code;
/**
* 失败提示
*/
private String msg;
/**
* 失败具体原因
*/
private String result;
/**
* 返回数据
*/
private String data;
/**
* 配置项变更个数
*/
private Integer changed;
/**
* false:未录制,true:正在录制
*/
private Boolean status;
/**
* 接收端口方便获取随机端口号
*/
private Integer port;
/**
* 是否找到记录并关闭
*/
private Integer hit;
public String getMsg() {
return StrUtil.format("{}:{}", code, msg);
}
public Boolean isSuccess() {
return code == 0;
}
public <T> T getData(Class<T> clazz) {
return JSON.parseObject(data, clazz);
}
public <T> List<T> getList(Class<T> clazz) {
return JSON.parseArray(data, clazz);
}
}

View File

@ -1,25 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 11:59
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CloseStreamResp extends BaseResp {
/**
* 筛选命中的流个数
*/
@JSONField(name = "count_hit")
private Integer countHit;
/**
* 被关闭的流个数可能小于count_hit
*/
@JSONField(name = "count_closed")
private Integer countClosed;
}

View File

@ -1,61 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import com.dite.znpt.monitor.media.zlm.dto.event.BaseEventReq;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/29 11:18
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class MediaResp extends BaseEventReq implements Serializable {
private static final long serialVersionUID = -8710934021370904914L;
/**
* 本协议观看人数
*/
private Integer readerCount;
/**
* 观看总人数包括hls/rtsp/rtmp/http-flv/ws-flv
*/
private Integer totalReaderCount;
/**
* 客户端和服务器网络信息可能为null类型
*/
private OriginSock originSock;
/**
* 产生源类型包括 unknown = 0, rtmp_push = 1, rtsp_push = 2, rtp_push = 3, pull = 4, ffmpeg_pull = 5, mp4_vod = 6, device_chn = 7
*/
private Integer originType;
/**
* 产生源类型
*/
private String originTypeStr;
/**
* 产生源的url
*/
private String originUrl;
/**
* GMT unix系统时间戳单位秒
*/
private Long createStamp;
/**
* 存活时间单位秒
*/
private Long aliveSecond;
/**
* 数据产生速度单位byte/s
*/
private Long bytesSpeed;
/**
* 音视频轨道
*/
private List<Track> tracks;
}

View File

@ -1,23 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import lombok.Data;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/29 13:08
* @Description:
*/
@Data
public class Mp4RecordFileResp {
/**
* 文件列表
*/
private List<String> paths;
/**
* 根路径
*/
private String rootPath;
}

View File

@ -1,26 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/29 11:20
* @Description:
*/
@Data
public class OriginSock implements Serializable {
private static final long serialVersionUID = 5628294142872524316L;
private String identifier;
@JSONField(name = "local_ip")
private String localIp;
@JSONField(name = "local_port")
private Integer localPort;
@JSONField(name = "peer_ip")
private String peerIp;
@JSONField(name = "peer_port")
private Integer peerPort;
}

View File

@ -1,39 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Author: huise23
* @Date: 2022/8/29 12:32
* @Description:
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RtpInfoResp extends BaseResp {
/**
* 是否存在
*/
private Boolean exist;
/**
* 推流客户端ip
*/
@JSONField(name = "peer_ip")
private String peerIp;
/**
* 客户端端口号
*/
@JSONField(name = "peer_port")
private Integer peerPort;
/**
* 本地监听的网卡ip
*/
@JSONField(name = "local_ip")
private String localIp;
/**
* 本地监听端口号
*/
@JSONField(name = "local_port")
private Integer localPort;
}

View File

@ -1,22 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
/**
* @Author: huise23
* @Date: 2022/8/29 13:22
* @Description:
*/
@Data
public class RtpServerResp {
/**
* 绑定的端口号
*/
private Integer port;
/**
* 绑定的流ID
*/
@JSONField(name = "stream_id")
private String streamId;
}

View File

@ -1,41 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
/**
* @Author: huise23
* @Date: 2022/8/29 12:04
* @Description:
*/
@Data
public class SessionResp {
/**
* 该tcp链接唯一id
*/
private Long id;
/**
* 本机网卡ip
*/
@JSONField(name = "local_ip")
private String localIp;
/**
* 本机端口号 (这是个rtmp播放器或推流器)
*/
@JSONField(name = "local_port")
private Integer localPort;
/**
* 客户端ip
*/
@JSONField(name = "peer_ip")
private String peerIp;
/**
* 客户端端口号
*/
@JSONField(name = "peer_port")
private Integer peerPort;
/**
* 客户端TCPSession typeid
*/
private String typeid;
}

View File

@ -1,28 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import lombok.Data;
/**
* @Author: huise23
* @Date: 2022/8/29 13:47
* @Description:
*/
@Data
public class StatisticResp {
private Integer Buffer;
private Integer BufferLikeString;
private Integer BufferList;
private Integer BufferRaw;
private Integer Frame;
private Integer FrameImp;
private Integer MediaSource;
private Integer MultiMediaSourceMuxer;
private Integer RtmpPacket;
private Integer RtpPacket;
private Integer Socket;
private Integer TcpClient;
private Integer TcpServer;
private Integer TcpSession;
private Integer UdpServer;
private Integer UdpSession;
}

View File

@ -1,22 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import lombok.Data;
/**
* @Author: huise23
* @Date: 2022/8/29 10:40
* @Description: 各epoll(或select)线程负载以及延时
*/
@Data
public class ThreadsLoadResp {
/**
* 该线程延时
*/
private Integer delay;
/**
* 该线程负载
*/
private Integer load;
}

View File

@ -1,61 +0,0 @@
package com.dite.znpt.monitor.media.zlm.dto.resp;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: huise23
* @Date: 2022/8/29 11:25
* @Description:
*/
@Data
public class Track implements Serializable {
private static final long serialVersionUID = 5317048895056912057L;
/**
* 音频通道数
*/
private Integer channels;
/**
* H264 = 0, H265 = 1, AAC = 2, G711A = 3, G711U = 4
*/
@JSONField(name = "codec_id")
private Integer codecId;
/**
* 编码类型名称
*/
@JSONField(name = "codec_id_name")
private String codecIdName;
/**
* Video = 0, Audio = 1
*/
@JSONField(name = "codec_type")
private Integer codecType;
/**
* 轨道是否准备就绪
*/
private Boolean ready;
/**
* 音频采样位数
*/
@JSONField(name = "sample_bit")
private Integer sampleBit;
/**
* 音频采样率
*/
@JSONField(name = "sample_rate")
private Integer sampleRate;
/**
* 视频fps
*/
private Integer fps;
/**
* 视频高
*/
private Integer height;
/**
* 视频宽
*/
private Integer width;
}

View File

@ -1,43 +0,0 @@
package com.dite.znpt.monitor.media.zlm.enums;
import lombok.Getter;
/**
* @Author: huise23
* @Date: 2022/8/31 10:29
* @Description:
*/
@Getter
public enum MediaFormatType {
/**
* FLV
*/
flv(".live.flv"),
/**
* MP4
*/
mp4(".live.mp4"),
/**
* HLS
*/
hls("/hls.m3u8"),
/**
* RTS
*/
rts(".live.ts");
private final String suffix;
MediaFormatType(String suffix) {
this.suffix = suffix;
}
public static String getSuffix(String name) {
for (MediaFormatType value : MediaFormatType.values()) {
if (value.name().equals(name)) {
return value.getSuffix();
}
}
return "";
}
}

View File

@ -1,223 +0,0 @@
package com.dite.znpt.monitor.media.zlm.impl;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.dite.znpt.monitor.media.zlm.ZlmApi;
import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.ServerInfo;
import com.dite.znpt.monitor.media.zlm.dto.req.*;
import com.dite.znpt.monitor.media.zlm.dto.resp.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/29 10:22
* @Description:
*/
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ZlmApiImpl implements ZlmApi {
private final HttpServletResponse response;
public <T extends BaseReq, V extends BaseResp> V post(ServerInfo server, String url, T req, Class<V> clazz) {
url = StrUtil.format("http://{}:{}/index/api/{}", server.getApiHost(), server.getApiPort(), url);
log.info("ZLM:" + url);
log.info("REQ:" + req);
req.setSecret(server.getSecretKey());
String respStr = HttpUtil.post(url, JSON.toJSONString(req));
V resp = JSON.parseObject(respStr, clazz);
if (resp.isSuccess()) {
return resp;
}
throw new RuntimeException(resp.getMsg());
}
public <T extends BaseReq> BaseResp post(ServerInfo server, String url, T req) {
return post(server, url, req, BaseResp.class);
}
public <V extends BaseResp> V post(ServerInfo server, String url, Class<V> clazz) {
return post(server, url, new BaseReq(), clazz);
}
public BaseResp post(ServerInfo server, String url) {
return post(server, url, new BaseReq());
}
@Override
public List<String> getApiList(ServerInfo server) {
return post(server, "getApiList").getList(String.class);
}
@Override
public List<ThreadsLoadResp> getThreadsLoad(ServerInfo server) {
return post(server, "getThreadsLoad").getList(ThreadsLoadResp.class);
}
@Override
public List<ThreadsLoadResp> getWorkThreadsLoad(ServerInfo server) {
return post(server, "getWorkThreadsLoad").getList(ThreadsLoadResp.class);
}
@Override
public List<ServerConfig> getServerConfig(ServerInfo server) {
return post(server, "getServerConfig").getList(ServerConfig.class);
}
// public static void main(String[] args) {
// ZlmApi zlmApi = new ZlmApiImpl(null);
// ServerInfo server = new ServerInfo("10.12.1.41", 8819, "035c73f7-bb6b-4889-a715-d9eb2d1925cc");
// List<MediaResp> config = zlmApi.getMediaList(server,new StreamReq());
// System.out.println(JSONUtil.toJsonPrettyStr(config));
// for (MediaResp mediaResp : config) {
// zlmApi.closeRtpServer(server,mediaResp.getStream() );
// }
// }
@Override
public Integer setServerConfig(ServerInfo server, ServerConfig config) {
BaseResp req = post(server, "setServerConfig", config);
return req.getChanged();
}
@Override
public Boolean restartServer(ServerInfo server) {
BaseResp req = post(server, "restartServer");
return req.isSuccess();
}
@Override
public List<MediaResp> getMediaList(ServerInfo server, StreamReq req) {
return post(server, "getMediaList", req).getList(MediaResp.class);
}
@Override
public CloseStreamResp closeStreams(ServerInfo server, CloseStreamReq req) {
return post(server, "close_streams", req, CloseStreamResp.class);
}
@Override
public List<SessionResp> getAllSession(ServerInfo server, GetAllSessionReq req) {
return post(server, "getAllSession", req).getList(SessionResp.class);
}
@Override
public Boolean kickSession(ServerInfo server, Long id) {
return post(server, "kick_session", new IdReq(id)).isSuccess();
}
@Override
public Integer kickSession(ServerInfo server, GetAllSessionReq req) {
return post(server, "kick_sessions", req, CloseStreamResp.class).getCountHit();
}
@Override
public String addStreamProxy(ServerInfo server, StreamProxyReq req) {
return post(server, "addStreamProxy", req).getData(Dict.class).getStr("key");
}
@Override
public Boolean delStreamProxy(ServerInfo server, String key) {
return post(server, "delStreamProxy", new KeyReq(key)).getData(Dict.class).getBool("flag");
}
@Override
public String addFfMpegSource(ServerInfo server, FFmpegSourceReq req) {
return post(server, "addFFmpegSource", req).getData(Dict.class).getStr("key");
}
@Override
public Boolean delFfMpegSource(ServerInfo server, String key) {
return post(server, "delFFmpegSource", new KeyReq(key)).getData(Dict.class).getBool("flag");
}
@Override
public RtpInfoResp getRtpInfo(ServerInfo server, String streamId) {
return post(server, "getRtpInfo", new StreamIdReq(streamId), RtpInfoResp.class);
}
@Override
public Mp4RecordFileResp getMp4RecordFile(ServerInfo server, GetMp4RecordFileReq req) {
return post(server, "getMp4RecordFile", req).getData(Mp4RecordFileResp.class);
}
@Override
public Boolean startRecord(ServerInfo server, RecordReq req) {
return BooleanUtil.toBoolean(post(server, "startRecord", req).getResult());
}
@Override
public Boolean stopRecord(ServerInfo server, RecordReq req) {
return BooleanUtil.toBoolean(post(server, "stopRecord", req).getResult());
}
@Override
public Boolean isRecording(ServerInfo server, RecordReq req) {
return post(server, "isRecording", req).getStatus();
}
@Override
public void getSnap(ServerInfo server, SnapReq req) throws IOException {
String url = StrUtil.format("http://{}:{}/index/api/getSnap", server.getApiHost(), server.getApiPort());
req.setSecret(server.getSecretKey());
url += "?" + HttpUtil.toParams(JSON.parseObject(JSON.toJSONString(req)));
HttpUtil.download(url, response.getOutputStream(), true);
}
@Override
public Integer openRtpServer(ServerInfo server, RtpServerReq req) {
return post(server, "openRtpServer", req).getPort();
}
@Override
public Boolean closeRtpServer(ServerInfo server, String streamId) {
BaseResp closeRtpServer = post(server, "closeRtpServer", new StreamIdReq(streamId));
return closeRtpServer.getHit() == 1;
}
@Override
public List<RtpServerResp> listRtpServer(ServerInfo server) {
return post(server, "listRtpServer").getList(RtpServerResp.class);
}
@Override
public Integer startSendRtp(ServerInfo server, SendRtpReq req) {
return post(server, "startSendRtp", req, RtpInfoResp.class).getLocalPort();
}
@Override
public Integer startSendRtpPassive(ServerInfo server, SendRtpReq req) {
return post(server, "startSendRtpPassive", req, RtpInfoResp.class).getLocalPort();
}
@Override
public Boolean stopSendRtp(ServerInfo server, SendRtpReq req) {
return post(server, "stopSendRtp", req).isSuccess();
}
@Override
public StatisticResp getStatistic(ServerInfo server) {
return post(server, "getStatistic").getData(StatisticResp.class);
}
@Override
public String addStreamPusherProxy(ServerInfo server, StreamPusherProxyReq req) {
return post(server, "addStreamPusherProxy", req).getData(Dict.class).getStr("key");
}
@Override
public Boolean delStreamPusherProxy(ServerInfo server, String key) {
return post(server, "delStreamPusherProxy", new KeyReq(key)).getData(Dict.class).getBool("flag");
}
}

View File

@ -1,132 +0,0 @@
package com.dite.znpt.monitor.media.zlm.impl;
import com.dite.znpt.monitor.media.zlm.ZlmService;
import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.event.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: huise23
* @Date: 2022/8/30 10:32
* @Description:
*/
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ZlmHookService {
private final ZlmService zlmService;
/**
* TODO 流量统计事件播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件
* 阈值通过配置文件general.flowThreshold配置此事件对回复不敏感
*/
public BaseEventResp onFlowReport(FlowReportReq req) {
log.debug("[ZLM] onFlowReport : {}", req);
return BaseEventResp.success();
}
/**
* TODO 访问http文件服务器上hls之外的文件时触发
*/
public HttpAccessResp onHttpAccess(HttpAccessReq req) {
log.debug("[ZLM] onHttpAccess : {}", req);
return HttpAccessResp.success();
}
/**
* TODO 播放器鉴权事件rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件
* 如果流不存在那么先触发on_play事件然后触发on_stream_not_found事件
* 播放rtsp流时如果该流启动了rtsp专属鉴权(on_rtsp_realm)那么将不再触发on_play事件
*/
public BaseEventResp onPlay(PlayReq req) {
log.debug("[ZLM] onPlay : {}", req);
return BaseEventResp.success();
}
/**
* TODO rtsp/rtmp/rtp推流鉴权事件
*/
public PublishResp onPublish(PublishReq req) {
System.out.println("[ZLM] 接收到推流信息");
log.info("[ZLM] onPublish : {}", req);
return PublishResp.success();
}
/**
* TODO 录制mp4完成后通知事件此事件对回复不敏感
*/
public BaseEventResp onRecordMp4(RecordMp4Req req) {
log.debug("[ZLM] onRecordMp4 : {}", req);
return BaseEventResp.success();
}
/**
* TODO 该rtsp流是否开启rtsp专用方式的鉴权事件开启后才会触发on_rtsp_auth事件
* 需要指出的是rtsp也支持url参数鉴权它支持两种方式鉴权
*/
public BaseEventResp onRtspRealm(RtspRealmReq req) {
log.debug("[ZLM] onRtspRealm : {}", req);
return BaseEventResp.success();
}
/**
* TODO rtsp专用的鉴权事件先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件
*/
public RtspAuthResp onRtspAuth(RtspAuthReq req) {
log.debug("[ZLM] onRtspAuth : {}", req);
return RtspAuthResp.success();
}
/**
* TODO shell登录鉴权ZLMediaKit提供简单的telnet调试方式
* 使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面
*/
public BaseEventResp onShellLogin(ShellLoginReq req) {
log.debug("[ZLM] onShellLogin : {}", req);
return BaseEventResp.success();
}
/**
* TODO rtsp/rtmp流注册或注销时触发此事件此事件对回复不敏感
*/
public BaseEventResp onStreamChanged(StreamChangedReq req) {
log.info("[ZLM] onStreamChanged 流信息 : {}", req);
return BaseEventResp.success();
}
/**
* 流无人观看时事件用户可以通过此事件选择是否关闭无人看的流
*/
public BaseEventResp onStreamNoneReader(StreamNoneReaderReq req) {
log.debug("[ZLM] onStreamNoneReader : {}", req);
zlmService.display(req.getMediaServerId(), req.getStream());
return BaseEventResp.success().setClose(false);
}
/**
* TODO 流未找到事件用户可以在此事件触发时立即去拉流这样可以实现按需拉流此事件对回复不敏感
*/
public BaseEventResp onStreamNotFound(StreamNotFoundReq req) {
log.debug("[ZLM] onStreamNotFound : {}", req);
return BaseEventResp.success();
}
/**
* TODO 服务器启动事件可以用于监听服务器崩溃重启此事件对回复不敏感
*/
public BaseEventResp onServerStarted(ServerConfig req) {
log.debug("[ZLM] onServerStarted : {}", req);
return BaseEventResp.success();
}
/**
* TODO 服务器定时上报时间上报间隔可配置默认10s上报一次
*/
public BaseEventResp onServerKeepalive(ServerKeepaliveReq req) {
log.debug("[ZLM] onServerKeepalive : {}", req);
return BaseEventResp.success();
}
}

View File

@ -1,195 +0,0 @@
package com.dite.znpt.monitor.media.zlm.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.dite.znpt.exception.ServiceException;
import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
import com.dite.znpt.monitor.media.zlm.ZlmApi;
import com.dite.znpt.monitor.media.zlm.ZlmService;
import com.dite.znpt.monitor.media.zlm.cache.MediaServerCache;
import com.dite.znpt.monitor.media.zlm.cache.MediaServerChannelCache;
import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.MediaItem;
import com.dite.znpt.monitor.media.zlm.dto.ServerConfig;
import com.dite.znpt.monitor.media.zlm.dto.ServerItem;
import com.dite.znpt.monitor.media.zlm.dto.req.RtpServerReq;
import com.dite.znpt.monitor.media.zlm.dto.req.StreamReq;
import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp;
import com.dite.znpt.monitor.media.zlm.dto.resp.RtpInfoResp;
import com.dite.znpt.monitor.service.StreamMediaFormatService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.List;
import static com.dite.znpt.monitor.config.MediaFormatConfig.streamMediaFormatList;
/**
* @Author: huise23
* @Date: 2022/8/30 10:40
* @Description:
*/
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@EnableScheduling
public class ZlmServiceImpl implements ZlmService, ApplicationRunner {
private final StreamMediaFormatService formatService;
private final ZlmApi zlmApi;
private final MediaServerCache serverCache;
private final MediaServerChannelCache channelCache;
private final Environment environment;
private final StreamMediaServerConfig zlmConfig;
@Value("${spring.profiles.active:dev}")
private String activeProfile;
@Value("${server.host:''}")
private String serverHost;
@Value("${server.port:''}")
private String serverPort;
private String profile = "dev";
/**
* 系统启动加载服务器
*/
@Override
public void run(ApplicationArguments args) throws Exception {
//TODO 媒体格式注入
List<StreamMediaFormat> formatList = streamMediaFormatList();
ServerItem item = new ServerItem(zlmConfig, formatList);
try {
// 开发环境不去修改多媒体配置
// if (!StrUtil.equalsIgnoreCase(activeProfile, profile)) {
// String host = StrUtil.blankToDefault(serverHost, IpUtils.getHostIp());
// String port = StrUtil.blankToDefault(serverPort, environment.getProperty("server.port"));
// ServerConfig config = new ServerConfig();
// config.refreshHook(host, port, zlmConfig);
// zlmApi.setServerConfig(item.getServer(), config);
// }
List<ServerConfig> configList = zlmApi.getServerConfig(item.getServer());
item.setConfig(configList.get(0));
item.setStatus(true);
} catch (Exception e) {
log.error("流服务器节点配置错误:", e);
item.setStatus(false);
}
//初始化参数
serverCache.putLoad(item);
}
// @Scheduled(cron = "*/10 * * * * ?")
public void heartbeat1() {
log.debug("开始心跳检查...");
ServerItem item = serverCache.getLoad();
// 获取服务器流信息
try {
List<MediaResp> media = zlmApi.getMediaList(item.getServer(), new StreamReq());
item.setMedia(media);
item.setStatus(true);
} catch (Exception e) {
log.error("流服务器节点丢失:", e);
item.setStatus(false);
}
serverCache.putLoad(item);
}
@Override
public MediaItem play(String deviceCode, String channelCode) {
// 获取通道信息
MediaItem media;
if (channelCache.has(deviceCode, channelCode)) {
media = channelCache.get(deviceCode, channelCode);
} else {
media = new MediaItem().setDeviceCode(deviceCode).setChannelCode(channelCode);
}
media.setIsCache(true);
// 检查节点是否正常
if (!checkServer(media.getConfigId())) {
// 获取负载最小的节点
ServerItem server = serverCache.getLoad();
if (!server.getStatus()) {
throw new ServiceException("无正常的流媒体节点可用!");
}
media.setSsrc(server.genPlaySsrc(channelCode));
media.setConfigId(server.getConfigId());
media.setConfig(server.getConfig());
media.setServer(server.getServer());
media.setFormatList(server.getFormatList());
media.setIsCache(false);
serverCache.putLoad(server);
}
// 检查播放流是否正常
if (StrUtil.isNotBlank(media.getStreamId())) {
RtpInfoResp rtp = zlmApi.getRtpInfo(media.getServer(), media.getStreamId());
if (rtp.getExist()) {
media.setRtp(rtp);
media.setIsCache(true);
return media;
}
}
// 不正常需要重新拉流
Integer rtpPort = zlmApi.openRtpServer(media.getServer(), new RtpServerReq(media.getSsrc()));
media.setRtpPort(rtpPort);
media.setStreamId(media.getSsrc());
// 缓存链接信息
channelCache.put(deviceCode, channelCode, media);
media.setIsCache(false);
return media;
}
@Override
public void release(String deviceCode, String channelCode) {
// 获取通道信息
MediaItem media = channelCache.get(deviceCode, channelCode);
release(media);
}
@Override
public void release(MediaItem media) {
if (ObjectUtil.isNull(media)) {
return;
}
try {
List<MediaResp> list = zlmApi.getMediaList(media.getServer(), new StreamReq().setStream(media.getStreamId()));
if (!CollUtil.isEmpty(list) && list.get(0).getTotalReaderCount() > 0) {
// 当前还有观看者 不释放资源
return;
}
zlmApi.closeRtpServer(media.getServer(), media.getStreamId());
} catch (Exception e) {
log.error("流媒体服务器调用失败:", e);
}
// 释放SSRC句柄
serverCache.releaseSsrc(media.getSsrc());
// 删除链接信息
channelCache.delete(media);
}
@Override
public void display(String mediaServerId, String streamId) {
// 获取通道信息
MediaItem media = channelCache.getByStream(mediaServerId, streamId);
release(media);
}
/**
* 检查节点是否正常
*/
private boolean checkServer(String configId) {
if (ObjectUtil.isNull(configId)) {
return false;
}
ServerItem item = serverCache.getLoad();
return ObjectUtil.isNotNull(item) && item.getStatus();
}
}

View File

@ -1,120 +0,0 @@
package com.dite.znpt.monitor.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dite.znpt.domain.PageResult;
import com.dite.znpt.domain.Result;
import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity;
import com.dite.znpt.monitor.domain.req.VideoInfoReq;
import com.dite.znpt.monitor.domain.resp.VideoInfoResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelEditReq;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelResp;
import com.dite.znpt.monitor.domain.vo.video.VideoPayResp;
import java.util.List;
/**
* @Author: huise23
* @Date: 2022/8/11 18:10
* @Description:
*/
public interface DeviceVideoChannelService extends IService<DeviceVideoChannelEntity> {
/**
* 查询视频通道列表
*
* @param videoId 视频id
* @param keyword 查询条件
* @return {@link PageResult < DeviceVideoChannelListResp >}
*/
PageResult<DeviceVideoChannelListResp> selectDeviceVideoChannel(Long videoId, String keyword);
/**
* 查询所有视频通道列表
*
* @param keyword 查询条件
* @return {@link PageResult< DeviceVideoChannelListResp>}
*/
PageResult<DeviceVideoChannelListResp> selectAllDeviceVideoChannel(String keyword);
/**
* 查询视频通道详情
*
* @param channelCode
* @return {@link DeviceVideoChannelResp}
*/
DeviceVideoChannelResp getDeviceVideoChannelDetail(String channelCode);
/**
* 编辑视频设备通道
*
* @param channelId
* @param req
* @return
*/
void editDeviceVideoChannel(Long channelId, DeviceVideoChannelEditReq req);
/**
* 根据通道id删除通道信息
*
* @param channelIds
* @return {@link boolean}
*/
Result removeByChannelIds(List<Long> channelIds);
/**
* 根据通道编码查询通道信息
*
* @param channelCode 通道编码
* @return 通道信息
*/
DeviceVideoChannelEntity getByCode(String channelCode);
/**
* 根据设备id查询设备通道
* @param videoId 设备id
* @return {@link List< DeviceVideoChannelEntity>}
*/
List<DeviceVideoChannelEntity> selectDeviceVideoChannelByVideoCode(Long videoId);
/**
* 播放直播视频
*
* @param channelCode
* @return
*/
VideoPayResp play(String channelCode);
/**
* 是否在线
*
* @param channelCode
* @return
*/
boolean isOnline(String channelCode);
/**
* 停止播放直播
*
* @param channelCode
* @return
*/
void stop(String channelCode);
/**
* 下线视频设备下的所有通道
* @param videoId
* @return
*/
void offlineByVideoId(Long videoId);
/**
* 查询通道及视频信息
*
* @param videoInfoReq 查询参数
* @return {@link VideoInfoResp }
* @author huise23
* @since 2024-12-03 13:54:52
*/
List<VideoInfoResp> selectVideoInfoList(VideoInfoReq videoInfoReq);
}

View File

@ -1,104 +0,0 @@
package com.dite.znpt.monitor.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dite.znpt.domain.PageResult;
import com.dite.znpt.domain.Result;
import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoEditReq;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoNumResp;
/**
* @Author: huise23
* @Date: 2022/8/11 18:09
* @Description:
*/
public interface DeviceVideoService extends IService<DeviceVideoEntity> {
/**
* 查询视频设备列表
*
* @param status 状态
* @param keyword 设备编码或者设备名称
* @return {@link PageResult< DeviceVideoListResp>}
*/
PageResult<DeviceVideoListResp> selectDeviceVideoList(String status, String keyword, String hostAddress);
/**
* 查询视频设备数量
*
* @return {@link DeviceVideoNumResp}
*/
DeviceVideoNumResp countDeviceVideoNum();
/**
* 编辑视频设备信息
*
* @param videoId 视频设备id
* @param req 视频设备信息
* @return
*/
void editDeviceVideo(Long videoId, DeviceVideoEditReq req);
/**
* 删除视频设备
*
* @param videoId 视频设备id
* @return
*/
Result removeByVideoId(Long videoId);
/**
* 根据端口和host查询设备
*
* @param host 地址
* @param port 端口
* @return 设备
*/
DeviceVideoEntity getDeviceByHostAndPort(String host, int port);
/**
* 设备离线
*
* @param videoCode videoCode
*/
void offline(String videoCode);
/**
* 上线设备
* @param entity
* @return
*/
void online(DeviceVideoEntity entity);
/**
* 判断是否注册已经失效
* @param entity 设备信息
* @return {@link boolean}
*/
boolean expire(DeviceVideoEntity entity);
/**
* 根据 videoCode 获取设备
*
* @param videoCode videoCode
* @return 设备
*/
DeviceVideoEntity getByCode(String videoCode);
/**
* 根据 videoCode 获取设备
*
* @param channelCode
* @return 设备
*/
DeviceVideoEntity getByChannelCode(String channelCode);
/**
* 查询设备状态
* @param videoId
* @return
*/
void queryDeviceStatus(Long videoId);
}

View File

@ -1,27 +0,0 @@
package com.dite.znpt.monitor.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dite.znpt.monitor.domain.entity.IpConfigEntity;
import com.dite.znpt.monitor.domain.req.MonitorConfigAddReq;
import java.util.List;
/**
* @Date: 2023/09/05 16:39
* @Description: 监控设备IP配置表服务接口
*/
public interface IpConfigService extends IService<IpConfigEntity> {
/**
* 新增ip配置
* @param req
*/
void configAdd(MonitorConfigAddReq req);
/**
* 查询ip配置
* @return
*/
List<String> configList();
}

View File

@ -1,12 +0,0 @@
package com.dite.znpt.monitor.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat;
/**
* @Author: huise23
* @Date: 2022/8/11 14:58
* @Description:
*/
public interface StreamMediaFormatService extends IService<StreamMediaFormat> {
}

View File

@ -1,262 +0,0 @@
package com.dite.znpt.monitor.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dite.znpt.domain.PageResult;
import com.dite.znpt.domain.Result;
import com.dite.znpt.exception.ServiceException;
import com.dite.znpt.monitor.constant.IotRespMessage;
import com.dite.znpt.monitor.constant.dict.CameraType;
import com.dite.znpt.monitor.constant.dict.DeviceStatus;
import com.dite.znpt.monitor.constant.dict.YesOrNo;
import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity;
import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity;
import com.dite.znpt.monitor.domain.req.VideoInfoReq;
import com.dite.znpt.monitor.domain.resp.VideoInfoResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelEditReq;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelResp;
import com.dite.znpt.monitor.domain.vo.video.VideoPayResp;
import com.dite.znpt.monitor.mapper.DeviceVideoChannelMapper;
import com.dite.znpt.monitor.media.zlm.ZlmApi;
import com.dite.znpt.monitor.media.zlm.ZlmService;
import com.dite.znpt.monitor.media.zlm.cache.MediaServerChannelCache;
import com.dite.znpt.monitor.media.zlm.dto.MediaItem;
import com.dite.znpt.monitor.media.zlm.dto.req.StreamReq;
import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp;
import com.dite.znpt.monitor.service.DeviceVideoChannelService;
import com.dite.znpt.monitor.service.DeviceVideoService;
import com.dite.znpt.monitor.sip.config.SipConfig;
import com.dite.znpt.monitor.sip.transmit.cmd.ISipDeviceCommander;
import com.dite.znpt.monitor.utils.DictUtils;
import com.dite.znpt.util.PageUtil;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @Author: huise23
* @Date: 2022/8/11 18:10
* @Description:
*/
@Service
public class DeviceVideoChannelServiceImpl extends ServiceImpl<DeviceVideoChannelMapper, DeviceVideoChannelEntity> implements DeviceVideoChannelService {
@Resource
private MediaServerChannelCache channelCache;
@Resource
private DeviceVideoService deviceVideoService;
@Resource
private ISipDeviceCommander sipDeviceCommander;
@Resource
private ZlmService zlmService;
@Resource
private ZlmApi zlmApi;
@Resource
private SipConfig sipConfig;
/**
* 查询视频通道列表
*
* @param videoId 视频id
* @param keyword 查询条件
* @return {@link PageResult < DeviceVideoChannelListResp >}
*/
@Override
public PageResult<DeviceVideoChannelListResp> selectDeviceVideoChannel(Long videoId, String keyword) {
PageUtil.startPage();
List<DeviceVideoChannelListResp> list = this.baseMapper.selectDeviceVideoChannel(videoId, keyword);
return buildPageResult(list);
}
/**
* 查询所有视频通道列表
*
* @param keyword 查询条件
* @return {@link PageResult< DeviceVideoChannelListResp>}
*/
@Override
public PageResult<DeviceVideoChannelListResp> selectAllDeviceVideoChannel(String keyword) {
PageUtil.startPage();
List<DeviceVideoChannelListResp> list = this.baseMapper.selectAllDeviceVideoChannel(keyword);
return buildPageResult(list);
}
private PageResult<DeviceVideoChannelListResp> buildPageResult(List<DeviceVideoChannelListResp> list) {
if (CollectionUtil.isEmpty(list)) {
return PageResult.ok(list, 0);
}
long total = new PageInfo<>(list).getTotal();
list.stream().peek(resp -> {
resp.setCameraTypeLabel(DictUtils.getDictLabel(CameraType.class, resp.getCameraType()));
resp.setPtzControlLabel(DictUtils.getDictLabel(YesOrNo.class, resp.getPtzControl()));
resp.setStatusLabel(DictUtils.getDictLabel(DeviceStatus.class, resp.getStatus()));
}).collect(Collectors.toList());
return PageResult.ok(list, total);
}
/**
* 查询视频通道详情
*
* @param channelCode
* @return {@link DeviceVideoChannelResp}
*/
@Override
public DeviceVideoChannelResp getDeviceVideoChannelDetail(String channelCode) {
return this.baseMapper.getDeviceVideoChannelDetail(channelCode);
}
/**
* 编辑视频设备通道
*
* @param channelId
* @param req
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void editDeviceVideoChannel(Long channelId, DeviceVideoChannelEditReq req) {
DeviceVideoChannelEntity entity = this.getById(channelId);
BeanUtil.copyProperties(req, entity);
entity.setUpdateTime(LocalDateTime.now());
this.updateById(entity);
}
/**
* 根据通道id删除通道信息
*
* @param channelIds
* @return {@link boolean}
*/
@Transactional(rollbackFor = Exception.class)
@Override
public Result removeByChannelIds(List<Long> channelIds) {
List<DeviceVideoChannelEntity> list = this.listByIds(channelIds);
List<Long> ids = list.stream().map(DeviceVideoChannelEntity::getChannelId).collect(Collectors.toList());
if (ids.size() == list.size()) {
return Result.ok(this.removeByIds(ids));
} else {
return Result.error(IotRespMessage.DEVICE_VIDEO_CANNOT_DELETE);
}
}
@Override
public DeviceVideoChannelEntity getByCode(String channelCode) {
return super.getOne(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class)
.eq(DeviceVideoChannelEntity::getChannelCode, channelCode)
.orderByDesc(DeviceVideoChannelEntity::getCreateTime)
.last("limit 1"));
}
/**
* 根据设备国标编码查询设备通道
*
* @param videoId 设备id
* @return {@link List< DeviceVideoChannelEntity>}
*/
@Override
public List<DeviceVideoChannelEntity> selectDeviceVideoChannelByVideoCode(Long videoId) {
return this.list(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class).eq(DeviceVideoChannelEntity::getVideoId, videoId));
}
/**
* 播放视频
*
* @param channelCode
* @return
*/
@Override
public VideoPayResp play(String channelCode) {
DeviceVideoChannelEntity channelEntity = Optional.ofNullable(this.getByCode(channelCode)).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND));
DeviceVideoEntity videoEntity = Optional.ofNullable(deviceVideoService.getById(channelEntity.getVideoId())).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND));
MediaItem mediaItem = zlmService.play(videoEntity.getVideoCode(), channelEntity.getChannelCode());
if (!mediaItem.getIsCache()) {
sipDeviceCommander.playStreamCmd(videoEntity, channelEntity.getChannelCode(), mediaItem.getSsrc(), mediaItem.getRtpPort(), mediaItem.getServer().getApiHost());
}
return VideoPayResp.builder()
.mediaType(sipConfig.getMediaType())
.streamMediaFormatList(mediaItem.getFormatList(sipConfig.getMediaRouter()))
.build();
}
/**
* 是否在线
*
* @param channelCode
* @return
*/
@Override
public boolean isOnline(String channelCode) {
DeviceVideoChannelEntity channelEntity = this.getByCode(channelCode);
return Objects.nonNull(channelEntity) && channelEntity.getStatus().equals(DeviceStatus.ONLINE.getValue());
}
/**
* 停止播放直播
*
* @param channelCode
* @return
*/
@Override
public void stop(String channelCode) {
DeviceVideoChannelEntity channelEntity = Optional.ofNullable(this.getByCode(channelCode)).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND));
DeviceVideoEntity videoEntity = Optional.ofNullable(deviceVideoService.getById(channelEntity.getVideoId())).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND));
MediaItem mediaItem = channelCache.get(videoEntity.getVideoCode(), channelEntity.getChannelCode());
if (null != mediaItem) {
List<MediaResp> list = zlmApi.getMediaList(mediaItem.getServer(), new StreamReq().setStream(mediaItem.getStreamId()));
if (CollectionUtil.isNotEmpty(list) && list.get(0).getTotalReaderCount() <= 1) {
// 当只有一个人观看时想设备发送停止推流命令
sipDeviceCommander.stopStreamCmd(mediaItem.getSsrc());
}
zlmService.release(videoEntity.getVideoCode(), channelEntity.getChannelCode());
}
}
/**
* 下线视频设备下的所有通道
*
* @param videoId
* @return
*/
@Override
public void offlineByVideoId(Long videoId) {
List<DeviceVideoChannelEntity> list = this.list(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class).eq(DeviceVideoChannelEntity::getVideoId, videoId));
if (ObjectUtil.isNotNull(list)) {
this.updateBatchById(
list.stream().peek(entity -> {
entity.setUpdateTime(LocalDateTime.now());
entity.setStatus(DeviceStatus.OFFLINE.getValue());
}).collect(Collectors.toList())
);
}
}
/**
* 查询通道及视频信息
*
* @param videoInfoReq 查询参数
* @return {@link VideoInfoResp }
* @author huise23
* @since 2024-12-03 13:54:52
*/
@Override
public List<VideoInfoResp> selectVideoInfoList(VideoInfoReq videoInfoReq) {
return this.baseMapper.selectVideoInfoList(videoInfoReq);
}
}

View File

@ -1,225 +0,0 @@
package com.dite.znpt.monitor.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dite.znpt.domain.PageResult;
import com.dite.znpt.domain.Result;
import com.dite.znpt.exception.ServiceException;
import com.dite.znpt.monitor.constant.IotCacheConstants;
import com.dite.znpt.monitor.constant.IotDictConstants;
import com.dite.znpt.monitor.constant.IotRespMessage;
import com.dite.znpt.monitor.constant.dict.DeviceStatus;
import com.dite.znpt.monitor.constant.dict.SipTransferMode;
import com.dite.znpt.monitor.constant.dict.StreamTransferMode;
import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity;
import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoEditReq;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp;
import com.dite.znpt.monitor.domain.vo.video.DeviceVideoNumResp;
import com.dite.znpt.monitor.mapper.DeviceVideoMapper;
import com.dite.znpt.monitor.service.DeviceVideoChannelService;
import com.dite.znpt.monitor.service.DeviceVideoService;
import com.dite.znpt.monitor.sip.transmit.cmd.ISipDeviceCommander;
import com.dite.znpt.monitor.utils.DictUtils;
import com.dite.znpt.service.impl.RedisService;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @Author: huise23
* @Date: 2022/8/11 18:11
* @Description:
*/
@Slf4j
@Service
public class DeviceVideoServiceImpl extends ServiceImpl<DeviceVideoMapper, DeviceVideoEntity> implements DeviceVideoService {
@Resource
private RedisService redisService;
@Resource
private DeviceVideoChannelService deviceVideoChannelService;
@Resource
private ISipDeviceCommander sipDeviceCommander;
/**
* 查询视频设备列表
*
* @param status 状态
* @param keyword 设备编码或者设备名称
* @return {@link List < DeviceVideoListResp >}
*/
@Override
public PageResult<DeviceVideoListResp> selectDeviceVideoList(String status, String keyword, String hostAddress) {
List<DeviceVideoListResp> deviceVideoListResps = this.baseMapper.selectDeviceVideoList(status, keyword,hostAddress);
int total = (int) new PageInfo(deviceVideoListResps).getTotal();
deviceVideoListResps = deviceVideoListResps.stream().peek(resp -> {
resp.setStatusLabel(DictUtils.getDictLabel(DeviceStatus.class, resp.getStatus()));
resp.setStreamModeLabel(DictUtils.getDictLabel(StreamTransferMode.class, resp.getStreamMode()));
resp.setTransportLabel(DictUtils.getDictLabel(SipTransferMode.class, resp.getTransport()));
}).collect(Collectors.toList());
return PageResult.ok(deviceVideoListResps,total);
}
/**
* 查询视频设备数量
*
* @param
* @return {@link DeviceVideoNumResp}
*/
@Override
public DeviceVideoNumResp countDeviceVideoNum() {
DeviceVideoNumResp deviceVideoNumResp = new DeviceVideoNumResp();
List<DeviceVideoListResp> deviceVideoList = this.baseMapper.selectDeviceVideoList(null, null,null);
deviceVideoNumResp.setAllDevice(deviceVideoList.stream().count());
deviceVideoNumResp.setOnlineDevice(deviceVideoList.stream().filter(item -> IotDictConstants.IOT_DEVICE_STATUS_ONLINE.equals(item.getStatus())).count());
deviceVideoNumResp.setOfflineDevice(deviceVideoList.stream().filter(item -> IotDictConstants.IOT_DEVICE_STATUS_OFFLINE.equals(item.getStatus())).count());
return deviceVideoNumResp;
}
/**
* 编辑视频设备信息
*
* @param videoId 视频设备id
* @param req 视频设备信息
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void editDeviceVideo(Long videoId, DeviceVideoEditReq req) {
DeviceVideoEntity entity = this.getById(videoId);
if (null == entity) {
throw new ServiceException(IotRespMessage.ID_NOT_FOUND);
}
BeanUtil.copyProperties(req, entity);
this.updateById(entity);
}
/**
* 删除视频设备
*
* @param videoId 视频设备id
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public Result removeByVideoId(Long videoId) {
DeviceVideoEntity entity = this.getById(videoId);
if (null == entity) {
throw new ServiceException(IotRespMessage.ID_NOT_FOUND);
}
if (!DeviceStatus.OFFLINE.getValue().equals(entity.getStatus())) {
return Result.error(IotRespMessage.DEVICE_VIDEO_CANNOT_DELETE);
}
Result result = Result.ok();
List<DeviceVideoChannelEntity> list = deviceVideoChannelService.list(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class).eq(DeviceVideoChannelEntity::getVideoId,videoId));
List<Long> channelIds = list.stream().map(DeviceVideoChannelEntity::getChannelId).collect(Collectors.toList());
if(CollectionUtil.isNotEmpty(channelIds)){
result = deviceVideoChannelService.removeByChannelIds(channelIds);
}
if (HttpStatus.HTTP_OK == result.getCode()) {
return this.removeById(videoId) ? Result.ok() : Result.error();
} else {
return result;
}
}
@Override
public DeviceVideoEntity getDeviceByHostAndPort(String host, int port) {
return super.getOne(Wrappers.lambdaQuery(DeviceVideoEntity.class)
.eq(DeviceVideoEntity::getIp, host)
.eq(DeviceVideoEntity::getPort, port)
// .orderByDesc(DeviceVideoEntity::getCreateTime)
.last("limit 1"));
}
@Transactional(rollbackFor = Exception.class)
@Override
public void offline(String videoCode) {
DeviceVideoEntity entity = this.getByCode(videoCode);
entity.setStatus(DeviceStatus.OFFLINE.getValue());
// entity.setUpdateTime(LocalDateTime.now());
this.updateById(entity);
deviceVideoChannelService.offlineByVideoId(entity.getVideoId());
}
/**
* 上线设备
*
* @param entity
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void online(DeviceVideoEntity entity) {
log.info("[设备上线] deviceId{}->{}:{}",entity.getVideoCode(), entity.getIp(), entity.getPort());
String deviceCacheKey = IotCacheConstants.getIotDeviceVideoKey(entity.getVideoCode());
entity.setStatus(DeviceStatus.ONLINE.getValue());
if(entity.getVideoId() == null){
// 设备首次上线
if(redisService.hasKey(deviceCacheKey)){
// 脏数据
redisService.deleteObject(deviceCacheKey);
}
this.save(entity);
} else {
entity.setUpdateTime(LocalDateTime.now());
this.updateById(entity);
}
sipDeviceCommander.queryDeviceInfo(entity);
sipDeviceCommander.queryCatalog(entity);
}
@Override
public boolean expire(DeviceVideoEntity entity) {
LocalDateTime expireDateTime = entity.getRegisterTime().plus(entity.getExpires(), ChronoUnit.MILLIS);
return expireDateTime.isBefore(LocalDateTime.now());
}
@Override
public DeviceVideoEntity getByCode(String videoCode) {
return super.getOne(Wrappers.lambdaQuery(DeviceVideoEntity.class)
.eq(DeviceVideoEntity::getVideoCode, videoCode)
.orderByDesc(DeviceVideoEntity::getCreateTime)
.last("limit 1"));
}
/**
* 根据 videoCode 获取设备
*
* @param channelCode
* @return 设备
*/
@Override
public DeviceVideoEntity getByChannelCode(String channelCode) {
DeviceVideoChannelEntity channelEntity = Optional.ofNullable(deviceVideoChannelService.getByCode(channelCode)).orElseThrow(()-> new ServiceException(IotRespMessage.ID_NOT_FOUND));
return this.getById(channelEntity.getVideoId());
}
/**
* 查询设备状态
*
* @param videoId
* @return
*/
@Override
public void queryDeviceStatus(Long videoId) {
DeviceVideoEntity entity = Optional.ofNullable(this.getById(videoId)).orElseThrow( () -> new ServiceException(IotRespMessage.ID_NOT_FOUND));
sipDeviceCommander.queryDeviceStatus(entity);
}
}

View File

@ -1,80 +0,0 @@
package com.dite.znpt.monitor.service.impl;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dite.znpt.exception.ServiceException;
import com.dite.znpt.monitor.domain.entity.IpConfigEntity;
import com.dite.znpt.monitor.domain.req.MonitorConfigAddReq;
import com.dite.znpt.monitor.mapper.IpConfigMapper;
import com.dite.znpt.monitor.service.IpConfigService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Date: 2023/09/05 16:39
* @Description: 监控设备IP配置表服务实现类
*/
@Service
public class IpConfigServiceImpl extends ServiceImpl<IpConfigMapper, IpConfigEntity> implements IpConfigService {
@Override
@Transactional(rollbackFor = Exception.class)
public void configAdd(MonitorConfigAddReq req) {
//先删除再新增--全量新增
final List<IpConfigEntity> configAdds = req.getIpAddresses().stream().map(this::BuildConfigEntity).collect(Collectors.toList());
//校验是否有重复的--用前三位来判断重复
checkDup(configAdds);
deleteConfig();
this.saveBatch(configAdds);
}
@Override
public List<String> configList() {
LambdaQueryWrapper<IpConfigEntity> wrapper = new LambdaQueryWrapper<>();
final List<IpConfigEntity> ifConfigs = this.list(wrapper);
return ifConfigs.stream().map(t->t.getIp()).collect(Collectors.toList());
}
private void deleteConfig(){
LambdaQueryWrapper<IpConfigEntity> wrapper = new LambdaQueryWrapper<>();
this.remove(wrapper);
}
private void checkDup(List<IpConfigEntity> configAdds) {
final List<String> ipTopThreeList = configAdds.stream().map(t -> t.getIpTopThree()).collect(Collectors.toList());
LambdaQueryWrapper<IpConfigEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.in(IpConfigEntity::getIpTopThree,ipTopThreeList)
.last("limit 1");
final IpConfigEntity one = this.getOne(wrapper);
if(one != null){
throw new ServiceException("与现有视频地址重复,请重新选择!");
}
}
private IpConfigEntity BuildConfigEntity( String ip) {
IpConfigEntity entity = new IpConfigEntity();
final String[] ipArray = ip.split("\\.");
ipValidate(ipArray);
entity.setIp(ip);
entity.setIpTopThree(ipArray[0]+"."+ipArray[1]+"."+ipArray[2]);
return entity;
}
private void ipValidate(String[] ipArray) {
if(ipArray.length!=4){
throw new ServiceException("ip地址长度不对");
}
for (int i = 0; i < 4; i++) {
if(! (NumberUtil.isInteger(ipArray[i]) || (i==3 && "*".equals(ipArray[i])))){
throw new ServiceException("ip地址为非数字");
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More