likes
comments
collection
share

【Node.js】“寓教于乐”的学习记录

作者站长头像
站长
· 阅读数 68

「本文正在参与技术专题征文Node.js进阶之路,点击查看详情

前言

完成对Node.js的从了解到熟练的进阶这个Flag设立已久,久到去年就有它了。惊蛰已过,风暖云开,隔年的Flag是时候拿出来实现了。出去踏青or在家码字,我决定选择后者。

至少对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.js】“寓教于乐”的学习记录

其中

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

【Node.js】“寓教于乐”的学习记录

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

【Node.js】“寓教于乐”的学习记录

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

小结

  • Nodemailer 是一个简单易用的 Node.JS 邮件发送模块(通过 SMTP,sendmail,或者 Amazon SES),支持 unicode,可以使用任何你喜欢的字符集。实际开发中定制化更强一些,这个我还有待后续的继续探索;
  • 参考文章如何使用nodejs自动发送邮件? 这篇文章中详细列出了每个字段的含义和用法,文末还列出了开源的邮件模板,很值得一看。

耳闻已久的定时任务

node定时任务的模块node-schedule,可以帮助实现定时任务功能。很多业务场景需要执行定时任务,比如每个月末发给用户的账单邮件、每隔两天跑一次销售数据等。

Node Schedule是适用于 Node.js 的灵活的类似 cron 且不类似 cron 的作业调度程序。它允许您安排作业(任意函数)以在特定日期执行,并带有可选的重复规则。它在任何给定时间只使用一个计时器(而不是每秒/分钟重新评估即将到来的工作)。

有趣的Cron风格

我看到Cron官网给出的格式,怎么有星号又有数字和字母呢?真是有趣。

【Node.js】“寓教于乐”的学习记录

再看每个位置的详细解释,不难发现Cron提供的字段几乎涵盖了每一个时间点。

字段名允许值允许的特殊字符
秒(Seconds)0~59, - * /
分(Minutes)0-59, - * /
小时(Hours)0-23* / , -
日期(Day of month)1-31* / , - ?
月份(Month)1-12 or JAN-DEC* / , -
星期(Day of week)0-6 or SUN-SAT* / , - ?

注意:月份和星期几字段值不区分大小写。“SUN”、“Sun”和“sun”同样被接受。

Cron表达式

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: 

Seconds Minutes Hours DayofMonth Month DayofWeek YearSeconds 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.js】“寓教于乐”的学习记录

除了使用数字和星号等字符设置定时时间,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.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: '北京市朝阳区' }

输出结果

【Node.js】“寓教于乐”的学习记录

小结

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

总结

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

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

参考文章

转载自:https://juejin.cn/post/7076259680011419655
评论
请登录