Compare commits

...

55 Commits

Author SHA1 Message Date
lin
70df9b829f 更新README.md 2026-04-09 22:57:46 +08:00
lin
1026fb769b 更新README.md 2026-04-09 22:54:14 +08:00
lin
c9af2381f6 Merge branch 'master' into 报警管理
# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java
2026-04-09 17:43:17 +08:00
lin
b89344c939 调整报警视频回放时间范围至前后各10秒 2026-04-08 14:36:05 +08:00
lin
7d6ba802a7 适配新版本h265web 2026-04-07 15:42:24 +08:00
lin
ab15075186 适配新版本h265web 2026-04-07 14:50:02 +08:00
lin
4e54db42da 添加自动清理过期报警记录功能,允许用户设置报警记录保留天数 2026-04-07 10:54:56 +08:00
lin
d1de343167 添加报警订阅白名单功能,允许用户设置接收的报警类型 2026-04-07 09:56:33 +08:00
lin
f5f86c40a0 实现按筛选条件清空报警信息功能 2026-04-07 09:43:59 +08:00
lin
280ed4aee3 优化报警信息入库逻辑 2026-04-07 09:30:26 +08:00
lin
149c5a550b 实现报警快照获取功能,优化相关接口返回数据类型 2026-04-07 08:58:42 +08:00
lin
2d1608ca7a 优化报警信息构建和数据库批量操作逻辑 2026-04-04 00:12:52 +08:00
lin
d307cf1073 使用UUID生成报警快照路径 2026-04-03 18:03:02 +08:00
lin
e51d000588 移除默认私钥文件,异步处理报警消息 2026-04-03 17:50:06 +08:00
lin
99d19d0623 修改报警信息解析规则 2026-04-02 18:03:34 +08:00
lin
472000200d 补充丢失的页面 2026-04-02 16:40:08 +08:00
lin
99b270dbfc 补充丢失的页面 2026-04-02 16:38:28 +08:00
lin
c2835db8d4 移除旧的报警信息表,增加报警管理页面 2026-04-02 16:33:50 +08:00
lin
b476e08d19 更新H265播放器配置,添加新的JS和WASM文件,调整播放器初始化逻辑 2026-03-31 18:02:27 +08:00
lin
4b90b79b3b 简化注册逻辑 2026-03-31 14:30:48 +08:00
lin
88bd3c2ae3 修复报警订阅调用 2026-03-31 11:34:01 +08:00
lin
383081f244 调整订阅周期处理和任务创建逻辑 2026-03-31 11:22:05 +08:00
lin
85c7ec3d3e 将多个响应处理逻辑更新为异步处理,调整订阅添加和移除逻辑。 2026-03-31 10:47:17 +08:00
lin
01c71f8800 Merge branch 'refs/heads/dev/压力测试' into 报警管理
# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java
2026-03-31 09:10:02 +08:00
lin
1a83d5a504 将心跳记录更新日志的日志级别从info调整为debug 2026-03-30 17:01:05 +08:00
lin
ede979dbf9 添加定时任务配置类并记录心跳更新日志 2026-03-30 17:00:11 +08:00
lin
cd5c10cf48 添加调试日志 2026-03-30 16:29:58 +08:00
lin
d6b63e13c0 调整订阅列宽以改善用户界面 2026-03-30 16:22:16 +08:00
lin
2b7cab7572 添加报警订阅功能,包括API接口、前端订阅逻辑及相关服务实现 2026-03-30 16:20:51 +08:00
lin
0dc297bf02 修复时间统计列表处理逻辑并增强心跳记录更新的异常处理 2026-03-30 14:28:52 +08:00
lin
2b090bbf23 优化报警和心跳通知的回复方式,改为异步处理 2026-03-30 11:34:44 +08:00
lin
7fcd3d3404 简化异步处理的心跳回复方法注解 2026-03-30 11:24:23 +08:00
lin
0eebcceefb Merge branch 'master' into dev/压力测试
# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
#	src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java
2026-03-30 11:23:47 +08:00
lin
6a1f2887e5 Merge branch 'master' into dev/压力测试
# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
2026-01-30 06:15:34 +08:00
lin
83057d81d8 心跳历史展示为从新到旧 2026-01-27 22:26:46 +08:00
lin
7dea67b472 优化心跳处理逻辑,新增commons-pool2依赖 2026-01-27 21:08:26 +08:00
lin
7f2db96ac1 添加国标设备数据库状态检查功能,优化设备状态处理和移动位置订阅逻辑 2026-01-27 17:04:07 +08:00
lin
2bdced8b9c 为心跳历史和注册历史设置过期时间 2026-01-27 14:21:41 +08:00
lin
27b06a84d7 优化心跳和注册时间记录的取值和展示 2026-01-27 12:31:57 +08:00
lin
4067dcf8d1 修复心跳历史记录不全的BUG 2026-01-27 11:16:04 +08:00
lin
a33e7949a4 支持心跳和注册统计 2026-01-26 15:55:07 +08:00
lin
31549bce09 去除心跳信息入库,提升性能 2026-01-25 21:59:42 +08:00
lin
2c774ae155 优化心跳时间的写入 2026-01-25 20:22:31 +08:00
lin
b2772a0a1b 修复启动时媒体信息写入异常 2026-01-25 09:13:11 +08:00
lin
588de2d5ec 优化设备批量离线性能 2026-01-24 22:51:04 +08:00
lin
519ccccd7b 暂时关闭统计功能 2026-01-21 22:52:57 +08:00
lin
ec45deda5e Merge remote-tracking branch 'origin/dev/压力测试' into dev/压力测试
# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataManager.java
2026-01-19 21:47:59 +08:00
lin
8e9e75997a 优化注册参数 2026-01-19 21:47:15 +08:00
lin
46e9d56c24 优化目录接收 2026-01-19 09:17:54 +08:00
lin
7068898c7b 去除media server 表多余的约束 2026-01-15 23:29:26 +08:00
lin
d29bdec648 使用zset管理设备状态,以减少内存开销 2026-01-15 23:10:49 +08:00
lin
5373e6082c 临时提交 2026-01-15 18:05:31 +08:00
lin
332626150f 动态任务管理使用虚拟线程 2026-01-14 18:09:52 +08:00
lin
4b0cdd5718 优化心跳处理逻辑,可接入设备数量更多 2026-01-14 17:17:44 +08:00
lin
7a4d5e551d 开启虚拟线程,修改代码已使用虚拟线程 2026-01-14 11:02:58 +08:00
149 changed files with 12923 additions and 2141 deletions

