基于koa2搭建的跨域email邮件发送服务

最近一直在沉淀,很长一段时间没写东西了,正好前段时间用到Nodejs发送邮件相关东西,顺便记录下来。

什么是跨域email邮件发送服务?例如:当前域名是 www.page.com ,一个页面里面包含如下表单:

点击发送信息按钮的时候,提交表单到一个 www.email.com/send 请求地址,然后我的QQ邮箱就收到这条表单的内容,如下图。

并且前端浏览器显示发送成功,如下图:

如果表单验证不通过,服务端返回类似如下图:

整个流程包含以下几个重点:

  • 跨域请求 CORS
  • Nodejs 发送邮件
  • 配置邮箱 SMTP 服务
  • 前后端表单校验

项目搭建

我们先把邮件服务 www.email.com 架子给搭好,关于koa2框架的搭建,网上有很多,这里就不贴一大坨了。下面展示主要文件 email.js,也就是发送邮件的路由配置。其中有两个关键函数 validatorsender,分别用于表单校验和发送邮件,代码如下。先别懵,下面会慢慢讲到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// routers/email.js

const router = require('koa-router')()

const validator = require('../modules/validator') // 表单校验
const sender = require('../modules/sender') // 发送邮件

// www.email.com/send
router.post('/send', async (ctx, next) => {
let params = Object.assign({}, ctx.request.body)

if(validator(ctx, params)){
const sendResult = await sender(params)
ctx.body = sendResult
}
})

module.exports = router

跨域请求 CORS

之所以有跨域是因为上面的需求,www.page.com 域名中一个页面,请求 www.email.com/send 这个地址。由于浏览器有同源策略的限制,请求是无法发送过去的。什么是同源策略,可以参考mozilla
前端跨域请求有很多种实现方式,JSONP、服务端代理、CORS。而 JSONP 只能是get请求,服务端代理的方式需要再开一个node服务,或者也有可能你的服务端是Java、PHP等其它语言,配置起来比较麻烦。而有时候需求是一个静态页面我就要能发送跨域请求,不需要服务端配置,那么采用 CORS 来实现跨域就是不二的选择。

CORS 全称为 Cross-Origin Resource Sharing,也就是跨域资源共享。是一种比较安全灵活的解决方案,支持 GETPOST 等所有类型的 HTTP 请求方式。这里使用的koa2框架,跨域解决方法采用的一个koa2中间件koa2-cors,使用很简单。核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// app.js

const Koa = require('koa')
const app = new Koa()

const cors = require('koa2-cors')

// 跨域请求
app.use(cors({
origin: 'http://www.page.com',
maxAge: 5,
credentials: true,
allowMethods: ['POST'],
allowHeaders: ['Content-Type', 'X-Requested-With', 'Accept'],
}))

几个参数关键在于发送请求时会在 Response Headers 头中包含指定的信息,注意是响应头 Response Headers,不是请求头 Request Headers,相当于如下代码:

1
2
3
4
5
6
7
8
9
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', 'http://www.page.com')
ctx.set('Access-Control-Max-Age', '5')
ctx.set('Access-Control-Allow-Credentials', 'true')
ctx.set('Access-Control-Allow-Methods', 'POST')
ctx.set('Access-Control-Allow-Headers', 'Content-Type,X-Requested-With,Accept')

await next();
})

然后浏览器会自动识别这是一个 CORS 请求。在 http://www.page.com 这个提交页面就可以发送成功了。在其它域名,例如 www.abc.com 是不能请求成功的。因为上面设置的是指定域名,如果要所有域名都能够请求,那么设置 origin 参数为 * 即可。其实跨域请求里面涉及到的知识点还有很多,本人也在逐渐学习中,有想深入了解的可以看看阮老师的博客跨域资源共享 CORS 详解

Nodejs 发送邮件

