从零开始的使用SpringBoot和WebSocket打造实时共享文档应用

news2025/2/28 11:39:47

在现代应用中,实时协作已经成为了非常重要的功能,尤其是在文档编辑、聊天系统和在线编程等场景中。通过实时共享文档,多个用户可以同时对同一份文档进行编辑,并能看到其他人的编辑内容。这种功能广泛应用于 Google Docs、Notion 等产品中。

在本文中,我们将实现一个简单的共享文本框,使用 WebSocket 技术来实现多人实时编辑同一份文本。通过 WebSocket 协议,客户端和服务器可以保持一个持续的连接,使得文档的内容能够实时同步到所有参与者。
(引流:https://juejin.cn/post/7445187277558628387)
效果图如下:

tutieshi_640x272_3s

1. 什么是 WebSocket?

WebSocket 是一种网络协议,它提供了一个全双工的通信通道,允许客户端和服务器之间进行实时、双向的数据传输。与传统的 HTTP 协议不同,WebSocket 连接在建立后会保持打开状态,不需要频繁的建立连接,从而大大提高了数据交换的效率。

WebSocket 协议通常用于实时聊天、在线游戏、金融行情推送等场景。在本文中,我们将利用 WebSocket 来实现一个共享文本框。

2. 项目需求

我们的目标是实现一个简单的共享文本框功能,要求如下:

  • 多个用户可以同时连接到同一个文档并进行编辑。
  • 每次用户编辑文本时,修改内容会即时同步到其他用户的浏览器。
  • 实现基本的文本框功能,包括输入和显示。

3. 技术栈

  • 前端:HTML、CSS、JavaScript(使用 WebSocket API)
  • 后端:SpringBoot
  • 通信协议:WebSocket

4. 实现步骤

4.1 搭建 WebSocket 服务端

首先,我们需要创建一个 WebSocket 服务器来处理客户端连接。具体步骤如下:

  1. 是maven依赖中引入websocket的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 对WebSocket进行一些配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @ Description: 开启WebSocket支持
 * 用于在Spring框架的应用中配置和启用WebSocket功能。
 * 通过相关注解和方法的定义,使得应用能够正确地处理WebSocket连接和通信。
 */
@Configuration
public class WebSocketConfig {
    //Bean生命周期的初始化
    // 用于将方法返回的ServerEndpointExporter对象作为一个Bean注册到Spring的容器中
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        //创建并返回一个ServerEndpointExporter对象。
        // ServerEndpointExporter主要作用是扫描带有@ServerEndpoint注解的WebSocket端点类,并将它们注册到Servlet容器中,
        // 从而使得应用能够正确地处理WebSocket连接请求,实现WebSocket的通信功能。
        return new ServerEndpointExporter();
    }
}
  1. WebSocket服务器实现
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.stereotype.Service;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@Service
@ServerEndpoint("/api/websocket/sharedText/{sid}")
public class WebSocketServer {

    // 每个连接的 Session
    private Session session;
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    private static int onlineCount = 0;
    // 存储每个连接的 sid
    private String sid = "";
    // 存储每个连接的内容
    private static String content = "";

    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        this.session = session;
        // 使用 URL 中的 sid 参数为当前连接设置 sid
        this.sid = sid;
        webSocketSet.add(this); // 将当前连接添加到 WebSocket 客户端集合中
        addOnlineCount(); // 增加在线连接数
        try {
            sendMessage(this.content); // 向当前客户端发送连接成功消息
            System.out.println("有新窗口开始监听:" + sid + ", 当前在线人数为:" + getOnlineCount());
        } catch (IOException e) {
            System.out.println("websocket IO Exception");
        }
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this); // 从 WebSocket 客户端集合中移除当前连接
        subOnlineCount(); // 减少在线连接数
        System.out.println("释放的 sid 为:" + sid);
        System.out.println("有一连接关闭!当前在线人数为 " + getOnlineCount());
    }

    @OnMessage
    public void onMessage(String message, Session session) throws JsonProcessingException {
        this.content = message;
        // 打印来自某个 sid 的消息内容
        System.out.println("收到来自窗口 " + sid + " 的信息: " + message);

        // 群发消息给所有已连接的客户端
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message); // 向所有连接的客户端广播消息
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    // 向客户端发送消息
    public void sendMessage(String message) throws IOException {
        if (this.session != null && this.session.isOpen()) {
            this.session.getBasicRemote().sendText(message); // 发送消息
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
        return webSocketSet;
    }
}

