Compare commits

...

15 Commits

Author SHA1 Message Date
阿斌
523e9fa2d8
Pre Merge pull request !36 from 阿斌/N/A 2025-05-22 13:15:22 +00:00
648540858
a1a5c53fad 合并国标级联状态重构 2025-05-22 21:14:50 +08:00
648540858
7c7ef34045 Merge branch '2.7.3'
# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java
2025-05-22 21:09:22 +08:00
lin
243f7f62b7 国标级联调整心跳逻辑 2025-05-22 17:40:10 +08:00
lin
b0b5a0f5e0 国标级联调整注册逻辑 2025-05-22 15:17:13 +08:00
648540858
706806a138
Merge pull request #1857 from sxh-nice/master
修复开始时间和结束时间写反问题
2025-05-22 14:58:58 +08:00
yosixiaohu
83f603238a 修复开始时间和结束时间写反问题 2025-05-22 13:50:06 +08:00
648540858
7bb0cc19f4 重构平台保活逻辑-启动对未离线的平台注销 2025-05-22 07:09:20 +08:00
648540858
1d172cb387 重构平台保活逻辑-启动对未离线的平台注销 2025-05-22 07:02:10 +08:00
648540858
bcf08d27fa 重构平台保活逻辑 2025-05-21 23:17:52 +08:00
lin
29ac4850f4 去除移除订阅时设置订阅周期 2025-05-21 18:06:07 +08:00
lin
dd17462b68 重构设备订阅保活和更新机制 2025-05-21 14:57:10 +08:00
lin
a1ba811962 调整推流获取逻辑 2025-05-20 17:59:16 +08:00
lin
bfc063e4f7 修复通过redis唤醒推流 2025-05-19 16:52:12 +08:00
阿斌
da98101aac
update src/main/resources/civilCode.csv.
行政规划错误。江苏南通海门市,修改为海门区,浙江杭州删除下城区、江干区,新增钱塘区,临平区

Signed-off-by: 阿斌 <38912748@qq.com>
2024-12-15 08:58:42 +00:00
51 changed files with 1449 additions and 1132 deletions

View File

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

View File

@ -22,10 +22,6 @@ public class VideoManagerConstants {
public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO"; 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_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_CALLID = "VMP_SEND_RTP_INFO:CALL_ID:";
public static final String SEND_RTP_INFO_STREAM = "VMP_SEND_RTP_INFO:STREAM:"; public static final String SEND_RTP_INFO_STREAM = "VMP_SEND_RTP_INFO:STREAM:";

View File

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

View File

@ -38,7 +38,6 @@ public class RedisMsgListenConfig {
@Autowired @Autowired
private RedisCloseStreamMsgListener redisCloseStreamMsgListener; private RedisCloseStreamMsgListener redisCloseStreamMsgListener;
@Autowired @Autowired
private RedisRpcConfig redisRpcConfig; private RedisRpcConfig redisRpcConfig;

View File

@ -1,18 +1,23 @@
package com.genersoft.iot.vmp.gb28181.bean; package com.genersoft.iot.vmp.gb28181.bean;
import lombok.Data;
/** /**
* 通过redis分发报警消息 * 通过redis分发报警消息
*/ */
@Data
public class AlarmChannelMessage { public class AlarmChannelMessage {
/** /**
* 国标编号 * 通道国标编号
*/ */
private String gbId; private String gbId;
/** /**
* 报警编号 * 报警编号
*/ */
private int alarmSn; private int alarmSn;
/** /**
* 告警类型 * 告警类型
*/ */
@ -22,36 +27,4 @@ public class AlarmChannelMessage {
* 报警描述 * 报警描述
*/ */
private String alarmDescription; 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;
}
} }

View File

@ -0,0 +1,5 @@
package com.genersoft.iot.vmp.gb28181.bean;
public interface PlatformKeepaliveCallback {
public void run(String platformServerGbId, int failCount);
}

View File

@ -1,13 +1,17 @@
package com.genersoft.iot.vmp.gb28181.bean; package com.genersoft.iot.vmp.gb28181.bean;
import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.message.SIPResponse;
import lombok.Data;
@Data
public class SipTransactionInfo { public class SipTransactionInfo {
private String callId; private String callId;
private String fromTag; private String fromTag;
private String toTag; private String toTag;
private String viaBranch; private String viaBranch;
private int expires;
private String user;
// 自己是否媒体流发送者 // 自己是否媒体流发送者
private boolean asSender; private boolean asSender;
@ -31,43 +35,4 @@ public class SipTransactionInfo {
public 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;
}
} }

View File

@ -1,19 +1,20 @@
package com.genersoft.iot.vmp.gb28181.bean; 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.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting; 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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* @author lin * @author lin
*/ */
@Slf4j
@Component @Component
public class SubscribeHolder { public class SubscribeHolder {
@ -23,107 +24,97 @@ public class SubscribeHolder {
@Autowired @Autowired
private UserSetting userSetting; private UserSetting userSetting;
private final String taskOverduePrefix = "subscribe_overdue_"; @Autowired
private RedisTemplate<Object, Object> redisTemplate;
private static ConcurrentHashMap<String, SubscribeInfo> catalogMap = new ConcurrentHashMap<>();
private static ConcurrentHashMap<String, SubscribeInfo> mobilePositionMap = new ConcurrentHashMap<>();
private final String prefix = "VMP_SUBSCRIBE_OVERDUE";
public void putCatalogSubscribe(String platformId, SubscribeInfo subscribeInfo) { 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) { if (subscribeInfo.getExpires() > 0) {
// 添加订阅到期 Duration duration = Duration.ofSeconds(subscribeInfo.getExpires());
String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; redisTemplate.opsForValue().set(key, subscribeInfo, duration);
// 添加任务处理订阅过期 }else {
dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()), redisTemplate.opsForValue().set(key, subscribeInfo);
subscribeInfo.getExpires() * 1000);
} }
} }
public SubscribeInfo getCatalogSubscribe(String platformId) { public SubscribeInfo getCatalogSubscribe(String platformId) {
if (platformId == null) { String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platformId);
return null; return (SubscribeInfo)redisTemplate.opsForValue().get(key);
}
return catalogMap.get(platformId);
} }
public void removeCatalogSubscribe(String platformId) { public void removeCatalogSubscribe(String platformId) {
String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "catalog", platformId);
catalogMap.remove(platformId); redisTemplate.delete(key);
String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId;
Runnable runnable = dynamicTask.get(taskOverdueKey);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop(null);
}
// 添加任务处理订阅过期
dynamicTask.stop(taskOverdueKey);
} }
public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo, Runnable gpsTask) { public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo, Runnable gpsTask) {
mobilePositionMap.put(platformId, subscribeInfo); log.info("[国标级联] 添加移动位置订阅,平台: {} 有效期: {}s", platformId, subscribeInfo.getExpires());
String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetting.getServerId() + "MobilePosition_" + platformId; String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platformId);
// 添加任务处理GPS定时推送 if (subscribeInfo.getExpires() > 0) {
Duration duration = Duration.ofSeconds(subscribeInfo.getExpires());
redisTemplate.opsForValue().set(key, subscribeInfo, duration);
}else {
redisTemplate.opsForValue().set(key, subscribeInfo);
}
int cycleForCatalog; int cycleForCatalog;
if (subscribeInfo.getGpsInterval() <= 0) { if (subscribeInfo.getGpsInterval() <= 0) {
cycleForCatalog = 5; cycleForCatalog = 5;
}else { }else {
cycleForCatalog = subscribeInfo.getGpsInterval(); 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); 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) { 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) { public void removeMobilePositionSubscribe(String platformId) {
mobilePositionMap.remove(platformId); String key = String.format("%s_%s_%s_%s", prefix, userSetting.getServerId(), "mobilePosition", platformId);
String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetting.getServerId() + "MobilePosition_" + platformId; redisTemplate.delete(key);
// 结束任务处理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);
} }
public List<String> getAllCatalogSubscribePlatform() { public List<String> getAllCatalogSubscribePlatform(List<Platform> platformList) {
List<String> platforms = new ArrayList<>(); if (platformList == null || platformList.isEmpty()) {
if(catalogMap.size() > 0) { return new ArrayList<>();
for (String key : catalogMap.keySet()) { }
platforms.add(catalogMap.get(key).getId()); List<String> 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.getServerId());
} }
} }
return platforms; return result;
} }
public List<String> getAllMobilePositionSubscribePlatform() { public List<String> getAllMobilePositionSubscribePlatform(List<Platform> platformList) {
List<String> platforms = new ArrayList<>(); if (platformList == null || platformList.isEmpty()) {
if(!mobilePositionMap.isEmpty()) { return new ArrayList<>();
for (String key : mobilePositionMap.keySet()) { }
platforms.add(mobilePositionMap.get(key).getId()); List<String> 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.getServerId());
} }
} }
return platforms; return result;
}
public void removeAllSubscribe(String platformId) {
removeMobilePositionSubscribe(platformId);
removeCatalogSubscribe(platformId);
} }
} }

