新功能: 添加后端代码
This commit is contained in:
0
backend/application/__init__.py
Normal file
0
backend/application/__init__.py
Normal file
28
backend/application/asgi.py
Normal file
28
backend/application/asgi.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
ASGI config for application project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
from django.core.asgi import get_asgi_application
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||
|
||||
|
||||
|
||||
http_application = get_asgi_application()
|
||||
from application.routing import websocket_urlpatterns
|
||||
application = ProtocolTypeRouter({
|
||||
"http":http_application,
|
||||
'websocket': AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
websocket_urlpatterns #指明路由文件是devops/routing.py
|
||||
)
|
||||
),
|
||||
})
|
||||
18
backend/application/celery.py
Normal file
18
backend/application/celery.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import os
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
|
||||
from django.conf import settings
|
||||
from celery import platforms
|
||||
# 租户模式
|
||||
if "django_tenants" in settings.INSTALLED_APPS:
|
||||
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
|
||||
|
||||
app = TenantAwareCeleryApp()
|
||||
else:
|
||||
from celery import Celery
|
||||
|
||||
app = Celery(f"application")
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
platforms.C_FORCE_ROOT = True
|
||||
222
backend/application/dispatch.py
Normal file
222
backend/application/dispatch.py
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
|
||||
|
||||
def is_tenants_mode():
|
||||
"""
|
||||
判断是否为租户模式
|
||||
:return:
|
||||
"""
|
||||
return hasattr(connection, "tenant") and connection.tenant.schema_name
|
||||
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 初始化 ******************** #
|
||||
# ================================================= #
|
||||
def _get_all_dictionary():
|
||||
from dvadmin.system.models import Dictionary
|
||||
|
||||
queryset = Dictionary.objects.filter(status=True, is_value=False)
|
||||
data = []
|
||||
for instance in queryset:
|
||||
data.append(
|
||||
{
|
||||
"id": instance.id,
|
||||
"value": instance.value,
|
||||
"children": list(
|
||||
Dictionary.objects.filter(parent=instance.id)
|
||||
.filter(status=1)
|
||||
.values("label", "value", "type", "color")
|
||||
),
|
||||
}
|
||||
)
|
||||
return {ele.get("value"): ele for ele in data}
|
||||
|
||||
|
||||
def _get_all_system_config():
|
||||
data = {}
|
||||
from dvadmin.system.models import SystemConfig
|
||||
|
||||
system_config_obj = (
|
||||
SystemConfig.objects.filter(parent_id__isnull=False)
|
||||
.values("parent__key", "key", "value", "form_item_type")
|
||||
.order_by("sort")
|
||||
)
|
||||
for system_config in system_config_obj:
|
||||
value = system_config.get("value", "")
|
||||
if value and system_config.get("form_item_type") == 7:
|
||||
value = value[0].get("url")
|
||||
if value and system_config.get("form_item_type") == 11:
|
||||
new_value = []
|
||||
for ele in value:
|
||||
new_value.append({
|
||||
"key": ele.get('key'),
|
||||
"title": ele.get('title'),
|
||||
"value": ele.get('value'),
|
||||
})
|
||||
new_value.sort(key=lambda s: s["key"])
|
||||
value = new_value
|
||||
data[f"{system_config.get('parent__key')}.{system_config.get('key')}"] = value
|
||||
return data
|
||||
|
||||
|
||||
def init_dictionary():
|
||||
"""
|
||||
初始化字典配置
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
if is_tenants_mode():
|
||||
from django_tenants.utils import tenant_context, get_tenant_model
|
||||
|
||||
for tenant in get_tenant_model().objects.filter():
|
||||
with tenant_context(tenant):
|
||||
settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary()
|
||||
else:
|
||||
settings.DICTIONARY_CONFIG = _get_all_dictionary()
|
||||
except Exception as e:
|
||||
print("请先进行数据库迁移!")
|
||||
return
|
||||
|
||||
|
||||
def init_system_config():
|
||||
"""
|
||||
初始化系统配置
|
||||
:param name:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
|
||||
if is_tenants_mode():
|
||||
from django_tenants.utils import tenant_context, get_tenant_model
|
||||
|
||||
for tenant in get_tenant_model().objects.filter():
|
||||
with tenant_context(tenant):
|
||||
settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config()
|
||||
else:
|
||||
settings.SYSTEM_CONFIG = _get_all_system_config()
|
||||
except Exception as e:
|
||||
print("请先进行数据库迁移!")
|
||||
return
|
||||
|
||||
|
||||
def refresh_dictionary():
|
||||
"""
|
||||
刷新字典配置
|
||||
:return:
|
||||
"""
|
||||
if is_tenants_mode():
|
||||
from django_tenants.utils import tenant_context, get_tenant_model
|
||||
|
||||
for tenant in get_tenant_model().objects.filter():
|
||||
with tenant_context(tenant):
|
||||
settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary()
|
||||
else:
|
||||
settings.DICTIONARY_CONFIG = _get_all_dictionary()
|
||||
|
||||
|
||||
def refresh_system_config():
|
||||
"""
|
||||
刷新系统配置
|
||||
:return:
|
||||
"""
|
||||
if is_tenants_mode():
|
||||
from django_tenants.utils import tenant_context, get_tenant_model
|
||||
|
||||
for tenant in get_tenant_model().objects.filter():
|
||||
with tenant_context(tenant):
|
||||
settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config()
|
||||
else:
|
||||
settings.SYSTEM_CONFIG = _get_all_system_config()
|
||||
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 字典管理 ******************** #
|
||||
# ================================================= #
|
||||
def get_dictionary_config(schema_name=None):
|
||||
"""
|
||||
获取字典所有配置
|
||||
:param schema_name: 对应字典配置的租户schema_name值
|
||||
:return:
|
||||
"""
|
||||
if not settings.DICTIONARY_CONFIG:
|
||||
refresh_dictionary()
|
||||
if is_tenants_mode():
|
||||
dictionary_config = settings.DICTIONARY_CONFIG[schema_name or connection.tenant.schema_name]
|
||||
else:
|
||||
dictionary_config = settings.DICTIONARY_CONFIG
|
||||
return dictionary_config or {}
|
||||
|
||||
|
||||
def get_dictionary_values(key, schema_name=None):
|
||||
"""
|
||||
获取字典数据数组
|
||||
:param key: 对应字典配置的key值(字典编号)
|
||||
:param schema_name: 对应字典配置的租户schema_name值
|
||||
:return:
|
||||
"""
|
||||
dictionary_config = get_dictionary_config(schema_name)
|
||||
return dictionary_config.get(key)
|
||||
|
||||
|
||||
def get_dictionary_label(key, name, schema_name=None):
|
||||
"""
|
||||
获取获取字典label值
|
||||
:param key: 字典管理中的key值(字典编号)
|
||||
:param name: 对应字典配置的value值
|
||||
:param schema_name: 对应字典配置的租户schema_name值
|
||||
:return:
|
||||
"""
|
||||
children = get_dictionary_values(key, schema_name) or []
|
||||
for ele in children:
|
||||
if ele.get("value") == str(name):
|
||||
return ele.get("label")
|
||||
return ""
|
||||
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 系统配置 ******************** #
|
||||
# ================================================= #
|
||||
def get_system_config(schema_name=None):
|
||||
"""
|
||||
获取系统配置中所有配置
|
||||
1.只传父级的key,返回全部子级,{ "父级key.子级key" : "值" }
|
||||
2."父级key.子级key",返回子级值
|
||||
:param schema_name: 对应字典配置的租户schema_name值
|
||||
:return:
|
||||
"""
|
||||
if not settings.SYSTEM_CONFIG:
|
||||
refresh_system_config()
|
||||
if is_tenants_mode():
|
||||
dictionary_config = settings.SYSTEM_CONFIG[schema_name or connection.tenant.schema_name]
|
||||
else:
|
||||
dictionary_config = settings.SYSTEM_CONFIG
|
||||
return dictionary_config or {}
|
||||
|
||||
|
||||
def get_system_config_values(key, schema_name=None):
|
||||
"""
|
||||
获取系统配置数据数组
|
||||
:param key: 对应系统配置的key值(字典编号)
|
||||
:param schema_name: 对应系统配置的租户schema_name值
|
||||
:return:
|
||||
"""
|
||||
system_config = get_system_config(schema_name)
|
||||
return system_config.get(key)
|
||||
|
||||
|
||||
def get_system_config_label(key, name, schema_name=None):
|
||||
"""
|
||||
获取获取系统配置label值
|
||||
:param key: 系统配置中的key值(字典编号)
|
||||
:param name: 对应系统配置的value值
|
||||
:param schema_name: 对应系统配置的租户schema_name值
|
||||
:return:
|
||||
"""
|
||||
children = get_system_config_values(key, schema_name) or []
|
||||
for ele in children:
|
||||
if ele.get("value") == str(name):
|
||||
return ele.get("label")
|
||||
return ""
|
||||
8
backend/application/routing.py
Normal file
8
backend/application/routing.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.urls import path
|
||||
from application.websocketConfig import MegCenter
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/<str:service_uid>/', MegCenter.as_asgi()), #consumers.DvadminWebSocket 是该路由的消费者
|
||||
]
|
||||
|
||||
405
backend/application/settings.py
Normal file
405
backend/application/settings.py
Normal file
@@ -0,0 +1,405 @@
|
||||
"""
|
||||
Django settings for application project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.2.3.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 动态配置 ******************** #
|
||||
# ================================================= #
|
||||
|
||||
from conf.env import *
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = "django-insecure--z8%exyzt7e_%i@1+#1mm=%lb5=^fx_57=1@a+_y7bg5-w%)sm"
|
||||
# 初始化plugins插件路径到环境变量中
|
||||
PLUGINS_PATH = os.path.join(BASE_DIR, "plugins")
|
||||
sys.path.insert(0, os.path.join(PLUGINS_PATH))
|
||||
|
||||
[
|
||||
sys.path.insert(0, os.path.join(PLUGINS_PATH, ele))
|
||||
for ele in os.listdir(PLUGINS_PATH)
|
||||
if os.path.isdir(os.path.join(PLUGINS_PATH, ele)) and not ele.startswith("__")
|
||||
]
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = locals().get("DEBUG", True)
|
||||
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django_comment_migrate",
|
||||
"rest_framework",
|
||||
"django_filters",
|
||||
"corsheaders", # 注册跨域app
|
||||
"dvadmin.system",
|
||||
"drf_yasg",
|
||||
"captcha",
|
||||
'channels',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware", # 跨域中间件
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"dvadmin.utils.middleware.ApiLoggingMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "application.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "application.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": DATABASE_ENGINE,
|
||||
"NAME": DATABASE_NAME,
|
||||
"USER": DATABASE_USER,
|
||||
"PASSWORD": DATABASE_PASSWORD,
|
||||
"HOST": DATABASE_HOST,
|
||||
"PORT": DATABASE_PORT,
|
||||
}
|
||||
}
|
||||
AUTH_USER_MODEL = "system.Users"
|
||||
USERNAME_FIELD = "username"
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "zh-hans"
|
||||
|
||||
TIME_ZONE = "Asia/Shanghai"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = False
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
# # 设置django的静态文件目录
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
]
|
||||
|
||||
MEDIA_ROOT = "media" # 项目下的目录
|
||||
MEDIA_URL = "/media/" # 跟STATIC_URL类似,指定用户可以通过这个url找到文件
|
||||
|
||||
# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释
|
||||
# python manage.py collectstatic
|
||||
# STATIC_ROOT=os.path.join(BASE_DIR,'static')
|
||||
|
||||
# ================================================= #
|
||||
# ******************* 跨域的配置 ******************* #
|
||||
# ================================================= #
|
||||
|
||||
# 全部允许配置
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
# 允许cookie
|
||||
CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持对cookie的操作
|
||||
|
||||
# ================================================= #
|
||||
# ********************* channels配置 ******************* #
|
||||
# ================================================= #
|
||||
ASGI_APPLICATION = 'application.asgi.application'
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
# CHANNEL_LAYERS = {
|
||||
# 'default': {
|
||||
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
# 'CONFIG': {
|
||||
# "hosts": [('127.0.0.1', 6379)], #需修改
|
||||
# },
|
||||
# },
|
||||
# }
|
||||
|
||||
|
||||
# ================================================= #
|
||||
# ********************* 日志配置 ******************* #
|
||||
# ================================================= #
|
||||
|
||||
# log 配置部分BEGIN #
|
||||
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
|
||||
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
|
||||
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
|
||||
os.makedirs(os.path.join(BASE_DIR, "logs"))
|
||||
|
||||
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
|
||||
# 格式:[日期][模块.函数名称():行号] [级别] 信息
|
||||
STANDARD_LOG_FORMAT = (
|
||||
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
||||
)
|
||||
CONSOLE_LOG_FORMAT = (
|
||||
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
||||
)
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"standard": {"format": STANDARD_LOG_FORMAT},
|
||||
"console": {
|
||||
"format": CONSOLE_LOG_FORMAT,
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
"file": {
|
||||
"format": CONSOLE_LOG_FORMAT,
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"file": {
|
||||
"level": "INFO",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": SERVER_LOGS_FILE,
|
||||
"maxBytes": 1024 * 1024 * 100, # 100 MB
|
||||
"backupCount": 5, # 最多备份5个
|
||||
"formatter": "standard",
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
"error": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": ERROR_LOGS_FILE,
|
||||
"maxBytes": 1024 * 1024 * 100, # 100 MB
|
||||
"backupCount": 3, # 最多备份3个
|
||||
"formatter": "standard",
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
"console": {
|
||||
"level": "INFO",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "console",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
# default日志
|
||||
"": {
|
||||
"handlers": ["console", "error", "file"],
|
||||
"level": "INFO",
|
||||
},
|
||||
"django": {
|
||||
"handlers": ["console", "error", "file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"scripts": {
|
||||
"handlers": ["console", "error", "file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
# 数据库相关日志
|
||||
"django.db.backends": {
|
||||
"handlers": [],
|
||||
"propagate": True,
|
||||
"level": "INFO",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# ================================================= #
|
||||
# *************** REST_FRAMEWORK配置 *************** #
|
||||
# ================================================= #
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 日期时间格式配置
|
||||
"DATE_FORMAT": "%Y-%m-%d",
|
||||
"DEFAULT_FILTER_BACKENDS": (
|
||||
# 'django_filters.rest_framework.DjangoFilterBackend',
|
||||
"dvadmin.utils.filters.CustomDjangoFilterBackend",
|
||||
"rest_framework.filters.SearchFilter",
|
||||
"rest_framework.filters.OrderingFilter",
|
||||
),
|
||||
"DEFAULT_PAGINATION_CLASS": "dvadmin.utils.pagination.CustomPagination", # 自定义分页
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
),
|
||||
"DEFAULT_PERMISSION_CLASSES": [
|
||||
"rest_framework.permissions.IsAuthenticated", # 只有经过身份认证确定用户身份才能访问
|
||||
# 'rest_framework.permissions.IsAdminUser', # is_staff=True才能访问 —— 管理员(员工)权限
|
||||
# 'rest_framework.permissions.AllowAny', # 允许所有
|
||||
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 有身份 或者 只读访问(self.list,self.retrieve)
|
||||
],
|
||||
"EXCEPTION_HANDLER": "dvadmin.utils.exception.CustomExceptionHandler", # 自定义的异常处理
|
||||
}
|
||||
# ================================================= #
|
||||
# ******************** 登录方式配置 ******************** #
|
||||
# ================================================= #
|
||||
|
||||
AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"]
|
||||
# ================================================= #
|
||||
# ****************** simplejwt配置 ***************** #
|
||||
# ================================================= #
|
||||
from datetime import timedelta
|
||||
|
||||
SIMPLE_JWT = {
|
||||
# token有效时长
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=120),
|
||||
# token刷新后的有效时间
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
|
||||
# 设置前缀
|
||||
"AUTH_HEADER_TYPES": ("JWT",),
|
||||
"ROTATE_REFRESH_TOKENS": True,
|
||||
}
|
||||
|
||||
# ====================================#
|
||||
# ****************swagger************#
|
||||
# ====================================#
|
||||
SWAGGER_SETTINGS = {
|
||||
# 基础样式
|
||||
"SECURITY_DEFINITIONS": {"basic": {"type": "basic"}},
|
||||
# 如果需要登录才能够查看接口文档, 登录的链接使用restframework自带的.
|
||||
"LOGIN_URL": "apiLogin/",
|
||||
# 'LOGIN_URL': 'rest_framework:login',
|
||||
"LOGOUT_URL": "rest_framework:logout",
|
||||
# 'DOC_EXPANSION': None,
|
||||
# 'SHOW_REQUEST_HEADERS':True,
|
||||
# 'USE_SESSION_AUTH': True,
|
||||
# 'DOC_EXPANSION': 'list',
|
||||
# 接口文档中方法列表以首字母升序排列
|
||||
"APIS_SORTER": "alpha",
|
||||
# 如果支持json提交, 则接口文档中包含json输入框
|
||||
"JSON_EDITOR": True,
|
||||
# 方法列表字母排序
|
||||
"OPERATIONS_SORTER": "alpha",
|
||||
"VALIDATOR_URL": None,
|
||||
"AUTO_SCHEMA_TYPE": 2, # 分组根据url层级分,0、1 或 2 层
|
||||
"DEFAULT_AUTO_SCHEMA_CLASS": "dvadmin.utils.swagger.CustomSwaggerAutoSchema",
|
||||
}
|
||||
|
||||
# ================================================= #
|
||||
# **************** 验证码配置 ******************* #
|
||||
# ================================================= #
|
||||
CAPTCHA_IMAGE_SIZE = (160, 60) # 设置 captcha 图片大小
|
||||
CAPTCHA_LENGTH = 4 # 字符个数
|
||||
CAPTCHA_TIMEOUT = 1 # 超时(minutes)
|
||||
CAPTCHA_OUTPUT_FORMAT = "%(image)s %(text_field)s %(hidden_field)s "
|
||||
CAPTCHA_FONT_SIZE = 40 # 字体大小
|
||||
CAPTCHA_FOREGROUND_COLOR = "#64DAAA" # 前景色
|
||||
CAPTCHA_BACKGROUND_COLOR = "#F5F7F4" # 背景色
|
||||
CAPTCHA_NOISE_FUNCTIONS = (
|
||||
"captcha.helpers.noise_arcs", # 线
|
||||
# "captcha.helpers.noise_dots", # 点
|
||||
)
|
||||
# CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' #字母验证码
|
||||
CAPTCHA_CHALLENGE_FUNCT = "captcha.helpers.math_challenge" # 加减乘除验证码
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 其他配置 ******************** #
|
||||
# ================================================= #
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
API_LOG_ENABLE = True
|
||||
# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE']
|
||||
API_LOG_METHODS = ["POST", "UPDATE", "DELETE", "PUT"] # ['POST', 'DELETE']
|
||||
API_MODEL_MAP = {
|
||||
"/token/": "登录模块",
|
||||
"/api/login/": "登录模块",
|
||||
"/api/plugins_market/plugins/": "插件市场",
|
||||
}
|
||||
|
||||
DJANGO_CELERY_BEAT_TZ_AWARE = False
|
||||
CELERY_TIMEZONE = "Asia/Shanghai" # celery 时区问题
|
||||
# 静态页面压缩
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"
|
||||
|
||||
ALL_MODELS_OBJECTS = [] # 所有app models 对象
|
||||
|
||||
# 初始化需要执行的列表,用来初始化后执行
|
||||
INITIALIZE_LIST = []
|
||||
INITIALIZE_RESET_LIST = []
|
||||
# 表前缀
|
||||
TABLE_PREFIX = locals().get('TABLE_PREFIX', "")
|
||||
# 系统配置
|
||||
SYSTEM_CONFIG = {}
|
||||
# 字典配置
|
||||
DICTIONARY_CONFIG = {}
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 插件配置 ******************** #
|
||||
# ================================================= #
|
||||
# 租户共享app
|
||||
TENANT_SHARED_APPS = []
|
||||
# 插件 urlpatterns
|
||||
PLUGINS_URL_PATTERNS = []
|
||||
# ********** 一键导入插件配置开始 **********
|
||||
# 例如:
|
||||
# from dvadmin_upgrade_center.settings import * # 升级中心
|
||||
# from dvadmin_celery.settings import * # celery 异步任务
|
||||
# from dvadmin_third.settings import * # 第三方用户管理
|
||||
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
||||
# from dvadmin_tenants.settings import * # 租户管理
|
||||
# ...
|
||||
# ********** 一键导入插件配置结束 **********
|
||||
88
backend/application/urls.py
Normal file
88
backend/application/urls.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""backend URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/3.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import path, include, re_path
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import permissions
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenRefreshView,
|
||||
)
|
||||
|
||||
from application import dispatch
|
||||
from application import settings
|
||||
from dvadmin.system.views.dictionary import InitDictionaryViewSet
|
||||
from dvadmin.system.views.login import (
|
||||
LoginView,
|
||||
CaptchaView,
|
||||
ApiLogin,
|
||||
LogoutView,
|
||||
)
|
||||
from dvadmin.system.views.system_config import InitSettingsViewSet
|
||||
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
|
||||
|
||||
# =========== 初始化系统配置 =================
|
||||
dispatch.init_system_config()
|
||||
dispatch.init_dictionary()
|
||||
# =========== 初始化系统配置 =================
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="Snippets API",
|
||||
default_version="v1",
|
||||
description="Test description",
|
||||
terms_of_service="https://www.google.com/policies/terms/",
|
||||
contact=openapi.Contact(email="contact@snippets.local"),
|
||||
license=openapi.License(name="BSD License"),
|
||||
),
|
||||
public=True,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
generator_class=CustomOpenAPISchemaGenerator,
|
||||
)
|
||||
|
||||
urlpatterns = (
|
||||
[
|
||||
re_path(
|
||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||
schema_view.without_ui(cache_timeout=0),
|
||||
name="schema-json",
|
||||
),
|
||||
path(
|
||||
"",
|
||||
schema_view.with_ui("swagger", cache_timeout=0),
|
||||
name="schema-swagger-ui",
|
||||
),
|
||||
path(
|
||||
r"redoc/",
|
||||
schema_view.with_ui("redoc", cache_timeout=0),
|
||||
name="schema-redoc",
|
||||
),
|
||||
path("api/system/", include("dvadmin.system.urls")),
|
||||
path("api/login/", LoginView.as_view(), name="token_obtain_pair"),
|
||||
path("api/logout/", LogoutView.as_view(), name="token_obtain_pair"),
|
||||
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
|
||||
re_path(
|
||||
r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")
|
||||
),
|
||||
path("api/captcha/", CaptchaView.as_view()),
|
||||
path("api/init/dictionary/", InitDictionaryViewSet.as_view()),
|
||||
path("api/init/settings/", InitSettingsViewSet.as_view()),
|
||||
path("apiLogin/", ApiLogin.as_view()),
|
||||
]
|
||||
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
|
||||
+ [re_path(ele.get('re_path'), include(ele.get('include'))) for ele in settings.PLUGINS_URL_PATTERNS]
|
||||
)
|
||||
181
backend/application/websocketConfig.py
Normal file
181
backend/application/websocketConfig.py
Normal file
@@ -0,0 +1,181 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import urllib
|
||||
|
||||
from asgiref.sync import sync_to_async, async_to_sync
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer, AsyncWebsocketConsumer
|
||||
import json
|
||||
|
||||
from channels.layers import get_channel_layer
|
||||
from jwt import InvalidSignatureError
|
||||
from rest_framework.request import Request
|
||||
|
||||
from application import settings
|
||||
from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser
|
||||
from dvadmin.system.views.message_center import MessageCenterTargetUserSerializer
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
|
||||
send_dict = {}
|
||||
|
||||
|
||||
# 发送消息结构体
|
||||
def set_message(sender, msg_type, msg, unread=0):
|
||||
text = {
|
||||
'sender': sender,
|
||||
'contentType': msg_type,
|
||||
'content': msg,
|
||||
'unread': unread
|
||||
}
|
||||
return text
|
||||
|
||||
|
||||
# 异步获取消息中心的目标用户
|
||||
@database_sync_to_async
|
||||
def _get_message_center_instance(message_id):
|
||||
from dvadmin.system.models import MessageCenter
|
||||
_MessageCenter = MessageCenter.objects.filter(id=message_id).values_list('target_user', flat=True)
|
||||
if _MessageCenter:
|
||||
return _MessageCenter
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def _get_message_unread(user_id):
|
||||
"""获取用户的未读消息数量"""
|
||||
from dvadmin.system.models import MessageCenterTargetUser
|
||||
count = MessageCenterTargetUser.objects.filter(users=user_id, is_read=False).count()
|
||||
return count or 0
|
||||
|
||||
|
||||
def request_data(scope):
|
||||
query_string = scope.get('query_string', b'').decode('utf-8')
|
||||
qs = urllib.parse.parse_qs(query_string)
|
||||
return qs
|
||||
|
||||
|
||||
class DvadminWebSocket(AsyncJsonWebsocketConsumer):
|
||||
async def connect(self):
|
||||
try:
|
||||
import jwt
|
||||
self.service_uid = self.scope["url_route"]["kwargs"]["service_uid"]
|
||||
decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
if decoded_result:
|
||||
self.user_id = decoded_result.get('user_id')
|
||||
self.chat_group_name = "user_" + str(self.user_id)
|
||||
# 收到连接时候处理,
|
||||
await self.channel_layer.group_add(
|
||||
self.chat_group_name,
|
||||
self.channel_name
|
||||
)
|
||||
await self.accept()
|
||||
# 主动推送消息
|
||||
unread_count = await _get_message_unread(self.user_id)
|
||||
if unread_count == 0:
|
||||
# 发送连接成功
|
||||
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
|
||||
else:
|
||||
await self.send_json(
|
||||
set_message('system', 'SYSTEM', "请查看您的未读消息~",
|
||||
unread=unread_count))
|
||||
except InvalidSignatureError:
|
||||
await self.disconnect(None)
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
# Leave room group
|
||||
await self.channel_layer.group_discard(self.chat_group_name, self.channel_name)
|
||||
print("连接关闭")
|
||||
try:
|
||||
await self.close(close_code)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class MegCenter(DvadminWebSocket):
|
||||
"""
|
||||
消息中心
|
||||
"""
|
||||
|
||||
async def receive(self, text_data):
|
||||
# 接受客户端的信息,你处理的函数
|
||||
text_data_json = json.loads(text_data)
|
||||
message_id = text_data_json.get('message_id', None)
|
||||
user_list = await _get_message_center_instance(message_id)
|
||||
for send_user in user_list:
|
||||
await self.channel_layer.group_send(
|
||||
"user_" + str(send_user),
|
||||
{'type': 'push.message', 'json': text_data_json}
|
||||
)
|
||||
|
||||
async def push_message(self, event):
|
||||
"""消息发送"""
|
||||
message = event['json']
|
||||
await self.send(text_data=json.dumps(message))
|
||||
|
||||
|
||||
class MessageCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
消息中心-新增-序列化器
|
||||
"""
|
||||
class Meta:
|
||||
model = MessageCenter
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def websocket_push(user_id,message):
|
||||
username = "user_" + str(user_id)
|
||||
channel_layer = get_channel_layer()
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
username,
|
||||
{
|
||||
"type": "push.message",
|
||||
"json": message
|
||||
}
|
||||
)
|
||||
|
||||
def create_message_push(title: str, content: str, target_type: int=0, target_user: list=[], target_dept=None, target_role=None,
|
||||
message: dict = {'contentType': 'INFO', 'content': '测试~'}, request= Request):
|
||||
if message is None:
|
||||
message = {"contentType": "INFO", "content": None}
|
||||
if target_role is None:
|
||||
target_role = []
|
||||
if target_dept is None:
|
||||
target_dept = []
|
||||
data = {
|
||||
"title": title,
|
||||
"content": content,
|
||||
"target_type": target_type,
|
||||
"target_user":target_user,
|
||||
"target_dept":target_dept,
|
||||
"target_role":target_role
|
||||
}
|
||||
message_center_instance = MessageCreateSerializer(data=data,request=request)
|
||||
message_center_instance.is_valid(raise_exception=True)
|
||||
message_center_instance.save()
|
||||
users = target_user or []
|
||||
if target_type in [1]: # 按角色
|
||||
users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True)
|
||||
if target_type in [2]: # 按部门
|
||||
users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True)
|
||||
if target_type in [3]: # 系统通知
|
||||
users = Users.objects.values_list('id', flat=True)
|
||||
targetuser_data = []
|
||||
for user in users:
|
||||
targetuser_data.append({
|
||||
"messagecenter": message_center_instance.instance.id,
|
||||
"users": user
|
||||
})
|
||||
targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=request)
|
||||
targetuser_instance.is_valid(raise_exception=True)
|
||||
targetuser_instance.save()
|
||||
for user in users:
|
||||
username = "user_" + str(user)
|
||||
unread_count = async_to_sync(_get_message_unread)(user)
|
||||
channel_layer = get_channel_layer()
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
username,
|
||||
{
|
||||
"type": "push.message",
|
||||
"json": {**message,'unread':unread_count}
|
||||
}
|
||||
)
|
||||
17
backend/application/wsgi.py
Normal file
17
backend/application/wsgi.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
WSGI config for backend project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||
|
||||
application = get_wsgi_application()
|
||||
Reference in New Issue
Block a user