From 3226d7eb7edae89755cfe1e8d28698080a4c0f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Thu, 25 Mar 2021 01:09:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++- docker-compose.yml | 43 ++++++----- docker_env/celery/Dockerfile | 6 +- dvadmin-backend/application/celery.py | 14 ++++ dvadmin-backend/application/settings.py | 4 +- dvadmin-backend/apps/vadmin/celery/filters.py | 4 +- .../apps/vadmin/celery/serializers.py | 4 +- dvadmin-backend/apps/vadmin/celery/views.py | 4 +- .../apps/vadmin/permission/tasks.py | 20 +++++ .../apps/vadmin/system/models/__init__.py | 1 + .../apps/vadmin/system/models/celery_log.py | 18 +++++ .../apps/vadmin/utils/decorators.py | 74 +++++++++++++++++-- dvadmin-backend/requirements.txt | 2 +- .../periodic-task/edit-form-periodic-task.vue | 2 +- 14 files changed, 162 insertions(+), 43 deletions(-) create mode 100644 dvadmin-backend/application/celery.py create mode 100644 dvadmin-backend/apps/vadmin/system/models/celery_log.py diff --git a/README.md b/README.md index a5702af..a678795 100644 --- a/README.md +++ b/README.md @@ -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/ diff --git a/docker-compose.yml b/docker-compose.yml index 9161fa7..7cb5dd6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/docker_env/celery/Dockerfile b/docker_env/celery/Dockerfile index a45c4a1..9bc12a1 100644 --- a/docker_env/celery/Dockerfile +++ b/docker_env/celery/Dockerfile @@ -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"] diff --git a/dvadmin-backend/application/celery.py b/dvadmin-backend/application/celery.py new file mode 100644 index 0000000..bba1cb2 --- /dev/null +++ b/dvadmin-backend/application/celery.py @@ -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 diff --git a/dvadmin-backend/application/settings.py b/dvadmin-backend/application/settings.py index 60e33c0..f2894a4 100644 --- a/dvadmin-backend/application/settings.py +++ b/dvadmin-backend/application/settings.py @@ -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数据库 diff --git a/dvadmin-backend/apps/vadmin/celery/filters.py b/dvadmin-backend/apps/vadmin/celery/filters.py index 8d5b806..a4501de 100644 --- a/dvadmin-backend/apps/vadmin/celery/filters.py +++ b/dvadmin-backend/apps/vadmin/celery/filters.py @@ -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): diff --git a/dvadmin-backend/apps/vadmin/celery/serializers.py b/dvadmin-backend/apps/vadmin/celery/serializers.py index e029cb4..d2f24b3 100644 --- a/dvadmin-backend/apps/vadmin/celery/serializers.py +++ b/dvadmin-backend/apps/vadmin/celery/serializers.py @@ -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): diff --git a/dvadmin-backend/apps/vadmin/celery/views.py b/dvadmin-backend/apps/vadmin/celery/views.py index df26138..8c159b8 100644 --- a/dvadmin-backend/apps/vadmin/celery/views.py +++ b/dvadmin-backend/apps/vadmin/celery/views.py @@ -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 diff --git a/dvadmin-backend/apps/vadmin/permission/tasks.py b/dvadmin-backend/apps/vadmin/permission/tasks.py index e69de29..7da3b18 100644 --- a/dvadmin-backend/apps/vadmin/permission/tasks.py +++ b/dvadmin-backend/apps/vadmin/permission/tasks.py @@ -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) diff --git a/dvadmin-backend/apps/vadmin/system/models/__init__.py b/dvadmin-backend/apps/vadmin/system/models/__init__.py index 3d85a6d..51ad9c7 100644 --- a/dvadmin-backend/apps/vadmin/system/models/__init__.py +++ b/dvadmin-backend/apps/vadmin/system/models/__init__.py @@ -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 diff --git a/dvadmin-backend/apps/vadmin/system/models/celery_log.py b/dvadmin-backend/apps/vadmin/system/models/celery_log.py new file mode 100644 index 0000000..b20c332 --- /dev/null +++ b/dvadmin-backend/apps/vadmin/system/models/celery_log.py @@ -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}" diff --git a/dvadmin-backend/apps/vadmin/utils/decorators.py b/dvadmin-backend/apps/vadmin/utils/decorators.py index 9b352cd..1c3eb85 100644 --- a/dvadmin-backend/apps/vadmin/utils/decorators.py +++ b/dvadmin-backend/apps/vadmin/utils/decorators.py @@ -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 - diff --git a/dvadmin-backend/requirements.txt b/dvadmin-backend/requirements.txt index b99e992..e6f6139 100644 --- a/dvadmin-backend/requirements.txt +++ b/dvadmin-backend/requirements.txt @@ -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 diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/edit-form-periodic-task.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/edit-form-periodic-task.vue index b402cda..6a612a9 100644 --- a/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/edit-form-periodic-task.vue +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/edit-form-periodic-task.vue @@ -31,7 +31,7 @@ - +