|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- # spring-boot-demo-websocket-socketio
-
- > 此 demo 主要演示了 Spring Boot 如何使用 `netty-socketio` 集成 WebSocket,实现一个简单的聊天室。
-
- ## 1. 代码
-
- ### 1.1. pom.xml
-
- ```xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>spring-boot-demo-websocket-socketio</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- <packaging>jar</packaging>
-
- <name>spring-boot-demo-websocket-socketio</name>
- <description>Demo project for Spring Boot</description>
-
- <parent>
- <groupId>com.xkcoding</groupId>
- <artifactId>spring-boot-demo</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- </parent>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <java.version>1.8</java.version>
- <netty-socketio.version>1.7.16</netty-socketio.version>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>com.corundumstudio.socketio</groupId>
- <artifactId>netty-socketio</artifactId>
- <version>${netty-socketio.version}</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-configuration-processor</artifactId>
- <optional>true</optional>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- </dependencies>
-
- <build>
- <finalName>spring-boot-demo-websocket-socketio</finalName>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
-
- </project>
- ```
-
- ### 1.2. ServerConfig.java
-
- > websocket服务器配置,包括服务器IP、端口信息、以及连接认证等配置
-
- ```java
- /**
- * <p>
- * websocket服务器配置
- * </p>
- *
- * @author yangkai.shen
- * @date Created in 2018-12-18 16:42
- */
- @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
- /**
- * <p>
- * 消息事件处理
- * </p>
- *
- * @author yangkai.shen
- * @date Created in 2018-12-18 18:57
- */
- @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<UUID> toUser = dbTemplate.findByUserId(data.getToUid());
- if (toUser.isPresent()) {
- log.info("用户 {} 刚刚私信了用户 {}:{}", data.getFromUid(), data.getToUid(), data.getMessage());
- sendToSingle(toUser.get(), data);
- client.sendEvent(Event.CHAT_RECEIVED, "发送成功");
- } else {
- client.sendEvent(Event.CHAT_REFUSED, "发送失败,对方不想理你");
- }
- }
-
- @OnEvent(value = Event.GROUP)
- public void onGroupEvent(SocketIOClient client, AckRequest request, GroupMessageRequest data) {
- Collection<SocketIOClient> clients = server.getRoomOperations(data.getGroupId()).getClients();
-
- boolean inGroup = false;
- for (SocketIOClient socketIOClient : clients) {
- if (ObjectUtil.equal(socketIOClient.getSessionId(), client.getSessionId())) {
- inGroup = true;
- break;
- }
- }
- if (inGroup) {
- log.info("群号 {} 收到来自 {} 的群聊消息:{}", data.getGroupId(), data.getFromUid(), data.getMessage());
- sendToGroup(data);
- } else {
- request.sendAckData("请先加群!");
- }
- }
-
- /**
- * 单聊
- */
- public void sendToSingle(UUID sessionId, SingleMessageRequest message) {
- server.getClient(sessionId).sendEvent(Event.CHAT, message);
- }
-
- /**
- * 广播
- */
- public void sendToBroadcast(BroadcastMessageRequest message) {
- log.info("系统紧急广播一条通知:{}", message.getMessage());
- for (UUID clientId : dbTemplate.findAll()) {
- if (server.getClient(clientId) == null) {
- continue;
- }
- server.getClient(clientId).sendEvent(Event.BROADCAST, message);
- }
- }
-
- /**
- * 群聊
- */
- public void sendToGroup(GroupMessageRequest message) {
- server.getRoomOperations(message.getGroupId()).sendEvent(Event.GROUP, message);
- }
- }
- ```
-
- ### 1.4. ServerRunner.java
-
- > websocket 服务器启动类
-
- ```java
- /**
- * <p>
- * websocket服务器启动
- * </p>
- *
- * @author yangkai.shen
- * @date Created in 2018-12-18 17:07
- */
- @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:**
-
- **浏览器2:**
-
- ## 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
|