processflow流程图多人协作预热

前言

在线上办公如火如荼的今天,多人协作功能是每个应用绕不开的门槛。processflow在线流程图(前身基于drawio二次开发)沉寂两年之久,经过长时间设计开发,调整,最终完成了多人协作的核心模块设计。废话不多说,上操作视频展示下效果:

Video_2023-09-04_150131

多人协作技术原理剖析

秉着都2023年了,谁还重复造轮子的理念,这里现学现用,利用了一些商业成熟的技术和软件来实现自己的目标。主要用到了两点技术:

  • 利用websocket的push&subscribe实现多人之间的行为共享(鼠标点击;移动;图标内容变更)
  • 利用onedrive实现历史快照文件的存储

client生产事件主要分为以下几种:

javascript 复制代码
                    switch (data.action)
					{
						//传递鼠标移动和点击事件
						case 'message':
							processMsg(data.msg, data.from);
						break;
						case 'clientsList':
							clientsList(data.msg);
						break;
						case 'signal':
							signal(data.msg);
						break;
						case 'newClient':
							newClient(data.msg);
						break;
						case 'clientLeft':
							clientLeft(data.msg);
						break;
						case 'sendSignalFailed':
							sendSignalFailed(data.msg);
						break;
					}

server端主要作用就是接收client端的各个事件,然后对事件进行处理,然后广播出去;

比如:newClient新客户端加入,server端会将clientId和session记录到缓存中,然后将新的clientId广播出去,所有的客户端接收到有新协作者加入后,会进行相关准备操作。

clientLeft:当有客户端下线,也会将session剔除,然后广播出去,所有客户端会将下线的client的光标移除。

push队列主要对各个客户端的事件进行排序。幂等操作,然后发布。

存储端:

主要用onedrive进行存储,历史版本管理,冲突解决。这里插一句,不要问为什么用onedrive,因为drawio集成了onedrive,这里不想重写一套新的文件操作api,直接用现成的了。鉴于国内对onedrive网络支持的不太友好,这也是为什么迟迟不上线的原因之一,后续考虑将接口统一走后端代理,由后端统一访问onedrive。

