定时任务完成

pull/2/head
李强 2021-03-25 01:09:30 +08:00
parent 29993485bb
commit 3226d7eb7e
14 changed files with 162 additions and 43 deletions

View File

@ -101,11 +101,14 @@ npm run build:prod
5. 执行迁移命令:
python3 manage.py makemigrations
python3 manage.py migrate
5. 初始化数据
6. 初始化数据
python3 manage.py init
6. 启动项目
7. 启动项目
python3 manage.py runserver 0.0.0.0:8000
定时任务启动命令:
celery -A application worker -B --loglevel=info
初始账号admin 密码123456
后端接口文档地址http://127.0.0.1:8000/docs/

View File

@ -113,28 +113,27 @@ services:
- dvadmin_net
# docker-celery:
# build:
# context: .
# dockerfile: ./docker_env/celery/Dockerfile
# # image: celery:1.0
# container_name: docker-celery
# working_dir: /dvadmin-backend
# # command: bash
# depends_on:
# - dvadmin-mysql
# - dvadmin-redis
# - dvadmin-django
# volumes:
# - ./dvadmin-backend:/dvadmin-backend
# - ./logs/log:/var/log
# ports:
# - "5672:5672"
# expose:
# - "5672"
# restart: always
# networks:
# - dvadmin_net
dvadmin-celery:
build:
context: .
dockerfile: ./docker_env/celery/Dockerfile
# image: django:2.2
container_name: dvadmin-celery
working_dir: /dvadmin-backend
# command: bash -c "python manage.py makemigrations && python manage.py migrate && python manage.py collectstatic --noinput && python manage.py runserver"
# command: bash
depends_on:
- dvadmin-mysql
- dvadmin-redis
environment:
- REDIS_HOST=dvadmin-redis
- DATABASE_HOST=dvadmin-mysql
volumes:
- ./dvadmin-backend:/dvadmin-backend
- ./logs/log:/var/log
restart: always
networks:
- dvadmin_net
dvadmin-nginx:

View File

@ -1,16 +1,16 @@
FROM python:3.7
# ENV PYTHONUNBUFFERED 1
# RUN sed -i s/deb.debian.org/mirrors.163.com/g /etc/apt/sources.list
# RUN cat /etc/apt/sources.list
# RUN cat /etc/apt/sources.list
# RUN apt-get clean
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get install -y python3.7-dev libpq-dev libopencv-dev python-opencv
RUN apt-get install -y redis-tools
RUN apt-get install -y redis-tools
COPY ./docker_env/celery/requirement.txt /
RUN mkdir /backend
WORKDIR /backend
RUN python3.7 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /requirement.txt --use-feature=2020-resolver
CMD ["celery", "-A", "azcrm", "worker", "-B", "--loglevel=info"]
CMD ["celery", "-A", "application", "worker", "-B", "--loglevel=info"]

View File

@ -0,0 +1,14 @@
import os
import django
from celery import Celery, platforms
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', "application.settings")
django.setup()
app = Celery(f"dvadmin")
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
platforms.C_FORCE_ROOT = True

View File

