Why
疫情后来到公司被告知,以后每天的晚饭都需要自己提前一天预订了,不能像以前一样人人有份儿了。虽然不知道公司是出于什么方面考虑改成这样的,但是确实引起了很多不便。比如群里多了很多“要饭”的:

当然,在某个加班的夜晚我和我的小伙伴也因为没有提前订餐导致无法吃饭,只能默默的啃着自费面包度过。。。

俗话说得好,谁痛谁劳动!作为新时代的码农一枚,在能自动化的就不手动化,能智能化就不自动化的光辉思想照耀下,就想着能不能把订餐这事儿交给程序做呢?
答案当然是:能!
How
首先对订餐页面进行分析发现就是简单的一个form
表单而已嘛,然后依稀记得之前团队小伙伴技术分享的时候提到过谷歌的一个通过DevTools
协议控制headless Chrome
的Node
库 —— Puppeteer
,感觉有搞头诶。
Puppeteer
Puppeteer
类似其他框架,通过操作Browser
实例来操作浏览器作出相应的反应,看以下示例代码:
1 2 3 4 5 6 7 8 9 10
| const puppeteer = require('puppeteer');
(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://rennaiqian.com'); await page.screenshot({path: 'example.png'}); await page.pdf({path: 'example.pdf', format: 'A4'}); await browser.close(); })();
|
通过这些我们就能打开订餐页面了,但是页面表单中的数据如何输入呢?
1
| page.type('.field_2', name, {delay: 100});
|
可以使用page
实例上的方法type
来完成,type
的三个参数定义分别为:
- 选择器,和jQuery中的选择器语法一致;
- 向输入域中赋的值;
- 一些其他选项,可空。
delay: 100
表示每次输入之间间隔 100
毫秒;
有了这些准备,接下来开搞!
订餐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| const puppeteer = require('puppeteer'); const utils = require('./utils');
async function order(name, jobNo) { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); await page.goto('https://xxxxxxxxx.xxxxxx/xxxxxx'); await page.type('.field_2', name, {delay: 100}); await page.type('.field_6', jobNo, {delay: 100}); const tomorrowStr = utils.getTomorrowStr(); await page.type('.ant-picker-input > input', tomorrowStr); await page.click('.field_5 div:nth-child(5)'); await page.click('.published-form__footer-buttons > button'); await page.waitForNavigation(); const clip = await page.evaluate(() => { let { x, y, width, height } = document.querySelector('.code-info').getBoundingClientRect(); return { x, y, width, height }; }); await page.waitFor(1000); const fileName = `${name}_${tomorrowStr}`; await page.screenshot({ path: `./screenshot/${fileName}.png`, clip }); await page.waitFor(1000); browser.close(); return fileName; }
module.exports = { order, }
|
通过上述代码即可将订餐码保存到本地,但是还是不太方便,每天都要从电脑上发送给手机,于是想到了钉钉机器人。
订餐成功通知
通过查看API发现,如果发送图片的话,一定得是图片的在线链接,怎么把本地图片转换成在线链接呢? 买个阿里云吧!嗯!这样可以把图片传到OSS从而生成在线链接了,好在我已经有阿里云了,这样想来一下省了1000多块钱,好赚!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const OSS = require('ali-oss'); const fs = require('fs');
async function uploadToOSS(fileName) { const client = new OSS({ region: 'oss-cn-beijing', accessKeyId: 'accessKeyId', accessKeySecret: 'accessKeySecret', bucket: 'roc-auto-order', }); const path = `./screenshot/${fileName}.png`; const result = await client.putStream(`${fileName}.png`, fs.createReadStream(path)); return client.signatureUrl(result.name, { expires: 604800 }); }
module.exports = { uploadToOSS };
|
直接调用ali-oss
完成图片上传并获取到图片链接,给下一步钉钉推送使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const request = require('request');
function pushDingding(fileName, picUrl){ const url = 'https://oapi.dingtalk.com/robot/send?access_token=ACCESS_TOKEN'; const data = { "msgtype": "markdown", "markdown": { "title":"订餐成功", "text": `### ${fileName} 订餐成功 \n> \n> ` }, "at": { "isAtAll": false } };
return new Promise(function(resolve, reject) { request({ url: url, method: "POST", json: true, headers: { "content-type": "application/json", }, body: data }, function(error, response, body) { if (!error && response.statusCode == 200) { resolve(); } }); }); };
module.exports = { pushDingding, }
|
定时调度
经过以上操作后已经可以实现在执行的时候自动下单了,但是我想实现每天中午十二点都下单,不用我操作,怎么处理呢,以前做java的时候有定时跑批任务,nodejs应该也有的吧,去npm上找了一下发现有个node-schedule
,非常符合需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const schedule = require('node-schedule'); const autoOrder = require('./auto-order'); const fileUpload = require('./file-upload'); const messagePush = require('./message-push'); const config = require('./config'); const utils = require('./utils');
async function doTask() { for(const one of config.personList) { const fileName = await autoOrder.order(one.name, one.jobNo); const picUrl = await fileUpload.uploadToOSS(fileName); messagePush.pushDingding(fileName, picUrl); } }
function scheduleObjectLiteralSyntax() { schedule.scheduleJob('0 30 12 * * 1-5', () => { utils.delDir('../screenshot'); doTask(); }); }
scheduleObjectLiteralSyntax();
|
最后
最后,使用 pm2
启动上面的文件即可。
成果
