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也不愿意给人家点。可悲。

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

相关推荐
zincsweet4 天前
Linux 命名管道(FIFO)详解:原理分析、源码封装与通信流程图解
linux·服务器·c++·流程图
优思学苑6 天前
价值流程图:看到流程,而不只是步骤【精益管理CLMP】
流程图
bug总结7 天前
前端流程图vueflow
前端·流程图
米饭不加菜8 天前
Mermaid 流程图语法参考四
流程图
米饭不加菜10 天前
Mermaid 流程图语法参考三
流程图
米饭不加菜10 天前
Typora 原生流程图语法完全指南(Flowchart.js)
前端·javascript·流程图
米饭不加菜10 天前
Mermaid 流程图语法参考二
数据库·流程图
米饭不加菜11 天前
Mermaid 流程图语法参考一
流程图
Ysn071911 天前
利用豆包和draw.io快速绘制流程图
流程图·draw.io
Daorigin_com12 天前
从“被动领罚”到“主动合规”:强监管时代下,道本科技用数字化为企业筑牢“合规生命线”
大数据·数据仓库·科技·流程图·软件构建·数据库开发·数据库架构