Spring Boot 整合 WebSocket 模拟聊天
1.后端搭建
a.创建SpringBoot工程,选择引入Web、Thymeleaf、Websocket依赖,并手动引入其他依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> ...... </dependencies>
b.配置application.yml配置文件
# 服务端口 server: port: 8080 spring: # 服务名称 application: name: springboot-websocket # thymeleaf热更新 thymeleaf: cache: false
c.创建WebSocket配置类
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
d.创建WebSocket消息监听类
@ServerEndpoint("/ws/{userId}") @Component public class WebSocket { private Logger logger = LoggerFactory.getLogger(this.getClass()); private Session session; private String userId; private WebSocketHandler webSocketHandler = (WebSocketHandler) BeanFactoryUtil.getBean("webSocketHandler"); @OnOpen public void onOpen(Session session, @PathParam("userId") String userId) throws Exception { this.session = session; this.userId = userId; webSocketHandler.putSocket(userId, this); } @OnClose public void onClose() { webSocketHandler.removeSocket(userId); } @OnMessage public void onMessage(String message, Session session) { webSocketHandler.handleMsg(message); } @OnError public void onError(Session session, Throwable error) { logger.error("WebSocket发生错误", error); } //发送消息 public void sendMessage(String message) throws Exception { if (this.session.isOpen()) { this.session.getAsyncRemote().sendText(message); } } }
e.创建消息处理Handler
@Component public class WebSocketHandler { private Logger logger = LoggerFactory.getLogger(this.getClass()); //用户ID与Socket的对应关系 public Map<String, WebSocket> userSocketMap = new ConcurrentHashMap<>(); //添加socket public void putSocket(String userId, WebSocket socket){ this.userSocketMap.put(userId, socket); } //删除socket public void removeSocket(String userId){ this.userSocketMap.remove(userId); } //发送消息 public void sendMsg(String userId, String msg){ WebSocket socket = this.userSocketMap.get(userId); if(socket != null){ try { socket.sendMessage(msg); }catch (Exception e){ logger.error("WebSocket发送消息异常", e); } } } //群发消息 public void sendMsg2All(String msg){ for(WebSocket socket : this.userSocketMap.values()){ try { socket.sendMessage(msg); }catch (Exception e){ logger.error("WebSocket发送消息异常", e); } } } //处理消息 public void handleMsg(String msgJson){ MsgVo msgVo = JSON.parseObject(msgJson, MsgVo.class); if(msgVo.getType() == MsgTypeEnum.ONE2ONE.getCode()){ this.sendMsg(msgVo.getToId(), msgJson); this.sendMsg(msgVo.getFromId(), msgJson); }else if(msgVo.getType() == MsgTypeEnum.ONE2ALL.getCode()){ this.sendMsg2All(msgJson); } } }
f.创建BeanFactory工具类
@Component public class BeanFactoryUtil implements ApplicationContextAware { private static ApplicationContext beanFactory; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { beanFactory = applicationContext; } public static ApplicationContext getBeanFactory() { return beanFactory; } public static Object getBean(String beanName){ return beanFactory.getBean(beanName); } }
g.创建消息VO类
public class MsgVo implements Serializable { private int type; private String fromId; private String toId; private String body; public int getType() { return type; } public void setType(int type) { this.type = type; } public String getFromId() { return fromId; } public void setFromId(String fromId) { this.fromId = fromId; } public String getToId() { return toId; } public void setToId(String toId) { this.toId = toId; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } }
h.创建消息类型枚举
public enum MsgTypeEnum { ONE2ONE(1,"私聊"), ONE2ALL(2,"公屏"); private int code; private String name; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } private MsgTypeEnum(int code, String name) { this.code = code; this.name = name; } public static MsgTypeEnum getMsgTypeEnum(int code) { for(MsgTypeEnum item : MsgTypeEnum.values()) { if (item.getCode() == code) { return item; } } return null; } }
i.创建公用Controller
@Controller public class CommonController { @RequestMapping("/") public String index(){ return "home"; } }
j.注意:由于 @ServerEndpoint 是每创建一个连接,则new一个对象,所以无法通过 @Resource 和 @Autowired 注入其他依赖,需要通过调用 ApplicationContext 的 getBean 方法手动获取。
2,前端测试
a.在 resources/templates 下创建 home.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home</title> <style type="text/css"> h1{ text-align:center; } .row{ margin:10px 0px; } .col{ display: inline-block; margin:0px 5px; } .msg-container{ width: 40%; height: 500px; } .type-select-wrapper{ margin: 0 0 0 10%; } .msg-div{ width: 80%; margin:0 0 0 10%; height: 300px; border:solid 1px; overflow: scroll; } </style> </head> <body> <div> <div class="row"> <div class="col msg-container"> <div class="row"> <h1>消息窗口1</h1> </div> <div class="row"> <div class="col msg-div"></div> </div> <div class="row"> <div class="col type-select-wrapper"> <select class="type-select"> <option data-value="1">私聊</option> <option selected data-value="2">公屏</option> </select> </div> <div class="col msg-input-wrapper"> <input class="msg-input" type="text"/> </div> <div class="col"> <button id="send-btn-1">发送</button> </div> </div> </div> <div class="col msg-container"> <div class="row"> <h1>消息窗口2</h1> </div> <div class="row"> <div class="col msg-div"></div> </div> <div class="row"> <div class="col type-select-wrapper"> <select class="type-select"> <option data-value="1">私聊</option> <option selected data-value="2">公屏</option> </select> </div> <div class="col msg-input-wrapper"> <input class="msg-input" type="text"/> </div> <div class="col"> <button id="send-btn-2">发送</button> </div> </div> </div> </div> <div class="row"> <div class="col msg-container"> <div class="row"> <h1>消息窗口3</h1> </div> <div class="row"> <div class="col msg-div"></div> </div> <div class="row"> <div class="col type-select-wrapper"> <select class="type-select"> <option data-value="1">私聊</option> <option selected data-value="2">公屏</option> </select> </div> <div class="col msg-input-wrapper"> <input class="msg-input" type="text"/> </div> <div class="col"> <button id="send-btn-3">发送</button> </div> </div> </div> </div> </div> </body> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <script> let userIdArray = ["张三", "李四", "王五"]; let wsArray = new Array(3); $(function(){ initWebSocketFunc(userIdArray[0], 0); initWebSocketFunc(userIdArray[1], 1); initWebSocketFunc(userIdArray[2], 2); $("#send-btn-1").on("click", {num: 0, fromId: userIdArray[0], toId: userIdArray[1]}, sendMsgFunc); $("#send-btn-2").on("click",{num: 1, fromId: userIdArray[1], toId: userIdArray[2]}, sendMsgFunc); $("#send-btn-3").on("click",{num: 2, fromId: userIdArray[2], toId: userIdArray[0]}, sendMsgFunc); }); let initWebSocketFunc = function(userId, num){ // 初始化一个 WebSocket 对象 let ws = new WebSocket("ws://localhost:8080/ws/" + userId); // 建立 web socket 连接成功触发事件 ws.onopen = function () { console.log("正在建立连接..."); }; // 接收服务端数据时触发事件 ws.onmessage = function (evt) { let msg = JSON.parse(evt.data); let out; if(msg.type == 1){ out = "[私聊] " + (msg.fromId==userId?"你":msg.fromId) + " 对 " + (msg.toId==userId?"你":msg.toId) + " 说:" + msg.body; }else if(msg.type == 2){ out = "[公屏] " + (msg.fromId==userId?"你":msg.fromId) + " 说:" + msg.body; } $(".msg-div:eq(" + num + ")").append(out + "<br/>"); // $($(".msg-div")[num]).append(out + "<br/>"); }; // 断开 web socket 连接成功触发事件 ws.onclose = function () { console.log("连接已关闭..."); }; wsArray[num] = ws; }; let sendMsgFunc = function(e){ let num = e.data.num; let fromId = e.data.fromId; let toId = e.data.toId; let type = $(".type-select:eq(" + num + ")").find("option:selected").attr("data-value"); let msg = $(".msg-input:eq(" + num + ")").val(); // let type = $($(".type-select")[num]).find("option:selected").attr("data-value"); // let msg = $($(".msg-input")[num]).val(); let msgData = { type: type, fromId: fromId, body: msg }; if(type == 1){ msgData.toId = toId; } let msgStr = JSON.stringify(msgData); wsArray[num].send(msgStr); } </script> </html>
b.访问 localhost:8080 测试