定时任务完成

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. 执行迁移命令: 5. 执行迁移命令:
python3 manage.py makemigrations python3 manage.py makemigrations
python3 manage.py migrate python3 manage.py migrate
5. 初始化数据 6. 初始化数据
python3 manage.py init python3 manage.py init
6. 启动项目 7. 启动项目
python3 manage.py runserver 0.0.0.0:8000 python3 manage.py runserver 0.0.0.0:8000
定时任务启动命令:
celery -A application worker -B --loglevel=info
初始账号admin 密码123456 初始账号admin 密码123456
后端接口文档地址http://127.0.0.1:8000/docs/ 后端接口文档地址http://127.0.0.1:8000/docs/

View File

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

View File

@ -1,16 +1,16 @@
FROM python:3.7 FROM python:3.7
# ENV PYTHONUNBUFFERED 1 # ENV PYTHONUNBUFFERED 1
# RUN sed -i s/deb.debian.org/mirrors.163.com/g /etc/apt/sources.list # 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 clean
RUN apt-get update RUN apt-get update
RUN apt-get install -y build-essential 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 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 / COPY ./docker_env/celery/requirement.txt /
RUN mkdir /backend RUN mkdir /backend
WORKDIR /backend WORKDIR /backend
RUN python3.7 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /requirement.txt --use-feature=2020-resolver 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', 'rest_framework',
'corsheaders', 'corsheaders',
'captcha', 'captcha',
'djcelery', 'django_celery_beat',
# 自定义app # 自定义app
'apps.vadmin.permission', 'apps.vadmin.permission',
'apps.vadmin.op_drf', 'apps.vadmin.op_drf',
@ -320,3 +320,5 @@ CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge'
API_LOG_ENABLE = True API_LOG_ENABLE = True
# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE'] # API_LOG_METHODS = 'ALL' # ['POST', 'DELETE']
# API_LOG_METHODS = ['POST', 'DELETE'] # ['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 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): 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 CrontabScheduleFilter(django_filters.rest_framework.FilterSet):
class Meta: class Meta:
model = CrontabSchedule model = CrontabSchedule
fields = '__all__' exclude = ('timezone',)
class PeriodicTaskFilter(django_filters.rest_framework.FilterSet): 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 rest_framework import serializers
from ..op_drf.serializers import CustomModelSerializer from ..op_drf.serializers import CustomModelSerializer
@ -23,7 +23,7 @@ class CrontabScheduleSerializer(CustomModelSerializer):
class Meta: class Meta:
model = CrontabSchedule model = CrontabSchedule
fields = '__all__' exclude = ('timezone',)
class PeriodicTaskSerializer(CustomModelSerializer): class PeriodicTaskSerializer(CustomModelSerializer):

View File

