携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情
上次我们封装了配置,请求等方法,这次我们做一下机器人事件通知和数据库连接。
事件通知
我们在src/utils/
下创建message
文件夹,并新建index.js
文件(上节已创建)。
然后在创建ding-bot
、feishu-bot
、wx-bot
文件。
如果感觉枯燥,可以直接跳过,重点关注加签即可。
钉钉机器人
const axios = require("axios");
const crypto = require("crypto");
const dayjs = require("dayjs");
const defaultOptions = {
msgtype: "text",
text: {
content: "hello~",
},
};
class DingBot {
constructor(options = {}) {
this.text = "";
this.webhook = options.webhook;
this.secret = options.secret;
const timestamp = new Date().getTime();
const sign = this.signFn(this.secret, `${timestamp}\n${this.secret}`);
this.allWebhookUrl = `${this.webhook}×tamp=${timestamp}&sign=${sign}`;
}
signFn(secret, content) {
// 加签
const str = crypto
.createHmac("sha256", secret)
.update(content)
.digest()
.toString("base64");
return encodeURIComponent(str);
}
send(data = defaultOptions) {
let p;
// 没有这两个参数则静默失败
if (!this.webhook || !this.secret) {
p = Promise.resolve({
errcode: -1,
errmsg: "webhook和secret不能为空",
});
} else {
p = axios({
url: this.allWebhookUrl,
method: "POST",
data,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
}).then((res) => {
return res.data;
});
}
return p;
}
sendMessage(msg) {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.text += `- ${dayjs().format("HH:mm:ss")} ${msg}\n`;
this.timer = setTimeout(() => {
this.send({
msgtype: "markdown",
markdown: {
title: "Push标题",
text: this.text,
},
}).then(() => {
this.text = "";
});
}, 1000);
}
}
module.exports = DingBot;
飞书机器人
const axios = require("axios");
const crypto = require("crypto");
const dayjs = require("dayjs");
class FeishuBot {
constructor(options = {}) {
this.text = "";
this.webhook = options.webhook;
this.secret = options.secret;
// 飞书官方文档描述不清楚,这里的 timestamp 应该精确到秒
this.timestamp = ~~(Date.now() / 1000);
this.sign = this.signFn(`${this.timestamp}\n${this.secret}`);
}
signFn(content) {
// 加签
return (
crypto
// 加密一次即可
.createHmac("sha256", content)
.digest()
.toString("base64")
);
}
send(data) {
let p;
// 没有这两个参数则静默失败
if (!this.webhook || !this.secret) {
p = Promise.resolve({
errcode: -1,
errmsg: "webhook和secret不能为空",
});
} else {
p = axios({
url: this.webhook,
method: "POST",
data,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
}).then((res) => {
return res.data;
});
}
return p;
}
sendMessage(msg) {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.text += `${dayjs().format("HH:mm:ss")} ${msg}\n`;
this.timer = setTimeout(() => {
this.send({
timestamp: this.timestamp,
sign: this.sign,
msg_type: "text",
content: {
text: this.text,
},
}).then(() => {
this.text = "";
});
}, 1000);
}
}
module.exports = FeishuBot;
企业微信机器人
const https = require("https");
const config = require("../../config");
function getToken({ id, secret }) {
return new Promise((resolve, reject) => {
const option = {
hostname: "qyapi.weixin.qq.com",
path: `/cgi-bin/gettoken?corpid=${id}&corpsecret=${secret}`,
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const req = https.request(option, (res) => {
const datas = [];
let size = 0;
res.on("data", (d) => {
datas.push(d);
size += d.length;
});
res.on("end", function () {
const buff = Buffer.concat(datas, size);
let result = buff.toString();
if (result) {
result = JSON.parse(result);
if (result.errcode == 0) {
console.log(`获取 accessToken 成功`);
resolve(result.access_token);
} else {
reject(result.errmsg || "获取 accessToken 失败");
}
} else {
reject("获取 accessToken 失败");
}
});
});
req.on("error", (error) => {
reject(error);
});
req.end();
});
}
function send({ agentId, touser = "@all", msgData, accessToken }) {
return new Promise((resolve, reject) => {
console.log("发送企业微信通知...");
data = new TextEncoder().encode(
JSON.stringify({
touser,
agentid: agentId,
duplicate_check_interval: 600,
...msgData,
})
);
const option = {
hostname: "qyapi.weixin.qq.com",
path: `/cgi-bin/message/send?access_token=${accessToken}`,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": data.length,
},
};
const req = https.request(option, (res) => {
const datas = [];
let size = 0;
res.on("data", (d) => {
datas.push(d);
size += d.length;
});
res.on("end", function () {
const buff = Buffer.concat(datas, size);
let result = buff.toString();
if (result) {
result = JSON.parse(result);
if (result.errcode === 0) {
console.log("发送通知成功");
resolve();
} else {
reject(result.errmsg || "发送失败");
}
}
});
});
req.on("error", (error) => {
reject(error);
});
req.write(data);
req.end();
});
}
async function WXWorkNotify({ id, secret, agentId, touser, msgData }) {
try {
const accessToken = await getToken({ id, secret });
await send({
agentId,
touser,
msgData,
accessToken,
});
} catch (error) {
console.log(`发送失败 => ${error}`);
}
}
let msg = "\n";
let timer = "";
const wxWorkBot = (message) => {
if (config.WX_COMPANY_ID && config.WX_APP_ID && config.WX_APP_SECRET) {
msg += message + "\n";
timer && clearTimeout(timer);
timer = setTimeout(function () {
WXWorkNotify({
id: config.WX_COMPANY_ID, // 企业 ID
agentId: config.WX_APP_ID, // 应用 ID
secret: config.WX_APP_SECRET, // 应用 secret
msgData: {
msgtype: "text",
text: {
content: msg,
},
},
});
}, 500);
}
};
module.exports = wxWorkBot;
连接数据库
通常情况下插入数据库的动作较多,我们暂时先封装一个insert
const mysql = require("mysql");
const config = require("../config");
const connection = mysql.createConnection(config.mysql);
connection.connect();
function insert(table, data) {
return new Promise((resolve, reject) => {
connection.query(
"INSERT INTO " + table + " SET ?",
data,
function (error, results, fields) {
if (error) reject(error);
resolve(results);
}
);
});
}
module.exports = {
insert,
};
fs操作相关
通常情况下,我们会保存文件到本地,为了防止重复创建文件或路径,我们需要一些路径判断方法。
const fs = require("fs");
const path = require("path");
/**
* @description: 读取路径信息,判断路径是否存在
* @param { string } path
* @return {Promise().then(false|stats)}
*/
function isHasPath(path) {
return new Promise((resolve, reject) => {
fs.stat(path, (err, stats) => {
if (err) {
resolve(false);
} else {
resolve(stats);
}
});
});
}
/**
* @description: 创建路径
* @param { string } dir 路径
* @return {Promise().then(true|false)}
*/
function mkdir(dir) {
return new Promise((resolve, reject) => {
fs.mkdir(dir, (err) => {
if (err) {
resolve(false);
} else {
resolve(true);
}
});
});
}
/**
* @description: 判断路径是否存在,如果不存在则创建
* @param { string } dir
* @return {boolean}
*/
async function dirExists(dir) {
let isExists = await getStat(dir);
if (isExists && isExists.isDirectory()) {
return true;
}
//如果该路径不存在
let tempDir = path.parse(dir).dir; //拿到上级路径
//递归判断,如果上级目录也不存在,则会代码会在此处继续循环执行,直到目录存在
let status = await dirExists(tempDir);
let mkdirStatus;
if (status) {
// 如果路径存在则创建文件夹
mkdirStatus = await mkdir(dir);
}
return mkdirStatus;
}
module.exports = {
isHasPath,
mkdir,
dirExists,
};
总结
本节课,我们封装了Push常用事件,数据库连接事件,文件操作事件等,爬虫基础框架完成了5%了,是否跟我一样很开心。一行又一行,10分钟搞定。
如果喜欢我的文章,麻烦点个赞,评个论,收个藏,关个注。
手绘图,手打字,纯原创,摘自未发布的书籍:《高阶前端指北》,转载请获得本人同意。
如果喜欢我的文章,麻烦点个赞,评个论,收个藏,关个注。
手绘图,手打字,纯原创,摘自未发布的书籍:《高阶前端指北》,转载请获得本人同意。