- 场景描述: 写了个构造数据的自动化脚本,准备集成到web上。 前端form表单提交构造数据所需的入参。实际操作时,前端点击保存后,由于构造数据的脚本要执行很久,导致前端的页面驻留太久。于是准备使用异步的方法解决这个问题。
- 需求:前端给个form表单填写参数,保存后数据存入DB(一条任务数据),状态默认为未处理;调用异步方法,执行脚本,脚本执行成功后修改任务状态。
- 技术栈 celery
- urls.py
from django.conf.urls import url
from web.views import account,project
# 增加name属性是为了方便反向解析url
urlpatterns = [
# 小工具
url(r'^home/create_sim/$', project.CreateSim.as_view(), name="create_sim"),
]
- views.py
class CreateSim(View):
def get(self, request):
form = CreateSimModelForm()
# 去数据库获取任务列表传给前端{'form': form,'list':list}
# 待实现
return render(request, 'web/create_sim.html', {'form': form})
def post(self, request):
form = CreateSimModelForm(request, request.POST)
if form.is_valid():
# task_id 不是从request中获取的,所以要单独写入值
form.instance.task_id = ''.join(str(uuid4()).split('-'))
form_content = form.save()
# 此处应该要将我的脚本中的内容放到这里来,但是执行太久,会导致前端一直停留在数据提交页面
# 异步处理可以上场了。身为一个测试,当然知道异步处理的存在了,只是不知道如何实现。。。
# def create_sim_task(operator, operator_name, operator_account, create_num, apn, product, offer)
# 异步任务函数的调用方法.delay是将task任务放入到celery的队列里,函数需要的参数都放在delay()里
asynchronous_tasks = create_sim_task.delay(form_content.get_operator_display(),
form_content.operator_name,
form_content.operator_account,
form_content.create_num,
form_content.apn,
form_content.product,
form_content.offer,
form_content.task_id,
)
return JsonResponse({'status': True})
return JsonResponse({'status': False, 'error': form.errors})
- form.py
class CreateSimModelForm(Bootstrp, forms.ModelForm):
class Meta:
model = models.Task
fields = ['operator', 'create_num', 'operator_name', 'operator_account', 'apn', 'offer', 'product']
def __init__(self, request=None, *args, **kwargs):
'''request实际没用到'''
super().__init__(*args, **kwargs)
self.request = request
def clean_operator(self):
operator = self.cleaned_data.get('operator')
if operator <= 0:
raise ValidationError('运营商类型必须选择')
return self.cleaned_data['operator']
def clean_offer(self):
operator = self.cleaned_data.get('operator')
offer = self.cleaned_data.get('offer')
if not offer and operator == 3:
raise ValidationError('运行商类型为JT,offer不允许为空')
return self.cleaned_data['offer']
def clean_product(self):
''' 主要限制运营商为Syniverse时,product不允许为空'''
operator = self.cleaned_data.get('operator')
product = self.cleaned_data.get('product')
if not product and operator == 5:
raise ValidationError('运行商类型为Syniverse,product不允许为空')
return self.cleaned_data['product']
- models.py
class Task(models.Model):
operator_choice = (
(1, '请选择运营商类型'),
(2, 'onelink'),
(3, 'jt'),
(4, 'jasper'),
(5, 'syniverse'),
)
status_choice = (
(1, '未处理'),
(2, '处理中'),
(3, '已完成'),
)
task_id = models.CharField(verbose_name='任务id', max_length=64)
operator = models.SmallIntegerField(verbose_name='运营商', choices=operator_choice, default=1)
create_num = models.SmallIntegerField(verbose_name='创建数量')
operator_name = models.CharField(verbose_name='运营商名称', max_length=64)
operator_account = models.CharField(verbose_name='运营商账户', max_length=64)
apn = models.CharField(verbose_name='APN', max_length=64)
product = models.CharField(verbose_name='product(Syniverse卡必填)', max_length=64, null=True, blank=True, default="")
offer = models.CharField(verbose_name='offer(JT卡必填)', max_length=64, null=True, blank=True, default="")
status = models.SmallIntegerField(verbose_name='任务状态', choices=status_choice, default=1)
- 下面是重点了,主要是如何在django中使用celery
- 先安装celery模块
- 在django的项目目录中(跟app同级)建立一个包文件夹(就是一个文件夹里面包含一个空的__init__文件)
- 新建task.py文件用于定义异步任务(函数)供其他模块调用。
1. 先要初始化django的配置,参考wsgi.py文件中的写法
2. 启动worker,进入项目根目录执行(如果不是在django项目中才需要执行步骤1),启动成功celery -A celery_tasks.task worker -l info
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bugManagerSys.settings")
# 创建一个Celery实例backend 存放返回结果;broker 任务存放;'celery_tasks.tasks'随便定义一个名称即可,习惯性根据tasks.py文件路径写
celery_obj = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/1',backend="redis://127.0.0.1:6379/1")
# ImportSim 我写的脚本类
class ImportSim:
def make_import_data(self):
pass
...
@celery_obj.task
def create_sim_task(operator, operator_name, operator_account, create_num, apn, product, offer,task_id):
loggings.debug("开始处理异步任务")
import_sim_url = "http://XXX:XX/admin/web/batchJobs/import/sims/data"
inventory_sim_url = "http://XXX:XX/admin/web/boss/inventory/inboundRecords"
oo = ImportSim(operator=operator)
# 构造导入SIM卡信息的数据
oo.make_import_data(num=create_num, operator_name=operator_name,
operator_acount=operator_account, apn=apn, product=product,
offer=offer)
# 构造库存数据
oo.make_inventory_import_data("单", "插拔卡三合一", "运营商提供")
# 库存数据写入excel
oo.write_excel(inventory=True)
# 调用API接口,将库存数据上传到系统
oo.import_excel(inventory_sim_url)
# 构造测试桩数据
oo.make_insert_sql()
# 生成excel文件 ,会清理import data 所以必须在 make_inert_sql() 后调用write_excel(),将导入的SIM卡信息写入excel
oo.write_excel()
# 修改点 len(oo.onelink_insert_sql) > 0:
# 定义线程池
sum_sql_list = oo.jasper_insert_sql + oo.onelink_insert_sql + oo.syniverse_insert_sql + oo.jt_insert_sql
with ThreadPoolExecutor(max_workers=20) as executor:
tasks = [executor.submit(oo.insert_mock_db) for i in range(len(sum_sql_list))]
wait(tasks, return_when=ALL_COMPLETED)
print('all_cone')
# 调用API接口将SIM卡的excel 文件上传
# print('主线程有没有等?')
res = oo.import_excel(import_sim_url)
# obj = models.Task.objects.filter(task_id=task_id).first()
# 准备在这里判断res的内容,如果返回的是成功,就修改数据库任务的状态为已完成,否则失败
print(f"result ----->{res}")
loggings.debug("处理异步任务完成")
return res
- 调用后,检查redis,在数据库1中存放了队列,数据库2中存放了返回值(注意返回值需要转换成json格式)
遗留问题:返回值如何处理。
1、异步任务生成的id要存起来
2、弄个定时任务,根据task_id去轮询任务表中状态为处理中的任务在redis中的返回值