Springboot3.X集成WebSocket完整流程

avatar
作者
猴君
阅读量:0

WebSocket介绍

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

环境介绍

springboot

pom文件

  <!--spring 版本-->   <parent>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-parent</artifactId>     <version>3.0.5</version>   </parent> 

websocket依赖

<!--websocket-->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-websocket</artifactId>        </dependency> 

Vue

"vue": "3.2.45", "websocket": "^1.0.34" 

后端代码示例

目录结构

在这里插入图片描述

配置类

package gzsf.epg.sever.webSocket.config;  import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter;  /**  * webSocket的配置类  * @author   * @date 2023/10/7  */  @Configuration public class WebSocketConfig {      @Bean     public ServerEndpointExporter serverEndpointExporter(){         return  new ServerEndpointExporter();     }  } 
package gzsf.epg.sever.webSocket.websocket;  import jakarta.websocket.Session;  /**  * @author  * @date 2023/10/7  */ public class WebSocketClient {      // 与某个客户端的连接会话,需要通过它来给客户端发送数据     private Session session;      //连接的uri     private String uri;      public Session getSession() {         return session;     }      public void setSession(Session session) {         this.session = session;     }      public String getUri() {         return uri;     }      public void setUri(String uri) {         this.uri = uri;     }  }  

如下是后端发送消息的配置,如需发送文本类的数据需要配置这个

package gzsf.epg.sever.webSocket.util;  import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.json.JsonMapper; import gzsf.epg.sever.datacheck.vo.ExportVo; import jakarta.websocket.EncodeException; import jakarta.websocket.Encoder; import jakarta.websocket.EndpointConfig;  public class ServerEncoder implements Encoder.Text<ExportVo> {       @Override     public void destroy() {         // TODO Auto-generated method stub         // 这里不重要     }       @Override     public void init(EndpointConfig arg0) {         // TODO Auto-generated method stub         // 这里也不重要       }       /*     *  encode()方法里的参数和Text<T>里的T一致,如果你是Student,这里就是encode(Student student)     */     @Override     public String encode(ExportVo responseMessage) throws EncodeException {         try {             JsonMapper jsonMapper = new JsonMapper();             return jsonMapper.writeValueAsString(responseMessage);           } catch ( JsonProcessingException e) {             e.printStackTrace();             return null;         }     } } 

服务类

package gzsf.epg.sever.webSocket.service;  import gzsf.epg.sever.datacheck.vo.ExportVo; import gzsf.epg.sever.webSocket.util.ServerEncoder; import gzsf.epg.sever.webSocket.websocket.WebSocketClient; import jakarta.websocket.*; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component;  import java.io.IOException;  import java.util.concurrent.ConcurrentHashMap;  /**  * @author   * @date 2023/10/7  */  @ServerEndpoint(value = "/websocket/{userName}", encoders = {ServerEncoder.class}) @Component public class WebSocketService {     private static final Logger log = LoggerFactory.getLogger(WebSocketService.class);      private static ApplicationContext applicationContext;      public static void setApplicationContext(ApplicationContext context) {         applicationContext = context;     }      private static ConcurrentHashMap<String, WebSocketClient> webSocketMap = new ConcurrentHashMap<>();       /**      * 与某个客户端的连接会话,需要通过它来给客户端发送数据      */     private Session session;     /**      * 接收userName      */     private String userName;       /**      * 客户端与服务端连接成功      *      * @param session      */     @OnOpen     public void onOpen(Session session, @PathParam("userName") String userName) {         this.session = session;         //根据token获取用户名         this.userName = userName;         WebSocketClient client = new WebSocketClient();         client.setSession(session);         client.setUri(session.getRequestURI().toString());         webSocketMap.put(userName, client);         log.info("连接成功!"+session.getRequestURI().toString());         log.info("连接成功!"+session);     }      /**      * 客户端与服务端连接关闭      */     @OnClose     public void onClose() {         log.info("连接关闭!");     }      /**      * 客户端与服务端连接异常      *      * @param error      * @param session      */     @OnError     public void onError(Throwable error, Session session) {     }      /**      * 客户端向服务端发送消息      *      * @param message      * @throws IOException      */     @OnMessage     public void onMsg(Session session, String message) throws IOException {         log.info("消息测试!");     }      public static void sendVo(String userName, ExportVo vo) {         try {             WebSocketClient webSocketClient = webSocketMap.get(userName);             if (webSocketClient != null) {                 webSocketClient.getSession().getBasicRemote().sendObject(vo);             }         } catch (IOException e) {             e.printStackTrace();             throw new RuntimeException(e.getMessage());         } catch (EncodeException e) {             throw new RuntimeException(e.getMessage());         }     }      /**      * 向指定客户端发送消息      *      * @param message      */     public static void sendMessage(String userName, String message) {         try {             log.info("准备发消息!"+userName);             WebSocketClient webSocketClient = webSocketMap.get(userName);             log.info("连接成功》》》》》》》》》"+webSocketMap);             if (webSocketClient != null) {                 log.info("发送的消息是!"+message);                 webSocketClient.getSession().getBasicRemote().sendText(message);             }         } catch (IOException e) {             e.printStackTrace();             log.info("失败");             throw new RuntimeException(e.getMessage());         }     } }  

需要注意

修改启动类如下,服务类就能注入,否则在调用service的时候会空指针异常,具体原因可以去了解下 spring 默认管理

package gzsf.epg.sever;  import gzsf.epg.common.core.utils.SpringContextUtils; import gzsf.epg.common.security.annotation.EnableCustomConfiguration; import gzsf.epg.common.security.annotation.EnableFeignBeanClients; import gzsf.epg.sever.webSocket.service.WebSocketService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;  /**  * @author   * @date 2023/10/19  */ @EnableCustomConfiguration @EnableFeignBeanClients @SpringBootApplication @Import(SpringContextUtils.class) public class ServiceApplication {     private static final Logger logger = LoggerFactory.getLogger(ServiceApplication.class);      public static void main(String[] args) {         ConfigurableApplicationContext run = SpringApplication.run(ServiceApplication.class, args);         //解决WebSocket不能注入的问题         WebSocketService.setApplicationContext(run);     } }  

前端代码示例

连接地址里 admin为用户名,因为websocket是单独为每个用户创建长连接,发送的消息也是一对一发送,当然也可以改为一对多,全权在业务怎么用。前端可获取用户名传到后端,如下代码示例是写死的
注意:前端也需下载依赖
下面展示一些 内联代码片

npm install vue-native-websocket 
/**  * 创建webSocket连接  */ function createWS() {   webSocket.value = new WebSocket("ws://127.0.0.1:8080/server/websocket/admin");   //onopen事件监听   webSocket.value.addEventListener('open', e => {     console.log('与服务端连接打开->', e)   }, false) //onclose事件监听   webSocket.value.addEventListener('close', e => {     console.log('与服务端连接关闭->', e)   }, false) //onmessage事件监听   webSocket.value.addEventListener('message', e => {     console.log('接收到服务端的消息->', e)   }, false) //onerror事件监听   webSocket.value.addEventListener('error', e => {   }, false) } 

只需在需要开启长连接的时候调用createWS();即可;

简单说明一下执行流程

前端

在需要开启长连接处调用createWS();开启长连接即可,需注意用户名(该用户名就是唯一标识)传递。

后端

前端发起建立长连接请求后,执行WebSocketService类中的onOpen方法,创建client并存放在map集合中以便执行完业务后发送反馈消息。

    /**      * 客户端与服务端连接成功      *      * @param session      */     @OnOpen     public void onOpen(Session session, @PathParam("userName") String userName) {         this.session = session;         //根据token获取用户名         this.userName = userName;         WebSocketClient client = new WebSocketClient();         client.setSession(session);         client.setUri(session.getRequestURI().toString());         webSocketMap.put(userName, client);         log.info("连接成功!"+session);     } 

如何调用

后端接收到前端请求后可新建线程执行业务流程,先返回执行中状态给前端,防止前后端连接超时的报错。

    @PostMapping("/importDx")     public AjaxResult importDx(@RequestParam("xlsxFile") MultipartFile file) {         this.onCallback(file);         return AjaxResult.success();     }      public void onCallback(MultipartFile file) {         ExecutorService executor = Executors.newCachedThreadPool();         executor.execute(() -> {             try {                 //服务类代码             } catch (IOException e) {                 throw new RuntimeException(e);             }         });         executor.shutdown();     } 

业务执行完成后可调用,username为创建websocket的唯一标识。

/**              * 调用sendMessage方法使用              */             WebSocketService.sendMessage(username, "执行成功"); 

只要是在长连接里面,想做什么全看业务,简单来说干什么都行!至此WebSocket使用结束

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!