引入WebSocket所需要starter模块依赖包;pom.xml文件引入依赖
org.springframework.boot spring-boot-starter-websocket com.alibaba fastjson 1.2.75
编写WebSocketConfig配置类
package com.gremlin.webSocket;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @className: WebSocketConfig* @author: gremlin* @version: 1.0.0* @description: 引入自动配置类这个配置类ServerEndpointExporter,主要用于扫描WebSocket相关的注解* @date: 2023/03/08 14:00*/
@Configuration
@EnableWebSocket
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}
编写发送对象数据的编码器
package com.gremlin.webSocket;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.apache.poi.ss.formula.functions.T;import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;/*** 发送object对象的编码器* @author admin*/
public class ServerEncoder implements Encoder.Text> {@Overridepublic void destroy() { }@Overridepublic void init(EndpointConfig arg0) { }@Overridepublic String encode(Page tPage) throws EncodeException {try {/** 这里是重点,只需要返回Object序列化后的json字符串就行* 你也可以使用gosn,fastJson来序列化。*/JsonMapper jsonMapper = new JsonMapper();return jsonMapper.writeValueAsString(tPage);} catch ( JsonProcessingException e) {e.printStackTrace();return null;}}
}
定义一个websocket类,注册到Spring容器中,使用@ServerEndpoint注解把当前类定义成一个服务器端并标记好客户端发起WebSocket连接请求的URL,暴露ws应用的路径
package com.gremlin.webSocket;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gremlin.service.NewsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @className: WebSocket* @author: gremlin* @version: 1.0.0* @description: 注解@ServerEndpoint 暴露的ws应用的路径* @date: 2023/03/08 14:03*/
@Component
@Slf4j
@ServerEndpoint(value = "/websocket/{userId}", encoders = {ServerEncoder.class})
public class WebSocket {/*** spring默认是单例的,而WebSocket是多对象的,也就是每次会产生不同的对象。* 在初始化项目的时候,WebSocket就会产生一个对象,这时候就会注入service了,而当客户端与WebSocket服务端连接过后,* 又会产生一个新对象,而spring默认是单例,只会给一个相同的对象注入一次service,* 因此这时候的WebSocket新对象就不会再注入service了,* 再去调用该service中的方法的话也就会发生空指针异常。* 因此要写出静态 构造注入 注入的时候,给类的 service 注入*/private static NewsService newsService;@Autowiredpublic void setNewsService (NewsService newsService) {WebSocket.newsService= newsService;}/*** 用来存储服务连接对象*/private static Map clientMap = new ConcurrentHashMap<>();/*** @Open:当WebSocket连接建立成功后会触发这个注解修饰的方法;* @Close:当WebSocket连接关闭或中断后会触发这个注解修饰的方法;* @OnMessage:当客户端发送消息到服务端时,会触发这个注解修饰的方法;* @OnError:当 websocket 建立连接时出现异常会触发这个注解修饰的方法;*/private Session session;private String userId;/*** 客户端与服务端连接成功*/@OnOpenpublic void onOpen(Session session,@PathParam("userId") String userId) throws IOException {this.session = session;this.userId = userId;// 与当前客户端连接成功时if (clientMap.containsKey(userId)){clientMap.remove(userId);clientMap.put(userId,this);} else{clientMap.put(userId,this);}// sendAllMessage("连接成功","连接成功");// 连接成功则立刻发送内容sendInfo("连接成功",userId);log.info("-----------------连接成功---------------: " + userId);}/*** 客户端与服务端连接关闭*/@OnClosepublic void onClose(Session session){log.info("连接关闭:"+userId);// 与当前客户端连接关闭时clientMap.remove(userId);}/*** 客户端向服务端发送消息*/@OnMessagepublic void onMsg(Session session,String message) throws IOException {// 收到来自当前客户端的消息时log.info("客户端向服务端发送消息: "+ userId);clientMap.get(userId).sendAllMessage(message,"");}/*** 向客户端发送消息*/private void sendAllMessage(String message,Object data) throws IOException {log.info("向客户端发送消息:"+userId);try {this.session.getBasicRemote().sendObject(data);log.info("服务端向客户端发送消息成功!");} catch (IOException | EncodeException e) {e.printStackTrace();}}/*** 发送自定义消息* */public void sendInfo(String message,@PathParam("userId") String userId) throws IOException {log.info("发送消息到: " + userId + ",message: " + message);if(StringUtils.isNotBlank(userId) && clientMap.containsKey(userId)){// 此处调用接口获取需要发送的数据,此处的data可以是集合也可以是Page等clientMap.get(userId).sendAllMessage(message,data);}else{log.error("发送消息错误,接收用户不在线");}}/*** 客户端与服务端连接异常*/@OnErrorpublic void onError(Throwable error,Session session) {log.info("客户端与服务端连接异常:"+userId);error.printStackTrace();}
}