Commit 9b1af8ef1396de45884fe86c56844714045b82ec

Authored by 648540858
1 parent a2f08541

适配zlm的hook保活

Showing 27 changed files with 223 additions and 87 deletions
sql/mysql.sql
@@ -148,6 +148,7 @@ create table media_server @@ -148,6 +148,7 @@ create table media_server
148 defaultServer int not null, 148 defaultServer int not null,
149 createTime varchar(50) not null, 149 createTime varchar(50) not null,
150 updateTime varchar(50) not null, 150 updateTime varchar(50) not null,
  151 + hookAliveInterval int not null,
151 constraint media_server_i 152 constraint media_server_i
152 unique (ip, httpPort) 153 unique (ip, httpPort)
153 ); 154 );
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -10,10 +10,12 @@ public class VideoManagerConstants { @@ -10,10 +10,12 @@ public class VideoManagerConstants {
10 10
11 public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_"; 11 public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_";
12 12
13 - public static final String WVP_SERVER_STREAM_PUSH_PREFIX = "VMP_SIGNALLING_STREAM_"; 13 + public static final String WVP_SERVER_STREAM_PREFIX = "VMP_SIGNALLING_STREAM_";
14 14
15 public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_"; 15 public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
16 16
  17 + public static final String MEDIA_SERVER_KEEPALIVE_PREFIX = "VMP_MEDIA_SERVER_KEEPALIVE_";
  18 +
17 public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_"; 19 public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_";
18 20
19 public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM"; 21 public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
@@ -203,6 +203,7 @@ public class MediaConfig{ @@ -203,6 +203,7 @@ public class MediaConfig{
203 mediaServerItem.setRtpPortRange(rtpPortRange); 203 mediaServerItem.setRtpPortRange(rtpPortRange);
204 mediaServerItem.setSendRtpPortRange(sendRtpPortRange); 204 mediaServerItem.setSendRtpPortRange(sendRtpPortRange);
205 mediaServerItem.setRecordAssistPort(recordAssistPort); 205 mediaServerItem.setRecordAssistPort(recordAssistPort);
  206 + mediaServerItem.setHookAliveInterval(120);
206 207
207 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 208 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
208 mediaServerItem.setCreateTime(format.format(System.currentTimeMillis())); 209 mediaServerItem.setCreateTime(format.format(System.currentTimeMillis()));
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
@@ -27,7 +27,7 @@ public class SipConfig { @@ -27,7 +27,7 @@ public class SipConfig {
27 27
28 Integer keepaliveTimeOut = 255; 28 Integer keepaliveTimeOut = 255;
29 29
30 - Integer registerTimeInterval = 60; 30 + Integer registerTimeInterval = 120;
31 31
32 private boolean alarm = false; 32 private boolean alarm = false;
33 33
src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java
1 package com.genersoft.iot.vmp.gb28181.event; 1 package com.genersoft.iot.vmp.gb28181.event;
2 2
3 import com.genersoft.iot.vmp.gb28181.bean.Device; 3 import com.genersoft.iot.vmp.gb28181.bean.Device;
  4 +import com.genersoft.iot.vmp.gb28181.event.offline.OfflineEvent;
4 import com.genersoft.iot.vmp.gb28181.event.platformKeepaliveExpire.PlatformKeepaliveExpireEvent; 5 import com.genersoft.iot.vmp.gb28181.event.platformKeepaliveExpire.PlatformKeepaliveExpireEvent;
5 import com.genersoft.iot.vmp.gb28181.event.platformNotRegister.PlatformNotRegisterEvent; 6 import com.genersoft.iot.vmp.gb28181.event.platformNotRegister.PlatformNotRegisterEvent;
  7 +import com.genersoft.iot.vmp.media.zlm.event.ZLMOfflineEvent;
6 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.beans.factory.annotation.Autowired;
7 import org.springframework.context.ApplicationEventPublisher; 9 import org.springframework.context.ApplicationEventPublisher;
8 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Component;
9 11
10 import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; 12 import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
11 import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent; 13 import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent;
12 -import com.genersoft.iot.vmp.gb28181.event.offline.OfflineEvent;  
13 import com.genersoft.iot.vmp.gb28181.event.online.OnlineEvent; 14 import com.genersoft.iot.vmp.gb28181.event.online.OnlineEvent;
14 15
15 /** 16 /**
@@ -66,5 +67,11 @@ public class EventPublisher { @@ -66,5 +67,11 @@ public class EventPublisher {
66 alarmEvent.setAlarmInfo(deviceAlarm); 67 alarmEvent.setAlarmInfo(deviceAlarm);
67 applicationEventPublisher.publishEvent(alarmEvent); 68 applicationEventPublisher.publishEvent(alarmEvent);
68 } 69 }
  70 +
  71 + public void zlmOfflineEventPublish(String mediaServerId){
  72 + ZLMOfflineEvent outEvent = new ZLMOfflineEvent(this);
  73 + outEvent.setMediaServerId(mediaServerId);
  74 + applicationEventPublisher.publishEvent(outEvent);
  75 + }
69 76
70 } 77 }
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -359,8 +359,8 @@ public class ZLMHttpHookListener { @@ -359,8 +359,8 @@ public class ZLMHttpHookListener {
359 type = "PULL"; 359 type = "PULL";
360 } 360 }
361 } 361 }
362 - zlmMediaListManager.removeMedia( app, streamId);  
363 - redisCatchStorage.removeStream(mediaServerItem, OriginType.values()[item.getOriginType()].getType(), app, streamId); 362 + zlmMediaListManager.removeMedia(app, streamId);
  363 + redisCatchStorage.removeStream(mediaServerItem, type, app, streamId);
364 } 364 }
365 365
366 // 发送流变化redis消息 366 // 发送流变化redis消息
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.conf.MediaConfig; @@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.conf.MediaConfig;
7 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; 7 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
8 import com.genersoft.iot.vmp.service.IMediaServerService; 8 import com.genersoft.iot.vmp.service.IMediaServerService;
9 import com.genersoft.iot.vmp.service.IStreamProxyService; 9 import com.genersoft.iot.vmp.service.IStreamProxyService;
  10 +import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
10 import org.slf4j.Logger; 11 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory; 12 import org.slf4j.LoggerFactory;
12 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.beans.factory.annotation.Autowired;
@@ -40,6 +41,9 @@ public class ZLMRunner implements CommandLineRunner { @@ -40,6 +41,9 @@ public class ZLMRunner implements CommandLineRunner {
40 private IMediaServerService mediaServerService; 41 private IMediaServerService mediaServerService;
41 42
42 @Autowired 43 @Autowired
  44 + private IRedisCatchStorage redisCatchStorage;
  45 +
  46 + @Autowired
43 private MediaConfig mediaConfig; 47 private MediaConfig mediaConfig;
44 48
45 @Qualifier("taskExecutor") 49 @Qualifier("taskExecutor")
@@ -70,8 +74,14 @@ public class ZLMRunner implements CommandLineRunner { @@ -70,8 +74,14 @@ public class ZLMRunner implements CommandLineRunner {
70 } 74 }
71 }); 75 });
72 76
73 - // TODO 订阅 zlm保活事件, 当zlm离线时做业务的处理  
74 - 77 + // 订阅 zlm保活事件, 当zlm离线时做业务的处理
  78 + hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_keepalive,null,
  79 + (MediaServerItem mediaServerItem, JSONObject response)->{
  80 + String mediaServerId = response.getString("mediaServerId");
  81 + if (mediaServerId !=null ) {
  82 + mediaServerService.updateMediaServerKeepalive(mediaServerId, response.getJSONObject("data"));
  83 + }
  84 + });
75 85
76 // 获取zlm信息 86 // 获取zlm信息
77 logger.info("等待默认zlm接入..."); 87 logger.info("等待默认zlm接入...");
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java
@@ -65,6 +65,9 @@ public class ZLMServerConfig { @@ -65,6 +65,9 @@ public class ZLMServerConfig {
65 @JSONField(name = "hook.admin_params") 65 @JSONField(name = "hook.admin_params")
66 private String hookAdminParams; 66 private String hookAdminParams;
67 67
  68 + @JSONField(name = "hook.alive_interval")
  69 + private int hookAliveInterval;
  70 +
68 @JSONField(name = "hook.enable") 71 @JSONField(name = "hook.enable")
69 private String hookEnable; 72 private String hookEnable;
70 73
@@ -791,4 +794,12 @@ public class ZLMServerConfig { @@ -791,4 +794,12 @@ public class ZLMServerConfig {
791 public void setShellPhell(String shellPhell) { 794 public void setShellPhell(String shellPhell) {
792 this.shellPhell = shellPhell; 795 this.shellPhell = shellPhell;
793 } 796 }
  797 +
  798 + public int getHookAliveInterval() {
  799 + return hookAliveInterval;
  800 + }
  801 +
  802 + public void setHookAliveInterval(int hookAliveInterval) {
  803 + this.hookAliveInterval = hookAliveInterval;
  804 + }
794 } 805 }
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java
@@ -39,6 +39,8 @@ public class MediaServerItem{ @@ -39,6 +39,8 @@ public class MediaServerItem{
39 39
40 private int streamNoneReaderDelayMS; 40 private int streamNoneReaderDelayMS;
41 41
  42 + private int hookAliveInterval;
  43 +
42 private boolean rtpEnable; 44 private boolean rtpEnable;
43 45
44 private boolean status; 46 private boolean status;
@@ -87,6 +89,7 @@ public class MediaServerItem{ @@ -87,6 +89,7 @@ public class MediaServerItem{
87 autoConfig = true; // 默认值true; 89 autoConfig = true; // 默认值true;
88 secret = zlmServerConfig.getApiSecret(); 90 secret = zlmServerConfig.getApiSecret();
89 streamNoneReaderDelayMS = zlmServerConfig.getGeneralStreamNoneReaderDelayMS(); 91 streamNoneReaderDelayMS = zlmServerConfig.getGeneralStreamNoneReaderDelayMS();
  92 + hookAliveInterval = zlmServerConfig.getHookAliveInterval();
90 rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口 93 rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
91 rtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号 94 rtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号
92 sendRtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号 95 sendRtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号
@@ -309,4 +312,12 @@ public class MediaServerItem{ @@ -309,4 +312,12 @@ public class MediaServerItem{
309 public void setSendRtpPortRange(String sendRtpPortRange) { 312 public void setSendRtpPortRange(String sendRtpPortRange) {
310 this.sendRtpPortRange = sendRtpPortRange; 313 this.sendRtpPortRange = sendRtpPortRange;
311 } 314 }
  315 +
  316 + public int getHookAliveInterval() {
  317 + return hookAliveInterval;
  318 + }
  319 +
  320 + public void setHookAliveInterval(int hookAliveInterval) {
  321 + this.hookAliveInterval = hookAliveInterval;
  322 + }
312 } 323 }
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMEventAbstract.java 0 → 100644
  1 +package com.genersoft.iot.vmp.media.zlm.event;
  2 +
  3 +import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
  4 +import org.springframework.context.ApplicationEvent;
  5 +
  6 +public abstract class ZLMEventAbstract extends ApplicationEvent {
  7 +
  8 +
  9 + private static final long serialVersionUID = 1L;
  10 +
  11 + private String mediaServerId;
  12 +
  13 +
  14 + public ZLMEventAbstract(Object source) {
  15 + super(source);
  16 + }
  17 +
  18 + public String getMediaServerId() {
  19 + return mediaServerId;
  20 + }
  21 +
  22 + public void setMediaServerId(String mediaServerId) {
  23 + this.mediaServerId = mediaServerId;
  24 + }
  25 +}
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
1 package com.genersoft.iot.vmp.service; 1 package com.genersoft.iot.vmp.service;
2 2
  3 +import com.alibaba.fastjson.JSONObject;
3 import com.genersoft.iot.vmp.gb28181.bean.Device; 4 import com.genersoft.iot.vmp.gb28181.bean.Device;
4 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig; 5 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
5 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; 6 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@@ -30,6 +31,13 @@ public interface IMediaServerService { @@ -30,6 +31,13 @@ public interface IMediaServerService {
30 */ 31 */
31 void zlmServerOnline(ZLMServerConfig zlmServerConfig); 32 void zlmServerOnline(ZLMServerConfig zlmServerConfig);
32 33
  34 + /**
  35 + * 节点离线
  36 + * @param mediaServerId
  37 + * @return
  38 + */
  39 + void zlmServerOffline(String mediaServerId);
  40 +
33 MediaServerItem getMediaServerForMinimumLoad(); 41 MediaServerItem getMediaServerForMinimumLoad();
34 42
35 void setZLMConfig(MediaServerItem mediaServerItem); 43 void setZLMConfig(MediaServerItem mediaServerItem);
@@ -67,4 +75,6 @@ public interface IMediaServerService { @@ -67,4 +75,6 @@ public interface IMediaServerService {
67 void delete(String id); 75 void delete(String id);
68 76
69 MediaServerItem getDefaultMediaServer(); 77 MediaServerItem getDefaultMediaServer();
  78 +
  79 + void updateMediaServerKeepalive(String zlmServerConfig, JSONObject data);
70 } 80 }
src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java
@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.service; @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.service;
2 2
3 import com.alibaba.fastjson.JSONObject; 3 import com.alibaba.fastjson.JSONObject;
4 import com.genersoft.iot.vmp.common.StreamInfo; 4 import com.genersoft.iot.vmp.common.StreamInfo;
  5 +import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
5 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; 6 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
6 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; 7 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
7 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; 8 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
@@ -73,4 +74,19 @@ public interface IStreamProxyService { @@ -73,4 +74,19 @@ public interface IStreamProxyService {
73 * @return 74 * @return
74 */ 75 */
75 StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId); 76 StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId);
  77 +
  78 +
  79 + /**
  80 + * 新的节点加入
  81 + * @param zlmServerConfig
  82 + * @return
  83 + */
  84 + void zlmServerOnline(ZLMServerConfig zlmServerConfig);
  85 +
  86 + /**
  87 + * 节点离线
  88 + * @param mediaServerId
  89 + * @return
  90 + */
  91 + void zlmServerOffline(String mediaServerId);
76 } 92 }
src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java
1 package com.genersoft.iot.vmp.service; 1 package com.genersoft.iot.vmp.service;
2 2
3 import com.genersoft.iot.vmp.gb28181.bean.GbStream; 3 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
  4 +import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
4 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; 5 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
5 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; 6 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
6 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; 7 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
@@ -46,4 +47,18 @@ public interface IStreamPushService { @@ -46,4 +47,18 @@ public interface IStreamPushService {
46 */ 47 */
47 boolean stop(String app, String streamId); 48 boolean stop(String app, String streamId);
48 49
  50 + /**
  51 + * 新的节点加入
  52 + * @param zlmServerConfig
  53 + * @return
  54 + */
  55 + void zlmServerOnline(ZLMServerConfig zlmServerConfig);
  56 +
  57 + /**
  58 + * 节点离线
  59 + * @param mediaServerId
  60 + * @return
  61 + */
  62 + void zlmServerOffline(String mediaServerId);
  63 +
49 } 64 }
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
@@ -97,6 +97,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR @@ -97,6 +97,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR
97 if (!redisUtil.hasKey(key)) { 97 if (!redisUtil.hasKey(key)) {
98 redisUtil.set(key, mediaServerItem); 98 redisUtil.set(key, mediaServerItem);
99 } 99 }
  100 +
100 } 101 }
101 } 102 }
102 103
@@ -272,6 +273,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR @@ -272,6 +273,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR
272 WVPResult<String> result = new WVPResult<>(); 273 WVPResult<String> result = new WVPResult<>();
273 mediaServerItem.setCreateTime(this.format.format(System.currentTimeMillis())); 274 mediaServerItem.setCreateTime(this.format.format(System.currentTimeMillis()));
274 mediaServerItem.setUpdateTime(this.format.format(System.currentTimeMillis())); 275 mediaServerItem.setUpdateTime(this.format.format(System.currentTimeMillis()));
  276 + mediaServerItem.setHookAliveInterval(120);
