diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d3a2bc7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +FROM python:3.7 + + +# 安装netcat +RUN apt-get update + +# 可选:设置镜像源为国内 +COPY pip.conf /root/.pip/pip.conf + +# 容器内创建 myproject 文件夹 +ENV APP_HOME=/home/myproject +RUN mkdir -p $APP_HOME +WORKDIR $APP_HOME + +# 将当前目录加入到工作目录中(. 表示当前目录) +ADD . $APP_HOME + +# 更新pip版本 +RUN /usr/local/bin/python -m pip install --upgrade pip + +# 安装vim +RUN apt-get install -y vim + +# 安装项目依赖 +RUN pip install -r requirements.txt + +# 移除\r in windows +RUN sed -i 's/\r//' ./start.sh + +# 给start.sh可执行权限 +RUN chmod +x ./start.sh +RUN chmod 777 -R /home/myproject/logs/ + +EXPOSE 8000 + +# 数据迁移,并使用uwsgi启动服务 +ENTRYPOINT /bin/bash ./start.sh \ No newline at end of file diff --git a/README.md b/README.md index b65f8e6..3ec6770 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# TP_API_3.0 +# TP_Admin +TP后台 \ No newline at end of file diff --git a/TP_API/__init__.py b/TP_API/__init__.py new file mode 100644 index 0000000..063cd2c --- /dev/null +++ b/TP_API/__init__.py @@ -0,0 +1,2 @@ +import pymysql +pymysql.install_as_MySQLdb() diff --git a/TP_API/asgi.py b/TP_API/asgi.py new file mode 100644 index 0000000..29f5acb --- /dev/null +++ b/TP_API/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for TP_API 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 + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TP_API.settings') + +application = get_asgi_application() diff --git a/TP_API/settings.py b/TP_API/settings.py new file mode 100644 index 0000000..275359b --- /dev/null +++ b/TP_API/settings.py @@ -0,0 +1,263 @@ +""" +Django settings for TP_API project. + +Generated by 'django-admin startproject' using Django 3.2.19. + +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 +from pathlib import Path +from elasticsearch_dsl import connections + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# 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-kqm6r(-!q-emw5c!!@dc)9l^hm@m#4!)-d7ecq85&u^5leu5)_' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = ["*"] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'django_filters', + 'corsheaders', + 'app', + 'event', + 'user', + 'department', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + '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', + 'mymiddleware.middleware.CheckTokenMiddleware', +] + +ROOT_URLCONF = 'TP_API.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + '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 = 'TP_API.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': BASE_DIR / 'db.sqlite3', +# } +# } + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': os.getenv("MYSQL_DATABASE") or 'tp_11_23', + 'USER': os.getenv("MYSQL_USER") or 'root', + 'PASSWORD': os.getenv("MYSQL_PASSWORD") or '#Yaxin0504', + 'HOST': os.getenv("MYSQL_HOST") or '192.168.10.96', + 'PORT': os.getenv("MYSQL_PORT") or '3306', + } +} + + +# 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/' + +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'static'), +] + +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# 解决跨域 +CORS_ALLOW_CREDENTIALS = True +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_HEADERS = ["*"] + +# CORS_ORIGIN_WHITELIST = ('*',) +# 对应的发送的请求的跨域 +CORS_ALLOW_METHODS = ( + 'DELETE', + 'GET', + 'OPTIONS', + 'PATCH', + 'POST', + 'PUT', + 'VIEW', +) + +USE_L10N = False +DATE_FORMAT = 'Y-m-d' +DATETIME_FORMAT = 'Y-m-d H:M:S' + +REST_FRAMEWORK = { + 'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S", + 'DATE_FORMAT': "%Y-%m-%d", + 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], + 'EXCEPTION_HANDLER': 'app.exception.exception_handler', + # 'DEFAULT_AUTHENTICATION_CLASSES': ( + # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 第一种jwt方式 + # # 'rest_framework.authentication.SessionAuthentication', # 第二种session方式 + # # 'rest_framework.authentication.BasicAuthentication', # 第三种Django的基本方式 + # ), + # + # 'DEFAULT_PERMISSION_CLASSES': ( + # 'rest_framework.permissions.IsAuthenticated', + # ), +} + +# 配置日志 +LOG_DIR = os.path.join(BASE_DIR, 'logs') +LOGGING = { + 'version': 1, # 保留字 + 'disable_existing_loggers': False, # 是否禁用已经存在的日志实例 + 'formatters': { # 定义日志的格式 + 'standard': { + 'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' + '[%(levelname)s]%(message)s' + }, + 'simple': { + 'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' + }, + 'collect': { + 'format': '%(message)s' + } + }, + 'filters': { # 定义日志的过滤器 + 'require_debug_true': { + '()': 'django.utils.log.RequireDebugTrue', + }, + }, + 'handlers': { # 日志处理程序 + 'console': { + 'level': 'DEBUG', + # 'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志 + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + # 'filename': os.path.join(BASE_LOG_DIR, "tpservice.log") + }, + 'file': { + 'level': 'INFO', + 'class': 'logging.handlers.RotatingFileHandler', # window多进程 需要 pip install concurrent-log-handler + 'filename': os.path.join(BASE_DIR, "logs/debug.log"), # 日志文件 + 'backupCount': 10, # 保留的最大文件数,超过则删除日期最早的 + 'maxBytes': 1024 * 1024 * 100, # 文件大小 + 'formatter': 'standard', + 'encoding': 'utf-8', + }, + # 'file': { + # 'level': 'INFO', + # 'class': 'cloghandler.ConcurrentRotatingFileHandler', # linux多进程 需要 pip install ConcurrentLogHandler + # 'filename': os.path.join(BASE_DIR, "logs/debug.log"), # 日志文件 + # 'backupCount': 10, # 保留的最大文件数,超过则删除日期最早的 + # 'maxBytes': 1024 * 1024 * 10, # 文件大小 + # 'formatter': 'standard', + # 'encoding': 'utf-8', + # }, + }, + 'loggers': { # 日志实例 记录器 + 'mylogger': { # 默认的logger应用如下配置 + 'handlers': ['console', 'file'], + 'level': 'DEBUG', + 'propagate': True, # 是否向上一级logger实例传递日志信息 + }, + }, +} + +AUTH_USER_MODEL = 'user.UserProfile' + +# connections.create_connection( +# hosts=['192.168.10.96:9208'], +# http_auth=('elastic', 'changeme') +# ) +ES_USER = os.getenv("ES_USER") or "elastic" +ES_PASSWORD = os.getenv("ES_PASSWORD") or "changeme" +ES_IP = os.getenv("ES_IP") or "192.168.10.96:9209" +connections.create_connection(hosts=[f"http://{ES_USER}:{ES_PASSWORD}@{ES_IP}"]) + +VIDEO_DOWNLOAD_PATH = os.getenv("VIDEO_DOWNLOAD_PATH") or '/home/tp/dsr/TP-Code-AI/src/media/videos' + diff --git a/TP_API/urls.py b/TP_API/urls.py new file mode 100644 index 0000000..36a6826 --- /dev/null +++ b/TP_API/urls.py @@ -0,0 +1,35 @@ +"""TP_Api 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.contrib import admin +from django.urls import path, include, re_path +from django.views.static import serve +from django.conf.urls.static import static +from django.conf import settings +from .settings import MEDIA_ROOT + + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/tps/', include('app.urls')), + path('event/', include('event.urls')), + path('api/user/', include('user.urls')), + path('api/departments/', include('department.urls')), +] + + +urlpatterns += [ + re_path(r'^media/(?P.*)$', serve, {'document_root': MEDIA_ROOT}), +] diff --git a/TP_API/wsgi.py b/TP_API/wsgi.py new file mode 100644 index 0000000..c70609e --- /dev/null +++ b/TP_API/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for TP_API 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', 'TP_API.settings') + +application = get_wsgi_application() diff --git a/TP后台接口文档.md b/TP后台接口文档.md new file mode 100644 index 0000000..a334e90 --- /dev/null +++ b/TP后台接口文档.md @@ -0,0 +1,279 @@ + + + + + + +# TP后台接口文档 +
+条件查询 + +- 请求方式:GET + +- 请求链接:http://127.0.0.1:8002/api/ + +- 请求参数: + + | 参数名 | 参数值 | 是否必填 | 参数类型 | 描述说明 | + | ----------- |---------------------| -------- |--------| ---------- | + | record_time | 2023-05-26 13:09:05 | 否 | string | 记录仪时间 | + | police_id | 00000001 | 否 | string | 警号 | + | event_type | 0 | 否 | string | 事件类型 | + +- 可选参数: + + | 参数名 | 参数值 | 是否必填 | 参数类型 | 描述说明 | + | --------- | ------ | -------- | -------- | -------------------- | + | page | 1 | 否 | string | 页码 | + | page_size | 20 | 否 | string | 页面大小(每页条数) | + +- 返回值: + + | 参数名 | 参数值 | 参数类型 | 描述说明 | + | ---------------- | ------------------------------------------------- | -------- |--------------| + | uid | 1 | int | 自增 | + | video_hash | 38fb463b135fa12534104f85492cc6f1 | string | 视频哈希值 | + | record_time | 2023-05-26 13:09:05 | string | 记录仪时间 | + | police_id | 00000001 | string | 警号 | + | event_type | 1 | string | 事件类型/车辆违法原因 | + | is_violation | true | bool | 执法人员是否违规 | + | small_image | http://192.168.0.47:8000/media/images/0000609.jpg | string | 缩略图 | + | relative_time | 4.0 | float | 相对时间 | + | video_dir | http://192.168.0.47:8000/media/video/B1.MP4 | string | 视频地址 | + | car_number | 苏a045689 | string | 车牌号 | + | ai_analysis | 违规 | string | 执法人员违规行为 | + | add_time | 2023-05-31 18:42:15 | string | 记录添加时间(自动添加) | + | update_time | 2023-05-31 18:42:15 | string | 记录更新时间(自动添加) | + | is_display | true | bool | 是否展示(自动添加) | + | is_illegal | True | bool | 行人是否违法 | + + + + +
+ +
+查询所有 + +- 请求方式:GET + +- 请求链接:http://127.0.0.1:8002/api/ + +- 可选参数: + + | 参数名 | 参数值 | 是否必填 | 参数类型 | 描述说明 | + | --------- | ------ | -------- | -------- | -------------------- | + | page | 1 | 否 | string | 页码 | + | page_size | 20 | 否 | string | 页面大小(每页条数) | + +- 返回值 + + | 参数名 | 参数值 | 参数类型 | 描述说明 | + | ------------- | ------------------------------------------------- | -------- | ------------------------ | + | uid | 1 | int | 自增 | + | video_hash | 38fb463b135fa12534104f85492cc6f1 | string | 视频哈希值 | + | record_time | 2023-05-26 13:09:05 | string | 记录仪时间 | + | police_id | 00000001 | string | 警号 | + | event_type | 1 | string | 事件类型 | + | is_violation | true | bool | 是否违规 | + | small_image | http://192.168.0.47:8000/media/images/0000609.jpg | string | 缩略图 | + | relative_time | 4.0 | float | 相对时间 | + | video_dir | http://192.168.0.47:8000/media/video/B1.MP4 | string | 视频地址 | + | car_number | 苏a045689 | string | 车牌号 | + | ai_analysis | 违规 | string | 分析结果 | + | add_time | 2023-05-31 18:42:15 | string | 记录添加时间(自动添加) | + | update_time | 2023-05-31 18:42:15 | string | 记录更新时间(自动添加) | + | is_display | true | bool | 是否展示(自动添加) | + + +
+ +
+新增数据 + +- 请求方式:POST + +- 请求链接:http://127.0.0.1:8002/api/ + +- 请求body: + + ```json + { + "video_hash": "vbhdrbvcw", + "record_time": "2023-05-26 13:09:05", + "police_id": "00000002", + "event_type": "1", + "is_violation": true, + "small_image": "nvikefrooiwer", + "relative_time": 4.0, + "video_dir": "/d/test", + "car_number": "苏a045689", + "ai_analysis": "违规" + } + ``` + + | 参数名 | 参数值 | 是否必填 | 参数类型 | 描述说明 | + | ------------- |---------------------| -------- |----------| ------ | +| video_hash | vbhdrbvcw | 是 | string | 视频哈希 | + | record_time | 2023-05-26 13:09:05 | 是 | datetime | 记录仪时间 | + | police_id | 00000002 | 是 | string | 警号 | + | event_type | 1 | 是 | string | 事件类型 | + | is_violation | true | 是 | bool | 是否违规 | + | small_image | nvikefrooiwer | 是 | string | 缩略图 | + | relative_time | 4.0 | 是 | int | 相对时间 | + | video_dir | /d/test | 是 | string | 视频路径 | + | car_number | 苏a045689 | 是 | string | 车牌号 | + | ai_analysis | 违规 | 是 | string | 分析结果 | + +- 返回值 + + | 参数名 | 参数值 | 参数类型 | 描述说明 | + | ------------- | ------------------------------------------------- | -------- | ------------------------ | + | uid | 1 | int | 自增 | + | video_hash | 38fb463b135fa12534104f85492cc6f1 | string | 视频哈希值 | + | record_time | 2023-05-26 13:09:05 | string | 记录仪时间 | + | police_id | 00000001 | string | 警号 | + | event_type | 1 | string | 事件类型 | + | is_violation | true | bool | 是否违规 | + | small_image | http://192.168.0.47:8000/media/images/0000609.jpg | string | 缩略图 | + | relative_time | 4.0 | float | 相对时间 | + | video_dir | http://192.168.0.47:8000/media/video/B1.MP4 | string | 视频地址 | + | car_number | 苏a045689 | string | 车牌号 | + | ai_analysis | 违规 | string | 分析结果 | + | add_time | 2023-05-31 18:42:15 | string | 记录添加时间(自动添加) | + | update_time | 2023-05-31 18:42:15 | string | 记录更新时间(自动添加) | + | is_display | true | bool | 是否展示(自动添加) | + + +
+ +
+修改数据 + +- 请求方式:put + +- 请求链接:http://127.0.0.1:8002/api/20/ + +- 请求body: + + ```json + { + "video_hash": "vbhdrbvcw", + "record_time": "2023-05-26 13:09:05", + "police_id": "00000002", + "event_type": "1", + "is_violation": true, + "small_image": "nvikefrooiwer", + "relative_time": 4.0, + "video_dir": "/d/test", + "car_number": "苏a045689", + "ai_analysis": "违规" + } + ``` + + | 参数名 | 参数值 | 是否必填 | 参数类型 | 描述说明 | + | ------------- |---------------------| -------- |-----|-------| +| video_hash | vbhdrbvcw | 是 | string | 视频哈希 | + | record_time | 2023-05-26 13:09:05 | 是 | string | 记录仪时间 | + | police_id | 00000002 | 是 | string | 警号 | + | event_type | 1 | 是 | string | 事件类型 | + | is_violation | true | 是 | bool | 是否违规 | + | small_image | nvikefrooiwer | 是 | string | 缩略图 | + | relative_time | 4.0 | 是 | float | 相对时间 | + | video_dir | /d/test | 是 | string | 视频路径 | + | car_number | 苏a045689 | 是 | string | 车牌号 | + | ai_analysis | 违规 | 是 | string | 分析结果 | + +- 备注:操作会修改uid为20的数据 + +- 返回值: + + | 参数名 | 参数值 | 参数类型 | 描述说明 | + | ------------- | ------------------------------------------------- | -------- | ---------------------- | + | uid | 1 | int | 自增 | + | video_hash | 38fb463b135fa12534104f85492cc6f1 | string | 视频哈希值 | + | record_time | 2023-05-26 13:09:05 | string | 记录仪时间 | + | police_id | 00000001 | string | 警号 | + | event_type | 1 | string | 事件类型 | + | is_violation | true | bool | 是否违规 | + | small_image | http://192.168.0.47:8000/media/images/0000609.jpg | string | 缩略图 | + | relative_time | 4.0 | float | 相对时间 | + | video_dir | http://192.168.0.47:8000/media/video/B1.MP4 | string | 视频地址 | + | car_number | 苏a045689 | string | 车牌号 | + | ai_analysis | 违规 | string | 分析结果 | + | add_time | 2023-05-31 18:42:15 | string | 记录添加时间(自动添加) | + | update_time | 2023-05-31 18:42:15 | string | 记录更新时间(自动添加) | + | is_display | true | bool | 是否展示(自动添加) | + + +
+ +
+ +删除数据 + +- 请求方式:delete + +- 请求链接:http://127.0.0.1:8002/api/20/ + +- 备注:操作会删除uid为20的数据 + +- 返回值: + + | 参数名 | 参数值 | 参数类型 | 描述说明 | + | ------------- | ------------------------------------------------- | -------- | ------------------------ | + | uid | 1 | int | 自增 | + | video_hash | 38fb463b135fa12534104f85492cc6f1 | string | 视频哈希值 | + | record_time | 2023-05-26 13:09:05 | string | 记录仪时间 | + | police_id | 00000001 | string | 警号 | + | event_type | 1 | string | 事件类型 | + | is_violation | true | bool | 是否违规 | + | small_image | http://192.168.0.47:8000/media/images/0000609.jpg | string | 缩略图 | + | relative_time | 4.0 | float | 相对时间 | + | video_dir | http://192.168.0.47:8000/media/video/B1.MP4 | string | 视频地址 | + | car_number | 苏a045689 | string | 车牌号 | + | ai_analysis | 违规 | string | 分析结果 | + | add_time | 2023-05-31 18:42:15 | string | 记录添加时间(自动添加) | + | update_time | 2023-05-31 18:42:15 | string | 记录更新时间(自动添加) | + | is_display | true | bool | 是否展示(自动添加) | + + +
+ +
+ +登录 + +- 请求方式:POST + +- 请求链接:http://192.168.10.13:8000/api/login + +- 请求body: + + ```json + { + "username": "xfc", + "password": "Xfc980516" + } + ``` + | 参数名 | 参数值 | 是否必填 | 参数类型 | 描述说明 | + |----------|------------| -------- |--------|------| + | username | xfc | 是 | string | 用户名 | + | password | Xfc980516 | 是 | string | 密码 | + +- 备注:无 + +- 返回值: + + | 参数名 | 参数值 | 参数类型 | 描述说明 | + |-------------|-----------------------------------------|--------|--------------| + | success | True | bool | 成功 | + | msg | 登录成功 | string | 返回信息 | + | data | | dict | 返回数据 | + | username | xfc | string | 用户名 | + | roles | [] | list | 角色列表 | + | accessToken | eyJhbGciOiJIUzI1NiIsInR5cCI6 | string | token值 | + | expires | Wed Jul 5 16:03:31 2023 | string | token过期时间 | + + +
\ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/admin.py b/app/admin.py new file mode 100644 index 0000000..6fffc08 --- /dev/null +++ b/app/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +# Register your models here. +# from app.models import TP +# admin.site.register(TP) diff --git a/app/apps.py b/app/apps.py new file mode 100644 index 0000000..ab972ce --- /dev/null +++ b/app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class App01Config(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app' diff --git a/app/es_models.py b/app/es_models.py new file mode 100644 index 0000000..563edc6 --- /dev/null +++ b/app/es_models.py @@ -0,0 +1,59 @@ +from elasticsearch_dsl import Document, Date, Text, Integer, Nested, Keyword, Object + + +class VideoIndex(Document): + STATUS = ((0, "未分析"), (1, "分析中"), (2, "分析完成"), (3, "分析失败")) + DOWNLOAD_STATUS = ((0, "未下载"), (1, "下载中"), (2, "下载完成")) + + video_name = Text(fields={'keyword': {'type': 'keyword'}}) # 添加一个 keyword 字段以支持精确匹配 + video_path = Text(fields={'keyword': {'type': 'keyword'}}) + ftp_path = Text(fields={'keyword': {'type': 'keyword'}}) + status = Integer() + download_status = Integer() + create_time = Date() + update_time = Date() + video_date = Date() + video_hash = Text(fields={'keyword': {'type': 'keyword'}}) + video_status = Integer() + + class Index: + name = 'video_index' # 索引名称 + + class Meta: + db_table = "tp_videos" + + +class TaskIndex(Document): + STATUS = ((0, "未分析"), (1, "分析中"), (2, "分析完成"), (3, "分析失败")) + + taskId = Integer() + analysis_status = Integer() + create_time = Date() + update_time = Date() + error_reason = Text(fields={'keyword': {'type': 'keyword'}}) + + video_id = Integer() + + class Index: + name = 'task_index' + + class Meta: + db_table = 'tp_tasks' + + +class TPResIndex(Document): + relative_time = Date() + exception_type = Text() + abnormal_id = Text() + abnormal_behavior = Text(fields={'keyword': {'type': 'keyword'}}) + abnormal_pic = Text() + abnormal_video = Text() + video_id = Integer() + create_time = Date() + update_time = Date() + + class Index: + name = 'tpres_index' + + class Meta: + db_table = 'tp_res' diff --git a/app/exception.py b/app/exception.py new file mode 100644 index 0000000..8ca9a8a --- /dev/null +++ b/app/exception.py @@ -0,0 +1,23 @@ +import logging +from rest_framework.views import exception_handler as drf_exception_handler # drf原生处理异常函数取别名 +from rest_framework.views import Response +from rest_framework import status + + +logger = logging.getLogger('mylogger') + + +def exception_handler(exc, context): + # drf的exception_handler做基础处理 + response = drf_exception_handler(exc, context) + # 为空,进行自定义二次处理 + logger.error(str(exc)) + if response is None: + # print(exc) # 错误原因 + # print(context) # 错误信息 + # print('%s - %s - %s' % (context['view'], context['request'].method, exc)) + return Response({ + 'detail': '服务器错误' + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True) + return response + diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py new file mode 100644 index 0000000..574eb03 --- /dev/null +++ b/app/migrations/0001_initial.py @@ -0,0 +1,67 @@ +# Generated by Django 3.2.19 on 2023-09-22 09:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Video', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('video_name', models.CharField(max_length=512, null=True, verbose_name='视频名称')), + ('video_path', models.CharField(max_length=512, null=True, verbose_name='视频路径')), + ], + options={ + 'db_table': 'tp_videos', + }, + ), + migrations.CreateModel( + name='TPRes', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('relative_time', models.FloatField(blank=True, null=True, verbose_name='相对时间')), + ('exception_type', models.CharField(max_length=128, null=True, verbose_name='异常类型')), + ('abnormal_id', models.CharField(max_length=256, null=True, verbose_name='异常ID')), + ('abnormal_behavior', models.CharField(max_length=256, null=True, verbose_name='异常行为')), + ('abnormal_pic', models.CharField(max_length=1024, null=True, verbose_name='异常关键帧/图片')), + ('abnormal_video', models.CharField(max_length=1024, null=True, verbose_name='异常视频定位/视频')), + ('video', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='app.video')), + ], + options={ + 'db_table': 'tp_tp', + }, + ), + migrations.CreateModel( + name='Person', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('pic_path', models.CharField(max_length=512, null=True, verbose_name='图片路径')), + ('pic_quality', models.IntegerField(null=True, verbose_name='图片质量')), + ('video', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='app.video')), + ], + options={ + 'db_table': 'tp_person', + }, + ), + migrations.CreateModel( + name='Car', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('pic_path', models.CharField(max_length=512, null=True, verbose_name='图片路径')), + ('pic_quality', models.IntegerField(null=True, verbose_name='图片质量')), + ('car_number', models.CharField(max_length=512, null=True, verbose_name='车牌号')), + ('video', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='app.video')), + ], + options={ + 'db_table': 'tp_car', + }, + ), + ] diff --git a/app/migrations/0002_video_status.py b/app/migrations/0002_video_status.py new file mode 100644 index 0000000..3fc0c96 --- /dev/null +++ b/app/migrations/0002_video_status.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-09-26 14:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='status', + field=models.IntegerField(choices=[(0, '未分析'), (1, '分析中'), (2, '分析完成')], default=0, verbose_name='视频状态'), + ), + ] diff --git a/app/migrations/0003_auto_20230928_0952.py b/app/migrations/0003_auto_20230928_0952.py new file mode 100644 index 0000000..aabcaa8 --- /dev/null +++ b/app/migrations/0003_auto_20230928_0952.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.19 on 2023-09-28 09:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_video_status'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='create_time', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间'), + ), + migrations.AddField( + model_name='video', + name='update_time', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间'), + ), + ] diff --git a/app/migrations/0004_auto_20231012_1512.py b/app/migrations/0004_auto_20231012_1512.py new file mode 100644 index 0000000..0eb6fdd --- /dev/null +++ b/app/migrations/0004_auto_20231012_1512.py @@ -0,0 +1,43 @@ +# Generated by Django 3.2.19 on 2023-10-12 15:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_auto_20230928_0952'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='download_status', + field=models.IntegerField(choices=[(0, '未下载'), (1, '下载中'), (2, '下载完成')], default=0, verbose_name='视频下载状态'), + ), + migrations.AddField( + model_name='video', + name='ftp_path', + field=models.CharField(max_length=512, null=True, verbose_name='视频ftp路径'), + ), + migrations.AddField( + model_name='video', + name='video_date', + field=models.CharField(max_length=512, null=True, verbose_name='视频所属日期'), + ), + migrations.AddField( + model_name='video', + name='video_hash', + field=models.CharField(max_length=512, null=True, unique=True, verbose_name='视频哈希'), + ), + migrations.AlterField( + model_name='video', + name='status', + field=models.IntegerField(choices=[(0, '未分析'), (1, '分析中'), (2, '分析完成')], default=0, verbose_name='视频分析状态'), + ), + migrations.AlterField( + model_name='video', + name='video_path', + field=models.CharField(max_length=512, null=True, verbose_name='视频本地路径'), + ), + ] diff --git a/app/migrations/0005_auto_20231017_1022.py b/app/migrations/0005_auto_20231017_1022.py new file mode 100644 index 0000000..99e1364 --- /dev/null +++ b/app/migrations/0005_auto_20231017_1022.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.19 on 2023-10-17 10:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0004_auto_20231012_1512'), + ] + + operations = [ + migrations.AlterField( + model_name='video', + name='status', + field=models.IntegerField(choices=[(0, '未分析'), (1, '分析中'), (2, '分析完成'), (3, '分析失败')], default=0, verbose_name='视频分析状态'), + ), + migrations.AlterField( + model_name='video', + name='video_date', + field=models.DateField(max_length=512, null=True, verbose_name='视频所属日期'), + ), + ] diff --git a/app/migrations/0006_video_error_reason.py b/app/migrations/0006_video_error_reason.py new file mode 100644 index 0000000..fda1c3c --- /dev/null +++ b/app/migrations/0006_video_error_reason.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-10-17 16:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0005_auto_20231017_1022'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='error_reason', + field=models.CharField(max_length=512, null=True, unique=True, verbose_name='視頻哈希'), + ), + ] diff --git a/app/migrations/0007_alter_video_error_reason.py b/app/migrations/0007_alter_video_error_reason.py new file mode 100644 index 0000000..6e37833 --- /dev/null +++ b/app/migrations/0007_alter_video_error_reason.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-10-17 17:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0006_video_error_reason'), + ] + + operations = [ + migrations.AlterField( + model_name='video', + name='error_reason', + field=models.CharField(max_length=512, null=True, verbose_name='視頻哈希'), + ), + ] diff --git a/app/migrations/0008_alter_video_ftp_path.py b/app/migrations/0008_alter_video_ftp_path.py new file mode 100644 index 0000000..1d0b88e --- /dev/null +++ b/app/migrations/0008_alter_video_ftp_path.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-10-25 13:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0007_alter_video_error_reason'), + ] + + operations = [ + migrations.AlterField( + model_name='video', + name='ftp_path', + field=models.CharField(max_length=512, null=True, unique=True, verbose_name='视频ftp路径'), + ), + ] diff --git a/app/migrations/0009_auto_20231030_1136.py b/app/migrations/0009_auto_20231030_1136.py new file mode 100644 index 0000000..00a4732 --- /dev/null +++ b/app/migrations/0009_auto_20231030_1136.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.19 on 2023-10-30 11:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0008_alter_video_ftp_path'), + ] + + operations = [ + migrations.AlterField( + model_name='car', + name='pic_quality', + field=models.CharField(max_length=128, null=True, verbose_name='图片质量'), + ), + migrations.AlterField( + model_name='person', + name='pic_quality', + field=models.CharField(max_length=128, null=True, verbose_name='图片质量'), + ), + ] diff --git a/app/migrations/0010_task.py b/app/migrations/0010_task.py new file mode 100644 index 0000000..b5300a0 --- /dev/null +++ b/app/migrations/0010_task.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.19 on 2023-11-08 13:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0009_auto_20231030_1136'), + ] + + operations = [ + migrations.CreateModel( + name='Task', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('analysis_status', models.IntegerField(choices=[(0, '未分析'), (1, '分析中'), (2, '分析完成'), (3, '分析失败')], default=0, verbose_name='视频分析状态')), + ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')), + ('video', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='app.video')), + ], + options={ + 'db_table': 'tp_tasks', + }, + ), + ] diff --git a/app/migrations/0011_auto_20231109_1109.py b/app/migrations/0011_auto_20231109_1109.py new file mode 100644 index 0000000..1dc4ff5 --- /dev/null +++ b/app/migrations/0011_auto_20231109_1109.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.19 on 2023-11-09 11:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0010_task'), + ] + + operations = [ + migrations.RemoveField( + model_name='task', + name='id', + ), + migrations.AddField( + model_name='task', + name='taskId', + field=models.AutoField(default=None, primary_key=True, serialize=False), + ), + ] diff --git a/app/migrations/0012_auto_20231109_1117.py b/app/migrations/0012_auto_20231109_1117.py new file mode 100644 index 0000000..f3da143 --- /dev/null +++ b/app/migrations/0012_auto_20231109_1117.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.19 on 2023-11-09 11:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0011_auto_20231109_1109'), + ] + + operations = [ + migrations.RemoveField( + model_name='video', + name='error_reason', + ), + migrations.AddField( + model_name='task', + name='error_reason', + field=models.CharField(max_length=512, null=True, verbose_name='错误信息'), + ), + ] diff --git a/app/migrations/0013_video_video_status.py b/app/migrations/0013_video_video_status.py new file mode 100644 index 0000000..dc75c86 --- /dev/null +++ b/app/migrations/0013_video_video_status.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-11-17 15:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0012_auto_20231109_1117'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='video_status', + field=models.IntegerField(choices=[(1, '有效视频'), (2, '无效视频'), (3, '异常视频')], default=1, verbose_name='视频有效/无效'), + ), + ] diff --git a/app/migrations/0014_task_callbackurl.py b/app/migrations/0014_task_callbackurl.py new file mode 100644 index 0000000..3448b95 --- /dev/null +++ b/app/migrations/0014_task_callbackurl.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-11-24 10:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0013_video_video_status'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='callbackUrl', + field=models.CharField(max_length=512, null=True, verbose_name='回调地址'), + ), + ] diff --git a/app/migrations/0015_alter_task_analysis_status.py b/app/migrations/0015_alter_task_analysis_status.py new file mode 100644 index 0000000..666bc05 --- /dev/null +++ b/app/migrations/0015_alter_task_analysis_status.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-12-13 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0014_task_callbackurl'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='analysis_status', + field=models.IntegerField(choices=[(100, '未分析'), (101, '分析中'), (102, '分析完成'), (103, "Couldn't open webcam or video"), (104, '视频文件不存在'), (105, '切分视频失败'), (106, '未知异常')], default=100, verbose_name='视频分析状态'), + ), + ] diff --git a/app/migrations/0016_auto_20240314_1627.py b/app/migrations/0016_auto_20240314_1627.py new file mode 100644 index 0000000..cae3688 --- /dev/null +++ b/app/migrations/0016_auto_20240314_1627.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.25 on 2024-03-14 16:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0015_alter_task_analysis_status'), + ] + + operations = [ + migrations.AlterField( + model_name='video', + name='download_status', + field=models.IntegerField(choices=[(0, '未下载'), (1, '下载中'), (2, '下载完成'), (3, '下载异常')], default=0, verbose_name='视频下载状态'), + ), + migrations.AlterField( + model_name='video', + name='video_hash', + field=models.CharField(max_length=512, null=True, verbose_name='视频哈希'), + ), + migrations.AlterField( + model_name='video', + name='video_status', + field=models.IntegerField(choices=[(1, '有效视频'), (2, '无效视频'), (3, '异常视频'), (4, '重复视频')], default=1, verbose_name='视频有效/无效'), + ), + ] diff --git a/app/migrations/__init__.py b/app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..e18d582 --- /dev/null +++ b/app/models.py @@ -0,0 +1,112 @@ +from django.db import models + +# Create your models here. + + +class Video(models.Model): + STATUS = ((0, "未分析"), (1, "分析中"), (2, "分析完成"), (3, "分析失败")) + DOWNLOAD_STATUS = ((0, "未下载"), (1, "下载中"), (2, "下载完成"), (3, "下载异常")) + VIDEO_STATUS = ((1, "有效视频"), (2, "无效视频"), (3, "异常视频"), (4, "重复视频")) + video_name = models.CharField(max_length=512, verbose_name='视频名称', null=True) + video_path = models.CharField(max_length=512, verbose_name='视频本地路径', null=True) + ftp_path = models.CharField(max_length=512, verbose_name='视频ftp路径', null=True, unique=True) + status = models.IntegerField(verbose_name='视频分析状态', choices=STATUS, default=0) + download_status = models.IntegerField(verbose_name='视频下载状态', default=0, choices=DOWNLOAD_STATUS) + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', null=True) + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', null=True) + video_date = models.DateField(verbose_name='视频所属日期', max_length=512, null=True) + video_hash = models.CharField(verbose_name='视频哈希', max_length=512, null=True) + video_status = models.IntegerField(verbose_name='视频有效/无效', choices=VIDEO_STATUS, default=1) + + class Meta: + db_table = "tp_videos" + + +class Task(models.Model): + """任务表 与视频表一对多?""" + STATUS = ((100, "未分析"), (101, "分析中"), (102, "分析完成"), (103, "Couldn't open webcam or video"), (104, '视频文件不存在'), (105, '切分视频失败'), (106, '未知异常')) + taskId = models.AutoField(primary_key=True, default=None) + analysis_status = models.IntegerField(verbose_name='视频分析状态', choices=STATUS, default=100) + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', null=True) + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间', null=True) + video = models.ForeignKey(Video, on_delete=models.CASCADE, null=True) + error_reason = models.CharField(max_length=512, verbose_name='错误信息', null=True) + callbackUrl = models.CharField(max_length=512, verbose_name='回调地址', null=True) + + class Meta: + db_table = 'tp_tasks' + +# class TP(models.Model): +# # uid +# uid = models.AutoField(primary_key=True) +# # 视频哈希 +# video_hash = models.CharField(max_length=1024, verbose_name='视频哈希') +# # 记录仪时间 +# record_time = models.DateTimeField(verbose_name='记录仪时间', null=True, blank=True) +# # 设备号 +# equipment_id = models.CharField(max_length=64, null=True, blank=True, verbose_name='设备号') +# # 警号 +# police_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='警号') # 警号可为空可不传 +# # 事件类型 +# event_type = models.CharField(max_length=50, verbose_name='事件类型/车辆违法原因', null=True, blank=True) # 还没迁移 +# # 是否违规 +# is_violation = models.BooleanField(verbose_name='执法人员是否违规', null=True, blank=True) +# # 缩略图 +# small_image = models.CharField(max_length=1024, verbose_name='缩略图', null=True, blank=True) +# # 相对时间 +# relative_time = models.FloatField(verbose_name='相对时间', null=True, blank=True) +# # 视频路径 +# video_dir = models.CharField(max_length=1024, verbose_name='视频路径') +# # 车牌号 +# car_number = models.CharField(max_length=1024, verbose_name='车牌号', null=True, blank=True) # 车牌可为空可不传 +# # 分析结果 +# ai_analysis = models.CharField(max_length=255, verbose_name='执法人员违规行为', null=True, blank=True) # 分析结果可为空可不传 +# # 加入时间 +# add_time = models.DateTimeField(auto_now_add=True, verbose_name='加入时间') +# # 更新时间 +# update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') +# # 是否显示 +# is_display = models.BooleanField(default=True, verbose_name='是否显示') +# +# is_illegal = models.BooleanField(verbose_name='车辆是否违法', null=True, blank=True) +# +# video = models.ForeignKey(Video, on_delete=models.CASCADE, null=True) +# +# class Meta: +# db_table = "app_tp" +# +# # 排序 uid倒序 +# ordering = ['-uid'] + + +class Person(models.Model): + pic_path = models.CharField(max_length=512, verbose_name='图片路径', null=True) + pic_quality = models.CharField(verbose_name='图片质量', null=True, max_length=128) + video = models.ForeignKey(Video, on_delete=models.CASCADE, null=True) + + class Meta: + db_table = "tp_person" + + +class Car(models.Model): + pic_path = models.CharField(max_length=512, verbose_name='图片路径', null=True) + pic_quality = models.CharField(verbose_name='图片质量', null=True, max_length=128) + car_number = models.CharField(max_length=512, verbose_name='车牌号', null=True) + video = models.ForeignKey(Video, on_delete=models.CASCADE, null=True) + + class Meta: + db_table = "tp_car" + + +# 视频内时间、异常类型、异常ID、异常行为、异常关键帧、异常视频定位 +class TPRes(models.Model): + relative_time = models.FloatField(verbose_name='相对时间', null=True, blank=True) + exception_type = models.CharField(max_length=128, verbose_name='异常类型', null=True) + abnormal_id = models.CharField(max_length=256, verbose_name='异常ID', null=True) + abnormal_behavior = models.CharField(max_length=256, verbose_name='异常行为', null=True) + abnormal_pic = models.CharField(max_length=1024, verbose_name='异常关键帧/图片', null=True) + abnormal_video = models.CharField(max_length=1024, verbose_name='异常视频定位/视频', null=True) + video = models.ForeignKey(Video, on_delete=models.CASCADE, null=True) + + class Meta: + db_table = "tp_tp" diff --git a/app/pagination.py b/app/pagination.py new file mode 100644 index 0000000..3160052 --- /dev/null +++ b/app/pagination.py @@ -0,0 +1,8 @@ +from rest_framework.pagination import PageNumberPagination + + +class MyPageNumberPagination(PageNumberPagination): + page_size = 10 + page_query_param = "page" + page_size_query_param = "page_size" + max_page_size = 100 diff --git a/app/serializers.py b/app/serializers.py new file mode 100644 index 0000000..55d4927 --- /dev/null +++ b/app/serializers.py @@ -0,0 +1,103 @@ +import datetime + +from rest_framework import serializers +from django_filters.rest_framework import FilterSet +import django_filters +from app.models import TPRes, Video, Task +import logging +logger = logging.getLogger('mylogger') + + +# class SerialMyModel(serializers.ModelSerializer): +# class Meta: +# model = TP +# fields = "__all__" +# +# def create(self, validated_data): +# instance = TP.objects.create(**validated_data) +# return instance + + # def update(self, instance, validated_data): + # instance.video_hash = validated_data.get('video_hash', instance.video_hash) + # instance.record_time = validated_data.get('record_time', instance.record_time) + # instance.equipment_id = validated_data.get('equipment_id', instance.equipment_id) + # instance.police_id = validated_data.get('police_id', instance.police_id) + # instance.event_type = validated_data.get('event_type', instance.event_type) + # instance.is_violation = validated_data.get('is_violation', instance.is_violation) + # instance.small_image = validated_data.get('small_image', instance.small_image) + # instance.relative_time = validated_data.get('relative_time', instance.relative_time) + # instance.video_dir = validated_data.get('video_dir', instance.video_dir) + # instance.car_number = validated_data.get('car_number', instance.car_number) + # instance.ai_analysis = validated_data.get('ai_analysis', instance.ai_analysis) + # instance.is_display = validated_data.get('is_display', instance.is_display) + # instance.is_illegal = validated_data.get('is_illegal', instance.is_illegal) + # instance.save() + # + # return instance + + +# class SerialFilter(FilterSet): +# """ +# 过滤器,支持模糊查询 +# record_time,日期时间格式,如 2023-01-01 00:00:00 +# police_id,支持模糊匹配 +# event_type,支持模糊匹配 +# """ +# record_time = django_filters.DateTimeFilter(field_name='record_time', lookup_expr='icontains') +# police_id = django_filters.CharFilter(field_name='police_id', lookup_expr='icontains') +# event_type = django_filters.CharFilter(field_name='event_type', lookup_expr='icontains') +# # 记录时间范围查询 +# start_time = django_filters.DateTimeFilter(field_name='record_time', lookup_expr='gte') +# # end_time = django_filters.DateTimeFilter(field_name='record_time', lookup_expr='lte') +# end_time = django_filters.DateTimeFilter(field_name='record_time', method='time_range_filter') +# # todo bug: start_time 和 end_time同一天的情况 done +# violation = django_filters.NumberFilter(field_name='is_violation', method='is_violation_query') +# violation_type = django_filters.CharFilter(field_name='ai_analysis', lookup_expr='icontains') +# +# def time_range_filter(self, queryset, name, value): +# """ +# @params queryset: 为TP.objects.all()返回的合集, 即视图类中的的queryset +# @params name: 为field_name +# @params value: 为前端通过end_time字段传过来的值 +# """ +# +# return queryset.filter(record_time__lte=value + datetime.timedelta(days=1)) +# +# def is_violation_query(self, queryset, name, value): +# if value == 2: +# return queryset +# elif value == 1: +# return queryset.filter(is_violation=True) +# elif value == 0: +# return queryset.filter(is_violation=False) +# else: +# return queryset.filter(is_violation=True) +# +# class Meta: +# # 指定模型 +# models = TP +# # 指定需要模糊查询的字段 +# fields = ("record_time", "police_id", "event_type", "ai_analysis",) + + +class TPResSerializer(serializers.ModelSerializer): + class Meta: + model = TPRes + fields = "__all__" + # exclude = ('video_id',) + + # def create(self, validated_data): + # instance = TPRes.objects.create(**validated_data) + # return instance + + +class AnalyzedVideoSerializer(serializers.ModelSerializer): + class Meta: + model = Video + fields = ('id', 'video_name', 'video_date', 'video_path', 'video_hash') + + +class TaskSerializer(serializers.ModelSerializer): + class Meta: + model = Task + fields = "__all__" diff --git a/app/tests.py b/app/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/urls.py b/app/urls.py new file mode 100644 index 0000000..917400d --- /dev/null +++ b/app/urls.py @@ -0,0 +1,39 @@ +"""TP_Api 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.contrib import admin +from django.urls import path, include, re_path +from rest_framework.routers import DefaultRouter +from app import views + +# router = DefaultRouter() +# router.register('', views.ModelQuery) + +urlpatterns = [ + # path('', include(router.urls)), + # path('events', views.ModelQuery.as_view({"get": "query_event"})), + # path('', views.TPAPIView.as_view()), + # path('/', views.TPAPIView.as_view()), + path('upload_video/', views.VideoProcess.as_view({"post": "create", 'get': 'list'})), + path('analysis_video/', views.AnalysisVideo.as_view({"post": "create"})), + path('pics/', views.PicModelViewSet.as_view({'get': 'list'})), + path('', views.TPModelViewSet.as_view({'get': 'list', 'post': 'create'})), + path('get_analyzed_videos/', views.GetAnalyzedVideo.as_view({'post': 'list'})), + path('get_unanalyzed_videos/', views.VideoAnalysis.as_view({'get': 'list'})), + path('update_video_status/', views.UpdateVideoStatus.as_view({'post': 'update'})), + path('tasks/', views.TaskViewSet.as_view({'get': 'list', 'post': 'update'})), + path('download_video/', views.DownloadVideo.as_view({'post': 'create'})), + path('get_video_result/', views.VideoResult.as_view({'get': 'list'})), +] diff --git a/app/utils.py b/app/utils.py new file mode 100644 index 0000000..f3cf55f --- /dev/null +++ b/app/utils.py @@ -0,0 +1,73 @@ +import time +import datetime +import jwt +import hashlib +from os import path +from django.conf import settings +from rest_framework.response import Response +from django.contrib.auth.models import User + + +def generate_token(user): + """ + :param user: 用户对象 + :return: 生成的token + """ + payload = { + 'user_id': user.id, + 'username': user.username, + 'exp': datetime.datetime.utcnow() + datetime.timedelta(weeks=1) # token过期时间 1week + } + token = jwt.encode(payload=payload, key=settings.SECRET_KEY, algorithm='HS256') + return token + + +def decode_token_exp_time(token): + try: + res_dict = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + exp = res_dict.get('exp') + exp_time = time.ctime(exp) + return exp_time + except Exception as e: + return None + + +def update_tp_querydict(querydict): + new_querydict = dict() + if 'record_time' in querydict: + new_querydict['record_time__icontains'] = ''.join(querydict.pop('record_time')) + if 'police_id' in querydict: + new_querydict['police_id__icontains'] = ''.join(querydict.pop('police_id')) + if 'event_type' in querydict: + new_querydict['event_type__icontains'] = ''.join(querydict.pop('event_type')) + if 'start_time' in querydict: + new_querydict['record_time__gte'] = ''.join(querydict.pop('start_time')) + if 'end_time' in querydict: + new_querydict['record_time__lte'] = datetime.datetime.strptime(''.join(querydict.pop('end_time')), "%Y-%m-%d") + datetime.timedelta(days=1) + if 'violation' in querydict: + if ''.join(querydict.get('violation')) == '1': + new_querydict['is_violation'] = True + elif ''.join(querydict.get('violation')) == '0': + new_querydict['is_violation'] = False + if 'violation_type' in querydict: + new_querydict['ai_analysis__icontains'] = ''.join(querydict.pop('violation_type')) + return new_querydict + + +def get_file_hash(filename): + if path.isfile(filename) is False: + return + + # make a hash object + h_sha256 = hashlib.sha256() + + # open file for reading in binary mode + with open(filename, "rb") as file: + # read file in chunks and update hash + chunk = 0 + while chunk != b"": + chunk = file.read(1024) + h_sha256.update(chunk) + + # return the hex digest + return h_sha256.hexdigest() diff --git a/app/views.py b/app/views.py new file mode 100644 index 0000000..b27a2ec --- /dev/null +++ b/app/views.py @@ -0,0 +1,686 @@ +import asyncio +import logging +import os +import subprocess +import sys +import threading +import datetime +import time +from pathlib import Path + +import requests +from django.db import transaction +from django.db.models import Count +from django.shortcuts import render +from django.views.decorators.http import require_http_methods +from elasticsearch_dsl import Search, connections + +# Create your views here. +from rest_framework import viewsets, status +from rest_framework.response import Response +from app.models import Video, Car, Person, TPRes, Task +from app.serializers import TPResSerializer, AnalyzedVideoSerializer, TaskSerializer +from app.pagination import MyPageNumberPagination +from user.models import UserProfile +from django.contrib.auth import authenticate, login, logout +from .utils import generate_token, decode_token_exp_time, update_tp_querydict, get_file_hash +from rest_framework.views import APIView +from django.conf import settings + +logger = logging.getLogger('mylogger') + +system_video_flag = False +sys.path.append('/TP_922/TP_PMP/src') + +def get_data_by_model(model, kwargs): + try: + # 使用.objects.get(pk=id)来获取特定ID的数据 + data = model.objects.get(**kwargs) + return data + except model.DoesNotExist: + # 处理未找到数据的情况 + return None + +# class TPAPIView(APIView): +# +# def get(self, request): +# query_params = dict(request.query_params) +# logger.info(query_params) +# querydict = update_tp_querydict(query_params) +# logger.info(querydict) +# queryset = TP.objects.filter(**querydict) +# paginator = MyPageNumberPagination() +# paginated_queryset = paginator.paginate_queryset(queryset, request) +# serializer = SerialMyModel(paginated_queryset, many=True) +# +# # paginated_data = { +# # 'count': queryset.count(), +# # 'next': self.pagination_class().get_next_link(), +# # 'previous': self.pagination_class().get_previous_link(), +# # 'results': serializer.data +# # } +# # return Response(paginated_data) +# return paginator.get_paginated_response(serializer.data) +# +# def post(self, request, *args, **kwargs): +# data = request.data +# serializer = SerialMyModel(data=data) +# if serializer.is_valid(): +# try: +# with transaction.atomic(): +# instance = serializer.save() # 保存数据 +# logger.info(instance) +# return Response(serializer.data, status=status.HTTP_201_CREATED) +# except Exception as e: +# return Response(str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR) +# else: +# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) +# +# def put(self, request, pk, *args, **kwargs): +# try: +# with transaction.atomic(): +# instance = TP.objects.select_for_update().get(uid=pk) +# serializer = SerialMyModel(instance, data=request.data, partial=True) +# if serializer.is_valid(): +# serializer.save() +# return Response(serializer.data, status=status.HTTP_200_OK) +# +# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) +# except TP.DoesNotExist: +# return Response("Object not found", status=status.HTTP_404_NOT_FOUND) +# except Exception as e: +# return Response(str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR) +# +# def delete(self, request, pk): +# try: +# with transaction.atomic(): +# instance = TP.objects.get(uid=pk) +# instance.delete() +# except TP.DoesNotExist: +# return Response("Object not found", status=status.HTTP_404_NOT_FOUND) +# except Exception as e: +# return Response(str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR) +# +# return Response('删除成功', status=status.HTTP_200_OK) + + +# class ModelQuery(viewsets.ModelViewSet): +# # 查询类 +# queryset = TP.objects.all().order_by("-uid") # 按照uid倒序 +# # 序列化类 +# serializer_class = SerialMyModel +# # 分页类 +# pagination_class = MyPageNumberPagination +# +# # 条件筛选 +# filterset_class = SerialFilter +# +# def query_event(self, request, *args, **kwargs): +# res = TP.objects.exclude(event_type__isnull=True).exclude(event_type__exact='').values('event_type').annotate(count=Count('event_type')).order_by('-count') +# result = list(res) +# data = dict() +# for index, item in enumerate(result, 1): +# data[index] = item.get('event_type') +# response = { +# 'success': True, +# 'msg': '查询成功', +# 'results': data +# } +# return Response(response) + +# 用APIVIEW或者视图函数,改到user模块下 +# class RegisterLoginViewSet(viewsets.ModelViewSet): +# def tp_register(self, request, *args, **kwargs): +# """注册 POST""" +# # 判断用户是否为管理员 +# # user = request.user +# # if not user.is_superuser: +# # return Response({'msg': '您不是管理员,无权限添加成员'}) +# data = request.data +# username = data.get('username') +# password = data.get('password') +# try: +# if UserProfile.objects.filter(username=username).first(): +# return Response({'msg': '该用户名已存在,请换一个'}) +# UserProfile.objects.create_user(username=username, password=password) +# return Response({'msg': '注册成功'}) +# except Exception as e: +# logger.info(e) +# response = { +# "msg": f'注册失败, 原因:{e}' +# } +# return Response(response) + + +class VideoProcess(viewsets.ModelViewSet): + def list(self, request, *args, **kwargs): + query_params = dict(request.query_params) + logger.info(query_params) + if query_params.get('video_name'): + video_name = query_params.get('video_name')[0] + file_path = os.path.join('/TP_922/TP_PMP/media/video_input/', video_name) + if os.path.exists(file_path): + return Response({'msg': '该视频已存在', 'success': False, "code": 201}) + else: + return Response({'msg': '请选择文件上传', 'success': False}) + if Video.objects.filter(status=1): + return Response({'msg': '有视频正在分析中,请暂勿上传', 'success': False}) + return Response({'success': True}) + + def create(self, request, *args, **kwargs): + video_obj = request.FILES.get('file') + if not video_obj: + return Response({'msg': '请选中文件', 'success': False}) + video_path = os.path.join(r'/TP_922/TP_PMP/media/video_input/', video_obj.name) + logger.info(video_path) + if not video_obj.name.lower().endswith((".mp4", ".avi", ".mkv", ".mov")): + return Response({'msg': '请选择 .mp4, .MP4, .avi, .AVI, .mkv, .MKV, .mov, .MOV视频文件类型', 'success': False}) + # 清空Video表 + # Video.objects.all().delete() + # 如果有视频正在跑 status=1, 则不行 + + try: + with transaction.atomic(): + video = Video.objects.create(video_name=video_obj.name, video_path=video_path) + except Exception as e: + return Response({'msg': f'上传失败,原因{e}', 'success': False}) + # 清空视频 + try: + old_files = os.listdir(r'/TP_922/TP_PMP/media/video_input/') + for file in old_files: + file_path = os.path.join(r'/TP_922/TP_PMP/media/video_input/', file) + if os.path.isfile(file_path): + os.remove(file_path) + except Exception as e: + return Response({'msg': f'清空旧视频失败,原因{e}', 'success': False}) + + try: + with open(video_path, 'wb+') as destination: + for chunk in video_obj.chunks(): + destination.write(chunk) + return Response({'msg': '上传成功', 'file_path': video_path, 'success': True}) + except Exception as e: + return Response({'msg': f'上传失败,原因{e}', 'success': False}) + + +def analysis_video(): + os.chdir('/TP_922/TP_PMP/src') + res = os.system('python main.py') + return res + + +class AnalysisVideo(viewsets.ModelViewSet): + def create(self, request, *args, **kwargs): + data = request.data + video_name = data.get('video_name') + if not video_name: + running_video = Video.objects.filter(status=1).first() + if running_video: + return Response({'msg': f'有正在分析中的视频, {running_video.video_name}', 'success': False}) + else: + latest_video = Video.objects.filter(status=2).order_by('-update_time').first() + if latest_video: + return Response({'msg': f'最新分析完成的视频 {latest_video.video_name}', 'success': True}) + else: + return Response({'msg': '没有正在分析或已经分析完成的视频,请上传', 'success': False}) + video_obj = Video.objects.filter(video_name=video_name).first() + if not video_obj: + return Response({'msg': '请正确选择视频文件'}) + if video_obj.status == 1: + # if Person.objects.filter(video_id=video_obj.id) and Car.objects.filter(video_id=video_obj.id) and TPRes.objects.filter(video_id=video_obj.id): + if TPRes.objects.filter(video_id=video_obj.id): + video_obj.status = 2 + video_obj.update_time = datetime.datetime.now() + video_obj.save() + return Response({'msg': '该视频已分析完毕,可查看结果', 'success': True}) + return Response({'msg': '视频正在分析中,请稍等'}) + if video_obj.status == 2: + return Response({'msg': '该视频已分析完毕,可查看结果', 'success': True}) + # status 为 0 + thread = threading.Thread(target=analysis_video) + thread.start() + video_obj.status = 1 # 分析中 + video_obj.update_time = datetime.datetime.now() + video_obj.save() + + return Response({'msg': '视频开始分析', 'success': True}) + + +class PicModelViewSet(viewsets.ModelViewSet): + + def list(self, request, *args, **kwargs): + query_params = dict(request.query_params) + logger.info(query_params) + if query_params.get('video_name'): + video_name = query_params.get('video_name')[0] + else: + running_video = Video.objects.filter(status=1).first() + if running_video: + return Response({'msg': f'有正在分析中的视频, {running_video.video_name}', 'success': False}) + else: + latest_video = Video.objects.filter(status=2).order_by('-update_time').first() + if latest_video: + person_data = list(Person.objects.filter(video_id=latest_video.id).values()) + car_data = list(Car.objects.filter(video_id=latest_video.id).values()) + data = dict() + data['person'] = person_data + data['car'] = car_data + return Response({'msg': f'最新分析完成的视频 {latest_video.video_name}', 'success': True, 'data': data}) + else: + return Response({'msg': '没有正在分析或已经分析完成的视频,请上传', 'success': False}) + video_obj = Video.objects.filter(video_name=video_name).first() + if not video_obj: + return Response({'msg': '该视频不存在'}) + try: + person_data = list(Person.objects.filter(video_id=video_obj.id).values()) + car_data = list(Car.objects.filter(video_id=video_obj.id).values()) + data = dict() + data['person'] = person_data + data['car'] = car_data + response = {'msg': '查询成功', 'data': data, 'success': True} + return Response(response) + except Exception as e: + return Response({'msg': f'查询失败,{e}', 'success': False}) + + +class TPModelViewSet(viewsets.ModelViewSet): + def list(self, request, *args, **kwargs): + query_params = dict(request.query_params) + logger.info(query_params) + if query_params.get('video_name'): + video_name = query_params.get('video_name')[0] + else: + running_video = Video.objects.filter(status=1).first() + if running_video: + return Response({'msg': f'有正在分析中的视频, {running_video.video_name}', 'success': False}) + else: + latest_video = Video.objects.filter(status=2).order_by('-update_time').first() + if latest_video: + tp_queryset = TPRes.objects.filter(video_id=latest_video.id).order_by('-id') + paginator = MyPageNumberPagination() + paginated_queryset = paginator.paginate_queryset(tp_queryset, request) + serializer = TPResSerializer(paginated_queryset, many=True) + paginator_response = paginator.get_paginated_response(serializer.data) + response_data = { + 'success': True, + 'count': paginator_response.data['count'], + 'next': paginator_response.data['next'], + 'previous': paginator_response.data['previous'], + 'results': list(paginator_response.data['results']), + } + return Response(response_data) + else: + return Response({'msg': '没有正在分析或已经分析完成的视频,请上传', 'success': False}) + video_obj = Video.objects.filter(video_name=video_name).first() + if not video_obj: + return Response({'msg': '该视频不存在'}) + tp_queryset = TPRes.objects.filter(video_id=video_obj.id).order_by('-id') + paginator = MyPageNumberPagination() + paginated_queryset = paginator.paginate_queryset(tp_queryset, request) + serializer = TPResSerializer(paginated_queryset, many=True) + paginator_response = paginator.get_paginated_response(serializer.data) + response_data = { + 'success': True, + 'count': paginator_response.data['count'], + 'next': paginator_response.data['next'], + 'previous': paginator_response.data['previous'], + 'results': list(paginator_response.data['results']), + } + return Response(response_data) + + def create(self, request, *args, **kwargs): + data = request.data + video_name = data.get('video_name') + video_id = data.get('video_id') + video_obj = Video.objects.filter(id=video_id).first() + if not video_obj: + return Response({'msg': '该视频不存在'}) + video_obj.video_hash = data.get('video_hash') + video_obj.save() + person_dict = data.get('person') + car_dict = data.get('car') + msg_list = data.get('message') + video_status = data.get('video_status') + try: + with transaction.atomic(): + # 暂改 + if video_status: + video_obj.video_status = video_status + video_obj.save() + if person_dict: + for values in person_dict.values(): + if len(values) == 2: + person_obj = Person.objects.create(pic_path=values[0], pic_quality=values[1], video_id=video_obj.id) + + if car_dict: + for values in car_dict.values(): + if len(values) == 3: + car_obj = Car.objects.create(pic_path=values[0], pic_quality=values[1], car_number=values[2], video_id=video_obj.id) + + if msg_list: + for tp_dict in msg_list: + tp_obj = TPRes.objects.create(relative_time=tp_dict.get('relative_time'), exception_type=tp_dict.get('exception_type'), abnormal_id=tp_dict.get('abnormal_id'), abnormal_behavior=tp_dict.get('abnormal_behavior'), abnormal_pic=tp_dict.get('abnormal_pic'), abnormal_video=data.get('video_dir'), video_id=video_obj.id) + video_obj.status = 2 + video_obj.save() + return Response({'msg': '上传成功'}) + except Exception as e: + return Response({'msg': f'上传失败,原因{e}'}) + + +class GetAnalyzedVideo(viewsets.ModelViewSet): + def list(self, request, *args, **kwargs): + data = request.data + logger.info(data) + start_time = data.get('start_time') + end_time = data.get('end_time') + abnormal_behavior = data.get('abnormal_behavior') + page = data.get('page', 1) + page_size = data.get('page_size', 10) + if page: # 判断请求中是否有page和size参数 + request.query_params._mutable = True # 让查询参数dict变为可编辑 + # query_params该参数会返回请求的查询参数,是个dict _mutable属性表示是否可编辑,默认是False + request.query_params['page'] = page # 添加page查询参数 + if page_size: + request.query_params['page_size'] = page_size # 添加size查询参数 + request.query_params._mutable = False + if start_time and end_time: + start_date = datetime.datetime.strptime(start_time, '%Y-%m-%d').date() + end_date = datetime.datetime.strptime(end_time, '%Y-%m-%d').date() + videos = Video.objects.filter(video_date__range=(start_date, end_date), tpres__isnull=False, status=2).distinct() + else: + # videos = Video.objects.all() + videos = Video.objects.filter(tpres__isnull=False, status=2).distinct() + if abnormal_behavior: + videos = videos.filter(tpres__abnormal_behavior__in=abnormal_behavior).distinct() + paginator = MyPageNumberPagination() + # paginator.page = page + # paginator.page_size = page_size + paginated_queryset = paginator.paginate_queryset(videos, request) + serializer = AnalyzedVideoSerializer(paginated_queryset, many=True) + paginator_response = paginator.get_paginated_response(serializer.data) + results = list(paginator_response.data['results']) + + for item in results: + video_id = item.get('id') + abnormal_count = TPRes.objects.filter(video_id=video_id).count() + abnormal_performance = list(TPRes.objects.filter(video_id=video_id).values_list('abnormal_behavior', flat=True).distinct()) + item['abnormal_count'] = abnormal_count + item['abnormal_performance'] = abnormal_performance + logger.info(paginator_response.data) + response_data = { + 'success': True, + 'count': paginator_response.data['count'], + 'next': paginator_response.data['next'], + 'previous': paginator_response.data['previous'], + 'results': results, + } + return Response(response_data) + + +class VideoAnalysis(viewsets.ModelViewSet): + def list(self, request, *args, **kwargs): + # TODO 替换路径 + not_analyzed_videos = Video.objects.filter(status=0, download_status=2) + paginator = MyPageNumberPagination() + paginated_queryset = paginator.paginate_queryset(not_analyzed_videos, request) + serializer = AnalyzedVideoSerializer(paginated_queryset, many=True) + paginator_response = paginator.get_paginated_response(serializer.data) + results = list(paginator_response.data['results']) + for item in results: + item['video_path'] = item.pop('video_path').replace('/home/TP_922/TP_PMP', '..') + response_data = { + 'success': True, + 'count': paginator_response.data['count'], + 'next': paginator_response.data['next'], + 'previous': paginator_response.data['previous'], + # 'results': list(paginator_response.data['results']), + 'results': results, + } + return Response(response_data) + + +class UpdateVideoStatus(viewsets.ModelViewSet): + def update(self, request, *args, **kwargs): + data = request.data + video_hash = data.get('video_hash') + video_id = data.get('video_id') + video_status = data.get('video_status') + msg = data.get('msg') + if video_hash: + video_obj = Video.objects.filter(video_hash=video_hash).first() + else: + video_obj = Video.objects.filter(id=video_id).first() + if not video_obj: + return Response({'success': False, 'msg': '該視頻不存在'}) + try: + with transaction.atomic(): + video_obj.status = video_status + video_obj.error_reason = msg + video_obj.save() + return Response({'success': True, 'msg': '更新成功'}) + except Exception as e: + return Response({'success': False, 'msg': f'更新失敗,原因{e}'}) + + +class TaskViewSet(viewsets.ModelViewSet): + def list(self, request, *args, **kwargs): + task_objs = Task.objects.filter(analysis_status__in=[101, 100], video__download_status=2).exclude(video__video_status=4).order_by("pk") + paginator = MyPageNumberPagination() + paginated_queryset = paginator.paginate_queryset(task_objs, request) + serializer = TaskSerializer(paginated_queryset, many=True) + paginator_response = paginator.get_paginated_response(serializer.data) + results = list(paginator_response.data['results']) + for item in results: + video_id = item.pop('video') + item['video_name'] = Video.objects.filter(id=video_id).first().video_name + item['video_path'] = Video.objects.filter(id=video_id).first().video_path + task_object = Task.objects.filter(video_id=video_id).first() + task_object.analysis_status = 101 + task_object.save() + response_data = { + 'success': True, + 'count': paginator_response.data['count'], + 'next': paginator_response.data['next'], + 'previous': paginator_response.data['previous'], + 'results': results, + } + return Response(response_data) + + def create(self, request, *args, **kwargs): + pass + + def update(self, request, *args, **kwargs): + data = request.data + logger.info(data) + task_id = data.get('taskId') + analysis_status = data.get('status') + message = data.get('msg') + res = data.get('total_res') + task_obj = Task.objects.filter(taskId=task_id).first() + callbackUrl = task_obj.callbackUrl + if not task_obj: + return Response({'msg': '该任务不存在'}) + if analysis_status == 102: + # s = Search(index='tp-log') + # s = s.params(size=1000) + # response = s.execute() + result = list() + # # 遍历结果 + # for hit in response: + # data = hit.to_dict().get('data') + # if data.get('taskId') == task_id: + # result.append(data) + if res.get('taskId') == task_id: + result.append(res) + if result and callbackUrl: + latest_data = result[-1] + res = { + 'status': 0, + 'message': 'success', + 'data': { + 'taskId': task_id, + 'videoStatus': latest_data.get('data').get('videoStatus'), + 'invalidReason': latest_data.get('data').get('invalidReason'), + 'statistics': latest_data.get('data').get('statistics'), + 'result': latest_data.get('data').get('result'), + "resultTime": latest_data.get('data').get('resultTime') + } + } + try: + response = requests.post(callbackUrl, json=res) + + # 检查响应状态码 + if response.status_code == 200: + logger.info("回调请求发送成功!") + else: + logger.info(f"请求失败,状态码: {response.status_code}") + + except requests.RequestException as e: + logger.info("请求发生异常:", e) + try: + with transaction.atomic(): + task_obj.analysis_status = analysis_status + task_obj.error_reason = message + task_obj.save() + return Response({'success': True, 'msg': '更改任务状态成功'}) + except Exception as e: + return Response({'success': False, 'msg': f'更新失败, {e}'}) + + +video_download_path = settings.VIDEO_DOWNLOAD_PATH + + +class DownloadVideo(viewsets.ModelViewSet): + def create(self, request, *args, **kwargs): + data = request.data + fileUrl = data.get('fileUrl') + callbackUrl = data.get('callbackUrl') + + filename = fileUrl.split('/')[-1] + download_path = os.path.join(video_download_path, filename) + if os.path.exists(download_path): + file_stem = Path(download_path).stem + file_suffix = Path(download_path).suffix + download_path = os.path.join(video_download_path, f"{file_stem}_{str(time.time()).split('.')[1]}{file_suffix}") + + + # 新增同一视频直接返回结果 + video_obj = Video.objects.filter(ftp_path=fileUrl, download_status=2).last() + if video_obj: + video_id = video_obj.id + task_obj = Task.objects.filter(video_id=video_id).last() + if task_obj: + logger.info(f"该链接{fileUrl}任务已存在,任务结果为{task_obj.analysis_status}") + # 有分析已完成任务 + if task_obj.analysis_status == 102: + response = VideoResult.get_data_from_es(taskId=task_obj.taskId) + if response.get("message") == "success": + logger.info(f"es中获取数据,taskId: {task_obj.taskId}") + return Response(response) + else: + res = {} + res['status'] = 0000 + res['message'] = 'success' + res['data'] = {'taskId': task_obj.taskId} + return Response(res) + # 有未分析或正在分析任务 + elif task_obj.analysis_status in (100, 101): + print(222, task_obj.taskId) + res = {} + res['status'] = 0000 + res['message'] = 'success' + res['data'] = {'taskId': task_obj.taskId} + return Response(res) + + res = {'status': 400, 'message': 'false'} + # video_hash = get_file_hash(download_path) + # if video_hash: + # duplicate_video = Video.objects.filter(video_hash=video_hash).last() + # if duplicate_video: + # task_obj = Task.objects.filter(video_id=duplicate_video.id).last() + # logger.warning(f"当前下载视频{download_path}与视频{duplicate_video.video_name}的hash值相同") + # if task_obj.analysis_status == 102: + # response = VideoResult.get_data_from_es(taskId=task_obj.taskId) + # if response.get("message") == "success": + # return Response(response) + # return Response({'taskId': task_obj.taskId, 'status': 0000, 'message': 'success'}) + # else: + # return Response({'taskId': task_obj.taskId, 'status': 0000, 'message': 'success'}) + + try: + with transaction.atomic(): + video_obj = Video.objects.create(video_name=filename, video_path=download_path, status=0, download_status=0, ftp_path=fileUrl) + video_id = video_obj.id + task_obj = Task.objects.create(analysis_status=100, video_id=video_id, callbackUrl=callbackUrl) + taskId = task_obj.taskId + except Exception as e: + logger.info(e) + return Response(res) + res['status'] = 0000 + res['message'] = 'success' + res['data'] = {'taskId': taskId} + logger.info(f"当前链接{fileUrl}任务创建成功,任务id为:{taskId}") + return Response(res) + + +class VideoResult(viewsets.ModelViewSet): + def list(self, request, *args, **kwargs): + query_params = dict(request.query_params) + logger.info(query_params) + if query_params.get('taskId'): + taskId = query_params.get('taskId')[0] + taskId = int(taskId) if isinstance(taskId, str) else taskId + else: + return Response({'msg': '请携带taskId'}) + + task = get_data_by_model(Task, {'taskId': taskId}) + if task: + # task = get_data_by_model(Video, {'taskId': taskId}) + if task.video.video_status == 4: + duplicate_video = Video.objects.filter(video_hash=task.video.video_hash).last() + if duplicate_video: + task_obj = Task.objects.filter(video_id=duplicate_video.id).last() + taskId = task_obj.taskId + + res = self.get_data_from_es(taskId) + + return Response(res) + + @classmethod + def get_data_from_es(cls, taskId): + + task_obj = Task.objects.filter(taskId=taskId).first() + analysis_status = task_obj.analysis_status if task_obj else 106 + + try: + s = Search(index='tp-log') + s = s.from_dict({ + "query": { + "match": { + "data.@fields.taskId": task_obj.taskId + } + }, + "size": 1 + }).sort({"@timestamp": {"order": "desc"}}) + response = s.execute() + if not response: + return {'status': analysis_status, 'message': '无分析结果,请查看状态码'} + data = response.hits[0].to_dict().get('data').get('@fields') + + res = { + 'status': 0, + 'message': 'success', + 'data': { + 'videoStatus': data.get('data').get('videoStatus'), + 'invalidReason': data.get('data').get('invalidReason'), + 'statistics': data.get('data').get('statistics'), + 'result': data.get('data').get('result'), + "resultTime": data.get('data').get('resultTime'), + "taskId": data.get('taskId'), + } + } + return res + except Exception as e: + return {'status': analysis_status, 'message': f'获取视频解析结果失败,原因{e}'} + + diff --git a/department/__init__.py b/department/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/department/admin.py b/department/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/department/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/department/apps.py b/department/apps.py new file mode 100644 index 0000000..e47ee31 --- /dev/null +++ b/department/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DepartmentConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'department' diff --git a/department/migrations/0001_initial.py b/department/migrations/0001_initial.py new file mode 100644 index 0000000..1dd442b --- /dev/null +++ b/department/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.19 on 2023-09-22 09:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Department', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=128, verbose_name='部门名称')), + ('parent_id', models.IntegerField(blank=True, null=True, verbose_name='上级部门id')), + ('status', models.IntegerField(default=1, verbose_name='状态')), + ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')), + ], + options={ + 'db_table': 'tp_department', + }, + ), + ] diff --git a/department/migrations/__init__.py b/department/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/department/models.py b/department/models.py new file mode 100644 index 0000000..9b98864 --- /dev/null +++ b/department/models.py @@ -0,0 +1,14 @@ +from django.db import models + +# Create your models here. + + +class Department(models.Model): + name = models.CharField(max_length=128, verbose_name='部门名称') + parent_id = models.IntegerField(verbose_name='上级部门id', null=True, blank=True) + status = models.IntegerField(verbose_name='状态', default=1) + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间', null=True, blank=True) + + class Meta: + db_table = 'tp_department' + diff --git a/department/serializers.py b/department/serializers.py new file mode 100644 index 0000000..56b2e2d --- /dev/null +++ b/department/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from .models import Department + + +class DepartmentSerializer(serializers.ModelSerializer): + class Meta: + model = Department + fields = "__all__" diff --git a/department/tests.py b/department/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/department/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/department/urls.py b/department/urls.py new file mode 100644 index 0000000..4da54d2 --- /dev/null +++ b/department/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from .views import DepartmentView + +urlpatterns = [ + path('', DepartmentView.as_view({'get': 'list', 'post': 'create'})), + path('/', DepartmentView.as_view({'put': 'update', 'delete': 'destroy'})), + # path('query_depart_users/', DepartmentView.as_view({'post': 'query_department_users'})), + # path('son_departments/', DepartmentView.as_view({'post': 'create_son_department'})), + +] + diff --git a/department/views.py b/department/views.py new file mode 100644 index 0000000..a749a53 --- /dev/null +++ b/department/views.py @@ -0,0 +1,159 @@ +import logging +from django.db import transaction +from django.db.models import Q +from django.shortcuts import render +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import viewsets +from app.pagination import MyPageNumberPagination +from user.models import UserProfile +from .models import Department +from .serializers import DepartmentSerializer +# Create your views here. + +logger = logging.getLogger('mylogger') + + +class DepartmentView(viewsets.GenericViewSet): + def list(self, request, *args, **kwargs): + data = dict() + try: + parent_departments = list(Department.objects.filter(parent_id__isnull=True).values()) + for parent_department in parent_departments: + parent_id = parent_department.get('id') + son_departs = list(Department.objects.filter(parent_id=parent_id).values()) + parent_department['children'] = son_departs + data['data'] = parent_departments + return Response(data) + except Exception as e: + return Response({'msg': f'查询失败, {e}'}) + + def query_department_users(self, request, *args, **kwargs): + res = dict() + data = request.data + department_id = data.get('department_id') + try: + users = list(UserProfile.objects.filter(department_id=department_id).values('id', 'username', 'phone_number', 'status', 'department_id')) + res['data'] = users + return Response(res) + except Exception as e: + return Response({'msg': f'查询失败,原因{e}'}) + + # def list_son_departments(self, request, *args, **kwargs): + # query_params = dict(request.query_params) + # parent_name = query_params.get('parent_name') + # if isinstance(parent_name, list): + # parent_name = ''.join(parent_name) + # logger.info(parent_name) + # parent_obj = Department.objects.filter(name=parent_name).first() + # if not parent_obj: + # return Response({'msg': '非法请求,该父级部门不存在'}) + # parent_id = parent_obj.id + # queryset = Department.objects.filter(parent_id=parent_id).order_by('-id') + # paginator = MyPageNumberPagination() + # paginated_queryset = paginator.paginate_queryset(queryset, request) + # serializer = DepartmentSerializer(paginated_queryset, many=True) + # + # return paginator.get_paginated_response(serializer.data) + + # def create(self, request, *args, **kwargs): + # data = request.data + # parent_depart_name = data.get('parent_department') + # if not parent_depart_name: + # return Response({"msg": "请输入父级部门名称"}) + # parent_obj = Department.objects.filter(name=parent_depart_name).first() + # if parent_obj: + # return Response({'msg': '该部门已存在,请换一个'}) + # try: + # with transaction.atomic(): + # new_obj = Department.objects.create(name=parent_depart_name) + # return Response({'msg': '创建父级部门成功'}) + # except Exception as e: + # return Response({'msg': f'创建失败,原因{e}'}) + + # def create(self, request, *args, **kwargs): + # data = request.data + # parent_name = data.get('parent_department_name') + # if not parent_name: + # return Response({'msg': '请输入父级部门名称'}) + # depart_name = data.get('department_name') + # try: + # with transaction.atomic(): + # if not depart_name: + # old_parent_obj = Department.objects.filter(name=parent_name).first() + # if old_parent_obj: + # return Response({'msg': '该父级部门名称已存在,请换一个'}) + # parent_obj = Department.objects.create(name=parent_name) + # return Response({'msg': '创建父级部门成功'}) + # else: + # old_parent_obj = Department.objects.filter(name=parent_name).first() + # if not old_parent_obj: + # old_parent_obj = Department.objects.create(name=parent_name) + # old_depart_obj = Department.objects.filter(name=depart_name, parent_id=old_parent_obj.id).first() + # if old_depart_obj: + # return Response({'msg': f'{parent_name}下已有{depart_name}啦,请换一个'}) + # new_depart_obj = Department.objects.create(name=depart_name, parent_id=old_parent_obj.id) + # return Response({'msg': '创建子部门成功'}) + # except Exception as e: + # return Response({'msg': f'创建失败,{e}'}) + + def create(self, request, *args, **kwargs): + data = request.data + department_name = data.get('department_name') + department_id = data.get('department_id') + if not department_id or department_id == '': + old_depart_obj = Department.objects.filter(name=department_name, parent_id__isnull=True).first() + if old_depart_obj: + return Response({'msg': f'该父级部门{department_name}已存在'}) + depart_obj = Department.objects.create(name=department_name) + return Response({'msg': f'创建父级部门{department_name}成功', 'success': True}) + else: + parent_obj = Department.objects.filter(id=department_id, parent_id__isnull=True).first() + if not parent_obj: + return Response({'msg': '非法请求'}) + old_son_obj = Department.objects.filter(name=department_name, parent_id=department_id).first() + if old_son_obj: + return Response({'msg': f'该父级部门{parent_obj.name}下已有{old_son_obj.name}啦'}) + son_obj = Department.objects.create(name=department_name, parent_id=department_id) + return Response({'msg': f'创建父级部门{parent_obj.name}下{son_obj.name}成功', 'success': True}) + + # def create_son_department(self, request, *args, **kwargs): + # data = request.data + # parent_name = data.get('parent_name') + # if not parent_name: + # return Response({'msg': '请输入父级部门名称以便创建子部门'}) + # parent_obj = Department.objects.filter(name=parent_name).first() + # parent_id = parent_obj.id + # department_name = data.get('department_name') + # if not department_name: + # return Response({'msg': '请输入子部门名称'}) + # depart_obj = Department.objects.filter(name=department_name, parent_id=parent_id).first() + # if depart_obj: + # return Response({'msg': f'{parent_name}下已有{department_name},请换一个'}) + # try: + # with transaction.atomic(): + # Department.objects.create(name=department_name, parent_id=parent_id) + # return Response({'msg': '创建成功'}) + # except Exception as e: + # return Response({'msg': f'创建失败,原因{e}'}) + + def update(self, request, pk, *args, **kwargs): + data = request.data + try: + with transaction.atomic(): + depart_obj = Department.objects.filter(id=pk).update(**data) + return Response({'msg': '更新成功', 'success': True}) + except Department.DoesNotExist: + return Response({'msg': '该部门不存在'}) + except Exception as e: + return Response({'msg': f'更新失败,原因{e}'}) + + def destroy(self, request, pk, *args, **kwargs): + try: + with transaction.atomic(): + depart_obj = Department.objects.filter(id=pk).delete() + return Response({'msg': '删除成功', 'success': True}) + except Department.DoesNotExist: + return Response({'msg': '该部门不存在'}) + except Exception as e: + return Response({'msg': f'删除失败,原因{e}'}) diff --git a/dev.py b/dev.py new file mode 100644 index 0000000..0605cfa --- /dev/null +++ b/dev.py @@ -0,0 +1,33 @@ +# 输出查询结果]) + +from datetime import datetime +from elasticsearch import Elasticsearch + +# 连接到 Elasticsearch +client = Elasticsearch("http://localhost:9209", basic_auth=("elastic", "changeme")) + +start_date = datetime(2024, 1, 1) # 开始时间 +end_date = datetime(2024, 12, 31) # 结束时间 + +# 构建查询体 +query = { + "query": { + "range": { + "@timestamp": { + "gte": start_date.strftime("%Y-%m-%dT%H:%M:%S"), + "lte": end_date.strftime("%Y-%m-%dT%H:%M:%S") + } + } + }, + "size": 10000 +} + +# 执行查询操作 +response = client.search(index="tp-log", body=query) + +# 打印结果 +print("Found {} documents:".format(response['hits']['total']['value'])) +result = client.delete_by_query(index="tp-log", body=query) + +# 打印删除结果 +print("Deleted:", result) \ No newline at end of file diff --git a/event/__init__.py b/event/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/event/admin.py b/event/admin.py new file mode 100644 index 0000000..557811a --- /dev/null +++ b/event/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +# Register your models here. +from event.models import Event +admin.site.register(Event) diff --git a/event/apps.py b/event/apps.py new file mode 100644 index 0000000..49fdd6a --- /dev/null +++ b/event/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EventConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'event' diff --git a/event/filters.py b/event/filters.py new file mode 100644 index 0000000..f4d30fe --- /dev/null +++ b/event/filters.py @@ -0,0 +1,14 @@ +import django_filters +from django_filters.rest_framework import FilterSet + +from event.models import Event + + +class EventFilter(FilterSet): + event_name = django_filters.CharFilter(field_name='event_name', lookup_expr='icontains') + + class Meta: + # 指定模型 + models = Event + # 指定需要模糊查询的字段 + fields = ("event_name",) diff --git a/event/migrations/0001_initial.py b/event/migrations/0001_initial.py new file mode 100644 index 0000000..6f24e8f --- /dev/null +++ b/event/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.19 on 2023-09-22 09:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('event_name', models.CharField(default='', max_length=256, verbose_name='事件名')), + ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ], + options={ + 'db_table': 'tp_event', + 'ordering': ['-id'], + }, + ), + ] diff --git a/event/migrations/__init__.py b/event/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/event/models.py b/event/models.py new file mode 100644 index 0000000..4126726 --- /dev/null +++ b/event/models.py @@ -0,0 +1,13 @@ +from django.db import models + +# Create your models here. + + +class Event(models.Model): + event_name = models.CharField(max_length=256, default='', verbose_name='事件名') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'tp_event' + ordering = ['-id'] diff --git a/event/serializers.py b/event/serializers.py new file mode 100644 index 0000000..d93aab7 --- /dev/null +++ b/event/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from event.models import Event + + +class EventSerializer(serializers.ModelSerializer): + class Meta: + model = Event + fields = "__all__" diff --git a/event/tests.py b/event/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/event/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/event/urls.py b/event/urls.py new file mode 100644 index 0000000..13eaef9 --- /dev/null +++ b/event/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from event import views + +router = DefaultRouter() +router.register('', views.EventViewSet) + +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/event/views.py b/event/views.py new file mode 100644 index 0000000..a8f93af --- /dev/null +++ b/event/views.py @@ -0,0 +1,23 @@ +from django.shortcuts import render +from rest_framework import viewsets + +from app.pagination import MyPageNumberPagination +from event.filters import EventFilter +from event.models import Event +from event.serializers import EventSerializer + + +# Create your views here. + +class EventViewSet(viewsets.ModelViewSet): + # 查询集 + queryset = Event.objects.all().order_by('-id') + # 序列化器 + serializer_class = EventSerializer + # 分页器 + pagination_class = MyPageNumberPagination + # 过滤器 + filterset_class = EventFilter + + + diff --git a/logs/.__debug.lock b/logs/.__debug.lock new file mode 100644 index 0000000..e69de29 diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..366b772 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TP_API.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/media/images/.gitkeep b/media/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/media/video/.gitkeep b/media/video/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mymiddleware/middleware.py b/mymiddleware/middleware.py new file mode 100644 index 0000000..db4f86e --- /dev/null +++ b/mymiddleware/middleware.py @@ -0,0 +1,30 @@ +import jwt +from django.conf import settings +from user.models import UserProfile +from django.http import HttpResponse, JsonResponse +from django.utils.deprecation import MiddlewareMixin + +import logging + +from rest_framework.response import Response + +logger = logging.getLogger('mylogger') +class CheckTokenMiddleware(MiddlewareMixin): + def process_request(self, request): + # todo 登录时不需要校验token + path_info = request.path_info + if path_info.endswith('login/') or path_info.endswith('nanjing/'): + return + my_auth = request.META.get('HTTP_TOKEN') + if not my_auth: + return JsonResponse(data={'msg': '非法请求,请求头中未携带token'}, status=201) + try: + token = my_auth.split(' ')[1] + res_dict = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + except Exception as e: + logger.info(e) + return JsonResponse({'msg': f'非法token,{e}'}, status=201) + # request.user = UserProfile.objects.filter(id=res_dict.get('user_id')).first() + # logger.info(res_dict) + return + diff --git a/pip.conf b/pip.conf new file mode 100644 index 0000000..c969f0d --- /dev/null +++ b/pip.conf @@ -0,0 +1,6 @@ +[global] +index-url = https://pypi.tuna.tsinghua.edu.cn/simple +[install] +trusted-host = https://pypi.tuna.tsinghua.edu.cn +[list] +format=columns \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..85fa8cb Binary files /dev/null and b/requirements.txt differ diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..a278599 --- /dev/null +++ b/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +echo "Appling database migrations..." +sleep 5 +python manage.py makemigrations && python manage.py migrate && python manage.py runserver 0.0.0.0:8000&& +echo 'TP项目启动完成' +tail -f /dev/null + +exec "$@" \ No newline at end of file diff --git a/static/nanjing.mbtiles b/static/nanjing.mbtiles new file mode 100644 index 0000000..0953602 Binary files /dev/null and b/static/nanjing.mbtiles differ diff --git a/static/zhanweitu.png b/static/zhanweitu.png new file mode 100644 index 0000000..b67f3b8 Binary files /dev/null and b/static/zhanweitu.png differ diff --git a/static/zwt.png b/static/zwt.png new file mode 100644 index 0000000..ec46673 Binary files /dev/null and b/static/zwt.png differ diff --git a/user/__init__.py b/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/admin.py b/user/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/user/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/user/apps.py b/user/apps.py new file mode 100644 index 0000000..36cce4c --- /dev/null +++ b/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py new file mode 100644 index 0000000..9500d5e --- /dev/null +++ b/user/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# Generated by Django 3.2.19 on 2023-09-22 09:47 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('department', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('organization', models.CharField(blank=True, max_length=256, null=True, verbose_name='组织')), + ('gender', models.IntegerField(choices=[(0, '女'), (1, '男')], default=1, verbose_name='性别')), + ('phone_number', models.CharField(blank=True, max_length=12, null=True, verbose_name='手机号')), + ('head_sculpture', models.CharField(blank=True, max_length=512, null=True, verbose_name='头像')), + ('status', models.IntegerField(default=1, verbose_name='状态')), + ('recorder_number', models.CharField(blank=True, max_length=256, null=True, verbose_name='执法记录仪编号')), + ('department', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='department.department')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'db_table': 'tp_user', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/user/migrations/__init__.py b/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/models.py b/user/models.py new file mode 100644 index 0000000..b997a8c --- /dev/null +++ b/user/models.py @@ -0,0 +1,28 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from department.models import Department + +# Create your models here. + + +# class Department(models.Model): +# department_name = models.CharField(max_length=128, verbose_name='部门名称') +# # parent_id = models.IntegerField(verbose_name='上级部门id', null=True, blank=True) +# # status = models.IntegerField(verbose_name='状态', default=1) +# +# class Meta: +# db_table = 'user_department' + + +class UserProfile(AbstractUser): + GENDER = ((0, '女'), (1, '男')) + organization = models.CharField(max_length=256, verbose_name='组织', null=True, blank=True) + gender = models.IntegerField(choices=GENDER, default=1, verbose_name='性别') + phone_number = models.CharField(max_length=12, verbose_name='手机号', null=True, blank=True) + head_sculpture = models.CharField(max_length=512, verbose_name='头像', blank=True, null=True) + status = models.IntegerField(verbose_name='状态', default=1) + recorder_number = models.CharField(max_length=256, verbose_name='执法记录仪编号', null=True, blank=True) + department = models.ForeignKey(Department, on_delete=models.CASCADE, null=True) + + class Meta: + db_table = 'tp_user' diff --git a/user/serializers.py b/user/serializers.py new file mode 100644 index 0000000..47b08ca --- /dev/null +++ b/user/serializers.py @@ -0,0 +1,12 @@ +from rest_framework import serializers +from .models import UserProfile + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = UserProfile + # fields = "__all__" + + exclude = ('password',) + + diff --git a/user/tests.py b/user/tests.py new file mode 100644 index 0000000..5a31890 --- /dev/null +++ b/user/tests.py @@ -0,0 +1,278 @@ +from django.test import TestCase + +# Create your tests here. + +l1 = [ + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-14 15:43:25", + "police_id": "8888888", + "event_type": "卡车", + "is_violation": False, + "small_image": "http://192.168.10.28:8000/media/images/B2_006.png", + "relative_time": 6.0, + "video_dir": "http://192.168.10.28:8000/media/videos/B2.mp4", + "car_number": "赣A·98980", + "ai_analysis": "", + "add_time": "2023-06-12 09:17:45", + "update_time": "2023-06-27 02:36:51", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-14 15:43:24", + "police_id": "8888888", + "event_type": "卡车", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/B2_004.png", + "relative_time": 4.0, + "video_dir": "http://192.168.10.28:8000/media/videos/B2.mp4", + "car_number": "赣A·98980", + "ai_analysis": "", + "add_time": "2023-06-12 09:16:41", + "update_time": "2023-06-27 02:36:51", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2023-05-14 17:27:40", + "police_id": "", + "event_type": "推搡", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/shoving_005_1.png", + "relative_time": 9.0, + "video_dir": "http://192.168.10.28:8000/media/videos/shoving.mp4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 07:13:24", + "update_time": "2023-06-27 02:36:51", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2023-05-14 17:27:40", + "police_id": "", + "event_type": "推搡", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/shoving_009_1.png", + "relative_time": 9.0, + "video_dir": "http://192.168.10.28:8000/media/videos/shoving.mp4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 07:13:19", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2023-05-14 17:27:40", + "police_id": "", + "event_type": "推搡", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/shoving_009.png", + "relative_time": 9.0, + "video_dir": "http://192.168.10.28:8000/media/videos/shoving.mp4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 07:12:34", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2023-05-14 17:27:40", + "police_id": "", + "event_type": "推搡", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/shoving_005.png", + "relative_time": 5.0, + "video_dir": "http://192.168.10.28:8000/media/videos/shoving.mp4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 07:10:03", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-14 17:27:40", + "police_id": "8888888", + "event_type": "未戴头盔", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G2_018_1.png", + "relative_time": 18.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G2.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:53:50", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-14 17:27:40", + "police_id": "", + "event_type": "未戴头盔", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G2_018.png", + "relative_time": 18.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G2.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:53:26", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 07:42:48", + "police_id": "0890151", + "event_type": "未戴头盔以及载人", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G1_024_1.png", + "relative_time": 24.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:52:12", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 07:42:48", + "police_id": "0890151", + "event_type": "未戴头盔以及载人", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G1_024.png", + "relative_time": 24.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:51:33", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 07:42:46", + "police_id": "0890151", + "event_type": "未戴头盔以及载人", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G1_022.png", + "relative_time": 22.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:50:53", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 07:42:28", + "police_id": "0890151", + "event_type": "未戴头盔以及载人", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G1_004.png", + "relative_time": 4.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:49:36", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 07:44:05", + "police_id": "0890151", + "event_type": "未戴头盔", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G1_141_1.png", + "relative_time": 101.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:48:34", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 07:44:05", + "police_id": "0890151", + "event_type": "未戴头盔", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G1_141.jpg", + "relative_time": 141.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:46:45", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 07:42:26", + "police_id": "0890151", + "event_type": "未戴头盔以及载人", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/G1_002.png", + "relative_time": 2.0, + "video_dir": "http://192.168.10.28:8000/media/videos/G1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:45:39", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 10:36:13", + "police_id": "6666666", + "event_type": "未戴头盔", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/B1_056.png", + "relative_time": 56.0, + "video_dir": "http://192.168.10.28:8000/media/videos/B1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:44:04", + "update_time": "2023-06-27 02:36:52", + "is_display": True + }, + { + "video_hash": "vbhdrbvcw", + "record_time": "2022-07-15 10:36:13", + "police_id": "6666666", + "event_type": "未戴头盔", + "is_violation": True, + "small_image": "http://192.168.10.28:8000/media/images/056.png", + "relative_time": 56.0, + "video_dir": "http://192.168.10.28:8000/media/videos/B1.MP4", + "car_number": "", + "ai_analysis": "", + "add_time": "2023-06-12 06:18:40", + "update_time": "2023-06-27 02:36:52", + "is_display": True + } +] + +l2 = [] + +from app.models import TP + + +def import_data(data): + try: + for item in data: + TP.objects.create(**item) + return + except Exception as e: + print(e) + return + +if __name__ == '__main__': + import_data(l1) diff --git a/user/urls.py b/user/urls.py new file mode 100644 index 0000000..bc18c97 --- /dev/null +++ b/user/urls.py @@ -0,0 +1,13 @@ +from django.urls import path +from .views import UserViewSet + +urlpatterns = [ + path('', UserViewSet.as_view({"get": "query_users", "post": "create"})), + path('login/', UserViewSet.as_view({"post": "tp_login"})), + path('/', UserViewSet.as_view({"put": "update_user", "delete": "delete_user"})), + # path('set_user_role', UserViewSet.as_view({"post": "set_user_role"})), + # path('role/', RoleView.as_view()), + # path('role//', RoleView.as_view()) +] + + diff --git a/user/utils.py b/user/utils.py new file mode 100644 index 0000000..5c94bb1 --- /dev/null +++ b/user/utils.py @@ -0,0 +1,13 @@ +def update_user_querydict(querydict): + new_querydict = dict() + if 'username' in querydict: + new_querydict['username__icontains'] = ''.join(querydict.pop('username')) + if 'phone_number' in querydict and querydict.get('phone_number') != ['']: + new_querydict['phone_number__icontains'] = ''.join(querydict.pop('phone_number')) + if 'status' in querydict and querydict.get('status') != ['']: + new_querydict['status'] = ''.join(querydict.pop('status')) + return new_querydict + + + + diff --git a/user/views.py b/user/views.py new file mode 100644 index 0000000..7d6b7ec --- /dev/null +++ b/user/views.py @@ -0,0 +1,242 @@ +import json +import logging + +from django.contrib.auth import authenticate +from django.contrib.auth.hashers import make_password +from django.db import transaction +from django.shortcuts import render + +# Create your views here. +from rest_framework import viewsets +from rest_framework.response import Response +from rest_framework.views import APIView +from django.contrib.auth.models import Group +from app.pagination import MyPageNumberPagination +from app.utils import generate_token, decode_token_exp_time +from department.models import Department +from .models import UserProfile +from .serializers import UserSerializer +from .utils import update_user_querydict + +logger = logging.getLogger('mylogger') + + +class UserViewSet(viewsets.GenericViewSet): + def tp_login(self, request, *args, **kwargs): + """登录 POST""" + data = request.data + username = data.get('username') + password = data.get('password') + login_user = authenticate(username=username, password=password) + if login_user and login_user.is_active and login_user.status == 1: + # 生成token + token = generate_token(login_user) + response = { + 'success': True, + 'msg': '登录成功', + 'data': { + 'username': login_user.username, + 'roles': ['admin'] if login_user.is_superuser else ['common'], + 'accessToken': token, + 'expires': decode_token_exp_time(token) + } + } + return Response(response) + else: + response = { + 'success': False, + 'msg': '登录失败', + 'data': { + 'username': None, + 'roles': [], + 'accessToken': None, + 'expires': None + } + } + return Response(response) + + def create(self, request, *args, **kwargs): + """注册 POST 用户名 手机号 状态 性别 部门""" + data = request.data + username = data.get('username') + if not username: + return Response({'msg': '用户名不能为空'}) + phone_number = data.get('phone_number') + gender = data.get('gender') + password = data.get('password', '#Yaxin0504') + # role = data.get('role') + # role_obj = Group.objects.filter(name=role).first() + department_id = data.get('department_id') + department_obj = Department.objects.filter(id=department_id).first() + status = data.get('status', 1) + try: + if UserProfile.objects.filter(username=username).first(): + return Response({'msg': '该用户名已存在,请换一个'}) + user = UserProfile.objects.create_user(username=username, password=password, department=department_obj, status=status, gender=gender, phone_number=phone_number) + # user.groups.add(role_obj) + # user.save() + return Response({'msg': '添加成功', 'success': True}) + except Exception as e: + response = { + "msg": f'添加失败, 原因:{e}' + } + return Response(response) + + def query_users(self, request, *args, **kwargs): + """查询所有用户,默认全部,可根据部门查询""" + query_params = dict(request.query_params) + logger.info(query_params) + querydict = update_user_querydict(query_params) + logger.info(querydict) + if query_params.get('department_id') and query_params.get('department_id') != ['']: + department_id = ''.join(query_params.get('department_id')) + depart_obj = Department.objects.filter(id=department_id).first() + if not depart_obj.parent_id: + # 父级部门 + dp_ids = list(Department.objects.filter(parent_id=depart_obj.id).values_list('id', flat=True).distinct()) + else: + dp_ids = [depart_obj.id] + queryset = UserProfile.objects.filter(department_id__in=dp_ids, **querydict).order_by('-id') + else: + queryset = UserProfile.objects.filter(**querydict).order_by('-id') + paginator = MyPageNumberPagination() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = UserSerializer(paginated_queryset, many=True) + paginator_response = paginator.get_paginated_response(serializer.data) + response_data = { + 'count': paginator_response.data['count'], + 'next': paginator_response.data['next'], + 'previous': paginator_response.data['previous'], + 'results': list(paginator_response.data['results']), + } + results = response_data.get('results') + res_list = json.loads(json.dumps(results)) + for res_dict in res_list: + department_obj = Department.objects.filter(id=res_dict.get('department')).first() + if department_obj: + res_dict['department_name'] = department_obj.name + res_dict['department_id'] = res_dict.pop('department') + else: + res_dict['department_name'] = '暂无部门' + res_dict['department_id'] = '暂无' + response_data['results'] = res_list + return Response(response_data) + + # def set_user_role(self, request, *args, **kwargs): + # try: + # user_id = request.data.get('user_id') + # user = UserProfile.objects.get(id=user_id) + # group = Group.objects.get(name=request.data.get('role_name')) + # is_member = group.user_set.filter(id=user.id).exists() + # if is_member: + # return Response({'msg': f'用户{user.username}已经是{group.name}啦'}) + # user.groups.add(group) + # user.save() + # return Response({'msg': '修改用户角色成功'}) + # except UserProfile.DoesNotExist: + # return Response({'msg': '该用户不存在'}) + # except Group.DoesNotExist: + # return Response({'msg': '该角色不存在'}) + # except Exception as e: + # return Response({'msg': f'更改用户角色失败: {e}'}) + + def update_user(self, request, pk, *args, **kwargs): + data = request.data + if 'username' in data: + user_obj = UserProfile.objects.exclude(id=pk).filter(username=data.get('username')).first() + if user_obj: + return Response({'msg': '更新失败,该用户名已存在,请换一个'}) + # user_id = data.pop('user_id', None) + if 'password' in data: + # hashed_pwd = make_password(data.get('password')) + data['password'] = make_password(data.pop('password')) + try: + with transaction.atomic(): + user = UserProfile.objects.filter(id=pk).update(**data) + return Response({'msg': '更新成功', 'success': True}) + except UserProfile.DoesNotExist: + return Response({'msg': '该用户不存在'}) + except Exception as e: + return Response({'msg': f'更新失败,原因{e}'}) + + def delete_user(self, request, pk, *args, **kwargs): + try: + with transaction.atomic(): + user = UserProfile.objects.filter(id=pk).delete() + return Response({'msg': '删除成功', 'success': True}) + except UserProfile.DoesNotExist: + return Response({'msg': '该用户不存在'}) + except Exception as e: + return Response({'msg': f'删除失败,原因{e}'}) + + +# class RoleView(APIView): +# def get(self, request): +# queryset = Group.objects.all().order_by('-id') +# paginator = MyPageNumberPagination() +# paginated_queryset = paginator.paginate_queryset(queryset, request) +# +# results = [] +# for group in paginated_queryset: +# results.append({ +# 'id': group.id, +# 'name': group.name, +# }) +# +# response = { +# 'count': paginator.page.paginator.count, +# 'next': paginator.get_next_link(), +# 'previous': paginator.get_previous_link(), +# 'results': results +# } +# +# return Response(response) +# +# def post(self, request, *args, **kwargs): +# """添加角色""" +# data = request.data +# group_name = data.get('role_name') +# try: +# old_group = Group.objects.filter(name=group_name).first() +# if old_group: +# return Response({"msg": "该角色名已存在"}) +# with transaction.atomic(): +# group = Group.objects.create(name=group_name) +# group.save() +# return Response({"msg": "创建成功"}) +# except Exception as e: +# return Response({"msg": f"创建失败:{e}"}) +# +# # +# def put(self, request, pk): +# group_name = request.data.get('role_name') +# try: +# with transaction.atomic(): # 开启事务 +# group = Group.objects.get(id=pk) +# group.name = group_name +# group.save() +# return Response({'success': True, 'msg': 'Group updated successfully'}) +# except Group.DoesNotExist: +# return Response({'success': False, 'msg': 'Group not found'}) +# except Exception as e: +# return Response({'success': False, 'msg': str(e)}) +# +# def delete(self, request, pk): +# try: +# with transaction.atomic(): # 开启事务 +# group = Group.objects.get(id=pk) +# group.delete() +# return Response({'success': True, 'msg': 'Group deleted successfully'}) +# except Group.DoesNotExist: +# return Response({'success': False, 'msg': 'Group not found'}) +# except Exception as e: +# return Response({'success': False, 'msg': str(e)}) +# +# def modify_permission(self, request, *args, **kwargs): +# pass + + + + + + diff --git a/uwsgi.ini b/uwsgi.ini new file mode 100644 index 0000000..b602047 --- /dev/null +++ b/uwsgi.ini @@ -0,0 +1,46 @@ +[uwsgi] + +project=myproject +#uid=www-data +#gid=www-data +base=/home + +chdir=%(base)/%(project) +module=TP_API.wsgi:application +master=True +processes=3 +threads=4 + +http=0.0.0.0:8000 + +chown-socket=%(uid):www-data +chmod-socket=664 + +vacuum=True +max-requests=5000 + +pidfile=%(base)/%(project)/logs/%(project)-master.pid +daemonize=%(base)/%(project)/logs/%(project)-uwsgi.log + +#设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃 +harakiri = 60 +post buffering = 8192 +buffer-size= 65535 +#当一个请求被harakiri杀掉会,会输出一条日志 +harakiri-verbose = true + +#开启内存使用情况报告 +memory-report = true + +#设置平滑的重启(直到处理完接收到的请求)的长等待时间(秒) +reload-mercy = 10 + +#设置工作进程使用虚拟内存超过N MB就回收重启 +reload-on-as= 1024 + +# 序列化接受的内容,如果可能的话 +thunder-lock=true + +enable-threads = true + +workers = 3 \ No newline at end of file diff --git a/wait-for.sh b/wait-for.sh new file mode 100755 index 0000000..d5ab9f5 --- /dev/null +++ b/wait-for.sh @@ -0,0 +1,192 @@ +#!/bin/sh + +# The MIT License (MIT) +# +# Copyright (c) 2017 Eficode Oy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +VERSION="2.2.4" + +set -- "$@" -- "$TIMEOUT" "$QUIET" "$PROTOCOL" "$HOST" "$PORT" "$result" +TIMEOUT=15 +QUIET=0 +# The protocol to make the request with, either "tcp" or "http" +PROTOCOL="tcp" + +echoerr() { + if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi +} + +usage() { + exitcode="$1" + cat << USAGE >&2 +Usage: + $0 host:port|url [-t timeout] [-- command args] + -q | --quiet Do not output any status messages + -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout + Defaults to 15 seconds + -v | --version Show the version of this tool + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit "$exitcode" +} + +wait_for() { + case "$PROTOCOL" in + tcp) + if ! command -v nc >/dev/null; then + echoerr 'nc command is missing!' + exit 1 + fi + ;; + http) + if ! command -v wget >/dev/null; then + echoerr 'wget command is missing!' + exit 1 + fi + ;; + esac + + TIMEOUT_END=$(($(date +%s) + TIMEOUT)) + + while :; do + case "$PROTOCOL" in + tcp) + nc -w 1 -z "$HOST" "$PORT" > /dev/null 2>&1 + ;; + http) + wget --timeout=1 --tries=1 -q "$HOST" -O /dev/null > /dev/null 2>&1 + ;; + *) + echoerr "Unknown protocol '$PROTOCOL'" + exit 1 + ;; + esac + + result=$? + + if [ $result -eq 0 ] ; then + if [ $# -gt 7 ] ; then + for result in $(seq $(($# - 7))); do + result=$1 + shift + set -- "$@" "$result" + done + + TIMEOUT=$2 QUIET=$3 PROTOCOL=$4 HOST=$5 PORT=$6 result=$7 + shift 7 + exec "$@" + fi + exit 0 + fi + + if [ $TIMEOUT -ne 0 -a $(date +%s) -ge $TIMEOUT_END ]; then + echo "Operation timed out" >&2 + exit 1 + fi + + sleep 1 + done +} + +while :; do + case "$1" in + http://*|https://*) + HOST="$1" + PROTOCOL="http" + shift 1 + ;; + *:* ) + HOST=$(printf "%s\n" "$1"| cut -d : -f 1) + PORT=$(printf "%s\n" "$1"| cut -d : -f 2) + shift 1 + ;; + -v | --version) + echo $VERSION + exit + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -q-*) + QUIET=0 + echoerr "Unknown option: $1" + usage 1 + ;; + -q*) + QUIET=1 + result=$1 + shift 1 + set -- -"${result#-q}" "$@" + ;; + -t | --timeout) + TIMEOUT="$2" + shift 2 + ;; + -t*) + TIMEOUT="${1#-t}" + shift 1 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + break + ;; + --help) + usage 0 + ;; + -*) + QUIET=0 + echoerr "Unknown option: $1" + usage 1 + ;; + *) + QUIET=0 + echoerr "Unknown argument: $1" + usage 1 + ;; + esac +done + +if ! [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then + echoerr "Error: invalid timeout '$TIMEOUT'" + usage 3 +fi + +case "$PROTOCOL" in + tcp) + if [ "$HOST" = "" ] || [ "$PORT" = "" ]; then + echoerr "Error: you need to provide a host and port to test." + usage 2 + fi + ;; + http) + if [ "$HOST" = "" ]; then + echoerr "Error: you need to provide a host to test." + usage 2 + fi + ;; +esac + +wait_for "$@" \ No newline at end of file