写点什么

「趣学前端」“寓教于乐”的 Node.js 学习记录

作者:叶一一
  • 2022 年 9 月 13 日
    北京
  • 本文字数:5565 字

    阅读完需:约 18 分钟

「趣学前端」“寓教于乐”的Node.js学习记录

前言

完成对 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 wrapperasync 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);
复制代码


收件人邮箱接收到的邮件信息


其中


  • 发送人邮箱授权码,在邮箱的设置中查看,以 qq 邮箱为例,在邮箱设置->账户中,POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务选项卡下,点击生成授权码,通过短信验证之后就可以获取授权码;也可以看腾讯提供的官方文档有很详细的获取流程。


  • 发送人邮箱的主机地址和端口号,以 qq 邮箱为例,我是在帮助中心里搜到的,主机地址 smtp.qq.com,端口 465 或 587


  • 可以通过设置 attachments 为邮件添加附件,支持自定义附件名称和附件的资源地址

小结

  • Nodemailer 是一个简单易用的 Node.JS 邮件发送模块(通过 SMTP,sendmail,或者 Amazon SES),支持 unicode,可以使用任何你喜欢的字符集。实际开发中定制化更强一些,这个我还有待后续的继续探索;

  • 参考文章如何使用nodejs自动发送邮件? 这篇文章中详细列出了每个字段的含义和用法,文末还列出了开源的邮件模板,很值得一看。

耳闻已久的定时任务

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 实现的定时任务

  • 每分钟的第 10 秒发送一次邮件,发送邮件的功能我们前面已经实现了;

  • 当邮件发送三次之后,取消定时任务;

  • 尝试更多定时任务的时间设置;

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 还提供了自定义规则,可以根据自定义规则,更直观的设置定时时间。


  • 新增 schedule.RecurrenceRule 的实例 timePoint;

  • RecurrenceRule 属性包括


second (0-59)

minute (0-59)

hour (0-23)

date (1-31)

month (0-11)

year

dayOfWeek (0-6) Starting with Sunday

tz


  • 每分钟的第 11 秒发送一次邮件,其他设置更上面的一致;


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);
复制代码

小结

  • 了解了 buffer 知识点,这个是额外收获;

  • 通过实际的功能实现,加深了 fs 关于 read 和 write 两个 API 的认知;

  • 文件 I/O 是较为基础的知识内容,在前端日常开发中挺少见的,这次简单的实现了一个小功能,算是自己前进的一小步。

不再神秘的加密与解密

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 类的实例用于加密数据。 可以通过以下两种方式之一使用该类:


  • 作为既可读又可写的,其中写入未加密的纯数据以在可读端生成加密的数据,或

  • 使用 cipher.update()cipher.final() 方法生成加密的数据。


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: '北京市朝阳区' }
复制代码


输出结果

小结

  • 加密一个很好的应用是在 HTTP 或 HTTPS 连接过程中,封装数据传输的安全方法。可以根据内部自定义的 key,进行数据传输中的信息加密。

总结

在学习一门新的技术的时候,如果发现自己通过文档学习无法达到实际功能开发的程度的时候,建议在学习之后,做一些小功能辅助练习和应用学到的知识点。


康肃问曰:“汝亦知射乎?吾射不亦精乎?”翁曰:“无他,但手熟尔。”康肃忿然曰:“尔安敢轻吾射!”翁曰:“以我酌油知之。”乃取一葫芦置于地,以钱覆其口,徐以杓酌油沥之,自钱孔入,而钱不湿。因曰:“我亦无他,惟手熟尔。

参考文章

发布于: 刚刚阅读数: 4
用户头像

叶一一

关注

苍生涂涂,天下缭燎,诸子百家,唯我纵横。 2022.09.01 加入

非职业传道受业解惑前端程序媛,华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。

评论

发布
暂无评论
「趣学前端」“寓教于乐”的Node.js学习记录_node.js_叶一一_InfoQ写作社区