275 JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); 277 JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
276 if (responseJSON != null) { 278 if (responseJSON != null) {
277 JSONArray data = responseJSON.getJSONArray("data"); 279 JSONArray data = responseJSON.getJSONArray("data");
@@ -329,6 +331,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR @@ -329,6 +331,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR
329 logger.warn("[未注册的zlm] 拒接接入:来自{}:{}", zlmServerConfig.getIp(),zlmServerConfig.getHttpPort() ); 331 logger.warn("[未注册的zlm] 拒接接入:来自{}:{}", zlmServerConfig.getIp(),zlmServerConfig.getHttpPort() );
330 return; 332 return;
331 } 333 }
  334 + serverItem.setHookAliveInterval(zlmServerConfig.getHookAliveInterval());
332 if (serverItem.getHttpPort() == 0) { 335 if (serverItem.getHttpPort() == 0) {
333 serverItem.setHttpPort(zlmServerConfig.getHttpPort()); 336 serverItem.setHttpPort(zlmServerConfig.getHttpPort());
334 } 337 }
@@ -352,85 +355,29 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR @@ -352,85 +355,29 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR
352 } 355 }
353 if (StringUtils.isEmpty(serverItem.getId())) { 356 if (StringUtils.isEmpty(serverItem.getId())) {
354 serverItem.setId(zlmServerConfig.getGeneralMediaServerId()); 357 serverItem.setId(zlmServerConfig.getGeneralMediaServerId());
  358 + }
  359 + serverItem.setStatus(true);
  360 + if (StringUtils.isEmpty(serverItem.getId())) {
  361 + serverItem.setId(zlmServerConfig.getGeneralMediaServerId());
355 mediaServerMapper.updateByHostAndPort(serverItem); 362 mediaServerMapper.updateByHostAndPort(serverItem);
356 }else { 363 }else {
357 mediaServerMapper.update(serverItem); 364 mediaServerMapper.update(serverItem);
358 } 365 }
359 - if (redisUtil.get(VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + serverItem.getId()) == null) { 366 + String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + serverItem.getId();
  367 + if (redisUtil.get(key) == null) {
360 SsrcConfig ssrcConfig = new SsrcConfig(serverItem.getId(), null, sipConfig.getDomain()); 368 SsrcConfig ssrcConfig = new SsrcConfig(serverItem.getId(), null, sipConfig.getDomain());
361 serverItem.setSsrcConfig(ssrcConfig); 369 serverItem.setSsrcConfig(ssrcConfig);
362 - redisUtil.set(VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + serverItem.getId(), serverItem); 370 + redisUtil.set(key, serverItem);
363 } 371 }
364 372
365 - serverItem.setStatus(true);  
366 resetOnlineServerItem(serverItem); 373 resetOnlineServerItem(serverItem);
  374 + updateMediaServerKeepalive(serverItem.getId(), null);