View File

@ -1,36 +1,19 @@
package com.genersoft.iot.vmp.gb28181.bean; package com.genersoft.iot.vmp.gb28181.bean;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.message.SIPResponse;
import lombok.Data; import lombok.Data;
import javax.sip.header.*; import javax.sip.header.EventHeader;
import java.util.UUID; import java.util.UUID;
@Data @Data
public class SubscribeInfo { 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 String id;
private SIPRequest request;
private int expires; private int expires;
private String eventId; private String eventId;
private String eventType; private String eventType;
private SIPResponse response; private SipTransactionInfo transactionInfo;
/** /**
* 以下为可选字段 * 以下为可选字段
@ -55,6 +38,16 @@ public class SubscribeInfo {
private String simulatedCallId; 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){ public static SubscribeInfo buildSimulated(String platFormServerId, String platFormServerIp){
SubscribeInfo subscribeInfo = new SubscribeInfo(); SubscribeInfo subscribeInfo = new SubscribeInfo();
subscribeInfo.setId(platFormServerId); subscribeInfo.setId(platFormServerId);

View File

@ -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.IDeviceChannelService;
import com.genersoft.iot.vmp.gb28181.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; 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.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
@ -40,9 +37,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Tag(name = "国标设备查询", description = "国标设备查询") @Tag(name = "国标设备查询", description = "国标设备查询")
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -144,28 +138,10 @@ public class DeviceQuery {
} }
// 清除redis记录 // 清除redis记录
boolean isSuccess = deviceService.delete(deviceId); deviceService.delete(deviceId);
if (isSuccess) { JSONObject json = new JSONObject();
inviteStreamService.clearInviteInfo(deviceId); json.put("deviceId", deviceId);
// 停止此设备的订阅更新 return json.toString();
Set<String> 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调用失败");
}
} }
@Operation(summary = "分页查询子目录通道", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Operation(summary = "分页查询子目录通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@ -358,28 +334,6 @@ public class DeviceQuery {
return wvpResult; return wvpResult;
} }
@GetMapping("/{deviceId}/subscribe_info")
@Operation(summary = "获取设备的订阅状态", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
public WVPResult<Map<String, Integer>> getSubscribeInfo(@PathVariable String deviceId) {
Set<String> allKeys = dynamicTask.getAllKeys();
Map<String, Integer> 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<Map<String, Integer>> wvpResult = new WVPResult<>();
wvpResult.setCode(0);
wvpResult.setData(dialogStateMap);
return wvpResult;
}
@GetMapping("/snap/{deviceId}/{channelId}") @GetMapping("/snap/{deviceId}/{channelId}")
@Operation(summary = "请求截图") @Operation(summary = "请求截图")
@Parameter(name = "deviceId", description = "设备国标编号", required = true) @Parameter(name = "deviceId", description = "设备国标编号", required = true)

View File

@ -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.service.IDeviceService;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; 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.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.service.IMobilePositionService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.github.pagehelper.util.StringUtil; import com.github.pagehelper.util.StringUtil;
@ -39,7 +39,7 @@ public class MobilePositionController {
private IMobilePositionService mobilePositionService; private IMobilePositionService mobilePositionService;
@Autowired @Autowired
private SIPCommander cmder; private ISIPCommander cmder;
@Autowired @Autowired
private DeferredResultHolder resultHolder; private DeferredResultHolder resultHolder;

View File

@ -123,23 +123,27 @@ public interface DeviceMapper {
@Update(value = {" <script>" + @Update(value = {" <script>" +
"UPDATE wvp_device " + "UPDATE wvp_device " +
"SET update_time=#{updateTime}" + "SET update_time=#{updateTime}" +
"<if test=\"name != null\">, name=#{name}</if>" + ", name=#{name}" +
"<if test=\"manufacturer != null\">, manufacturer=#{manufacturer}</if>" + ", manufacturer=#{manufacturer}" +
"<if test=\"model != null\">, model=#{model}</if>" + ", model=#{model}" +
"<if test=\"firmware != null\">, firmware=#{firmware}</if>" + ", firmware=#{firmware}" +
"<if test=\"transport != null\">, transport=#{transport}</if>" + ", transport=#{transport}" +
"<if test=\"ip != null\">, ip=#{ip}</if>" + ", ip=#{ip}" +
"<if test=\"localIp != null\">, local_ip=#{localIp}</if>" + ", local_ip=#{localIp}" +
"<if test=\"port != null\">, port=#{port}</if>" + ", port=#{port}" +
"<if test=\"hostAddress != null\">, host_address=#{hostAddress}</if>" + ", host_address=#{hostAddress}" +
"<if test=\"onLine != null\">, on_line=#{onLine}</if>" + ", on_line=#{onLine}" +
"<if test=\"registerTime != null\">, register_time=#{registerTime}</if>" + ", register_time=#{registerTime}" +
"<if test=\"keepaliveTime != null\">, keepalive_time=#{keepaliveTime}</if>" + ", keepalive_time=#{keepaliveTime}" +
"<if test=\"heartBeatInterval != null\">, heart_beat_interval=#{heartBeatInterval}</if>" + ", heart_beat_interval=#{heartBeatInterval}" +
"<if test=\"positionCapability != null\">, position_capability=#{positionCapability}</if>" + ", position_capability=#{positionCapability}" +
"<if test=\"heartBeatCount != null\">, heart_beat_count=#{heartBeatCount}</if>" + ", heart_beat_count=#{heartBeatCount}" +
"<if test=\"expires != null\">, expires=#{expires}</if>" + ", subscribe_cycle_for_catalog=#{subscribeCycleForCatalog}" +
"<if test=\"serverId != null\">, server_id=#{serverId}</if>" + ", subscribe_cycle_for_mobile_position=#{subscribeCycleForMobilePosition}" +
", mobile_position_submission_interval=#{mobilePositionSubmissionInterval}" +
", subscribe_cycle_for_alarm=#{subscribeCycleForAlarm}" +
", expires=#{expires}" +
", server_id=#{serverId}" +
"WHERE device_id=#{deviceId}"+ "WHERE device_id=#{deviceId}"+
" </script>"}) " </script>"})
int update(Device device); int update(Device device);

View File

@ -78,7 +78,7 @@ public interface PlatformMapper {
List<Platform> queryList(@Param("query") String query); List<Platform> queryList(@Param("query") String query);
@Select("SELECT * FROM wvp_platform WHERE server_id=#{serverId} and enable=#{enable} ") @Select("SELECT * FROM wvp_platform WHERE server_id=#{serverId} and enable=#{enable} ")
List<Platform> queryEnableParentPlatformList(@Param("serverId") String serverId, @Param("enable") boolean enable); List<Platform> queryEnableParentPlatformListByServerId(@Param("serverId") String serverId, @Param("enable") boolean enable);
@Select("SELECT * FROM wvp_platform WHERE enable=true and as_message_channel=true") @Select("SELECT * FROM wvp_platform WHERE enable=true and as_message_channel=true")
List<Platform> queryEnablePlatformListWithAsMessageChannel(); List<Platform> queryEnablePlatformListWithAsMessageChannel();
@ -89,12 +89,22 @@ public interface PlatformMapper {
@Select("SELECT * FROM wvp_platform WHERE id=#{id}") @Select("SELECT * FROM wvp_platform WHERE id=#{id}")
Platform query(int id); Platform query(int id);
@Update("UPDATE wvp_platform SET status=#{online} WHERE server_gb_id=#{platformGbID}" ) @Update("UPDATE wvp_platform SET status=#{online} WHERE id=#{id}" )
int updateStatus(@Param("platformGbID") String platformGbID, @Param("online") boolean online); int updateStatus(@Param("id") int id, @Param("online") boolean online);
@Select("SELECT server_id FROM wvp_platform WHERE enable=true and server_id != #{serverId} group by server_id") @Select("SELECT server_id FROM wvp_platform WHERE enable=true and server_id != #{serverId} group by server_id")
List<String> queryServerIdsWithEnableAndNotInServer(@Param("serverId") String serverId); List<String> queryServerIdsWithEnableAndNotInServer(@Param("serverId") String serverId);
@Select("SELECT * FROM wvp_platform WHERE server_id = #{serverId}") @Select("SELECT * FROM wvp_platform WHERE server_id = #{serverId}")
List<Platform> queryByServerId(@Param("serverId") String serverId); List<Platform> queryByServerId(@Param("serverId") String serverId);
@Select("SELECT * FROM wvp_platform ")
List<Platform> queryAll();
@Select("SELECT * FROM wvp_platform WHERE enable=true and server_id = #{serverId}")
List<Platform> queryServerIdsWithEnableAndServer(@Param("serverId") String serverId);
@Update("UPDATE wvp_platform SET status=false" )
void offlineAll();
} }

View File

@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.gb28181.event.sip; 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 com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import lombok.Data; import lombok.Data;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -27,6 +28,8 @@ public class SipEvent implements Delayed {
*/ */
private long delay; private long delay;
private SipTransactionInfo sipTransactionInfo;
public static SipEvent getInstance(String key, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, long delay) { public static SipEvent getInstance(String key, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, long delay) {
SipEvent sipEvent = new SipEvent(); SipEvent sipEvent = new SipEvent();
sipEvent.setKey(key); sipEvent.setKey(key);

View File

@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; 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.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -26,6 +27,9 @@ import java.util.Map;
@Component @Component
public class CatalogEventLister implements ApplicationListener<CatalogEvent> { public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
@Autowired
private IPlatformService platformService;
@Autowired @Autowired
private IPlatformChannelService platformChannelService; private IPlatformChannelService platformChannelService;
@ -50,8 +54,9 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
} }
}else { }else {
List<Platform> allPlatform = platformService.queryAll();
// 获取所用订阅 // 获取所用订阅
List<String> platforms = subscribeHolder.getAllCatalogSubscribePlatform(); List<String> platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform);
if (event.getChannels() != null) { if (event.getChannels() != null) {
if (!platforms.isEmpty()) { if (!platforms.isEmpty()) {
for (CommonGBChannel deviceChannel : event.getChannels()) { for (CommonGBChannel deviceChannel : event.getChannels()) {

View File

@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; 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.gb28181.transmit.cmd.impl.SIPCommanderForPlatform;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -24,6 +25,9 @@ import java.util.List;
@Component @Component
public class MobilePositionEventLister implements ApplicationListener<MobilePositionEvent> { public class MobilePositionEventLister implements ApplicationListener<MobilePositionEvent> {
@Autowired
private IPlatformService platformService;
@Autowired @Autowired
private IPlatformChannelService platformChannelService; private IPlatformChannelService platformChannelService;
@ -38,9 +42,9 @@ public class MobilePositionEventLister implements ApplicationListener<MobilePosi
if (event.getMobilePosition().getChannelId() == 0) { if (event.getMobilePosition().getChannelId() == 0) {
return; return;
} }
List<Platform> allPlatforms = platformService.queryAll();
// 获取所用订阅 // 获取所用订阅
List<String> platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(); List<String> platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(allPlatforms);
if (platforms.isEmpty()) { if (platforms.isEmpty()) {
return; return;
} }
@ -65,4 +69,3 @@ public class MobilePositionEventLister implements ApplicationListener<MobilePosi
} }
} }
} }

View File

@ -32,7 +32,7 @@ public interface IDeviceService {
* @param device 设备信息 * @param device 设备信息
* @return 布尔 * @return 布尔
*/ */
boolean addCatalogSubscribe(Device device); boolean addCatalogSubscribe(Device device, SipTransactionInfo transactionInfo);
/** /**
* 移除目录订阅 * 移除目录订阅
@ -46,7 +46,7 @@ public interface IDeviceService {
* @param device 设备信息 * @param device 设备信息
* @return 布尔 * @return 布尔
*/ */
boolean addMobilePositionSubscribe(Device device); boolean addMobilePositionSubscribe(Device device, SipTransactionInfo transactionInfo);
/** /**
* 移除移动位置订阅 * 移除移动位置订阅

View File

@ -48,4 +48,6 @@ public interface IPlatformChannelService {
void checkRegionAdd(List<CommonGBChannel> channelList); void checkRegionAdd(List<CommonGBChannel> channelList);
void checkRegionRemove(List<CommonGBChannel> channelList, List<Region> regionList); void checkRegionRemove(List<CommonGBChannel> channelList, List<Region> regionList);
List<Platform> queryByPlatformBySharChannelId(String gbId);
} }

View File

@ -53,13 +53,7 @@ public interface IPlatformService {
* 平台离线 * 平台离线
* @param parentPlatform 平台信息 * @param parentPlatform 平台信息
*/ */
void offline(Platform parentPlatform, boolean stopRegisterTask); void offline(Platform parentPlatform);
/**
* 向上级平台发起注册
* @param parentPlatform
*/
void login(Platform parentPlatform);
/** /**
* 向上级平台发送位置订阅 * 向上级平台发送位置订阅
@ -85,4 +79,7 @@ public interface IPlatformService {
List<Platform> queryEnablePlatformList(String serverId); List<Platform> queryEnablePlatformList(String serverId);
void delete(Integer platformId, CommonCallback<Object> callback); void delete(Integer platformId, CommonCallback<Object> callback);
List<Platform> queryAll();
} }

View File

@ -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.service.IInviteStreamService;
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTaskInfo;
import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; 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.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import com.genersoft.iot.vmp.media.bean.MediaServer; 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.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.SipException; import javax.sip.SipException;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.text.ParseException; import java.text.ParseException;
@ -53,7 +60,8 @@ import java.util.concurrent.TimeUnit;
*/ */
@Slf4j @Slf4j
@Service @Service
public class DeviceServiceImpl implements IDeviceService { @Order(value=16)
public class DeviceServiceImpl implements IDeviceService, CommandLineRunner {
@Autowired @Autowired
private DynamicTask dynamicTask; private DynamicTask dynamicTask;
@ -100,10 +108,46 @@ public class DeviceServiceImpl implements IDeviceService {
@Autowired @Autowired
private IRedisRpcService redisRpcService; private IRedisRpcService redisRpcService;
@Autowired
private SubscribeTaskRunner subscribeTaskRunner;
private Device getDeviceByDeviceIdFromDb(String deviceId) { private Device getDeviceByDeviceIdFromDb(String deviceId) {
return deviceMapper.getDeviceByDeviceId(deviceId); return deviceMapper.getDeviceByDeviceId(deviceId);
} }
@Override
public void run(String... args) throws Exception {
// TODO 处理设备离线
// 处理订阅任务
List<SubscribeTaskInfo> 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 @Override
public void online(Device device, SipTransactionInfo sipTransactionInfo) { public void online(Device device, SipTransactionInfo sipTransactionInfo) {
log.info("[设备上线] deviceId{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort()); log.info("[设备上线] deviceId{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
@ -164,12 +208,12 @@ public class DeviceServiceImpl implements IDeviceService {
// TODO 如果设备下的通道级联到了其他平台那么需要发送事件或者notify给上级平台 // TODO 如果设备下的通道级联到了其他平台那么需要发送事件或者notify给上级平台
} }
// 上线添加订阅 // 上线添加订阅
if (device.getSubscribeCycleForCatalog() > 0) { if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) {
// 查询在线设备那些开启了订阅为设备开启定时的目录订阅 // 查询在线设备那些开启了订阅为设备开启定时的目录订阅
addCatalogSubscribe(device); addCatalogSubscribe(device, null);
} }
if (device.getSubscribeCycleForMobilePosition() > 0) { if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
addMobilePositionSubscribe(device); addMobilePositionSubscribe(device, null);
} }
if (userSetting.getDeviceStatusNotify()) { if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息 // 发送redis消息
@ -254,98 +298,173 @@ public class DeviceServiceImpl implements IDeviceService {
} }
} }
@Override // 订阅丢失检查
public boolean addCatalogSubscribe(Device device) { @Scheduled(fixedDelay = 10, timeUnit = TimeUnit.SECONDS)
if (device == null || device.getSubscribeCycleForCatalog() < 0) { public void lostCheck(){
return false; // 获取所有设备
List<Device> deviceList = redisCatchStorage.getAllDevices();
if (deviceList.isEmpty()) {
return;
} }
log.info("[添加目录订阅] 设备{}", device.getDeviceId()); for (Device device : deviceList) {
// 添加目录订阅 if (device == null || !device.isOnLine()) {
CatalogSubscribeTask catalogSubscribeTask = new CatalogSubscribeTask(device, sipCommander, dynamicTask); continue;
// 刷新订阅 }
int subscribeCycleForCatalog = Math.max(device.getSubscribeCycleForCatalog(),30); if (device.getSubscribeCycleForCatalog() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) {
// 设置最小值为30 log.info("[订阅丢失] 目录订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
dynamicTask.startCron(device.getDeviceId() + "catalog", catalogSubscribeTask, (subscribeCycleForCatalog -1) * 1000); addCatalogSubscribe(device, null);
}
if (device.getSubscribeCycleForMobilePosition() > 0 && !subscribeTaskRunner.containsKey(SubscribeTaskForMobilPosition.getKey(device))) {
log.info("[订阅丢失] 移动位置订阅, 编号: {}, 重新发起订阅", device.getDeviceId());
addMobilePositionSubscribe(device, null);
}
}
}
catalogSubscribeTask.run(); private void catalogSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) {
return true; 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 @Override
public boolean removeCatalogSubscribe(Device device, CommonCallback<Boolean> callback) { public boolean addCatalogSubscribe(@NotNull Device device, SipTransactionInfo transactionInfo) {
if (device == null || device.getSubscribeCycleForCatalog() < 0) { if (device == null || device.getSubscribeCycleForCatalog() < 0) {
if (callback != null) {
callback.run(false);
}
return false; return false;
} }
log.info("[移除目录订阅]: {}", device.getDeviceId()); log.info("[添加目录订阅] 设备 {}", device.getDeviceId());
String taskKey = device.getDeviceId() + "catalog"; try {
if (device.isOnLine()) { sipCommander.catalogSubscribe(device, transactionInfo, eventResult -> {
Runnable runnable = dynamicTask.get(taskKey); ResponseEvent event = (ResponseEvent) eventResult.event;
if (runnable instanceof ISubscribeTask) { // 成功
ISubscribeTask subscribeTask = (ISubscribeTask) runnable; log.info("[目录订阅]成功: {}", device.getDeviceId());
subscribeTask.stop(callback); if (!subscribeTaskRunner.containsKey(SubscribeTaskForCatalog.getKey(device))) {
}else { SIPResponse response = (SIPResponse) event.getResponse();
log.info("[移除目录订阅]失败,未找到订阅任务 : {}", device.getDeviceId()); SipTransactionInfo transactionInfoForResonse = new SipTransactionInfo(response);
if (callback != null) { SubscribeTask subscribeTask = SubscribeTaskForCatalog.getInstance(device, this::catalogSubscribeExpire, transactionInfoForResonse);
callback.run(false); if (subscribeTask != null) {
subscribeTaskRunner.addSubscribe(subscribeTask);
}
}else {
subscribeTaskRunner.updateDelay(SubscribeTaskForCatalog.getKey(device), (device.getSubscribeCycleForCatalog() * 1000L - 500L) + System.currentTimeMillis());
} }
}
}else { },eventResult -> {
log.info("[移除目录订阅订阅]失败,设备已经离线 : {}", device.getDeviceId()); // 失败
if (callback != null) { log.warn("[目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
callback.run(false); });
} } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 目录订阅: {}", e.getMessage());
return false;
} }
dynamicTask.stop(taskKey);
return true; return true;
} }
@Override @Override
public boolean addMobilePositionSubscribe(Device device) { public boolean removeCatalogSubscribe(@NotNull Device device, CommonCallback<Boolean> callback) {
if (device == null || device.getSubscribeCycleForMobilePosition() < 0) { 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; 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; return true;
} }
@Override @Override
public boolean removeMobilePositionSubscribe(Device device, CommonCallback<Boolean> callback) { public boolean removeMobilePositionSubscribe(Device device, CommonCallback<Boolean> callback) {
if (device == null || device.getSubscribeCycleForCatalog() < 0) {
if (callback != null) {
callback.run(false);
}
return false;
}
log.info("[移除移动位置订阅]: {}", device.getDeviceId()); log.info("[移除移动位置订阅]: {}", device.getDeviceId());
String taskKey = device.getDeviceId() + "mobile_position"; String key = SubscribeTaskForMobilPosition.getKey(device);
if (device.isOnLine()) { if (subscribeTaskRunner.containsKey(key)) {
Runnable runnable = dynamicTask.get(taskKey); SipTransactionInfo transactionInfo = subscribeTaskRunner.getTransactionInfo(key);
if (runnable instanceof ISubscribeTask) { if (transactionInfo == null) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable; log.warn("[移除移动位置订阅] 未找到事务信息,{}", device.getDeviceId());
subscribeTask.stop(callback);
}else {
log.info("[移除移动位置订阅]失败,未找到订阅任务 : {}", device.getDeviceId());
if (callback != null) {
callback.run(false);
}
} }
}else { try {
log.info("[移除移动位置订阅]失败,设备已经离线 : {}", device.getDeviceId()); device.setSubscribeCycleForMobilePosition(0);
if (callback != null) { sipCommander.mobilePositionSubscribe(device, transactionInfo, eventResult -> {
callback.run(false); // 成功
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; return true;
} }
@ -499,10 +618,20 @@ public class DeviceServiceImpl implements IDeviceService {
public boolean delete(String deviceId) { public boolean delete(String deviceId) {
Device device = getDeviceByDeviceIdFromDb(deviceId); Device device = getDeviceByDeviceIdFromDb(deviceId);
Assert.notNull(device, "未找到设备"); 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); platformChannelMapper.delChannelForDeviceId(deviceId);
deviceChannelMapper.cleanChannelsByDeviceId(device.getId()); deviceChannelMapper.cleanChannelsByDeviceId(device.getId());
deviceMapper.del(deviceId); deviceMapper.del(deviceId);
redisCatchStorage.removeDevice(deviceId); redisCatchStorage.removeDevice(deviceId);
inviteStreamService.clearInviteInfo(deviceId);
return true; return true;
} }
@ -562,20 +691,17 @@ public class DeviceServiceImpl implements IDeviceService {
// 订阅周期不同则先取消 // 订阅周期不同则先取消
removeCatalogSubscribe(device, result->{ removeCatalogSubscribe(device, result->{
device.setSubscribeCycleForCatalog(cycle); device.setSubscribeCycleForCatalog(cycle);
updateDevice(device);
if (cycle > 0) { if (cycle > 0) {
// 开启订阅 // 开启订阅
addCatalogSubscribe(device); addCatalogSubscribe(device, null);
} }
// 因为是异步执行需要在这里更新下数据
deviceMapper.updateSubscribeCatalog(device);
redisCatchStorage.updateDevice(device);
}); });
}else { }else {
// 开启订阅 // 开启订阅
device.setSubscribeCycleForCatalog(cycle); device.setSubscribeCycleForCatalog(cycle);
addCatalogSubscribe(device); updateDevice(device);
deviceMapper.updateSubscribeCatalog(device); addCatalogSubscribe(device, null);
redisCatchStorage.updateDevice(device);
} }
} }
@ -583,6 +709,7 @@ public class DeviceServiceImpl implements IDeviceService {
public void subscribeMobilePosition(int id, int cycle, int interval) { public void subscribeMobilePosition(int id, int cycle, int interval) {
Device device = deviceMapper.query(id); Device device = deviceMapper.query(id);
Assert.notNull(device, "未找到设备"); Assert.notNull(device, "未找到设备");
if (device.getSubscribeCycleForMobilePosition() == cycle) { if (device.getSubscribeCycleForMobilePosition() == cycle) {
return; return;
} }
@ -598,21 +725,16 @@ public class DeviceServiceImpl implements IDeviceService {
device.setSubscribeCycleForMobilePosition(cycle); device.setSubscribeCycleForMobilePosition(cycle);
device.setMobilePositionSubmissionInterval(interval); device.setMobilePositionSubmissionInterval(interval);
if (cycle > 0) { if (cycle > 0) {
addMobilePositionSubscribe(device); addMobilePositionSubscribe(device, null);
} }
// 因为是异步执行需要在这里更新下数据
deviceMapper.updateSubscribeMobilePosition(device);
redisCatchStorage.updateDevice(device);
}); });
}else { }else {
// 订阅未开启 // 订阅未开启
device.setSubscribeCycleForMobilePosition(cycle); device.setSubscribeCycleForMobilePosition(cycle);
device.setMobilePositionSubmissionInterval(interval); device.setMobilePositionSubmissionInterval(interval);
updateDevice(device);
// 开启订阅 // 开启订阅
addMobilePositionSubscribe(device); addMobilePositionSubscribe(device, null);
// 因为是异步执行需要在这里更新下数据
deviceMapper.updateSubscribeMobilePosition(device);
redisCatchStorage.updateDevice(device);
} }
} }

View File

@ -601,4 +601,12 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
public List<CommonGBChannel> queryChannelByPlatformIdAndChannelIds(Integer platformId, List<Integer> channelIds) { public List<CommonGBChannel> queryChannelByPlatformIdAndChannelIds(Integer platformId, List<Integer> channelIds) {
return platformChannelMapper.queryShare(platformId, channelIds); return platformChannelMapper.queryShare(platformId, channelIds);
} }
@Override
public List<Platform> queryByPlatformBySharChannelId(String channelDeviceId) {
CommonGBChannel commonGBChannel = commonGBChannelMapper.queryByDeviceId(channelDeviceId);
ArrayList<Integer> ids = new ArrayList<>();
ids.add(commonGBChannel.getGbId());
return platformChannelMapper.queryPlatFormListByChannelList(ids);
}
} }

View File

@ -1,6 +1,5 @@
package com.genersoft.iot.vmp.gb28181.service.impl; 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.common.*;
import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig; 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.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager; 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.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.bean.MediaInfo; 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.bean.*;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService; import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.message.SIPResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -56,7 +60,8 @@ import java.util.concurrent.TimeUnit;
*/ */
@Slf4j @Slf4j
@Service @Service
public class PlatformServiceImpl implements IPlatformService { @Order(value=15)
public class PlatformServiceImpl implements IPlatformService, CommandLineRunner {
private final static String REGISTER_KEY_PREFIX = "platform_register_"; private final static String REGISTER_KEY_PREFIX = "platform_register_";
@ -69,7 +74,6 @@ public class PlatformServiceImpl implements IPlatformService {
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Autowired @Autowired
private SSRCFactory ssrcFactory; private SSRCFactory ssrcFactory;
@ -109,6 +113,74 @@ public class PlatformServiceImpl implements IPlatformService {
@Autowired @Autowired
private ISendRtpServerService sendRtpServerService; private ISendRtpServerService sendRtpServerService;
@Autowired
private PlatformStatusTaskRunner statusTaskRunner;
@Override
public void run(String... args) throws Exception {
// 启动时 如果存在未过期的注册平台则发送注销
List<PlatformRegisterTaskInfo> 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();
}
@Scheduled(fixedDelay = 20, timeUnit = TimeUnit.SECONDS) //每3秒执行一次
public void statusLostCheck(){
// 每隔20秒检测是否存在启用但是未注册的平台存在则发起注册
// 获取所有在线并且启用的平台
List<Platform> 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执行 // 定时监听国标级联所进行的WVP服务是否正常 如果异常则选择新的wvp执行
@Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) //每3秒执行一次 @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) //每3秒执行一次
public void execute(){ public void execute(){
@ -139,20 +211,26 @@ public class PlatformServiceImpl implements IPlatformService {
platform.setAddress(getIpWithSameNetwork(platform.getAddress())); platform.setAddress(getIpWithSameNetwork(platform.getAddress()));
platform.setServerId(userSetting.getServerId()); platform.setServerId(userSetting.getServerId());
platformMapper.update(platform); platformMapper.update(platform);
// 更新redis // 检查就平台是否注册到期没有则注销由本平台重新注册
redisCatchStorage.delPlatformCatchInfo(platform.getServerGBId()); List<PlatformRegisterTaskInfo> taskInfoList = statusTaskRunner.getRegisterTransactionInfoByServerId(serverId);
PlatformCatch platformCatch = new PlatformCatch(); boolean needUnregister = false;
platformCatch.setPlatform(platform); SipTransactionInfo sipTransactionInfo = null;
platformCatch.setId(platform.getServerGBId()); if (!taskInfoList.isEmpty()) {
redisCatchStorage.updatePlatformCatchInfo(platformCatch); for (PlatformRegisterTaskInfo taskInfo : taskInfoList) {
// 开始注册 if (taskInfo.getPlatformServerId().equals(platform.getServerGBId())
// 注册成功时由程序直接调用了online方法 && taskInfo.getSipTransactionInfo() != null) {
try { needUnregister = true;
commanderForPlatform.register(platform, eventResult -> { sipTransactionInfo = taskInfo.getSipTransactionInfo();
log.info("[国标级联] {}{},添加向上级注册失败,请确定上级平台可用时重新保存", platform.getName(), platform.getServerGBId()); break;
}, null); }
} catch (InvalidArgumentException | ParseException | SipException e) { }
log.error("[命令发送失败] 国标级联: {}", e.getMessage()); }
if (needUnregister) {
sendUnRegister(platform, sipTransactionInfo);
}else {
// 开始注册
// 注册成功时由程序直接调用了online方法
sendRegister(platform, null);
} }
}); });
}); });
@ -265,25 +343,17 @@ public class PlatformServiceImpl implements IPlatformService {
} }
platform.setServerId(userSetting.getServerId()); platform.setServerId(userSetting.getServerId());
int result = platformMapper.add(platform); int result = platformMapper.add(platform);
// 添加缓存
PlatformCatch platformCatch = new PlatformCatch();
platformCatch.setPlatform(platform);
platformCatch.setId(platform.getServerGBId());
redisCatchStorage.updatePlatformCatchInfo(platformCatch);
if (platform.isEnable()) { if (platform.isEnable()) {
// 保存时启用就发送注册 // 保存时启用就发送注册
// 注册成功时由程序直接调用了online方法 // 注册成功时由程序直接调用了online方法
try { sendRegister(platform, null);
commanderForPlatform.register(platform, eventResult -> {
log.info("[国标级联] {}{},添加向上级注册失败,请确定上级平台可用时重新保存", platform.getName(), platform.getServerGBId());
}, null);
} catch (InvalidArgumentException | ParseException | SipException e) {
log.error("[命令发送失败] 国标级联: {}", e.getMessage());
}
} }
return result > 0; return result > 0;
} }
@Override @Override
public boolean update(Platform platform) { public boolean update(Platform platform) {
Assert.isTrue(platform.getId() > 0, "ID必须存在"); Assert.isTrue(platform.getId() > 0, "ID必须存在");
@ -294,52 +364,15 @@ public class PlatformServiceImpl implements IPlatformService {
if (!userSetting.getServerId().equals(platformInDb.getServerId())) { if (!userSetting.getServerId().equals(platformInDb.getServerId())) {
return redisRpcService.updatePlatform(platformInDb.getServerId(), platform); 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) { if (platform.getCatalogGroup() == 0) {
platform.setCatalogGroup(1); platform.setCatalogGroup(1);
} }
platformMapper.update(platform); platformMapper.update(platform);
// 更新redis if (statusTaskRunner.containsRegister(platformInDb.getServerGBId())) {
redisCatchStorage.delPlatformCatchInfo(platformInDb.getServerGBId()); SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platformInDb.getServerGBId());
PlatformCatch platformCatch = new PlatformCatch(); // 注销后出发平台离线 如果是启用的平台那么下次丢失检测会检测到并重新注册上线
platformCatch.setPlatform(platform); sendUnRegister(platformInDb, transactionInfo);
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());
}
} }
return false; return false;
@ -348,79 +381,22 @@ public class PlatformServiceImpl implements IPlatformService {
@Override @Override
public void online(Platform platform, SipTransactionInfo sipTransactionInfo) { public void online(Platform platform, SipTransactionInfo sipTransactionInfo) {
log.info("[国标级联]{}, 平台上线", platform.getServerGBId()); log.info("[国标级联]{}, 平台上线", platform.getServerGBId());
final String registerFailAgainTaskKey = REGISTER_FAIL_AGAIN_KEY_PREFIX + platform.getServerGBId(); PlatformRegisterTask registerTask = new PlatformRegisterTask(platform.getServerGBId(), platform.getExpires() * 1000L - 500L,
dynamicTask.stop(registerFailAgainTaskKey); sipTransactionInfo, (platformServerGbId) -> {
this.registerExpire(platformServerGbId, sipTransactionInfo);
});
statusTaskRunner.addRegisterTask(registerTask);
platformMapper.updateStatus(platform.getServerGBId(), true); PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L,
PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); this::keepaliveExpire);
if (platformCatch == null) { statusTaskRunner.addKeepAliveTask(keepaliveTask);
platformCatch = new PlatformCatch();
platformCatch.setPlatform(platform);
platformCatch.setId(platform.getServerGBId());
platform.setStatus(true);
platformCatch.setPlatform(platform);
}
platformCatch.getPlatform().setStatus(true); platformMapper.updateStatus(platform.getId(), 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 (platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) {
if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) { if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) {
log.info("[国标级联]{}, 添加自动通道推送模拟订阅信息", platform.getServerGBId()); log.info("[国标级联]{}, 添加自动通道推送模拟订阅信息", platform.getServerGBId());
addSimulatedSubscribeInfo(platform); addSimulatedSubscribeInfo(platform);
} }
}else { }else {
SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
@ -430,6 +406,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 @Override
public void addSimulatedSubscribeInfo(Platform platform) { public void addSimulatedSubscribeInfo(Platform platform) {
// 自动添加一条模拟的订阅信息 // 自动添加一条模拟的订阅信息
@ -437,87 +472,25 @@ public class PlatformServiceImpl implements IPlatformService {
SubscribeInfo.buildSimulated(platform.getServerGBId(), platform.getServerIp())); 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 @Override
public void offline(Platform platform, boolean stopRegister) { public void offline(Platform platform) {
log.info("[平台离线]{}({})", platform.getName(), platform.getServerGBId()); log.info("[平台离线]{}({})", platform.getName(), platform.getServerGBId());
PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId()); statusTaskRunner.removeRegisterTask(platform.getServerGBId());
platformCatch.setKeepAliveReply(0); statusTaskRunner.removeKeepAliveTask(platform.getServerGBId());
platformCatch.setRegisterAliveReply(0);
Platform catchPlatform = platformCatch.getPlatform(); subscribeHolder.removeCatalogSubscribe(platform.getServerGBId());
catchPlatform.setStatus(false); subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId());
platformCatch.setPlatform(catchPlatform);
redisCatchStorage.updatePlatformCatchInfo(platformCatch); platformMapper.updateStatus(platform.getId(), false);
platformMapper.updateStatus(platform.getServerGBId(), false);
// 停止所有推流 // 停止所有推流
log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId()); log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId());
stopAllPush(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) { private void stopAllPush(String platformId) {
List<SendRtpInfo> sendRtpItems = sendRtpServerService.queryForPlatform(platformId); List<SendRtpInfo> sendRtpItems = sendRtpServerService.queryForPlatform(platformId);
if (sendRtpItems != null && sendRtpItems.size() > 0) { if (sendRtpItems != null && !sendRtpItems.isEmpty()) {
for (SendRtpInfo sendRtpItem : sendRtpItems) { for (SendRtpInfo sendRtpItem : sendRtpItems) {
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
sendRtpServerService.delete(sendRtpItem); sendRtpServerService.delete(sendRtpItem);
@ -527,23 +500,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 @Override
public void sendNotifyMobilePosition(String platformId) { public void sendNotifyMobilePosition(String platformId) {
Platform platform = platformMapper.getParentPlatByServerGBId(platformId); Platform platform = platformMapper.getParentPlatByServerGBId(platformId);
@ -890,7 +846,7 @@ public class PlatformServiceImpl implements IPlatformService {
@Override @Override
public List<Platform> queryEnablePlatformList(String serverId) { public List<Platform> queryEnablePlatformList(String serverId) {
return platformMapper.queryEnableParentPlatformList(serverId,true); return platformMapper.queryEnableParentPlatformListByServerId(serverId,true);
} }
@Override @Override
@ -898,55 +854,23 @@ public class PlatformServiceImpl implements IPlatformService {
public void delete(Integer platformId, CommonCallback<Object> callback) { public void delete(Integer platformId, CommonCallback<Object> callback) {
Platform platform = platformMapper.query(platformId); Platform platform = platformMapper.query(platformId);
Assert.notNull(platform, "平台不存在"); Assert.notNull(platform, "平台不存在");
// 发送离线消息,无论是否成功都删除缓存 if (statusTaskRunner.containsRegister(platform.getServerGBId())) {
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);
try { try {
commanderForPlatform.unregister(platform, platformCatch.getSipTransactionInfo(), (event -> { SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platform.getServerGBId());
dynamicTask.stop(key); sendUnRegister(platform, transactionInfo);
// 移除平台相关的信息 }catch (Exception ignored) {}
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);
}
} }
platformMapper.delete(platform.getId());
statusTaskRunner.removeRegisterTask(platform.getServerGBId());
statusTaskRunner.removeKeepAliveTask(platform.getServerGBId());
subscribeHolder.removeCatalogSubscribe(platform.getServerGBId());
subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId());
} }
@Transactional @Override
public void deletePlatformInfo(Platform platform) { public List<Platform> queryAll() {
// 删除关联的通道 return platformMapper.queryAll();
platformChannelMapper.removeChannelsByPlatformId(platform.getId());
// 删除关联的分组
platformChannelMapper.removePlatformGroupsByPlatformId(platform.getId());
// 删除关联的行政区划
platformChannelMapper.removePlatformRegionByPlatformId(platform.getId());
// 删除redis缓存
redisCatchStorage.delPlatformCatchInfo(platform.getServerGBId());
// 删除平台信息
platformMapper.delete(platform.getId());
} }
} }

View File

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

View File

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

View File

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

View File

@ -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<String, SubscribeTask> subscribes = new ConcurrentHashMap<>();
private final DelayQueue<SubscribeTask> delayQueue = new DelayQueue<>();
@Autowired
private RedisTemplate<Object, Object> 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<SubscribeTaskInfo> getAllTaskInfo(){
String scanKey = String.format("%s_%s_*", prefix, userSetting.getServerId());
List<Object> values = RedisUtil.scan(redisTemplate, scanKey);
if (values.isEmpty()) {
return new ArrayList<>();
}
List<SubscribeTaskInfo> 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;
}
}

View File

@ -0,0 +1,48 @@
package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl;
import com.genersoft.iot.vmp.common.SubscribeCallback;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class 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());
}
}

View File

@ -0,0 +1,48 @@
package com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.impl;
import com.genersoft.iot.vmp.common.SubscribeCallback;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.genersoft.iot.vmp.gb28181.task.deviceSubscribe.SubscribeTask;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class 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());
}
}

