基于SpringBoot + Vue的项目整合WebSocket的入门教程

1、WebSocket简介

WebSocket是一种网络通信协议,可以在单个TCP连接上进行全双工通信。它于2011年被IETF定为标准RFC 6455,并由RFC7936进行补充规范。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。这使得客户端和服务器之间的数据交换变得更加简单,并允许服务端主动向客户端推送数据。

2、服务端环境搭建

服务端基于SpringBoot实现,首先引入对应的jar包,然后进行ServerEndpointExporter配置,然后再定义了一个WebSocket操作类,最后编写了一个测试的类WebSocketController(一个普通的Controller类)。

2.1、maven依赖
xml 复制代码
<!-- websocket 依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.2、配置类WebSocketConfig

ServerEndpointExporter是Spring框架中的一个类,主要用于在Spring Boot应用中启动和管理WebSocket服务器端点。

在Spring Boot内置容器(嵌入式容器)中运行时,必须由ServerEndpointExporter提供ServerEndpointExporter bean,它会在启动时自动扫描和注册应用中的WebSocket端点。

注意:在Tomcat等其他容器中运行时,容器的扫描工作会由容器自己处理,不需要手动注入ServerEndpointExporter bean,即不需要该配置!!!

java 复制代码
@Configuration
public class WebSocketConfig {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
2.3、WebSocket操作类

WebSocket操作类定义了WebSocket中的onOpen()、onMessage()、onClose()、onError()等方法,同时提供了一个发送广播(全部订阅用户)和点对点信息的方法。

1、这里@ServerEndpoint("/api/websocket/{userId}")中的定义可以根据自己的需要进行修改,因为我的项目里使用了SpringSecurity了,为了避免登录鉴权,这里使用"/api/**"配置了免登陆Api。后续会继续完善需要登录鉴权的使用方式。

2、这里的WebSocket操作类,每次建立 WebSocket 连接时,就会初始化一个新的 bean。这是由于 WebSocket 是一种双向的、长时间的通信机制,它需要维护每个连接的状态,处理每个从客户端发来的消息,并根据这些消息生成响应消息发送回客户端。因此,为每个 WebSocket 连接创建一个单独的 bean 是必要的,这样可以让 Spring Boot 为每个连接提供必要的管理和生命周期控制。这种方式也可以使用单例模式实现,后续再更新相关用法。

java 复制代码
@Component
@Slf4j
@ServerEndpoint("/api/websocket/{userId}")
public class WebSocket {
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    //用户ID
    private String userId;

    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    //虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
    private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>();
    // 用来存在线连接用户信息
    private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();

    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="userId")String userId) {
        try {
            this.session = session;
            this.userId = userId;
            webSockets.add(this);
            sessionPool.put(userId, session);
            log.info("WebSocket消息有新的连接,总数为:"+webSockets.size());
        } catch (Exception e) {
            log.error("WebSocket异常-链接失败(onOpen),原因:" + e.getMessage());
        }
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);
            sessionPool.remove(this.userId);
            log.info("WebSocket消息连接断开,总数为:"+webSockets.size());
        } catch (Exception e) {
            log.error("WebSocket异常-链接关闭失败(onClose),原因:" + e.getMessage());
        }
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("WebSocket消息收到客户端消息:"+message);
    }

    /** 发送错误时的处理
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误,原因:"+error.getMessage());
        log.error("WebSocket异常-错误信息(onError),原因:" + error.getMessage());
    }


    // 此为广播消息
    public void sendAllMessage(String message) {
        log.info("WebSocket消息-广播消息:"+message);
        for(WebSocket webSocket : webSockets) {
            try {
                if(webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                log.error("WebSocket异常-广播消息异常(sendAllMessage),原因:" + e.getMessage());
            }
        }
    }

    // 此为单点消息
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            try {
                log.info("WebSocket消息-点对点消息:"+message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                log.error("WebSocket异常-点对点消息异常(sendOneMessage),原因:" + e.getMessage());
            }
        }
    }

}
2.4、测试类WebSocketController

这个测试类,主要是为了测试发送信息后,页面可以自动更新。至此,服务端的配置就完成了。

java 复制代码
@Controller
@RequestMapping("/api/msg")
public class WebSocketController {

    @Resource
    private WebSocket webSocket;

    @RequestMapping("/all/{msg}") // 将消息发送到/topic/greetings路径下
    public void all(@PathVariable String msg) {
        //创建业务消息信息
        JSONObject obj = new JSONObject();
        obj.put("msg", msg);//消息内容
        //全体发送
        webSocket.sendAllMessage(obj.toJSONString());
    }
    @RequestMapping("/{userId}/{msg}") // 将消息发送到/topic/greetings路径下
    public void sendUser(@PathVariable String userId,@PathVariable String msg) {
        //创建业务消息信息
        JSONObject obj = new JSONObject();
        obj.put("msg", msg);//消息内容
        webSocket.sendOneMessage(userId, obj.toJSONString());
    }

}

3、前端环境搭建

前端是基于Vue实现,具体代码如下:

html 复制代码
<template>
  <div class="order-list">
    <span>TestMsg:{{ message }}</span>
  </div>
</template>
<script>
export default {
    name: 'HomeView',
    data() {
      return {
          message:''
      }
    },
    components: {},
    created() {},
    mounted() {
      //初始化websocket
      this.initWebSocket()
    },
    destroyed: function () { // 离开页面生命周期函数
    	this.websocketclose();
    },
    computed: {},
    methods: {
      initWebSocket: function () { // 建立连接
        var userId = "test"//this.COMMON.getStorage("user");
        //对应@ServerEndpoint("/api/websocket/{userId}")中的地址
        var url = "ws://ip:port/项目名/api/websocket/" + userId;
        this.websock = new WebSocket(url);
        this.websock.onopen = this.websocketonopen;
        this.websock.send = this.websocketsend;
        this.websock.onerror = this.websocketonerror;
        this.websock.onmessage = this.websocketonmessage;
        this.websock.onclose = this.websocketclose;
      },
      // 连接成功后调用
      websocketonopen: function () {
        console.log("WebSocket连接成功");
      },
      // 发生错误时调用
      websocketonerror: function (e) {
        console.log("WebSocket连接发生错误" + JSON.stringify(e));
      },
      // 给后端发消息时调用
      websocketsend: function (e) {
        console.log("WebSocket连接发生错误" + JSON.stringify(e));
      },
// 接收后端消息
      // vue 客户端根据返回的cmd类型处理不同的业务响应
      websocketonmessage: function (e) {
        this.message = data;
      },
      // 关闭连接时调用
      websocketclose: function (e) {
        console.log("connection closed (" + e.code + ")");
      }
    },
  }
</script>
<style lang="scss"></style>

4、测试

至此,我们就完成了WebSocket的服务端和前端的环境搭建,首先启动后台服务,然后启动前端服务,进入上述页面,这个时候就会建立起来链接,然后访问"http://localhost:8803/qriver-lab/api/msg/test/6666",其中test对应的是userId,6666是msg信息,这个时候就会发现页面会自动显示6666,不需要进行刷新。

相关推荐
王王碎冰冰5 小时前
基于 Vue3@3.5+跟Ant Design of Vue 的二次封装的 Form跟搜索Table
前端·vue.js
天蓝色的鱼鱼6 小时前
Element UI 2.X 主题定制完整指南:解决官方工具失效的实战方案
前端·vue.js
我是日安7 小时前
从零到一打造 Vue3 响应式系统 Day 8 - Effect:深入剖析嵌套 effect
前端·vue.js
DevUI团队7 小时前
🚀 MateChat V1.8.0 震撼发布!对话卡片可视化升级,对话体验全面进化~
前端·vue.js·人工智能
好好好明天会更好7 小时前
pinia从定义到运用
前端·vue.js
代码小学僧7 小时前
Vite 项目最简单方法解决部署后 Failed to fetch dynamically imported Error问题
前端·vue.js·vite
后端小张8 小时前
基于飞算AI的图书管理系统设计与实现
spring boot
东坡白菜8 小时前
SSE 实现 AI 对话中的流式输出
javascript·vue.js
猩兵哥哥12 小时前
前端面向对象设计原则运用 - 策略模式
前端·javascript·vue.js
EMT13 小时前
在 Vue 项目中使用 URL Query 保存和恢复搜索条件
javascript·vue.js