367 setZLMConfig(serverItem); 375 setZLMConfig(serverItem);
  376 + }
368 377
369 -// if (zlmServerConfig.getGeneralMediaServerId().equals(mediaConfig.getId())  
370 -// || (zlmServerConfig.getIp().equals(mediaConfig.getIp()) && zlmServerConfig.getHttpPort() == mediaConfig.getHttpPort())) {  
371 -// // 配置文件的zlm  
372 -// // 如果是配置文件中的zlm。 也就是默认zlm。 一切以配置文件内容为准  
373 -// // wvp互惠修改zlm的端口,需要自行配置。  
374 -// MediaServerItem serverItemFromConfig = mediaConfig.getMediaSerItem();  
375 -// serverItemFromConfig.setId(zlmServerConfig.getGeneralMediaServerId());  
376 -// if (mediaConfig.getHttpPort() == 0) {  
377 -// serverItemFromConfig.setHttpPort(zlmServerConfig.getHttpPort());  
378 -// }  
379 -// if (mediaConfig.getHttpSSlPort() == 0) {  
380 -// serverItemFromConfig.setHttpSSlPort(zlmServerConfig.getHttpSSLport());  
381 -// }  
382 -// if (mediaConfig.getRtmpPort() == 0) {  
383 -// serverItemFromConfig.setRtmpPort(zlmServerConfig.getRtmpPort());  
384 -// }  
385 -// if (mediaConfig.getRtmpSSlPort() == 0) {  
386 -// serverItemFromConfig.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());  
387 -// }  
388 -// if (mediaConfig.getRtspPort() == 0) {  
389 -// serverItemFromConfig.setRtspPort(zlmServerConfig.getRtspPort());  
390 -// }  
391 -// if (mediaConfig.getRtspSSLPort() == 0) {  
392 -// serverItemFromConfig.setRtspSSLPort(zlmServerConfig.getRtspSSlport());  
393 -// }  
394 -// if (mediaConfig.getRtpProxyPort() == 0) {  
395 -// serverItemFromConfig.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());  
396 -// }  
397 -// if (serverItem != null){  
398 -// mediaServerMapper.delDefault();  
399 -// mediaServerMapper.add(serverItemFromConfig);  
400 -// String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverItemFromConfig.getId();  
401 -// MediaServerItem serverItemInRedis = (MediaServerItem)redisUtil.get(key);  
402 -// if (serverItemInRedis != null) {  
403 -// serverItemFromConfig.setSsrcConfig(serverItemInRedis.getSsrcConfig());  
404 -// }else {  
405 -// serverItemFromConfig.setSsrcConfig(new SsrcConfig(serverItemFromConfig.getId(), null, sipConfig.getDomain()));  
406 -// }  
407 -// redisUtil.set(key, serverItemFromConfig);  
408 -// }else {  
409 -// String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverItemFromConfig.getId();  
410 -// serverItemFromConfig.setSsrcConfig(new SsrcConfig(serverItemFromConfig.getId(), null, sipConfig.getDomain()));  
411 -// redisUtil.set(key, serverItemFromConfig);  
412 -// mediaServerMapper.add(serverItemFromConfig);  
413 -// }  
414 -// resetOnlineServerItem(serverItemFromConfig);  
415 -// setZLMConfig(serverItemFromConfig);  
416 -// }  
417 - // 移除未添加的zlm的接入,所有的zlm必须先添加后才可以加入使用  
418 -// else {  
419 -// String now = this.format.format(System.currentTimeMillis());  
420 -// if (serverItem == null){  
421 -// // 一个新的zlm接入wvp  
422 -// serverItem = new MediaServerItem(zlmServerConfig, sipConfig.getIp());  
423 -// serverItem.setCreateTime(now);  
424 -// serverItem.setUpdateTime(now);  
425 -// String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverItem.getId();  
426 -// serverItem.setSsrcConfig(new SsrcConfig(serverItem.getId(), null, sipConfig.getDomain()));  
427 -// redisUtil.set(key, serverItem);  
428 -// // 存入数据库  
429 -// mediaServerMapper.add(serverItem);  
430 -// setZLMConfig(serverItem);  
431 -// }  
432 -// resetOnlineServerItem(serverItem);  
433 -// } 378 + @Override
  379 + public void zlmServerOffline(String mediaServerId) {
  380 + delete(mediaServerId);
434 } 381 }
435 382
436 @Override 383 @Override
@@ -611,9 +558,17 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR @@ -611,9 +558,17 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR
611 558
612 @Override 559 @Override
613 public void delete(String id) { 560 public void delete(String id) {
614 - redisUtil.zRemove(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId() + "_", id); 561 + redisUtil.zRemove(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId(), id);
615 String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + id; 562 String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + id;
616 redisUtil.del(key); 563 redisUtil.del(key);
617 mediaServerMapper.delOne(id); 564 mediaServerMapper.delOne(id);
618 } 565 }
  566 +
  567 + @Override
  568 + public void updateMediaServerKeepalive(String mediaServerId, JSONObject data) {
  569 + MediaServerItem mediaServerItem = getOne(mediaServerId);
  570 + String key = VideoManagerConstants.MEDIA_SERVER_KEEPALIVE_PREFIX + userSetup.getServerId() + "_" + mediaServerId;
  571 + int hookAliveInterval = mediaServerItem.getHookAliveInterval() + 2;
  572 + redisUtil.set(key, data, hookAliveInterval);
  573 + }
