processflow流程图多人协作预热

news/2024/5/19 1:38:55 标签: 流程图

前言

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

Video_2023-09-04_150131

多人协作技术原理剖析

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

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

 

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

                    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。

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

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


http://www.niftyadmin.cn/n/5001030.html

相关文章

leetcode88合并两个有序数组

题目: 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意:最终&…

GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图教程

详情点击链接:GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图教程 前沿 GPT对于每个科研人员已经成为不可或缺的辅助工具,不同的研究领域和项目具有不同的需求。 如在科研编程、绘图领域: 1、编程建议和示例代码: 无论你使用的编程语言是…

C#使用Panel

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System

算法通关村十三关-青铜:数字与数学基础问题

1.数字统计专题 统计特定场景下的符号或数字个数等 1.1符号统计 LeetCode1822 数组元素积的符号 https://leetcode.cn/problems/sign-of-the-product-of-an-array/description/ 思路分析 如果将所有的数都乘起来,再判断正负,工作量大,还…

prometheus通过blackbox-exporter监控web站点证书

1 概述 线上站点普遍是https,因此监控https web站点的证书的过期时间,是一个基础性需求。例如,证书过期会导致tls握手失败,进而导致用户无法正常访问web站点。 blackbox-expoter是一个web服务,它暴露了一个接口&#…

三步搭建个人网站并发布上线【内网穿透】

三步搭建个人网站并发布上线【内网穿透】 文章目录 三步搭建个人网站并发布上线【内网穿透】前言一、在本地电脑上制作一个网站二、使用WordPress建立网站三、通过cpolar建立的数据隧道发布到公网上 前言 在这个个性飞扬的时代,每个人都希望拥有表现自我的平台&…

无涯教程-JavaScript - EOMONTH函数

描述 EOMONTH函数返回该月最后一天的序列号,该序列号是start_date之前或之后的月份数。 语法 EOMONTH (start_date, months)争论 Argument描述Required/OptionalStart_date 代表开始日期的日期。 应该使用DATE函数或其他公式或函数的输出输入日期。 如果将日期作为文本输入…

leetcode1288. 删除被覆盖区间(java)

删除被覆盖区间 题目描述贪心法代码演示 题目描述 难度 - 中等 leetcode1288. 删除被覆盖区间 给你一个区间列表&#xff0c;请你删除列表中被其他区间所覆盖的区间。 只有当 c < a 且 b < d 时&#xff0c;我们才认为区间 [a,b) 被区间 [c,d) 覆盖。 在完成所有删除操作…