WebSocket简易聊天室实现(有详细解释)

完整代码

Arata08/online-chat-demo

服务端:

1.编写配置类,扫描有 @ServerEndpoint 注解的 Bean

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

解释:
@Configuration 注解表示该类是一个配置类,用于定义Spring容器中的bean。配置类可以替代传统的XML配置文件,通过Java代码来声明和管理bean。
@ServerEndpointExporter 是Spring WebSocket提供的一个类,用于自动注册使用 @ServerEndpoint 注解标注的WebSocket端点。它会扫描应用程序中的所有 @ServerEndpoint 注解的类,并将它们注册为WebSocket端点。

2.编写配置类,用于获取 HttpSession 对象

java 复制代码
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
/**
 * 获取HttpSession,这样的话,ChatEndpoint类就能操作HttpSession
 */
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig serverEndpointConfig, HandshakeRequest request, HandshakeResponse response) {
        // 获取 HttpSession 对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();

        // 将 httpSession 对象保存起来,存到 ServerEndpointConfig 对象中
        // 在 ChatEndpoint 类的 onOpen 方法就能通过 EndpointConfig 对象获取在这里存入的数据
        serverEndpointConfig.getUserProperties().put(HttpSession.class.getName(), httpSession);
    }
}

解释:

EndpointConfig端点配置类使用-CSDN博客文章浏览阅读2次。接口位于包中,它是Java WebSocket API(JSR 356)的一部分。Spring WebSocket框架也提供了对这个接口的支持。https://blog.csdn.net/m0_61160520/article/details/143819154?fromshare=blogdetail&sharetype=blogdetail&sharerId=143819154&sharerefer=PC&sharesource=m0_61160520&sharefrom=from_link

modifyHandshake方法在WebSocket握手过程中被调用。它允许你在握手阶段修改 ServerEndpointConfig 对象,并访问HTTP请求和响应对象。这个方法的签名如下:

java 复制代码
public void modifyHandshake(
    ServerEndpointConfig serverEndpointConfig, 
    HandshakeRequest request, 
    HandshakeResponse response)
  • serverEndpointConfig:当前WebSocket端点的配置对象。
  • request:握手请求对象,包含客户端发起握手请求的信息。
  • response:握手响应对象,包含服务器对握手请求的响应信息。

3.注册一个WebSocket端点类

解释: https://blog.csdn.net/m0_61160520/article/details/143818152?fromshare=blogdetail&sharetype=blogdetail&sharerId=143818152&sharerefer=PC&sharesource=m0_61160520&sharefrom=from_linkhttps://blog.csdn.net/m0_61160520/article/details/143818152?fromshare=blogdetail&sharetype=blogdetail&sharerId=143818152&sharerefer=PC&sharesource=m0_61160520&sharefrom=from_link

java 复制代码
import cn.edu.scau.config.GetHttpSessionConfig;
import cn.edu.scau.utils.MessageUtils;
import cn.edu.scau.websocket.pojo.Message;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;

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

@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {

    // 保存在线的用户,key为用户名,value为 Session 对象
    private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();

    private HttpSession httpSession;

    /**
     * 建立websocket连接后,被调用
     *
     * @param session Session
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());

        String user = (String) this.httpSession.getAttribute("currentUser");
        if (user != null) {
            onlineUsers.put(user, session);
        }

        // 通知所有用户,当前用户上线了
        String message = MessageUtils.getMessage(true, null, getFriends());
        broadcastAllUsers(message);
    }


    private Set<String> getFriends() {
        return onlineUsers.keySet();
    }

    private void broadcastAllUsers(String message) {
        try {
            Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();

            for (Map.Entry<String, Session> entry : entries) {
                // 获取到所有用户对应的 session 对象
                Session session = entry.getValue();

                // 使用 getBasicRemote() 方法发送同步消息
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    /**
     * 浏览器发送消息到服务端时该方法会被调用,也就是私聊
     * 张三  -->  李四
     *
     * @param message String
     */
    @OnMessage
    public void onMessage(String message) {
        try {
            // 将消息推送给指定的用户
            Message msg = JSON.parseObject(message, Message.class);

            // 获取消息接收方的用户名
            String toName = msg.getToName();
            String tempMessage = msg.getMessage();

            // 获取消息接收方用户对象的 session 对象
            Session session = onlineUsers.get(toName);
            String currentUser = (String) this.httpSession.getAttribute("currentUser");
            String messageToSend = MessageUtils.getMessage(false, currentUser, tempMessage);

            session.getBasicRemote().sendText(messageToSend);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    /**
     * 断开 websocket 连接时被调用
     *
     * @param session Session
     */
    @OnClose
    public void onClose(Session session) throws IOException {
        // 1.从 onlineUsers 中删除当前用户的 session 对象,表示当前用户已下线
        String user = (String) this.httpSession.getAttribute("currentUser");
        if (user != null) {
            Session remove = onlineUsers.remove(user);
            if (remove != null) {
                remove.close();
            }

            session.close();
        }

        // 2.通知其他用户,当前用户已下线
        // 注意:不是发送类似于 xxx 已下线的消息,而是向在线用户重新发送一次当前在线的所有用户
        String message = MessageUtils.getMessage(true, null, getFriends());
        broadcastAllUsers(message);
    }

}

客户端

1.创建一个 axios 实例

向后端发送登录请求需要使用这个 axios 实例

javascript 复制代码
import axios from 'axios'

const request = axios.create({
  baseURL: '/api',
  timeout: 60000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})

request.interceptors.request.use(

)

request.interceptors.response.use(response => {
  if (response.data) {
    return response.data
  }
  return response
}, (error) => {
  return Promise.reject(error)
})

export default request

2.编写代理规则

vite.config.js

javascript 复制代码
import {fileURLToPath, URL} from 'node:url'

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue()
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:7024',
        changeOrigin: true,
        rewrite: (path) => {
          return path.replace('/api', '')
        }
      }
    }
  }
})

3.创建 WebSocket 对象

javascript 复制代码
webSocket.value = new WebSocket('ws://localhost:7024/chat')

4.为 WebSocket 对象绑定事件

javascript 复制代码
webSocket.value.onopen = onOpen

// 接收到服务端推送的消息后触发
webSocket.value.onmessage = onMessage

webSocket.value.onclose = onClose
相关推荐
金銀銅鐵2 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
福建佰胜张工3 小时前
SMC 薄型气爪 MHF2-16D1 深度解析:平行开闭、低剖面结构在自动化装配中的选型与落地
java·服务器·安全·自动化
catoop3 小时前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
system_error_x3 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
驭渊的小故事4 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客4 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1884 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn5 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su5 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
g3voip5 小时前
工业防水电话到底怎么选?隧道、管廊与码头的需求差异
服务器·网络·信息与通信·ip