processflow流程图多人协作预热

news2024/11/18 22:30:47

前言

在线上办公如火如荼的今天,多人协作功能是每个应用绕不开的门槛。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.coloradmin.cn/o/971988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SeaTunnel扩展Transform插件,自定义转换插件

代码结构 在seatunnel-transforms-v2中新建数据包名,新建XXXTransform,XXXTransformConfig,XXXTransformFactory三个类 自定义转换插件功能说明 这是个适配KafkaSource的转换插件,接收到的原文格式为: {"path&…

使用Akka的Actor模拟Spark的Master和Worker工作机制

使用Akka的Actor模拟Spark的Master和Worker工作机制 Spark的Master和Worker协调工作原理 在 Apache Spark 中,Master 和 Worker 之间通过心跳机制进行通信和保持活动状态。下面是 Master 和 Worker 之间心跳机制的工作流程: Worker 启动后&#xff0c…

【Unity】VS Code 没有自动补全 MonoBehaviour 的方法

正常来说,在VS Code 输入类似 OnTriggerEnter2D等方法名时,VS Code会根据已经输入的前缀自动提示相关方法。 在不正常的情况下,根据StackOverFlow上面的回答,依次试过了 安装 .NET SDK安装 .NET Framework Dev PackVS Code安装 …

【ES6】js中的__proto__和prototype

在JavaScript中,__proto__和prototype都是用于实现对象继承的关键概念。 1、proto __proto__是一个非标准的属性,用于设置或获取一个对象的原型。这个属性提供了直接访问对象内部原型对象的途径。对于浏览器中的宿主对象和大多数对象来说,可…

【STM32】学习笔记-PWR(Power Control)电源控制

PWR(Power Control)电源控制 PWR(Power Control)电源控制是一种技术或设备,用于控制电源的开关和输出。它通常用于电源管理和节能,可以通过控制电源的工作状态来延长电子设备的使用寿命,减少能…

SQL注入-盲注 Burp盲注方法

文章目录 判断库名位数Burp 抓取数据包设置payload位置设置payload 1设置payload 2点击开始攻击 判断库名下表名的位数Burp 抓取数据包点击开始攻击 判断库名下第二张表名判断表名下的字段名判断表中具体数据 什么是盲注? 有时目标存在注入,但在页面上没…

Unity中Shader的UV扭曲效果的实现

文章目录 前言一、实现的思路1、在属性面板暴露一个 扭曲贴图的属性2、在片元结构体中,新增一个float2类型的变量,用于独立存储将用于扭曲的纹理的信息3、在顶点着色器中,根据需要使用TRANSFORM_TEX对Tilling 和 Offset 插值;以及…

使用QT操作Excel 表格的常用方法

VBA 简介 Microsoft Office软件通常使用VBA来扩展Windows的应用程序功能,Visual Basic for Applications(VBA)是一种Visual Basic的一种宏语言。 在VBA的参考手册中就可以看到具体函数、属性的用法,Qt操作Excel主要通过 QAxObj…

Redis 缓存预热+缓存雪崩+缓存击穿+缓存穿透

面试题: 缓存预热、雪萌、穿透、击穿分别是什么?你遇到过那几个情况?缓存预热你是怎么做的?如何造免或者减少缓存雪崩?穿透和击穿有什么区别?他两是一个意思还是载然不同?穿适和击穿你有什么解…

【每日一题】1041. 困于环中的机器人

1041. 困于环中的机器人 - 力扣(LeetCode) 在无限的平面上,机器人最初位于 (0, 0) 处,面朝北方。注意: 北方向 是y轴的正方向。南方向 是y轴的负方向。东方向 是x轴的正方向。西方向 是x轴的负方向。 机器人可以接受下列三条指令之…

入门力扣自学笔记277 C++ (题目编号:42)(动态规划)

42. 接雨水 题目: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组…

时序预测 | MATLAB实现CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(风电功率预测)

时序预测 | MATLAB实现CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(风电功率预测) 目录 时序预测 | MATLAB实现CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(风电功率预测)预测效果基本介绍程序设计参考资料 预测效果…

小白学go基础03-了解Go项目的项目结构

我们先来看看第一个Go项目——Go语言自身——的项目结构是什么样的。Go项目的项目结构自1.0版本发布以来一直十分稳定,直到现在Go项目的顶层结构基本没有大的改变。 截至Go项目commit 1e3ffb0c(2019.5.14),Go1.0 项目结构如下&am…

js+vue,前端关于页面滚动让头部菜单淡入淡出实现原理

今天遇到个需求:我这里借用小米商城的详情页做个比喻吧。 刚开始其商品详情页是这样的: 当滚动到一定高度时,是这样的: 可以看到当滚动到轮播图底下的时候,详情页的菜单完全显现出来。 以下上代码: HTML…

uniApp webview 中调用底座蓝牙打印功能异常

背景: 使用uniApp, 安卓底座 webView 方式开发; 调用方式采用H5 向 底座发送消息, 底座判断消息类型, 然后连接打印机进行打印; 内容通过指令集方式传递给打印机; 过程当中发现部分标签可以正常打印, 但又有部分不行,打印机没反应, 也没有报错; 原因分析: 对比标签内容…

医院安全(不良)事件上报系统源码 不良事件报告平台源码 前后端分离,支持二开

医院安全(不良)事件上报系统源码 系统定义: 规范医院安全(不良)事件的主动报告,增强风险防范意识,及时发现医院不良事件和安全隐患,将获取的医院安全信息进行分析反馈,…

“金融级”数字底座:从时代的“源启”,到“源启”的时代

今年初《数字中国建设整体布局规划》正式发布,这代表着数字中国建设迈向了实质的落地阶段,其背后的驱动就是遍及各行各业的数字化转型。 千姿百态、复杂多样的应用场景,可以看作是遍布数字中国的“点”;千行百业、各种类型的行业…

阿里云2核4G服务器5M带宽5年费用价格明细表

阿里云2核4G服务器5M带宽可以选择轻量应用服务器或云服务器ECS,轻量2核4G4M带宽服务器297元一年,2核4G云服务器ECS可以选择计算型c7、c6或通用算力型u1实例等,买5年可以享受3折优惠,阿腾云分享阿里云服务器2核4G5M带宽五年费用表&…

【系统编程】线程池以及API接口简介

(꒪ꇴ꒪ ),Hello我是祐言QAQ我的博客主页:C/C语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍快上🚘,一起学习,让我们成为一个强大的攻城狮&#xff0…

非比较排序——计数排序

本章gitee代码:计数排序 文章目录 🍇0. 前言🍈1. 思路🍉2. 代码实现🍊3. 优势与缺陷🍋4. 其他的非比较排序🫴桶排序🫴基数排序 🍇0. 前言 传统的排序方法通常需要逐个比…