前言
完成对 Node.js 的从了解到熟练的进阶这个 Flag 设立已久,久到去年就有它了。惊蛰已过,风暖云开,隔年的 Flag 是时候拿出来实现了。出去踏青 or 在家码字,我决定选择后者。
至少对 Node.js 的探索,今年能有一个完美的叹号。
目标明确
我个人更喜欢边学边实际编写功能,但是限于对 Node 的接触较少,所以我转而求助于掘金大佬的文章,这些优秀的文章中有些是写具体功能实现。于是我便开启一段欢乐的学习之旅。参考文章已放在文末,主要参考的掘金大佬是徐小夕,他有很多关于 node.js 开发的项目的文章,无论是文章内容还是编程思维都非常赞。
本篇文章的主要目的是记录我学习中的一些收获以及遇到问题的解决方案。
随波逐流无归处,乘风破浪济沧海
欢乐的小例子们
发送邮件功能
功能实现
使用 node 提供的 Nodemailer,30 行左右代码即可实现发送邮件的功能。发送邮件功能,需要填写发送人邮箱、发送人邮箱授权码、发送人邮箱的主机地址和端口号、收件人邮箱等信息。如果需要添加附件,需要填写附件名称和附件的资源地址。
'use strict';
const nodemailer = require('nodemailer');
// async..await is not allowed in global scope, must use a wrapper
async function main() {
let user = '实际发送人的邮箱';
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: 'smtp.qq.com',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: user, // generated ethereal user
pass: '发送人邮箱授权码', // generated ethereal password
},
});
return await transporter.sendMail({
from: `"叶一一🐇" <${user}>`, // sender address
to: 'xxjsds2010@163.com', // list of receivers
subject: 'Hello World', // Subject line
text: 'Hello World', // plain text body
html: `你好:<b>年轻人</b>`, // html body
attachments: [
{
filename: '第一个.doc',
path: 'https://xxx.doc',
},
], // Add Attachments to messages
});
}
main().catch(console.error);
复制代码
收件人邮箱接收到的邮件信息
其中
小结
耳闻已久的定时任务
node 定时任务的模块 node-schedule,可以帮助实现定时任务功能。很多业务场景需要执行定时任务,比如每个月末发给用户的账单邮件、每隔两天跑一次销售数据等。
Node Schedule是适用于 Node.js 的灵活的类似 cron 且不类似 cron 的作业调度程序。它允许您安排作业(任意函数)以在特定日期执行,并带有可选的重复规则。它在任何给定时间只使用一个计时器(而不是每秒/分钟重新评估即将到来的工作)。
有趣的 Cron 风格
我看到Cron官网给出的格式,怎么有星号又有数字和字母呢?真是有趣。
再看每个位置的详细解释,不难发现 Cron 提供的字段几乎涵盖了每一个时间点。
注意:月份和星期几字段值不区分大小写。“SUN”、“Sun”和“sun”同样被接受。
Cron 表达式
Cron 表达式是一个字符串,字符串以 5 或 6 个空格隔开,分为 6 或 7 个域,每一个域代表一个含义,Cron 有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year或
Seconds Minutes Hours DayofMonth Month DayofWeek
复制代码
使用 node-schedule 实现的定时任务
const schedule = require('node-schedule');
const sendEMail = require('./sendEmail');
/**
* 定时任务
*/
const creatSchedule = timePoint => {
let count = 1;
const all = schedule.scheduleJob(timePoint, () => {
console.log('发送邮件:' + new Date());
// 发送邮件
sendEMail.main();
count++;
});
setTimeout(() => {
console.log('取消定时任务:' + new Date());
all.cancel();
}, 180000);
};
let timePoint = '10 * * * * *';
creatSchedule(timePoint);
复制代码
打印结果
除了使用数字和星号等字符设置定时时间,node-schedule 还提供了自定义规则,可以根据自定义规则,更直观的设置定时时间。
second (0-59)
minute (0-59)
hour (0-23)
date (1-31)
month (0-11)
year
dayOfWeek (0-6) Starting with Sunday
tz
const schedule = require('node-schedule');
const sendEMail = require('./sendEmail');
/**
* 定时任务
*/
const creatSchedule = timePoint => {
let count = 1;
const all = schedule.scheduleJob(timePoint, () => {
console.log('发送邮件:' + new Date());
// 发送邮件
sendEMail.main();
count++;
});
setTimeout(() => {
console.log('取消定时任务:' + new Date());
all.cancel();
}, 180000);
};
let timePoint = new schedule.RecurrenceRule();
timePoint.second = 11;
creatSchedule(timePoint);
复制代码
打印结果
小结
掌握了基础的新增定时任务;
也尝试了取消定时任务;
实际业务情况会更复杂一些,更多乐趣有待探索
官网的讲解还是比较详细的,有疑问可以查看官网
文件拷贝
node.js 的学习必然绕不开模块的学习,但是 node.js 提供了大量的模块,如何在项目中应用它们显然也是衡量对 node 掌握程度的一个标杆。
如果想在我们的项目中进行文件操作,那么 fs 模块需要熟练掌握。
fs 模块有大量的 API,详细的介绍可以参考官网。这里我主要尝试文件拷贝功能。
初次见面的 buffer
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在处理像 TCP 流或文件流时,必须使用到二进制数据。因此在 Node.js 中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
Buffer 对象用于表示固定长度的字节序列。 许多 Node.js API 都支持 Buffer。
Buffer 类是 JavaScript Uint8Array 类的子类,并使用涵盖额外用例的方法对其进行扩展。 Node.js API 在支持 Buffer 的地方也接受普通的 Uint8Array。
虽然 Buffer 类在全局作用域内可用,但仍然建议通过 import 或 require 语句显式地引用它。
const buffer = require('buffer');
// 创建长度为 10 的以零填充的缓冲区。
const buf1 = buffer.Buffer.alloc(10);
// 创建长度为 10 的缓冲区,
// 使用值为 `1` 的字节填充。
const buf2 = buffer.Buffer.alloc(10, 1);
复制代码
多次读取实现文件拷贝
buffer 开辟缓冲区,每次读取和写入的都是缓冲区的数据;
fs.open 异步地打开需要操作的文件;
fs.read 读取源文件;
fs.write 写入目标文件;
当没有需要读取的数据之后,关闭源文件和目标文件,同步缓冲区;
注意,多次读取方法 next 是循环调用的,所以当没有需要读取的数据的时候,需要通过 return 中止 next 方法的继续执行;
const fs = require('fs');
const buffer = require('buffer');
/**
* 多次读取实现文件拷贝
* @param {string} initFile 源文件路径
* @param {string} copyFile 目标文件路径
* @param {number} bufSize 缓冲区的大小
*/
function copyFunc(initFile, copyFile, bufSize) {
// 打开源文件
fs.open(initFile, 'r', (err, readFd) => {
// 打开目标文件
fs.open(copyFile, 'w', (err, writeFd) => {
let buf = buffer.Buffer.alloc(bufSize); // 创建一个空的缓冲区,大小为size的值
let readFlag = 0; // 下次读取的源文件的位置
let writeFlag = 0; // 下次写入的目标文件的位置
(function next() {
// 读取源文件
fs.read(readFd, buf, 0, bufSize, readFlag, (err, bytesRead) => {
readFlag += bytesRead;
// 如果源文件没有可复制内容则关闭源文件
if (!bytesRead) fs.close(readFd, err => console.log('拷贝完成,关闭源文件'));
// 写入目标文件
fs.write(writeFd, buf, 0, bytesRead, writeFlag, (err, bytesWritten) => {
// 如果源文件没有可复制内容同步缓冲区关闭目标文件
if (!bytesWritten) {
fs.fsync(writeFd, err => {
console.log('拷贝完成,同步缓存');
fs.close(writeFd, err => {
console.log('拷贝完成,关闭目标文件');
});
});
return; // 关闭next函数的执行
}
writeFlag += bytesWritten;
// 继续读取、写入
next();
});
});
})();
});
});
}
// buffer 的长度
const bufSize = 20;
copyFunc('./files/init.txt', './files/copy.txt', bufSize);
复制代码
小结
不再神秘的加密与解密
node.js 的crypto模块提供了加密功能,其中包括了用于 OpenSSL 散列、HMAC、加密、解密、签名、以及验证的函数的一整套封装。
一个小例子
crypto 提供了丰富的 API,下面是使用这些 API 实现数据加密的一个小例子。
const crypto = require('crypto');
const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret).update('I love crypto').digest('hex');
console.log(hash);
// 打印:
// 4a3c854f9e6a829b561f24a110f79e3ea84aa00dcd74cfd1aa099baa42be426d
复制代码
Cipher 类
Cipher 类的实例用于加密数据。 可以通过以下两种方式之一使用该类:
crypto.createCipher() 或 crypto.createCipheriv() 方法用于创建 Cipher 实例。 Cipher 对象不能直接使用 new 关键字创建。
实现加密和解密功能
既然介绍了 Cipher 类,那么我接下来实现加密主要使用 Cipher 类的实例进行数据的加密和解密。
const crypto = require('crypto');
// 加密方法
function encryptFun(data, key, iv) {
// 使用给定的 algorithm、key 和初始化向量(iv)创建并返回 Cipher 对象。
const cipher = crypto.createCipheriv('aes-192-cbc', key, iv);
// 数据的编码:utf8,返回值的编码:hex,十六进制
// 第三个参数指定加密数据的输出格式
var crypted = cipher.update(data, 'utf8', 'hex');
crypted += cipher.final('hex');
return crypted;
}
// 解密方法
function decryptFun(data, key, iv) {
// 使用给定的 algorithm、key 和初始化向量(iv)创建并返回 Cipher 对象。
const decipher = crypto.createDecipheriv('aes-192-cbc', key, iv);
// 数据的编码:utf8,返回值的编码:hex,十六进制
// 第三个参数指定加密数据的输出格式
var decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// 加密对象
const params = {
name: '张三',
address: '北京市朝阳区',
};
// 初始化向量(iv)16位16进制的数
// crypto.randomBytes:生成加密强伪随机数据。 size 参数是数字,指示要生成的字节数。
const IV = crypto.randomBytes(16);
// 需要加解密的内容
let data = JSON.stringify(params);
// 24位秘钥密钥
let key = 'abcdabcdabcdabcdabcdabcd';
let encrypt = encryptFun(data, key, IV);
let decrypt = decryptFun(encrypt, key, IV);
decrypt = JSON.parse(decrypt);
console.log(encrypt);
console.log(decrypt);
// 打印encrypt:
// 8471246715b5d508c2782418693eee1998970768aa826c03a1935a660862449cf4174646a56c7b6f8295493981c6fcfa2fa811273b982702b5b68b2ea0250a1f
// 打印decrypt:
// { name: '张三', address: '北京市朝阳区' }
复制代码
输出结果
小结
总结
在学习一门新的技术的时候,如果发现自己通过文档学习无法达到实际功能开发的程度的时候,建议在学习之后,做一些小功能辅助练习和应用学到的知识点。
康肃问曰:“汝亦知射乎?吾射不亦精乎?”翁曰:“无他,但手熟尔。”康肃忿然曰:“尔安敢轻吾射!”翁曰:“以我酌油知之。”乃取一葫芦置于地,以钱覆其口,徐以杓酌油沥之,自钱孔入,而钱不湿。因曰:“我亦无他,惟手熟尔。
参考文章
评论