从厌烦不停的登录公司内网到使用Electron制作自己的客户端工具(一)

语言: CN / TW / HK

theme: smartblue highlight: a11y-dark


起因

事情的起因是这样,由于本人是在一家与政府机关有合作的单位工作,处于部分保密性原因,公司对员工电脑的外网连接是有限制的,即:商业部门每个人上网都需要登录自己的网络账号。本身这个问题是不大的,但是令人烦恼的是,每当你有一段空当时间没有上网,这个登录就过期了...然后你会突然看到如下画面: uTools_1676353370711.png

其实这也没什么问题,再重新登录就好咯~但是作为一个极致懒惰的程序员,我不允许自己像个傻子一样:一天得打开那个登录页n多次,重复的输入用户名、密码,点击我同意什么什么协议,再点击一个登录button,再告诉我“哦亲爱的的垃圾前端,你可以上网了!”。这简直无法忍受!

1628686964686.jpg

于是我就思考了:能不能写一个程序能自己检测现在的外网连接,如果发现断网了,就自动帮我登录呢?

听起来是完全可行的,因为大致思路上的伪代码已经有了:写一个定时器,每间隔几秒钟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 ', '用户名', 'dyggod') .option('-p, --password ', '密码', 'password') .option('-t, --time

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就可以使用自己的账户去运行了。

到现在为止,我们的运行输出如下:

image.png

接下来,就是需要完成我们的登录逻辑,让这个脚本可以真正解决我的问题。那么首先,我们要去看登录内网的那个网站的请求构成是怎样的。我用控制台将网络设置为脱机,登录内网观察它的过程看到的情况:

image.png

哇喔哇喔哇喔,瞅瞅,瞅瞅我们看到了什么??php!

此时我的内心:

15684496873735.jpg

在我原有的思路中,是在login函数中发送ajax请求,将用户名、密码等参数发过去,实现登录的功能。但现在看起来不太可能了,因为众所周知,php是服务端语言,虽然有部分逻辑是运行在浏览器里的,但发送请求的时候其实是调用的服务端的资源脚本,如果我们直接发送请求,大概率会被服务器限制,因为所处的环境不一样,被服务端隔离限制。我用postman试一下,果然:

image.png

返回NOT Found,被限制了,至于想搞清楚它到底需要再携带什么头参数才能绕过限制,是很麻烦的,或者服务端干脆不允许任何外部请求也说不定。

并且我们看到它的pwd参数其实是做了加密的,这个加密逻辑在查看网络源代码的时候,可以看到:

image.png

其实也是很麻烦。

所以我决定不再使用请求去登录,而是:模拟页面登录!意思是我在登录函数中,直接启动一个模拟浏览器,像执行前端集成测试一样,自动输入用户名、密码、选中复选框,点击登录,岂不美哉?说干就干。

那么于是乎,登录函数就变成下下面这个样子:

```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是可以通过控制台找到的。

到这里,看起来似乎我已经成功了呢,那么,就来一发:

image.png

哇哦!大功告成,随着我故意断开网络连接,数秒后看到一闪而过几乎看不到的浏览器窗口划过,在我再次打开百度看到网页的时候,证明这个脚本确实已经可以解决我的问题了。

当然,只是登录一下还不能说明什么,不过我把这个程序运行了一天,也确实能证明它是在时刻监控并执行登录的。

至此,nodejs脚本版的自动登录工具就做完了。

不过...还是不太满意,因为这是个nodejs的脚本,如果拿给别人用,比如我们的UI大人,他的电脑不会装nodejs环境,难道还要人家为了运行这个脚本特意装一下?不行不行,不够优雅,我应该让他不用在意环境而直接使用。那么...似乎...可以把它做成一个桌面端应用软件,因为我们都是window系统,所以如果是一个.exe文件,那么自然就不用去装什么破烂环境啦!

15838963755668.jpg

于是乎,这个小小登录脚本衍生出了它的Electron版本,请大家看本篇的下章内容。