@ -45,7 +45,7 @@ INSTALLED_APPS = [
'rest_framework',
'corsheaders',
'captcha',
'djcelery',
'django_celery_beat',
# 自定义app
'apps.vadmin.permission',
'apps.vadmin.op_drf',
@ -320,3 +320,5 @@ CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge'
API_LOG_ENABLE = True
# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE']
# API_LOG_METHODS = ['POST', 'DELETE'] # ['POST', 'DELETE']
BROKER_URL = f'redis://:{REDIS_PASSWORD if REDIS_PASSWORD else ""}@{os.getenv("REDIS_HOST") or REDIS_HOST}:{REDIS_PORT}/2' #Broker使用Redis, 使用0数据库(暂时不是很清楚原理)
CELERYBEAT_SCHEDULER = 'django_celery_beat.schedulers.DatabaseScheduler' #Backend数据库

View File

@ -1,5 +1,5 @@
import django_filters
from djcelery.models import IntervalSchedule, CrontabSchedule, PeriodicTask
from django_celery_beat.models import IntervalSchedule, CrontabSchedule, PeriodicTask
class IntervalScheduleFilter(django_filters.rest_framework.FilterSet):
@ -11,7 +11,7 @@ class IntervalScheduleFilter(django_filters.rest_framework.FilterSet):
class CrontabScheduleFilter(django_filters.rest_framework.FilterSet):
class Meta:
model = CrontabSchedule
fields = '__all__'
exclude = ('timezone',)
class PeriodicTaskFilter(django_filters.rest_framework.FilterSet):

View File

@ -1,4 +1,4 @@
from djcelery.models import IntervalSchedule, CrontabSchedule, PeriodicTask
from django_celery_beat.models import IntervalSchedule, CrontabSchedule, PeriodicTask
from rest_framework import serializers
from ..op_drf.serializers import CustomModelSerializer
@ -23,7 +23,7 @@ class CrontabScheduleSerializer(CustomModelSerializer):
class Meta:
model = CrontabSchedule
fields = '__all__'
exclude = ('timezone',)
class PeriodicTaskSerializer(CustomModelSerializer):

View File

@ -1,5 +1,5 @@
from djcelery.admin import TaskSelectWidget
from djcelery.models import IntervalSchedule, CrontabSchedule, PeriodicTask
from django_celery_beat.admin import TaskSelectWidget
from django_celery_beat.models import IntervalSchedule, CrontabSchedule, PeriodicTask
from rest_framework.views import APIView
from ..celery.filters import IntervalScheduleFilter, CrontabScheduleFilter, PeriodicTaskFilter

View File

@ -0,0 +1,20 @@
import datetime
import logging
from captcha.models import CaptchaStore
from ..utils.decorators import BaseCeleryApp
from ..utils.response import SuccessResponse
logger = logging.getLogger(__name__)
@BaseCeleryApp(name='apps.vadmin.permission.tasks.clear_invalid_captcha')
def clear_invalid_captcha():
"""
清除数据库中废弃失效的验证码
:return:
"""
queryset = CaptchaStore.objects.filter(expiration__lt=datetime.datetime.now())
msg = f"成功删除 {queryset.count()} 条失效验证码!"
logger.info(msg)
queryset.delete()
return SuccessResponse(msg=msg)

View File

@ -7,4 +7,5 @@ from ..models.message_push import MessagePush
from ..models.message_push import MessagePushUser
from ..models.logininfor import LoginInfor
from ..models.operation_log import OperationLog
from ..models.celery_log import CeleryLog

View File

@ -0,0 +1,18 @@
from django.db.models import CharField, BooleanField, TextField
from ...op_drf.models import CoreModel
class CeleryLog(CoreModel):
name = CharField(max_length=256, verbose_name='任务名称', help_text='任务名称')
kwargs = TextField(max_length=1024, verbose_name='执行参数', help_text='运行参数')
seconds = CharField(max_length=8, verbose_name='执行时间')
state = BooleanField(default=False, verbose_name='运行状态')
result = TextField(max_length=10240, verbose_name='任务结果', help_text='任务返回内容')
class Meta:
verbose_name = 'celery定时日志'
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.creator and self.creator.name}"

View File

