Commit 08c2fa45f7f5e6626f83270363a0d16f7d752d4c

Authored by 648540858
Committed by GitHub
2 parents e3be7963 bea5b40b

Merge pull request #771 from mrjackwang/wvp-28181-2.0

修复历史录像下载问题,查询历史录像问题
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -122,7 +122,7 @@ public interface ISIPCommander { @@ -122,7 +122,7 @@ public interface ISIPCommander {
122 */ 122 */
123 void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, 123 void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
124 String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, 124 String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
125 - SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; 125 + SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
126 126
127 /** 127 /**
128 * 视频流停止 128 * 视频流停止
@@ -221,7 +221,6 @@ public interface ISIPCommander { @@ -221,7 +221,6 @@ public interface ISIPCommander {
221 * 221 *
222 * @param device 视频设备 222 * @param device 视频设备
223 * @param channelId 通道id,非通道则是设备本身 223 * @param channelId 通道id,非通道则是设备本身
224 - * @param frontCmd 上级平台的指令,如果存在则直接下发  
225 * @param enabled 看守位使能:1 = 开启,0 = 关闭 224 * @param enabled 看守位使能:1 = 开启,0 = 关闭
226 * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) 225 * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s)
227 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 226 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -29,7 +29,6 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -29,7 +29,6 @@ import org.springframework.beans.factory.annotation.Autowired;
29 import org.springframework.context.annotation.DependsOn; 29 import org.springframework.context.annotation.DependsOn;
30 import org.springframework.stereotype.Component; 30 import org.springframework.stereotype.Component;
31 import org.springframework.util.ObjectUtils; 31 import org.springframework.util.ObjectUtils;
32 -import org.springframework.util.StringUtils;  
33 32
34 import javax.sip.InvalidArgumentException; 33 import javax.sip.InvalidArgumentException;
35 import javax.sip.ResponseEvent; 34 import javax.sip.ResponseEvent;
@@ -471,8 +470,9 @@ public class SIPCommander implements ISIPCommander { @@ -471,8 +470,9 @@ public class SIPCommander implements ISIPCommander {
471 */ 470 */
472 @Override 471 @Override
473 public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, 472 public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
474 - String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,  
475 - SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { 473 + String startTime, String endTime, int downloadSpeed,
  474 + InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
  475 + SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
476 476
477 logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); 477 logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
478 String sdpIp; 478 String sdpIp;
@@ -541,11 +541,14 @@ public class SIPCommander implements ISIPCommander { @@ -541,11 +541,14 @@ public class SIPCommander implements ISIPCommander {
541 content.append("a=downloadspeed:" + downloadSpeed + "\r\n"); 541 content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
542 542
543 content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc 543 content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
544 - 544 + logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc());
545 HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId()); 545 HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
546 // 添加订阅 546 // 添加订阅
  547 + CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
  548 + String callId=newCallIdHeader.getCallId();
547 subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { 549 subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
548 - hookEvent.call(new InviteStreamInfo(mediaServerItem, json,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream())); 550 + logger.debug("sipc 添加订阅===callId {}",callId);
  551 + hookEvent.call(new InviteStreamInfo(mediaServerItem, json,callId, "rtp", ssrcInfo.getStream()));
549 subscribe.removeSubscribe(hookSubscribe); 552 subscribe.removeSubscribe(hookSubscribe);
550 hookSubscribe.getContent().put("regist", false); 553 hookSubscribe.getContent().put("regist", false);
551 hookSubscribe.getContent().put("schema", "rtsp"); 554 hookSubscribe.getContent().put("schema", "rtsp");
@@ -554,7 +557,7 @@ public class SIPCommander implements ISIPCommander { @@ -554,7 +557,7 @@ public class SIPCommander implements ISIPCommander {
554 (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> { 557 (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> {
555 logger.info("[录像]下载结束, 发送BYE"); 558 logger.info("[录像]下载结束, 发送BYE");
556 try { 559 try {
557 - streamByeCmd(device, channelId, ssrcInfo.getStream(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId()); 560 + streamByeCmd(device, channelId, ssrcInfo.getStream(),callId);
558 } catch (InvalidArgumentException | ParseException | SipException | 561 } catch (InvalidArgumentException | ParseException | SipException |
559 SsrcTransactionNotFoundException e) { 562 SsrcTransactionNotFoundException e) {
560 logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage()); 563 logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
@@ -562,15 +565,24 @@ public class SIPCommander implements ISIPCommander { @@ -562,15 +565,24 @@ public class SIPCommander implements ISIPCommander {
562 }); 565 });
563 }); 566 });
564 567
565 - Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc()); 568 + Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
566 if (inviteStreamCallback != null) { 569 if (inviteStreamCallback != null) {
567 - inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream())); 570 + inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,callId, "rtp", ssrcInfo.getStream()));
568 } 571 }
569 572
570 - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent -> {  
571 - ResponseEvent responseEvent = (ResponseEvent) okEvent.event; 573 + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
  574 + ResponseEvent responseEvent = (ResponseEvent) event.event;
572 SIPResponse response = (SIPResponse) responseEvent.getResponse(); 575 SIPResponse response = (SIPResponse) responseEvent.getResponse();
573 - streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download); 576 + String contentString =new String(response.getRawContent());
  577 + int ssrcIndex = contentString.indexOf("y=");
  578 + String ssrc=ssrcInfo.getSsrc();
  579 + if (ssrcIndex >= 0) {
  580 + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  581 + }
  582 + logger.debug("接收到的下载响应ssrc====>{}",ssrcInfo.getSsrc());
  583 + logger.debug("接收到的下载响应ssrc====>{}",ssrc);
  584 + streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
  585 + okEvent.response(event);