OK,上面配置完成之后仅仅是能够 发生成功,也就是 www.email.com 这个node服务可以收到请求,但是发送邮件怎么办呢。了解邮件相关的服务都知道有个东西叫 SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议。只要有一个邮箱账号并开启了 SMTP,那么我们就可以用nodejs发送邮件了。而npm社区有一个开源包叫nodemailer,它就可以用来发送邮件。使用起来也是相当easy,如下,我们封装一个发送邮件的函数叫 sender.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// modules/sender.js

const nodemailer = require('nodemailer')

const sender = (params) => {
return new Promise((resolve, reject) => {
let transporter = nodemailer.createTransport({
transport: 'SMTP',
host: 'smtp.163.com',
port: 465,
secure: true,
auth: {
user: 'aaa@163.com', // 替换成自己的发件人
pass: 'aaa' // 替换成自己的授权码
}
});

let mailOptions = {
from: 'aaa@163.com', // 替换成自己的发件人
to: 'bbb@qq.com', // 替换成自己的收件人
subject: '测试邮件发送',
html: `姓名:${params.name || ''}


邮箱:${params.email || ''}


手机:${params.phone || ''}


QQ:${params.qq || ''}


内容:${params.message || ''}`
};

transporter.sendMail(mailOptions, (error, info) => {
try{
transporter.close()
}catch(e){
}

if (error) {
resolve({
status: 'failure',
message: '邮件发送失败'
})
return console.log(error);
}

resolve({
status: 'success',
message: '邮件发送成功'
})
});
})
}

module.exports = sender

配置邮箱 SMTP 服务

注意将上面的邮箱替换成自己的,明白人可能看到这里有两个邮箱,是的一个是发件人一个是收件人。邮箱服务又很多,像国内的163,126,QQ等邮箱都不错。将上面的账号替换成自己的邮箱,注意:一定要记得在邮箱设置中心开启 SMTP 服务。下面以163邮箱为例:

然后获取授权码,将授权码写入上面的 pass 就行了,别写成密码了,不然无法成功的。这些设置在邮箱设置中心都很容易找到。

前后端表单校验

上面完成的差不多之后就可以发送邮件了,然而细心的同学可能发现,没有做表单校验,每次提交表单都会直接发送。表单校验最好是前后端都做下,这样更好更安全。这里使用本人之前封装的一个表单校验插件we-validator,支持浏览器和Nodejs。使用npm install we-validator --save安装即可。

那么上面的表单校验我们大概是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// modules/validator.js

const WeValidator = require('we-validator')

const oValidator = new WeValidator({
rules: {
name: {
required: true,
chinese2to8: true
},
email: {
required: true,
email: true
},
message: {
required: true,
regex: /^.{2,200}$/
}
},
messages: {
name: {
required: '姓名不能为空',
chinese2to8: '您的姓名太长了'
},
email: {
required: '邮箱不能为空',
email: '邮箱格式不正确'
},
message: {
required: '内容不能为空',
regex: '内容太长啦,应该小于200个字哦'
}
}
})

const validator = (ctx, params) => {
return oValidator.checkData(params, (data) => {
ctx.body = {
status: 'failure',
message: data.msg
}
})
}

module.exports = validator

上面是Nodejs端的代码,前端的使用方式也是差不多。OK,上面贴了那么多代码,我们再从头看看那个路由配置 email.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// routers/email.js

const router = require('koa-router')()

const validator = require('../modules/validator') // 表单校验
const sender = require('../modules/sender') // 发送邮件

// www.email.com/send
router.post('/send', async (ctx, next) => {
let params = Object.assign({}, ctx.request.body)

if(validator(ctx, params)){
const sendResult = await sender(params)
ctx.body = sendResult
}
})

module.exports = router

是不是有种豁然开朗的感觉?如果不是那可能是我没有讲好,这里面其实有很多细节知识点,需要大家掌握,如Nodejs基本使用、Koa2、npm、CORS,还有网站搭建。其中某一个知识点够写几篇文章了。所以本片篇章只说整体流程和主要使用方法。细节东西还得道友自己摸索,以后的文章可能会讲到这些。最后祝道友使用成功!想要看源码的,去基于koa2搭建的跨域email邮件发送服务