实现页面实时在线文本协同编辑,且显示当前同时编辑文本的用户。
Springboot(2.7.0)+Websocket+javascript
4.0.0 org.springframework.boot spring-boot-starter-parent 2.7.0 com.example demo 0.0.1-SNAPSHOT war demo Demo project for Spring Boot 8 UTF-8 1.7.30 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-websocket org.springframework.boot spring-boot-starter-tomcat provided org.slf4j slf4j-api ${slf4j.version} org.slf4j slf4j-log4j12 ${slf4j.version} org.apache.logging.log4j log4j-to-slf4j 2.14.0 com.alibaba fastjson 1.2.69 org.apache.maven.plugins maven-compiler-plugin 3.1 ${java.version} ${java.version} ${java.encoding} org.apache.maven.plugins maven-surefire-plugin 2.6 org.apache.maven.plugins maven-release-plugin -Prelease org.apache.maven.plugins maven-source-plugin 2.1 true compile jar
编写WebSocketConfig和WebSocketHandler配置类,实现对WebSocket的配置。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @author * @date 2023年01月31日 14:21*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {// @Override
// public void configureMessageBroker(MessageBrokerRegistry registry) {
// registry.enableSimpleBroker("/topic");
// registry.setApplicationDestinationPrefixes("/app");
// }@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/doc-collaboration").withSockJS();}@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** handler * @date2023年01月31日 14:22*/
@Component
public class WebSocketHandler extends TextWebSocketHandler {private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketHandler.class);private static final List sessions = new ArrayList<>();@Overridepublic void handleTextMessage(WebSocketSession session, TextMessage message) {LOGGER.info("Received message: {}", message.getPayload());for (WebSocketSession webSocketSession : sessions) {try {webSocketSession.sendMessage(message);} catch (IOException e) {LOGGER.error("Error: {}", e.getMessage());}}}@Overridepublic void afterConnectionEstablished(WebSocketSession session) {sessions.add(session);}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) {sessions.remove(session);}
}
定义 WebSocket 端点以处理来自客户端的传入消息。
/*** @author * @date 2023年01月31日 11:19*/import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;@ServerEndpoint("/doc-collaboration")
@Component
@Slf4j
public class DocWebSocketServer {private static Set sessions = new HashSet<>();private static Set editingUsers = new HashSet<>();private static String content = "";@OnOpenpublic void onOpen(Session session) {sessions.add(session);}@OnClosepublic void onClose(Session session) {sessions.remove(session);String username = (String) session.getUserProperties().get("username");if (username != null) {editingUsers.remove(username);broadcastEditingUsers();}}@OnMessagepublic void onMessage(String message, Session session) {Gson gson = new Gson();Map data = gson.fromJson(message, Map.class);String type = (String) data.get("type");log.info("Message type: {}, message data: {}", type, data);String jsonStr = "";switch (type) {case "connect":String username = (String) data.get("username");session.getUserProperties().put("username", username);jsonStr = JSON.toJSONString(new HashMap() {{put("type", "update");put("content", content);}});broadcast(jsonStr);break;case "update":content = (String) data.get("content");jsonStr = JSON.toJSONString(new HashMap() {{put("type", "update");put("content", content);}});broadcast(jsonStr);break;case "start-editing":username = (String) session.getUserProperties().get("username");editingUsers.add(username);broadcastEditingUsers();break;case "stop-editing":username = (String) session.getUserProperties().get("username");editingUsers.remove(username);broadcastEditingUsers();break;case "getUser":broadcastEditingUsers();break;}}/*** 广播当前文本信息* @param message*/private void broadcast(String message) {log.info("message {}", message);for (Session session : sessions) {try {session.getBasicRemote().sendText(message);} catch (IOException e) {e.printStackTrace();}}}/*** 广播当前正在编辑文本的用户*/private void broadcastEditingUsers() {broadcast(JSON.toJSONString(new HashMap() {{put("type", "editing");put("editingUsers", new ArrayList<>(editingUsers));}}));}
}
创建一个 JavaScript 客户端,它与端点建立 WebSocket 连接并将更新发送到服务器。展示当前用户以及同时编辑文本的人员名称。
好久没写前端了,写起来有点费劲!😂
当前用户:
同时编辑的用户: