648540858
2023-03-28 91bfbc36f10382434a9e8c79eeeaf07a531b08b5
优化设备注册,支持到期续订,优化国标级联到期续订。
19个文件已修改
295 ■■■■■ 已修改文件
pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -11,7 +11,7 @@
    <groupId>com.genersoft</groupId>
    <artifactId>wvp-pro</artifactId>
    <version>2.6.7</version>
    <version>2.6.8</version>
    <name>web video platform</name>
    <description>国标28181视频平台</description>
    <packaging>${project.packaging}</packaging>
src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
@@ -40,17 +40,20 @@
        List<ParentPlatform> parentPlatforms = storager.queryEnableParentPlatformList(true);
        for (ParentPlatform parentPlatform : parentPlatforms) {
            ParentPlatformCatch parentPlatformCatchOld = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
            // 更新缓存
            ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch();
            parentPlatformCatch.setParentPlatform(parentPlatform);
            parentPlatformCatch.setId(parentPlatform.getServerGBId());
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
            // 设置所有平台离线
            platformService.offline(parentPlatform, true);
            // 取消订阅
            sipCommanderForPlatform.unregister(parentPlatform, null, (eventResult)->{
            sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{
                platformService.login(parentPlatform);
            });
            // 设置所有平台离线
            platformService.offline(parentPlatform, true);
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
@@ -191,6 +191,9 @@
    @Schema(description = "是否作为消息通道")
    private boolean asMessageChannel;
    @Schema(description = "设备注册的事务信息")
    private SipTransactionInfo sipTransactionInfo;
    public String getDeviceId() {
        return deviceId;
@@ -439,4 +442,12 @@
    public void setAsMessageChannel(boolean asMessageChannel) {
        this.asMessageChannel = asMessageChannel;
    }
    public SipTransactionInfo getSipTransactionInfo() {
        return sipTransactionInfo;
    }
    public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
        this.sipTransactionInfo = sipTransactionInfo;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java
@@ -16,6 +16,8 @@
    private ParentPlatform parentPlatform;
    private SipTransactionInfo sipTransactionInfo;
    public String getId() {
        return id;
    }
@@ -55,4 +57,12 @@
    public void setCallId(String callId) {
        this.callId = callId;
    }
    public SipTransactionInfo getSipTransactionInfo() {
        return sipTransactionInfo;
    }
    public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
        this.sipTransactionInfo = sipTransactionInfo;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
@@ -63,7 +63,7 @@
            if (deviceService.expire(device)){
                deviceService.offline(device.getDeviceId(), "注册已过期");
            }else {
                deviceService.online(device);
                deviceService.online(device, null);
            }
        }
        // 重置cseq计数
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
@@ -18,14 +18,16 @@
     * @return
     */
    void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
    void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException;
    void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
    void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException;
    /**
     * 向上级平台注销
     * @param parentPlatform
     * @return
     */
    void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
    void unregister(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
    /**
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
@@ -14,7 +14,8 @@
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import javax.sip.*;
import javax.sip.InvalidArgumentException;
import javax.sip.PeerUnavailableException;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.*;
@@ -22,7 +23,6 @@
import javax.validation.constraints.NotNull;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
@@ -45,7 +45,7 @@
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, long CSeq, String fromTag, String viaTag, CallIdHeader callIdHeader, boolean isRegister) throws ParseException, InvalidArgumentException, PeerUnavailableException {
    public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, long CSeq, String fromTag, String toTag, CallIdHeader callIdHeader, boolean isRegister) throws ParseException, InvalidArgumentException, PeerUnavailableException {
        Request request = null;
        String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort();
        //请求行
@@ -53,7 +53,8 @@
                parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
        //via
        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(), parentPlatform.getServerPort(), parentPlatform.getTransport(), viaTag);
        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(),
                parentPlatform.getServerPort(), parentPlatform.getTransport(), SipUtils.getNewViaTag());
        viaHeader.setRPort();
        viaHeaders.add(viaHeader);
        //from
@@ -63,7 +64,7 @@
        //to
        SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
        Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
        ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
        ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,toTag);
        //Forwards
        MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
@@ -85,11 +86,11 @@
        return request;
    }
    public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, String fromTag, String viaTag,
                                         String callId, WWWAuthenticateHeader www , CallIdHeader callIdHeader, boolean isRegister) throws ParseException, PeerUnavailableException, InvalidArgumentException {
    public Request createRegisterRequest(@NotNull ParentPlatform parentPlatform, String fromTag, String toTag,
                                         WWWAuthenticateHeader www , CallIdHeader callIdHeader, boolean isRegister) throws ParseException, PeerUnavailableException, InvalidArgumentException {
        Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, viaTag, callIdHeader, isRegister);
        Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, toTag, callIdHeader, isRegister);
        SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
        if (www == null) {
            AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader("Digest");
@@ -106,8 +107,6 @@
        // 参考 https://blog.csdn.net/y673533511/article/details/88388138
        // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
        String qop = www.getQop();
        callIdHeader.setCallId(callId);
        String cNonce = null;
        String nc = "00000001";
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
@@ -75,20 +75,40 @@
    }
    @Override
    public void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
        register(parentPlatform, null, null, errorEvent, okEvent, false, false);
    public void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
        register(parentPlatform, sipTransactionInfo, null, errorEvent, okEvent, false, true);
    }
    @Override
    public void register(ParentPlatform parentPlatform, @Nullable String callId, @Nullable WWWAuthenticateHeader www,
    public void unregister(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
        register(parentPlatform, sipTransactionInfo, null, errorEvent, okEvent, false, false);
    }
    @Override
    public void register(ParentPlatform parentPlatform, @Nullable SipTransactionInfo sipTransactionInfo, @Nullable WWWAuthenticateHeader www,
                            SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException {
            Request request;
            if (!registerAgain ) {
                CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
            CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
            String fromTag = SipUtils.getNewFromTag();
            String toTag = null;
            if (sipTransactionInfo != null ) {
                if (sipTransactionInfo.getCallId() != null) {
                    callIdHeader.setCallId(sipTransactionInfo.getCallId());
                }
                if (sipTransactionInfo.getFromTag() != null) {
                    fromTag = sipTransactionInfo.getFromTag();
                }
                if (sipTransactionInfo.getToTag() != null) {
                    toTag = sipTransactionInfo.getToTag();
                }
            }
            if (!registerAgain ) {
                request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform,
                        redisCatchStorage.getCSEQ(), SipUtils.getNewFromTag(),
                        SipUtils.getNewViaTag(), callIdHeader, isRegister);
                        redisCatchStorage.getCSEQ(), fromTag,
                        toTag, callIdHeader, isRegister);
                // 将 callid 写入缓存, 等注册成功可以更新状态
                String callIdFromHeader = callIdHeader.getCallId();
                redisCatchStorage.updatePlatformRegisterInfo(callIdFromHeader, PlatformRegisterInfo.getInstance(parentPlatform.getServerGBId(), isRegister));
@@ -106,8 +126,7 @@
                });
            }else {
                CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
                request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, SipUtils.getNewFromTag(), null, callId, www, callIdHeader, isRegister);
                request = headerProviderPlatformProvider.createRegisterRequest(parentPlatform, fromTag, toTag, www, callIdHeader, isRegister);
            }
            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, okEvent);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
@@ -5,6 +5,7 @@
import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@@ -18,6 +19,7 @@
import gov.nist.javax.sip.address.SipUri;
import gov.nist.javax.sip.header.SIPDateHeader;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
@@ -31,6 +33,7 @@
import javax.sip.header.ContactHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
@@ -102,6 +105,30 @@
            SipUri uri = (SipUri) address.getURI();
            String deviceId = uri.getUser();
            Device device = deviceService.getDevice(deviceId);
            RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request,
                    userSetting.getSipUseSourceIpAsRemoteAddress());
            if (device != null &&
                device.getSipTransactionInfo() != null &&
                request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {
                logger.info("[注册请求] 注册续订: {}", device.getDeviceId());
                device.setExpires(request.getExpires().getExpires());
                device.setIp(remoteAddressInfo.getIp());
                device.setPort(remoteAddressInfo.getPort());
                device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
                device.setLocalIp(request.getLocalAddress().getHostAddress());
                Response registerOkResponse = getRegisterOkResponse(request);
                // 判断TCP还是UDP
                ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
                String transport = reqViaHeader.getTransport();
                device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
                sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
                device.setRegisterTime(DateUtil.getNow());
                SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)registerOkResponse);
                deviceService.online(device, sipTransactionInfo);
                return;
            }
            String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword();
            AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
            if (authHead == null && !ObjectUtils.isEmpty(password)) {
@@ -144,9 +171,6 @@
            // 添加Expires头
            response.addHeader(request.getExpires());
            RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request,
                    userSetting.getSipUseSourceIpAsRemoteAddress());
            if (device == null) {
                device = new Device();
                device.setStreamMode("UDP");
@@ -179,7 +203,8 @@
            if (registerFlag) {
                logger.info("[注册成功] deviceId: {}->{}",  deviceId, requestAddress);
                device.setRegisterTime(DateUtil.getNow());
                deviceService.online(device);
                SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)response);
                deviceService.online(device, sipTransactionInfo);
            } else {
                logger.info("[注销成功] deviceId: {}->{}" ,deviceId, requestAddress);
                deviceService.offline(deviceId, "主动注销");
@@ -188,4 +213,23 @@
            logger.error("未处理的异常 ", e);
        }
    }
    private Response getRegisterOkResponse(Request request) throws ParseException {
        // 携带授权头并且密码正确
        Response  response = getMessageFactory().createResponse(Response.OK, request);
        // 添加date头
        SIPDateHeader dateHeader = new SIPDateHeader();
        // 使用自己修改的
        WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
        dateHeader.setDate(wvpSipDate);
        response.addHeader(dateHeader);
        // 添加Contact头
        response.addHeader(request.getHeader(ContactHeader.NAME));
        // 添加Expires头
        response.addHeader(request.getExpires());
        return response;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java
@@ -73,35 +73,38 @@
        String channelId = getText(rootElement, "DeviceID");
        // 远程启动功能
        if (!ObjectUtils.isEmpty(getText(rootElement, "TeleBoot"))) {
            if (parentPlatform.getServerGBId().equals(targetGBId)) {
                // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
                logger.info("执行远程启动本平台命令");
                try {
                    cmderFroPlatform.unregister(parentPlatform, null, null);
                } catch (InvalidArgumentException | ParseException | SipException e) {
                    logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
                }
                taskExecutor.execute(() -> {
                    // 远程启动
//                    try {
//                        Thread.sleep(3000);
//                        SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
//                        SipStackImpl stack = (SipStackImpl)up.getSipStack();
//                        stack.stop();
//                        Iterator listener = stack.getListeningPoints();
//                        while (listener.hasNext()) {
//                            stack.deleteListeningPoint((ListeningPoint) listener.next());
//                        }
//                        Iterator providers = stack.getSipProviders();
//                        while (providers.hasNext()) {
//                            stack.deleteSipProvider((SipProvider) providers.next());
//                        }
//                        VManageBootstrap.restart();
//                    } catch (InterruptedException | ObjectInUseException e) {
//                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
//                    }
                });
            }
            // TODO 拒绝远程启动命令
            logger.warn("[国标级联]收到平台的远程启动命令, 不处理");
//            if (parentPlatform.getServerGBId().equals(targetGBId)) {
//                // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
//                logger.info("执行远程启动本平台命令");
//                try {
//                    cmderFroPlatform.unregister(parentPlatform, null, null);
//                } catch (InvalidArgumentException | ParseException | SipException e) {
//                    logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
//                }
//                taskExecutor.execute(() -> {
//                    // 远程启动
////                    try {
////                        Thread.sleep(3000);
////                        SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
////                        SipStackImpl stack = (SipStackImpl)up.getSipStack();
////                        stack.stop();
////                        Iterator listener = stack.getListeningPoints();
////                        while (listener.hasNext()) {
////                            stack.deleteListeningPoint((ListeningPoint) listener.next());
////                        }
////                        Iterator providers = stack.getSipProviders();
////                        while (providers.hasNext()) {
////                            stack.deleteSipProvider((SipProvider) providers.next());
////                        }
////                        VManageBootstrap.restart();
////                    } catch (InterruptedException | ObjectInUseException e) {
////                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
////                    }
//                });
//            }
        }
        DeviceControlType deviceControlType = DeviceControlType.typeOf(rootElement);
        logger.info("[接受deviceControl命令] 命令: {}", deviceControlType);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
@@ -88,7 +88,7 @@
            // 对于已经离线的设备判断他的注册是否已经过期
            if (!deviceService.expire(device)){
                device.setOnline(0);
                deviceService.online(device);
                deviceService.online(device, null);
            }
        }
        // 刷新过期任务
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
@@ -71,7 +71,7 @@
        }
        String text = onlineElement.getText();
        if ("ONLINE".equalsIgnoreCase(text.trim())) {
            deviceService.online(device);
            deviceService.online(device, null);
        }else {
            deviceService.offline(device.getDeviceId(), "设备状态查询结果:" + text.trim());
        }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
@@ -2,6 +2,7 @@
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@@ -10,6 +11,7 @@
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
import gov.nist.javax.sip.message.SIPResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,7 +20,6 @@
import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.header.CallIdHeader;
import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.message.Response;
import java.text.ParseException;
@@ -65,9 +66,8 @@
     */
    @Override
    public void process(ResponseEvent evt) {
        Response response = evt.getResponse();
        CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
        String callId = callIdHeader.getCallId();
        SIPResponse response = (SIPResponse)evt.getResponse();
        String callId = response.getCallIdHeader().getCallId();
        PlatformRegisterInfo platformRegisterInfo = redisCatchStorage.queryPlatformRegisterInfo(callId);
        if (platformRegisterInfo == null) {
            logger.info(String.format("[国标级联]未找到callId: %s 的注册/注销平台id", callId ));
@@ -90,15 +90,17 @@
        if (response.getStatusCode() == Response.UNAUTHORIZED) {
            WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
            SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response);
            try {
                sipCommanderForPlatform.register(parentPlatform, callId, www, null, null, true, platformRegisterInfo.isRegister());
                sipCommanderForPlatform.register(parentPlatform, sipTransactionInfo, www, null, null, true, platformRegisterInfo.isRegister());
            } catch (SipException | InvalidArgumentException | ParseException e) {
                logger.error("[命令发送失败] 国标级联 再次注册: {}", e.getMessage());
            }
        }else if (response.getStatusCode() == Response.OK){
            if (platformRegisterInfo.isRegister()) {
                platformService.online(parentPlatform);
                SipTransactionInfo sipTransactionInfo = new SipTransactionInfo(response);
                platformService.online(parentPlatform, sipTransactionInfo);
            }else {
                platformService.offline(parentPlatform, false);
            }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -116,7 +116,7 @@
    @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
    public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
        logger.info("[ZLM HOOK] 收到zlm心跳:" + param.getMediaServerId());
//        logger.info("[ZLM HOOK] 收到zlm心跳:" + param.getMediaServerId());
        taskExecutor.execute(() -> {
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java
@@ -2,6 +2,7 @@
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
import com.genersoft.iot.vmp.vmanager.bean.BaseTree;
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaceInfo;
@@ -18,7 +19,7 @@
     * 设备上线
     * @param device 设备信息
     */
    void online(Device device);
    void online(Device device, SipTransactionInfo sipTransactionInfo);
    /**
     * 设备下线
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
@@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.github.pagehelper.PageInfo;
/**
@@ -35,7 +36,7 @@
     * 平台上线
     * @param parentPlatform 平台信息
     */
    void online(ParentPlatform parentPlatform);
    void online(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo);
    /**
     * 平台离线
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
@@ -89,7 +89,7 @@
    private IMediaServerService mediaServerService;
    @Override
    public void online(Device device) {
    public void online(Device device, SipTransactionInfo sipTransactionInfo) {
        logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
        Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId());
        Device deviceInDb = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
@@ -104,6 +104,14 @@
            // 默认心跳间隔60
            device.setKeepaliveIntervalTime(60);
        }
        if (sipTransactionInfo != null) {
            device.setSipTransactionInfo(sipTransactionInfo);
        }else {
            if (deviceInRedis != null) {
                device.setSipTransactionInfo(deviceInRedis.getSipTransactionInfo());
            }
        }
        // 第一次上线 或则设备之前是离线状态--进行通道同步和设备信息查询
        if (device.getCreateTime() == null) {
            device.setOnline(1);
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
@@ -123,8 +123,10 @@
    @Override
    public boolean update(ParentPlatform parentPlatform) {
        logger.info("[国标级联]更新平台 {}", parentPlatform.getDeviceGBId());
        parentPlatform.setCharacterSet(parentPlatform.getCharacterSet().toUpperCase());
        ParentPlatform parentPlatformOld = platformMapper.getParentPlatById(parentPlatform.getId());
        ParentPlatformCatch parentPlatformCatchOld = redisCatchStorage.queryPlatformCatchInfo(parentPlatformOld.getServerGBId());
        parentPlatform.setUpdateTime(DateUtil.getNow());
        if (!parentPlatformOld.getTreeType().equals(parentPlatform.getTreeType())) {
            // 目录结构发生变化,清空之前的关联关系
@@ -134,6 +136,7 @@
            platformGbStreamMapper.delByPlatformId(parentPlatformOld.getServerGBId());
        }
        // 停止心跳定时
        final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + parentPlatformOld.getServerGBId();
        dynamicTask.stop(keepaliveTaskKey);
@@ -142,9 +145,13 @@
        dynamicTask.stop(registerTaskKey);
        // 注销旧的
        try {
            commanderForPlatform.unregister(parentPlatformOld, null, eventResult -> {
                logger.info("[国标级联] 注销成功, 平台:{}", parentPlatformOld.getServerGBId());
            });
            if (parentPlatformOld.isStatus()) {
                logger.info("保存平台{}时发现救平台在线,发送注销命令", parentPlatform.getDeviceGBId());
                commanderForPlatform.unregister(parentPlatformOld, parentPlatformCatchOld.getSipTransactionInfo(), null, eventResult -> {
                    logger.info("[国标级联] 注销成功, 平台:{}", parentPlatformOld.getServerGBId());
                });
            }
        } catch (InvalidArgumentException | ParseException | SipException e) {
            logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
        }
@@ -185,36 +192,36 @@
    @Override
    public void online(ParentPlatform parentPlatform) {
        logger.info("[国标级联]:{}, 平台上线/更新注册", parentPlatform.getServerGBId());
    public void online(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo) {
        logger.info("[国标级联]:{}, 平台上线", parentPlatform.getServerGBId());
        platformMapper.updateParentPlatformStatus(parentPlatform.getServerGBId(), true);
        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
        if (parentPlatformCatch != null) {
            parentPlatformCatch.getParentPlatform().setStatus(true);
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        }else {
        if (parentPlatformCatch == null) {
            parentPlatformCatch = new ParentPlatformCatch();
            parentPlatformCatch.setParentPlatform(parentPlatform);
            parentPlatformCatch.setId(parentPlatform.getServerGBId());
            parentPlatform.setStatus(true);
            parentPlatformCatch.setParentPlatform(parentPlatform);
            redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        }
        parentPlatformCatch.getParentPlatform().setStatus(true);
        parentPlatformCatch.setSipTransactionInfo(sipTransactionInfo);
        redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
        final String registerTaskKey = REGISTER_KEY_PREFIX + parentPlatform.getServerGBId();
        if (!dynamicTask.isAlive(registerTaskKey)) {
            logger.info("[国标级联]:{}, 添加定时注册任务", parentPlatform.getServerGBId());
            // 添加注册任务
            dynamicTask.startCron(registerTaskKey,
                // 注册失败(注册成功时由程序直接调用了online方法)
                ()-> {
                    registerTask(parentPlatform);
                },
                (parentPlatform.getExpires()) *1000);
                ()-> registerTask(parentPlatform, sipTransactionInfo),
                    parentPlatform.getExpires() * 1000);
        }
        final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + parentPlatform.getServerGBId();
        if (!dynamicTask.contains(keepaliveTaskKey)) {
            logger.info("[国标级联]:{}, 添加定时心跳任务", parentPlatform.getServerGBId());
            // 添加心跳任务
            dynamicTask.startCron(keepaliveTaskKey,
                    ()-> {
@@ -259,7 +266,7 @@
        }
    }
    private void registerTask(ParentPlatform parentPlatform){
    private void registerTask(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo){
        try {
            // 设置超时重发, 后续从底层支持消息重发
            String key = KEEPALIVE_KEY_PREFIX + parentPlatform.getServerGBId() + "_timeout";
@@ -267,10 +274,10 @@
                return;
            }
            dynamicTask.startDelay(key, ()->{
                registerTask(parentPlatform);
                registerTask(parentPlatform, sipTransactionInfo);
            }, 1000);
            logger.info("[国标级联] 平台:{}注册即将到期,重新注册", parentPlatform.getServerGBId());
            commanderForPlatform.register(parentPlatform, eventResult -> {
            logger.info("[国标级联] 平台:{}注册即将到期,开始续订", parentPlatform.getServerGBId());
            commanderForPlatform.register(parentPlatform, sipTransactionInfo,  eventResult -> {
                dynamicTask.stop(key);
                offline(parentPlatform, false);
            },eventResult -> {
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java
@@ -7,6 +7,7 @@
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.PlatformCatalog;
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@@ -229,12 +230,16 @@
            throw new ControllerException(ErrorCode.ERROR400);
        }
        ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(serverGBId);
        ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(serverGBId);
        if (parentPlatform == null) {
            throw new ControllerException(ErrorCode.ERROR100.getCode(), "平台不存在");
        }
        if (parentPlatformCatch == null) {
            throw new ControllerException(ErrorCode.ERROR100.getCode(), "平台不存在");
        }
        // 发送离线消息,无论是否成功都删除缓存
        try {
            commanderForPlatform.unregister(parentPlatform, (event -> {
            commanderForPlatform.unregister(parentPlatform, parentPlatformCatch.getSipTransactionInfo(), (event -> {
                // 清空redis缓存
                redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
                redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());