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

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

相关推荐
TMS320VC5257H19 小时前
通义灵码生成的流程图是黑色背景怎么办
流程图·通义灵码·mermaid
安的列斯凯奇3 天前
苍穹外卖的分层所用到的技术以及工具+jwt令牌流程图(jwt验证)
流程图
小阮的学习笔记7 天前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
爱分享的淘金达人9 天前
2025年山东省考报名流程图解
java·考研·spring·eclipse·tomcat·流程图
烟雨国度12 天前
Spring MVC 完整生命周期和异常处理流程图
spring·mvc·流程图
冰淇淋噢!13 天前
一般公司流程图详情版
流程图
半块菠萝17 天前
html简易流程图
css·html·流程图
寰梦21 天前
上传Gitee仓库流程图
gitee·流程图
技术路上的苦行僧21 天前
Spring源码解析(35)之Spring全体系源码流程图
java·spring·流程图·1024程序员节
烟雨国度21 天前
阿里云用STS上传oss的完整程序执行流程图 和前端需要哪些参数uniapp
javascript·阿里云·uni-app·流程图·1024程序员节