@ -1,17 +1,22 @@
"""
常用的装饰器以及DRF的装饰器
"""
import functools
import logging
import time
import traceback
from datetime import datetime
import functools
from collections import Iterable
from datetime import datetime
from django.conf import settings
from rest_framework_extensions.settings import extensions_api_settings
from django.utils import six
from django.utils.decorators import available_attrs
from rest_framework.response import Response
from rest_framework_extensions.settings import extensions_api_settings
from application.celery import app
from .string_util import bas64_encode_text, bas64_decode_text
from ..system.models import CeleryLog
def get_cache(alias=None):
@ -19,12 +24,46 @@ def get_cache(alias=None):
return caches[alias or extensions_api_settings.DEFAULT_USE_CACHE]
logger = logging.getLogger(__name__)
def BaseCeleryApp(name):
def wraps(func):
@app.task(name=name)
@functools.wraps(func)
def wrapper(*args, **kwargs):
obj = CeleryLog()
obj.name = str(func.__name__)
obj.kwargs = f"*args{args}\n**kwargs{kwargs}"
start_time = datetime.now()
res = None
try:
res = func(*args, **kwargs)
obj.result = str(res)
obj.state = True
except Exception as exc:
obj.state = False
obj.result = f"执行失败,错误信息:{exc}"
logger.info(f"传入参数:{args, kwargs}")
logger.error(f"执行失败,错误信息:{exc}")
end_time = datetime.now()
seconds = (end_time - start_time).seconds
obj.seconds = seconds
obj.save()
return res
return wrapper
return wraps
def print_fun_time(logger=None):
"""
打印函数执行时间
:param logger:
:return:
"""
def wrapper(func):
@functools.wraps(func)
def inner(*args, **kwargs):
@ -37,28 +76,34 @@ def print_fun_time(logger=None):
else:
print(f"{func.__name__}耗时:{seconds}")
return res
return inner
return wrapper
def print_time(logger=None):
"""
打印函数执行时间
:param logger:
:return:
"""
def wrapper(func):
@functools.wraps(func)
def inner(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
end_time = time.time()
seconds = "%.3f"% (end_time - start_time)
seconds = "%.3f" % (end_time - start_time)
if logger:
logger.info(f"{func.__name__}耗时:{seconds}")
else:
print(f"{func.__name__}耗时:{seconds}")
return res
return inner
return wrapper
@ -68,11 +113,13 @@ def bas64_encode(func):
:param func:
:return:
"""
def inner(*args, **kwargs):
text = func(*args, **kwargs)
if isinstance(text, str):
text = bas64_encode_text(text)
return text
return inner
@ -82,11 +129,13 @@ def bas64_decode(func):
:param func:
:return:
"""
def inner(*args, **kwargs):
text = func(*args, **kwargs)
if isinstance(text, str):
text = bas64_decode_text(text)
return text
return inner
@ -95,6 +144,7 @@ def decode(crypto=""):
解密装饰器:BASE64
:param crypto: 解密算法名称(忽略大小写)
"""
def wrapper(func):
def inner(*args, **kwargs):
text = func(*args, **kwargs)
@ -104,7 +154,9 @@ def decode(crypto=""):
else:
text = text
return text
return inner
return wrapper
@ -113,11 +165,14 @@ def encode(crypto=""):
解密装饰器:BASE64
:param crypto: 解密算法名称(忽略大小写)
"""
def wrapper(func):
def inner(*args, **kwargs):
text = func(*args, **kwargs)
return text
return inner
return wrapper
@ -133,6 +188,7 @@ def envFunction(envs='', execute=True, result=None):
pass
"""
def wrapper(func):
@functools.wraps(func)
def inner(*args, **kwargs):
@ -141,14 +197,16 @@ def envFunction(envs='', execute=True, result=None):
environments = []
elif isinstance(envs, str):
environments = [envs, ]
elif isinstance(envs, (Iterable, )):
elif isinstance(envs, (Iterable,)):
environments = envs
if settings.PROJECT_ENV in environments and execute:
return func(*args, **kwargs)
elif settings.PROJECT_ENV not in environments and not execute:
return func(*args, **kwargs)
return result
return inner
return wrapper
@ -161,6 +219,7 @@ def exceptionHandler(logger=None, throw=False, result=None, message=None):
:param message: 错误信息
:return:
"""
def wrapper(func):
@functools.wraps(func)
def inner(*args, **kwargs):
@ -174,7 +233,9 @@ def exceptionHandler(logger=None, throw=False, result=None, message=None):
if throw:
raise e
return result
return inner
return wrapper
@ -203,6 +264,7 @@ class CacheResponse(object):
def __call__(self, func):
this = self
@functools.wraps(func, assigned=available_attrs(func))
def inner(self, request, *args, **kwargs):
return this.process_cache_response(
@ -212,6 +274,7 @@ class CacheResponse(object):
args=args,
kwargs=kwargs,
)
return inner
def process_cache_response(self,
@ -274,4 +337,3 @@ class CacheResponse(object):
cache_response = CacheResponse

View File

@ -1,7 +1,7 @@
asgiref==3.3.1
Django==2.2.16
django-cors-headers==3.7.0
django-celery==3.2.2
django_celery_beat==2.2.0
django-filter==2.4.0
django-ranged-response==0.2.0
django-redis==4.12.1

View File

@ -31,7 +31,7 @@
</el-autocomplete>
</el-form-item>
<el-form-item :rules="[{ required: true, message: '名称不能为空'}]" prop="name" label="名称:">
<el-input v-model="form.name" placeholder="例如: 主机表同步任务" style="width: 400px;"/>
<el-input v-model="form.name" placeholder="例如: XXX同步任务" style="width: 400px;"/>
</el-form-item>
<el-form-item prop="interval" label="任务频率:">
<el-select v-model="form.interval" placeholder="请选择任务频率" style="width: 400px;" @change="form.crontab = ''">