SpringBoot 2.2 使用 Spring 封装注解简易集成 websocket

文章目录

      • 1 摘要
      • 2 核心 Maven 依赖
      • 3 配置信息
      • 4 核心 Java 类
        • 4.1 websocket 会话管理类
        • 4.2 websocket 握手拦截器
        • 4.3 websocket 连接拦截器
        • 4.4 websocket 拦截器配置类
        • 4.5 SpringBoot 应用启动类
      • 5 测试
      • 6 参考资料推荐
      • 7 Github 源码


1 摘要

在 SpringBoot 中使用原生注解简易集成 websocket 可参考:

Spring Boot 2.2 原生注解简易集成 websocket

本文将介绍使用 Spring 封装的注解简易集成 websocket

2 核心 Maven 依赖

./demo-websocket-spring/pom.xml
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-websocketartifactId>
        dependency>

        
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>${hutool.version}version>
        dependency>

其中 springboot 版本为 2.2.5.RELEASE, ${hutool.version} 版本为 5.2.3

3 配置信息

./demo-websocket-spring/src/main/resources/application.yml
## config

## server
server:
  port: 8201

4 核心 Java 类

4.1 websocket 会话管理类

./demo-websocket-spring/src/main/java/com/ljq/demo/springboot/websocketspring/web/SocketSessionManager.java
package com.ljq.demo.springboot.websocketspring.web;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description: websocket 会话管理
 * @Author: junqiang.lu
 * @Date: 2020/3/19
 */
@Slf4j
public class SocketSessionManager {

    /**
     * websocket 会话池
     */
    private static ConcurrentHashMap<String, WebSocketSession> webSocketSessionMap = new ConcurrentHashMap<>();

    /**
     * 添加 websocket 会话
     *
     * @param key
     * @param session
     */
    public static void add(String key, WebSocketSession session) {
        webSocketSessionMap.put(key, session);
    }

    /**
     * 移除 websocket 会话,并将该会话内容返回
     *
     * @param key
     * @return
     */
    public static WebSocketSession remove(String key) {
        return webSocketSessionMap.remove(key);
    }

    /**
     * 删除 websocket,并关闭连接
     *
     * @param key
     */
    public static void removeAndClose(String key) {
        WebSocketSession session = remove(key);
        if (session != null) {
            try {
                session.close();
            } catch (IOException e) {
                log.error("Websocket session close exception ",e);
            }
        }
    }

    /**
     * 获取 websocket 会话
     *
     * @param key
     * @return
     */
    public static WebSocketSession get(String key) {
        return webSocketSessionMap.get(key);
    }

    /**
     * 获取会话数量
     *
     * @return
     */
    public static int count() {
        return webSocketSessionMap.size();
    }

}

4.2 websocket 握手拦截器

在进行 websocket 握手成功之前进行身份验证

./demo-websocket-spring/src/main/java/com/ljq/demo/springboot/websocketspring/interceptor/SocketInterceptor.java
package com.ljq.demo.springboot.websocketspring.interceptor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description: websocket 握手拦截器
 * @Author: junqiang.lu
 * @Date: 2020/3/19
 */
@Slf4j
@Component
public class SocketInterceptor implements HandshakeInterceptor {

    private static final String TOKEN_FIELD = "token";

    /**
     * websocket 握手之前
     *
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @param webSocketHandler
     * @param map
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
                                   WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        log.debug("websocket starts handshaking");
        // 获取请求参数
        HashMap<String, String> paramMap = HttpUtil.decodeParamMap(serverHttpRequest.getURI().getQuery(), "utf-8");
        String token = paramMap.get(TOKEN_FIELD);
        if (StrUtil.isNotBlank(token)) {
            map.put("token", token);
            log.debug("用户 [ {} ]握手成功", token);
            return true;
        }
        log.debug("用户登录已失效");
        return false;
    }

    /**
     * websocket 握手之后
     *
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @param webSocketHandler
     * @param e
     */
    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
                               WebSocketHandler webSocketHandler, Exception e) {
        log.debug("握手完成!");
    }
}

4.3 websocket 连接拦截器

在 websocket 握手成功之后,管理已经创建的 websocket 连接

./demo-websocket-spring/src/main/java/com/ljq/demo/springboot/websocketspring/interceptor/SocketAuthHandler.java
package com.ljq.demo.springboot.websocketspring.interceptor;

import com.ljq.demo.springboot.websocketspring.web.SocketSessionManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.time.LocalDateTime;
import java.util.Objects;

/**
 * @Description: websocket 连接拦截器
 * @Author: junqiang.lu
 * @Date: 2020/3/19
 */
@Slf4j
@Component
public class SocketAuthHandler extends TextWebSocketHandler {

    private static final String TOKEN_FIELD = "token";

