從厭煩不停的登入公司內網到使用Electron製作自己的客戶端工具(一)
theme: smartblue highlight: a11y-dark
起因
事情的起因是這樣,由於本人是在一家與政府機關有合作的單位工作,處於部分保密性原因,公司對員工電腦的外網連線是有限制的,即:商業部門每個人上網都需要登入自己的網路賬號。本身這個問題是不大的,但是令人煩惱的是,每當你有一段空當時間沒有上網,這個登入就過期了...然後你會突然看到如下畫面:
其實這也沒什麼問題,再重新登入就好咯~但是作為一個極致懶惰的程式設計師,我不允許自己像個傻子一樣:一天得開啟那個登入頁n多次,重複的輸入使用者名稱、密碼,點選我同意什麼什麼協議,再點選一個登入button,再告訴我“哦親愛的的垃圾前端,你可以上網了!”。這簡直無法忍受!
於是我就思考了:能不能寫一個程式能自己檢測現在的外網連線,如果發現斷網了,就自動幫我登入呢?
聽起來是完全可行的,因為大致思路上的虛擬碼已經有了:寫一個定時器,每間隔幾秒鐘ping一下百度,如果ping不通,那麼就開啟內網登入頁自動登入或直接發個登入請求,如此就ok了。
那麼說幹就幹,走起。
使用Nodejs指令碼
思考這個程式的時候,我首先想到的就是寫一個nodejs的指令碼,畢竟是做前端的,所以對js比較熟悉。在指令碼中做我想做的事,然後命令列node xxx.js
就好了。很簡單,於是唰唰唰就把基本函式寫好了:
```js // main.js
const ping = require('ping');
const username = 'dyggod' const password = 'password' const time = 3000; let timer = null;
/* * 啟動一個定時器,呼叫監聽函式,並再次執行自己 / function start() { timer = setTimeout(() => { watchInternet(); start(); }, time); }
/* * 清空定時器並釋放記憶體 / function pause() { clearTimeout(timer); timer = null; }
/* * 呼叫系統ping命令,檢測電腦是否聯網 / async function isOnline() { const { alive } = await ping.promise.probe('www.baidu.com'); return alive; };
/* * 監聽網路狀態,如果斷網,則呼叫login函式 / async function watchInternet() { const online = await isOnline(); if (!online) { try { console.log('\x1b[33m%s\x1b[0m', '網路斷開,嘗試重新登入...'); await login(); } catch (error) { console.log('\x1b[31m%s\x1b[0m', '登入失敗,正在重試...'); } } }
/* * 登入函式 / async function login() { console.log('登入成功') }
start(); ```
這裡說明一下這個指令碼引入了ping
這個依賴,它可以允許我們像命令列中的ping那樣檢查網路是否是通的。
寫成這個樣子,其實大部分的事情已經做完了,執行這個js檔案,在沒有外網的時候,是會有對應的列印內容,剩下的無非是補充login
函式中的程式碼,寫成真實的邏輯。
但是在此之前,我要把這個指令碼優化一下,因為這種把使用者名稱、密碼、定時時間寫死的方式太不優雅了,我還想著如果效果不錯,能分享給我的同事使用呢。於是,讓我來安裝commander
依賴,接收命令列引數,讓它更加的通用:
```js // main.js
const ping = require('ping'); const commander = require('commander');
const { Builder } = require('selenium-webdriver');
const program = new commander.Command();
program.command('start')
.description('開始監測內網登入狀態......')
.option('-u, --username
let username; let password; let time; let timer = null;
/* * 主函式,配置內網登入的使用者名稱和密碼 * 執行啟動函式 / function main(cwd, options) { console.log('監聽正在執行,引數列表為:', cwd); username = cwd.username; password = cwd.password; time = cwd.time; start(); };
/* * 啟動一個定時器,呼叫監聽函式,並再次執行自己 / function start() { timer = setTimeout(() => { watchInternet(); start(); }, time); }
/* * 呼叫系統ping命令,檢測電腦是否聯網 / async function isOnline() { const { alive } = await ping.promise.probe('www.baidu.com'); return alive; };
/* * 監聽網路狀態,如果斷網,則呼叫login函式 / async function watchInternet() { const online = await isOnline(); if (!online) { try { console.log('\x1b[33m%s\x1b[0m', '網路斷開,嘗試重新登入...'); await login(); } catch (error) { console.log('\x1b[31m%s\x1b[0m', '登入失敗,正在重試...'); } } }
/* * 登入函式 / async function login() { console.log('登入成功') }
// 執行 program.parse(process.argv).version('1.0.0'); ```
commander
是一個方便我們定義並接收node執行時的命令列引數的依賴庫,我們現在定義了username、password、time三個引數,那麼如果是別人使用,只需用node main.js start -u xxx -p xxx -t 5000
就可以使用自己的賬戶去運行了。
到現在為止,我們的執行輸出如下:
接下來,就是需要完成我們的登入邏輯,讓這個指令碼可以真正解決我的問題。那麼首先,我們要去看登入內網的那個網站的請求構成是怎樣的。我用控制檯將網路設定為離線,登入內網觀察它的過程看到的情況:
哇喔哇喔哇喔,瞅瞅,瞅瞅我們看到了什麼??php!
此時我的內心:
在我原有的思路中,是在login函式中傳送ajax請求,將使用者名稱、密碼等引數發過去,實現登入的功能。但現在看起來不太可能了,因為眾所周知,php是服務端語言,雖然有部分邏輯是執行在瀏覽器裡的,但傳送請求的時候其實是呼叫的服務端的資源指令碼,如果我們直接傳送請求,大概率會被伺服器限制,因為所處的環境不一樣,被服務端隔離限制。我用postman試一下,果然:
返回NOT Found
,被限制了,至於想搞清楚它到底需要再攜帶什麼頭引數才能繞過限制,是很麻煩的,或者服務端乾脆不允許任何外部請求也說不定。
並且我們看到它的pwd
引數其實是做了加密的,這個加密邏輯在檢視網路原始碼的時候,可以看到:
其實也是很麻煩。
所以我決定不再使用請求去登入,而是:模擬頁面登入!意思是我在登入函式中,直接啟動一個模擬瀏覽器,像執行前端整合測試一樣,自動輸入使用者名稱、密碼、選中複選框,點選登入,豈不美哉?說幹就幹。
那麼於是乎,登入函式就變成下下面這個樣子:
```js const { Builder } = require('selenium-webdriver');
/*
* 開啟chrome瀏覽器,填寫使用者名稱和密碼,然後提交
/
async function login() {
const loginUrl = 'http://xxx.xx.x.xxx/ac_portal/20220831163936/pc.html';
const driver = await new Builder().forBrowser('chrome').build();
await driver.get(loginUrl);
await driver.findElement({ id: 'password_name' }).sendKeys(username);
await driver.findElement({ id: 'password_pwd' }).sendKeys(password);
await driver.findElement({ id: 'password_disclaimer' }).click();
await driver.findElement({ id: 'password_submitBtn' }).click();
console.log('\x1b[32m%s\x1b[0m', '登入成功');
// 關閉瀏覽器並退出driver
await driver.close();
await driver.quit();
}
``
這裡我使用了
selenium-webdriver`依賴,它可以在nodejs環境中呼叫一個無頭瀏覽器,來做模擬人的互動操作,忽然彷彿回到了用python寫測試指令碼的辛酸時候...其中輸入框、按鈕等元素的id是可以通過控制檯找到的。
到這裡,看起來似乎我已經成功了呢,那麼,就來一發:
哇哦!大功告成,隨著我故意斷開網路連線,數秒後看到一閃而過幾乎看不到的瀏覽器視窗劃過,在我再次開啟百度看到網頁的時候,證明這個指令碼確實已經可以解決我的問題了。
當然,只是登入一下還不能說明什麼,不過我把這個程式運行了一天,也確實能證明它是在時刻監控並執行登入的。
至此,nodejs指令碼版的自動登入工具就做完了。
不過...還是不太滿意,因為這是個nodejs的指令碼,如果拿給別人用,比如我們的UI大人,他的電腦不會裝nodejs環境,難道還要人家為了執行這個指令碼特意裝一下?不行不行,不夠優雅,我應該讓他不用在意環境而直接使用。那麼...似乎...可以把它做成一個桌面端應用軟體,因為我們都是window系統,所以如果是一個.exe
檔案,那麼自然就不用去裝什麼破爛環境啦!
於是乎,這個小小登入指令碼衍生出了它的Electron版本,請大家看本篇的下章內容。