我在业余时间写了不少小网站或是应用,比如时光邮局就是目前知名度比较高的一个,其他大多算是自娱自乐为主。

网站的框架,主要是以tp的某个历史版本为基础,做了一些适合自己的修改之后一直沿用下来的。

在开发各种应用的过程中,对我所有的产品的架构也做了下整理,在这一篇以及之后的一些文章中会分别进行介绍。


ok,这次讲第一个问题,统一邮件服务器。

在我开发各个网站的时候,不可避免地会涉及到邮件的功能,比如时光邮局,要在特定时间给用户发邮件,比如某个微信后台,需要给自己发送统计邮件,比如某个应用,需要处理用户注册时候的邮件或者是用户反馈转发给自己等等。

别小看邮件,如果只是一两个简单应用,邮件量也不大,这倒也没什么,可是现实真的太过骨感,小小的邮件发送上,问题还真不少。


梳理一下我这边的主要问题:

1. 不同的应用,会使用不同的邮件账户(这样才显得专业嘛),用户名、密码、邮件发送服务器地址之类都不同。

2. 不同的邮件账户,使用方法也不尽相同。例如有一些日发送量小的,尤其是给自己发送统计信息的,这种我通常会用腾讯企业邮箱或是域名邮箱,简单嘛。可有一些,可能会用第三方的邮件服务,例如之前用过的有sendcloud和submail(submail每天前500封免费,超过之后的价格也不贵,这一点比较吸引我),这种第三方的邮件服务通常是用SDK(或是http请求)的方式来调用。甚至我之前也试过用自己搭建的邮件服务器来进行发送(这种比较麻烦,也容易被放到垃圾箱,不推荐)。

3. 不同的应用,部署在不同的服务器上,有的是在SAE上,有的是我自己买的虚拟主机上,虚拟主机也有不止一台。现在SAE搞了应用帐户等级之后,直接使用他们的Mail服务,一个月只能发1000封邮件,真是丧心病狂。总之,不同的部署环境下,邮件的使用方式可能也有一些区别,或是限额。

4. 有的应用,可能会调整邮件的配置。之前有一次,submail把我暂时封号了,因为我的邮件发送服务被人用来恶意地发送赌场之类的广告,所以当时就需要紧急调整邮箱发送的方式。

5. 邮件的各种配置在不同的服务器上,有的应用使用的是一个用户名密码,但是有时为了安全起见需要修改密码,就要到各个地方去调整配置。

6. 所有的邮件发送服务,都需要一套管理机制,例如自动重试,记录发送的状态等。


所以,在种种问题的驱动之下,我做了一个简单的统一邮件服务器(不过管理机制暂时还没有加上),谈不上多么复杂的架构,但是对于我这样的个人开发者,算是足够使用了。


设计思路:

1. 这套方案分为两个部分,服务端和客户端,两者之间通过http进行通信。

2. 服务端提供一个单一接口(暂时是单独一台机器,已经足够我使用了。更好的方式是通过一些机制可以让请求落到不同的机器上,并且在一台服务器挂掉之后,再更换服务器重发)

3. 客户端的请求进行加密,防止信息泄露(这个暂时没做),或者至少要对请求的信息进行签名,防止接口被盗用。

4. 服务端根据客户端提供的参数,选择不同的邮件发送方式。

5. 服务端的代码,要尽可能复用,比如有的只需要更改用户名密码即可,服务器端需要统一处理邮件发送的重试、记录(这个暂时没做),参数的解析、校验等,避免重复代码。

6. 客户端尽量精简,最好在一个文件(class)内完成。新增一个需要发送邮件的应用时,只要提供最少量的参数即可,而邮件的帐户、密码、调用方式之类都在服务端处理,客户端无需关心。


简单画一下设计图,毕业太久,什么UML图之类的真的不记得是要怎么画了,反正能看懂就好。

ok,对照着图来做一些说明,看看整个流程是怎样的


客户端

1. 客户端仅有一个client文件,可以放在不同的应用中(后来我实现了所有应用的代码统一管理之后,这一点也无所谓啦)

2. 客户端被分配了一个appid和一个appseed,比如appid=2,appseed=abcdefg一个随机字符串

3. 客户端代码调用时,接口传入邮件属性配置,例如html或是text类型,收件人地址,标题和内容。

4. 客户端根据appid和appseed,以及邮件属性,计算出一个hash值

5. 客户端将appid、hash、邮件属性以post的方式发送至服务端


服务端

1. 服务端接收到请求,根据appid映射到配置中,拿到自己存储的appseed值,将这个值代入,和appid、邮件属性一起,计算hash值(和客户端的算法一样),看hash值与客户端传过来的hash值是否一致。

2. 服务端根据appid的配置,实例化对应的Mailer,例如appid=2,那么可能就直接使用Mailer2(其实是做过一次映射的,appid=2可能会映射到Mailer3,甚至可能会映射到多个Mailer)

3. 不同的Mailer,分别继承自Submail或者SMTP类型Mailer的积累,只是各自的细微配置有所区别。

4. 被调用到的Mailer实例,执行发送,并判断状态,做相关的记录。

5. 以json的方式给客户端返回结果。


前面也说过,服务端还应该有个很重要的功能,应该去统一做记录,进行重试,这一块我还没有时间去完成,之后会继续完善。当然,这个统一邮件服务器可以做的还有很多,例如服务端可以统一做一些过滤(例如过滤垃圾邮件的发送),做各种统计,甚至可以学习第三方邮件服务商那样,通过对邮件内容的注入,去得到邮件的打开率等信息。


以上就是我的统一邮件服务器的设计思路,也仅限于架构的思路讲解,代码并不复杂,就不放了。