zoukankan      html  css  js  c++  java
  • nodejs后台邮件服务器原理详解

    一、发送邮件

      单一的node后台其实本身并没有发送邮件的功能,要想实现发送邮件的效果,还是需要借助一个邮箱来实现邮件的发送。

      流程:前端提出发送需求 —— Node后台收集需要发送的信息 —— 发送给邮箱服务器来进行发送

      在node后台 —— 邮箱后台的这个过程中,遵循了SMTP协议(SMTP(Simple Mail Transfer Protocol)简单邮件传输协议)。这个协议来控制邮件的中转方式,用于因特网中邮件服务器之间交换邮件。

    二、接收邮件

      后台接收邮件其实就是把邮箱已经接受的邮件获取到node后台。

      流程:node后台请求邮件 —— 邮箱根据请求的情况返回对应的邮件

      在邮箱收到邮件 —— 将信息获取到后台的过程中,遵循pop3或imap协议。

    1、pop3协议

      POP3协议允许电子邮件客户端下载服务器上的邮件,但是在客户端的操作(如移动邮件、删除邮件、标记已读等),不会反馈到服务器上。比如通过客户端收取了邮箱中的3封邮件并移动到其他文件夹,邮箱服务器上的这些邮件是没有同时被移动的。也就是说POP3协议实际上是下载了一份邮件的副本到本地邮件客户端,而且对本地邮件副本的操作只会影响本地数据。多个邮件客户端里面的邮件的状态可能会不一致。

    2、imap协议

      IMAP也是提供面向用户的邮件收取服务。与POP3不同的是,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上。

      比如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。换句话说,IMAP把远程文件夹当成本地文件夹来操作,它们之间类似于双向同步。这样的好处是,当你在多个邮件客户端看见的邮件的状态是一致的。

    三、实现详解

    1、第三方模块

      在node的生态中,实现这样的功能有很多的第三方模块。比如:

    • nodemailer
    • node-red
    • node-imap

      大家可以在npm上搜索并查看文档进行开发。

      下面我们使用nodemailer、imap这两个模块进行讲解,使用的邮箱是qq邮箱。

    2、发送邮件(需要使用nodemailer模块)

      一共分为三步:创建连接 - 消息体 - 发送消息对象

    1. 使用SMTP或其他传输机制创建Nodemailer传输器
    2. 设置消息选项(谁向谁发送消息)
    3. 使用先前创建的传输程序的sendMail()方法传递消息对象
    let transporter = nodemailer.createTransport({
            host: 'smtp.qq.com',
            port: 465,
            secure: true, // true for 465, false for other ports
            auth: {
                user: "xxx@qq.com", // generated ethereal user
                pass: '邮箱的验证码'// generated ethereal password
            }
        });
    //创建Nodemailer传输器
    
    let mailOptions = {
            from: '"khy" <xxx@qq.com>', // sender address
            to: 'xxx@qq.com', // list of receivers
            subject: 'Hello', // Subject line
            text: 'Hello world?', // plain text body
            html: '<b>Hello world?</b>' // html body
        };
    //设置消息选项
    
    transporter.sendMail(mailOptions, (error, info) => {
            if (error) {
                return console.log(error);
            }
            console.log('Message sent: %s', info.messageId);
            // Preview only available when sending through an Ethereal account
            console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
    
            // Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com>
            // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
        });
    //sendMail()方法传递消息对象

    (1)创建Nodemailer传输器

      这个部分是设置node后台需要向哪一个邮箱后台,提交邮件请求

    nodemailer.createTransport(options[, defaults])
    
        options – 设置链接邮箱SMTP数据
    
            host:SMTP地址,从邮件服务提供商获取
    
            port: SMTP端口号,从邮件服务提供商获取
    
            secure: true, //465端口为true,其他接口为false
    
            auth: {
            ​ user: "xxx@qq.com", 请求代理的邮箱地址
            ​ pass: 'jfxksdbztpcohjff’邮箱邀请码,从邮件服务提供商获取
            }
    
        defaults – 共享数据对象

      transporter.verify(callback);

      这个是后台验证是否授权成功,一般用于后台链接测试,实际项目中,不需要写!

    let transporter = nodemailer.createTransport({
            host: 'smtp.qq.com',
            port: 465,
            secure: true, 
            auth: {
                user: "xxx@qq.com", 
                pass: '邮箱的验证码'
            }
    });
    transporter.verify(function(error, success) {
       if (error) {
            console.log(error);
       } else {
            console.log('授权成功!');
       }
    });

    (2)设置消息选项

      这个部分是编辑邮件的内容部分,这一步其实就是建立一个对象,把相关信息作为对象的属性进行存储。

      属性列表:(表中尖括号括起来的内容表示数据类型)
        from - 邮件发出的地址
        to - 邮件送到的地址
        cc - 抄送的地址
        bcc - 加密抄送的地址
        subject - 邮件的标题
        text - 发送的文本 , ,
        html - 发送html页, ,
        attachments - 添加附件对应的属性:
            filename - 文件名
            content - 传输内容, , 可以将数据导入文件,作为附件发送。
            path - 本地路径如果希望流式传输文件而不是包含该文件,则使用该文件的路径(对于较大的附件更好)
            href – 网络数据信息路径。
            contentType - 规定附件的格式,如果未设置,将从文件名生成
            contentDisposition - optional content disposition type for the attachment, defaults to ‘attachment’
            cid - optional content id for using inline images in HTML message source
            encoding - If set and content is string, then encodes the content to a Buffer using the specified encoding. Example values: ‘base64’, ‘hex’, ‘binary’ etc. Useful if you want to use binary attachments in a JSON formatted email object.
            headers - custom headers for the attachment node. Same usage as with message headers
            raw - is an optional special value that overrides entire contents of current mime node including mime headers. Useful if you want to prepare node contents yourself

      上边部分不太常用的的内容没有翻译,一般在邮件发送时也不需要编写,英文内容来源于nodemailer官方文档。

    //address实例---符合address格式的例子
    1.字符串:'foobar@example.com'
    2.字符串嵌套:'"Майлер, Ноде" <foobar@example.com>'
    3.对象:{
        name: 'Майлер, Ноде',
        address: 'foobar@example.com'
    }
    4.数组[
        '"Ноде Майлер" <bar@example.com>,
        '"Name, User" <bar@example.com>'
    ]
    5.混合数组[
        'foobar@example.com',
        {
            name: 'Майлер, Ноде',
            address: 'foobar@example.com'
        }
    ]
    
    //attachments实例
    attachments: [
            {   //字符串生成附件
                filename: 'text1.txt',
                content: 'hello world!'
            },
            {   // 字符流生成附件
                filename: 'text2.txt',
                content: new Buffer('hello world!','utf-8')
            },
            {   // 磁盘文件生成附件
                filename: 'text3.txt',
                path: '/path/to/file.txt' 
            },
            {  
                //文件名及类型会自动从路径获取
                path: '/path/to/file.txt'
            },
            {   // 字符流生成附件
                filename: 'text4.txt',
                content: fs.createReadStream('file.txt')
            },
            {   // 为附件定义自定义内容类型
                filename: 'text.bin',
                content: 'hello world!',
                contentType: 'text/plain'
            },
            {   // 使用URL添加附件
                filename: 'license.txt',
                path: 'https://raw.github.com/nodemailer/nodemailer/master/LICENSE'
            }
        ]

      注意:

      在属性中“from”必填,且必须是Nodemailer传输器中已经授权的邮箱。否则会出现错误
      在属性中“to,cc,bcc”三者都表示发送到邮箱的地址。可以同时选则多个属性,每个属性也可以同时添加多个目标地址,但是不能三个属性一个收件地址都不填。
      subject,text,html,attachments可以为空。当他们为空时会发送空邮件。
      磁盘文件只能使用path路径,网络连接可以使用path或href。
      附件名称可以修改,可以与文件名称不一致。
      在设置对象中有两个属性分别是text、和html。这两个对象都是邮件需要发送的内容,当html为空时,邮件会发送空的文本。当html不为空时,html会覆盖text内容(邮件主体呈现出html内容,而不显示text内容)。
      所有文本字段(电子邮件地址、明文主体、html主体、附件文件名)都使用UTF-8作为编码。附件是以二进制方式传输的。

    (3)sendMail()方法传递消息对象

      使用了这个方法之后,邮件请求就发送到了SMTP服务器中了。

      nodemailer.sendMail(object,callback(err,info))

    • object就是第二步创建的邮件信息对象对象
    • 回调函数有两个参数,一个是错误信息,一个是发送的信息

    三,获取邮件

      IMAP:https://www.npmjs.com/package/imap,好好看文档

      在获取邮件的时候,无非就是以下几个步骤:

      连接到服务器 —— 打开指定邮箱 —— 筛选(操作)邮件 —— 解析邮件 —— 查看结果

    var Imap = require('imap'),
        inspect = require('util').inspect;
     
    var imap = new Imap({
      user: 'mygmailname@gmail.com',
      password: 'mygmailpassword',
      host: 'imap.gmail.com',
      port: 993,
      tls: true
    });
     
    function openInbox(cb) {
      imap.openBox('INBOX', true, cb);
    }
     
    imap.once('ready', function() {
      openInbox(function(err, box) {
        if (err) throw err;
        var f = imap.seq.fetch('1:3', {
          bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',
          struct: true
        });
        f.on('message', function(msg, seqno) {
          console.log('Message #%d', seqno);
          var prefix = '(#' + seqno + ') ';
          msg.on('body', function(stream, info) {
            var buffer = '';
            stream.on('data', function(chunk) {
              buffer += chunk.toString('utf8');
            });
            stream.once('end', function() {
              console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));
            });
          });
          msg.once('attributes', function(attrs) {
            console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
          });
          msg.once('end', function() {
            console.log(prefix + 'Finished');
          });
        });
        f.once('error', function(err) {
          console.log('Fetch error: ' + err);
        });
        f.once('end', function() {
          console.log('Done fetching all messages!');
          imap.end();
        });
      });
    });
     
    imap.once('error', function(err) {
      console.log(err);
    });
     
    imap.once('end', function() {
      console.log('Connection ended');
    });
     
    imap.connect();

    (1)连接邮箱

      构造器:使用指定的配置对象创建并返回Connection的新实例。设置需要连接到哪一个服务器

    var imap = new Imap({
      user: 'xxx@qq.com', //你的邮箱账号
      password: 'xxx', //你的邮箱授权码
      host: 'imap.qq.com', //邮箱服务器的主机地址
      port: 993, //邮箱服务器的端口地址
      tls: true, //使用安全传输协议
      tlsOptions: { rejectUnauthorized: false } //禁用对证书有效性的检查
      connTimeout: 10000,//链接超时等待数,默认10000毫秒 
      authTimeout: 5000,//身份验证的毫秒数
    });

      与服务器构建链接:connect()

    imap.connect();
    //链接邮箱,并身份验证

      与服务器断开链接:end(),destroy()

    imap.end();
    //当所有的请求结束后,断开链接
    imap.destroy();
    //忽略正在传输的内容,立即断开链接

    (2)打开指定邮箱

      获取所有邮箱列表:getBoxes

    imap.getBoxes(function(err,box){
        //box就是邮箱的列表
        //box具体内容如下:
            //INBOX:收件箱
            //Sent Messages:发送的邮件
            //Drafts:草稿箱
            //Deleted:删除邮件
            //Junk:垃圾邮件
    })

      打开邮箱:openBox(< string >mailboxName[, < boolean >openReadOnly=false], < function >callback)

    imap.openBox('INBOX',true,function(err,box){
        //打开邮箱后的操作
    })

      关闭邮箱:closeBox([< boolean >autoExpunge=true, ]< function >callback)

    imap.closeBox(true,function(err){
        //如果关闭失败,触发回调函数
    })

      注意:这里的第一个参数可选,默认是true。如果autoExpunge为真,则在当前打开的邮箱中标记为Deleted的任何消息都将被删除,如果邮箱未在只读模式下打开。如果autoExpunge为false,则断开连接或打开另一个邮箱,标记为Deleted的消息将不会从当前打开的邮箱中删除。

      addBox、delBox、renameBox等一些不常用的方法参考npm-imap文档:https://www.npmjs.com/package/imap

    (3)筛选(操作)邮件

      搜索邮件:search(< array >criteria, < function >callback)

    • callback函数有两个参数(err,list)list是符合要求的UID数组(UID是邮箱用来标识你这个账户的每一封邮件的编号)

      criteria:设置筛选条件

    • UID:通过邮箱标识符查找
    //常用实例
    imap.search( [['UID', '491']],function(err,list){
                console.log(box);
    })//搜索UID为491的邮件
    imap.search( [['UID', '430:450']],function(err,list){
                console.log(box);
    })//搜索UID为430到450的邮件
    imap.search( [['UID', '430:*']],function(err,list){
                console.log(box);
    })//搜索UID为430及以上的邮件
    imap.search( [['UID', '430:450','491']],function(err,list){
                console.log(box);
    })//搜索UID为430到450或491的邮件
    • ALL:查找所有邮件
    • UNSEEN:查找未读信息
    • UNSEEN:查找已读信息
    • ANSWERED:已经回复后的消息
    imap.search( ['ALL'],function(err,list){
                console.log(box);
    })//搜索所有邮件
    //其他功能替换ALL即可
    • ‘BEFORE’ - 在指定日期之前的所有邮件
    • ‘ON’ - 在指定日期当天的邮件
    • ‘SINCE’ - 在指定日期及之后的邮件
    imap.search( [['BEFORE','2018/12/7']],function(err,list){
                console.log(box);
    })//搜索所有邮件
    //第二个参数为,js可以解析的日期字符串或日期对象
    • LARGER’ :邮件的大小大于指定字节数。
    • ‘SMALLER’:邮件的 大小小于指定字节数。
    imap.search( [['LARGER',1280]],function(err,list){
                console.log(box);
    })//搜索所有邮件
    //第二个参数为int类型的数字
    • 数组嵌套表示且
    • 数组结合‘OR’表示或
    [ 'UNSEEN', ['SINCE', 'April 20, 2010'] ]
      [ ['OR', 'UNSEEN', ['SINCE', 'April 20, 2010'] ] ]

      注意:

    • 国内的邮件服务商,并没有对所有的search语句进行实现。建议大家使用Gmail。
    • 以上只列出了一些常用方法,全部方法请参考:https://www.npmjs.com/package/imap

    (4)抓取邮件内容

      fetch(< MessageSource >source, < object >options)

      source表示UID数组,options表示可以添加的属性,return

      options属性:

    var f = imap.fetch([491,496,493],{bodies:''});
    f.on('message',function(msg, seqno){
        //msg是抓取对应邮件的事件触发器
        //seqno是邮件在邮箱的编号(不是UID)
        msg.on('body',function(stream,info){
            //stream表示可读流,是邮件内容的流
            //info邮件的基础信息,包括大小编号
        })//当对应邮件接收流触发
        msg.on('end',function(){
        })//当对应邮件所有内容接收完后触发
    })
    //抓取完一封邮件后触发
    f.once('error',function(err){})
    //抓去错误后触发
    f.once('end',function(){})
    //所有邮件抓取结束后触发

      注意:

    • on,once都是添加事件,只不过once为一次性事件,on为多次事件

    • f本身就是一个事件的触发器,每抓取一封邮件,message的回调函数会执行,生成对应的那一封邮件的触发器msg

    • msg也是一个触发器,当抓取对应邮件下载到本地后触发

    (5)邮件内容解析–使用mailparser

    f.on('message', function(msg, seqno) {
        var mailparser = new MailParser();
        //每封邮件添加一个mailparser
        msg.on('body', function(stream, info) {        
            stream.pipe(mailparser);
            //将为解析的数据流pipe到mailparser
     
            mailparser.on("headers", function(headers) {
              //headers是Map类型
              console.log("邮件主题: " + headers.get('subject'));
               console.log("发件人: " + headers.get('from').text);
              console.log("收件人: " + headers.get('to').text);
            });
            //当邮件头部流全部传入mailparser后触发
            
            mailparser.on("data", function(data) {
                //data是对象
                if (data.type === 'text') {
                  console.log("邮件内容: " + data.html);
                }
                if (data.type === 'attachment') {
                  console.log("附件名称:"+data.filename);
                  data.content.pipe(fs.createWriteStream(data.filename)); 
                   
                  //保存附件到当前目录下
                  data.release();
                }
        });
    });

      在这次的邮件服务器研究过程中,有一些问题没有深入分析,只是学习了基本使用方法。比如:在抓取邮件的过程中,涉及到了流式传输,这里没有再深入分析。

  • 相关阅读:
    《华东交通大学2018年ACM“双基”程序设计竞赛*补》
    《多校补题》
    《HDU多校第五场》
    前端开发框架
    Myeclipse Weblogic Launches下的classpath配置文件目录
    正则表达式:元字符 简
    Freemarker
    SSM整合
    MySQL基础
    Redis与Spring Data Redis
  • 原文地址:https://www.cnblogs.com/goloving/p/12491923.html
Copyright © 2011-2022 走看看