View File

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

View File

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

View File

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

View File

@ -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<String> callback;
public PlatformRegisterTask(String platformServerId, long delayTime, SipTransactionInfo sipTransactionInfo, CommonCallback<String> 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;
}
}

View File

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

View File

@ -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<String, PlatformRegisterTask> registerSubscribes = new ConcurrentHashMap<>();
private final DelayQueue<PlatformRegisterTask> registerDelayQueue = new DelayQueue<>();
private final Map<String, PlatformKeepaliveTask> keepaliveSubscribes = new ConcurrentHashMap<>();
private final DelayQueue<PlatformKeepaliveTask> keepaliveTaskDelayQueue = new DelayQueue<>();
@Autowired
private RedisTemplate<Object, Object> 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<PlatformRegisterTaskInfo> 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<PlatformRegisterTaskInfo> getRegisterTransactionInfoByServerId(String serverId) {
String scanKey = String.format("%s_%s_*", prefix, serverId);
List<Object> values = RedisUtil.scan(redisTemplate, scanKey);
if (values.isEmpty()) {
return new ArrayList<>();
}
List<PlatformRegisterTaskInfo> 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;
}
}

View File

@ -84,6 +84,11 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
// Success // Success
if (((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) || status == Response.UNAUTHORIZED) { 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(); CallIdHeader callIdHeader = response.getCallIdHeader();
CSeqHeader cSeqHeader = response.getCSeqHeader(); CSeqHeader cSeqHeader = response.getCSeqHeader();
if (callIdHeader != null) { if (callIdHeader != null) {
@ -96,10 +101,6 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
sipSubscribe.removeSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber()); 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)) { } else if ((status >= Response.TRYING) && (status < Response.OK)) {
// 增加其它无需回复的响应如101180等 // 增加其它无需回复的响应如101180等
// 更新sip订阅的时间 // 更新sip订阅的时间

View File

@ -2,21 +2,22 @@ package com.genersoft.iot.vmp.gb28181.transmit;
import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer; 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.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent; import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.utils.GitUtil; import com.genersoft.iot.vmp.utils.GitUtil;
import gov.nist.javax.sip.SipProviderImpl; 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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import javax.sip.SipException; import javax.sip.SipException;
import javax.sip.header.CSeqHeader; import javax.sip.header.*;
import javax.sip.header.CallIdHeader;
import javax.sip.header.UserAgentHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Message; import javax.sip.message.Message;
import javax.sip.message.Request; import javax.sip.message.Request;
import javax.sip.message.Response; import javax.sip.message.Response;
@ -39,6 +40,7 @@ public class SIPSender {
@Autowired @Autowired
private SipSubscribe sipSubscribe; private SipSubscribe sipSubscribe;
@Autowired @Autowired
private SipConfig sipConfig; private SipConfig sipConfig;
@ -69,11 +71,12 @@ public class SIPSender {
log.error("添加UserAgentHeader失败", e); log.error("添加UserAgentHeader失败", e);
} }
} }
CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME); CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME);
CSeqHeader cSeqHeader = (CSeqHeader) message.getHeader(CSeqHeader.NAME); CSeqHeader cSeqHeader = (CSeqHeader) message.getHeader(CSeqHeader.NAME);
String key = callIdHeader.getCallId() + cSeqHeader.getSeqNumber(); String key = callIdHeader.getCallId() + cSeqHeader.getSeqNumber();
if (okEvent != null || errorEvent != null) { if (okEvent != null || errorEvent != null) {
FromHeader fromHeader = (FromHeader) message.getHeader(FromHeader.NAME);
SipEvent sipEvent = SipEvent.getInstance(key, eventResult -> { SipEvent sipEvent = SipEvent.getInstance(key, eventResult -> {
sipSubscribe.removeSubscribe(key); sipSubscribe.removeSubscribe(key);
if(okEvent != null) { if(okEvent != null) {
@ -85,6 +88,26 @@ public class SIPSender {
errorEvent.response(eventResult); errorEvent.response(eventResult);
} }
}), timeout == null ? sipConfig.getTimeout() : timeout); }), 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); sipSubscribe.addSubscribe(key, sipEvent);
} }
try { try {

View File

@ -271,7 +271,7 @@ public interface ISIPCommander {
* @param device 视频设备 * @param device 视频设备
* @return true = 命令发送成功 * @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 视频设备 * @param device 视频设备
* @return true = 命令发送成功 * @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;
/** /**
* 拉框控制命令 * 拉框控制命令

View File

@ -225,11 +225,11 @@ public class SIPRequestHeaderPlarformProvider {
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(),
parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort()); parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort());
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); 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 // to
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain()); SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); 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 // Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
@ -239,7 +239,7 @@ public class SIPRequestHeaderPlarformProvider {
// 设置编码 防止中文乱码 // 设置编码 防止中文乱码
messageFactory.setDefaultContentEncodingCharset("gb2312"); 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, request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader,
toHeader, viaHeaders, maxForwards); toHeader, viaHeaders, maxForwards);

View File

@ -231,7 +231,7 @@ public class SIPRequestHeaderProvider {
return request; 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; Request request = null;
// sipuri // sipuri
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
@ -244,11 +244,11 @@ public class SIPRequestHeaderProvider {
// from // from
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); 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 // to
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); 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 // Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);

View File

@ -1179,7 +1179,7 @@ public class SIPCommander implements ISIPCommander {
* @return true = 命令发送成功 * @return true = 命令发送成功
*/ */
@Override @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); StringBuffer subscribePostitionXml = new StringBuffer(200);
String charset = device.getCharset(); String charset = device.getCharset();
@ -1197,12 +1197,12 @@ public class SIPCommander implements ISIPCommander {
CallIdHeader callIdHeader; CallIdHeader callIdHeader;
if (requestOld != null) { if (sipTransactionInfo != null) {
callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId());
} else { } else {
callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); 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); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
return request; return request;
@ -1255,7 +1255,7 @@ public class SIPCommander implements ISIPCommander {
} }
@Override @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); StringBuffer cmdXml = new StringBuffer(200);
String charset = device.getCharset(); String charset = device.getCharset();
@ -1268,14 +1268,14 @@ public class SIPCommander implements ISIPCommander {
CallIdHeader callIdHeader; CallIdHeader callIdHeader;
if (requestOld != null) { if (sipTransactionInfo != null) {
callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId());
} else { } else {
callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
} }
// 有效时间默认为60秒以上 // 有效时间默认为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); callIdHeader);
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
return request; return request;