java 复制代码
private Response contactOAuthServer(String authSrvUrl, String code, String refreshToken, String secret,
                                        String client, String redirectUri, boolean directResp, int retryCount) {
        HttpURLConnection con = null;
        Response response = new Response();

        try {
            URL obj = new URL(authSrvUrl);
            con = (HttpURLConnection) obj.openConnection();

            con.setRequestMethod("POST");

            boolean jsonResponse = false;
            StringBuilder urlParameters = new StringBuilder();

            if (postType == X_WWW_FORM_URLENCODED) {
                con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

                if (withAcceptJsonHeader) {
                    con.setRequestProperty("Accept", "application/json");
                }

                urlParameters.append("client_id=");
                urlParameters.append(Utils.encodeURIComponent(client, "UTF-8"));
                urlParameters.append("&client_secret=");
                urlParameters.append(Utils.encodeURIComponent(secret, "UTF-8"));

                if (code != null) {
                    if (withRedirectUrl) {
                        urlParameters.append("&redirect_uri=");
                        urlParameters.append(Utils.encodeURIComponent(redirectUri, "UTF-8"));
                    }

                    urlParameters.append("&code=");
                    urlParameters.append(Utils.encodeURIComponent(code, "UTF-8"));
                    urlParameters.append("&grant_type=authorization_code");
                } else {
                    if (withRedirectUrlInRefresh) {
                        urlParameters.append("&redirect_uri=");
                        urlParameters.append(Utils.encodeURIComponent(redirectUri, "UTF-8"));
                    }

                    urlParameters.append("&refresh_token=");
                    urlParameters.append(Utils.encodeURIComponent(refreshToken, "UTF-8"));
                    urlParameters.append("&grant_type=refresh_token");
                    jsonResponse = true;
                }
            } else if (postType == JSON) {
                con.setRequestProperty("Content-Type", "application/json");

                JsonObject urlParamsObj = new JsonObject();

                urlParamsObj.addProperty("client_id", client);
                urlParamsObj.addProperty("redirect_uri", redirectUri);
                urlParamsObj.addProperty("client_secret", secret);

                if (code != null) {
                    urlParamsObj.addProperty("code", code);
                    urlParamsObj.addProperty("grant_type", "authorization_code");
                } else {
                    urlParamsObj.addProperty("refresh_token", refreshToken);
                    urlParamsObj.addProperty("grant_type", "refresh_token");
                    jsonResponse = true;
                }

                urlParameters.append(urlParamsObj.toString());
            }

            // Send post request
            con.setDoOutput(true);
            DataOutputStream wr = new DataOutputStream(con.getOutputStream());
            wr.writeBytes(urlParameters.toString());
            wr.flush();
            wr.close();

            BufferedReader in = new BufferedReader(
                    new InputStreamReader(con.getInputStream()));
            String inputLine;
            StringBuffer authRes = new StringBuffer();

            while ((inputLine = in.readLine()) != null) {
                authRes.append(inputLine);
            }
            in.close();

            response.status = con.getResponseCode();

            Gson gson = new Gson();

            JsonObject json = gson.fromJson(authRes.toString(), JsonElement.class).getAsJsonObject();
            String accessToken = getAccessToken(json);
            int expiresIn = getExpiresIn(json);
            response.refreshToken = getRefreshToken(json);
            response.accessToken = accessToken;

            JsonObject respObj = new JsonObject();
            respObj.addProperty("access_token", accessToken);

            if (expiresIn > -1) {
                respObj.addProperty("expires_in", expiresIn);
            }

            if (directResp) {
                response.content = respObj.toString();
            } else {
                // Writes JavaScript code
                response.content = processAuthResponse(respObj.toString(), jsonResponse);
            }
        } catch (IOException e) {
            StringBuilder details = new StringBuilder("");

            if (con != null) {
                try {
                    BufferedReader in = new BufferedReader(
                            new InputStreamReader(con.getErrorStream()));

                    String inputLine;

                    while ((inputLine = in.readLine()) != null) {
                        System.err.println(inputLine);
                        details.append(inputLine);
                        details.append("\n");
                    }
                    in.close();
                } catch (Exception e2) {
                    // Ignore
                }
            }

            if (e.getMessage() != null && e.getMessage().contains("401")) {
                response.status = HttpServletResponse.SC_UNAUTHORIZED;
            } else if (retryCount > 0 && e.getMessage() != null && e.getMessage().contains("Connection timed out")) {
                return contactOAuthServer(authSrvUrl, code, refreshToken, secret,
                        client, redirectUri, directResp, --retryCount);
            } else {
                response.status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                e.printStackTrace();
                log.error("AUTH-SERVLET: [" + authSrvUrl + "] ERROR: " + e.getMessage() + " -> " + details.toString());
            }

            if (DEBUG) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                e.printStackTrace(pw);
                pw.println(details.toString());
                pw.flush();
                response.content = sw.toString();
            }
        }

        return response;
    }

感悟心得

开源不易,请大家多给drawio点点star,没有他,就没有中国繁荣的流程图商业化盛景...拿着人家的代码来挣钱,一个start也不愿意给人家点。可悲。

虽然作者挺操蛋的,多人协作的代码没有开源(我只能靠研究官方网站的协作功能和数据格式一点点猜测用到了哪些逻辑),白板转流程图的代码也没开源。不过我觉得他做的挺对的,农夫与蛇的故事见的太多了。所以我决定和他一样,让白嫖的人踩坑去吧,这里只做原理讲解,不提供源码内容。

相关推荐
岳哥i14 小时前
Vue BPMN Modeler流程图
流程图
dj Yang14 小时前
RTK部分模糊度固定测量流程图
流程图
huaqianzkh14 小时前
数据流图和流程图的区别
架构·流程图
ProcessOn官方账号3 天前
如何绘制网络拓扑图?附详细分类解说和用户案例!
网络·职场和发展·流程图·拓扑学
CoderCodingNo5 天前
【GESP】C++二级考试大纲知识点梳理, (4)流程图
开发语言·c++·流程图
猫咪-95276 天前
水仙花数(流程图,NS流程图)
流程图
万维——组态8 天前
web组态可视化编辑器
前端·物联网·低代码·编辑器·流程图·组态
BY-组态8 天前
web组态可视化编辑器
前端·物联网·开源·编辑器·流程图·web组态
正在走向自律8 天前
解锁 draw.io 流程图制作工具Docker私有化部署(2/2)
流程图·draw.io
看山还是山,看水还是。13 天前
软件工程 设计的复杂性
笔记·流程图·软件工程·团队开发·代码规范·内容运营·代码覆盖率