Merge branch 'master' into dev/设备和平台使用不同的优化策略

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceServiceImpl.java
This commit is contained in:
lin 2025-05-26 09:09:36 +08:00
commit ca37162dd5
69 changed files with 2120 additions and 1568 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

@ -1,13 +1,13 @@
package com.genersoft.iot.vmp.common;
/**
* @description: 定义常量
/**
* @description: 定义常量
* @author: swwheihei
* @date: 2019年5月30日 下午3:04:04
*
* @date: 2019年5月30日 下午3:04:04
*
*/
public class VideoManagerConstants {
public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_";
public static final String WVP_SERVER_LIST = "VMP_SERVER_LIST";
@ -22,10 +22,6 @@ public class VideoManagerConstants {
public static final String INVITE_PREFIX = "VMP_GB_INVITE_INFO";
public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_";
public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_";
public static final String SEND_RTP_PORT = "VM_SEND_RTP_PORT:";
public static final String SEND_RTP_INFO_CALLID = "VMP_SEND_RTP_INFO:CALL_ID:";
public static final String SEND_RTP_INFO_STREAM = "VMP_SEND_RTP_INFO:STREAM:";

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

@ -16,7 +16,7 @@ import org.springframework.data.redis.listener.RedisMessageListenerContainer;
* @description:Redis中间件配置类使用spring-data-redis集成自动从application.yml中加载redis配置
* @author: swwheihei
* @date: 2019年5月30日 上午10:58:25
*
*
*/
@Configuration
@Order(value=1)
@ -38,7 +38,6 @@ public class RedisMsgListenConfig {
@Autowired
private RedisCloseStreamMsgListener redisCloseStreamMsgListener;
@Autowired
private RedisRpcConfig redisRpcConfig;
@ -49,7 +48,7 @@ public class RedisMsgListenConfig {
/**
* redis消息监听器容器 可以添加多个监听不同话题的redis监听器只需要把消息监听器和相应的消息订阅处理器绑定该消息监听器
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
*
*
* @param connectionFactory
* @return
*/

View File

@ -1,18 +1,23 @@
package com.genersoft.iot.vmp.gb28181.bean;
import lombok.Data;
/**
* 通过redis分发报警消息
*/
@Data
public class AlarmChannelMessage {
/**
* 国标编号
* 通道国标编号
*/
private String gbId;
/**
* 报警编号
*/
private int alarmSn;
/**
* 告警类型
*/
@ -22,36 +27,4 @@ public class AlarmChannelMessage {
* 报警描述
*/
private String alarmDescription;
public String getGbId() {
return gbId;
}
public void setGbId(String gbId) {
this.gbId = gbId;
}
public int getAlarmSn() {
return alarmSn;
}
public void setAlarmSn(int alarmSn) {
this.alarmSn = alarmSn;
}
public int getAlarmType() {
return alarmType;
}
public void setAlarmType(int alarmType) {
this.alarmType = alarmType;
}
public String getAlarmDescription() {
return alarmDescription;
}
public void setAlarmDescription(String alarmDescription) {
this.alarmDescription = alarmDescription;
}
}

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;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.Data;
@Data
public class SipTransactionInfo {
private String callId;
private String fromTag;
private String toTag;
private String viaBranch;
private int expires;
private String user;
// 自己是否媒体流发送者
private boolean asSender;
@ -31,43 +35,4 @@ public class SipTransactionInfo {
public SipTransactionInfo() {
}
public String getCallId() {
return callId;
}
public void setCallId(String callId) {
this.callId = callId;
}
public String getFromTag() {
return fromTag;
}
public void setFromTag(String fromTag) {
this.fromTag = fromTag;
}
public String getToTag() {
return toTag;
}
public void setToTag(String toTag) {
this.toTag = toTag;
}
public String getViaBranch() {
return viaBranch;
}
public void setViaBranch(String viaBranch) {
this.viaBranch = viaBranch;
}
public boolean isAsSender() {
return asSender;
}
public void setAsSender(boolean asSender) {
this.asSender = asSender;
}
}

View File

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

View File

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

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.IDeviceService;
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
@ -40,9 +37,6 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Tag(name = "国标设备查询", description = "国标设备查询")
@SuppressWarnings("rawtypes")
@ -144,28 +138,10 @@ public class DeviceQuery {
}
// 清除redis记录
boolean isSuccess = deviceService.delete(deviceId);
if (isSuccess) {
inviteStreamService.clearInviteInfo(deviceId);
// 停止此设备的订阅更新
Set<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调用失败");
}
deviceService.delete(deviceId);
JSONObject json = new JSONObject();
json.put("deviceId", deviceId);
return json.toString();
}
@Operation(summary = "分页查询子目录通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@ -358,28 +334,6 @@ public class DeviceQuery {
return wvpResult;
}
@GetMapping("/{deviceId}/subscribe_info")
@Operation(summary = "获取设备的订阅状态", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
public WVPResult<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}")
@Operation(summary = "请求截图")
@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.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.service.IMobilePositionService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.github.pagehelper.util.StringUtil;
@ -37,10 +37,10 @@ public class MobilePositionController {
@Autowired
private IMobilePositionService mobilePositionService;
@Autowired
private SIPCommander cmder;
private ISIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOfflineEvent;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerOnlineEvent;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@ -21,11 +22,12 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
/**
* @description:Event事件通知推送器支持推送在线事件离线事件
* @author: swwheihei
* @date: 2020年5月6日 上午11:30:50
* @date: 2020年5月6日 上午11:30:50
*/
@Slf4j
@Component
public class EventPublisher {
@ -72,12 +74,7 @@ public class EventPublisher {
}
public void catalogEventPublish(Platform platform, List<CommonGBChannel> deviceChannels, String type, boolean share) {
if (platform != null && !userSetting.getServerId().equals(platform.getServerId())) {
// 指定了上级平台的推送则发送到指定的设备未指定的则全部发送 接收后各自处理自己的
CatalogEvent outEvent = new CatalogEvent(this);
outEvent.setChannels(deviceChannels);
outEvent.setType(type);
outEvent.setPlatform(platform);
redisRpcService.catalogEventPublish(platform.getServerId(), outEvent);
log.info("[国标级联] 目录状态推送, 此上级平台由其他服务处理,消息已经忽略");
return;
}
CatalogEvent outEvent = new CatalogEvent(this);
@ -96,12 +93,11 @@ public class EventPublisher {
}
outEvent.setChannels(channels);
outEvent.setType(type);
outEvent.setPlatform(platform);
applicationEventPublisher.publishEvent(outEvent);
if (platform == null && share) {
// 如果没指定上级平台则推送消息到所有在线的wvp处理自己含有的平台的目录更新
redisRpcService.catalogEventPublish(null, outEvent);
if (platform != null) {
outEvent.setPlatform(platform);
}
applicationEventPublisher.publishEvent(outEvent);
}
public void mobilePositionEventPublish(MobilePosition mobilePosition) {

View File

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

View File

@ -1,10 +1,12 @@
package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService;
import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -26,6 +28,9 @@ import java.util.Map;
@Component
public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
@Autowired
private IPlatformService platformService;
@Autowired
private IPlatformChannelService platformChannelService;
@ -35,32 +40,45 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
@Autowired
private SubscribeHolder subscribeHolder;
@Autowired
private UserSetting userSetting;
@Override
public void onApplicationEvent(CatalogEvent event) {
SubscribeInfo subscribe = null;
Platform parentPlatform = null;
Map<String, List<Platform>> parentPlatformMap = new HashMap<>();
log.info("[Catalog事件: {}] 通道数量: {}", event.getType(), event.getChannels().size());
Map<String, List<Platform>> platformMap = new HashMap<>();
Map<String, CommonGBChannel> channelMap = new HashMap<>();
if (event.getPlatform() != null) {
parentPlatform = event.getPlatform();
if (parentPlatform.getServerGBId() == null) {
log.info("[Catalog事件: {}] 平台服务国标编码未找到", event.getType());
return;
}
subscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId());
if (subscribe == null) {
log.info("[Catalog事件: {}] 未订阅目录事件", event.getType());
return;
}
}else {
List<Platform> allPlatform = platformService.queryAll(userSetting.getServerId());
// 获取所用订阅
List<String> platforms = subscribeHolder.getAllCatalogSubscribePlatform();
List<String> platforms = subscribeHolder.getAllCatalogSubscribePlatform(allPlatform);
if (event.getChannels() != null) {
if (!platforms.isEmpty()) {
for (CommonGBChannel deviceChannel : event.getChannels()) {
List<Platform> parentPlatformsForGB = platformChannelService.queryPlatFormListByChannelDeviceId(
deviceChannel.getGbId(), platforms);
parentPlatformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB);
platformMap.put(deviceChannel.getGbDeviceId(), parentPlatformsForGB);
channelMap.put(deviceChannel.getGbDeviceId(), deviceChannel);
}
}else {
log.info("[Catalog事件: {}] 未订阅目录事件", event.getType());
}
}else {
log.info("[Catalog事件: {}] 事件内通道数为0", event.getType());
}
}
switch (event.getType()) {
@ -69,32 +87,32 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
case CatalogEvent.DEL:
if (parentPlatform != null) {
List<CommonGBChannel> deviceChannelList = new ArrayList<>();
List<CommonGBChannel> channels = new ArrayList<>();
if (event.getChannels() != null) {
deviceChannelList.addAll(event.getChannels());
channels.addAll(event.getChannels());
}
if (!deviceChannelList.isEmpty()) {
log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), deviceChannelList.size());
if (!channels.isEmpty()) {
log.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), parentPlatform.getServerGBId(), channels.size());
try {
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, deviceChannelList, subscribe, null);
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, channels, subscribe, null);
} catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
IllegalAccessException e) {
log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
}
}
}else if (!parentPlatformMap.keySet().isEmpty()) {
for (String gbId : parentPlatformMap.keySet()) {
List<Platform> parentPlatforms = parentPlatformMap.get(gbId);
if (parentPlatforms != null && !parentPlatforms.isEmpty()) {
for (Platform platform : parentPlatforms) {
}else if (!platformMap.keySet().isEmpty()) {
for (String serverGbId : platformMap.keySet()) {
List<Platform> platformList = platformMap.get(serverGbId);
if (platformList != null && !platformList.isEmpty()) {
for (Platform platform : platformList) {
SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
if (subscribeInfo == null) {
continue;
}
log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId);
log.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), serverGbId);
List<CommonGBChannel> deviceChannelList = new ArrayList<>();
CommonGBChannel deviceChannel = new CommonGBChannel();
deviceChannel.setGbDeviceId(gbId);
deviceChannel.setGbDeviceId(serverGbId);
deviceChannelList.add(deviceChannel);
try {
sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null);
@ -103,6 +121,8 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
}
}
}else {
log.info("[Catalog事件: {}] 未找到上级平台: {}", event.getType(), serverGbId);
}
}
}
@ -127,9 +147,9 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
log.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
}
}
}else if (!parentPlatformMap.keySet().isEmpty()) {
for (String gbId : parentPlatformMap.keySet()) {
List<Platform> parentPlatforms = parentPlatformMap.get(gbId);
}else if (!platformMap.keySet().isEmpty()) {
for (String gbId : platformMap.keySet()) {
List<Platform> parentPlatforms = platformMap.get(gbId);
if (parentPlatforms != null && !parentPlatforms.isEmpty()) {
for (Platform platform : parentPlatforms) {
SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());

View File

@ -1,10 +1,12 @@
package com.genersoft.iot.vmp.gb28181.event.subscribe.mobilePosition;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService;
import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderForPlatform;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import lombok.extern.slf4j.Slf4j;
@ -24,6 +26,9 @@ import java.util.List;
@Component
public class MobilePositionEventLister implements ApplicationListener<MobilePositionEvent> {
@Autowired
private IPlatformService platformService;
@Autowired
private IPlatformChannelService platformChannelService;
@ -33,14 +38,17 @@ public class MobilePositionEventLister implements ApplicationListener<MobilePosi
@Autowired
private SubscribeHolder subscribeHolder;
@Autowired
private UserSetting userSetting;
@Override
public void onApplicationEvent(MobilePositionEvent event) {
if (event.getMobilePosition().getChannelId() == 0) {
return;
}
List<Platform> allPlatforms = platformService.queryAll(userSetting.getServerId());
// 获取所用订阅
List<String> platforms = subscribeHolder.getAllMobilePositionSubscribePlatform();
List<String> platforms = subscribeHolder.getAllMobilePositionSubscribePlatform(allPlatforms);
if (platforms.isEmpty()) {
return;
}
@ -65,4 +73,3 @@ public class MobilePositionEventLister implements ApplicationListener<MobilePosi
}
}
}

View File

@ -32,7 +32,7 @@ public interface IDeviceService {
* @param device 设备信息
* @return 布尔
*/
boolean addCatalogSubscribe(Device device);
boolean addCatalogSubscribe(Device device, SipTransactionInfo transactionInfo);
/**
* 移除目录订阅
@ -46,7 +46,7 @@ public interface IDeviceService {
* @param device 设备信息
* @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 checkRegionRemove(List<CommonGBChannel> channelList, List<Region> regionList);
List<Platform> queryByPlatformBySharChannelId(String gbId);
}

View File

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

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

View File

@ -160,30 +160,26 @@ public class GbChannelServiceImpl implements IGbChannelService {
log.warn("[多个通道离线] 通道数量为0更新失败");
return 0;
}
List<CommonGBChannel> onlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "ON");
if (onlineChannelList.isEmpty()) {
log.info("[多个通道离线] 更新失败, 参数内通道已经离线, 无需更新");
return 0;
}
log.info("[通道离线] 共 {} 个", commonGBChannelList.size());
int limitCount = 1000;
int result = 0;
if (onlineChannelList.size() > limitCount) {
for (int i = 0; i < onlineChannelList.size(); i += limitCount) {
if (commonGBChannelList.size() > limitCount) {
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
int toIndex = i + limitCount;
if (i + limitCount > onlineChannelList.size()) {
toIndex = onlineChannelList.size();
if (i + limitCount > commonGBChannelList.size()) {
toIndex = commonGBChannelList.size();
}
result += commonGBChannelMapper.updateStatusForListById(onlineChannelList.subList(i, toIndex), "OFF");
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "OFF");
}
} else {
result += commonGBChannelMapper.updateStatusForListById(onlineChannelList, "OFF");
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "OFF");
}
if (result > 0) {
try {
// 发送catalog
eventPublisher.catalogEventPublish(null, onlineChannelList, CatalogEvent.OFF);
eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.OFF);
} catch (Exception e) {
log.warn("[多个通道离线] 发送失败,数量:{}", onlineChannelList.size(), e);
log.warn("[多个通道离线] 发送失败,数量:{}", commonGBChannelList.size(), e);
}
}
return result;
@ -214,32 +210,25 @@ public class GbChannelServiceImpl implements IGbChannelService {
log.warn("[多个通道上线] 通道数量为0更新失败");
return 0;
}
List<CommonGBChannel> offlineChannelList = commonGBChannelMapper.queryInListByStatus(commonGBChannelList, "OFF");
if (offlineChannelList.isEmpty()) {
log.warn("[多个通道上线] 更新失败, 参数内通道已经上线");
return 0;
}
// 批量更新
int limitCount = 1000;
int result = 0;
if (offlineChannelList.size() > limitCount) {
for (int i = 0; i < offlineChannelList.size(); i += limitCount) {
if (commonGBChannelList.size() > limitCount) {
for (int i = 0; i < commonGBChannelList.size(); i += limitCount) {
int toIndex = i + limitCount;
if (i + limitCount > offlineChannelList.size()) {
toIndex = offlineChannelList.size();
if (i + limitCount > commonGBChannelList.size()) {
toIndex = commonGBChannelList.size();
}
result += commonGBChannelMapper.updateStatusForListById(offlineChannelList.subList(i, toIndex), "ON");
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList.subList(i, toIndex), "ON");
}
} else {
result += commonGBChannelMapper.updateStatusForListById(offlineChannelList, "ON");
result += commonGBChannelMapper.updateStatusForListById(commonGBChannelList, "ON");
}
if (result > 0) {
try {
// 发送catalog
eventPublisher.catalogEventPublish(null, offlineChannelList, CatalogEvent.ON);
} catch (Exception e) {
log.warn("[多个通道上线] 发送失败,数量:{}", offlineChannelList.size(), e);
}
try {
// 发送catalog
eventPublisher.catalogEventPublish(null, commonGBChannelList, CatalogEvent.ON);
} catch (Exception e) {
log.warn("[多个通道上线] 发送失败,数量:{}", commonGBChannelList.size(), e);
}
return result;

View File

@ -601,4 +601,12 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
public List<CommonGBChannel> queryChannelByPlatformIdAndChannelIds(Integer platformId, List<Integer> 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;
import com.genersoft.iot.vmp.common.InviteInfo;
import com.genersoft.iot.vmp.common.*;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig;
@ -15,6 +14,10 @@ import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager;
import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformKeepaliveTask;
import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformRegisterTask;
import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformRegisterTaskInfo;
import com.genersoft.iot.vmp.gb28181.task.platformStatus.PlatformStatusTaskRunner;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
@ -28,13 +31,14 @@ import com.genersoft.iot.vmp.service.ISendRtpServerService;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@ -56,12 +60,8 @@ import java.util.concurrent.TimeUnit;
*/
@Slf4j
@Service
public class PlatformServiceImpl implements IPlatformService {
private final static String REGISTER_KEY_PREFIX = "platform_register_";
private final static String REGISTER_FAIL_AGAIN_KEY_PREFIX = "platform_register_fail_again_";
private final static String KEEPALIVE_KEY_PREFIX = "platform_keepalive_";
@Order(value=15)
public class PlatformServiceImpl implements IPlatformService, CommandLineRunner {
@Autowired
private PlatformMapper platformMapper;
@ -69,7 +69,6 @@ public class PlatformServiceImpl implements IPlatformService {
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private SSRCFactory ssrcFactory;
@ -109,6 +108,74 @@ public class PlatformServiceImpl implements IPlatformService {
@Autowired
private ISendRtpServerService sendRtpServerService;
@Autowired
private PlatformStatusTaskRunner statusTaskRunner;
@Override
public void run(String... args) throws Exception {
// 启动时 如果存在未过期的注册平台则发送注销
List<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(userSetting.getServerId());
}
@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执行
@Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS) //每3秒执行一次
public void execute(){
@ -127,6 +194,7 @@ public class PlatformServiceImpl implements IPlatformService {
return;
}
log.info("[集群] 检测到 {} 已离线", serverId);
redisCatchStorage.removeOfflineWVPInfo(serverId);
String chooseServerId = redisCatchStorage.chooseOneServer(serverId);
if (!userSetting.getServerId().equals(chooseServerId)){
return;
@ -139,20 +207,26 @@ public class PlatformServiceImpl implements IPlatformService {
platform.setAddress(getIpWithSameNetwork(platform.getAddress()));
platform.setServerId(userSetting.getServerId());
platformMapper.update(platform);
// 更新redis
redisCatchStorage.delPlatformCatchInfo(platform.getServerGBId());
PlatformCatch platformCatch = new PlatformCatch();
platformCatch.setPlatform(platform);
platformCatch.setId(platform.getServerGBId());
redisCatchStorage.updatePlatformCatchInfo(platformCatch);
// 开始注册
// 注册成功时由程序直接调用了online方法
try {
commanderForPlatform.register(platform, eventResult -> {
log.info("[国标级联] {}{},添加向上级注册失败,请确定上级平台可用时重新保存", platform.getName(), platform.getServerGBId());
}, null);
} catch (InvalidArgumentException | ParseException | SipException e) {
log.error("[命令发送失败] 国标级联: {}", e.getMessage());
// 检查就平台是否注册到期没有则注销由本平台重新注册
List<PlatformRegisterTaskInfo> taskInfoList = statusTaskRunner.getRegisterTransactionInfoByServerId(serverId);
boolean needUnregister = false;
SipTransactionInfo sipTransactionInfo = null;
if (!taskInfoList.isEmpty()) {
for (PlatformRegisterTaskInfo taskInfo : taskInfoList) {
if (taskInfo.getPlatformServerId().equals(platform.getServerGBId())
&& taskInfo.getSipTransactionInfo() != null) {
needUnregister = true;
sipTransactionInfo = taskInfo.getSipTransactionInfo();
break;
}
}
}
if (needUnregister) {
sendUnRegister(platform, sipTransactionInfo);
}else {
// 开始注册
// 注册成功时由程序直接调用了online方法
sendRegister(platform, null);
}
});
});
@ -265,25 +339,17 @@ public class PlatformServiceImpl implements IPlatformService {
}
platform.setServerId(userSetting.getServerId());
int result = platformMapper.add(platform);
// 添加缓存
PlatformCatch platformCatch = new PlatformCatch();
platformCatch.setPlatform(platform);
platformCatch.setId(platform.getServerGBId());
redisCatchStorage.updatePlatformCatchInfo(platformCatch);
if (platform.isEnable()) {
// 保存时启用就发送注册
// 注册成功时由程序直接调用了online方法
try {
commanderForPlatform.register(platform, eventResult -> {
log.info("[国标级联] {}{},添加向上级注册失败,请确定上级平台可用时重新保存", platform.getName(), platform.getServerGBId());
}, null);
} catch (InvalidArgumentException | ParseException | SipException e) {
log.error("[命令发送失败] 国标级联: {}", e.getMessage());
}
sendRegister(platform, null);
}
return result > 0;
}
@Override
public boolean update(Platform platform) {
Assert.isTrue(platform.getId() > 0, "ID必须存在");
@ -294,52 +360,15 @@ public class PlatformServiceImpl implements IPlatformService {
if (!userSetting.getServerId().equals(platformInDb.getServerId())) {
return redisRpcService.updatePlatform(platformInDb.getServerId(), platform);
}
PlatformCatch platformCatchOld = redisCatchStorage.queryPlatformCatchInfo(platformInDb.getServerGBId());
platform.setUpdateTime(DateUtil.getNow());
// 停止心跳定时
final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + platformInDb.getServerGBId();
dynamicTask.stop(keepaliveTaskKey);
// 停止注册定时
final String registerTaskKey = REGISTER_KEY_PREFIX + platformInDb.getServerGBId();
dynamicTask.stop(registerTaskKey);
// 注销旧的
try {
if (platformInDb.isStatus() && platformCatchOld != null) {
log.info("保存平台{}时发现旧平台在线,发送注销命令", platformInDb.getServerGBId());
commanderForPlatform.unregister(platformInDb, platformCatchOld.getSipTransactionInfo(), null, eventResult -> {
log.info("[国标级联] 注销成功, 平台:{}", platformInDb.getServerGBId());
});
}
} catch (InvalidArgumentException | ParseException | SipException e) {
log.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
}
// 更新数据库
if (platform.getCatalogGroup() == 0) {
platform.setCatalogGroup(1);
}
platformMapper.update(platform);
// 更新redis
redisCatchStorage.delPlatformCatchInfo(platformInDb.getServerGBId());
PlatformCatch platformCatch = new PlatformCatch();
platformCatch.setPlatform(platform);
platformCatch.setId(platform.getServerGBId());
redisCatchStorage.updatePlatformCatchInfo(platformCatch);
// 注册
if (platform.isEnable()) {
// 保存时启用就发送注册
// 注册成功时由程序直接调用了online方法
try {
log.info("[国标级联] 平台注册 {}", platform.getDeviceGBId());
commanderForPlatform.register(platform, eventResult -> {
log.info("[国标级联] {},添加向上级注册失败,请确定上级平台可用时重新保存", platform.getServerGBId());
}, null);
} catch (InvalidArgumentException | ParseException | SipException e) {
log.error("[命令发送失败] 国标级联: {}", e.getMessage());
}
if (statusTaskRunner.containsRegister(platformInDb.getServerGBId())) {
SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platformInDb.getServerGBId());
// 注销后出发平台离线 如果是启用的平台那么下次丢失检测会检测到并重新注册上线
sendUnRegister(platformInDb, transactionInfo);
}
return false;
@ -348,79 +377,21 @@ public class PlatformServiceImpl implements IPlatformService {
@Override
public void online(Platform platform, SipTransactionInfo sipTransactionInfo) {
log.info("[国标级联]{}, 平台上线", platform.getServerGBId());
final String registerFailAgainTaskKey = REGISTER_FAIL_AGAIN_KEY_PREFIX + platform.getServerGBId();
dynamicTask.stop(registerFailAgainTaskKey);
PlatformRegisterTask registerTask = new PlatformRegisterTask(platform.getServerGBId(), platform.getExpires() * 1000L - 500L,
sipTransactionInfo, (platformServerGbId) -> {
this.registerExpire(platformServerGbId, sipTransactionInfo);
});
statusTaskRunner.addRegisterTask(registerTask);
platformMapper.updateStatus(platform.getServerGBId(), true);
PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId());
if (platformCatch == null) {
platformCatch = new PlatformCatch();
platformCatch.setPlatform(platform);
platformCatch.setId(platform.getServerGBId());
platform.setStatus(true);
platformCatch.setPlatform(platform);
}
PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L,
this::keepaliveExpire);
statusTaskRunner.addKeepAliveTask(keepaliveTask);
platformMapper.updateStatus(platform.getId(), true, userSetting.getServerId());
platformCatch.getPlatform().setStatus(true);
platformCatch.setSipTransactionInfo(sipTransactionInfo);
redisCatchStorage.updatePlatformCatchInfo(platformCatch);
final String registerTaskKey = REGISTER_KEY_PREFIX + platform.getServerGBId();
if (!dynamicTask.isAlive(registerTaskKey)) {
log.info("[国标级联]{}, 添加定时注册任务", platform.getServerGBId());
// 添加注册任务
dynamicTask.startCron(registerTaskKey,
// 注册失败注册成功时由程序直接调用了online方法
()-> registerTask(platform, sipTransactionInfo),
platform.getExpires() * 1000);
}
final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + platform.getServerGBId();
if (!dynamicTask.contains(keepaliveTaskKey)) {
log.info("[国标级联]{}, 添加定时心跳任务", platform.getServerGBId());
// 添加心跳任务
dynamicTask.startCron(keepaliveTaskKey,
()-> {
try {
commanderForPlatform.keepalive(platform, eventResult -> {
// 心跳失败
if (eventResult.type != SipSubscribe.EventResultType.timeout) {
log.warn("[国标级联]发送心跳收到错误code {}, msg: {}", eventResult.statusCode, eventResult.msg);
}
// 心跳失败
PlatformCatch platformCatchForNow = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId());
// 此时是第三次心跳超时 平台离线
if (platformCatchForNow.getKeepAliveReply() == 2) {
// 设置平台离线并重新注册
log.info("[国标级联] 三次心跳失败, 平台{}({})离线", platform.getName(), platform.getServerGBId());
offline(platform, false);
}else {
platformCatchForNow.setKeepAliveReply(platformCatchForNow.getKeepAliveReply() + 1);
redisCatchStorage.updatePlatformCatchInfo(platformCatchForNow);
}
}, eventResult -> {
// 心跳成功
// 清空之前的心跳超时计数
PlatformCatch platformCatchForNow = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId());
if (platformCatchForNow != null && platformCatchForNow.getKeepAliveReply() > 0) {
platformCatchForNow.setKeepAliveReply(0);
redisCatchStorage.updatePlatformCatchInfo(platformCatchForNow);
}
log.info("[国标级联] 发送心跳,平台{}({}), code {}, msg: {}", platform.getName(), platform.getServerGBId(), eventResult.statusCode, eventResult.msg);
});
} catch (SipException | InvalidArgumentException | ParseException e) {
log.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage());
}
},
(platform.getKeepTimeout())*1000);
}
if (platform.getAutoPushChannel() != null && platform.getAutoPushChannel()) {
if (subscribeHolder.getCatalogSubscribe(platform.getServerGBId()) == null) {
log.info("[国标级联]{}, 添加自动通道推送模拟订阅信息", platform.getServerGBId());
addSimulatedSubscribeInfo(platform);
}
}else {
SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
@ -430,6 +401,65 @@ public class PlatformServiceImpl implements IPlatformService {
}
}
/**
* 注册到期处理
*/
private void registerExpire(String platformServerId, SipTransactionInfo transactionInfo) {
log.info("[国标级联] 注册到期, 上级平台编号: {}", platformServerId);
Platform platform = queryPlatformByServerGBId(platformServerId);
if (platform == null || !platform.isEnable()) {
log.info("[国标级联] 注册到期, 上级平台编号: {}, 平台不存在或者未启用, 忽略", platformServerId);
return;
}
sendRegister(platform, transactionInfo);
}
private void keepaliveExpire(String platformServerId, int failCount) {
log.info("[国标级联] 心跳到期, 上级平台编号: {}", platformServerId);
Platform platform = queryPlatformByServerGBId(platformServerId);
if (platform == null || !platform.isEnable()) {
log.info("[国标级联] 心跳到期, 上级平台编号: {}, 平台不存在或者未启用, 忽略", platformServerId);
return;
}
try {
commanderForPlatform.keepalive(platform, eventResult -> {
// 心跳失败
if (eventResult.type != SipSubscribe.EventResultType.timeout) {
log.warn("[国标级联] 发送心跳收到错误code {}, msg: {}", eventResult.statusCode, eventResult.msg);
}
// 心跳超时失败
if (failCount < 2) {
log.info("[国标级联] 心跳发送超时, 平台服务编号: {}", platformServerId);
PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L,
this::keepaliveExpire);
keepaliveTask.setFailCount(failCount + 1);
statusTaskRunner.addKeepAliveTask(keepaliveTask);
}else {
// 心跳超时三次, 不再发送心跳 平台离线
log.info("[国标级联] 心跳发送超时三次,平台离线, 平台服务编号: {}", platformServerId);
offline(platform);
}
}, eventResult -> {
PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L,
this::keepaliveExpire);
statusTaskRunner.addKeepAliveTask(keepaliveTask);
});
} catch (SipException | InvalidArgumentException | ParseException e) {
log.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage());
if (failCount < 2) {
PlatformKeepaliveTask keepaliveTask = new PlatformKeepaliveTask(platform.getServerGBId(), platform.getKeepTimeout() * 1000L,
this::keepaliveExpire);
keepaliveTask.setFailCount(failCount + 1);
statusTaskRunner.addKeepAliveTask(keepaliveTask);
}else {
// 心跳超时三次, 不再发送心跳 平台离线
log.info("[国标级联] 心跳发送失败三次,平台离线, 平台服务编号: {}", platformServerId);
offline(platform);
}
}
}
@Override
public void addSimulatedSubscribeInfo(Platform platform) {
// 自动添加一条模拟的订阅信息
@ -437,87 +467,25 @@ public class PlatformServiceImpl implements IPlatformService {
SubscribeInfo.buildSimulated(platform.getServerGBId(), platform.getServerIp()));
}
private void registerTask(Platform platform, SipTransactionInfo sipTransactionInfo){
try {
// 不在同一个会话中续订则每次全新注册
if (!userSetting.isRegisterKeepIntDialog()) {
sipTransactionInfo = null;
}
if (sipTransactionInfo == null) {
log.info("[国标级联] 平台:{}注册即将到期,开始重新注册", platform.getServerGBId());
}else {
log.info("[国标级联] 平台:{}注册即将到期,开始续订", platform.getServerGBId());
}
commanderForPlatform.register(platform, sipTransactionInfo, eventResult -> {
log.info("[国标级联] 平台:{}注册失败,{}:{}", platform.getServerGBId(),
eventResult.statusCode, eventResult.msg);
if (platform.isStatus()) {
offline(platform, false);
}
}, null);
} catch (Exception e) {
log.error("[命令发送失败] 国标级联定时注册: {}", e.getMessage());
}
}
@Override
public void offline(Platform platform, boolean stopRegister) {
public void offline(Platform platform) {
log.info("[平台离线]{}({})", platform.getName(), platform.getServerGBId());
PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId());
platformCatch.setKeepAliveReply(0);
platformCatch.setRegisterAliveReply(0);
Platform catchPlatform = platformCatch.getPlatform();
catchPlatform.setStatus(false);
platformCatch.setPlatform(catchPlatform);
redisCatchStorage.updatePlatformCatchInfo(platformCatch);
platformMapper.updateStatus(platform.getServerGBId(), false);
statusTaskRunner.removeRegisterTask(platform.getServerGBId());
statusTaskRunner.removeKeepAliveTask(platform.getServerGBId());
subscribeHolder.removeCatalogSubscribe(platform.getServerGBId());
subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId());
platformMapper.updateStatus(platform.getId(), false, userSetting.getServerId());
// 停止所有推流
log.info("[平台离线] {}({}), 停止所有推流", platform.getName(), platform.getServerGBId());
stopAllPush(platform.getServerGBId());
// 清除注册定时
log.info("[平台离线] {}({}), 停止定时注册任务", platform.getName(), platform.getServerGBId());
final String registerTaskKey = REGISTER_KEY_PREFIX + platform.getServerGBId();
if (dynamicTask.contains(registerTaskKey)) {
dynamicTask.stop(registerTaskKey);
}
// 清除心跳定时
log.info("[平台离线] {}({}), 停止定时发送心跳任务", platform.getName(), platform.getServerGBId());
final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + platform.getServerGBId();
if (dynamicTask.contains(keepaliveTaskKey)) {
// 清除心跳任务
dynamicTask.stop(keepaliveTaskKey);
}
// 停止订阅回复
SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
if (catalogSubscribe != null) {
if (catalogSubscribe.getExpires() > 0) {
log.info("[平台离线] {}({}), 停止目录订阅回复", platform.getName(), platform.getServerGBId());
subscribeHolder.removeCatalogSubscribe(platform.getServerGBId());
}
}
log.info("[平台离线] {}({}), 停止移动位置订阅回复", platform.getName(), platform.getServerGBId());
subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId());
// 发起定时自动重新注册
if (!stopRegister) {
// 设置为60秒自动尝试重新注册
final String registerFailAgainTaskKey = REGISTER_FAIL_AGAIN_KEY_PREFIX + platform.getServerGBId();
Platform platformInDb = platformMapper.query(platform.getId());
if (platformInDb.isEnable()) {
dynamicTask.startCron(registerFailAgainTaskKey,
()-> registerTask(platformInDb, null),
userSetting.getRegisterAgainAfterTime() * 1000);
}
}
}
private void stopAllPush(String platformId) {
List<SendRtpInfo> sendRtpItems = sendRtpServerService.queryForPlatform(platformId);
if (sendRtpItems != null && sendRtpItems.size() > 0) {
if (sendRtpItems != null && !sendRtpItems.isEmpty()) {
for (SendRtpInfo sendRtpItem : sendRtpItems) {
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
sendRtpServerService.delete(sendRtpItem);
@ -527,23 +495,6 @@ public class PlatformServiceImpl implements IPlatformService {
}
}
@Override
public void login(Platform platform) {
final String registerTaskKey = REGISTER_KEY_PREFIX + platform.getServerGBId();
try {
commanderForPlatform.register(platform, eventResult1 -> {
log.info("[国标级联] {}开始定时发起注册间隔为1分钟", platform.getServerGBId());
// 添加注册任务
dynamicTask.startCron(registerTaskKey,
// 注册失败注册成功时由程序直接调用了online方法
()-> log.info("[国标级联] {}({}),平台离线后持续发起注册,失败", platform.getName(), platform.getServerGBId()),
60*1000);
}, null);
} catch (InvalidArgumentException | ParseException | SipException e) {
log.error("[命令发送失败] 国标级联注册: {}", e.getMessage());
}
}
@Override
public void sendNotifyMobilePosition(String platformId) {
Platform platform = platformMapper.getParentPlatByServerGBId(platformId);
@ -565,7 +516,6 @@ public class PlatformServiceImpl implements IPlatformService {
gpsMsgInfo = null;
}
if (gpsMsgInfo == null && !userSetting.isSendPositionOnDemand()){
gpsMsgInfo = new GPSMsgInfo();
gpsMsgInfo.setId(channel.getGbDeviceId());
@ -890,7 +840,7 @@ public class PlatformServiceImpl implements IPlatformService {
@Override
public List<Platform> queryEnablePlatformList(String serverId) {
return platformMapper.queryEnableParentPlatformList(serverId,true);
return platformMapper.queryEnableParentPlatformListByServerId(serverId,true);
}
@Override
@ -898,55 +848,23 @@ public class PlatformServiceImpl implements IPlatformService {
public void delete(Integer platformId, CommonCallback<Object> callback) {
Platform platform = platformMapper.query(platformId);
Assert.notNull(platform, "平台不存在");
// 发送离线消息,无论是否成功都删除缓存
PlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(platform.getServerGBId());
if (platformCatch != null) {
String key = UUID.randomUUID().toString();
dynamicTask.startDelay(key, ()->{
deletePlatformInfo(platform);
if (callback != null) {
callback.run(null);
}
}, 2000);
if (statusTaskRunner.containsRegister(platform.getServerGBId())) {
try {
commanderForPlatform.unregister(platform, platformCatch.getSipTransactionInfo(), (event -> {
dynamicTask.stop(key);
// 移除平台相关的信息
deletePlatformInfo(platform);
if (callback != null) {
callback.run(null);
}
}), (event -> {
dynamicTask.stop(key);
// 移除平台相关的信息
deletePlatformInfo(platform);
if (callback != null) {
callback.run(null);
}
}));
} catch (InvalidArgumentException | ParseException | SipException e) {
log.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
}
}else {
deletePlatformInfo(platform);
if (callback != null) {
callback.run(null);
}
SipTransactionInfo transactionInfo = statusTaskRunner.getRegisterTransactionInfo(platform.getServerGBId());
sendUnRegister(platform, transactionInfo);
}catch (Exception ignored) {}
}
platformMapper.delete(platform.getId());
statusTaskRunner.removeRegisterTask(platform.getServerGBId());
statusTaskRunner.removeKeepAliveTask(platform.getServerGBId());
subscribeHolder.removeCatalogSubscribe(platform.getServerGBId());
subscribeHolder.removeMobilePositionSubscribe(platform.getServerGBId());
}
@Transactional
public void deletePlatformInfo(Platform platform) {
// 删除关联的通道
platformChannelMapper.removeChannelsByPlatformId(platform.getId());
// 删除关联的分组
platformChannelMapper.removePlatformGroupsByPlatformId(platform.getId());
// 删除关联的行政区划
platformChannelMapper.removePlatformRegionByPlatformId(platform.getId());
// 删除redis缓存
redisCatchStorage.delPlatformCatchInfo(platform.getServerGBId());
// 删除平台信息
platformMapper.delete(platform.getId());
@Override
public List<Platform> queryAll(String serverId) {
return platformMapper.queryByServerId(serverId);
}
}

View File

@ -340,7 +340,7 @@ public class PlayServiceImpl implements IPlayService {
InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, channel.getId());
if (inviteInfoInCatch != null ) {
if (inviteInfoInCatch.getStreamInfo() == null) {
// 释放生成的ssrc使用上一次申请的322
// 释放生成的ssrc使用上一次申请的
ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc);
// 点播发起了但是尚未成功, 仅注册回调等待结果即可

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.session;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@ -13,6 +14,7 @@ import java.util.Set;
/**
* ssrc使用
*/
@Slf4j
@Component
public class SSRCFactory {
@ -93,6 +95,7 @@ public class SSRCFactory {
String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
Long size = redisTemplate.opsForSet().size(redisKey);
if (size == null || size == 0) {
log.info("[获取 SSRC 失败] redisKey {}", redisKey);
throw new RuntimeException("ssrc已经用完");
} else {
// 在集合中移除并返回一个随机成员

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
if (((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) || status == Response.UNAUTHORIZED) {
ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(response.getCSeqHeader().getMethod());
if (sipRequestProcessor != null) {
sipRequestProcessor.process(responseEvent);
}
CallIdHeader callIdHeader = response.getCallIdHeader();
CSeqHeader cSeqHeader = response.getCSeqHeader();
if (callIdHeader != null) {
@ -96,10 +101,6 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
sipSubscribe.removeSubscribe(callIdHeader.getCallId() + cSeqHeader.getSeqNumber());
}
}
ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(response.getCSeqHeader().getMethod());
if (sipRequestProcessor != null) {
sipRequestProcessor.process(responseEvent);
}
} else if ((status >= Response.TRYING) && (status < Response.OK)) {
// 增加其它无需回复的响应如101180等
// 更新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.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.event.sip.SipEvent;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.utils.GitUtil;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.address.SipUri;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.sip.SipException;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.UserAgentHeader;
import javax.sip.header.ViaHeader;
import javax.sip.header.*;
import javax.sip.message.Message;
import javax.sip.message.Request;
import javax.sip.message.Response;
@ -39,6 +40,7 @@ public class SIPSender {
@Autowired
private SipSubscribe sipSubscribe;
@Autowired
private SipConfig sipConfig;
@ -69,11 +71,12 @@ public class SIPSender {
log.error("添加UserAgentHeader失败", e);
}
}
CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME);
CSeqHeader cSeqHeader = (CSeqHeader) message.getHeader(CSeqHeader.NAME);
String key = callIdHeader.getCallId() + cSeqHeader.getSeqNumber();
if (okEvent != null || errorEvent != null) {
FromHeader fromHeader = (FromHeader) message.getHeader(FromHeader.NAME);
SipEvent sipEvent = SipEvent.getInstance(key, eventResult -> {
sipSubscribe.removeSubscribe(key);
if(okEvent != null) {
@ -85,6 +88,26 @@ public class SIPSender {
errorEvent.response(eventResult);
}
}), timeout == null ? sipConfig.getTimeout() : timeout);
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo();
sipTransactionInfo.setFromTag(fromHeader.getTag());
sipTransactionInfo.setCallId(callIdHeader.getCallId());
if (message instanceof SIPResponse) {
SIPResponse response = (SIPResponse) message;
sipTransactionInfo.setToTag(response.getToHeader().getTag());
sipTransactionInfo.setViaBranch(response.getTopmostViaHeader().getBranch());
}else if (message instanceof SIPRequest) {
SIPRequest request = (SIPRequest) message;
sipTransactionInfo.setViaBranch(request.getTopmostViaHeader().getBranch());
SipUri sipUri = (SipUri)request.getRequestLine().getUri();
sipTransactionInfo.setUser(sipUri.getUser());
}
ExpiresHeader expiresHeader = (ExpiresHeader) message.getHeader(ExpiresHeader.NAME);
if (expiresHeader != null) {
sipTransactionInfo.setExpires(expiresHeader.getExpires());
}
sipEvent.setSipTransactionInfo(sipTransactionInfo);
sipSubscribe.addSubscribe(key, sipEvent);
}
try {

View File

@ -17,16 +17,16 @@ import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
/**
* @description:设备能力接口用于定义设备的控制查询能力
/**
* @description:设备能力接口用于定义设备的控制查询能力
* @author: swwheihei
* @date: 2020年5月3日 下午9:16:34
* @date: 2020年5月3日 下午9:16:34
*/
public interface ISIPCommander {
/**
* 云台控制支持方向与缩放控制
*
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
@ -36,10 +36,10 @@ public interface ISIPCommander {
* @param zoomSpeed 镜头缩放速度
*/
void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException;
/**
* 前端控制包括PTZ指令FI指令预置位指令巡航指令扫描指令和辅助开关指令
*
*
* @param device 控制设备
* @param channelId 预览通道
* @param cmdCode 指令码
@ -48,7 +48,7 @@ public interface ISIPCommander {
* @param combineCode2 组合码2
*/
void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException;
/**
* 前端控制指令用于转发上级指令
* @param device 控制设备
@ -66,7 +66,7 @@ public interface ISIPCommander {
/**
* 请求回放视频流
*
*
* @param device 视频设备
* @param channel 预览通道
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
@ -76,13 +76,13 @@ public interface ISIPCommander {
/**
* 请求历史媒体下载
*
*
* @param device 视频设备
* @param channel 预览通道
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
* @param downloadSpeed 下载倍速参数
*/
*/
void downloadStreamCmd(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
String startTime, String endTime, int downloadSpeed,
SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, Long timeout) throws InvalidArgumentException, SipException, ParseException;
@ -116,7 +116,7 @@ public interface ISIPCommander {
* 回放倍速播放
*/
void playSpeedCmd(Device device, DeviceChannel channel, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException;
/**
* 回放控制
* @param device
@ -138,39 +138,39 @@ public interface ISIPCommander {
/**
* 音视频录像控制
*
*
* @param device 视频设备
* @param channelId 预览通道
* @param recordCmdStr 录像命令Record / StopRecord
*/
void recordCmd(Device device, String channelId, String recordCmdStr, ErrorCallback<String> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 远程启动控制命令
*
*
* @param device 视频设备
*/
void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
/**
* 报警布防/撤防命令
*
*
* @param device 视频设备
*/
void guardCmd(Device device, String guardCmdStr, ErrorCallback<String> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 报警复位命令
*
*
* @param device 视频设备
* @param alarmMethod 报警方式可选
* @param alarmType 报警类型可选
*/
void alarmResetCmd(Device device, String alarmMethod, String alarmType, ErrorCallback<String> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
*
*
* @param device 视频设备
* @param channelId 预览通道
*/
@ -184,11 +184,11 @@ public interface ISIPCommander {
/**
* 设备配置命令
*
*
* @param device 视频设备
*/
void deviceConfigCmd(Device device);
/**
* 设备配置命令basicParam
*/
@ -196,11 +196,11 @@ public interface ISIPCommander {
/**
* 查询设备状态
*
*
* @param device 视频设备
*/
void deviceStatusQuery(Device device, ErrorCallback<String> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 查询设备信息
*
@ -209,27 +209,27 @@ public interface ISIPCommander {
* @return
*/
void deviceInfoQuery(Device device, ErrorCallback<Object> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 查询目录列表
*
*
* @param device 视频设备
*/
void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException;
/**
* 查询录像信息
*
*
* @param device 视频设备
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
* @param sn
*/
void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
/**
* 查询报警信息
*
*
* @param device 视频设备
* @param startPriority 报警起始级别可选
* @param endPriority 报警终止级别可选
@ -241,37 +241,37 @@ public interface ISIPCommander {
*/
void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod,
String alarmType, String startTime, String endTime, ErrorCallback<Object> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 查询设备配置
*
*
* @param device 视频设备
* @param channelId 通道编码可选
* @param configType 配置类型
*/
void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback<Object> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 查询设备预置位置
*
*
* @param device 视频设备
*/
void presetQuery(Device device, String channelId, ErrorCallback<Object> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 查询移动设备位置数据
*
*
* @param device 视频设备
*/
void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
/**
* 订阅取消订阅移动位置
*
*
* @param device 视频设备
* @return true = 命令发送成功
*/
SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
SIPRequest mobilePositionSubscribe(Device device, SipTransactionInfo transactionInfo, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
/**
* 订阅取消订阅报警信息
@ -290,7 +290,7 @@ public interface ISIPCommander {
* @param device 视频设备
* @return true = 命令发送成功
*/
SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
SIPRequest catalogSubscribe(Device device, SipTransactionInfo transactionInfo, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
/**
* 拉框控制命令

View File

@ -34,7 +34,7 @@ public class SIPRequestHeaderPlarformProvider {
@Autowired
private SipConfig sipConfig;
@Autowired
private SipLayer sipLayer;
@ -225,11 +225,11 @@ public class SIPRequestHeaderPlarformProvider {
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(),
parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort());
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse() != null ? subscribeInfo.getResponse().getToTag(): subscribeInfo.getSimulatedToTag());
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getTransactionInfo() != null ? subscribeInfo.getTransactionInfo() .getToTag(): subscribeInfo.getSimulatedToTag());
// to
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest() != null ?subscribeInfo.getRequest().getFromTag(): subscribeInfo.getSimulatedFromTag());
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getTransactionInfo() != null ?subscribeInfo.getTransactionInfo().getFromTag(): subscribeInfo.getSimulatedFromTag());
// Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
@ -239,7 +239,7 @@ public class SIPRequestHeaderPlarformProvider {
// 设置编码 防止中文乱码
messageFactory.setDefaultContentEncodingCharset("gb2312");
CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest() != null ? subscribeInfo.getRequest().getCallIdHeader().getCallId(): subscribeInfo.getSimulatedCallId());
CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getTransactionInfo() != null ? subscribeInfo.getTransactionInfo().getCallId(): subscribeInfo.getSimulatedCallId());
request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader,
toHeader, viaHeaders, maxForwards);

View File

@ -33,7 +33,7 @@ public class SIPRequestHeaderProvider {
@Autowired
private SipConfig sipConfig;
@Autowired
private SipLayer sipLayer;
@ -43,7 +43,7 @@ public class SIPRequestHeaderProvider {
@Autowired
private IRedisCatchStorage redisCatchStorage;
public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null;
// sipuri
@ -76,7 +76,7 @@ public class SIPRequestHeaderProvider {
request.setContent(content, contentTypeHeader);
return request;
}
public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null;
//请求行
@ -96,10 +96,10 @@ public class SIPRequestHeaderProvider {
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
//Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
//ceq
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
@ -116,7 +116,7 @@ public class SIPRequestHeaderProvider {
request.setContent(content, contentTypeHeader);
return request;
}
public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null;
//请求行
@ -134,14 +134,14 @@ public class SIPRequestHeaderProvider {
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
//Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
//ceq
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
@ -231,7 +231,7 @@ public class SIPRequestHeaderProvider {
return request;
}
public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
public Request createSubscribeRequest(Device device, String content, SipTransactionInfo sipTransactionInfo, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null;
// sipuri
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
@ -244,11 +244,11 @@ public class SIPRequestHeaderProvider {
// from
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sipTransactionInfo == null ? SipUtils.getNewFromTag() :sipTransactionInfo.getFromTag());
// to
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, sipTransactionInfo == null ? null :sipTransactionInfo.getToTag());
// Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);

View File

@ -1179,7 +1179,7 @@ public class SIPCommander implements ISIPCommander {
* @return true = 命令发送成功
*/
@Override
public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
public SIPRequest mobilePositionSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
StringBuffer subscribePostitionXml = new StringBuffer(200);
String charset = device.getCharset();
@ -1197,12 +1197,12 @@ public class SIPCommander implements ISIPCommander {
CallIdHeader callIdHeader;
if (requestOld != null) {
callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
if (sipTransactionInfo != null) {
callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId());
} else {
callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
}
SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), sipTransactionInfo, device.getSubscribeCycleForMobilePosition(), "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
return request;
@ -1255,7 +1255,7 @@ public class SIPCommander implements ISIPCommander {
}
@Override
public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
public SIPRequest catalogSubscribe(Device device, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
StringBuffer cmdXml = new StringBuffer(200);
String charset = device.getCharset();
@ -1268,14 +1268,14 @@ public class SIPCommander implements ISIPCommander {
CallIdHeader callIdHeader;
if (requestOld != null) {
callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
if (sipTransactionInfo != null) {
callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sipTransactionInfo.getCallId());
} else {
callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
}
// 有效时间默认为60秒以上
SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld, device.getSubscribeCycleForCatalog(), "Catalog",
SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), sipTransactionInfo, device.getSubscribeCycleForCatalog(), "Catalog",
callIdHeader);
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
return request;

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

View File

@ -143,39 +143,44 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc());
}
}
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaServer != null) {
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId());
if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) {
// 来自上级平台的停止对讲
log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
audioBroadcastManager.del(sendRtpItem.getChannelId());
}
if (sendRtpItem.getServerId().equals(userSetting.getServerId())) {
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaServer != null) {
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getChannelId());
if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) {
// 来自上级平台的停止对讲
log.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
audioBroadcastManager.del(sendRtpItem.getChannelId());
}
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId);
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), streamId);
if (mediaInfo.getReaderCount() <= 0) {
log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId());
if (device == null) {
log.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
return;
}
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId());
if (deviceChannel == null) {
log.info("[收到bye] {} 通知设备停止推流时未找到通道信息", streamId);
return;
}
try {
log.info("[停止点播] {}/{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
cmder.streamByeCmd(device, deviceChannel.getDeviceId(), sendRtpItem.getApp(), sendRtpItem.getStream(), null, null);
} catch (InvalidArgumentException | ParseException | SipException |
SsrcTransactionNotFoundException e) {
log.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
if (mediaInfo != null && mediaInfo.getReaderCount() <= 0) {
log.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
Device device = deviceService.getDeviceByDeviceId(sendRtpItem.getTargetId());
if (device == null) {
log.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
return;
}
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(sendRtpItem.getChannelId());
if (deviceChannel == null) {
log.info("[收到bye] {} 通知设备停止推流时未找到通道信息", streamId);
return;
}
try {
log.info("[停止点播] {}/{}", sendRtpItem.getTargetId(), sendRtpItem.getChannelId());
cmder.streamByeCmd(device, deviceChannel.getDeviceId(), sendRtpItem.getApp(), sendRtpItem.getStream(), null, null);
} catch (InvalidArgumentException | ParseException | SipException |
SsrcTransactionNotFoundException e) {
log.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
}
}
}
}
} else {
// TODO 流再其他wvp上时应该通知这个wvp停止推流和发送BYE
}
}
// 可能是设备发送的停止

View File

@ -172,10 +172,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
// 点播成功 TODO 可以在此处检测cancel命令是否存在存在则不发送
if (userSetting.getUseCustomSsrcForParentInvite()) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName())
MediaServer mediaServer = mediaServerService.getOne(streamInfo.getMediaServer().getId());
if (mediaServer != null) {
String ssrc = "Play".equalsIgnoreCase(inviteInfo.getSessionName())
? ssrcFactory.getPlaySsrc(streamInfo.getMediaServer().getId())
: ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId());
inviteInfo.setSsrc(ssrc);
: ssrcFactory.getPlayBackSsrc(streamInfo.getMediaServer().getId());
inviteInfo.setSsrc(ssrc);
}
}
// 构建sendRTP内容
SendRtpInfo sendRtpItem = sendRtpServerService.createSendRtpInfo(streamInfo.getMediaServer(),

View File

@ -1,9 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.genersoft.iot.vmp.gb28181.bean.CmdType;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@ -23,6 +20,7 @@ import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.header.EventHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.message.Response;
import java.text.ParseException;
@ -56,9 +54,9 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
sipProcessorObserver.addRequestProcessor(method, this);
}
/**
* 处理SUBSCRIBE请求
*
/**
* 处理SUBSCRIBE请求
*
* @param evt 事件
*/
@Override
@ -106,7 +104,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
String platformId = SipUtils.getUserIdFromFromHeader(request);
String deviceId = XmlUtil.getText(rootElement, "DeviceID");
Platform platform = platformService.queryPlatformByServerGBId(platformId);
SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId);
if (platform == null) {
return;
}
@ -122,23 +119,28 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
.append("<Result>OK</Result>\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 {
SIPResponse response = responseXmlAck(request, resultXml.toString(), platform, subscribeInfo.getExpires());
int expires = request.getExpires().getExpires();
SIPResponse response = responseXmlAck(request, resultXml.toString(), platform, expires);
SubscribeInfo subscribeInfo = SubscribeInfo.getInstance(response, platformId, expires,
(EventHeader)request.getHeader(EventHeader.NAME));
if (subscribeInfo.getExpires() > 0) {
// GPS上报时间间隔
String interval = XmlUtil.getText(rootElement, "Interval");
if (interval == null) {
subscribeInfo.setGpsInterval(5);
}else {
subscribeInfo.setGpsInterval(Integer.parseInt(interval));
}
subscribeInfo.setSn(sn);
}
if (subscribeInfo.getExpires() == 0) {
subscribeHolder.removeMobilePositionSubscribe(platformId);
}else {
subscribeInfo.setResponse(response);
subscribeInfo.setTransactionInfo(new SipTransactionInfo(response));
subscribeHolder.putMobilePositionSubscribe(platformId, subscribeInfo, ()->{
platformService.sendNotifyMobilePosition(platformId);
});
@ -163,7 +165,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
if (platform == null){
return;
}
SubscribeInfo subscribeInfo = new SubscribeInfo(request, platformId);
String sn = XmlUtil.getText(rootElement, "SN");
log.info("[回复上级的目录订阅请求]: {}/{}", platformId, deviceId);
@ -176,18 +177,25 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
.append("<Result>OK</Result>\r\n")
.append("</Response>\r\n");
if (subscribeInfo.getExpires() > 0) {
subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo);
}else if (subscribeInfo.getExpires() == 0) {
subscribeHolder.removeCatalogSubscribe(platformId);
}
try {
int expires = request.getExpires().getExpires();
Platform parentPlatform = platformService.queryPlatformByServerGBId(platformId);
SIPResponse response = responseXmlAck(request, resultXml.toString(), parentPlatform, subscribeInfo.getExpires());
SIPResponse response = responseXmlAck(request, resultXml.toString(), parentPlatform, expires);
SubscribeInfo subscribeInfo = SubscribeInfo.getInstance(response, platformId, expires,
(EventHeader)request.getHeader(EventHeader.NAME));
if (subscribeInfo.getExpires() > 0) {
subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo);
}else if (subscribeInfo.getExpires() == 0) {
subscribeHolder.removeCatalogSubscribe(platformId);
}
if (subscribeInfo.getExpires() == 0) {
subscribeHolder.removeCatalogSubscribe(platformId);
}else {
subscribeInfo.setResponse(response);
subscribeInfo.setTransactionInfo(new SipTransactionInfo(response));
subscribeHolder.putCatalogSubscribe(platformId, subscribeInfo);
}
} catch (SipException | InvalidArgumentException | ParseException e) {

View File

@ -97,10 +97,6 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
}
Device device = sipMsgInfo.getDevice();
SIPRequest request = (SIPRequest) evt.getRequest();
// if (!ObjectUtils.isEmpty(device.getKeepaliveTime()) && DateUtil.getDifferenceForNow(device.getKeepaliveTime()) <= 3000L) {
// log.info("[收到心跳] 心跳发送过于频繁,已忽略 device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId());
// return;
// }
RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) {
@ -109,12 +105,6 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
device.setIp(remoteAddressInfo.getIp());
device.setLocalIp(request.getLocalAddress().getHostAddress());
// 设备地址变化会引起目录订阅任务失效需要重新添加
if (device.getSubscribeCycleForCatalog() > 0) {
deviceService.removeCatalogSubscribe(device, result -> {
deviceService.addCatalogSubscribe(device);
});
}
}
device.setKeepaliveTime(DateUtil.getNow());

View File

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

View File

@ -26,7 +26,7 @@ public interface IRedisRpcPlayService {
String frontEndCommand(String serverId, Integer channelId, int cmdCode, int parameter1, int parameter2, int combindCode2);
void playPush(Integer id, ErrorCallback<StreamInfo> callback);
void playPush(String serverId, Integer id, ErrorCallback<StreamInfo> callback);
StreamInfo playProxy(String serverId, int id);

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.service.IDeviceChannelService;
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService;
import com.genersoft.iot.vmp.gb28181.service.IPlatformService;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@ -55,6 +56,9 @@ public class RedisAlarmMsgListener implements MessageListener {
@Autowired
private IPlatformService platformService;
@Autowired
private IPlatformChannelService platformChannelService;
private final ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
@Autowired
@ -89,11 +93,11 @@ public class RedisAlarmMsgListener implements MessageListener {
log.warn("[REDIS的ALARM通知]消息解析失败");
continue;
}
String gbId = alarmChannelMessage.getGbId();
String chanelId = alarmChannelMessage.getGbId();
DeviceAlarm deviceAlarm = new DeviceAlarm();
deviceAlarm.setCreateTime(DateUtil.getNow());
deviceAlarm.setChannelId(gbId);
deviceAlarm.setChannelId(chanelId);
deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType());
@ -102,7 +106,7 @@ public class RedisAlarmMsgListener implements MessageListener {
deviceAlarm.setLongitude(0);
deviceAlarm.setLatitude(0);
if (ObjectUtils.isEmpty(gbId)) {
if (ObjectUtils.isEmpty(chanelId)) {
if (userSetting.getSendToPlatformsWhenIdLost()) {
// 发送给所有的上级
List<Platform> parentPlatforms = platformService.queryEnablePlatformList(userSetting.getServerId());
@ -129,7 +133,6 @@ public class RedisAlarmMsgListener implements MessageListener {
}
}
}
}
// 获取开启了消息推送的设备和平台
List<Device> devices = channelService.queryDeviceWithAsMessageChannel();
@ -143,24 +146,28 @@ public class RedisAlarmMsgListener implements MessageListener {
}
}
}
} else {
Device device = deviceService.getDeviceByDeviceId(gbId);
Platform platform = platformService.queryPlatformByServerGBId(gbId);
if (device != null && platform == null) {
// 获取该通道ID是属于设备还是对应的上级平台
Device device = deviceService.getDeviceBySourceChannelDeviceId(chanelId);
List<Platform> platforms = platformChannelService.queryByPlatformBySharChannelId(chanelId);
if (device != null && device.getServerId().equals(userSetting.getServerId()) && (platforms == null || platforms.isEmpty())) {
try {
commander.sendAlarmMessage(device, deviceAlarm);
} catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 发送报警: {}", e.getMessage());
}
} else if (device == null && platform != null) {
try {
commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
} catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 发送报警: {}", e.getMessage());
} else if (device == null && (platforms != null && !platforms.isEmpty() )) {
for (Platform platform : platforms) {
if (platform.getServerId().equals(userSetting.getServerId())) {
try {
commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
} catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 发送报警: {}", e.getMessage());
}
}
}
} else {
log.warn("无法确定" + gbId + "是平台还是设备");
log.warn("[REDIS的ALARM通知] 未查询到" + chanelId + "所属的平台或设备");
}
}
} catch (Exception e) {

View File

@ -48,7 +48,7 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic
@Override
public void onMessage(Message message, byte[] bytes) {
log.info("[REDIS: 流设备状态变化] {}", new String(message.getBody()));
log.info("[REDIS: 流设备状态变化] {}", new String(message.getBody()));
taskQueue.offer(message);
}
@ -84,11 +84,13 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic
if (streamStatusMessage.getOfflineStreams() != null
&& !streamStatusMessage.getOfflineStreams().isEmpty()) {
// 更新部分设备离线
log.info("[REDIS: 推流设备状态变化] 更新部分设备离线: {}个", streamStatusMessage.getOfflineStreams().size());
streamPushService.offline(streamStatusMessage.getOfflineStreams());
}
if (streamStatusMessage.getOnlineStreams() != null &&
!streamStatusMessage.getOnlineStreams().isEmpty()) {
// 更新部分设备上线
log.info("[REDIS: 推流设备状态变化] 更新部分设备上线: {}个", streamStatusMessage.getOnlineStreams().size());
streamPushService.online(streamStatusMessage.getOnlineStreams());
}
} catch (Exception e) {

View File

@ -181,7 +181,8 @@ public class RedisRpcStreamPushController extends RpcController {
*/
@RedisRpcMapping("play")
public RedisRpcResponse play(RedisRpcRequest request) {
int id = Integer.parseInt(request.getParam().toString());
JSONObject paramJson = JSONObject.parseObject(request.getParam().toString());
int id = paramJson.getInteger("id");
RedisRpcResponse response = request.getResponse();
if (id <= 0) {
response.setStatusCode(ErrorCode.ERROR400.getCode());

View File

@ -193,8 +193,11 @@ public class RedisRpcPlayServiceImpl implements IRedisRpcPlayService {
}
@Override
public void playPush(Integer id, ErrorCallback<StreamInfo> callback) {
RedisRpcRequest request = buildRequest("streamPush/play", id);
public void playPush(String serverId, Integer id, ErrorCallback<StreamInfo> callback) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
RedisRpcRequest request = buildRequest("streamPush/play", jsonObject);
request.setToId(serverId);
RedisRpcResponse response = redisRpcConfig.request(request, userSetting.getPlayTimeout(), TimeUnit.SECONDS);
if (response == null) {
callback.run(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg(), null);

View File

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

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.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import java.util.List;
import java.util.Map;
@ -23,23 +22,13 @@ public interface IRedisCatchStorage {
*/
Long getCSEQ();
void updatePlatformCatchInfo(PlatformCatch parentPlatformCatch);
PlatformCatch queryPlatformCatchInfo(String platformGbId);
void delPlatformCatchInfo(String platformGbId);
void updatePlatformRegisterInfo(String callId, PlatformRegisterInfo platformRegisterInfo);
PlatformRegisterInfo queryPlatformRegisterInfo(String callId);
void delPlatformRegisterInfo(String callId);
/**
* 在redis添加wvp的信息
*/
void updateWVPInfo(ServerInfo serverInfo, int time);
void removeOfflineWVPInfo(String serverId);
/**
* 发送推流生成与推流消失消息
* @param jsonObject 消息内容

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.MessageForPushChannel;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.JsonUtil;
import com.genersoft.iot.vmp.utils.SystemInfoUtils;
@ -73,41 +72,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
redisTemplate.opsForValue().set(key, 1);
}
@Override
public void updatePlatformCatchInfo(PlatformCatch parentPlatformCatch) {
String key = VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + parentPlatformCatch.getId();
redisTemplate.opsForValue().set(key, parentPlatformCatch);
}
@Override
public PlatformCatch queryPlatformCatchInfo(String platformGbId) {
return (PlatformCatch)redisTemplate.opsForValue().get(VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + platformGbId);
}
@Override
public void delPlatformCatchInfo(String platformGbId) {
redisTemplate.delete(VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + platformGbId);
}
@Override
public void updatePlatformRegisterInfo(String callId, PlatformRegisterInfo platformRegisterInfo) {
String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId;
Duration duration = Duration.ofSeconds(30L);
redisTemplate.opsForValue().set(key, platformRegisterInfo, duration);
}
@Override
public PlatformRegisterInfo queryPlatformRegisterInfo(String callId) {
return (PlatformRegisterInfo)redisTemplate.opsForValue().get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId);
}
@Override
public void delPlatformRegisterInfo(String callId) {
redisTemplate.delete(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId);
}
@Override
public void updateWVPInfo(ServerInfo serverInfo, int time) {
@ -120,6 +84,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
redisTemplate.opsForZSet().add(setKey, userSetting.getServerId(), System.currentTimeMillis());
}
@Override
public void removeOfflineWVPInfo(String serverId) {
String setKey = VideoManagerConstants.WVP_SERVER_LIST;
// 首次设置就设置为0, 后续值越小说明越是最近启动的
redisTemplate.opsForZSet().remove(setKey, serverId);
}
@Override
public void sendStreamChangeMsg(String type, JSONObject jsonObject) {
String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type;

View File

@ -23,6 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
@SuppressWarnings("rawtypes")
@ -190,12 +193,23 @@ public class StreamProxyController {
@ResponseBody
@Operation(summary = "启用代理", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "id", description = "代理Id", required = true)
public StreamContent start(int id){
public StreamContent start(HttpServletRequest request, int id){
log.info("播放代理: {}", id);
StreamInfo streamInfo = streamProxyPlayService.start(id, null, null);
if (streamInfo == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), ErrorCode.ERROR100.getMsg());
}else {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
return new StreamContent(streamInfo);
}
}

View File

@ -35,8 +35,11 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -245,7 +248,7 @@ public class StreamPushController {
@GetMapping(value = "/start")
@ResponseBody
@Operation(summary = "开始播放", security = @SecurityRequirement(name = JwtUtils.HEADER))
public DeferredResult<WVPResult<StreamContent>> start(Integer id){
public DeferredResult<WVPResult<StreamContent>> start(HttpServletRequest request, Integer id){
Assert.notNull(id, "推流ID不可为NULL");
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
result.onTimeout(()->{
@ -254,6 +257,17 @@ public class StreamPushController {
});
streamPushPlayService.start(id, (code, msg, streamInfo) -> {
if (code == 0 && streamInfo != null) {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
WVPResult<StreamContent> success = WVPResult.success(new StreamContent(streamInfo));
result.setResult(success);
}

View File

@ -91,7 +91,7 @@ public interface StreamPushMapper {
"(#{item.app}, #{item.stream}) " +
"</foreach>" +
")</script>")
List<StreamPush> getListFromRedis(List<StreamPushItemFromRedis> offlineStreams);
List<StreamPush> getListInList(List<StreamPushItemFromRedis> offlineStreams);
@Select("SELECT CONCAT(app,stream) from wvp_stream_push")

View File

@ -39,7 +39,7 @@ public class StreamPushPlayServiceImpl implements IStreamPushPlayService {
@Autowired
private UserSetting userSetting;
@Autowired
private DynamicTask dynamicTask;
@ -57,27 +57,28 @@ public class StreamPushPlayServiceImpl implements IStreamPushPlayService {
StreamPush streamPush = streamPushMapper.queryOne(id);
Assert.notNull(streamPush, "推流信息未找到");
if (!userSetting.getServerId().equals(streamPush.getServerId())) {
redisRpcPlayService.playPush(id, callback);
if (streamPush.isPushing() && !userSetting.getServerId().equals(streamPush.getServerId())) {
redisRpcPlayService.playPush(streamPush.getServerId(), id, callback);
return;
}
MediaServer mediaServer = mediaServerService.getOne(streamPush.getMediaServerId());
Assert.notNull(mediaServer, "节点" + streamPush.getMediaServerId() + "未找到");
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamPush.getApp(), streamPush.getStream());
if (mediaInfo != null) {
String callId = null;
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(streamPush.getApp(), streamPush.getStream());
if (streamAuthorityInfo != null) {
callId = streamAuthorityInfo.getCallId();
if (mediaServer != null) {
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, streamPush.getApp(), streamPush.getStream());
if (mediaInfo != null) {
String callId = null;
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(streamPush.getApp(), streamPush.getStream());
if (streamAuthorityInfo != null) {
callId = streamAuthorityInfo.getCallId();
}
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), mediaServerService.getStreamInfoByAppAndStream(mediaServer,
streamPush.getApp(), streamPush.getStream(), mediaInfo, callId));
if (!streamPush.isPushing()) {
streamPush.setPushing(true);
streamPushMapper.update(streamPush);
}
return;
}
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), mediaServerService.getStreamInfoByAppAndStream(mediaServer,
streamPush.getApp(), streamPush.getStream(), mediaInfo, callId));
if (!streamPush.isPushing()) {
streamPush.setPushing(true);
streamPushMapper.update(streamPush);
}
return;
}
Assert.isTrue(streamPush.isStartOfflinePush(), "通道未推流");
// 发送redis消息以使设备上线流上线后被

View File

@ -458,16 +458,27 @@ public class StreamPushServiceImpl implements IStreamPushService {
@Override
public void offline(List<StreamPushItemFromRedis> offlineStreams) {
// 更新部分设备离线
List<StreamPush> streamPushList = streamPushMapper.getListFromRedis(offlineStreams);
List<StreamPush> streamPushList = streamPushMapper.getListInList(offlineStreams);
if (streamPushList.isEmpty()) {
log.info("[推流设备] 设备离线操作未发现可操作数据。");
return;
}
List<CommonGBChannel> commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList);
gbChannelService.offline(commonGBChannelList);
}
@Override
public void online(List<StreamPushItemFromRedis> onlineStreams) {
if (onlineStreams.isEmpty()) {
log.info("[设备上线] 推流设备列表为空");
return;
}
// 更新部分设备上线streamPushService
List<StreamPush> streamPushList = streamPushMapper.getListFromRedis(onlineStreams);
List<StreamPush> streamPushList = streamPushMapper.getListInList(onlineStreams);
if (streamPushList.isEmpty()) {
for (StreamPushItemFromRedis onlineStream : onlineStreams) {
log.info("[设备上线] 未查询到这些通道: {}/{}", onlineStream.getApp(), onlineStream.getStream());
}
return;
}
List<CommonGBChannel> commonGBChannelList = gbChannelService.queryListByStreamPushList(streamPushList);

View File

@ -195,6 +195,14 @@ public class DateUtil {
}
Instant startInstant = Instant.from(formatter.parse(startTime));
Instant endInstant = Instant.from(formatter.parse(endTime));
return ChronoUnit.MILLIS.between(endInstant, startInstant);
return ChronoUnit.MILLIS.between(startInstant, endInstant);
}
public static void main(String[] args) {
long difference = getDifference("2025-05-21 13:00:00", "2025-05-21 13:30:00")/1000;
System.out.println(difference);
}
}

View File

@ -1,198 +1,48 @@
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
browser: true,
},
extends: ["plugin:vue/essential", "eslint:recommended"],
parserOptions: {
parser: "babel-eslint",
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
// Disable or downgrade problematic rules
"vue/require-prop-types": "off",
"vue/require-default-prop": "off",
"vue/no-unused-vars": "warn",
"no-unused-vars": "warn",
"no-undef": "warn",
eqeqeq: "warn",
"no-return-assign": "warn",
"new-cap": "warn",
"vue/html-self-closing": "off",
"vue/html-closing-bracket-spacing": "off",
"vue/this-in-template": "off",
"vue/require-v-for-key": "warn",
"vue/valid-v-model": "warn",
"vue/attributes-order": "off",
"no-multiple-empty-lines": "warn",
// Style rules - make them warnings instead of errors
quotes: ["warn", "single"],
"comma-dangle": ["warn", "never"],
"space-in-parens": "warn",
"comma-spacing": "warn",
"object-curly-spacing": "warn",
"arrow-spacing": "warn",
semi: ["warn", "never"],
"no-multi-spaces": "warn",
// Turn off console warnings for development
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
globals: {
// Define global variables to prevent 'undefined' errors
ZLMRTCClient: "readonly",
jessibuca: "readonly",
},
}

View File

@ -1,8 +1,8 @@
<template>
<div id="DeviceTree" style="width: 100%;height: 100%; background-color: #FFFFFF; overflow: auto; padding: 30px">
<div style="height: 30px; display: grid; grid-template-columns: auto auto">
<div>通道列表</div>
<div>
<div id="DeviceTree" class="device-tree-container">
<div class="device-tree-header">
<div class="header-title">通道列表</div>
<div class="header-switch">
<el-switch
v-model="showRegion"
active-color="#13ce66"
@ -12,9 +12,27 @@
/>
</div>
</div>
<div>
<RegionTree v-if="showRegion" ref="regionTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" />
<GroupTree v-if="!showRegion" ref="groupTree" :edit="false" :show-header="false" :has-channel="true" :click-event="treeNodeClickEvent" />
<div class="tree-content">
<div class="tree-wrapper">
<RegionTree
v-if="showRegion"
ref="regionTree"
:edit="false"
:show-header="false"
:has-channel="true"
:click-event="treeNodeClickEvent"
:default-expanded-keys="[]"
/>
<GroupTree
v-if="!showRegion"
ref="groupTree"
:edit="false"
:show-header="false"
:has-channel="true"
:click-event="treeNodeClickEvent"
:default-expanded-keys="[]"
/>
</div>
</div>
</div>
</template>
@ -26,7 +44,24 @@ import GroupTree from './GroupTree.vue'
export default {
name: 'DeviceTree',
components: { GroupTree, RegionTree },
props: ['device', 'onlyCatalog', 'clickEvent', 'contextMenuEvent'],
props: {
device: {
type: Object,
default: () => ({})
},
onlyCatalog: {
type: Boolean,
default: false
},
clickEvent: {
type: Function,
default: null
},
contextMenuEvent: {
type: Function,
default: null
}
},
data() {
return {
showRegion: true,
@ -37,15 +72,67 @@ export default {
}
}
},
destroyed() {
// if (this.jessibuca) {
// this.jessibuca.destroy();
// }
// this.playing = false;
// this.loaded = false;
// this.performance = "";
mounted() {
// Apply fix for Element UI tree scrollbars after component is mounted
this.$nextTick(() => {
this.fixTreeScrollbars()
this.adjustTreeHeight()
// Add resize event listener to handle window resizing
window.addEventListener('resize', this.adjustTreeHeight)
})
},
updated() {
// Re-apply fix when component updates (e.g., when switching between RegionTree and GroupTree)
this.$nextTick(() => {
this.fixTreeScrollbars()
this.adjustTreeHeight()
})
},
beforeDestroy() {
// Remove event listener when component is destroyed
window.removeEventListener('resize', this.adjustTreeHeight)
},
methods: {
adjustTreeHeight() {
// Get the container height
const containerHeight = this.$el.clientHeight
// Get the header height
const headerHeight = this.$el.querySelector('.device-tree-header').clientHeight
// Calculate available height for tree
const availableHeight = containerHeight - headerHeight - 30 // 30px for padding
// Set the tree content height
const treeContent = this.$el.querySelector('.tree-content')
if (treeContent) {
treeContent.style.height = `${availableHeight}px`
}
// Ensure tree components adapt to the available height
const treeComponents = this.$el.querySelectorAll('.el-tree')
treeComponents.forEach(tree => {
tree.style.height = '100%'
tree.style.maxHeight = '100%'
})
},
fixTreeScrollbars() {
// Find all el-tree elements within this component and fix their scrolling behavior
const trees = this.$el.querySelectorAll('.el-tree')
trees.forEach(tree => {
tree.style.overflow = 'visible'
tree.style.width = '100%'
// Also fix any scrollable containers within the tree
const scrollContainers = tree.querySelectorAll('[style*="overflow"]')
scrollContainers.forEach(container => {
if (container.style.overflow === 'auto' || container.style.overflow === 'scroll') {
container.style.overflow = 'visible'
}
})
})
},
handleClick: function(tab, event) {
},
treeNodeClickEvent: function(data) {
@ -62,13 +149,130 @@ export default {
</script>
<style>
.device-tree-main-box{
.device-tree-container {
width: 100%;
height: 100%;
background-color: #FFFFFF;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 15px;
overflow: hidden !important; /* Force no overflow on container */
}
.device-tree-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 10px;
min-height: 30px;
}
.header-title {
font-size: 16px;
font-weight: 500;
}
.tree-content {
flex: 1;
overflow: hidden !important;
width: 100%;
margin: 0;
padding: 0;
position: relative;
}
.tree-wrapper {
width: 100%;
height: 100%;
min-width: 0; /* Prevent flex items from overflowing */
position: relative;
}
/* Global fixes for Element UI tree components */
.el-tree {
overflow: visible !important;
width: 100% !important;
min-width: 0 !important;
height: 100% !important;
}
.el-tree-node {
width: 100% !important;
min-width: 0 !important;
}
.el-tree-node__content {
width: 100% !important;
min-width: 0 !important;
}
.el-tree-node__label {
word-break: break-word !important;
white-space: normal !important;
}
/* Fix for any scrollable containers */
[style*="overflow: auto"],
[style*="overflow:auto"],
[style*="overflow: scroll"],
[style*="overflow:scroll"] {
overflow: visible !important;
}
/* Make sure tree nodes are fully visible */
.el-tree-node__children {
overflow: visible !important;
}
/* Ensure tree nodes can be expanded/collapsed */
.el-tree-node__expand-icon {
cursor: pointer;
}
.device-tree-main-box {
text-align: left;
}
.device-online{
.device-online {
color: #252525;
}
.device-offline{
.device-offline {
color: #727272;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.device-tree-container {
padding: 10px;
}
.device-tree-header {
flex-direction: column;
align-items: flex-start;
}
.header-switch {
width: 100%;
margin-top: 5px;
}
}
@media (max-width: 480px) {
.device-tree-container {
padding: 8px;
}
.header-title {
font-size: 14px;
}
/* Adjust el-switch text size for mobile */
.el-switch__label {
font-size: 12px;
}
}
</style>

View File

@ -1,25 +1,26 @@
<template>
<div id="live" style="height: calc(100vh - 124px)">
<div v-loading="loading" style="height: 100%; display: grid; grid-template-columns: 400px auto" element-loading-text="拼命加载中">
<div style="background-color: #ffffff">
<div id="live" class="live-container">
<div v-loading="loading" class="live-content" element-loading-text="拼命加载中">
<div class="device-tree-container">
<DeviceTree :click-event="clickEvent" :context-menu-event="contextMenuEvent" />
</div>
<div style="display: grid; grid-template-rows: 5vh auto">
<div style="font-size: 17px;line-height:5vh; display: grid; grid-template-columns: 1fr 1fr">
<div style="text-align: left">
<div class="video-container">
<div class="control-bar">
<div class="split-controls">
分屏:
<i class="iconfont icon-a-mti-1fenpingshi btn" :class="{active:spiltIndex === 0}" @click="spiltIndex=0" />
<i class="iconfont icon-a-mti-4fenpingshi btn" :class="{active: spiltIndex === 1}" @click="spiltIndex=1" />
<i class="iconfont icon-a-mti-6fenpingshi btn" :class="{active: spiltIndex === 2}" @click="spiltIndex=2" />
<i class="iconfont icon-a-mti-9fenpingshi btn" :class="{active: spiltIndex === 3}" @click="spiltIndex=3" />
</div>
<div style="text-align: right; margin-right: 10px;">
<div class="fullscreen-control">
<i class="el-icon-full-screen btn" @click="fullScreen()" />
</div>
</div>
<div style="padding: 0; margin: 0 auto;">
<div class="player-container">
<div
ref="playBox"
class="play-grid"
:style="liveStyle"
>
<div
@ -29,7 +30,7 @@
:class="getPlayerClass(spiltIndex, i)"
@click="playerIdx = (i-1)"
>
<div v-if="!videoUrl[i-1]" style="color: #ffffff;font-size: 15px;font-weight: bold;">{{ videoTip[i-1]?videoTip[i-1]:"无信号" }}</div>
<div v-if="!videoUrl[i-1]" class="no-signal">{{ videoTip[i-1]?videoTip[i-1]:"无信号" }}</div>
<player
v-else
:ref="'player'[i-1]"
@ -111,21 +112,13 @@ export default {
computed: {
liveStyle() {
if (!this.$store.getters.sidebar.opened) {
return { width: '151vh', height: '85vh', display: 'grid', gridTemplateColumns: this.layout[this.spiltIndex].columns,
gridTemplateRows: this.layout[this.spiltIndex].rows, gap: '4px', backgroundColor: '#a9a8a8' }
} else {
return { width: '140vh', height: '79vh', display: 'grid', gridTemplateColumns: this.layout[this.spiltIndex].columns,
gridTemplateRows: this.layout[this.spiltIndex].rows, gap: '4px', backgroundColor: '#a9a8a8' }
return {
display: 'grid',
gridTemplateColumns: this.layout[this.spiltIndex].columns,
gridTemplateRows: this.layout[this.spiltIndex].rows,
gap: '4px',
backgroundColor: '#a9a8a8'
}
// this.$nextTick(() => {
// for (let i = 0; i < this.spilt; i++) {
// const player = this.$refs.player
// player && player[i] && player[i].updatePlayerDomSize()
// }
// })
// return style
}
},
watch: {
@ -149,15 +142,37 @@ export default {
'$route.fullPath': 'checkPlayByParam'
},
mounted() {
// Add window resize event listener to handle responsive behavior
window.addEventListener('resize', this.handleResize)
this.handleResize()
},
created() {
this.checkPlayByParam()
},
destroyed() {
clearTimeout(this.updateLooper)
// Remove event listener when component is destroyed
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
// Force update to recalculate responsive layout
this.$forceUpdate()
// Resize any active players
this.$nextTick(() => {
for (let i = 0; i < this.layout[this.spiltIndex].spilt; i++) {
const playerRef = this.$refs[`player${i + 1}`]
if (playerRef) {
if (playerRef instanceof Array) {
playerRef[0].resize && playerRef[0].resize()
} else {
playerRef.resize && playerRef.resize()
}
}
}
})
},
destroy(idx) {
console.log(idx)
this.clear(idx.substring(idx.length - 1))
@ -212,8 +227,6 @@ export default {
}
},
shot(e) {
// console.log(e)
// send({code:'image',data:e})
var base64ToBlob = function(code) {
const parts = code.split(';base64,')
const contentType = parts[0].split(':')[1]
@ -257,9 +270,86 @@ export default {
}
</script>
<style>
.live-container {
height: calc(100vh - 124px);
width: 100%;
}
.live-content {
height: 100%;
display: flex;
flex-direction: row;
}
.device-tree-container {
width: 300px;
min-width: 250px;
max-width: 400px;
background-color: #ffffff;
overflow: auto;
resize: horizontal;
}
@media (max-width: 768px) {
.live-content {
flex-direction: column;
}
.device-tree-container {
width: 100%;
max-width: 100%;
height: 200px;
min-height: 150px;
max-height: 300px;
resize: vertical;
}
}
.video-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.control-bar {
height: 5vh;
min-height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 17px;
}
.split-controls {
text-align: left;
padding-left: 10px;
}
.fullscreen-control {
text-align: right;
padding-right: 10px;
}
.player-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
overflow: hidden;
}
.play-grid {
width: 100%;
height: 100%;
max-height: calc(100vh - 180px);
aspect-ratio: 16/9;
}
.btn {
margin: 0 10px;
cursor: pointer;
}
.btn:hover {
@ -268,7 +358,6 @@ export default {
.btn.active {
color: #409EFF;
}
.redborder {
@ -280,13 +369,39 @@ export default {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.no-signal {
color: #ffffff;
font-size: 15px;
font-weight: bold;
}
.play-box-2-1 {
grid-column: 1 / span 2;
grid-row: 1 / span 2;
}
</style>
<style>
/* Responsive adjustments for smaller screens */
@media (max-width: 576px) {
.control-bar {
flex-direction: column;
height: auto;
padding: 5px 0;
}
.split-controls, .fullscreen-control {
width: 100%;
text-align: center;
padding: 5px 0;
}
.btn {
margin: 0 5px;
}
}
.videoList {
display: flex;
flex-wrap: wrap;

View File

@ -1,12 +1,11 @@
'use strict'
const path = require('path')
const defaultSettings = require('./src/settings.js')
const path = require("path")
const defaultSettings = require("./src/settings.js")
function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title || 'WVP视频平台' // page title
const name = defaultSettings.title || "WVP视频平台" // page title
// If your port is set to 80,
// use administrator privileges to execute the command line.
@ -24,37 +23,37 @@ module.exports = {
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
publicPath: '/',
outputDir: '../src/main/resources/static',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
publicPath: "/",
outputDir: "../src/main/resources/static",
assetsDir: "static",
lintOnSave: false, // Disable ESLint to avoid warnings
productionSourceMap: false,
devServer: {
public: 'localhost:' + port,
host: 'localhost',
public: "localhost:" + port,
host: "localhost",
port: port,
open: true,
overlay: {
warnings: false,
errors: true
errors: false, // Changed to false to hide ESLint errors
},
// before: require('./mock/mock-server.js'),
proxy: {
'/dev-api': {
target: 'http://127.0.0.1:18080',
"/dev-api": {
target: "http://127.0.0.1:18080",
changeOrigin: true,
pathRewrite: {
'^/dev-api': '/'
}
"^/dev-api": "/",
},
},
'/static/snap': {
target: 'http://127.0.0.1:18080',
changeOrigin: true
"/static/snap": {
target: "http://127.0.0.1:18080",
changeOrigin: true,
// pathRewrite: {
// '^/static/snap': '/static/snap'
// }
}
}
},
},
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
@ -62,80 +61,75 @@ module.exports = {
name: name,
resolve: {
alias: {
'@': resolve('src')
}
}
"@": resolve("src"),
},
},
},
chainWebpack(config) {
// it can improve the speed of the first screen, it is recommended to turn on preload
config.plugin('preload').tap(() => [
config.plugin("preload").tap(() => [
{
rel: 'preload',
rel: "preload",
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial'
}
include: "initial",
},
])
// when there are many pages, it will cause too many meaningless requests
config.plugins.delete('prefetch')
config.plugins.delete("prefetch")
// set svg-sprite-loader
config.module.rule("svg").exclude.add(resolve("src/icons")).end()
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.rule("icons")
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.include.add(resolve("src/icons"))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: 'icon-[name]'
symbolId: "icon-[name]",
})
.end()
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
config.when(process.env.NODE_ENV !== "development", (config) => {
config
.plugin("ScriptExtHtmlWebpackPlugin")
.after("html")
.use("script-ext-html-webpack-plugin", [
{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk('single')
}
)
}
inline: /runtime\..*\.js$/,
},
])
.end()
config.optimization.splitChunks({
chunks: "all",
cacheGroups: {
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial", // only package third parties that are initially dependent
},
elementUI: {
name: "chunk-elementUI", // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
},
commons: {
name: "chunk-commons",
test: resolve("src/components"), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true,
},
},
})
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk("single")
})
},
}

View File

@ -89,15 +89,21 @@ Vue.prototype.$channelTypeList = {
new Vue({
beforeCreate: function () {
// 获取本平台的服务ID
axios({
method: 'get',
url: `/api/server/system/configInfo`,
}).then( (res)=> {
if (res.data.code === 0) {
Vue.prototype.$myServerId = res.data.data.addOn.serverId;
}
}).catch( (error)=> {
});
console.log("获取本平台的服务ID")
if (!this.$myServerId) {
axios({
method: 'get',
url: `/api/server/system/configInfo`,
}).then( (res)=> {
if (res.data.code === 0) {
console.log(res.data)
console.log("当前服务ID " + res.data.data.addOn.serverId)
Vue.prototype.$myServerId = res.data.data.addOn.serverId;
}
}).catch( (error)=> {
});
}
},
router: router,
render: h => h(App),