flask,celery实现定时发送邮件,参考了很多资料,结果都不详细,最终自己写了一个。。。。。
前言:
想实现一个简单的定时任务,因为用的是flask框架,所以最终决定使用 flask+celery 去实现定时发送邮件
因为celery的层级管理非常严格,踩了很多坑。。。
现在总结出来以供参考。。
1、版本依赖:
1 Flask 2.1.1 2 Flask-Mail 0.9.1 3 celery 5.2.6 4 redis 4.2.2
2、目录层级:
3、详细内容:
遇到的bug:之前把 celery 实例 也放到app 下面了,导致出现了很多问题
问题1:flask app 和 celery 都成功启动,蓝图调用 task 任务失败,出现的问题要么是 task 任务没有被注册,要么就是 导入路径问题
问题2:flask app 和 celery 成功启动,蓝图也顺利传给 task,task 拿到任务之后,运行的时候用到了 flask-mail,最终提示的意思是 运行在了 应用上下文 之外,需要用 with app.app_context(): 来管理。。 总之一直是有问题。
最终能够成功运行的代码如下:
# sms 目录下
task01.py
1 from celery_manage import celery_app 2 from flask_mail import Mail,Message 3 from flask import current_app 4 5 6 @celery_app.task 7 def send_email(data): 8 mail = Mail(current_app) 9 addr = data.get('addr') 10 title = data.get('title') 11 content = data.get('content') 12 msg = Message(title, recipients=[addr], body=content) 13 mail.send(msg) 14 print('ok')
# views.py 目录下
cattle.__init__.py
1 @cattle.route('/ctime_email',methods=['GET','POST']) 2 def ctime_email(): 3 if request.method=='GET': 4 return render_template('cattle/index.html') 5 else: 6 from app01.sms.task01 import send_email 7 data = request.json 8 print(data) 9 if data: 10 cdatelist = data.get('cdate').split('-') 11 ctimelist = data.get('ctime').split(':') 12 v1 = datetime.datetime(int(cdatelist[0]),int(cdatelist[1]),int(cdatelist[2]),int(ctimelist[0]),int(ctimelist[1])) 13 new_date = datetime.datetime.utcfromtimestamp(v1.timestamp()) # 将v1转成时间戳 14 print('newdata',new_date) 15 result = send_email.apply_async(args=[data],eta=new_date) 16 print( 17 result.id, 18 result 19 ) 20 return 'ok'
# app01.__init__py
1 def create_app(config_name='development',*args,**kwargs): 2 3 4 app = Flask(__name__,static_folder=Config[config_name].STATIC_ROOT,static_url_path='/static') 5 app.config.from_object(Config[config_name]) 6 7 auto_register_blueprint(app) 8 9 10 register_celery(celery=kwargs.get('celery'), app=app) # 注册celery 11 12 13 return app 14 15 def register_celery(celery, app): 16 class ContextTask(celery.Task): 17 abstract = True 18 19 def __call__(self, *args, **kwargs): 20 with app.app_context(): 21 return self.run(*args, **kwargs) 22 23 celery.Task = ContextTask
# celery_manage.py
1 from celery import Celery 2 from app01 import celeryconfig 3 4 celery_app = Celery('celery_worker', backend=celeryconfig.result_backend, broker=celeryconfig.broker_url, include=[ 5 "app01.sms.task01", 6 "app01.sms.task02" 7 ]) 8 celery_app.config_from_object(celeryconfig)
# manage.py
1 from app01 import create_app 2 from celery_manage import celery_app 3 from flask_script import Manager 4 5 app = create_app(celery=celery_app) 6 manager = Manager(app) 7 8 if __name__ == '__main__': 9 manager.run()
启动命令,都是在根目录下运行的:
启动flask:python manage.py runserver
启动celery:celery -A celery_manage worker -l info -P eventlet
4、重点说明:
celery 实例话时候的 incluede 参数
1 celery_app = Celery('celery_worker', backend=celeryconfig.result_backend, broker=celeryconfig.broker_url, include=[ 2 "app01.sms.task01", 3 "app01.sms.task02" 4 ]) 5 6 # include 是告诉 celery 去 参数的路径下面寻找任务,值得注意的是,celery是从当前实例化的路径开始往下找