View File

@ -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.GPSMsgInfo;
import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; 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.DateUtil;
import com.genersoft.iot.vmp.utils.GitUtil; import com.genersoft.iot.vmp.utils.GitUtil;
import gov.nist.javax.sip.message.MessageFactoryImpl; import gov.nist.javax.sip.message.MessageFactoryImpl;
@ -121,9 +120,6 @@ public class SIPCommanderForPlatform implements ISIPCommanderForPlatform {
request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform,
redisCatchStorage.getCSEQ(), fromTag, redisCatchStorage.getCSEQ(), fromTag,
toTag, callIdHeader, isRegister? parentPlatform.getExpires() : 0); toTag, callIdHeader, isRegister? parentPlatform.getExpires() : 0);
// callid 写入缓存 等注册成功可以更新状态
String callIdFromHeader = callIdHeader.getCallId();
redisCatchStorage.updatePlatformRegisterInfo(callIdFromHeader, PlatformRegisterInfo.getInstance(parentPlatform.getServerGBId(), isRegister));
}else { }else {
request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, fromTag, toTag, www, callIdHeader, isRegister? parentPlatform.getExpires() : 0); request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, fromTag, toTag, www, callIdHeader, isRegister? parentPlatform.getExpires() : 0);
} }
@ -132,7 +128,6 @@ public class SIPCommanderForPlatform implements ISIPCommanderForPlatform {
if (event != null) { if (event != null) {
log.info("[国标级联]{}, 注册失败: {} ", parentPlatform.getServerGBId(), event.msg); log.info("[国标级联]{}, 注册失败: {} ", parentPlatform.getServerGBId(), event.msg);
} }
redisCatchStorage.delPlatformRegisterInfo(callIdHeader.getCallId());
if (errorEvent != null ) { if (errorEvent != null ) {
errorEvent.response(event); errorEvent.response(event);
} }

View File

@ -1,9 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; 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.*;
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.IPlatformService; import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@ -23,6 +20,7 @@ import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent; import javax.sip.RequestEvent;
import javax.sip.SipException; import javax.sip.SipException;
import javax.sip.header.EventHeader;
import javax.sip.header.ExpiresHeader; import javax.sip.header.ExpiresHeader;
import javax.sip.message.Response; import javax.sip.message.Response;
import java.text.ParseException; import java.text.ParseException;
@ -106,7 +104,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
String platformId = SipUtils.getUserIdFromFromHeader(request); String platformId = SipUtils.getUserIdFromFromHeader(request);
String deviceId = XmlUtil.getText(rootElement, "DeviceID"); String deviceId = XmlUtil.getText(rootElement, "DeviceID");
Platform platform = platformService.queryPlatformByServerGBId(platformId); Platform platform = platformService.queryPlatformByServerGBId(platformId);
SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId);
if (platform == null) { if (platform == null) {
return; return;
} }
@ -122,23 +119,28 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
.append("<Result>OK</Result>\r\n") .append("<Result>OK</Result>\r\n")
.append("</Response>\r\n"); .append("</Response>\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 { 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) { if (subscribeInfo.getExpires() == 0) {
subscribeHolder.removeMobilePositionSubscribe(platformId); subscribeHolder.removeMobilePositionSubscribe(platformId);
}else { }else {
subscribeInfo.setResponse(response); subscribeInfo.setTransactionInfo(new SipTransactionInfo(response));
subscribeHolder.putMobilePositionSubscribe(platformId, subscribeInfo, ()->{ subscribeHolder.putMobilePositionSubscribe(platformId, subscribeInfo, ()->{
platformService.sendNotifyMobilePosition(platformId); platformService.sendNotifyMobilePosition(platformId);
}); });
@ -163,7 +165,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
if (platform == null){ if (platform == null){
return; return;
} }
SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId);
String sn = XmlUtil.getText(rootElement, "SN"); String sn = XmlUtil.getText(rootElement, "SN");
log.info("[回复上级的目录订阅请求]: {}/{}", platformId, deviceId); log.info("[回复上级的目录订阅请求]: {}/{}", platformId, deviceId);
@ -176,18 +177,25 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
.append("<Result>OK</Result>\r\n") .append("<Result>OK</Result>\r\n")
.append("</Response>\r\n"); .append("</Response>\r\n");
if (subscribeInfo.getExpires() > 0) {
subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo);
}else if (subscribeInfo.getExpires() == 0) {
subscribeHolder.removeCatalogSubscribe(platformId);
}
try { try {
int expires = request.getExpires().getExpires();
Platform parentPlatform = platformService.queryPlatformByServerGBId(platformId); 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) { if (subscribeInfo.getExpires() == 0) {
subscribeHolder.removeCatalogSubscribe(platformId); subscribeHolder.removeCatalogSubscribe(platformId);
}else { }else {
subscribeInfo.setResponse(response); subscribeInfo.setTransactionInfo(new SipTransactionInfo(response));
subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo); subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo);
} }
} catch (SipException | InvalidArgumentException | ParseException e) { } catch (SipException | InvalidArgumentException | ParseException e) {

View File

@ -97,10 +97,6 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
} }
Device device = sipMsgInfo.getDevice(); Device device = sipMsgInfo.getDevice();
SIPRequest request = (SIPRequest) evt.getRequest(); 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()); RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) { 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.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
device.setIp(remoteAddressInfo.getIp()); device.setIp(remoteAddressInfo.getIp());
device.setLocalIp(request.getLocalAddress().getHostAddress()); device.setLocalIp(request.getLocalAddress().getHostAddress());
// 设备地址变化会引起目录订阅任务失效需要重新添加
if (device.getSubscribeCycleForCatalog() > 0) {
deviceService.removeCatalogSubscribe(device, result -> {
deviceService.addCatalogSubscribe(device);
});
}
} }
device.setKeepaliveTime(DateUtil.getNow()); device.setKeepaliveTime(DateUtil.getNow());

