【开源宝藏】Jeepay VUE和React构建WebSocket通用模板

WebSocket 服务实现:Spring Boot 示例

在现代应用程序中,WebSocket 是实现双向实时通信的重要技术。本文将介绍如何使用 Spring Boot 创建一个简单的 WebSocket 服务,并提供相关的代码示例。

1. WebSocket 简介

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式相比,WebSocket 允许服务器主动向客户端推送消息,适用于实时应用,如在线聊天、实时通知和游戏等。

2. 项目结构

在开始之前,确保你的项目中包含必要的依赖。在 pom.xml 中添加以下依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
</dependency>

3. WebSocket 配置类

为了启用 WebSocket 支持,我们需要创建一个配置类 WebSocketConfig,如下所示:

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

/**
 * 开启WebSocket支持
 * 
 * @author terrfly
 * @site https://www.jeequan.com
 * @date 2021/6/22 12:57
 */
@Configuration
public class WebSocketConfig {

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

3.1 配置类解析

  • @Configuration: 表示该类是一个配置类,Spring 会在启动时加载它。
  • ServerEndpointExporter : 这个 Bean 会自动注册所有带有 @ServerEndpoint 注解的 WebSocket 端点。

4. WebSocket 服务类

以下是一个简单的 WebSocket 服务类 WsChannelUserIdServer 的实现。该类负责处理客户端的连接、消息接收和发送。

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnError;
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.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/api/anon/ws/channelUserId/{appId}/{cid}")
@Component
public class WsChannelUserIdServer {

    private final static Logger logger = LoggerFactory.getLogger(WsChannelUserIdServer.class);

    private static int onlineClientSize = 0;
    private static Map<String, Set<WsChannelUserIdServer>> wsAppIdMap = new ConcurrentHashMap<>();

    private Session session;
    private String cid = "";
    private String appId = "";

    @OnOpen
    public void onOpen(Session session, @PathParam("appId") String appId, @PathParam("cid") String cid) {
        try {
            this.cid = cid;
            this.appId = appId;
            this.session = session;

            Set<WsChannelUserIdServer> wsServerSet = wsAppIdMap.get(appId);
            if (wsServerSet == null) {
                wsServerSet = new CopyOnWriteArraySet<>();
            }
            wsServerSet.add(this);
            wsAppIdMap.put(appId, wsServerSet);

            addOnlineCount();
            logger.info("cid[{}], appId[{}] 连接开启监听!当前在线人数为 {}", cid, appId, onlineClientSize);

        } catch (Exception e) {
            logger.error("ws监听异常 cid[{}], appId[{}]", cid, appId, e);
        }
    }

    @OnClose
    public void onClose() {
        Set<WsChannelUserIdServer> wsSet = wsAppIdMap.get(this.appId);
        wsSet.remove(this);
        if (wsSet.isEmpty()) {
            wsAppIdMap.remove(this.appId);
        }

        subOnlineCount();
        logger.info("cid[{}], appId[{}] 连接关闭!当前在线人数为 {}", cid, appId, onlineClientSize);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("ws发生错误", error);
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    public static void sendMsgByAppAndCid(String appId, String cid, String msg) {
        try {
            logger.info("推送ws消息到浏览器, appId={}, cid={}, msg={}", appId, cid, msg);

            Set<WsChannelUserIdServer> wsSet = wsAppIdMap.get(appId);
            if (wsSet == null || wsSet.isEmpty()) {
                logger.info("appId[{}] 无ws监听客户端", appId);
                return;
            }

            for (WsChannelUserIdServer item : wsSet) {
                if (!cid.equals(item.cid)) {
                    continue;
                }
                try {
                    item.sendMessage(msg);
                } catch (Exception e) {
                    logger.info("推送设备消息时异常,appId={}, cid={}", appId, item.cid, e);
                }
            }
        } catch (Exception e) {
            logger.info("推送消息时异常,appId={}", appId, e);
        }
    }

    public static synchronized int getOnlineClientSize() {
        return onlineClientSize;
    }

    public static synchronized void addOnlineCount() {
        onlineClientSize++;
    }

    public static synchronized void subOnlineCount() {
        onlineClientSize--;
    }
}

5. 代码解析

5.1 连接管理

  • @OnOpen : 当客户端连接成功时调用此方法。可以在此方法中获取客户端的 appIdcid,并将当前连接的会话存储到 wsAppIdMap 中。
  • @OnClose : 当客户端连接关闭时调用此方法,从 wsAppIdMap 中移除该连接。
  • @OnError: 处理连接错误。

5.2 消息发送

