Flask框架配置Celery-[2]:将前端上传的文件,通过异步任务保存,异步处理上传的文件

一、项目大致目录

主要介绍celery的配置,flask相关配置就默认大家都会了。

flask-object

        |--apps

                |--user

                        views.py

                        __init__.py

        |--celery_task

                __init__.py

                asycn_task.py

                celery.py        

                celeryconfig.py

                check_task.py

                scheduler_task.py

        app.py

依赖包:

celery==4.4.7
eventlet==0.33.3
Flask==2.1.3
Flask-Caching==1.10.1
Flask-Cors==3.0.10
Flask-Migrate==2.7.0
Flask-RESTful==0.3.9
Flask-SocketIO==5.1.1
Flask-SQLAlchemy==2.5.1
PyMySQL==1.0.2
redis==3.5.3
SQLAlchemy==1.4.0
Werkzeug==2.0.2

二、celery项目配置

1、celery相关配置:celeryconfig.py

celery默认使用json作为序列化工具,要操作flask上传的文件,需要改为pickle

from celery.schedules import crontab
from datetime import timedelta
'''
参数解析:
accept_content:允许的内容类型/序列化程序的白名单,如果收到不在此列表中的消息,则该消息将被丢弃并出现错误,默认只为json;
task_serializer:标识要使用的默认序列化方法的字符串,默认值为json;
result_serializer:结果序列化格式,默认值为json;
timezone:配置Celery以使用自定义时区;
enable_utc:启用消息中的日期和时间,将转换为使用 UTC 时区,与timezone连用,当设置为 false 时,将使用系统本地时区。
result_expires: 异步任务结果存活时长
beat_schedule:设置定时任务
'''
#手动注册celery的异步任务:将所有celery异步任务所在的模块找到,写成字符串
task_module = [
    'celery_task.async_task',  # 写任务模块导入路径,该模块主要写异步任务的方法
    'celery_task.scheduler_task',  # 写任务模块导入路径,该模块主要写定时任务的方法
]

#celery的配置
config = {
    "broker_url" :'redis://127.0.0.1:6379/0',   #'redis://:[email protected]:6379/1' 有密码时,123456是密码
    "result_backend" : 'redis://127.0.0.1:6379/1',
    "task_serializer" : 'pickle', #json格式支持的数据格式比较少,改为pickle
    "result_serializer" : 'pickle', #json格式支持的数据格式比较少,改为pickle
    "accept_content" : ['pickle'], #json格式支持的数据格式比较少,改为pickle
    "timezone" : 'Asia/Shanghai',
    "enable_utc" : False,
    "result_expires" : 1*60*60,
    "beat_schedule" : { #定时任务配置
            # 名字随意命名
            'add-func-30-seconds': {
                # 执行add_task下的addy函数
                'task': 'celery_task.scheduler_task.add_func',  # 任务函数的导入路径,from celery_task.scheduler_task import add_func
                # 每10秒执行一次
                'schedule': timedelta(seconds=30),
                # add函数传递的参数
                'args': (10, 21)
            },
            # 名字随意起
            'add-func-5-minutes': {
                'task': 'celery_task.scheduler_task.add_func',  # 任务函数的导入路径,from celery_task.scheduler_task import add_func
                # crontab不传的参数默认就是每的意思,比如这里是每年每月每日每天每小时的5分执行该任务
                'schedule': crontab(minute='5'),  # 之前时间点执行,每小时的第5分钟执行任务, 改成小时,分钟,秒 就是每天的哪个小时哪分钟哪秒钟执行
                'args': (19, 22)  # 定时任务需要的参数
            },
            # 缓存用户数据到cache中
            'cache-user-func': {
                'task': 'celery_task.scheduler_task.cache_user_func',
                # 导入任务函数:from celery_task.scheduler_task import cache_user_func
                'schedule': timedelta(minutes=1),  # 每1分钟执行一次,将用户消息缓存到cache中
            }
        }
}

2、创建celery对象:celery.py

from celery import Celery,Task
from .celeryconfig import config,task_module
import sys
import os
'1、把flask项目路径添加到系统环境变量中'
project_path = os.path.dirname(os.path.dirname(__file__))
sys.path.append(project_path)

'''
2、创建celery应用对象
  'task'可以任务是该celery对象名字,用于区分celery对象
  broker是指定消息中间件
  backend是指定任务结果存储位置
  include是手动指定异步任务所在的模块的位置
'''
#创建celery异步对象
celery = Celery('task', broker=config.get('broker_url'), backend=config.get('result_backend'), include=task_module)
#导入一些基本配置
celery.conf.update(**config)

'3、给celery所有任务添加flask的应用上下文,在celery异步任务中就可以调用flask中的对象了'
class ContextTask(celery.Task):
    def __call__(self, *args, **kwargs):
        from apps import create_app
        app = create_app()
        with app.app_context():
            return self.run(*args, **kwargs)
celery.Task = ContextTask

  3、异步任务模块:async_task.py

import time
# 导入celery对象app
from celery_task.celery import celery
import os
#导入发送邮件的模块
from base.email_ import send_email
from ext import cache


'''
所有异步任务:
1、没有返回值的,@app.task(ignore_result=True)
2、有返回值的任务,@app.task 默认就是(ignore_result=False)
'''


