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