View File

@ -1,14 +1,14 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; 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.Platform;
import com.genersoft.iot.vmp.gb28181.bean.PlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; 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.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; 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.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.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.message.SIPResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -44,6 +44,9 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
@Autowired @Autowired
private IPlatformService platformService; private IPlatformService platformService;
@Autowired
private SipSubscribe sipSubscribe;
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
// 添加消息处理的订阅 // 添加消息处理的订阅
@ -59,23 +62,19 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
public void process(ResponseEvent evt) { public void process(ResponseEvent evt) {
SIPResponse response = (SIPResponse)evt.getResponse(); SIPResponse response = (SIPResponse)evt.getResponse();
String callId = response.getCallIdHeader().getCallId(); String callId = response.getCallIdHeader().getCallId();
PlatformRegisterInfo platformRegisterInfo = redisCatchStorage.queryPlatformRegisterInfo(callId); long seqNumber = response.getCSeqHeader().getSeqNumber();
if (platformRegisterInfo == null) { SipEvent subscribe = sipSubscribe.getSubscribe(callId + seqNumber);
log.info(String.format("[国标级联]未找到callId %s 的注册/注销平台id", callId )); if (subscribe == null || subscribe.getSipTransactionInfo() == null || subscribe.getSipTransactionInfo().getUser() == null) {
return; return;
} }
PlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformRegisterInfo.getPlatformId()); String action = subscribe.getSipTransactionInfo().getExpires() > 0 ? "注册" : "注销";
if (parentPlatformCatch == null) { String platFormServerGbId = subscribe.getSipTransactionInfo().getUser();
log.warn(String.format("[国标级联]收到注册/注销%S请求平台%s但是平台缓存信息未查询到!!!", response.getStatusCode(),platformRegisterInfo.getPlatformId()));
return;
}
String action = platformRegisterInfo.isRegister() ? "注册" : "注销"; log.info("[国标级联]{} {}响应 {} ", action, response.getStatusCode(), platFormServerGbId);
log.info(String.format("[国标级联]%s %S响应,%s ", action, response.getStatusCode(), platformRegisterInfo.getPlatformId() )); Platform platform = platformService.queryPlatformByServerGBId(platFormServerGbId);
Platform parentPlatform = parentPlatformCatch.getPlatform(); if (platform == null) {
if (parentPlatform == null) { log.warn("[国标级联]收到 来自{}的 {} 回复 {}, 但是平台信息未查询到!!!", platFormServerGbId, action, response.getStatusCode());
log.warn(String.format("[国标级联]收到 %s %s的%S请求, 但是平台信息未查询到!!!", platformRegisterInfo.getPlatformId(), action, response.getStatusCode()));
return; return;
} }
@ -83,21 +82,17 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME); WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response); SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response);
try { 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) { } catch (SipException | InvalidArgumentException | ParseException e) {
log.error("[命令发送失败] 国标级联 再次注册: {}", e.getMessage()); log.error("[命令发送失败] 国标级联 再次注册: {}", e.getMessage());
} }
}else if (response.getStatusCode() == Response.OK){ }else if (response.getStatusCode() == Response.OK){
if (subscribe.getSipTransactionInfo().getExpires() > 0) {
if (platformRegisterInfo.isRegister()) {
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response); SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response);
platformService.online(parentPlatform, sipTransactionInfo); platformService.online(platform, sipTransactionInfo);
}else { }else {
platformService.offline(parentPlatform, true); platformService.offline(platform);
} }
// 注册/注销成功移除缓存的信息
redisCatchStorage.delPlatformRegisterInfo(callId);
} }
} }

