From f0f6ffd5f05fba843b0688fcd35dc691a744ac4a Mon Sep 17 00:00:00 2001
From: "Yangkai.Shen" <237497819@qq.com>
Date: Wed, 19 Dec 2018 15:32:51 +0800
Subject: [PATCH] =?UTF-8?q?:sparkles:=20spring-boot-demo-websocket-socketi?=
=?UTF-8?q?o=20=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
spring-boot-demo-websocket-socketio/README.md | 334 ++++++++++++++++++
.../image-20181219152318079-5204198.png | Bin 0 -> 344097 bytes
.../image-20181219152330156-5204210.png | Bin 0 -> 427693 bytes
3 files changed, 334 insertions(+)
create mode 100644 spring-boot-demo-websocket-socketio/README.md
create mode 100644 spring-boot-demo-websocket-socketio/assets/image-20181219152318079-5204198.png
create mode 100644 spring-boot-demo-websocket-socketio/assets/image-20181219152330156-5204210.png
diff --git a/spring-boot-demo-websocket-socketio/README.md b/spring-boot-demo-websocket-socketio/README.md
new file mode 100644
index 0000000..f43fb51
--- /dev/null
+++ b/spring-boot-demo-websocket-socketio/README.md
@@ -0,0 +1,334 @@
+# spring-boot-demo-websocket-socketio
+
+> 此 demo 主要演示了 Spring Boot 如何使用 `netty-socketio` 集成 WebSocket,实现一个简单的聊天室。
+
+## 1. 代码
+
+### 1.1. pom.xml
+
+```xml
+
+
+ * websocket服务器配置 + *
+ * + * @package: com.xkcoding.websocket.socketio.config + * @description: websocket服务器配置 + * @author: yangkai.shen + * @date: Created in 2018-12-18 16:42 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Configuration +@EnableConfigurationProperties({WsConfig.class}) +public class ServerConfig { + + @Bean + public SocketIOServer server(WsConfig wsConfig) { + com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); + config.setHostname(wsConfig.getHost()); + config.setPort(wsConfig.getPort()); + + //这个listener可以用来进行身份验证 + config.setAuthorizationListener(data -> { + // http://localhost:8081?token=xxxxxxx + // 例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证 + String token = data.getSingleUrlParam("token"); + // 校验token的合法性,实际业务需要校验token是否过期等等,参考 spring-boot-demo-rbac-security 里的 JwtUtil + // 如果认证不通过会返回一个 Socket.EVENT_CONNECT_ERROR 事件 + return StrUtil.isNotBlank(token); + }); + + return new SocketIOServer(config); + } + + /** + * Spring 扫描自定义注解 + */ + @Bean + public SpringAnnotationScanner springAnnotationScanner(SocketIOServer server) { + return new SpringAnnotationScanner(server); + } +} +``` + +### 1.3. MessageEventHandler.java + +> 核心事件处理类,主要处理客户端发起的消息事件,以及主动往客户端发起事件 + +```java +/** + *+ * 消息事件处理 + *
+ * + * @package: com.xkcoding.websocket.socketio.handler + * @description: 消息事件处理 + * @author: yangkai.shen + * @date: Created in 2018-12-18 18:57 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Component +@Slf4j +public class MessageEventHandler { + @Autowired + private SocketIOServer server; + + @Autowired + private DbTemplate dbTemplate; + + /** + * 添加connect事件,当客户端发起连接时调用 + * + * @param client 客户端对象 + */ + @OnConnect + public void onConnect(SocketIOClient client) { + if (client != null) { + String token = client.getHandshakeData().getSingleUrlParam("token"); + // 模拟用户id 和token一致 + String userId = client.getHandshakeData().getSingleUrlParam("token"); + UUID sessionId = client.getSessionId(); + + dbTemplate.save(userId, sessionId); + log.info("连接成功,【token】= {},【sessionId】= {}", token, sessionId); + } else { + log.error("客户端为空"); + } + } + + /** + * 添加disconnect事件,客户端断开连接时调用,刷新客户端信息 + * + * @param client 客户端对象 + */ + @OnDisconnect + public void onDisconnect(SocketIOClient client) { + if (client != null) { + String token = client.getHandshakeData().getSingleUrlParam("token"); + // 模拟用户id 和token一致 + String userId = client.getHandshakeData().getSingleUrlParam("token"); + UUID sessionId = client.getSessionId(); + + dbTemplate.deleteByUserId(userId); + log.info("客户端断开连接,【token】= {},【sessionId】= {}", token, sessionId); + client.disconnect(); + } else { + log.error("客户端为空"); + } + } + + /** + * 加入群聊 + * + * @param client 客户端 + * @param request 请求 + * @param data 群聊 + */ + @OnEvent(value = Event.JOIN) + public void onJoinEvent(SocketIOClient client, AckRequest request, JoinRequest data) { + log.info("用户:{} 已加入群聊:{}", data.getUserId(), data.getGroupId()); + client.joinRoom(data.getGroupId()); + + server.getRoomOperations(data.getGroupId()).sendEvent(Event.JOIN, data); + } + + + @OnEvent(value = Event.CHAT) + public void onChatEvent(SocketIOClient client, AckRequest request, SingleMessageRequest data) { + Optional+ * websocket服务器启动 + *
+ * + * @package: com.xkcoding.websocket.socketio.init + * @description: websocket服务器启动 + * @author: yangkai.shen + * @date: Created in 2018-12-18 17:07 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Component +@Slf4j +public class ServerRunner implements CommandLineRunner { + @Autowired + private SocketIOServer server; + + @Override + public void run(String... args) { + server.start(); + log.info("websocket 服务器启动成功。。。"); + } +} +``` + +## 2. 运行方式 + +1. 启动 `SpringBootDemoWebsocketSocketioApplication.java` +2. 使用不同的浏览器,访问 http://localhost:8080/demo/index.html + +## 3. 运行效果 + +**浏览器1:**![image-20181219152318079](assets/image-20181219152318079-5204198.png) + +**浏览器2:**![image-20181219152330156](assets/image-20181219152330156-5204210.png) + +## 4. 参考 + +### 4.1. 后端 + +1. Netty-socketio 官方仓库:https://github.com/mrniko/netty-socketio +2. SpringBoot系列 - 集成SocketIO实时通信:https://www.xncoding.com/2017/07/16/spring/sb-socketio.html +3. Spring Boot 集成 socket.io 后端实现消息实时通信:http://alexpdh.com/2017/09/03/springboot-socketio/ +4. Spring Boot实战之netty-socketio实现简单聊天室:http://blog.csdn.net/sun_t89/article/details/52060946 + +### 4.2. 前端 + +1. socket.io 官网:https://socket.io/ +2. axios.js 用法:https://github.com/axios/axios#example \ No newline at end of file diff --git a/spring-boot-demo-websocket-socketio/assets/image-20181219152318079-5204198.png b/spring-boot-demo-websocket-socketio/assets/image-20181219152318079-5204198.png new file mode 100644 index 0000000000000000000000000000000000000000..c6167b0194b69ceaa44b44747d33e9bfdbb64e2b GIT binary patch literal 344097 zcmbSzcR1U9+c#RRQKQ4&-L*??LaA!2XlskDHbLzXt5jP>OO4v2R)|;$f+&jC3Q{w+ z5_^RNG2ZmL?)SZ)`#Ij@d3yB^IUJ5(zT^Cz^K*VqZ=PstFwk+*QBY7YJbZBP83hHs z3I)ZPF`6^vzZlpOA}J`&J#bW0d-70CjsM9DH#Es*z<^84|F7Z=<@9=sPepg4c3#r~T?L{~3m1K&JPt7lbPCn(kk&0tVAk}**{blvB
zpUZ^QGn(r5Me}*Ycx-J asOBwwoS{^-GWH)K>7Oa@d-6jd??GJN#>6p)--Vf264z-jUezAGvfIZv
zzgWQcGOgXIjE|r5=yRK0+6<{Czd+v=_C^#>&ADzB^k(6zZ9?YG!_UuylA7^i84P2h
z2gdbFbyY=elcW3Gv{`c!9h<%+=P}| hW
zGo~Sol~!N6#kK;DMhi79B4P0v58~`ZtaY!J11Q?ns?_yP8Qap|#m0cw6idc;l29&v
z9i_Vg>*wi*W@*+(asq$bGTNU(ijs6D)|#;m<8w~a*i!e0GXCAT6=YFBAu-LKRhzEf
zEV0P=59#jF^T}3j!Vs{qY@K@+M
x
zBWo4wC9I_*wWgioBBnML!L#80ZIynUdk7&{{U>~Q6Zh`b6I4nN-IDqfRrV%eH*u*~IVxUN%^_+?y3O!REz0DfX8WU=j1(Hoe84-@*r3
zEVK@BhIXqJK^vjM=e?(&jFj_JkOEq+6Gqe+b2P@Te1pr!#RvRdK3snSYmjjn1=lIc
zAE>fa_E)JGZHHgJ@AX)rjD7cGG*&Hni0@K4woqww64MjyN!)unI1L{y$HcVts}AP;
z@u#~Y9->a(G>L0qdYRP}_hiIO@?~}EO@hR>h|~2}Cp9+Is1qFI21JIeMS>f8g^27c
zj=V(4TC|9)yOp>ZW$0Er@G`q)th&l7y<`VF#@7QFwG1ow7UCakR3f%hsmk}I?DNg{
z+8+fDiJDiMtX*lnr`Pq|d1OV+F3!8Ezd?QmIa_Astg+vorrtm#D%uQh6;BGHzssDd
zti)=qDeU~lu5I|Io1sHo_mOScFR$p^-Un|Cs9FF)NIAWUw(<`+ja(#ot)0hm&xV{4
z`!aM;o#zu@I>T2ek*62Y(
`#Eh@8hlVIO#M~56CbPyy3j#Cu9whK>s
-@PH
z9D&Hae%<`0JV-A>V$S$Ao2{vhP&c>5nuhC&WmnyK0;n&ZwH}t3ITfjqoyr(XzGtwB
z?5K>{^(2>-W~>2Cslx3j=!Z(l7>;-Hvw}Kt(E7tibunT=%fFk}$0kH@KdZg4MU%jU
zjFoaCd^4}@p+UrDV-p$M@s6KyrPa?}w2uT!AGx9ATzM?OtaXmR&Xk-OSxoez5=3ht
z_6xtDkNX>BS=dbyXg4V>*I@j{$Hm?5oVRTMiQ+p4jY>pC3G;MfSY0#K)O*ee?YzAx
zy8TGsROEHNby5Vp*-aPNEUH>t#|)4MDTB}(_x(9=I!6}k*%bEqU+})u