mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-05-06 14:07:49 +08:00
Compare commits
55 Commits
4e602713d4
...
70df9b829f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70df9b829f | ||
|
|
1026fb769b | ||
|
|
c9af2381f6 | ||
|
|
b89344c939 | ||
|
|
7d6ba802a7 | ||
|
|
ab15075186 | ||
|
|
4e54db42da | ||
|
|
d1de343167 | ||
|
|
f5f86c40a0 | ||
|
|
280ed4aee3 | ||
|
|
149c5a550b | ||
|
|
2d1608ca7a | ||
|
|
d307cf1073 | ||
|
|
e51d000588 | ||
|
|
99d19d0623 | ||
|
|
472000200d | ||
|
|
99b270dbfc | ||
|
|
c2835db8d4 | ||
|
|
b476e08d19 | ||
|
|
4b90b79b3b | ||
|
|
88bd3c2ae3 | ||
|
|
383081f244 | ||
|
|
85c7ec3d3e | ||
|
|
01c71f8800 | ||
|
|
1a83d5a504 | ||
|
|
ede979dbf9 | ||
|
|
cd5c10cf48 | ||
|
|
d6b63e13c0 | ||
|
|
2b7cab7572 | ||
|
|
0dc297bf02 | ||
|
|
2b090bbf23 | ||
|
|
7fcd3d3404 | ||
|
|
0eebcceefb | ||
|
|
6a1f2887e5 | ||
|
|
83057d81d8 | ||
|
|
7dea67b472 | ||
|
|
7f2db96ac1 | ||
|
|
2bdced8b9c | ||
|
|
27b06a84d7 | ||
|
|
4067dcf8d1 | ||
|
|
a33e7949a4 | ||
|
|
31549bce09 | ||
|
|
2c774ae155 | ||
|
|
b2772a0a1b | ||
|
|
588de2d5ec | ||
|
|
519ccccd7b | ||
|
|
ec45deda5e | ||
|
|
8e9e75997a | ||
|
|
46e9d56c24 | ||
|
|
7068898c7b | ||
|
|
d29bdec648 | ||
|
|
5373e6082c | ||
|
|
332626150f | ||
|
|
4b0cdd5718 | ||
|
|
7a4d5e551d |
3
.gitignore
vendored
3
.gitignore
vendored
@ -31,3 +31,6 @@ certificates
|
||||
/.vs
|
||||
/docker/volumes
|
||||
/docker/wvp/config/jwk.json
|
||||
/打包/
|
||||
/snap/
|
||||
/config/
|
||||
|
||||
45
README.md
45
README.md
@ -123,12 +123,15 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
||||
- [X] 新增支持部标808和部标1078,大量新特性不一一列表了。支持作为网关被国标上级调用部标设备
|
||||
- [X] 支持电子地图。支持展示通道位置,支持在地图上修改通道位置。支持了数据分层抽稀数据能力,百万级数据也可以轻松展示。提供标准的矢量瓦片图层,常见地图引擎都可以直接展示。
|
||||
- [X] 借用zlm闭源版本新能力,可以支持录像保存至s3存储,支持minio。
|
||||
- [X] **全新虚拟线程支持,极大提升了平台的并发能力,局域网压测轻松接入五万+设备,这不是服务极限,这是我的压测工具和硬件测试服务器的极限,大家可自行测试。生产环境实际性能取决于服务器性能和网络带宽。**
|
||||
- [X] **支持报警订阅和报警管理,支持报警事件的展示和查询,支持报警时自动获取快照、播放录像。**
|
||||
|
||||
# 闭源内容
|
||||
- [X] 国标增强版: 支持国标28181-2022协议,支持巡航轨迹查询,PTZ精准控制,存储卡格式化,设备软件升级,OSD配置,h265+aac,支持辅码流,录像倒放等。
|
||||
- [X] 全功能版:
|
||||
- [X] 支持开源所有功能
|
||||
- [X] ONVIF协议
|
||||
- 自研协议实现,安全可靠。
|
||||
- 设备检索
|
||||
- 实时图像预览
|
||||
- 录像回放、回放倍速控制
|
||||
@ -139,6 +142,30 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
||||
- 恢复出厂设置
|
||||
- 自动获取设备品牌等信息、支持展示DNS信息、支持协议的展示
|
||||
- 国标级联点播、自动点播等。
|
||||
- [X] **海康私有协议-ISUP5.0/ISUP4.0/ISUP2.0**
|
||||
- 设备注册
|
||||
- 资源获取
|
||||
- 预览
|
||||
- 录像查询与回放
|
||||
- 云台控制
|
||||
- 预置位控制
|
||||
- 报警,支持大量报警类型的解析与展示,
|
||||
- 绊线检测
|
||||
- 区域入侵
|
||||
- 移动侦测
|
||||
- 逆行检测
|
||||
- 徘徊检测
|
||||
- 人员聚集
|
||||
- 声音异常
|
||||
- 设备异常等。
|
||||
- 抓图(设备直接上传快照图片到服务器,流量消耗低,无需服务端拉流解码)
|
||||
- 对讲支持
|
||||
- 设备配置(设备名称、循环录像等配置)
|
||||
- 设备信息(设备序列号、类型等)
|
||||
- 版本信息(软件、编码、面板、硬件的版本号)
|
||||
- 编码配置(主辅码流分辨率、码率、帧率等配置)
|
||||
- 图像参数配置(色调、对比度、亮度、饱和度配置)
|
||||
- [ ] 大华私有协议-设备网络SDK(开发中...)
|
||||
- [X] 国网B接口协议
|
||||
- 设备注册
|
||||
- 资源获取
|
||||
@ -148,8 +175,8 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
||||
- 可免费定制支持语音对讲、录像回放和抓拍图像。
|
||||
- [X] 支持按权限分配可以使用的通道
|
||||
- [X] 支持表格导出
|
||||
- [X] 拉流代理支持按照品牌拼接url。
|
||||
- [X] 播放鉴权,更加安全。
|
||||
- [X] 拉流代理支持按照品牌拼接url
|
||||
- [X] 播放鉴权,给授权设备无法进行播放,拿到播放地址也不行
|
||||
|
||||
|
||||
# 授权协议
|
||||
@ -157,14 +184,18 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
||||
|
||||
# 技术支持
|
||||
|
||||
# 付费社群
|
||||
## 官方公众号
|
||||
<img src="doc/_media/gongzhonghao.jpg" width="40%" height="40%">
|
||||
|
||||
> 为大家提供WVP最新的开发进展,未来规划等内容。欢迎关注。
|
||||
|
||||
## 付费社群
|
||||
<img src="doc/_media/shequ.png" width="50%" height="50%">
|
||||
|
||||
> 付费社群即可以对作者提供支持,也可以为大家更加快速的解决问题。如果暂时无法加入,给项目点个星也是极大的鼓励。
|
||||
> 付费社群即可以对作者提供支持,也可以为大家更加快速的解决问题,对星球内容不满意,三天之内退出支持自动退款。如果暂时无法加入,给项目点个星也是极大的鼓励。
|
||||
|
||||
|
||||
[知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:,
|
||||
- [使用入门系列一:WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp)
|
||||
[知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:
|
||||
- [WVP 部署安全加固指南:新手必看,防范攻击与漏洞](https://articles.zsxq.com/id_tv8wz4uubx2n.html)
|
||||
|
||||
有偿技术支持,一对一开发辅导,闭源内容合作请发送邮件到648540858@qq.com咨询
|
||||
|
||||
|
||||
BIN
doc/_media/gongzhonghao.jpg
Normal file
BIN
doc/_media/gongzhonghao.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
7
pom.xml
7
pom.xml
@ -60,6 +60,7 @@
|
||||
<asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
|
||||
<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
|
||||
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
@ -392,6 +393,12 @@
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>3.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.genersoft.iot.vmp;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.jt1078.util.ClassUtil;
|
||||
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
|
||||
@ -43,6 +44,7 @@ public class VManageBootstrap extends SpringBootServletInitializer {
|
||||
log.info("构建时间: {}", gitUtil.getBuildDate());
|
||||
log.info("GIT信息: 分支: {}, ID: {}, 时间: {}", gitUtil.getBranch(), gitUtil.getCommitIdShort(), gitUtil.getCommitTime());
|
||||
}
|
||||
|
||||
}
|
||||
// 项目重启
|
||||
public static void restart() {
|
||||
|
||||
@ -19,6 +19,8 @@ public class VideoManagerConstants {
|
||||
public static final String ONLINE_MEDIA_SERVERS_PREFIX = "VMP_ONLINE_MEDIA_SERVERS:";
|
||||
|
||||
public static final String DEVICE_PREFIX = "VMP_DEVICE_INFO";
|
||||
public static final String DEVICE_KEEPALIVE_PREFIX = "VMP_DEVICE_KEEPALIVE:";
|
||||
public static final String DEVICE_REGISTER_PREFIX = "VMP_DEVICE_REGISTER:";
|
||||
|
||||
public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO";
|
||||
|
||||
|
||||
@ -2,11 +2,11 @@ package com.genersoft.iot.vmp.conf;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
@ -23,20 +23,12 @@ import java.util.concurrent.TimeUnit;
|
||||
@Component
|
||||
public class DynamicTask {
|
||||
|
||||
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
|
||||
@Autowired
|
||||
private TaskScheduler taskScheduler;
|
||||
|
||||
private final Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, Runnable> runnableMap = new ConcurrentHashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void DynamicTask() {
|
||||
threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
|
||||
threadPoolTaskScheduler.setPoolSize(300);
|
||||
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
|
||||
threadPoolTaskScheduler.setAwaitTerminationSeconds(10);
|
||||
threadPoolTaskScheduler.setThreadNamePrefix("dynamicTask-");
|
||||
threadPoolTaskScheduler.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环执行的任务
|
||||
@ -60,7 +52,7 @@ public class DynamicTask {
|
||||
}
|
||||
// scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔
|
||||
|
||||
future = threadPoolTaskScheduler.scheduleAtFixedRate(task, new Date(System.currentTimeMillis() + cycleForCatalog), cycleForCatalog);
|
||||
future = taskScheduler.scheduleAtFixedRate(task, new Date(System.currentTimeMillis() + cycleForCatalog), cycleForCatalog);
|
||||
if (future != null){
|
||||
futureMap.put(key, future);
|
||||
runnableMap.put(key, task);
|
||||
@ -96,7 +88,7 @@ public class DynamicTask {
|
||||
}
|
||||
}
|
||||
// scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔
|
||||
future = threadPoolTaskScheduler.schedule(task, startInstant);
|
||||
future = taskScheduler.schedule(task, startInstant);
|
||||
if (future != null){
|
||||
futureMap.put(key, future);
|
||||
runnableMap.put(key, task);
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package com.genersoft.iot.vmp.conf;
|
||||
|
||||
import com.genersoft.iot.vmp.service.bean.AlarmType;
|
||||
import org.apache.ibatis.logging.stdout.StdOutImpl;
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.type.EnumOrdinalTypeHandler;
|
||||
import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -54,6 +56,7 @@ public class MybatisConfig {
|
||||
config.setLogImpl(StdOutImpl.class);
|
||||
}
|
||||
config.setMapUnderscoreToCamelCase(true);
|
||||
config.getTypeHandlerRegistry().register(AlarmType.class, EnumOrdinalTypeHandler.class);
|
||||
sqlSessionFactory.setConfiguration(config);
|
||||
sqlSessionFactory.setDatabaseIdProvider(databaseIdProvider);
|
||||
return sqlSessionFactory.getObject();
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
package com.genersoft.iot.vmp.conf;
|
||||
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import static com.genersoft.iot.vmp.conf.ThreadPoolTaskConfig.cpuNum;
|
||||
|
||||
/**
|
||||
* "@Scheduled"是Spring框架提供的一种定时任务执行机制,默认情况下它是单线程的,在同时执行多个定时任务时可能会出现阻塞和性能问题。
|
||||
* 为了解决这种单线程瓶颈问题,可以将定时任务的执行机制改为支持多线程
|
||||
*/
|
||||
@Configuration
|
||||
public class ScheduleConfig implements SchedulingConfigurer {
|
||||
|
||||
/**
|
||||
* 核心线程数(默认线程数)
|
||||
*/
|
||||
private static final int corePoolSize = Math.max(cpuNum, 20);
|
||||
|
||||
/**
|
||||
* 线程池名前缀
|
||||
*/
|
||||
private static final String threadNamePrefix = "schedule";
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize,
|
||||
new BasicThreadFactory.Builder().namingPattern(threadNamePrefix).daemon(true).build(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
taskRegistrar.setScheduler(scheduledThreadPoolExecutor);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.genersoft.iot.vmp.conf;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
@Configuration
|
||||
public class SchedulingConfig {
|
||||
|
||||
@Bean
|
||||
public TaskScheduler taskScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(5);
|
||||
scheduler.setThreadNamePrefix("scheduled-");
|
||||
scheduler.setVirtualThreads(true); // 必须在 initialize() 之前
|
||||
scheduler.initialize();
|
||||
return scheduler;
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,7 @@ public class SipConfig {
|
||||
|
||||
Integer registerTimeInterval = 120;
|
||||
|
||||
private boolean alarm = false;
|
||||
private boolean alarm = true;
|
||||
|
||||
private long timeout = 1000;
|
||||
}
|
||||
|
||||
@ -20,22 +20,22 @@ public class SystemInfoTimerTask {
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Scheduled(fixedRate = 2000) //每1秒执行一次
|
||||
public void execute(){
|
||||
try {
|
||||
double cpuInfo = SystemInfoUtils.getCpuInfo();
|
||||
redisCatchStorage.addCpuInfo(cpuInfo);
|
||||
double memInfo = SystemInfoUtils.getMemInfo();
|
||||
redisCatchStorage.addMemInfo(memInfo);
|
||||
Map<String, Double> networkInterfaces = SystemInfoUtils.getNetworkInterfaces();
|
||||
redisCatchStorage.addNetInfo(networkInterfaces);
|
||||
List<Map<String, Object>> diskInfo =SystemInfoUtils.getDiskInfo();
|
||||
redisCatchStorage.addDiskInfo(diskInfo);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("[获取系统信息失败] {}", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
// @Scheduled(fixedRate = 2000) //每1秒执行一次
|
||||
// public void execute(){
|
||||
// try {
|
||||
// double cpuInfo = SystemInfoUtils.getCpuInfo();
|
||||
// redisCatchStorage.addCpuInfo(cpuInfo);
|
||||
// double memInfo = SystemInfoUtils.getMemInfo();
|
||||
// redisCatchStorage.addMemInfo(memInfo);
|
||||
// Map<String, Double> networkInterfaces = SystemInfoUtils.getNetworkInterfaces();
|
||||
// redisCatchStorage.addNetInfo(networkInterfaces);
|
||||
// List<Map<String, Object>> diskInfo =SystemInfoUtils.getDiskInfo();
|
||||
// redisCatchStorage.addDiskInfo(diskInfo);
|
||||
// } catch (InterruptedException e) {
|
||||
// log.error("[获取系统信息失败] {}", e.getMessage());
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
package com.genersoft.iot.vmp.conf;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* ThreadPoolTask 配置类
|
||||
* @author lin
|
||||
*/
|
||||
@Configuration
|
||||
@Order(1)
|
||||
@EnableAsync(proxyTargetClass = true)
|
||||
public class ThreadPoolTaskConfig {
|
||||
|
||||
public static final int cpuNum = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/**
|
||||
* 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
|
||||
* 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
|
||||
* 当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝
|
||||
*/
|
||||
|
||||
/**
|
||||
* 核心线程数(默认线程数)
|
||||
*/
|
||||
private static final int corePoolSize = Math.max(cpuNum * 2, 16);
|
||||
/**
|
||||
* 最大线程数
|
||||
*/
|
||||
private static final int maxPoolSize = corePoolSize * 10;
|
||||
/**
|
||||
* 允许线程空闲时间(单位:默认为秒)
|
||||
*/
|
||||
private static final int keepAliveTime = 30;
|
||||
|
||||
/**
|
||||
* 缓冲队列大小
|
||||
*/
|
||||
private static final int queueCapacity = 10000;
|
||||
/**
|
||||
* 线程池名前缀
|
||||
*/
|
||||
private static final String threadNamePrefix = "async-";
|
||||
|
||||
|
||||
@Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
|
||||
public ThreadPoolTaskExecutor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(corePoolSize);
|
||||
executor.setMaxPoolSize(maxPoolSize);
|
||||
executor.setQueueCapacity(queueCapacity);
|
||||
executor.setKeepAliveSeconds(keepAliveTime);
|
||||
executor.setThreadNamePrefix(threadNamePrefix);
|
||||
|
||||
// 线程池对拒绝任务的处理策略
|
||||
// CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.genersoft.iot.vmp.conf;
|
||||
|
||||
import com.genersoft.iot.vmp.service.bean.AlarmType;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@ -183,7 +184,7 @@ public class UserSetting {
|
||||
/**
|
||||
* jwk文件路径,若不指定则使用resources目录下的jwk.json
|
||||
*/
|
||||
private String jwkFile = "classpath:jwk.json";
|
||||
private String jwkFile = null;
|
||||
|
||||
/**
|
||||
* wvp集群模式下如果注册向上级的wvp奔溃,则自动选择一个其他wvp继续注册到上级
|
||||
@ -224,4 +225,30 @@ public class UserSetting {
|
||||
*/
|
||||
private boolean deviceIdStrict = true;
|
||||
|
||||
/**
|
||||
* 对于识别为设备的国标设备的,是否默认开启位置订阅
|
||||
*/
|
||||
private boolean subscribeMobilePosition = false;
|
||||
|
||||
/**
|
||||
* 处理报警消息时,会缓存通道数据,如果超出则丢弃低热度消息,被丢弃的通道下次使用就需要重新查询数据库,默认10000,
|
||||
* 建议根据实际情况调整,过大可能会占用较多内存,过小可能会增加数据库查询压力
|
||||
*/
|
||||
private long alarmCatchSize = 10000;
|
||||
|
||||
/**
|
||||
* 是否使用拉流的方式获取快照,默认false,避免流量大规模消耗,开启后则使用拉流的方式获取快照
|
||||
*/
|
||||
private boolean alarmSnapByStream = false;
|
||||
|
||||
/**
|
||||
* 报警订阅白名单,设置后只有在此列表中的上级平台才会接收报警订阅消息,默认不设置则不限制
|
||||
*/
|
||||
private List<AlarmType> allowedAlarmType = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 报警记录保留天数,超过此天数的报警记录将在每天凌晨自动清理,默认30天,设置为0则不自动清理
|
||||
*/
|
||||
private int alarmKeepDays = 7;
|
||||
|
||||
}
|
||||
|
||||
@ -11,11 +11,10 @@ import com.genersoft.iot.vmp.service.redisMsg.dto.RpcController;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
import org.springframework.data.redis.connection.MessageListener;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@ -43,9 +42,8 @@ public class RedisRpcConfig implements MessageListener {
|
||||
|
||||
private ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Qualifier("taskExecutor")
|
||||
@Autowired
|
||||
private ThreadPoolTaskExecutor taskExecutor;
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private final static Map<String, RedisRpcClassHandler> protocolHash = new HashMap<>();
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericToStringSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
@ -43,4 +44,22 @@ public class RedisTemplateConfig {
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Long> redisLongTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Long> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// Key 使用 String 序列化
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// Value 使用 GenericToStringSerializer,它能将 Long 转换为纯文本字符串存入 Redis
|
||||
// 这样在 Redis 命令行输入 'get key' 看到的是 "123" 而不是二进制乱码
|
||||
template.setValueSerializer(new GenericToStringSerializer<>(Long.class));
|
||||
template.setHashValueSerializer(new GenericToStringSerializer<>(Long.class));
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -104,8 +104,9 @@ public class JwtUtils implements InitializingBean {
|
||||
}
|
||||
String jwkFile = userSetting.getJwkFile();
|
||||
if (jwkFile == null || jwkFile.trim().isEmpty()) {
|
||||
log.error("[API AUTH] JWK文件路径未配置!");
|
||||
return createDefaultRsaKey();
|
||||
log.warn("[API AUTH] JWK文件路径未配置!使用默认配置路径:./config/jwk.json");
|
||||
jwkFile = "config" + File.separator + "jwk.json"; // 默认外部路径
|
||||
return createAndPersistDefaultRsaKey(jwkFile);
|
||||
}
|
||||
|
||||
// 尝试读取JWK文件(自动处理classpath/本地文件,用try-with-resources自动关流,无泄露)
|
||||
@ -131,7 +132,7 @@ public class JwtUtils implements InitializingBean {
|
||||
log.error("[API AUTH] JWK文件中无有效RSA私钥(仅公钥无法签名JWT)");
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("[API AUTH] 读取JWK文件失败(路径:{})", jwkFile, e);
|
||||
log.error("[API AUTH] 读取JWK文件失败(路径:{})", jwkFile);
|
||||
} catch (Exception e) {
|
||||
log.error("[API AUTH] 解析JWK文件失败(JSON格式错误或密钥无效)", e);
|
||||
}
|
||||
@ -154,7 +155,7 @@ public class JwtUtils implements InitializingBean {
|
||||
return resource.getInputStream();
|
||||
}
|
||||
// throw new IOException("classpath下JWK文件不存在:" + filePath);
|
||||
}
|
||||
}
|
||||
{
|
||||
File file = determinePersistPath(jwkFile).toFile();// 外部配置与classpath失败场景下
|
||||
if (file.exists() && file.canRead()) {
|
||||
|
||||
@ -92,6 +92,7 @@ public class WebSecurityConfig {
|
||||
|
||||
defaultExcludes.add("/js/**");
|
||||
defaultExcludes.add("/api/device/query/snap/**");
|
||||
defaultExcludes.add("/api/alarm/snap/**");
|
||||
defaultExcludes.add("/record_proxy/*/**");
|
||||
defaultExcludes.add("/api/emit");
|
||||
defaultExcludes.add("/favicon.ico");
|
||||
|
||||
@ -89,15 +89,15 @@ public class Device {
|
||||
/**
|
||||
* 注册时间
|
||||
*/
|
||||
@Schema(description = "注册时间")
|
||||
private String registerTime;
|
||||
@Schema(description = "注册时间戳")
|
||||
private Long registerTimeStamp;
|
||||
|
||||
|
||||
/**
|
||||
* 心跳时间
|
||||
*/
|
||||
@Schema(description = "心跳时间")
|
||||
private String keepaliveTime;
|
||||
private Long keepaliveTimeStamp;
|
||||
|
||||
|
||||
/**
|
||||
@ -216,4 +216,18 @@ public class Device {
|
||||
public boolean checkWgs84() {
|
||||
return geoCoordSys.equalsIgnoreCase("WGS84");
|
||||
}
|
||||
|
||||
public Integer getHeartBeatCount() {
|
||||
if (heartBeatCount == null) {
|
||||
return 3;
|
||||
}
|
||||
return heartBeatCount;
|
||||
}
|
||||
|
||||
public Integer getHeartBeatInterval() {
|
||||
if (heartBeatCount == null) {
|
||||
return 60;
|
||||
}
|
||||
return heartBeatInterval;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,269 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
*/
|
||||
@Schema(description = "报警信息")
|
||||
@Data
|
||||
public class DeviceAlarm {
|
||||
|
||||
@Schema(description = "数据库id")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "设备的国标编号")
|
||||
private String deviceId;
|
||||
|
||||
@Schema(description = "设备名称")
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 通道Id
|
||||
*/
|
||||
@Schema(description = "通道的国标编号")
|
||||
private String channelId;
|
||||
|
||||
/**
|
||||
* 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情
|
||||
*/
|
||||
@Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情")
|
||||
private String alarmPriority;
|
||||
|
||||
@Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情")
|
||||
private String alarmPriorityDescription;
|
||||
|
||||
public String getAlarmPriorityDescription() {
|
||||
switch (alarmPriority) {
|
||||
case "1":
|
||||
return "一级警情";
|
||||
case "2":
|
||||
return "二级警情";
|
||||
case "3":
|
||||
return "三级警情";
|
||||
case "4":
|
||||
return "四级警情";
|
||||
default:
|
||||
return alarmPriority;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,
|
||||
* 7其他报警;可以为直接组合如12为电话报警或 设备报警-
|
||||
*/
|
||||
@Schema(description = "报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,\n" +
|
||||
"\t * 7其他报警;可以为直接组合如12为电话报警或设备报警")
|
||||
private String alarmMethod;
|
||||
|
||||
|
||||
private String alarmMethodDescription;
|
||||
|
||||
public String getAlarmMethodDescription() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
char[] charArray = alarmMethod.toCharArray();
|
||||
for (char c : charArray) {
|
||||
switch (c) {
|
||||
case '1':
|
||||
stringBuilder.append("-电话报警");
|
||||
break;
|
||||
case '2':
|
||||
stringBuilder.append("-设备报警");
|
||||
break;
|
||||
case '3':
|
||||
stringBuilder.append("-短信报警");
|
||||
break;
|
||||
case '4':
|
||||
stringBuilder.append("-GPS报警");
|
||||
break;
|
||||
case '5':
|
||||
stringBuilder.append("-视频报警");
|
||||
break;
|
||||
case '6':
|
||||
stringBuilder.append("-设备故障报警");
|
||||
break;
|
||||
case '7':
|
||||
stringBuilder.append("-其他报警");
|
||||
break;
|
||||
}
|
||||
}
|
||||
stringBuilder.delete(0, 1);
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 报警时间
|
||||
*/
|
||||
@Schema(description = "报警时间")
|
||||
private String alarmTime;
|
||||
|
||||
/**
|
||||
* 报警内容描述
|
||||
*/
|
||||
@Schema(description = "报警内容描述")
|
||||
private String alarmDescription;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
@Schema(description = "经度")
|
||||
private double longitude;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
@Schema(description = "纬度")
|
||||
private double latitude;
|
||||
|
||||
/**
|
||||
* 报警类型,
|
||||
* 报警方式为2时,不携带 AlarmType为默认的报警设备报警,
|
||||
* 携带 AlarmType取值及对应报警类型如下:
|
||||
* 1-视频丢失报警;
|
||||
* 2-设备防拆报警;
|
||||
* 3-存储设备磁盘满报警;
|
||||
* 4-设备高温报警;
|
||||
* 5-设备低温报警。
|
||||
* 报警方式为5时,取值如下:
|
||||
* 1-人工视频报警;
|
||||
* 2-运动目标检测报警;
|
||||
* 3-遗留物检测报警;
|
||||
* 4-物体移除检测报警;
|
||||
* 5-绊线检测报警;
|
||||
* 6-入侵检测报警;
|
||||
* 7-逆行检测报警;
|
||||
* 8-徘徊检测报警;
|
||||
* 9-流量统计报警;
|
||||
* 10-密度检测报警;
|
||||
* 11-视频异常检测报警;
|
||||
* 12-快速移动报警。
|
||||
* 报警方式为6时,取值下:
|
||||
* 1-存储设备磁盘故障报警;
|
||||
* 2-存储设备风扇故障报警。
|
||||
*/
|
||||
@Schema(description = "报警类型")
|
||||
private String alarmType;
|
||||
|
||||
public String getAlarmTypeDescription() {
|
||||
if (alarmType == null) {
|
||||
return "";
|
||||
}
|
||||
char[] charArray = alarmMethod.toCharArray();
|
||||
Set<String> alarmMethodSet = new HashSet<>();
|
||||
for (char c : charArray) {
|
||||
alarmMethodSet.add(Character.toString(c));
|
||||
}
|
||||
String result = alarmType;
|
||||
if (alarmMethodSet.contains("2")) {
|
||||
switch (alarmType) {
|
||||
case "1":
|
||||
result = "视频丢失报警";
|
||||
break;
|
||||
case "2":
|
||||
result = "设备防拆报警";
|
||||
break;
|
||||
case "3":
|
||||
result = "存储设备磁盘满报警";
|
||||
break;
|
||||
case "4":
|
||||
result = "设备高温报警";
|
||||
break;
|
||||
case "5":
|
||||
result = "设备低温报警";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (alarmMethodSet.contains("5")) {
|
||||
switch (alarmType) {
|
||||
case "1":
|
||||
result = "人工视频报警";
|
||||
break;
|
||||
case "2":
|
||||
result = "运动目标检测报警";
|
||||
break;
|
||||
case "3":
|
||||
result = "遗留物检测报警";
|
||||
break;
|
||||
case "4":
|
||||
result = "物体移除检测报警";
|
||||
break;
|
||||
case "5":
|
||||
result = "绊线检测报警";
|
||||
break;
|
||||
case "6":
|
||||
result = "入侵检测报警";
|
||||
break;
|
||||
case "7":
|
||||
result = "逆行检测报警";
|
||||
break;
|
||||
case "8":
|
||||
result = "徘徊检测报警";
|
||||
break;
|
||||
case "9":
|
||||
result = "流量统计报警";
|
||||
break;
|
||||
case "10":
|
||||
result = "密度检测报警";
|
||||
break;
|
||||
case "11":
|
||||
result = "视频异常检测报警";
|
||||
break;
|
||||
case "12":
|
||||
result = "快速移动报警";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (alarmMethodSet.contains("6")) {
|
||||
switch (alarmType) {
|
||||
case "1":
|
||||
result = "人工视频报警";
|
||||
break;
|
||||
case "2":
|
||||
result = "运动目标检测报警";
|
||||
break;
|
||||
case "3":
|
||||
result = "遗留物检测报警";
|
||||
break;
|
||||
case "4":
|
||||
result = "物体移除检测报警";
|
||||
break;
|
||||
case "5":
|
||||
result = "绊线检测报警";
|
||||
break;
|
||||
case "6":
|
||||
result = "入侵检测报警";
|
||||
break;
|
||||
case "7":
|
||||
result = "逆行检测报警";
|
||||
break;
|
||||
case "8":
|
||||
result = "徘徊检测报警";
|
||||
break;
|
||||
case "9":
|
||||
result = "流量统计报警";
|
||||
break;
|
||||
case "10":
|
||||
result = "密度检测报警";
|
||||
break;
|
||||
case "11":
|
||||
result = "视频异常检测报警";
|
||||
break;
|
||||
case "12":
|
||||
result = "快速移动报警";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Schema(description = "报警类型描述")
|
||||
private String alarmTypeDescription;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private String createTime;
|
||||
|
||||
}
|
||||
@ -0,0 +1,214 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
||||
import com.genersoft.iot.vmp.service.bean.AlarmType;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.dom4j.Element;
|
||||
|
||||
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.*;
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
*/
|
||||
@Schema(description = "报警通知")
|
||||
@Data
|
||||
public class DeviceAlarmNotify {
|
||||
|
||||
@Schema(description = "设备的国标编号")
|
||||
private String deviceId;
|
||||
|
||||
@Schema(description = "设备名称")
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 通道Id
|
||||
*/
|
||||
@Schema(description = "通道的国标编号")
|
||||
private String channelId;
|
||||
|
||||
/**
|
||||
* 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情
|
||||
*/
|
||||
@Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情")
|
||||
private String alarmPriority;
|
||||
|
||||
@Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情")
|
||||
private String alarmPriorityDescription;
|
||||
|
||||
/**
|
||||
* 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,
|
||||
* 7其他报警;可以为直接组合如12为电话报警或 设备报警-
|
||||
*/
|
||||
@Schema(description = "报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,\n" +
|
||||
"\t * 7其他报警;可以为直接组合如12为电话报警或设备报警")
|
||||
private Integer alarmMethod;
|
||||
|
||||
|
||||
private String alarmMethodDescription;
|
||||
|
||||
|
||||
/**
|
||||
* 报警时间
|
||||
*/
|
||||
@Schema(description = "报警时间")
|
||||
private String alarmTime;
|
||||
|
||||
/**
|
||||
* 报警内容描述
|
||||
*/
|
||||
@Schema(description = "报警内容描述")
|
||||
private String alarmDescription;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
@Schema(description = "经度")
|
||||
private double longitude;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
@Schema(description = "纬度")
|
||||
private double latitude;
|
||||
|
||||
/**
|
||||
* 报警类型,
|
||||
* 报警方式为2时,不携带 AlarmType为默认的报警设备报警,
|
||||
* 携带 AlarmType取值及对应报警类型如下:
|
||||
* 1-视频丢失报警;
|
||||
* 2-设备防拆报警;
|
||||
* 3-存储设备磁盘满报警;
|
||||
* 4-设备高温报警;
|
||||
* 5-设备低温报警。
|
||||
* 报警方式为5时,取值如下:
|
||||
* 1-人工视频报警;
|
||||
* 2-运动目标检测报警;
|
||||
* 3-遗留物检测报警;
|
||||
* 4-物体移除检测报警;
|
||||
* 5-绊线检测报警;
|
||||
* 6-入侵检测报警;
|
||||
* 7-逆行检测报警;
|
||||
* 8-徘徊检测报警;
|
||||
* 9-流量统计报警;
|
||||
* 10-密度检测报警;
|
||||
* 11-视频异常检测报警;
|
||||
* 12-快速移动报警。
|
||||
* 报警方式为6时,取值下:
|
||||
* 1-存储设备磁盘故障报警;
|
||||
* 2-存储设备风扇故障报警。
|
||||
*/
|
||||
@Schema(description = "报警类型")
|
||||
private Integer alarmType;
|
||||
|
||||
@Schema(description = "事件类型, 在入侵检测报警时可携带")
|
||||
private Integer eventType;
|
||||
|
||||
public AlarmType getAlarmTypeEnum() {
|
||||
if (alarmType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (alarmMethod == DeviceAlarmMethod.Device.getVal()) {
|
||||
// 2为设备报警,
|
||||
// 报警方式为2时,
|
||||
// 不携带 AlarmType为默认的报警设备报警,
|
||||
// 携带 AlarmType取值及对应报警类型如下:
|
||||
// 1-视频丢失报警;2-设备防拆报警;3-存储设备磁盘满报警;4-设备高温报警;5-设备低温报警
|
||||
switch (alarmType) {
|
||||
case 1:
|
||||
return AlarmType.VideoLoss;
|
||||
case 2:
|
||||
return AlarmType.DeviceTamper;
|
||||
case 3:
|
||||
return AlarmType.StorageFull;
|
||||
case 4:
|
||||
return AlarmType.DeviceHighTemperature;
|
||||
case 5:
|
||||
return AlarmType.DeviceLowTemperature;
|
||||
}
|
||||
}
|
||||
if (alarmMethod == DeviceAlarmMethod.Video.getVal()) {
|
||||
// 5为视频报警
|
||||
// 报警方式为5时,
|
||||
// 取值如下:
|
||||
// 1-人工视频报警;2-运动目标检测报警;3-遗留物检测报警;4-物体移除检测报警;5-绊线检测报警;
|
||||
// 6-入侵检测报警;7-逆行检测报警;8-徘徊检测报警;9-流量统计报警;
|
||||
// 10-密度检测报警;11-视频异常检测报警;12-快速移动报警。
|
||||
switch (alarmType) {
|
||||
case 1:
|
||||
return AlarmType.ManualVideo;
|
||||
case 2:
|
||||
return AlarmType.MotionDetection;
|
||||
case 3:
|
||||
return AlarmType.LeftObjectDetection;
|
||||
case 4:
|
||||
return AlarmType.ObjectRemovalDetection;
|
||||
case 5:
|
||||
return AlarmType.TripwireDetection;
|
||||
case 6:
|
||||
return AlarmType.IntrusionDetection;
|
||||
case 7:
|
||||
return AlarmType.ReverseDetection;
|
||||
case 8:
|
||||
return AlarmType.LoiteringDetection;
|
||||
case 9:
|
||||
return AlarmType.FlowStatistics;
|
||||
case 10:
|
||||
return AlarmType.DensityDetection;
|
||||
case 11:
|
||||
return AlarmType.VideoAbnormal;
|
||||
case 12:
|
||||
return AlarmType.RapidMovement;
|
||||
}
|
||||
}
|
||||
if (alarmMethod == DeviceAlarmMethod.DeviceFailure.getVal()) {
|
||||
switch (alarmType) {
|
||||
case 1:
|
||||
return AlarmType.StorageFault;
|
||||
case 2:
|
||||
return AlarmType.StorageFanFault;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Schema(description = "报警类型描述")
|
||||
private String alarmTypeDescription;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private String createTime;
|
||||
|
||||
|
||||
public static DeviceAlarmNotify fromXml(Element rootElement) {
|
||||
Element deviceIdElement = rootElement.element("DeviceID");
|
||||
String channelId = deviceIdElement.getText();
|
||||
|
||||
DeviceAlarmNotify deviceAlarm = new DeviceAlarmNotify();
|
||||
deviceAlarm.setCreateTime(DateUtil.getNow());
|
||||
deviceAlarm.setChannelId(channelId);
|
||||
deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
|
||||
deviceAlarm.setAlarmMethod(getInteger(rootElement, "AlarmMethod"));
|
||||
String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
|
||||
deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
|
||||
deviceAlarm.setAlarmDescription(getText(rootElement, "AlarmDescription"));
|
||||
|
||||
Double longitude = getDouble(rootElement, "Longitude");
|
||||
deviceAlarm.setLongitude(longitude != null ? longitude: 0.00D);
|
||||
Double latitude = getDouble(rootElement, "Latitude");
|
||||
deviceAlarm.setLatitude(latitude != null ? latitude: 0.00D);
|
||||
deviceAlarm.setAlarmType(getInteger(rootElement, "AlarmType"));
|
||||
Element info = rootElement.element("Info");
|
||||
if (info != null) {
|
||||
deviceAlarm.setAlarmType(getInteger(info, "AlarmType"));
|
||||
Element alarmTypeParam = info.element("AlarmTypeParam");
|
||||
if (alarmTypeParam != null) {
|
||||
deviceAlarm.setAlarmDescription(alarmTypeParam.elementText("AlarmDescription"));
|
||||
}
|
||||
}
|
||||
deviceAlarm.setCreateTime(DateUtil.getNow());
|
||||
return deviceAlarm;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "时间统计信息")
|
||||
public class TimeStatistics {
|
||||
|
||||
@Schema(description = "时间")
|
||||
private String time;
|
||||
|
||||
@Schema(description = "时间差")
|
||||
private Long timeDiff;
|
||||
}
|
||||
@ -1,194 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.controller;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.conf.security.JwtUtils;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Platform;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceAlarmService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "报警信息管理")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/alarm")
|
||||
public class AlarmController {
|
||||
|
||||
@Autowired
|
||||
private IDeviceAlarmService deviceAlarmService;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommander commander;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommanderForPlatform commanderForPlatform;
|
||||
|
||||
@Autowired
|
||||
private IPlatformService platformService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
|
||||
/**
|
||||
* 删除报警
|
||||
*
|
||||
* @param id 报警id
|
||||
* @param deviceIds 多个设备id,逗号分隔
|
||||
* @param time 结束时间(这个时间之前的报警会被删除)
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除报警", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "id", description = "ID")
|
||||
@Parameter(name = "deviceIds", description = "多个设备id,逗号分隔")
|
||||
@Parameter(name = "time", description = "结束时间")
|
||||
public Integer delete(
|
||||
@RequestParam(required = false) Integer id,
|
||||
@RequestParam(required = false) String deviceIds,
|
||||
@RequestParam(required = false) String time
|
||||
) {
|
||||
if (ObjectUtils.isEmpty(id)) {
|
||||
id = null;
|
||||
}
|
||||
if (ObjectUtils.isEmpty(deviceIds)) {
|
||||
deviceIds = null;
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(time)) {
|
||||
time = null;
|
||||
}else if (!DateUtil.verification(time, DateUtil.formatter) ){
|
||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "time格式为" + DateUtil.PATTERN);
|
||||
}
|
||||
List<String> deviceIdList = null;
|
||||
if (deviceIds != null) {
|
||||
String[] deviceIdArray = deviceIds.split(",");
|
||||
deviceIdList = Arrays.asList(deviceIdArray);
|
||||
}
|
||||
|
||||
return deviceAlarmService.clearAlarmBeforeTime(id, deviceIdList, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试向上级/设备发送模拟报警通知
|
||||
*
|
||||
* @param deviceId 报警id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/test/notify/alarm")
|
||||
@Operation(summary = "测试向上级/设备发送模拟报警通知", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "deviceId", description = "设备国标编号")
|
||||
public void delete(@RequestParam String deviceId) {
|
||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||
Platform platform = platformService.queryPlatformByServerGBId(deviceId);
|
||||
DeviceAlarm deviceAlarm = new DeviceAlarm();
|
||||
deviceAlarm.setChannelId(deviceId);
|
||||
deviceAlarm.setAlarmDescription("test");
|
||||
deviceAlarm.setAlarmMethod("1");
|
||||
deviceAlarm.setAlarmPriority("1");
|
||||
deviceAlarm.setAlarmTime(DateUtil.getNow());
|
||||
deviceAlarm.setAlarmType("1");
|
||||
deviceAlarm.setLongitude(115.33333);
|
||||
deviceAlarm.setLatitude(39.33333);
|
||||
|
||||
if (device != null && platform == null) {
|
||||
|
||||
try {
|
||||
commander.sendAlarmMessage(device, deviceAlarm);
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
|
||||
}
|
||||
}else if (device == null && platform != null){
|
||||
try {
|
||||
commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
|
||||
}
|
||||
}else {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(),"无法确定" + deviceId + "是平台还是设备");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询报警
|
||||
*
|
||||
* @param deviceId 设备id
|
||||
* @param page 当前页
|
||||
* @param count 每页查询数量
|
||||
* @param alarmPriority 报警级别
|
||||
* @param alarmMethod 报警方式
|
||||
* @param alarmType 报警类型
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary = "分页查询报警", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "page",description = "当前页",required = true)
|
||||
@Parameter(name = "count",description = "每页查询数量",required = true)
|
||||
@Parameter(name = "deviceId",description = "设备id")
|
||||
@Parameter(name = "channelId",description = "通道id")
|
||||
@Parameter(name = "alarmPriority",description = "查询内容")
|
||||
@Parameter(name = "alarmMethod",description = "查询内容")
|
||||
@Parameter(name = "alarmType",description = "每页查询数量")
|
||||
@Parameter(name = "startTime",description = "开始时间")
|
||||
@Parameter(name = "endTime",description = "结束时间")
|
||||
@GetMapping("/all")
|
||||
public PageInfo<DeviceAlarm> getAll(
|
||||
@RequestParam int page,
|
||||
@RequestParam int count,
|
||||
@RequestParam(required = false) String deviceId,
|
||||
@RequestParam(required = false) String channelId,
|
||||
@RequestParam(required = false) String alarmPriority,
|
||||
@RequestParam(required = false) String alarmMethod,
|
||||
@RequestParam(required = false) String alarmType,
|
||||
@RequestParam(required = false) String startTime,
|
||||
@RequestParam(required = false) String endTime
|
||||
) {
|
||||
if (ObjectUtils.isEmpty(alarmPriority)) {
|
||||
alarmPriority = null;
|
||||
}
|
||||
if (ObjectUtils.isEmpty(alarmMethod)) {
|
||||
alarmMethod = null;
|
||||
}
|
||||
if (ObjectUtils.isEmpty(alarmType)) {
|
||||
alarmType = null;
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(startTime)) {
|
||||
startTime = null;
|
||||
}else if (!DateUtil.verification(startTime, DateUtil.formatter) ){
|
||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "startTime格式为" + DateUtil.PATTERN);
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(endTime)) {
|
||||
endTime = null;
|
||||
}else if (!DateUtil.verification(endTime, DateUtil.formatter) ){
|
||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "endTime格式为" + DateUtil.PATTERN);
|
||||
}
|
||||
|
||||
return deviceAlarmService.getAllAlarm(page, count, deviceId, channelId, alarmPriority, alarmMethod,
|
||||
alarmType, startTime, endTime);
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.conf.security.JwtUtils;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.TimeStatistics;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
||||
@ -21,8 +22,10 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.compress.utils.IOUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.ibatis.annotations.Options;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
@ -31,12 +34,11 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "国标设备查询", description = "国标设备查询")
|
||||
@SuppressWarnings("rawtypes")
|
||||
@ -136,7 +138,7 @@ public class DeviceQuery {
|
||||
log.debug("设备通道信息同步API调用,deviceId:" + deviceId);
|
||||
}
|
||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||
if (device.getRegisterTime() == null) {
|
||||
if (device.getTransport() == null) {
|
||||
WVPResult<SyncStatus> wvpResult = new WVPResult<>();
|
||||
wvpResult.setCode(ErrorCode.ERROR100.getCode());
|
||||
wvpResult.setMsg("设备尚未注册过");
|
||||
@ -155,7 +157,7 @@ public class DeviceQuery {
|
||||
log.debug("设备信息删除API调用,deviceId:" + deviceId);
|
||||
}
|
||||
|
||||
// 清除redis记录
|
||||
// 清除 redis 记录
|
||||
deviceService.delete(deviceId);
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("deviceId", deviceId);
|
||||
@ -181,8 +183,7 @@ public class DeviceQuery {
|
||||
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId,channelId);
|
||||
if (deviceChannel == null) {
|
||||
PageInfo<DeviceChannel> deviceChannelPageResult = new PageInfo<>();
|
||||
return deviceChannelPageResult;
|
||||
return new PageInfo<>();
|
||||
}
|
||||
|
||||
return deviceChannelService.getSubChannels(deviceChannel.getDataDeviceId(), channelId, query, channelType, online, page, count);
|
||||
@ -430,4 +431,34 @@ public class DeviceQuery {
|
||||
public void subscribeMobilePosition(int id, int cycle, int interval) {
|
||||
deviceService.subscribeMobilePosition(id, cycle, interval);
|
||||
}
|
||||
|
||||
@GetMapping("/statistics/keepalive")
|
||||
@Operation(summary = "请求心跳统计")
|
||||
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
||||
@Parameter(name = "count", description = "返回的数量,按时间正向排序,返回的最新的", required = true)
|
||||
public List<TimeStatistics> getKeepaliveTimeStatistics(String deviceId, Integer count) {
|
||||
if (ObjectUtils.isEmpty(deviceId)) {
|
||||
return List.of();
|
||||
}
|
||||
return deviceService.getKeepaliveTimeStatistics(deviceId, count);
|
||||
}
|
||||
|
||||
@GetMapping("/statistics/register")
|
||||
@Operation(summary = "请求注册统计")
|
||||
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
||||
@Parameter(name = "count", description = "返回的数量,按时间正向排序,返回的最新的", required = true)
|
||||
public List<TimeStatistics> getRegisterTimeStatistics(String deviceId, Integer count) {
|
||||
if (ObjectUtils.isEmpty(deviceId)) {
|
||||
return List.of();
|
||||
}
|
||||
return deviceService.getRegisterTimeStatistics(deviceId, count);
|
||||
}
|
||||
|
||||
@GetMapping("/subscribe/alarm")
|
||||
@Operation(summary = "开启/关闭报警订阅")
|
||||
@Parameter(name = "id", description = "通道的Id", required = true)
|
||||
@Parameter(name = "cycle", description = "订阅周期", required = true)
|
||||
public void subscribeAlarm(int id, int cycle) {
|
||||
deviceService.subscribeAlarm(id, cycle);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.controller;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.session.SseSessionManager;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* SSE 推送.
|
||||
*
|
||||
* @author lawrencehj
|
||||
* @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
|
||||
* @since 2021/01/20
|
||||
*/
|
||||
@Tag(name = "SSE 推送")
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class SseController {
|
||||
|
||||
@Resource
|
||||
private SseSessionManager sseSessionManager;
|
||||
|
||||
/**
|
||||
* SSE 推送.
|
||||
*
|
||||
* @param browserId 浏览器ID
|
||||
*/
|
||||
@GetMapping("/emit")
|
||||
public SseEmitter emit(HttpServletResponse response, @RequestParam String browserId) throws IOException, InterruptedException {
|
||||
// response.setContentType("text/event-stream");
|
||||
// response.setCharacterEncoding("utf-8");
|
||||
return sseSessionManager.conect(browserId);
|
||||
}
|
||||
}
|
||||
@ -591,6 +591,9 @@ public interface CommonGBChannelMapper {
|
||||
@SelectProvider(type = ChannelProvider.class, method = "queryOnlineListsByGbDeviceId")
|
||||
List<CommonGBChannel> queryOnlineListsByGbDeviceId(@Param("deviceId") int deviceId);
|
||||
|
||||
@SelectProvider(type = ChannelProvider.class, method = "queryOnlineListsByGbDeviceIds")
|
||||
List<CommonGBChannel> queryOnlineListsByGbDeviceIds(List<Device> deviceList);
|
||||
|
||||
@SelectProvider(type = ChannelProvider.class, method = "queryCommonChannelByDeviceChannel")
|
||||
CommonGBChannel queryCommonChannelByDeviceChannel(@Param("dataType") Integer dataType, @Param("dataDeviceId") Integer dataDeviceId, @Param("deviceId") String deviceId);
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.genersoft.iot.vmp.gb28181.dao;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarmNotify;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@ -19,7 +19,7 @@ public interface DeviceAlarmMapper {
|
||||
|
||||
@Insert("INSERT INTO wvp_device_alarm (device_id, channel_id, alarm_priority, alarm_method, alarm_time, alarm_description, longitude, latitude, alarm_type , create_time ) " +
|
||||
"VALUES (#{deviceId}, #{channelId}, #{alarmPriority}, #{alarmMethod}, #{alarmTime}, #{alarmDescription}, #{longitude}, #{latitude}, #{alarmType}, #{createTime})")
|
||||
int add(DeviceAlarm alarm);
|
||||
int add(DeviceAlarmNotify alarm);
|
||||
|
||||
|
||||
@Select( value = {" <script>" +
|
||||
@ -34,8 +34,8 @@ public interface DeviceAlarmMapper {
|
||||
" <if test=\"endTime != null\" > AND alarm_time <= #{endTime} </if>" +
|
||||
" ORDER BY alarm_time ASC " +
|
||||
" </script>"})
|
||||
List<DeviceAlarm> query(@Param("deviceId") String deviceId, @Param("channelId") String channelId, @Param("alarmPriority") String alarmPriority, @Param("alarmMethod") String alarmMethod,
|
||||
@Param("alarmType") String alarmType, @Param("startTime") String startTime, @Param("endTime") String endTime);
|
||||
List<DeviceAlarmNotify> query(@Param("deviceId") String deviceId, @Param("channelId") String channelId, @Param("alarmPriority") String alarmPriority, @Param("alarmMethod") String alarmMethod,
|
||||
@Param("alarmType") String alarmType, @Param("startTime") String startTime, @Param("endTime") String endTime);
|
||||
|
||||
|
||||
@Delete(" <script>" +
|
||||
|
||||
@ -610,5 +610,11 @@ public interface DeviceChannelMapper {
|
||||
@Update(value = {"UPDATE wvp_device_channel SET status = 'OFF' WHERE data_type = 1 and data_device_id=#{deviceId}"})
|
||||
void offlineByDeviceId(@Param("deviceId") int deviceId);
|
||||
|
||||
@Update(value = {"<script>" +
|
||||
"UPDATE wvp_device_channel SET status = 'OFF' WHERE data_type = 1 and data_device_id in " +
|
||||
" <foreach item='item' index='index' collection='deviceList' open='(' separator=',' close=')'> #{item.id} </foreach>" +
|
||||
" </script>"})
|
||||
void offlineByDeviceIds(List<Device> deviceList);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -30,8 +30,6 @@ public interface DeviceMapper {
|
||||
"port," +
|
||||
"host_address," +
|
||||
"expires," +
|
||||
"register_time," +
|
||||
"keepalive_time," +
|
||||
"create_time," +
|
||||
"update_time," +
|
||||
"charset," +
|
||||
@ -65,8 +63,6 @@ public interface DeviceMapper {
|
||||
"port," +
|
||||
"host_address," +
|
||||
"expires," +
|
||||
"register_time," +
|
||||
"keepalive_time," +
|
||||
"heart_beat_interval," +
|
||||
"heart_beat_count," +
|
||||
"position_capability," +
|
||||
@ -98,8 +94,6 @@ public interface DeviceMapper {
|
||||
"#{port}," +
|
||||
"#{hostAddress}," +
|
||||
"#{expires}," +
|
||||
"#{registerTime}," +
|
||||
"#{keepaliveTime}," +
|
||||
"#{heartBeatInterval}," +
|
||||
"#{heartBeatCount}," +
|
||||
"#{positionCapability}," +
|
||||
@ -133,8 +127,6 @@ public interface DeviceMapper {
|
||||
", port=#{port}" +
|
||||
", host_address=#{hostAddress}" +
|
||||
", on_line=#{onLine}" +
|
||||
", register_time=#{registerTime}" +
|
||||
", keepalive_time=#{keepaliveTime}" +
|
||||
", heart_beat_interval=#{heartBeatInterval}" +
|
||||
", position_capability=#{positionCapability}" +
|
||||
", heart_beat_count=#{heartBeatCount}" +
|
||||
@ -166,8 +158,6 @@ public interface DeviceMapper {
|
||||
"port,"+
|
||||
"host_address,"+
|
||||
"expires,"+
|
||||
"register_time,"+
|
||||
"keepalive_time,"+
|
||||
"create_time,"+
|
||||
"update_time,"+
|
||||
"charset,"+
|
||||
@ -208,8 +198,6 @@ public interface DeviceMapper {
|
||||
"port,"+
|
||||
"host_address,"+
|
||||
"expires,"+
|
||||
"register_time,"+
|
||||
"keepalive_time,"+
|
||||
"create_time,"+
|
||||
"update_time,"+
|
||||
"charset,"+
|
||||
@ -242,8 +230,6 @@ public interface DeviceMapper {
|
||||
"port,"+
|
||||
"host_address,"+
|
||||
"expires,"+
|
||||
"register_time,"+
|
||||
"keepalive_time,"+
|
||||
"create_time,"+
|
||||
"update_time,"+
|
||||
"charset,"+
|
||||
@ -277,8 +263,6 @@ public interface DeviceMapper {
|
||||
"port,"+
|
||||
"host_address,"+
|
||||
"expires,"+
|
||||
"register_time,"+
|
||||
"keepalive_time,"+
|
||||
"create_time,"+
|
||||
"update_time,"+
|
||||
"charset,"+
|
||||
@ -356,8 +340,6 @@ public interface DeviceMapper {
|
||||
",transport" +
|
||||
",stream_mode" +
|
||||
",on_line" +
|
||||
",register_time" +
|
||||
",keepalive_time" +
|
||||
",ip" +
|
||||
",create_time" +
|
||||
",update_time" +
|
||||
@ -444,8 +426,6 @@ public interface DeviceMapper {
|
||||
", port=#{item.port}" +
|
||||
", host_address=#{item.hostAddress}" +
|
||||
", on_line=#{item.onLine}" +
|
||||
", register_time=#{item.registerTime}" +
|
||||
", keepalive_time=#{item.keepaliveTime}" +
|
||||
", heart_beat_interval=#{item.heartBeatInterval}" +
|
||||
", position_capability=#{item.positionCapability}" +
|
||||
", heart_beat_count=#{item.heartBeatCount}" +
|
||||
@ -460,7 +440,6 @@ public interface DeviceMapper {
|
||||
"</script>"})
|
||||
void batchUpdate(List<Device> devices);
|
||||
|
||||
|
||||
@Select(value = {" <script>" +
|
||||
"SELECT " +
|
||||
"coalesce(custom_name, name) as name, " +
|
||||
@ -472,8 +451,6 @@ public interface DeviceMapper {
|
||||
",transport" +
|
||||
",stream_mode" +
|
||||
",on_line" +
|
||||
",register_time" +
|
||||
",keepalive_time" +
|
||||
",ip" +
|
||||
",create_time" +
|
||||
",update_time" +
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.genersoft.iot.vmp.gb28181.dao.provider;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Group;
|
||||
import com.genersoft.iot.vmp.streamPush.bean.StreamPush;
|
||||
import com.genersoft.iot.vmp.web.custom.bean.CameraGroup;
|
||||
@ -601,6 +602,28 @@ public class ChannelProvider {
|
||||
return sqlBuild.toString();
|
||||
}
|
||||
|
||||
public String queryOnlineListsByGbDeviceIds(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_TABLE_NAME);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND coalesce(wdc.gb_status, wdc.status) = 'ON' AND wdc.data_type = 1 ");
|
||||
|
||||
List<Device> deviceList = (List<Device>)params.get("deviceList");
|
||||
if (deviceList != null && !deviceList.isEmpty()) {
|
||||
sqlBuild.append(" AND data_device_id in (");
|
||||
boolean first = true;
|
||||
for (Device device : deviceList) {
|
||||
if (!first) {
|
||||
sqlBuild.append(",");
|
||||
}
|
||||
sqlBuild.append("'" + device.getId() + "'");
|
||||
first = false;
|
||||
}
|
||||
sqlBuild.append(" )");
|
||||
}
|
||||
|
||||
return sqlBuild.toString();
|
||||
}
|
||||
|
||||
public String queryListForSy(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
package com.genersoft.iot.vmp.gb28181.event;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Platform;
|
||||
import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.alarm.DeviceAlarmEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.event.device.DeviceOfflineEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition.MobilePositionEvent;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
@ -40,11 +38,10 @@ public class EventPublisher {
|
||||
|
||||
/**
|
||||
* 设备报警事件
|
||||
* @param deviceAlarm
|
||||
*/
|
||||
public void deviceAlarmEventPublish(DeviceAlarm deviceAlarm) {
|
||||
AlarmEvent alarmEvent = new AlarmEvent(this);
|
||||
alarmEvent.setAlarmInfo(deviceAlarm);
|
||||
public void deviceAlarmEventPublish(List<DeviceAlarmNotify> deviceAlarmList) {
|
||||
DeviceAlarmEvent alarmEvent = new DeviceAlarmEvent(this);
|
||||
alarmEvent.setDeviceAlarmList(deviceAlarmList);
|
||||
applicationEventPublisher.publishEvent(alarmEvent);
|
||||
}
|
||||
|
||||
@ -120,4 +117,9 @@ public class EventPublisher {
|
||||
}
|
||||
|
||||
|
||||
public void deviceOfflineEventPublish(Set<String> deviceIds) {
|
||||
DeviceOfflineEvent event = new DeviceOfflineEvent(this);
|
||||
event.setDeviceIds(deviceIds);
|
||||
applicationEventPublisher.publishEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.event.alarm;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* @description: 报警事件
|
||||
* @author: lawrencehj
|
||||
* @data: 2021-01-20
|
||||
*/
|
||||
|
||||
public class AlarmEvent extends ApplicationEvent {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AlarmEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
private DeviceAlarm deviceAlarm;
|
||||
|
||||
public DeviceAlarm getAlarmInfo() {
|
||||
return deviceAlarm;
|
||||
}
|
||||
|
||||
public void setAlarmInfo(DeviceAlarm deviceAlarm) {
|
||||
this.deviceAlarm = deviceAlarm;
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.event.alarm;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.session.SseSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 报警事件监听器.
|
||||
*
|
||||
* @author lawrencehj
|
||||
* @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
|
||||
* @since 2021/01/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlarmEventListener implements ApplicationListener<AlarmEvent> {
|
||||
|
||||
@Resource
|
||||
private SseSessionManager sseSessionManager;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(@NotNull AlarmEvent event) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("设备报警事件触发, deviceId: {}, {}", event.getAlarmInfo().getDeviceId(), event.getAlarmInfo().getAlarmDescription());
|
||||
}
|
||||
sseSessionManager.sendForAll("message", event.getAlarmInfo());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.genersoft.iot.vmp.gb28181.event.alarm;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarmNotify;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: 报警事件
|
||||
* @author: lawrencehj
|
||||
* @data: 2021-01-20
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class DeviceAlarmEvent extends ApplicationEvent {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DeviceAlarmEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
private List<DeviceAlarmNotify> deviceAlarmList;
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.genersoft.iot.vmp.gb28181.event.device;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DeviceOfflineEvent extends ApplicationEvent {
|
||||
|
||||
private Set<String> deviceIds;
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DeviceOfflineEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.genersoft.iot.vmp.gb28181.event.device;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DeviceOnlineEvent extends ApplicationEvent {
|
||||
|
||||
private Device device;
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DeviceOnlineEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@ -159,6 +159,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
|
||||
List<CommonGBChannel> channelList = new ArrayList<>();
|
||||
CommonGBChannel deviceChannel = channelMap.get(gbId);
|
||||
channelList.add(deviceChannel);
|
||||
|
||||
try {
|
||||
sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), platform, channelList, subscribeInfo, null);
|
||||
} catch (InvalidArgumentException | ParseException | NoSuchFieldException |
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.service;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 报警相关业务处理
|
||||
*/
|
||||
public interface IDeviceAlarmService {
|
||||
|
||||
/**
|
||||
* 根据多个添加获取报警列表
|
||||
* @param page 当前页
|
||||
* @param count 每页数量
|
||||
* @param deviceId 设备id
|
||||
* @param alarmPriority 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情-
|
||||
* @param alarmMethod 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,
|
||||
* 7其他报警;可以为直接组合如12为电话报警或 设备报警-
|
||||
* @param alarmType 报警类型
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 报警列表
|
||||
*/
|
||||
PageInfo<DeviceAlarm> getAllAlarm(int page, int count, String deviceId, String channelId, String alarmPriority, String alarmMethod,
|
||||
String alarmType, String startTime, String endTime);
|
||||
|
||||
/**
|
||||
* 添加一个报警
|
||||
* @param deviceAlarm 添加报警
|
||||
*/
|
||||
void add(DeviceAlarm deviceAlarm);
|
||||
|
||||
/**
|
||||
* 清空时间以前的报警
|
||||
* @param id 数据库id
|
||||
* @param deviceIdList 制定需要清理的设备id
|
||||
* @param time 不写时间则清空所有时间的
|
||||
*/
|
||||
int clearAlarmBeforeTime(Integer id, List<String> deviceIdList, String time);
|
||||
|
||||
}
|
||||
@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
@ -20,13 +21,12 @@ public interface IDeviceService {
|
||||
* 设备上线
|
||||
* @param device 设备信息
|
||||
*/
|
||||
void online(Device device, SipTransactionInfo sipTransactionInfo);
|
||||
void online(Device device);
|
||||
|
||||
/**
|
||||
* 设备下线
|
||||
* @param deviceId 设备编号
|
||||
*/
|
||||
void offline(String deviceId, String reason, boolean check);
|
||||
void offline(Device device);
|
||||
|
||||
/**
|
||||
* 添加目录订阅
|
||||
@ -56,6 +56,10 @@ public interface IDeviceService {
|
||||
*/
|
||||
boolean removeMobilePositionSubscribe(Device device, CommonCallback<Boolean> callback);
|
||||
|
||||
boolean addAlarmSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo);
|
||||
|
||||
boolean removeAlarmSubscribe(Device device, CommonCallback<Boolean> callback);
|
||||
|
||||
/**
|
||||
* 移除移动位置订阅
|
||||
* @param deviceId 设备ID
|
||||
@ -91,13 +95,6 @@ public interface IDeviceService {
|
||||
|
||||
List<Device> getAllByStatus(Boolean status);
|
||||
|
||||
/**
|
||||
* 判断是否注册已经失效
|
||||
* @param device 设备信息
|
||||
* @return 布尔
|
||||
*/
|
||||
boolean expire(Device device);
|
||||
|
||||
/**
|
||||
* 检查设备状态
|
||||
* @param device 设备信息
|
||||
@ -194,6 +191,8 @@ public interface IDeviceService {
|
||||
|
||||
void deviceStatus(Device device, ErrorCallback<String> callback);
|
||||
|
||||
void subscribeAlarm(int id, int cycle);
|
||||
|
||||
void updateDeviceHeartInfo(Device device);
|
||||
|
||||
void alarm(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime, ErrorCallback<Object> callback);
|
||||
@ -202,4 +201,7 @@ public interface IDeviceService {
|
||||
|
||||
void queryPreset(Device device, String channelId, ErrorCallback<List<Preset>> callback);
|
||||
|
||||
List<TimeStatistics> getKeepaliveTimeStatistics(String deviceId, Integer count);
|
||||
|
||||
List<TimeStatistics> getRegisterTimeStatistics(String deviceId, Integer count);
|
||||
}
|
||||
|
||||
@ -38,4 +38,7 @@ public interface IGbChannelPlayService {
|
||||
void playbackSpeed(CommonGBChannel channel, String stream, Double speed);
|
||||
|
||||
void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback<List<CommonRecordInfo>> callback);
|
||||
|
||||
|
||||
void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback);
|
||||
}
|
||||
|
||||
@ -66,6 +66,8 @@ public interface IPlayService {
|
||||
|
||||
void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
|
||||
|
||||
void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> errorCallback);
|
||||
|
||||
void stop(InviteSessionType type, Device device, DeviceChannel channel, String stream);
|
||||
|
||||
void stop(InviteInfo inviteInfo);
|
||||
|
||||
@ -14,4 +14,6 @@ public interface ISourcePlayService {
|
||||
|
||||
void stopPlay(CommonGBChannel channel);
|
||||
|
||||
void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback);
|
||||
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.service.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.dao.DeviceAlarmMapper;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceAlarmService;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
|
||||
|
||||
@Autowired
|
||||
private DeviceAlarmMapper deviceAlarmMapper;
|
||||
|
||||
@Override
|
||||
public PageInfo<DeviceAlarm> getAllAlarm(int page, int count, String deviceId, String channelId, String alarmPriority, String alarmMethod, String alarmType, String startTime, String endTime) {
|
||||
PageHelper.startPage(page, count);
|
||||
List<DeviceAlarm> all = deviceAlarmMapper.query(deviceId, channelId, alarmPriority, alarmMethod, alarmType, startTime, endTime);
|
||||
return new PageInfo<>(all);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(DeviceAlarm deviceAlarm) {
|
||||
deviceAlarmMapper.add(deviceAlarm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clearAlarmBeforeTime(Integer id, List<String> deviceIdList, String time) {
|
||||
return deviceAlarmMapper.clearAlarmBeforeTime(id, deviceIdList, time);
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.service.bean.Alarm;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
@ -97,7 +98,7 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
|
||||
/**
|
||||
* 监听录像查询结束事件
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@org.springframework.context.event.EventListener
|
||||
public void onApplicationEvent(RecordInfoEndEvent event) {
|
||||
SynchronousQueue<RecordInfo> queue = topicSubscribers.get("record" + event.getRecordInfo().getSn());
|
||||
@ -173,29 +174,17 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
|
||||
|
||||
int limitCount = 500;
|
||||
if (!addChannelList.isEmpty()) {
|
||||
if (addChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < addChannelList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > addChannelList.size()) {
|
||||
toIndex = addChannelList.size();
|
||||
}
|
||||
result += channelMapper.batchAdd(addChannelList.subList(i, toIndex));
|
||||
}
|
||||
}else {
|
||||
result += channelMapper.batchAdd(addChannelList);
|
||||
for (int i = 0; i < addChannelList.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, addChannelList.size());
|
||||
List<DeviceChannel> batchList = addChannelList.subList(i, end);
|
||||
result += channelMapper.batchAdd(batchList);
|
||||
}
|
||||
}
|
||||
if (!updateChannelList.isEmpty()) {
|
||||
if (updateChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < updateChannelList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > updateChannelList.size()) {
|
||||
toIndex = updateChannelList.size();
|
||||
}
|
||||
result += channelMapper.batchUpdate(updateChannelList.subList(i, toIndex));
|
||||
}
|
||||
}else {
|
||||
result += channelMapper.batchUpdate(updateChannelList);
|
||||
for (int i = 0; i < updateChannelList.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, updateChannelList.size());
|
||||
List<DeviceChannel> batchList = updateChannelList.subList(i, end);
|
||||
result += channelMapper.batchUpdate(batchList);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -461,29 +450,17 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
|
||||
}
|
||||
int limitCount = 500;
|
||||
if (!addChannels.isEmpty()) {
|
||||
if (addChannels.size() > limitCount) {
|
||||
for (int i = 0; i < addChannels.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > addChannels.size()) {
|
||||
toIndex = addChannels.size();
|
||||
}
|
||||
channelMapper.batchAdd(addChannels.subList(i, toIndex));
|
||||
}
|
||||
}else {
|
||||
channelMapper.batchAdd(addChannels);
|
||||
for (int i = 0; i < addChannels.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, addChannels.size());
|
||||
List<DeviceChannel> batchList = addChannels.subList(i, end);
|
||||
channelMapper.batchAdd(batchList);
|
||||
}
|
||||
}
|
||||
if (!updateChannels.isEmpty()) {
|
||||
if (updateChannels.size() > limitCount) {
|
||||
for (int i = 0; i < updateChannels.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > updateChannels.size()) {
|
||||
toIndex = updateChannels.size();
|
||||
}
|
||||
channelMapper.batchUpdate(updateChannels.subList(i, toIndex));
|
||||
}
|
||||
}else {
|
||||
channelMapper.batchUpdate(updateChannels);
|
||||
for (int i = 0; i < updateChannels.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, updateChannels.size());
|
||||
List<DeviceChannel> batchList = updateChannels.subList(i, end);
|
||||
channelMapper.batchUpdate(batchList);
|
||||
}
|
||||
// 不对收到的通道做比较,已确定是否真的发生变化,所以不发送更新通知
|
||||
|
||||
|
||||
@ -12,16 +12,16 @@ import com.genersoft.iot.vmp.gb28181.dao.DeviceMapper;
|
||||
import com.genersoft.iot.vmp.gb28181.dao.PlatformChannelMapper;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.event.device.DeviceOfflineEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTask;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTaskInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTaskRunner;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusManager;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTaskInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTaskRunner;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl.SubscribeTaskForAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl.SubscribeTaskForCatalog;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl.SubscribeTaskForMobilPosition;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
@ -44,7 +44,9 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -54,7 +56,6 @@ import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -122,7 +123,7 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
private SubscribeTaskRunner subscribeTaskRunner;
|
||||
|
||||
@Autowired
|
||||
private DeviceStatusTaskRunner deviceStatusTaskRunner;
|
||||
private DeviceStatusManager deviceStatusManager;
|
||||
|
||||
private Device getDeviceByDeviceIdFromDb(String deviceId) {
|
||||
return deviceMapper.getDeviceByDeviceId(deviceId);
|
||||
@ -131,10 +132,11 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
|
||||
// 清理数据库不存在但是redis中存在的数据
|
||||
// 清理数据库不存在但是 redis 中存在的数据
|
||||
List<Device> devicesInDb = getAll();
|
||||
if (devicesInDb.isEmpty()) {
|
||||
redisCatchStorage.removeAllDevice();
|
||||
deviceStatusManager.clear();
|
||||
}else {
|
||||
List<Device> devicesInRedis = redisCatchStorage.getAllDevices();
|
||||
if (!devicesInRedis.isEmpty()) {
|
||||
@ -151,30 +153,26 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
}
|
||||
}
|
||||
|
||||
// 重置cseq计数
|
||||
// 重置 cseq 计数
|
||||
redisCatchStorage.resetAllCSEQ();
|
||||
// 处理设备状态
|
||||
List<DeviceStatusTaskInfo> allTaskInfo = deviceStatusTaskRunner.getAllTaskInfo();
|
||||
List<String> onlineDeviceIds = new ArrayList<>();
|
||||
if (!allTaskInfo.isEmpty()) {
|
||||
for (DeviceStatusTaskInfo taskInfo : allTaskInfo) {
|
||||
Device device = getDeviceByDeviceId(taskInfo.getDeviceId());
|
||||
if (device == null) {
|
||||
deviceStatusTaskRunner.removeTask(taskInfo.getDeviceId());
|
||||
continue;
|
||||
}
|
||||
// 恢复定时任务, TCP因为连接已经断开必须等待设备重新连接
|
||||
DeviceStatusTask deviceStatusTask = DeviceStatusTask.getInstance(taskInfo.getDeviceId(),
|
||||
taskInfo.getTransactionInfo(), taskInfo.getExpireTime() + 1000 + System.currentTimeMillis(), this::deviceStatusExpire);
|
||||
deviceStatusTaskRunner.addTask(deviceStatusTask);
|
||||
onlineDeviceIds.add(taskInfo.getDeviceId());
|
||||
}
|
||||
dbStatusCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库状态检查, 每6小时检查一次
|
||||
*/
|
||||
@Scheduled(fixedDelay = 6, initialDelay = 6, timeUnit = TimeUnit.HOURS)
|
||||
public void dbStatusCheck(){
|
||||
// 处理设备状态
|
||||
Set<String> allDeviceIds = deviceStatusManager.getAll();
|
||||
if (!allDeviceIds.isEmpty()) {
|
||||
// 除了记录的设备以外, 其他设备全部离线
|
||||
List<Device> onlineDevice = getAllOnlineDevice(userSetting.getServerId());
|
||||
if (!onlineDevice.isEmpty()) {
|
||||
List<Device> offlineDevices = new ArrayList<>();
|
||||
for (Device device : onlineDevice) {
|
||||
if (!onlineDeviceIds.contains(device.getDeviceId())) {
|
||||
if (!allDeviceIds.contains(device.getDeviceId())) {
|
||||
// 此设备需要离线
|
||||
device.setOnLine(false);
|
||||
// 清理离线设备的相关缓存
|
||||
@ -207,7 +205,7 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
continue;
|
||||
}
|
||||
Device device = getDeviceByDeviceId(taskInfo.getDeviceId());
|
||||
if (device == null || !device.isOnLine() || !onlineDeviceIds.contains(taskInfo.getDeviceId())) {
|
||||
if (device == null || !device.isOnLine() || !allDeviceIds.contains(taskInfo.getDeviceId())) {
|
||||
subscribeTaskRunner.removeSubscribe(taskInfo.getKey());
|
||||
continue;
|
||||
}
|
||||
@ -223,9 +221,16 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
if (subscribeTask != null) {
|
||||
subscribeTaskRunner.addSubscribe(subscribeTask);
|
||||
}
|
||||
}else if (SubscribeTaskForAlarm.name.equals(taskInfo.getName())) {
|
||||
device.setSubscribeCycleForAlarm((int)taskInfo.getExpireTime());
|
||||
SubscribeTask subscribeTask = SubscribeTaskForAlarm.getInstance(device, this::mobilPositionSubscribeExpire, taskInfo.getTransactionInfo());
|
||||
if (subscribeTask != null) {
|
||||
subscribeTaskRunner.addSubscribe(subscribeTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void offlineByIds(List<Device> offlineDevices) {
|
||||
@ -233,16 +238,17 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
log.info("[更新多个离线设备信息] 参数为空");
|
||||
return;
|
||||
}
|
||||
int limitCount = 400;
|
||||
int limitCount = 300;
|
||||
for (int i = 0; i < offlineDevices.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, offlineDevices.size());
|
||||
List<Device> batchList = offlineDevices.subList(i, end);
|
||||
deviceMapper.offlineByList(batchList);
|
||||
int endIndex = Math.min(i + limitCount, offlineDevices.size());
|
||||
List<Device> subList = offlineDevices.subList(i, endIndex);
|
||||
deviceMapper.offlineByList(subList);
|
||||
}
|
||||
|
||||
for (Device device : offlineDevices) {
|
||||
device.setOnLine(false);
|
||||
redisCatchStorage.updateDevice(device);
|
||||
deviceStatusManager.remove(device.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,6 +259,11 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
if (subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
|
||||
subscribeTaskRunner.removeSubscribe(SubscribeTaskForMobilPosition.getKey(device));
|
||||
}
|
||||
// 离线释放所有 ssrc
|
||||
if (subscribeTaskRunner.containsKey(SubscribeTaskForAlarm.getKey(device))) {
|
||||
subscribeTaskRunner.removeSubscribe(SubscribeTaskForAlarm.getKey(device));
|
||||
}
|
||||
deviceStatusManager.remove(device.getDeviceId());
|
||||
// 离线释放所有ssrc
|
||||
List<SsrcTransaction> ssrcTransactions = sessionManager.getSsrcTransactionByDeviceId(device.getDeviceId());
|
||||
if (ssrcTransactions != null && !ssrcTransactions.isEmpty()) {
|
||||
@ -282,13 +293,17 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private void deviceStatusExpire(String deviceId, SipTransactionInfo transactionInfo) {
|
||||
log.info("[设备状态] 到期, 编号: {}", deviceId);
|
||||
offline(deviceId, "保活到期", true);
|
||||
// 监听设备过期事件
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(DeviceOfflineEvent event) {
|
||||
log.info("[设备状态] 到期, 编号: {}", event.getDeviceIds().toString());
|
||||
List<Device> deviceList = redisCatchStorage.getDeviceList(event.getDeviceIds());
|
||||
offline(deviceList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void online(Device device, SipTransactionInfo sipTransactionInfo) {
|
||||
public void online(Device device) {
|
||||
log.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
|
||||
Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId());
|
||||
Device deviceInDb = getDeviceByDeviceIdFromDb(device.getDeviceId());
|
||||
@ -299,20 +314,11 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
inviteStreamService.clearInviteInfo(device.getDeviceId());
|
||||
}
|
||||
device.setUpdateTime(now);
|
||||
device.setKeepaliveTime(now);
|
||||
if (device.getHeartBeatCount() == null) {
|
||||
// 读取设备配置, 获取心跳间隔和心跳超时次数, 在次之前暂时设置为默认值
|
||||
device.setHeartBeatCount(3);
|
||||
device.setHeartBeatInterval(60);
|
||||
device.setPositionCapability(0);
|
||||
|
||||
}
|
||||
if (sipTransactionInfo != null) {
|
||||
device.setSipTransactionInfo(sipTransactionInfo);
|
||||
}else {
|
||||
if (deviceInRedis != null) {
|
||||
device.setSipTransactionInfo(deviceInRedis.getSipTransactionInfo());
|
||||
}
|
||||
}
|
||||
|
||||
// 第一次上线 或则设备之前是离线状态--进行通道同步和设备信息查询
|
||||
@ -332,6 +338,14 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
log.error("[命令发送失败] 查询设备信息: {}", e.getMessage());
|
||||
}
|
||||
// 上线添加订阅
|
||||
if (userSetting.isSubscribeMobilePosition() && isDevice(device.getDeviceId())) {
|
||||
// 开启订阅
|
||||
device.setSubscribeCycleForMobilePosition(60);
|
||||
device.setMobilePositionSubmissionInterval(5);
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}
|
||||
|
||||
sync(device);
|
||||
}else {
|
||||
device.setServerId(userSetting.getServerId());
|
||||
@ -360,10 +374,20 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
}
|
||||
if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}else{
|
||||
if (userSetting.isSubscribeMobilePosition() && isDevice(device.getDeviceId())) {
|
||||
// 开启订阅
|
||||
device.setSubscribeCycleForMobilePosition(60);
|
||||
device.setMobilePositionSubmissionInterval(5);
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}
|
||||
}
|
||||
if (device.getSubscribeCycleForAlarm() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForAlarm.getKey(device))) {
|
||||
addAlarmSubscribe(device, null);
|
||||
}
|
||||
|
||||
if (userSetting.getDeviceStatusNotify()) {
|
||||
// 发送redis消息
|
||||
// 发送 redis消息
|
||||
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, true);
|
||||
}
|
||||
}else {
|
||||
@ -375,62 +399,69 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
sync(device);
|
||||
}
|
||||
}
|
||||
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
|
||||
if (deviceStatusTaskRunner.containsKey(device.getDeviceId())) {
|
||||
if (sipTransactionInfo == null) {
|
||||
deviceStatusTaskRunner.updateDelay(device.getDeviceId(), expiresTime + System.currentTimeMillis());
|
||||
}else {
|
||||
deviceStatusTaskRunner.removeTask(device.getDeviceId());
|
||||
DeviceStatusTask task = DeviceStatusTask.getInstance(device.getDeviceId(), sipTransactionInfo, expiresTime + System.currentTimeMillis(), this::deviceStatusExpire);
|
||||
deviceStatusTaskRunner.addTask(task);
|
||||
}
|
||||
}else {
|
||||
DeviceStatusTask task = DeviceStatusTask.getInstance(device.getDeviceId(), sipTransactionInfo, expiresTime + System.currentTimeMillis(), this::deviceStatusExpire);
|
||||
deviceStatusTaskRunner.addTask(task);
|
||||
}
|
||||
|
||||
// 设备状态任务添加
|
||||
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
|
||||
deviceStatusManager.add(device.getDeviceId(), expiresTime + System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void offline(String deviceId, String reason, boolean check) {
|
||||
Device device = getDeviceByDeviceIdFromDb(deviceId);
|
||||
public void offline(Device device) {
|
||||
if (device == null) {
|
||||
log.warn("[设备不存在] device:{}", deviceId);
|
||||
log.warn("[设备不存在]");
|
||||
return;
|
||||
}
|
||||
|
||||
// 主动查询设备状态, 没有HostAddress无法发送请求,可能是手动添加的设备
|
||||
if (check && device.getHostAddress() != null) {
|
||||
Boolean deviceStatus = getDeviceStatus(device);
|
||||
if (deviceStatus != null && deviceStatus) {
|
||||
log.info("[设备离线] 主动探测发现设备在线,暂不处理 device:{}", deviceId);
|
||||
online(device, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.info("[设备离线] {}, device:{}, 心跳间隔: {},心跳超时次数: {}, 上次心跳时间:{}, 上次注册时间: {}", reason, deviceId,
|
||||
device.getHeartBeatInterval(), device.getHeartBeatCount(), device.getKeepaliveTime(), device.getRegisterTime());
|
||||
String deviceId = device.getDeviceId();
|
||||
log.info("[设备离线] device:{}, 心跳间隔: {},心跳超时次数: {}", deviceId, device.getHeartBeatInterval(), device.getHeartBeatCount());
|
||||
device.setOnLine(false);
|
||||
cleanOfflineDevice(device);
|
||||
redisCatchStorage.updateDevice(device);
|
||||
deviceMapper.update(device);
|
||||
if (userSetting.getDeviceStatusNotify()) {
|
||||
// 发送redis消息
|
||||
// 发送 redis 消息
|
||||
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, false);
|
||||
}
|
||||
if (isDevice(deviceId)) {
|
||||
channelOfflineByDevice(device);
|
||||
channelOfflineByDevice(List.of(device));
|
||||
}
|
||||
}
|
||||
|
||||
private void channelOfflineByDevice(Device device) {
|
||||
public void offline(List<Device> deviceList) {
|
||||
if (deviceList == null || deviceList.isEmpty()) {
|
||||
log.warn("[设备不存在]");
|
||||
return;
|
||||
}
|
||||
List<Device> realDeviceList = new ArrayList<>();
|
||||
for (Device device : deviceList) {
|
||||
if (device == null) {
|
||||
continue;
|
||||
}
|
||||
log.info("[设备离线] device:{}, 心跳间隔: {},心跳超时次数: {}", device.getDeviceId(), device.getHeartBeatInterval(), device.getHeartBeatCount());
|
||||
device.setOnLine(false);
|
||||
cleanOfflineDevice(device);
|
||||
if (isDevice(device.getDeviceId())) {
|
||||
realDeviceList.add(device);
|
||||
}
|
||||
redisCatchStorage.updateDevice(device);
|
||||
if (userSetting.getDeviceStatusNotify()) {
|
||||
// 发送 redis 消息
|
||||
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, false);
|
||||
}
|
||||
}
|
||||
deviceMapper.offlineByList(deviceList);
|
||||
|
||||
if (!realDeviceList.isEmpty()) {
|
||||
channelOfflineByDevice(realDeviceList);
|
||||
}
|
||||
}
|
||||
|
||||
private void channelOfflineByDevice(List<Device> deviceList) {
|
||||
// 进行通道离线
|
||||
List<CommonGBChannel> channelList = commonGBChannelMapper.queryOnlineListsByGbDeviceId(device.getId());
|
||||
List<CommonGBChannel> channelList = commonGBChannelMapper.queryOnlineListsByGbDeviceIds(deviceList);
|
||||
if (channelList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
deviceChannelMapper.offlineByDeviceId(device.getId());
|
||||
deviceChannelMapper.offlineByDeviceIds(deviceList);
|
||||
// 发送通道离线通知
|
||||
eventPublisher.channelEventPublish(channelList, ChannelEvent.ChannelEventMessageType.OFF);
|
||||
}
|
||||
@ -464,24 +495,9 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
log.debug("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设备状态丢失检查
|
||||
@Scheduled(fixedDelay = 30, timeUnit = TimeUnit.SECONDS)
|
||||
public void lostCheckForStatus(){
|
||||
// 获取所有设备
|
||||
List<Device> deviceList = redisCatchStorage.getAllDevices();
|
||||
if (deviceList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Device device : deviceList) {
|
||||
if (device == null || !device.isOnLine() || !userSetting.getServerId().equals(device.getServerId())) {
|
||||
continue;
|
||||
}
|
||||
if (!deviceStatusTaskRunner.containsKey(device.getDeviceId())) {
|
||||
log.debug("[状态丢失] 执行设备离线, 编号: {},", device.getDeviceId());
|
||||
offline(device.getDeviceId(), "", true);
|
||||
if (device.getSubscribeCycleForAlarm() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForAlarm.getKey(device))) {
|
||||
log.debug("[订阅丢失] 报警订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
|
||||
addAlarmSubscribe(device, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -510,6 +526,18 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private void alarmSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) {
|
||||
log.info("[报警订阅] 到期, 编号: {}", deviceId);
|
||||
Device device = getDeviceByDeviceId(deviceId);
|
||||
if (device == null) {
|
||||
log.info("[移报警订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId);
|
||||
return;
|
||||
}
|
||||
if (device.isOnLine() && device.getSubscribeCycleForAlarm() > 0) {
|
||||
addAlarmSubscribe(device, transactionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addCatalogSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) {
|
||||
if (device == null || device.getSubscribeCycleForCatalog() < 0) {
|
||||
@ -549,9 +577,9 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
|
||||
@Override
|
||||
public boolean removeCatalogSubscribe(@NotNull Device device, CommonCallback<Boolean> callback) {
|
||||
log.info("[移除目录订阅]: {}", device.getDeviceId());
|
||||
String key = SubscribeTaskForCatalog.getKey(device);
|
||||
if (subscribeTaskRunner.containsKey(key)) {
|
||||
log.info("[移除目录订阅]: {}", device.getDeviceId());
|
||||
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
||||
if (transactionInfo == null) {
|
||||
log.warn("[移除目录订阅] 未找到事务信息,{}", device.getDeviceId());
|
||||
@ -613,10 +641,9 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
|
||||
@Override
|
||||
public boolean removeMobilePositionSubscribe(Device device, CommonCallback<Boolean> callback) {
|
||||
|
||||
log.info("[移除移动位置订阅]: {}", device.getDeviceId());
|
||||
String key = SubscribeTaskForMobilPosition.getKey(device);
|
||||
if (subscribeTaskRunner.containsKey(key)) {
|
||||
log.info("[移除移动位置订阅]: {}", device.getDeviceId());
|
||||
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
||||
if (transactionInfo == null) {
|
||||
log.warn("[移除移动位置订阅] 未找到事务信息,{}", device.getDeviceId());
|
||||
@ -642,6 +669,71 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAlarmSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) {
|
||||
if (transactionInfo == null) {
|
||||
log.info("[添加报警订阅] 设备 {}", device.getDeviceId());
|
||||
}else {
|
||||
log.info("[报警订阅续期] 设备 {}", device.getDeviceId());
|
||||
}
|
||||
|
||||
try {
|
||||
sipCommander.alarmSubscribe(device, transactionInfo, eventResult -> {
|
||||
ResponseEvent event = (ResponseEvent) eventResult.event;
|
||||
// 成功
|
||||
log.info("[报警订阅]成功: {}", device.getDeviceId());
|
||||
if (!subscribeTaskRunner.containsKey(SubscribeTaskForAlarm.getKey(device))) {
|
||||
SIPResponse response = (SIPResponse) event.getResponse();
|
||||
SipTransactionInfo transactionInfoForResponse = new SipTransactionInfo(response);
|
||||
SubscribeTask subscribeTask = SubscribeTaskForAlarm.getInstance(device, this::alarmSubscribeExpire, transactionInfoForResponse);
|
||||
if (subscribeTask != null) {
|
||||
subscribeTaskRunner.addSubscribe(subscribeTask);
|
||||
}
|
||||
}else {
|
||||
subscribeTaskRunner.updateDelay(SubscribeTaskForAlarm.getKey(device), (device.getSubscribeCycleForAlarm() * 1000L - 500L) + System.currentTimeMillis());
|
||||
}
|
||||
|
||||
},eventResult -> {
|
||||
// 失败
|
||||
log.warn("[报警订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
|
||||
});
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
log.error("[命令发送失败] 报警订阅: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAlarmSubscribe(Device device, CommonCallback<Boolean> callback) {
|
||||
log.info("[移除报警订阅]: {}", device.getDeviceId());
|
||||
String key = SubscribeTaskForAlarm.getKey(device);
|
||||
if (subscribeTaskRunner.containsKey(key)) {
|
||||
SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
|
||||
if (transactionInfo == null) {
|
||||
log.warn("[移除报警订阅] 未找到事务信息,{}", device.getDeviceId());
|
||||
}
|
||||
try {
|
||||
device.setSubscribeCycleForAlarm(0);
|
||||
sipCommander.alarmSubscribe(device, transactionInfo, eventResult -> {
|
||||
// 成功
|
||||
log.info("[取消报警订阅]成功: {}", device.getDeviceId());
|
||||
subscribeTaskRunner.removeSubscribe(SubscribeTaskForAlarm.getKey(device));
|
||||
if (callback != null) {
|
||||
callback.run(true);
|
||||
}
|
||||
},eventResult -> {
|
||||
// 失败
|
||||
log.warn("[取消报警订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
|
||||
});
|
||||
}catch (Exception e) {
|
||||
// 失败
|
||||
log.warn("[取消报警订阅]失败: {}-{} ", device.getDeviceId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncStatus getChannelSyncStatus(String deviceId) {
|
||||
Device device = deviceMapper.getDeviceByDeviceId(deviceId);
|
||||
@ -703,13 +795,6 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
return deviceMapper.getDevices(ChannelDataType.GB28181, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expire(Device device) {
|
||||
Instant registerTimeDate = Instant.from(DateUtil.formatter.parse(device.getRegisterTime()));
|
||||
Instant expireInstant = registerTimeDate.plusMillis(TimeUnit.SECONDS.toMillis(device.getExpires()));
|
||||
return expireInstant.isBefore(Instant.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getDeviceStatus(@NotNull Device device) {
|
||||
SynchronousQueue<String> queue = new SynchronousQueue<>();
|
||||
@ -761,16 +846,10 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
}
|
||||
int limitCount = 300;
|
||||
if (!deviceList.isEmpty()) {
|
||||
if (deviceList.size() > limitCount) {
|
||||
for (int i = 0; i < deviceList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > deviceList.size()) {
|
||||
toIndex = deviceList.size();
|
||||
}
|
||||
deviceMapper.batchUpdate(deviceList.subList(i, toIndex));
|
||||
}
|
||||
}else {
|
||||
deviceMapper.batchUpdate(deviceList);
|
||||
for (int i = 0; i < deviceList.size(); i += limitCount) {
|
||||
int endIndex = Math.min(i + limitCount, deviceList.size());
|
||||
List<Device> subList = deviceList.subList(i, endIndex);
|
||||
deviceMapper.batchUpdate(subList);
|
||||
}
|
||||
for (Device device : deviceList) {
|
||||
redisCatchStorage.updateDevice(device);
|
||||
@ -830,8 +909,11 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
if (subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
|
||||
removeMobilePositionSubscribe(device, null);
|
||||
}
|
||||
if (deviceStatusTaskRunner.containsKey(deviceId)) {
|
||||
deviceStatusTaskRunner.removeTask(deviceId);
|
||||
if (subscribeTaskRunner.containsKey(SubscribeTaskForAlarm.getKey(device))) {
|
||||
removeAlarmSubscribe(device, null);
|
||||
}
|
||||
if (deviceStatusManager.contains(deviceId)) {
|
||||
deviceStatusManager.remove(deviceId);
|
||||
}
|
||||
List<CommonGBChannel> commonGBChannels = commonGBChannelMapper.queryByDataTypeAndDeviceIds(1, List.of(device.getId()));
|
||||
|
||||
@ -901,22 +983,27 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
redisRpcService.subscribeCatalog(id, cycle);
|
||||
return;
|
||||
}
|
||||
// 目录订阅相关的信息
|
||||
if (device.getSubscribeCycleForCatalog() > 0) {
|
||||
// 订阅周期不同,则先取消
|
||||
removeCatalogSubscribe(device, result->{
|
||||
device.setSubscribeCycleForCatalog(cycle);
|
||||
updateDevice(device);
|
||||
if (cycle > 0) {
|
||||
if (cycle > 0) {
|
||||
// 目录订阅相关的信息
|
||||
if (device.getSubscribeCycleForCatalog() > 0) {
|
||||
// 订阅周期不同,则先取消
|
||||
removeCatalogSubscribe(device, result->{
|
||||
device.setSubscribeCycleForCatalog(cycle);
|
||||
updateDevice(device);
|
||||
// 开启订阅
|
||||
addCatalogSubscribe(device, null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}else {
|
||||
// 开启订阅
|
||||
device.setSubscribeCycleForCatalog(cycle);
|
||||
updateDevice(device);
|
||||
addCatalogSubscribe(device, null);
|
||||
}
|
||||
}else {
|
||||
// 开启订阅
|
||||
device.setSubscribeCycleForCatalog(cycle);
|
||||
// 取消订阅
|
||||
removeCatalogSubscribe(device, null);
|
||||
device.setSubscribeCycleForCatalog(0);
|
||||
updateDevice(device);
|
||||
addCatalogSubscribe(device, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -942,25 +1029,66 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
redisRpcService.subscribeMobilePosition(id, cycle, interval);
|
||||
return;
|
||||
}
|
||||
// 目录订阅相关的信息
|
||||
if (device.getSubscribeCycleForMobilePosition() > 0) {
|
||||
// 订阅周期已经开启,则先取消
|
||||
removeMobilePositionSubscribe(device, result->{
|
||||
// 开启订阅
|
||||
if (cycle > 0) {
|
||||
// 目录订阅相关的信息
|
||||
if (device.getSubscribeCycleForMobilePosition() > 0) {
|
||||
// 订阅周期已经开启,则先取消
|
||||
removeMobilePositionSubscribe(device, result->{
|
||||
// 开启订阅
|
||||
device.setSubscribeCycleForMobilePosition(cycle);
|
||||
device.setMobilePositionSubmissionInterval(interval);
|
||||
updateDevice(device);
|
||||
addMobilePositionSubscribe(device, null);
|
||||
});
|
||||
}else {
|
||||
// 订阅未开启
|
||||
device.setSubscribeCycleForMobilePosition(cycle);
|
||||
device.setMobilePositionSubmissionInterval(interval);
|
||||
updateDevice(device);
|
||||
if (cycle > 0) {
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}
|
||||
});
|
||||
// 开启订阅
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}
|
||||
}else {
|
||||
// 订阅未开启
|
||||
device.setSubscribeCycleForMobilePosition(cycle);
|
||||
device.setMobilePositionSubmissionInterval(interval);
|
||||
// 取消订阅
|
||||
removeMobilePositionSubscribe(device, null);
|
||||
device.setSubscribeCycleForMobilePosition(0);
|
||||
updateDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeAlarm(int id, int cycle) {
|
||||
Device device = deviceMapper.query(id);
|
||||
Assert.notNull(device, "未找到设备");
|
||||
Assert.isTrue(device.isOnLine(), "设备已离线");
|
||||
if (device.getSubscribeCycleForAlarm() == cycle) {
|
||||
return;
|
||||
}
|
||||
if (!userSetting.getServerId().equals(device.getServerId())) {
|
||||
redisRpcService.subscribeAlarm(id, cycle);
|
||||
return;
|
||||
}
|
||||
if (cycle > 0) {
|
||||
// 报警订阅相关的信息
|
||||
if (device.getSubscribeCycleForAlarm() > 0) {
|
||||
// 订阅周期不同,则先取消
|
||||
removeAlarmSubscribe(device, result->{
|
||||
device.setSubscribeCycleForAlarm(cycle);
|
||||
updateDevice(device);
|
||||
// 开启订阅
|
||||
addAlarmSubscribe(device, null);
|
||||
});
|
||||
}else {
|
||||
// 开启订阅
|
||||
device.setSubscribeCycleForAlarm(cycle);
|
||||
updateDevice(device);
|
||||
addAlarmSubscribe(device, null);
|
||||
}
|
||||
}else {
|
||||
// 取消订阅
|
||||
removeAlarmSubscribe(device, null);
|
||||
device.setSubscribeCycleForAlarm(0);
|
||||
updateDevice(device);
|
||||
// 开启订阅
|
||||
addMobilePositionSubscribe(device, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -979,9 +1107,7 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
updateDevice(deviceInDb);
|
||||
|
||||
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
|
||||
if (deviceStatusTaskRunner.containsKey(device.getDeviceId())) {
|
||||
deviceStatusTaskRunner.updateDelay(device.getDeviceId(), expiresTime + System.currentTimeMillis());
|
||||
}
|
||||
deviceStatusManager.add(device.getDeviceId(), expiresTime + System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1210,9 +1336,9 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
try {
|
||||
sipCommander.deviceStatusQuery(device, (code, msg, data) -> {
|
||||
if ("ONLINE".equalsIgnoreCase(data.trim())) {
|
||||
online(device, null);
|
||||
online(device);
|
||||
}else {
|
||||
offline(device.getDeviceId(), "设备状态查询结果:" + data.trim(), true);
|
||||
offline(device);
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.run(code, msg, data);
|
||||
@ -1286,5 +1412,38 @@ public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TimeStatistics> getKeepaliveTimeStatistics(String deviceId, Integer count) {
|
||||
List<Long> timeStampList = redisCatchStorage.getDeviceKeepaliveTimeStamp(deviceId, count);
|
||||
return formateTimeStatistics(timeStampList, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TimeStatistics> getRegisterTimeStatistics(String deviceId, Integer count) {
|
||||
List<Long> timeStampList = redisCatchStorage.getDeviceRegisterTimeStamp(deviceId, count);
|
||||
return formateTimeStatistics(timeStampList, count);
|
||||
}
|
||||
|
||||
private List<TimeStatistics> formateTimeStatistics(List<Long> timeStampList, Integer count) {
|
||||
if (timeStampList.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<TimeStatistics> timeStatisticsList = new ArrayList<>();
|
||||
for (int i = 0; i < timeStampList.size(); i++) {
|
||||
Long timeStamp = timeStampList.get(i);
|
||||
TimeStatistics timeStatistics = new TimeStatistics();
|
||||
timeStatistics.setTime(DateUtil.timestampMsTo_yyyy_MM_dd_HH_mm_ss(timeStamp));
|
||||
if (i > 0) {
|
||||
Long lastTimeStamp = timeStampList.get(i - 1);
|
||||
timeStatistics.setTimeDiff((timeStamp - lastTimeStamp) / 1000);
|
||||
}
|
||||
timeStatisticsList.add(timeStatistics);
|
||||
}
|
||||
// 第一个数据由于没有上一个时间戳,无法计算时间差,去掉
|
||||
timeStatisticsList.removeFirst();
|
||||
if (timeStatisticsList.size() - 1 > count) {
|
||||
timeStatisticsList = timeStatisticsList.subList(timeStatisticsList.size() - count, timeStatisticsList.size());
|
||||
}
|
||||
return timeStatisticsList;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.ISourceDownloadService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.ISourcePlayService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService;
|
||||
import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -34,9 +33,6 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
|
||||
@Autowired
|
||||
private Map<String, ISourcePlayService> sourcePlayServiceMap;
|
||||
|
||||
@Autowired
|
||||
private Ijt1078PlayService jt1078PlayService;
|
||||
|
||||
@Autowired
|
||||
private Map<String, ISourcePlaybackService> sourcePlaybackServiceMap;
|
||||
|
||||
@ -238,4 +234,17 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
|
||||
}
|
||||
playbackService.queryRecord(channel, startTime, endTime, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback) {
|
||||
log.info("[通用通道] 获取快照, 类型: {}, 编号:{}", ChannelDataType.getDateTypeDesc(channel.getDataType()), channel.getGbDeviceId());
|
||||
Integer dataType = channel.getDataType();
|
||||
ISourcePlayService sourceChannelPlayService = sourcePlayServiceMap.get(ChannelDataType.PLAY_SERVICE + dataType);
|
||||
if (sourceChannelPlayService == null) {
|
||||
// 通道数据异常
|
||||
log.error("[通用通道] 获取快照 类型编号: {} 不支持实时流预览相关服务", ChannelDataType.getDateTypeDesc(channel.getDataType()));
|
||||
throw new PlayException(Response.BUSY_HERE, "channel not support");
|
||||
}
|
||||
sourceChannelPlayService.getSnap(channel, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,16 +258,10 @@ public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunne
|
||||
int result = 0;
|
||||
if (permission) {
|
||||
int limitCount = 1000;
|
||||
if (commonGBChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > commonGBChannelList.size()) {
|
||||
toIndex = commonGBChannelList.size();
|
||||
}
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "OFF");
|
||||
}
|
||||
} else {
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "OFF");
|
||||
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, commonGBChannelList.size());
|
||||
List<CommonGBChannel> batchList = commonGBChannelList.subList(i, end);
|
||||
result += commonGBChannelMapper.updateStatusForListById(batchList, "OFF");
|
||||
}
|
||||
log.info("[通道离线] 保存入库 共 {} 个改变", result);
|
||||
}
|
||||
@ -309,16 +303,10 @@ public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunne
|
||||
if (permission) {
|
||||
// 批量更新
|
||||
int limitCount = 1000;
|
||||
if (commonGBChannelList.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > commonGBChannelList.size()) {
|
||||
toIndex = commonGBChannelList.size();
|
||||
}
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "ON");
|
||||
}
|
||||
} else {
|
||||
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "ON");
|
||||
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, commonGBChannelList.size());
|
||||
List<CommonGBChannel> batchList = commonGBChannelList.subList(i, end);
|
||||
result += commonGBChannelMapper.updateStatusForListById(batchList, "ON");
|
||||
}
|
||||
}
|
||||
try {
|
||||
@ -341,16 +329,10 @@ public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunne
|
||||
// 批量保存
|
||||
int limitCount = 1000;
|
||||
int result = 0;
|
||||
if (commonGBChannels.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > commonGBChannels.size()) {
|
||||
toIndex = commonGBChannels.size();
|
||||
}
|
||||
result += commonGBChannelMapper.batchAdd(commonGBChannels.subList(i, toIndex));
|
||||
}
|
||||
} else {
|
||||
result += commonGBChannelMapper.batchAdd(commonGBChannels);
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, commonGBChannels.size());
|
||||
List<CommonGBChannel> batchList = commonGBChannels.subList(i, end);
|
||||
result += commonGBChannelMapper.batchAdd(batchList);
|
||||
}
|
||||
try {
|
||||
// 发送catalog
|
||||
@ -372,16 +354,10 @@ public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunne
|
||||
// 批量保存
|
||||
int limitCount = 1000;
|
||||
int result = 0;
|
||||
if (commonGBChannels.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > commonGBChannels.size()) {
|
||||
toIndex = commonGBChannels.size();
|
||||
}
|
||||
result += commonGBChannelMapper.batchUpdate(commonGBChannels.subList(i, toIndex));
|
||||
}
|
||||
} else {
|
||||
result += commonGBChannelMapper.batchUpdate(commonGBChannels);
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, commonGBChannels.size());
|
||||
List<CommonGBChannel> batchList = commonGBChannels.subList(i, end);
|
||||
result += commonGBChannelMapper.batchUpdate(batchList);
|
||||
}
|
||||
log.info("[更新多个通道] 通道数量为{},成功保存:{}", commonGBChannels.size(), result);
|
||||
}
|
||||
@ -404,16 +380,10 @@ public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunne
|
||||
List<CommonGBChannel> oldChanelListByChannels = commonGBChannelMapper.queryOldChanelListByChannels(commonGBChannels);
|
||||
int limitCount = 1000;
|
||||
int result = 0;
|
||||
if (commonGBChannels.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > commonGBChannels.size()) {
|
||||
toIndex = commonGBChannels.size();
|
||||
}
|
||||
result += commonGBChannelMapper.updateStatus(commonGBChannels.subList(i, toIndex));
|
||||
}
|
||||
} else {
|
||||
result += commonGBChannelMapper.updateStatus(commonGBChannels);
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, commonGBChannels.size());
|
||||
List<CommonGBChannel> batchList = commonGBChannels.subList(i, end);
|
||||
result += commonGBChannelMapper.updateStatus(batchList);
|
||||
}
|
||||
log.warn("[更新多个通道状态] 通道数量为{},成功保存:{}", commonGBChannels.size(), result);
|
||||
// 发送通过更新通知
|
||||
@ -925,16 +895,10 @@ public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunne
|
||||
@Override
|
||||
public void updateGPS(List<CommonGBChannel> commonGBChannels) {
|
||||
int limitCount = 1000;
|
||||
if (commonGBChannels.size() > limitCount) {
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > commonGBChannels.size()) {
|
||||
toIndex = commonGBChannels.size();
|
||||
}
|
||||
commonGBChannelMapper.updateGps(commonGBChannels.subList(i, toIndex));
|
||||
}
|
||||
} else {
|
||||
commonGBChannelMapper.updateGps(commonGBChannels);
|
||||
for (int i = 0; i < commonGBChannels.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, commonGBChannels.size());
|
||||
List<CommonGBChannel> batchList = commonGBChannels.subList(i, end);
|
||||
commonGBChannelMapper.updateGps(batchList);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1196,16 +1160,10 @@ public class GbChannelServiceImpl implements IGbChannelService, CommandLineRunne
|
||||
List<CommonGBChannel> channelList = vectorTileCatch.getChannelList(id);
|
||||
if (channelList != null && !channelList.isEmpty()) {
|
||||
int limitCount = 1000;
|
||||
if (channelList.size() > limitCount) {
|
||||
for (int i = 0; i < channelList.size(); i += limitCount) {
|
||||
int toIndex = i + limitCount;
|
||||
if (i + limitCount > channelList.size()) {
|
||||
toIndex = channelList.size();
|
||||
}
|
||||
commonGBChannelMapper.saveLevel(channelList.subList(i, toIndex));
|
||||
}
|
||||
} else {
|
||||
commonGBChannelMapper.saveLevel(channelList);
|
||||
for (int i = 0; i < channelList.size(); i += limitCount) {
|
||||
int end = Math.min(i + limitCount, channelList.size());
|
||||
List<CommonGBChannel> batchList = channelList.subList(i, end);
|
||||
commonGBChannelMapper.saveLevel(batchList);
|
||||
}
|
||||
}
|
||||
vectorTileCatch.save(id);
|
||||
|
||||
@ -47,7 +47,7 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
|
||||
/**
|
||||
* 流离开的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaDepartureEvent event) {
|
||||
if ("rtsp".equals(event.getSchema()) && MediaApp.GB28181.equals(event.getApp())) {
|
||||
|
||||
@ -123,7 +123,7 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
|
||||
}
|
||||
}
|
||||
}else {
|
||||
log.info("[Catalog事件: {}] 未找到上级平台: {}", event.getMessageType(), serverGbId);
|
||||
log.info("[Catalog事件: {}] 没有需要通知的上级平台: {}", event.getMessageType(), serverGbId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -303,7 +303,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
/**
|
||||
* 流离开的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaDepartureEvent event) {
|
||||
List<SendRtpInfo> sendRtpItems = sendRtpServerService.queryByStream(event.getStream());
|
||||
@ -330,7 +330,7 @@ public class PlatformServiceImpl implements IPlatformService, CommandLineRunner
|
||||
/**
|
||||
* 发流停止
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaSendRtpStoppedEvent event) {
|
||||
List<SendRtpInfo> sendRtpItems = sendRtpServerService.queryByStream(event.getStream());
|
||||
|
||||
@ -130,7 +130,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
/**
|
||||
* 流到来的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaArrivalEvent event) {
|
||||
if (MediaApp.GB28181_BROADCAST.equals(event.getApp()) || MediaApp.GB28181_TALK.equals(event.getApp())) {
|
||||
@ -174,7 +174,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
/**
|
||||
* 流离开的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaDepartureEvent event) {
|
||||
List<SendRtpInfo> sendRtpInfos = sendRtpServerService.queryByStream(event.getStream());
|
||||
@ -243,7 +243,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
/**
|
||||
* 流未找到的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaNotFoundEvent event) {
|
||||
if (!MediaApp.GB28181.equals(event.getApp())) {
|
||||
@ -1630,6 +1630,57 @@ public class PlayServiceImpl implements IPlayService {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> errorCallback) {
|
||||
// 2016协议不支持直接获取国标通道的抓图, 只能通过点播的方式获取
|
||||
Device device = deviceService.getDevice(channel.getDataDeviceId());
|
||||
if (device == null) {
|
||||
log.warn("[快照] 未找到通道{}的设备信息", channel);
|
||||
errorCallback.run(InviteErrorCode.FAIL.getCode(), "未找到设备信息", null);
|
||||
return;
|
||||
}
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
|
||||
if (deviceChannel == null) {
|
||||
log.warn("[快照] 未找到通道{}的设备信息", channel);
|
||||
errorCallback.run(InviteErrorCode.FAIL.getCode(), "未找到原始通道", null);
|
||||
return;
|
||||
}
|
||||
|
||||
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getGbId());
|
||||
if (inviteInfo != null) {
|
||||
if (inviteInfo.getStreamInfo() != null) {
|
||||
// 已存在线直接截图
|
||||
MediaServer mediaServer = inviteInfo.getStreamInfo().getMediaServer();
|
||||
String path = "snap";
|
||||
// 请求截图
|
||||
log.info("[请求截图]: 返回byte数组" );
|
||||
byte[] snapByteArray = mediaServerService.getSnap(mediaServer, MediaApp.GB28181, inviteInfo.getStreamInfo().getStream(), 15, 1, path, null);
|
||||
if (snapByteArray != null) {
|
||||
errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapByteArray);
|
||||
}else {
|
||||
errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
play(device, deviceChannel, (code, msg, data)->{
|
||||
if (code == InviteErrorCode.SUCCESS.getCode()) {
|
||||
InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getGbId());
|
||||
if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) {
|
||||
byte[] snapByteArray = mediaServerService.getSnap(data.getMediaServer(), MediaApp.GB28181, data.getStream(), 15, 1, null, null);
|
||||
errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapByteArray);
|
||||
}else {
|
||||
errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null);
|
||||
}
|
||||
}else {
|
||||
errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void stop(InviteSessionType type, Device device, DeviceChannel channel, String stream) {
|
||||
if (!userSetting.getServerId().equals(device.getServerId())) {
|
||||
|
||||
@ -48,4 +48,19 @@ public class SourcePlayServiceForGbImpl implements ISourcePlayService {
|
||||
log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback) {
|
||||
try {
|
||||
deviceChannelPlayService.getSnap(channel, callback);
|
||||
} catch (PlayException e) {
|
||||
callback.run(e.getCode(), e.getMsg(), null);
|
||||
} catch (ControllerException e) {
|
||||
log.error("[获取抓图失败] {}({}), {}", channel.getGbName(), channel.getGbDeviceId(), e.getMsg());
|
||||
callback.run(Response.BUSY_HERE, "busy here", null);
|
||||
} catch (Exception e) {
|
||||
log.error("[获取抓图失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
|
||||
callback.run(Response.BUSY_HERE, "busy here", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +215,8 @@ public class CatalogDataManager implements CommandLineRunner {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 5 * 1000) //每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时
|
||||
//每5秒执行一次, 发现数据5秒未更新则移除数据并认为数据接收超时
|
||||
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
|
||||
private void timerTask(){
|
||||
if (dataMap.isEmpty()) {
|
||||
return;
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.session;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SseSessionManager {
|
||||
|
||||
private static final Map<String, SseEmitter> sseSessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private DynamicTask dynamicTask;
|
||||
|
||||
public SseEmitter conect(String browserId){
|
||||
SseEmitter sseEmitter = new SseEmitter(0L);
|
||||
sseEmitter.onError((err)-> {
|
||||
log.error("[SSE推送] 连接错误, 浏览器 ID: {}, {}", browserId, err.getMessage());
|
||||
sseSessionMap.remove(browserId);
|
||||
sseEmitter.completeWithError(err);
|
||||
});
|
||||
|
||||
// sseEmitter.onTimeout(() -> {
|
||||
// log.info("[SSE推送] 连接超时, 浏览器 ID: {}", browserId);
|
||||
// sseSessionMap.remove(browserId);
|
||||
// sseEmitter.complete();
|
||||
// dynamicTask.stop(key);
|
||||
// });
|
||||
|
||||
sseEmitter.onCompletion(() -> {
|
||||
log.info("[SSE推送] 连接结束, 浏览器 ID: {}", browserId);
|
||||
sseSessionMap.remove(browserId);
|
||||
});
|
||||
|
||||
sseSessionMap.put(browserId, sseEmitter);
|
||||
|
||||
log.info("[SSE推送] 连接已建立, 浏览器 ID: {}, 当前在线数: {}", browserId, sseSessionMap.size());
|
||||
return sseEmitter;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 1000) //每1秒执行一次
|
||||
public void execute(){
|
||||
if (sseSessionMap.isEmpty()){
|
||||
return;
|
||||
}
|
||||
sendForAll("keepalive", "alive");
|
||||
}
|
||||
|
||||
|
||||
public void sendForAll(String event, Object data) {
|
||||
for (String browserId : sseSessionMap.keySet()) {
|
||||
SseEmitter sseEmitter = sseSessionMap.get(browserId);
|
||||
if (sseEmitter == null) {
|
||||
continue;
|
||||
};
|
||||
try {
|
||||
sseEmitter.send(SseEmitter.event().name(event).data(data));
|
||||
} catch (Exception e) {
|
||||
log.error("[SSE推送] 发送失败: {}", e.getMessage());
|
||||
sseSessionMap.remove(browserId);
|
||||
sseEmitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package com.genersoft.iot.vmp.gb28181.task.deviceStatus;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceStatusManager {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private EventPublisher eventPublisher;
|
||||
|
||||
private final String prefix = "VMP_DEVICE_EXPIRES";
|
||||
|
||||
public String redisKey(){
|
||||
return String.format("%s_%s", prefix, userSetting.getServerId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态过期检查, 每秒检查一次, 系统启动10秒后开始检查
|
||||
*/
|
||||
@Scheduled(fixedDelay = 1, initialDelay = 10, timeUnit = TimeUnit.SECONDS)
|
||||
public void expirationCheck(){
|
||||
long now = System.currentTimeMillis();
|
||||
// 获取已过期的 deviceId (Score 介于 0 到 现在之间)
|
||||
Set<String> expiredIds = redisTemplate.opsForZSet().rangeByScore(redisKey(), 0, now);
|
||||
|
||||
if (expiredIds != null && !expiredIds.isEmpty()) {
|
||||
redisTemplate.opsForZSet().remove(redisKey(), expiredIds.toArray());
|
||||
// 使用 JDK 21 虚拟线程异步分发事件
|
||||
Thread.startVirtualThread(() -> {
|
||||
// 获取详情后删除缓存
|
||||
// Device device = redisCatchStorage.getDevice(deviceId);
|
||||
// redisCatchStorage.removeDevice(deviceId);
|
||||
// 发送 Spring 异步事件
|
||||
eventPublisher.deviceOfflineEventPublish(expiredIds);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void add(String deviceId, long expireTime) {
|
||||
redisTemplate.opsForZSet().add(redisKey(), deviceId, expireTime);
|
||||
}
|
||||
|
||||
public void remove(String deviceId) {
|
||||
redisTemplate.opsForZSet().remove(redisKey(), deviceId);
|
||||
}
|
||||
|
||||
public boolean contains(String deviceId) {
|
||||
if (ObjectUtils.isEmpty(deviceId)) {
|
||||
return false;
|
||||
}
|
||||
return redisTemplate.opsForZSet().score(redisKey(), deviceId) != null;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
redisTemplate.opsForZSet().removeRangeByScore(redisKey(), 0, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public Set<String> getAll() {
|
||||
return redisTemplate.opsForZSet().rangeByScore(redisKey(), 0, Long.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.task.deviceStatus;
|
||||
|
||||
import com.genersoft.iot.vmp.common.DeviceStatusCallback;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
public class DeviceStatusTask implements Delayed {
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private SipTransactionInfo transactionInfo;
|
||||
|
||||
/**
|
||||
* 超时时间(单位: 毫秒)
|
||||
*/
|
||||
private long delayTime;
|
||||
|
||||
private DeviceStatusCallback callback;
|
||||
|
||||
public static DeviceStatusTask getInstance(String deviceId, SipTransactionInfo transactionInfo, long delayTime, DeviceStatusCallback callback) {
|
||||
DeviceStatusTask deviceStatusTask = new DeviceStatusTask();
|
||||
deviceStatusTask.setDeviceId(deviceId);
|
||||
deviceStatusTask.setTransactionInfo(transactionInfo);
|
||||
deviceStatusTask.setDelayTime(delayTime);
|
||||
deviceStatusTask.setCallback(callback);
|
||||
return deviceStatusTask;
|
||||
}
|
||||
|
||||
public void expired() {
|
||||
if (callback == null) {
|
||||
log.info("[设备离线] 未找到过期处理回调, {}", deviceId);
|
||||
return;
|
||||
}
|
||||
callback.run(deviceId, transactionInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDelay(@NotNull TimeUnit unit) {
|
||||
return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Delayed o) {
|
||||
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
public DeviceStatusTaskInfo getInfo(){
|
||||
DeviceStatusTaskInfo taskInfo = new DeviceStatusTaskInfo();
|
||||
taskInfo.setTransactionInfo(transactionInfo);
|
||||
taskInfo.setDeviceId(deviceId);
|
||||
return taskInfo;
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.task.deviceStatus;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceStatusTaskInfo{
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private SipTransactionInfo transactionInfo;
|
||||
|
||||
/**
|
||||
* 过期时间,单位毫秒
|
||||
*/
|
||||
private long expireTime;
|
||||
}
|
||||
@ -1,131 +0,0 @@
|
||||
package com.genersoft.iot.vmp.gb28181.task.deviceStatus;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceStatusTaskRunner {
|
||||
|
||||
private final Map<String, DeviceStatusTask> subscribes = new ConcurrentHashMap<>();
|
||||
|
||||
private final DelayQueue<DeviceStatusTask> delayQueue = new DelayQueue<>();
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
private final String prefix = "VMP_DEVICE_STATUS";
|
||||
|
||||
// 状态过期检查
|
||||
@Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS)
|
||||
public void expirationCheck(){
|
||||
while (!delayQueue.isEmpty()) {
|
||||
DeviceStatusTask take = null;
|
||||
try {
|
||||
take = delayQueue.take();
|
||||
try {
|
||||
removeTask(take.getDeviceId());
|
||||
take.expired();
|
||||
}catch (Exception e) {
|
||||
log.error("[设备状态到期] 到期处理时出现异常, 设备编号: {} ", take.getDeviceId());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("[设备状态任务] ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addTask(DeviceStatusTask task) {
|
||||
Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000);
|
||||
if (duration.getSeconds() < 0) {
|
||||
return;
|
||||
}
|
||||
subscribes.put(task.getDeviceId(), task);
|
||||
String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId());
|
||||
redisTemplate.opsForValue().set(key, task.getInfo(), duration);
|
||||
delayQueue.offer(task);
|
||||
}
|
||||
|
||||
public boolean removeTask(String key) {
|
||||
DeviceStatusTask task = subscribes.get(key);
|
||||
if (task == null) {
|
||||
return false;
|
||||
}
|
||||
String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId());
|
||||
redisTemplate.delete(redisKey);
|
||||
subscribes.remove(key);
|
||||
if (delayQueue.contains(task)) {
|
||||
boolean remove = delayQueue.remove(task);
|
||||
if (!remove) {
|
||||
log.info("[移除状态任务] 从延时队列内移除失败: {}", key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public SipTransactionInfo getTransactionInfo(String key) {
|
||||
DeviceStatusTask task = subscribes.get(key);
|
||||
if (task == null) {
|
||||
return null;
|
||||
}
|
||||
return task.getTransactionInfo();
|
||||
}
|
||||
|
||||
public boolean updateDelay(String key, long expirationTime) {
|
||||
DeviceStatusTask task = subscribes.get(key);
|
||||
if (task == null) {
|
||||
return false;
|
||||
}
|
||||
log.debug("[更新状态任务时间] 编号: {}", key);
|
||||
// 如果值更改时间,如果队列中有多个元素时 超时无法出发。目前采用移除再加入的方法
|
||||
delayQueue.remove(task);
|
||||
task.setDelayTime(expirationTime);
|
||||
delayQueue.offer(task);
|
||||
String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId());
|
||||
Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis())/1000);
|
||||
redisTemplate.expire(redisKey, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean containsKey(String key) {
|
||||
return subscribes.containsKey(key);
|
||||
}
|
||||
|
||||
public List<DeviceStatusTaskInfo> getAllTaskInfo(){
|
||||
String scanKey = String.format("%s_%s_*", prefix, userSetting.getServerId());
|
||||
List<Object> values = RedisUtil.scan(redisTemplate, scanKey);
|
||||
if (values.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<DeviceStatusTaskInfo> result = new ArrayList<>();
|
||||
for (Object value : values) {
|
||||
String redisKey = (String)value;
|
||||
DeviceStatusTaskInfo taskInfo = (DeviceStatusTaskInfo)redisTemplate.opsForValue().get(redisKey);
|
||||
if (taskInfo == null) {
|
||||
continue;
|
||||
}
|
||||
Long expire = redisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);
|
||||
taskInfo.setExpireTime(expire);
|
||||
result.add(taskInfo);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.common.SubscribeCallback;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SubscribeTaskForAlarm extends SubscribeTask {
|
||||
|
||||
public static final String name = "alarm";
|
||||
|
||||
public static SubscribeTask getInstance(Device device, SubscribeCallback callback, SipTransactionInfo transactionInfo) {
|
||||
if (device.getSubscribeCycleForAlarm() <= 0) {
|
||||
return null;
|
||||
}
|
||||
SubscribeTaskForAlarm subscribeTaskForAlarm = new SubscribeTaskForAlarm();
|
||||
subscribeTaskForAlarm.setDelayTime((device.getSubscribeCycleForAlarm() * 1000L - 500L) + System.currentTimeMillis());
|
||||
subscribeTaskForAlarm.setDeviceId(device.getDeviceId());
|
||||
subscribeTaskForAlarm.setCallback(callback);
|
||||
subscribeTaskForAlarm.setTransactionInfo(transactionInfo);
|
||||
return subscribeTaskForAlarm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expired() {
|
||||
if (super.getCallback() == null) {
|
||||
log.info("[设备订阅到期] 报警订阅 未找到到期处理回调, 编号: {}", getDeviceId());
|
||||
return;
|
||||
}
|
||||
getCallback().run(getDeviceId(), getTransactionInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return String.format("%s_%s", name, getDeviceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static String getKey(Device device) {
|
||||
return String.format("%s_%s", SubscribeTaskForAlarm.name, device.getDeviceId());
|
||||
}
|
||||
}
|
||||
@ -59,7 +59,7 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
|
||||
* @param requestEvent RequestEvent事件
|
||||
*/
|
||||
@Override
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
public void processRequest(RequestEvent requestEvent) {
|
||||
String method = requestEvent.getRequest().getMethod();
|
||||
ISIPRequestProcessor sipRequestProcessor = requestProcessorMap.get(method);
|
||||
@ -77,7 +77,7 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
|
||||
* @param responseEvent responseEvent事件
|
||||
*/
|
||||
@Override
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
public void processResponse(ResponseEvent responseEvent) {
|
||||
SIPResponse response = (SIPResponse)responseEvent.getResponse();
|
||||
int status = response.getStatusCode();
|
||||
|
||||
@ -4,7 +4,7 @@ import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarmNotify;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
|
||||
@ -276,15 +276,8 @@ public interface ISIPCommander {
|
||||
|
||||
/**
|
||||
* 订阅、取消订阅报警信息
|
||||
* @param device 视频设备
|
||||
* @param expires 订阅过期时间(0 = 取消订阅)
|
||||
* @param startPriority 报警起始级别(可选)
|
||||
* @param endPriority 报警终止级别(可选)
|
||||
* @param startTime 报警发生起始时间(可选)
|
||||
* @param endTime 报警发生终止时间(可选)
|
||||
* @return true = 命令发送成功
|
||||
*/
|
||||
void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException;
|
||||
SIPRequest alarmSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
/**
|
||||
* 订阅、取消订阅目录信息
|
||||
@ -311,7 +304,7 @@ public interface ISIPCommander {
|
||||
* @param deviceAlarm 报警信息信息
|
||||
* @return
|
||||
*/
|
||||
void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
|
||||
void sendAlarmMessage(Device device, DeviceAlarmNotify deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
@ -102,7 +101,7 @@ public interface ISIPCommanderForPlatform {
|
||||
* @param deviceAlarm 报警信息信息
|
||||
* @return
|
||||
*/
|
||||
void sendAlarmMessage(Platform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException;
|
||||
void sendAlarmMessage(Platform parentPlatform, DeviceAlarmNotify deviceAlarm) throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
/**
|
||||
* 回复catalog事件-增加/更新
|
||||
|
||||
@ -1219,17 +1219,9 @@ public class SIPCommander implements ISIPCommander {
|
||||
/**
|
||||
* 订阅、取消订阅报警信息
|
||||
*
|
||||
* @param device 视频设备
|
||||
* @param expires 订阅过期时间(0 = 取消订阅)
|
||||
* @param startPriority 报警起始级别(可选)
|
||||
* @param endPriority 报警终止级别(可选)
|
||||
* @param alarmMethod 报警方式条件(可选)
|
||||
* @param startTime 报警发生起始时间(可选)
|
||||
* @param endTime 报警发生终止时间(可选)
|
||||
* @return true = 命令发送成功
|
||||
*/
|
||||
@Override
|
||||
public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException {
|
||||
public SIPRequest alarmSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
|
||||
StringBuffer cmdXml = new StringBuffer(200);
|
||||
String charset = device.getCharset();
|
||||
@ -1238,28 +1230,39 @@ public class SIPCommander implements ISIPCommander {
|
||||
cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
|
||||
cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
|
||||
cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
|
||||
if (!ObjectUtils.isEmpty(startPriority)) {
|
||||
cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(endPriority)) {
|
||||
cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(alarmMethod)) {
|
||||
cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(startTime)) {
|
||||
cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(endTime)) {
|
||||
cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
|
||||
}
|
||||
// cmdXml.append("<StartAlarmPriority>1</StartAlarmPriority>\r\n");
|
||||
// cmdXml.append("<EndAlarmPriority>4/EndAlarmPriority>\r\n");
|
||||
// cmdXml.append("<AlarmMethod>0</AlarmMethod>\r\n");
|
||||
//
|
||||
// LocalDateTime nowDateTime = LocalDateTime.now();
|
||||
// String startTime = DateUtil.formatterISO8601.format(nowDateTime);
|
||||
// // 退后一个月作为结束时间
|
||||
// String endTime = DateUtil.formatterISO8601.format(nowDateTime.plusMonths(1));
|
||||
//
|
||||
// cmdXml.append("<StartTime>" + startTime + "</StartTime>\r\n");
|
||||
// cmdXml.append("<EndTime>" + endTime + "</EndTime>\r\n");
|
||||
|
||||
cmdXml.append("</Query>\r\n");
|
||||
|
||||
CallIdHeader callIdHeader;
|
||||
|
||||
if (sipTransactionInfo != null) {
|
||||
callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId());
|
||||
} else {
|
||||
callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
|
||||
}
|
||||
|
||||
Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence",sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
|
||||
int subscribeCycleForAlarm = device.getSubscribeCycleForAlarm();
|
||||
if (subscribeCycleForAlarm > 0) {
|
||||
// 目录订阅有效期不小于 30 秒
|
||||
subscribeCycleForAlarm = Math.max(subscribeCycleForAlarm, 30);
|
||||
}
|
||||
|
||||
// 有效时间默认为60秒以上
|
||||
SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), sipTransactionInfo, subscribeCycleForAlarm, "presence",
|
||||
callIdHeader);
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1405,7 +1408,7 @@ public class SIPCommander implements ISIPCommander {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException {
|
||||
public void sendAlarmMessage(Device device, DeviceAlarmNotify deviceAlarm) throws InvalidArgumentException, SipException, ParseException {
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||
import com.genersoft.iot.vmp.common.enums.MediaApp;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
@ -15,9 +14,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.event.hook.Hook;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookType;
|
||||
import com.genersoft.iot.vmp.media.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.IReceiveRtpServerService;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
@ -396,7 +393,7 @@ public class SIPCommanderForPlatform implements ISIPCommanderForPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAlarmMessage(Platform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException {
|
||||
public void sendAlarmMessage(Platform parentPlatform, DeviceAlarmNotify deviceAlarm) throws SipException, InvalidArgumentException, ParseException {
|
||||
if (parentPlatform == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ public abstract class SIPRequestProcessorParent {
|
||||
return responseAck(sipRequest, statusCode, null);
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
public void responseAckAsync(SIPRequest sipRequest, int statusCode) throws SipException, InvalidArgumentException, ParseException {
|
||||
responseAck(sipRequest, statusCode, null);
|
||||
}
|
||||
|
||||
@ -0,0 +1,141 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.header.FromHeader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* SIP命令类型: NOTIFY请求中的报警通知请求处理
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NotifyRequestForAlarm extends SIPRequestProcessorParent {
|
||||
|
||||
private final ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private EventPublisher eventPublisher;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Autowired
|
||||
private IDeviceChannelService deviceChannelService;
|
||||
|
||||
|
||||
public void process(RequestEvent evt) {
|
||||
if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
|
||||
log.error("[notify-报警订阅] 待处理消息队列已满 {},返回486 BUSY_HERE", userSetting.getMaxNotifyCountQueue());
|
||||
return;
|
||||
}
|
||||
taskQueue.offer(new HandlerCatchData(evt, null, null));
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 400) //每400毫秒执行一次
|
||||
@Async
|
||||
public void executeTaskQueue(){
|
||||
if (taskQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<HandlerCatchData> handlerCatchDataList = new ArrayList<>();
|
||||
int size = taskQueue.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
HandlerCatchData poll = taskQueue.poll();
|
||||
if (poll != null) {
|
||||
handlerCatchDataList.add(poll);
|
||||
}
|
||||
}
|
||||
if (handlerCatchDataList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<DeviceAlarmNotify> deviceAlarmList = new ArrayList<>();
|
||||
for (HandlerCatchData take : handlerCatchDataList) {
|
||||
if (take == null) {
|
||||
continue;
|
||||
}
|
||||
RequestEvent evt = take.getEvt();
|
||||
try {
|
||||
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
|
||||
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
|
||||
|
||||
Element rootElement = getRootElement(evt);
|
||||
if (rootElement == null) {
|
||||
log.error("处理alarm设备报警Notify时未获取到消息体{}", evt.getRequest());
|
||||
return;
|
||||
}
|
||||
String channelId = rootElement.elementText("DeviceID");
|
||||
|
||||
Device device = redisCatchStorage.getDevice(deviceId);
|
||||
if (device == null) {
|
||||
log.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId);
|
||||
return;
|
||||
}
|
||||
rootElement = getRootElement(evt, device.getCharset());
|
||||
if (rootElement == null) {
|
||||
log.warn("[ NotifyAlarm ] content cannot be null, {}", evt.getRequest());
|
||||
return;
|
||||
}
|
||||
DeviceAlarmNotify deviceAlarmNotify = DeviceAlarmNotify.fromXml(rootElement);
|
||||
deviceAlarmNotify.setDeviceId(deviceId);
|
||||
deviceAlarmNotify.setDeviceName(device.getName());
|
||||
log.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarmNotify.getChannelId());
|
||||
if (deviceAlarmNotify.getAlarmMethod() != null && deviceAlarmNotify.getAlarmMethod() == DeviceAlarmMethod.GPS.getVal()) { // GPS报警
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId);
|
||||
if (deviceChannel == null) {
|
||||
log.warn("[解析报警通知] 未找到通道:{}/{}", device.getDeviceId(), channelId);
|
||||
}else {
|
||||
MobilePosition mobilePosition = new MobilePosition();
|
||||
mobilePosition.setChannelId(deviceChannel.getId());
|
||||
mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId());
|
||||
mobilePosition.setCreateTime(DateUtil.getNow());
|
||||
mobilePosition.setDeviceId(deviceAlarmNotify.getDeviceId());
|
||||
mobilePosition.setTime(deviceAlarmNotify.getAlarmTime());
|
||||
mobilePosition.setLongitude(deviceAlarmNotify.getLongitude());
|
||||
mobilePosition.setLatitude(deviceAlarmNotify.getLatitude());
|
||||
mobilePosition.setReportSource("GPS Alarm");
|
||||
|
||||
// 更新device channel 的经纬度
|
||||
deviceChannel.setLongitude(mobilePosition.getLongitude());
|
||||
deviceChannel.setLatitude(mobilePosition.getLatitude());
|
||||
deviceChannel.setGpsTime(mobilePosition.getTime());
|
||||
|
||||
deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition);
|
||||
}
|
||||
}
|
||||
|
||||
// 回复200 OK
|
||||
if (redisCatchStorage.deviceIsOnline(deviceId)) {
|
||||
deviceAlarmList.add(deviceAlarmNotify);
|
||||
}
|
||||
|
||||
} catch (DocumentException e) {
|
||||
log.error("未处理的异常 ", e);
|
||||
}
|
||||
}
|
||||
if (deviceAlarmList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
eventPublisher.deviceAlarmEventPublish(deviceAlarmList);
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -55,10 +56,6 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
|
||||
@Autowired
|
||||
private IGbChannelService channelService;
|
||||
|
||||
// @Scheduled(fixedRate = 2000) //每400毫秒执行一次
|
||||
// public void showSize(){
|
||||
// log.warn("[notify-目录订阅] 待处理消息数量: {}", taskQueue.size() );
|
||||
// }
|
||||
|
||||
public void process(RequestEvent evt) {
|
||||
if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
|
||||
@ -69,6 +66,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 400) //每400毫秒执行一次
|
||||
@Async
|
||||
public void executeTaskQueue(){
|
||||
if (taskQueue.isEmpty()) {
|
||||
return;
|
||||
|
||||
@ -17,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@ -61,6 +62,7 @@ public class NotifyRequestForMobilePositionProcessor extends SIPRequestProcessor
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 200) //每200毫秒执行一次
|
||||
@Async
|
||||
public void executeTaskQueue() {
|
||||
if (taskQueue.isEmpty()) {
|
||||
return;
|
||||
@ -157,7 +159,7 @@ public class NotifyRequestForMobilePositionProcessor extends SIPRequestProcessor
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("[收到移动位置订阅通知]:{}/{}->{}.{}, 时间: {}", mobilePosition.getDeviceId(), mobilePosition.getChannelId(),
|
||||
log.info("[收到移动位置订阅通知]:{}/{}->{}.{}, 时间: {}", mobilePosition.getDeviceId(), mobilePosition.getChannelDeviceId(),
|
||||
mobilePosition.getLongitude(), mobilePosition.getLatitude(), System.currentTimeMillis() - startTime);
|
||||
mobilePosition.setReportSource("Mobile Position");
|
||||
|
||||
|
||||
@ -1,17 +1,10 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.CmdType;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.DocumentException;
|
||||
@ -23,7 +16,6 @@ import org.springframework.stereotype.Component;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.header.FromHeader;
|
||||
import javax.sip.message.Response;
|
||||
import java.text.ParseException;
|
||||
|
||||
@ -34,29 +26,20 @@ import java.text.ParseException;
|
||||
@Component
|
||||
public class NotifyRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
|
||||
|
||||
@Autowired
|
||||
private SipConfig sipConfig;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Autowired
|
||||
private EventPublisher publisher;
|
||||
|
||||
private final String method = "NOTIFY";
|
||||
|
||||
@Autowired
|
||||
private SIPProcessorObserver sipProcessorObserver;
|
||||
|
||||
@Autowired
|
||||
private IDeviceChannelService deviceChannelService;
|
||||
|
||||
@Autowired
|
||||
private NotifyRequestForCatalogProcessor notifyRequestForCatalogProcessor;
|
||||
|
||||
@Autowired
|
||||
private NotifyRequestForMobilePositionProcessor notifyRequestForMobilePositionProcessor;
|
||||
|
||||
@Autowired
|
||||
private NotifyRequestForAlarm notifyRequestForAlarm;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// 添加消息处理的订阅
|
||||
@ -66,11 +49,10 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
|
||||
@Override
|
||||
public void process(RequestEvent evt) {
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
Element rootElement = getRootElement(evt);
|
||||
if (rootElement == null) {
|
||||
log.error("处理NOTIFY消息时未获取到消息体,{}", evt.getRequest());
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
|
||||
return;
|
||||
}
|
||||
String cmd = XmlUtil.getText(rootElement, "CmdType");
|
||||
@ -78,107 +60,16 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
|
||||
if (CmdType.CATALOG.equals(cmd)) {
|
||||
notifyRequestForCatalogProcessor.process(evt);
|
||||
} else if (CmdType.ALARM.equals(cmd)) {
|
||||
processNotifyAlarm(evt);
|
||||
notifyRequestForAlarm.process(evt);
|
||||
} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
|
||||
notifyRequestForMobilePositionProcessor.process(evt);
|
||||
} else {
|
||||
log.info("接收到消息:" + cmd);
|
||||
log.info("[Notify] 收到位置类型消息:{}, \r\n {}", cmd, evt.getRequest());
|
||||
}
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("未处理的异常 ", e);
|
||||
} catch (DocumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
/***
|
||||
* 处理alarm设备报警Notify
|
||||
*/
|
||||
private void processNotifyAlarm(RequestEvent evt) {
|
||||
if (!sipConfig.isAlarm()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
|
||||
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
|
||||
|
||||
Element rootElement = getRootElement(evt);
|
||||
if (rootElement == null) {
|
||||
log.error("处理alarm设备报警Notify时未获取到消息体{}", evt.getRequest());
|
||||
return;
|
||||
}
|
||||
Element deviceIdElement = rootElement.element("DeviceID");
|
||||
String channelId = deviceIdElement.getText().toString();
|
||||
|
||||
Device device = redisCatchStorage.getDevice(deviceId);
|
||||
if (device == null) {
|
||||
log.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId);
|
||||
return;
|
||||
}
|
||||
rootElement = getRootElement(evt, device.getCharset());
|
||||
if (rootElement == null) {
|
||||
log.warn("[ NotifyAlarm ] content cannot be null, {}", evt.getRequest());
|
||||
return;
|
||||
}
|
||||
DeviceAlarm deviceAlarm = new DeviceAlarm();
|
||||
deviceAlarm.setDeviceId(deviceId);
|
||||
deviceAlarm.setDeviceName(device.getName());
|
||||
deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority"));
|
||||
deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod"));
|
||||
String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
|
||||
if (alarmTime == null) {
|
||||
log.warn("[ NotifyAlarm ] AlarmTime cannot be null");
|
||||
return;
|
||||
}
|
||||
deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
|
||||
if (XmlUtil.getText(rootElement, "AlarmDescription") == null) {
|
||||
deviceAlarm.setAlarmDescription("");
|
||||
} else {
|
||||
deviceAlarm.setAlarmDescription(XmlUtil.getText(rootElement, "AlarmDescription"));
|
||||
}
|
||||
if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Longitude"))) {
|
||||
deviceAlarm.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude")));
|
||||
} else {
|
||||
deviceAlarm.setLongitude(0.00);
|
||||
}
|
||||
if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Latitude"))) {
|
||||
deviceAlarm.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude")));
|
||||
} else {
|
||||
deviceAlarm.setLatitude(0.00);
|
||||
}
|
||||
log.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId());
|
||||
if ("4".equals(deviceAlarm.getAlarmMethod())) {
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId);
|
||||
if (deviceChannel == null) {
|
||||
log.warn("[解析报警通知] 未找到通道:{}/{}", device.getDeviceId(), channelId);
|
||||
}else {
|
||||
MobilePosition mobilePosition = new MobilePosition();
|
||||
mobilePosition.setChannelId(deviceChannel.getId());
|
||||
mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId());
|
||||
mobilePosition.setCreateTime(DateUtil.getNow());
|
||||
mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
|
||||
mobilePosition.setTime(deviceAlarm.getAlarmTime());
|
||||
mobilePosition.setLongitude(deviceAlarm.getLongitude());
|
||||
mobilePosition.setLatitude(deviceAlarm.getLatitude());
|
||||
mobilePosition.setReportSource("GPS Alarm");
|
||||
|
||||
// 更新device channel 的经纬度
|
||||
deviceChannel.setLongitude(mobilePosition.getLongitude());
|
||||
deviceChannel.setLatitude(mobilePosition.getLatitude());
|
||||
deviceChannel.setGpsTime(mobilePosition.getTime());
|
||||
|
||||
deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition);
|
||||
}
|
||||
}
|
||||
|
||||
// 回复200 OK
|
||||
if (redisCatchStorage.deviceIsOnline(deviceId)) {
|
||||
publisher.deviceAlarmEventPublish(deviceAlarm);
|
||||
}
|
||||
} catch (DocumentException e) {
|
||||
log.error("未处理的异常 ", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.common.RemoteAddressInfo;
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.GbCode;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.GbSipDate;
|
||||
import com.genersoft.iot.vmp.common.RemoteAddressInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
@ -14,7 +14,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.utils.IpPortUtil;
|
||||
import gov.nist.javax.sip.address.AddressImpl;
|
||||
import gov.nist.javax.sip.address.SipUri;
|
||||
@ -38,6 +38,7 @@ import javax.sip.message.Response;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@ -64,6 +65,10 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// 添加消息处理的订阅
|
||||
@ -80,11 +85,8 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
Response response = null;
|
||||
boolean passwordCorrect = false;
|
||||
// 注册标志
|
||||
boolean registerFlag = true;
|
||||
if (request.getExpires().getExpires() == 0) {
|
||||
// 注销成功
|
||||
registerFlag = false;
|
||||
}
|
||||
boolean registerFlag = request.getExpires().getExpires() != 0;
|
||||
// 注销成功
|
||||
FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
|
||||
AddressImpl address = (AddressImpl) fromHeader.getAddress();
|
||||
SipUri uri = (SipUri) address.getURI();
|
||||
@ -98,7 +100,6 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
// 调整逻辑,如果为设置公共密码,那么就必须要预设用户信息,否则无法注册。
|
||||
Device device = deviceService.getDeviceByDeviceId(deviceId);
|
||||
@ -126,10 +127,10 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
String transport = reqViaHeader.getTransport();
|
||||
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
|
||||
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
|
||||
device.setRegisterTime(DateUtil.getNow());
|
||||
deviceService.online(device, null);
|
||||
device.setRegisterTimeStamp(System.currentTimeMillis());
|
||||
deviceService.online(device);
|
||||
} else {
|
||||
deviceService.offline(deviceId, "主动注销", false);
|
||||
deviceService.offline(device);
|
||||
}
|
||||
return;
|
||||
}else {
|
||||
@ -137,6 +138,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
if (!ObjectUtils.isEmpty(device.getPassword()) || !ObjectUtils.isEmpty(sipConfig.getPassword())) {
|
||||
password = (!ObjectUtils.isEmpty(device.getPassword())) ? device.getPassword() : sipConfig.getPassword();
|
||||
}
|
||||
// 如果设置了一个无密码的设备,那么这里就会自动跳动,后续会直接注册成功
|
||||
}
|
||||
}else {
|
||||
if (ObjectUtils.isEmpty(sipConfig.getPassword())) {
|
||||
@ -166,7 +168,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
// 注册失败
|
||||
response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
|
||||
response.setReasonPhrase("wrong password");
|
||||
log.info(title + " 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", deviceId, requestAddress);
|
||||
log.info("{} 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", title, deviceId, requestAddress);
|
||||
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
|
||||
return;
|
||||
}
|
||||
@ -175,7 +177,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
response = getMessageFactory().createResponse(Response.OK, request);
|
||||
// 如果主动禁用了Date头,则不添加
|
||||
if (!userSetting.isDisableDateHeader()) {
|
||||
// 添加date头
|
||||
// 添加 date头
|
||||
SIPDateHeader dateHeader = new SIPDateHeader();
|
||||
// 使用自己修改的
|
||||
GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
|
||||
@ -188,9 +190,9 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
|
||||
return;
|
||||
}
|
||||
// 添加Contact头
|
||||
// 添加 Contact头
|
||||
response.addHeader(request.getHeader(ContactHeader.NAME));
|
||||
// 添加Expires头
|
||||
// 添加 Expires头
|
||||
response.addHeader(request.getExpires());
|
||||
|
||||
if (device == null) {
|
||||
@ -224,7 +226,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
// 注册成功
|
||||
device.setExpires(request.getExpires().getExpires());
|
||||
registerFlag = true;
|
||||
// 判断TCP还是UDP
|
||||
// 判断 TCP/UDP
|
||||
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
|
||||
String transport = reqViaHeader.getTransport();
|
||||
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
|
||||
@ -232,16 +234,18 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
|
||||
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
|
||||
// 注册成功
|
||||
// 保存到redis
|
||||
device.setRegisterTimeStamp(System.currentTimeMillis());
|
||||
// 保存到 redis
|
||||
if (registerFlag) {
|
||||
log.info("[注册成功] deviceId: {}->{}", deviceId, requestAddress);
|
||||
device.setRegisterTime(DateUtil.getNow());
|
||||
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse) response);
|
||||
deviceService.online(device, sipTransactionInfo);
|
||||
device.setSipTransactionInfo(sipTransactionInfo);
|
||||
deviceService.online(device);
|
||||
} else {
|
||||
log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress);
|
||||
deviceService.offline(deviceId, "主动注销", false);
|
||||
deviceService.offline(device);
|
||||
}
|
||||
redisCatchStorage.updateDeviceRegisterTimeStamp(List.of(device));
|
||||
} catch (SipException | NoSuchAlgorithmException | ParseException e) {
|
||||
log.error("未处理的异常 ", e);
|
||||
}
|
||||
@ -268,4 +272,5 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import gov.nist.javax.sip.message.SIPRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
@ -74,9 +75,8 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Async
|
||||
public void handMessageEvent(Element element, Object data) {
|
||||
|
||||
String cmd = getText(element, "CmdType");
|
||||
String sn = getText(element, "SN");
|
||||
MessageEvent<Object> subscribe = (MessageEvent<Object>)messageSubscribe.getSubscribe(cmd + sn);
|
||||
|
||||
@ -6,13 +6,10 @@ import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceAlarmService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
@ -22,7 +19,6 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
@ -33,8 +29,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
|
||||
|
||||
/**
|
||||
* 报警事件的处理,参考:9.4
|
||||
*/
|
||||
@ -59,9 +53,6 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Autowired
|
||||
private IDeviceAlarmService deviceAlarmService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceChannelService deviceChannelService;
|
||||
|
||||
@ -78,6 +69,12 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
|
||||
log.error("[Alarm] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue());
|
||||
return;
|
||||
}
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 报警通知回复: {}", e.getMessage());
|
||||
}
|
||||
taskQueue.offer(new SipMsgInfo(evt, device, rootElement));
|
||||
}
|
||||
|
||||
@ -97,66 +94,34 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
|
||||
if (handlerCatchDataList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<DeviceAlarmNotify> deviceAlarmList = new ArrayList<>();
|
||||
for (SipMsgInfo sipMsgInfo : handlerCatchDataList) {
|
||||
if (sipMsgInfo == null) {
|
||||
if (sipMsgInfo == null || sipMsgInfo.getDevice() == null) {
|
||||
continue;
|
||||
}
|
||||
RequestEvent evt = sipMsgInfo.getEvt();
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 报警通知回复: {}", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
DeviceAlarmNotify deviceAlarmNotify = DeviceAlarmNotify.fromXml(sipMsgInfo.getRootElement());
|
||||
Device device = sipMsgInfo.getDevice();
|
||||
Element deviceIdElement = sipMsgInfo.getRootElement().element("DeviceID");
|
||||
String channelId = deviceIdElement.getText();
|
||||
|
||||
DeviceAlarm deviceAlarm = new DeviceAlarm();
|
||||
deviceAlarm.setCreateTime(DateUtil.getNow());
|
||||
deviceAlarm.setDeviceId(sipMsgInfo.getDevice().getDeviceId());
|
||||
deviceAlarm.setDeviceName(sipMsgInfo.getDevice().getName());
|
||||
deviceAlarm.setChannelId(channelId);
|
||||
deviceAlarm.setAlarmPriority(getText(sipMsgInfo.getRootElement(), "AlarmPriority"));
|
||||
deviceAlarm.setAlarmMethod(getText(sipMsgInfo.getRootElement(), "AlarmMethod"));
|
||||
String alarmTime = XmlUtil.getText(sipMsgInfo.getRootElement(), "AlarmTime");
|
||||
if (alarmTime == null) {
|
||||
continue;
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("[收到报警通知]设备:{}, 内容:{}", device.getDeviceId(), JSON.toJSONString(deviceAlarmNotify));
|
||||
}
|
||||
deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
|
||||
String alarmDescription = getText(sipMsgInfo.getRootElement(), "AlarmDescription");
|
||||
if (alarmDescription == null) {
|
||||
deviceAlarm.setAlarmDescription("");
|
||||
} else {
|
||||
deviceAlarm.setAlarmDescription(alarmDescription);
|
||||
}
|
||||
String longitude = getText(sipMsgInfo.getRootElement(), "Longitude");
|
||||
if (longitude != null && NumericUtil.isDouble(longitude)) {
|
||||
deviceAlarm.setLongitude(Double.parseDouble(longitude));
|
||||
} else {
|
||||
deviceAlarm.setLongitude(0.00);
|
||||
}
|
||||
String latitude = getText(sipMsgInfo.getRootElement(), "Latitude");
|
||||
if (latitude != null && NumericUtil.isDouble(latitude)) {
|
||||
deviceAlarm.setLatitude(Double.parseDouble(latitude));
|
||||
} else {
|
||||
deviceAlarm.setLatitude(0.00);
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(deviceAlarm.getAlarmMethod()) && deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.GPS.getVal() + "")) {
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), channelId);
|
||||
deviceAlarmNotify.setDeviceId(device.getDeviceId());
|
||||
deviceAlarmNotify.setDeviceName(device.getName());
|
||||
if (deviceAlarmNotify.getAlarmMethod() != null && deviceAlarmNotify.getAlarmMethod() == DeviceAlarmMethod.GPS.getVal()) {
|
||||
DeviceChannel deviceChannel = deviceChannelService.getOne(device.getDeviceId(), deviceAlarmNotify.getChannelId());
|
||||
if (deviceChannel == null) {
|
||||
log.warn("[解析报警消息] 未找到通道:{}/{}", device.getDeviceId(), channelId);
|
||||
log.warn("[解析报警消息] 未找到通道:{}/{}", device.getDeviceId(), deviceAlarmNotify.getChannelId());
|
||||
} else {
|
||||
MobilePosition mobilePosition = new MobilePosition();
|
||||
mobilePosition.setCreateTime(DateUtil.getNow());
|
||||
mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
|
||||
mobilePosition.setDeviceId(device.getDeviceId());
|
||||
mobilePosition.setChannelId(deviceChannel.getId());
|
||||
mobilePosition.setChannelDeviceId(deviceChannel.getDeviceId());
|
||||
mobilePosition.setTime(deviceAlarm.getAlarmTime());
|
||||
mobilePosition.setLongitude(deviceAlarm.getLongitude());
|
||||
mobilePosition.setLatitude(deviceAlarm.getLatitude());
|
||||
mobilePosition.setTime(deviceAlarmNotify.getAlarmTime());
|
||||
mobilePosition.setLongitude(deviceAlarmNotify.getLongitude());
|
||||
mobilePosition.setLatitude(deviceAlarmNotify.getLatitude());
|
||||
mobilePosition.setReportSource("GPS Alarm");
|
||||
|
||||
// 更新device channel 的经纬度
|
||||
@ -167,45 +132,33 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
|
||||
deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition);
|
||||
}
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(deviceAlarm.getDeviceId())) {
|
||||
if (deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.Video.getVal() + "")) {
|
||||
deviceAlarm.setAlarmType(getText(sipMsgInfo.getRootElement().element("Info"), "AlarmType"));
|
||||
}
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("[收到报警通知]设备:{}, 内容:{}", device.getDeviceId(), JSON.toJSONString(deviceAlarm));
|
||||
}
|
||||
|
||||
// 作者自用判断,其他小伙伴需要此消息可以自行修改,但是不要提在pr里
|
||||
if (DeviceAlarmMethod.Other.getVal() == Integer.parseInt(deviceAlarm.getAlarmMethod())) {
|
||||
if (deviceAlarmNotify.getAlarmMethod() != null
|
||||
&& DeviceAlarmMethod.Other.getVal() == deviceAlarmNotify.getAlarmMethod()) {
|
||||
// 发送给平台的报警信息。 发送redis通知
|
||||
log.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarm));
|
||||
log.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarmNotify));
|
||||
AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
|
||||
if (deviceAlarm.getAlarmMethod() != null) {
|
||||
alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
|
||||
}
|
||||
alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
|
||||
if (deviceAlarm.getAlarmType() != null) {
|
||||
alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
|
||||
}
|
||||
alarmChannelMessage.setGbId(channelId);
|
||||
alarmChannelMessage.setAlarmSn(deviceAlarmNotify.getAlarmMethod());
|
||||
alarmChannelMessage.setAlarmDescription(deviceAlarmNotify.getAlarmDescription());
|
||||
alarmChannelMessage.setAlarmType(deviceAlarmNotify.getAlarmType());
|
||||
alarmChannelMessage.setGbId(deviceAlarmNotify.getChannelId());
|
||||
redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.debug("存储报警信息、报警分类");
|
||||
// 存储报警信息、报警分类
|
||||
if (sipConfig.isAlarm()) {
|
||||
deviceAlarmService.add(deviceAlarm);
|
||||
}
|
||||
|
||||
if (redisCatchStorage.deviceIsOnline(sipMsgInfo.getDevice().getDeviceId())) {
|
||||
publisher.deviceAlarmEventPublish(deviceAlarm);
|
||||
deviceAlarmList.add(deviceAlarmNotify);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("未处理的异常 ", e);
|
||||
log.warn("[收到报警通知] 发现未处理的异常, {}\r\n{}", e.getMessage(), evt.getRequest());
|
||||
}
|
||||
}
|
||||
if (deviceAlarmList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
publisher.deviceAlarmEventPublish(deviceAlarmList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -219,57 +172,18 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
|
||||
}
|
||||
Element deviceIdElement = rootElement.element("DeviceID");
|
||||
String channelId = deviceIdElement.getText();
|
||||
|
||||
|
||||
DeviceAlarm deviceAlarm = new DeviceAlarm();
|
||||
deviceAlarm.setCreateTime(DateUtil.getNow());
|
||||
deviceAlarm.setDeviceId(parentPlatform.getServerGBId());
|
||||
deviceAlarm.setDeviceName(parentPlatform.getName());
|
||||
deviceAlarm.setChannelId(channelId);
|
||||
deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
|
||||
deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
|
||||
String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
|
||||
if (alarmTime == null) {
|
||||
return;
|
||||
}
|
||||
deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
|
||||
String alarmDescription = getText(rootElement, "AlarmDescription");
|
||||
if (alarmDescription == null) {
|
||||
deviceAlarm.setAlarmDescription("");
|
||||
} else {
|
||||
deviceAlarm.setAlarmDescription(alarmDescription);
|
||||
}
|
||||
String longitude = getText(rootElement, "Longitude");
|
||||
if (longitude != null && NumericUtil.isDouble(longitude)) {
|
||||
deviceAlarm.setLongitude(Double.parseDouble(longitude));
|
||||
} else {
|
||||
deviceAlarm.setLongitude(0.00);
|
||||
}
|
||||
String latitude = getText(rootElement, "Latitude");
|
||||
if (latitude != null && NumericUtil.isDouble(latitude)) {
|
||||
deviceAlarm.setLatitude(Double.parseDouble(latitude));
|
||||
} else {
|
||||
deviceAlarm.setLatitude(0.00);
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(deviceAlarm.getAlarmMethod())) {
|
||||
|
||||
if (deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethod.Video.getVal() + "")) {
|
||||
deviceAlarm.setAlarmType(getText(rootElement.element("Info"), "AlarmType"));
|
||||
}
|
||||
}
|
||||
DeviceAlarmNotify deviceAlarmNotify = DeviceAlarmNotify.fromXml(rootElement);
|
||||
deviceAlarmNotify.setDeviceId(parentPlatform.getServerGBId());
|
||||
deviceAlarmNotify.setDeviceName(parentPlatform.getName());
|
||||
deviceAlarmNotify.setChannelId(channelId);
|
||||
|
||||
if (channelId.equals(parentPlatform.getDeviceGBId())) {
|
||||
// 发送给平台的报警信息。 发送redis通知
|
||||
AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
|
||||
if (deviceAlarm.getAlarmMethod() != null) {
|
||||
alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
|
||||
}
|
||||
alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
|
||||
alarmChannelMessage.setAlarmSn(deviceAlarmNotify.getAlarmMethod());
|
||||
alarmChannelMessage.setAlarmDescription(deviceAlarmNotify.getAlarmDescription());
|
||||
alarmChannelMessage.setGbId(channelId);
|
||||
if (deviceAlarm.getAlarmType() != null) {
|
||||
alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
|
||||
}
|
||||
alarmChannelMessage.setAlarmType(deviceAlarmNotify.getAlarmType());
|
||||
redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
|
||||
|
||||
import com.genersoft.iot.vmp.common.RemoteAddressInfo;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Platform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipMsgInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusTaskRunner;
|
||||
import com.genersoft.iot.vmp.gb28181.task.deviceStatus.DeviceStatusManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.utils.IpPortUtil;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -27,9 +25,9 @@ import javax.sip.RequestEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.message.Response;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 状态信息(心跳)报送
|
||||
@ -41,7 +39,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
|
||||
|
||||
private final static String cmdType = "Keepalive";
|
||||
|
||||
private final ConcurrentLinkedQueue<SipMsgInfo> taskQueue = new ConcurrentLinkedQueue<>();
|
||||
private final BlockingQueue<Device> taskQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
@Autowired
|
||||
private NotifyMessageHandler notifyMessageHandler;
|
||||
@ -50,13 +48,13 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private DeviceStatusTaskRunner statusTaskRunner;
|
||||
private DeviceStatusManager deviceStatusManager;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private DynamicTask dynamicTask;
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
@ -65,80 +63,56 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
|
||||
|
||||
@Override
|
||||
public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
|
||||
if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
|
||||
log.error("[心跳] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue());
|
||||
return;
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 心跳回复: {}", e.getMessage());
|
||||
}
|
||||
taskQueue.add(device);
|
||||
SIPRequest request = (SIPRequest) evt.getRequest();
|
||||
|
||||
RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
|
||||
if (device.getIp() == null || !device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) {
|
||||
log.info("[收到心跳] 地址变化, {}({}), {}:{}->{}:{}", device.getName(), device.getDeviceId(), device.getIp(), device.getPort(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort());
|
||||
device.setPort(remoteAddressInfo.getPort());
|
||||
device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort())));
|
||||
device.setIp(remoteAddressInfo.getIp());
|
||||
device.setLocalIp(request.getLocalAddress().getHostAddress());
|
||||
}
|
||||
device.setKeepaliveTimeStamp(System.currentTimeMillis());
|
||||
|
||||
if (device.isOnLine()) {
|
||||
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
|
||||
deviceStatusManager.add(device.getDeviceId(), expiresTime + System.currentTimeMillis());
|
||||
} else {
|
||||
if (userSetting.getGbDeviceOnline() == 1) {
|
||||
// 对于已经离线的设备判断他的注册是否已经过期
|
||||
deviceService.online(device);
|
||||
}
|
||||
}
|
||||
taskQueue.offer(new SipMsgInfo(evt, device, rootElement));
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 100)
|
||||
public void executeTaskQueue() {
|
||||
if (taskQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<SipMsgInfo> handlerCatchDataList = new ArrayList<>();
|
||||
int size = taskQueue.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
SipMsgInfo poll = taskQueue.poll();
|
||||
if (poll != null) {
|
||||
handlerCatchDataList.add(poll);
|
||||
@Scheduled(fixedDelay = 10, timeUnit = TimeUnit.SECONDS)
|
||||
public void executeUpdateDeviceList() {
|
||||
log.debug("[定时任务] 更新心跳记录,待处理设备数量: {}", taskQueue.size());
|
||||
try {
|
||||
if (!taskQueue.isEmpty()) {
|
||||
redisCatchStorage.updateDeviceKeepaliveTimeStamp(taskQueue.stream().toList());
|
||||
taskQueue.clear();
|
||||
}
|
||||
}
|
||||
if (handlerCatchDataList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Device> deviceListForUpdate = new ArrayList<>();
|
||||
for (SipMsgInfo sipMsgInfo : handlerCatchDataList) {
|
||||
if (sipMsgInfo == null) {
|
||||
continue;
|
||||
}
|
||||
RequestEvent evt = sipMsgInfo.getEvt();
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 心跳回复: {}", e.getMessage());
|
||||
}
|
||||
Device device = sipMsgInfo.getDevice();
|
||||
SIPRequest request = (SIPRequest) evt.getRequest();
|
||||
|
||||
RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
|
||||
if (device.getIp() == null || !device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) {
|
||||
log.info("[收到心跳] 地址变化, {}({}), {}:{}->{}", device.getName(), device.getDeviceId(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort(), request.getLocalAddress().getHostAddress());
|
||||
device.setPort(remoteAddressInfo.getPort());
|
||||
device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort())));
|
||||
device.setIp(remoteAddressInfo.getIp());
|
||||
device.setLocalIp(request.getLocalAddress().getHostAddress());
|
||||
}
|
||||
|
||||
device.setKeepaliveTime(DateUtil.getNow());
|
||||
|
||||
if (device.isOnLine()) {
|
||||
deviceListForUpdate.add(device);
|
||||
long expiresTime = Math.min(device.getExpires(), device.getHeartBeatInterval() * device.getHeartBeatCount()) * 1000L;
|
||||
if (statusTaskRunner.containsKey(device.getDeviceId())) {
|
||||
statusTaskRunner.updateDelay(device.getDeviceId(), expiresTime + System.currentTimeMillis());
|
||||
}
|
||||
} else {
|
||||
if (userSetting.getGbDeviceOnline() == 1) {
|
||||
// 对于已经离线的设备判断他的注册是否已经过期
|
||||
deviceService.online(device, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!deviceListForUpdate.isEmpty()) {
|
||||
deviceService.updateDeviceList(deviceListForUpdate);
|
||||
} catch (Exception e) {
|
||||
log.error("[定时任务] 更新心跳记录 执行异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) {
|
||||
// 个别平台保活不回复200OK会判定离线
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 心跳回复: {}", e.getMessage());
|
||||
log.error("[命令发送失败] 报警通知回复: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,13 +9,13 @@ import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@ -33,6 +33,7 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
|
||||
|
||||
private final String cmdType = "MobilePosition";
|
||||
@ -45,9 +46,8 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
|
||||
|
||||
private ConcurrentLinkedQueue<SipMsgInfo> taskQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Qualifier("taskExecutor")
|
||||
@Autowired
|
||||
private ThreadPoolTaskExecutor taskExecutor;
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
@ -61,7 +61,7 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
|
||||
taskQueue.offer(new SipMsgInfo(evt, device, rootElement));
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 移动位置通知回复: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ public class AlarmResponseMessageHandler extends SIPRequestProcessorParent imple
|
||||
public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 目录查询回复: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import org.dom4j.DocumentException;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@ -30,6 +29,7 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.lang.Thread;
|
||||
|
||||
/**
|
||||
* 目录查询的回复
|
||||
@ -67,136 +67,114 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
|
||||
|
||||
@Override
|
||||
public void handForDevice(RequestEvent evt, Device device, Element element) {
|
||||
taskQueue.offer(new HandlerCatchData(evt, device, element));
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 目录查询回复: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 50)
|
||||
@Transactional
|
||||
public void executeTaskQueue(){
|
||||
if (taskQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<HandlerCatchData> handlerCatchDataList = new ArrayList<>();
|
||||
int size = taskQueue.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
HandlerCatchData poll = taskQueue.poll();
|
||||
if (poll != null) {
|
||||
handlerCatchDataList.add(poll);
|
||||
}
|
||||
}
|
||||
if (handlerCatchDataList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (HandlerCatchData take : handlerCatchDataList) {
|
||||
if (take == null) {
|
||||
continue;
|
||||
}
|
||||
RequestEvent evt = take.getEvt();
|
||||
int sn = 0;
|
||||
// 全局异常捕获,保证下一条可以得到处理
|
||||
int sn = 0;
|
||||
// 全局异常捕获,保证下一条可以得到处理
|
||||
try {
|
||||
Element rootElement = null;
|
||||
try {
|
||||
Element rootElement = null;
|
||||
try {
|
||||
rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
|
||||
} catch (DocumentException e) {
|
||||
log.error("[xml解析] 失败: ", e);
|
||||
continue;
|
||||
}
|
||||
if (rootElement == null) {
|
||||
log.warn("[ 收到通道 ] content cannot be null, {}", evt.getRequest());
|
||||
continue;
|
||||
}
|
||||
Element deviceListElement = rootElement.element("DeviceList");
|
||||
Element sumNumElement = rootElement.element("SumNum");
|
||||
Element snElement = rootElement.element("SN");
|
||||
rootElement = getRootElement(evt, device.getCharset());
|
||||
} catch (DocumentException e) {
|
||||
log.error("[xml解析] 失败: ", e);
|
||||
return;
|
||||
}
|
||||
if (rootElement == null) {
|
||||
log.warn("[ 收到通道 ] content cannot be null, {}", evt.getRequest());
|
||||
return;
|
||||
}
|
||||
Element deviceListElement = rootElement.element("DeviceList");
|
||||
Element sumNumElement = rootElement.element("SumNum");
|
||||
Element snElement = rootElement.element("SN");
|
||||
|
||||
sn = Integer.parseInt(snElement.getText());
|
||||
int sumNum = Integer.parseInt(sumNumElement.getText());
|
||||
sn = Integer.parseInt(snElement.getText());
|
||||
int sumNum = Integer.parseInt(sumNumElement.getText());
|
||||
|
||||
if (sumNum == 0) {
|
||||
log.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId());
|
||||
// 数据已经完整接收
|
||||
deviceChannelService.cleanChannelsForDevice(take.getDevice().getId());
|
||||
// 推送空数据,不然无法及时结束
|
||||
catalogDataCatch.put(take.getDevice().getDeviceId(), sn, 0, take.getDevice(),
|
||||
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
||||
catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), sn, null);
|
||||
return;
|
||||
} else {
|
||||
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
|
||||
if (deviceListIterator != null) {
|
||||
List<DeviceChannel> channelList = new ArrayList<>();
|
||||
List<Region> regionList = new ArrayList<>();
|
||||
List<Group> groupList = new ArrayList<>();
|
||||
// 遍历DeviceList
|
||||
while (deviceListIterator.hasNext()) {
|
||||
Element itemDevice = deviceListIterator.next();
|
||||
Element channelDeviceElement = itemDevice.element("DeviceID");
|
||||
if (channelDeviceElement == null) {
|
||||
// 总数减一, 避免最后总数不对 无法确定问题
|
||||
continue;
|
||||
}
|
||||
// 从xml解析内容到 DeviceChannel 对象
|
||||
DeviceChannel channel = DeviceChannel.decode(itemDevice);
|
||||
if (channel.getDeviceId() == null) {
|
||||
log.info("[收到目录订阅]:但是解析失败 {}", new String(evt.getRequest().getRawContent()));
|
||||
continue;
|
||||
}
|
||||
channel.setDataDeviceId(take.getDevice().getId());
|
||||
if (channel.getParentId() != null && channel.getParentId().equals(sipConfig.getId())) {
|
||||
channel.setParentId(null);
|
||||
}
|
||||
// 解析通道类型
|
||||
if (channel.getDeviceId().length() <= 8) {
|
||||
// 行政区划
|
||||
Region region = Region.getInstance(channel);
|
||||
regionList.add(region);
|
||||
channel.setChannelType(1);
|
||||
}else if (channel.getDeviceId().length() == 20){
|
||||
// 业务分组/虚拟组织
|
||||
Group group = Group.getInstance(channel);
|
||||
if (group != null) {
|
||||
channel.setParental(1);
|
||||
channel.setChannelType(2);
|
||||
groupList.add(group);
|
||||
}
|
||||
if (channel.getLongitude() != null && channel.getLatitude() != null && channel.getLongitude() > 0 && channel.getLatitude() > 0) {
|
||||
Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(channel.getLongitude(), channel.getLatitude());
|
||||
channel.setGbLongitude(wgs84Position[0]);
|
||||
channel.setGbLatitude(wgs84Position[1]);
|
||||
}
|
||||
}
|
||||
channelList.add(channel);
|
||||
if (sumNum == 0) {
|
||||
log.info("[收到通道]设备:{}的: 0个", device.getDeviceId());
|
||||
// 数据已经完整接收
|
||||
deviceChannelService.cleanChannelsForDevice(device.getId());
|
||||
// 推送空数据,不然无法及时结束
|
||||
catalogDataCatch.put(device.getDeviceId(), sn, 0, device,
|
||||
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
||||
catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), sn, null);
|
||||
return;
|
||||
} else {
|
||||
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
|
||||
if (deviceListIterator != null) {
|
||||
List<DeviceChannel> channelList = new ArrayList<>();
|
||||
List<Region> regionList = new ArrayList<>();
|
||||
List<Group> groupList = new ArrayList<>();
|
||||
// 遍历DeviceList
|
||||
while (deviceListIterator.hasNext()) {
|
||||
Element itemDevice = deviceListIterator.next();
|
||||
Element channelDeviceElement = itemDevice.element("DeviceID");
|
||||
if (channelDeviceElement == null) {
|
||||
// 总数减一, 避免最后总数不对 无法确定问题
|
||||
continue;
|
||||
}
|
||||
// 从xml解析内容到 DeviceChannel 对象
|
||||
DeviceChannel channel = DeviceChannel.decode(itemDevice);
|
||||
if (channel.getDeviceId() == null) {
|
||||
log.info("[收到目录订阅]:但是解析失败 {}", new String(evt.getRequest().getRawContent()));
|
||||
continue;
|
||||
}
|
||||
channel.setDataDeviceId(device.getId());
|
||||
if (channel.getParentId() != null && channel.getParentId().equals(sipConfig.getId())) {
|
||||
channel.setParentId(null);
|
||||
}
|
||||
// 解析通道类型
|
||||
if (channel.getDeviceId().length() <= 8) {
|
||||
// 行政区划
|
||||
Region region = Region.getInstance(channel);
|
||||
regionList.add(region);
|
||||
channel.setChannelType(1);
|
||||
}else if (channel.getDeviceId().length() == 20){
|
||||
// 业务分组/虚拟组织
|
||||
Group group = Group.getInstance(channel);
|
||||
if (group != null) {
|
||||
channel.setParental(1);
|
||||
channel.setChannelType(2);
|
||||
groupList.add(group);
|
||||
}
|
||||
if (channel.getLongitude() != null && channel.getLatitude() != null && channel.getLongitude() > 0 && channel.getLatitude() > 0) {
|
||||
Double[] wgs84Position = Coordtransform.GCJ02ToWGS84(channel.getLongitude(), channel.getLatitude());
|
||||
channel.setGbLongitude(wgs84Position[0]);
|
||||
channel.setGbLatitude(wgs84Position[1]);
|
||||
}
|
||||
}
|
||||
channelList.add(channel);
|
||||
}
|
||||
|
||||
catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(),
|
||||
channelList, regionList, groupList);
|
||||
log.info("[收到通道]设备: {} -> {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.size(take.getDevice().getDeviceId(), sn), sumNum);
|
||||
}
|
||||
catalogDataCatch.put(device.getDeviceId(), sn, sumNum, device,
|
||||
channelList, regionList, groupList);
|
||||
log.info("[收到通道]设备: {} -> {}个,{}/{}", device.getDeviceId(), channelList.size(), catalogDataCatch.size(device.getDeviceId(), sn), sumNum);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[收到通道] 发现未处理的异常, \r\n{}", evt.getRequest());
|
||||
log.error("[收到通道] 异常内容: ", e);
|
||||
} finally {
|
||||
String deviceId = take.getDevice().getDeviceId();
|
||||
if (catalogDataCatch.size(deviceId, sn) > 0
|
||||
&& catalogDataCatch.size(deviceId, sn) == catalogDataCatch.sumNum(deviceId, sn)) {
|
||||
// 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理,
|
||||
// 目前支持设备通道上线通知时和设备上线时向上级通知
|
||||
boolean resetChannelsResult = saveData(take.getDevice(), sn);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[收到通道] 发现未处理的异常, \r\n{}", evt.getRequest());
|
||||
log.error("[收到通道] 异常内容: ", e);
|
||||
} finally {
|
||||
String deviceId = device.getDeviceId();
|
||||
if (catalogDataCatch.size(deviceId, sn) > 0
|
||||
&& catalogDataCatch.size(deviceId, sn) == catalogDataCatch.sumNum(deviceId, sn)) {
|
||||
// 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理,
|
||||
// 目前支持设备通道上线通知时和设备上线时向上级通知
|
||||
int finalSn = sn;
|
||||
Thread.startVirtualThread(() -> {
|
||||
boolean resetChannelsResult = saveData(device, finalSn);
|
||||
if (!resetChannelsResult) {
|
||||
String errorMsg = "接收成功,写入失败,共" + catalogDataCatch.sumNum(deviceId, sn) + "条,已接收" + catalogDataCatch.getDeviceChannelList(take.getDevice().getDeviceId(), sn).size() + "条";
|
||||
catalogDataCatch.setChannelSyncEnd(deviceId, sn, errorMsg);
|
||||
String errorMsg = "接收成功,写入失败,共" + catalogDataCatch.sumNum(deviceId, finalSn) + "条,已接收" + catalogDataCatch.getDeviceChannelList(device.getDeviceId(), finalSn).size() + "条";
|
||||
catalogDataCatch.setChannelSyncEnd(deviceId, finalSn, errorMsg);
|
||||
} else {
|
||||
catalogDataCatch.setChannelSyncEnd(deviceId, sn, null);
|
||||
catalogDataCatch.setChannelSyncEnd(deviceId, finalSn, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Platform;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.message.Response;
|
||||
import java.text.ParseException;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceControlResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
|
||||
|
||||
private final String cmdType = "DeviceControl";
|
||||
|
||||
@Autowired
|
||||
private ResponseMessageHandler responseMessageHandler;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
responseMessageHandler.addHandler(cmdType, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handForDevice(RequestEvent evt, Device device, Element element) {
|
||||
log.info("[DeviceControl Response] \n {}", element.asXML());
|
||||
// 检查设备是否存在, 不存在则不回复
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 国标级联 设备状态应答回复200OK: {}", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element rootElement) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -81,7 +81,7 @@ public class DeviceInfoResponseMessageHandler extends SIPRequestProcessorParent
|
||||
}
|
||||
try {
|
||||
// 回复200 OK
|
||||
responseAck(request, Response.OK);
|
||||
responseAckAsync(request, Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] DeviceInfo应答消息 200: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@ -47,17 +47,11 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
|
||||
}
|
||||
// 回复200 OK
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
responseAckAsync((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 国标级联 设备状态应答回复200OK: {}", e.getMessage());
|
||||
}
|
||||
Element onlineElement = element.element("Online");
|
||||
JSONObject json = new JSONObject();
|
||||
XmlUtil.node2Json(element, json);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(json.toJSONString());
|
||||
}
|
||||
String text = onlineElement.getText();
|
||||
String text = element.elementText("Online");
|
||||
responseMessageHandler.handMessageEvent(element, text);
|
||||
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
|
||||
if (rootElement == null) {
|
||||
log.warn("[ 移动设备位置数据查询回复 ] content cannot be null, {}", evt.getRequest());
|
||||
try {
|
||||
responseAck(request, Response.BAD_REQUEST);
|
||||
responseAckAsync(request, Response.BAD_REQUEST);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 移动设备位置数据查询 BAD_REQUEST: {}", e.getMessage());
|
||||
}
|
||||
@ -124,7 +124,7 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
|
||||
|
||||
//回复 200 OK
|
||||
try {
|
||||
responseAck(request, Response.OK);
|
||||
responseAckAsync(request, Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
log.error("[命令发送失败] 移动设备位置数据查询 200: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@ -5,11 +5,8 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Platform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.event.record.RecordInfoEndEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.event.record.RecordInfoEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
|
||||
@ -20,10 +17,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@ import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -44,4 +44,9 @@ public class SourcePlayServiceForJTImpl implements ISourcePlayService {
|
||||
log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -86,7 +86,7 @@ public class jt1078PlayServiceImpl implements Ijt1078PlayService {
|
||||
/**
|
||||
* 流到来的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaArrivalEvent event) {
|
||||
if (event.getApp().equals(talkApp) && event.getStream().endsWith("_talk")) {
|
||||
@ -115,7 +115,7 @@ public class jt1078PlayServiceImpl implements Ijt1078PlayService {
|
||||
/**
|
||||
* 流离开的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaDepartureEvent event) {
|
||||
|
||||
@ -124,7 +124,7 @@ public class jt1078PlayServiceImpl implements Ijt1078PlayService {
|
||||
/**
|
||||
* 流未找到的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaNotFoundEvent event) {
|
||||
if (!userSetting.getAutoApplyPlay()) {
|
||||
|
||||
@ -119,7 +119,7 @@ public class jt1078ServiceImpl implements Ijt1078Service {
|
||||
/**
|
||||
* 流到来的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@org.springframework.context.event.EventListener
|
||||
public void onApplicationEvent(MediaArrivalEvent event) {
|
||||
|
||||
@ -128,7 +128,7 @@ public class jt1078ServiceImpl implements Ijt1078Service {
|
||||
/**
|
||||
* 流离开的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaDepartureEvent event) {
|
||||
|
||||
@ -137,7 +137,7 @@ public class jt1078ServiceImpl implements Ijt1078Service {
|
||||
/**
|
||||
* 设备更新的通知
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(DeviceUpdateEvent event) {
|
||||
JTDevice device = event.getDevice();
|
||||
@ -163,7 +163,7 @@ public class jt1078ServiceImpl implements Ijt1078Service {
|
||||
/**
|
||||
* 位置更新的通知
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(JTPositionEvent event) {
|
||||
if (event.getPhoneNumber() == null || event.getPositionInfo() == null
|
||||
|
||||
@ -26,10 +26,8 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -81,10 +79,6 @@ public class ABLHttpHookListener {
|
||||
@Autowired
|
||||
private SSRCFactory ssrcFactory;
|
||||
|
||||
@Qualifier("taskExecutor")
|
||||
@Autowired
|
||||
private ThreadPoolTaskExecutor taskExecutor;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
|
||||
@ -264,8 +264,8 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) {
|
||||
ablresTfulUtils.getSnap(mediaServer, app, stream, timeoutSec, path, fileName);
|
||||
public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) {
|
||||
return ablresTfulUtils.getSnap(mediaServer, app, stream, timeoutSec, path, fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -59,7 +59,7 @@ public class ABLMediaServerStatusManger {
|
||||
|
||||
private final String type = "abl";
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaServerChangeEvent event) {
|
||||
if (event.getMediaServerItemList() == null
|
||||
@ -77,7 +77,7 @@ public class ABLMediaServerStatusManger {
|
||||
execute();
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(HookAblServerStartEvent event) {
|
||||
if (event.getMediaServerItem() == null
|
||||
@ -93,7 +93,7 @@ public class ABLMediaServerStatusManger {
|
||||
online(serverItem, null);
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(HookAblServerKeepaliveEvent event) {
|
||||
if (event.getMediaServerItem() == null) {
|
||||
@ -107,7 +107,7 @@ public class ABLMediaServerStatusManger {
|
||||
online(serverItem, null);
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaServerDeleteEvent event) {
|
||||
if (event.getMediaServer() == null) {
|
||||
|
||||
@ -203,11 +203,11 @@ public class ABLRESTfulUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void sendGetForImg(MediaServer mediaServerItem, String api, Map<String, Object> params, String targetPath, String fileName) {
|
||||
public byte[] sendGetForImg(MediaServer mediaServerItem, String api, Map<String, Object> params, String targetPath, String fileName) {
|
||||
String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
|
||||
HttpUrl parseUrl = HttpUrl.parse(url);
|
||||
if (parseUrl == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
HttpUrl.Builder httpBuilder = parseUrl.newBuilder();
|
||||
|
||||
@ -239,9 +239,8 @@ public class ABLRESTfulUtils {
|
||||
outStream.write(Objects.requireNonNull(response.body()).bytes());
|
||||
outStream.flush();
|
||||
outStream.close();
|
||||
} else {
|
||||
logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));
|
||||
}
|
||||
return Objects.requireNonNull(response.body()).bytes();
|
||||
} else {
|
||||
logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));
|
||||
}
|
||||
@ -252,6 +251,7 @@ public class ABLRESTfulUtils {
|
||||
} catch (IOException e) {
|
||||
logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void sendGetForImgForUrl(String url, String targetPath, String fileName) {
|
||||
@ -414,7 +414,7 @@ public class ABLRESTfulUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, String path, String fileName) {
|
||||
public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, String path, String fileName) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("app", app);
|
||||
param.put("stream", stream);
|
||||
@ -425,8 +425,7 @@ public class ABLRESTfulUtils {
|
||||
// String url = jsonObject.getString("url");
|
||||
// sendGetForImgForUrl(url, path, fileName);
|
||||
// }
|
||||
sendGetForImg(mediaServer, "getSnap", param, path, fileName);
|
||||
|
||||
return sendGetForImg(mediaServer, "getSnap", param, path, fileName);
|
||||
}
|
||||
|
||||
public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
|
||||
|
||||
@ -32,7 +32,7 @@ public class HookSubscribe {
|
||||
/**
|
||||
* 流到来的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaArrivalEvent event) {
|
||||
if (event.getSchema() == null || "rtsp".equals(event.getSchema())) {
|
||||
@ -44,7 +44,7 @@ public class HookSubscribe {
|
||||
/**
|
||||
* 流结束事件
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaDepartureEvent event) {
|
||||
if (event.getSchema() == null || "rtsp".equals(event.getSchema())) {
|
||||
@ -55,7 +55,7 @@ public class HookSubscribe {
|
||||
/**
|
||||
* 推流鉴权事件
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaPublishEvent event) {
|
||||
sendNotify(HookType.on_publish, event);
|
||||
@ -63,7 +63,7 @@ public class HookSubscribe {
|
||||
/**
|
||||
* 生成录像文件事件
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaRecordMp4Event event) {
|
||||
sendNotify(HookType.on_record_mp4, event);
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
package com.genersoft.iot.vmp.media.event.mediaServer;
|
||||
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class MediaServerChangeEvent extends ApplicationEvent {
|
||||
|
||||
public MediaServerChangeEvent(Object source) {
|
||||
@ -15,10 +17,6 @@ public class MediaServerChangeEvent extends ApplicationEvent {
|
||||
|
||||
private List<MediaServer> mediaServerItemList;
|
||||
|
||||
public List<MediaServer> getMediaServerItemList() {
|
||||
return mediaServerItemList;
|
||||
}
|
||||
|
||||
public void setMediaServerItemList(List<MediaServer> mediaServerItemList) {
|
||||
this.mediaServerItemList = mediaServerItemList;
|
||||
}
|
||||
|
||||
@ -22,14 +22,14 @@ public class MediaServerStatusEventListener {
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaServerOnlineEvent event) {
|
||||
log.info("[媒体节点] 上线 ID:" + event.getMediaServer().getId());
|
||||
playService.zlmServerOnline(event.getMediaServer());
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaServerOfflineEvent event) {
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ public interface IMediaNodeServerService {
|
||||
|
||||
Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String app, String stream);
|
||||
|
||||
void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName);
|
||||
byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName);
|
||||
|
||||
MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream);
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ public interface IMediaServerService {
|
||||
|
||||
Boolean connectRtpServer(MediaServer mediaServerItem, String address, int port, String app, String stream);
|
||||
|
||||
void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName);
|
||||
byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName);
|
||||
|
||||
MediaInfo getMediaInfo(MediaServer mediaServerItem, String app, String stream);
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@ import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Synchronized;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
@ -86,7 +88,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
/**
|
||||
* 流到来的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@org.springframework.context.event.EventListener
|
||||
public void onApplicationEvent(MediaArrivalEvent event) {
|
||||
if ("rtsp".equals(event.getSchema())) {
|
||||
@ -100,7 +102,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
/**
|
||||
* 流离开的处理
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaDepartureEvent event) {
|
||||
if ("rtsp".equals(event.getSchema())) {
|
||||
@ -119,7 +121,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
/**
|
||||
* 流媒体节点上线
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
@Transactional
|
||||
public void onApplicationEvent(MediaServerOnlineEvent event) {
|
||||
@ -130,7 +132,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
/**
|
||||
* 流媒体节点离线
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
@Transactional
|
||||
public void onApplicationEvent(MediaServerOfflineEvent event) {
|
||||
@ -632,13 +634,13 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) {
|
||||
public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) {
|
||||
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
|
||||
if (mediaNodeServerService == null) {
|
||||
log.info("[getSnap] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType());
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
mediaNodeServerService.getSnap(mediaServer, app, stream, timeoutSec, expireSec, path, fileName);
|
||||
return mediaNodeServerService.getSnap(mediaServer, app, stream, timeoutSec, expireSec, path, fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -217,14 +217,14 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) {
|
||||
public byte[] getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, int expireSec, String path, String fileName) {
|
||||
String streamUrl;
|
||||
if (mediaServer.getRtspPort() != 0) {
|
||||
streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServer.getRtspPort(), app, stream);
|
||||
} else {
|
||||
streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServer.getHttpPort(), app, stream);
|
||||
}
|
||||
zlmresTfulUtils.getSnap(mediaServer, streamUrl, timeoutSec, expireSec, path, fileName);
|
||||
return zlmresTfulUtils.getSnap(mediaServer, streamUrl, timeoutSec, expireSec, path, fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Component
|
||||
public class ZLMMediaServerStatusManager {
|
||||
|
||||
|
||||
private final Map<Object, MediaServer> offlineZlmPrimaryMap = new ConcurrentHashMap<>();
|
||||
private final Map<Object, MediaServer> offlineZlmsecondaryMap = new ConcurrentHashMap<>();
|
||||
private final Map<Object, Long> offlineZlmTimeMap = new ConcurrentHashMap<>();
|
||||
@ -66,7 +67,7 @@ public class ZLMMediaServerStatusManager {
|
||||
|
||||
private final String type = "zlm";
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaServerChangeEvent event) {
|
||||
if (event.getMediaServerItemList() == null
|
||||
@ -80,11 +81,10 @@ public class ZLMMediaServerStatusManager {
|
||||
log.info("[ZLM-添加待上线节点] ID:{}", mediaServerItem.getId());
|
||||
offlineZlmPrimaryMap.put(mediaServerItem.getId(), mediaServerItem);
|
||||
offlineZlmTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis());
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(HookZlmServerStartEvent event) {
|
||||
if (event.getMediaServer() == null
|
||||
@ -96,7 +96,7 @@ public class ZLMMediaServerStatusManager {
|
||||
online(event.getMediaServer(), event.getConfig());
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(HookZlmServerKeepaliveEvent event) {
|
||||
if (event.getMediaServerItem() == null) {
|
||||
@ -110,7 +110,7 @@ public class ZLMMediaServerStatusManager {
|
||||
online(mediaServer, null);
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
@Async
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaServerDeleteEvent event) {
|
||||
if (event.getMediaServer() == null) {
|
||||
@ -183,6 +183,8 @@ public class ZLMMediaServerStatusManager {
|
||||
MediaServer mediaServerInDb = mediaServerService.getOne(mediaServer.getId());
|
||||
if (mediaServerInDb == null || !mediaServerInDb.isStatus()) {
|
||||
log.info("[ZLM-连接成功] ID:{}, 地址: {}:{}", mediaServer.getId(), mediaServer.getIp(), mediaServer.getHttpPort());
|
||||
offlineZlmPrimaryMap.remove(mediaServer.getId());
|
||||
offlineZlmsecondaryMap.remove(mediaServer.getId());
|
||||
if (config == null) {
|
||||
ZLMResult<List<JSONObject>> mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServer);
|
||||
List<JSONObject> data = mediaServerConfig.getData();
|
||||
|
||||
@ -167,11 +167,11 @@ public class ZLMRESTfulUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void sendGetForImg(MediaServer mediaServer, String api, Map<String, Object> params, String targetPath, String fileName) {
|
||||
public byte[] sendGetForImg(MediaServer mediaServer, String api, Map<String, Object> params, String targetPath, String fileName) {
|
||||
String url = String.format("http://%s:%s/index/api/%s", mediaServer.getIp(), mediaServer.getHttpPort(), api);
|
||||
HttpUrl parseUrl = HttpUrl.parse(url);
|
||||
if (parseUrl == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
HttpUrl.Builder httpBuilder = parseUrl.newBuilder();
|
||||
|
||||
@ -188,6 +188,7 @@ public class ZLMRESTfulUtils {
|
||||
if (log.isDebugEnabled()){
|
||||
log.debug(request.toString());
|
||||
}
|
||||
byte[] result = null;
|
||||
try {
|
||||
OkHttpClient client = getClient();
|
||||
Response response = client.newCall(request).execute();
|
||||
@ -198,27 +199,24 @@ public class ZLMRESTfulUtils {
|
||||
if (!snapFolder.mkdirs()) {
|
||||
log.warn("{}路径创建失败", snapFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
}
|
||||
File snapFile = new File(targetPath + File.separator + fileName);
|
||||
FileOutputStream outStream = new FileOutputStream(snapFile);
|
||||
|
||||
outStream.write(Objects.requireNonNull(response.body()).bytes());
|
||||
result = Objects.requireNonNull(response.body()).bytes();
|
||||
outStream.write(result);
|
||||
outStream.flush();
|
||||
outStream.close();
|
||||
} else {
|
||||
log.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));
|
||||
}
|
||||
} else {
|
||||
log.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));
|
||||
log.error("[ {} ]请求失败: {} {}", url, response.code(), response.message());
|
||||
}
|
||||
Objects.requireNonNull(response.body()).close();
|
||||
} catch (ConnectException e) {
|
||||
log.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
|
||||
log.error("连接ZLM失败: {}, {}", e.getCause().getMessage(), e.getMessage());
|
||||
log.info("请检查media配置并确认ZLM已启动...");
|
||||
} catch (IOException e) {
|
||||
log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
|
||||
log.error("[ {} ]请求失败: {}", url, e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public ZLMResult<?> isMediaOnline(MediaServer mediaServer, String app, String stream, String schema){
|
||||
@ -657,13 +655,13 @@ public class ZLMRESTfulUtils {
|
||||
sendPost(mediaServer, "kick_sessions",param, null);
|
||||
}
|
||||
|
||||
public void getSnap(MediaServer mediaServer, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
|
||||
public byte[] getSnap(MediaServer mediaServer, String streamUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
|
||||
Map<String, Object> param = new HashMap<>(3);
|
||||
param.put("url", streamUrl);
|
||||
param.put("timeout_sec", timeout_sec);
|
||||
param.put("expire_sec", expire_sec);
|
||||
param.put("async", 1);
|
||||
sendGetForImg(mediaServer, "getSnap", param, targetPath, fileName);
|
||||
return sendGetForImg(mediaServer, "getSnap", param, targetPath, fileName);
|
||||
}
|
||||
|
||||
public ZLMResult<?> pauseRtpCheck(MediaServer mediaServer, String streamId) {
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package com.genersoft.iot.vmp.service;
|
||||
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.Alarm;
|
||||
import com.genersoft.iot.vmp.service.bean.AlarmType;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IAlarmService {
|
||||
// 保存报警信息
|
||||
void saveAlarmInfo(Alarm alarm);
|
||||
|
||||
// 分页获取报警信息
|
||||
PageInfo<Alarm> getAlarms(int page, int size, List<AlarmType> alarmType, String beginTime, String endTime);
|
||||
|
||||
// 删除报警信息
|
||||
void deleteAlarmInfo(List<Long> ids);
|
||||
|
||||
// 按筛选条件清空报警信息
|
||||
int clearAlarmsByCondition(List<AlarmType> alarmType, String beginTime, String endTime);
|
||||
|
||||
// 根据ID获取报警快照
|
||||
String getAlarmSnapById(Long id);
|
||||
|
||||
// 根据ID获取报警录像
|
||||
StreamInfo getAlarmRecordById(Long id);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user