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 @@
-
+