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

举报
叶一一 发表于 2023/02/14 20:07:03 2023/02/14
【摘要】 用技术实现梦想。今天分享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);

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

image.jpeg

其中

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

image.jpeg

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

image.jpeg

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


小结

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


耳闻已久的定时任务

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

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


有趣的Cron风格

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


image.jpeg

再看每个位置的详细解释,不难发现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 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);

打印结果

image.jpeg

除了使用数字和星号等字符设置定时时间,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);

打印结果

image.jpeg

小结

  • 掌握了基础的新增定时任务;
  • 也尝试了取消定时任务;
  • 实际业务情况会更复杂一些,更多乐趣有待探索
  • 官网的讲解还是比较详细的,有疑问可以查看官网


文件拷贝

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是较为基础的知识内容,在前端日常开发中挺少见的,这次简单的实现了一个小功能,算是自己前进的一小步。


总结

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

作者:非职业「传道授业解惑」的开发者叶一一
简介:「趣学前端」、「CSS畅想」系列作者,华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。