上述代码中,我们创建了一个 WebSocket 服务器并监听了 8080 端口。当有客户端连接时,服务器会触发 connection 事件,处理来自客户端的消息并将其广播给所有已连接的客户端。

4.2 创建前端页面

接下来,我们需要创建一个前端页面,用户可以在其中输入文本并实时看到其他用户的编辑内容。我们将使用 WebSocket API 与后端建立连接。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>实时共享文本框 - WebSocket 实现</title>
  <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color: #f4f4f9;
      padding: 20px;
      display: flex;
      flex-direction: column;
      align-items: center;
    }

    h2 {
      margin-bottom: 20px;
    }

    #textBox {
      width: 80%;
      max-width: 900px;
      height: 300px;
      padding: 10px;
      font-size: 16px;
      border: 1px solid #ddd;
      border-radius: 5px;
      background-color: #fff;
      resize: none;
      box-sizing: border-box;
    }

    #message {
      margin-top: 20px;
      padding: 10px;
      width: 80%;
      max-width: 900px;
      border: 1px solid #ddd;
      border-radius: 5px;
      background-color: #fafafa;
      font-size: 14px;
      color: #555;
    }

    #message span {
      font-weight: bold;
    }

    .status {
      margin: 10px 0;
      color: #333;
    }

    .error {
      color: red;
    }

    .success {
      color: green;
    }

    .info {
      color: #555;
    }
  </style>
</head>

<body>
<h2>实时共享文本框</h2>
<textarea id="textBox" rows="20" cols="80" placeholder="开始编辑文本..."></textarea><br />

<script type="text/javascript">
  let websocket = null;
  const sid = "100"; // 这里可以更改为动态获取的 sid,例如通过 URL 获取

  // 判断浏览器是否支持 WebSocket
  if ('WebSocket' in window) {
    websocket = new WebSocket(`ws://192.168.113.45:8080/api/websocket/sharedText/${sid}`);
  } else {
    alert('当前浏览器不支持 WebSocket');
  }

  // 连接错误时处理
  websocket.onerror = () => {
    updateStatus('WebSocket连接发生错误', 'error');
  };

  // 连接成功时处理
  websocket.onopen = () => {
    updateStatus('WebSocket连接成功', 'success');
  };

  // 接收消息时处理
  websocket.onmessage = (event) => {
    console.log(event);
    updateTextBox(event.data);
  };

  // 连接关闭时处理
  websocket.onclose = () => {
    updateStatus('WebSocket连接关闭', 'info');
  };

  // 窗口关闭时确保关闭 WebSocket 连接
  window.onbeforeunload = () => {
    closeWebSocket();
  };

  // 更新状态消息
  function updateStatus(message, type) {
    const statusDiv = document.getElementById('message');
    statusDiv.innerHTML = `<span class="${type}">${message}</span>`;
  }

  // 关闭 WebSocket 连接
  function closeWebSocket() {
    if (websocket) {
      websocket.close();
    }
  }

  // 监听文本框输入事件
  document.getElementById('textBox').addEventListener('input', function () {
    const message = this.value;
    if (message !== previousMessage) {
      websocket.send(message); // 发送消息到 WebSocket
      previousMessage = message; // 更新当前文本
    }
  });

  let previousMessage = ''; // 用于记录文本框内容,避免重复发送

  // 更新文本框内容
  function updateTextBox(content) {
    // 防止不停地将同一内容发送给其他用户
    if (document.getElementById('textBox').value !== content) {
      document.getElementById('textBox').value = content;
    }
  }
</script>
</body>

</html>

在前端页面中,我们创建了一个简单的文本框 (<textarea>) 供用户输入文本。当用户在文本框中输入内容时,input 事件会触发,内容会通过 WebSocket 发送给服务器。服务器收到消息后,会将其广播给所有其他连接的客户端,客户端接收到广播消息后会更新自己的文本框内容。

