├── README.md ├── im-client ├── pom.xml └── src │ ├── main │ ├── java │ │ └── ai │ │ │ └── yunxi │ │ │ └── im │ │ │ └── client │ │ │ ├── IMClientApplication.java │ │ │ ├── config │ │ │ ├── BeanConfiguration.java │ │ │ ├── InitConfiguration.java │ │ │ └── SpringBeanFactory.java │ │ │ ├── handle │ │ │ └── IMClientHandle.java │ │ │ ├── init │ │ │ └── IMClientInit.java │ │ │ └── scanner │ │ │ └── Scan.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── ai │ └── yunxi │ └── im │ └── client │ └── AppTest.java ├── im-common ├── pom.xml ├── protobuf │ └── MessageProto.proto └── src │ └── main │ └── java │ └── ai │ └── yunxi │ └── im │ └── common │ ├── constant │ ├── BasicConstant.java │ └── MessageConstant.java │ ├── enums │ └── StatusEnum.java │ ├── pojo │ ├── ChatInfo.java │ ├── ServerInfo.java │ └── UserInfo.java │ ├── protocol │ └── MessageProto.java │ └── utils │ └── StringUtil.java ├── im-route ├── pom.xml └── src │ ├── main │ ├── java │ │ └── ai │ │ │ └── yunxi │ │ │ └── im │ │ │ └── route │ │ │ ├── RouteApplication.java │ │ │ ├── config │ │ │ ├── BeanConfiguration.java │ │ │ └── InitConfiguration.java │ │ │ ├── controller │ │ │ └── IMRouteController.java │ │ │ ├── service │ │ │ ├── RouteService.java │ │ │ └── impl │ │ │ │ └── RouteServiceImpl.java │ │ │ └── zk │ │ │ └── ZKUtil.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── ai │ └── yunxi │ └── im │ └── route │ └── AppTest.java ├── im-server ├── pom.xml └── src │ └── main │ ├── java │ └── ai │ │ └── yunxi │ │ └── im │ │ └── server │ │ ├── IMServerApplication.java │ │ ├── config │ │ ├── BeanConfiguration.java │ │ ├── InitConfiguration.java │ │ └── SpringBeanFactory.java │ │ ├── controller │ │ └── IMServerController.java │ │ ├── handle │ │ ├── ChannelMap.java │ │ ├── ClientProcessor.java │ │ └── IMServerHandle.java │ │ ├── init │ │ └── IMServerInit.java │ │ └── zk │ │ ├── RegisterToZK.java │ │ └── ZKUtil.java │ └── resources │ └── application.properties ├── pic └── im.png └── pom.xml /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallFive55/im/42c11418fc8d2e702f0ab0f0596ccc86bfe5e643/README.md -------------------------------------------------------------------------------- /im-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | ai.yunxi 7 | im 8 | 0.0.1-SNAPSHOT 9 | 10 | ai.yunxi 11 | im-client 12 | 0.0.1-SNAPSHOT 13 | im-client 14 | http://maven.apache.org 15 | 16 | UTF-8 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-test 26 | test 27 | 28 | 29 | ai.yunxi 30 | im-common 31 | 0.0.1-SNAPSHOT 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-maven-plugin 40 | 41 | 42 | 43 | repackage 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /im-client/src/main/java/ai/yunxi/im/client/IMClientApplication.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | import ai.yunxi.im.client.scanner.Scan; 10 | 11 | /** 12 | * 13 | * @author 小五老师-云析学院 14 | * @createTime 2019年2月26日 下午2:59:36 15 | * 16 | */ 17 | @SpringBootApplication 18 | public class IMClientApplication implements CommandLineRunner { 19 | 20 | private final static Logger LOGGER = LoggerFactory.getLogger(IMClientApplication.class); 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(IMClientApplication.class, args); 24 | LOGGER.info("启动 Client 服务成功"); 25 | } 26 | 27 | @Override 28 | public void run(String... args) throws Exception { 29 | try { 30 | Thread th = new Thread(new Scan()); 31 | th.setName("client-scanner-thread"); 32 | th.start(); 33 | } catch (Exception e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /im-client/src/main/java/ai/yunxi/im/client/config/BeanConfiguration.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client.config; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import okhttp3.OkHttpClient; 9 | 10 | /** 11 | * 12 | * @author 小五老师-云析学院 13 | * @createTime 2019年2月26日 下午10:08:36 14 | * 15 | */ 16 | @Configuration 17 | public class BeanConfiguration { 18 | 19 | /** 20 | * http client 21 | * @return okHttp 22 | */ 23 | @Bean 24 | public OkHttpClient okHttpClient() { 25 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 26 | builder.connectTimeout(30, TimeUnit.SECONDS) 27 | .readTimeout(10, TimeUnit.SECONDS) 28 | .writeTimeout(10,TimeUnit.SECONDS) 29 | .retryOnConnectionFailure(true); 30 | return builder.build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /im-client/src/main/java/ai/yunxi/im/client/config/InitConfiguration.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author 小五老师-云析学院 8 | * @createTime 2019年3月12日 下午8:56:48 9 | * 10 | */ 11 | @Component 12 | public class InitConfiguration { 13 | 14 | @Value("${im.user.id}") 15 | private int userId; 16 | @Value("${im.user.userName}") 17 | private String userName; 18 | @Value("${im.route.login.url}") 19 | private String routeLoginUrl; 20 | @Value("${im.route.chat.url}") 21 | private String routeChatUrl; 22 | @Value("${im.route.logout.url}") 23 | private String routeLogoutUrl; 24 | 25 | public int getUserId() { 26 | return userId; 27 | } 28 | public void setUserId(int userId) { 29 | this.userId = userId; 30 | } 31 | public String getUserName() { 32 | return userName; 33 | } 34 | public void setUserName(String userName) { 35 | this.userName = userName; 36 | } 37 | public String getRouteLoginUrl() { 38 | return routeLoginUrl; 39 | } 40 | public void setRouteLoginUrl(String routeLoginUrl) { 41 | this.routeLoginUrl = routeLoginUrl; 42 | } 43 | public String getRouteChatUrl() { 44 | return routeChatUrl; 45 | } 46 | public void setRouteChatUrl(String routeChatUrl) { 47 | this.routeChatUrl = routeChatUrl; 48 | } 49 | public String getRouteLogoutUrl() { 50 | return routeLogoutUrl; 51 | } 52 | public void setRouteLogoutUrl(String routeLogoutUrl) { 53 | this.routeLogoutUrl = routeLogoutUrl; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /im-client/src/main/java/ai/yunxi/im/client/config/SpringBeanFactory.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client.config; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public final class SpringBeanFactory implements ApplicationContextAware{ 10 | private static ApplicationContext context; 11 | 12 | public static T getBean(Class c){ 13 | return context.getBean(c); 14 | } 15 | 16 | 17 | public static T getBean(String name,Class clazz){ 18 | return context.getBean(name,clazz); 19 | } 20 | 21 | @Override 22 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 23 | context = applicationContext; 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /im-client/src/main/java/ai/yunxi/im/client/handle/IMClientHandle.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client.handle; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import ai.yunxi.im.client.config.SpringBeanFactory; 7 | import ai.yunxi.im.client.init.IMClientInit; 8 | import ai.yunxi.im.common.protocol.MessageProto; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.ChannelInboundHandlerAdapter; 11 | 12 | /** 13 | * 14 | * @author 小五老师-云析学院 15 | * @createTime 2019年2月26日 下午10:01:02 16 | * 17 | */ 18 | public class IMClientHandle extends ChannelInboundHandlerAdapter { 19 | 20 | private final static Logger LOGGER = LoggerFactory.getLogger(IMClientHandle.class); 21 | 22 | @Override 23 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 24 | 25 | MessageProto.MessageProtocol message = (MessageProto.MessageProtocol) msg; 26 | LOGGER.info("客户端接收到消息:"+message.getContent()); 27 | } 28 | 29 | /** 30 | * 当客户端发现服务端断线后,发起重连 31 | **/ 32 | @Override 33 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 34 | IMClientInit client = SpringBeanFactory.getBean(IMClientInit.class); 35 | LOGGER.info("所连接的服务端断开了连接,发起重连请求..."); 36 | try { 37 | client.restart(); 38 | LOGGER.info("客户端重连成功!"); 39 | } catch (Exception e) { 40 | LOGGER.info("客户端重连失败!"); 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /im-client/src/main/java/ai/yunxi/im/client/init/IMClientInit.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client.init; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.annotation.PostConstruct; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import com.alibaba.fastjson.JSON; 13 | import com.alibaba.fastjson.JSONObject; 14 | 15 | import ai.yunxi.im.client.config.InitConfiguration; 16 | import ai.yunxi.im.client.handle.IMClientHandle; 17 | import ai.yunxi.im.common.constant.BasicConstant; 18 | import ai.yunxi.im.common.constant.MessageConstant; 19 | import ai.yunxi.im.common.pojo.ChatInfo; 20 | import ai.yunxi.im.common.pojo.ServerInfo; 21 | import ai.yunxi.im.common.protocol.MessageProto; 22 | import io.netty.bootstrap.Bootstrap; 23 | import io.netty.channel.Channel; 24 | import io.netty.channel.ChannelFuture; 25 | import io.netty.channel.ChannelInitializer; 26 | import io.netty.channel.ChannelPipeline; 27 | import io.netty.channel.EventLoopGroup; 28 | import io.netty.channel.nio.NioEventLoopGroup; 29 | import io.netty.channel.socket.SocketChannel; 30 | import io.netty.channel.socket.nio.NioSocketChannel; 31 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 32 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 33 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 34 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 35 | import io.netty.util.concurrent.DefaultThreadFactory; 36 | import okhttp3.OkHttpClient; 37 | import okhttp3.Request; 38 | import okhttp3.RequestBody; 39 | import okhttp3.Response; 40 | import okhttp3.ResponseBody; 41 | 42 | /** 43 | * 44 | * @author 小五老师-云析学院 45 | * @createTime 2019年2月26日 下午9:34:17 46 | * 客户端启动初始化: 47 | * 1、与服务端建立连接 48 | * 2、处理客户端输入 49 | */ 50 | @Component 51 | public class IMClientInit { 52 | 53 | private final static Logger LOGGER = LoggerFactory.getLogger(IMClientInit.class); 54 | private ServerInfo server; 55 | public Channel channel; 56 | private EventLoopGroup group = new NioEventLoopGroup(0, new DefaultThreadFactory("im-client-work")); 57 | 58 | @Autowired 59 | private InitConfiguration conf; 60 | @Autowired 61 | private OkHttpClient okHttpClient; 62 | 63 | @PostConstruct 64 | public void start() throws Exception{ 65 | if(server != null){ 66 | LOGGER.info("---客户端当前已登录"); 67 | return; 68 | } 69 | //1.获取服务端ip+port 70 | getServerInfo(); 71 | //2.启动客户端 72 | startClient(); 73 | //3.登录到服务端 74 | registerToServer(); 75 | 76 | } 77 | 78 | /** 79 | * 与服务端通信 80 | */ 81 | private void registerToServer() { 82 | MessageProto.MessageProtocol login = MessageProto.MessageProtocol.newBuilder() 83 | .setUserId(conf.getUserId()) 84 | .setContent(conf.getUserName()) 85 | .setCommand(MessageConstant.LOGIN) 86 | .setTime(System.currentTimeMillis()) 87 | .build(); 88 | channel.writeAndFlush(login); 89 | } 90 | 91 | /** 92 | * 向路由服务器获取服务端IP与端口 93 | */ 94 | private void getServerInfo() { 95 | try { 96 | JSONObject jsonObject = new JSONObject(); 97 | jsonObject.put("userId",conf.getUserId()); 98 | jsonObject.put("userName",conf.getUserName()); 99 | RequestBody requestBody = RequestBody.create(BasicConstant.MEDIA_TYPE,jsonObject.toString()); 100 | 101 | Request request = new Request.Builder() 102 | .url(conf.getRouteLoginUrl()) 103 | .post(requestBody) 104 | .build(); 105 | 106 | Response response = okHttpClient.newCall(request).execute() ; 107 | if (!response.isSuccessful()){ 108 | throw new IOException("Unexpected code " + response); 109 | } 110 | ResponseBody body = response.body(); 111 | try { 112 | String json = body.string(); 113 | server = JSON.parseObject(json, ServerInfo.class); 114 | 115 | }finally { 116 | body.close(); 117 | } 118 | } catch (IOException e) { 119 | LOGGER.error("连接失败!"); 120 | } 121 | } 122 | 123 | /** 124 | * 启动客户端,建立连接 125 | */ 126 | public void startClient(){ 127 | try { 128 | Bootstrap bootstrap = new Bootstrap(); 129 | bootstrap.group(group) 130 | .channel(NioSocketChannel.class) 131 | .handler(new ChannelInitializer() { 132 | @Override 133 | protected void initChannel(SocketChannel ch) throws Exception { 134 | ChannelPipeline pipeline = ch.pipeline(); 135 | // google Protobuf 编解码 136 | pipeline.addLast(new ProtobufVarint32FrameDecoder()); 137 | pipeline.addLast(new ProtobufDecoder(MessageProto.MessageProtocol.getDefaultInstance())); 138 | pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); 139 | pipeline.addLast(new ProtobufEncoder()); 140 | 141 | pipeline.addLast(new IMClientHandle()); 142 | } 143 | }); 144 | 145 | ChannelFuture future = bootstrap.connect(server.getIp(), server.getNettyPort()).sync(); 146 | if (future.isSuccess()) { 147 | LOGGER.info("---客户端启动成功[nettyport:"+8090+"]"); 148 | } 149 | channel = future.channel(); 150 | } catch (InterruptedException e) { 151 | LOGGER.error("---连接失败", e); 152 | } catch (Exception e) { 153 | e.printStackTrace(); 154 | } finally { 155 | // group.shutdownGracefully(); 156 | } 157 | 158 | } 159 | 160 | /** 161 | * 客户端发送消息 162 | **/ 163 | public void sendMessage(ChatInfo chat){ 164 | try { 165 | JSONObject jsonObject = new JSONObject(); 166 | jsonObject.put("command",chat.getCommand()); 167 | jsonObject.put("time",chat.getTime()); 168 | jsonObject.put("userId",chat.getUserId()); 169 | jsonObject.put("content",chat.getContent()); 170 | RequestBody requestBody = RequestBody.create(BasicConstant.MEDIA_TYPE,jsonObject.toString()); 171 | 172 | Request request = new Request.Builder() 173 | .url(conf.getRouteChatUrl()) 174 | .post(requestBody) 175 | .build(); 176 | Response response = okHttpClient.newCall(request).execute() ; 177 | if (!response.isSuccessful()){ 178 | throw new IOException("Unexpected code " + response); 179 | } 180 | } catch (IOException e) { 181 | e.printStackTrace(); 182 | } 183 | } 184 | 185 | /** 186 | * 清理客户端登录 187 | **/ 188 | public void clear(){ 189 | logoutRoute(); 190 | logoutServer(); 191 | server = null; 192 | } 193 | 194 | /** 195 | * 客户端登出命令-路由端处理 196 | **/ 197 | private void logoutRoute(){ 198 | try { 199 | JSONObject jsonObject = new JSONObject(); 200 | jsonObject.put("userId",conf.getUserId()); 201 | RequestBody requestBody = RequestBody.create(BasicConstant.MEDIA_TYPE,jsonObject.toString()); 202 | 203 | Request request = new Request.Builder() 204 | .url(conf.getRouteLogoutUrl()) 205 | .post(requestBody) 206 | .build(); 207 | 208 | Response response = okHttpClient.newCall(request).execute() ; 209 | if (!response.isSuccessful()){ 210 | throw new IOException("Unexpected code " + response); 211 | } 212 | } catch (IOException e) { 213 | e.printStackTrace(); 214 | } 215 | } 216 | 217 | /** 218 | * 客户端登出命令-路由端处理 219 | **/ 220 | private void logoutServer() { 221 | try { 222 | JSONObject jsonObject = new JSONObject(); 223 | jsonObject.put("userId",conf.getUserId()); 224 | RequestBody requestBody = RequestBody.create(BasicConstant.MEDIA_TYPE,jsonObject.toString()); 225 | 226 | Request request = new Request.Builder() 227 | .url("http://"+server.getIp()+":"+server.getHttpPort()+"/clientLogout") 228 | .post(requestBody) 229 | .build(); 230 | 231 | Response response = okHttpClient.newCall(request).execute() ; 232 | if (!response.isSuccessful()){ 233 | throw new IOException("Unexpected code " + response); 234 | } 235 | } catch (IOException e) { 236 | e.printStackTrace(); 237 | } 238 | } 239 | 240 | public void restart() throws Exception { 241 | //1.清理客户端信息(路由) 242 | logoutRoute(); 243 | server = null; 244 | //2.start 245 | start(); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /im-client/src/main/java/ai/yunxi/im/client/scanner/Scan.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client.scanner; 2 | 3 | import java.util.Scanner; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import ai.yunxi.im.client.config.InitConfiguration; 9 | import ai.yunxi.im.client.config.SpringBeanFactory; 10 | import ai.yunxi.im.client.init.IMClientInit; 11 | import ai.yunxi.im.common.constant.MessageConstant; 12 | import ai.yunxi.im.common.pojo.ChatInfo; 13 | import ai.yunxi.im.common.utils.StringUtil; 14 | 15 | public class Scan implements Runnable { 16 | 17 | private final static Logger LOGGER = LoggerFactory.getLogger(Scan.class); 18 | 19 | private IMClientInit client; 20 | private InitConfiguration conf; 21 | 22 | public Scan() { 23 | super(); 24 | this.client = SpringBeanFactory.getBean(IMClientInit.class); 25 | this.conf = SpringBeanFactory.getBean(InitConfiguration.class); 26 | } 27 | 28 | @Override 29 | public void run() { 30 | Scanner scanner = new Scanner(System.in); 31 | try { 32 | while(true){ 33 | String msg = scanner.nextLine(); 34 | 35 | if(StringUtil.isEmpty(msg)){ 36 | LOGGER.info("---不允许发送空消息!"); 37 | continue; 38 | } 39 | 40 | //处理系统指令,如:LOGIN LOGOUT 等 41 | if(MessageConstant.LOGOUT.equals(msg)){ 42 | //移除登录状态数据 43 | client.clear(); 44 | LOGGER.info("---下线成功,如需加入聊天室,请重新登录"); 45 | continue; 46 | } else if(MessageConstant.LOGIN.equals(msg)){ 47 | client.start(); 48 | LOGGER.info("---重新登录成功"); 49 | continue; 50 | } 51 | 52 | //调用Route端API进行消息发送 53 | ChatInfo chat = new ChatInfo(MessageConstant.CHAT, System.currentTimeMillis(), conf.getUserId(), msg); 54 | client.sendMessage(chat); 55 | } 56 | } catch (Exception e) { 57 | e.printStackTrace(); 58 | } finally { 59 | scanner.close(); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /im-client/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8070 2 | 3 | im.user.id=1001 4 | im.user.userName=Five 5 | 6 | im.route.login.url=http://localhost:8880/login 7 | im.route.chat.url=http://localhost:8880/chat 8 | im.route.logout.url=http://localhost:8880/logout -------------------------------------------------------------------------------- /im-client/src/test/java/ai/yunxi/im/client/AppTest.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.client; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /im-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | ai.yunxi 7 | im 8 | 0.0.1-SNAPSHOT 9 | 10 | im-common 11 | im-common 12 | http://maven.apache.org 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.msgpack 19 | msgpack 20 | 0.6.12 21 | 22 | 23 | com.google.protobuf 24 | protobuf-java 25 | 3.4.0 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /im-common/protobuf/MessageProto.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package protocol; 4 | 5 | option java_package = "ai.yunxi.im.common.protocol"; 6 | option java_outer_classname = "MessageProto"; 7 | 8 | message MessageProtocol { 9 | 10 | required string command = 1; 11 | required int64 time = 2; 12 | required int32 userId = 3; 13 | required string content = 4; 14 | 15 | } -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/constant/BasicConstant.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.common.constant; 2 | 3 | import okhttp3.MediaType; 4 | 5 | /** 6 | * @author 小五老师-云析学院 7 | * @createTime 2019年3月12日 下午10:02:08 8 | * 9 | */ 10 | public final class BasicConstant { 11 | 12 | /** 13 | * redis中客户端服务端映射前缀 14 | **/ 15 | public static final String ROUTE_PREFIX = "im-route:"; 16 | 17 | /** 18 | * 响应格式 19 | **/ 20 | public static final MediaType MEDIA_TYPE = MediaType.parse("application/json"); 21 | } 22 | -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/constant/MessageConstant.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.common.constant; 2 | /** 3 | * 4 | * @author Five老师 5 | * @createTime 2018年3月27日 下午8:23:31 6 | * 7 | */ 8 | public class MessageConstant { 9 | 10 | public static final String LOGIN="LOGIN"; 11 | public static final String LOGOUT="LOGOUT"; 12 | public static final String CHAT="CHAT"; 13 | public static final String SYSTEM="SYSTEM"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/enums/StatusEnum.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.common.enums; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public enum StatusEnum { 7 | 8 | /** 成功 */ 9 | SUCCESS("9000", "成功"), 10 | /** 成功 */ 11 | FALLBACK("8000", "FALL_BACK"), 12 | /** 参数校验失败**/ 13 | VALIDATION_FAIL("3000", "参数校验失败"), 14 | /** 失败 */ 15 | FAIL("4000", "失败"), 16 | 17 | /** 重复登录 */ 18 | REPEAT_LOGIN("5000", "账号重复登录,请退出一个账号!"), 19 | 20 | /** 账号不在线 */ 21 | OFF_LINE("7000", "你选择的账号不在线,请重新选择!"), 22 | 23 | /** 登录信息不匹配 */ 24 | ACCOUNT_NOT_MATCH("9100", "登录信息不匹配!"), 25 | 26 | /** 请求限流 */ 27 | REQUEST_LIMIT("6000", "请求限流"), 28 | ; 29 | 30 | 31 | /** 枚举值码 */ 32 | private final String code; 33 | 34 | /** 枚举描述 */ 35 | private final String message; 36 | 37 | /** 38 | * 构建一个 StatusEnum 。 39 | * @param code 枚举值码。 40 | * @param message 枚举描述。 41 | */ 42 | private StatusEnum(String code, String message) { 43 | this.code = code; 44 | this.message = message; 45 | } 46 | 47 | /** 48 | * 得到枚举值码。 49 | * @return 枚举值码。 50 | */ 51 | public String getCode() { 52 | return code; 53 | } 54 | 55 | /** 56 | * 得到枚举描述。 57 | * @return 枚举描述。 58 | */ 59 | public String getMessage() { 60 | return message; 61 | } 62 | 63 | /** 64 | * 得到枚举值码。 65 | * @return 枚举值码。 66 | */ 67 | public String code() { 68 | return code; 69 | } 70 | 71 | /** 72 | * 得到枚举描述。 73 | * @return 枚举描述。 74 | */ 75 | public String message() { 76 | return message; 77 | } 78 | 79 | /** 80 | * 通过枚举值码查找枚举值。 81 | * @param code 查找枚举值的枚举值码。 82 | * @return 枚举值码对应的枚举值。 83 | * @throws IllegalArgumentException 如果 code 没有对应的 StatusEnum 。 84 | */ 85 | public static StatusEnum findStatus(String code) { 86 | for (StatusEnum status : values()) { 87 | if (status.getCode().equals(code)) { 88 | return status; 89 | } 90 | } 91 | throw new IllegalArgumentException("ResultInfo StatusEnum not legal:" + code); 92 | } 93 | 94 | /** 95 | * 获取全部枚举值。 96 | * 97 | * @return 全部枚举值。 98 | */ 99 | public static List getAllStatus() { 100 | List list = new ArrayList(); 101 | for (StatusEnum status : values()) { 102 | list.add(status); 103 | } 104 | return list; 105 | } 106 | 107 | /** 108 | * 获取全部枚举值码。 109 | * 110 | * @return 全部枚举值码。 111 | */ 112 | public static List getAllStatusCode() { 113 | List list = new ArrayList(); 114 | for (StatusEnum status : values()) { 115 | list.add(status.code()); 116 | } 117 | return list; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/pojo/ChatInfo.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.common.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 7 | * @author 小五老师-云析学院 8 | * @createTime 2019年3月7日 下午5:27:21 9 | * 10 | */ 11 | public class ChatInfo implements Serializable { 12 | 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1360647504610967672L; 17 | private String command; 18 | private Long time; 19 | private Integer userId; 20 | private String content; 21 | 22 | public ChatInfo(String command, Long time, Integer userId, String content) { 23 | this.command = command; 24 | this.time = time; 25 | this.userId = userId; 26 | this.content = content; 27 | } 28 | public ChatInfo() { 29 | } 30 | public String getCommand() { 31 | return command; 32 | } 33 | public void setCommand(String command) { 34 | this.command = command; 35 | } 36 | public Long getTime() { 37 | return time; 38 | } 39 | public void setTime(Long time) { 40 | this.time = time; 41 | } 42 | public Integer getUserId() { 43 | return userId; 44 | } 45 | public void setUserId(Integer userId) { 46 | this.userId = userId; 47 | } 48 | public String getContent() { 49 | return content; 50 | } 51 | public void setContent(String content) { 52 | this.content = content; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/pojo/ServerInfo.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.common.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 7 | * @author 小五老师-云析学院 8 | * @createTime 2019年2月26日 下午9:14:28 9 | * 服务端基本信息 10 | */ 11 | public class ServerInfo implements Serializable { 12 | 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = -2230742812761280401L; 17 | private String ip; 18 | private Integer nettyPort; 19 | private Integer httpPort; 20 | 21 | public ServerInfo() { 22 | } 23 | public ServerInfo(String ip, Integer nettyPort, Integer httpPort) { 24 | super(); 25 | this.ip = ip; 26 | this.nettyPort = nettyPort; 27 | this.httpPort = httpPort; 28 | } 29 | public String getIp() { 30 | return ip; 31 | } 32 | public void setIp(String ip) { 33 | this.ip = ip; 34 | } 35 | public Integer getNettyPort() { 36 | return nettyPort; 37 | } 38 | public void setNettyPort(Integer nettyPort) { 39 | this.nettyPort = nettyPort; 40 | } 41 | public Integer getHttpPort() { 42 | return httpPort; 43 | } 44 | public void setHttpPort(Integer httpPort) { 45 | this.httpPort = httpPort; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/pojo/UserInfo.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.common.pojo; 2 | /** 3 | * 4 | * @author 小五老师-云析学院 5 | * @createTime 2019年2月27日 下午3:29:38 6 | * 7 | */ 8 | 9 | import java.io.Serializable; 10 | 11 | public class UserInfo implements Serializable { 12 | 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 4227244327808705724L; 17 | private Integer userId; 18 | private String userName; 19 | public Integer getUserId() { 20 | return userId; 21 | } 22 | public void setUserId(Integer userId) { 23 | this.userId = userId; 24 | } 25 | public String getUserName() { 26 | return userName; 27 | } 28 | public void setUserName(String userName) { 29 | this.userName = userName; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/protocol/MessageProto.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: MessageProto.proto 3 | 4 | package ai.yunxi.im.common.protocol; 5 | 6 | public final class MessageProto { 7 | private MessageProto() {} 8 | public static void registerAllExtensions( 9 | com.google.protobuf.ExtensionRegistryLite registry) { 10 | } 11 | 12 | public static void registerAllExtensions( 13 | com.google.protobuf.ExtensionRegistry registry) { 14 | registerAllExtensions( 15 | (com.google.protobuf.ExtensionRegistryLite) registry); 16 | } 17 | public interface MessageProtocolOrBuilder extends 18 | // @@protoc_insertion_point(interface_extends:protocol.MessageProtocol) 19 | com.google.protobuf.MessageOrBuilder { 20 | 21 | /** 22 | * required string command = 1; 23 | */ 24 | boolean hasCommand(); 25 | /** 26 | * required string command = 1; 27 | */ 28 | java.lang.String getCommand(); 29 | /** 30 | * required string command = 1; 31 | */ 32 | com.google.protobuf.ByteString 33 | getCommandBytes(); 34 | 35 | /** 36 | * required int64 time = 2; 37 | */ 38 | boolean hasTime(); 39 | /** 40 | * required int64 time = 2; 41 | */ 42 | long getTime(); 43 | 44 | /** 45 | * required int32 userId = 3; 46 | */ 47 | boolean hasUserId(); 48 | /** 49 | * required int32 userId = 3; 50 | */ 51 | int getUserId(); 52 | 53 | /** 54 | * required string content = 4; 55 | */ 56 | boolean hasContent(); 57 | /** 58 | * required string content = 4; 59 | */ 60 | java.lang.String getContent(); 61 | /** 62 | * required string content = 4; 63 | */ 64 | com.google.protobuf.ByteString 65 | getContentBytes(); 66 | } 67 | /** 68 | * Protobuf type {@code protocol.MessageProtocol} 69 | */ 70 | public static final class MessageProtocol extends 71 | com.google.protobuf.GeneratedMessageV3 implements 72 | // @@protoc_insertion_point(message_implements:protocol.MessageProtocol) 73 | MessageProtocolOrBuilder { 74 | private static final long serialVersionUID = 0L; 75 | // Use MessageProtocol.newBuilder() to construct. 76 | private MessageProtocol(com.google.protobuf.GeneratedMessageV3.Builder builder) { 77 | super(builder); 78 | } 79 | private MessageProtocol() { 80 | command_ = ""; 81 | content_ = ""; 82 | } 83 | 84 | @java.lang.Override 85 | public final com.google.protobuf.UnknownFieldSet 86 | getUnknownFields() { 87 | return this.unknownFields; 88 | } 89 | private MessageProtocol( 90 | com.google.protobuf.CodedInputStream input, 91 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 92 | throws com.google.protobuf.InvalidProtocolBufferException { 93 | this(); 94 | if (extensionRegistry == null) { 95 | throw new java.lang.NullPointerException(); 96 | } 97 | com.google.protobuf.UnknownFieldSet.Builder unknownFields = 98 | com.google.protobuf.UnknownFieldSet.newBuilder(); 99 | try { 100 | boolean done = false; 101 | while (!done) { 102 | int tag = input.readTag(); 103 | switch (tag) { 104 | case 0: 105 | done = true; 106 | break; 107 | case 10: { 108 | com.google.protobuf.ByteString bs = input.readBytes(); 109 | bitField0_ |= 0x00000001; 110 | command_ = bs; 111 | break; 112 | } 113 | case 16: { 114 | bitField0_ |= 0x00000002; 115 | time_ = input.readInt64(); 116 | break; 117 | } 118 | case 24: { 119 | bitField0_ |= 0x00000004; 120 | userId_ = input.readInt32(); 121 | break; 122 | } 123 | case 34: { 124 | com.google.protobuf.ByteString bs = input.readBytes(); 125 | bitField0_ |= 0x00000008; 126 | content_ = bs; 127 | break; 128 | } 129 | default: { 130 | if (!parseUnknownField( 131 | input, unknownFields, extensionRegistry, tag)) { 132 | done = true; 133 | } 134 | break; 135 | } 136 | } 137 | } 138 | } catch (com.google.protobuf.InvalidProtocolBufferException e) { 139 | throw e.setUnfinishedMessage(this); 140 | } catch (java.io.IOException e) { 141 | throw new com.google.protobuf.InvalidProtocolBufferException( 142 | e).setUnfinishedMessage(this); 143 | } finally { 144 | this.unknownFields = unknownFields.build(); 145 | makeExtensionsImmutable(); 146 | } 147 | } 148 | public static final com.google.protobuf.Descriptors.Descriptor 149 | getDescriptor() { 150 | return ai.yunxi.im.common.protocol.MessageProto.internal_static_protocol_MessageProtocol_descriptor; 151 | } 152 | 153 | @java.lang.Override 154 | protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable 155 | internalGetFieldAccessorTable() { 156 | return ai.yunxi.im.common.protocol.MessageProto.internal_static_protocol_MessageProtocol_fieldAccessorTable 157 | .ensureFieldAccessorsInitialized( 158 | ai.yunxi.im.common.protocol.MessageProto.MessageProtocol.class, ai.yunxi.im.common.protocol.MessageProto.MessageProtocol.Builder.class); 159 | } 160 | 161 | private int bitField0_; 162 | public static final int COMMAND_FIELD_NUMBER = 1; 163 | private volatile java.lang.Object command_; 164 | /** 165 | * required string command = 1; 166 | */ 167 | public boolean hasCommand() { 168 | return ((bitField0_ & 0x00000001) != 0); 169 | } 170 | /** 171 | * required string command = 1; 172 | */ 173 | public java.lang.String getCommand() { 174 | java.lang.Object ref = command_; 175 | if (ref instanceof java.lang.String) { 176 | return (java.lang.String) ref; 177 | } else { 178 | com.google.protobuf.ByteString bs = 179 | (com.google.protobuf.ByteString) ref; 180 | java.lang.String s = bs.toStringUtf8(); 181 | if (bs.isValidUtf8()) { 182 | command_ = s; 183 | } 184 | return s; 185 | } 186 | } 187 | /** 188 | * required string command = 1; 189 | */ 190 | public com.google.protobuf.ByteString 191 | getCommandBytes() { 192 | java.lang.Object ref = command_; 193 | if (ref instanceof java.lang.String) { 194 | com.google.protobuf.ByteString b = 195 | com.google.protobuf.ByteString.copyFromUtf8( 196 | (java.lang.String) ref); 197 | command_ = b; 198 | return b; 199 | } else { 200 | return (com.google.protobuf.ByteString) ref; 201 | } 202 | } 203 | 204 | public static final int TIME_FIELD_NUMBER = 2; 205 | private long time_; 206 | /** 207 | * required int64 time = 2; 208 | */ 209 | public boolean hasTime() { 210 | return ((bitField0_ & 0x00000002) != 0); 211 | } 212 | /** 213 | * required int64 time = 2; 214 | */ 215 | public long getTime() { 216 | return time_; 217 | } 218 | 219 | public static final int USERID_FIELD_NUMBER = 3; 220 | private int userId_; 221 | /** 222 | * required int32 userId = 3; 223 | */ 224 | public boolean hasUserId() { 225 | return ((bitField0_ & 0x00000004) != 0); 226 | } 227 | /** 228 | * required int32 userId = 3; 229 | */ 230 | public int getUserId() { 231 | return userId_; 232 | } 233 | 234 | public static final int CONTENT_FIELD_NUMBER = 4; 235 | private volatile java.lang.Object content_; 236 | /** 237 | * required string content = 4; 238 | */ 239 | public boolean hasContent() { 240 | return ((bitField0_ & 0x00000008) != 0); 241 | } 242 | /** 243 | * required string content = 4; 244 | */ 245 | public java.lang.String getContent() { 246 | java.lang.Object ref = content_; 247 | if (ref instanceof java.lang.String) { 248 | return (java.lang.String) ref; 249 | } else { 250 | com.google.protobuf.ByteString bs = 251 | (com.google.protobuf.ByteString) ref; 252 | java.lang.String s = bs.toStringUtf8(); 253 | if (bs.isValidUtf8()) { 254 | content_ = s; 255 | } 256 | return s; 257 | } 258 | } 259 | /** 260 | * required string content = 4; 261 | */ 262 | public com.google.protobuf.ByteString 263 | getContentBytes() { 264 | java.lang.Object ref = content_; 265 | if (ref instanceof java.lang.String) { 266 | com.google.protobuf.ByteString b = 267 | com.google.protobuf.ByteString.copyFromUtf8( 268 | (java.lang.String) ref); 269 | content_ = b; 270 | return b; 271 | } else { 272 | return (com.google.protobuf.ByteString) ref; 273 | } 274 | } 275 | 276 | private byte memoizedIsInitialized = -1; 277 | @java.lang.Override 278 | public final boolean isInitialized() { 279 | byte isInitialized = memoizedIsInitialized; 280 | if (isInitialized == 1) return true; 281 | if (isInitialized == 0) return false; 282 | 283 | if (!hasCommand()) { 284 | memoizedIsInitialized = 0; 285 | return false; 286 | } 287 | if (!hasTime()) { 288 | memoizedIsInitialized = 0; 289 | return false; 290 | } 291 | if (!hasUserId()) { 292 | memoizedIsInitialized = 0; 293 | return false; 294 | } 295 | if (!hasContent()) { 296 | memoizedIsInitialized = 0; 297 | return false; 298 | } 299 | memoizedIsInitialized = 1; 300 | return true; 301 | } 302 | 303 | @java.lang.Override 304 | public void writeTo(com.google.protobuf.CodedOutputStream output) 305 | throws java.io.IOException { 306 | if (((bitField0_ & 0x00000001) != 0)) { 307 | com.google.protobuf.GeneratedMessageV3.writeString(output, 1, command_); 308 | } 309 | if (((bitField0_ & 0x00000002) != 0)) { 310 | output.writeInt64(2, time_); 311 | } 312 | if (((bitField0_ & 0x00000004) != 0)) { 313 | output.writeInt32(3, userId_); 314 | } 315 | if (((bitField0_ & 0x00000008) != 0)) { 316 | com.google.protobuf.GeneratedMessageV3.writeString(output, 4, content_); 317 | } 318 | unknownFields.writeTo(output); 319 | } 320 | 321 | @java.lang.Override 322 | public int getSerializedSize() { 323 | int size = memoizedSize; 324 | if (size != -1) return size; 325 | 326 | size = 0; 327 | if (((bitField0_ & 0x00000001) != 0)) { 328 | size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, command_); 329 | } 330 | if (((bitField0_ & 0x00000002) != 0)) { 331 | size += com.google.protobuf.CodedOutputStream 332 | .computeInt64Size(2, time_); 333 | } 334 | if (((bitField0_ & 0x00000004) != 0)) { 335 | size += com.google.protobuf.CodedOutputStream 336 | .computeInt32Size(3, userId_); 337 | } 338 | if (((bitField0_ & 0x00000008) != 0)) { 339 | size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, content_); 340 | } 341 | size += unknownFields.getSerializedSize(); 342 | memoizedSize = size; 343 | return size; 344 | } 345 | 346 | @java.lang.Override 347 | public boolean equals(final java.lang.Object obj) { 348 | if (obj == this) { 349 | return true; 350 | } 351 | if (!(obj instanceof ai.yunxi.im.common.protocol.MessageProto.MessageProtocol)) { 352 | return super.equals(obj); 353 | } 354 | ai.yunxi.im.common.protocol.MessageProto.MessageProtocol other = (ai.yunxi.im.common.protocol.MessageProto.MessageProtocol) obj; 355 | 356 | if (hasCommand() != other.hasCommand()) return false; 357 | if (hasCommand()) { 358 | if (!getCommand() 359 | .equals(other.getCommand())) return false; 360 | } 361 | if (hasTime() != other.hasTime()) return false; 362 | if (hasTime()) { 363 | if (getTime() 364 | != other.getTime()) return false; 365 | } 366 | if (hasUserId() != other.hasUserId()) return false; 367 | if (hasUserId()) { 368 | if (getUserId() 369 | != other.getUserId()) return false; 370 | } 371 | if (hasContent() != other.hasContent()) return false; 372 | if (hasContent()) { 373 | if (!getContent() 374 | .equals(other.getContent())) return false; 375 | } 376 | if (!unknownFields.equals(other.unknownFields)) return false; 377 | return true; 378 | } 379 | 380 | @java.lang.Override 381 | public int hashCode() { 382 | if (memoizedHashCode != 0) { 383 | return memoizedHashCode; 384 | } 385 | int hash = 41; 386 | hash = (19 * hash) + getDescriptor().hashCode(); 387 | if (hasCommand()) { 388 | hash = (37 * hash) + COMMAND_FIELD_NUMBER; 389 | hash = (53 * hash) + getCommand().hashCode(); 390 | } 391 | if (hasTime()) { 392 | hash = (37 * hash) + TIME_FIELD_NUMBER; 393 | hash = (53 * hash) + com.google.protobuf.Internal.hashLong( 394 | getTime()); 395 | } 396 | if (hasUserId()) { 397 | hash = (37 * hash) + USERID_FIELD_NUMBER; 398 | hash = (53 * hash) + getUserId(); 399 | } 400 | if (hasContent()) { 401 | hash = (37 * hash) + CONTENT_FIELD_NUMBER; 402 | hash = (53 * hash) + getContent().hashCode(); 403 | } 404 | hash = (29 * hash) + unknownFields.hashCode(); 405 | return hash; 406 | } 407 | 408 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 409 | java.nio.ByteBuffer data) 410 | throws com.google.protobuf.InvalidProtocolBufferException { 411 | return PARSER.parseFrom(data); 412 | } 413 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 414 | java.nio.ByteBuffer data, 415 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 416 | throws com.google.protobuf.InvalidProtocolBufferException { 417 | return PARSER.parseFrom(data, extensionRegistry); 418 | } 419 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 420 | com.google.protobuf.ByteString data) 421 | throws com.google.protobuf.InvalidProtocolBufferException { 422 | return PARSER.parseFrom(data); 423 | } 424 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 425 | com.google.protobuf.ByteString data, 426 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 427 | throws com.google.protobuf.InvalidProtocolBufferException { 428 | return PARSER.parseFrom(data, extensionRegistry); 429 | } 430 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom(byte[] data) 431 | throws com.google.protobuf.InvalidProtocolBufferException { 432 | return PARSER.parseFrom(data); 433 | } 434 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 435 | byte[] data, 436 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 437 | throws com.google.protobuf.InvalidProtocolBufferException { 438 | return PARSER.parseFrom(data, extensionRegistry); 439 | } 440 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom(java.io.InputStream input) 441 | throws java.io.IOException { 442 | return com.google.protobuf.GeneratedMessageV3 443 | .parseWithIOException(PARSER, input); 444 | } 445 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 446 | java.io.InputStream input, 447 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 448 | throws java.io.IOException { 449 | return com.google.protobuf.GeneratedMessageV3 450 | .parseWithIOException(PARSER, input, extensionRegistry); 451 | } 452 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseDelimitedFrom(java.io.InputStream input) 453 | throws java.io.IOException { 454 | return com.google.protobuf.GeneratedMessageV3 455 | .parseDelimitedWithIOException(PARSER, input); 456 | } 457 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseDelimitedFrom( 458 | java.io.InputStream input, 459 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 460 | throws java.io.IOException { 461 | return com.google.protobuf.GeneratedMessageV3 462 | .parseDelimitedWithIOException(PARSER, input, extensionRegistry); 463 | } 464 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 465 | com.google.protobuf.CodedInputStream input) 466 | throws java.io.IOException { 467 | return com.google.protobuf.GeneratedMessageV3 468 | .parseWithIOException(PARSER, input); 469 | } 470 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parseFrom( 471 | com.google.protobuf.CodedInputStream input, 472 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 473 | throws java.io.IOException { 474 | return com.google.protobuf.GeneratedMessageV3 475 | .parseWithIOException(PARSER, input, extensionRegistry); 476 | } 477 | 478 | @java.lang.Override 479 | public Builder newBuilderForType() { return newBuilder(); } 480 | public static Builder newBuilder() { 481 | return DEFAULT_INSTANCE.toBuilder(); 482 | } 483 | public static Builder newBuilder(ai.yunxi.im.common.protocol.MessageProto.MessageProtocol prototype) { 484 | return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); 485 | } 486 | @java.lang.Override 487 | public Builder toBuilder() { 488 | return this == DEFAULT_INSTANCE 489 | ? new Builder() : new Builder().mergeFrom(this); 490 | } 491 | 492 | @java.lang.Override 493 | protected Builder newBuilderForType( 494 | com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { 495 | Builder builder = new Builder(parent); 496 | return builder; 497 | } 498 | /** 499 | * Protobuf type {@code protocol.MessageProtocol} 500 | */ 501 | public static final class Builder extends 502 | com.google.protobuf.GeneratedMessageV3.Builder implements 503 | // @@protoc_insertion_point(builder_implements:protocol.MessageProtocol) 504 | ai.yunxi.im.common.protocol.MessageProto.MessageProtocolOrBuilder { 505 | public static final com.google.protobuf.Descriptors.Descriptor 506 | getDescriptor() { 507 | return ai.yunxi.im.common.protocol.MessageProto.internal_static_protocol_MessageProtocol_descriptor; 508 | } 509 | 510 | @java.lang.Override 511 | protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable 512 | internalGetFieldAccessorTable() { 513 | return ai.yunxi.im.common.protocol.MessageProto.internal_static_protocol_MessageProtocol_fieldAccessorTable 514 | .ensureFieldAccessorsInitialized( 515 | ai.yunxi.im.common.protocol.MessageProto.MessageProtocol.class, ai.yunxi.im.common.protocol.MessageProto.MessageProtocol.Builder.class); 516 | } 517 | 518 | // Construct using ai.yunxi.im.common.protocol.MessageProto.MessageProtocol.newBuilder() 519 | private Builder() { 520 | maybeForceBuilderInitialization(); 521 | } 522 | 523 | private Builder( 524 | com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { 525 | super(parent); 526 | maybeForceBuilderInitialization(); 527 | } 528 | private void maybeForceBuilderInitialization() { 529 | if (com.google.protobuf.GeneratedMessageV3 530 | .alwaysUseFieldBuilders) { 531 | } 532 | } 533 | @java.lang.Override 534 | public Builder clear() { 535 | super.clear(); 536 | command_ = ""; 537 | bitField0_ = (bitField0_ & ~0x00000001); 538 | time_ = 0L; 539 | bitField0_ = (bitField0_ & ~0x00000002); 540 | userId_ = 0; 541 | bitField0_ = (bitField0_ & ~0x00000004); 542 | content_ = ""; 543 | bitField0_ = (bitField0_ & ~0x00000008); 544 | return this; 545 | } 546 | 547 | @java.lang.Override 548 | public com.google.protobuf.Descriptors.Descriptor 549 | getDescriptorForType() { 550 | return ai.yunxi.im.common.protocol.MessageProto.internal_static_protocol_MessageProtocol_descriptor; 551 | } 552 | 553 | @java.lang.Override 554 | public ai.yunxi.im.common.protocol.MessageProto.MessageProtocol getDefaultInstanceForType() { 555 | return ai.yunxi.im.common.protocol.MessageProto.MessageProtocol.getDefaultInstance(); 556 | } 557 | 558 | @java.lang.Override 559 | public ai.yunxi.im.common.protocol.MessageProto.MessageProtocol build() { 560 | ai.yunxi.im.common.protocol.MessageProto.MessageProtocol result = buildPartial(); 561 | if (!result.isInitialized()) { 562 | throw newUninitializedMessageException(result); 563 | } 564 | return result; 565 | } 566 | 567 | @java.lang.Override 568 | public ai.yunxi.im.common.protocol.MessageProto.MessageProtocol buildPartial() { 569 | ai.yunxi.im.common.protocol.MessageProto.MessageProtocol result = new ai.yunxi.im.common.protocol.MessageProto.MessageProtocol(this); 570 | int from_bitField0_ = bitField0_; 571 | int to_bitField0_ = 0; 572 | if (((from_bitField0_ & 0x00000001) != 0)) { 573 | to_bitField0_ |= 0x00000001; 574 | } 575 | result.command_ = command_; 576 | if (((from_bitField0_ & 0x00000002) != 0)) { 577 | result.time_ = time_; 578 | to_bitField0_ |= 0x00000002; 579 | } 580 | if (((from_bitField0_ & 0x00000004) != 0)) { 581 | result.userId_ = userId_; 582 | to_bitField0_ |= 0x00000004; 583 | } 584 | if (((from_bitField0_ & 0x00000008) != 0)) { 585 | to_bitField0_ |= 0x00000008; 586 | } 587 | result.content_ = content_; 588 | result.bitField0_ = to_bitField0_; 589 | onBuilt(); 590 | return result; 591 | } 592 | 593 | @java.lang.Override 594 | public Builder clone() { 595 | return super.clone(); 596 | } 597 | @java.lang.Override 598 | public Builder setField( 599 | com.google.protobuf.Descriptors.FieldDescriptor field, 600 | java.lang.Object value) { 601 | return super.setField(field, value); 602 | } 603 | @java.lang.Override 604 | public Builder clearField( 605 | com.google.protobuf.Descriptors.FieldDescriptor field) { 606 | return super.clearField(field); 607 | } 608 | @java.lang.Override 609 | public Builder clearOneof( 610 | com.google.protobuf.Descriptors.OneofDescriptor oneof) { 611 | return super.clearOneof(oneof); 612 | } 613 | @java.lang.Override 614 | public Builder setRepeatedField( 615 | com.google.protobuf.Descriptors.FieldDescriptor field, 616 | int index, java.lang.Object value) { 617 | return super.setRepeatedField(field, index, value); 618 | } 619 | @java.lang.Override 620 | public Builder addRepeatedField( 621 | com.google.protobuf.Descriptors.FieldDescriptor field, 622 | java.lang.Object value) { 623 | return super.addRepeatedField(field, value); 624 | } 625 | @java.lang.Override 626 | public Builder mergeFrom(com.google.protobuf.Message other) { 627 | if (other instanceof ai.yunxi.im.common.protocol.MessageProto.MessageProtocol) { 628 | return mergeFrom((ai.yunxi.im.common.protocol.MessageProto.MessageProtocol)other); 629 | } else { 630 | super.mergeFrom(other); 631 | return this; 632 | } 633 | } 634 | 635 | public Builder mergeFrom(ai.yunxi.im.common.protocol.MessageProto.MessageProtocol other) { 636 | if (other == ai.yunxi.im.common.protocol.MessageProto.MessageProtocol.getDefaultInstance()) return this; 637 | if (other.hasCommand()) { 638 | bitField0_ |= 0x00000001; 639 | command_ = other.command_; 640 | onChanged(); 641 | } 642 | if (other.hasTime()) { 643 | setTime(other.getTime()); 644 | } 645 | if (other.hasUserId()) { 646 | setUserId(other.getUserId()); 647 | } 648 | if (other.hasContent()) { 649 | bitField0_ |= 0x00000008; 650 | content_ = other.content_; 651 | onChanged(); 652 | } 653 | this.mergeUnknownFields(other.unknownFields); 654 | onChanged(); 655 | return this; 656 | } 657 | 658 | @java.lang.Override 659 | public final boolean isInitialized() { 660 | if (!hasCommand()) { 661 | return false; 662 | } 663 | if (!hasTime()) { 664 | return false; 665 | } 666 | if (!hasUserId()) { 667 | return false; 668 | } 669 | if (!hasContent()) { 670 | return false; 671 | } 672 | return true; 673 | } 674 | 675 | @java.lang.Override 676 | public Builder mergeFrom( 677 | com.google.protobuf.CodedInputStream input, 678 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 679 | throws java.io.IOException { 680 | ai.yunxi.im.common.protocol.MessageProto.MessageProtocol parsedMessage = null; 681 | try { 682 | parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); 683 | } catch (com.google.protobuf.InvalidProtocolBufferException e) { 684 | parsedMessage = (ai.yunxi.im.common.protocol.MessageProto.MessageProtocol) e.getUnfinishedMessage(); 685 | throw e.unwrapIOException(); 686 | } finally { 687 | if (parsedMessage != null) { 688 | mergeFrom(parsedMessage); 689 | } 690 | } 691 | return this; 692 | } 693 | private int bitField0_; 694 | 695 | private java.lang.Object command_ = ""; 696 | /** 697 | * required string command = 1; 698 | */ 699 | public boolean hasCommand() { 700 | return ((bitField0_ & 0x00000001) != 0); 701 | } 702 | /** 703 | * required string command = 1; 704 | */ 705 | public java.lang.String getCommand() { 706 | java.lang.Object ref = command_; 707 | if (!(ref instanceof java.lang.String)) { 708 | com.google.protobuf.ByteString bs = 709 | (com.google.protobuf.ByteString) ref; 710 | java.lang.String s = bs.toStringUtf8(); 711 | if (bs.isValidUtf8()) { 712 | command_ = s; 713 | } 714 | return s; 715 | } else { 716 | return (java.lang.String) ref; 717 | } 718 | } 719 | /** 720 | * required string command = 1; 721 | */ 722 | public com.google.protobuf.ByteString 723 | getCommandBytes() { 724 | java.lang.Object ref = command_; 725 | if (ref instanceof String) { 726 | com.google.protobuf.ByteString b = 727 | com.google.protobuf.ByteString.copyFromUtf8( 728 | (java.lang.String) ref); 729 | command_ = b; 730 | return b; 731 | } else { 732 | return (com.google.protobuf.ByteString) ref; 733 | } 734 | } 735 | /** 736 | * required string command = 1; 737 | */ 738 | public Builder setCommand( 739 | java.lang.String value) { 740 | if (value == null) { 741 | throw new NullPointerException(); 742 | } 743 | bitField0_ |= 0x00000001; 744 | command_ = value; 745 | onChanged(); 746 | return this; 747 | } 748 | /** 749 | * required string command = 1; 750 | */ 751 | public Builder clearCommand() { 752 | bitField0_ = (bitField0_ & ~0x00000001); 753 | command_ = getDefaultInstance().getCommand(); 754 | onChanged(); 755 | return this; 756 | } 757 | /** 758 | * required string command = 1; 759 | */ 760 | public Builder setCommandBytes( 761 | com.google.protobuf.ByteString value) { 762 | if (value == null) { 763 | throw new NullPointerException(); 764 | } 765 | bitField0_ |= 0x00000001; 766 | command_ = value; 767 | onChanged(); 768 | return this; 769 | } 770 | 771 | private long time_ ; 772 | /** 773 | * required int64 time = 2; 774 | */ 775 | public boolean hasTime() { 776 | return ((bitField0_ & 0x00000002) != 0); 777 | } 778 | /** 779 | * required int64 time = 2; 780 | */ 781 | public long getTime() { 782 | return time_; 783 | } 784 | /** 785 | * required int64 time = 2; 786 | */ 787 | public Builder setTime(long value) { 788 | bitField0_ |= 0x00000002; 789 | time_ = value; 790 | onChanged(); 791 | return this; 792 | } 793 | /** 794 | * required int64 time = 2; 795 | */ 796 | public Builder clearTime() { 797 | bitField0_ = (bitField0_ & ~0x00000002); 798 | time_ = 0L; 799 | onChanged(); 800 | return this; 801 | } 802 | 803 | private int userId_ ; 804 | /** 805 | * required int32 userId = 3; 806 | */ 807 | public boolean hasUserId() { 808 | return ((bitField0_ & 0x00000004) != 0); 809 | } 810 | /** 811 | * required int32 userId = 3; 812 | */ 813 | public int getUserId() { 814 | return userId_; 815 | } 816 | /** 817 | * required int32 userId = 3; 818 | */ 819 | public Builder setUserId(int value) { 820 | bitField0_ |= 0x00000004; 821 | userId_ = value; 822 | onChanged(); 823 | return this; 824 | } 825 | /** 826 | * required int32 userId = 3; 827 | */ 828 | public Builder clearUserId() { 829 | bitField0_ = (bitField0_ & ~0x00000004); 830 | userId_ = 0; 831 | onChanged(); 832 | return this; 833 | } 834 | 835 | private java.lang.Object content_ = ""; 836 | /** 837 | * required string content = 4; 838 | */ 839 | public boolean hasContent() { 840 | return ((bitField0_ & 0x00000008) != 0); 841 | } 842 | /** 843 | * required string content = 4; 844 | */ 845 | public java.lang.String getContent() { 846 | java.lang.Object ref = content_; 847 | if (!(ref instanceof java.lang.String)) { 848 | com.google.protobuf.ByteString bs = 849 | (com.google.protobuf.ByteString) ref; 850 | java.lang.String s = bs.toStringUtf8(); 851 | if (bs.isValidUtf8()) { 852 | content_ = s; 853 | } 854 | return s; 855 | } else { 856 | return (java.lang.String) ref; 857 | } 858 | } 859 | /** 860 | * required string content = 4; 861 | */ 862 | public com.google.protobuf.ByteString 863 | getContentBytes() { 864 | java.lang.Object ref = content_; 865 | if (ref instanceof String) { 866 | com.google.protobuf.ByteString b = 867 | com.google.protobuf.ByteString.copyFromUtf8( 868 | (java.lang.String) ref); 869 | content_ = b; 870 | return b; 871 | } else { 872 | return (com.google.protobuf.ByteString) ref; 873 | } 874 | } 875 | /** 876 | * required string content = 4; 877 | */ 878 | public Builder setContent( 879 | java.lang.String value) { 880 | if (value == null) { 881 | throw new NullPointerException(); 882 | } 883 | bitField0_ |= 0x00000008; 884 | content_ = value; 885 | onChanged(); 886 | return this; 887 | } 888 | /** 889 | * required string content = 4; 890 | */ 891 | public Builder clearContent() { 892 | bitField0_ = (bitField0_ & ~0x00000008); 893 | content_ = getDefaultInstance().getContent(); 894 | onChanged(); 895 | return this; 896 | } 897 | /** 898 | * required string content = 4; 899 | */ 900 | public Builder setContentBytes( 901 | com.google.protobuf.ByteString value) { 902 | if (value == null) { 903 | throw new NullPointerException(); 904 | } 905 | bitField0_ |= 0x00000008; 906 | content_ = value; 907 | onChanged(); 908 | return this; 909 | } 910 | @java.lang.Override 911 | public final Builder setUnknownFields( 912 | final com.google.protobuf.UnknownFieldSet unknownFields) { 913 | return super.setUnknownFields(unknownFields); 914 | } 915 | 916 | @java.lang.Override 917 | public final Builder mergeUnknownFields( 918 | final com.google.protobuf.UnknownFieldSet unknownFields) { 919 | return super.mergeUnknownFields(unknownFields); 920 | } 921 | 922 | 923 | // @@protoc_insertion_point(builder_scope:protocol.MessageProtocol) 924 | } 925 | 926 | // @@protoc_insertion_point(class_scope:protocol.MessageProtocol) 927 | private static final ai.yunxi.im.common.protocol.MessageProto.MessageProtocol DEFAULT_INSTANCE; 928 | static { 929 | DEFAULT_INSTANCE = new ai.yunxi.im.common.protocol.MessageProto.MessageProtocol(); 930 | } 931 | 932 | public static ai.yunxi.im.common.protocol.MessageProto.MessageProtocol getDefaultInstance() { 933 | return DEFAULT_INSTANCE; 934 | } 935 | 936 | @java.lang.Deprecated public static final com.google.protobuf.Parser 937 | PARSER = new com.google.protobuf.AbstractParser() { 938 | @java.lang.Override 939 | public MessageProtocol parsePartialFrom( 940 | com.google.protobuf.CodedInputStream input, 941 | com.google.protobuf.ExtensionRegistryLite extensionRegistry) 942 | throws com.google.protobuf.InvalidProtocolBufferException { 943 | return new MessageProtocol(input, extensionRegistry); 944 | } 945 | }; 946 | 947 | public static com.google.protobuf.Parser parser() { 948 | return PARSER; 949 | } 950 | 951 | @java.lang.Override 952 | public com.google.protobuf.Parser getParserForType() { 953 | return PARSER; 954 | } 955 | 956 | @java.lang.Override 957 | public ai.yunxi.im.common.protocol.MessageProto.MessageProtocol getDefaultInstanceForType() { 958 | return DEFAULT_INSTANCE; 959 | } 960 | 961 | } 962 | 963 | private static final com.google.protobuf.Descriptors.Descriptor 964 | internal_static_protocol_MessageProtocol_descriptor; 965 | private static final 966 | com.google.protobuf.GeneratedMessageV3.FieldAccessorTable 967 | internal_static_protocol_MessageProtocol_fieldAccessorTable; 968 | 969 | public static com.google.protobuf.Descriptors.FileDescriptor 970 | getDescriptor() { 971 | return descriptor; 972 | } 973 | private static com.google.protobuf.Descriptors.FileDescriptor 974 | descriptor; 975 | static { 976 | java.lang.String[] descriptorData = { 977 | "\n\022MessageProto.proto\022\010protocol\"Q\n\017Messag" + 978 | "eProtocol\022\017\n\007command\030\001 \002(\t\022\014\n\004time\030\002 \002(\003" + 979 | "\022\016\n\006userId\030\003 \002(\005\022\017\n\007content\030\004 \002(\tB+\n\033ai." + 980 | "yunxi.im.common.protocolB\014MessageProto" 981 | }; 982 | com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = 983 | new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { 984 | public com.google.protobuf.ExtensionRegistry assignDescriptors( 985 | com.google.protobuf.Descriptors.FileDescriptor root) { 986 | descriptor = root; 987 | return null; 988 | } 989 | }; 990 | com.google.protobuf.Descriptors.FileDescriptor 991 | .internalBuildGeneratedFileFrom(descriptorData, 992 | new com.google.protobuf.Descriptors.FileDescriptor[] { 993 | }, assigner); 994 | internal_static_protocol_MessageProtocol_descriptor = 995 | getDescriptor().getMessageTypes().get(0); 996 | internal_static_protocol_MessageProtocol_fieldAccessorTable = new 997 | com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( 998 | internal_static_protocol_MessageProtocol_descriptor, 999 | new java.lang.String[] { "Command", "Time", "UserId", "Content", }); 1000 | } 1001 | 1002 | // @@protoc_insertion_point(outer_class_scope) 1003 | } 1004 | -------------------------------------------------------------------------------- /im-common/src/main/java/ai/yunxi/im/common/utils/StringUtil.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.common.utils; 2 | 3 | public class StringUtil { 4 | public StringUtil() { 5 | } 6 | 7 | public static boolean isNullOrEmpty(String str) { 8 | return null == str || 0 == str.trim().length(); 9 | } 10 | 11 | public static boolean isEmpty(String str) { 12 | return str == null || "".equals(str.trim()); 13 | } 14 | 15 | public static boolean isNotEmpty(String str) { 16 | return str != null && !"".equals(str.trim()); 17 | } 18 | 19 | public static String formatLike(String str) { 20 | return isNotEmpty(str)?"%" + str + "%":null; 21 | } 22 | } -------------------------------------------------------------------------------- /im-route/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | ai.yunxi 7 | im 8 | 0.0.1-SNAPSHOT 9 | 10 | im-route 11 | im-route 12 | http://maven.apache.org 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-data-redis 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | ai.yunxi 32 | im-common 33 | 0.0.1-SNAPSHOT 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-maven-plugin 42 | 43 | 44 | 45 | repackage 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /im-route/src/main/java/ai/yunxi/im/route/RouteApplication.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * 8 | * @author 小五老师-云析学院 9 | * @createTime 2019年2月27日 下午4:20:02 10 | * 11 | */ 12 | @SpringBootApplication 13 | public class RouteApplication { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(RouteApplication.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /im-route/src/main/java/ai/yunxi/im/route/config/BeanConfiguration.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route.config; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import org.I0Itec.zkclient.IZkChildListener; 7 | import org.I0Itec.zkclient.ZkClient; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.data.redis.connection.RedisConnectionFactory; 12 | import org.springframework.data.redis.core.RedisTemplate; 13 | import org.springframework.data.redis.core.StringRedisTemplate; 14 | import org.springframework.data.redis.serializer.StringRedisSerializer; 15 | 16 | import ai.yunxi.im.route.zk.ZKUtil; 17 | import okhttp3.OkHttpClient; 18 | 19 | /** 20 | * 21 | * @author 小五老师-云析学院 22 | * @createTime 2019年2月26日 下午10:08:36 23 | * 24 | */ 25 | @Configuration 26 | public class BeanConfiguration { 27 | @Autowired 28 | private InitConfiguration conf; 29 | @Autowired 30 | private ZKUtil zkUtil; 31 | 32 | @Bean 33 | public ZkClient createZKClient(){ 34 | ZkClient zk = new ZkClient(conf.getAddr()); 35 | 36 | //监听/route节点下子节点的变化,实时更新server list 37 | zk.subscribeChildChanges(conf.getRoot(), new IZkChildListener() { 38 | 39 | @Override 40 | public void handleChildChange(String parentPath, List currentChilds) throws Exception { 41 | zkUtil.setAllNode(currentChilds); 42 | } 43 | }); 44 | return zk; 45 | } 46 | 47 | /** 48 | * Redis bean 49 | * @param factory 50 | * @return 51 | */ 52 | @Bean 53 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 54 | StringRedisTemplate redisTemplate = new StringRedisTemplate(factory); 55 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 56 | redisTemplate.setValueSerializer(new StringRedisSerializer()); 57 | redisTemplate.afterPropertiesSet(); 58 | return redisTemplate; 59 | } 60 | 61 | /** 62 | * http client 63 | * @return okHttp 64 | */ 65 | @Bean 66 | public OkHttpClient okHttpClient() { 67 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 68 | builder.connectTimeout(30, TimeUnit.SECONDS) 69 | .readTimeout(10, TimeUnit.SECONDS) 70 | .writeTimeout(10,TimeUnit.SECONDS) 71 | .retryOnConnectionFailure(true); 72 | return builder.build(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /im-route/src/main/java/ai/yunxi/im/route/config/InitConfiguration.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class InitConfiguration { 8 | 9 | @Value("${im.zk.switch}") 10 | private boolean zkSwitch; 11 | @Value("${im.zk.root}") 12 | private String root; 13 | @Value("${im.zk.addr}") 14 | private String addr; 15 | public boolean isZkSwitch() { 16 | return zkSwitch; 17 | } 18 | public void setZkSwitch(boolean zkSwitch) { 19 | this.zkSwitch = zkSwitch; 20 | } 21 | public String getRoot() { 22 | return root; 23 | } 24 | public void setRoot(String root) { 25 | this.root = root; 26 | } 27 | public String getAddr() { 28 | return addr; 29 | } 30 | public void setAddr(String addr) { 31 | this.addr = addr; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /im-route/src/main/java/ai/yunxi/im/route/controller/IMRouteController.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route.controller; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import ai.yunxi.im.common.constant.BasicConstant; 17 | import ai.yunxi.im.common.pojo.ChatInfo; 18 | import ai.yunxi.im.common.pojo.ServerInfo; 19 | import ai.yunxi.im.common.pojo.UserInfo; 20 | import ai.yunxi.im.common.utils.StringUtil; 21 | import ai.yunxi.im.route.service.RouteService; 22 | import ai.yunxi.im.route.zk.ZKUtil; 23 | 24 | /** 25 | * 26 | * @author 小五老师-云析学院 27 | * @createTime 2019年2月27日 下午3:11:53 28 | * 29 | */ 30 | @RestController 31 | @RequestMapping("/") 32 | public class IMRouteController { 33 | 34 | private static final Logger LOGGER = LoggerFactory.getLogger(IMRouteController.class); 35 | private AtomicLong index = new AtomicLong(); 36 | 37 | @Autowired 38 | private ZKUtil zk; 39 | @Autowired 40 | private RedisTemplate redisTemplate; 41 | @Autowired 42 | private RouteService routeService; 43 | 44 | /** 45 | * 客户端登录,发现可用服务端: 46 | * 1、获取所有zk上的节点; 47 | * 2、轮询法得到一个节点 48 | **/ 49 | @RequestMapping(value="/login", method=RequestMethod.POST) 50 | public ServerInfo login(@RequestBody UserInfo userInfo){ 51 | String server=""; 52 | try { 53 | List all = zk.getAllNode(); 54 | if(all.size()<=0){ 55 | LOGGER.info("no server start..."); 56 | return null; 57 | } 58 | Long position = index.incrementAndGet() % all.size(); 59 | if (position < 0) { 60 | position = 0L; 61 | } 62 | server = all.get(position.intValue()); 63 | redisTemplate.opsForValue().set(BasicConstant.ROUTE_PREFIX+userInfo.getUserId(), server); 64 | LOGGER.info("get server info :"+server); 65 | } catch (Exception e) { 66 | e.printStackTrace(); 67 | } 68 | String[] serv = server.split("-"); 69 | ServerInfo serviceInfo = new ServerInfo(serv[0], Integer.parseInt(serv[1]), Integer.parseInt(serv[2])); 70 | return serviceInfo; 71 | } 72 | 73 | /** 74 | * 分发消息 75 | **/ 76 | @RequestMapping(value="/chat", method=RequestMethod.POST) 77 | public void chat(@RequestBody ChatInfo chat){ 78 | //判断userId是否登录——从缓存取数据 ... 79 | String islogin = redisTemplate.opsForValue().get(BasicConstant.ROUTE_PREFIX + chat.getUserId()); 80 | if(StringUtil.isEmpty(islogin)){ 81 | LOGGER.info("该用户并未登录["+chat.getUserId()+"]"); 82 | return; 83 | } 84 | try { 85 | //从ZK拿到所有节点,分发消息 86 | List all = zk.getAllNode(); 87 | for (String server : all) { 88 | String[] serv = server.split("-"); 89 | String ip = serv[0]; 90 | int httpPort = Integer.parseInt(serv[2]); 91 | String url = "http://"+ip+":"+httpPort+"/pushMessage"; 92 | routeService.sendMessage(url, chat); 93 | } 94 | } catch (NumberFormatException e) { 95 | e.printStackTrace(); 96 | } catch (IOException e) { 97 | e.printStackTrace(); 98 | } catch (Exception e) { 99 | e.printStackTrace(); 100 | } 101 | } 102 | 103 | /** 104 | * 客户端下线,从缓存中删除客户端与服务端映射关系 105 | **/ 106 | @RequestMapping(value="/logout", method=RequestMethod.POST) 107 | public void logout(@RequestBody UserInfo userInfo){ 108 | redisTemplate.opsForValue().getOperations().delete(BasicConstant.ROUTE_PREFIX+userInfo.getUserId()); 109 | LOGGER.info("路由端处理了用户下线逻辑:"+userInfo.getUserId()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /im-route/src/main/java/ai/yunxi/im/route/service/RouteService.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route.service; 2 | 3 | import java.io.IOException; 4 | 5 | import ai.yunxi.im.common.pojo.ChatInfo; 6 | 7 | /** 8 | * 9 | * @author 小五老师-云析学院 10 | * @createTime 2019年3月7日 下午8:38:44 11 | * 12 | */ 13 | public interface RouteService { 14 | 15 | public void sendMessage(String url, ChatInfo chat) throws IOException; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /im-route/src/main/java/ai/yunxi/im/route/service/impl/RouteServiceImpl.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route.service.impl; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.alibaba.fastjson.JSONObject; 9 | 10 | import ai.yunxi.im.common.pojo.ChatInfo; 11 | import ai.yunxi.im.route.service.RouteService; 12 | import okhttp3.MediaType; 13 | import okhttp3.OkHttpClient; 14 | import okhttp3.Request; 15 | import okhttp3.RequestBody; 16 | import okhttp3.Response; 17 | 18 | /** 19 | * 20 | * @author 小五老师-云析学院 21 | * @createTime 2019年3月7日 下午8:40:34 22 | * 23 | */ 24 | @Service 25 | public class RouteServiceImpl implements RouteService { 26 | 27 | private MediaType mediaType = MediaType.parse("application/json"); 28 | @Autowired 29 | private OkHttpClient okHttpClient; 30 | 31 | @Override 32 | public void sendMessage(String url, ChatInfo chat) throws IOException { 33 | JSONObject jsonObject = new JSONObject(); 34 | jsonObject.put("command",chat.getCommand()); 35 | jsonObject.put("time",chat.getTime()); 36 | jsonObject.put("userId",chat.getUserId()); 37 | jsonObject.put("content",chat.getContent()); 38 | 39 | RequestBody requestBody = RequestBody.create(mediaType,jsonObject.toString()); 40 | 41 | Request request = new Request.Builder() 42 | .url(url) 43 | .post(requestBody) 44 | .build(); 45 | 46 | Response response = okHttpClient.newCall(request).execute() ; 47 | if (!response.isSuccessful()){ 48 | throw new IOException("Unexpected code " + response); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /im-route/src/main/java/ai/yunxi/im/route/zk/ZKUtil.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route.zk; 2 | 3 | import java.util.List; 4 | 5 | import org.I0Itec.zkclient.ZkClient; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | import ai.yunxi.im.route.config.InitConfiguration; 12 | 13 | 14 | @Component 15 | public class ZKUtil { 16 | 17 | private static Logger LOGGER = LoggerFactory.getLogger(ZKUtil.class); 18 | 19 | @Autowired 20 | private ZkClient zkClient; 21 | 22 | @Autowired 23 | private InitConfiguration conf ; 24 | 25 | List allNode; 26 | 27 | /** 28 | * 创建父级节点 29 | */ 30 | public void createRootNode(){ 31 | boolean exists = zkClient.exists(conf.getRoot()); 32 | if (exists){ 33 | return; 34 | } 35 | //创建 root 36 | zkClient.createPersistent(conf.getRoot()) ; 37 | } 38 | 39 | /** 40 | * 写入指定节点 临时目录 41 | */ 42 | public void createNode(String path) { 43 | zkClient.createEphemeral(path); 44 | } 45 | 46 | /** 47 | * 获取所有注册节点 48 | * @return 49 | */ 50 | public List getAllNode(){ 51 | LOGGER.info("查询所有节点成功,节点数:"+allNode.size()); 52 | return allNode; 53 | } 54 | 55 | /** 56 | * 更新server list 57 | */ 58 | public void setAllNode(List allNode){ 59 | LOGGER.info("server节点更新,节点数:"+allNode.size()); 60 | this.allNode = allNode; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /im-route/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8880 2 | 3 | im.zk.switch=true 4 | im.zk.root=/route 5 | im.zk.addr=localhost:2181 6 | 7 | spring.redis.host=localhost 8 | spring.redis.port=6379 9 | spring.redis.pool.max-active=100 10 | spring.redis.pool.max-idle=100 11 | spring.redis.pool.max-wait=1000 12 | spring.redis.pool.min-idle=10 -------------------------------------------------------------------------------- /im-route/src/test/java/ai/yunxi/im/route/AppTest.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.route; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /im-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | ai.yunxi 7 | im 8 | 0.0.1-SNAPSHOT 9 | 10 | im-server 11 | im-server 12 | http://maven.apache.org 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | test 25 | 26 | 27 | ai.yunxi 28 | im-common 29 | 0.0.1-SNAPSHOT 30 | 31 | 32 | org.msgpack 33 | msgpack 34 | 0.6.12 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-maven-plugin 43 | 44 | 45 | 46 | repackage 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/IMServerApplication.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | import ai.yunxi.im.server.zk.RegisterToZK; 10 | 11 | /** 12 | * 13 | * @author 小五老师-云析学院 14 | * @createTime 2019年2月26日 下午2:59:36 15 | * 16 | */ 17 | @SpringBootApplication 18 | public class IMServerApplication implements CommandLineRunner { 19 | 20 | private final static Logger LOGGER = LoggerFactory.getLogger(IMServerApplication.class); 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(IMServerApplication.class, args); 24 | LOGGER.info("启动 Service 服务成功"); 25 | } 26 | 27 | /** 28 | * 启动后,将节点注册在Zookeeper 29 | **/ 30 | @Override 31 | public void run(String... args) throws Exception { 32 | try { 33 | Thread thread = new Thread(new RegisterToZK()); 34 | thread.setName("im-server-register-thread"); 35 | thread.start(); 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/config/BeanConfiguration.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.config; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.I0Itec.zkclient.ZkClient; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import okhttp3.OkHttpClient; 11 | 12 | /** 13 | * 14 | * @author 小五老师-云析学院 15 | * @createTime 2019年2月26日 下午5:48:27 16 | * 17 | */ 18 | @Configuration 19 | public class BeanConfiguration { 20 | @Autowired 21 | private InitConfiguration conf; 22 | 23 | @Bean 24 | public ZkClient createZKClient(){ 25 | return new ZkClient(conf.getAddr()); 26 | } 27 | 28 | /** 29 | * http client 30 | * @return okHttp 31 | */ 32 | @Bean 33 | public OkHttpClient okHttpClient() { 34 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 35 | builder.connectTimeout(30, TimeUnit.SECONDS) 36 | .readTimeout(10, TimeUnit.SECONDS) 37 | .writeTimeout(10,TimeUnit.SECONDS) 38 | .retryOnConnectionFailure(true); 39 | return builder.build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/config/InitConfiguration.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author 小五老师-云析学院 8 | * @createTime 2019年3月12日 下午8:56:48 9 | * 10 | */ 11 | @Component 12 | public class InitConfiguration { 13 | 14 | @Value("${server.port}") 15 | private int httpPort; 16 | @Value("${im.server.port}") 17 | private int nettyPort; 18 | 19 | @Value("${im.zk.switch}") 20 | private boolean zkSwitch; 21 | @Value("${im.zk.root}") 22 | private String root; 23 | @Value("${im.zk.addr}") 24 | private String addr; 25 | 26 | @Value("${im.route.logout.url}") 27 | private String routeLogoutUrl; 28 | public int getHttpPort() { 29 | return httpPort; 30 | } 31 | public void setHttpPort(int httpPort) { 32 | this.httpPort = httpPort; 33 | } 34 | public int getNettyPort() { 35 | return nettyPort; 36 | } 37 | public void setNettyPort(int nettyPort) { 38 | this.nettyPort = nettyPort; 39 | } 40 | public boolean isZkSwitch() { 41 | return zkSwitch; 42 | } 43 | public void setZkSwitch(boolean zkSwitch) { 44 | this.zkSwitch = zkSwitch; 45 | } 46 | public String getRoot() { 47 | return root; 48 | } 49 | public void setRoot(String root) { 50 | this.root = root; 51 | } 52 | public String getAddr() { 53 | return addr; 54 | } 55 | public void setAddr(String addr) { 56 | this.addr = addr; 57 | } 58 | public String getRouteLogoutUrl() { 59 | return routeLogoutUrl; 60 | } 61 | public void setRouteLogoutUrl(String routeLogoutUrl) { 62 | this.routeLogoutUrl = routeLogoutUrl; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/config/SpringBeanFactory.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.config; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public final class SpringBeanFactory implements ApplicationContextAware{ 10 | private static ApplicationContext context; 11 | 12 | public static T getBean(Class c){ 13 | return context.getBean(c); 14 | } 15 | 16 | 17 | public static T getBean(String name,Class clazz){ 18 | return context.getBean(name,clazz); 19 | } 20 | 21 | @Override 22 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 23 | context = applicationContext; 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/controller/IMServerController.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.controller; 2 | 3 | import java.util.Map.Entry; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import ai.yunxi.im.common.constant.MessageConstant; 14 | import ai.yunxi.im.common.pojo.ChatInfo; 15 | import ai.yunxi.im.common.protocol.MessageProto; 16 | import ai.yunxi.im.server.handle.ChannelMap; 17 | import io.netty.channel.Channel; 18 | import io.netty.util.AttributeKey; 19 | 20 | /** 21 | * 22 | * @author 小五老师-云析学院 23 | * @createTime 2019年2月26日 下午3:10:41 24 | * 服务端处理器 25 | */ 26 | @RestController 27 | @RequestMapping("/") 28 | public class IMServerController { 29 | 30 | private static final Logger LOGGER = LoggerFactory.getLogger(IMServerController.class); 31 | private ChannelMap CHANNEL_MAP = ChannelMap.newInstance(); 32 | private AttributeKey userId = AttributeKey.valueOf("userId"); 33 | /** 34 | * 服务端接收消息,并推送到指定客户端 35 | **/ 36 | @RequestMapping(value="/pushMessage", method=RequestMethod.POST) 37 | public void pushMessage(@RequestBody ChatInfo chat){ 38 | //1.接收客户端封装好的消息对象 39 | MessageProto.MessageProtocol message = MessageProto.MessageProtocol.newBuilder() 40 | .setCommand(chat.getCommand()) 41 | .setTime(chat.getTime()) 42 | .setUserId(chat.getUserId()) 43 | .setContent(chat.getContent()).build(); 44 | //2.根据消息发送给指定客户端(群发) 45 | // 根据userID,从本地Map集合中得到对应的客户端Channel,发送消息 46 | if(MessageConstant.CHAT.equals(message.getCommand())){ 47 | for (Entry entry : CHANNEL_MAP.getCHANNEL_MAP().entrySet()) { 48 | //过滤客户端本身 49 | if(entry.getKey() != message.getUserId()){ 50 | LOGGER.info("----服务端向"+entry.getValue().attr(userId).get()+"发送了消息,来自userId="+message.getUserId()+", content="+message.getContent()); 51 | entry.getValue().writeAndFlush(message); 52 | } 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * 服务端处理客户端下线事件 59 | **/ 60 | @RequestMapping(value="/clientLogout", method=RequestMethod.POST) 61 | public void clientLogout(@RequestBody ChatInfo chatinfo){ 62 | 63 | CHANNEL_MAP.getCHANNEL_MAP().remove(chatinfo.getUserId()); 64 | LOGGER.info("---客户端下线["+chatinfo.getUserId()+"]"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/handle/ChannelMap.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.handle; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import io.netty.channel.Channel; 9 | 10 | /** 11 | * 12 | * @author 小五老师-云析学院 13 | * @createTime 2019年3月7日 下午9:12:59 14 | * 15 | */ 16 | public class ChannelMap { 17 | 18 | private static ChannelMap instance; 19 | private final Map CHANNEL_MAP = new ConcurrentHashMap(); 20 | 21 | private ChannelMap() { 22 | } 23 | public static ChannelMap newInstance(){ 24 | if(instance == null){ 25 | instance = new ChannelMap(); 26 | } 27 | return instance; 28 | } 29 | 30 | public Map getCHANNEL_MAP() { 31 | return CHANNEL_MAP; 32 | } 33 | 34 | public void putClient(Integer userId, Channel channel){ 35 | CHANNEL_MAP.put(userId, channel); 36 | } 37 | 38 | public Channel getClient(Integer userId){ 39 | return CHANNEL_MAP.get(userId); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/handle/ClientProcessor.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.handle; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.alibaba.fastjson.JSONObject; 9 | 10 | import ai.yunxi.im.common.constant.BasicConstant; 11 | import ai.yunxi.im.server.config.InitConfiguration; 12 | import okhttp3.OkHttpClient; 13 | import okhttp3.Request; 14 | import okhttp3.RequestBody; 15 | import okhttp3.Response; 16 | 17 | /** 18 | * 19 | * @author 小五老师-云析学院 20 | * @createTime 2019年3月7日 下午10:38:47 21 | * 22 | */ 23 | @Component 24 | public class ClientProcessor { 25 | 26 | @Autowired 27 | private OkHttpClient okHttpClient; 28 | @Autowired 29 | private InitConfiguration conf; 30 | 31 | public void down(Integer userId){ 32 | try { 33 | JSONObject jsonObject = new JSONObject(); 34 | jsonObject.put("userId",userId); 35 | RequestBody requestBody = RequestBody.create(BasicConstant.MEDIA_TYPE,jsonObject.toString()); 36 | 37 | Request request = new Request.Builder() 38 | .url(conf.getRouteLogoutUrl()) 39 | .post(requestBody) 40 | .build(); 41 | 42 | Response response = okHttpClient.newCall(request).execute() ; 43 | if (!response.isSuccessful()){ 44 | throw new IOException("Unexpected code " + response); 45 | } 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/handle/IMServerHandle.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.handle; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import ai.yunxi.im.common.constant.MessageConstant; 7 | import ai.yunxi.im.common.protocol.MessageProto; 8 | import ai.yunxi.im.server.config.SpringBeanFactory; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.ChannelInboundHandlerAdapter; 11 | import io.netty.util.AttributeKey; 12 | 13 | /** 14 | * 15 | * @author 小五老师-云析学院 16 | * @createTime 2019年2月27日 下午2:02:42 17 | * 18 | */ 19 | public class IMServerHandle extends ChannelInboundHandlerAdapter { 20 | 21 | private final static Logger LOGGER = LoggerFactory.getLogger(IMServerHandle.class); 22 | 23 | private AttributeKey userId = AttributeKey.valueOf("userId"); 24 | 25 | private ChannelMap CHANNEL_MAP = ChannelMap.newInstance(); 26 | 27 | private ClientProcessor clientProcessor; 28 | 29 | 30 | public IMServerHandle() { 31 | this.clientProcessor = SpringBeanFactory.getBean(ClientProcessor.class); 32 | } 33 | 34 | @Override 35 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 36 | MessageProto.MessageProtocol message = (MessageProto.MessageProtocol)msg; 37 | 38 | //处理客户端向服务端推送的消息 39 | if(MessageConstant.LOGIN.equals(message.getCommand())){ 40 | //登录,保存Channel 41 | ctx.channel().attr(userId).set(message.getUserId()); 42 | CHANNEL_MAP.putClient(message.getUserId(), ctx.channel()); 43 | LOGGER.info("---客户端登录成功。userId:"+message.getUserId()); 44 | } 45 | } 46 | 47 | @Override 48 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 49 | Integer uid = ctx.channel().attr(userId).get(); 50 | //从Channel缓存删除客户端 51 | CHANNEL_MAP.getCHANNEL_MAP().remove(uid); 52 | clientProcessor.down(uid); 53 | 54 | LOGGER.info("----客户端强制下线。userId:"+uid); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/init/IMServerInit.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.init; 2 | 3 | import javax.annotation.PostConstruct; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | import ai.yunxi.im.common.protocol.MessageProto; 11 | import ai.yunxi.im.server.config.InitConfiguration; 12 | import ai.yunxi.im.server.handle.IMServerHandle; 13 | import io.netty.bootstrap.ServerBootstrap; 14 | import io.netty.channel.ChannelFuture; 15 | import io.netty.channel.ChannelInitializer; 16 | import io.netty.channel.ChannelOption; 17 | import io.netty.channel.ChannelPipeline; 18 | import io.netty.channel.EventLoopGroup; 19 | import io.netty.channel.nio.NioEventLoopGroup; 20 | import io.netty.channel.socket.SocketChannel; 21 | import io.netty.channel.socket.nio.NioServerSocketChannel; 22 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 23 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 24 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 25 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 26 | 27 | /** 28 | * 29 | * @author 小五老师-云析学院 30 | * @createTime 2019年2月27日 下午1:35:52 31 | * 32 | */ 33 | @Component 34 | public class IMServerInit { 35 | 36 | private final static Logger LOGGER = LoggerFactory.getLogger(IMServerInit.class); 37 | 38 | private EventLoopGroup acceptorGroup = new NioEventLoopGroup(); 39 | private EventLoopGroup workerGroup = new NioEventLoopGroup(); 40 | 41 | @Autowired 42 | private InitConfiguration conf; 43 | 44 | @PostConstruct 45 | public void start() throws Exception{ 46 | try { 47 | //Netty用于启动NIO服务器的辅助启动类 48 | ServerBootstrap sb = new ServerBootstrap(); 49 | //将两个NIO线程组传入辅助启动类中 50 | sb.group(acceptorGroup, workerGroup) 51 | //设置创建的Channel为NioServerSocketChannel类型 52 | .channel(NioServerSocketChannel.class) 53 | //保持长连接 54 | .childOption(ChannelOption.SO_KEEPALIVE, true) 55 | //设置绑定IO事件的处理类 56 | .childHandler(new ChannelInitializer() { 57 | @Override 58 | protected void initChannel(SocketChannel ch) throws Exception { 59 | ChannelPipeline pipeline = ch.pipeline(); 60 | // google Protobuf 编解码 61 | pipeline.addLast(new ProtobufVarint32FrameDecoder()); 62 | pipeline.addLast(new ProtobufDecoder(MessageProto.MessageProtocol.getDefaultInstance())); 63 | pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); 64 | pipeline.addLast(new ProtobufEncoder()); 65 | 66 | pipeline.addLast(new IMServerHandle()); 67 | } 68 | }); 69 | ChannelFuture conn = sb.bind(conf.getNettyPort()).sync(); 70 | if(conn.isSuccess()){ 71 | LOGGER.info("---服务端启动成功,端口["+conf.getNettyPort()+"]"); 72 | } 73 | } finally { 74 | // acceptorGroup.shutdownGracefully(); 75 | // workerGroup.shutdownGracefully(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/zk/RegisterToZK.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.zk; 2 | 3 | import java.net.InetAddress; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import ai.yunxi.im.server.config.InitConfiguration; 9 | import ai.yunxi.im.server.config.SpringBeanFactory; 10 | 11 | public class RegisterToZK implements Runnable { 12 | 13 | private static Logger LOGGER = LoggerFactory.getLogger(RegisterToZK.class); 14 | 15 | private InitConfiguration conf; 16 | private ZKUtil zk; 17 | 18 | public RegisterToZK() { 19 | conf = SpringBeanFactory.getBean(InitConfiguration.class); 20 | zk = SpringBeanFactory.getBean(ZKUtil.class); 21 | } 22 | 23 | @Override 24 | public void run() { 25 | try { 26 | String ip = InetAddress.getLocalHost().getHostAddress(); 27 | int httpPort = conf.getHttpPort(); 28 | int nettyPort = conf.getNettyPort(); 29 | LOGGER.info("---服务端注册到Zookeeper. ip:"+ip+"; httpPort:"+httpPort+"; nettyPort:"+nettyPort); 30 | 31 | //创建父节点 32 | zk.createRootNode(); 33 | //判断是否需要注册到zk 34 | if(conf.isZkSwitch()){ 35 | String path = conf.getRoot() + "/"+ip+"-"+conf.getNettyPort()+"-"+conf.getHttpPort(); 36 | zk.createNode(path); 37 | LOGGER.info("---服务端注册到ZK成功,Path="+path); 38 | } 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /im-server/src/main/java/ai/yunxi/im/server/zk/ZKUtil.java: -------------------------------------------------------------------------------- 1 | package ai.yunxi.im.server.zk; 2 | 3 | import java.util.List; 4 | 5 | import org.I0Itec.zkclient.ZkClient; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | import ai.yunxi.im.server.config.InitConfiguration; 12 | 13 | @Component 14 | public class ZKUtil { 15 | 16 | private static Logger LOGGER = LoggerFactory.getLogger(ZKUtil.class); 17 | 18 | @Autowired 19 | private ZkClient zkClient; 20 | 21 | @Autowired 22 | private InitConfiguration conf ; 23 | 24 | /** 25 | * 创建父级节点 26 | */ 27 | public void createRootNode(){ 28 | boolean exists = zkClient.exists(conf.getRoot()); 29 | if (exists){ 30 | return; 31 | } 32 | //创建 root 33 | zkClient.createPersistent(conf.getRoot()) ; 34 | } 35 | 36 | /** 37 | * 写入指定节点 临时目录 38 | */ 39 | public void createNode(String path) { 40 | zkClient.createEphemeral(path); 41 | } 42 | 43 | /** 44 | * 获取所有注册节点 45 | * @return 46 | */ 47 | public List getAllNode(){ 48 | List children = zkClient.getChildren("/route"); 49 | LOGGER.info("查询所有节点成功,节点数:"+children.size()); 50 | return children; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /im-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | im.server.port=8090 3 | 4 | im.zk.switch=true 5 | im.zk.root=/route 6 | im.zk.addr=localhost:2181 7 | 8 | im.route.logout.url=http://localhost:8880/logout -------------------------------------------------------------------------------- /pic/im.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallFive55/im/42c11418fc8d2e702f0ab0f0596ccc86bfe5e643/pic/im.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | ai.yunxi 4 | im 5 | pom 6 | 0.0.1-SNAPSHOT 7 | 8 | 9 | org.springframework.boot 10 | spring-boot-parent 11 | 2.1.2.RELEASE 12 | 13 | 14 | 15 | com.101tec 16 | zkclient 17 | 0.10 18 | 19 | 20 | io.netty 21 | netty-all 22 | 4.1.5.Final 23 | 24 | 25 | com.squareup.okhttp3 26 | okhttp 27 | 3.3.1 28 | 29 | 30 | com.alibaba 31 | fastjson 32 | 1.2.31 33 | 34 | 35 | 36 | im-server 37 | im-route 38 | im-client 39 | im-common 40 | 41 | --------------------------------------------------------------------------------