作者 XellossRyan
提交者 GitHub

Merge pull request #1 from crossoverJie/master

merge
正在显示 51 个修改的文件 包含 1239 行增加253 行删除
@@ -49,8 +49,9 @@ @@ -49,8 +49,9 @@
49 * [x] 根据实际情况灵活的水平扩容、缩容。 49 * [x] 根据实际情况灵活的水平扩容、缩容。
50 * [x] 路由(`cim-forward-route`)服务自身是无状态,可用 `Nginx` 代理支持高可用。 50 * [x] 路由(`cim-forward-route`)服务自身是无状态,可用 `Nginx` 代理支持高可用。
51 * [x] 服务端自动剔除离线客户端。 51 * [x] 服务端自动剔除离线客户端。
52 -* [ ] 弱网环境下客户端自动重连。 52 +* [x] 客户端自动重连。
53 * [ ] 分组群聊。 53 * [ ] 分组群聊。
  54 +* [ ] Android SDK。
54 * [ ] 离线消息。 55 * [ ] 离线消息。
55 * [ ] 协议支持消息加密。 56 * [ ] 协议支持消息加密。
56 * [ ] 更多的客户端路由策略。 57 * [ ] 更多的客户端路由策略。
@@ -179,6 +180,7 @@ java -jar cim-client-1.0.0-SNAPSHOT.jar --server.port=8084 --cim.user.id=上方 @@ -179,6 +180,7 @@ java -jar cim-client-1.0.0-SNAPSHOT.jar --server.port=8084 --cim.user.id=上方
179 | `:ai` | 开启 AI 模式 | 180 | `:ai` | 开启 AI 模式 |
180 | `:qai` | 关闭 AI 模式 | 181 | `:qai` | 关闭 AI 模式 |
181 | `:pu` | 模糊匹配用户 | 182 | `:pu` | 模糊匹配用户 |
  183 +| `:info` | 获取客户端信息 |
