我們設計目前的門戶基座,可以快速瀏覽各個平臺,同時串聯數據開發與管理的工作,減少用戶的試錯成本,提升工作效率。 ...
作者:京東科技 陳雲飛
一,需求背景
1 業務背景
在以往的業務場景中,用戶進入五花八門的菜單體系中,往往會產生迷茫情緒,難以理解平臺名稱及具體作用,導致數據開發與管理學習成本較高,降低工作效率。為此我們整合從數據接入,數據開發,數據管理的全鏈路流程,期望讓用戶體驗一站式數據開發與管理的便捷性;並提供不同業務場景,方便根據業務場景進行進一步數據開發與管理工作,為數據應用平臺打下夯實規範的數據基礎,方便用戶在數據平臺里,對於數據開發和數據應用進行便捷性的切換,因此我們設計目前的門戶基座,可以快速瀏覽各個平臺,同時串聯數據開發與管理的工作,減少用戶的試錯成本,提升工作效率。
2 標品需求
基座子-項目交互簡圖如圖1;
1,基座的業務頁面比較簡單,主要包含:頂部邊欄、左側邊欄、公共子菜單、頂級平臺菜單;
2,點擊左上角圖標,顯示頂級平臺菜單,點擊平臺,在基座左側邊欄動態顯示平臺一級菜單;
3,點擊基座左側邊欄,在公共子菜單,動態顯示一級菜單下邊的二級、三級菜單;
4,點擊基座左側邊欄或者公共子菜單,需要基座調度,在子項目區域正確載入子項目及子項目頁面;
圖 1
數據中台新門戶基座要接入老數據平臺一、老數據平臺二等 多平臺的前端項目,並且原有前端子項目在門戶基座呈現任意子項目、任意子項目頁面 任意混搭的需求;新門戶要接入的項目關係詳情如圖2;
圖 2
3 數據中台融合;
數據中台融合指的是京東體系內,其他對外獨立交付的數據中台,比如京東工業、京東城市等項目;數據中台商業化的子項目不僅在新門戶容器內,也可以按需打包進其他數據中台容器;下麵簡稱 數據中台融合;
二,微前端技術調研
原有數據中台接入子應用的方式有多種:iframe 嵌套、@weus 微應用、鏈接跳轉等;
1 iframe 存在問題:
• url 不同步。瀏覽器刷新 iframe url 狀態丟失、後退前進按鈕無法使用。
• UI 不同步,DOM 結構不共用。想象一下屏幕右下角 1/4 的 iframe 里來一個帶遮罩層的彈框,同時我們要求這個彈框要瀏覽器居中顯示,還要瀏覽器 resize 時自動居中..
• 全局上下文完全隔離,記憶體變數不共用。iframe 內外系統的通信、數據同步等需求,主應用的 cookie 要透傳到根功能變數名稱都不同的子應用中實現免登效果。
• 慢。每次子應用進入都是一次瀏覽器上下文重建、資源重新載入的過程。
2 weus 存在問題:
•weus 是京東內部研發已經不再維護了,如果有新的問題需要自己解決,對微前端有新需求也需要自己去實現;
•weus 沒有嚴格的 css 沙箱、js 沙箱,而在我們的需求中,沙箱機制是剛需,我們要接入的子項目在 window 上掛在哪些變數,無法通過規範做到強有力的制約(因為要接入的項目是已經寫完了)
•weus 在微前端功能實現,沒有qiankun 豐富健全,比如全局狀態管理、雖然 weus 實現了子應用的預載入,但是比較機械,是把所有註冊的子應用都緩存,實際可能不需要,qiankun 就比較靈活可以根據需要手動緩存等;
3 鏈接跳轉
鏈接跳轉,指的是點擊一個菜單,跳轉到另一個頁面。這種方式不符合 “一站式 ”大數據平臺產品定位;
4 最終結論
通過幾種實現方式的對比,最終決定以 qiankun 微前端為基礎,結合我們的實際業務場景,通過許可權菜單樹 和 子項目關聯來實現基座對子項目的調度,具體方案請參照 三,基座技術方案詳細描述;
三,技術方案詳細描述;
1 名詞解釋
跨子項目跳轉
指的是子項目沒通過基座自行跳轉,基座此時需要根據 url 匹配正確的基座顯示;
觸發節點
點擊菜單,菜單對應的節點即為觸發節點,跨子項目跳轉(刷新頁面)沒有觸發節點,以跳轉後url 對應的頁面節點為觸發節點;
頁面節點
許可權菜單樹中,掛載了子項目頁面的節點
門戶節點
狹義指的是,當前頁面節點對應的公共子菜單父級節點;廣義指的是通過頁面節點向父系節點查詢,最終確認公共子菜單、基座邊欄的顯示,並通過頁面節點確定菜單高亮等行為;
第一子系節點
指的是在由觸發節點查頁面節點時,只關註子節點中的第一個,如果當前節點的第一個節點不是頁面節點,繼續在孫子節點的第一個,直到節點類型為頁面節點為止;
調度引擎
指的是在基座中,對許可權樹數據處理,接收基座業務層調度,通過對許可權樹遍歷、查詢操作對業務層輸出運算結果的一個抽象分層;
2 整體流程圖
如圖3 為基座技術方案的整體流程圖,下麵詳細介紹
圖 3
3 技術方案-分步描述
第 1 步,配置許可權菜單樹,關聯子項目頁面
•如圖 3 所示,首先會在許可權中心配置許可權菜單樹,許可權菜單樹有很多級,比如一級平臺,二級平臺,二級平臺一級菜單,二級菜單等,三級菜單等;
•然後需要在菜單樹上,掛在子項目的頁面,子項目頁面的一些許可權按鈕等;
•最後許可權中心對不同的用戶賦予許可權菜單樹的子集;
第 2 步,基座菜單樹數據處理;
•基座從介面拿到許可權菜單樹關聯頁面數據後,觸發調度引擎初始化、並把基座的業務更新函數傳給調度引擎;
•基座獲取到菜單樹以後,廣度優先演算法(如圖4)遍歷整個樹結構,並建立節點間父子關聯關係。根據節點是否掛載了子項目頁面定義前端節點類型 ;最終形成圖 5 的數據;
•在樹遍歷過程中,會統一在基座左側邊欄這一層級的節點,做一個門戶節點的標記;
圖4
•如圖 5 所示,前端調度關係中,把許可權菜單樹分成 兩種節點類型,掛載了子項目頁面的節點,定義為頁面節點。另一種沒掛載子項目頁面的節點,定義為分組節點;在基座調度中,不關心許可權按鈕節點,在數據處理中,頁面節點就是整個許可權菜單樹的葉子節點;
圖 5
第 3 步,點擊基座邊欄或公共子菜單
•點擊基座邊欄,觸發點擊菜單流程,點擊的節點即為 “觸發節點”;
•由觸發節點,向第一子系節點查頁面節點,查頁面節點同時,會同時匹配門戶節點;
•沒有匹配到門戶節點,由觸發節點向父系節點匹配門戶節點;
第 4 步,刷新頁面或跨子項目跳轉
•這時沒有直接的觸發節點,只能通過 url 上的標識和菜單樹上的頁面節點進行匹配
•匹配到頁面節點以後,由頁面節點向父系節點查門戶節點;
第 5 步,通過門戶節點輸出 “運算結果”
•這裡的運算結果主要包含:基座左側邊欄、公共子菜單菜單樹列表、菜單高亮、產品平臺名稱等一系列基座需要正確顯示所需要的數據;
•基座調度引擎,根據觸發調度的類型;如果是點擊菜單觸發會執行 切換頁面操作,刷新和跨子項目跳轉則不需要觸發;
第 6 步,基座業務層執行運算結果;
•基座調度引擎,在接收基座業務層調度指令後,通過對圖 5 許可權樹的遍歷、查詢操作,最終獲取運算結果;
• 調用基座業務層的更新函數,執行運算結果,到此基座調度流程結束;
四,基座-子項目結構通信圖
1 基座分為業務層、調度引擎兩部分;
•基座業務層主要包含:左側邊欄、頂部邊欄、頂級平臺菜單等 UI顯示;
•基座調度層,以 qiankun 微前端為基礎,豐富擴展了許可權樹數據處理、調度運算、雙向通信、子項目分組分步預載入策略等;
2 基座子項目通信和路由調度
•基座在執行主題切換時候,通過 qiankun 的 setGlobalState 通知子項目;子項目在綁定項目空間等特定需求時,通過調度引擎封裝的 eventCenter 和基座反向通信;
•跨子項目跳轉,子項目會自行觸發 pushState,點擊基座菜單跳轉流程,由調度引擎觸發 pushState 觸達
• qiankun 的 pushSate 劫持策略,觸發 popstate 子項目頁面因此觸發更新;
圖 6
3 路由劫持策略原理
基座調度引擎通過監聽 popstate 來獲取刷新、跨子項目跳轉的指令,從而觸發調度流程;以下為原理解析:
•以 Vue 為例,Vue 通過 主動觸發 pushState、replaceState 或者監聽 popstate 變化觸發頁面發生變化;
•但是跨子項目跳轉,執行 pushState 並沒有觸發 popState 基座調度引擎又怎麼能監聽到呢?
•通過閱讀 qiankun 中依賴的庫 single-spa 的源碼,navigation 模塊劫持了 pushstate 方法,只要觸發 pushstate,就會觸發 popstate 事件,關鍵代碼如下所示:
function patchedUpdateState(updateState, methodName) {
return function () {
const urlBefore = window.location.href;
const result = updateState.apply(this, arguments);
const urlAfter = window.location.href;
if (!urlRerouteOnly || urlBefore !== urlAfter) {
if (isStarted()) {
window.dispatchEvent(
createPopStateEvent(window.history.state, methodName)
);
} else {
reroute([]);
}
}
return result;
};
}
window.history.pushState = patchedUpdateState(
window.history.pushState,
"pushState"
);
五,基於 qiankun 功能擴展:
1 沙箱隔離
js 沙箱利用 qiankun 沙箱機制;
css 沙箱,qiankun 的 css 沙箱不健全;我們接入的又是老項目,目前的策略是 :
•基座通過特殊命名空間 .susceptor、element-ui 通過 .spr 跟子項目隔離;
•子項目通過特殊命名空間,例如 .datacenter-xxx 跟基座隔離
•子項目對 .el- 不執行隔離,目的是為了統一控制佈局、主題,同時做性能優化;
2 通信機制
基座和子項目,屬於典型的主從結構,採用單向數據流通信;為了滿足特殊場景子項目跟基座通信需求,在 qiankun 的通信基礎上,封裝了 eventCenter 僅用於 子項目 跟基座通信;
•基座 -> 子項目,通過qiankun 的 onGlobalStateChange 通信
•子項目 -> 基座,通過 subActions 封裝的 eventCenter 通信
3 分組、分步、動態預載入機制
目前標品基座已經載入了 30多個前端子項目,這麼多前端子項目在首個子項目掛載後執行預載入,有可能會阻塞正常頁面訪問;
分組指的是,通過產品劃定的平臺,只有切換到當前平臺才會載入到當前平臺的子項目;
分步指的是,當前平臺子項目過多時,一次預載入少量子項目,分多次預載入;
動態指的是,基座在首個子項目掛載後,會檢測網速,只有網速良好時才會執行預載入;
4 跨子項目跳轉
跨子項目跳轉的背景在於 BDP 一站式開發與管理平臺,是一個大產品,數據規劃、數據集成 等是其中的模塊;模塊之間存在一些跳轉,對於前端就是跨子項目跳轉;
我們目前的跨子項目跳轉,由 subActions 封裝,抹平了標品基座 和 數據中台融合容器的區別;
基座跳轉邏輯如下:
crossAppJump: ({ subApp='', path= '', paramsStr='', target= ''})=> {
let jumpUrl = `/susceptor/${subApp}${path}`
if(paramsStr){
jumpUrl = `${jumpUrl}${paramsStr}`
}
if(target === '_blank'){
window.open(`${location.origin}${jumpUrl}`)
} else {
window.history.pushState({ portalPushState: true }, null, jumpUrl);
}
},
數據中台融合跳轉邏輯如下:
crossAppJump: ({ subApp='', path= '', paramsStr='', target= ''})=> {
let jumpUrl = `/${subApp}${path}`
window.__datafuse_jssdk__.crossAppJumpFnGetter({
path: jumpUrl,
paramsStr,
target
})();
}
5,介面訪問及登錄鑒權;
由於微前端的技術形態,子項目在基座中載入實質是基座容器的 一段 html,所有介面均是以基座的 功能變數名稱進行介面轉發;由因為數據中台融合,所以子項目請求後端介面均是以 /api/datacenter/項目名 開頭;
通過以下 nginx 示例,我們把基座、子項目 的頁面訪問、介面訪問鏈路 說明白;
# proxy.conf 示例(已做脫敏處理,均不是項目升級名稱)
server {
listen 80;
server_name unify.external.dadacenter;
charset utf-8;
location /sub-app{
alias /export/web/sub-app-web;
try_files $uri $uri/ /index.html =404;
}
location /api/datacenter/subapp {
proxy_pass http://server-sup-app/;
}
location /susceptor{
alias /export/web/susceptor; on;
try_files $uri $uri/ /index.html =404;
}
location /api{
proxy_pass http://sever-api/;
}
}
# server.conf 示例(已做脫敏處理,均不是實際項目)
upstream server-sup-app{
server 111.112.113.114:10001;
}
upstream sever-api{
server 111.112.113.114:1000;
}
鑒權,前端不需要開發,但是需要知道,後端是通過頂級功能變數名稱種 cookie 鑒權的;例如:unify.external.bigdata 測試環境是在 .external.bigdatao 功能變數名稱下,這也是為什麼本地開發需要配置 host:127.0.01 loca.external.dadacenter ;
六,寫在最後,不忘初心
本節提供兩個圖, 對上文介紹的微前端實踐,有進一步的能力提升,有感興趣的同學歡迎一起討論;
1,整體流程圖;
如圖 6 所示,配置流程:配置許可權菜單樹,然後配置子項目-子項目頁面兩級;最後把許可權菜單樹 和 子項目-子項目頁面關聯起來,形成如圖 7 的許可權樹-子項目關聯數據模型;
基座兩個調度流程,跟上文類似,但是多了子項目維度,基座在載入子項目的時候,就可以把子項目 在許可權樹的 許可權按鈕信息全部給到子項目;
圖 6
2 許可權菜單樹模型;
上文介紹的基座調度流程是簡化後的版本,項目節點只有 分組節點、頁面節點;但是從能力層缺失了 子項目維度;在設計之初,如圖 7 所示:項目節點包含了 分組節點、子項目節點、子項目頁面節點、頁面節點 4種節點類型;
圖 7