哈騎士是哈啰的一款終端安全應用,本文主要介紹我們在做新版哈騎士桌面端時的一些技術架構思考和實踐,分享我們沉淀的一些桌面端應用的解決方案和經驗。
為什么選擇Electron
前端開發者入門快
Electron是一個使用 JavaScript、HTML 和 CSS 構建桌面應用程序的框架。嵌入 Chromium 和 node.js 到 二進制的 Electron 允許您保持一個 JavaScript 代碼代碼庫并創建 在Windows上運行的跨平臺應用 macOS和Linux——不需要本地開發經驗,有了它,前端開發者就可以使用前端開發技術來開發桌面應用了。
支持跨端&開發效率高
如上圖所示:
- Native(C /C#/Objective-C)不管從原生體驗、包的體積、性能方面來說都是最佳的選擇,但是開發門檻高、迭代速度慢。
- QT是基于C 的跨平臺開發框架,跨平臺應用十分廣泛(Mac、Windows、ios、Android、Linux、嵌入式),眾所周知的WPS就是用QT開發的。性能很好,甚至于可以媲美原生的體驗,但是整體門檻還是比較高的。
- NW也是一個跨平臺的框架,但是其社區以及解決方案相對于Electron來說并不是那么強大,而且所有的非javascript編寫的模塊都需要重新用nw-gyp重新編譯,相對于Electron來說,不是那么靈活。
- Tauri也是一個非常火爆的跨平臺的桌面端框架,相對于Electron來說還不是那么成熟,生態方面也略顯青澀,兼容性問題有待考證。
作為一個跨平臺的桌面應用開發框架,Electron 的迷人之處在于,它是建立在 Chromium 和 Node.js 之上的,二位分工明確,一個負責界面,一個負責背后的邏輯。雖然系統間還是會有很大的差異,需要相應地做一些額外處理,使得打包出的應用在不同系統下都能正常運轉,但相比于 80% 都能完全復用的代碼,這些時間和成本都是可以忽略的,開發效率直接翻倍,如果你開發一個不需要太關注底層的桌面端應用,基本不需要做底層的抹平邏輯。
另外,Electron 是基于 Node.js 的,這就意味著,Node 這個大生態下的模塊,Electron 都可以用。同時,跨平臺也讓 Electron 可同時開發 Web 應用和桌面應用,無論是 UI,還是代碼,很多資源都可以共享,大幅減少了開發者的工作量。
生態繁榮&案例成熟
Electron生態的確很強大,各種庫和工具包都為你構建一個桌面端應用提供了很多方案。
當然,不止如此,現在用Electron做桌面端的案例也非常成熟了。上圖已經說明了Electron應用是有多廣泛了,這其中不乏大名鼎鼎、如雷貫耳的應用,例如 Postman、Skype、VScode 等。而且我敢打賭,各位看官的電腦上一定安裝過用 Electron 開發的應用,如果你用的是 Mac 電腦,請在命令行運行下面的命令來檢測本地采用 Electron 技術開發的桌面軟件:
for app in /Applications/*; do;[ -d $app/Contents/Frameworks/Electron Framework.framework ] && echo $app; done
Electron生態開發技術選型
腳手架選型
關于腳手架的選擇,其實也很多。
官方提供的有Electron Forge,Electron Fiddle,electron-quick-start,其實如果你的應用不復雜,可以用官方的腳手架生成一個快速上手的模版,然后就可以愉快地開發了。
當然也有一些開源的腳手架,比如electron-vue或vue-cli-plugin-electron-builder之類的,也可以讓你快速的生成一個固定的模版,然后往里面填充你的內容。
個人認為,官方的腳手架工具可以用來嘗鮮,學習使用,electron-vue這類工具,如果是在一個企業級的項目中使用,前期會給你帶來便利,但是后期擴展不會太友好,另外就是他們是基于webpack構建的工具,在日常的開發和使用中會覺得編譯得不夠快(相對于Vite)。
另外就是如果你想自己完成一個項目腳手架(項目框架),完全可以憑借自己的經驗或者參考開源項目的架構自己來完成一個腳手架,一來是為了更加了解Electron的構建原理,二來是可以搭建出適合自己風格項目的腳手架,后期利于擴展和豐富。
所以我們腳手架的選型就是自己來造一個Electron的項目架構,從package.json開始,用Vite Electron React構建一個Electron項目。
網絡模塊選型
Electron發送HTTP請求的方案有很多。
第一種就是渲染進程和主進程分別用相應的請求HTTP請求工具來進行網絡請求,比如渲染進程可以使用fetch,主進程用net模塊。這種方案的優點就是可以把渲染進程和主進程的請求分開,分工明確,而且調試也方便,渲染進程可以直接看network;缺點就是,如果要對請求進行統一封裝的話,比較麻煩。
第二種就是所有的請求統一封裝,如果你都使用net模塊或者其他的請求工具包對請求進行統一的封裝,然后主進程直接使用,渲染進程調用統一的橋接方法。這種方案就是完全可以統一請求封裝,但是如果想調試的請求的話,不方便,需要在主進程來日志信息。
第三種就是,直接axios直接一把梭,它既支持node環境,也支持瀏覽器環境。這種方案非常方便,你就按照之前封裝Web應用請求的思路去封裝自己的請求模塊就行,不過需要注意跨域問題。
對于上面的幾種方案,各有各的優缺點,可以根據自己的場景需求來決定使用哪種方案。我們選擇了axios來設計網絡請求模塊。
本地數據庫選型
Electron的本地數據存儲方式也有很多種,可以直接讀寫文件,也可以用相關的庫,方便數據管理。一些庫的對比,詳情:https://www.npmtrends.com/electron-store-vs-lokijs-vs-lowdb-vs-nedb-vs-realm。
綜合來看lowdb更勝一籌,所以選擇lowdb做本地數據庫,非常好的一點是它支持同步,不必擔心數據沒有寫入就進行了下一步需要本地數據的業務操作。
日志工具選型
日志工具對Electron的開發也是尤為重要的,可以給你定位到一些表層無法定位的問題,所以一款好的日志工具對開發是非常有幫助的。
比較常見的日志工具就是electron-log和log4js-node,這兩款日志工具我都有用過。可以看下npm的排行,這里把express-winston和logging也加上看一下,詳情:https://npmtrends.com/electron-log-vs-express-winston-vs-log4js-vs-logging。
這里簡單說一下electron-log和log4js-node的比較,兩者上手都比較簡單,log4js-node暴露的API 非常多,electron-log就稍顯遜色了,另外最直觀的感受就是,electron-log的日志文件路徑不好找,暫時沒發現自定義日志路徑的方法,log4js-node有相應的方法,而且你可以自定義各種文件類型。
根據使用體驗,覺得log4js-node更好,推薦log4js-node。
構建工具選型
三種構建工具electron-builder, electron-forge, electron-packager 對比一下。
從這個排行來看electron-builder的確很強,electron-forge最近又更新大的版本,不過沒有嘗鮮,我在electron-builder上倒是踩了不少坑,可以分享給大家。所以我在開發的時候選擇的構建打包工具是electron-builder,它把整套解決方案都集成了,包括打包、更新、簽名、分發,基本的鉤子和配置都有相應的暴露。
核心架構實現
架構概覽
我們整個框架是基于Eletcorn Vite構建的,在底層依賴的安全能力和存儲模塊的基礎設施之上設計了一層基礎框架,實現構建打包,架構分層的設計,然后給整個桌面應用提供一些應用管理能力和GUI管理相關的能力,最上層就是為了一些業務場景提供的一些應用能力,包括核心的幾個應用和主要的策略引擎應用(終端策略和合規策略)。
開發構建
Electron是多進程架構的體系,所以我們在開發構建的時候就是構建多個進程來實現我們的應用。核心思路是通過Vite構建三個進程:渲染進程,任務進程,主進程,然后最后將三個進程融合起來,就形成了一個應用。核心代碼如下:
幾個注意點:
- 我們這里利用了writeBundle,就是等chunk都寫入文件后,再啟動Electron進程。
- 這里沒有利用Electron的命令啟動,而是通過Node.js的child_process模塊的spawn方法啟動Electron子進程,主要是因為我們需要依賴開發環境的渲染進程。
- 另外就是config/vite/main.js中需要對rollupOptions的external進行electron的配置,把導入包轉成外部依賴,不然在啟動Electron會找不到Electron的路徑。
- 在createMainServer中我們注入了全局可使用的變量,以便Electorn加載頁面的時候可以使用這些變量。
架構分層
因為需要跨端開發,Mac和Windows有些底層模塊的實現還是有不一樣的地方,所以我們在開發設計的時候將代碼進行了分層設計,這樣至上而下的調用在上層看來是一樣的,所以我們需要磨平端上底層的差異,現階段我們底層模塊的實現是通過目錄來嚴格區分的,這樣在開發一個底層的功能的時候就可以做到各段相互不影響。
打包升級
桌面客戶端相當于傳統的Web應用在打包和更新這一塊還是有非常大的不同的,傳統的web應用幾乎不用所謂的升級,瀏覽器刷新頁面即可,但是桌面客戶端就需要完整的給用戶一個可以立即執行的安裝應用程序,而且還要可持續迭代和更新,所以在打包升級這一塊,我們也是踩了不少坑。
1. 關于打包
打包其實Electron的生態也是非常成熟的,如上面提到的構建技術選型,我們選擇的是electron-builder,它提供了一套打包構建升級的流程,暴露了很多API,傻瓜式的配置就基本可以讓你實現一個應用的打包了,唯一麻煩的就是簽名和認證應用。
在Windows端我們使用pfx格式的證書進行認證,在進行打包的時候會和證書客戶端軟件交互,完成各個文件的簽名,這樣用戶使用客戶端的時候就是簽名過的軟件了。
在Mac端我們需要使用蘋果認證的開發者證書進行簽名和認證,配置相應的identity后,構建打包的時候會直接跟你本地的證書進行交互,然后對文件進行簽名,當前我們還需要讓應用可以不必嚴格使用 MAP_JIT 標識也能寫入和運行內存內容。所以需要加入entitlements和entitlementsInherit。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"> <dict> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> </dict></plist>
到這一步其實Mac端的軟件簽名就完成了,但是如果應用想App Store上架的話還需要對應用進行公證。公證主要是使用electron-notarize來進行公證,啟用afterSign即可,
afterSign: './script/notarize.js',
下面的Apple ID就是你的開發者賬號,appleIdPassword需要生成一個專用的應用密碼,不要使用你本來的Apple ID密碼。
const { notarize } = require("electron-notarize");exports.default = async function notarizing(context) { const { electronPlatformName, appOutDir } = context; if (electronPlatformName !== "darwin") { return; } const appName = context.packager.appInfo.productFilename; console.log(`公證中...`) return await notarize({ appBundleId: "mac.hellobike.knight", appPath: `${appOutDir}/${appName}.app`,1 appleId: "XXXXX@outlook.com", appleIdPassword: "XXXXX", });};
notarize會根據你的配置去校驗你的應用是否可以公證成功,公證的時候會和蘋果的服務器進行通訊,所以需要保持網絡不要斷開,成功或者失敗之后都會發送相應的郵件到你的開發者郵箱里面。
到這里打包的核心工作就做完了,如果你需要其他個性化配置,參考electron-builder官方的文檔即可。
2. 關于升級
升級我們在Mac和Windows上的實現各有不同,因為相比于傳統的軟件,我們哈騎士會一直保活在用戶的進程中,所以在更新升級的時候也會打破原本Electron升級的機制。
在Windows上其實還好,可以利用electron-updater本身的生命周期來完成下載,更新,重啟應用,因為Windows的保活是用另外的服務來實現的,所以并不會對整個更新周期產生破壞性的影響。
但是Mac端的保活實現是打破了electron-updater本身的生命周期的,探究其源碼會發現Electron自己的升級服務其實也是一個保活的應用服務,所以在升級之前需要將其Kill后才能完成哈騎士自己本身的更新邏輯,另外就是文件占用和鎖定的問題,為此我們自研了一套更新腳本程序結合electron-updater的下載更新的能力實現了Mac端軟件的升級。
核心能力沉淀
基礎能力
我們在做哈騎士客戶端的時候,也沉淀了一些與業務無耦合的組件和工具類,這些組件和工具在桌面端應用的場景都比較通用。
- 本地數據庫管理
本地數據存儲是業務場景中隨處可見的重要功能。為此,我們封裝了常用的增刪改查數據庫的能力,并提供給各個進程使用,以實現數據持久化存儲。 - 底層橋接
底層橋接是解決Electron和Node無法覆蓋所有應用場景的必要手段。我們在橋接層封裝了三種橋接模式,分別為渲染進程調用的jsBridge能力、主進程調用dll和dylib插件的能力,以及橋接rust程序的能力。這三種模式基本上可以解決所有技術瓶頸。 - 客戶端請求
客戶端請求模塊也是至關重要的。我們將其封裝成了通用的http請求庫,支持主進程、渲染進程和任務進程的調用,以抹平上層調用的差異性。 - 任務管理
由于業務場景和客戶端的特殊性,我們經常需要進行本地任務管理。因此,我們將任務管理模塊封裝成了通用的工具類,以支持對任務的注冊、啟動、停止和銷毀等各項生命周期的管理。
應用能力
在上面這些基礎能力的組合應用下,我們形成了一個強大的策略引擎應用。
該策略引擎應用實現了端上任務調度和分發功能。首先接收后臺配置的策略信息,然后生成對應的任務,并分發到各個子任務中心以執行對應的策略。最后,將策略執行情況報告給服務端。
總結
Electron在哈騎士的應用非常成功,雖然在使用過程中遇到了一些問題,但不可否認它是目前最適合我們業務目標和開發資源的框架。使用Electron使需求交付效率得到了很大的提升。
我們也將持續關注性能和穩定性的優化、桌面端全鏈路日志的完善以及增量更新升級能力等方面的改進。
作者:徐濤燾
來源:微信公眾號:哈啰技術
出處:https://mp.weixin.qq.com/s/8v5lyl-yI4AMxQgSwDmkWw
版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。