619 } 574 }
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.common.StreamInfo; @@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.common.StreamInfo;
5 import com.genersoft.iot.vmp.gb28181.bean.GbStream; 5 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
6 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; 6 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
7 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 7 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  8 +import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
8 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; 9 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
9 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; 10 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
10 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; 11 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
@@ -51,6 +52,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService { @@ -51,6 +52,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
51 private StreamProxyMapper streamProxyMapper; 52 private StreamProxyMapper streamProxyMapper;
52 53
53 @Autowired 54 @Autowired
  55 + private IRedisCatchStorage redisCatchStorage;
  56 +
  57 + @Autowired
54 private GbStreamMapper gbStreamMapper; 58 private GbStreamMapper gbStreamMapper;
55 59
56 @Autowired 60 @Autowired
@@ -249,4 +253,20 @@ public class StreamProxyServiceImpl implements IStreamProxyService { @@ -249,4 +253,20 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
249 public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) { 253 public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) {
250 return videoManagerStorager.getStreamProxyByAppAndStream(app, streamId); 254 return videoManagerStorager.getStreamProxyByAppAndStream(app, streamId);
251 } 255 }
  256 +
  257 + @Override
  258 + public void zlmServerOnline(ZLMServerConfig zlmServerConfig) {
  259 +
  260 + }
  261 +
  262 + @Override
  263 + public void zlmServerOffline(String mediaServerId) {
  264 + // 移除开启了无人观看自动移除的流
  265 + streamProxyMapper.deleteAutoRemoveItemByMediaServerId(mediaServerId);
  266 + // 其他的流设置未启用
  267 + streamProxyMapper.updateStatus(false, mediaServerId);
  268 + // 移除redis内流的信息
  269 + redisCatchStorage.removeStream(mediaServerId, "PULL");
  270 +
  271 + }