574 }); 586 });
575 } 587 }
576 588
@@ -802,7 +814,6 @@ public class SIPCommander implements ISIPCommander { @@ -802,7 +814,6 @@ public class SIPCommander implements ISIPCommander {
802 * 814 *
803 * @param device 视频设备 815 * @param device 视频设备
804 * @param channelId 通道id,非通道则是设备本身 816 * @param channelId 通道id,非通道则是设备本身
805 - * @param frontCmd 上级平台的指令,如果存在则直接下发  
806 * @param enabled 看守位使能:1 = 开启,0 = 关闭 817 * @param enabled 看守位使能:1 = 开启,0 = 关闭
807 * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) 818 * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s)
808 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 819 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
@@ -165,7 +165,10 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent @@ -165,7 +165,10 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
165 // 对数据进行排序 165 // 对数据进行排序
166 if(recordInfo!=null && recordInfo.getRecordList()!=null) { 166 if(recordInfo!=null && recordInfo.getRecordList()!=null) {
167 Collections.sort(recordInfo.getRecordList()); 167 Collections.sort(recordInfo.getRecordList());
  168 + }else{
  169 + recordInfo.setRecordList(new ArrayList<>());
168 } 170 }
  171 +
169 RequestMessage msg = new RequestMessage(); 172 RequestMessage msg = new RequestMessage();
170 msg.setKey(key); 173 msg.setKey(key);
171 msg.setData(recordInfo); 174 msg.setData(recordInfo);
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -635,23 +635,75 @@ public class PlayServiceImpl implements IPlayService { @@ -635,23 +635,75 @@ public class PlayServiceImpl implements IPlayService {
635 hookCallBack.call(downloadResult); 635 hookCallBack.call(downloadResult);
636 streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); 636 streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
637 }; 637 };
638 - 638 + InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
  639 + logger.info("收到订阅消息: " + inviteStreamInfo.getCallId());
  640 + dynamicTask.stop(downLoadTimeOutTaskKey);
  641 + StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  642 + streamInfo.setStartTime(startTime);
  643 + streamInfo.setEndTime(endTime);
  644 + redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
  645 + downloadResult.setCode(ErrorCode.SUCCESS.getCode());
  646 + downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
  647 + downloadResult.setData(streamInfo);
  648 + downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
  649 + downloadResult.setResponse(inviteStreamInfo.getResponse());
  650 + hookCallBack.call(downloadResult);
  651 + };
639 try { 652 try {
640 cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack, 653 cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
641 - inviteStreamInfo -> {  
642 - logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());  
643 - dynamicTask.stop(downLoadTimeOutTaskKey);  
644 - StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);  
645 - streamInfo.setStartTime(startTime);  
646 - streamInfo.setEndTime(endTime);  
647 - redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());  
648 - downloadResult.setCode(ErrorCode.SUCCESS.getCode());  
649 - downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());  
650 - downloadResult.setData(streamInfo);  
651 - downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());  
652 - downloadResult.setResponse(inviteStreamInfo.getResponse());  
653 - hookCallBack.call(downloadResult);  
654 - }, errorEvent); 654 + hookEvent, errorEvent, eventResult ->
  655 + {
  656 + if (eventResult.type == SipSubscribe.EventResultType.response) {
  657 + ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
  658 + String contentString = new String(responseEvent.getResponse().getRawContent());
  659 + // 获取ssrc
  660 + int ssrcIndex = contentString.indexOf("y=");
  661 + // 检查是否有y字段
  662 + if (ssrcIndex >= 0) {
  663 + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  664 + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  665 + // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  666 + if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  667 + return;
  668 + }
  669 + logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  670 + if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
  671 + logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  672 +
  673 + if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  674 + // ssrc 不可用
  675 + // 释放ssrc
  676 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  677 + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  678 + eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
  679 + eventResult.statusCode = 400;
  680 + errorEvent.response(eventResult);
  681 + return;
  682 + }
  683 +
  684 + // 单端口模式streamId也有变化,需要重新设置监听
  685 + if (!mediaServerItem.isRtpEnable()) {
  686 + // 添加订阅
  687 + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  688 + subscribe.removeSubscribe(hookSubscribe);
  689 + hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  690 + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
  691 + logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
  692 + dynamicTask.stop(downLoadTimeOutTaskKey);
  693 + // hook响应
  694 + onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, hookCallBack);
  695 + hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
  696 + });
  697 + }
  698 + // 关闭rtp server
  699 + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  700 + // 重新开启ssrc server
  701 + mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
  702 + }
  703 + }
  704 + }
  705 +
  706 + });