View File

@ -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.bean.Platform;
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
import com.genersoft.iot.vmp.gb28181.service.IDeviceService; 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.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@ -55,6 +56,9 @@ public class RedisAlarmMsgListener implements MessageListener {
@Autowired @Autowired
private IPlatformService platformService; private IPlatformService platformService;
@Autowired
private IPlatformChannelService platformChannelService;
private final ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
@Autowired @Autowired
@ -129,7 +133,6 @@ public class RedisAlarmMsgListener implements MessageListener {
} }
} }
} }
} }
// 获取开启了消息推送的设备和平台 // 获取开启了消息推送的设备和平台
List<Device> devices = channelService.queryDeviceWithAsMessageChannel(); List<Device> devices = channelService.queryDeviceWithAsMessageChannel();
@ -143,24 +146,26 @@ public class RedisAlarmMsgListener implements MessageListener {
} }
} }
} }
} else { } else {
Device device = deviceService.getDeviceByDeviceId(gbId); // 获取该通道ID是属于设备还是对应的上级平台
Platform platform = platformService.queryPlatformByServerGBId(gbId); Device device = deviceService.getDeviceBySourceChannelDeviceId(gbId);
if (device != null && platform == null) { List<Platform> platforms = platformChannelService.queryByPlatformBySharChannelId(gbId);
if (device != null && (platforms == null || platforms.isEmpty())) {
try { try {
commander.sendAlarmMessage(device, deviceAlarm); commander.sendAlarmMessage(device, deviceAlarm);
} catch (InvalidArgumentException | SipException | ParseException e) { } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 发送报警: {}", e.getMessage()); log.error("[命令发送失败] 发送报警: {}", e.getMessage());
} }
} else if (device == null && platform != null) { } else if (device == null && (platforms != null && !platforms.isEmpty())) {
try { for (Platform platform : platforms) {
commanderForPlatform.sendAlarmMessage(platform, deviceAlarm); try {
} catch (InvalidArgumentException | SipException | ParseException e) { commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
log.error("[命令发送失败] 发送报警: {}", e.getMessage()); } catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 发送报警: {}", e.getMessage());
}
} }
} else { } else {
log.warn("无法确定" + gbId + "是平台还是设备"); log.warn("[REDIS的ALARM通知] 未查询到" + gbId + "所属的平台或设备");
} }
} }
} catch (Exception e) { } catch (Exception e) {

View File

@ -231,6 +231,9 @@ public class RedisRpcServiceImpl implements IRedisRpcService {
RedisRpcRequest request = buildRequest("platform/update", platform); RedisRpcRequest request = buildRequest("platform/update", platform);
request.setToId(serverId); request.setToId(serverId);
RedisRpcResponse response = redisRpcConfig.request(request, 40, TimeUnit.MILLISECONDS); RedisRpcResponse response = redisRpcConfig.request(request, 40, TimeUnit.MILLISECONDS);
if(response == null) {
return false;
}
return Boolean.parseBoolean(response.getBody().toString()); return Boolean.parseBoolean(response.getBody().toString());
} }

