UMind1.0釋出

mozheng發表於2019-02-15

UMind 1.0 釋出

UMind 是一款線上腦圖產品,由 腦圖編輯 和 多人協作 兩部份主要功能所組成。自 2018 年 09 月立項至今經歷大大小小 24 個版本打磨終於迎來 1.0 正式版本。

架構大圖

01.png

從架構大圖中我們可以看到 UMind 依賴於 GGEditor,是一個前端全棧產品。其中 Node 部分主要用於多人協同操作時的資料同步,後面章節中會詳細講解。最上一層的產品端則可通過 UMind 提供的 PASS 服務能力獨立部署腦圖產品。如果你想要馬上嚐鮮多人協同功能,現在可以登入 Cloud Mind 平臺進行體驗,年後 Team File 也將接入 UMind,屆時 UMind 將會服務於集團所有的小夥伴:)

視覺化圖編輯器

說起 UMind 就必須提到 GGEditor,曾經有很多小夥伴質疑過 GGEditor 的專案名,其實 GGEditor 的全稱是 Great Graphic Editor,是不是突然感覺上高大上了不少 ?

流程圖 腦圖 拓撲圖
image.png image.png image.png 

GGEditor 基於 Antv G6 與 React,提供流程圖、腦圖、拓撲圖的視覺化編輯能力。集團內部我們專注於技術賦能,起初用於 Schema 編輯器、AIBoost 流程化搭建專案,目前已經超過 25+ 專案接入,集團外部我們在 GitHub 上的 Star 數已超過 650+,並有幸入選開源中國 2018 年度國產新秀榜

多人協作版腦圖

02.png

可能大家之前會感覺多人協作的實現比較複雜,但是真正實現起來無非需要解決以下兩個問題:

  • 運算元據的實時同步
  • 運算元據的衝突處理

操作轉換

首先我們需要將使用者的操作行為轉換為對應的操作模型以方便後繼的運算元據同步與衝突處理,格式如下:

{
  "command": {
    "name": "INSERT / DELETE / UPDATE / MOVE",
    "data": {
      "id": "",
      "model": {...}
    }
  }
}

name 欄位表示操作行為,分別對應插入、刪除、更新、移動四種使用者操作行為。

data 欄位包含運算元據,其中 id 用來標識節點,model 則是節點資料模型。

資料同步

這裡講的資料同步分為兩層:

  • 第一層是瀏覽器與 Node 伺服器之間的雙向通訊
  • 第二層是存在多臺 Node 伺服器之間的資料同步

第一層可以通過 WebSocket 協議實現雙向通訊,我們使用的是 Socket.IO 類庫。第二層則是能過 MetaQ(對應 MQ 產品)來保障分散式多伺服器之間的訊息廣播。

衝突處理

相較於富文字編輯器的多人協作,得利於腦圖每個節點的唯一標識,使得衝突的處理能夠更加的簡單。

實現思路

03.png

上圖所示便是最常見的衝突例子:A 使用者與 B 使用者對同一節點同時執行更新與刪除操作,C 使用者則需要同步操作。

為了達到所有使用者最終結果統一,每當同步運算元據後需要根據被操作節點的狀態標識來決定是否同步或捨棄操作:

  • A 使用者需要同步刪除操作
  • B 使用者需要捨棄更新操作
  • C 使用者如果先同步刪除操作(廣播訊息無法保證順利)則也需捨棄後續的更新操作。

那麼應該如何標識各節點狀態呢?還記得在這之前我們已經定義過了使用者的基本操作行為,再配合使用者執行操作時的時間戳便能夠標識各節點的狀態。這裡以 B 使用者為例,執行刪除操作之後的節點狀態如下:

{
  "NODE01": {
    "INSERT": 100000000001,
    "DELETE": 100000000003
  }
}

隨後 B 使用者接收到更新操作廣播:

{
  "command": {
    "name": "UPDATE",
    "data": {
      "id": "NODE01",
    }
  },
  "t": 100000000002
}

經過對比 DELETE 與 UPDATE 的操作時間,最終決定捨棄本次更新操作,至此衝突解決。

實現細節

上個章節我們大體介紹了衝突處理的實現思路,但是實際開發過程中遇到的問題往往更加複雜,這個章節我們就來聊聊衝突處理中的實現細節。

分解移動操作

05.png

節點的移動操作可以看作是刪除操作與插入操作的組合,最終只需記錄下插入操作的時間戳以備衝突處理時使用。

刪除父級節點

04.png

當刪除一個父級節點時不僅需要標識該節點的刪除狀態,也需同時標識其後繼子節點為刪除以備衝突處理時使用。

更新多級屬性

06.png

更新節點較於插入與刪除更為複雜,因為會涉及到節點預設的主題樣式,所以需考慮多層屬性合併衝突處理。

如上圖所示:

  • A 使用者選擇了預設樣式(文字顏色:紅色,文字大小:14,節點背景:白色)
  • B 使用者更新了節點樣式(文字顏色:藍色)

然後經過沖突處理,希望得到的結果是:

  • A 使用者在原有的預設樣式基礎上合併 B 使用者的更新操作。
  • B 使用者則需要同步到除文字顏色之外 A 使用者的更新操作。

為了能夠實現以上這種衝突處理的效果,我們需要分別記錄各個屬性的操作時間,收到更新廣播之後再依次對比進行取捨。這裡我們以 B 使用者為例來講解具體實現:

B 使用者更新字型顏色之後的結點狀態:

const NODE_STATUS = {
  NODE01: {
    UPDATE: {
    	`textStyle.fill`: 100000000002
    }
  }
};

緊接著收到了 A 使用者發出的更新廣播:

const message = {
  command: {
    name: "UPDATE",
    data: {
      id: "NODE01",
      model: {
      	textStyle: {
        	fill: "red",
          fontSize: 14
        },
        rectStyle: {
        	fill: "white"
        }
      },
      paths: [
      	"textStyle.fill",
        "textStyle.fontSize",
        "rectStyle.fill"
      ]
    }
  },
  t: 100000000001
};

開始依次對比獲取真正需更新的屬性:

使用 . 符號分割屬性層級的好處:能夠在 pick 方法中直接當作引數使用

const { command, t } = message;
const { id, model, paths } = command.data;

const updatePaths = [];

paths.forEach((path) => {
	if (NODE_STATUS[id][`UPDATE`][path] > t) {
  	return;
  }
  
  updatePaths.push(path);
});

console.log(_.pick(model, updatePaths));

寫在最後

UMind 1.0 版本釋出可以視為從 0 到 1 的過程,這裡特別感謝 Antv 團隊 @蕭慶 @有田 的支援。接下來無論在腦圖功能的打磨還是多人協同的探索方面我們都還有很多的路要走,如果正巧看到這篇文章的你有任何意見或是興趣都歡迎通過以下方式來聯絡我們。

簡歷投遞:fangqin.fq@alibaba-inc.com

業務接入

image.png image.png