655 } catch (InvalidArgumentException | SipException | ParseException e) { 707 } catch (InvalidArgumentException | SipException | ParseException e) {
656 logger.error("[命令发送失败] 录像下载: {}", e.getMessage()); 708 logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
657 709
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
@@ -316,10 +316,10 @@ public interface DeviceChannelMapper { @@ -316,10 +316,10 @@ public interface DeviceChannelMapper {
316 "select * " + 316 "select * " +
317 "from device_channel " + 317 "from device_channel " +
318 "where deviceId=#{deviceId}" + 318 "where deviceId=#{deviceId}" +
319 - " <if test='parentId != null and length != null' > and parentId = #{parentId} or left(channelId, #{parentId.length()}) = #{parentId} and length(channelId)=#{length} </if>" + 319 + " <if test='parentId != null and length != null' > and parentId = #{parentId} or left(channelId, LENGTH(#{parentId})) = #{parentId} and length(channelId)=#{length} </if>" +
320 " <if test='parentId == null and length != null' > and parentId = #{parentId} or length(channelId)=#{length} </if>" + 320 " <if test='parentId == null and length != null' > and parentId = #{parentId} or length(channelId)=#{length} </if>" +
321 " <if test='parentId == null and length == null' > and parentId = #{parentId} </if>" + 321 " <if test='parentId == null and length == null' > and parentId = #{parentId} </if>" +
322 - " <if test='parentId != null and length == null' > and parentId = #{parentId} or left(channelId, #{parentId.length()}) = #{parentId} </if>" + 322 + " <if test='parentId != null and length == null' > and parentId = #{parentId} or left(channelId, LENGTH(#{parentId})) = #{parentId} </if>" +
323 " </script>"}) 323 " </script>"})
324 List<DeviceChannel> getChannelsWithCivilCodeAndLength(String deviceId, String parentId, Integer length); 324 List<DeviceChannel> getChannelsWithCivilCodeAndLength(String deviceId, String parentId, Integer length);
325 325
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -177,12 +177,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @@ -177,12 +177,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
177 @Override 177 @Override
178 public boolean startDownload(StreamInfo stream, String callId) { 178 public boolean startDownload(StreamInfo stream, String callId) {
179 boolean result; 179 boolean result;
  180 + String key=String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
  181 + userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId);
180 if (stream.getProgress() == 1) { 182 if (stream.getProgress() == 1) {
181 - result = RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,  
182 - userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream); 183 + logger.debug("添加下载缓存==已完成下载=》{}",key);
  184 + result = RedisUtil.set(key, stream);
183 }else { 185 }else {
184 - result = RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,  
185 - userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream, 60*60); 186 + logger.debug("添加下载缓存==未完成下载=》{}",key);
  187 + result = RedisUtil.set(key, stream, 60*60);
186 } 188 }
187 return result; 189 return result;
188 } 190 }
@@ -617,7 +619,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @@ -617,7 +619,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
617 stream, 619 stream,
618 callId 620 callId
619 ); 621 );
620 - List<Object> streamInfoScan = RedisUtil.scan(key); 622 + List<Object> streamInfoScan = RedisUtil.scan2(key);
621 if (streamInfoScan.size() > 0) { 623 if (streamInfoScan.size() > 0) {
622 return (StreamInfo) RedisUtil.get((String) streamInfoScan.get(0)); 624 return (StreamInfo) RedisUtil.get((String) streamInfoScan.get(0));
623 }else { 625 }else {
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
@@ -881,7 +881,13 @@ public class RedisUtil { @@ -881,7 +881,13 @@ public class RedisUtil {
881 881
882 return new ArrayList<>(resultKeys); 882 return new ArrayList<>(resultKeys);
883 } 883 }
884 - 884 + public static List<Object> scan2(String query) {
  885 + if (redisTemplate == null) {
  886 + redisTemplate = SpringBeanFactory.getBean("redisTemplate");
  887 + }
  888 + Set<String> keys = redisTemplate.keys(query);
  889 + return new ArrayList<>(keys);
  890 + }
885 // ============================== 消息发送与订阅 ============================== 891 // ============================== 消息发送与订阅 ==============================
886 public static void convertAndSend(String channel, JSONObject msg) { 892 public static void convertAndSend(String channel, JSONObject msg) {
887 if (redisTemplate == null) { 893 if (redisTemplate == null) {