diff --git a/pom.xml b/pom.xml index 0b77d003e..8ab69582a 100644 --- a/pom.xml +++ b/pom.xml @@ -242,12 +242,17 @@ com.alibaba.fastjson2 fastjson2 - 2.0.17 + 2.0.57 com.alibaba.fastjson2 fastjson2-extension - 2.0.17 + 2.0.57 + + + com.alibaba.fastjson2 + fastjson2-extension-spring5 + 2.0.57 diff --git a/src/main/java/com/genersoft/iot/vmp/common/SubscribeCallback.java b/src/main/java/com/genersoft/iot/vmp/common/SubscribeCallback.java new file mode 100644 index 000000000..5443b50e0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/common/SubscribeCallback.java @@ -0,0 +1,7 @@ +package com.genersoft.iot.vmp.common; + +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; + +public interface SubscribeCallback{ + public void run(String deviceId, SipTransactionInfo transactionInfo); +} diff --git a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java index f498ba51b..096f89d86 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java +++ b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java @@ -1,13 +1,13 @@ package com.genersoft.iot.vmp.common; -/** - * @description: 定义常量 +/** + * @description: 定义常量 * @author: swwheihei - * @date: 2019年5月30日 下午3:04:04 - * + * @date: 2019年5月30日 下午3:04:04 + * */ public class VideoManagerConstants { - + public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_"; public static final String WVP_SERVER_LIST = "VMP_SERVER_LIST"; @@ -22,10 +22,6 @@ public class VideoManagerConstants { public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO"; - public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_"; - - public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_"; - public static final String SEND_RTP_PORT = "VM_SEND_RTP_PORT:"; public static final String SEND_RTP_INFO_CALLID = "VMP_SEND_RTP_INFO:CALL_ID:"; public static final String SEND_RTP_INFO_STREAM = "VMP_SEND_RTP_INFO:STREAM:"; diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java b/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java deleted file mode 100644 index cb12754e6..000000000 --- a/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.genersoft.iot.vmp.conf; - -import com.genersoft.iot.vmp.gb28181.bean.Platform; -import com.genersoft.iot.vmp.gb28181.bean.PlatformCatch; -import com.genersoft.iot.vmp.gb28181.service.IPlatformService; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; - -import java.util.List; - -/** - * 系统启动时控制上级平台重新注册 - * @author lin - */ -@Slf4j -@Component -@Order(value=13) -public class SipPlatformRunner implements CommandLineRunner { - - @Autowired - private IRedisCatchStorage redisCatchStorage; - - @Autowired - private IPlatformService platformService; - - @Autowired - private ISIPCommanderForPlatform sipCommanderForPlatform; - - @Autowired - private UserSetting userSetting; - - @Override - public void run(String... args) throws Exception { - // 获取所有启用的平台 - List parentPlatforms = platformService.queryEnablePlatformList(userSetting.getServerId()); - - for (Platform platform : parentPlatforms) { - - PlatformCatch platformCatchOld = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); - - // 更新缓存 - PlatformCatch platformCatch = new PlatformCatch(); - platformCatch.setPlatform(platform); - platformCatch.setId(platform.getServerGBId()); - redisCatchStorage.updatePlatformCatchInfo(platformCatch); - if (platformCatchOld != null) { - // 取消订阅 - try { - log.info("[平台主动注销] {}({})", platform.getName(), platform.getServerGBId()); - sipCommanderForPlatform.unregister(platform, platformCatchOld.getSipTransactionInfo(), null, (eventResult)->{ - platformService.login(platform); - }); - } catch (Exception e) { - log.error("[命令发送失败] 国标级联 注销: {}", e.getMessage()); - platformService.offline(platform, true); - continue; - } - }else { - platformService.login(platform); - } - - // 设置平台离线 - platformService.offline(platform, false); - } - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java index f70a9e319..bc8b5f6a1 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java @@ -16,7 +16,7 @@ import org.springframework.data.redis.listener.RedisMessageListenerContainer; * @description:Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置 * @author: swwheihei * @date: 2019年5月30日 上午10:58:25 - * + * */ @Configuration @Order(value=1) @@ -38,7 +38,6 @@ public class RedisMsgListenConfig { @Autowired private RedisCloseStreamMsgListener redisCloseStreamMsgListener; - @Autowired private RedisRpcConfig redisRpcConfig; @@ -49,7 +48,7 @@ public class RedisMsgListenConfig { /** * redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器 * 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理 - * + * * @param connectionFactory * @return */ diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java index 7f50f4d37..4b3bb132a 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java @@ -1,18 +1,23 @@ package com.genersoft.iot.vmp.gb28181.bean; +import lombok.Data; + /** * 通过redis分发报警消息 */ +@Data public class AlarmChannelMessage { /** - * 国标编号 + * 通道国标编号 */ private String gbId; + /** * 报警编号 */ private int alarmSn; + /** * 告警类型 */ @@ -22,36 +27,4 @@ public class AlarmChannelMessage { * 报警描述 */ private String alarmDescription; - - public String getGbId() { - return gbId; - } - - public void setGbId(String gbId) { - this.gbId = gbId; - } - - public int getAlarmSn() { - return alarmSn; - } - - public void setAlarmSn(int alarmSn) { - this.alarmSn = alarmSn; - } - - public int getAlarmType() { - return alarmType; - } - - public void setAlarmType(int alarmType) { - this.alarmType = alarmType; - } - - public String getAlarmDescription() { - return alarmDescription; - } - - public void setAlarmDescription(String alarmDescription) { - this.alarmDescription = alarmDescription; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformKeepaliveCallback.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformKeepaliveCallback.java new file mode 100644 index 000000000..2e98fa830 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformKeepaliveCallback.java @@ -0,0 +1,5 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +public interface PlatformKeepaliveCallback { + public void run(String platformServerGbId, int failCount); +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java index 885843980..74c63e112 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java @@ -1,13 +1,17 @@ package com.genersoft.iot.vmp.gb28181.bean; import gov.nist.javax.sip.message.SIPResponse; +import lombok.Data; +@Data public class SipTransactionInfo { private String callId; private String fromTag; private String toTag; private String viaBranch; + private int expires; + private String user; // 自己是否媒体流发送者 private boolean asSender; @@ -31,43 +35,4 @@ public class SipTransactionInfo { public SipTransactionInfo() { } - public String getCallId() { - return callId; - } - - public void setCallId(String callId) { - this.callId = callId; - } - - public String getFromTag() { - return fromTag; - } - - public void setFromTag(String fromTag) { - this.fromTag = fromTag; - } - - public String getToTag() { - return toTag; - } - - public void setToTag(String toTag) { - this.toTag = toTag; - } - - public String getViaBranch() { - return viaBranch; - } - - public void setViaBranch(String viaBranch) { - this.viaBranch = viaBranch; - } - - public boolean isAsSender() { - return asSender; - } - - public void setAsSender(boolean asSender) { - this.asSender = asSender; - } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java index a15de224a..17a5284b3 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java @@ -1,19 +1,20 @@ package com.genersoft.iot.vmp.gb28181.bean; -import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; -import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; +import java.time.Duration; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; /** * @author lin */ +@Slf4j @Component public class SubscribeHolder { @@ -23,104 +24,97 @@ public class SubscribeHolder { @Autowired private UserSetting userSetting; - private final String taskOverduePrefix = "subscribe_overdue_"; - - private static ConcurrentHashMap catalogMap = new ConcurrentHashMap<>(); - private static ConcurrentHashMap mobilePositionMap = new ConcurrentHashMap<>(); + @Autowired + private RedisTemplate redisTemplate; + private final String prefix = "VMP_SUBSCRIBE_OVERDUE"; public void putCatalogSubscribe(String platformId, SubscribeInfo subscribeInfo) { - catalogMap.put(platformId, subscribeInfo); + log.info("[国标级联] 添加目录订阅,平台: {}, 有效期: {}", platformId, subscribeInfo.getExpires()); + + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platformId); if (subscribeInfo.getExpires() > 0) { - // 添加订阅到期 - String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; - // 添加任务处理订阅过期 - dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()), - subscribeInfo.getExpires() * 1000); + Duration duration = Duration.ofSeconds(subscribeInfo.getExpires()); + redisTemplate.opsForValue().set(key, subscribeInfo, duration); + }else { + redisTemplate.opsForValue().set(key, subscribeInfo); } } public SubscribeInfo getCatalogSubscribe(String platformId) { - return catalogMap.get(platformId); + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platformId); + return (SubscribeInfo)redisTemplate.opsForValue().get(key); } public void removeCatalogSubscribe(String platformId) { - - catalogMap.remove(platformId); - String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; - Runnable runnable = dynamicTask.get(taskOverdueKey); - if (runnable instanceof ISubscribeTask) { - ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(null); - } - // 添加任务处理订阅过期 - dynamicTask.stop(taskOverdueKey); + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platformId); + redisTemplate.delete(key); } public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo, Runnable gpsTask) { - mobilePositionMap.put(platformId, subscribeInfo); - String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetting.getServerId() + "MobilePosition_" + platformId; - // 添加任务处理GPS定时推送 - + log.info("[国标级联] 添加移动位置订阅,平台: {}, 有效期: {}s", platformId, subscribeInfo.getExpires()); + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platformId); + if (subscribeInfo.getExpires() > 0) { + Duration duration = Duration.ofSeconds(subscribeInfo.getExpires()); + redisTemplate.opsForValue().set(key, subscribeInfo, duration); + }else { + redisTemplate.opsForValue().set(key, subscribeInfo); + } int cycleForCatalog; if (subscribeInfo.getGpsInterval() <= 0) { cycleForCatalog = 5; }else { cycleForCatalog = subscribeInfo.getGpsInterval(); } - dynamicTask.startCron(key, gpsTask, + dynamicTask.startCron( + key, + () -> { + SubscribeInfo subscribe = getMobilePositionSubscribe(platformId); + if (subscribe != null) { + gpsTask.run(); + }else { + dynamicTask.stop(key); + } + }, cycleForCatalog * 1000); - String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; - if (subscribeInfo.getExpires() > 0) { - // 添加任务处理订阅过期 - dynamicTask.startDelay(taskOverdueKey, () -> { - removeMobilePositionSubscribe(subscribeInfo.getId()); - }, - subscribeInfo.getExpires() * 1000); - } + } public SubscribeInfo getMobilePositionSubscribe(String platformId) { - return mobilePositionMap.get(platformId); + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platformId); + return (SubscribeInfo)redisTemplate.opsForValue().get(key); } public void removeMobilePositionSubscribe(String platformId) { - mobilePositionMap.remove(platformId); - String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetting.getServerId() + "MobilePosition_" + platformId; - // 结束任务处理GPS定时推送 - dynamicTask.stop(key); - String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; - Runnable runnable = dynamicTask.get(taskOverdueKey); - if (runnable instanceof ISubscribeTask) { - ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(null); - } - // 添加任务处理订阅过期 - dynamicTask.stop(taskOverdueKey); + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platformId); + redisTemplate.delete(key); } - public List getAllCatalogSubscribePlatform() { - List platforms = new ArrayList<>(); - if(catalogMap.size() > 0) { - for (String key : catalogMap.keySet()) { - platforms.add(catalogMap.get(key).getId()); + public List getAllCatalogSubscribePlatform(List platformList) { + if (platformList == null || platformList.isEmpty()) { + return new ArrayList<>(); + } + List result = new ArrayList<>(); + for (Platform platform : platformList) { + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platform.getServerGBId()); + if (redisTemplate.hasKey(key)) { + result.add(platform.getServerGBId()); } } - return platforms; + return result; } - public List getAllMobilePositionSubscribePlatform() { - List platforms = new ArrayList<>(); - if(!mobilePositionMap.isEmpty()) { - for (String key : mobilePositionMap.keySet()) { - platforms.add(mobilePositionMap.get(key).getId()); + public List getAllMobilePositionSubscribePlatform(List platformList) { + if (platformList == null || platformList.isEmpty()) { + return new ArrayList<>(); + } + List result = new ArrayList<>(); + for (Platform platform : platformList) { + String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platform.getServerGBId()); + if (redisTemplate.hasKey(key)) { + result.add(platform.getServerGBId()); } } - return platforms; - } - - public void removeAllSubscribe(String platformId) { - removeMobilePositionSubscribe(platformId); - removeCatalogSubscribe(platformId); + return result; } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java index a131ccb43..5820cb67c 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java @@ -1,36 +1,19 @@ package com.genersoft.iot.vmp.gb28181.bean; -import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.Data; -import javax.sip.header.*; +import javax.sip.header.EventHeader; import java.util.UUID; @Data public class SubscribeInfo { - - public SubscribeInfo(SIPRequest request, String id) { - this.id = id; - this.request = request; - this.expires = request.getExpires().getExpires(); - EventHeader eventHeader = (EventHeader)request.getHeader(EventHeader.NAME); - this.eventId = eventHeader.getEventId(); - this.eventType = eventHeader.getEventType(); - - } - - public SubscribeInfo() { - } - private String id; - - private SIPRequest request; private int expires; private String eventId; private String eventType; - private SIPResponse response; + private SipTransactionInfo transactionInfo; /** * 以下为可选字段 @@ -55,6 +38,16 @@ public class SubscribeInfo { private String simulatedCallId; + public static SubscribeInfo getInstance(SIPResponse response, String id, int expires, EventHeader eventHeader){ + SubscribeInfo subscribeInfo = new SubscribeInfo(); + subscribeInfo.id = id; + subscribeInfo.transactionInfo = new SipTransactionInfo(response); + + subscribeInfo.expires = expires; + subscribeInfo.eventId = eventHeader.getEventId(); + subscribeInfo.eventType = eventHeader.getEventType(); + return subscribeInfo; + } public static SubscribeInfo buildSimulated(String platFormServerId, String platFormServerIp){ SubscribeInfo subscribeInfo = new SubscribeInfo(); subscribeInfo.setId(platFormServerId); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java index 80b4ba4de..6788a8eb3 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java @@ -11,9 +11,6 @@ import com.genersoft.iot.vmp.gb28181.bean.SyncStatus; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; -import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; -import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; -import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; @@ -40,9 +37,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; @Tag(name = "国标设备查询", description = "国标设备查询") @SuppressWarnings("rawtypes") @@ -144,28 +138,10 @@ public class DeviceQuery { } // 清除redis记录 - boolean isSuccess = deviceService.delete(deviceId); - if (isSuccess) { - inviteStreamService.clearInviteInfo(deviceId); - // 停止此设备的订阅更新 - Set allKeys = dynamicTask.getAllKeys(); - for (String key : allKeys) { - if (key.startsWith(deviceId)) { - Runnable runnable = dynamicTask.get(key); - if (runnable instanceof ISubscribeTask) { - ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(null); - } - dynamicTask.stop(key); - } - } - JSONObject json = new JSONObject(); - json.put("deviceId", deviceId); - return json.toString(); - } else { - log.warn("设备信息删除API调用失败!"); - throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备信息删除API调用失败!"); - } + deviceService.delete(deviceId); + JSONObject json = new JSONObject(); + json.put("deviceId", deviceId); + return json.toString(); } @Operation(summary = "分页查询子目录通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @@ -358,28 +334,6 @@ public class DeviceQuery { return wvpResult; } - @GetMapping("/{deviceId}/subscribe_info") - @Operation(summary = "获取设备的订阅状态", security = @SecurityRequirement(name = JwtUtils.HEADER)) - @Parameter(name = "deviceId", description = "设备国标编号", required = true) - public WVPResult> getSubscribeInfo(@PathVariable String deviceId) { - Set allKeys = dynamicTask.getAllKeys(); - Map dialogStateMap = new HashMap<>(); - for (String key : allKeys) { - if (key.startsWith(deviceId)) { - ISubscribeTask subscribeTask = (ISubscribeTask)dynamicTask.get(key); - if (subscribeTask instanceof CatalogSubscribeTask) { - dialogStateMap.put("catalog", 1); - }else if (subscribeTask instanceof MobilePositionSubscribeTask) { - dialogStateMap.put("mobilePosition", 1); - } - } - } - WVPResult> wvpResult = new WVPResult<>(); - wvpResult.setCode(0); - wvpResult.setData(dialogStateMap); - return wvpResult; - } - @GetMapping("/snap/{deviceId}/{channelId}") @Operation(summary = "请求截图") @Parameter(name = "deviceId", description = "设备国标编号", required = true) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MobilePositionController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MobilePositionController.java index 1c3406f85..7ef963509 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MobilePositionController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/MobilePositionController.java @@ -7,7 +7,7 @@ import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; 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.cmd.impl.SIPCommander; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.service.IMobilePositionService; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.github.pagehelper.util.StringUtil; @@ -37,10 +37,10 @@ public class MobilePositionController { @Autowired private IMobilePositionService mobilePositionService; - + @Autowired - private SIPCommander cmder; - + private ISIPCommander cmder; + @Autowired private DeferredResultHolder resultHolder; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java index ffb8e683b..b999fecd1 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/DeviceMapper.java @@ -123,23 +123,27 @@ public interface DeviceMapper { @Update(value = {" "}) int update(Device device); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/PlatformMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/PlatformMapper.java index 5218537f9..bb3c0f0e2 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/PlatformMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/PlatformMapper.java @@ -78,7 +78,7 @@ public interface PlatformMapper { List queryList(@Param("query") String query); @Select("SELECT * FROM wvp_platform WHERE server_id=#{serverId} and enable=#{enable} ") - List queryEnableParentPlatformList(@Param("serverId") String serverId, @Param("enable") boolean enable); + List queryEnableParentPlatformListByServerId(@Param("serverId") String serverId, @Param("enable") boolean enable); @Select("SELECT * FROM wvp_platform WHERE enable=true and as_message_channel=true") List queryEnablePlatformListWithAsMessageChannel(); @@ -89,12 +89,22 @@ public interface PlatformMapper { @Select("SELECT * FROM wvp_platform WHERE id=#{id}") Platform query(int id); - @Update("UPDATE wvp_platform SET status=#{online} WHERE server_gb_id=#{platformGbID}" ) - int updateStatus(@Param("platformGbID") String platformGbID, @Param("online") boolean online); + @Update("UPDATE wvp_platform SET status=#{online}, server_id = #{serverId} WHERE id=#{id}" ) + int updateStatus(@Param("id") int id, @Param("online") boolean online, @Param("serverId") String serverId); @Select("SELECT server_id FROM wvp_platform WHERE enable=true and server_id != #{serverId} group by server_id") List queryServerIdsWithEnableAndNotInServer(@Param("serverId") String serverId); @Select("SELECT * FROM wvp_platform WHERE server_id = #{serverId}") List queryByServerId(@Param("serverId") String serverId); + + @Select("SELECT * FROM wvp_platform ") + List queryAll(); + + @Select("SELECT * FROM wvp_platform WHERE enable=true and server_id = #{serverId}") + List queryServerIdsWithEnableAndServer(@Param("serverId") String serverId); + + @Update("UPDATE wvp_platform SET status=false where server_id = #{serverId}" ) + void offlineAll(@Param("serverId") String serverId); + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/DeviceChannelProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/DeviceChannelProvider.java index 9578682be..6cfafb7c7 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/DeviceChannelProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/DeviceChannelProvider.java @@ -101,7 +101,7 @@ public class DeviceChannelProvider { } sqlBuild.append(" )"); } - sqlBuild.append("ORDER BY device_id"); + sqlBuild.append(" ORDER BY device_id"); return sqlBuild.toString(); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java index 9aa75b712..018abc5c6 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java @@ -12,6 +12,7 @@ import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @@ -21,11 +22,12 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -/** +/** * @description:Event事件通知推送器,支持推送在线事件、离线事件 * @author: swwheihei - * @date: 2020年5月6日 上午11:30:50 + * @date: 2020年5月6日 上午11:30:50 */ +@Slf4j @Component public class EventPublisher { @@ -72,12 +74,7 @@ public class EventPublisher { } public void catalogEventPublish(Platform platform, List deviceChannels, String type, boolean share) { if (platform != null && !userSetting.getServerId().equals(platform.getServerId())) { - // 指定了上级平台的推送,则发送到指定的设备,未指定的则全部发送, 接收后各自处理自己的 - CatalogEvent outEvent = new CatalogEvent(this); - outEvent.setChannels(deviceChannels); - outEvent.setType(type); - outEvent.setPlatform(platform); - redisRpcService.catalogEventPublish(platform.getServerId(), outEvent); + log.info("[国标级联] 目录状态推送, 此上级平台由其他服务处理,消息已经忽略"); return; } CatalogEvent outEvent = new CatalogEvent(this); @@ -96,12 +93,11 @@ public class EventPublisher { } outEvent.setChannels(channels); outEvent.setType(type); - outEvent.setPlatform(platform); - applicationEventPublisher.publishEvent(outEvent); - if (platform == null && share) { - // 如果没指定上级平台,则推送消息到所有在线的wvp处理自己含有的平台的目录更新 - redisRpcService.catalogEventPublish(null, outEvent); + if (platform != null) { + outEvent.setPlatform(platform); } + applicationEventPublisher.publishEvent(outEvent); + } public void mobilePositionEventPublish(MobilePosition mobilePosition) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/sip/SipEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/sip/SipEvent.java index be20a54e2..0bca7cd13 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/sip/SipEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/sip/SipEvent.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.event.sip; +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import lombok.Data; import org.jetbrains.annotations.NotNull; @@ -27,6 +28,8 @@ public class SipEvent implements Delayed { */ private long delay; + private SipTransactionInfo sipTransactionInfo; + public static SipEvent getInstance(String key, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, long delay) { SipEvent sipEvent = new SipEvent(); sipEvent.setKey(key); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java index 08a329022..69b55445c 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java @@ -8,7 +8,6 @@ import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; -import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; @@ -17,7 +16,10 @@ import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * catalog事件 @@ -26,6 +28,9 @@ import java.util.*; @Component public class CatalogEventLister implements ApplicationListener { + @Autowired + private IPlatformService platformService; + @Autowired private IPlatformChannelService platformChannelService; @@ -35,32 +40,45 @@ public class CatalogEventLister implements ApplicationListener { @Autowired private SubscribeHolder subscribeHolder; + @Autowired + private UserSetting userSetting; + @Override public void onApplicationEvent(CatalogEvent event) { SubscribeInfo subscribe = null; Platform parentPlatform = null; - - Map> parentPlatformMap = new HashMap<>(); + log.info("[Catalog事件: {}] 通道数量: {}", event.getType(), event.getChannels().size()); + Map> platformMap = new HashMap<>(); Map channelMap = new HashMap<>(); if (event.getPlatform() != null) { parentPlatform = event.getPlatform(); + if (parentPlatform.getServerGBId() == null) { + log.info("[Catalog事件: {}] 平台服务国标编码未找到", event.getType()); + return; + } subscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId()); if (subscribe == null) { + log.info("[Catalog事件: {}] 未订阅目录事件", event.getType()); return; } }else { + List allPlatform = platformService.queryAll(userSetting.getServerId()); // 获取所用订阅 - List platforms = subscribeHolder.getAllCatalogSubscribePlatform(); + List platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform); if (event.getChannels() != null) { if (!platforms.isEmpty()) { for (CommonGBChannel deviceChannel : event.getChannels()) { List parentPlatformsForGB = platformChannelService.queryPlatFormListByChannelDeviceId( deviceChannel.getGbId(), platforms); - parentPlatformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB); + platformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB); channelMap.put(deviceChannel.getGbDeviceId(), deviceChannel); } + }else { + log.info("[Catalog事件: {}] 未订阅目录事件", event.getType()); } + }else { + log.info("[Catalog事件: {}] 事件内通道数为0", event.getType()); } } switch (event.getType()) { @@ -69,32 +87,32 @@ public class CatalogEventLister implements ApplicationListener { case CatalogEvent.DEL: if (parentPlatform != null) { - List deviceChannelList = new ArrayList<>(); + List channels = new ArrayList<>(); if (event.getChannels() != null) { - deviceChannelList.addAll(event.getChannels()); + channels.addAll(event.getChannels()); } - if (!deviceChannelList.isEmpty()) { - log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), deviceChannelList.size()); + if (!channels.isEmpty()) { + log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), channels.size()); try { - sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, deviceChannelList, subscribe, null); + sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, channels, subscribe, null); } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException | IllegalAccessException e) { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } - }else if (!parentPlatformMap.keySet().isEmpty()) { - for (String gbId : parentPlatformMap.keySet()) { - List parentPlatforms = parentPlatformMap.get(gbId); - if (parentPlatforms != null && !parentPlatforms.isEmpty()) { - for (Platform platform : parentPlatforms) { + }else if (!platformMap.keySet().isEmpty()) { + for (String serverGbId : platformMap.keySet()) { + List platformList = platformMap.get(serverGbId); + if (platformList != null && !platformList.isEmpty()) { + for (Platform platform : platformList) { SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (subscribeInfo == null) { continue; } - log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId); + log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), serverGbId); List deviceChannelList = new ArrayList<>(); CommonGBChannel deviceChannel = new CommonGBChannel(); - deviceChannel.setGbDeviceId(gbId); + deviceChannel.setGbDeviceId(serverGbId); deviceChannelList.add(deviceChannel); try { sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null); @@ -103,6 +121,8 @@ public class CatalogEventLister implements ApplicationListener { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } + }else { + log.info("[Catalog事件: {}] 未找到上级平台: {}", event.getType(), serverGbId); } } } @@ -127,9 +147,9 @@ public class CatalogEventLister implements ApplicationListener { log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } - }else if (!parentPlatformMap.keySet().isEmpty()) { - for (String gbId : parentPlatformMap.keySet()) { - List parentPlatforms = parentPlatformMap.get(gbId); + }else if (!platformMap.keySet().isEmpty()) { + for (String gbId : platformMap.keySet()) { + List parentPlatforms = platformMap.get(gbId); if (parentPlatforms != null && !parentPlatforms.isEmpty()) { for (Platform platform : parentPlatforms) { SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); @@ -156,4 +176,4 @@ public class CatalogEventLister implements ApplicationListener { } } } - \ No newline at end of file + diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/mobilePosition/MobilePositionEventLister.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/mobilePosition/MobilePositionEventLister.java index 40dc6c853..7b06f07fc 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/mobilePosition/MobilePositionEventLister.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/mobilePosition/MobilePositionEventLister.java @@ -1,10 +1,12 @@ package com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition; +import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; +import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderForPlatform; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import lombok.extern.slf4j.Slf4j; @@ -24,6 +26,9 @@ import java.util.List; @Component public class MobilePositionEventLister implements ApplicationListener { + @Autowired + private IPlatformService platformService; + @Autowired private IPlatformChannelService platformChannelService; @@ -33,14 +38,17 @@ public class MobilePositionEventLister implements ApplicationListener allPlatforms = platformService.queryAll(userSetting.getServerId()); // 获取所用订阅 - List platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(); + List platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(allPlatforms); if (platforms.isEmpty()) { return; } @@ -65,4 +73,3 @@ public class MobilePositionEventLister implements ApplicationListener channelList); void checkRegionRemove(List channelList, List regionList); + + List queryByPlatformBySharChannelId(String gbId); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlatformService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlatformService.java index a6c2b2466..b0bea7d14 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlatformService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IPlatformService.java @@ -53,13 +53,7 @@ public interface IPlatformService { * 平台离线 * @param parentPlatform 平台信息 */ - void offline(Platform parentPlatform, boolean stopRegisterTask); - - /** - * 向上级平台发起注册 - * @param parentPlatform - */ - void login(Platform parentPlatform); + void offline(Platform parentPlatform); /** * 向上级平台发送位置订阅 @@ -85,4 +79,7 @@ public interface IPlatformService { List queryEnablePlatformList(String serverId); void delete(Integer platformId, CommonCallback callback); + + List queryAll(String serverId); + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java index 0ce89c198..47dbfc931 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java @@ -15,9 +15,11 @@ 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.ISubscribeTask; -import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; -import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; +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.SubscribeTaskForCatalog; +import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl.SubscribeTaskForMobilPosition; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; import com.genersoft.iot.vmp.media.bean.MediaServer; @@ -32,13 +34,18 @@ import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; +import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import javax.sip.InvalidArgumentException; +import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.validation.constraints.NotNull; import java.text.ParseException; @@ -53,7 +60,8 @@ import java.util.concurrent.TimeUnit; */ @Slf4j @Service -public class DeviceServiceImpl implements IDeviceService { +@Order(value=16) +public class DeviceServiceImpl implements IDeviceService, CommandLineRunner { @Autowired private DynamicTask dynamicTask; @@ -100,10 +108,46 @@ public class DeviceServiceImpl implements IDeviceService { @Autowired private IRedisRpcService redisRpcService; + @Autowired + private SubscribeTaskRunner subscribeTaskRunner; + private Device getDeviceByDeviceIdFromDb(String deviceId) { return deviceMapper.getDeviceByDeviceId(deviceId); } + @Override + public void run(String... args) throws Exception { + // TODO 处理设备离线 + + // 处理订阅任务 + List taskInfoList = subscribeTaskRunner.getAllTaskInfo(); + if (!taskInfoList.isEmpty()) { + for (SubscribeTaskInfo taskInfo : taskInfoList) { + if (taskInfo == null) { + continue; + } + Device device = getDeviceByDeviceId(taskInfo.getDeviceId()); + if (device == null || !device.isOnLine()) { + subscribeTaskRunner.removeSubscribe(taskInfo.getKey()); + continue; + } + if (SubscribeTaskForCatalog.name.equals(taskInfo.getName())) { + device.setSubscribeCycleForCatalog((int)taskInfo.getExpireTime()); + SubscribeTask subscribeTask = SubscribeTaskForCatalog.getInstance(device, this::catalogSubscribeExpire, taskInfo.getTransactionInfo()); + if (subscribeTask != null) { + subscribeTaskRunner.addSubscribe(subscribeTask); + } + }else if (SubscribeTaskForMobilPosition.name.equals(taskInfo.getName())) { + device.setSubscribeCycleForMobilePosition((int)taskInfo.getExpireTime()); + SubscribeTask subscribeTask = SubscribeTaskForMobilPosition.getInstance(device, this::mobilPositionSubscribeExpire, taskInfo.getTransactionInfo()); + if (subscribeTask != null) { + subscribeTaskRunner.addSubscribe(subscribeTask); + } + } + } + } + } + @Override public void online(Device device, SipTransactionInfo sipTransactionInfo) { log.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort()); @@ -148,6 +192,7 @@ public class DeviceServiceImpl implements IDeviceService { } sync(device); }else { + device.setServerId(userSetting.getServerId()); if(!device.isOnLine()){ device.setOnLine(true); device.setCreateTime(now); @@ -164,12 +209,12 @@ public class DeviceServiceImpl implements IDeviceService { // TODO 如果设备下的通道级联到了其他平台,那么需要发送事件或者notify给上级平台 } // 上线添加订阅 - if (device.getSubscribeCycleForCatalog() > 0) { + if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅 - addCatalogSubscribe(device); + addCatalogSubscribe(device, null); } - if (device.getSubscribeCycleForMobilePosition() > 0) { - addMobilePositionSubscribe(device); + if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { + addMobilePositionSubscribe(device, null); } if (userSetting.getDeviceStatusNotify()) { // 发送redis消息 @@ -254,98 +299,173 @@ public class DeviceServiceImpl implements IDeviceService { } } - @Override - public boolean addCatalogSubscribe(Device device) { - if (device == null || device.getSubscribeCycleForCatalog() < 0) { - return false; + // 订阅丢失检查 + @Scheduled(fixedDelay = 10, timeUnit = TimeUnit.SECONDS) + public void lostCheck(){ + // 获取所有设备 + List deviceList = redisCatchStorage.getAllDevices(); + if (deviceList.isEmpty()) { + return; } - log.info("[添加目录订阅] 设备{}", device.getDeviceId()); - // 添加目录订阅 - CatalogSubscribeTask catalogSubscribeTask = new CatalogSubscribeTask(device, sipCommander, dynamicTask); - // 刷新订阅 - int subscribeCycleForCatalog = Math.max(device.getSubscribeCycleForCatalog(),30); - // 设置最小值为30 - dynamicTask.startCron(device.getDeviceId() + "catalog", catalogSubscribeTask, (subscribeCycleForCatalog -1) * 1000); + for (Device device : deviceList) { + if (device == null || !device.isOnLine() || !device.getServerId().equals(userSetting.getServerId())) { + continue; + } + if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { + log.debug("[订阅丢失] 目录订阅, 编号: {}, 重新发起订阅", device.getDeviceId()); + addCatalogSubscribe(device, null); + } + if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { + log.debug("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId()); + addMobilePositionSubscribe(device, null); + } + } + } - catalogSubscribeTask.run(); - return true; + private void catalogSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) { + log.info("[目录订阅] 到期, 编号: {}", deviceId); + Device device = getDeviceByDeviceId(deviceId); + if (device == null) { + log.info("[目录订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId); + return; + } + if (device.getSubscribeCycleForCatalog() > 0) { + addCatalogSubscribe(device, transactionInfo); + } + } + + private void mobilPositionSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) { + log.info("[移动位置订阅] 到期, 编号: {}", deviceId); + Device device = getDeviceByDeviceId(deviceId); + if (device == null) { + log.info("[移动位置订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId); + return; + } + if (device.getSubscribeCycleForMobilePosition() > 0) { + addMobilePositionSubscribe(device, transactionInfo); + } } @Override - public boolean removeCatalogSubscribe(Device device, CommonCallback callback) { + public boolean addCatalogSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) { if (device == null || device.getSubscribeCycleForCatalog() < 0) { - if (callback != null) { - callback.run(false); - } return false; } - log.info("[移除目录订阅]: {}", device.getDeviceId()); - String taskKey = device.getDeviceId() + "catalog"; - if (device.isOnLine()) { - Runnable runnable = dynamicTask.get(taskKey); - if (runnable instanceof ISubscribeTask) { - ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(callback); - }else { - log.info("[移除目录订阅]失败,未找到订阅任务 : {}", device.getDeviceId()); - if (callback != null) { - callback.run(false); + log.info("[添加目录订阅] 设备 {}", device.getDeviceId()); + try { + sipCommander.catalogSubscribe(device, transactionInfo, eventResult -> { + ResponseEvent event = (ResponseEvent) eventResult.event; + // 成功 + log.info("[目录订阅]成功: {}", device.getDeviceId()); + if (!subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { + SIPResponse response = (SIPResponse) event.getResponse(); + SipTransactionInfo transactionInfoForResonse = new SipTransactionInfo(response); + SubscribeTask subscribeTask = SubscribeTaskForCatalog.getInstance(device, this::catalogSubscribeExpire, transactionInfoForResonse); + if (subscribeTask != null) { + subscribeTaskRunner.addSubscribe(subscribeTask); + } + }else { + subscribeTaskRunner.updateDelay(SubscribeTaskForCatalog.getKey(device), (device.getSubscribeCycleForCatalog() * 1000L - 500L) + System.currentTimeMillis()); } - } - }else { - log.info("[移除目录订阅订阅]失败,设备已经离线 : {}", device.getDeviceId()); - if (callback != null) { - callback.run(false); - } + + },eventResult -> { + // 失败 + log.warn("[目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); + }); + } catch (InvalidArgumentException | SipException | ParseException e) { + log.error("[命令发送失败] 目录订阅: {}", e.getMessage()); + return false; } - dynamicTask.stop(taskKey); return true; } @Override - public boolean addMobilePositionSubscribe(Device device) { - if (device == null || device.getSubscribeCycleForMobilePosition() < 0) { + public boolean removeCatalogSubscribe(@NotNull Device device, CommonCallback callback) { + log.info("[移除目录订阅]: {}", device.getDeviceId()); + String key = SubscribeTaskForCatalog.getKey(device); + if (subscribeTaskRunner.containsKey(key)) { + SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key); + if (transactionInfo == null) { + log.warn("[移除目录订阅] 未找到事务信息,{}", device.getDeviceId()); + } + try { + device.setSubscribeCycleForCatalog(0); + sipCommander.catalogSubscribe(device, transactionInfo, eventResult -> { + // 成功 + log.info("[取消目录订阅]成功: {}", device.getDeviceId()); + subscribeTaskRunner.removeSubscribe(SubscribeTaskForCatalog.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 boolean addMobilePositionSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) { + log.info("[添加移动位置订阅] 设备 {}", device.getDeviceId()); + try { + sipCommander.mobilePositionSubscribe(device, transactionInfo, eventResult -> { + ResponseEvent event = (ResponseEvent) eventResult.event; + // 成功 + log.info("[移动位置订阅]成功: {}", device.getDeviceId()); + if (!subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { + SIPResponse response = (SIPResponse) event.getResponse(); + SipTransactionInfo transactionInfoForResonse = new SipTransactionInfo(response); + SubscribeTask subscribeTask = SubscribeTaskForMobilPosition.getInstance(device, this::catalogSubscribeExpire, transactionInfoForResonse); + if (subscribeTask != null) { + subscribeTaskRunner.addSubscribe(subscribeTask); + } + }else { + subscribeTaskRunner.updateDelay(SubscribeTaskForMobilPosition.getKey(device), (device.getSubscribeCycleForCatalog() * 1000L - 500L) + System.currentTimeMillis()); + } + + },eventResult -> { + // 失败 + log.warn("[移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); + }); + } catch (InvalidArgumentException | SipException | ParseException e) { + log.error("[命令发送失败] 移动位置订阅: {}", e.getMessage()); return false; } - log.info("[添加移动位置订阅] 设备{}", device.getDeviceId()); - // 添加目录订阅 - MobilePositionSubscribeTask mobilePositionSubscribeTask = new MobilePositionSubscribeTask(device, sipCommander, dynamicTask); - // 设置最小值为30 - int subscribeCycleForCatalog = Math.max(device.getSubscribeCycleForMobilePosition(),30); - // 刷新订阅 - dynamicTask.startCron(device.getDeviceId() + "mobile_position" , mobilePositionSubscribeTask, subscribeCycleForCatalog * 1000); - mobilePositionSubscribeTask.run(); return true; } @Override public boolean removeMobilePositionSubscribe(Device device, CommonCallback callback) { - if (device == null || device.getSubscribeCycleForCatalog() < 0) { - if (callback != null) { - callback.run(false); - } - return false; - } log.info("[移除移动位置订阅]: {}", device.getDeviceId()); - String taskKey = device.getDeviceId() + "mobile_position"; - if (device.isOnLine()) { - Runnable runnable = dynamicTask.get(taskKey); - if (runnable instanceof ISubscribeTask) { - ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(callback); - }else { - log.info("[移除移动位置订阅]失败,未找到订阅任务 : {}", device.getDeviceId()); - if (callback != null) { - callback.run(false); - } + String key = SubscribeTaskForMobilPosition.getKey(device); + if (subscribeTaskRunner.containsKey(key)) { + SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key); + if (transactionInfo == null) { + log.warn("[移除移动位置订阅] 未找到事务信息,{}", device.getDeviceId()); } - }else { - log.info("[移除移动位置订阅]失败,设备已经离线 : {}", device.getDeviceId()); - if (callback != null) { - callback.run(false); + try { + device.setSubscribeCycleForMobilePosition(0); + sipCommander.mobilePositionSubscribe(device, transactionInfo, eventResult -> { + // 成功 + log.info("[取消移动位置订阅]成功: {}", device.getDeviceId()); + subscribeTaskRunner.removeSubscribe(SubscribeTaskForMobilPosition.getKey(device)); + if (callback != null) { + callback.run(true); + } + },eventResult -> { + // 失败 + log.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); + }); + }catch (Exception e) { + // 失败 + log.warn("[取消移动位置订阅]失败: {}-{} ", device.getDeviceId(), e.getMessage()); } } - dynamicTask.stop(taskKey); return true; } @@ -499,10 +619,20 @@ public class DeviceServiceImpl implements IDeviceService { public boolean delete(String deviceId) { Device device = getDeviceByDeviceIdFromDb(deviceId); Assert.notNull(device, "未找到设备"); + if (subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) { + removeCatalogSubscribe(device, null); + } + if (subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) { + removeMobilePositionSubscribe(device, null); + } + // 停止状态检测 + String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + device.getDeviceId(); + dynamicTask.stop(registerExpireTaskKey); platformChannelMapper.delChannelForDeviceId(deviceId); deviceChannelMapper.cleanChannelsByDeviceId(device.getId()); deviceMapper.del(deviceId); redisCatchStorage.removeDevice(deviceId); + inviteStreamService.clearInviteInfo(deviceId); return true; } @@ -562,20 +692,17 @@ public class DeviceServiceImpl implements IDeviceService { // 订阅周期不同,则先取消 removeCatalogSubscribe(device, result->{ device.setSubscribeCycleForCatalog(cycle); + updateDevice(device); if (cycle > 0) { // 开启订阅 - addCatalogSubscribe(device); + addCatalogSubscribe(device, null); } - // 因为是异步执行,需要在这里更新下数据 - deviceMapper.updateSubscribeCatalog(device); - redisCatchStorage.updateDevice(device); }); }else { // 开启订阅 device.setSubscribeCycleForCatalog(cycle); - addCatalogSubscribe(device); - deviceMapper.updateSubscribeCatalog(device); - redisCatchStorage.updateDevice(device); + updateDevice(device); + addCatalogSubscribe(device, null); } } @@ -583,6 +710,7 @@ public class DeviceServiceImpl implements IDeviceService { public void subscribeMobilePosition(int id, int cycle, int interval) { Device device = deviceMapper.query(id); Assert.notNull(device, "未找到设备"); + if (device.getSubscribeCycleForMobilePosition() == cycle) { return; } @@ -598,21 +726,16 @@ public class DeviceServiceImpl implements IDeviceService { device.setSubscribeCycleForMobilePosition(cycle); device.setMobilePositionSubmissionInterval(interval); if (cycle > 0) { - addMobilePositionSubscribe(device); + addMobilePositionSubscribe(device, null); } - // 因为是异步执行,需要在这里更新下数据 - deviceMapper.updateSubscribeMobilePosition(device); - redisCatchStorage.updateDevice(device); }); }else { // 订阅未开启 device.setSubscribeCycleForMobilePosition(cycle); device.setMobilePositionSubmissionInterval(interval); + updateDevice(device); // 开启订阅 - addMobilePositionSubscribe(device); - // 因为是异步执行,需要在这里更新下数据 - deviceMapper.updateSubscribeMobilePosition(device); - redisCatchStorage.updateDevice(device); + addMobilePositionSubscribe(device, null); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java index 2019a0bdc..faeecf4ba 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java @@ -160,30 +160,26 @@ public class GbChannelServiceImpl implements IGbChannelService { log.warn("[多个通道离线] 通道数量为0,更新失败"); return 0; } - List onlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "ON"); - if (onlineChannelList.isEmpty()) { - log.info("[多个通道离线] 更新失败, 参数内通道已经离线, 无需更新"); - return 0; - } + log.info("[通道离线] 共 {} 个", commonGBChannelList.size()); int limitCount = 1000; int result = 0; - if (onlineChannelList.size() > limitCount) { - for (int i = 0; i < onlineChannelList.size(); i += limitCount) { + if (commonGBChannelList.size() > limitCount) { + for (int i = 0; i < commonGBChannelList.size(); i += limitCount) { int toIndex = i + limitCount; - if (i + limitCount > onlineChannelList.size()) { - toIndex = onlineChannelList.size(); + if (i + limitCount > commonGBChannelList.size()) { + toIndex = commonGBChannelList.size(); } - result += commonGBChannelMapper.updateStatusForListById(onlineChannelList.subList(i, toIndex), "OFF"); + result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "OFF"); } } else { - result += commonGBChannelMapper.updateStatusForListById(onlineChannelList, "OFF"); + result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "OFF"); } if (result > 0) { try { // 发送catalog - eventPublisher.catalogEventPublish(null, onlineChannelList, CatalogEvent.OFF); + eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.OFF); } catch (Exception e) { - log.warn("[多个通道离线] 发送失败,数量:{}", onlineChannelList.size(), e); + log.warn("[多个通道离线] 发送失败,数量:{}", commonGBChannelList.size(), e); } } return result; @@ -214,32 +210,25 @@ public class GbChannelServiceImpl implements IGbChannelService { log.warn("[多个通道上线] 通道数量为0,更新失败"); return 0; } - List offlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "OFF"); - if (offlineChannelList.isEmpty()) { - log.warn("[多个通道上线] 更新失败, 参数内通道已经上线"); - return 0; - } // 批量更新 int limitCount = 1000; int result = 0; - if (offlineChannelList.size() > limitCount) { - for (int i = 0; i < offlineChannelList.size(); i += limitCount) { + if (commonGBChannelList.size() > limitCount) { + for (int i = 0; i < commonGBChannelList.size(); i += limitCount) { int toIndex = i + limitCount; - if (i + limitCount > offlineChannelList.size()) { - toIndex = offlineChannelList.size(); + if (i + limitCount > commonGBChannelList.size()) { + toIndex = commonGBChannelList.size(); } - result += commonGBChannelMapper.updateStatusForListById(offlineChannelList.subList(i, toIndex), "ON"); + result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "ON"); } } else { - result += commonGBChannelMapper.updateStatusForListById(offlineChannelList, "ON"); + result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "ON"); } - if (result > 0) { - try { - // 发送catalog - eventPublisher.catalogEventPublish(null, offlineChannelList, CatalogEvent.ON); - } catch (Exception e) { - log.warn("[多个通道上线] 发送失败,数量:{}", offlineChannelList.size(), e); - } + try { + // 发送catalog + eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.ON); + } catch (Exception e) { + log.warn("[多个通道上线] 发送失败,数量:{}", commonGBChannelList.size(), e); } return result; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java index ff11f2ef7..f4bd244a2 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformChannelServiceImpl.java @@ -601,4 +601,12 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService { public List queryChannelByPlatformIdAndChannelIds(Integer platformId, List channelIds) { return platformChannelMapper.queryShare(platformId, channelIds); } + + @Override + public List queryByPlatformBySharChannelId(String channelDeviceId) { + CommonGBChannel commonGBChannel = commonGBChannelMapper.queryByDeviceId(channelDeviceId); + ArrayList ids = new ArrayList<>(); + ids.add(commonGBChannel.getGbId()); + return platformChannelMapper.queryPlatFormListByChannelList(ids); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformServiceImpl.java index 2cd5f8930..c33247fe4 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlatformServiceImpl.java @@ -1,6 +1,5 @@ package com.genersoft.iot.vmp.gb28181.service.impl; -import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.*; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.SipConfig; @@ -15,6 +14,10 @@ import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; +import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformKeepaliveTask; +import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformRegisterTask; +import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformRegisterTaskInfo; +import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformStatusTaskRunner; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.bean.MediaInfo; @@ -28,13 +31,14 @@ import com.genersoft.iot.vmp.service.ISendRtpServerService; import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.utils.DateUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import gov.nist.javax.sip.message.SIPResponse; 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; @@ -56,12 +60,8 @@ import java.util.concurrent.TimeUnit; */ @Slf4j @Service -public class PlatformServiceImpl implements IPlatformService { - - private final static String REGISTER_KEY_PREFIX = "platform_register_"; - - private final static String REGISTER_FAIL_AGAIN_KEY_PREFIX = "platform_register_fail_again_"; - private final static String KEEPALIVE_KEY_PREFIX = "platform_keepalive_"; +@Order(value=15) +public class PlatformServiceImpl implements IPlatformService, CommandLineRunner { @Autowired private PlatformMapper platformMapper; @@ -69,7 +69,6 @@ public class PlatformServiceImpl implements IPlatformService { @Autowired private IRedisCatchStorage redisCatchStorage; - @Autowired private SSRCFactory ssrcFactory; @@ -109,6 +108,74 @@ public class PlatformServiceImpl implements IPlatformService { @Autowired private ISendRtpServerService sendRtpServerService; + @Autowired + private PlatformStatusTaskRunner statusTaskRunner; + + @Override + public void run(String... args) throws Exception { + // 启动时 如果存在未过期的注册平台,则发送注销 + List registerTaskInfoList = statusTaskRunner.getAllRegisterTaskInfo(); + if (registerTaskInfoList.isEmpty()) { + return; + } + for (PlatformRegisterTaskInfo taskInfo : registerTaskInfoList) { + log.info("[国标级联] 启动服务是发现平台注册仍在有效期,注销: {}", taskInfo.getPlatformServerId()); + Platform platform = queryPlatformByServerGBId(taskInfo.getPlatformServerId()); + if (platform == null) { + statusTaskRunner.removeRegisterTask(taskInfo.getPlatformServerId()); + continue; + } + sendUnRegister(platform, taskInfo.getSipTransactionInfo()); + } + // 启动时所有平台默认离线 + platformMapper.offlineAll(userSetting.getServerId()); + } + @Scheduled(fixedDelay = 20, timeUnit = TimeUnit.SECONDS) //每3秒执行一次 + public void statusLostCheck(){ + // 每隔20秒检测,是否存在启用但是未注册的平台,存在则发起注册 + // 获取所有在线并且启用的平台 + List platformList = platformMapper.queryServerIdsWithEnableAndServer(userSetting.getServerId()); + if (platformList.isEmpty()) { + return; + } + for (Platform platform : platformList) { + if (statusTaskRunner.containsRegister(platform.getServerGBId()) && statusTaskRunner.containsKeepAlive(platform.getServerGBId())) { + continue; + } + if (statusTaskRunner.containsRegister(platform.getServerGBId())) { + SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platform.getServerGBId()); + // 注销后出发平台离线, 如果是启用的平台,那么下次丢失检测会检测到并重新注册上线 + sendUnRegister(platform, transactionInfo); + }else { + statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); + sendRegister(platform, null); + } + } + } + + private void sendRegister(Platform platform, SipTransactionInfo sipTransactionInfo) { + try { + commanderForPlatform.register(platform, sipTransactionInfo, eventResult -> { + log.info("[国标级联] {}({}),注册失败", platform.getName(), platform.getServerGBId()); + offline(platform); + }, null); + } catch (InvalidArgumentException | ParseException | SipException e) { + log.error("[命令发送失败] 国标级联: {}", e.getMessage()); + } + } + + private void sendUnRegister(Platform platform, SipTransactionInfo sipTransactionInfo) { + statusTaskRunner.removeRegisterTask(platform.getServerGBId()); + statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); + try { + commanderForPlatform.unregister(platform, sipTransactionInfo, null, eventResult -> { + log.info("[国标级联] 注销成功, 平台:{}", platform.getServerGBId()); + }); + } catch (InvalidArgumentException | ParseException | SipException e) { + log.error("[命令发送失败] 国标级联: {}", e.getMessage()); + } + } + // 定时监听国标级联所进行的WVP服务是否正常, 如果异常则选择新的wvp执行 @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) //每3秒执行一次 public void execute(){ @@ -127,6 +194,7 @@ public class PlatformServiceImpl implements IPlatformService { return; } log.info("[集群] 检测到 {} 已离线", serverId); + redisCatchStorage.removeOfflineWVPInfo(serverId); String chooseServerId = redisCatchStorage.chooseOneServer(serverId); if (!userSetting.getServerId().equals(chooseServerId)){ return; @@ -139,20 +207,26 @@ public class PlatformServiceImpl implements IPlatformService { platform.setAddress(getIpWithSameNetwork(platform.getAddress())); platform.setServerId(userSetting.getServerId()); platformMapper.update(platform); - // 更新redis - redisCatchStorage.delPlatformCatchInfo(platform.getServerGBId()); - PlatformCatch platformCatch = new PlatformCatch(); - platformCatch.setPlatform(platform); - platformCatch.setId(platform.getServerGBId()); - redisCatchStorage.updatePlatformCatchInfo(platformCatch); - // 开始注册 - // 注册成功时由程序直接调用了online方法 - try { - commanderForPlatform.register(platform, eventResult -> { - log.info("[国标级联] {}({}),添加向上级注册失败,请确定上级平台可用时重新保存", platform.getName(), platform.getServerGBId()); - }, null); - } catch (InvalidArgumentException | ParseException | SipException e) { - log.error("[命令发送失败] 国标级联: {}", e.getMessage()); + // 检查就平台是否注册到期,没有则注销,由本平台重新注册 + List taskInfoList = statusTaskRunner.getRegisterTransactionInfoByServerId(serverId); + boolean needUnregister = false; + SipTransactionInfo sipTransactionInfo = null; + if (!taskInfoList.isEmpty()) { + for (PlatformRegisterTaskInfo taskInfo : taskInfoList) { + if (taskInfo.getPlatformServerId().equals(platform.getServerGBId()) + && taskInfo.getSipTransactionInfo() != null) { + needUnregister = true; + sipTransactionInfo = taskInfo.getSipTransactionInfo(); + break; + } + } + } + if (needUnregister) { + sendUnRegister(platform, sipTransactionInfo); + }else { + // 开始注册 + // 注册成功时由程序直接调用了online方法 + sendRegister(platform, null); } }); }); @@ -265,25 +339,17 @@ public class PlatformServiceImpl implements IPlatformService { } platform.setServerId(userSetting.getServerId()); int result = platformMapper.add(platform); - // 添加缓存 - PlatformCatch platformCatch = new PlatformCatch(); - platformCatch.setPlatform(platform); - platformCatch.setId(platform.getServerGBId()); - redisCatchStorage.updatePlatformCatchInfo(platformCatch); + if (platform.isEnable()) { // 保存时启用就发送注册 // 注册成功时由程序直接调用了online方法 - try { - commanderForPlatform.register(platform, eventResult -> { - log.info("[国标级联] {}({}),添加向上级注册失败,请确定上级平台可用时重新保存", platform.getName(), platform.getServerGBId()); - }, null); - } catch (InvalidArgumentException | ParseException | SipException e) { - log.error("[命令发送失败] 国标级联: {}", e.getMessage()); - } + sendRegister(platform, null); } return result > 0; } + + @Override public boolean update(Platform platform) { Assert.isTrue(platform.getId() > 0, "ID必须存在"); @@ -294,52 +360,15 @@ public class PlatformServiceImpl implements IPlatformService { if (!userSetting.getServerId().equals(platformInDb.getServerId())) { return redisRpcService.updatePlatform(platformInDb.getServerId(), platform); } - - PlatformCatch platformCatchOld = redisCatchStorage.queryPlatformCatchInfo(platformInDb.getServerGBId()); - platform.setUpdateTime(DateUtil.getNow()); - - // 停止心跳定时 - final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + platformInDb.getServerGBId(); - dynamicTask.stop(keepaliveTaskKey); - // 停止注册定时 - final String registerTaskKey = REGISTER_KEY_PREFIX + platformInDb.getServerGBId(); - dynamicTask.stop(registerTaskKey); - // 注销旧的 - try { - if (platformInDb.isStatus() && platformCatchOld != null) { - log.info("保存平台{}时发现旧平台在线,发送注销命令", platformInDb.getServerGBId()); - commanderForPlatform.unregister(platformInDb, platformCatchOld.getSipTransactionInfo(), null, eventResult -> { - log.info("[国标级联] 注销成功, 平台:{}", platformInDb.getServerGBId()); - }); - } - } catch (InvalidArgumentException | ParseException | SipException e) { - log.error("[命令发送失败] 国标级联 注销: {}", e.getMessage()); - } - // 更新数据库 if (platform.getCatalogGroup() == 0) { platform.setCatalogGroup(1); } - platformMapper.update(platform); - // 更新redis - redisCatchStorage.delPlatformCatchInfo(platformInDb.getServerGBId()); - PlatformCatch platformCatch = new PlatformCatch(); - platformCatch.setPlatform(platform); - platformCatch.setId(platform.getServerGBId()); - redisCatchStorage.updatePlatformCatchInfo(platformCatch); - // 注册 - if (platform.isEnable()) { - // 保存时启用就发送注册 - // 注册成功时由程序直接调用了online方法 - try { - log.info("[国标级联] 平台注册 {}", platform.getDeviceGBId()); - commanderForPlatform.register(platform, eventResult -> { - log.info("[国标级联] {},添加向上级注册失败,请确定上级平台可用时重新保存", platform.getServerGBId()); - }, null); - } catch (InvalidArgumentException | ParseException | SipException e) { - log.error("[命令发送失败] 国标级联: {}", e.getMessage()); - } + if (statusTaskRunner.containsRegister(platformInDb.getServerGBId())) { + SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platformInDb.getServerGBId()); + // 注销后出发平台离线, 如果是启用的平台,那么下次丢失检测会检测到并重新注册上线 + sendUnRegister(platformInDb, transactionInfo); } return false; @@ -348,79 +377,21 @@ public class PlatformServiceImpl implements IPlatformService { @Override public void online(Platform platform, SipTransactionInfo sipTransactionInfo) { log.info("[国标级联]:{}, 平台上线", platform.getServerGBId()); - final String registerFailAgainTaskKey = REGISTER_FAIL_AGAIN_KEY_PREFIX + platform.getServerGBId(); - dynamicTask.stop(registerFailAgainTaskKey); + PlatformRegisterTask registerTask = new PlatformRegisterTask(platform.getServerGBId(), platform.getExpires() * 1000L - 500L, + sipTransactionInfo, (platformServerGbId) -> { + this.registerExpire(platformServerGbId, sipTransactionInfo); + }); + statusTaskRunner.addRegisterTask(registerTask); - platformMapper.updateStatus(platform.getServerGBId(), true); - PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); - if (platformCatch == null) { - platformCatch = new PlatformCatch(); - platformCatch.setPlatform(platform); - platformCatch.setId(platform.getServerGBId()); - platform.setStatus(true); - platformCatch.setPlatform(platform); - } + PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, + this::keepaliveExpire); + statusTaskRunner.addKeepAliveTask(keepaliveTask); + platformMapper.updateStatus(platform.getId(), true, userSetting.getServerId()); - platformCatch.getPlatform().setStatus(true); - platformCatch.setSipTransactionInfo(sipTransactionInfo); - redisCatchStorage.updatePlatformCatchInfo(platformCatch); - - final String registerTaskKey = REGISTER_KEY_PREFIX + platform.getServerGBId(); - if (!dynamicTask.isAlive(registerTaskKey)) { - log.info("[国标级联]:{}, 添加定时注册任务", platform.getServerGBId()); - // 添加注册任务 - dynamicTask.startCron(registerTaskKey, - // 注册失败(注册成功时由程序直接调用了online方法) - ()-> registerTask(platform, sipTransactionInfo), - platform.getExpires() * 1000); - } - - - final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + platform.getServerGBId(); - if (!dynamicTask.contains(keepaliveTaskKey)) { - log.info("[国标级联]:{}, 添加定时心跳任务", platform.getServerGBId()); - // 添加心跳任务 - dynamicTask.startCron(keepaliveTaskKey, - ()-> { - try { - commanderForPlatform.keepalive(platform, eventResult -> { - // 心跳失败 - if (eventResult.type != SipSubscribe.EventResultType.timeout) { - log.warn("[国标级联]发送心跳收到错误,code: {}, msg: {}", eventResult.statusCode, eventResult.msg); - } - // 心跳失败 - PlatformCatch platformCatchForNow = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); - // 此时是第三次心跳超时, 平台离线 - if (platformCatchForNow.getKeepAliveReply() == 2) { - // 设置平台离线,并重新注册 - log.info("[国标级联] 三次心跳失败, 平台{}({})离线", platform.getName(), platform.getServerGBId()); - offline(platform, false); - }else { - platformCatchForNow.setKeepAliveReply(platformCatchForNow.getKeepAliveReply() + 1); - redisCatchStorage.updatePlatformCatchInfo(platformCatchForNow); - } - - }, eventResult -> { - // 心跳成功 - // 清空之前的心跳超时计数 - PlatformCatch platformCatchForNow = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); - if (platformCatchForNow != null && platformCatchForNow.getKeepAliveReply() > 0) { - platformCatchForNow.setKeepAliveReply(0); - redisCatchStorage.updatePlatformCatchInfo(platformCatchForNow); - } - log.info("[国标级联] 发送心跳,平台{}({}), code: {}, msg: {}", platform.getName(), platform.getServerGBId(), eventResult.statusCode, eventResult.msg); - }); - } catch (SipException | InvalidArgumentException | ParseException e) { - log.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage()); - } - }, - (platform.getKeepTimeout())*1000); - } if (platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) { if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) { log.info("[国标级联]:{}, 添加自动通道推送模拟订阅信息", platform.getServerGBId()); addSimulatedSubscribeInfo(platform); - } }else { SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); @@ -430,6 +401,65 @@ public class PlatformServiceImpl implements IPlatformService { } } + /** + * 注册到期处理 + */ + private void registerExpire(String platformServerId, SipTransactionInfo transactionInfo) { + log.info("[国标级联] 注册到期, 上级平台编号: {}", platformServerId); + Platform platform = queryPlatformByServerGBId(platformServerId); + if (platform == null || !platform.isEnable()) { + log.info("[国标级联] 注册到期, 上级平台编号: {}, 平台不存在或者未启用, 忽略", platformServerId); + return; + } + sendRegister(platform, transactionInfo); + } + + private void keepaliveExpire(String platformServerId, int failCount) { + log.info("[国标级联] 心跳到期, 上级平台编号: {}", platformServerId); + Platform platform = queryPlatformByServerGBId(platformServerId); + if (platform == null || !platform.isEnable()) { + log.info("[国标级联] 心跳到期, 上级平台编号: {}, 平台不存在或者未启用, 忽略", platformServerId); + return; + } + try { + commanderForPlatform.keepalive(platform, eventResult -> { + // 心跳失败 + if (eventResult.type != SipSubscribe.EventResultType.timeout) { + log.warn("[国标级联] 发送心跳收到错误,code: {}, msg: {}", eventResult.statusCode, eventResult.msg); + } + + // 心跳超时失败 + if (failCount < 2) { + log.info("[国标级联] 心跳发送超时, 平台服务编号: {}", platformServerId); + PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, + this::keepaliveExpire); + keepaliveTask.setFailCount(failCount + 1); + statusTaskRunner.addKeepAliveTask(keepaliveTask); + }else { + // 心跳超时三次, 不再发送心跳, 平台离线 + log.info("[国标级联] 心跳发送超时三次,平台离线, 平台服务编号: {}", platformServerId); + offline(platform); + } + }, eventResult -> { + PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, + this::keepaliveExpire); + statusTaskRunner.addKeepAliveTask(keepaliveTask); + }); + } catch (SipException | InvalidArgumentException | ParseException e) { + log.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage()); + if (failCount < 2) { + PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L, + this::keepaliveExpire); + keepaliveTask.setFailCount(failCount + 1); + statusTaskRunner.addKeepAliveTask(keepaliveTask); + }else { + // 心跳超时三次, 不再发送心跳, 平台离线 + log.info("[国标级联] 心跳发送失败三次,平台离线, 平台服务编号: {}", platformServerId); + offline(platform); + } + } + } + @Override public void addSimulatedSubscribeInfo(Platform platform) { // 自动添加一条模拟的订阅信息 @@ -437,87 +467,25 @@ public class PlatformServiceImpl implements IPlatformService { SubscribeInfo.buildSimulated(platform.getServerGBId(), platform.getServerIp())); } - private void registerTask(Platform platform, SipTransactionInfo sipTransactionInfo){ - try { - // 不在同一个会话中续订则每次全新注册 - if (!userSetting.isRegisterKeepIntDialog()) { - sipTransactionInfo = null; - } - - if (sipTransactionInfo == null) { - log.info("[国标级联] 平台:{}注册即将到期,开始重新注册", platform.getServerGBId()); - }else { - log.info("[国标级联] 平台:{}注册即将到期,开始续订", platform.getServerGBId()); - } - - commanderForPlatform.register(platform, sipTransactionInfo, eventResult -> { - log.info("[国标级联] 平台:{}注册失败,{}:{}", platform.getServerGBId(), - eventResult.statusCode, eventResult.msg); - if (platform.isStatus()) { - offline(platform, false); - } - }, null); - } catch (Exception e) { - log.error("[命令发送失败] 国标级联定时注册: {}", e.getMessage()); - } - } - @Override - public void offline(Platform platform, boolean stopRegister) { + public void offline(Platform platform) { log.info("[平台离线]:{}({})", platform.getName(), platform.getServerGBId()); - PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); - platformCatch.setKeepAliveReply(0); - platformCatch.setRegisterAliveReply(0); - Platform catchPlatform = platformCatch.getPlatform(); - catchPlatform.setStatus(false); - platformCatch.setPlatform(catchPlatform); - redisCatchStorage.updatePlatformCatchInfo(platformCatch); - platformMapper.updateStatus(platform.getServerGBId(), false); + statusTaskRunner.removeRegisterTask(platform.getServerGBId()); + statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); + + subscribeHolder.removeCatalogSubscribe(platform.getServerGBId()); + subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId()); + + platformMapper.updateStatus(platform.getId(), false, userSetting.getServerId()); // 停止所有推流 log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId()); stopAllPush(platform.getServerGBId()); - - // 清除注册定时 - log.info("[平台离线] {}({}), 停止定时注册任务", platform.getName(), platform.getServerGBId()); - final String registerTaskKey = REGISTER_KEY_PREFIX + platform.getServerGBId(); - if (dynamicTask.contains(registerTaskKey)) { - dynamicTask.stop(registerTaskKey); - } - // 清除心跳定时 - log.info("[平台离线] {}({}), 停止定时发送心跳任务", platform.getName(), platform.getServerGBId()); - final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + platform.getServerGBId(); - if (dynamicTask.contains(keepaliveTaskKey)) { - // 清除心跳任务 - dynamicTask.stop(keepaliveTaskKey); - } - // 停止订阅回复 - SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); - if (catalogSubscribe != null) { - if (catalogSubscribe.getExpires() > 0) { - log.info("[平台离线] {}({}), 停止目录订阅回复", platform.getName(), platform.getServerGBId()); - subscribeHolder.removeCatalogSubscribe(platform.getServerGBId()); - } - } - - log.info("[平台离线] {}({}), 停止移动位置订阅回复", platform.getName(), platform.getServerGBId()); - subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId()); - // 发起定时自动重新注册 - if (!stopRegister) { - // 设置为60秒自动尝试重新注册 - final String registerFailAgainTaskKey = REGISTER_FAIL_AGAIN_KEY_PREFIX + platform.getServerGBId(); - Platform platformInDb = platformMapper.query(platform.getId()); - if (platformInDb.isEnable()) { - dynamicTask.startCron(registerFailAgainTaskKey, - ()-> registerTask(platformInDb, null), - userSetting.getRegisterAgainAfterTime() * 1000); - } - } } private void stopAllPush(String platformId) { List sendRtpItems = sendRtpServerService.queryForPlatform(platformId); - if (sendRtpItems != null && sendRtpItems.size() > 0) { + if (sendRtpItems != null && !sendRtpItems.isEmpty()) { for (SendRtpInfo sendRtpItem : sendRtpItems) { ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); sendRtpServerService.delete(sendRtpItem); @@ -527,23 +495,6 @@ public class PlatformServiceImpl implements IPlatformService { } } - @Override - public void login(Platform platform) { - final String registerTaskKey = REGISTER_KEY_PREFIX + platform.getServerGBId(); - try { - commanderForPlatform.register(platform, eventResult1 -> { - log.info("[国标级联] {},开始定时发起注册,间隔为1分钟", platform.getServerGBId()); - // 添加注册任务 - dynamicTask.startCron(registerTaskKey, - // 注册失败(注册成功时由程序直接调用了online方法) - ()-> log.info("[国标级联] {}({}),平台离线后持续发起注册,失败", platform.getName(), platform.getServerGBId()), - 60*1000); - }, null); - } catch (InvalidArgumentException | ParseException | SipException e) { - log.error("[命令发送失败] 国标级联注册: {}", e.getMessage()); - } - } - @Override public void sendNotifyMobilePosition(String platformId) { Platform platform = platformMapper.getParentPlatByServerGBId(platformId); @@ -565,7 +516,6 @@ public class PlatformServiceImpl implements IPlatformService { gpsMsgInfo = null; } - if (gpsMsgInfo == null && !userSetting.isSendPositionOnDemand()){ gpsMsgInfo = new GPSMsgInfo(); gpsMsgInfo.setId(channel.getGbDeviceId()); @@ -890,7 +840,7 @@ public class PlatformServiceImpl implements IPlatformService { @Override public List queryEnablePlatformList(String serverId) { - return platformMapper.queryEnableParentPlatformList(serverId,true); + return platformMapper.queryEnableParentPlatformListByServerId(serverId,true); } @Override @@ -898,55 +848,23 @@ public class PlatformServiceImpl implements IPlatformService { public void delete(Integer platformId, CommonCallback callback) { Platform platform = platformMapper.query(platformId); Assert.notNull(platform, "平台不存在"); - // 发送离线消息,无论是否成功都删除缓存 - PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); - if (platformCatch != null) { - String key = UUID.randomUUID().toString(); - dynamicTask.startDelay(key, ()->{ - deletePlatformInfo(platform); - if (callback != null) { - callback.run(null); - } - }, 2000); + if (statusTaskRunner.containsRegister(platform.getServerGBId())) { try { - commanderForPlatform.unregister(platform, platformCatch.getSipTransactionInfo(), (event -> { - dynamicTask.stop(key); - // 移除平台相关的信息 - deletePlatformInfo(platform); - if (callback != null) { - callback.run(null); - } - }), (event -> { - dynamicTask.stop(key); - // 移除平台相关的信息 - deletePlatformInfo(platform); - if (callback != null) { - callback.run(null); - } - })); - } catch (InvalidArgumentException | ParseException | SipException e) { - log.error("[命令发送失败] 国标级联 注销: {}", e.getMessage()); - } - }else { - deletePlatformInfo(platform); - if (callback != null) { - callback.run(null); - } + SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platform.getServerGBId()); + sendUnRegister(platform, transactionInfo); + }catch (Exception ignored) {} } + platformMapper.delete(platform.getId()); + statusTaskRunner.removeRegisterTask(platform.getServerGBId()); + statusTaskRunner.removeKeepAliveTask(platform.getServerGBId()); + + subscribeHolder.removeCatalogSubscribe(platform.getServerGBId()); + subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId()); } - @Transactional - public void deletePlatformInfo(Platform platform) { - // 删除关联的通道 - platformChannelMapper.removeChannelsByPlatformId(platform.getId()); - // 删除关联的分组 - platformChannelMapper.removePlatformGroupsByPlatformId(platform.getId()); - // 删除关联的行政区划 - platformChannelMapper.removePlatformRegionByPlatformId(platform.getId()); - // 删除redis缓存 - redisCatchStorage.delPlatformCatchInfo(platform.getServerGBId()); - // 删除平台信息 - platformMapper.delete(platform.getId()); + @Override + public List queryAll(String serverId) { + return platformMapper.queryByServerId(serverId); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java index 6a27dd816..fb56c689b 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/PlayServiceImpl.java @@ -340,7 +340,7 @@ public class PlayServiceImpl implements IPlayService { InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId()); if (inviteInfoInCatch != null ) { if (inviteInfoInCatch.getStreamInfo() == null) { - // 释放生成的ssrc,使用上一次申请的322 + // 释放生成的ssrc,使用上一次申请的 ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc); // 点播发起了但是尚未成功, 仅注册回调等待结果即可 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java index aaf6eff44..67f9c29d7 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.session; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @@ -13,6 +14,7 @@ import java.util.Set; /** * ssrc使用 */ +@Slf4j @Component public class SSRCFactory { @@ -93,6 +95,7 @@ public class SSRCFactory { String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; Long size = redisTemplate.opsForSet().size(redisKey); if (size == null || size == 0) { + log.info("[获取 SSRC 失败] redisKey: {}", redisKey); throw new RuntimeException("ssrc已经用完"); } else { // 在集合中移除并返回一个随机成员。 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java deleted file mode 100755 index 8d1c7d2ee..000000000 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.genersoft.iot.vmp.gb28181.task; - -import com.genersoft.iot.vmp.common.CommonCallback; - -/** - * @author lin - */ -public interface ISubscribeTask extends Runnable{ - void stop(CommonCallback callback); -} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTask.java new file mode 100644 index 000000000..e33ac3813 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTask.java @@ -0,0 +1,49 @@ +package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe; + +import com.genersoft.iot.vmp.common.SubscribeCallback; +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; +import lombok.Data; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +@Data +public abstract class SubscribeTask implements Delayed { + + private String deviceId; + + private SubscribeCallback callback; + + private SipTransactionInfo transactionInfo; + + /** + * 超时时间(单位: 毫秒) + */ + private long delayTime; + + public abstract void expired(); + + public abstract String getKey(); + + public abstract String getName(); + + @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 SubscribeTaskInfo getInfo(){ + SubscribeTaskInfo subscribeTaskInfo = new SubscribeTaskInfo(); + subscribeTaskInfo.setName(getName()); + subscribeTaskInfo.setTransactionInfo(transactionInfo); + subscribeTaskInfo.setDeviceId(deviceId); + subscribeTaskInfo.setKey(getKey()); + return subscribeTaskInfo; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskInfo.java new file mode 100644 index 000000000..3a8741130 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskInfo.java @@ -0,0 +1,22 @@ +package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe; + +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; +import lombok.Data; + +@Data +public class SubscribeTaskInfo { + + private String deviceId; + + private SipTransactionInfo transactionInfo; + + private String name; + + private String key; + + /** + * 过期时间 + */ + private long expireTime; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskRunner.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskRunner.java new file mode 100644 index 000000000..7019121d3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/SubscribeTaskRunner.java @@ -0,0 +1,135 @@ +package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe; + +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 SubscribeTaskRunner{ + + private final Map subscribes = new ConcurrentHashMap<>(); + + private final DelayQueue delayQueue = new DelayQueue<>(); + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private UserSetting userSetting; + + private final String prefix = "VMP_DEVICE_SUBSCRIBE"; + + // 订阅过期检查 + @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) + public void expirationCheck(){ + while (!delayQueue.isEmpty()) { + SubscribeTask take = null; + try { + take = delayQueue.take(); + try { + removeSubscribe(take.getKey()); + take.expired(); + }catch (Exception e) { + log.error("[设备订阅到期] {} 到期处理时出现异常, 设备编号: {} ", take.getName(), take.getDeviceId()); + } + } catch (InterruptedException e) { + log.error("[设备订阅任务] ", e); + } + } + } + + public void addSubscribe(SubscribeTask task) { + Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000); + if (duration.getSeconds() < 0) { + return; + } + subscribes.put(task.getKey(), task); + String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey()); + redisTemplate.opsForValue().set(key, task.getInfo(), duration); + delayQueue.offer(task); + } + + public boolean removeSubscribe(String key) { + SubscribeTask task = subscribes.get(key); + if (task == null) { + return false; + } + String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey()); + 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) { + SubscribeTask task = subscribes.get(key); + if (task == null) { + return null; + } + return task.getTransactionInfo(); + } + + public boolean updateDelay(String key, long expirationTime) { + SubscribeTask task = subscribes.get(key); + if (task == null) { + return false; + } + log.info("[更新订阅任务时间] {}, 编号: {}", task.getName(), key); + if (delayQueue.contains(task)) { + boolean remove = delayQueue.remove(task); + if (!remove) { + log.info("[更新订阅任务时间] 从延时队列内移除失败: {}", key); + } + } + task.setDelayTime(expirationTime); + delayQueue.offer(task); + String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey()); + 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 getAllTaskInfo(){ + String scanKey = String.format("%s_%s_*", prefix, userSetting.getServerId()); + List values = RedisUtil.scan(redisTemplate, scanKey); + if (values.isEmpty()) { + return new ArrayList<>(); + } + List result = new ArrayList<>(); + for (Object value : values) { + String redisKey = (String)value; + SubscribeTaskInfo taskInfo = (SubscribeTaskInfo)redisTemplate.opsForValue().get(redisKey); + if (taskInfo == null) { + continue; + } + Long expire = redisTemplate.getExpire(redisKey); + taskInfo.setExpireTime(expire); + result.add(taskInfo); + } + return result; + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForCatalog.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForCatalog.java new file mode 100644 index 000000000..8ca7b390e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForCatalog.java @@ -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 SubscribeTaskForCatalog extends SubscribeTask { + + public static final String name = "catalog"; + + public static SubscribeTask getInstance(Device device, SubscribeCallback callback, SipTransactionInfo transactionInfo) { + if (device.getSubscribeCycleForCatalog() <= 0) { + return null; + } + SubscribeTaskForCatalog subscribeTaskForCatalog = new SubscribeTaskForCatalog(); + subscribeTaskForCatalog.setDelayTime((device.getSubscribeCycleForCatalog() * 1000L - 500L) + System.currentTimeMillis()); + subscribeTaskForCatalog.setDeviceId(device.getDeviceId()); + subscribeTaskForCatalog.setCallback(callback); + subscribeTaskForCatalog.setTransactionInfo(transactionInfo); + return subscribeTaskForCatalog; + } + + @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", SubscribeTaskForCatalog.name, device.getDeviceId()); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForMobilPosition.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForMobilPosition.java new file mode 100644 index 000000000..5ff3c6198 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/deviceSubscribe/impl/SubscribeTaskForMobilPosition.java @@ -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 SubscribeTaskForMobilPosition extends SubscribeTask { + + public static final String name = "mobilPosition"; + + public static SubscribeTask getInstance(Device device, SubscribeCallback callback, SipTransactionInfo transactionInfo) { + if (device.getSubscribeCycleForCatalog() <= 0) { + return null; + } + SubscribeTaskForMobilPosition subscribeTaskForMobilPosition = new SubscribeTaskForMobilPosition(); + subscribeTaskForMobilPosition.setDelayTime((device.getSubscribeCycleForMobilePosition() * 1000L - 500L) + System.currentTimeMillis()); + subscribeTaskForMobilPosition.setDeviceId(device.getDeviceId()); + subscribeTaskForMobilPosition.setCallback(callback); + subscribeTaskForMobilPosition.setTransactionInfo(transactionInfo); + return subscribeTaskForMobilPosition; + } + + @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", SubscribeTaskForMobilPosition.name, device.getDeviceId()); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java deleted file mode 100755 index e3f191242..000000000 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.genersoft.iot.vmp.gb28181.task.impl; - -import com.genersoft.iot.vmp.common.CommonCallback; -import com.genersoft.iot.vmp.conf.DynamicTask; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; -import gov.nist.javax.sip.message.SIPRequest; -import lombok.extern.slf4j.Slf4j; - -import javax.sip.DialogState; -import javax.sip.InvalidArgumentException; -import javax.sip.ResponseEvent; -import javax.sip.SipException; -import javax.sip.header.ToHeader; -import java.text.ParseException; - -/** - * 目录订阅任务 - * @author lin - */ -@Slf4j -public class CatalogSubscribeTask implements ISubscribeTask { - private final Device device; - private final ISIPCommander sipCommander; - private SIPRequest request; - - private final DynamicTask dynamicTask; - - private final String taskKey = "catalog-subscribe-timeout"; - - - public CatalogSubscribeTask(Device device, ISIPCommander sipCommander, DynamicTask dynamicTask) { - this.device = device; - this.sipCommander = sipCommander; - this.dynamicTask = dynamicTask; - } - - @Override - public void run() { - if (dynamicTask.get(taskKey) != null) { - dynamicTask.stop(taskKey); - } - SIPRequest sipRequest = null; - try { - sipRequest = sipCommander.catalogSubscribe(device, request, eventResult -> { - ResponseEvent event = (ResponseEvent) eventResult.event; - // 成功 - log.info("[目录订阅]成功: {}", device.getDeviceId()); - ToHeader toHeader = (ToHeader)event.getResponse().getHeader(ToHeader.NAME); - try { - this.request.getToHeader().setTag(toHeader.getTag()); - } catch (ParseException e) { - log.info("[目录订阅]成功: 但为request设置ToTag失败"); - this.request = null; - } - },eventResult -> { - this.request = null; - // 失败 - log.warn("[目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); - dynamicTask.startDelay(taskKey, CatalogSubscribeTask.this, 2000); - }); - } catch (InvalidArgumentException | SipException | ParseException e) { - log.error("[命令发送失败] 目录订阅: {}", e.getMessage()); - - } - if (sipRequest != null) { - this.request = sipRequest; - } - } - - @Override - public void stop(CommonCallback callback) { - /** - * dialog 的各个状态 - * EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息 - * CONFIRMED-> Confirmed Dialog状态-已确认 - * COMPLETED-> Completed Dialog状态-已完成 - * TERMINATED-> Terminated Dialog状态-终止 - */ - log.info("取消目录订阅时dialog状态为{}", DialogState.CONFIRMED); - if (dynamicTask.get(taskKey) != null) { - dynamicTask.stop(taskKey); - } - device.setSubscribeCycleForCatalog(0); - try { - sipCommander.catalogSubscribe(device, request, eventResult -> { - ResponseEvent event = (ResponseEvent) eventResult.event; - if (event.getResponse().getRawContent() != null) { - // 成功 - log.info("[取消目录订阅]成功: {}", device.getDeviceId()); - }else { - // 成功 - log.info("[取消目录订阅]成功: {}", device.getDeviceId()); - } - if (callback != null) { - callback.run(event.getResponse().getRawContent() != null); - } - },eventResult -> { - // 失败 - log.warn("[取消目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); - }); - } catch (InvalidArgumentException | SipException | ParseException e) { - log.error("[命令发送失败] 取消目录订阅: {}", e.getMessage()); - } - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java deleted file mode 100755 index 646b31eac..000000000 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.genersoft.iot.vmp.gb28181.task.impl; - -import com.genersoft.iot.vmp.common.CommonCallback; -import com.genersoft.iot.vmp.conf.DynamicTask; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; -import gov.nist.javax.sip.message.SIPRequest; -import lombok.extern.slf4j.Slf4j; - -import javax.sip.InvalidArgumentException; -import javax.sip.ResponseEvent; -import javax.sip.SipException; -import javax.sip.header.ToHeader; -import java.text.ParseException; - -/** - * 移动位置订阅的定时更新 - * @author lin - */ -@Slf4j -public class MobilePositionSubscribeTask implements ISubscribeTask { - private final Device device; - private final ISIPCommander sipCommander; - - private SIPRequest request; - private final DynamicTask dynamicTask; - private final String taskKey = "mobile-position-subscribe-timeout"; - - public MobilePositionSubscribeTask(Device device, ISIPCommander sipCommander, DynamicTask dynamicTask) { - this.device = device; - this.sipCommander = sipCommander; - this.dynamicTask = dynamicTask; - } - - @Override - public void run() { - if (dynamicTask.get(taskKey) != null) { - dynamicTask.stop(taskKey); - } - SIPRequest sipRequest = null; - try { - sipRequest = sipCommander.mobilePositionSubscribe(device, request, eventResult -> { - // 成功 - log.info("[移动位置订阅]成功: {}", device.getDeviceId()); - ResponseEvent event = (ResponseEvent) eventResult.event; - ToHeader toHeader = (ToHeader)event.getResponse().getHeader(ToHeader.NAME); - try { - this.request.getToHeader().setTag(toHeader.getTag()); - } catch (ParseException e) { - log.info("[移动位置订阅]成功: 为request设置ToTag失败"); - this.request = null; - } - },eventResult -> { - this.request = null; - // 失败 - log.warn("[移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); - dynamicTask.startDelay(taskKey, MobilePositionSubscribeTask.this, 2000); - }); - } catch (InvalidArgumentException | SipException | ParseException e) { - log.error("[命令发送失败] 移动位置订阅: {}", e.getMessage()); - } - if (sipRequest != null) { - this.request = sipRequest; - } - - } - - @Override - public void stop(CommonCallback callback) { - /** - * dialog 的各个状态 - * EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息 - * CONFIRMED-> Confirmed Dialog状态-已确认 - * COMPLETED-> Completed Dialog状态-已完成 - * TERMINATED-> Terminated Dialog状态-终止 - */ - if (dynamicTask.get(taskKey) != null) { - dynamicTask.stop(taskKey); - } - device.setSubscribeCycleForMobilePosition(0); - try { - sipCommander.mobilePositionSubscribe(device, request, eventResult -> { - ResponseEvent event = (ResponseEvent) eventResult.event; - if (event.getResponse().getRawContent() != null) { - // 成功 - log.info("[取消移动位置订阅]成功: {}", device.getDeviceId()); - }else { - // 成功 - log.info("[取消移动位置订阅]成功: {}", device.getDeviceId()); - } - if (callback != null) { - callback.run(event.getResponse().getRawContent() != null); - } - },eventResult -> { - // 失败 - log.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); - }); - } catch (InvalidArgumentException | SipException | ParseException e) { - log.error("[命令发送失败] 取消移动位置订阅: {}", e.getMessage()); - } - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformKeepaliveTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformKeepaliveTask.java new file mode 100644 index 000000000..a1b2f400d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformKeepaliveTask.java @@ -0,0 +1,64 @@ +package com.genersoft.iot.vmp.gb28181.task.platformStatus; + +import com.genersoft.iot.vmp.gb28181.bean.PlatformKeepaliveCallback; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +/** + * 平台心跳任务 + */ +@Slf4j +public class PlatformKeepaliveTask implements Delayed { + + @Getter + private String platformServerId; + + /** + * 超时时间(单位: 毫秒) + */ + @Getter + @Setter + private long delayTime; + + /** + * 到期回调 + */ + @Getter + private PlatformKeepaliveCallback callback; + + /** + * 心跳发送失败次数 + */ + @Getter + @Setter + private int failCount; + + public PlatformKeepaliveTask(String platformServerId, long delayTime, PlatformKeepaliveCallback callback) { + this.platformServerId = platformServerId; + this.delayTime = System.currentTimeMillis() + delayTime; + this.callback = callback; + } + + public void expired() { + if (callback == null) { + log.info("[平台心跳到期] 未找到到期处理回调, 平台上级编号: {}", platformServerId); + return; + } + getCallback().run(platformServerId, failCount); + } + + @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)); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTask.java new file mode 100644 index 000000000..781dc96f7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTask.java @@ -0,0 +1,70 @@ +package com.genersoft.iot.vmp.gb28181.task.platformStatus; + +import com.genersoft.iot.vmp.common.CommonCallback; +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +/** + * 平台注册任务 + */ +@Slf4j +public class PlatformRegisterTask implements Delayed { + + @Getter + private String platformServerId; + + /** + * 超时时间(单位: 毫秒) + */ + @Getter + @Setter + private long delayTime; + + @Getter + private SipTransactionInfo sipTransactionInfo; + + /** + * 到期回调 + */ + @Getter + private CommonCallback callback; + + + public PlatformRegisterTask(String platformServerId, long delayTime, SipTransactionInfo sipTransactionInfo, CommonCallback callback) { + this.platformServerId = platformServerId; + this.delayTime = System.currentTimeMillis() + delayTime; + this.callback = callback; + this.sipTransactionInfo = sipTransactionInfo; + } + + public void expired() { + if (callback == null) { + log.info("[平台注册到期] 未找到到期处理回调, 平台上级编号: {}", platformServerId); + return; + } + getCallback().run(platformServerId); + } + + @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 PlatformRegisterTaskInfo getInfo() { + PlatformRegisterTaskInfo taskInfo = new PlatformRegisterTaskInfo(); + taskInfo.setPlatformServerId(platformServerId); + taskInfo.setSipTransactionInfo(sipTransactionInfo); + return taskInfo; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTaskInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTaskInfo.java new file mode 100644 index 000000000..d7593eb90 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformRegisterTaskInfo.java @@ -0,0 +1,29 @@ +package com.genersoft.iot.vmp.gb28181.task.platformStatus; + +import com.genersoft.iot.vmp.common.CommonCallback; +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +/** + * 平台注册任务可序列化的信息 + */ +@Slf4j +@Data +public class PlatformRegisterTaskInfo{ + + private String platformServerId; + + private SipTransactionInfo sipTransactionInfo; + + /** + * 过期时间 + */ + private long expireTime; +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformStatusTaskRunner.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformStatusTaskRunner.java new file mode 100644 index 000000000..eaa96aedf --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/platformStatus/PlatformStatusTaskRunner.java @@ -0,0 +1,202 @@ +package com.genersoft.iot.vmp.gb28181.task.platformStatus; + +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 PlatformStatusTaskRunner { + + private final Map registerSubscribes = new ConcurrentHashMap<>(); + + private final DelayQueue registerDelayQueue = new DelayQueue<>(); + + private final Map keepaliveSubscribes = new ConcurrentHashMap<>(); + + private final DelayQueue keepaliveTaskDelayQueue = new DelayQueue<>(); + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private UserSetting userSetting; + + private final String prefix = "VMP_PLATFORM_STATUS"; + + // 订阅过期检查 + @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) + public void expirationCheckForRegister(){ + while (!registerDelayQueue.isEmpty()) { + PlatformRegisterTask take = null; + try { + take = registerDelayQueue.take(); + try { + removeRegisterTask(take.getPlatformServerId()); + take.expired(); + }catch (Exception e) { + log.error("[平台注册到期] 到期处理时出现异常, 平台上级编号: {} ", take.getPlatformServerId()); + } + } catch (InterruptedException e) { + log.error("[平台注册到期] ", e); + } + } + } + @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS) + public void expirationCheckForKeepalive(){ + while (!keepaliveTaskDelayQueue.isEmpty()) { + PlatformKeepaliveTask take = null; + try { + take = keepaliveTaskDelayQueue.take(); + try { + removeKeepAliveTask(take.getPlatformServerId()); + take.expired(); + }catch (Exception e) { + log.error("[平台心跳到期] 到期处理时出现异常, 平台上级编号: {} ", take.getPlatformServerId()); + } + } catch (InterruptedException e) { + log.error("[平台心跳到期] ", e); + } + } + } + + public void addRegisterTask(PlatformRegisterTask task) { + Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000); + if (duration.getSeconds() < 0) { + return; + } + registerSubscribes.put(task.getPlatformServerId(), task); + String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getPlatformServerId()); + redisTemplate.opsForValue().set(key, task.getInfo(), duration); + registerDelayQueue.offer(task); + } + + public boolean removeRegisterTask(String platformServerId) { + PlatformRegisterTask task = registerSubscribes.get(platformServerId); + if (task != null) { + registerSubscribes.remove(platformServerId); + } + String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), platformServerId); + redisTemplate.delete(redisKey); + if (registerDelayQueue.contains(task)) { + boolean remove = registerDelayQueue.remove(task); + if (!remove) { + log.info("[移除平台注册任务] 从延时队列内移除失败: {}", platformServerId); + } + } + return true; + } + + public SipTransactionInfo getRegisterTransactionInfo(String platformServerId) { + PlatformRegisterTask task = registerSubscribes.get(platformServerId); + if (task == null) { + return null; + } + return task.getSipTransactionInfo(); + } + + public boolean updateRegisterDelay(String platformServerId, long expirationTime) { + PlatformRegisterTask task = registerSubscribes.get(platformServerId); + if (task == null) { + return false; + } + log.info("[更新平台注册任务时间] 平台上级编号: {}", platformServerId); + if (registerDelayQueue.contains(task)) { + boolean remove = registerDelayQueue.remove(task); + if (!remove) { + log.info("[更新平台注册任务时间] 从延时队列内移除失败: {}", platformServerId); + } + } + task.setDelayTime(expirationTime); + registerDelayQueue.offer(task); + String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), platformServerId); + Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis())/1000); + redisTemplate.expire(redisKey, duration); + return true; + } + + public boolean containsRegister(String platformServerId) { + return registerSubscribes.containsKey(platformServerId); + } + + public List getAllRegisterTaskInfo(){ + return getRegisterTransactionInfoByServerId(userSetting.getServerId()); + } + + public void addKeepAliveTask(PlatformKeepaliveTask task) { + Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis())/1000); + if (duration.getSeconds() < 0) { + return; + } + keepaliveSubscribes.put(task.getPlatformServerId(), task); + keepaliveTaskDelayQueue.offer(task); + } + + public boolean removeKeepAliveTask(String platformServerId) { + PlatformKeepaliveTask task = keepaliveSubscribes.get(platformServerId); + if (task != null) { + keepaliveSubscribes.remove(platformServerId); + } + if (keepaliveTaskDelayQueue.contains(task)) { + boolean remove = keepaliveTaskDelayQueue.remove(task); + if (!remove) { + log.info("[移除平台心跳任务] 从延时队列内移除失败: {}", platformServerId); + } + } + return true; + } + + public boolean updateKeepAliveDelay(String platformServerId, long expirationTime) { + PlatformKeepaliveTask task = keepaliveSubscribes.get(platformServerId); + if (task == null) { + return false; + } + log.info("[更新平台心跳任务时间] 平台上级编号: {}", platformServerId); + if (keepaliveTaskDelayQueue.contains(task)) { + boolean remove = keepaliveTaskDelayQueue.remove(task); + if (!remove) { + log.info("[更新平台心跳任务时间] 从延时队列内移除失败: {}", platformServerId); + } + } + task.setDelayTime(expirationTime); + keepaliveTaskDelayQueue.offer(task); + return true; + } + + public boolean containsKeepAlive(String platformServerId) { + return keepaliveSubscribes.containsKey(platformServerId); + } + + public List getRegisterTransactionInfoByServerId(String serverId) { + String scanKey = String.format("%s_%s_*", prefix, serverId); + List values = RedisUtil.scan(redisTemplate, scanKey); + if (values.isEmpty()) { + return new ArrayList<>(); + } + List result = new ArrayList<>(); + for (Object value : values) { + String redisKey = (String)value; + PlatformRegisterTaskInfo taskInfo = (PlatformRegisterTaskInfo)redisTemplate.opsForValue().get(redisKey); + if (taskInfo == null) { + continue; + } + Long expire = redisTemplate.getExpire(redisKey); + taskInfo.setExpireTime(expire); + result.add(taskInfo); + } + return result; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java index 0fb336397..3ac4be1bf 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java @@ -84,6 +84,11 @@ public class SIPProcessorObserver implements ISIPProcessorObserver { // Success if (((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) || status == Response.UNAUTHORIZED) { + ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(response.getCSeqHeader().getMethod()); + if (sipRequestProcessor != null) { + sipRequestProcessor.process(responseEvent); + } + CallIdHeader callIdHeader = response.getCallIdHeader(); CSeqHeader cSeqHeader = response.getCSeqHeader(); if (callIdHeader != null) { @@ -96,10 +101,6 @@ public class SIPProcessorObserver implements ISIPProcessorObserver { sipSubscribe.removeSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber()); } } - ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(response.getCSeqHeader().getMethod()); - if (sipRequestProcessor != null) { - sipRequestProcessor.process(responseEvent); - } } else if ((status >= Response.TRYING) && (status < Response.OK)) { // 增加其它无需回复的响应,如101、180等 // 更新sip订阅的时间 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java index a3d3c4f4d..c2d908001 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java @@ -2,21 +2,22 @@ package com.genersoft.iot.vmp.gb28181.transmit; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.SipLayer; +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.utils.GitUtil; import gov.nist.javax.sip.SipProviderImpl; +import gov.nist.javax.sip.address.SipUri; +import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.SipException; -import javax.sip.header.CSeqHeader; -import javax.sip.header.CallIdHeader; -import javax.sip.header.UserAgentHeader; -import javax.sip.header.ViaHeader; +import javax.sip.header.*; import javax.sip.message.Message; import javax.sip.message.Request; import javax.sip.message.Response; @@ -39,6 +40,7 @@ public class SIPSender { @Autowired private SipSubscribe sipSubscribe; + @Autowired private SipConfig sipConfig; @@ -69,11 +71,12 @@ public class SIPSender { log.error("添加UserAgentHeader失败", e); } } - CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME); CSeqHeader cSeqHeader = (CSeqHeader) message.getHeader(CSeqHeader.NAME); String key = callIdHeader.getCallId() + cSeqHeader.getSeqNumber(); if (okEvent != null || errorEvent != null) { + + FromHeader fromHeader = (FromHeader) message.getHeader(FromHeader.NAME); SipEvent sipEvent = SipEvent.getInstance(key, eventResult -> { sipSubscribe.removeSubscribe(key); if(okEvent != null) { @@ -85,6 +88,26 @@ public class SIPSender { errorEvent.response(eventResult); } }), timeout == null ? sipConfig.getTimeout() : timeout); + SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(); + sipTransactionInfo.setFromTag(fromHeader.getTag()); + sipTransactionInfo.setCallId(callIdHeader.getCallId()); + + if (message instanceof SIPResponse) { + SIPResponse response = (SIPResponse) message; + sipTransactionInfo.setToTag(response.getToHeader().getTag()); + sipTransactionInfo.setViaBranch(response.getTopmostViaHeader().getBranch()); + }else if (message instanceof SIPRequest) { + SIPRequest request = (SIPRequest) message; + sipTransactionInfo.setViaBranch(request.getTopmostViaHeader().getBranch()); + SipUri sipUri = (SipUri)request.getRequestLine().getUri(); + sipTransactionInfo.setUser(sipUri.getUser()); + } + + ExpiresHeader expiresHeader = (ExpiresHeader) message.getHeader(ExpiresHeader.NAME); + if (expiresHeader != null) { + sipTransactionInfo.setExpires(expiresHeader.getExpires()); + } + sipEvent.setSipTransactionInfo(sipTransactionInfo); sipSubscribe.addSubscribe(key, sipEvent); } try { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java index 784a73c64..4d252d5d2 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java @@ -17,16 +17,16 @@ import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; -/** - * @description:设备能力接口,用于定义设备的控制、查询能力 +/** + * @description:设备能力接口,用于定义设备的控制、查询能力 * @author: swwheihei - * @date: 2020年5月3日 下午9:16:34 + * @date: 2020年5月3日 下午9:16:34 */ public interface ISIPCommander { /** * 云台控制,支持方向与缩放控制 - * + * * @param device 控制设备 * @param channelId 预览通道 * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 @@ -36,10 +36,10 @@ public interface ISIPCommander { * @param zoomSpeed 镜头缩放速度 */ void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException; - + /** * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令 - * + * * @param device 控制设备 * @param channelId 预览通道 * @param cmdCode 指令码 @@ -48,7 +48,7 @@ public interface ISIPCommander { * @param combineCode2 组合码2 */ void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException; - + /** * 前端控制指令(用于转发上级指令) * @param device 控制设备 @@ -66,7 +66,7 @@ public interface ISIPCommander { /** * 请求回放视频流 - * + * * @param device 视频设备 * @param channel 预览通道 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss @@ -76,13 +76,13 @@ public interface ISIPCommander { /** * 请求历史媒体下载 - * + * * @param device 视频设备 * @param channel 预览通道 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss * @param downloadSpeed 下载倍速参数 - */ + */ void downloadStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException; @@ -116,7 +116,7 @@ public interface ISIPCommander { * 回放倍速播放 */ void playSpeedCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException; - + /** * 回放控制 * @param device @@ -138,39 +138,39 @@ public interface ISIPCommander { /** * 音视频录像控制 - * + * * @param device 视频设备 * @param channelId 预览通道 * @param recordCmdStr 录像命令:Record / StopRecord */ void recordCmd(Device device, String channelId, String recordCmdStr, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 远程启动控制命令 - * + * * @param device 视频设备 */ void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException; /** * 报警布防/撤防命令 - * + * * @param device 视频设备 */ void guardCmd(Device device, String guardCmdStr, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 报警复位命令 - * + * * @param device 视频设备 * @param alarmMethod 报警方式(可选) * @param alarmType 报警类型(可选) */ void alarmResetCmd(Device device, String alarmMethod, String alarmType, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 - * + * * @param device 视频设备 * @param channelId 预览通道 */ @@ -184,11 +184,11 @@ public interface ISIPCommander { /** * 设备配置命令 - * + * * @param device 视频设备 */ void deviceConfigCmd(Device device); - + /** * 设备配置命令:basicParam */ @@ -196,11 +196,11 @@ public interface ISIPCommander { /** * 查询设备状态 - * + * * @param device 视频设备 */ void deviceStatusQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 查询设备信息 * @@ -209,27 +209,27 @@ public interface ISIPCommander { * @return */ void deviceInfoQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 查询目录列表 - * + * * @param device 视频设备 */ void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException; - + /** * 查询录像信息 - * + * * @param device 视频设备 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss * @param sn */ void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - + /** * 查询报警信息 - * + * * @param device 视频设备 * @param startPriority 报警起始级别(可选) * @param endPriority 报警终止级别(可选) @@ -241,37 +241,37 @@ public interface ISIPCommander { */ void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 查询设备配置 - * + * * @param device 视频设备 * @param channelId 通道编码(可选) * @param configType 配置类型: */ void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 查询设备预置位置 - * + * * @param device 视频设备 */ void presetQuery(Device device, String channelId, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException; - + /** * 查询移动设备位置数据 - * + * * @param device 视频设备 */ void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 订阅、取消订阅移动位置 - * + * * @param device 视频设备 * @return true = 命令发送成功 */ - SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + SIPRequest mobilePositionSubscribe(Device device, SipTransactionInfo transactionInfo, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 订阅、取消订阅报警信息 @@ -290,7 +290,7 @@ public interface ISIPCommander { * @param device 视频设备 * @return true = 命令发送成功 */ - SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + SIPRequest catalogSubscribe(Device device, SipTransactionInfo transactionInfo, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; /** * 拉框控制命令 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java index 4b7176590..8e3fb4b36 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java @@ -34,7 +34,7 @@ public class SIPRequestHeaderPlarformProvider { @Autowired private SipConfig sipConfig; - + @Autowired private SipLayer sipLayer; @@ -225,11 +225,11 @@ public class SIPRequestHeaderPlarformProvider { SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse() != null ? subscribeInfo.getResponse().getToTag(): subscribeInfo.getSimulatedToTag()); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getTransactionInfo() != null ? subscribeInfo.getTransactionInfo() .getToTag(): subscribeInfo.getSimulatedToTag()); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest() != null ?subscribeInfo.getRequest().getFromTag(): subscribeInfo.getSimulatedFromTag()); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getTransactionInfo() != null ?subscribeInfo.getTransactionInfo().getFromTag(): subscribeInfo.getSimulatedFromTag()); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); @@ -239,7 +239,7 @@ public class SIPRequestHeaderPlarformProvider { // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset("gb2312"); - CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest() != null ? subscribeInfo.getRequest().getCallIdHeader().getCallId(): subscribeInfo.getSimulatedCallId()); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getTransactionInfo() != null ? subscribeInfo.getTransactionInfo().getCallId(): subscribeInfo.getSimulatedCallId()); request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java index 7bfd91bfa..3c084f7ce 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java @@ -33,7 +33,7 @@ public class SIPRequestHeaderProvider { @Autowired private SipConfig sipConfig; - + @Autowired private SipLayer sipLayer; @@ -43,7 +43,7 @@ public class SIPRequestHeaderProvider { @Autowired private IRedisCatchStorage redisCatchStorage; - + public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri @@ -76,7 +76,7 @@ public class SIPRequestHeaderProvider { request.setContent(content, contentTypeHeader); return request; } - + public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 @@ -96,10 +96,10 @@ public class SIPRequestHeaderProvider { SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); - + //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); - + //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); @@ -116,7 +116,7 @@ public class SIPRequestHeaderProvider { request.setContent(content, contentTypeHeader); return request; } - + public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 @@ -134,14 +134,14 @@ public class SIPRequestHeaderProvider { SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); - + //Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); - + //ceq CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); - + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); // Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); @@ -231,7 +231,7 @@ public class SIPRequestHeaderProvider { return request; } - public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { + public Request createSubscribeRequest(Device device, String content, SipTransactionInfo sipTransactionInfo, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); @@ -244,11 +244,11 @@ public class SIPRequestHeaderProvider { // from SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag()); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sipTransactionInfo == null ? SipUtils.getNewFromTag() :sipTransactionInfo.getFromTag()); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag()); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, sipTransactionInfo == null ? null :sipTransactionInfo.getToTag()); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java index 8e607ecc2..fd53e18e9 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -1179,7 +1179,7 @@ public class SIPCommander implements ISIPCommander { * @return true = 命令发送成功 */ @Override - public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { + public SIPRequest mobilePositionSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer subscribePostitionXml = new StringBuffer(200); String charset = device.getCharset(); @@ -1197,12 +1197,12 @@ public class SIPCommander implements ISIPCommander { CallIdHeader callIdHeader; - if (requestOld != null) { - callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); + if (sipTransactionInfo != null) { + callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId()); } else { callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); } - SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4)); + SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), sipTransactionInfo, device.getSubscribeCycleForMobilePosition(), "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4)); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); return request; @@ -1255,7 +1255,7 @@ public class SIPCommander implements ISIPCommander { } @Override - public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { + public SIPRequest catalogSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); @@ -1268,14 +1268,14 @@ public class SIPCommander implements ISIPCommander { CallIdHeader callIdHeader; - if (requestOld != null) { - callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); + if (sipTransactionInfo != null) { + callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId()); } else { callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); } // 有效时间默认为60秒以上 - SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld, device.getSubscribeCycleForCatalog(), "Catalog", + SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), sipTransactionInfo, device.getSubscribeCycleForCatalog(), "Catalog", callIdHeader); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); return request; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java index bce60a796..8ad0a6c2f 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java @@ -21,7 +21,6 @@ import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.GitUtil; import gov.nist.javax.sip.message.MessageFactoryImpl; @@ -121,9 +120,6 @@ public class SIPCommanderForPlatform implements ISIPCommanderForPlatform { request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, toTag, callIdHeader, isRegister? parentPlatform.getExpires() : 0); - // 将 callid 写入缓存, 等注册成功可以更新状态 - String callIdFromHeader = callIdHeader.getCallId(); - redisCatchStorage.updatePlatformRegisterInfo(callIdFromHeader, PlatformRegisterInfo.getInstance(parentPlatform.getServerGBId(), isRegister)); }else { request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, fromTag, toTag, www, callIdHeader, isRegister? parentPlatform.getExpires() : 0); } @@ -132,7 +128,6 @@ public class SIPCommanderForPlatform implements ISIPCommanderForPlatform { if (event != null) { log.info("[国标级联]:{}, 注册失败: {} ", parentPlatform.getServerGBId(), event.msg); } - redisCatchStorage.delPlatformRegisterInfo(callIdHeader.getCallId()); if (errorEvent != null ) { errorEvent.response(event); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java index 26a886b78..bacf50456 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java @@ -143,39 +143,44 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc()); } } - MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); - if (mediaServer != null) { - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId()); - if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) { - // 来自上级平台的停止对讲 - log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId()); - audioBroadcastManager.del(sendRtpItem.getChannelId()); - } + if (sendRtpItem.getServerId().equals(userSetting.getServerId())) { + MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); + if (mediaServer != null) { + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId()); + if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) { + // 来自上级平台的停止对讲 + log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId()); + audioBroadcastManager.del(sendRtpItem.getChannelId()); + } - MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId); + MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId); - if (mediaInfo.getReaderCount() <= 0) { - log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); - if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { - Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId()); - if (device == null) { - log.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); - return; - } - DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId()); - if (deviceChannel == null) { - log.info("[收到bye] {} 通知设备停止推流时未找到通道信息", streamId); - return; - } - try { - log.info("[停止点播] {}/{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId()); - cmder.streamByeCmd(device, deviceChannel.getDeviceId(), sendRtpItem.getApp(), sendRtpItem.getStream(), null, null); - } catch (InvalidArgumentException | ParseException | SipException | - SsrcTransactionNotFoundException e) { - log.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage()); + if (mediaInfo != null && mediaInfo.getReaderCount() <= 0) { + log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); + if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { + Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId()); + if (device == null) { + log.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); + return; + } + DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId()); + if (deviceChannel == null) { + log.info("[收到bye] {} 通知设备停止推流时未找到通道信息", streamId); + return; + } + try { + log.info("[停止点播] {}/{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId()); + cmder.streamByeCmd(device, deviceChannel.getDeviceId(), sendRtpItem.getApp(), sendRtpItem.getStream(), null, null); + } catch (InvalidArgumentException | ParseException | SipException | + SsrcTransactionNotFoundException e) { + log.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage()); + } } } } + } else { + // TODO 流再其他wvp上时应该通知这个wvp停止推流和发送BYE + } } // 可能是设备发送的停止 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java index 63c66516e..5ba83e85b 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java @@ -172,10 +172,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // 点播成功, TODO 可以在此处检测cancel命令是否存在,存在则不发送 if (userSetting.getUseCustomSsrcForParentInvite()) { // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 - String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName()) + MediaServer mediaServer = mediaServerService.getOne(streamInfo.getMediaServer().getId()); + if (mediaServer != null) { + String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName()) ? ssrcFactory.getPlaySsrc(streamInfo.getMediaServer().getId()) - : ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId()); - inviteInfo.setSsrc(ssrc); + : ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId()); + inviteInfo.setSsrc(ssrc); + } } // 构建sendRTP内容 SendRtpInfo sendRtpItem = sendRtpServerService.createSendRtpInfo(streamInfo.getMediaServer(), diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java index acdff1ce3..f59091ccd 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java @@ -1,9 +1,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; -import com.genersoft.iot.vmp.gb28181.bean.CmdType; -import com.genersoft.iot.vmp.gb28181.bean.Platform; -import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; -import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; +import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; @@ -23,6 +20,7 @@ import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; +import javax.sip.header.EventHeader; import javax.sip.header.ExpiresHeader; import javax.sip.message.Response; import java.text.ParseException; @@ -56,9 +54,9 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme sipProcessorObserver.addRequestProcessor(method, this); } - /** - * 处理SUBSCRIBE请求 - * + /** + * 处理SUBSCRIBE请求 + * * @param evt 事件 */ @Override @@ -106,7 +104,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme String platformId = SipUtils.getUserIdFromFromHeader(request); String deviceId = XmlUtil.getText(rootElement, "DeviceID"); Platform platform = platformService.queryPlatformByServerGBId(platformId); - SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId); if (platform == null) { return; } @@ -122,23 +119,28 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme .append("OK\r\n") .append("\r\n"); - if (subscribeInfo.getExpires() > 0) { - // GPS上报时间间隔 - String interval = XmlUtil.getText(rootElement, "Interval"); - if (interval == null) { - subscribeInfo.setGpsInterval(5); - }else { - subscribeInfo.setGpsInterval(Integer.parseInt(interval)); - } - subscribeInfo.setSn(sn); - } + try { - SIPResponse response = responseXmlAck(request, resultXml.toString(), platform, subscribeInfo.getExpires()); + int expires = request.getExpires().getExpires(); + SIPResponse response = responseXmlAck(request, resultXml.toString(), platform, expires); + + SubscribeInfo subscribeInfo = SubscribeInfo.getInstance(response, platformId, expires, + (EventHeader)request.getHeader(EventHeader.NAME)); + if (subscribeInfo.getExpires() > 0) { + // GPS上报时间间隔 + String interval = XmlUtil.getText(rootElement, "Interval"); + if (interval == null) { + subscribeInfo.setGpsInterval(5); + }else { + subscribeInfo.setGpsInterval(Integer.parseInt(interval)); + } + subscribeInfo.setSn(sn); + } if (subscribeInfo.getExpires() == 0) { subscribeHolder.removeMobilePositionSubscribe(platformId); }else { - subscribeInfo.setResponse(response); + subscribeInfo.setTransactionInfo(new SipTransactionInfo(response)); subscribeHolder.putMobilePositionSubscribe(platformId, subscribeInfo, ()->{ platformService.sendNotifyMobilePosition(platformId); }); @@ -163,7 +165,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme if (platform == null){ return; } - SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId); String sn = XmlUtil.getText(rootElement, "SN"); log.info("[回复上级的目录订阅请求]: {}/{}", platformId, deviceId); @@ -176,18 +177,25 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme .append("OK\r\n") .append("\r\n"); - if (subscribeInfo.getExpires() > 0) { - subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo); - }else if (subscribeInfo.getExpires() == 0) { - subscribeHolder.removeCatalogSubscribe(platformId); - } + try { + int expires = request.getExpires().getExpires(); Platform parentPlatform = platformService.queryPlatformByServerGBId(platformId); - SIPResponse response = responseXmlAck(request, resultXml.toString(), parentPlatform, subscribeInfo.getExpires()); + SIPResponse response = responseXmlAck(request, resultXml.toString(), parentPlatform, expires); + + SubscribeInfo subscribeInfo = SubscribeInfo.getInstance(response, platformId, expires, + (EventHeader)request.getHeader(EventHeader.NAME)); + + if (subscribeInfo.getExpires() > 0) { + subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo); + }else if (subscribeInfo.getExpires() == 0) { + subscribeHolder.removeCatalogSubscribe(platformId); + } + if (subscribeInfo.getExpires() == 0) { subscribeHolder.removeCatalogSubscribe(platformId); }else { - subscribeInfo.setResponse(response); + subscribeInfo.setTransactionInfo(new SipTransactionInfo(response)); subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo); } } catch (SipException | InvalidArgumentException | ParseException e) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java index 462751775..cdaea441d 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java @@ -97,10 +97,6 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp } Device device = sipMsgInfo.getDevice(); SIPRequest request = (SIPRequest) evt.getRequest(); -// if (!ObjectUtils.isEmpty(device.getKeepaliveTime()) && DateUtil.getDifferenceForNow(device.getKeepaliveTime()) <= 3000L) { -// log.info("[收到心跳] 心跳发送过于频繁,已忽略 device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId()); -// return; -// } RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress()); if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) { @@ -109,12 +105,6 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort()))); device.setIp(remoteAddressInfo.getIp()); device.setLocalIp(request.getLocalAddress().getHostAddress()); - // 设备地址变化会引起目录订阅任务失效,需要重新添加 - if (device.getSubscribeCycleForCatalog() > 0) { - deviceService.removeCatalogSubscribe(device, result -> { - deviceService.addCatalogSubscribe(device); - }); - } } device.setKeepaliveTime(DateUtil.getNow()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java index 3564e86c5..1f73370b1 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java @@ -1,14 +1,14 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; import com.genersoft.iot.vmp.gb28181.bean.Platform; -import com.genersoft.iot.vmp.gb28181.bean.PlatformCatch; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; +import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; -import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -21,10 +21,10 @@ import javax.sip.header.WWWAuthenticateHeader; import javax.sip.message.Response; import java.text.ParseException; -/** +/** * @description:Register响应处理器 * @author: swwheihei - * @date: 2020年5月3日 下午5:32:23 + * @date: 2020年5月3日 下午5:32:23 */ @Slf4j @Component @@ -44,6 +44,9 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract { @Autowired private IPlatformService platformService; + @Autowired + private SipSubscribe sipSubscribe; + @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 @@ -59,23 +62,19 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract { public void process(ResponseEvent evt) { SIPResponse response = (SIPResponse)evt.getResponse(); String callId = response.getCallIdHeader().getCallId(); - PlatformRegisterInfo platformRegisterInfo = redisCatchStorage.queryPlatformRegisterInfo(callId); - if (platformRegisterInfo == null) { - log.info(String.format("[国标级联]未找到callId: %s 的注册/注销平台id", callId )); + long seqNumber = response.getCSeqHeader().getSeqNumber(); + SipEvent subscribe = sipSubscribe.getSubscribe(callId + seqNumber); + if (subscribe == null || subscribe.getSipTransactionInfo() == null || subscribe.getSipTransactionInfo().getUser() == null) { return; } - PlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformRegisterInfo.getPlatformId()); - if (parentPlatformCatch == null) { - log.warn(String.format("[国标级联]收到注册/注销%S请求,平台:%s,但是平台缓存信息未查询到!!!", response.getStatusCode(),platformRegisterInfo.getPlatformId())); - return; - } + String action = subscribe.getSipTransactionInfo().getExpires() > 0 ? "注册" : "注销"; + String platFormServerGbId = subscribe.getSipTransactionInfo().getUser(); - String action = platformRegisterInfo.isRegister() ? "注册" : "注销"; - log.info(String.format("[国标级联]%s %S响应,%s ", action, response.getStatusCode(), platformRegisterInfo.getPlatformId() )); - Platform parentPlatform = parentPlatformCatch.getPlatform(); - if (parentPlatform == null) { - log.warn(String.format("[国标级联]收到 %s %s的%S请求, 但是平台信息未查询到!!!", platformRegisterInfo.getPlatformId(), action, response.getStatusCode())); + log.info("[国标级联]{} {}响应 {} ", action, response.getStatusCode(), platFormServerGbId); + Platform platform = platformService.queryPlatformByServerGBId(platFormServerGbId); + if (platform == null) { + log.warn("[国标级联]收到 来自{}的 {} 回复 {}, 但是平台信息未查询到!!!", platFormServerGbId, action, response.getStatusCode()); return; } @@ -83,21 +82,17 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract { WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME); SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response); try { - sipCommanderForPlatform.register(parentPlatform, sipTransactionInfo, www, null, null, platformRegisterInfo.isRegister()); + sipCommanderForPlatform.register(platform, sipTransactionInfo, www, null, null, subscribe.getSipTransactionInfo().getExpires() > 0); } catch (SipException | InvalidArgumentException | ParseException e) { log.error("[命令发送失败] 国标级联 再次注册: {}", e.getMessage()); } }else if (response.getStatusCode() == Response.OK){ - - if (platformRegisterInfo.isRegister()) { + if (subscribe.getSipTransactionInfo().getExpires() > 0) { SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response); - platformService.online(parentPlatform, sipTransactionInfo); + platformService.online(platform, sipTransactionInfo); }else { - platformService.offline(parentPlatform, true); + platformService.offline(platform); } - - // 注册/注销成功移除缓存的信息 - redisCatchStorage.delPlatformRegisterInfo(callId); } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java index 174fa6b67..db1035e3e 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/IRedisRpcPlayService.java @@ -26,7 +26,7 @@ public interface IRedisRpcPlayService { String frontEndCommand(String serverId, Integer channelId, int cmdCode, int parameter1, int parameter2, int combindCode2); - void playPush(Integer id, ErrorCallback callback); + void playPush(String serverId, Integer id, ErrorCallback callback); StreamInfo playProxy(String serverId, int id); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java index 886cd88eb..f524a9218 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; +import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; 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; @@ -55,6 +56,9 @@ public class RedisAlarmMsgListener implements MessageListener { @Autowired private IPlatformService platformService; + @Autowired + private IPlatformChannelService platformChannelService; + private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Autowired @@ -89,11 +93,11 @@ public class RedisAlarmMsgListener implements MessageListener { log.warn("[REDIS的ALARM通知]消息解析失败"); continue; } - String gbId = alarmChannelMessage.getGbId(); + String chanelId = alarmChannelMessage.getGbId(); DeviceAlarm deviceAlarm = new DeviceAlarm(); deviceAlarm.setCreateTime(DateUtil.getNow()); - deviceAlarm.setChannelId(gbId); + deviceAlarm.setChannelId(chanelId); deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription()); deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn()); deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType()); @@ -102,7 +106,7 @@ public class RedisAlarmMsgListener implements MessageListener { deviceAlarm.setLongitude(0); deviceAlarm.setLatitude(0); - if (ObjectUtils.isEmpty(gbId)) { + if (ObjectUtils.isEmpty(chanelId)) { if (userSetting.getSendToPlatformsWhenIdLost()) { // 发送给所有的上级 List parentPlatforms = platformService.queryEnablePlatformList(userSetting.getServerId()); @@ -129,7 +133,6 @@ public class RedisAlarmMsgListener implements MessageListener { } } } - } // 获取开启了消息推送的设备和平台 List devices = channelService.queryDeviceWithAsMessageChannel(); @@ -143,24 +146,28 @@ public class RedisAlarmMsgListener implements MessageListener { } } } - } else { - Device device = deviceService.getDeviceByDeviceId(gbId); - Platform platform = platformService.queryPlatformByServerGBId(gbId); - if (device != null && platform == null) { + // 获取该通道ID是属于设备还是对应的上级平台 + Device device = deviceService.getDeviceBySourceChannelDeviceId(chanelId); + List platforms = platformChannelService.queryByPlatformBySharChannelId(chanelId); + if (device != null && device.getServerId().equals(userSetting.getServerId()) && (platforms == null || platforms.isEmpty())) { try { commander.sendAlarmMessage(device, deviceAlarm); } catch (InvalidArgumentException | SipException | ParseException e) { log.error("[命令发送失败] 发送报警: {}", e.getMessage()); } - } else if (device == null && platform != null) { - try { - commanderForPlatform.sendAlarmMessage(platform, deviceAlarm); - } catch (InvalidArgumentException | SipException | ParseException e) { - log.error("[命令发送失败] 发送报警: {}", e.getMessage()); + } else if (device == null && (platforms != null && !platforms.isEmpty() )) { + for (Platform platform : platforms) { + if (platform.getServerId().equals(userSetting.getServerId())) { + try { + commanderForPlatform.sendAlarmMessage(platform, deviceAlarm); + } catch (InvalidArgumentException | SipException | ParseException e) { + log.error("[命令发送失败] 发送报警: {}", e.getMessage()); + } + } } } else { - log.warn("无法确定" + gbId + "是平台还是设备"); + log.warn("[REDIS的ALARM通知] 未查询到" + chanelId + "所属的平台或设备"); } } } catch (Exception e) { diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java index d5d2b68c0..e3d4e9670 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java @@ -48,7 +48,7 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic @Override public void onMessage(Message message, byte[] bytes) { - log.info("[REDIS: 流设备状态变化]: {}", new String(message.getBody())); + log.info("[REDIS: 推流设备状态变化]: {}", new String(message.getBody())); taskQueue.offer(message); } @@ -84,11 +84,13 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic if (streamStatusMessage.getOfflineStreams() != null && !streamStatusMessage.getOfflineStreams().isEmpty()) { // 更新部分设备离线 + log.info("[REDIS: 推流设备状态变化] 更新部分设备离线: {}个", streamStatusMessage.getOfflineStreams().size()); streamPushService.offline(streamStatusMessage.getOfflineStreams()); } if (streamStatusMessage.getOnlineStreams() != null && !streamStatusMessage.getOnlineStreams().isEmpty()) { // 更新部分设备上线 + log.info("[REDIS: 推流设备状态变化] 更新部分设备上线: {}个", streamStatusMessage.getOnlineStreams().size()); streamPushService.online(streamStatusMessage.getOnlineStreams()); } } catch (Exception e) { diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcPlatformController.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcPlatformController.java index c5a9f4604..cf037934d 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcPlatformController.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcPlatformController.java @@ -9,7 +9,6 @@ import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse; import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; import com.genersoft.iot.vmp.gb28181.bean.Platform; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService; import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController; @@ -72,6 +71,7 @@ public class RedisRpcPlatformController extends RpcController { public RedisRpcResponse catalogEventPublish(RedisRpcRequest request) { JSONObject jsonObject = JSONObject.parseObject(request.getParam().toString()); Platform platform = jsonObject.getObject("platform", Platform.class); + List channels = jsonObject.getJSONArray("channels").toJavaList(CommonGBChannel.class); String type = jsonObject.getString("type"); eventPublisher.catalogEventPublish(platform, channels, type, false); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamPushController.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamPushController.java index d9fd90ed2..53212b178 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamPushController.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/control/RedisRpcStreamPushController.java @@ -181,7 +181,8 @@ public class RedisRpcStreamPushController extends RpcController { */ @RedisRpcMapping("play") public RedisRpcResponse play(RedisRpcRequest request) { - int id = Integer.parseInt(request.getParam().toString()); + JSONObject paramJson = JSONObject.parseObject(request.getParam().toString()); + int id = paramJson.getInteger("id"); RedisRpcResponse response = request.getResponse(); if (id <= 0) { response.setStatusCode(ErrorCode.ERROR400.getCode()); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java index 4404a7205..5f369efaf 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcPlayServiceImpl.java @@ -193,8 +193,11 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService { } @Override - public void playPush(Integer id, ErrorCallback callback) { - RedisRpcRequest request = buildRequest("streamPush/play", id); + public void playPush(String serverId, Integer id, ErrorCallback callback) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", id); + RedisRpcRequest request = buildRequest("streamPush/play", jsonObject); + request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS); if (response == null) { callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java index b2d65963c..531ef6aaf 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/service/RedisRpcServiceImpl.java @@ -231,6 +231,9 @@ public class RedisRpcServiceImpl implements IRedisRpcService { RedisRpcRequest request = buildRequest("platform/update", platform); request.setToId(serverId); RedisRpcResponse response = redisRpcConfig.request(request, 40, TimeUnit.MILLISECONDS); + if(response == null) { + return false; + } return Boolean.parseBoolean(response.getBody().toString()); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java index 1f05a7b48..106cf3aea 100755 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -9,7 +9,6 @@ import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; -import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; import java.util.List; import java.util.Map; @@ -23,23 +22,13 @@ public interface IRedisCatchStorage { */ Long getCSEQ(); - void updatePlatformCatchInfo(PlatformCatch parentPlatformCatch); - - PlatformCatch queryPlatformCatchInfo(String platformGbId); - - void delPlatformCatchInfo(String platformGbId); - - void updatePlatformRegisterInfo(String callId, PlatformRegisterInfo platformRegisterInfo); - - PlatformRegisterInfo queryPlatformRegisterInfo(String callId); - - void delPlatformRegisterInfo(String callId); - /** * 在redis添加wvp的信息 */ void updateWVPInfo(ServerInfo serverInfo, int time); + void removeOfflineWVPInfo(String serverId); + /** * 发送推流生成与推流消失消息 * @param jsonObject 消息内容 diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java index 634f6cc22..a5d53f55b 100755 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -15,7 +15,6 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.JsonUtil; import com.genersoft.iot.vmp.utils.SystemInfoUtils; @@ -73,41 +72,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { redisTemplate.opsForValue().set(key, 1); } - @Override - public void updatePlatformCatchInfo(PlatformCatch parentPlatformCatch) { - String key = VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + parentPlatformCatch.getId(); - redisTemplate.opsForValue().set(key, parentPlatformCatch); - } - - @Override - public PlatformCatch queryPlatformCatchInfo(String platformGbId) { - return (PlatformCatch)redisTemplate.opsForValue().get(VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + platformGbId); - } - - @Override - public void delPlatformCatchInfo(String platformGbId) { - redisTemplate.delete(VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + platformGbId); - } - - @Override - public void updatePlatformRegisterInfo(String callId, PlatformRegisterInfo platformRegisterInfo) { - String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId; - Duration duration = Duration.ofSeconds(30L); - redisTemplate.opsForValue().set(key, platformRegisterInfo, duration); - } - - - @Override - public PlatformRegisterInfo queryPlatformRegisterInfo(String callId) { - return (PlatformRegisterInfo)redisTemplate.opsForValue().get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId); - } - - @Override - public void delPlatformRegisterInfo(String callId) { - redisTemplate.delete(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId); - } - - @Override public void updateWVPInfo(ServerInfo serverInfo, int time) { @@ -120,6 +84,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { redisTemplate.opsForZSet().add(setKey, userSetting.getServerId(), System.currentTimeMillis()); } + @Override + public void removeOfflineWVPInfo(String serverId) { + String setKey = VideoManagerConstants.WVP_SERVER_LIST; + // 首次设置就设置为0, 后续值越小说明越是最近启动的 + redisTemplate.opsForZSet().remove(setKey, serverId); + } + @Override public void sendStreamChangeMsg(String type, JSONObject jsonObject) { String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type; diff --git a/src/main/java/com/genersoft/iot/vmp/streamProxy/controller/StreamProxyController.java b/src/main/java/com/genersoft/iot/vmp/streamProxy/controller/StreamProxyController.java index be4d91b33..7d651219e 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamProxy/controller/StreamProxyController.java +++ b/src/main/java/com/genersoft/iot/vmp/streamProxy/controller/StreamProxyController.java @@ -23,6 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Map; @SuppressWarnings("rawtypes") @@ -190,12 +193,23 @@ public class StreamProxyController { @ResponseBody @Operation(summary = "启用代理", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "id", description = "代理Id", required = true) - public StreamContent start(int id){ + public StreamContent start(HttpServletRequest request, int id){ log.info("播放代理: {}", id); StreamInfo streamInfo = streamProxyPlayService.start(id, null, null); if (streamInfo == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg()); }else { + if (userSetting.getUseSourceIpAsStreamIp()) { + streamInfo=streamInfo.clone();//深拷贝 + String host; + try { + URL url=new URL(request.getRequestURL().toString()); + host=url.getHost(); + } catch (MalformedURLException e) { + host=request.getLocalAddr(); + } + streamInfo.changeStreamIp(host); + } return new StreamContent(streamInfo); } } diff --git a/src/main/java/com/genersoft/iot/vmp/streamPush/bean/StreamPush.java b/src/main/java/com/genersoft/iot/vmp/streamPush/bean/StreamPush.java index 5b282c2d7..7ae700471 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamPush/bean/StreamPush.java +++ b/src/main/java/com/genersoft/iot/vmp/streamPush/bean/StreamPush.java @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.utils.DateUtil; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.jetbrains.annotations.NotNull; import org.springframework.util.ObjectUtils; @@ -15,6 +16,7 @@ import org.springframework.util.ObjectUtils; @Data @Schema(description = "推流信息") @EqualsAndHashCode(callSuper = true) +@NoArgsConstructor public class StreamPush extends CommonGBChannel implements Comparable{ /** @@ -105,6 +107,7 @@ public class StreamPush extends CommonGBChannel implements Comparable> start(Integer id){ + public DeferredResult> start(HttpServletRequest request, Integer id){ Assert.notNull(id, "推流ID不可为NULL"); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); result.onTimeout(()->{ @@ -254,6 +257,17 @@ public class StreamPushController { }); streamPushPlayService.start(id, (code, msg, streamInfo) -> { if (code == 0 && streamInfo != null) { + if (userSetting.getUseSourceIpAsStreamIp()) { + streamInfo=streamInfo.clone();//深拷贝 + String host; + try { + URL url=new URL(request.getRequestURL().toString()); + host=url.getHost(); + } catch (MalformedURLException e) { + host=request.getLocalAddr(); + } + streamInfo.changeStreamIp(host); + } WVPResult success = WVPResult.success(new StreamContent(streamInfo)); result.setResult(success); } diff --git a/src/main/java/com/genersoft/iot/vmp/streamPush/dao/StreamPushMapper.java b/src/main/java/com/genersoft/iot/vmp/streamPush/dao/StreamPushMapper.java index c77fa4194..2db9b4ef5 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamPush/dao/StreamPushMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/streamPush/dao/StreamPushMapper.java @@ -91,7 +91,7 @@ public interface StreamPushMapper { "(#{item.app}, #{item.stream}) " + "" + ")") - List getListFromRedis(List offlineStreams); + List getListInList(List offlineStreams); @Select("SELECT CONCAT(app,stream) from wvp_stream_push") diff --git a/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushPlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushPlayServiceImpl.java index 6f44f36e8..d8adfa3f0 100644 --- a/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushPlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushPlayServiceImpl.java @@ -39,7 +39,7 @@ public class StreamPushPlayServiceImpl implements IStreamPushPlayService { @Autowired private UserSetting userSetting; - + @Autowired private DynamicTask dynamicTask; @@ -57,27 +57,28 @@ public class StreamPushPlayServiceImpl implements IStreamPushPlayService { StreamPush streamPush = streamPushMapper.queryOne(id); Assert.notNull(streamPush, "推流信息未找到"); - if (!userSetting.getServerId().equals(streamPush.getServerId())) { - redisRpcPlayService.playPush(id, callback); + if (streamPush.isPushing() && !userSetting.getServerId().equals(streamPush.getServerId())) { + redisRpcPlayService.playPush(streamPush.getServerId(), id, callback); return; } MediaServer mediaServer = mediaServerService.getOne(streamPush.getMediaServerId()); - Assert.notNull(mediaServer, "节点" + streamPush.getMediaServerId() + "未找到"); - MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamPush.getApp(), streamPush.getStream()); - if (mediaInfo != null) { - String callId = null; - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(streamPush.getApp(), streamPush.getStream()); - if (streamAuthorityInfo != null) { - callId = streamAuthorityInfo.getCallId(); + if (mediaServer != null) { + MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamPush.getApp(), streamPush.getStream()); + if (mediaInfo != null) { + String callId = null; + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(streamPush.getApp(), streamPush.getStream()); + if (streamAuthorityInfo != null) { + callId = streamAuthorityInfo.getCallId(); + } + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), mediaServerService.getStreamInfoByAppAndStream(mediaServer, + streamPush.getApp(), streamPush.getStream(), mediaInfo, callId)); + if (!streamPush.isPushing()) { + streamPush.setPushing(true); + streamPushMapper.update(streamPush); + } + return; } - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), mediaServerService.getStreamInfoByAppAndStream(mediaServer, - streamPush.getApp(), streamPush.getStream(), mediaInfo, callId)); - if (!streamPush.isPushing()) { - streamPush.setPushing(true); - streamPushMapper.update(streamPush); - } - return; } Assert.isTrue(streamPush.isStartOfflinePush(), "通道未推流"); // 发送redis消息以使设备上线,流上线后被 diff --git a/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushServiceImpl.java index 4c8b8552b..1090d3c58 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/streamPush/service/impl/StreamPushServiceImpl.java @@ -458,16 +458,27 @@ public class StreamPushServiceImpl implements IStreamPushService { @Override public void offline(List offlineStreams) { // 更新部分设备离线 - List streamPushList = streamPushMapper.getListFromRedis(offlineStreams); + List streamPushList = streamPushMapper.getListInList(offlineStreams); + if (streamPushList.isEmpty()) { + log.info("[推流设备] 设备离线操作未发现可操作数据。"); + return; + } List commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList); gbChannelService.offline(commonGBChannelList); } @Override public void online(List onlineStreams) { + if (onlineStreams.isEmpty()) { + log.info("[设备上线] 推流设备列表为空"); + return; + } // 更新部分设备上线streamPushService - List streamPushList = streamPushMapper.getListFromRedis(onlineStreams); + List streamPushList = streamPushMapper.getListInList(onlineStreams); if (streamPushList.isEmpty()) { + for (StreamPushItemFromRedis onlineStream : onlineStreams) { + log.info("[设备上线] 未查询到这些通道: {}/{}", onlineStream.getApp(), onlineStream.getStream()); + } return; } List commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList); diff --git a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java index 1bc6311be..cd72be91a 100755 --- a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java @@ -208,6 +208,14 @@ public class DateUtil { } Instant startInstant = Instant.from(formatter.parse(startTime)); Instant endInstant = Instant.from(formatter.parse(endTime)); - return ChronoUnit.MILLIS.between(endInstant, startInstant); + return ChronoUnit.MILLIS.between(startInstant, endInstant); } + + + + public static void main(String[] args) { + long difference = getDifference("2025-05-21 13:00:00", "2025-05-21 13:30:00")/1000; + System.out.println(difference); + } + } diff --git a/web/.eslintrc.js b/web/.eslintrc.js index c97750547..446d8febc 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -1,198 +1,48 @@ module.exports = { root: true, - parserOptions: { - parser: 'babel-eslint', - sourceType: 'module' - }, env: { - browser: true, node: true, - es6: true, + browser: true, + }, + extends: ["plugin:vue/essential", "eslint:recommended"], + parserOptions: { + parser: "babel-eslint", }, - extends: ['plugin:vue/recommended', 'eslint:recommended'], - - // add your custom rules here - //it is base on https://github.com/vuejs/eslint-config-vue rules: { - "vue/max-attributes-per-line": [2, { - "singleline": 10, - "multiline": { - "max": 1, - "allowFirstLine": false - } - }], - "vue/singleline-html-element-content-newline": "off", - "vue/multiline-html-element-content-newline":"off", - "vue/name-property-casing": ["error", "PascalCase"], - "vue/no-v-html": "off", - 'accessor-pairs': 2, - 'arrow-spacing': [2, { - 'before': true, - 'after': true - }], - 'block-spacing': [2, 'always'], - 'brace-style': [2, '1tbs', { - 'allowSingleLine': true - }], - 'camelcase': [0, { - 'properties': 'always' - }], - 'comma-dangle': [2, 'never'], - 'comma-spacing': [2, { - 'before': false, - 'after': true - }], - 'comma-style': [2, 'last'], - 'constructor-super': 2, - 'curly': [2, 'multi-line'], - 'dot-location': [2, 'property'], - 'eol-last': 2, - 'eqeqeq': ["error", "always", {"null": "ignore"}], - 'generator-star-spacing': [2, { - 'before': true, - 'after': true - }], - 'handle-callback-err': [2, '^(err|error)$'], - 'indent': [2, 2, { - 'SwitchCase': 1 - }], - 'jsx-quotes': [2, 'prefer-single'], - 'key-spacing': [2, { - 'beforeColon': false, - 'afterColon': true - }], - 'keyword-spacing': [2, { - 'before': true, - 'after': true - }], - 'new-cap': [2, { - 'newIsCap': true, - 'capIsNew': false - }], - 'new-parens': 2, - 'no-array-constructor': 2, - 'no-caller': 2, - 'no-console': 'off', - 'no-class-assign': 2, - 'no-cond-assign': 2, - 'no-const-assign': 2, - 'no-control-regex': 0, - 'no-delete-var': 2, - 'no-dupe-args': 2, - 'no-dupe-class-members': 2, - 'no-dupe-keys': 2, - 'no-duplicate-case': 2, - 'no-empty-character-class': 2, - 'no-empty-pattern': 2, - 'no-eval': 2, - 'no-ex-assign': 2, - 'no-extend-native': 2, - 'no-extra-bind': 2, - 'no-extra-boolean-cast': 2, - 'no-extra-parens': [2, 'functions'], - 'no-fallthrough': 2, - 'no-floating-decimal': 2, - 'no-func-assign': 2, - 'no-implied-eval': 2, - 'no-inner-declarations': [2, 'functions'], - 'no-invalid-regexp': 2, - 'no-irregular-whitespace': 2, - 'no-iterator': 2, - 'no-label-var': 2, - 'no-labels': [2, { - 'allowLoop': false, - 'allowSwitch': false - }], - 'no-lone-blocks': 2, - 'no-mixed-spaces-and-tabs': 2, - 'no-multi-spaces': 2, - 'no-multi-str': 2, - 'no-multiple-empty-lines': [2, { - 'max': 1 - }], - 'no-native-reassign': 2, - 'no-negated-in-lhs': 2, - 'no-new-object': 2, - 'no-new-require': 2, - 'no-new-symbol': 2, - 'no-new-wrappers': 2, - 'no-obj-calls': 2, - 'no-octal': 2, - 'no-octal-escape': 2, - 'no-path-concat': 2, - 'no-proto': 2, - 'no-redeclare': 2, - 'no-regex-spaces': 2, - 'no-return-assign': [2, 'except-parens'], - 'no-self-assign': 2, - 'no-self-compare': 2, - 'no-sequences': 2, - 'no-shadow-restricted-names': 2, - 'no-spaced-func': 2, - 'no-sparse-arrays': 2, - 'no-this-before-super': 2, - 'no-throw-literal': 2, - 'no-trailing-spaces': 2, - 'no-undef': 2, - 'no-undef-init': 2, - 'no-unexpected-multiline': 2, - 'no-unmodified-loop-condition': 2, - 'no-unneeded-ternary': [2, { - 'defaultAssignment': false - }], - 'no-unreachable': 2, - 'no-unsafe-finally': 2, - 'no-unused-vars': [2, { - 'vars': 'all', - 'args': 'none' - }], - 'no-useless-call': 2, - 'no-useless-computed-key': 2, - 'no-useless-constructor': 2, - 'no-useless-escape': 0, - 'no-whitespace-before-property': 2, - 'no-with': 2, - 'one-var': [2, { - 'initialized': 'never' - }], - 'operator-linebreak': [2, 'after', { - 'overrides': { - '?': 'before', - ':': 'before' - } - }], - 'padded-blocks': [2, 'never'], - 'quotes': [2, 'single', { - 'avoidEscape': true, - 'allowTemplateLiterals': true - }], - 'semi': [2, 'never'], - 'semi-spacing': [2, { - 'before': false, - 'after': true - }], - 'space-before-blocks': [2, 'always'], - 'space-before-function-paren': [2, 'never'], - 'space-in-parens': [2, 'never'], - 'space-infix-ops': 2, - 'space-unary-ops': [2, { - 'words': true, - 'nonwords': false - }], - 'spaced-comment': [2, 'always', { - 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] - }], - 'template-curly-spacing': [2, 'never'], - 'use-isnan': 2, - 'valid-typeof': 2, - 'wrap-iife': [2, 'any'], - 'yield-star-spacing': [2, 'both'], - 'yoda': [2, 'never'], - 'prefer-const': 2, - 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, - 'object-curly-spacing': [2, 'always', { - objectsInObjects: false - }], - 'array-bracket-spacing': [2, 'never'] - } + // Disable or downgrade problematic rules + "vue/require-prop-types": "off", + "vue/require-default-prop": "off", + "vue/no-unused-vars": "warn", + "no-unused-vars": "warn", + "no-undef": "warn", + eqeqeq: "warn", + "no-return-assign": "warn", + "new-cap": "warn", + "vue/html-self-closing": "off", + "vue/html-closing-bracket-spacing": "off", + "vue/this-in-template": "off", + "vue/require-v-for-key": "warn", + "vue/valid-v-model": "warn", + "vue/attributes-order": "off", + "no-multiple-empty-lines": "warn", + + // Style rules - make them warnings instead of errors + quotes: ["warn", "single"], + "comma-dangle": ["warn", "never"], + "space-in-parens": "warn", + "comma-spacing": "warn", + "object-curly-spacing": "warn", + "arrow-spacing": "warn", + semi: ["warn", "never"], + "no-multi-spaces": "warn", + + // Turn off console warnings for development + "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", + }, + globals: { + // Define global variables to prevent 'undefined' errors + ZLMRTCClient: "readonly", + jessibuca: "readonly", + }, } diff --git a/web/src/views/common/DeviceTree.vue b/web/src/views/common/DeviceTree.vue index e7b8c46b5..edf71ff92 100755 --- a/web/src/views/common/DeviceTree.vue +++ b/web/src/views/common/DeviceTree.vue @@ -1,8 +1,8 @@ @@ -26,7 +44,24 @@ import GroupTree from './GroupTree.vue' export default { name: 'DeviceTree', components: { GroupTree, RegionTree }, - props: ['device', 'onlyCatalog', 'clickEvent', 'contextMenuEvent'], + props: { + device: { + type: Object, + default: () => ({}) + }, + onlyCatalog: { + type: Boolean, + default: false + }, + clickEvent: { + type: Function, + default: null + }, + contextMenuEvent: { + type: Function, + default: null + } + }, data() { return { showRegion: true, @@ -37,15 +72,67 @@ export default { } } }, - destroyed() { - // if (this.jessibuca) { - // this.jessibuca.destroy(); - // } - // this.playing = false; - // this.loaded = false; - // this.performance = ""; + mounted() { + // Apply fix for Element UI tree scrollbars after component is mounted + this.$nextTick(() => { + this.fixTreeScrollbars() + this.adjustTreeHeight() + + // Add resize event listener to handle window resizing + window.addEventListener('resize', this.adjustTreeHeight) + }) + }, + updated() { + // Re-apply fix when component updates (e.g., when switching between RegionTree and GroupTree) + this.$nextTick(() => { + this.fixTreeScrollbars() + this.adjustTreeHeight() + }) + }, + beforeDestroy() { + // Remove event listener when component is destroyed + window.removeEventListener('resize', this.adjustTreeHeight) }, methods: { + adjustTreeHeight() { + // Get the container height + const containerHeight = this.$el.clientHeight + + // Get the header height + const headerHeight = this.$el.querySelector('.device-tree-header').clientHeight + + // Calculate available height for tree + const availableHeight = containerHeight - headerHeight - 30 // 30px for padding + + // Set the tree content height + const treeContent = this.$el.querySelector('.tree-content') + if (treeContent) { + treeContent.style.height = `${availableHeight}px` + } + + // Ensure tree components adapt to the available height + const treeComponents = this.$el.querySelectorAll('.el-tree') + treeComponents.forEach(tree => { + tree.style.height = '100%' + tree.style.maxHeight = '100%' + }) + }, + fixTreeScrollbars() { + // Find all el-tree elements within this component and fix their scrolling behavior + const trees = this.$el.querySelectorAll('.el-tree') + trees.forEach(tree => { + tree.style.overflow = 'visible' + tree.style.width = '100%' + + // Also fix any scrollable containers within the tree + const scrollContainers = tree.querySelectorAll('[style*="overflow"]') + scrollContainers.forEach(container => { + if (container.style.overflow === 'auto' || container.style.overflow === 'scroll') { + container.style.overflow = 'visible' + } + }) + }) + }, handleClick: function(tab, event) { }, treeNodeClickEvent: function(data) { @@ -62,13 +149,130 @@ export default { diff --git a/web/src/views/live/index.vue b/web/src/views/live/index.vue index 60ddff6dd..e027ae3d5 100755 --- a/web/src/views/live/index.vue +++ b/web/src/views/live/index.vue @@ -1,25 +1,26 @@