252 } 272 }
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject; @@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
5 import com.alibaba.fastjson.TypeReference; 5 import com.alibaba.fastjson.TypeReference;
6 import com.genersoft.iot.vmp.gb28181.bean.GbStream; 6 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
7 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 7 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  8 +import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
8 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; 9 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
9 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; 10 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
10 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; 11 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
@@ -135,4 +136,18 @@ public class StreamPushServiceImpl implements IStreamPushService { @@ -135,4 +136,18 @@ public class StreamPushServiceImpl implements IStreamPushService {
135 return true; 136 return true;
136 } 137 }
137 138
  139 + @Override
  140 + public void zlmServerOnline(ZLMServerConfig zlmServerConfig) {
  141 + // 似乎没啥需要做的
  142 + }
  143 +
  144 + @Override
  145 + public void zlmServerOffline(String mediaServerId) {
  146 + // 移除没有serverId的推流
  147 + streamPushMapper.deleteWithoutGBId(mediaServerId);
  148 + // 其他的流设置未启用
  149 + gbStreamMapper.updateStatusByMediaServerId(mediaServerId, false);
  150 + // 移除redis内流的信息
  151 + redisCatchStorage.removeStream(mediaServerId, "PUSH");
  152 + }
138 } 153 }
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -146,6 +146,13 @@ public interface IRedisCatchStorage { @@ -146,6 +146,13 @@ public interface IRedisCatchStorage {
146 */ 146 */
147 void removeStream(MediaServerItem mediaServerItem, String type, String app, String streamId); 147 void removeStream(MediaServerItem mediaServerItem, String type, String app, String streamId);
148 148
  149 +
  150 + /**
  151 + * 移除流信息从redis
  152 + * @param mediaServerId
  153 + */
  154 + void removeStream(String mediaServerId, String type);
  155 +
149 /** 156 /**
150 * 开始下载录像时存入 157 * 开始下载录像时存入
151 * @param streamInfo 158 * @param streamInfo
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -422,4 +422,5 @@ public interface IVideoManagerStorager { @@ -422,4 +422,5 @@ public interface IVideoManagerStorager {
422 * @return 422 * @return
423 */ 423 */
424 StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId); 424 StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId);
  425 +