View File

@ -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.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -23,18 +22,6 @@ public interface IRedisCatchStorage {
*/ */
Long getCSEQ(); 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的信息 * 在redis添加wvp的信息
*/ */

View File

@ -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.GPSMsgInfo;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; 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.DateUtil;
import com.genersoft.iot.vmp.utils.JsonUtil; import com.genersoft.iot.vmp.utils.JsonUtil;
import com.genersoft.iot.vmp.utils.SystemInfoUtils; import com.genersoft.iot.vmp.utils.SystemInfoUtils;
@ -73,41 +72,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
redisTemplate.opsForValue().set(key, 1); 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 @Override
public void updateWVPInfo(ServerInfo serverInfo, int time) { public void updateWVPInfo(ServerInfo serverInfo, int time) {

View File

@ -57,27 +57,28 @@ public class StreamPushPlayServiceImpl implements IStreamPushPlayService {
StreamPush streamPush = streamPushMapper.queryOne(id); StreamPush streamPush = streamPushMapper.queryOne(id);
Assert.notNull(streamPush, "推流信息未找到"); Assert.notNull(streamPush, "推流信息未找到");
if (!userSetting.getServerId().equals(streamPush.getServerId())) { if (streamPush.isPushing() && !userSetting.getServerId().equals(streamPush.getServerId())) {
redisRpcPlayService.playPush(id, callback); redisRpcPlayService.playPush(id, callback);
return; return;
} }
MediaServer mediaServer = mediaServerService.getOne(streamPush.getMediaServerId()); MediaServer mediaServer = mediaServerService.getOne(streamPush.getMediaServerId());
Assert.notNull(mediaServer, "节点" + streamPush.getMediaServerId() + "未找到"); if (mediaServer != null) {
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamPush.getApp(), streamPush.getStream()); MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamPush.getApp(), streamPush.getStream());
if (mediaInfo != null) { if (mediaInfo != null) {
String callId = null; String callId = null;
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(streamPush.getApp(), streamPush.getStream()); StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(streamPush.getApp(), streamPush.getStream());
if (streamAuthorityInfo != null) { if (streamAuthorityInfo != null) {
callId = streamAuthorityInfo.getCallId(); 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(), "通道未推流"); Assert.isTrue(streamPush.isStartOfflinePush(), "通道未推流");
// 发送redis消息以使设备上线流上线后被 // 发送redis消息以使设备上线流上线后被

View File

@ -195,6 +195,14 @@ public class DateUtil {
} }
Instant startInstant = Instant.from(formatter.parse(startTime)); Instant startInstant = Instant.from(formatter.parse(startTime));
Instant endInstant = Instant.from(formatter.parse(endTime)); 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);
}
} }

View File

@ -861,7 +861,7 @@
320623,如东县,3206 320623,如东县,3206
320681,启东市,3206 320681,启东市,3206
320682,如皋市,3206 320682,如皋市,3206
320684,海门,3206 320684,海门,3206
320685,海安市,3206 320685,海安市,3206
3207,连云港市,32 3207,连云港市,32
320703,连云区,3207 320703,连云区,3207
@ -918,8 +918,6 @@
33,浙江省, 33,浙江省,
3301,杭州市,33 3301,杭州市,33
330102,上城区,3301 330102,上城区,3301
330103,下城区,3301
330104,江干区,3301
330105,拱墅区,3301 330105,拱墅区,3301
330106,西湖区,3301 330106,西湖区,3301
330108,滨江区,3301 330108,滨江区,3301
@ -927,6 +925,8 @@
330110,余杭区,3301 330110,余杭区,3301
330111,富阳区,3301 330111,富阳区,3301
330112,临安区,3301 330112,临安区,3301
330113,临平区,3301
330114,钱塘区,3301
330122,桐庐县,3301 330122,桐庐县,3301
330127,淳安县,3301 330127,淳安县,3301
330182,建德市,3301 330182,建德市,3301

1 编号 名称 上级
861 320623 如东县 3206
862 320681 启东市 3206
863 320682 如皋市 3206
864 320684 海门市 海门区 3206
865 320685 海安市 3206
866 3207 连云港市 32
867 320703 连云区 3207
918 33 浙江省
919 3301 杭州市 33
920 330102 上城区 3301
330103 下城区 3301
330104 江干区 3301
921 330105 拱墅区 3301
922 330106 西湖区 3301
923 330108 滨江区 3301
925 330110 余杭区 3301
926 330111 富阳区 3301
927 330112 临安区 3301
928 330113 临平区 3301
929 330114 钱塘区 3301
930 330122 桐庐县 3301
931 330127 淳安县 3301
932 330182 建德市 3301