3
.gitignore vendored
View File

@ -31,3 +31,6 @@ certificates
/.vs
/docker/volumes
/docker/wvp/config/jwk.json
/打包/
/snap/
/config/

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -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>

View File

@ -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() {

View File

@ -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";

View File

@ -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);

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -32,7 +32,7 @@ public class SipConfig {
Integer registerTimeInterval = 120;
private boolean alarm = false;
private boolean alarm = true;
private long timeout = 1000;
}

View File

@ -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());
// }
//
// }
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<>();

View File

@ -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;
}
}

View File

@ -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()) {

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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 &lt;= #{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>" +

View File

@ -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);
}

View File

@ -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" +

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 |

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -14,4 +14,6 @@ public interface ISourcePlayService {
void stopPlay(CommonGBChannel channel);
void getSnap(CommonGBChannel channel, ErrorCallback<byte[]> callback);
}

View File

@ -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);
}
}

View File

@ -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);
}
// 不对收到的通道做比较已确定是否真的发生变化所以不发送更新通知

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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())) {

View File

@ -123,7 +123,7 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
}
}
}else {
log.info("[Catalog事件: {}] 未找到上级平台: {}", event.getMessageType(), serverGbId);
log.info("[Catalog事件: {}] 没有需要通知的上级平台: {}", event.getMessageType(), serverGbId);
}
}
break;

View File

@ -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());

View File

@ -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())) {

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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事件-增加/更新

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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");

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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);
}
}
});
}
}
}

View File

@ -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) {
}
}

View File

@ -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());
}

View File

@ -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);
}

View File

@ -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());
}

View File

@ -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;

View File

@ -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;

View File

@ -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) {
}
}

View File

@ -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;

View File

@ -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()) {

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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) {

View File

@ -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