4.3 测试与运行

直接启动SpringBoot服务即可,同时打开web网页。

最终效果如下:

在一个网页端编辑,另一个网页端能及时收到变更。

5. 小结

通过这篇博客,我们实现了一个简单的实时共享文本框,利用 WebSocket 技术来实现多人实时编辑同一份文本。每当一个用户编辑文本时,服务器会将该编辑广播给其他在线用户,从而实现实时同步。这是一个基本的多人协作编辑功能,适用于在线文档编辑、聊天系统等场景。

在实际应用中,我们可以根据需求扩展更多功能,例如用户身份管理、权限控制、文本格式化、撤销/重做功能等。通过 WebSocket,我们不仅可以实现实时通信,还能为用户提供流畅的协作体验。在开发中,WebSocket 仍然是一个非常强大的工具,适用于许多实时协作的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2257848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP保存base64编码图片,图片有一部分是灰色块儿,原因和解决办法

文章目录 场景原因解决方案完整的代码前端代码php代码 场景 我有个需求&#xff0c;移动端h5上传多张的图片。用input file可以上传多张&#xff0c;但是现在照片体积越来越大&#xff0c;同时上传多张会因为体积过大&#xff0c;导致上传失败。如果是小程序会好很多&#xff…

vue图片之放大、缩小、1:1、刷新、左切换、全屏、右切换、左旋咋、右旋转、x轴翻转、y轴翻转

先上效果&#xff0c;代码在下面 <template><!-- 图片列表 --><div class"image-list"><img:src"imageSrc"v-for"(imageSrc, index) in images":key"index"click"openImage(index)"error"handleI…

Oracle EBS FA 如何打开关闭的资产会计期间?