# 没有返回值,禁用掉结果后端
@celery.task
def send_email_task(receiver_email,code):  # 此时可以直接传邮箱,还能减少一次数据库的IO操作
    '''
    :param email: 接收消息的邮箱,用户的邮箱
    :return:
    '''
    # 启用线程发送邮件,此处最好加线程池
    ret = send_email(
        receiver_email=receiver_email,
        subject='登录验证码',
        message=f'您的验证码是:{code}',
    )
    return {'result':ret,receiver_email:code}

@celery.task
def cache_user_task():
    from apps.user.models import UserModel
    user = UserModel.query.all()
    lis = []
    for u in user:
        id = u.id
        name = u.name
        dic = {'id':id,'name':name}
        lis.append(dic)
        print(dic)
    cache.set('all-user-data',lis)
    return {'code':200,'msg':'查询数据成功'}

@celery.task
def test_check_fun():
    print('耗时开始:30秒')
    time.sleep(30)
    print('耗时结束...')
    return {'result':'异步任务执行后返回值','time':30}

@celery.task(ignore_result=True)
def save_file_task(file_bytes,file_path,delete_file_path=None):
    '''
    file_bytes: flask调用使用传递的文件字节流
    file_path: 文件保存的绝对路径
    1、对与路径的拼接和文件名重复的逻辑,通通在视图函数中处理
    2、在这里,只负责将文件保存到系统中,不管其他的逻辑
    '''
    #1、查看文件所在路径是否存在,不存在就创建
    file_path_dir = os.path.dirname(file_path)
    if not os.path.exists(file_path_dir):
        os.makedirs(file_path_dir)

    #2、保存文件
    with open(file_path, 'wb+') as f:
        f.write(file_bytes)

    #3、删除旧文件
    if delete_file_path:
        #想头像图片,一个用户只保存一个头像文件就可以了,删除之前旧的头像文件
        try:
            os.remove(delete_file_path)
        except Exception as _:
            print('删除失败')
            pass

4、定时任务模块:scheduler_task.py

from celery_task.celery import celery
import time
'''
所有定时任务
'''

# 有返回值,返回值可以从结果后端中获取
@celery.task
def add_func(a, b):
    print('执行了加法函数',a+b)
    return a + b


# 不需要返回值,禁用掉结果后端
@celery.task(ignore_result=True)
def cache_user_func():
    print('all')


5、校验任务是否成功:check_task.py

from celery.result import AsyncResult
from celery_task.celery import celery

'''验证任务的执行状态的'''


def check_task_status(task_id):
    '''
    任务的执行状态:
        PENDING :等待执行
        STARTED :开始执行
        RETRY   :重新尝试执行
        SUCCESS :执行成功
        FAILURE :执行失败
    :param task_id:
    :return:
    '''
    result = AsyncResult(id=task_id, app=celery)
    dic = {
        'type': result.status,
        'msg': '',
        'data': None,
        'code': 400
    }
    if result.status == 'PENDING':
        dic['msg'] = '任务等待中'
    elif result.status == 'STARTED':
        dic['msg'] = '任务开始执行'
    elif result.status == 'RETRY':
        dic['msg'] = '任务重新尝试执行'
    elif result.status == 'FAILURE':
        dic['msg'] = '任务执行失败了'
    elif result.status == 'SUCCESS':
        result = result.get()
        dic['msg'] = '任务执行成功'
        dic['data'] = result
        dic['code'] = 200
        # result.forget() # 将结果删除
        # async.revoke(terminate=True)  # 无论现在是什么时候,都要终止
        # async.revoke(terminate=False) # 如果任务还没有开始执行呢,那么就可以终止。
    return dic

三、flask中的视图函数

写一个视图函数,接收用户上传的头像图片,将该图片保存到系统中,将保存的操作放到异步任务中。

class UserOneResource(Resource):
   
    def put(self,id):
        '''
        用户更新头像的接口
        '''
        file = request.files.get('picture')
        if not file:
            return NewResponse(error='请携带头像文件')
        user = models.UserModel.query.filter_by(id=id).first()

        if not user:
            return NewResponse(error='用户不存在')
        # print(file.filename,'=============')
        _,suffix = file.filename.split('.')
        #1、使用uuid创建文件名
        file_name = str(uuid.uuid4())+f'-{id}'+'.'+suffix
        file_path = os.path.join(STATIC_PATH,'picture',file_name).replace('\\','/')
        #2、读取出旧的头像路径
        delete_file_path = None
        if user.picture:
            picture = user.picture
            if picture[0] in ['\\','/']:
                picture = picture[1:]
            delete_file_path = os.path.join(BASE_PATH,picture).replace('\\','/')
            print(delete_file_path)
        #3、读取文件字节码
        file_bytes = file.read()
        #4、调用异步保存文件任务
        res = save_file_task.delay(file_bytes,file_path,delete_file_path)
        task_id = res.id
        #5、将头像新路径保存到数据库中

        picture = file_path.split('static/')[-1]
        picture = 'static/' + picture.replace('\\','/')
        user.picture = picture
        db.session.add(user)
        db.session.commit()
        return NewResponse(data={'task_id':task_id},msg='操作成功')

四、启动项目

1、启动celery
windows系统需要借助:eventlet

    celery -A celery_task.celery worker -l info  -P  eventlet

linux系统:

    celery -A celery_task.celery worker -l info


2、启动定时任务

    celery -A celery_task beat -l info

你可能感兴趣的:(celery,flask,前端,python)