前言
我个人更喜欢边学边实际编写功能,但是限于对Node的接触较少,所以我转而求助于大佬的文章,这些优秀的文章中有些是写具体功能实现。于是我便开启一段欢乐的学习之旅。参考文章已放在文末,主要参考的掘金大佬是徐小夕,他有很多关于node.js开发的项目的文章,无论是文章内容还是编程思维都非常赞。
本篇文章的主要目的是记录我学习中的一些收获以及遇到问题的解决方案。
随波逐流无归处,乘风破浪济沧海
欢乐的小例子们
发送邮件功能
功能实现
使用node提供的Nodemailer,30行左右代码即可实现发送邮件的功能。发送邮件功能,需要填写发送人邮箱、发送人邮箱授权码、发送人邮箱的主机地址和端口号、收件人邮箱等信息。如果需要添加附件,需要填写附件名称和附件的资源地址。
'use strict'; constnodemailer=require('nodemailer'); // async..await is not allowed in global scope, must use a wrapperasyncfunctionmain() { letuser='实际发送人的邮箱'; // create reusable transporter object using the default SMTP transportlettransporter=nodemailer.createTransport({ host: 'smtp.qq.com', port: 587, secure: false, // true for 465, false for other portsauth: { user: user, // generated ethereal userpass: '发送人邮箱授权码', // generated ethereal password }, }); returnawaittransporter.sendMail({ from: `"叶一一🐇" <${user}>`, // sender addressto: 'xxjsds2010@163.com', // list of receiverssubject: 'Hello World', // Subject linetext: 'Hello World', // plain text bodyhtml: `你好:<b>年轻人</b>`, // html bodyattachments: [ { 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提供的字段几乎涵盖了每一个时间点。
字段名 |
允许值 |
允许的特殊字符 |
秒(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有如下两种语法格式:
SecondsMinutesHoursDayofMonthMonthDayofWeekYear或SecondsMinutesHoursDayofMonthMonthDayofWeek
使用node-schedule实现的定时任务
- 每分钟的第10秒发送一次邮件,发送邮件的功能我们前面已经实现了;
- 当邮件发送三次之后,取消定时任务;
- 尝试更多定时任务的时间设置;
constschedule=require('node-schedule'); constsendEMail=require('./sendEmail'); /** * 定时任务 */constcreatSchedule=timePoint=> { letcount=1; constall=schedule.scheduleJob(timePoint, () => { console.log('发送邮件:'+newDate()); // 发送邮件sendEMail.main(); count++; }); setTimeout(() => { console.log('取消定时任务:'+newDate()); all.cancel(); }, 180000); }; lettimePoint='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秒发送一次邮件,其他设置更上面的一致;
constschedule=require('node-schedule'); constsendEMail=require('./sendEmail'); /** * 定时任务 */constcreatSchedule=timePoint=> { letcount=1; constall=schedule.scheduleJob(timePoint, () => { console.log('发送邮件:'+newDate()); // 发送邮件sendEMail.main(); count++; }); setTimeout(() => { console.log('取消定时任务:'+newDate()); all.cancel(); }, 180000); }; lettimePoint=newschedule.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 语句显式地引用它。
constbuffer=require('buffer'); // 创建长度为 10 的以零填充的缓冲区。constbuf1=buffer.Buffer.alloc(10); // 创建长度为 10 的缓冲区,// 使用值为 `1` 的字节填充。constbuf2=buffer.Buffer.alloc(10, 1);
多次读取实现文件拷贝
- buffer开辟缓冲区,每次读取和写入的都是缓冲区的数据;
- fs.open异步地打开需要操作的文件;
- fs.read读取源文件;
- fs.write写入目标文件;
- 当没有需要读取的数据之后,关闭源文件和目标文件,同步缓冲区;
- 注意,多次读取方法next是循环调用的,所以当没有需要读取的数据的时候,需要通过return中止next方法的继续执行;
constfs=require('fs'); constbuffer=require('buffer'); /** * 多次读取实现文件拷贝 * @param {string} initFile 源文件路径 * @param {string} copyFile 目标文件路径 * @param {number} bufSize 缓冲区的大小 */functioncopyFunc(initFile, copyFile, bufSize) { // 打开源文件fs.open(initFile, 'r', (err, readFd) => { // 打开目标文件fs.open(copyFile, 'w', (err, writeFd) => { letbuf=buffer.Buffer.alloc(bufSize); // 创建一个空的缓冲区,大小为size的值letreadFlag=0; // 下次读取的源文件的位置letwriteFlag=0; // 下次写入的目标文件的位置 (functionnext() { // 读取源文件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 的长度constbufSize=20; copyFunc('./files/init.txt', './files/copy.txt', bufSize);
小结
- 了解了buffer知识点,这个是额外收获;
- 通过实际的功能实现,加深了fs关于read和write两个API的认知;
- 文件I/O是较为基础的知识内容,在前端日常开发中挺少见的,这次简单的实现了一个小功能,算是自己前进的一小步。
总结
在学习一门新的技术的时候,如果发现自己通过文档学习无法达到实际功能开发的程度的时候,建议在学习之后,做一些小功能辅助练习和应用学到的知识点。
参考文章