425 } 426 }
src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java
@@ -60,4 +60,9 @@ public interface GbStreamMapper { @@ -60,4 +60,9 @@ public interface GbStreamMapper {
60 60
61 @Select("SELECT gs.*, pgs.platformId FROM gb_stream gs LEFT JOIN platform_gb_stream pgs ON gs.app = pgs.app AND gs.stream = pgs.stream WHERE mediaServerId=#{mediaServerId} ") 61 @Select("SELECT gs.*, pgs.platformId FROM gb_stream gs LEFT JOIN platform_gb_stream pgs ON gs.app = pgs.app AND gs.stream = pgs.stream WHERE mediaServerId=#{mediaServerId} ")
62 List<GbStream> selectAllByMediaServerId(String mediaServerId); 62 List<GbStream> selectAllByMediaServerId(String mediaServerId);
  63 +
  64 + @Update("UPDATE gb_stream " +
  65 + "SET status=${status} " +
  66 + "WHERE mediaServerId=#{mediaServerId} ")
  67 + void updateStatusByMediaServerId(String mediaServerId, boolean status);
63 } 68 }
src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java
@@ -36,7 +36,8 @@ public interface MediaServerMapper { @@ -36,7 +36,8 @@ public interface MediaServerMapper {
36 "recordAssistPort, " + 36 "recordAssistPort, " +
37 "defaultServer, " + 37 "defaultServer, " +
38 "createTime, " + 38 "createTime, " +
39 - "updateTime" + 39 + "updateTime, " +
  40 + "hookAliveInterval" +
40 ") VALUES " + 41 ") VALUES " +
41 "(" + 42 "(" +
42 "'${id}', " + 43 "'${id}', " +
@@ -60,7 +61,8 @@ public interface MediaServerMapper { @@ -60,7 +61,8 @@ public interface MediaServerMapper {
60 "${recordAssistPort}, " + 61 "${recordAssistPort}, " +
61 "${defaultServer}, " + 62 "${defaultServer}, " +
62 "'${createTime}', " + 63 "'${createTime}', " +
63 - "'${updateTime}')") 64 + "'${updateTime}', " +
  65 + "${hookAliveInterval})")
64 int add(MediaServerItem mediaServerItem); 66 int add(MediaServerItem mediaServerItem);
65 67
66 @Update(value = {" <script>" + 68 @Update(value = {" <script>" +
@@ -84,6 +86,7 @@ public interface MediaServerMapper { @@ -84,6 +86,7 @@ public interface MediaServerMapper {
84 "<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" + 86 "<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" +
85 "<if test=\"secret != null\">, secret='${secret}'</if>" + 87 "<if test=\"secret != null\">, secret='${secret}'</if>" +
86 "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" + 88 "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" +
  89 + "<if test=\"hookAliveInterval != null\">, hookAliveInterval=${hookAliveInterval}</if>" +
87 "WHERE id='${id}'"+ 90 "WHERE id='${id}'"+
88 " </script>"}) 91 " </script>"})
89 int update(MediaServerItem mediaServerItem); 92 int update(MediaServerItem mediaServerItem);
@@ -108,6 +111,7 @@ public interface MediaServerMapper { @@ -108,6 +111,7 @@ public interface MediaServerMapper {
108 "<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" + 111 "<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" +
109 "<if test=\"secret != null\">, secret='${secret}'</if>" + 112 "<if test=\"secret != null\">, secret='${secret}'</if>" +
110 "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" + 113 "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" +
  114 + "<if test=\"hookAliveInterval != null\">, hookAliveInterval=${hookAliveInterval}</if>" +
111 "WHERE ip='${ip}' and httpPort=${httpPort}"+ 115 "WHERE ip='${ip}' and httpPort=${httpPort}"+
112 " </script>"}) 116 " </script>"})
113 int updateByHostAndPort(MediaServerItem mediaServerItem); 117 int updateByHostAndPort(MediaServerItem mediaServerItem);
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java
@@ -51,4 +51,17 @@ public interface StreamProxyMapper { @@ -51,4 +51,17 @@ public interface StreamProxyMapper {
51 "LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream " + 51 "LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream " +
52 "WHERE st.enable=${enable} and st.mediaServerId = '${id}' order by st.createTime desc") 52 "WHERE st.enable=${enable} and st.mediaServerId = '${id}' order by st.createTime desc")
53 List<StreamProxyItem> selectForEnableInMediaServer(String id, boolean enable); 53 List<StreamProxyItem> selectForEnableInMediaServer(String id, boolean enable);
  54 +
  55 + @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st " +
  56 + "LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream " +
  57 + "WHERE st.mediaServerId = '${id}' order by st.createTime desc")
  58 + List<StreamProxyItem> selectInMediaServer(String id);
  59 +
  60 + @Update("UPDATE stream_proxy " +
  61 + "SET enable=#{status} " +
  62 + "WHERE mediaServerId=#{mediaServerId}")
  63 + void updateStatus(boolean status, String mediaServerId);
  64 +
  65 + @Delete("DELETE FROM stream_proxy WHERE mediaServerId=#{mediaServerId}")
  66 + void deleteAutoRemoveItemByMediaServerId(String mediaServerId);
54 } 67 }
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java
@@ -53,4 +53,7 @@ public interface StreamPushMapper { @@ -53,4 +53,7 @@ public interface StreamPushMapper {
53 @Delete("DELETE FROM stream_push") 53 @Delete("DELETE FROM stream_push")
54 void clear(); 54 void clear();
55 55
  56 + @Delete("DELETE FROM stream_push WHERE mediaServerId=#{mediaServerId}")
  57 + void deleteWithoutGBId(String mediaServerId);
  58 +
56 } 59 }
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -333,17 +333,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @@ -333,17 +333,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
333 333
334 @Override 334 @Override
335 public void addStream(MediaServerItem mediaServerItem, String type, String app, String streamId, StreamInfo streamInfo) { 335 public void addStream(MediaServerItem mediaServerItem, String type, String app, String streamId, StreamInfo streamInfo) {
336 - String key = VideoManagerConstants.WVP_SERVER_STREAM_PUSH_PREFIX + userSetup.getServerId() + "_" + type + "_" + app + "_" + streamId + "_" + mediaServerItem.getId(); 336 + String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetup.getServerId() + "_" + type + "_" + app + "_" + streamId + "_" + mediaServerItem.getId();
337 redis.set(key, streamInfo); 337 redis.set(key, streamInfo);
338 } 338 }
339 339
340 @Override 340 @Override
341 public void removeStream(MediaServerItem mediaServerItem, String type, String app, String streamId) { 341 public void removeStream(MediaServerItem mediaServerItem, String type, String app, String streamId) {
342 - String key = VideoManagerConstants.WVP_SERVER_STREAM_PUSH_PREFIX + userSetup.getServerId() + "_*_" + app + "_" + streamId + "_" + mediaServerItem.getId();  
343 - List<Object> streams = redis.scan(key);  
344 - for (Object stream : streams) {  
345 - redis.del((String) stream);  
346 - } 342 + String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetup.getServerId() + "_" + type + "_" + app + "_" + streamId + "_" + mediaServerItem.getId();
  343 + redis.del(key);
347 } 344 }
348 345
349 @Override 346 @Override
@@ -359,4 +356,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @@ -359,4 +356,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
359 JSONObject jsonObject = (JSONObject)redis.get(key); 356 JSONObject jsonObject = (JSONObject)redis.get(key);
360 return JSONObject.toJavaObject(jsonObject, ThirdPartyGB.class); 357 return JSONObject.toJavaObject(jsonObject, ThirdPartyGB.class);
361 } 358 }
  359 +
  360 + @Override
  361 + public void removeStream(String mediaServerId, String type) {
  362 + String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetup.getServerId() + "_" + type + "_*_*_" + mediaServerId;
  363 + List<Object> streams = redis.scan(key);
  364 + for (Object stream : streams) {
  365 + redis.del((String) stream);
  366 + }
  367 + }
