当前位置: 首页 > news >正文

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

在现代应用中,实时协作已经成为了非常重要的功能,尤其是在文档编辑、聊天系统和在线编程等场景中。通过实时共享文档,多个用户可以同时对同一份文档进行编辑,并能看到其他人的编辑内容。这种功能广泛应用于 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的容器中@Beanpublic 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 {// 每个连接的 Sessionprivate Session session;private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();private static int onlineCount = 0;// 存储每个连接的 sidprivate String sid = "";// 存储每个连接的内容private static String content = "";@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {this.session = session;// 使用 URL 中的 sid 参数为当前连接设置 sidthis.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");}}@OnClosepublic void onClose() {webSocketSet.remove(this); // 从 WebSocket 客户端集合中移除当前连接subOnlineCount(); // 减少在线连接数System.out.println("释放的 sid 为:" + sid);System.out.println("有一连接关闭!当前在线人数为 " + getOnlineCount());}@OnMessagepublic 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();}}}@OnErrorpublic 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 获取// 判断浏览器是否支持 WebSocketif ('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); // 发送消息到 WebSocketpreviousMessage = 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 仍然是一个非常强大的工具,适用于许多实时协作的场景。

相关文章:

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

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

Ant Design Pro实战--day01

下载nvm https://nvm.uihtm.com/nvm-1.1.12-setup.zip 下载node.js 16.16.0 //非此版本会报错 nvm install 16.16.0 安装Ant Design pro //安装脚手架 npm i ant-design/pro-cli -g //下载项目 pro create myapp //选择版本 simple 安装依赖 npm install 启动umi yarn add u…...

pcl点云库离线版本构建

某天在摸鱼的小邓接到任务需要进行点云数据的去噪&#xff0c;在万能的github中发现如下pcl库非常好使&#xff0c;so有了此&#xff0c; 1.下载vs2017连接如下&#xff1a; ed2k://|file|mu_visual_studio_community_2017_version_15.1_x86_x64_10254689.exe|1037144|12F5C1…...

字节高频算法面试题:小于 n 的最大数

问题描述&#xff08;感觉n的位数需要大于等于2&#xff0c;因为n的位数1的话会有点问题&#xff0c;“且无重复”是指nums中存在重复&#xff0c;但是最后返回的小于n最大数是可以重复使用nums中的元素的&#xff09;&#xff1a; 思路&#xff1a; 先对nums倒序排序 暴力回…...

ElasticSearch常见面试题汇总

一、ElasticSearch基础&#xff1a; 1、什么是Elasticsearch&#xff1a; Elasticsearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎&#xff0c;每个字段都被索引并可被搜索&#xff0c;可以快速存储、搜索、分析海量的数据。 全文检索是指对每一个词建立一个索引…...

Spring Boot如何实现防盗链

一、什么是盗链 盗链是个什么操作&#xff0c;看一下百度给出的解释&#xff1a;盗链是指服务提供商自己不提供服务的内容&#xff0c;通过技术手段绕过其它有利益的最终用户界面&#xff08;如广告&#xff09;&#xff0c;直接在自己的网站上向最终用户提供其它服务提供商的…...

工作中常用springboot启动后执行的方法

前言&#xff1a; 工作中难免会遇到一些&#xff0c;程序启动之后需要提前执行的需求。 例如&#xff1a; 初始化缓存&#xff1a;在启动时加载必要的缓存数据。定时任务创建或启动&#xff1a;程序启动后创建或启动定时任务。程序启动完成通知&#xff1a;程序启动完成后通…...

力扣-图论-3【算法学习day.53】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…...

Linux上的C语言编程实践

说明&#xff1a; 这是个人对该在Linux平台上的C语言学习网站笨办法学C上的每一个练习章节附加题的解析和回答 ex1: 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后运行它看看发生了什么。 vim ex1.c打开 ex1.c 文件。假如我们删除 return 0…...

芝法酱学习笔记(1.3)——SpringBoot+mybatis plus+atomikos实现多数据源事务

一、前言 1.1 业务需求 之前我们在讲解注册和登录的时候&#xff0c;有一个重要的技术点忽略了过去。那就是多数据源的事务问题。 按照我们的业务需求&#xff0c;monitor服务可能涉及同时对监控中心数据库和企业中心数据库进行操作&#xff0c;而我们希望这样的操作在一个事…...

【计算机网络】实验12:网际控制报文协议ICMP的应用

实验12 网际控制报文协议ICMP的应用 一、实验目的 验证ping命令和tracert命令的工作原理。 二、实验环境 Cisco Packet Tracer模拟器 三、实验过程 1.构建网络拓扑并进行信息标注&#xff0c;将所需要配置的IP地址写在对应的主机或者路由器旁边&#xff0c;如图1所示。 图…...

收缩 tempdb 数据库

1、 本文内容 注解使用 ALTER DATABASE 命令使用 DBCC SHRINKDATABASE 命令使用 DBCC SHRINKFILE 命令运行收缩操作时出现错误 8909 适用于&#xff1a; SQL ServerAzure SQL 托管实例 本文讨论可用于收缩 SQL Server 中 tempdb 数据库的各种方法。 可以使用下列任一方法来…...

kubesphere搭建 postgres15

创建configMap POSTGRES_PASSWORD数据库密码 PGDATA数据目录 创建【有状态副本集】工作负载 1.创建基本信息 2.容器组设置 配置环境变量 3.存储设置 完成之后点击下一步 配置服务 创建服务 配置基本信息 配置服务信息 外部访问选择nodePort&#xff0c;然后点击…...

解决npm问题用到的资源,错误原因和方法

资源&#xff1a; 1.node版本管理工具nvm: 下载地址&#xff1a;https://nvm.uihtm.com/nvm-1.1.12-setup.zip 使用方法&#xff1a;https://nvm.uihtm.com/ 2.node各版本&#xff1a; https://nodejs.org/en/about/previous-releases 3.nodejs: 下载地址&#xff1a;https://…...

【uni-app 微信小程序】新版本发布提示用户进行更新

知识准备 uni.getUpdateManager文档介绍 不支持APP与H5&#xff0c;所以在使用的时候要做好平台类型的判断&#xff0c;如何判断&#xff0c;参考条件编译处理多端差异 代码参考 export const updateApp () > {const updateManager uni.getUpdateManager()updateManag…...

Redis性能优化18招

Redis性能优化的18招 目录 前言选择合适的数据结构避免使用过大的key和value[使用Redis Pipeline](#使用Redis Pipeline)控制连接数量合理使用过期策略使用Redis集群充分利用内存优化使用Lua脚本监控与调优避免热点key使用压缩使用Geo位置功能控制数据的持久化尽量减少事务使…...

ElasticSearch 与向量数据库的结合实践:突破亿级大表查询瓶颈20241204

&#x1f4a1; ElasticSearch 与向量数据库的结合实践&#xff1a;突破亿级大表查询瓶颈 &#x1f4da; 引言 随着业务规模的不断扩大&#xff0c;传统关系型数据库在处理 亿级大表 时&#xff0c;性能瓶颈愈加凸显。关键词检索、模糊查询、多条件筛选等需求逐步升级&#xff…...

C#实现一个HttpClient集成通义千问-流式输出内容提取

返回对象处理 返回对象分析 根据流式返回的数据处理 内容对象 {"choices": [{"delta": { "content": "", "role": "assistant" },"index": 0,"logprobs": null,"finish_reason"…...

微信小程序后台搭建—node+mysql

想必大家都有一个困扰&#xff0c;想要用微信小程序作为前端&#xff0c;但是后端不知道如何用node连接微信小程序&#xff0c;我最近也一直困扰许久&#xff0c;所以我就想用node写后端接口在连接微信小程序&#xff0c;记录一下学习笔记 前言 前端:微信小程序 后端:nodeexp…...

断点续传+测试方法完整示例

因为看不懂网上的断点续传案例&#xff0c;而且又不能直接复制使用&#xff0c;干脆自己想想写了一个。 上传入参类&#xff1a; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProp…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...