From 75fc321db777091da08152398e29c66edf56855e Mon Sep 17 00:00:00 2001 From: kongfp Date: Fri, 7 Jul 2023 15:33:49 +0800 Subject: [PATCH] init project --- .gitignore | 26 +++ Dockerfile | 34 ++++ README.md | 3 + XZNSH_API/__init__.py | 2 + XZNSH_API/asgi.py | 16 ++ XZNSH_API/settings.py | 245 +++++++++++++++++++++++++++ XZNSH_API/urls.py | 33 ++++ XZNSH_API/wsgi.py | 16 ++ XZNSH后台接口文档.md | 279 +++++++++++++++++++++++++++++++ app/__init__.py | 0 app/admin.py | 5 + app/apps.py | 6 + app/exception.py | 23 +++ app/migrations/__init__.py | 0 app/models.py | 42 +++++ app/pagination.py | 9 + app/serializers.py | 58 +++++++ app/tests.py | 3 + app/urls.py | 29 ++++ app/utils.py | 31 ++++ app/views.py | 104 ++++++++++++ event/__init__.py | 0 event/admin.py | 5 + event/apps.py | 6 + event/filters.py | 14 ++ event/migrations/0001_initial.py | 27 +++ event/migrations/__init__.py | 0 event/models.py | 13 ++ event/serializers.py | 8 + event/tests.py | 3 + event/urls.py | 10 ++ event/views.py | 23 +++ logs/.__debug.lock | 0 logs/.gitkeep | 0 manage.py | 22 +++ media/images/.gitkeep | 0 media/video/.gitkeep | 0 mymiddleware/middleware.py | 30 ++++ pip.conf | 6 + requirements.txt | Bin 0 -> 670 bytes start.sh | 9 + user/__init__.py | 0 user/admin.py | 3 + user/apps.py | 6 + user/migrations/0001_initial.py | 48 ++++++ user/migrations/__init__.py | 0 user/models.py | 20 +++ user/tests.py | 278 ++++++++++++++++++++++++++++++ user/views.py | 3 + uwsgi.ini | 42 +++++ 50 files changed, 1540 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 XZNSH_API/__init__.py create mode 100644 XZNSH_API/asgi.py create mode 100644 XZNSH_API/settings.py create mode 100644 XZNSH_API/urls.py create mode 100644 XZNSH_API/wsgi.py create mode 100644 XZNSH后台接口文档.md create mode 100644 app/__init__.py create mode 100644 app/admin.py create mode 100644 app/apps.py create mode 100644 app/exception.py create mode 100644 app/migrations/__init__.py create mode 100644 app/models.py create mode 100644 app/pagination.py create mode 100644 app/serializers.py create mode 100644 app/tests.py create mode 100644 app/urls.py create mode 100644 app/utils.py create mode 100644 app/views.py create mode 100644 event/__init__.py create mode 100644 event/admin.py create mode 100644 event/apps.py create mode 100644 event/filters.py create mode 100644 event/migrations/0001_initial.py create mode 100644 event/migrations/__init__.py create mode 100644 event/models.py create mode 100644 event/serializers.py create mode 100644 event/tests.py create mode 100644 event/urls.py create mode 100644 event/views.py create mode 100644 logs/.__debug.lock create mode 100644 logs/.gitkeep create mode 100644 manage.py create mode 100644 media/images/.gitkeep create mode 100644 media/video/.gitkeep create mode 100644 mymiddleware/middleware.py create mode 100644 pip.conf create mode 100644 requirements.txt create mode 100644 start.sh create mode 100644 user/__init__.py create mode 100644 user/admin.py create mode 100644 user/apps.py create mode 100644 user/migrations/0001_initial.py create mode 100644 user/migrations/__init__.py create mode 100644 user/models.py create mode 100644 user/tests.py create mode 100644 user/views.py create mode 100644 uwsgi.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab115d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +.DS_Store +node_modules +/dist +.idea +__pycache__ + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.log +db.sqlite3 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f5338cf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# 建立 python 3.7环境 +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 + +# 安装项目依赖 +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 new file mode 100644 index 0000000..86c5099 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# XZNSH_Api + +XZNSH后台 \ No newline at end of file diff --git a/XZNSH_API/__init__.py b/XZNSH_API/__init__.py new file mode 100644 index 0000000..063cd2c --- /dev/null +++ b/XZNSH_API/__init__.py @@ -0,0 +1,2 @@ +import pymysql +pymysql.install_as_MySQLdb() diff --git a/XZNSH_API/asgi.py b/XZNSH_API/asgi.py new file mode 100644 index 0000000..1b83a78 --- /dev/null +++ b/XZNSH_API/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for XZNSH_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', 'XZNSH_API.settings') + +application = get_asgi_application() diff --git a/XZNSH_API/settings.py b/XZNSH_API/settings.py new file mode 100644 index 0000000..421671b --- /dev/null +++ b/XZNSH_API/settings.py @@ -0,0 +1,245 @@ +""" +Django settings for XZNSH_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/ +""" + +from pathlib import Path +import os + +# 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', +] + +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 = 'XZNSH_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 = 'XZNSH_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': 'xznsh_test', + 'USER': 'root', + 'PASSWORD': '123abc', + 'HOST': '127.0.0.1', + 'PORT': '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/' + +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': 'concurrent_log_handler.ConcurrentRotatingFileHandler', # window多进程 需要 pip install concurrent-log-handler + # 'filename': os.path.join(BASE_DIR, "logs/debug.log"), # 日志文件 + # 'backupCount': 10, # 保留的最大文件数,超过则删除日期最早的 + # 'maxBytes': 1024 * 1024 * 10, # 文件大小 + # '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'], + 'level': 'DEBUG', + 'propagate': True, # 是否向上一级logger实例传递日志信息 + }, + }, +} + +AUTH_USER_MODEL = 'user.UserProfile' diff --git a/XZNSH_API/urls.py b/XZNSH_API/urls.py new file mode 100644 index 0000000..13592fd --- /dev/null +++ b/XZNSH_API/urls.py @@ -0,0 +1,33 @@ +"""XZNSH_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/', include('app.urls')), + path('event/', include('event.urls')), +] + + +urlpatterns += [ + re_path(r'^media/(?P.*)$', serve, {'document_root': MEDIA_ROOT}), +] diff --git a/XZNSH_API/wsgi.py b/XZNSH_API/wsgi.py new file mode 100644 index 0000000..25446d1 --- /dev/null +++ b/XZNSH_API/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for XZNSH_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', 'XZNSH_API.settings') + +application = get_wsgi_application() diff --git a/XZNSH后台接口文档.md b/XZNSH后台接口文档.md new file mode 100644 index 0000000..344712d --- /dev/null +++ b/XZNSH后台接口文档.md @@ -0,0 +1,279 @@ + + + + + + +# XZNSH后台接口文档 +
+条件查询 + +- 请求方式: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..a932cee --- /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/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/__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..8d654b1 --- /dev/null +++ b/app/models.py @@ -0,0 +1,42 @@ +from django.db import models + +# Create your models here. + + +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='记录仪时间') + # 警号 + 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=100, verbose_name='缩略图') + # 相对时间 + relative_time = models.FloatField(verbose_name='相对时间') + # 视频路径 + video_dir = models.CharField(max_length=100, verbose_name='视频路径') + # 车牌号 + car_number = models.CharField(max_length=50, 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) + + class Meta: + db_table = "app_tp" + + # 排序 uid倒序 + ordering = ['-uid'] diff --git a/app/pagination.py b/app/pagination.py new file mode 100644 index 0000000..b60b4ab --- /dev/null +++ b/app/pagination.py @@ -0,0 +1,9 @@ +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..fabae95 --- /dev/null +++ b/app/serializers.py @@ -0,0 +1,58 @@ +import datetime + +from rest_framework import serializers +from django_filters.rest_framework import FilterSet +import django_filters +from app.models import TP +import logging +logger = logging.getLogger('mylogger') +class SerialMyModel(serializers.ModelSerializer): + class Meta: + model = TP + fields = "__all__" + + +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",) + + 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..eddd084 --- /dev/null +++ b/app/urls.py @@ -0,0 +1,29 @@ +"""XZNSH_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('add_user', views.RegisterLoginViewSet.as_view({"post": "tp_register"})), + path('login', views.LoginView.as_view({"post": "tp_login"})), + path('events', views.ModelQuery.as_view({"get": "query_event"})), +] diff --git a/app/utils.py b/app/utils.py new file mode 100644 index 0000000..3fe31f4 --- /dev/null +++ b/app/utils.py @@ -0,0 +1,31 @@ +import time +import datetime +import jwt +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 + diff --git a/app/views.py b/app/views.py new file mode 100644 index 0000000..b6a33ca --- /dev/null +++ b/app/views.py @@ -0,0 +1,104 @@ +import logging + +from django.db.models import Count +from django.shortcuts import render +from django.views.decorators.http import require_http_methods + +# Create your views here. +from rest_framework import viewsets +from rest_framework.response import Response +from app.models import TP +from app.serializers import SerialMyModel, SerialFilter +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 + +logger = logging.getLogger('mylogger') + + +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.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': '查询成功', + 'data': data + } + return Response(response) + + +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 LoginView(viewsets.ModelViewSet): + 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: + # 生成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 tp_logout(self, request, *args, **kwargs): + """登出 GET""" + pass 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..ebe90a7 --- /dev/null +++ b/event/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.19 on 2023-06-30 16:19 + +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..bbdca0c --- /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', 'XZNSH_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..81c96ab --- /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'): + 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 0000000000000000000000000000000000000000..829da450d991d0a5350c0abe1b1eac29fd75d339 GIT binary patch literal 670 zcmZva-A=+l5QWdRiH}lZ3yNHL=cOhZpP&krV%;L!#-E2*znP^CL_;>DowMg}`tw~X z>RGjRTB*_%zo{m=;GF4HCwk)jP92DfWAlPZ;iz*^^F3;<5?ln`DN|6((<|>9Zhbk& zz@3lW*V=#x=(nSC!8F&xS(gX%3@64JVPC^nYU!+TyqRk@G9TYG=MBCrNlIWDi$5CH zK?GNUD{;TlyKBDut5M@1fE#knobOJby5&6RUN=5Vc_%bxeQud`G;-WznwkEa`n`*k z%=(zp_^^)%qJd>-+jwkh*^`3~g)ND#*9@)?Paa5Clg9q-j^S;8Y0Eu{8{gF