362 } 368 }
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
@@ -738,4 +738,5 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager { @@ -738,4 +738,5 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
738 public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) { 738 public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) {
739 return streamProxyMapper.selectOne(app, streamId); 739 return streamProxyMapper.selectOne(app, streamId);
740 } 740 }
  741 +
741 } 742 }
src/main/resources/wvp.sqlite
No preview for this file type
src/test/java/com/genersoft/iot/vmp/service/impl/RoleServiceImplTest.java
@@ -25,7 +25,6 @@ class RoleServiceImplTest { @@ -25,7 +25,6 @@ class RoleServiceImplTest {
25 void getAllUser() { 25 void getAllUser() {
26 List<Role> all = roleService.getAll(); 26 List<Role> all = roleService.getAll();
27 Role roleById = roleService.getRoleById(1); 27 Role roleById = roleService.getRoleById(1);
28 - System.out.println();  
29 28
30 } 29 }
31 30
src/test/java/com/genersoft/iot/vmp/service/impl/UserServiceImplTest.java
@@ -27,10 +27,8 @@ class UserServiceImplTest { @@ -27,10 +27,8 @@ class UserServiceImplTest {
27 @org.junit.jupiter.api.Test 27 @org.junit.jupiter.api.Test
28 void getAllUser() { 28 void getAllUser() {
29 List<User> allUsers = userService.getAllUsers(); 29 List<User> allUsers = userService.getAllUsers();
30 - System.out.println(userService.getAllUsers().size());  
31 User admin = userService.getUser("admin", "21232f297a57a5a743894a0e4a801fc3"); 30 User admin = userService.getUser("admin", "21232f297a57a5a743894a0e4a801fc3");
32 User admin1 = userService.getUserByUsername("admin"); 31 User admin1 = userService.getUserByUsername("admin");
33 - System.out.println(12);  
34 } 32 }
35 33
36 34