  • sendMessage(String message): 通过当前会话向客户端发送消息。
  • sendMsgByAppAndCid(String appId, String cid, String msg): 根据 appIdcid 向特定客户端推送消息。

5.3 在线人数管理

使用 onlineClientSize 变量记录当前在线的客户端数量,并提供相应的增减方法。

好的!下面我将为你提供一个简单的前端实现示例,使用 Vue.jsReact 来连接我们之前创建的 WebSocket 服务。这样,你可以看到如何在前端与后端进行实时通信。

6. Vue.js 前端实现

6.1 安装 Vue.js

如果你还没有创建 Vue 项目,可以使用 Vue CLI 创建一个新的项目:

bash 复制代码
npm install -g @vue/cli
vue create websocket-demo
cd websocket-demo

6.2 创建 WebSocket 组件

src/components 目录下创建一个名为 WebSocketComponent.vue 的文件,并添加以下代码:

vue 复制代码
<template>
  <div>
    <h1>WebSocket Demo</h1>
    <input v-model="message" placeholder="Type a message" />
    <button @click="sendMessage">Send</button>
    <div>
      <h2>Messages:</h2>
      <ul>
        <li v-for="(msg, index) in messages" :key="index">{{ msg }}</li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      socket: null,
      message: '',
      messages: [],
      appId: 'yourAppId', // 替换为你的 appId
      cid: 'yourClientId'  // 替换为你的客户端自定义ID
    };
  },
  created() {
    this.connect();
  },
  methods: {
    connect() {
      this.socket = new WebSocket(`ws://localhost:8080/api/anon/ws/channelUserId/${this.appId}/${this.cid}`);

      this.socket.onopen = () => {
        console.log('WebSocket connection established.');
      };

      this.socket.onmessage = (event) => {
        const data = JSON.parse(event.data);
        this.messages.push(data.message);
      };

      this.socket.onclose = () => {
        console.log('WebSocket connection closed.');
      };

      this.socket.onerror = (error) => {
        console.error('WebSocket error:', error);
      };
    },
    sendMessage() {
      if (this.socket && this.socket.readyState === WebSocket.OPEN) {
        this.socket.send(JSON.stringify({ message: this.message }));
        this.message = ''; // 清空输入框
      } else {
        console.error('WebSocket is not open.');
      }
    }
  }
};
</script>

<style scoped>
/* 添加样式 */
</style>

6.3 使用组件

src/App.vue 中使用这个组件:

vue 复制代码
<template>
  <div id="app">
    <WebSocketComponent />
  </div>
</template>

<script>
import WebSocketComponent from './components/WebSocketComponent.vue';

export default {
  components: {
    WebSocketComponent
  }
};
</script>

<style>
/* 添加样式 */
</style>

6.4 运行 Vue 应用

在项目根目录下运行以下命令启动 Vue 应用:

bash 复制代码
npm run serve

7. React 前端实现

7.1 安装 React

如果你还没有创建 React 项目,可以使用 Create React App 创建一个新的项目:

bash 复制代码
npx create-react-app websocket-demo
cd websocket-demo

7.2 创建 WebSocket 组件

src 目录下创建一个名为 WebSocketComponent.js 的文件,并添加以下代码:

javascript 复制代码
import React, { useEffect, useState } from 'react';

const WebSocketComponent = () => {
  const [socket, setSocket] = useState(null);
  const [message, setMessage] = useState('');
  const [messages, setMessages] = useState([]);
  const appId = 'yourAppId'; // 替换为你的 appId
  const cid = 'yourClientId'; // 替换为你的客户端自定义ID

  useEffect(() => {
    const ws = new WebSocket(`ws://localhost:8080/api/anon/ws/channelUserId/${appId}/${cid}`);
    setSocket(ws);

    ws.onopen = () => {
      console.log('WebSocket connection established.');
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setMessages((prevMessages) => [...prevMessages, data.message]);
    };

    ws.onclose = () => {
      console.log('WebSocket connection closed.');
    };

    ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    return () => {
      ws.close();
    };
  }, [appId, cid]);

  const sendMessage = () => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify({ message }));
      setMessage(''); // 清空输入框
    } else {
      console.error('WebSocket is not open.');
    }
  };

  return (
    <div>
      <h1>WebSocket Demo</h1>
      <input
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        placeholder="Type a message"
      />
      <button onClick={sendMessage}>Send</button>
      <div>
        <h2>Messages:</h2>
        <ul>
          {messages.map((msg, index) => (
            <li key={index}>{msg}</li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default WebSocketComponent;

7.3 使用组件

src/App.js 中使用这个组件:

javascript 复制代码
import React from 'react';
import WebSocketComponent from './WebSocketComponent';

function App() {
  return (
    <div className="App">
      <WebSocketComponent />
    </div>
  );
}

export default App;

7.4 运行 React 应用

在项目根目录下运行以下命令启动 React 应用:

bash 复制代码
npm start

8. 总结

通过以上步骤,我们实现了一个简单的 WebSocket 前端示例,分别使用了 Vue.js 和 React。用户可以通过输入框发送消息,接收来自 WebSocket 服务器的消息。

8.1 注意事项

  • 确保 WebSocket 服务器正在运行,并且前端应用能够访问到它。
  • 替换 yourAppIdyourClientId 为实际的应用 ID 和客户端 ID。

希望这能帮助你更好地理解如何在前端实现 WebSocket 通信!如有任何问题,请随时询问。

相关推荐
长空任鸟飞_阿康31 分钟前
在 Vue 3.5 中优雅地集成 wangEditor,并定制“AI 工具”下拉菜单(总结/润色/翻译)
前端·vue.js·人工智能
lapiii35835 分钟前
快速学完React计划(第一天)
前端·react.js·前端框架
liangshanbo121535 分钟前
React + TypeScript 企业级编码规范指南
ubuntu·react.js·typescript
NocoBase1 小时前
11 个在 GitHub 上最受欢迎的开源无代码 AI 工具
低代码·ai·开源·github·无代码·ai agent·airtable·内部工具·app builder
JNU freshman2 小时前
vue 技巧与易错
前端·javascript·vue.js
北冥有鱼2 小时前
Vue3 中子组件修改父组件样式之—— global() 样式穿透使用指南
vue.js
我是日安2 小时前
从零到一打造 Vue3 响应式系统 Day 28 - shallowRef、shallowReactive
前端·javascript·vue.js
LRH2 小时前
时间切片 + 双工作循环 + 优先级模型:React 的并发任务管理策略
前端·react.js
歪歪1002 小时前
React Native开发有哪些优势和劣势?
服务器·前端·javascript·react native·react.js·前端框架