其实,协同编辑无非就是字面意思,多人同时编辑,并且能够同步看到对方问保存的数据,典型的例子可以参考石墨文档,腾讯文档。
2、技术解决核心技术就是信息的实时通信
以及多人编辑时所产生的冲突
这里我采用websocket来进行实时通信,大家都知道他是一个全双工通信协议,经过时间的考证,还是非常好用的,多数流行语言都有与之响应封装好的软件包
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。详细的概念可以百度自行查找,比比皆是
编辑冲突的问题可以使用合并算法,上锁等技术(这里没有做过多的研究,所以我使用下面的方式,嘻嘻)
编辑冲突问题交给用户,当前用户实时地看到了别人正在编辑,那么当前用户就自觉性地停止编辑。
3、实现思路1. 用户打开图像编辑页面,与后端建立长连接。
2. 后端将当前用户加入当前图像编辑列表。
3. 前端监听用户对于图像内容的修改,每一次修改将整个修改内容发送给后端。
4. 后端接收到信息,不做任何处理,直接将图像信息发送给图像编辑用户列表中其他的所有用户。
5. 前端收到后端的文本信息直接覆盖掉当前图像内容。
废话就不多说了,直接上代码(复制粘贴即可使用呦)
这里是使用java语言编写的,采用的是原生注解方式,还有其他实现方式,在这里不一一介绍了(主要是上百度搜的没几个能用的,不是相关代码不全,就是长篇大论,最后还是不行,我实在是不会用啊!!!)
首先我们要使用websocket,肯定是要在pom里导入依赖包的(maven无法解析的,可以添加一下响应的版本号)
org.springframework.boot
spring-boot-starter-websocket
org.springframework.boot
spring-boot-starter-test
test
创建MyWebSocketConfig文件来注入bean
package com.example.javawebsocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
//@Configuration注解标识的类中声明了1个或者多个@Bean方法,Spring容器可以使用这些方法来注入Bean
@Configuration
public class MyWebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
接下来就是创建一个原生注解的文件
package cn.staitech.system.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import cn.staitech.common.security.utils.SecurityUtils;
import com.alibaba.fastjson.parser.JSONToken;
import io.swagger.models.auth.In;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
@Component
//主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
@ServerEndpoint(value = "/websocket/{imageid}")
//此注解相当于设置访问URL
public class WebSocketServer {
private Session session;
private String userName;
private static CopyOnWriteArraySet webSockets =new CopyOnWriteArraySet<>();
private static Map sessionPool = new HashMap();
private static Map> newwebSockets = new HashMap>();
@OnOpen
public void onOpen(Session session,@PathParam(value = "imageid") Integer imageid) throws IOException {
Long userid = SecurityUtils.getUserId();
this.session = session;
webSockets.add(this);
// 如果id不存在,创建一个新的用户存储池,格式为 图像id:[用户1,用户2]
if(! newwebSockets.containsKey(imageid)){
CopyOnWriteArraySet webSocketslist =new CopyOnWriteArraySet<>();
newwebSockets.put(imageid,webSocketslist);
newwebSockets.get(imageid).add(this);
}else{
newwebSockets.get(imageid).add(this);
}
sessionPool.put(userid, session);
Session res = sessionPool.get(userid);
System.out.println(imageid+"【websocket消息】有新的连接,总数为:"+newwebSockets.get(imageid).size());
}
@OnClose
public void onClose(@PathParam(value = "imageid") Integer imageid) {
webSockets.remove(this);
newwebSockets.get(imageid).remove(this);
System.out.println(imageid + "【websocket消息】连接断开,总数为:"+newwebSockets.get(imageid).size());
}
@OnError
public void onError(Session session, Throwable error) {
System.out.println("[连接ID:{}] 错误原因:{}" + this.session + error.getMessage());
}
@OnMessage
public String onMessage(String message) {
System.out.println("【websocket消息】收到客户端消息:"+message);
return message;
}
// 此为广播消息
public void sendAllMessage(String message,Integer imageid) {
for(WebSocketServer webSocket : newwebSockets.get(imageid)) {
System.out.println("【websocket消息】广播消息:"+message);
try {
webSocket.session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息
public void sendOneMessage(String userName, String message) {
System.out.println("【websocket消息】单点消息:"+message);
System.out.println(sessionPool);
Session session = sessionPool.get(userName);
if (session != null) {
try {
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
我们我两个接口来调用一下上面两个接口
package com.example.javawebsocket;
import com.example.javawebsocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/annotation")
public class Websocket {
@Autowired
private WebSocketServer webSocketServer;
// @GetMapping("/sendAllWebSocket")
// public String test() {
// String text="你们好!这是websocket群体发送!";
// webSocketServer.sendAllMessage(text);
// return text;
// }
@GetMapping("/sendOneWebSocket/{userName}")
public String sendOneWebSocket(@PathVariable("userName") String userName) {
String text=userName+" 你好! 这是websocket单人发送!";
webSocketServer.sendOneMessage(userName,text);
return text;
}
@CrossOrigin
@RequestMapping(value = "/newimage",method = RequestMethod.POST)
public String newimage(@RequestBody socketvo req){
webSocketServer.sendAllMessage(req.getImagename(), req.getImageid());
System.out.println(req.getImagename());
return "ok";
}
}
基本上大致就是酱紫了,可以根据不同需求来自行更改