    /**
     * 握手成功之后
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Object token = session.getAttributes().get(TOKEN_FIELD);
        if (Objects.nonNull(token)) {
            // 用户连接成功,缓存用户会话
            log.debug("用户[ {} ]创建连接", token);
            SocketSessionManager.add(String.valueOf(token), session);
        } else {
            throw new RuntimeException("用户登录已失效");
        }
    }

    /**
     * 接收客户端消息
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 读取客户端消息
        Object token = session.getAttributes().get(TOKEN_FIELD);
        String payload = message.getPayload();
        log.debug("收到用户 [{}] 的消息,消息内容为: {}",token, payload);

        StringBuilder responseBuilder = new StringBuilder();
        responseBuilder.append("服务端已接收到用户 [").append(token).append("] 的消息,消息内容为:");
        responseBuilder.append(payload).append(",当前服务器时间: ");
        responseBuilder.append(LocalDateTime.now());

        session.sendMessage(new TextMessage(responseBuilder.toString()));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Object token = session.getAttributes().get(TOKEN_FIELD);
        if (Objects.nonNull(token)) {
            log.debug("用户 [{}] 断开连接", token);
            SocketSessionManager.remove(String.valueOf(token));
        }
    }
}

方法说明:

afterConnectionEstablished() 方法是在 websocket 握手成功之后,创建 socket 连接的时候触发,等价于 socket 的原生注解 @OnOpen

handleTextMessage() 方法是在客户端向服务端发送文本消息的时候触发,等价于 socket 的原生注解 @OnMessage 。Spring 封装的还有 handleMessage() 方法,所有客户端发送消息都会触发该方法,无论什么类型的数据。

afterConnectionClosed() 方法是在客户端(请求)断开连接的时候触发,等价于 socket 的原生注解 @OnClose

4.4 websocket 拦截器配置类

./demo-websocket-spring/src/main/java/com/ljq/demo/springboot/websocketspring/interceptor/SocketConfig.java
package com.ljq.demo.springboot.websocketspring.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @Description: websocket 拦截器配置
 * @Author: junqiang.lu
 * @Date: 2020/3/19
 */
@Configuration
@EnableWebSocket
public class SocketConfig implements WebSocketConfigurer {

    @Autowired
    private SocketAuthHandler socketAuthHandler;
    @Autowired
    private SocketInterceptor socketInterceptor;

    private static final String WEB_SOCKET_PATH = "socketSpring";

    /**
     *
     * @param webSocketHandlerRegistry
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(socketAuthHandler, WEB_SOCKET_PATH)
                .addInterceptors(socketInterceptor)
                .setAllowedOrigins("*");
    }
}

4.5 SpringBoot 应用启动类

./demo-websocket-spring/src/main/java/com/ljq/demo/springboot/websocketspring/DemoWebsocketSpringApplication.java
package com.ljq.demo.springboot.websocketspring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author junqiang.lu
 */
@SpringBootApplication(scanBasePackages = {"com.ljq.demo.springboot"})
public class DemoWebsocketSpringApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoWebsocketSpringApplication.class, args);
    }

}

5 测试

在线 websocket 测试地址:

WebSocket在线测试工具

在线WebSocket测试工具

启动项目,在测试网站输入 websocket 地址:

ws://127.0.0.1:8201/socketSpring?token=demo1

127.0.0.1 为本机 ip 地址,可根据需要自行更改 ip 地址

demo1 为用户名,测试时可自行更换

连接成功,无返回结果,控制台日志:

2020-03-20 15:00:30 | DEBUG | http-nio-8201-exec-5 | c.l.d.s.w.interceptor.SocketInterceptor 39| websocket starts handshaking
2020-03-20 15:00:30 | DEBUG | http-nio-8201-exec-5 | c.l.d.s.w.interceptor.SocketInterceptor 45| 用户 [ demo1 ]握手成功
2020-03-20 15:00:30 | DEBUG | http-nio-8201-exec-5 | c.l.d.s.w.interceptor.SocketInterceptor 63| 握手完成!
2020-03-20 15:00:30 | DEBUG | http-nio-8201-exec-5 | c.l.d.s.w.interceptor.SocketAuthHandler 36| 用户[ demo1 ]创建连接

发送消息,消息内容为:

55555

返回结果为:

服务端已接收到用户 [demo1] 的消息,消息内容为:55555,当前服务器时间: 2020-03-20T15:01:20.990

控制台日志为:

2020-03-20 15:01:20 | DEBUG | http-nio-8201-exec-6 | c.l.d.s.w.interceptor.SocketAuthHandler 55| 收到用户 [demo1] 的消息,消息内容为: 55555

断开连接,无返回结果,控制台日志为:

2020-03-20 15:03:08 | DEBUG | http-nio-8201-exec-7 | c.l.d.s.w.interceptor.SocketAuthHandler 69| 用户 [demo1] 断开连接

至此,在 SpringBoot 中使用 Spring 封装注解简易集成 websocket 已实现

6 参考资料推荐

A Guide to the Java API for WebSocket

Spring Boot系列十六 WebSocket简介和spring boot集成简单消息代理

【websocket】spring boot 集成 websocket 的四种方式

WebSocket在线测试工具

在线WebSocket测试工具

7 Github 源码

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.
404Code

你可能感兴趣的:(Java)