@ -1,5 +1,5 @@
from djcelery.admin import TaskSelectWidget from django_celery_beat.admin import TaskSelectWidget
from djcelery.models import IntervalSchedule, CrontabSchedule, PeriodicTask from django_celery_beat.models import IntervalSchedule, CrontabSchedule, PeriodicTask
from rest_framework.views import APIView from rest_framework.views import APIView
from ..celery.filters import IntervalScheduleFilter, CrontabScheduleFilter, PeriodicTaskFilter 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.message_push import MessagePushUser
from ..models.logininfor import LoginInfor from ..models.logininfor import LoginInfor
from ..models.operation_log import OperationLog 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的装饰器 常用的装饰器以及DRF的装饰器
""" """
import functools
import logging
import time import time
import traceback import traceback
from datetime import datetime
import functools
from collections import Iterable from collections import Iterable
from datetime import datetime
from django.conf import settings from django.conf import settings
from rest_framework_extensions.settings import extensions_api_settings
from django.utils import six from django.utils import six
from django.utils.decorators import available_attrs from django.utils.decorators import available_attrs
from rest_framework.response import Response 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 .string_util import bas64_encode_text, bas64_decode_text
from ..system.models import CeleryLog
def get_cache(alias=None): def get_cache(alias=None):
@ -19,12 +24,46 @@ def get_cache(alias=None):
return caches[alias or extensions_api_settings.DEFAULT_USE_CACHE] 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): def print_fun_time(logger=None):
""" """
打印函数执行时间 打印函数执行时间
:param logger: :param logger:
:return: :return:
""" """
def wrapper(func): def wrapper(func):
@functools.wraps(func) @functools.wraps(func)
def inner(*args, **kwargs): def inner(*args, **kwargs):
@ -37,28 +76,34 @@ def print_fun_time(logger=None):
else: else:
print(f"{func.__name__}耗时:{seconds}") print(f"{func.__name__}耗时:{seconds}")
return res return res
return inner return inner
return wrapper return wrapper
def print_time(logger=None): def print_time(logger=None):
""" """
打印函数执行时间 打印函数执行时间
:param logger: :param logger:
:return: :return:
""" """
def wrapper(func): def wrapper(func):
@functools.wraps(func) @functools.wraps(func)
def inner(*args, **kwargs): def inner(*args, **kwargs):
start_time = time.time() start_time = time.time()
res = func(*args, **kwargs) res = func(*args, **kwargs)
end_time = time.time() end_time = time.time()
seconds = "%.3f"% (end_time - start_time) seconds = "%.3f" % (end_time - start_time)
if logger: if logger:
logger.info(f"{func.__name__}耗时:{seconds}") logger.info(f"{func.__name__}耗时:{seconds}")
else: else:
print(f"{func.__name__}耗时:{seconds}") print(f"{func.__name__}耗时:{seconds}")
return res return res
return inner return inner
return wrapper return wrapper
@ -68,11 +113,13 @@ def bas64_encode(func):
:param func: :param func:
:return: :return:
""" """
def inner(*args, **kwargs): def inner(*args, **kwargs):
text = func(*args, **kwargs) text = func(*args, **kwargs)
if isinstance(text, str): if isinstance(text, str):
text = bas64_encode_text(text) text = bas64_encode_text(text)
return text return text
return inner return inner
@ -82,11 +129,13 @@ def bas64_decode(func):
:param func: :param func:
:return: :return:
""" """
def inner(*args, **kwargs): def inner(*args, **kwargs):
text = func(*args, **kwargs) text = func(*args, **kwargs)
if isinstance(text, str): if isinstance(text, str):
text = bas64_decode_text(text) text = bas64_decode_text(text)
return text return text
return inner return inner
@ -95,6 +144,7 @@ def decode(crypto=""):
解密装饰器:BASE64 解密装饰器:BASE64
:param crypto: 解密算法名称(忽略大小写) :param crypto: 解密算法名称(忽略大小写)
""" """
def wrapper(func): def wrapper(func):
def inner(*args, **kwargs): def inner(*args, **kwargs):
text = func(*args, **kwargs) text = func(*args, **kwargs)
@ -104,7 +154,9 @@ def decode(crypto=""):
else: else:
text = text text = text
return text return text
return inner return inner
return wrapper return wrapper
@ -113,11 +165,14 @@ def encode(crypto=""):
解密装饰器:BASE64 解密装饰器:BASE64
:param crypto: 解密算法名称(忽略大小写) :param crypto: 解密算法名称(忽略大小写)
""" """
def wrapper(func): def wrapper(func):
def inner(*args, **kwargs): def inner(*args, **kwargs):
text = func(*args, **kwargs) text = func(*args, **kwargs)
return text return text
return inner return inner
return wrapper return wrapper
@ -133,6 +188,7 @@ def envFunction(envs='', execute=True, result=None):
pass pass
""" """
def wrapper(func): def wrapper(func):
@functools.wraps(func) @functools.wraps(func)
def inner(*args, **kwargs): def inner(*args, **kwargs):
@ -141,14 +197,16 @@ def envFunction(envs='', execute=True, result=None):
environments = [] environments = []
elif isinstance(envs, str): elif isinstance(envs, str):
environments = [envs, ] environments = [envs, ]
elif isinstance(envs, (Iterable, )): elif isinstance(envs, (Iterable,)):
environments = envs environments = envs
if settings.PROJECT_ENV in environments and execute: if settings.PROJECT_ENV in environments and execute:
return func(*args, **kwargs) return func(*args, **kwargs)
elif settings.PROJECT_ENV not in environments and not execute: elif settings.PROJECT_ENV not in environments and not execute:
return func(*args, **kwargs) return func(*args, **kwargs)
return result return result
return inner return inner
return wrapper return wrapper
@ -161,6 +219,7 @@ def exceptionHandler(logger=None, throw=False, result=None, message=None):
:param message: 错误信息 :param message: 错误信息
:return: :return:
""" """
def wrapper(func): def wrapper(func):
@functools.wraps(func) @functools.wraps(func)
def inner(*args, **kwargs): def inner(*args, **kwargs):
@ -174,7 +233,9 @@ def exceptionHandler(logger=None, throw=False, result=None, message=None):
if throw: if throw:
raise e raise e
return result return result
return inner return inner
return wrapper return wrapper
@ -203,6 +264,7 @@ class CacheResponse(object):
def __call__(self, func): def __call__(self, func):
this = self this = self
@functools.wraps(func, assigned=available_attrs(func)) @functools.wraps(func, assigned=available_attrs(func))
def inner(self, request, *args, **kwargs): def inner(self, request, *args, **kwargs):
return this.process_cache_response( return this.process_cache_response(
@ -212,6 +274,7 @@ class CacheResponse(object):
args=args, args=args,
kwargs=kwargs, kwargs=kwargs,
) )
return inner return inner
def process_cache_response(self, def process_cache_response(self,
@ -274,4 +337,3 @@ class CacheResponse(object):
cache_response = CacheResponse cache_response = CacheResponse

View File

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

View File

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