182 | `:` | 更多命令正在开发中。。 | 184 | `:` | 更多命令正在开发中。。 |
183 185
184 ![](https://ws3.sinaimg.cn/large/006tNbRwly1fylh7bdlo6g30go01shdt.gif) 186 ![](https://ws3.sinaimg.cn/large/006tNbRwly1fylh7bdlo6g30go01shdt.gif)
@@ -205,7 +207,7 @@ java -jar cim-client-1.0.0-SNAPSHOT.jar --server.port=8084 --cim.user.id=上方 @@ -205,7 +207,7 @@ java -jar cim-client-1.0.0-SNAPSHOT.jar --server.port=8084 --cim.user.id=上方
205 207
206 ![](https://ws4.sinaimg.cn/large/006tNc79gy1fz3vo4tgkjj31ni09s41u.jpg) 208 ![](https://ws4.sinaimg.cn/large/006tNc79gy1fz3vo4tgkjj31ni09s41u.jpg)
207 209
208 -使用命令 `:qu prefix` 可以按照前缀的方式重新用户信息。 210 +使用命令 `:qu prefix` 可以按照前缀的方式搜索用户信息。
209 211
210 > 该功能主要用于在移动端中的输入框中搜索用户。 212 > 该功能主要用于在移动端中的输入框中搜索用户。
211 213
@@ -248,6 +250,12 @@ java -jar cim-client-1.0.0-SNAPSHOT.jar --server.port=8084 --cim.user.id=上方 @@ -248,6 +250,12 @@ java -jar cim-client-1.0.0-SNAPSHOT.jar --server.port=8084 --cim.user.id=上方
248 250
249 ![](https://ws1.sinaimg.cn/large/006tKfTcly1ftmfdo6mhmj30760760t7.jpg) 251 ![](https://ws1.sinaimg.cn/large/006tKfTcly1ftmfdo6mhmj30760760t7.jpg)
250 252
251 -# Code Visualization  
252 253
253 - [![Watch the video](https://img.youtube.com/vi/NhV_brPIG74/maxresdefault.jpg)](https://youtu.be/NhV_brPIG74) 254 +### Code Visualization:
  255 +
  256 +Here is a cool visualization of the code evolution
  257 +
  258 + [![Watch the video](https://img.youtube.com/vi/NhV_brPIG74/0.jpg)](https://www.youtube.com/watch?v=NhV_brPIG74)
  259 +
  260 + [https://www.youtube.com/watch?v=NhV_brPIG74](https://www.youtube.com/watch?v=NhV_brPIG74)
  261 +
@@ -73,12 +73,6 @@ @@ -73,12 +73,6 @@
73 </dependency> 73 </dependency>
74 74
75 <dependency> 75 <dependency>
76 - <groupId>io.netty</groupId>  
77 - <artifactId>netty-all</artifactId>  
78 - <version>${netty.version}</version>  
79 - </dependency>  
80 -  
81 - <dependency>  
82 <groupId>junit</groupId> 76 <groupId>junit</groupId>
83 <artifactId>junit</artifactId> 77 <artifactId>junit</artifactId>
84 </dependency> 78 </dependency>
1 package com.crossoverjie.cim.client; 1 package com.crossoverjie.cim.client;
2 2
3 import com.crossoverjie.cim.client.scanner.Scan; 3 import com.crossoverjie.cim.client.scanner.Scan;
  4 +import com.crossoverjie.cim.client.service.impl.ClientInfo;
4 import org.slf4j.Logger; 5 import org.slf4j.Logger;
5 import org.slf4j.LoggerFactory; 6 import org.slf4j.LoggerFactory;
  7 +import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.boot.CommandLineRunner; 8 import org.springframework.boot.CommandLineRunner;
7 import org.springframework.boot.SpringApplication; 9 import org.springframework.boot.SpringApplication;
8 import org.springframework.boot.autoconfigure.SpringBootApplication; 10 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -15,6 +17,8 @@ public class CIMClientApplication implements CommandLineRunner{ @@ -15,6 +17,8 @@ public class CIMClientApplication implements CommandLineRunner{
15 17
16 private final static Logger LOGGER = LoggerFactory.getLogger(CIMClientApplication.class); 18 private final static Logger LOGGER = LoggerFactory.getLogger(CIMClientApplication.class);
17 19
  20 + @Autowired
  21 + private ClientInfo clientInfo ;
18 public static void main(String[] args) { 22 public static void main(String[] args) {
19 SpringApplication.run(CIMClientApplication.class, args); 23 SpringApplication.run(CIMClientApplication.class, args);
20 LOGGER.info("启动 Client 服务成功"); 24 LOGGER.info("启动 Client 服务成功");
@@ -26,5 +30,6 @@ public class CIMClientApplication implements CommandLineRunner{ @@ -26,5 +30,6 @@ public class CIMClientApplication implements CommandLineRunner{
26 Thread thread = new Thread(scan); 30 Thread thread = new Thread(scan);
27 thread.setName("scan-thread"); 31 thread.setName("scan-thread");
28 thread.start(); 32 thread.start();
  33 + clientInfo.saveStartDate();
29 } 34 }
30 } 35 }
1 package com.crossoverjie.cim.client.client; 1 package com.crossoverjie.cim.client.client;
2 2
  3 +import com.crossoverjie.cim.client.config.AppConfiguration;
3 import com.crossoverjie.cim.client.init.CIMClientHandleInitializer; 4 import com.crossoverjie.cim.client.init.CIMClientHandleInitializer;
  5 +import com.crossoverjie.cim.client.service.MsgHandle;
4 import com.crossoverjie.cim.client.service.RouteRequest; 6 import com.crossoverjie.cim.client.service.RouteRequest;
  7 +import com.crossoverjie.cim.client.service.impl.ClientInfo;
5 import com.crossoverjie.cim.client.vo.req.GoogleProtocolVO; 8 import com.crossoverjie.cim.client.vo.req.GoogleProtocolVO;
6 import com.crossoverjie.cim.client.vo.req.LoginReqVO; 9 import com.crossoverjie.cim.client.vo.req.LoginReqVO;
7 import com.crossoverjie.cim.client.vo.res.CIMServerResVO; 10 import com.crossoverjie.cim.client.vo.res.CIMServerResVO;
@@ -16,6 +19,7 @@ import io.netty.channel.EventLoopGroup; @@ -16,6 +19,7 @@ import io.netty.channel.EventLoopGroup;
16 import io.netty.channel.nio.NioEventLoopGroup; 19 import io.netty.channel.nio.NioEventLoopGroup;
17 import io.netty.channel.socket.SocketChannel; 20 import io.netty.channel.socket.SocketChannel;
18 import io.netty.channel.socket.nio.NioSocketChannel; 21 import io.netty.channel.socket.nio.NioSocketChannel;
  22 +import io.netty.util.concurrent.DefaultThreadFactory;
19 import org.slf4j.Logger; 23 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory; 24 import org.slf4j.LoggerFactory;
21 import org.springframework.beans.factory.annotation.Autowired; 25 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,7 +32,7 @@ import javax.annotation.PostConstruct; @@ -28,7 +32,7 @@ import javax.annotation.PostConstruct;
28 * Function: 32 * Function:
29 * 33 *
30 * @author crossoverJie 34 * @author crossoverJie
31 - * Date: 22/05/2018 14:19 35 + * Date: 22/05/2018 14:19
32 * @since JDK 1.8 36 * @since JDK 1.8
33 */ 37 */
34 @Component 38 @Component
@@ -36,7 +40,7 @@ public class CIMClient { @@ -36,7 +40,7 @@ public class CIMClient {
36 40
37 private final static Logger LOGGER = LoggerFactory.getLogger(CIMClient.class); 41 private final static Logger LOGGER = LoggerFactory.getLogger(CIMClient.class);
38 42
39 - private EventLoopGroup group = new NioEventLoopGroup(); 43 + private EventLoopGroup group = new NioEventLoopGroup(0, new DefaultThreadFactory("cim-work"));
40 44
41 @Value("${cim.user.id}") 45 @Value("${cim.user.id}")
42 private long userId; 46 private long userId;
@@ -49,6 +53,20 @@ public class CIMClient { @@ -49,6 +53,20 @@ public class CIMClient {
49 @Autowired 53 @Autowired
50 private RouteRequest routeRequest; 54 private RouteRequest routeRequest;
51 55
  56 + @Autowired
  57 + private AppConfiguration configuration;
  58 +
  59 + @Autowired
  60 + private MsgHandle msgHandle;
  61 +
  62 + @Autowired
  63 + private ClientInfo clientInfo;
  64 +
  65 + /**
  66 + * 重试次数
  67 + */
  68 + private int errorCount;
  69 +
52 @PostConstruct 70 @PostConstruct
53 public void start() throws Exception { 71 public void start() throws Exception {
54 72
@@ -70,14 +88,25 @@ public class CIMClient { @@ -70,14 +88,25 @@ public class CIMClient {
70 * @param cimServer 88 * @param cimServer
71 * @throws InterruptedException 89 * @throws InterruptedException
72 */ 90 */
73 - private void startClient(CIMServerResVO.ServerInfo cimServer) throws InterruptedException { 91 + private void startClient(CIMServerResVO.ServerInfo cimServer) {
74 Bootstrap bootstrap = new Bootstrap(); 92 Bootstrap bootstrap = new Bootstrap();
75 bootstrap.group(group) 93 bootstrap.group(group)
76 .channel(NioSocketChannel.class) 94 .channel(NioSocketChannel.class)
77 .handler(new CIMClientHandleInitializer()) 95 .handler(new CIMClientHandleInitializer())
78 ; 96 ;
79 97
80 - ChannelFuture future = bootstrap.connect(cimServer.getIp(), cimServer.getCimServerPort()).sync(); 98 + ChannelFuture future = null;
  99 + try {
  100 + future = bootstrap.connect(cimServer.getIp(), cimServer.getCimServerPort()).sync();
  101 + } catch (InterruptedException e) {
  102 + errorCount++;
  103 +
  104 + if (errorCount >= configuration.getErrorCount()) {
  105 + LOGGER.error("链接失败次数达到上限[{}]次", errorCount);
  106 + msgHandle.shutdown();
  107 + }
  108 + LOGGER.error("连接失败", e);
  109 + }
81 if (future.isSuccess()) { 110 if (future.isSuccess()) {
82 LOGGER.info("启动 cim client 成功"); 111 LOGGER.info("启动 cim client 成功");
83 } 112 }
@@ -90,10 +119,26 @@ public class CIMClient { @@ -90,10 +119,26 @@ public class CIMClient {
90 * @return 路由服务器信息 119 * @return 路由服务器信息
91 * @throws Exception 120 * @throws Exception
92 */ 121 */
93 - private CIMServerResVO.ServerInfo userLogin() throws Exception { 122 + private CIMServerResVO.ServerInfo userLogin() {
94 LoginReqVO loginReqVO = new LoginReqVO(userId, userName); 123 LoginReqVO loginReqVO = new LoginReqVO(userId, userName);
95 - CIMServerResVO.ServerInfo cimServer = routeRequest.getCIMServer(loginReqVO);  
96 - LOGGER.info("cimServer=[{}]", cimServer.toString()); 124 + CIMServerResVO.ServerInfo cimServer = null;
  125 + try {
  126 + cimServer = routeRequest.getCIMServer(loginReqVO);
  127 +
  128 + //保存系统信息
  129 + clientInfo.saveServiceInfo(cimServer.getIp() + ":" + cimServer.getCimServerPort())
  130 + .saveUserInfo(userId, userName);
  131 +
  132 + LOGGER.info("cimServer=[{}]", cimServer.toString());
  133 + } catch (Exception e) {
  134 + errorCount++;
  135 +
  136 + if (errorCount >= configuration.getErrorCount()) {
  137 + LOGGER.error("重连次数达到上限[{}]次", errorCount);
  138 + msgHandle.shutdown();
  139 + }
  140 + LOGGER.error("登录失败", e);
  141 + }
97 return cimServer; 142 return cimServer;
98 } 143 }
99 144
@@ -145,11 +190,27 @@ public class CIMClient { @@ -145,11 +190,27 @@ public class CIMClient {
145 190
146 } 191 }
147 192
  193 +
  194 + public void reconnect() throws Exception {
  195 + if (channel != null && channel.isActive()) {
  196 + return;
  197 + }
  198 + //首先清除路由信息,下线
  199 + routeRequest.offLine();
  200 +
  201 + LOGGER.info("开始重连。。");
  202 + start();
  203 + LOGGER.info("重连成功!!");
  204 + }
  205 +
148 /** 206 /**
149 * 关闭 207 * 关闭
  208 + *
150 * @throws InterruptedException 209 * @throws InterruptedException
151 */ 210 */
152 public void close() throws InterruptedException { 211 public void close() throws InterruptedException {
153 - channel.close() ; 212 + if (channel != null){
  213 + channel.close();
  214 + }
154 } 215 }
155 } 216 }
@@ -22,6 +22,15 @@ public class AppConfiguration { @@ -22,6 +22,15 @@ public class AppConfiguration {
22 @Value("${cim.msg.logger.path}") 22 @Value("${cim.msg.logger.path}")
23 private String msgLoggerPath ; 23 private String msgLoggerPath ;
24 24
  25 + @Value("${cim.clear.route.request.url}")
  26 + private String clearRouteUrl ;
  27 +
  28 + @Value("${cim.heartbeat.time}")
  29 + private long heartBeatTime ;
  30 +
  31 + @Value("${cim.reconnect.count}")
  32 + private int errorCount ;
  33 +
25 public Long getUserId() { 34 public Long getUserId() {
26 return userId; 35 return userId;
27 } 36 }
@@ -45,4 +54,30 @@ public class AppConfiguration { @@ -45,4 +54,30 @@ public class AppConfiguration {
45 public void setMsgLoggerPath(String msgLoggerPath) { 54 public void setMsgLoggerPath(String msgLoggerPath) {
46 this.msgLoggerPath = msgLoggerPath; 55 this.msgLoggerPath = msgLoggerPath;
47 } 56 }
  57 +
  58 +
  59 + public long getHeartBeatTime() {
  60 + return heartBeatTime;
  61 + }
  62 +
  63 + public void setHeartBeatTime(long heartBeatTime) {
  64 + this.heartBeatTime = heartBeatTime;
  65 + }
  66 +
  67 +
  68 + public String getClearRouteUrl() {
  69 + return clearRouteUrl;
  70 + }
  71 +
  72 + public void setClearRouteUrl(String clearRouteUrl) {
  73 + this.clearRouteUrl = clearRouteUrl;
  74 + }
  75 +
  76 + public int getErrorCount() {
  77 + return errorCount;
  78 + }
  79 +
  80 + public void setErrorCount(int errorCount) {
  81 + this.errorCount = errorCount;
  82 + }
48 } 83 }
@@ -82,6 +82,17 @@ public class BeanConfig { @@ -82,6 +82,17 @@ public class BeanConfig {
82 return productExecutor ; 82 return productExecutor ;
83 } 83 }
84 84
  85 +
  86 + @Bean("scheduledTask")
  87 + public ScheduledExecutorService buildSchedule(){
  88 + ThreadFactory sche = new ThreadFactoryBuilder()
  89 + .setNameFormat("scheduled-%d")
  90 + .setDaemon(true)
  91 + .build();
  92 + ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1,sche) ;
  93 + return scheduledExecutorService ;
  94 + }
  95 +
85 /** 96 /**
86 * 回调 bean 97 * 回调 bean
87 * @return 98 * @return
1 package com.crossoverjie.cim.client.handle; 1 package com.crossoverjie.cim.client.handle;
2 2
  3 +import com.crossoverjie.cim.client.thread.ReConnectJob;
3 import com.crossoverjie.cim.client.util.SpringBeanFactory; 4 import com.crossoverjie.cim.client.util.SpringBeanFactory;
4 import com.crossoverjie.cim.common.constant.Constants; 5 import com.crossoverjie.cim.common.constant.Constants;
5 import com.crossoverjie.cim.common.protocol.CIMRequestProto; 6 import com.crossoverjie.cim.common.protocol.CIMRequestProto;
6 import com.crossoverjie.cim.common.protocol.CIMResponseProto; 7 import com.crossoverjie.cim.common.protocol.CIMResponseProto;
  8 +import com.crossoverjie.cim.common.util.NettyAttrUtil;
7 import io.netty.channel.ChannelFutureListener; 9 import io.netty.channel.ChannelFutureListener;
8 import io.netty.channel.ChannelHandler; 10 import io.netty.channel.ChannelHandler;
9 import io.netty.channel.ChannelHandlerContext; 11 import io.netty.channel.ChannelHandlerContext;
@@ -13,7 +15,9 @@ import io.netty.handler.timeout.IdleStateEvent; @@ -13,7 +15,9 @@ import io.netty.handler.timeout.IdleStateEvent;
13 import org.slf4j.Logger; 15 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory; 16 import org.slf4j.LoggerFactory;
15 17
  18 +import java.util.concurrent.ScheduledExecutorService;
16 import java.util.concurrent.ThreadPoolExecutor; 19 import java.util.concurrent.ThreadPoolExecutor;
  20 +import java.util.concurrent.TimeUnit;
17 21
18 /** 22 /**
19 * Function: 23 * Function:
@@ -31,6 +35,8 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt @@ -31,6 +35,8 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt
31 35
32 private ThreadPoolExecutor threadPoolExecutor ; 36 private ThreadPoolExecutor threadPoolExecutor ;
33 37
  38 + private ScheduledExecutorService scheduledExecutorService ;
  39 +
34 40
35 @Override 41 @Override
36 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 42 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
@@ -38,13 +44,19 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt @@ -38,13 +44,19 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt
38 if (evt instanceof IdleStateEvent){ 44 if (evt instanceof IdleStateEvent){
39 IdleStateEvent idleStateEvent = (IdleStateEvent) evt ; 45 IdleStateEvent idleStateEvent = (IdleStateEvent) evt ;
40 46
  47 + //LOGGER.info("定时检测服务端是否存活");
  48 +
41 if (idleStateEvent.state() == IdleState.WRITER_IDLE){ 49 if (idleStateEvent.state() == IdleState.WRITER_IDLE){
42 CIMRequestProto.CIMReqProtocol heartBeat = SpringBeanFactory.getBean("heartBeat", 50 CIMRequestProto.CIMReqProtocol heartBeat = SpringBeanFactory.getBean("heartBeat",
43 CIMRequestProto.CIMReqProtocol.class); 51 CIMRequestProto.CIMReqProtocol.class);
44 - ctx.writeAndFlush(heartBeat).addListeners(ChannelFutureListener.CLOSE_ON_FAILURE) ; 52 + ctx.writeAndFlush(heartBeat).addListeners((ChannelFutureListener) future -> {
  53 + if (!future.isSuccess()) {
  54 + LOGGER.error("IO error,close Channel");
  55 + future.channel().close();
  56 + }
  57 + }) ;
45 } 58 }
46 59
47 -  
48 } 60 }
49 61
50 super.userEventTriggered(ctx, evt); 62 super.userEventTriggered(ctx, evt);
@@ -58,10 +70,24 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt @@ -58,10 +70,24 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt
58 } 70 }
59 71
60 @Override 72 @Override
61 - protected void channelRead0(ChannelHandlerContext channelHandlerContext, CIMResponseProto.CIMResProtocol msg) throws Exception { 73 + public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  74 + LOGGER.info("客户端断开了,重新连接!");
62 75
63 - //从服务端收到消息时被调用  
64 - //LOGGER.info("客户端收到消息={}",in.toString(CharsetUtil.UTF_8)) ; 76 + if (scheduledExecutorService == null){
  77 + scheduledExecutorService = SpringBeanFactory.getBean("scheduledTask",ScheduledExecutorService.class) ;
  78 + }
  79 + // TODO: 2019-01-22 后期可以改为不用定时任务,连上后就关闭任务 节省性能。
  80 + scheduledExecutorService.scheduleAtFixedRate(new ReConnectJob(ctx),0,10, TimeUnit.SECONDS) ;
  81 + }
  82 +
  83 + @Override
  84 + protected void channelRead0(ChannelHandlerContext ctx, CIMResponseProto.CIMResProtocol msg) throws Exception {
  85 +
  86 + //心跳更新时间
  87 + if (msg.getType() == Constants.CommandType.PING){
  88 + //LOGGER.info("收到服务端心跳!!!");
  89 + NettyAttrUtil.updateReaderTime(ctx.channel(),System.currentTimeMillis());
  90 + }
65 91
66 if (msg.getType() != Constants.CommandType.PING) { 92 if (msg.getType() != Constants.CommandType.PING) {
67 //回调消息 93 //回调消息
@@ -70,6 +96,10 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt @@ -70,6 +96,10 @@ public class CIMClientHandle extends SimpleChannelInboundHandler<CIMResponseProt
70 LOGGER.info(msg.getResMsg()); 96 LOGGER.info(msg.getResMsg());
71 } 97 }
72 98
  99 +
  100 +
  101 +
  102 +
73 } 103 }
74 104
75 /** 105 /**
@@ -24,8 +24,8 @@ public class CIMClientHandleInitializer extends ChannelInitializer<Channel> { @@ -24,8 +24,8 @@ public class CIMClientHandleInitializer extends ChannelInitializer<Channel> {
24 @Override 24 @Override
25 protected void initChannel(Channel ch) throws Exception { 25 protected void initChannel(Channel ch) throws Exception {
26 ch.pipeline() 26 ch.pipeline()
27 - //30 秒没发送消息 将IdleStateHandler 添加到 ChannelPipeline 中  
28 - .addLast(new IdleStateHandler(0, 30, 0)) 27 + //10 秒没发送消息 将IdleStateHandler 添加到 ChannelPipeline 中
  28 + .addLast(new IdleStateHandler(0, 10, 0))
29 29
30 //心跳解码 30 //心跳解码
31 //.addLast(new HeartbeatEncode()) 31 //.addLast(new HeartbeatEncode())
  1 +package com.crossoverjie.cim.client.service;
  2 +
  3 +/**
  4 + * Function:
  5 + *
  6 + * @author crossoverJie
  7 + * Date: 2019-01-27 19:26
  8 + * @since JDK 1.8
  9 + */
  10 +public interface InnerCommand {
  11 +
  12 + /**
  13 + * 执行
  14 + * @param msg
  15 + */
  16 + void process(String msg) ;
  17 +}
  1 +package com.crossoverjie.cim.client.service;
  2 +
  3 +import com.crossoverjie.cim.client.service.impl.command.PrintAllCommand;
  4 +import com.crossoverjie.cim.client.util.SpringBeanFactory;
  5 +import com.crossoverjie.cim.common.enums.SystemCommandEnum;
  6 +import com.crossoverjie.cim.common.util.StringUtil;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +import java.util.Map;
  12 +
  13 +/**
  14 + * Function:
  15 + *
  16 + * @author crossoverJie
  17 + * Date: 2019-01-27 19:39
  18 + * @since JDK 1.8
  19 + */
  20 +@Component
  21 +public class InnerCommandContext {
  22 + private final static Logger LOGGER = LoggerFactory.getLogger(InnerCommandContext.class);
  23 +
  24 + /**
  25 + * 获取执行器实例
  26 + * @param command 执行器实例
  27 + * @return
  28 + */
  29 + public InnerCommand getInstance(String command) {
  30 +
  31 + Map<String, String> allClazz = SystemCommandEnum.getAllClazz();
  32 +
  33 + //兼容需要命令后接参数的数据 :q cross
  34 + String[] trim = command.trim().split(" ");
  35 + String clazz = allClazz.get(trim[0]);
  36 + InnerCommand innerCommand = null;
  37 + try {
  38 + if (StringUtil.isEmpty(clazz)){
  39 + clazz = PrintAllCommand.class.getName() ;
  40 + }
  41 + innerCommand = (InnerCommand) SpringBeanFactory.getBean(Class.forName(clazz));
  42 + } catch (Exception e) {
  43 + LOGGER.error("Exception", e);
  44 + }
  45 +
  46 + return innerCommand;
  47 + }
  48 +
  49 +}
@@ -48,4 +48,20 @@ public interface MsgHandle { @@ -48,4 +48,20 @@ public interface MsgHandle {
48 * @return 是否应当跳过当前消息(包含了":" 就需要跳过) 48 * @return 是否应当跳过当前消息(包含了":" 就需要跳过)
49 */ 49 */
50 boolean innerCommand(String msg) ; 50 boolean innerCommand(String msg) ;
  51 +
  52 +
  53 + /**
  54 + * 关闭系统
  55 + */
  56 + void shutdown() ;
  57 +
  58 + /**
  59 + * 开启 AI 模式
  60 + */
  61 + void openAIModel() ;
  62 +
  63 + /**
  64 + * 关闭 AI 模式
  65 + */
  66 + void closeAIModel() ;
51 } 67 }
@@ -41,10 +41,13 @@ public interface RouteRequest { @@ -41,10 +41,13 @@ public interface RouteRequest {
41 CIMServerResVO.ServerInfo getCIMServer(LoginReqVO loginReqVO) throws Exception; 41 CIMServerResVO.ServerInfo getCIMServer(LoginReqVO loginReqVO) throws Exception;
42 42
43 /** 43 /**
44 - *  
45 - * @return 获取所有在线用户 44 + * 获取所有在线用户
  45 + * @return
  46 + * @throws Exception
46 */ 47 */
47 List<OnlineUsersResVO.DataBodyBean> onlineUsers()throws Exception ; 48 List<OnlineUsersResVO.DataBodyBean> onlineUsers()throws Exception ;
48 49
49 50
  51 + void offLine() ;
  52 +
50 } 53 }
  1 +package com.crossoverjie.cim.client.service.impl;
  2 +
  3 +import com.crossoverjie.cim.client.client.CIMClient;
  4 +import com.crossoverjie.cim.common.kit.HeartBeatHandler;
  5 +import io.netty.channel.ChannelHandlerContext;
  6 +import org.slf4j.Logger;
  7 +import org.slf4j.LoggerFactory;
  8 +import org.springframework.beans.factory.annotation.Autowired;
  9 +import org.springframework.stereotype.Service;
  10 +
  11 +/**
  12 + * Function:
  13 + *
  14 + * @author crossoverJie
  15 + * Date: 2019-01-20 17:16
  16 + * @since JDK 1.8
  17 + */
  18 +@Service
  19 +public class ClientHeartBeatHandlerImpl implements HeartBeatHandler {
  20 +
  21 + private final static Logger LOGGER = LoggerFactory.getLogger(ClientHeartBeatHandlerImpl.class);
  22 +
  23 + @Autowired
  24 + private CIMClient cimClient;
  25 +
  26 +
  27 + @Override
  28 + public void process(ChannelHandlerContext ctx) throws Exception {
  29 +
  30 + //重连
  31 + cimClient.reconnect();
  32 +
  33 + }
  34 +
  35 +
  36 +}
  1 +package com.crossoverjie.cim.client.service.impl;
  2 +
  3 +
  4 +import org.springframework.stereotype.Component;
  5 +
  6 +import java.util.Date;
  7 +
  8 +/**
  9 + * Function:
  10 + *
  11 + * @author crossoverJie
  12 + * Date: 2019-01-21 23:35
  13 + * @since JDK 1.8
  14 + */
  15 +@Component
  16 +public class ClientInfo {
  17 +
  18 + private Info info = new Info() ;
  19 +
  20 + public Info get(){
  21 + return info ;
  22 + }
  23 +
  24 + public ClientInfo saveUserInfo(long userId,String userName){
  25 + info.setUserId(userId);
  26 + info.setUserName(userName);
  27 + return this;
  28 + }
  29 +
  30 +
  31 + public ClientInfo saveServiceInfo(String serviceInfo){
  32 + info.setServiceInfo(serviceInfo);
  33 + return this;
  34 + }
  35 +
  36 + public ClientInfo saveStartDate(){
  37 + info.setStartDate(new Date());
  38 + return this;
  39 + }
  40 +
  41 + private class Info{
  42 + private String userName;
  43 + private long userId ;
  44 + private String serviceInfo ;
  45 + private Date startDate ;
  46 +
  47 + public Info() {
  48 + }
  49 +
  50 + public String getUserName() {
  51 + return userName;
  52 + }
  53 +
  54 + public void setUserName(String userName) {
  55 + this.userName = userName;
  56 + }
  57 +
  58 + public long getUserId() {
  59 + return userId;
  60 + }
  61 +
  62 + public void setUserId(long userId) {
  63 + this.userId = userId;
  64 + }
  65 +
  66 + public String getServiceInfo() {
  67 + return serviceInfo;
  68 + }
  69 +
  70 + public void setServiceInfo(String serviceInfo) {
  71 + this.serviceInfo = serviceInfo;
  72 + }
  73 +
  74 + public Date getStartDate() {
  75 + return startDate;
  76 + }
  77 +
  78 + public void setStartDate(Date startDate) {
  79 + this.startDate = startDate;
  80 + }
  81 + }
  82 +}
@@ -2,20 +2,19 @@ package com.crossoverjie.cim.client.service.impl; @@ -2,20 +2,19 @@ package com.crossoverjie.cim.client.service.impl;
2 2
3 import com.crossoverjie.cim.client.client.CIMClient; 3 import com.crossoverjie.cim.client.client.CIMClient;
4 import com.crossoverjie.cim.client.config.AppConfiguration; 4 import com.crossoverjie.cim.client.config.AppConfiguration;
5 -import com.crossoverjie.cim.client.service.MsgHandle;  
6 -import com.crossoverjie.cim.client.service.MsgLogger;  
7 -import com.crossoverjie.cim.client.service.RouteRequest; 5 +import com.crossoverjie.cim.client.service.*;
8 import com.crossoverjie.cim.client.vo.req.GroupReqVO; 6 import com.crossoverjie.cim.client.vo.req.GroupReqVO;
9 import com.crossoverjie.cim.client.vo.req.P2PReqVO; 7 import com.crossoverjie.cim.client.vo.req.P2PReqVO;
10 import com.crossoverjie.cim.client.vo.res.OnlineUsersResVO; 8 import com.crossoverjie.cim.client.vo.res.OnlineUsersResVO;
11 import com.crossoverjie.cim.common.data.construct.TrieTree; 9 import com.crossoverjie.cim.common.data.construct.TrieTree;
12 -import com.crossoverjie.cim.common.enums.SystemCommandEnumType; 10 +import com.crossoverjie.cim.common.enums.SystemCommandEnum;
13 import com.crossoverjie.cim.common.util.StringUtil; 11 import com.crossoverjie.cim.common.util.StringUtil;
14 import org.slf4j.Logger; 12 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory; 13 import org.slf4j.LoggerFactory;
16 import org.springframework.beans.factory.annotation.Autowired; 14 import org.springframework.beans.factory.annotation.Autowired;
17 import org.springframework.stereotype.Service; 15 import org.springframework.stereotype.Service;
18 16
  17 +import javax.annotation.Resource;
19 import java.util.List; 18 import java.util.List;
20 import java.util.Map; 19 import java.util.Map;
21 import java.util.concurrent.ThreadPoolExecutor; 20 import java.util.concurrent.ThreadPoolExecutor;
@@ -25,40 +24,47 @@ import java.util.concurrent.TimeUnit; @@ -25,40 +24,47 @@ import java.util.concurrent.TimeUnit;
25 * Function: 24 * Function:
26 * 25 *
27 * @author crossoverJie 26 * @author crossoverJie
28 - * Date: 2018/12/26 11:15 27 + * Date: 2018/12/26 11:15
29 * @since JDK 1.8 28 * @since JDK 1.8
30 */ 29 */
31 @Service 30 @Service
32 public class MsgHandler implements MsgHandle { 31 public class MsgHandler implements MsgHandle {
33 private final static Logger LOGGER = LoggerFactory.getLogger(MsgHandler.class); 32 private final static Logger LOGGER = LoggerFactory.getLogger(MsgHandler.class);
34 @Autowired 33 @Autowired
35 - private RouteRequest routeRequest ; 34 + private RouteRequest routeRequest;
36 35
37 @Autowired 36 @Autowired
38 private AppConfiguration configuration; 37 private AppConfiguration configuration;
39 38
  39 + @Resource(name = "callBackThreadPool")
  40 + private ThreadPoolExecutor executor;
  41 +
  42 + @Autowired
  43 + private CIMClient cimClient;
  44 +
40 @Autowired 45 @Autowired
41 - private ThreadPoolExecutor executor ; 46 + private MsgLogger msgLogger;
42 47
43 @Autowired 48 @Autowired
44 - private CIMClient cimClient ; 49 + private ClientInfo clientInfo;
45 50
46 @Autowired 51 @Autowired
47 - private MsgLogger msgLogger ; 52 + private InnerCommandContext innerCommandContext ;
48 53
49 - private boolean aiModel = false ; 54 + private boolean aiModel = false;
50 55
51 @Override 56 @Override
52 public void sendMsg(String msg) { 57 public void sendMsg(String msg) {
53 - if (aiModel){ 58 + if (aiModel) {
54 aiChat(msg); 59 aiChat(msg);
55 - }else { 60 + } else {
56 normalChat(msg); 61 normalChat(msg);
57 } 62 }
58 } 63 }
59 64
60 /** 65 /**
61 * 正常聊天 66 * 正常聊天
  67 + *
62 * @param msg 68 * @param msg
63 */ 69 */
64 private void normalChat(String msg) { 70 private void normalChat(String msg) {
@@ -72,7 +78,7 @@ public class MsgHandler implements MsgHandle { @@ -72,7 +78,7 @@ public class MsgHandler implements MsgHandle {
72 try { 78 try {
73 p2pChat(p2PReqVO); 79 p2pChat(p2PReqVO);
74 } catch (Exception e) { 80 } catch (Exception e) {
75 - LOGGER.error("Exception",e); 81 + LOGGER.error("Exception", e);
76 } 82 }
77 83
78 } else { 84 } else {
@@ -81,21 +87,22 @@ public class MsgHandler implements MsgHandle { @@ -81,21 +87,22 @@ public class MsgHandler implements MsgHandle {
81 try { 87 try {
82 groupChat(groupReqVO); 88 groupChat(groupReqVO);
83 } catch (Exception e) { 89 } catch (Exception e) {
84 - LOGGER.error("Exception",e); 90 + LOGGER.error("Exception", e);
85 } 91 }
86 } 92 }
87 } 93 }
88 94
89 /** 95 /**
90 * AI model 96 * AI model
  97 + *
91 * @param msg 98 * @param msg
92 */ 99 */
93 private void aiChat(String msg) { 100 private void aiChat(String msg) {
94 - msg = msg.replace("吗","") ;  
95 - msg = msg.replace("嘛","") ;  
96 - msg = msg.replace("?","!");  
97 - msg = msg.replace("?","!");  
98 - msg = msg.replace("你","我"); 101 + msg = msg.replace("吗", "");
  102 + msg = msg.replace("嘛", "");
  103 + msg = msg.replace("?", "!");
  104 + msg = msg.replace("?", "!");
  105 + msg = msg.replace("你", "我");
99 System.out.println("AI:\033[31;4m" + msg + "\033[0m"); 106 System.out.println("AI:\033[31;4m" + msg + "\033[0m");
100 } 107 }
101 108
@@ -113,7 +120,7 @@ public class MsgHandler implements MsgHandle { @@ -113,7 +120,7 @@ public class MsgHandler implements MsgHandle {
113 120
114 @Override 121 @Override
115 public boolean checkMsg(String msg) { 122 public boolean checkMsg(String msg) {
116 - if (StringUtil.isEmpty(msg)){ 123 + if (StringUtil.isEmpty(msg)) {
117 LOGGER.warn("不能发送空消息!"); 124 LOGGER.warn("不能发送空消息!");
118 return true; 125 return true;
119 } 126 }
@@ -123,41 +130,15 @@ public class MsgHandler implements MsgHandle { @@ -123,41 +130,15 @@ public class MsgHandler implements MsgHandle {
123 @Override 130 @Override
124 public boolean innerCommand(String msg) { 131 public boolean innerCommand(String msg) {
125 132
126 - if (msg.startsWith(":")){  
127 - Map<String, String> allStatusCode = SystemCommandEnumType.getAllStatusCode();  
128 -  
129 - if (SystemCommandEnumType.QUIT.getCommandType().trim().equals(msg)){  
130 - //关闭系统  
131 - shutdown();  
132 - } else if (SystemCommandEnumType.ALL.getCommandType().trim().equals(msg)){  
133 - printAllCommand(allStatusCode);  
134 -  
135 - } else if (SystemCommandEnumType.ONLINE_USER.getCommandType().toLowerCase().trim().equals(msg.toLowerCase())){  
136 - //打印在线用户  
137 - printOnlineUsers();  
138 -  
139 - } else if (msg.startsWith(SystemCommandEnumType.QUERY.getCommandType().trim() + " ")){  
140 - //查询聊天记录  
141 - queryChatHistory(msg);  
142 - }else if (SystemCommandEnumType.AI.getCommandType().trim().equals(msg.toLowerCase())){  
143 - //开启 AI 模式  
144 - aiModel = true ;  
145 - System.out.println("\033[31;4m" + "Hello,我是估值两亿的 AI 机器人!" + "\033[0m");  
146 - }else if (SystemCommandEnumType.QAI.getCommandType().trim().equals(msg.toLowerCase())){  
147 - //关闭 AI 模式  
148 - aiModel = false ;  
149 - System.out.println("\033[31;4m" + "。゚(゚´ω`゚)゚。 AI 下线了!" + "\033[0m");  
150 - }else if (msg.startsWith(SystemCommandEnumType.PREFIX.getCommandType().trim() + " ")){  
151 - //模糊匹配  
152 - prefixSearch(msg);  
153 - }else {  
154 - printAllCommand(allStatusCode);  
155 - } 133 + if (msg.startsWith(":")) {
  134 +
  135 + InnerCommand instance = innerCommandContext.getInstance(msg);
  136 + instance.process(msg) ;
156 137
157 - return true ; 138 + return true;
158 139
159 - }else {  
160 - return false ; 140 + } else {
  141 + return false;
161 } 142 }
162 143
163 144
@@ -166,12 +147,13 @@ public class MsgHandler implements MsgHandle { @@ -166,12 +147,13 @@ public class MsgHandler implements MsgHandle {
166 147
167 /** 148 /**
168 * 模糊匹配 149 * 模糊匹配
  150 + *
169 * @param msg 151 * @param msg
170 */ 152 */
171 private void prefixSearch(String msg) { 153 private void prefixSearch(String msg) {
172 try { 154 try {
173 List<OnlineUsersResVO.DataBodyBean> onlineUsers = routeRequest.onlineUsers(); 155 List<OnlineUsersResVO.DataBodyBean> onlineUsers = routeRequest.onlineUsers();
174 - TrieTree trieTree = new TrieTree() ; 156 + TrieTree trieTree = new TrieTree();
175 for (OnlineUsersResVO.DataBodyBean onlineUser : onlineUsers) { 157 for (OnlineUsersResVO.DataBodyBean onlineUser : onlineUsers) {
176 trieTree.insert(onlineUser.getUserName()); 158 trieTree.insert(onlineUser.getUserName());
177 } 159 }
@@ -186,16 +168,17 @@ public class MsgHandler implements MsgHandle { @@ -186,16 +168,17 @@ public class MsgHandler implements MsgHandle {
186 } 168 }
187 169
188 } catch (Exception e) { 170 } catch (Exception e) {
189 - LOGGER.error("Exception" ,e); 171 + LOGGER.error("Exception", e);
190 } 172 }
191 } 173 }
192 174
193 /** 175 /**
194 * 查询聊天记录 176 * 查询聊天记录
  177 + *
195 * @param msg 178 * @param msg
196 */ 179 */
197 private void queryChatHistory(String msg) { 180 private void queryChatHistory(String msg) {
198 - String[] split = msg.split(" ") ; 181 + String[] split = msg.split(" ");
199 String res = msgLogger.query(split[1]); 182 String res = msgLogger.query(split[1]);
200 System.out.println(res); 183 System.out.println(res);
201 } 184 }
@@ -209,20 +192,22 @@ public class MsgHandler implements MsgHandle { @@ -209,20 +192,22 @@ public class MsgHandler implements MsgHandle {
209 192
210 LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 193 LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
211 for (OnlineUsersResVO.DataBodyBean onlineUser : onlineUsers) { 194 for (OnlineUsersResVO.DataBodyBean onlineUser : onlineUsers) {
212 - LOGGER.info("userId={}=====userName={}",onlineUser.getUserId(),onlineUser.getUserName()); 195 + LOGGER.info("userId={}=====userName={}", onlineUser.getUserId(), onlineUser.getUserName());
213 } 196 }
214 LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 197 LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
215 198
216 } catch (Exception e) { 199 } catch (Exception e) {
217 - LOGGER.error("Exception" ,e); 200 + LOGGER.error("Exception", e);
218 } 201 }
219 } 202 }
220 203
221 /** 204 /**
222 * 关闭系统 205 * 关闭系统
223 */ 206 */
224 - private void shutdown() { 207 + @Override
  208 + public void shutdown() {
225 LOGGER.info("系统关闭中。。。。"); 209 LOGGER.info("系统关闭中。。。。");
  210 + routeRequest.offLine();
226 msgLogger.stop(); 211 msgLogger.stop();
227 executor.shutdown(); 212 executor.shutdown();
228 try { 213 try {
@@ -231,11 +216,21 @@ public class MsgHandler implements MsgHandle { @@ -231,11 +216,21 @@ public class MsgHandler implements MsgHandle {
231 } 216 }
232 cimClient.close(); 217 cimClient.close();
233 } catch (InterruptedException e) { 218 } catch (InterruptedException e) {
234 - LOGGER.error("InterruptedException",e); 219 + LOGGER.error("InterruptedException", e);
235 } 220 }
236 System.exit(0); 221 System.exit(0);
237 } 222 }
238 223
  224 + @Override
  225 + public void openAIModel() {
  226 + aiModel = true;
  227 + }
  228 +
  229 + @Override
  230 + public void closeAIModel() {
  231 + aiModel = false ;
  232 + }
  233 +
239 private void printAllCommand(Map<String, String> allStatusCode) { 234 private void printAllCommand(Map<String, String> allStatusCode) {
240 LOGGER.warn("===================================="); 235 LOGGER.warn("====================================");
241 for (Map.Entry<String, String> stringStringEntry : allStatusCode.entrySet()) { 236 for (Map.Entry<String, String> stringStringEntry : allStatusCode.entrySet()) {
@@ -45,7 +45,7 @@ public class RouteRequestImpl implements RouteRequest { @@ -45,7 +45,7 @@ public class RouteRequestImpl implements RouteRequest {
45 private String p2pRouteRequestUrl; 45 private String p2pRouteRequestUrl;
46 46
47 @Value("${cim.server.route.request.url}") 47 @Value("${cim.server.route.request.url}")
48 - private String serverRouteRequestUrl; 48 + private String serverRouteLoginUrl;
49 49
50 @Value("${cim.server.online.user.url}") 50 @Value("${cim.server.online.user.url}")
51 private String onlineUserUrl; 51 private String onlineUserUrl;
@@ -120,7 +120,7 @@ public class RouteRequestImpl implements RouteRequest { @@ -120,7 +120,7 @@ public class RouteRequestImpl implements RouteRequest {
120 RequestBody requestBody = RequestBody.create(mediaType,jsonObject.toString()); 120 RequestBody requestBody = RequestBody.create(mediaType,jsonObject.toString());
121 121
122 Request request = new Request.Builder() 122 Request request = new Request.Builder()
123 - .url(serverRouteRequestUrl) 123 + .url(serverRouteLoginUrl)
124 .post(requestBody) 124 .post(requestBody)
125 .build(); 125 .build();
126 126
@@ -178,4 +178,26 @@ public class RouteRequestImpl implements RouteRequest { @@ -178,4 +178,26 @@ public class RouteRequestImpl implements RouteRequest {
178 178
179 return onlineUsersResVO.getDataBody(); 179 return onlineUsersResVO.getDataBody();
180 } 180 }
  181 +
  182 + @Override
  183 + public void offLine() {
  184 + JSONObject jsonObject = new JSONObject();
  185 + jsonObject.put("userId", appConfiguration.getUserId());
  186 + jsonObject.put("msg", "offLine");
  187 + RequestBody requestBody = RequestBody.create(mediaType, jsonObject.toString());
  188 +
  189 + Request request = new Request.Builder()
  190 + .url(appConfiguration.getClearRouteUrl())
  191 + .post(requestBody)
  192 + .build();
  193 +
  194 + Response response = null;
  195 + try {
  196 + response = okHttpClient.newCall(request).execute();
  197 + } catch (IOException e) {
  198 + LOGGER.error("exception",e);
  199 + } finally {
  200 + response.body().close();
  201 + }
  202 + }
181 } 203 }
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.crossoverjie.cim.client.service.InnerCommand;
  4 +import com.crossoverjie.cim.client.service.MsgHandle;
  5 +import org.slf4j.Logger;
  6 +import org.slf4j.LoggerFactory;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +/**
  11 + * Function:
  12 + *
  13 + * @author crossoverJie
  14 + * Date: 2019-01-27 19:37
  15 + * @since JDK 1.8
  16 + */
  17 +@Service
  18 +public class CloseAIModelCommand implements InnerCommand {
  19 + private final static Logger LOGGER = LoggerFactory.getLogger(CloseAIModelCommand.class);
  20 +
  21 +
  22 + @Autowired
  23 + private MsgHandle msgHandle ;
  24 +
  25 + @Override
  26 + public void process(String msg) {
  27 + msgHandle.closeAIModel();
  28 + System.out.println("\033[31;4m" + "。゚(゚´ω`゚)゚。 AI 下线了!" + "\033[0m");
  29 + }
  30 +}
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.alibaba.fastjson.JSON;
  4 +import com.crossoverjie.cim.client.service.InnerCommand;
  5 +import com.crossoverjie.cim.client.service.impl.ClientInfo;
  6 +import org.slf4j.Logger;
  7 +import org.slf4j.LoggerFactory;
  8 +import org.springframework.beans.factory.annotation.Autowired;
  9 +import org.springframework.stereotype.Service;
  10 +
  11 +/**
  12 + * Function:
  13 + *
  14 + * @author crossoverJie
  15 + * Date: 2019-01-27 19:37
  16 + * @since JDK 1.8
  17 + */
  18 +@Service
  19 +public class EchoInfoCommand implements InnerCommand {
  20 + private final static Logger LOGGER = LoggerFactory.getLogger(EchoInfoCommand.class);
  21 +
  22 +
  23 + @Autowired
  24 + private ClientInfo clientInfo;
  25 +
  26 + @Override
  27 + public void process(String msg) {
  28 + LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  29 + LOGGER.info("client info=[{}]", JSON.toJSONString(clientInfo.get()));
  30 + LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  31 + }
  32 +}
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.crossoverjie.cim.client.service.InnerCommand;
  4 +import com.crossoverjie.cim.client.service.MsgHandle;
  5 +import org.slf4j.Logger;
  6 +import org.slf4j.LoggerFactory;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +/**
  11 + * Function:
  12 + *
  13 + * @author crossoverJie
  14 + * Date: 2019-01-27 19:37
  15 + * @since JDK 1.8
  16 + */
  17 +@Service
  18 +public class OpenAIModelCommand implements InnerCommand {
  19 + private final static Logger LOGGER = LoggerFactory.getLogger(OpenAIModelCommand.class);
  20 +
  21 +
  22 + @Autowired
  23 + private MsgHandle msgHandle ;
  24 +
  25 + @Override
  26 + public void process(String msg) {
  27 + msgHandle.openAIModel();
  28 + System.out.println("\033[31;4m" + "Hello,我是估值两亿的 AI 机器人!" + "\033[0m");
  29 + }
  30 +}
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.crossoverjie.cim.client.service.InnerCommand;
  4 +import com.crossoverjie.cim.client.service.RouteRequest;
  5 +import com.crossoverjie.cim.client.vo.res.OnlineUsersResVO;
  6 +import com.crossoverjie.cim.common.data.construct.TrieTree;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.stereotype.Service;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * Function:
  16 + *
  17 + * @author crossoverJie
  18 + * Date: 2019-01-27 19:37
  19 + * @since JDK 1.8
  20 + */
  21 +@Service
  22 +public class PrefixSearchCommand implements InnerCommand {
  23 + private final static Logger LOGGER = LoggerFactory.getLogger(PrefixSearchCommand.class);
  24 +
  25 +
  26 + @Autowired
  27 + private RouteRequest routeRequest ;
  28 +
  29 + @Override
  30 + public void process(String msg) {
  31 + try {
  32 + List<OnlineUsersResVO.DataBodyBean> onlineUsers = routeRequest.onlineUsers();
  33 + TrieTree trieTree = new TrieTree();
  34 + for (OnlineUsersResVO.DataBodyBean onlineUser : onlineUsers) {
  35 + trieTree.insert(onlineUser.getUserName());
  36 + }
  37 +
  38 + String[] split = msg.split(" ");
  39 + String key = split[1];
  40 + List<String> list = trieTree.prefixSearch(key);
  41 +
  42 + for (String res : list) {
  43 + res = res.replace(key, "\033[31;4m" + key + "\033[0m");
  44 + System.out.println(res);
  45 + }
  46 +
  47 + } catch (Exception e) {
  48 + LOGGER.error("Exception", e);
  49 + }
  50 + }
  51 +}
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.crossoverjie.cim.client.service.InnerCommand;
  4 +import com.crossoverjie.cim.common.enums.SystemCommandEnum;
  5 +import org.slf4j.Logger;
  6 +import org.slf4j.LoggerFactory;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.Map;
  10 +
  11 +/**
  12 + * Function:
  13 + *
  14 + * @author crossoverJie
  15 + * Date: 2019-01-27 19:37
  16 + * @since JDK 1.8
  17 + */
  18 +@Service
  19 +public class PrintAllCommand implements InnerCommand {
  20 + private final static Logger LOGGER = LoggerFactory.getLogger(PrintAllCommand.class);
  21 +
  22 + @Override
  23 + public void process(String msg) {
  24 + Map<String, String> allStatusCode = SystemCommandEnum.getAllStatusCode();
  25 + LOGGER.warn("====================================");
  26 + for (Map.Entry<String, String> stringStringEntry : allStatusCode.entrySet()) {
  27 + String key = stringStringEntry.getKey();
  28 + String value = stringStringEntry.getValue();
  29 + LOGGER.warn(key + "----->" + value);
  30 + }
  31 + LOGGER.warn("====================================");
  32 + }
  33 +}
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.crossoverjie.cim.client.service.InnerCommand;
  4 +import com.crossoverjie.cim.client.service.RouteRequest;
  5 +import com.crossoverjie.cim.client.vo.res.OnlineUsersResVO;
  6 +import org.slf4j.Logger;
  7 +import org.slf4j.LoggerFactory;
  8 +import org.springframework.beans.factory.annotation.Autowired;
  9 +import org.springframework.stereotype.Service;
  10 +
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * Function:
  15 + *
  16 + * @author crossoverJie
  17 + * Date: 2019-01-27 19:37
  18 + * @since JDK 1.8
  19 + */
  20 +@Service
  21 +public class PrintOnlineUsersCommand implements InnerCommand {
  22 + private final static Logger LOGGER = LoggerFactory.getLogger(PrintOnlineUsersCommand.class);
  23 +
  24 +
  25 + @Autowired
  26 + private RouteRequest routeRequest ;
  27 +
  28 + @Override
  29 + public void process(String msg) {
  30 + try {
  31 + List<OnlineUsersResVO.DataBodyBean> onlineUsers = routeRequest.onlineUsers();
  32 +
  33 + LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  34 + for (OnlineUsersResVO.DataBodyBean onlineUser : onlineUsers) {
  35 + LOGGER.info("userId={}=====userName={}", onlineUser.getUserId(), onlineUser.getUserName());
  36 + }
  37 + LOGGER.info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  38 +
  39 + } catch (Exception e) {
  40 + LOGGER.error("Exception", e);
  41 + }
  42 + }
  43 +}
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.crossoverjie.cim.client.service.InnerCommand;
  4 +import com.crossoverjie.cim.client.service.MsgLogger;
  5 +import org.slf4j.Logger;
  6 +import org.slf4j.LoggerFactory;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +/**
  11 + * Function:
  12 + *
  13 + * @author crossoverJie
  14 + * Date: 2019-01-27 19:37
  15 + * @since JDK 1.8
  16 + */
  17 +@Service
  18 +public class QueryHistoryCommand implements InnerCommand {
  19 + private final static Logger LOGGER = LoggerFactory.getLogger(QueryHistoryCommand.class);
  20 +
  21 +
  22 + @Autowired
  23 + private MsgLogger msgLogger ;
  24 +
  25 + @Override
  26 + public void process(String msg) {
  27 + String[] split = msg.split(" ");
  28 + String res = msgLogger.query(split[1]);
  29 + System.out.println(res);
  30 + }
  31 +}
  1 +package com.crossoverjie.cim.client.service.impl.command;
  2 +
  3 +import com.crossoverjie.cim.client.client.CIMClient;
  4 +import com.crossoverjie.cim.client.service.InnerCommand;
  5 +import com.crossoverjie.cim.client.service.MsgLogger;
  6 +import com.crossoverjie.cim.client.service.RouteRequest;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.stereotype.Service;
  11 +
  12 +import javax.annotation.Resource;
  13 +import java.util.concurrent.ThreadPoolExecutor;
  14 +import java.util.concurrent.TimeUnit;
  15 +
  16 +/**
  17 + * Function:
  18 + *
  19 + * @author crossoverJie
  20 + * Date: 2019-01-27 19:28
  21 + * @since JDK 1.8
  22 + */
  23 +@Service
  24 +public class ShutDownCommand implements InnerCommand {
  25 + private final static Logger LOGGER = LoggerFactory.getLogger(ShutDownCommand.class);
  26 +
  27 + @Autowired
  28 + private RouteRequest routeRequest ;
  29 +
  30 + @Autowired
  31 + private CIMClient cimClient;
  32 +
  33 + @Autowired
  34 + private MsgLogger msgLogger;
  35 +
  36 + @Resource(name = "callBackThreadPool")
  37 + private ThreadPoolExecutor executor;
  38 +
  39 + @Override
  40 + public void process(String msg) {
  41 + LOGGER.info("系统关闭中。。。。");
  42 + routeRequest.offLine();
  43 + msgLogger.stop();
  44 + executor.shutdown();
  45 + try {
  46 + while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
  47 + LOGGER.info("线程池关闭中。。。。");
  48 + }
  49 + cimClient.close();
  50 + } catch (InterruptedException e) {
  51 + LOGGER.error("InterruptedException", e);
  52 + }
  53 + System.exit(0);
  54 + }
  55 +}
  1 +package com.crossoverjie.cim.client.thread;
  2 +
  3 +import com.crossoverjie.cim.client.service.impl.ClientHeartBeatHandlerImpl;
  4 +import com.crossoverjie.cim.client.util.SpringBeanFactory;
  5 +import com.crossoverjie.cim.common.kit.HeartBeatHandler;
  6 +import io.netty.channel.ChannelHandlerContext;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +
  10 +/**
  11 + * Function:
  12 + *
  13 + * @author crossoverJie
  14 + * Date: 2019-01-20 21:35
  15 + * @since JDK 1.8
  16 + */
  17 +public class ReConnectJob implements Runnable {
  18 +
  19 + private final static Logger LOGGER = LoggerFactory.getLogger(ReConnectJob.class);
  20 +
  21 + private ChannelHandlerContext context ;
  22 +
  23 + private HeartBeatHandler heartBeatHandler ;
  24 +
  25 + public ReConnectJob(ChannelHandlerContext context) {
  26 + this.context = context;
  27 + this.heartBeatHandler = SpringBeanFactory.getBean(ClientHeartBeatHandlerImpl.class) ;
  28 + }
  29 +
  30 + @Override
  31 + public void run() {
  32 + try {
  33 + heartBeatHandler.process(context);
  34 + } catch (Exception e) {
  35 + LOGGER.error("Exception",e);
  36 + }
  37 + }
  38 +}
@@ -25,6 +25,8 @@ cim.server.route.request.url=http://45.78.28.220:8083/login @@ -25,6 +25,8 @@ cim.server.route.request.url=http://45.78.28.220:8083/login
25 # 在线用户 25 # 在线用户
26 cim.server.online.user.url=http://45.78.28.220:8083/onlineUser 26 cim.server.online.user.url=http://45.78.28.220:8083/onlineUser
27 27
  28 +# 清除路由信息
  29 +cim.clear.route.request.url=http://45.78.28.220:8083/offLine
28 30
29 ###=======本地模拟======### 31 ###=======本地模拟======###
30 ## 群发消息 32 ## 群发消息
@@ -39,6 +41,9 @@ cim.server.online.user.url=http://45.78.28.220:8083/onlineUser @@ -39,6 +41,9 @@ cim.server.online.user.url=http://45.78.28.220:8083/onlineUser
39 ## 在线用户 41 ## 在线用户
40 #cim.server.online.user.url=http://localhost:8083/onlineUser 42 #cim.server.online.user.url=http://localhost:8083/onlineUser
41 43
  44 +# 清除路由信息
  45 +#cim.clear.route.request.url=http://localhost:8083/offLine
  46 +
42 # 客户端唯一ID 47 # 客户端唯一ID
43 cim.user.id=1545574841528 48 cim.user.id=1545574841528
44 cim.user.userName=zhangsan 49 cim.user.userName=zhangsan
@@ -51,4 +56,10 @@ cim.callback.thread.pool.size = 2 @@ -51,4 +56,10 @@ cim.callback.thread.pool.size = 2
51 # 关闭健康检查权限 56 # 关闭健康检查权限
52 management.security.enabled=false 57 management.security.enabled=false
53 # SpringAdmin 地址 58 # SpringAdmin 地址
54 -spring.boot.admin.url=http://127.0.0.1:8888  
  59 +spring.boot.admin.url=http://127.0.0.1:8888
  60 +
  61 +# 检测多少秒没有收到服务端端心跳后重新登录获取连接
  62 +cim.heartbeat.time = 60
  63 +
  64 +# 客户端连接失败重连次数
  65 +cim.reconnect.count =3
  1 +package com.crossoverjie.cim.client.service;
  2 +
  3 +import com.crossoverjie.cim.client.CIMClientApplication;
  4 +import com.crossoverjie.cim.common.enums.SystemCommandEnum;
  5 +import org.junit.Test;
  6 +import org.junit.runner.RunWith;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.boot.test.context.SpringBootTest;
  9 +import org.springframework.test.context.junit4.SpringRunner;
  10 +
  11 +@SpringBootTest(classes = CIMClientApplication.class)
  12 +@RunWith(SpringRunner.class)
  13 +public class InnerCommandContextTest {
  14 +
  15 + @Autowired
  16 + private InnerCommandContext context;
  17 +
  18 + @Test
  19 + public void execute() {
  20 + String msg = ":all";
  21 + InnerCommand execute = context.getInstance(msg);
  22 + execute.process(msg) ;
  23 + }
  24 +
  25 + @Test
  26 + public void execute3() {
  27 + String msg = SystemCommandEnum.ONLINE_USER.getCommandType();
  28 + InnerCommand execute = context.getInstance(msg);
  29 + execute.process(msg) ;
  30 + }
  31 +
  32 + @Test
  33 + public void execute4() {
  34 + String msg = ":q 天气";
  35 + InnerCommand execute = context.getInstance(msg);
  36 + execute.process(msg) ;
  37 + }
  38 +
  39 + @Test
  40 + public void execute5() {
  41 + String msg = ":q crossoverJie";
  42 + InnerCommand execute = context.getInstance(msg);
  43 + execute.process(msg) ;
  44 + }
  45 +
  46 + @Test
  47 + public void execute6() {
  48 + String msg = SystemCommandEnum.AI.getCommandType();
  49 + InnerCommand execute = context.getInstance(msg);
  50 + execute.process(msg) ;
  51 + }
  52 +
  53 + @Test
  54 + public void execute7() {
  55 + String msg = SystemCommandEnum.QAI.getCommandType();
  56 + InnerCommand execute = context.getInstance(msg);
  57 + execute.process(msg) ;
  58 + }
  59 +
  60 + @Test
  61 + public void execute8() {
  62 + String msg = ":pu cross";
  63 + InnerCommand execute = context.getInstance(msg);
  64 + execute.process(msg) ;
  65 + }
  66 +
  67 + @Test
  68 + public void execute9() {
  69 + String msg = SystemCommandEnum.INFO.getCommandType();
  70 + InnerCommand execute = context.getInstance(msg);
  71 + execute.process(msg) ;
  72 + }
  73 +
  74 + @Test
  75 + public void execute10() {
  76 + String msg = "dsds";
  77 + InnerCommand execute = context.getInstance(msg);
  78 + execute.process(msg) ;
  79 + }
  80 +
  81 +
  82 +
  83 + // @Test
  84 + public void quit() {
  85 + String msg = ":q!";
  86 + InnerCommand execute = context.getInstance(msg);
  87 + execute.process(msg) ;
  88 + }
  89 +}
@@ -38,5 +38,11 @@ @@ -38,5 +38,11 @@
38 <groupId>com.github.sgroschupf</groupId> 38 <groupId>com.github.sgroschupf</groupId>
39 <artifactId>zkclient</artifactId> 39 <artifactId>zkclient</artifactId>
40 </dependency> 40 </dependency>
  41 +
  42 + <dependency>
  43 + <groupId>io.netty</groupId>
  44 + <artifactId>netty-all</artifactId>
  45 + <version>${netty.version}</version>
  46 + </dependency>
41 </dependencies> 47 </dependencies>
42 </project> 48 </project>
@@ -18,6 +18,10 @@ public class TrieTree { @@ -18,6 +18,10 @@ public class TrieTree {
18 * 大小写都可保存 18 * 大小写都可保存
19 */ 19 */
20 private static final int CHILDREN_LENGTH = 26 * 2; 20 private static final int CHILDREN_LENGTH = 26 * 2;
  21 +
  22 + /**
  23 + * 存放的最大字符串长度
  24 + */
21 private static final int MAX_CHAR_LENGTH = 16; 25 private static final int MAX_CHAR_LENGTH = 16;
22 26
23 private static final char UPPERCASE_STAR = 'A'; 27 private static final char UPPERCASE_STAR = 'A';
@@ -209,7 +213,7 @@ public class TrieTree { @@ -209,7 +213,7 @@ public class TrieTree {
209 public boolean isEnd = false; 213 public boolean isEnd = false;
210 214
211 /** 215 /**
212 - * 如果支持查询,则不需要存储数据 216 + * 如果只是查询,则不需要存储数据
213 */ 217 */
214 public char data; 218 public char data;
215 219
1 package com.crossoverjie.cim.common.enums; 1 package com.crossoverjie.cim.common.enums;
2 2
3 -import java.util.ArrayList;  
4 import java.util.HashMap; 3 import java.util.HashMap;
5 -import java.util.List;  
6 import java.util.Map; 4 import java.util.Map;
7 5
8 /** 6 /**
@@ -12,15 +10,16 @@ import java.util.Map; @@ -12,15 +10,16 @@ import java.util.Map;
12 * Date: 2018/12/26 18:38 10 * Date: 2018/12/26 18:38
13 * @since JDK 1.8 11 * @since JDK 1.8
14 */ 12 */
15 -public enum SystemCommandEnumType { 13 +public enum SystemCommandEnum {
16 14
17 - ALL(":all ","获取所有命令"),  
18 - ONLINE_USER(":olu ","获取所有在线用户"),  
19 - QUIT(":q! ","退出程序"),  
20 - QUERY(":q ","【:q 关键字】查询聊天记录"),  
21 - AI(":ai ","开启 AI 模式"),  
22 - QAI(":qai ","关闭 AI 模式"),  
23 - PREFIX(":pu ","模糊匹配用户") 15 + ALL(":all ","获取所有命令","PrintAllCommand"),
  16 + ONLINE_USER(":olu ","获取所有在线用户","PrintOnlineUsersCommand"),
  17 + QUIT(":q! ","退出程序","ShutDownCommand"),
  18 + QUERY(":q ","【:q 关键字】查询聊天记录","QueryHistoryCommand"),
  19 + AI(":ai ","开启 AI 模式","OpenAIModelCommand"),
  20 + QAI(":qai ","关闭 AI 模式","CloseAIModelCommand"),
  21 + PREFIX(":pu ","模糊匹配用户","PrefixSearchCommand"),
  22 + INFO(":info ","获取客户端信息","EchoInfoCommand")
24 23
25 ; 24 ;
26 25
@@ -30,15 +29,21 @@ public enum SystemCommandEnumType { @@ -30,15 +29,21 @@ public enum SystemCommandEnumType {
30 /** 枚举描述 */ 29 /** 枚举描述 */
31 private final String desc; 30 private final String desc;
32 31
  32 + /**
  33 + * 实现类
  34 + */
  35 + private final String clazz ;
  36 +
33 37
34 /** 38 /**
35 * 构建一个 。 39 * 构建一个 。
36 * @param commandType 枚举值码。 40 * @param commandType 枚举值码。
37 * @param desc 枚举描述。 41 * @param desc 枚举描述。
38 */ 42 */
39 - private SystemCommandEnumType(String commandType, String desc) { 43 + private SystemCommandEnum(String commandType, String desc, String clazz) {
40 this.commandType = commandType; 44 this.commandType = commandType;
41 this.desc = desc; 45 this.desc = desc;
  46 + this.clazz = clazz ;
42 } 47 }
43 48
44 /** 49 /**
@@ -48,6 +53,13 @@ public enum SystemCommandEnumType { @@ -48,6 +53,13 @@ public enum SystemCommandEnumType {
48 public String getCommandType() { 53 public String getCommandType() {
49 return commandType; 54 return commandType;
50 } 55 }
  56 + /**
  57 + * 获取 class。
  58 + * @return class。
  59 + */
  60 + public String getClazz() {
  61 + return clazz;
  62 + }
51 63
52 /** 64 /**
53 * 得到枚举描述。 65 * 得到枚举描述。
@@ -79,13 +91,21 @@ public enum SystemCommandEnumType { @@ -79,13 +91,21 @@ public enum SystemCommandEnumType {
79 * @return 全部枚举值码。 91 * @return 全部枚举值码。
80 */ 92 */
81 public static Map<String,String> getAllStatusCode() { 93 public static Map<String,String> getAllStatusCode() {
82 - List<String> list = new ArrayList<String>();  
83 Map<String,String> map = new HashMap<String, String>(16) ; 94 Map<String,String> map = new HashMap<String, String>(16) ;
84 - for (SystemCommandEnumType status : values()) {  
85 - list.add(status.code()); 95 + for (SystemCommandEnum status : values()) {
86 map.put(status.getCommandType(),status.getDesc()) ; 96 map.put(status.getCommandType(),status.getDesc()) ;
87 } 97 }
88 return map; 98 return map;
89 } 99 }
90 100
  101 + public static Map<String,String> getAllClazz() {
  102 + Map<String,String> map = new HashMap<String, String>(16) ;
  103 + for (SystemCommandEnum status : values()) {
  104 + map.put(status.getCommandType().trim(),"com.crossoverjie.cim.client.service.impl.command." + status.getClazz()) ;
  105 + }
  106 + return map;
  107 + }
  108 +
  109 +
  110 +
91 } 111 }
  1 +package com.crossoverjie.cim.common.kit;
  2 +
  3 +import io.netty.channel.ChannelHandlerContext;
  4 +
  5 +/**
  6 + * Function:
  7 + *
  8 + * @author crossoverJie
  9 + * Date: 2019-01-20 17:15
  10 + * @since JDK 1.8
  11 + */
  12 +public interface HeartBeatHandler {
  13 +
  14 + /**
  15 + * 处理心跳
  16 + * @param ctx
  17 + * @throws Exception
  18 + */
  19 + void process(ChannelHandlerContext ctx) throws Exception ;
  20 +}
1 -package com.crossoverjie.cim.server.util; 1 +package com.crossoverjie.cim.common.util;
2 2
3 import io.netty.channel.Channel; 3 import io.netty.channel.Channel;
4 import io.netty.util.Attribute; 4 import io.netty.util.Attribute;
@@ -46,6 +46,102 @@ public class TrieTreeTest { @@ -46,6 +46,102 @@ public class TrieTreeTest {
46 } 46 }
47 47
48 @Test 48 @Test
  49 + public void prefixSea() throws Exception {
  50 + TrieTree trieTree = new TrieTree();
  51 + trieTree.insert("java");
  52 + trieTree.insert("jsf");
  53 + trieTree.insert("jsp");
  54 + trieTree.insert("javascript");
  55 + trieTree.insert("php");
  56 +
  57 + String result ="";
  58 + List<String> ab = trieTree.prefixSearch("jav");
  59 + for (String s : ab) {
  60 + result += s+",";
  61 + System.out.println(s);
  62 + }
  63 +
  64 + Assert.assertTrue(result.equals("java,javascript,"));
  65 +
  66 + }
  67 + @Test
  68 + public void prefixSea2() throws Exception {
  69 + TrieTree trieTree = new TrieTree();
  70 + trieTree.insert("java");
  71 + trieTree.insert("jsf");
  72 + trieTree.insert("jsp");
  73 + trieTree.insert("javascript");
  74 + trieTree.insert("php");
  75 +
  76 + String result ="";
  77 + List<String> ab = trieTree.prefixSearch("j");
  78 + for (String s : ab) {
  79 + result += s+",";
  80 + System.out.println(s);
  81 + }
  82 +
  83 + Assert.assertTrue(result.equals("java,javascript,jsf,jsp,"));
  84 +
  85 + }
  86 + @Test
  87 + public void prefixSea3() throws Exception {
  88 + TrieTree trieTree = new TrieTree();
  89 + trieTree.insert("java");
  90 + trieTree.insert("jsf");
  91 + trieTree.insert("jsp");
  92 + trieTree.insert("javascript");
  93 + trieTree.insert("php");
  94 +
  95 + String result ="";
  96 + List<String> ab = trieTree.prefixSearch("js");
  97 + for (String s : ab) {
  98 + result += s+",";
  99 + System.out.println(s);
  100 + }
  101 +
  102 + Assert.assertTrue(result.equals("jsf,jsp,"));
  103 +
  104 + }
  105 + @Test
  106 + public void prefixSea4() throws Exception {
  107 + TrieTree trieTree = new TrieTree();
  108 + trieTree.insert("java");
  109 + trieTree.insert("jsf");
  110 + trieTree.insert("jsp");
  111 + trieTree.insert("javascript");
  112 + trieTree.insert("php");
  113 +
  114 + String result ="";
  115 + List<String> ab = trieTree.prefixSearch("jav");
  116 + for (String s : ab) {
  117 + result += s+",";
  118 + System.out.println(s);
  119 + }
  120 +
  121 + Assert.assertTrue(result.equals("java,javascript,"));
  122 +
  123 + }
  124 + @Test
  125 + public void prefixSea5() throws Exception {
  126 + TrieTree trieTree = new TrieTree();
  127 + trieTree.insert("java");
  128 + trieTree.insert("jsf");
  129 + trieTree.insert("jsp");
  130 + trieTree.insert("javascript");
  131 + trieTree.insert("php");
  132 +
  133 + String result ="";
  134 + List<String> ab = trieTree.prefixSearch("js");
  135 + for (String s : ab) {
  136 + result += s+",";
  137 + System.out.println(s);
  138 + }
  139 +
  140 + Assert.assertTrue(result.equals("jsf,jsp,"));
  141 +
  142 + }
  143 +
  144 + @Test
49 public void prefixSearch() throws Exception { 145 public void prefixSearch() throws Exception {
50 TrieTree trieTree = new TrieTree(); 146 TrieTree trieTree = new TrieTree();
51 trieTree.insert("abc"); 147 trieTree.insert("abc");
@@ -112,6 +208,19 @@ public class TrieTreeTest { @@ -112,6 +208,19 @@ public class TrieTreeTest {
112 } 208 }
113 Assert.assertTrue(result.equals("Cde,")); 209 Assert.assertTrue(result.equals("Cde,"));
114 } 210 }
  211 + @Test
  212 + public void prefixSearch44() throws Exception {
  213 + TrieTree trieTree = new TrieTree();
  214 + trieTree.insert("a");
  215 + trieTree.insert("b");
  216 + trieTree.insert("c");
  217 + trieTree.insert("d");
  218 + trieTree.insert("e");
  219 + trieTree.insert("f");
  220 + trieTree.insert("g");
  221 + trieTree.insert("h");
  222 +
  223 + }
115 224
116 @Test 225 @Test
117 public void prefixSearch5() throws Exception { 226 public void prefixSearch5() throws Exception {
@@ -9,7 +9,7 @@ public class SystemCommandEnumTypeTest { @@ -9,7 +9,7 @@ public class SystemCommandEnumTypeTest {
9 9
10 @Test 10 @Test
11 public void getAllStatusCode() throws Exception { 11 public void getAllStatusCode() throws Exception {
12 - Map<String, String> allStatusCode = SystemCommandEnumType.getAllStatusCode(); 12 + Map<String, String> allStatusCode = SystemCommandEnum.getAllStatusCode();
13 for (Map.Entry<String, String> stringStringEntry : allStatusCode.entrySet()) { 13 for (Map.Entry<String, String> stringStringEntry : allStatusCode.entrySet()) {
14 String key = stringStringEntry.getKey(); 14 String key = stringStringEntry.getKey();
15 String value = stringStringEntry.getValue(); 15 String value = stringStringEntry.getValue();
@@ -81,7 +81,6 @@ public class RouteController { @@ -81,7 +81,6 @@ public class RouteController {
81 return res; 81 return res;
82 } 82 }
83 83
84 - // TODO: 2018/12/26 这些基于 HTTP 接口的远程通信都可以换为 SpringCloud  
85 84
86 /** 85 /**
87 * 私聊路由 86 * 私聊路由
@@ -174,6 +174,9 @@ public class AccountServiceRedisImpl implements AccountService { @@ -174,6 +174,9 @@ public class AccountServiceRedisImpl implements AccountService {
174 174
175 @Override 175 @Override
176 public void offLine(Long userId) throws Exception { 176 public void offLine(Long userId) throws Exception {
  177 +
  178 + // TODO: 2019-01-21 改为一个原子命令,以防数据一致性
  179 +
177 //删除路由 180 //删除路由
178 redisTemplate.delete(ROUTE_PREFIX + userId) ; 181 redisTemplate.delete(ROUTE_PREFIX + userId) ;
179 182
@@ -64,13 +64,6 @@ @@ -64,13 +64,6 @@
64 <artifactId>spring-boot-starter-actuator</artifactId> 64 <artifactId>spring-boot-starter-actuator</artifactId>
65 </dependency> 65 </dependency>
66 66
67 -  
68 - <dependency>  
69 - <groupId>io.netty</groupId>  
70 - <artifactId>netty-all</artifactId>  
71 - <version>${netty.version}</version>  
72 - </dependency>  
73 -  
74 <dependency> 67 <dependency>
75 <groupId>junit</groupId> 68 <groupId>junit</groupId>
76 <artifactId>junit</artifactId> 69 <artifactId>junit</artifactId>
@@ -51,7 +51,7 @@ public class BeanConfig { @@ -51,7 +51,7 @@ public class BeanConfig {
51 public CIMRequestProto.CIMReqProtocol heartBeat() { 51 public CIMRequestProto.CIMReqProtocol heartBeat() {
52 CIMRequestProto.CIMReqProtocol heart = CIMRequestProto.CIMReqProtocol.newBuilder() 52 CIMRequestProto.CIMReqProtocol heart = CIMRequestProto.CIMReqProtocol.newBuilder()
53 .setRequestId(0L) 53 .setRequestId(0L)
54 - .setReqMsg("ping") 54 + .setReqMsg("pong")
55 .setType(Constants.CommandType.PING) 55 .setType(Constants.CommandType.PING)
56 .build(); 56 .build();
57 return heart; 57 return heart;
@@ -3,13 +3,18 @@ package com.crossoverjie.cim.server.handle; @@ -3,13 +3,18 @@ package com.crossoverjie.cim.server.handle;
3 import com.alibaba.fastjson.JSONObject; 3 import com.alibaba.fastjson.JSONObject;
4 import com.crossoverjie.cim.common.constant.Constants; 4 import com.crossoverjie.cim.common.constant.Constants;
5 import com.crossoverjie.cim.common.exception.CIMException; 5 import com.crossoverjie.cim.common.exception.CIMException;
  6 +import com.crossoverjie.cim.common.kit.HeartBeatHandler;
6 import com.crossoverjie.cim.common.pojo.CIMUserInfo; 7 import com.crossoverjie.cim.common.pojo.CIMUserInfo;
7 import com.crossoverjie.cim.common.protocol.CIMRequestProto; 8 import com.crossoverjie.cim.common.protocol.CIMRequestProto;
  9 +import com.crossoverjie.cim.common.util.NettyAttrUtil;
8 import com.crossoverjie.cim.server.config.AppConfiguration; 10 import com.crossoverjie.cim.server.config.AppConfiguration;
9 -import com.crossoverjie.cim.server.util.NettyAttrUtil; 11 +import com.crossoverjie.cim.server.kit.ServerHeartBeatHandlerImpl;
10 import com.crossoverjie.cim.server.util.SessionSocketHolder; 12 import com.crossoverjie.cim.server.util.SessionSocketHolder;
11 import com.crossoverjie.cim.server.util.SpringBeanFactory; 13 import com.crossoverjie.cim.server.util.SpringBeanFactory;
12 -import io.netty.channel.*; 14 +import io.netty.channel.ChannelFutureListener;
  15 +import io.netty.channel.ChannelHandler;
  16 +import io.netty.channel.ChannelHandlerContext;
  17 +import io.netty.channel.SimpleChannelInboundHandler;
13 import io.netty.channel.socket.nio.NioSocketChannel; 18 import io.netty.channel.socket.nio.NioSocketChannel;
14 import io.netty.handler.timeout.IdleState; 19 import io.netty.handler.timeout.IdleState;
15 import io.netty.handler.timeout.IdleStateEvent; 20 import io.netty.handler.timeout.IdleStateEvent;
@@ -55,23 +60,11 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto @@ -55,23 +60,11 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto
55 if (evt instanceof IdleStateEvent) { 60 if (evt instanceof IdleStateEvent) {
56 IdleStateEvent idleStateEvent = (IdleStateEvent) evt; 61 IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
57 if (idleStateEvent.state() == IdleState.READER_IDLE) { 62 if (idleStateEvent.state() == IdleState.READER_IDLE) {
58 - AppConfiguration configuration = SpringBeanFactory.getBean(AppConfiguration.class);  
59 - long heartBeatTime = configuration.getHeartBeatTime() * 1000;  
60 -  
61 -  
62 - //向客户端发送消息  
63 - CIMRequestProto.CIMReqProtocol heartBeat = SpringBeanFactory.getBean("heartBeat",  
64 - CIMRequestProto.CIMReqProtocol.class);  
65 - ctx.writeAndFlush(heartBeat).addListeners(ChannelFutureListener.CLOSE_ON_FAILURE);  
66 -  
67 - Long lastReadTime = NettyAttrUtil.getReaderTime(ctx.channel());  
68 - long now = System.currentTimeMillis();  
69 - if (lastReadTime != null && now - lastReadTime > heartBeatTime){  
70 - CIMUserInfo userInfo = SessionSocketHolder.getUserId((NioSocketChannel) ctx.channel());  
71 - LOGGER.warn("客户端[{}]心跳超时[{}]ms,需要关闭连接!",userInfo.getUserName(),now - lastReadTime);  
72 - userOffLine(userInfo, (NioSocketChannel) ctx.channel());  
73 - ctx.channel().close();  
74 - } 63 +
  64 + LOGGER.info("定时检测客户端端是否存活");
  65 +
  66 + HeartBeatHandler heartBeatHandler = SpringBeanFactory.getBean(ServerHeartBeatHandlerImpl.class) ;
  67 + heartBeatHandler.process(ctx) ;
75 } 68 }
76 } 69 }
77 super.userEventTriggered(ctx, evt); 70 super.userEventTriggered(ctx, evt);
@@ -93,7 +86,7 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto @@ -93,7 +86,7 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto
93 } 86 }
94 87
95 /** 88 /**
96 - * 清除路由关系 89 + * 下线,清除路由关系
97 * 90 *
98 * @param userInfo 91 * @param userInfo
99 * @throws IOException 92 * @throws IOException
@@ -137,6 +130,15 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto @@ -137,6 +130,15 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto
137 //心跳更新时间 130 //心跳更新时间
138 if (msg.getType() == Constants.CommandType.PING){ 131 if (msg.getType() == Constants.CommandType.PING){
139 NettyAttrUtil.updateReaderTime(ctx.channel(),System.currentTimeMillis()); 132 NettyAttrUtil.updateReaderTime(ctx.channel(),System.currentTimeMillis());
  133 + //向客户端响应 pong 消息
  134 + CIMRequestProto.CIMReqProtocol heartBeat = SpringBeanFactory.getBean("heartBeat",
  135 + CIMRequestProto.CIMReqProtocol.class);
  136 + ctx.writeAndFlush(heartBeat).addListeners((ChannelFutureListener) future -> {
  137 + if (!future.isSuccess()) {
  138 + LOGGER.error("IO error,close Channel");
  139 + future.channel().close();
  140 + }
  141 + }) ;
140 } 142 }
141 143
142 } 144 }
@@ -25,8 +25,8 @@ public class CIMServerInitializer extends ChannelInitializer<Channel> { @@ -25,8 +25,8 @@ public class CIMServerInitializer extends ChannelInitializer<Channel> {
25 protected void initChannel(Channel ch) throws Exception { 25 protected void initChannel(Channel ch) throws Exception {
26 26
27 ch.pipeline() 27 ch.pipeline()
28 - //30 秒没有向客户端发送消息就发生心跳  
29 - .addLast(new IdleStateHandler(30, 0, 0)) 28 + //11 秒没有向客户端发送消息就发生心跳
  29 + .addLast(new IdleStateHandler(11, 0, 0))
30 // google Protobuf 编解码 30 // google Protobuf 编解码
31 .addLast(new ProtobufVarint32FrameDecoder()) 31 .addLast(new ProtobufVarint32FrameDecoder())
32 .addLast(new ProtobufDecoder(CIMRequestProto.CIMReqProtocol.getDefaultInstance())) 32 .addLast(new ProtobufDecoder(CIMRequestProto.CIMReqProtocol.getDefaultInstance()))
  1 +package com.crossoverjie.cim.server.kit;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.crossoverjie.cim.common.pojo.CIMUserInfo;
  5 +import com.crossoverjie.cim.server.config.AppConfiguration;
  6 +import com.crossoverjie.cim.server.util.SessionSocketHolder;
  7 +import com.crossoverjie.cim.server.util.SpringBeanFactory;
  8 +import io.netty.channel.socket.nio.NioSocketChannel;
  9 +import okhttp3.*;
  10 +import org.slf4j.Logger;
  11 +import org.slf4j.LoggerFactory;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import java.io.IOException;
  15 +
  16 +/**
  17 + * Function:
  18 + *
  19 + * @author crossoverJie
  20 + * Date: 2019-01-20 17:20
  21 + * @since JDK 1.8
  22 + */
  23 +@Component
  24 +public class RouteHandler {
  25 + private final static Logger LOGGER = LoggerFactory.getLogger(RouteHandler.class);
  26 +
  27 +
  28 + private final MediaType mediaType = MediaType.parse("application/json");
  29 +
  30 + /**
  31 + * 用户下线
  32 + * @param userInfo
  33 + * @param channel
  34 + * @throws IOException
  35 + */
  36 + public void userOffLine(CIMUserInfo userInfo, NioSocketChannel channel) throws IOException {
  37 + if (userInfo != null){
  38 + LOGGER.info("用户[{}]下线", userInfo.getUserName());
  39 + SessionSocketHolder.removeSession(userInfo.getUserId());
  40 + //清除路由关系
  41 + clearRouteInfo(userInfo);
  42 + }
  43 + SessionSocketHolder.remove(channel);
  44 +
  45 + }
  46 +
  47 +
  48 + /**
  49 + * 清除路由关系
  50 + *
  51 + * @param userInfo
  52 + * @throws IOException
  53 + */
  54 + private void clearRouteInfo(CIMUserInfo userInfo) throws IOException {
  55 + OkHttpClient okHttpClient = SpringBeanFactory.getBean(OkHttpClient.class);
  56 + AppConfiguration configuration = SpringBeanFactory.getBean(AppConfiguration.class);
  57 + JSONObject jsonObject = new JSONObject();
  58 + jsonObject.put("userId", userInfo.getUserId());
  59 + jsonObject.put("msg", "offLine");
  60 + RequestBody requestBody = RequestBody.create(mediaType, jsonObject.toString());
  61 +
  62 + Request request = new Request.Builder()
  63 + .url(configuration.getClearRouteUrl())
  64 + .post(requestBody)
  65 + .build();
  66 +
  67 + Response response = null;
  68 + try {
  69 + response = okHttpClient.newCall(request).execute();
  70 + if (!response.isSuccessful()) {
  71 + throw new IOException("Unexpected code " + response);
  72 + }
  73 + } finally {
  74 + response.body().close();
  75 + }
  76 + }
  77 +
  78 +}
  1 +package com.crossoverjie.cim.server.kit;
  2 +
  3 +import com.crossoverjie.cim.common.kit.HeartBeatHandler;
  4 +import com.crossoverjie.cim.common.pojo.CIMUserInfo;
  5 +import com.crossoverjie.cim.common.util.NettyAttrUtil;
  6 +import com.crossoverjie.cim.server.config.AppConfiguration;
  7 +import com.crossoverjie.cim.server.util.SessionSocketHolder;
  8 +import io.netty.channel.ChannelHandlerContext;
  9 +import io.netty.channel.socket.nio.NioSocketChannel;
  10 +import org.slf4j.Logger;
  11 +import org.slf4j.LoggerFactory;
  12 +import org.springframework.beans.factory.annotation.Autowired;
  13 +import org.springframework.stereotype.Service;
  14 +
  15 +/**
  16 + * Function:
  17 + *
  18 + * @author crossoverJie
  19 + * Date: 2019-01-20 17:16
  20 + * @since JDK 1.8
  21 + */
  22 +@Service
  23 +public class ServerHeartBeatHandlerImpl implements HeartBeatHandler {
  24 +
  25 + private final static Logger LOGGER = LoggerFactory.getLogger(ServerHeartBeatHandlerImpl.class);
  26 +
  27 + @Autowired
  28 + private RouteHandler routeHandler ;
  29 +
  30 + @Autowired
  31 + private AppConfiguration appConfiguration ;
  32 +
  33 + @Override
  34 + public void process(ChannelHandlerContext ctx) throws Exception {
  35 +
  36 + long heartBeatTime = appConfiguration.getHeartBeatTime() * 1000;
  37 +
  38 + Long lastReadTime = NettyAttrUtil.getReaderTime(ctx.channel());
  39 + long now = System.currentTimeMillis();
  40 + if (lastReadTime != null && now - lastReadTime > heartBeatTime){
  41 + CIMUserInfo userInfo = SessionSocketHolder.getUserId((NioSocketChannel) ctx.channel());
  42 + if (userInfo != null){
  43 + LOGGER.warn("客户端[{}]心跳超时[{}]ms,需要关闭连接!",userInfo.getUserName(),now - lastReadTime);
  44 + }
  45 + routeHandler.userOffLine(userInfo, (NioSocketChannel) ctx.channel());
  46 + ctx.channel().close();
  47 + }
  48 + }
  49 +}
@@ -34,4 +34,4 @@ app.zk.root=/route @@ -34,4 +34,4 @@ app.zk.root=/route
34 cim.clear.route.request.url=http://localhost:8083/offLine 34 cim.clear.route.request.url=http://localhost:8083/offLine
35 35
36 # 检测多少秒没有收到客户端心跳后服务端关闭连接 36 # 检测多少秒没有收到客户端心跳后服务端关闭连接
37 -cim.heartbeat.time = 40  
  37 +cim.heartbeat.time = 30
@@ -34,3 +34,13 @@ spring.redis.host=47.98.194.60
spring.redis.port=6379 @@ -34,3 +34,13 @@ spring.redis.host=47.98.194.60
spring.redis.port=6379
34 ![](https://ws2.sinaimg.cn/large/006tNbRwly1fymbjn98f6j31bn0u0aff.jpg) 34 ![](https://ws2.sinaimg.cn/large/006tNbRwly1fymbjn98f6j31bn0u0aff.jpg)
35 35
36 账号信息会存放在 `Redis` 36 账号信息会存放在 `Redis`
  37 +
  38 +
  39 +## 本地如何模拟调试?
  40 +
  41 +至少需要启动以下服务:
  42 +
  43 +1. 服务端
  44 +2. 路由
  45 +3. 至少两个客户端
  46 +4. `redis`、`zk` 基础组件
@@ -30,7 +30,6 @@ @@ -30,7 +30,6 @@
30 <module>cim-server</module> 30 <module>cim-server</module>
31 <module>cim-client</module> 31 <module>cim-client</module>
32 <module>cim-common</module> 32 <module>cim-common</module>
33 - <module>springboot-admin</module>  
34 <module>cim-zk</module> 33 <module>cim-zk</module>
35 <module>cim-forward-route</module> 34 <module>cim-forward-route</module>
36 </modules> 35 </modules>
1 -# 监控 admin  
2 -  
3 -[SpringBoot admin](https://github.com/codecentric/spring-boot-admin)  
1 -<?xml version="1.0" encoding="UTF-8"?>  
2 -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
3 - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
4 - <modelVersion>4.0.0</modelVersion>  
5 -  
6 - <parent>  
7 - <groupId>com.crossoverjie.netty</groupId>  
8 - <artifactId>cim</artifactId>  
9 - <version>1.0.0-SNAPSHOT</version>  
10 - </parent>  
11 -  
12 - <artifactId>springboot-admin</artifactId>  
13 - <packaging>jar</packaging>  
14 -  
15 - <name>admin</name>  
16 - <description>springBoot admin</description>  
17 -  
18 -  
19 - <properties>  
20 - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
21 - <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  
22 - <java.version>1.8</java.version>  
23 - <logback.version>1.2.3</logback.version>  
24 - </properties>  
25 -  
26 - <dependencies>  
27 - <dependency>  
28 - <groupId>org.springframework.boot</groupId>  
29 - <artifactId>spring-boot-starter</artifactId>  
30 - </dependency>  
31 -  
32 - <dependency>  
33 - <groupId>org.springframework.boot</groupId>  
34 - <artifactId>spring-boot-starter-test</artifactId>  
35 - <scope>test</scope>  
36 - </dependency>  
37 -  
38 - <dependency>  
39 - <groupId>org.springframework.boot</groupId>  
40 - <artifactId>spring-boot-starter-mail</artifactId>  
41 - </dependency>  
42 -  
43 - <dependency>  
44 - <groupId>de.codecentric</groupId>  
45 - <artifactId>spring-boot-admin-starter-server</artifactId>  
46 - <version>1.5.7</version>  
47 - </dependency>  
48 -  
49 - <dependency>  
50 - <groupId>ch.qos.logback</groupId>  
51 - <artifactId>logback-classic</artifactId>  
52 - </dependency>  
53 -  
54 - <dependency>  
55 - <groupId>de.codecentric</groupId>  
56 - <artifactId>spring-boot-admin-server-ui</artifactId>  
57 - <version>1.5.6</version>  
58 - </dependency>  
59 - </dependencies>  
60 -  
61 - <build>  
62 - <plugins>  
63 - <plugin>  
64 - <groupId>org.springframework.boot</groupId>  
65 - <artifactId>spring-boot-maven-plugin</artifactId>  
66 - </plugin>  
67 - </plugins>  
68 - </build>  
69 -  
70 -  
71 -</project>  
1 -package com.ai.obc.springboot.admin;  
2 -  
3 -import de.codecentric.boot.admin.config.EnableAdminServer;  
4 -import org.springframework.boot.SpringApplication;  
5 -import org.springframework.boot.autoconfigure.EnableAutoConfiguration;  
6 -import org.springframework.boot.autoconfigure.SpringBootApplication;  
7 -import org.springframework.context.annotation.Configuration;  
8 -  
9 -@SpringBootApplication  
10 -@Configuration  
11 -@EnableAutoConfiguration  
12 -@EnableAdminServer  
13 -public class AdminApplication {  
14 -  
15 - public static void main(String[] args) {  
16 - SpringApplication.run(AdminApplication.class, args);  
17 - }  
18 -}  
1 -  
2 -spring.application.name=spring-boot-admin  
3 -server.cimServerPort = 8888  
4 -  
5 -logging.level.root=info  
1 -package com.ai.obc.springboot.admin;  
2 -  
3 -import org.junit.Test;  
4 -import org.junit.runner.RunWith;  
5 -import org.springframework.boot.test.context.SpringBootTest;  
6 -import org.springframework.test.context.junit4.SpringRunner;  
7 -  
8 -@RunWith(SpringRunner.class)  
9 -@SpringBootTest  
10 -public class AdminApplicationTests {  
11 -  
12 - @Test  
13 - public void contextLoads() {  
14 - }  
15 -  
16 -}