用户“运行折旧”,误勾选为“关闭期间”,还有一部分资产还需要操作报废和调整,希望后台打开关闭的资产会计期 系统环境 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.9 解决方案 由官方提供SQL脚本代码如下: /*rollback120.sql - for Release 12.X only(based on r…

Hash、HASHTABLE底层原理【Redis对象篇】

&#x1f3c6; 作者简介&#xff1a;席万里 ⚡ 个人网站&#xff1a;https://dahua.bloggo.chat/ ✍️ 一名后端开发小趴菜&#xff0c;同时略懂Vue与React前端技术&#xff0c;也了解一点微信小程序开发。 &#x1f37b; 对计算机充满兴趣&#xff0c;愿意并且希望学习更多的技…

CentOS 二进制安装部署MongoDB 4.0

一、安装MongoDB 1. 下载 MongoDB 二进制文件 前往 MongoDB 官方下载页面(https://www.mongodb.com/try/download/community) 选择对应版本的 tar 包。 wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.28.tgz 2. 解压并移动至目标目录 解压文件&#xff…

Redis篇-5--原理篇4--Lua脚本

1、概述 Redis 支持使用 Lua 脚本来执行复杂的操作&#xff0c;这为 Redis 提供了更强的灵活性和性能优化能力。通过 Lua 脚本&#xff0c;你可以在服务器端执行一系列命令&#xff0c;而不需要多次往返客户端与服务器之间&#xff0c;从而减少了网络延迟并提高了效率。此外&a…

新手上路,学Go还是Python

对于新手来说&#xff0c;Go和Python都是很好的编程语言&#xff0c;它们各有特点&#xff0c;以下是详细的对比来帮助你决定先学哪一个&#xff1a; 一、语法和学习难度 Python 语法简洁易懂&#xff1a;Python以其简洁、优雅的语法而闻名&#xff0c;代码的可读性很高。例如…

OceanBase 社区版 4.0 离线方式升级bp1至bp2 指南(含避坑总结)

注&#xff1a;目前社区版对 4.0 升级 bp1至 bp2也未有完善的文档&#xff0c;本次升级中也是遇到不少坑&#xff0c;写本文也希望对OB感兴趣的可以尝试少些遇坑。 也希望对升级有更好方式建议方式的朋友一起切磋交流&#xff0c;以便再进一步完善升级方案。 第一次做OB的升级&…

python学opencv|读取图像(六)读取图像像素RGB值

【1】引言 前序已经掌握了如何获取灰度图像的像素&#xff0c;文章链接为&#xff1a; python学opencv|读取图像&#xff08;五&#xff09;读取灰度图像像素-CSDN博客 实际上像素就像一个坐标轴&#xff0c;约束了图像的大小。 但实际上我们在学习过程中&#xff0c;对于同…

Linux kill、killall、pkill 命令区别

注&#xff1a;本文为 “Linux kill、killall、pkill” 相关几篇文章合辑。 未整理去重。 kill、killall、pkill、kill -9 区别 区别 进程 ID 唯一&#xff0c;所以 kill 一次只能杀死 1 个进程&#xff0c;其他相同名称的进程仍然存在&#xff0c;而 pkill 和 killall&#…

1139: Coin-row problem

解法&#xff1a; #include <bits/stdc.h> using namespace std; const int N 1e53; int dp[N]; int main() {int n;cin>>n;for (int i1;i<n;i) cin>>dp[i];for (int i2;i<n;i) {dp[i]max(dp[i-1],dp[i-2]dp[i]);}cout<<dp[n]<<endl;retur…

Ubuntu压缩打包解压

ubuntu压缩打包 上图&#xff0c;压缩当前目录svn 为svn.tar.gaz&#xff0c;解压后再当前解压目录生成svn文件 在Ubuntu中&#xff0c;你可以使用tar命令来创建一个压缩包&#xff0c;或者使用zip命令来创建一个.zip压缩文件。以下是两种常见的压缩方法&#xff1a; 下图&am…

Excel 合并工具 将文件复制到目标工作表中与操作日志记录

指定文件夹中读取符合条件的 Excel 文件&#xff0c;将其中的数据按照一定规则复制到目标工作表中&#xff0c;并进行相关的日志记录和工作簿保存操作。 先看下 excel 的结构 合并的结果 log 记录 vba 代码 Sub DeltaCheck()作者和创建时间的注释 定义工作表变量Dim ws As Wor…

Github----提交人不是自己

账号用户名都设置对的,但是提交人不是自己 解决 发现是用户名和账号都夹了"号导致 git config --global user.name "Your Name" git config --global user.email "your.emailexample.com"不用引号 git config --global user.name Your Name git …

ZZCMS2023存在跨站脚本漏洞(CNVD-2024-44822、CVE-2024-44818)

ZZCMS是一款用于搭建招商网站的CMS系统&#xff0c;由PHP语言开发&#xff0c;可快速搭建&#xff1a;医药招商、保健品招商、化妆品招商、农资招商、孕婴童招商、酒类副食类等招商网站。 国家信息安全漏洞共享平台于2024-11-14公布其存在跨站脚本漏洞。 漏洞编号&#xff1a…

[免费]SpringBoot+Vue企业OA自动化办公管理系统【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue企业OA自动化办公管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue企业OA自动化办公管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 随着信息技术在管理上越来越深入…

【MySQL】表的基本查询(下)

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

目前Java后端就业前景到底怎么样?

很多人都说今年对于IT行业根本没有所谓的“金三银四”“金九银十”。在各大招聘网站或者软件上不管是大厂还是中小公司大多都是挂个招聘需求&#xff0c;实际并不招人&#xff1b;在行业内的程序员基本都已经感受到了任老前段时间口中所谓的“寒气”。 虽然事实确实是如此&…

机器学习--张量

机器学习–张量 机器学习的数据结构–张量 张量是机器学习程序中的数字容器&#xff0c;本质上就是各种不同维度的数组&#xff0c;如下图所示。 张量的维度称为轴&#xff08;axis&#xff09;&#xff0c;轴的个数称为阶&#xff08;rank&#xff09; 标量–0D张量 impor…

3D 视觉定位技术:汽车零部件制造的智能变革引擎

在汽车零部件制造领域&#xff0c;传统工艺正面临着前所未有的挑战。市场对于零部件精度与生产效率近乎苛刻的要求&#xff0c;促使企业寻求突破之道。而 3D 视觉定位技术&#xff0c;为汽车零部件制造开启了精准定位与智能化生产的新纪元。 3D 视觉定位系统的核心技术原理 3…