突然心血来潮,想给自己的小站(小站仍在建设)增加一个发送邮件的功能,方便当用户回复的时候能够通过通过邮件进行通知。
一开始打算直接通过调用web.py的mail模块来完成(前面有介绍),但是想到mail的发送会使页面卡住了,这样感觉不是很好,所以就想了下面的方法:
首先创建一张发送邮件的任务表,把发生邮件的相关信息保存进去,然后添加一个Task Queue,这样的话就可以让SAE的任务队列自己去调用发送邮件的页面。为了保险起见,再在Cron中添加一个定时任务,通过定时任务来检查任务表中是否还存在没有发送的邮件,有的话就发送。
其实我测试的结果是Task Queue还是比较可靠的,我测试了40封邮件,40封都能正常发送。
上代码吧,首先是邮件任务表相关的东西:
# -*- coding: UTF-8 -* ''' SinMailTask.py Created on 2012-12-6 @author: RobinTang ''' import config; import web import time from sae import taskqueue mailtasktable = 'tb_mailtasks' inittableable = False class MailTask: """邮件任务对象""" def __init__(self, mtid = None, from_address = None, to_address = None, subject = None, message = None, status = 0, addtime = None, sendtime = None): if addtime == None: addtime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) self.id = mtid # id self.from_address = from_address # 来自地址 self.to_address = to_address # 目的地址 self.subject = subject # 邮件主题 self.message = message # 邮件内容 self.addtime = addtime # 创建任务时间 self.sendtime = sendtime # 发送时间 self.status = status # 状态, 0:未发送 1:已发送 def initMailTaskTable(): """初始化邮件任务表, 第一次运行的时候调用,主要完成任务表的创建""" if not inittableable: raise Exception('can\'t init table!') drop_sql = 'DROP TABLE IF EXISTS `%s`'%mailtasktable create_sql = 'CREATE TABLE `%s` (`id` int(11) NOT NULL AUTO_INCREMENT,`from_address` varchar(256) NOT NULL,`to_address` varchar(256) NOT NULL,`subject` varchar(1024) NOT NULL,`message` text NOT NULL,`status` int(11) NOT NULL DEFAULT 0,`addtime` datetime DEFAULT NULL,`sendtime` datetime DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8'%mailtasktable config.db.query(drop_sql) config.db.query(create_sql) def addMailTask(mailtask): """添加一个邮件任务,无须直接调用""" config.db.insert(mailtasktable, from_address=mailtask.from_address, to_address=mailtask.to_address, subject=mailtask.subject, message=mailtask.message, status=mailtask.status, addtime=mailtask.addtime) def updateMailTask(mailtask): """修改邮件任务,无需直接调用""" config.db.update(mailtasktable, where = "id = %d"%mailtask.id, from_address=mailtask.from_address, to_address=mailtask.to_address, subject=mailtask.subject, message=mailtask.message, status=mailtask.status, addtime=mailtask.addtime, sendtime=mailtask.sendtime) def delMailTask(mailtask): """删除一个邮件任务,在发送之后将该任务删除,无需直接调用""" mailtask.status = 1 mailtask.sendtime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) updateMailTask(mailtask) def getMailTask(): """获取一个未发送的邮件任务,无需直接调用""" itms = config.db.select(mailtasktable, None, where="status=0", limit=1).list() mt = None if len(itms)>0: itm = itms[0] mt = MailTask(mtid = itm.id, from_address = itm.from_address, to_address = itm.to_address, subject = itm.subject, message = itm.message, status = itm.status, addtime = itm.addtime, sendtime= itm.sendtime) return mt def getMailTaskCount(where = '`status` = 0'): """获取邮件任务数,默认参数是获取未被执行的邮件任务数""" sql = "select count(*) as `count` from `%s` where %s"%(mailtasktable, where) res = config.db.query(sql) return res[0].count def doMailTask(): """执行任务,也就是处理一个邮件任务,一般是用过cron来调用""" mt = getMailTask() if mt != None: delMailTask(mt) web.sendmail(mt.from_address, mt.to_address, mt.subject, mt.message) return mt def sendMail(from_address = None, to_address = None, subject = None, message = None): """发送邮件,发送任务将会被添加到,调用之前你需要配置好webpy的邮件部分,之后通过调用doMailTask来完成发送""" mailtask = MailTask() mailtask.from_address = from_address mailtask.to_address = to_address mailtask.subject = subject mailtask.message = message addMailTask(mailtask) taskqueue.add_task('mailtask', '/domailtask') # 添加任务 """ 说明: 第一次首先调用initMailTaskTable()来初始化任务表,调用之后会自动的创建相关的表,表名通过mailtasktable来设置。 创建完成之后一般应该设置inittableable为False,以免以后不小心被再次初始化了。 通过sendMail()来添加邮件任务,需要提的是参数中from_address在使用gmail邮件服务器时没有用,所以一般设置成你的gmail邮件地址。 调用sendMail()会自动的在任务队列中增加一个任务 如果不放心的话,可以在cron任务表中添加定时调用diMailTask()来执行邮件任务,每次能够发送一封,cron的周期根据自己的需要进行设置。 """
初始的时候记得调用初始化方法:
SinMailTask.initMailTaskTable()
增加调用发送任务的处理:
class DoMailTask: def POST(self): self.GET(self) def GET(self): web.header('Content-Type', 'text/html; charset=UTF-8') SinMailTask.doMailTask() count = SinMailTask.getMailTaskCount(); return "<html><head></head><body>%d</body></html>"%count
然后在url中添加下面这行:
'/domailtask', 'DoMailTask',
在SAE对应的应用中增加一个队列,名称为“mailtask”,和SinMailTask中添加队列任务的时候的名称一样。
好了,那么以后就可以通过调用http://appname.sinaapp.com/domailtask来调用发送任务了。
如果你不放心还想在Cron中也添加定时任务的话就添加吧:
cron: - description: domailtask url: domailtask schedule: every 5 mins timezone: Beijing
时间不用太短的,之前提过Task Queue还是比较可靠的。
以后你只需要在代码中这样就可以发送邮件了(不过拜托,你可别真这样写,我可不想收到你的测试邮件~_- ):
SinMailTask.sendMail('trbbadboy@gmail.com', 'trbbadboy@qq.com', '测试邮件', '来自bybreeze')
之后发送邮件的事情就交给Task Queue或者Cron(Task Queue漏网的就给Cron了),你可以写一个查看邮件任务表的页面方便你观察。
想要讲的就这些,后面是一些讨论。
其实一开始(昨天)我都是打算使用Cron来完成的,今天早上上SAE的时候发现了Task Queue了,发现可以用它来实现我的需求,所以就改成Task Queue为主了。
需Task Queue和Cron的左右基本上是一样的(如果你细心的话你会发现Python的文档中这两个东西是放到一块介绍的),都是通过调用url来完成一些任务。
不同的是Task Queue是来完成一些零散无规律的任务调用,比如这次的邮件任务,因为什么时候有邮件是一个不确定的事情。
而Cron则是用来完成一些规律性的调用,比如你么个小时刷新一下你的抓取任务。
正因为这样,所以这次使用了Task Queue来做主要的调用。
再讲点Task Queue,事实上我们甚至可以不用创建任务表的,因为Task Queue允许我们指定Post的数据,只要我们把发送邮件的相关信息作为Task Queue的Post数据就行的。刚刚提到,一开始没看到Task Queue,所以自己做了邮件任务表,不过没觉得走了弯路,因为通过邮件任务表我们可以详细的记录邮件的发送情况,包括何事创建,何时被发送等等。
就讲这么多了,喜欢 SAE、喜欢Python、喜欢code。