first commit

This commit is contained in:
lihansani
2026-07-13 15:37:05 +08:00
commit 8b11aa2efc
299 changed files with 206339 additions and 0 deletions

99
code/.gitignore vendored Normal file
View File

@@ -0,0 +1,99 @@
# Byte-compiled / optimized / DLL files
*.py[cod]
*$py.class
__pycache__/
# C extensions
*.so
# Distribution / packaging
.Python
env/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
.idea/
*.db
.DS_Store
__pycache__
**/migrations
!**/migrations/__init__.py
*.pyc
!conf/env.example.py
db.sqlite3
media/
static/

18
code/.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "E:/software/mingw/mingw64/bin/gcc.exe",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

24
code/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": true,
"cwd": "e:/projects/wxwx/projects_job/VDES_Projects/C_project",
"program": "e:/projects/wxwx/projects_job/VDES_Projects/C_project/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

64
code/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,64 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false,
"files.associations": {
"math.h": "c",
"windows.h": "c",
"winsock2.h": "c"
}
}

146
code/README.md Normal file
View File

@@ -0,0 +1,146 @@
# FuAdmin
[![img](https://img.shields.io/badge/license-Apache%202.0-dark)](https://gitee.com/fuadmin/fu-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-4.0-blue)](https://docs.djangoproject.com/zh-hans/4.0.4/) [![img](https://img.shields.io/badge/node-%3E%3D%2016.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/fuadmin/fu-admin/badge/star.svg?theme=dark)](https://gitee.com/fuadmin/fu-admin) [![GitHub stars](https://img.shields.io/github/stars/FuAdmin/FuAdmin.svg?theme=dark&label=Github)](https://github.com/FuAdmin/FuAdmin)
## 为什么使用 Django-Ninja 和 Vue3
💡 **「Django Ninja 相比于 Django Rest Framework 」**
- 简单:旨在易于使用和直观,直接使用@装饰器java好像叫注解开发api
- 快速执行:由于 Pydantic 和异步的支持,性能非常高。
- 快速编码:输入提示和自动文档让您只关注业务逻辑。
- 基于标准:基于 API 的开放标准OpenAPI以前称为 Swagger和 JSON Schema。
- Django 集成:与 Django 自身的功能和 ORM 有很好的集成。
![](backend/utils/picture/benchmark.png)
💡 **「Vue3 」**
- 最火的框架,国内最火的前端框架之一。
- 性能提升,运行速度是 vue2 的 1.5 倍。
- 体积更小,按需编译体积 vue2 要更小。
- 类型推断,更好的支持 ts 这个也是趋势。
- 高级给予,暴露了更底层的 API 和提供更先进的内置组件。
- 组合 API能够更好的组织逻辑封装逻辑复用逻辑
## 平台简介
💡 [fu-admin-web](https://gitee.com/fuadmin/fu-admin-web) 采用 VUE3TS 开发。 💡 [fu-admin-backend](https://gitee.com/fuadmin/fu-admin-backend) 采用 PythonDjango 和 Django-Ninija 开发。数据库支持MySqlSqlServerSqlite。
- 🧑‍🤝‍🧑 前端采用[VbenAdmin](https://vvbin.cn/doc-next) 、[Vue3](https://cn.vuejs.org/)、[Ant Design Vue](https://www.antdv.com/docs/vue/getting-started-cn)。
- 👭 后端采用 Python 语言 Django 框架以及强大的 [Django Ninja](https://django-ninja.rest-framework.com/)。
- 👬 支持加载动态权限菜单,多方式轻松权限控制。
- 💡 Vue2 项目移步[Dvadmin](https://gitee.com/liqianglog/django-vue-admin)
- 💏 特别鸣谢:[VbenAdmin](https://github.com/vbenjs/vue-vben-admin) 、[Ant Design Vue](https://github.com/vueComponent/ant-design-vue)。
- 💡 特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
## 在线体验
👩‍👧‍👦 演示地址:[http://175.24.184.165:8080](http://175.24.184.165:8080)
- 账号superadmin
- 密码123456
## 交流
- FuAdmin 交流群726947525
<img src="backend/utils/picture/qq.png" alt="qq 群" style="zoom: 25%;" />
## 源码地址
| | 项目地址 |
|--------|-------------------------------------|
| github | https://github.com/FuAdmin/fu-admin |
| 码云 | https://gitee.com/fuadmin/fu-admin |
## 内置功能
1. 👨‍⚕️ 菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
2. 🧑‍⚕️ 部门管理:配置系统组织机构(公司、部门、角色)。
3. 👩‍⚕️ 角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
4. 🧑‍🎓 权限权限:授权角色的权限范围。
5. 👨‍🎓 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
6. 🧑‍🔧 数据字典:对系统中经常使用的一些较为固定的数据进行维护。
7. 🧑‍🔧 分类字典:对系统中经常使用的一些树形数据进行维护。
8. 📁 附件管理:对平台上所有文件、图片等进行统一管理。
9. 🗓️ 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 定时任务:系统设置定时任务。
## 准备工作
```
Python >= 3.8.0 (推荐3.8+版本)
nodejs >= 16.0 (推荐最新)
Mysql >= 5.7.0 (可选默认数据库sqlite3推荐8.0版本)
Redis(可选,最新版)
```
## 前端 ♝
```bash
# 克隆项目
git https://gitee.com/fuadmin/fu-admin.git
# 进入项目目录
cd web
# 安装依赖
yarn install --registry=https://registry.npm.taobao.org
# 启动服务
yarn run dev
# 浏览器访问 http://localhost:8080
# .env 文件中可配置启动端口等参数
# 构建生产环境
# yarn run build
```
## 后端 💈
```bash
# 克隆项目
git https://gitee.com/fuadmin/fu-admin.git
# 进入项目目录
cd backend
# 在 `env.py` 中配置数据库信息
# 默认是Mysql如果使用SqlServerqing在requirements.txt中打开
mssql-django==1.1.2
pyodbc==4.0.32
# 安装依赖环境
pip3 install -r requirements.txt
# 执行迁移命令:
python3 manage.py makemigrations system
python3 manage.py migrate
# 初始化数据
python3 manage.py init
# 初始化省市县数据:
python3 manage.py init_area
# 启动项目
python3 manage.py runserver 0.0.0.0:8000
# 或使用 daphne :
daphne -b 0.0.0.0 -p 8000 application.asgi:application
```
### 访问项目
- 文档访问地址:[http://localhost:8080/api/docs](http://localhost:8080/api/docs) (默认为此地址,如有修改请按照配置文件)
- 账号:`superadmin` 密码:`123456`
## 演示图 ✅
![](backend/utils/picture/1.png)
![](backend/utils/picture/2.png)
![](backend/utils/picture/3.png)
![](backend/utils/picture/4.png)
![](backend/utils/picture/5.png)
![](backend/utils/picture/6.png)
![](backend/utils/picture/7.png)
![](backend/utils/picture/8.png)
![](backend/utils/picture/9.png)
![](backend/utils/picture/10.png)

318
code/bk/settings.txt Normal file
View File

@@ -0,0 +1,318 @@
"""
Django settings for fuadmin project.
Generated by 'django-admin startproject' using Django 4.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
from datetime import timedelta
from pathlib import Path
from conf.env import *
# 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/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-x4k4^#6wovi1aep8%ow!5fr%(9o#1u=+0+nzi($_j=^d*ui6g3'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = locals().get('DEBUG', True)
ALLOWED_HOSTS = locals().get('ALLOWED_HOSTS', ['*'])
DEMO = locals().get('DEMO', False)
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_celery_beat',
'django_celery_results',
'system',
'demo',
'generator',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utils.middleware.ApiLoggingMiddleware',
]
ROOT_URLCONF = 'fuadmin.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'fuadmin.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/4.0/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/4.0/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/4.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'system.Users'
USERNAME_FIELD = 'username'
ALL_MODELS_OBJECTS = [] # 所有app models 对象
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
# 数据库配置
if DATABASE_TYPE == "MYSQL":
# Mysql数据库
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"HOST": DATABASE_HOST,
"PORT": DATABASE_PORT,
"USER": DATABASE_USER,
"PASSWORD": DATABASE_PASSWORD,
"NAME": DATABASE_NAME,
}
}
elif DATABASE_TYPE == "SQLSERVER":
# SqlServer数据库
DATABASES = {
"default": {
"ENGINE": "mssql",
"HOST": DATABASE_HOST,
"PORT": DATABASE_PORT,
"USER": DATABASE_USER,
"PASSWORD": DATABASE_PASSWORD,
"NAME": DATABASE_NAME,
# 全局开启事务绑定的是http请求响应整个过程
'ATOMIC_REQUESTS': True,
'OPTIONS': {
'driver': 'ODBC Driver 17 for SQL Server',
},
}
}
elif DATABASE_TYPE == "POSTGRESQL":
# POSTGRESQL
DATABASES = {
"default": {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
"HOST": DATABASE_HOST,
"PORT": DATABASE_PORT,
"USER": DATABASE_USER,
"PASSWORD": DATABASE_PASSWORD,
"NAME": DATABASE_NAME,
# 全局开启事务绑定的是http请求响应整个过程
'ATOMIC_REQUESTS': True,
}
}
else:
# sqlite3 数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'OPTIONS': {
'timeout': 20,
},
}
}
# 缓存配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": f'{REDIS_URL}/1',
"TIMEOUT": None,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
}
# # ================================================= #
# # ********************* 日志配置 ******************* #
# # ================================================= #
# # log 配置部分BEGIN #
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
LOGS_FILE = os.path.join(BASE_DIR, "logs")
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
os.makedirs(os.path.join(BASE_DIR, "logs"))
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
# 格式:[日期][模块.函数名称():行号] [级别] 信息
STANDARD_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
CONSOLE_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {"format": STANDARD_LOG_FORMAT},
"console": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
"file": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"filename": SERVER_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 5, # 最多备份5个
"formatter": "standard",
"encoding": "utf-8",
},
"error": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": ERROR_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 3, # 最多备份3个
"formatter": "standard",
"encoding": "utf-8",
},
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "console",
},
},
"loggers": {
"": {
"handlers": ["console", "error", "file"],
"level": "INFO",
},
"django": {
"handlers": ["console", "error", "file"],
"level": "INFO",
"propagate": False,
},
'django.db.backends': {
'handlers': ["console", "error", "file"],
'propagate': False,
'level': "INFO"
},
"uvicorn.error": {
"level": "INFO",
"handlers": ["console", "error", "file"],
},
"uvicorn.access": {
"handlers": ["console", "error", "file"],
"level": "INFO"
},
},
}
# celery 配置
CELERY_BROKER_URL = f'{REDIS_URL}/2'
DJANGO_CELERY_BEAT_TZ_AWARE = False
CELERY_ENABLE_UTC = False
CELERY_WORKER_CONCURRENCY = 2 # 并发数
CELERY_MAX_TASKS_PER_CHILD = 5 # 每个worker最多执行5个任务便自我销毁释放内存
CELERY_TIMEZONE = TIME_ZONE # celery 时区问题
CELERY_RESULT_BACKEND = 'django-db' # celery结果存储到数据库中
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' # Backend数据库
# token 有效时间 时 分 秒
TOKEN_LIFETIME = 12 * 60 * 60
# TOKEN_LIFETIME = 50
# 接口白名单,不需要授权直接访问
WHITE_LIST = ['/api/system/userinfo', '/api/system/permCode', '/api/system/menu/route/tree', '/api/system/user/*',
'/api/system/user/set/repassword']
# 接口日志记录
API_LOG_ENABLE = True
API_LOG_METHODS = ['POST', 'GET', 'DELETE', 'PUT']
API_MODEL_MAP = {}
# 初始化需要执行的列表,用来初始化后执行
INITIALIZE_RESET_LIST = []
# vdes监听器配置
VDES_MONITOR_HOST = 'localhost'
VDES_MONITOR_PORT = 8082
VDES_BUFFER_SIZE = 65536 # 接收缓冲区大小
# ais船只信息配置
OWN_SHIP_MMSI = '413323670'
OWN_SHIP_NAME = '湃星一号'
OWN_SHIP_TYPE = '货船'
RADIUS_NM = 50.0 #显示周边多少海里范围的船只

23
code/bk/urls.txt Normal file
View File

@@ -0,0 +1,23 @@
"""fuadmin URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.0/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
from .api import api
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', api.urls),
]

34
code/conf/docker.py Normal file
View File

@@ -0,0 +1,34 @@
import os
# ================================================= #
# ************** mysql数据库 配置 ************** #
# ================================================= #
# 数据库类型 MYSQL/SQLSERVER/SQLITE3
DATABASE_TYPE = "MYSQL"
# 数据库地址
DATABASE_HOST = "fuadmin-mysql"
# 数据库端口
DATABASE_PORT = 3306
# 数据库用户名
DATABASE_USER = "root"
# 数据库密码
DATABASE_PASSWORD = "root"
# 数据库名
DATABASE_NAME = "fuadmin"
# ================================================= #
# ************** redis配置无redis 可不进行配置 ************** #
# ================================================= #
REDIS_PASSWORD = ""
REDIS_HOST = "fuadmin-redis"
REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379'
# ================================================= #
# ************** 其他 配置 ************** #
# ================================================= #
DEBUG = False # 线上环境请设置为False
ALLOWED_HOSTS = ["*"]
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)
# 是否开启演示环境,开启后增 删 改功能失效
DEMO = False

32
code/conf/env.py Normal file
View File

@@ -0,0 +1,32 @@
# ================================================= #
# ************** mysql数据库 配置 ************** #
# ================================================= #
# 数据库类型 MYSQL/SQLSERVER/SQLITE3/POSTGRESQL
DATABASE_TYPE = "MYSQL"
# 数据库地址
DATABASE_HOST = "127.0.0.1"
# 数据库端口
DATABASE_PORT = 3306
# 数据库用户名
DATABASE_USER = "root"
# 数据库密码
DATABASE_PASSWORD = "010704"
# 数据库名
DATABASE_NAME = "vdes_data"
# ================================================= #
# ************** redis配置无redis 可不进行配置 ************** #
# ================================================= #
REDIS_PASSWORD = ''
REDIS_HOST = '127.0.0.1'
REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379'
# ================================================= #
# ************** 其他 配置 ************** #
# ================================================= #
DEBUG = True # 线上环境请设置为False
ALLOWED_HOSTS = ["*"]
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)
# 是否开启演示环境,开启后增 删 改功能失效
DEMO = False

0
code/demo/__init__.py Normal file
View File

3
code/demo/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

94
code/demo/api.py Normal file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/7/21 14:01
# file: api.py
# author: 臧成龙
# QQ: 939589097
from typing import List
from demo.models import Demo
from ninja import Field, ModelSchema, Query, Router
from ninja.pagination import paginate
from utils.fu_crud import (
ImportSchema,
create,
delete,
export_data,
import_data,
retrieve,
update,
)
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
# 设置过滤字段
class Filters(FuFilters):
name: str = Field(None, alias="name")
code: str = Field(None, alias="code")
status: int = Field(None, alias="status")
id: str = Field(None, alias="demo_id")
# 设置请求接收字段
class DemoSchemaIn(ModelSchema):
remark: list
class Config:
model = Demo
model_fields = ['name', 'code', 'sort', 'status']
# 设置响应字段
class DemoSchemaOut(ModelSchema):
class Config:
model = Demo
model_fields = ['id', 'name', 'code', 'sort', 'status', 'remark', 'create_datetime']
# 创建Demo
@router.post("/demo", response=DemoSchemaOut)
def create_demo(request, data: DemoSchemaIn):
data.remark = ','.join(data.remark)
demo = create(request, data, Demo)
return demo
# 删除Demo
@router.delete("/demo/{demo_id}")
def delete_demo(request, demo_id: int):
delete(demo_id, Demo)
return {"success": True}
# 更新Demo
@router.put("/demo/{demo_id}", response=DemoSchemaOut)
def update_demo(request, demo_id: int, data: DemoSchemaIn):
data.remark = ','.join(data.remark)
demo = update(request, demo_id, data, Demo)
return demo
# 获取Demo
@router.get("/demo", response=List[DemoSchemaOut])
@paginate(MyPagination)
def list_demo(request, filters: Filters = Query(...)):
qs = retrieve(request, Demo, filters)
return qs
# 导入
@router.get("/demo/all/export")
def export_demo(request):
export_fields = ['name', 'code', 'status']
return export_data(request, Demo, DemoSchemaOut, export_fields)
# 导出
@router.post("/demo/all/import")
def import_demo(request, data: ImportSchema):
import_fields = ['name', 'code', 'status']
return import_data(request, Demo, DemoSchemaIn, data, import_fields)

6
code/demo/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class DemoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'demo'

14
code/demo/models.py Normal file
View File

@@ -0,0 +1,14 @@
from django.db import models
from utils.models import CoreModel
class Demo(CoreModel):
name = models.CharField(null=False, max_length=64, verbose_name="项目名称", help_text="项目名称")
code = models.CharField(max_length=32, verbose_name="项目编码", help_text="项目编码")
status = models.CharField(max_length=64, verbose_name="项目状态", help_text="项目状态")
class Meta:
db_table = "Demo"
verbose_name = '项目演示'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)

11
code/demo/router.py Normal file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/7/21 14:10
# file: router.py
# author: 臧成龙
# QQ: 939589097
from ninja import Router
from demo.api import router
demo_router = Router()
demo_router.add_router('/', router, tags=["Demo"])

3
code/demo/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
code/demo/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

0
code/fuadmin/__init__.py Normal file
View File

29
code/fuadmin/api.py Normal file
View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/9 23:15
# @Author : 臧成龙
# @FileName: api.py
# @Software: PyCharm
from demo.router import demo_router
from system.router import system_router
from utils.fu_auth import GlobalAuth
from utils.fu_ninja import FuNinjaAPI
from generator.router import generator_router
# api = FuNinjaAPI(auth=GlobalAuth())
api = FuNinjaAPI() # 移除auth参数
# 统一处理server异常
@api.exception_handler(Exception)
def a(request, exc):
if hasattr(exc, 'errno'):
return api.create_response(request, data=[], msg=str(exc), code=exc.errno)
else:
return api.create_response(request, data=[], msg=str(exc), code=500)
api.add_router('/system/', system_router)
api.add_router('/demo/', demo_router)
api.add_router('/generator/', generator_router)

16
code/fuadmin/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for fuadmin 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/4.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'fuadmin.settings')
application = get_asgi_application()

14
code/fuadmin/celery.py Normal file
View File

@@ -0,0 +1,14 @@
import os
from celery import Celery, platforms
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', "fuadmin.settings")
# app = Celery(f"application")
app = Celery(f"system")
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
# app.autodiscover_tasks()
platforms.C_FORCE_ROOT = True

326
code/fuadmin/settings.py Normal file
View File

@@ -0,0 +1,326 @@
"""
Django settings for fuadmin project.
Generated by 'django-admin startproject' using Django 4.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
from datetime import timedelta
from pathlib import Path
from conf.env import *
# 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/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-x4k4^#6wovi1aep8%ow!5fr%(9o#1u=+0+nzi($_j=^d*ui6g3'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = locals().get('DEBUG', True)
ALLOWED_HOSTS = locals().get('ALLOWED_HOSTS', ['*'])
DEMO = locals().get('DEMO', False)
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_celery_beat',
'django_celery_results',
'system',
'demo',
'generator',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utils.middleware.ApiLoggingMiddleware',
]
ROOT_URLCONF = 'fuadmin.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'fuadmin.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/4.0/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/4.0/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/4.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'system.Users'
USERNAME_FIELD = 'username'
ALL_MODELS_OBJECTS = [] # 所有app models 对象
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
# 数据库配置
if DATABASE_TYPE == "MYSQL":
# Mysql数据库
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"HOST": DATABASE_HOST,
"PORT": DATABASE_PORT,
"USER": DATABASE_USER,
"PASSWORD": DATABASE_PASSWORD,
"NAME": DATABASE_NAME,
}
}
elif DATABASE_TYPE == "SQLSERVER":
# SqlServer数据库
DATABASES = {
"default": {
"ENGINE": "mssql",
"HOST": DATABASE_HOST,
"PORT": DATABASE_PORT,
"USER": DATABASE_USER,
"PASSWORD": DATABASE_PASSWORD,
"NAME": DATABASE_NAME,
# 全局开启事务绑定的是http请求响应整个过程
'ATOMIC_REQUESTS': True,
'OPTIONS': {
'driver': 'ODBC Driver 17 for SQL Server',
},
}
}
elif DATABASE_TYPE == "POSTGRESQL":
# POSTGRESQL
DATABASES = {
"default": {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
"HOST": DATABASE_HOST,
"PORT": DATABASE_PORT,
"USER": DATABASE_USER,
"PASSWORD": DATABASE_PASSWORD,
"NAME": DATABASE_NAME,
# 全局开启事务绑定的是http请求响应整个过程
'ATOMIC_REQUESTS': True,
}
}
else:
# sqlite3 数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'OPTIONS': {
'timeout': 20,
},
}
}
# 缓存配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": f'{REDIS_URL}/1',
"TIMEOUT": None,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
}
# # ================================================= #
# # ********************* 日志配置 ******************* #
# # ================================================= #
# # log 配置部分BEGIN #
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
LOGS_FILE = os.path.join(BASE_DIR, "logs")
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
os.makedirs(os.path.join(BASE_DIR, "logs"))
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
# 格式:[日期][模块.函数名称():行号] [级别] 信息
STANDARD_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
CONSOLE_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {"format": STANDARD_LOG_FORMAT},
"console": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
"file": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"filename": SERVER_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 5, # 最多备份5个
"formatter": "standard",
"encoding": "utf-8",
},
"error": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": ERROR_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 3, # 最多备份3个
"formatter": "standard",
"encoding": "utf-8",
},
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "console",
},
},
"loggers": {
"": {
"handlers": ["console", "error", "file"],
"level": "INFO",
},
"django": {
"handlers": ["console", "error", "file"],
"level": "INFO",
"propagate": False,
},
'django.db.backends': {
'handlers': ["console", "error", "file"],
'propagate': False,
'level': "INFO"
},
"uvicorn.error": {
"level": "INFO",
"handlers": ["console", "error", "file"],
},
"uvicorn.access": {
"handlers": ["console", "error", "file"],
"level": "INFO"
},
},
}
# celery 配置
CELERY_BROKER_URL = f'{REDIS_URL}/2'
DJANGO_CELERY_BEAT_TZ_AWARE = False
CELERY_ENABLE_UTC = False
CELERY_WORKER_CONCURRENCY = 2 # 并发数
CELERY_MAX_TASKS_PER_CHILD = 5 # 每个worker最多执行5个任务便自我销毁释放内存
CELERY_TIMEZONE = TIME_ZONE # celery 时区问题
CELERY_RESULT_BACKEND = 'django-db' # celery结果存储到数据库中
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' # Backend数据库
# token 有效时间 时 分 秒
TOKEN_LIFETIME = 12 * 60 * 60
# TOKEN_LIFETIME = 50
# 接口白名单,不需要授权直接访问
WHITE_LIST = ['/api/system/userinfo', '/api/system/permCode', '/api/system/menu/route/tree', '/api/system/user/*',
'/api/system/user/set/repassword']
# 接口日志记录
API_LOG_ENABLE = True
API_LOG_METHODS = ['POST', 'GET', 'DELETE', 'PUT']
API_MODEL_MAP = {}
# 初始化需要执行的列表,用来初始化后执行
INITIALIZE_RESET_LIST = []
# vdes监听器配置
# VDES_MONITOR_HOST = '192.168.2.183'
# VDES_MONITOR_PORT = 10
# VDES_MONITOR_HOST = '172.16.1.59'
# VDES_MONITOR_PORT = 8081
VDES_MONITOR_HOST = 'localhost'
VDES_MONITOR_PORT = 10
VDES_BUFFER_SIZE = 65536 # 接收缓冲区大小
# ais船只信息配置
OWN_SHIP_MMSI = '413323670'
OWN_SHIP_NAME = '湃星一号'
OWN_SHIP_TYPE = '货船'
RADIUS_NM = 150.0 #显示周边多少海里范围的船只

29
code/fuadmin/urls.py Normal file
View File

@@ -0,0 +1,29 @@
"""fuadmin URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.0/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
from django.conf import settings
from django.conf.urls.static import static
from .api import api
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', api.urls),
]
# 在开发环境中添加静态文件 URL 配置
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

16
code/fuadmin/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for fuadmin 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/4.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'fuadmin.settings')
application = get_wsgi_application()

View File

6
code/generator/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class GeneratorConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'generator'

19
code/generator/router.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/7/21 14:10
# file: router.py
# author: 臧成龙
# QQ: 939589097
from ninja import Router
from .test_demo.api import router as test_demo_router
# from .template_test.api import router as template_test_router
from .test.api import router as test_router
generator_router = Router()
generator_router.add_router('/', test_demo_router, tags=['测试案例'])
# generator_router.add_router('/', template_test_router, tags=['测试1'])
generator_router.add_router('/', test_router, tags=['模板测试'])

View File

@@ -0,0 +1,83 @@
from typing import List
from .model import TemplateTest
from ninja import Field, ModelSchema, Query, Router
from ninja.pagination import paginate
from utils.fu_crud import (
ImportSchema,
create,
delete,
export_data,
import_data,
retrieve,
update,
)
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
# 设置过滤字段
class Filters(FuFilters):
input_text_area_2: str = Field(None, alias='input_text_area_2')
input_1: str = Field(None, alias='input_1')
# 设置请求接收字段
class TemplateTestSchemaIn(ModelSchema):
class Config:
model = TemplateTest
model_exclude = ['create_datetime', 'update_datetime', 'belong_dept', 'modifier', 'creator', 'creator', 'id']
# 设置响应字段
class TemplateTestSchemaOut(ModelSchema):
class Config:
model = TemplateTest
model_fields = '__all__'
# 创建TemplateTest
@router.post('/template_test', response=TemplateTestSchemaOut)
def create_template_test(request, data: TemplateTestSchemaIn):
template_test = create(request, data, TemplateTest)
return template_test
# 删除TemplateTest
@router.delete('/template_test/{id}')
def delete_template_test(request, id: int):
delete(id, TemplateTest)
return {'success': True}
# 更新TemplateTest
@router.put('/template_test/{id}', response=TemplateTestSchemaOut)
def update_template_test(request, id: int, data: TemplateTestSchemaIn):
template_test = update(request, id, data, TemplateTest)
return template_test
# 获取TemplateTest
@router.get('/template_test', response=List[TemplateTestSchemaOut])
@paginate(MyPagination)
def list_template_test(request, filters: Filters = Query(...)):
qs = retrieve(request, TemplateTest, filters)
return qs
# 导入
@router.get('/template_test/all/export')
def export_template_test(request):
export_fields = ('input_text_area_2', 'input_1', )
return export_data(request, TemplateTest, TemplateTestSchemaOut, export_fields)
# 导出
@router.post('/template_test/all/import')
def import_template_test(request, data: ImportSchema):
import_fields = ('input_text_area_2', 'input_1', )
return import_data(request, TemplateTest, TemplateTestSchemaIn, data, import_fields)

View File

@@ -0,0 +1,15 @@
from django.db import models
from utils.models import CoreModel
class TemplateTest(CoreModel):
input_text_area_2 = models.TextField(null=True, blank=True, verbose_name='文本域', help_text='文本域')
input_1 = models.CharField(null=True, blank=True, max_length=255, verbose_name='输入框', help_text='输入框')
class Meta:
db_table = 'generator_template_test'
verbose_name = '测试1'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)

View File

@@ -0,0 +1,6 @@
from ninja import Router
from .api import router
template_test_router = Router()
template_test_router.add_router('/', router, tags=['测试1'])

View File

@@ -0,0 +1,85 @@
from typing import List
from .model import Test
from ninja import Field, ModelSchema, Query, Router
from ninja.pagination import paginate
from utils.fu_crud import (
ImportSchema,
create,
delete,
export_data,
import_data,
retrieve,
update,
)
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
# 设置过滤字段
class Filters(FuFilters):
icon: str = Field(None, alias='icon')
sequence: int = Field(None, alias='sequence')
code: str = Field(None, alias='code')
name: str = Field(None, alias='name')
# 设置请求接收字段
class TestSchemaIn(ModelSchema):
class Config:
model = Test
model_exclude = ['create_datetime', 'update_datetime', 'belong_dept', 'modifier', 'creator', 'creator', 'id']
# 设置响应字段
class TestSchemaOut(ModelSchema):
class Config:
model = Test
model_fields = '__all__'
# 创建Test
@router.post('/test', response=TestSchemaOut)
def create_test(request, data: TestSchemaIn):
test = create(request, data, Test)
return test
# 删除Test
@router.delete('/test/{id}')
def delete_test(request, id: int):
delete(id, Test)
return {'success': True}
# 更新Test
@router.put('/test/{id}', response=TestSchemaOut)
def update_test(request, id: int, data: TestSchemaIn):
test = update(request, id, data, Test)
return test
# 获取Test
@router.get('/test', response=List[TestSchemaOut])
@paginate(MyPagination)
def list_test(request, filters: Filters = Query(...)):
qs = retrieve(request, Test, filters)
return qs
# 导入
@router.get('/test/all/export')
def export_test(request):
export_fields = ('icon', 'sequence', 'code', 'name', )
return export_data(request, Test, TestSchemaOut, export_fields)
# 导出
@router.post('/test/all/import')
def import_test(request, data: ImportSchema):
import_fields = ('icon', 'sequence', 'code', 'name', )
return import_data(request, Test, TestSchemaIn, data, import_fields)

View File

@@ -0,0 +1,17 @@
from django.db import models
from utils.models import CoreModel
class Test(CoreModel):
icon = models.CharField(null=True, blank=True, max_length=255, verbose_name='图标', help_text='图标')
sequence = models.DecimalField(null=True, blank=True, max_digits = 13, decimal_places = 4, verbose_name='排序', help_text='排序')
code = models.CharField(null=True, blank=True, max_length=255, verbose_name='代码', help_text='代码')
name = models.CharField(null=True, blank=True, max_length=255, verbose_name='名称', help_text='名称')
class Meta:
db_table = 'generator_test'
verbose_name = '模板测试'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)

View File

@@ -0,0 +1,6 @@
from ninja import Router
from .api import router
test_router = Router()
test_router.add_router('/', router, tags=['模板测试'])

View File

@@ -0,0 +1,84 @@
from typing import List
from .model import TestDemo
from ninja import Field, ModelSchema, Query, Router
from ninja.pagination import paginate
from utils.fu_crud import (
ImportSchema,
create,
delete,
export_data,
import_data,
retrieve,
update,
)
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
# 设置过滤字段
class Filters(FuFilters):
des: str = Field(None, alias='des')
code: int = Field(None, alias='code')
name: str = Field(None, alias='name')
# 设置请求接收字段
class TestDemoSchemaIn(ModelSchema):
class Config:
model = TestDemo
model_exclude = ['create_datetime', 'update_datetime', 'belong_dept', 'modifier', 'creator', 'creator', 'id']
# 设置响应字段
class TestDemoSchemaOut(ModelSchema):
class Config:
model = TestDemo
model_fields = '__all__'
# 创建TestDemo
@router.post('/test_demo', response=TestDemoSchemaOut)
def create_test_demo(request, data: TestDemoSchemaIn):
test_demo = create(request, data, TestDemo)
return test_demo
# 删除TestDemo
@router.delete('/test_demo/{id}')
def delete_test_demo(request, id: int):
delete(id, TestDemo)
return {'success': True}
# 更新TestDemo
@router.put('/test_demo/{id}', response=TestDemoSchemaOut)
def update_test_demo(request, id: int, data: TestDemoSchemaIn):
test_demo = update(request, id, data, TestDemo)
return test_demo
# 获取TestDemo
@router.get('/test_demo', response=List[TestDemoSchemaOut])
@paginate(MyPagination)
def list_test_demo(request, filters: Filters = Query(...)):
qs = retrieve(request, TestDemo, filters)
return qs
# 导入
@router.get('/test_demo/all/export')
def export_test_demo(request):
export_fields = ('des', 'code', 'name', )
return export_data(request, TestDemo, TestDemoSchemaOut, export_fields)
# 导出
@router.post('/test_demo/all/import')
def import_test_demo(request, data: ImportSchema):
import_fields = ('des', 'code', 'name', )
return import_data(request, TestDemo, TestDemoSchemaIn, data, import_fields)

View File

@@ -0,0 +1,16 @@
from django.db import models
from utils.models import CoreModel
class TestDemo(CoreModel):
des = models.TextField(null=True, blank=True, verbose_name='描述', help_text='描述')
code = models.DecimalField(null=True, blank=True, max_digits = 13, decimal_places = 4, verbose_name='编码', help_text='编码')
name = models.CharField(null=True, blank=True, max_length=255, verbose_name='名称', help_text='名称')
class Meta:
db_table = 'generator_test_demo'
verbose_name = '测试案例'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)

View File

@@ -0,0 +1,6 @@
from ninja import Router
from .api import router
test_demo_router = Router()
test_demo_router.add_router('/', router, tags=['测试案例'])

22
code/manage.py Normal file
View File

@@ -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', 'fuadmin.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()

21
code/requirements.txt Normal file
View File

@@ -0,0 +1,21 @@
django-ninja==0.19.1
Django==4.0.8
typing==3.7.4.3
simplejwt==2.0.1
pydantic==1.9.2
requests==2.28.1
# mysql
mysqlclient==2.1.1
# sqlserver
# mssql-django==1.1.2
# pyodbc==4.0.32
django-celery-beat==2.3.0
django-celery-results==2.3.1
celery==5.2.7
pypinyin==0.46.0
user-agents==2.2.0
django-redis==5.2.0
openpyxl==3.0.10
psutil==5.9.1
daphne==4.0.0
psycopg2-binary==2.9.9

0
code/system/__init__.py Normal file
View File

3
code/system/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

View File

@@ -0,0 +1,79 @@
# from application.ninja_cof import api
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/21 02:36
# @File : button.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import Button
from utils.fu_ninja import MyPagination
router = Router()
class Filters(Schema):
name: str = Field(None, alias="name")
code: str = Field(None, alias="code")
id: str = Field(None, alias="id")
class SchemaIn(ModelSchema):
class Config:
model = Button
model_exclude = ['id', 'create_datetime', 'update_datetime']
class SchemaOut(ModelSchema):
class Config:
model = Button
model_fields = "__all__"
# model_fields = []
@router.post("/button", response=SchemaOut)
def create_button(request, data: SchemaIn):
post = Button.objects.create(**data.dict())
return post
@router.get("/button/{button_id}", response=SchemaOut)
def get_button(request, button_id: int):
post = get_object_or_404(Button, id=button_id)
return post
@router.get("/button", response=List[SchemaOut])
@paginate(MyPagination)
def list_button(request, filters: Filters = Query(...)):
qs = Button.objects.filter(**filters.dict(exclude_none=True))
return qs
@router.get("/button/all/list", response=List[SchemaOut])
def all_list_button(request):
qs = Button.objects.all()
return qs
@router.put("/button/{button_id}", response=SchemaOut)
def update_button(request, button_id: int, payload: SchemaIn):
post = get_object_or_404(Button, id=button_id)
for attr, value in payload.dict().items():
setattr(post, attr, value)
post.save()
return post
@router.delete("/button/{button_id}")
def delete_button(request, button_id: int):
post = get_object_or_404(Button, id=button_id)
post.delete()
return {"success": True}

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/14 00:13
# @Author : 臧成龙
# @FileName: crontab_schedule.py
# @Software: PyCharm
from typing import List
from django.shortcuts import get_object_or_404
from django_celery_beat.models import CrontabSchedule
from ninja import ModelSchema, Router
from ninja.pagination import paginate
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import MyPagination
router = Router()
class SchemaIn(ModelSchema):
class Config:
model = CrontabSchedule
model_fields = ['minute', 'hour', 'day_of_month', 'month_of_year', 'day_of_week']
class SchemaOut(ModelSchema):
class Config:
model = CrontabSchedule
model_fields = ['id', 'minute', 'hour', 'day_of_month', 'month_of_year', 'day_of_week']
@router.post("/crontab_schedule", response=SchemaOut)
def create_crontab_schedule(request, data: SchemaIn):
qs = CrontabSchedule.objects.create(**data.dict())
return qs
@router.delete("/crontab_schedule/{crontab_schedule_id}")
def delete_crontab_schedule(request, crontab_schedule_id: int):
delete(crontab_schedule_id, CrontabSchedule)
return {"success": True}
@router.put("/crontab_schedule/{crontab_schedule_id}", response=SchemaOut)
def update_crontab_schedule(request, crontab_schedule_id: int, data: SchemaIn):
instance = get_object_or_404(CrontabSchedule, id=crontab_schedule_id)
for attr, value in data.dict().items():
setattr(instance, attr, value)
instance.save()
return instance
@router.get("/crontab_schedule", response=List[SchemaOut])
@paginate(MyPagination)
def list_crontab_schedule(request):
qs = retrieve(request, CrontabSchedule)
return qs
@router.get("/crontab_schedule/{crontab_schedule_id}", response=SchemaOut)
def get_crontab_schedule(request, crontab_schedule_id: int):
qs = get_object_or_404(CrontabSchedule, id=crontab_schedule_id)
return qs
@router.get("/crontab_schedule/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, CrontabSchedule)
return qs

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/14 00:32
# @Author : 臧成龙
# @FileName: interval_schedule.py
# @Software: PyCharm
from typing import List
from django.shortcuts import get_object_or_404
from django_celery_beat.models import IntervalSchedule
from ninja import ModelSchema, Router
from ninja.pagination import paginate
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import MyPagination
router = Router()
class SchemaIn(ModelSchema):
class Config:
model = IntervalSchedule
model_fields = ['every', 'period']
class SchemaOut(ModelSchema):
class Config:
model = IntervalSchedule
model_fields = ['id', 'every', 'period']
@router.post("/interval_schedule", response=SchemaOut)
def create_interval_schedule(request, data: SchemaIn):
qs = IntervalSchedule.objects.create(**data.dict())
return qs
@router.delete("/interval_schedule/{interval_schedule_id}")
def delete_interval_schedule(request, interval_schedule_id: int):
delete(interval_schedule_id, IntervalSchedule)
return {"success": True}
@router.put("/interval_schedule/{interval_schedule_id}", response=SchemaOut)
def update_interval_schedule(request, interval_schedule_id: int, data: SchemaIn):
instance = get_object_or_404(IntervalSchedule, id=interval_schedule_id)
for attr, value in data.dict().items():
setattr(instance, attr, value)
instance.save()
return instance
@router.get("/interval_schedule", response=List[SchemaOut])
@paginate(MyPagination)
def list_interval_schedule(request):
qs = retrieve(request, IntervalSchedule)
return qs
@router.get("/interval_schedule/{interval_schedule_id}", response=SchemaOut)
def get_interval_schedule(request, interval_schedule_id: int):
qs = get_object_or_404(IntervalSchedule, id=interval_schedule_id)
return qs
@router.get("/interval_schedule/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, IntervalSchedule)
return qs

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/14 01:50
# @Author : 臧成龙
# @FileName: periodic_task.py
# @Software: PyCharm
from typing import List
from django.forms import model_to_dict
from django.shortcuts import get_object_or_404
from django_celery_beat.models import CrontabSchedule, IntervalSchedule, PeriodicTask
from ninja import ModelSchema, Router, Schema
from ninja.pagination import paginate
from pydantic import Field
from system.apis import celery_crontab, celery_interval
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import MyPagination
from utils.fu_response import FuResponse
router = Router()
class SchemaIn(ModelSchema):
interval_id: int = Field(None, alias="interval")
crontab_id: int = Field(None, alias="crontab")
class Config:
model = PeriodicTask
model_fields = ['task', 'name', 'enabled']
class SchemaOut(ModelSchema):
interval: celery_interval.SchemaOut = None
crontab: celery_crontab.SchemaOut = None
class Config:
model = PeriodicTask
model_fields = ['id', 'task', 'name', 'enabled']
@router.post("/periodic_task", response=SchemaOut)
def create_periodic_task(request, data: SchemaIn):
qs = PeriodicTask.objects.create(**data.dict())
return qs
@router.delete("/periodic_task/{periodic_task_id}")
def delete_periodic_task(request, periodic_task_id: int):
delete(periodic_task_id, PeriodicTask)
return {"success": True}
@router.put("/periodic_task/{periodic_task_id}", response=SchemaOut)
def update_periodic_task(request, periodic_task_id: int, data: SchemaIn):
instance = get_object_or_404(PeriodicTask, id=periodic_task_id)
for attr, value in data.dict().items():
setattr(instance, attr, value)
instance.save()
return instance
@router.get("/periodic_task", response=List[SchemaOut])
@paginate(MyPagination)
def list_periodic_task(request):
qs = retrieve(request, PeriodicTask)
return qs
@router.get("/periodic_task/{periodic_task_id}", response=SchemaOut)
def get_periodic_task(request, periodic_task_id: int):
qs = get_object_or_404(PeriodicTask, id=periodic_task_id)
return qs
@router.get("/periodic_task/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, PeriodicTask)
return qs
class SchemaExecIn(Schema):
task: str = None
@router.post("/periodic_task/immediate/exec")
def immediate_exec_task(request, data: SchemaExecIn):
task_name = data.task
data = {
'task': None
}
test = f"""
from {'.'.join(task_name.split('.')[:-1])} import {task_name.split('.')[-1]}
task = {task_name.split('.')[-1]}.delay()
"""
exec(test)
return FuResponse()

View File

@@ -0,0 +1,241 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/10 21:56
# @Author : 臧成龙
# @FileName: generator_template.py
# @Software: PyCharm
import json
import os
import subprocess
from typing import List
from django.core import management
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router
from ninja.pagination import paginate
from system.code_template.backend.api import generator_backend_api
from system.code_template.backend.model import generator_backend_model
from system.code_template.backend.router import generator_router
from system.code_template.web.api_template import generator_api
from system.code_template.web.data_template import generator_data
from system.code_template.web.drawer_template import generator_drawer
from system.code_template.web.index_template import generator_index
from system.models import GeneratorTemplate, Menu, MenuButton, MenuColumnField
from utils.fu_crud import (
ImportSchema,
create,
delete,
export_data,
import_data,
retrieve,
update, batch_create, )
from utils.fu_ninja import FuFilters, MyPagination
from utils.fu_response import FuResponse
from utils.usual import insert_content_after_line
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
code: str = Field(None, alias="code")
id: str = Field(None, alias="id")
class GeneratorTemplateSchemaIn(ModelSchema):
class Config:
model = GeneratorTemplate
model_fields = ['name', 'code', 'form_info', 'table_info', 'remark']
class GeneratorTemplateSchemaOut(ModelSchema):
class Config:
model = GeneratorTemplate
model_fields = "__all__"
@router.post("/generator_template", response=GeneratorTemplateSchemaOut)
def create_generator_template(request, data: GeneratorTemplateSchemaIn):
generator_template = create(request, data, GeneratorTemplate)
return generator_template
@router.delete("/generator_template/{generator_template_id}")
def delete_generator_template(request, generator_template_id: int):
delete(generator_template_id, GeneratorTemplate)
return {"success": True}
@router.put("/generator_template/{generator_template_id}", response=GeneratorTemplateSchemaOut)
def update_generator_template(request, generator_template_id: int, data: GeneratorTemplateSchemaIn):
generator_template = update(request, generator_template_id, data, GeneratorTemplate)
return generator_template
@router.get("/generator_template", response=List[GeneratorTemplateSchemaOut])
@paginate(MyPagination)
def list_generator_template(request, filters: Filters = Query(...)):
qs = retrieve(request, GeneratorTemplate, filters)
return qs
@router.get("/generator_template/{generator_template_id}", response=GeneratorTemplateSchemaOut)
def get_generator_template(request, generator_template_id: int):
generator_template = get_object_or_404(GeneratorTemplate, id=generator_template_id)
return generator_template
@router.get("/generator_template/all/list", response=List[GeneratorTemplateSchemaOut])
def all_list_generator_template(request):
qs = retrieve(request, GeneratorTemplate)
return qs
@router.get("/generator_template/all/export")
def export_generator_template(request):
export_fields = ['name', 'code', 'status', 'sort']
return export_data(request, GeneratorTemplate, GeneratorTemplateSchemaOut, export_fields)
@router.post("/generator_template/all/import")
def import_generator_template(request, data: ImportSchema):
import_fields = ['name', 'code', 'status', 'sort']
return import_data(request, GeneratorTemplate, GeneratorTemplateSchemaIn, data, import_fields)
@router.put("/generator_template/code/generate/{generator_template_id}")
def generate_code(request, generator_template_id: int):
instance = get_object_or_404(GeneratorTemplate, id=generator_template_id)
web_index_txt = generator_index(instance)
web_data_txt = generator_data(instance)
web_drawer_txt = generator_drawer(instance)
web_api_txt = generator_api(instance)
web_target_path = os.path.abspath(
os.path.join(os.getcwd(), "..", 'web', 'src', 'views', 'generator', instance.code))
# 判断当前路径是否存在,没有则创建文件夹
if not os.path.exists(web_target_path):
os.makedirs(web_target_path)
web_index_path = os.path.join(web_target_path, 'index.vue')
with open(web_index_path, 'w', encoding='utf-8') as file:
file.write(web_index_txt)
web_data_path = os.path.join(web_target_path, 'data.ts')
with open(web_data_path, 'w', encoding='utf-8') as file:
file.write(web_data_txt)
web_api_path = os.path.join(web_target_path, 'api.ts')
with open(web_api_path, 'w', encoding='utf-8') as file:
file.write(web_api_txt)
web_drawer_path = os.path.join(web_target_path, 'drawer.vue')
with open(web_drawer_path, 'w', encoding='utf-8') as file:
file.write(web_drawer_txt)
# 添加列表字段,菜单和菜单按钮
if not instance.has_menu:
# 添加菜单
menu_dic = {
"type": 1,
"title": instance.name,
"parent_id": 33,
"sort": 1,
"icon": "ant-design:book-outlined",
"path": instance.code,
"status": True,
"hide_menu": False,
"component": f"/generator/{instance.code}/index.vue",
"name": instance.code,
"is_ext": False,
"keepalive": False
}
menu_qr = create(request, menu_dic, Menu)
# 添加菜单按钮
button_list = [
{
"name": "新增",
"code": f"{instance.code}:add",
"method": 1,
"api": f"/api/generator/{instance.code}",
"sort": 1,
"menu_id": menu_qr.id
},
{
"name": "删除",
"code": f"{instance.code}:delete",
"method": 3,
"api": f"/api/generator/{instance.code}/{instance.code}_id",
"sort": 2,
"menu_id": menu_qr.id
},
{
"name": "修改",
"code": f"{instance.code}:update",
"method": 2,
"api": f"/api/generator/{instance.code}/{instance.code}_id",
"sort": 3,
"menu_id": menu_qr.id
},
{
"name": "查询",
"code": f"{instance.code}:search",
"method": 0,
"api": f"/api/generator/{instance.code}",
"sort": 4,
"menu_id": menu_qr.id
}
]
batch_create(request, button_list, MenuButton)
# 添加列表字段
table_info = json.loads(instance.table_info)
column_info = table_info.get('columnInfo')
column_list = []
for item in column_info:
column_list.append({
'code': instance.code + ':' + item['field_name'],
'name': item['column_name'],
'menu_id': menu_qr.id,
})
batch_create(request, column_list, MenuColumnField)
# 生成后端代码
backend_model_txt = generator_backend_model(instance)
backend_api_txt = generator_backend_api(instance)
backend_router_txt = generator_router(instance)
# 更新generator router
generator_router_path = os.path.abspath(os.path.join(os.getcwd(), 'generator', 'router.py'))
if not instance.has_menu:
insert_from_txt = f'''from .{instance.code}.api import router as {instance.code}_router'''
insert_content_after_line(generator_router_path, 'from ninja import Router', insert_from_txt)
insert_router_txt = f'''generator_router.add_router('/', {instance.code}_router, tags=['{instance.name}'])'''
insert_content_after_line(generator_router_path, 'generator_router = Router()', insert_router_txt)
instance.has_menu = True
instance.save()
backend_target_path = os.path.abspath(os.path.join(os.getcwd(), 'generator', instance.code))
# 判断当前路径是否存在,没有则创建文件夹
if not os.path.exists(backend_target_path):
os.makedirs(backend_target_path)
backend_model_path = os.path.join(backend_target_path, 'model.py')
with open(backend_model_path, 'w', encoding='utf-8') as file:
file.write(backend_model_txt)
backend_api_path = os.path.join(backend_target_path, 'api.py')
with open(backend_api_path, 'w', encoding='utf-8') as file:
file.write(backend_api_txt)
backend_router_path = os.path.join(backend_target_path, 'router.py')
with open(backend_router_path, 'w', encoding='utf-8') as file:
file.write(backend_router_txt)
return FuResponse(msg='代码生成成功')
@router.put("/generator_template/code/generate_db/{generator_template_id}")
def generate_db(request, generator_template_id: int):
management.call_command('makemigrations','generator')
management.call_command('migrate','generator')
return FuResponse(msg='数据库生成成功')

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/6/28 16:58
# file: __init__.py.py
# author: 臧成龙
# QQ: 939589097

View File

@@ -0,0 +1,79 @@
# from application.ninja_cof import api
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/15 21:47
# @File : category_dict.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import CategoryDict
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
from utils.fu_response import FuResponse
from utils.list_to_tree import list_to_tree
router = Router()
class Filters(FuFilters):
label: str = Field(None, alias="label")
value: str = Field(None, alias="value")
code: str = Field(None, alias="code")
class SchemaIn(ModelSchema):
parent_id: int = None
class Config:
model = CategoryDict
model_exclude = ['id', 'parent', 'create_datetime', 'update_datetime']
class SchemaOut(ModelSchema):
class Config:
model = CategoryDict
model_fields = "__all__"
# model_fields = []
@router.post("/category_dict", response=SchemaOut)
def create_category_dict(request, data: SchemaIn):
category_dict = create(request, data, CategoryDict)
return category_dict
@router.delete("/category_dict/{category_dict_id}")
def delete_category_dict(request, category_dict_id: int):
delete(category_dict_id, CategoryDict)
return {"success": True}
@router.put("/category_dict/{category_dict_id}", response=SchemaOut)
def update_category_dict(request, category_dict_id: int, data: SchemaIn):
category_dict = update(request, category_dict_id, data, CategoryDict)
return category_dict
@router.get("/category_dict", response=List[SchemaOut])
@paginate(MyPagination)
def list_category_dict(request, filters: Filters = Query(...)):
qs = retrieve(request, CategoryDict, filters)
return qs
@router.get("/category_dict/{category_dict_id}", response=SchemaOut)
def get_category_dict(request, category_dict_id: int):
category_dict = get_object_or_404(CategoryDict, id=category_dict_id)
return category_dict
@router.get("/category_dict/list/tree")
def list_category_dict_tree(request, filters: Filters = Query(...)):
qs = retrieve(request, CategoryDict, filters).values()
category_dict_tree = list_to_tree(list(qs))
return FuResponse(data=category_dict_tree)

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/07 00:56
# @Author : 臧成龙
# @FileName: dict.py
# @Software: PyCharm
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import Dict
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
status: bool = Field(None, alias="status")
id: str = Field(None, alias="dict_id")
class SchemaIn(ModelSchema):
class Config:
model = Dict
model_fields = ['name', 'code', 'sort', 'status']
class SchemaOut(ModelSchema):
class Config:
model = Dict
model_fields = ['id', 'name', 'code', 'sort', 'status']
@router.post("/dict", response=SchemaOut)
def create_dict(request, data: SchemaIn):
qs = create(request, data, Dict)
return qs
@router.delete("/dict/{dict_id}")
def delete_dict(request, dict_id: int):
delete(dict_id, Dict)
return {"success": True}
@router.put("/dict/{dict_id}", response=SchemaOut)
def update_dict(request, dict_id: int, data: SchemaIn):
qs = update(request, dict_id, data, Dict)
return qs
@router.get("/dict", response=List[SchemaOut])
@paginate(MyPagination)
def list_dict(request, filters: Filters = Query(...)):
qs = retrieve(request, Dict, filters)
return qs
@router.get("/dict/{dict_id}", response=SchemaOut)
def get_dict(request, dict_id: int):
qs = get_object_or_404(Dict, id=dict_id)
return qs
@router.get("/dict/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, Dict)
return qs

View File

@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/07 00:56
# @Author : 臧成龙
# @FileName: dict_item.py
# @Software: PyCharm
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import Dict, DictItem
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
label: str = Field(None, alias="label")
value: str = Field(None, alias="value")
dict_id: str = Field(None, alias="dict_id")
code: str = Field(None, alias="code")
status: bool = Field(None, alias="status")
class SchemaIn(ModelSchema):
dict_id: int = Field(None, alias="dict_id")
class Config:
model = DictItem
model_fields = ['label', 'value', 'sort', 'icon', 'status']
class SchemaOut(ModelSchema):
class Config:
model = DictItem
model_fields = ['id', 'label', 'value', 'sort', 'icon', 'status']
@router.post("/dict_item", response=SchemaOut)
def create_dict_item(request, data: SchemaIn):
qs = create(request, data, DictItem)
return qs
@router.delete("/dict_item/{dict_item_id}")
def delete_dict_item(request, dict_item_id: int):
delete(dict_item_id, DictItem)
return {"success": True}
@router.put("/dict_item/{dict_item_id}", response=SchemaOut)
def update_dict_item(request, dict_item_id: int, data: SchemaIn):
qs = update(request, dict_item_id, data, DictItem)
return qs
@router.get("/dict_item", response=List[SchemaOut])
@paginate(MyPagination)
def list_dict_item(request, filters: Filters = Query(...)):
qs = retrieve(request, DictItem, filters)
return qs
@router.get("/dict_item/{dict_item_id}", response=SchemaOut)
def get_dict_item(request, dict_item_id: int):
qs = get_object_or_404(DictItem, id=dict_item_id)
return qs
@router.get("/dict_item/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, DictItem)
return qs
@router.get("/dict_item/by/code", response=List[SchemaOut])
def list_dict_item_by_code(request, filters: Filters = Query(...)):
filters.status = True
dict_qs = retrieve(request, Dict, filters).first()
if dict_qs is None:
return []
else:
item_qs = dict_qs.dictItem.filter(status=True)
return item_qs

79
code/system/apis/dept.py Normal file
View File

@@ -0,0 +1,79 @@
# from application.ninja_cof import api
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/15 21:47
# @File : dept.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import Dept
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
from utils.fu_response import FuResponse
from utils.list_to_tree import list_to_route, list_to_tree
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
status: bool = Field(None, alias="status")
id: str = Field(None, alias="id")
class SchemaIn(ModelSchema):
parent_id: int = None
class Config:
model = Dept
model_exclude = ['id', 'parent', 'create_datetime', 'update_datetime']
class SchemaOut(ModelSchema):
class Config:
model = Dept
model_fields = "__all__"
# model_fields = []
@router.post("/dept", response=SchemaOut)
def create_dept(request, data: SchemaIn):
dept = create(request, data, Dept)
return dept
@router.delete("/dept/{dept_id}")
def delete_dept(request, dept_id: int):
delete(dept_id, Dept)
return {"success": True}
@router.put("/dept/{dept_id}", response=SchemaOut)
def update_dept(request, dept_id: int, data: SchemaIn):
dept = update(request, dept_id, data, Dept)
return dept
@router.get("/dept", response=List[SchemaOut])
@paginate(MyPagination)
def list_dept(request, filters: Filters = Query(...)):
qs = retrieve(request, Dept, filters)
return qs
@router.get("/dept/{dept_id}", response=SchemaOut)
def get_dept(request, dept_id: int):
dept = get_object_or_404(Dept, id=dept_id)
return dept
@router.get("/dept/list/tree")
def list_dept_tree(request, filters: Filters = Query(...)):
qs = retrieve(request, Dept, filters).values()
dept_tree = list_to_tree(list(qs))
return FuResponse(data=dept_tree)

99
code/system/apis/file.py Normal file
View File

@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/07 00:56
# @Author : 臧成龙
# @FileName: file.py
# @Software: PyCharm
import os
from datetime import datetime
from typing import List
from urllib.parse import unquote
from django.http import FileResponse, StreamingHttpResponse, HttpResponse
from django.shortcuts import get_object_or_404
from fuadmin.settings import BASE_DIR, STATIC_URL
from ninja import Field
from ninja import File as NinjaFile
from ninja import ModelSchema, Query, Router, Schema
from ninja.files import UploadedFile
from ninja.pagination import paginate
from system.models import File
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
class SchemaIn(Schema):
name: str = Field(None, alias="name")
url: str = Field(None, alias="url")
class SchemaOut(ModelSchema):
class Config:
model = File
model_fields = "__all__"
@router.delete("/file/{file_id}")
def delete_file(request, file_id: int):
delete(file_id, File)
return {"success": True}
@router.get("/file", response=List[SchemaOut])
@paginate(MyPagination)
def list_file(request, filters: Filters = Query(...)):
qs = retrieve(request, File, filters)
return qs
@router.get("/file/{file_id}", response=SchemaOut)
def get_file(request, file_id: int):
qs = get_object_or_404(File, id=file_id)
return qs
@router.get("/file/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, File)
return qs
@router.post("/upload", response=SchemaOut)
def upload(request, file: UploadedFile = NinjaFile(...)):
binary_data = file.read()
current_date = datetime.now().strftime('%Y%m%d%H%M%S%f')
current_ymd = datetime.now().strftime('%Y%m%d')
file_name = current_date + '_' + file.name.replace(' ', '_')
file_path = os.path.join(STATIC_URL, current_ymd)
if not os.path.exists(file_path):
os.makedirs(file_path)
file_url = os.path.join(file_path, file_name)
with open(file_url, 'wb') as f:
f.write(binary_data)
data = {
'name': file.name,
'size': file.size,
'save_name': file_name,
'url': file_url,
}
qs = create(request, data, File)
return qs
@router.post("/download")
def create_post(request, data: SchemaIn):
filePath = str(BASE_DIR) + unquote(data.url)
r = FileResponse(open(filePath, "rb"), as_attachment=True)
return r
@router.get("/image/{image_id}", auth=None)
def get_file(request, image_id: int):
qs = get_object_or_404(File, id=image_id)
return HttpResponse(open(os.path.join(str(BASE_DIR), str(qs.url)), "rb"), content_type='image/png')

172
code/system/apis/home.py Normal file
View File

@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/14 15:05
# @Author : 臧成龙
# @FileName: login.py
# @Software: PyCharm
import json
from datetime import datetime
# from django.core.cache import cache
from django.contrib import auth
from django.forms import model_to_dict
from django.shortcuts import get_object_or_404
from django.db.models.expressions import RawSQL
from ninja import Router, ModelSchema, Query, Schema, Field
from fuadmin.settings import RADIUS_NM, OWN_SHIP_MMSI, OWN_SHIP_NAME, OWN_SHIP_TYPE
from system.models import VDESData, ShipLatestStatus, GpsStatus
from utils.fu_jwt import FuJwt
from utils.fu_response import FuResponse
from utils.request_util import save_login_log
from utils.vdes_curd import safe_bulk_upsert, get_nearby_ships
from random import uniform, randint
from datetime import datetime, timedelta
router = Router()
class SchemaOut(ModelSchema):
class Config:
model = VDESData
model_exclude = ['id'] #排除字段
# @router.get("/home", response=SchemaOut)
@router.get("/home")
def get_vdesinfo(request):
# 初始化4-5艘船的固定MMSI
ship_mmsis = ['413323670', '413323671', '413323672', '413323673', '413323674', '423324323', '423343245']
ship_data = []
# 检查是否是第一次调用,如果是则初始化位置
if not hasattr(get_vdesinfo, 'ship_positions'):
# 初始位置在(31.0320, 122.1240)周围30公里范围内
base_lat = 31.0320
base_lng = 122.2240
delta = 0.18 # 约20公里
get_vdesinfo.ship_positions = []
for i, mmsi in enumerate(ship_mmsis):
# 初始位置随机分布在基准点周围
initial_lat = round(base_lat + uniform(-delta, delta), 6)
initial_lng = round(base_lng + uniform(-delta, delta), 6)
initial_course = randint(0, 359)
#固定本船初始位置
if mmsi == '413323670':
initial_lat = 30.9918
initial_lng = 122.2788
get_vdesinfo.ship_positions.append({
'mmsi': mmsi,
'lat': initial_lat,
'lng': initial_lng,
'course': initial_course,
'speed': round(uniform(5, 15), 1) # 初始速度5-15节
})
# 更新每艘船的位置
for ship in get_vdesinfo.ship_positions:
# 微调位置和航向
ship['lat'] += (uniform(0, 1) + 0.3) * 0.001
ship['lng'] += (uniform(0, 1) + 0.3) * 0.001
ship['course'] = (ship['course'] + uniform(-5, 5)) % 360
# 创建模拟数据
mock_data = {
'mmsi': ship['mmsi'],
'latitude': round(ship['lat'], 6),
'longitude': round(ship['lng'], 6),
'speed': ship['speed'],
'course': ship['course'],
'heading': ship['course'],
'nav_status': 0,
'timestamp': datetime.now(),
'ship_name': f'船舶_{ship["mmsi"][-4:]}',
'ship_type': randint(1, 99),
'draught': round(uniform(5.0, 10.0), 1),
'destination': '上海港',
'eta': datetime.now() + timedelta(hours=randint(1, 24)),
'is_ownship': ship['mmsi'] == '413323670' # 新增字段标识本船
}
ship_data.append(mock_data)
# print(ship_data)
# 批量创建并返回所有船的数据
# VDESData.objects.bulk_create(ship_data)
return ship_data # 返回所有船舶数据
def create_vdes_data_batch(count):
print('开始模拟')
data_list = []
for _ in range(count):
mock_data = {
'mmsi': str(randint(336295939, 336295969)),
'latitude': round(uniform(30.5, 31.0), 6),
'longitude': round(uniform(122.0, 123.0), 6),
'speed': round(uniform(0, 30), 1),
'course': randint(0, 359),
'heading': randint(0, 359),
'nav_status': randint(0, 8),
'timestamp': datetime.now(),
'ship_name': f'船舶_{randint(1000, 9999)}',
'ship_type': randint(1, 99),
'draught': round(uniform(1.0, 15.0), 1),
'destination': f'港口_{randint(1, 100)}',
'eta': datetime.now() + timedelta(days=randint(1, 7)),
}
data_list.append(mock_data)
return data_list
# 批量创建
ShipLatestStatus.objects.bulk_create(data_list)
print('模拟结束')
@router.get("/nearby")
# def nearby_ships(request, lat: float, lon: float, radius_nm: float = 20.0):
def nearby_ships(request):
# ship_data_list = create_vdes_data_batch(5)
# created, updated = safe_bulk_upsert(ship_data_list)
# print(f"操作结果: 新增 {created} 条, 更新 {updated} 条")
#查询最新经纬度数据
latest_gps = GpsStatus.objects.order_by('-utc_time').first()
lat = latest_gps.latitude
lon = latest_gps.longitude
radius_nm = RADIUS_NM
ships = list(get_nearby_ships(lat, lon, radius_nm))
ownship = ShipLatestStatus(
mmsi=OWN_SHIP_MMSI,
latitude=latest_gps.latitude,
longitude=latest_gps.longitude,
speed=latest_gps.speed,
course=latest_gps.direction,
heading=latest_gps.direction,
nav_status=randint(0, 8),
timestamp=datetime.now(),
ship_name=OWN_SHIP_NAME,
ship_type=OWN_SHIP_TYPE,
draught=round(uniform(1.0, 15.0), 1),
destination='上海港',
eta=datetime.now() + timedelta(days=randint(1, 7))
)
ships.append(ownship)
result = []
for ship in ships:
result.append({
'mmsi': ship.mmsi,
'latitude': ship.latitude,
'longitude': ship.longitude,
'speed': ship.speed,
'course': ship.course,
'heading': ship.heading,
'nav_status': 0,
'timestamp': datetime.now(),
'ship_name': ship.ship_name,
'ship_type': ship.ship_type,
'draught': ship.draught,
'destination': ship.destination,
'eta': ship.eta,
'is_ownship': ship.mmsi == OWN_SHIP_MMSI
})
return result

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/6/28 16:57
# file: __init__.py.py
# author: 臧成龙
# QQ: 939589097

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/14 23:
# @Author : 臧成龙
# @FileName: celery_log.py
# @Software: PyCharm
from typing import List
from django_celery_results.models import TaskResult
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from utils.fu_crud import delete, retrieve
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
periodic_task_name: str = Field(None, alias="periodic_task_name")
class SchemaOut(ModelSchema):
class Config:
model = TaskResult
model_fields = "__all__"
@router.delete("/celery_log/{celery_log_id}")
def delete_celery_log(request, celery_log_id: int):
delete(celery_log_id, TaskResult)
return {"success": True}
@router.get("/celery_log", response=List[SchemaOut])
@paginate(MyPagination)
def list_celery_log(request, filters: Filters = Query(...)):
qs = retrieve(request, TaskResult, filters)
return qs
@router.get("/celery_log/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, TaskResult)
return qs

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/07 00:56
# @Author : 臧成龙
# @FileName: login_log.py
# @Software: PyCharm
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import LoginLog
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
code: str = Field(None, alias="code")
id: str = Field(None, alias="login_log_id")
class SchemaOut(ModelSchema):
class Config:
model = LoginLog
model_fields = "__all__"
@router.delete("/login_log/{login_log_id}")
def delete_login_log(request, login_log_id: int):
delete(login_log_id, LoginLog)
return {"success": True}
@router.get("/login_log", response=List[SchemaOut])
@paginate(MyPagination)
def list_login_log(request, filters: Filters = Query(...)):
qs = retrieve(request, LoginLog, filters)
return qs
@router.get("/login_log/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, LoginLog)
return qs

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/07 00:56
# @Author : 臧成龙
# @FileName: operation_log.py
# @Software: PyCharm
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import OperationLog
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
request_username: str = Field(None, alias="request_username")
id: str = Field(None, alias="operation_log_id")
class SchemaOut(ModelSchema):
class Config:
model = OperationLog
model_fields = "__all__"
@router.delete("/operation_log/{operation_log_id}")
def delete_operation_log(request, operation_log_id: int):
delete(operation_log_id, OperationLog)
return {"success": True}
@router.get("/operation_log", response=List[SchemaOut])
@paginate(MyPagination)
def list_operation_log(request, filters: Filters = Query(...)):
qs = retrieve(request, OperationLog, filters)
return qs
@router.get("/operation_log/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, OperationLog)
return qs

119
code/system/apis/login.py Normal file
View File

@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/14 15:05
# @Author : 臧成龙
# @FileName: login.py
# @Software: PyCharm
import json
from datetime import datetime
# from django.core.cache import cache
from django.contrib import auth
from django.forms import model_to_dict
from django.shortcuts import get_object_or_404
from ninja import Router, ModelSchema, Query, Schema, Field
from fuadmin.settings import SECRET_KEY, TOKEN_LIFETIME
from system.models import Users, Role, MenuButton, MenuColumnField
from utils.fu_jwt import FuJwt
from utils.fu_response import FuResponse
from utils.request_util import save_login_log
from utils.usual import get_user_info_from_token
router = Router()
class SchemaOut(ModelSchema):
homePath: str = Field(None, alias="home_path")
class Config:
model = Users
model_exclude = ['password', 'role', 'post']
class LoginSchema(Schema):
username: str = Field(None, alias="username")
password: str = Field(None, alias="password")
class Out(Schema):
multi_depart: str
sysAllDictItems: str
departs: str
userInfo: SchemaOut
token: str
@router.post("/login", response=Out, auth=None)
def login(request, data: LoginSchema):
user_obj = auth.authenticate(request, **data.dict())
if user_obj:
request.user = user_obj
role = user_obj.role.all().values('id')
post = list(user_obj.post.all().values('id'))
role_list = []
post_list = []
for item in role:
role_list.append(item['id'])
for item in post:
post_list.append(item['id'])
user_obj_dic = model_to_dict(user_obj)
user_obj_dic['post'] = post_list
user_obj_dic['role'] = role_list
del user_obj_dic['password']
del user_obj_dic['avatar']
time_now = int(datetime.now().timestamp())
jwt = FuJwt(SECRET_KEY, user_obj_dic, valid_to=time_now + TOKEN_LIFETIME)
# 将生成的token加入缓存
# cache.set(user_obj.id, jwt.encode())
token = f"bearer {jwt.encode()}"
data = {
"multi_depart": 1,
"sysAllDictItems": "q",
"departs": "e",
'userInfo': user_obj_dic,
'token': token
}
save_login_log(request=request)
return data
else:
return FuResponse(code=500, msg="账号/密码错误")
@router.get("/logout", auth=None)
def get_post(request):
# 删除缓存
user_info = get_user_info_from_token(request)
# cache.delete(user_info['id'])
return FuResponse(msg="注销成功")
@router.get("/userinfo", response=SchemaOut)
def get_userinfo(request):
user_info = get_user_info_from_token(request)
user = get_object_or_404(Users, id=user_info['id'])
return user
@router.get("/permCode")
def route_menu_tree(request):
"""用于前端获取当前用户的按钮权限"""
token_user = get_user_info_from_token(request)
user = Users.objects.get(id=token_user['id'])
if not token_user['is_superuser']:
menu_button_ids = user.role.values_list('permission__id', flat=True)
menu_column_ids = user.role.values_list('column__id', flat=True)
# queryset = MenuButton.objects.filter(id__in=menuIds, status=1).values()
queryset_button = MenuButton.objects.filter(id__in=menu_button_ids)
queryset_column = MenuColumnField.objects.filter(id__in=menu_column_ids)
else:
queryset_button = MenuButton.objects.all()
queryset_column = MenuColumnField.objects.all()
queryset = [*queryset_button, *queryset_column]
code_list = []
for item in queryset:
code_list.append(item.code)
return FuResponse(data=code_list)

91
code/system/apis/menu.py Normal file
View File

@@ -0,0 +1,91 @@
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/15 21:47
# @File : menu.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.shortcuts import get_object_or_404
from fuadmin.settings import SECRET_KEY
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import Menu, MenuButton, Users
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_jwt import FuJwt
from utils.fu_ninja import FuFilters, MyPagination
from utils.fu_response import FuResponse
from utils.list_to_tree import list_to_route, list_to_tree
router = Router()
class Filters(FuFilters):
title: str = Field(None, alias="title")
status: bool = Field(None, alias="status")
id: str = Field(None, alias="id")
class SchemaIn(ModelSchema):
parent_id: int = None
component: str = 'LAYOUT'
class Config:
model = Menu
model_exclude = ['id', 'parent', 'create_datetime', 'update_datetime']
class SchemaOut(ModelSchema):
class Config:
model = Menu
model_fields = "__all__"
# model_fields = []
@router.post("/menu", response=SchemaOut)
def create_menu(request, data: SchemaIn):
menu = create(request, data, Menu)
return menu
@router.delete("/menu/{menu_id}")
def delete_menu(request, menu_id: int):
delete(menu_id, Menu)
return {"success": True}
@router.put("/menu/{menu_id}", response=SchemaOut)
def update_menu(request, menu_id: int, data: SchemaIn):
post = update(request, menu_id, data, Menu)
return post
@router.get("/menu", response=List[SchemaOut])
def list_menu_tree(request, filters: Filters = Query(...)):
qs = retrieve(request, Menu, filters).values()
# 将查询集转换成树形结构
menu_tree = list_to_tree(list(qs))
return FuResponse(data=menu_tree)
@router.get("/menu/{menu_id}", response=SchemaOut)
def get_menu(request, menu_id: int):
post = get_object_or_404(Menu, id=menu_id)
return post
@router.get("/menu/route/tree")
def route_menu_tree(request):
"""用于前端获取当前角色的路由"""
token = request.META.get("HTTP_AUTHORIZATION")
token = token.split(" ")[1]
token_user = FuJwt(SECRET_KEY).decode(SECRET_KEY, token).payload
user = Users.objects.get(id=token_user['id'])
queryset = Menu.objects.filter(status=1).values()
if not token_user['is_superuser']:
menuIds = user.role.values_list('menu__id', flat=True)
queryset = Menu.objects.filter(id__in=menuIds, status=1).values()
menu_tree = list_to_route(list(queryset))
return FuResponse(data=menu_tree)

View File

@@ -0,0 +1,69 @@
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/21 21:26
# @File : menu_menu_button.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import MenuButton
from utils.fu_crud import create, delete, retrieve, update
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
code: str = Field(None, alias="code")
menu_id: str = Field(None, alias="menu_id")
class SchemaIn(ModelSchema):
menu_id: int = None
class Config:
model = MenuButton
model_exclude = ['id', 'menu', 'create_datetime', 'update_datetime']
class SchemaOut(ModelSchema):
class Config:
model = MenuButton
model_fields = "__all__"
# model_fields = []
@router.post("/menu_button", response=SchemaOut)
def create_menu_button(request, data: SchemaIn):
menu_button = create(request, data, MenuButton)
return menu_button
@router.delete("/menu_button/{menu_button_id}")
def delete_menu_button(request, menu_button_id: int):
delete(menu_button_id, MenuButton)
return {"success": True}
@router.put("/menu_button/{menu_button_id}", response=SchemaOut)
def update_menu_button(request, menu_button_id: int, data: SchemaIn):
menu_button = update(request, menu_button_id, data, MenuButton)
return menu_button
@router.get("/menu_button", response=List[SchemaOut])
@paginate(MyPagination)
def list_menu_button(request, filters: Filters = Query(...)):
qs = retrieve(request, MenuButton, filters)
return qs
@router.get("/menu_button/{menu_button_id}", response=SchemaOut)
def get_menu_button(request, menu_button_id: int):
post = get_object_or_404(MenuButton, id=menu_button_id)
return post

View File

@@ -0,0 +1,80 @@
# Author 臧成龙
# coding=utf-8
# @Time : 2023/3/27 23:26
# @File : menu_menu_column_field.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import MenuColumnField
from utils.fu_crud import create, delete, retrieve, update, batch_create
from utils.fu_ninja import FuFilters, MyPagination
from utils.fu_response import FuResponse
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
code: str = Field(None, alias="code")
menu_id: str = Field(None, alias="menu_id")
class SchemaIn(ModelSchema):
menu_id: str = Field(None, alias="menu_id")
class Config:
model = MenuColumnField
model_exclude = ['id', 'menu', 'create_datetime', 'update_datetime']
class BatchSchemaIn(Schema):
batch_info: list = Field(None, alias="batch_info")
class SchemaOut(ModelSchema):
class Config:
model = MenuColumnField
model_fields = "__all__"
# model_fields = []
@router.post("/menu_column_field", response=SchemaOut)
def create_menu_column_field(request, data: SchemaIn):
menu_column_field = create(request, data, MenuColumnField)
return menu_column_field
@router.post("/menu_column_field/batch/create")
def batch_create_menu_column_field(request, data: BatchSchemaIn):
batch_create(request, data.batch_info, MenuColumnField)
return FuResponse(msg="导入成功")
@router.delete("/menu_column_field/{menu_column_field_id}")
def delete_menu_column_field(request, menu_column_field_id: int):
delete(menu_column_field_id, MenuColumnField)
return {"success": True}
@router.put("/menu_column_field/{menu_column_field_id}", response=SchemaOut)
def update_menu_column_field(request, menu_column_field_id: int, data: SchemaIn):
menu_column_field = update(request, menu_column_field_id, data, MenuColumnField)
return menu_column_field
@router.get("/menu_column_field", response=List[SchemaOut])
@paginate(MyPagination)
def list_menu_column_field(request, filters: Filters = Query(...)):
qs = retrieve(request, MenuColumnField, filters)
return qs
@router.get("/menu_column_field/{menu_column_field_id}", response=SchemaOut)
def get_menu_column_field(request, menu_column_field_id: int):
post = get_object_or_404(MenuColumnField, id=menu_column_field_id)
return post

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/7/26 14:12
# file: monitor.py
# author: 臧成龙
# QQ: 939589097
from ninja import Router
from django.core.cache import cache
from utils.fu_response import FuResponse
from utils.system import system
router = Router()
@router.get("/monitor")
def list_role(request):
data = cache.get('local_inspection_data')
qs = system().GetSystemAllInfo()
if data and isinstance(data, dict):
qs['local_inspection_data'] = {
'cpu0_usage': data.get('cpu0_usage'),
'cpu1_usage': data.get('cpu1_usage')
}
else:
# 如果data为空或不是字典类型设置默认值
qs['local_inspection_data'] = {
'cpu0_usage': None,
'cpu1_usage': None
}
# print("从系统获取监控数据:", qs)
return FuResponse(data=qs)
@router.get("/get_sbb")
def list_role(request):
sbb_data = {}
# 循环获取sbb1-sbb6的缓存数据
for i in range(1, 7):
key = f'sbb{i}'
value = cache.get(key)
print(f"sbb{i}缓存数据:", value)
sbb_data[key] = value
print("从缓存获取sbb数据", sbb_data)
# 模拟的sbb数据字典包含sbb1-sbb6这六个字段
# sample_sbb_data = {
# "sbb1": {'type': 1, 'satellite_id': 2, 'primary_network_id': 0, 'roaming_network_id': 0, 'sbb_version': 152, 'start_time': 181303439, 'validity_period': 1440, 'itu_compatibility': 0, 'reserved_bits': 0, 'sbb_spare_frequency': 1226, 'uplink_max_length': 26, 'reserved': 0, 'total_message_length': 20},
# "sbb2": {'type': 2, 'downlink_frequency_a': 2226, 'uplink_frequency_a': 1226, 'downlink_bandwidth_a': 1, 'uplink_bandwidth_a': 1, 'channel_slot_lengths_a': [6, 6, 2, 6, 6, 6, 6, 2, 2, 12, 0, 0], 'channel_functions_a': [0, 1, 4, 4, 4, 4, 4, 4, 2, 3, 5, 5], 'downlink_frequency_b': 1226, 'uplink_frequency_b': 2226, 'downlink_bandwidth_b': 1, 'uplink_bandwidth_b': 1, 'channel_slot_lengths_b': [6, 6, 2, 6, 6, 6, 6, 2, 2, 12, 0, 0], 'channel_functions_b': [0, 1, 4, 4, 4, 4, 4, 4, 2, 3, 5, 5]},
# "sbb3": {'type': 3, 'downlink_frequency_c': 2284, 'uplink_frequency_c': 1225, 'downlink_bandwidth_c': 2, 'uplink_bandwidth_c': 1, 'channel_slot_lengths_c': [6, 6, 2, 6, 6, 6, 6, 2, 2, 12, 0, 0], 'channel_functions_c': [5, 5, 4, 4, 4, 4, 4, 4, 2, 3, 5, 5], 'downlink_frequency_d': 1284, 'uplink_frequency_d': 2225, 'downlink_bandwidth_d': 2, 'uplink_bandwidth_d': 1, 'channel_slot_lengths_d': [6, 6, 2, 6, 6, 6, 6, 2, 2, 12, 0, 0], 'channel_functions_d': [5, 5, 4, 4, 4, 4, 4, 4, 2, 3, 5, 5]},
# "sbb4": {'type': 4, 'downlink_frequency_e': 2225, 'uplink_frequency_e': 1224, 'downlink_bandwidth_e': 3, 'uplink_bandwidth_e': 1, 'channel_slot_lengths_e': [6, 6, 2, 6, 6, 6, 6, 2, 2, 12, 0, 0], 'channel_functions_e': [5, 5, 4, 4, 4, 4, 4, 4, 2, 3, 5, 5], 'downlink_frequency_f': 1225, 'uplink_frequency_f': 2224, 'downlink_bandwidth_f': 3, 'uplink_bandwidth_f': 1, 'channel_slot_lengths_f': [6, 6, 2, 6, 6, 6, 6, 2, 2, 12, 0, 0], 'channel_functions_f': [5, 5, 4, 4, 4, 4, 4, 4, 2, 3, 5, 5]},
# "sbb5": {'type': 5, 'digital_signature_part1': '00000000400050033a529c960c39c0a44afbcc752414aa14b540233ada55b61d'},
# "sbb6": {'type': 5, 'digital_signature_part1': '00000000400050033a529c960c39c0a44afbcc752414aa14b540233ada55b61d'}
# }
return sbb_data

95
code/system/apis/post.py Normal file
View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/10 21:56
# @Author : 臧成龙
# @FileName: post.py
# @Software: PyCharm
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema, UploadedFile
from ninja.pagination import paginate
from system.models import Post
from utils.fu_crud import (
ImportSchema,
create,
delete,
export_data,
import_data,
retrieve,
update,
)
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
code: str = Field(None, alias="code")
status: int = Field(None, alias="status")
id: str = Field(None, alias="post_id")
class PostSchemaIn(ModelSchema):
class Config:
model = Post
model_fields = ['name', 'code', 'sort', 'status']
class PostSchemaOut(ModelSchema):
creator: str = Field(None, alias="creator.username")
class Config:
model = Post
model_fields = "__all__"
@router.post("/post", response=PostSchemaOut)
def create_post(request, data: PostSchemaIn):
post = create(request, data, Post)
return post
@router.delete("/post/{post_id}")
def delete_post(request, post_id: int):
delete(post_id, Post)
return {"success": True}
@router.put("/post/{post_id}", response=PostSchemaOut)
def update_post(request, post_id: int, data: PostSchemaIn):
post = update(request, post_id, data, Post)
return post
@router.get("/post", response=List[PostSchemaOut])
@paginate(MyPagination)
def list_post(request, filters: Filters = Query(...)):
qs = retrieve(request, Post, filters)
return qs
@router.get("/post/{post_id}", response=PostSchemaOut)
def get_post(request, post_id: int):
post = get_object_or_404(Post, id=post_id)
return post
@router.get("/post/all/list", response=List[PostSchemaOut])
def all_list_post(request):
qs = retrieve(request, Post)
return qs
@router.get("/post/all/export")
def export_post(request):
export_fields = ['name', 'code', 'status', 'sort', 'creator']
return export_data(request, Post, PostSchemaOut, export_fields)
@router.post("/post/all/import")
def import_post(request, data: ImportSchema):
import_fields = ['name', 'code', 'status', 'sort']
return import_data(request, Post, PostSchemaIn, data, import_fields)

191
code/system/apis/role.py Normal file
View File

@@ -0,0 +1,191 @@
# from application.ninja_cof import api
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/19 21:36
# @File : role.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import Menu, Role, MenuButton
from utils.fu_crud import create, delete, retrieve
from utils.fu_ninja import FuFilters, MyPagination
from utils.fu_response import FuResponse
from utils.list_to_tree import list_to_tree
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
status: bool = Field(None, alias="status")
id: str = Field(None, alias="id")
class SchemaIn(ModelSchema):
menu: list = []
permission: list = []
dept: list = []
column: list = []
class Config:
model = Role
model_exclude = ['id', 'dept', 'menu', 'permission', 'column', 'create_datetime', 'update_datetime']
class SchemaOut(ModelSchema):
class Config:
model = Role
model_fields = "__all__"
# model_fields = []
@router.post("/role", response=SchemaOut)
def create_role(request, data: SchemaIn):
dict_data = data.dict()
del dict_data['menu']
del dict_data['permission']
del dict_data['dept']
del dict_data['column']
role = create(request, dict_data, Role)
return role
@router.delete("/role/{role_id}")
def delete_role(request, role_id: int):
delete(role_id, Role)
return {"success": True}
@router.put("/role/{role_id}", response=SchemaOut)
def update_role(request, role_id: int, payload: SchemaIn):
post = get_object_or_404(Role, id=role_id)
for attr, value in payload.dict().items():
if attr == 'menu':
post.menu.set(value)
elif attr == 'permission':
post.permission.set(value)
elif attr == 'dept':
post.dept.set(value)
elif attr == 'column':
post.column.set(value)
else:
setattr(post, attr, value)
post.save()
return post
@router.get("/role", response=List[SchemaOut])
@paginate(MyPagination)
def list_role(request, filters: Filters = Query(...)):
qs = retrieve(request, Role, filters)
return qs
@router.get("/role/all/list", response=List[SchemaOut])
def all_list_role(request):
qs = retrieve(request, Role)
return qs
@router.get("/role/{role_id}", response=SchemaOut)
def get_role(request, role_id: int):
role = get_object_or_404(Role, id=role_id)
return role
class ButtonColumnFilters(FuFilters):
menu_ids: list = Field(None, alias="menu_ids")
@router.get("/role/list/menu_button")
def list_menu_button_tree(request, filters: ButtonColumnFilters = Query(...)):
qs = Menu.objects.filter()
result = []
menu_button_list = []
for item in qs:
dict_item = item.__dict__
menu_button = list(item.menuPermission.all().values())
for button_item in menu_button:
button_item['id'] = f"b{button_item['id']}"
button_item['parent_id'] = button_item.pop('menu_id')
button_item['title'] = button_item.pop('name')
# dict_item['menu_button'] = list(item.menuPermission.all().values())
del dict_item['_state']
result.extend(menu_button)
result.append(dict_item)
return get_button_or_column_menu(result, 'b')
@router.get("/role/list/menu_column")
def list_menu_button_tree(request, filters: ButtonColumnFilters = Query(...)):
qs = Menu.objects.all()
result = []
for item in qs:
dict_item = item.__dict__
column_field = list(item.menuColumnField.all().values())
for column_field_item in column_field:
column_field_item['id'] = f"c{column_field_item['id']}"
column_field_item['parent_id'] = column_field_item.pop('menu_id')
column_field_item['title'] = column_field_item.pop('name')
del dict_item['_state']
result.append(dict_item)
result.extend(column_field)
return get_button_or_column_menu(result, 'c')
class SchemaMenuOut(ModelSchema):
class Config:
model = Menu
model_fields = "__all__"
@router.get("/role/list/menu", response=List[SchemaMenuOut])
def list_menu_tree(request, filters: Filters = Query(...)):
qs = retrieve(request, Menu, filters).values()
# 将查询集转换成树形结构
menu_tree = list_to_tree(list(qs))
return FuResponse(data=menu_tree)
# class SchemaButtonOut(ModelSchema):
# class Config:
# model = MenuButton
# model_fields = "__all__"
#
#
# @router.get("/role/list/button/{menu_id}", response=List[SchemaButtonOut])
# def get_button_by_menu_id(request, menu_id: int):
# filters: Filters
# qs = MenuButton.objects.filter(menu_id=menu_id)
#
# return qs
def get_button_or_column_menu(data, flag):
return_data = []
for i in data:
m_id = i['id']
if flag in str(m_id):
return_data.append(i)
get_menu_by_parent(i['parent_id'], data, return_data)
return return_data
def get_menu_by_parent(parent_id, data, return_data):
for i in data:
if parent_id == i['id'] and i not in return_data:
return_data.append(i)
get_menu_by_parent(i['parent_id'], data, return_data)
if parent_id is None:
return

125
code/system/apis/user.py Normal file
View File

@@ -0,0 +1,125 @@
# from application.ninja_cof import api
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/29 01:36
# @File : user.py
# @Software: PyCharm
# @qq: 939589097
from typing import List
from django.contrib.auth.hashers import make_password
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, Query, Router, Schema
from ninja.pagination import paginate
from system.models import Users
from utils.fu_crud import create, delete, retrieve
from utils.fu_ninja import FuFilters, MyPagination
from utils.fu_response import FuResponse
from utils.usual import get_user_info_from_token
router = Router()
class Filters(FuFilters):
name: str = Field(None, alias="name")
mobile: str = Field(None, alias="mobile")
status: bool = Field(None, alias="status")
dept_id__in: list = Field(None, alias="dept_ids[]")
id: int = Field(None, alias="id")
class SchemaIn(ModelSchema):
dept_id: int = Field(None, alias="dept")
post: list = []
role: list = []
class Config:
model = Users
model_exclude = ['id', 'groups', 'user_permissions', 'is_superuser', 'dept', 'post', 'role', 'password',
'create_datetime', 'update_datetime']
class SchemaOut(ModelSchema):
class Config:
model = Users
model_exclude = ['password']
@router.post("/user", response=SchemaOut)
def create_user(request, data: SchemaIn):
data_dic = data.dict()
data_dic['password'] = make_password('123456')
post = data_dic.pop('post')
role = data_dic.pop('role')
user = create(request, data_dic, Users)
# 为多对多关系添加数据
user.post.set(post)
user.role.set(role)
return user
@router.delete("/user/{user_id}")
def delete_user(request, user_id: int):
delete(user_id, Users)
return {"success": True}
@router.put("/user/{user_id}", response=SchemaOut)
def update_user(request, user_id: int, payload: SchemaIn):
user = get_object_or_404(Users, id=user_id)
for attr, value in payload.dict().items():
if attr == 'role':
user.role.set(value)
elif attr == 'post':
user.post.set(value)
else:
setattr(user, attr, value)
user.save()
return user
@router.get("/user", response=List[SchemaOut])
@paginate(MyPagination)
def list_user(request, filters: Filters = Query(...)):
qs = retrieve(request, Users, filters)
return qs
@router.get("/user/all/list", response=List[SchemaOut])
def all_list_user(request):
qs = retrieve(request, Users)
return qs
@router.get("/user/{user_id}", response=SchemaOut)
def get_user(request, user_id: int):
user = get_object_or_404(Users, id=user_id)
return user
class SchemaIn(Schema):
id: int
password: str
@router.post("/user/set/repassword", response=SchemaOut)
def repassword(request, data: SchemaIn):
request_user = get_user_info_from_token(request)
request_user_id = request_user['id']
update_id = data.id
if request_user_id == update_id:
user = get_object_or_404(Users, id=update_id)
user.set_password(data.password)
user.save()
return FuResponse(msg='密码修改成功')
else:
return FuResponse(code=403, msg='对不起, 只能修改自己的密码')
@router.put("/user/reset/password/{id}", response=SchemaOut)
def reset_password(request, id:int):
user = get_object_or_404(Users, id=id)
user.set_password('123456')
user.save()
return FuResponse(msg='密码重置成功')

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/9 23:26
# @Author : 臧成龙
# @FileName: __init__.py.py
# @Software: PyCharm

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/9 23:26
# @Author : 臧成龙
# @FileName: __init__.py.py
# @Software: PyCharm

View File

@@ -0,0 +1,25 @@
from ninja import Router
from ninja import Router, UploadedFile, File
import os
from fuadmin import settings
router = Router()
# @router.get("/uploadFile")
# def upload_file(request, files: list[UploadedFile] = File(...)):
# file_list = []
# upload_dir = os.path.join(settings.BASE_DIR, 'upload')
# os.makedirs(upload_dir, exist_ok=True)
# for file in files:
# new_path = os.path.join(upload_dir, file.name)
# with open(new_path, 'wb+') as destination:
# for chunk in file.chunks():
# destination.write(chunk)
# file_list.append({
# 'name': file.name,
# 'path': new_path,
# 'size': file.size
# })
# return file_list

View File

@@ -0,0 +1,102 @@
from system.models import Contacts, HistorySession
from ninja import Router, Schema, Field
router = Router()
class addContactSchema(Schema):
MMSI: str = Field(None, alias="mmsi")
name: str = Field(None, alias="name")
phone: str = Field(None, alias="phone")
region: str = Field(None, alias="region")
class deleteContactSchema(Schema):
MMSI: str = Field(None, alias="mmsi")
class editContactSchema(Schema):
oldMMSI: str = Field(None, alias="old_mmsi")
newMMSI: str = Field(None, alias="new_mmsi")
name: str = Field(None, alias="name")
phone: str = Field(None, alias="phone")
region: str = Field(None, alias="region")
@router.get('/contactsList')
def get_contacts_list(request):
try:
contacts = Contacts.objects.all().values()
return {
"code": 200,
"message": "成功",
"data": list(contacts)
}
except Exception as e:
return {'code': 500, 'message': str(e)}
@router.post('/deleteContact')
def delete_contact(request, data: deleteContactSchema):
try:
if not data.MMSI:
return {'code': 400, 'message': 'MMSI不能为空'}
contact = Contacts.objects.filter(MMSI=data.MMSI).first()
if not contact:
return {'code': 404, 'message': '未找到对应联系人'}
# 删除关联的历史会话记录
HistorySession.objects.filter(FormId=contact.Id).delete()
contact.delete()
return {'code': 200, 'message': '删除成功'}
except Exception as e:
return {'code': 500, 'message': str(e)}
@router.post('/editContact')
def edit_contact(request, data: editContactSchema):
try:
if not data.oldMMSI:
return {'code': 400, 'message': '原MMSI不能为空'}
contact = Contacts.objects.filter(MMSI=data.oldMMSI).first()
if not contact:
return {'code': 404, 'message': '未找到对应联系人'}
# 更新联系人信息
contact.MMSI = data.newMMSI if data.newMMSI else contact.MMSI
contact.Name = data.name if data.name else contact.Name
contact.Mobile = data.phone if data.phone else contact.Mobile
contact.Region = data.region if data.region else contact.Region
contact.save()
# 更新关联的历史会话记录
if data.newMMSI or data.name:
HistorySession.objects.filter(FormId=contact.Id).update(
MMSI=data.newMMSI if data.newMMSI else contact.MMSI,
Name=data.name if data.name else contact.Name
)
return {
'code': 200,
'message': '修改成功',
'data': {
'id': contact.Id,
'MMSI': contact.MMSI
}
}
except Exception as e:
return {'code': 500, 'message': str(e)}
@router.post('/addContact')
def add_contact(request, data: addContactSchema):
try:
if not data.MMSI:
return {'code': 400, 'message': 'MMSI不能为空'}
name = data.name if data.name else data.MMSI
contact = Contacts.objects.create(
MMSI=data.MMSI,
Name=name,
Mobile=data.phone,
Region=data.region
)
return {
'code': 200,
'message': '成功',
}
except Exception as e:
return {'code': 500, 'message': str(e)}

View File

@@ -0,0 +1,53 @@
from system.models import Content
from ninja import Router, Schema, Field
from datetime import datetime
from vdes_utils.vdes_message_packaged import VdesMessagePackaged
router = Router()
@router.get('/getContent')
def get_content(request):
try:
contacts = Content.objects.all().order_by('Id').values()
return {
"code": 200,
"message": "成功",
"data": list(contacts)
}
except Exception as e:
return {'code': 500, 'message': str(e)}
@router.post('/sendMessage')
def send_message(request):
# try:
# 解析JSON数据
import json
data = json.loads(request.body)
MMSI = data['mmsi']
# 创建并保存Content记录
content = Content.objects.create(
SendId=data['SendId'],
ReciverId=data['ReciverId'],
Content=data['Content'],
Type=data['Type'],
# State=data['State'],
State=1, # 设置为已发送状态
NoCode=data['NoCode'],
CreateDateUtc=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
Title=data['Title'],
Description=data['Description'],
Label=data['Label'],
Thumbnail=data['Thumbnail'],
ReadFlag=data['ReadFlag'],
Avatar=data['Avatar'],
SoundStatus=0,
)
message = data['Content'].replace('<p>', '').replace('</p>', '')
# message = "这是一条长消息测试消息1111111111111111111111111111111111111111111111111111111"
vdes_message_packaged = VdesMessagePackaged(message, MMSI)
vdes_message_packaged.send_message()
return { "code": 200, "message": "消息保存成功" }
# except Exception as e:
# return {'code': 500, 'message': str(e)}

View File

@@ -0,0 +1,50 @@
from system.models import HistorySession
from ninja import Router, Schema, Field
router = Router()
class HistorySessionSchema(Schema):
FormId: str = Field(None, alias="formId")
Name: str = Field(None, alias="name")
MMSI: str = Field(None, alias="mmsi")
Mobile: str = Field(None, alias="mobile")
Region: str = Field(None, alias="region")
ById: str = Field(None, alias="byId")
@router.get('/getHistorySession')
def get_history_session(request):
try:
contacts = HistorySession.objects.all().values()
return {
"code": 200,
"message": "成功",
"data": list(contacts)
}
except Exception as e:
return {'code': 500, 'message': str(e)}
@router.post('/insertHistorySession')
def insert_history_session(request, data: HistorySessionSchema):
try:
existing_record = HistorySession.objects.filter(
FormId=data.FormId,
ById=data.ById
).first()
if not existing_record:
history_session = HistorySession.objects.create(
FormId=data.FormId,
Name=data.Name,
MMSI=data.MMSI,
Mobile=data.Mobile,
Region=data.Region,
ById=data.ById
)
return {
"code": 200,
"message": "成功",
}
except Exception as e:
return {'code': 500, 'message': str(e)}

View File

@@ -0,0 +1,50 @@
# from system.models import HistorySession
# from ninja import Router, Schema, Field
# router = Router()
# class HistorySessionSchema(Schema):
# FormId: str = Field(None, alias="formId")
# Name: str = Field(None, alias="name")
# MMSI: str = Field(None, alias="mmsi")
# Mobile: str = Field(None, alias="mobile")
# Region: str = Field(None, alias="region")
# ById: str = Field(None, alias="byId")
# @router.get('/getHistorySession')
# def get_history_session(request):
# try:
# contacts = HistorySession.objects.all().values()
# return {
# "code": 200,
# "message": "成功",
# "data": list(contacts)
# }
# except Exception as e:
# return {'code': 500, 'message': str(e)}
# @router.post('/insertHistorySession')
# def insert_history_session(request, data: HistorySessionSchema):
# try:
# existing_record = HistorySession.objects.filter(
# FormId=data.FormId,
# ById=data.ById
# ).first()
# if not existing_record:
# history_session = HistorySession.objects.create(
# FormId=data.FormId,
# Name=data.Name,
# MMSI=data.MMSI,
# Mobile=data.Mobile,
# Region=data.Region,
# ById=data.ById
# )
# return {
# "code": 200,
# "message": "成功",
# }
# except Exception as e:
# return {'code': 500, 'message': str(e)}

View File

@@ -0,0 +1,151 @@
import random
import math
from ninja import Router
from django.utils import timezone
from datetime import timedelta
from system.models import GpsStatus, ShipLatestStatus, MessageFlag
from fuadmin.settings import OWN_SHIP_MMSI
router = Router()
def haversine(lat1, lon1, lat2, lon2):
"""计算两点间的大圆距离(公里)"""
R = 6371 # 地球半径(公里)
# 将角度转换为弧度
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
# 差值
dlat = lat2 - lat1
dlon = lon2 - lon1
# Haversine公式
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
return R * c
def calculate_relative_bearing(own_lat, own_lon, own_course, target_lat, target_lon):
"""
计算目标点相对本船航向的方向角相对方位单位°0~360顺时针
"""
d_lon = math.radians(target_lon - own_lon)
lat1 = math.radians(own_lat)
lat2 = math.radians(target_lat)
x = math.sin(d_lon) * math.cos(lat2)
y = math.cos(lat1)*math.sin(lat2) - math.sin(lat1)*math.cos(lat2)*math.cos(d_lon)
initial_bearing = math.atan2(x, y)
initial_bearing_deg = (math.degrees(initial_bearing) + 360) % 360 # 转换为 0~360 度
# 相对航向(目标相对于本船正前的角度)
relative_bearing = (initial_bearing_deg - own_course + 360) % 360
return relative_bearing
@router.get("/header")
def get_header_info(request):
# 固定本船MMSI
ownship_mmsi = OWN_SHIP_MMSI
# current_time = datetime.now()
# current_date = current_time.strftime('%Y-%m-%d')
# current_time_str = current_time.strftime('%H:%M')
# # 检查是否是第一次调用,如果是则初始化位置
# if not hasattr(get_header_info, 'ship_data'):
# # 初始位置 (30.9918, 122.2788)
# get_header_info.ship_data = {
# 'mmsi': ownship_mmsi,
# 'latitude': 30.9918,
# 'longitude': 122.2788,
# 'speed': 12.0,
# 'course': 45,
# 'date': current_date,
# 'time': current_time_str
# }
# else:
# # 更新位置和航向
# get_header_info.ship_data['latitude'] = round(get_header_info.ship_data['latitude'] + 0.001, 4)
# get_header_info.ship_data['longitude'] = round(get_header_info.ship_data['longitude'] + 0.001, 4)
# get_header_info.ship_data['course'] = round((get_header_info.ship_data['course'] + 5) % 360, 1)
# get_header_info.ship_data['date'] = current_date
# get_header_info.ship_data['time'] = current_time_str
# # 将数据保存到GpsStatus模型
# GpsStatus.objects.create(
# utc_time=current_time,
# longitude=get_header_info.ship_data['longitude'],
# latitude=get_header_info.ship_data['latitude'],
# altitude=0.0, # 固定值
# used_satellites=10, # 固定值
# gps_satellites=6, # 固定值
# beidou_satellites=4, # 固定值
# gps_status=1, # 固定值1表示正常
# fix_mode=3, # 固定值3表示3D定位
# hdop=1.2, # 固定值
# speed=get_header_info.ship_data['speed'],
# direction=get_header_info.ship_data['course']
# )
#查询最新经纬度数据
latest_gps = GpsStatus.objects.order_by('-utc_time').first()
# 计算 10 分钟前的时间,基于 GpsStatus 中的时间
ten_minutes_ago = latest_gps.utc_time - timedelta(minutes=10)
# 筛选出 10 分钟内的船只记录
recent_ships = ShipLatestStatus.objects.filter(timestamp__gte=ten_minutes_ago)
closest_ship = None
min_distance_km = float('inf')
for ship in recent_ships:
# 计算本船与当前船只的距离(公里)
distance_km = haversine(latest_gps.latitude, latest_gps.longitude, ship.latitude, ship.longitude)
if distance_km < min_distance_km:
min_distance_km = distance_km
closest_ship = ship
# 判断是否小于 5 海里1 海里 = 1.852 公里)
warning_message = ''
# min_distance_km = random.randint(1, 20)
print(min_distance_km)
if 3 * 1.852 < min_distance_km < 5 * 1.852:
warning_message = '注意5海里范围内有其他船只'
print(warning_message)
elif min_distance_km <= 3 * 1.852:
relative_bearing = calculate_relative_bearing(
latest_gps.latitude,
latest_gps.longitude,
latest_gps.direction, # 本船航向
closest_ship.latitude,
closest_ship.longitude
)
warning_message = f'警告:有船靠近,相对方位 {relative_bearing:.1f}°,相距 {min_distance_km / 1.852:.2f} 海里, 速度{closest_ship.speed}'
else:
warning_message = '保持正常航行'
flag = MessageFlag.objects.first()
has_message = flag.has_new_message
if has_message:
flag.has_new_message = False
flag.save()
return {
'mmsi': ownship_mmsi, # 固定本船MMSI
'latitude': latest_gps.latitude,
'longitude': latest_gps.longitude,
'speed': latest_gps.speed,
'course': latest_gps.direction,
'date': latest_gps.utc_time.strftime('%Y-%m-%d'),
'time': latest_gps.utc_time.strftime('%H:%M'),
"satelliteCount": random.randint(0, 12),
"messageCount": 12,
"reminderCount": random.randint(0, 20),
"alertCount": random.randint(0, 15),
"approachingCount": random.randint(0, 5),
"trackStatus": False,
"gpsSignal": round(random.uniform(0, 5), 1),
"beidouSignal": round(random.uniform(0, 5), 1),
"vdesSignal": round(random.uniform(0, 5), 1),
"warning_message": warning_message,
"is_receive_vdes_data": has_message
}
# return get_header_info.ship_data

32
code/system/apps.py Normal file
View File

@@ -0,0 +1,32 @@
from django.apps import AppConfig
import logging
from .management.commands.vdes_monitor import VDESSocketMonitor
class SystemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'system'
def ready(self):
# 确保只在主进程中启动(避免在开发服务器中启动两次)
if not self._is_runserver_with_reload():
self.start_vdes_monitor()
from .models import MessageFlag
try:
# 确保表创建完成后检查是否存在记录
if not MessageFlag.objects.exists():
MessageFlag.objects.create(has_new_message=False)
print("初始化 MessageFlag: 已创建默认记录")
except:
print("初始化 MessageFlag 失败")
def _is_runserver_with_reload(self):
"""检查是否在开发服务器的重载进程中"""
import sys
return 'runserver' in sys.argv and '--noreload' not in sys.argv
def start_vdes_monitor(self):
"""启动 VDES 监控服务"""
monitor = VDESSocketMonitor()
monitor.start()
logger = logging.getLogger(__name__)
logger.info("VDES 监控服务已启动")

View File

View File

@@ -0,0 +1,100 @@
import json
from system.code_template.backend.mapping import component_to_search_type
from utils.ru_convert import RuleConvert
def generator_backend_api(api_info):
form_info = api_info.form_info
form_info_dict = json.loads(form_info)
filter_txt = ''
field_code = ''
for item in form_info_dict.get('schemas'):
field_code = f''''{item['field']}', {field_code}'''
column = f'''
{item['field']}: {component_to_search_type[item['component']]} = Field(None, alias='{item['field']}')'''
filter_txt = column + filter_txt
api_txt = f'''from typing import List
from .model import {RuleConvert.to_upper_camel_case(api_info.code)}
from ninja import Field, ModelSchema, Query, Router
from ninja.pagination import paginate
from utils.fu_crud import (
ImportSchema,
create,
delete,
export_data,
import_data,
retrieve,
update,
)
from utils.fu_ninja import FuFilters, MyPagination
router = Router()
# 设置过滤字段
class Filters(FuFilters):
{filter_txt}
# 设置请求接收字段
class {RuleConvert.to_upper_camel_case(api_info.code)}SchemaIn(ModelSchema):
class Config:
model = {RuleConvert.to_upper_camel_case(api_info.code)}
model_exclude = ['create_datetime', 'update_datetime', 'belong_dept', 'modifier', 'creator', 'id']
# 设置响应字段
class {RuleConvert.to_upper_camel_case(api_info.code)}SchemaOut(ModelSchema):
class Config:
model = {RuleConvert.to_upper_camel_case(api_info.code)}
model_fields = '__all__'
# 创建{RuleConvert.to_upper_camel_case(api_info.code)}
@router.post('/{api_info.code}', response={RuleConvert.to_upper_camel_case(api_info.code)}SchemaOut)
def create_{api_info.code}(request, data: {RuleConvert.to_upper_camel_case(api_info.code)}SchemaIn):
{api_info.code} = create(request, data, {RuleConvert.to_upper_camel_case(api_info.code)})
return {api_info.code}
# 删除{RuleConvert.to_upper_camel_case(api_info.code)}
@router.delete('/{api_info.code}/{{id}}')
def delete_{api_info.code}(request, id: int):
delete(id, {RuleConvert.to_upper_camel_case(api_info.code)})
return {{'success': True}}
# 更新{RuleConvert.to_upper_camel_case(api_info.code)}
@router.put('/{api_info.code}/{{id}}', response={RuleConvert.to_upper_camel_case(api_info.code)}SchemaOut)
def update_{api_info.code}(request, id: int, data: {RuleConvert.to_upper_camel_case(api_info.code)}SchemaIn):
{api_info.code} = update(request, id, data, {RuleConvert.to_upper_camel_case(api_info.code)})
return {api_info.code}
# 获取{RuleConvert.to_upper_camel_case(api_info.code)}
@router.get('/{api_info.code}', response=List[{RuleConvert.to_upper_camel_case(api_info.code)}SchemaOut])
@paginate(MyPagination)
def list_{api_info.code}(request, filters: Filters = Query(...)):
qs = retrieve(request, {RuleConvert.to_upper_camel_case(api_info.code)}, filters)
return qs
# 导入
@router.get('/{api_info.code}/all/export')
def export_{api_info.code}(request):
export_fields = ({field_code})
return export_data(request, {RuleConvert.to_upper_camel_case(api_info.code)}, {RuleConvert.to_upper_camel_case(api_info.code)}SchemaOut, export_fields)
# 导出
@router.post('/{api_info.code}/all/import')
def import_{api_info.code}(request, data: ImportSchema):
import_fields = ({field_code})
return import_data(request, {RuleConvert.to_upper_camel_case(api_info.code)}, {RuleConvert.to_upper_camel_case(api_info.code)}SchemaIn, data, import_fields)
'''
return api_txt

View File

@@ -0,0 +1,31 @@
component_to_db_type = {
'Input': 'CharField(null=True, blank=True, max_length=255, ',
'IconPicker': 'CharField(null=True, blank=True, max_length=255, ',
'StrengthMeter': 'CharField(null=True, blank=True, max_length=255, ',
'Select': 'CharField(null=True, blank=True, max_length=255, ',
'CheckboxGroup': 'CharField(null=True, blank=True, max_length=255, ',
'InputNumber': 'DecimalField(null=True, blank=True, max_digits = 13, decimal_places = 4, ',
'InputTextArea': 'TextField(null=True, blank=True, ',
'RadioGroup': 'CharField(null=True, blank=True, max_length=255, ',
'DatePicker': 'DateField(null=True, blank=True, ',
'MonthPicker': 'DateField(null=True, blank=True, ',
'TimePicker': 'TimeField(null=True, blank=True, ',
'Switch': 'BooleanField(null=True, blank=True, ',
'Rate': 'CharField(null=True, blank=True, max_length=255, ',
}
component_to_search_type = {
'Input': 'str',
'IconPicker': 'str',
'StrengthMeter': 'str',
'Select': 'str',
'CheckboxGroup': 'str',
'InputNumber': 'int',
'InputTextArea': 'str',
'RadioGroup': 'str',
'DatePicker': 'str',
'MonthPicker': 'str',
'TimePicker': 'str',
'Switch': 'bool',
'Rate': 'str',
}

View File

@@ -0,0 +1,31 @@
import json
from system.code_template.backend.mapping import component_to_db_type
from utils.ru_convert import RuleConvert
def generator_backend_model(model_info):
form_info = model_info.form_info
form_info_dict = json.loads(form_info)
header_txt = f'''from django.db import models
from utils.models import CoreModel
'''
columns_txt = ''
for item in form_info_dict.get('schemas'):
column = f'''
{item['field']} = models.{component_to_db_type[item['component']]}verbose_name='{item['label']}', help_text='{item['label']}')'''
columns_txt = column + columns_txt
bottom_txt = f'''class {RuleConvert.to_upper_camel_case(model_info.code)}(CoreModel):
{columns_txt}
class Meta:
db_table = 'generator_{model_info.code}'
verbose_name = '{model_info.name}'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
'''
model_txt = header_txt + bottom_txt
return model_txt

View File

@@ -0,0 +1,9 @@
def generator_router(router_info):
router_txt = f'''from ninja import Router
from .api import router
{router_info.code}_router = Router()
{router_info.code}_router.add_router('/', router, tags=['{router_info.name}'])
'''
return router_txt
pass

View File

@@ -0,0 +1,48 @@
def generator_api(api_info):
return f'''import {{ defHttp }} from '/@/utils/http/axios';
enum DeptApi {{
prefix = '/api/{'generator'}/{api_info.code}',
}}
/**
* 获取list
*/
export const getList = (params) => {{
return defHttp.get({{ url: DeptApi.prefix, params }});
}};
/**
* 保存或更新
*/
export const createOrUpdate = (params, isUpdate) => {{
if (isUpdate) {{
return defHttp.put({{ url: DeptApi.prefix + '/' + params.id, params }});
}} else {{
return defHttp.post({{ url: DeptApi.prefix, params }});
}}
}};
/**
* 导入
*/
export const importData = (params) => {{
return defHttp.post({{ url: DeptApi.prefix + '/all/import', params }});
}};
/**
* 导出
*/
export const exportData = () => {{
return defHttp.get(
{{ url: DeptApi.prefix + '/all/export', responseType: 'blob' }},
{{ isReturnNativeResponse: true }},
);
}};
/**
* 删除
*/
export const deleteItem = (id) => {{
return defHttp.delete({{ url: DeptApi.prefix + '/' + id }});
}};
'''

View File

@@ -0,0 +1,84 @@
import json
import re
# 自定义函数用于处理布尔值转换
def bool_to_str(val):
if isinstance(val, bool):
return str(val).lower() # 将布尔值转换为小写字符串
raise TypeError("Object of type %s is not JSON serializable" % type(val))
# Custom JSON decoder to convert boolean strings to boolean values
def custom_json_decoder(data):
for key, value in data.items():
if value == 'true':
data[key] = True
elif value == 'false':
data[key] = False
return data
def generator_data(data_info):
head_txt = f'''
import {{ BasicColumn }} from '/@/components/Table';
import {{ FormSchema }} from '/@/components/Table';
'''
column_schemas = ''
table_info = json.loads(data_info.table_info)
column_info = table_info.get('columnInfo')
for item in column_info:
column_schema = f'''
{{
title: '{item['column_name']}',
dataIndex: '{item['field_name']}',
width: '{item['width']}',
fixed: '{item['freeze']}',
align: '{item['align']}',
auth: ['{data_info.code + ':' + item['field_name']}'],
resizable: {str(item['resizable']).lower()},
}},
'''
column_schemas = column_schemas + column_schema
column_txt = f'''export const columns: BasicColumn[] = [
{column_schemas}
]
'''
search_schemas = ''
table_info = json.loads(data_info.table_info)
search_info = table_info.get('searchInfo')
for item in search_info:
search_schema = f'''
{{
label: '{item['column_name']}',
field: '{item['field_name']}',
component: 'Input',
colProps: {{ span: 6 }},
}},
'''
search_schemas = search_schemas + search_schema
search_txt = f'''export const searchFormSchema: FormSchema[] = [
{search_schemas}
];
'''
form_info = json.loads(data_info.form_info).get('schemas')
for item in form_info:
item.pop('key')
item.pop('icon')
id_dict = {
'field': 'id',
'label': 'id',
'component': 'Input',
'show': False,
}
form_info.insert(0, id_dict)
json_str = json.dumps(form_info, ensure_ascii=False, indent=2, default=bool_to_str)
js_object = re.sub(r'"\b(\w+)\b":', r'\1:', json_str)
form_schemas = js_object.replace('"', "'")
form_txt = f'''export const formSchema: FormSchema[] = {form_schemas}'''
return head_txt + column_txt + search_txt + form_txt

View File

@@ -0,0 +1,72 @@
def generator_drawer(drawer_info):
return f'''<template>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
showFooter
:title="getTitle"
width="50%"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicDrawer>
</template>
<script lang="ts">
import {{ defineComponent, ref, computed, unref }} from 'vue';
import {{ BasicForm, useForm }} from '/@/components/Form/index';
import {{ BasicDrawer, useDrawerInner }} from '/@/components/Drawer';
import {{ createOrUpdate }} from './api';
import {{ formSchema }} from './data';
import {{ useI18n }} from '/@/hooks/web/useI18n';
export default defineComponent({{
name: '{drawer_info.code}Drawer',
components: {{ BasicDrawer, BasicForm }},
emits: ['success', 'register'],
setup(_, {{ emit }}) {{
const isUpdate = ref(true);
const {{ t }} = useI18n();
const [registerForm, {{ resetFields, setFieldsValue, validate }}] = useForm({{
labelWidth: 100,
schemas: formSchema,
showActionButtonGroup: false,
}});
const [registerDrawer, {{ setDrawerProps, closeDrawer }}] = useDrawerInner(async (data) => {{
await resetFields();
setDrawerProps({{ confirmLoading: false }});
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {{
await setFieldsValue({{
...data.record,
}});
}}
}});
const getTitle = computed(() =>
!unref(isUpdate) ? t('common.addText') : t('common.updateText'),
);
async function handleSubmit() {{
try {{
const values = await validate();
setDrawerProps({{ confirmLoading: true }});
await createOrUpdate(values, unref(isUpdate));
closeDrawer();
emit('success');
}} finally {{
setDrawerProps({{ confirmLoading: false }});
}}
}}
return {{
registerDrawer,
registerForm,
getTitle,
handleSubmit,
}};
}},
}});
</script>'''

View File

@@ -0,0 +1,190 @@
def generator_index(index_info):
return f'''<template>
<div>
<BasicTable @register="registerTable">
<template #tableTitle>
<Space style="height: 40px">
<a-button
type="primary"
v-auth="['{index_info.code}:add']"
preIcon="ant-design:plus-outlined"
@click="handleCreate"
>
{{{{ t('common.addText') }}}}
</a-button>
<a-button
type="error"
v-auth="['{index_info.code}:delete']"
preIcon="ant-design:delete-outlined"
@click="handleBulkDelete"
>
{{{{ t('common.delText') }}}}
</a-button>
<BasicUpload
:maxSize="20"
:maxNumber="1"
@change="handleChange"
class="my-5"
type="warning"
:text="t('common.importText')"
v-auth="['{index_info.code}:update']"
/>
<a-button
type="success"
v-auth="['{index_info.code}:update']"
preIcon="carbon:cloud-download"
@click="handleExportData"
>
{{{{ t('common.exportText') }}}}
</a-button>
</Space>
</template>
<template #bodyCell="{{ column, record }}">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{{
type: 'button',
icon: 'clarity:note-edit-line',
color: 'primary',
auth: ['{index_info.code}:update'],
onClick: handleEdit.bind(null, record),
}},
{{
icon: 'ant-design:delete-outlined',
type: 'button',
color: 'error',
placement: 'left',
auth: ['{index_info.code}:delete'],
popConfirm: {{
title: t('common.delHintText'),
confirm: handleDelete.bind(null, record.id),
}},
}},
]"
/>
</template>
</template>
</BasicTable>
<Drawer @register="registerDrawer" @success="handleSuccess" />
</div>
</template>
<script lang="ts">
import {{ defineComponent }} from 'vue';
import {{ BasicTable, useTable, TableAction }} from '/@/components/Table';
import {{ usePermission }} from '/@/hooks/web/usePermission';
import {{ useDrawer }} from '/@/components/Drawer';
import Drawer from './drawer.vue';
import {{ Space }} from 'ant-design-vue';
import {{ BasicUpload }} from '/@/components/Upload';
import {{ deleteItem, getList, exportData, importData }} from './api';
import {{ columns, searchFormSchema }} from './data';
import {{ message }} from 'ant-design-vue';
import {{ useMessage }} from '/@/hooks/web/useMessage';
import {{ downloadByData }} from '/@/utils/file/download';
import {{ useI18n }} from '/@/hooks/web/useI18n';
export default defineComponent({{
name: '{index_info.code}',
components: {{ BasicTable, Drawer, TableAction, BasicUpload, Space }},
setup() {{
const {{ t }} = useI18n();
const [registerDrawer, {{ openDrawer }}] = useDrawer();
const {{ createConfirm }} = useMessage();
const {{ hasPermission }} = usePermission();
const [registerTable, {{ reload, getSelectRows }}] = useTable({{
api: getList,
columns,
formConfig: {{
labelWidth: 80,
schemas: searchFormSchema,
}},
useSearchForm: true,
showTableSetting: true,
tableSetting: {{ fullScreen: true }},
bordered: true,
showIndexColumn: false,
rowSelection: {{
type: 'checkbox',
}},
actionColumn: {{
width: 150,
title: t('common.operationText'),
dataIndex: 'action',
fixed: undefined,
}},
}});
function handleCreate() {{
openDrawer(true, {{
isUpdate: false,
}});
}}
function handleEdit(record: Recordable) {{
openDrawer(true, {{
record,
isUpdate: true,
}});
}}
async function handleDelete(id: number) {{
await deleteItem(id);
message.success(t('common.successText'));
await reload();
}}
function handleBulkDelete() {{
if (getSelectRows().length == 0) {{
message.warning(t('common.batchDelHintText'));
}} else {{
createConfirm({{
iconType: 'warning',
title: t('common.hintText'),
content: t('common.delHintText'),
async onOk() {{
for (const item of getSelectRows()) {{
await deleteItem(item.id);
}}
message.success(t('common.successText'));
await reload();
}},
}});
}}
}}
async function handleChange(list: string[]) {{
console.log(list[0]);
await importData({{ path: list[0] }});
message.success('导入成功');
await reload();
}}
async function handleExportData() {{
const response = await exportData();
await downloadByData(response.data, '{index_info.name}.xlsx');
}}
function handleSuccess() {{
message.success(t('common.successText'));
reload();
}}
return {{
registerTable,
registerDrawer,
handleCreate,
handleEdit,
handleDelete,
handleSuccess,
hasPermission,
handleBulkDelete,
getSelectRows,
handleExportData,
handleChange,
t,
}};
}},
}});
</script>'''

1503
code/system/initialize.py Normal file

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1,260 @@
import json
import logging
import os
import shutil
from django.core.management.base import BaseCommand
from fuadmin.settings import BASE_DIR
logger = logging.getLogger(__name__)
class Command(BaseCommand):
"""
创建App命令:
python manage.py createapp app名
python manage.py createapp app01 app02 ...
python manage.py createapp 一级文件名/app01 ... # 支持多级目录建app
"""
def add_arguments(self, parser):
parser.add_argument('app_info', nargs='*', type=str, )
def handle(self, *args, **options):
app_info = options.get('app_info')
for name in app_info:
app = json.loads(name)
name = app.get('app_name')
names = name.split('/')
dnames = ".".join(names)
app_path = os.path.join(BASE_DIR, "dvadmin", *names)
# 判断app是否存在
if os.path.exists(app_path):
print(f"App {name} 已存在!")
# break
else:
source_path = os.path.join(BASE_DIR, "dvadmin", "utils", "template")
target_path = app_path
if not os.path.exists(target_path):
# 如果目标路径不存在原文件夹的话就创建
os.makedirs(target_path)
if os.path.exists(source_path):
# 如果目标路径存在原文件夹的话就先删除
shutil.rmtree(target_path)
shutil.copytree(source_path, target_path)
# 注册app到 settings.py 中
injection(os.path.join(BASE_DIR, "application", "settings.py"), f" 'dvadmin.{dnames}',\n",
"INSTALLED_APPS",
"]")
# 注册app到 urls.py 中
injection(os.path.join(BASE_DIR, "application", "urls.py"),
f" path(r'api/{name}/', include('dvadmin.{dnames}.urls')),\n", "urlpatterns = [",
"]")
# 修改app中的apps 内容
app_content = f"""
from django.apps import AppConfig
class {name.capitalize()}Config(AppConfig):
name = 'dvadmin.{dnames}'
verbose_name = "{names[-1]}App"
"""
with open(os.path.join(app_path, "apps.py"), 'w', encoding='UTF-8') as f:
f.write(app_content)
# 修改Model的内容
table_name = app.get('table_name')
model_name = app.get('model_name')
# app中所有不重复的字段
app_fields = app.get('fields')
field_content = ''
# app中所有不重复的字段type
fields_type = set()
# app中所有的字段name
fields_name = set()
for field in app_fields:
fields_type.add(field['type'])
fields_name.add(field['name'])
if field['type'] == 'CharField':
field_content = f"""{field_content}
{field['name']} = {field['type']}(max_length={field['max_length']}, verbose_name='{field['name']}', help_text='{field['description']}')"""
elif field['type'] == 'TextField':
field_content = f"""{field_content}
{field['name']} = {field['type']}(verbose_name='{field['name']}', help_text='{field['description']}')"""
model_content = f"""
from django.db.models import {','.join(fields_type)}
from dvadmin.utils.models import CoreModel
class {model_name}(CoreModel):
{field_content}
class Meta:
verbose_name = '{model_name}'
verbose_name_plural = verbose_name
def __str__(self):
return self.{fields_name.pop()}
"""
with open(os.path.join(app_path, "models", f"{table_name}.py"), 'w', encoding='UTF-8') as f:
f.write(model_content)
# 修改 model init文件
model_init_content = f"from dvadmin.{name}.models.{table_name} import {model_name}"
with open(os.path.join(app_path, "models", "__init__.py"), 'a', encoding='UTF-8') as f:
f.write(model_init_content)
f.write('\n')
# 修改filters的内容
filter_content = f"""
import django_filters
from dvadmin.{name}.models import {model_name}
class {model_name}Filter(django_filters.rest_framework.FilterSet):
# 通过 lookup_expr 可进行模糊查询,其他配置可自行百度
# name = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = {model_name}
fields = '__all__'
"""
with open(os.path.join(app_path, "filters", f"{table_name}_filter.py"), 'w', encoding='UTF-8') as f:
f.write(filter_content)
# 修改 filters init文件
filter_init_content = f"from .{table_name}_filter import {model_name}Filter"
with open(os.path.join(app_path, "filters", "__init__.py"), 'a', encoding='UTF-8') as f:
f.write(filter_init_content)
f.write('\n')
# 修改serializers的内容
serializer_content = f"""
from rest_framework import serializers
from dvadmin.{name}.models import {model_name}
from dvadmin.utils.serializers import CustomModelSerializer
class {model_name}Serializer(CustomModelSerializer):
class Meta:
model = {model_name}
fields = '__all__'
class {model_name}CreateUpdateSerializer(CustomModelSerializer):
# 此处可写定制的 创建/更新 内容
def validate(self, attrs: dict):
return super().validate(attrs)
class Meta:
model = {model_name}
fields = '__all__'
class {model_name}ImportSerializer(CustomModelSerializer):
class Meta:
model = {model_name}
fields = '__all__'
class {model_name}ExportSerializer(CustomModelSerializer):
class Meta:
model = {model_name}
fields = '__all__'
"""
with open(os.path.join(app_path, 'serializers', f'{table_name}_serializer.py'), 'w', encoding='UTF-8') as f:
f.write(serializer_content)
# 修改 serializers init文件
serializer_init_content = f"from .{table_name}_serializer import {model_name}Serializer, " \
f"{model_name}CreateUpdateSerializer, " \
f"{model_name}ImportSerializer, " \
f"{model_name}ExportSerializer"
with open(os.path.join(app_path, "serializers", "__init__.py"), 'a', encoding='UTF-8') as f:
f.write(serializer_init_content)
f.write('\n')
# 修改view内容
view_content = f"""
from dvadmin.utils.filters import DataLevelPermissionsFilter
from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.{name}.models import {model_name}
from dvadmin.{name}.filters import {model_name}Filter
from dvadmin.{name}.serializers import {model_name}Serializer, {model_name}CreateUpdateSerializer, {model_name}ImportSerializer, {model_name}ExportSerializer
class {model_name}ModelViewSet(CustomModelViewSet):
queryset = {model_name}.objects.all()
serializer_class = {model_name}Serializer # 序列化器
create_serializer_class = {model_name}CreateUpdateSerializer # 创建/更新时的列化器
update_serializer_class = {model_name}CreateUpdateSerializer # 创建/更新时的列化器
filter_class = {model_name}Filter # 过滤器
extra_filter_backends = [DataLevelPermissionsFilter] # 数据权限类,不需要可注释掉
# ordering = ['create_datetime'] # 默认排序
# 导出
export_field_data = [] # 导出
export_serializer_class = {model_name}ExportSerializer # 导出序列化器
# 导入
import_field_data = []
import_serializer_class = {model_name}ImportSerializer
"""
with open(os.path.join(app_path, 'views', f'{table_name}_view.py'), 'w', encoding='UTF-8') as f:
f.write(view_content)
# 修改 views init文件
view_init_content = f"from .{table_name}_view import {model_name}ModelViewSet"
with open(os.path.join(app_path, "views", "__init__.py"), 'a', encoding='UTF-8') as f:
f.write(view_init_content)
f.write('\n')
# 修改 url文件
url_package = f"from dvadmin.{name}.views import {model_name}ModelViewSet"
injection(os.path.join(app_path, "urls.py"),
f"{url_package}\n", "from rest_framework.routers import DefaultRouter",
"router = DefaultRouter()")
url_path = f"router.register(r'{table_name}', {model_name}ModelViewSet)"
injection(os.path.join(app_path, "urls.py"),
f"{url_path}\n", "router = DefaultRouter()",
"urlpatterns = [")
export_import_url = f""" # 导出项目
path('{table_name}/export/', {model_name}ModelViewSet.as_view({{'get': 'export', }})),
# 项目导入模板下载及导入
path('{table_name}/importTemplate/', {model_name}ModelViewSet.as_view({{'get': 'importTemplate', 'post': 'importTemplate'}}))"""
injection(os.path.join(app_path, "urls.py"),
f"{export_import_url}\n", "urlpatterns = [", "]")
print(f"创建 {name} App成功")
def injection(file_path, insert_content, startswith, endswith):
with open(file_path, "r+", encoding="utf-8") as f:
data = f.readlines()
with open(file_path, 'w', encoding='UTF-8') as f1:
is_INSTALLED_APPS = False
is_insert = False
for content in data:
# 判断文件是否 INSTALLED_APPS 开头
if not is_insert and content.startswith(startswith):
is_INSTALLED_APPS = True
if not is_insert and content.startswith(endswith) and is_INSTALLED_APPS:
# 给前一行插入数据
content = insert_content + content
is_insert = True
f1.writelines(content)

View File

@@ -0,0 +1,39 @@
import logging
from django.core.management.base import BaseCommand
from fuadmin import settings
logger = logging.getLogger(__name__)
class Command(BaseCommand):
"""
项目初始化命令: python manage.py init
"""
def add_arguments(self, parser):
parser.add_argument('init_name', nargs='*', type=str, )
parser.add_argument('-y', nargs='*')
parser.add_argument('-Y', nargs='*')
parser.add_argument('-n', nargs='*')
parser.add_argument('-N', nargs='*')
def handle(self, *args, **options):
reset = False
if isinstance(options.get('y'), list) or isinstance(options.get('Y'), list):
reset = True
if isinstance(options.get('n'), list) or isinstance(options.get('N'), list):
reset = False
print(f"正在准备初始化数据,{'如有初始化数据,将会不做操作跳过' if not reset else '初始数据将会先删除后新增'}...")
for app in settings.INSTALLED_APPS:
try:
exec(f"""
from {app}.initialize import main
main(reset={reset})
""")
except ModuleNotFoundError:
pass
print("初始化数据完成!")

View File

@@ -0,0 +1,83 @@
# 城市联动
"""
到乡级 使用方法
1. https://www.npmjs.com/package/china-division 下载数据把对应的json放入对应目录
2. 修改此文件中对应json名
3. 右击执行此py文件进行初始化
"""
import json
import os
import django
import pypinyin
from django.core.management import BaseCommand
from django.db import connection
from fuadmin.settings import BASE_DIR
from system.models import Area
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
django.setup()
area_code_list = []
def area_list(code_list, pcode=None, depth=1):
"""
递归获取所有列表
"""
for code_dict in code_list:
code = code_dict.get('code', None)
name = code_dict.get('name', None)
children = code_dict.get('children', None)
pinyin = ''.join([''.join(i) for i in pypinyin.pinyin(name, style=pypinyin.NORMAL)])
area_code_list.append(
{
"name": name,
"code": code,
"level": depth,
"pinyin": pinyin,
"initials": pinyin[0].upper() if pinyin else "#",
"pcode_id": pcode,
}
)
if children:
area_list(code_list=children, pcode=code, depth=depth + 1)
def main():
with open(os.path.join(BASE_DIR, 'utils', 'pca-code.json'), 'r',encoding="utf-8") as load_f:
code_list = json.load(load_f)
area_list(code_list)
if Area.objects.count() == 0:
Area.objects.bulk_create([Area(**ele) for ele in area_code_list])
else:
for ele in area_code_list:
code = ele.pop("code")
Area.objects.update_or_create(code=code, defaults=ele)
class Command(BaseCommand):
"""
项目初始化命令: python manage.py init
"""
def add_arguments(self, parser):
pass
def handle(self, *args, **options):
print(f"正在准备初始化省份数据...")
if hasattr(connection, 'tenant') and connection.tenant.schema_name:
from django_tenants.utils import get_tenant_model
from django_tenants.utils import tenant_context,schema_context
for tenant in get_tenant_model().objects.exclude(schema_name='public'):
with tenant_context(tenant):
print(f"租户[{connection.tenant.schema_name}]初始化数据开始...")
main()
print(f"租户[{connection.tenant.schema_name}]初始化数据完成!")
else:
main()
print("省份数据初始化数据完成!")

View File

@@ -0,0 +1,42 @@
import time
import logging
from django.core.management.base import BaseCommand
from system.services.vdes_data_receive import VDESSocketMonitor
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = '控制 VDES 监控服务'
def add_arguments(self, parser):
parser.add_argument(
'action',
choices=['start', 'stop', 'restart'],
help='操作: start, stop 或 restart'
)
def handle(self, *args, **options):
monitor = VDESSocketMonitor()
action = options['action']
if action == 'start':
monitor.start()
self.stdout.write(self.style.SUCCESS('VDES 监控服务已启动'))
elif action == 'stop':
monitor.stop()
self.stdout.write(self.style.SUCCESS('VDES 监控服务已停止'))
elif action == 'restart':
monitor.stop()
time.sleep(1)
monitor.start()
self.stdout.write(self.style.SUCCESS('VDES 监控服务已重启'))
# 确保程序不会立即退出
if action == 'start' or action == 'restart':
try:
# 保持命令运行,直到用户中断
while True:
time.sleep(1)
except KeyboardInterrupt:
monitor.stop()
self.stdout.write(self.style.SUCCESS('\nVDES 监控服务已停止'))

550
code/system/models.py Normal file
View File

@@ -0,0 +1,550 @@
import hashlib
import os
from django.contrib.auth.models import AbstractUser
from django.db import models
from utils.models import CoreModel
STATUS_CHOICES = (
(0, "禁用"),
(1, "启用"),
)
class Users(AbstractUser, CoreModel):
username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name='用户账号',
help_text="用户账号")
email = models.EmailField(max_length=255, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
mobile = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话")
avatar = models.TextField(verbose_name="头像", null=True, blank=True, help_text="头像")
name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名")
status = models.BooleanField(default=True, verbose_name="状态", help_text="状态")
GENDER_CHOICES = (
(0, ""),
(1, ""),
)
gender = models.IntegerField(choices=GENDER_CHOICES, default=1, verbose_name="性别", null=True, blank=True,
help_text="性别")
USER_TYPE = (
(0, "后台用户"),
(1, "前台用户"),
)
user_type = models.IntegerField(choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True,
help_text="用户类型")
post = models.ManyToManyField(to='Post', verbose_name='关联岗位', db_constraint=False, help_text="关联岗位")
role = models.ManyToManyField(to='Role', verbose_name='关联角色', db_constraint=False, help_text="关联角色")
dept = models.ForeignKey(to='Dept', verbose_name='所属部门', on_delete=models.SET_NULL, db_constraint=False,
null=True,
blank=True, help_text="关联部门")
first_name = models.CharField(max_length=150, blank=True, null=True)
last_name = models.CharField(max_length=150, blank=True, null=True)
home_path = models.CharField(max_length=150, blank=True, null=True)
class Meta:
db_table = "system_users"
verbose_name = '用户表'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
class Post(CoreModel):
name = models.CharField(null=False, max_length=64, verbose_name="岗位名称", help_text="岗位名称")
code = models.CharField(max_length=32, verbose_name="岗位编码", help_text="岗位编码")
STATUS_CHOICES = (
(0, "离职"),
(1, "在职"),
)
status = models.IntegerField(choices=STATUS_CHOICES, default=1, verbose_name="岗位状态", help_text="岗位状态")
class Meta:
db_table = "system_post"
verbose_name = '岗位表'
verbose_name_plural = verbose_name
ordering = ('sort',)
class Role(CoreModel):
name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称")
code = models.CharField(max_length=64, unique=True, verbose_name="角色编码", help_text="角色编码")
# key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
DATASCOPE_CHOICES = (
(0, "仅本人数据权限"),
(1, "本部门数据权限"),
(2, "本部门及以下数据权限"),
(3, "全部数据权限"),
(4, "自定数据权限"),
)
data_range = models.IntegerField(default=0, choices=DATASCOPE_CHOICES, verbose_name="数据权限范围",
help_text="数据权限范围")
dept = models.ManyToManyField(to='Dept', verbose_name='数据权限-关联部门', db_constraint=False,
help_text="数据权限-关联部门")
menu = models.ManyToManyField(to='Menu', verbose_name='关联菜单', db_constraint=False, help_text="关联菜单")
permission = models.ManyToManyField(to='MenuButton', verbose_name='关联菜单的接口按钮', db_constraint=False,
help_text="关联菜单的接口按钮")
column = models.ManyToManyField(to='MenuColumnField', verbose_name='列表权限', db_constraint=False,
help_text="列表权限")
class Meta:
db_table = 'system_role'
verbose_name = '角色表'
verbose_name_plural = verbose_name
ordering = ('sort',)
class Dept(CoreModel):
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人")
phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
email = models.EmailField(max_length=32, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
status = models.BooleanField(default=True, verbose_name="部门状态", null=True, blank=True,
help_text="部门状态")
parent = models.ForeignKey(to='Dept', on_delete=models.PROTECT, default=None, verbose_name="上级部门",
db_constraint=False, null=True, blank=True, help_text="上级部门")
class Meta:
db_table = "system_dept"
verbose_name = '部门表'
verbose_name_plural = verbose_name
ordering = ('sort',)
class Button(CoreModel):
name = models.CharField(max_length=64, unique=True, verbose_name="权限名称", help_text="权限名称")
code = models.CharField(max_length=64, unique=True, verbose_name="权限值", help_text="权限值")
status = models.BooleanField(default=True, verbose_name="按钮状态", null=True, blank=True, help_text="按钮状态")
class Meta:
db_table = "system_button"
verbose_name = '权限标识表'
verbose_name_plural = verbose_name
ordering = ('sort',)
class Menu(CoreModel):
parent = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name="上级菜单", null=True, blank=True,
db_constraint=False, help_text="上级菜单")
icon = models.CharField(max_length=64, default='ant-design:book-outlined', verbose_name="菜单图标",
help_text="菜单图标")
title = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称")
permission = models.CharField(max_length=64, null=True, blank=True, verbose_name="权限标识", help_text="权限标识")
ISLINK_CHOICES = (
(0, ""),
(1, ""),
)
is_ext = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
TYPE_CHOICES = (
(0, "目录"),
(1, "菜单"),
)
type = models.IntegerField(choices=TYPE_CHOICES, verbose_name="是否目录", help_text="是否目录")
path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
redirect = models.CharField(max_length=128, verbose_name="重定向地址", null=True, blank=True,
help_text="重定向地址")
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True, help_text="组件名称")
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
keepalive = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
hide_menu = models.BooleanField(default=False, blank=True, verbose_name="侧边栏中是否隐藏",
help_text="侧边栏中是否隐藏")
class Meta:
db_table = "system_menu"
verbose_name = '菜单表'
verbose_name_plural = verbose_name
ordering = ('sort',)
class MenuButton(CoreModel):
menu = models.ForeignKey(to="Menu", db_constraint=False, related_name="menuPermission", on_delete=models.CASCADE,
verbose_name="关联菜单", help_text='关联菜单')
name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
code = models.CharField(max_length=64, verbose_name="权限值", help_text="权限值")
api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址")
METHOD_CHOICES = (
(0, "GET"),
(1, "POST"),
(2, "PUT"),
(3, "DELETE"),
)
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
help_text="接口请求方法")
class Meta:
db_table = "system_menu_button"
verbose_name = '菜单权限表'
verbose_name_plural = verbose_name
ordering = ('sort',)
class MenuColumnField(CoreModel):
menu = models.ForeignKey(to="Menu", db_constraint=False, related_name="menuColumnField", on_delete=models.CASCADE,
verbose_name="关联菜单", help_text='关联菜单')
name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
code = models.CharField(max_length=64, verbose_name="权限值", help_text="权限值")
class Meta:
db_table = "system_menu_column_field"
verbose_name = '菜单数据列表'
verbose_name_plural = verbose_name
ordering = ('sort',)
class Dict(CoreModel):
name = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称")
code = models.CharField(max_length=100, blank=True, null=True, verbose_name="编码", help_text="编码")
status = models.BooleanField(default=True, blank=True, verbose_name="状态", help_text="状态")
remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注")
class Meta:
db_table = 'system_dict'
verbose_name = "字典表"
verbose_name_plural = verbose_name
ordering = ('sort',)
class DictItem(CoreModel):
icon = models.CharField(max_length=100, blank=True, null=True, verbose_name="ICON", help_text="ICON")
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="显示名称", help_text="显示名称")
value = models.CharField(max_length=100, blank=True, null=True, verbose_name="实际值", help_text="实际值")
status = models.BooleanField(default=True, blank=True, verbose_name="状态", help_text="状态")
dict = models.ForeignKey(to="Dict", db_constraint=False, related_name="dictItem", on_delete=models.CASCADE,
help_text="字典")
remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注")
class Meta:
db_table = 'system_dict_item'
verbose_name = "字典表详情表"
verbose_name_plural = verbose_name
ordering = ('sort',)
class CategoryDict(CoreModel):
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="显示名称", help_text="显示名称")
value = models.CharField(max_length=100, blank=True, null=True, verbose_name="实际值", help_text="实际值")
code = models.CharField(max_length=100, unique=True, blank=True, null=True, verbose_name="编码", help_text="编码")
parent = models.ForeignKey(to='CategoryDict', on_delete=models.CASCADE, default=None, verbose_name="上级",
db_constraint=False, null=True, blank=True, help_text="上级")
class Meta:
db_table = 'system_category_dict'
verbose_name = "分类字典表"
verbose_name_plural = verbose_name
ordering = ('sort',)
class OperationLog(CoreModel):
request_username = models.CharField(max_length=50, blank=True, null=True, verbose_name="请求用户",
help_text="请求用户")
request_modular = models.CharField(max_length=64, verbose_name="请求模块", null=True, blank=True,
help_text="请求模块")
request_path = models.CharField(max_length=400, verbose_name="请求地址", null=True, blank=True,
help_text="请求地址")
request_body = models.TextField(verbose_name="请求参数", null=True, blank=True, help_text="请求参数")
request_method = models.CharField(max_length=8, verbose_name="请求方式", null=True, blank=True,
help_text="请求方式")
request_msg = models.TextField(verbose_name="操作说明", null=True, blank=True, help_text="操作说明")
request_ip = models.CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True,
help_text="请求ip地址")
request_browser = models.CharField(max_length=64, verbose_name="请求浏览器", null=True, blank=True,
help_text="请求浏览器")
response_code = models.CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True,
help_text="响应状态码")
request_os = models.CharField(max_length=64, verbose_name="操作系统", null=True, blank=True, help_text="操作系统")
json_result = models.TextField(verbose_name="返回信息", null=True, blank=True, help_text="返回信息")
status = models.BooleanField(default=False, verbose_name="响应状态", help_text="响应状态")
class Meta:
db_table = 'system_operation_log'
verbose_name = '操作日志'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
def media_file_name(instance, filename):
h = instance.md5sum
basename, ext = os.path.splitext(filename)
return os.path.join('files', h[0:1], h[1:2], h + ext.lower())
class File(CoreModel):
name = models.CharField(max_length=255, null=True, blank=True, verbose_name="实际名称", help_text="实际名称")
save_name = models.CharField(max_length=255, null=True, blank=True, verbose_name="存储名称", help_text="存储名称")
url = models.FileField(upload_to=media_file_name)
size = models.BigIntegerField(null=True, blank=True, verbose_name="大小", help_text="大小")
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
class Meta:
db_table = 'system_file'
verbose_name = '文件管理'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
class Area(CoreModel):
name = models.CharField(max_length=100, verbose_name="名称", help_text="名称")
code = models.CharField(max_length=20, verbose_name="地区编码", help_text="地区编码", unique=True, db_index=True)
level = models.BigIntegerField(verbose_name="地区层级(1省份 2城市 3区县 4乡级)",
help_text="地区层级(1省份 2城市 3区县 4乡级)")
pinyin = models.CharField(max_length=255, verbose_name="拼音", help_text="拼音")
initials = models.CharField(max_length=20, verbose_name="首字母", help_text="首字母")
enable = models.BooleanField(default=True, verbose_name="是否启用", help_text="是否启用")
pcode = models.ForeignKey(to='self', verbose_name='父地区编码', to_field="code", on_delete=models.CASCADE,
db_constraint=False, null=True, blank=True, help_text="父地区编码")
class Meta:
db_table = "system_area"
verbose_name = '地区表'
verbose_name_plural = verbose_name
ordering = ('code',)
def __str__(self):
return f"{self.name}"
class ApiWhiteList(CoreModel):
url = models.CharField(max_length=200, help_text="url地址", verbose_name="url")
METHOD_CHOICES = (
(0, "GET"),
(1, "POST"),
(2, "PUT"),
(3, "DELETE"),
)
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
help_text="接口请求方法")
enable_datasource = models.BooleanField(default=True, verbose_name="激活数据权限", help_text="激活数据权限",
blank=True)
class Meta:
db_table = "system_api_white_list"
verbose_name = '接口白名单'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
class SystemConfig(CoreModel):
parent = models.ForeignKey(to='self', verbose_name='父级', on_delete=models.CASCADE,
db_constraint=False, null=True, blank=True, help_text="父级")
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
key = models.CharField(max_length=20, verbose_name="", help_text="")
value = models.JSONField(max_length=100, verbose_name="", help_text="", null=True, blank=True)
status = models.BooleanField(default=False, verbose_name="启用状态", help_text="启用状态")
data_options = models.JSONField(verbose_name="数据options", help_text="数据options", null=True, blank=True)
FORM_ITEM_TYPE_LIST = (
(0, 'text'),
(1, 'textarea'),
(2, 'number'),
(3, 'select'),
(4, 'radio'),
(5, 'checkbox'),
(6, 'date'),
(7, 'datetime'),
(8, 'time'),
(9, 'imgs'),
(10, 'files'),
(11, 'array'),
(12, 'foreignkey'),
(13, 'manytomany'),
)
form_item_type = models.IntegerField(choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型",
default=0,
blank=True)
rule = models.JSONField(null=True, blank=True, verbose_name="校验规则", help_text="校验规则")
placeholder = models.CharField(max_length=50, null=True, blank=True, verbose_name="提示信息", help_text="提示信息")
setting = models.JSONField(null=True, blank=True, verbose_name="配置", help_text="配置")
class Meta:
db_table = "system_config"
verbose_name = '系统配置表'
verbose_name_plural = verbose_name
ordering = ('sort',)
def __str__(self):
return f"{self.title}"
class LoginLog(CoreModel):
LOGIN_TYPE_CHOICES = (
(1, '普通登录'),
)
username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名")
ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip")
agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息")
browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名")
os = models.CharField(max_length=200, verbose_name="操作系统", null=True, blank=True, help_text="操作系统")
continent = models.CharField(max_length=50, verbose_name="", null=True, blank=True, help_text="")
country = models.CharField(max_length=50, verbose_name="国家", null=True, blank=True, help_text="国家")
province = models.CharField(max_length=50, verbose_name="省份", null=True, blank=True, help_text="省份")
city = models.CharField(max_length=50, verbose_name="城市", null=True, blank=True, help_text="城市")
district = models.CharField(max_length=50, verbose_name="县区", null=True, blank=True, help_text="县区")
isp = models.CharField(max_length=50, verbose_name="运营商", null=True, blank=True, help_text="运营商")
area_code = models.CharField(max_length=50, verbose_name="区域代码", null=True, blank=True, help_text="区域代码")
country_english = models.CharField(max_length=50, verbose_name="英文全称", null=True, blank=True,
help_text="英文全称")
country_code = models.CharField(max_length=50, verbose_name="简称", null=True, blank=True, help_text="简称")
longitude = models.CharField(max_length=50, verbose_name="经度", null=True, blank=True, help_text="经度")
latitude = models.CharField(max_length=50, verbose_name="纬度", null=True, blank=True, help_text="纬度")
login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型",
help_text="登录类型")
class Meta:
db_table = 'system_login_log'
verbose_name = '登录日志'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
class GeneratorTemplate(CoreModel):
name = models.CharField(null=False, max_length=64, verbose_name="模板名称", help_text="模板名称")
code = models.CharField(max_length=32, verbose_name="模板编码", help_text="模板编码")
form_info = models.TextField(verbose_name="表单信息", help_text="表单信息")
table_info = models.TextField(verbose_name="表格信息", help_text="表格信息")
has_menu = models.BooleanField(default=False, verbose_name="是否已经生成菜单", help_text="是否已经生成菜单")
class Meta:
db_table = "system_generator_template"
verbose_name = '代码生成器模板'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)
#-------------------------------------------------追加----------------------------------------------------
class VDESData(models.Model):
"""本船VDES信息模型"""
mmsi = models.CharField(max_length=15, verbose_name="MMSI号码", unique=True, help_text="船舶唯一识别码") #唯一
latitude = models.FloatField(verbose_name="纬度", help_text="船舶当前位置纬度")
longitude = models.FloatField(verbose_name="经度", help_text="船舶当前位置经度")
speed = models.FloatField(verbose_name="航速", help_text="航速(节)", null=True, blank=True)
course = models.FloatField(verbose_name="航向", help_text="航向(度)", null=True, blank=True)
heading = models.FloatField(verbose_name="船首向", help_text="船首向(度)", null=True, blank=True)
nav_status = models.IntegerField(verbose_name="航行状态", null=True, blank=True, help_text="0=机动船在航,1=锚泊等")
timestamp = models.DateTimeField(verbose_name="时间戳", help_text="AIS数据更新时间")
ship_name = models.CharField(max_length=100, verbose_name="船名", null=True, blank=True)
ship_type = models.IntegerField(verbose_name="船舶类型", null=True, blank=True, help_text="1-99=各种船舶类型")
draught = models.FloatField(verbose_name="吃水", null=True, blank=True, help_text="单位:米")
destination = models.CharField(max_length=100, verbose_name="目的地", null=True, blank=True)
eta = models.DateTimeField(verbose_name="预计到达时间", null=True, blank=True)
class Meta:
db_table = "system_vdes_data"
verbose_name = 'VDES数据'
verbose_name_plural = verbose_name
ordering = ('-timestamp',) # 默认按时间倒序排列
class ShipLatestStatus(models.Model):
mmsi = models.CharField(max_length=15, verbose_name="MMSI号码", unique=True, help_text="船舶唯一识别码") #唯一
latitude = models.FloatField(verbose_name="纬度", help_text="船舶当前位置纬度")
longitude = models.FloatField(verbose_name="经度", help_text="船舶当前位置经度")
speed = models.FloatField(verbose_name="航速", help_text="航速(节)", null=True, blank=True)
course = models.FloatField(verbose_name="航向", help_text="航向(度)", null=True, blank=True)
heading = models.FloatField(verbose_name="船首向", help_text="船首向(度)", null=True, blank=True)
nav_status = models.IntegerField(verbose_name="航行状态", null=True, blank=True, help_text="0=机动船在航,1=锚泊等")
timestamp = models.DateTimeField(verbose_name="时间戳", help_text="AIS数据更新时间")
ship_name = models.CharField(max_length=100, verbose_name="船名", null=True, blank=True)
ship_type = models.IntegerField(verbose_name="船舶类型", null=True, blank=True, help_text="1-99=各种船舶类型")
draught = models.FloatField(verbose_name="吃水", null=True, blank=True, help_text="单位:米")
destination = models.CharField(max_length=100, verbose_name="目的地", null=True, blank=True)
eta = models.DateTimeField(verbose_name="预计到达时间", null=True, blank=True)
# 添加虚拟字段:不写入数据库,但可以在 raw 查询中赋值
distance_km = None
class Meta:
db_table = "ship_latest_status"
ordering = ('-timestamp',) # 默认按时间倒序排列
class GpsStatus(models.Model):
utc_time = models.DateTimeField(verbose_name="UTC时间")
longitude = models.FloatField(verbose_name="经度")
latitude = models.FloatField(verbose_name="纬度")
altitude = models.FloatField(verbose_name="海拔高度")
used_satellites = models.IntegerField(verbose_name="定位卫星数")
gps_satellites = models.IntegerField(verbose_name="GPS卫星数")
beidou_satellites = models.IntegerField(verbose_name="北斗卫星数")
gps_status = models.IntegerField(verbose_name="GPS状态")
fix_mode = models.IntegerField(verbose_name="定位模式")
hdop = models.FloatField(verbose_name="HDOP精度")
speed = models.FloatField(verbose_name="运动速度")
direction = models.FloatField(verbose_name="运动方向")
class Meta:
db_table = "gps_status"
ordering = ('-utc_time',)
class Content(models.Model):
Id = models.AutoField(primary_key=True, verbose_name="自增Id", help_text="自增Id")
SendId = models.BigIntegerField(verbose_name="发送人Id", null=True, help_text="发送人Id")
ReciverId = models.BigIntegerField(verbose_name="接收人Id", help_text="接收人Id")
Content = models.TextField(verbose_name="聊天内容", help_text="聊天内容")
Type = models.IntegerField(verbose_name="内容类型", help_text="内容类型0html文本1图片2视频3文件")
State = models.IntegerField(verbose_name="是否发送成功", help_text="是否发送成功0否1是")
NoCode = models.CharField(max_length=255, verbose_name="时间戳", help_text="时间戳")
CreateDateUtc = models.CharField(max_length=255, verbose_name="创建时间", help_text="创建时间")
Title = models.CharField(max_length=255, null=True, blank=True, verbose_name="拓展功能字段", help_text="拓展功能字段")
Description = models.CharField(max_length=255, null=True, blank=True, verbose_name="拓展功能字段", help_text="拓展功能字段")
Label = models.CharField(max_length=255, null=True, blank=True, verbose_name="拓展功能字段", help_text="拓展功能字段")
Thumbnail = models.CharField(max_length=255, null=True, blank=True, verbose_name="拓展功能字段", help_text="拓展功能字段")
ReadFlag = models.BooleanField(verbose_name="是否阅读", help_text="是否阅读0否1是")
Avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name="头像", help_text="头像")
SoundStatus = models.IntegerField(default=0, verbose_name="音频播放状态", help_text="音频播放状态0未播放1播放中2已播放")
class Meta:
db_table = "content"
verbose_name = "内容表"
verbose_name_plural = verbose_name
ordering = ('-CreateDateUtc',)
class Contacts(models.Model):
Id = models.AutoField(primary_key=True, verbose_name="Id", help_text="Id自增")
Name = models.CharField(max_length=255, null=True, blank=True, verbose_name="备注", help_text="备注")
MMSI = models.CharField(max_length=255, verbose_name="MMSI号码", help_text="MMSI号码")
Mobile = models.CharField(max_length=255, null=True, blank=True, verbose_name="手机号", help_text="手机号")
OnlineState = models.BooleanField(null=True, blank=True, verbose_name="在线状态", help_text="在线状态")
Region = models.CharField(max_length=255, null=True, blank=True, verbose_name="地区", help_text="地区")
Avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name="头像", help_text="头像")
HistorySessionList = models.TextField(null=True, blank=True, verbose_name="历史会话列表", help_text="历史会话列表")
OutTradeNo = models.CharField(max_length=255, null=True, blank=True, verbose_name="socket.id", help_text="socket.id")
NoCode = models.CharField(max_length=255, null=True, blank=True, verbose_name="时间戳", help_text="时间戳")
class Meta:
db_table = "contacts"
verbose_name = "通讯录"
verbose_name_plural = verbose_name
ordering = ('-Id',)
unique_together = ('Id', 'MMSI')
class HistorySession(models.Model):
Id = models.AutoField(primary_key=True, verbose_name="Id", help_text="Id自增")
FormId = models.BigIntegerField(verbose_name="来自用户Id", help_text="来自用户Id")
Name = models.CharField(max_length=255, null=True, blank=True, verbose_name="备注", help_text="备注")
MMSI = models.CharField(max_length=255, verbose_name="MMSI号码", help_text="MMSI号码")
Mobile = models.CharField(max_length=255, null=True, blank=True, verbose_name="手机号", help_text="手机号")
Region = models.CharField(max_length=255, null=True, blank=True, verbose_name="地区", help_text="地区")
Avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name="头像", help_text="头像")
ById = models.BigIntegerField(verbose_name="归属用户Id", help_text="归属用户Id", default=1)
class Meta:
db_table = "history_session"
verbose_name = "历史会话表"
verbose_name_plural = verbose_name
ordering = ('-Id',)
unique_together = ('Id', 'MMSI')
#记录是否获取到vdes消息
class MessageFlag(models.Model):
has_new_message = models.BooleanField(default=False) # 是否有新消息
class Meta:
db_table = "message_flag"

65
code/system/router.py Normal file
View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/10 00:02
# @Author : 臧成龙
# @FileName: router.py
# @Software: PyCharm
from ninja import Router
from system.apis.dept import router as dept_router
from system.apis.post import router as post_router
from system.apis.login import router as login_router
from system.apis.menu import router as menu_router
from system.apis.role import router as role_router
from system.apis.button import router as button_router
from system.apis.menu_button import router as menu_button_router
from system.apis.user import router as user_router
from system.apis.data_dict.dict import router as dict_router
from system.apis.data_dict.dict_item import router as dict_item_router
from system.apis.data_dict.category_dict import router as category_dict_router
from system.apis.log.login_log import router as login_log_router
from system.apis.log.operation_log import router as operation_log_router
from system.apis.celery_crontab import router as celery_crontab_router
from system.apis.celery_interval import router as celery_interval_router
from system.apis.celery_periodic import router as celery_periodic_router
from system.apis.log.celery_log import router as celery_log_router
from system.apis.file import router as file_router
from system.apis.monitor import router as monitor_router
from system.apis.menu_column import router as menu_column_field_router
from system.apis.code_generator import router as generator_template_router
from system.apis.home import router as home_router
from system.apis.vdes.header import router as header_router
from system.apis.vdes.chat.common import router as chat_common_router
from system.apis.vdes.chat.historySession import router as chat_history_session_router
from system.apis.vdes.chat.contacts import router as chat_contacts_router
from system.apis.vdes.chat.content import router as chat_content_router
system_router = Router()
system_router.add_router('/', dept_router, tags=["Dept"])
system_router.add_router('/', post_router, tags=["Post"])
system_router.add_router('/', login_router, tags=["Login"])
system_router.add_router('/', menu_router, tags=["Menu"])
system_router.add_router('/', role_router, tags=["Role"])
system_router.add_router('/', button_router, tags=["Button"])
system_router.add_router('/', menu_button_router, tags=["MenuButton"])
system_router.add_router('/', user_router, tags=["User"])
system_router.add_router('/', dict_router, tags=["Dict"])
system_router.add_router('/', dict_item_router, tags=["DictItem"])
system_router.add_router('/', category_dict_router, tags=["CategoryDict"])
system_router.add_router('/', login_log_router, tags=["LoginLog"])
system_router.add_router('/', operation_log_router, tags=["OperationLog"])
system_router.add_router('/', celery_crontab_router, tags=["CeleryCrontab"])
system_router.add_router('/', celery_interval_router, tags=["CeleryInterval"])
system_router.add_router('/', celery_periodic_router, tags=["CeleryPeriodic"])
system_router.add_router('/', celery_log_router, tags=["CeleryLog"])
system_router.add_router('/', file_router, tags=["File"])
system_router.add_router('/', monitor_router, tags=["Monitor"])
system_router.add_router('/', menu_column_field_router, tags=["MenuColumnField"])
system_router.add_router('/', generator_template_router, tags=["GeneratorTemplate"])
system_router.add_router('/', home_router, tags=["Home"])
system_router.add_router('/', header_router, tags=["Header"])
system_router.add_router('/', chat_common_router, tags=["ChatCommon"])
system_router.add_router('/', chat_history_session_router, tags=["ChatHistorySession"])
system_router.add_router('/', chat_contacts_router, tags=["ChatContacts"])
system_router.add_router('/', chat_content_router, tags=["ChatContent"])

View File

@@ -0,0 +1,80 @@
import socket
import logging
import time
import json
import struct
import threading
from fuadmin import settings
from system import apps
from vdes_utils.vdes_data_parsing import VdesDataParsing
logger = logging.getLogger(__name__)
class VDESSocketMonitor:
_instance = None
_lock = threading.Lock()
def __new__(cls):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self.host = getattr(settings, 'VDES_MONITOR_HOST', 'localhost')
self.port = getattr(settings, 'VDES_MONITOR_PORT', 8083)
self.buffer_size = getattr(settings, 'VDES_BUFFER_SIZE', 4096)
self.running = False
self.thread = None
self.vdes_data_parsing = VdesDataParsing()
logger.info(f"VDES 监控服务初始化完成 - {self.host}:{self.port}")
def start(self):
if self.running:
logger.warning("监控服务已在运行中")
return
self.running = True
self.thread = threading.Thread(
target=self._monitor_loop,
name="VDES-Monitor-Thread",
daemon=False
)
self.thread.start()
logger.info("VDES 监控服务已启动")
def stop(self):
self.running = False
if self.thread and self.thread.is_alive():
self.thread.join(timeout=5)
logger.info("VDES 监控服务已停止")
def _monitor_loop(self):
while self.running:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
print(f"尝试连接到服务器 {self.host}:{self.port}")
sock.connect((self.host, self.port))
print(f"成功连接到服务器 {self.host}:{self.port}")
while self.running:
data = sock.recv(self.buffer_size)
if data:
# print(data)
self.vdes_data_parsing.parse_vdes_data(data)
except (ConnectionRefusedError, TimeoutError) as e:
print(f"连接失败: {e}")
except Exception as e:
print(f"连接异常: {e}")
# 如果服务仍在运行则等待30秒后重新尝试连接
if self.running:
print("等待30秒后重新尝试连接...")
time.sleep(30)

9
code/system/tasks.py Normal file
View File

@@ -0,0 +1,9 @@
from celery.app import task
from fuadmin.celery import app
@app.task(name="system.tasks.test_task")
def test_task():
print('test')

14
code/system/tests.py Normal file
View File

@@ -0,0 +1,14 @@
from django.test import TestCase
# Create your tests here.
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper
@debug
def hello():
print("hello")
hello()

View File

@@ -0,0 +1,51 @@
# 初始化基类
from fuadmin import settings
class CoreInitialize:
"""
使用方法:继承此类,重写 run方法在 run 中调用 save 进行数据初始化
"""
creator_id = None
reset = False
def __init__(self, reset=False, creator_id=None):
"""
reset: 是否重置初始化数据
creator_id: 创建人id
"""
self.reset = reset or self.reset
self.creator_id = creator_id or self.creator_id
def save(self, obj, data: list, name=None, no_reset=False):
name = name or obj._meta.verbose_name
print(f"正在初始化[{obj._meta.label} => {name}]")
if not no_reset and self.reset and obj not in settings.INITIALIZE_RESET_LIST:
try:
obj.objects.all().delete()
settings.INITIALIZE_RESET_LIST.append(obj)
except Exception:
pass
for ele in data:
m2m_dict = {}
new_data = {}
for key, value in ele.items():
# 判断传的 value 为 list 的多对多进行抽离使用set 进行更新
if isinstance(value, list):
m2m_dict[key] = value
else:
new_data[key] = value
object, _ = obj.objects.get_or_create(id=ele.get("id"), defaults=new_data)
for key, m2m in m2m_dict.items():
m2m = list(set(m2m))
if m2m and len(m2m) > 0 and m2m[0]:
exec(f"""
if object.{key}:
values_list = object.{key}.all().values_list('id', flat=True)
values_list = list(set(list(values_list) + {m2m}))
object.{key}.set(values_list)
""")
print(f"初始化完成[{obj._meta.label} => {name}]")
def run(self):
raise NotImplementedError('.run() must be overridden')

113
code/utils/fu_auth.py Normal file
View File

@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/2 23:19
# @Author : 臧成龙
# @FileName: fu_auth.py
# @Software: PyCharm
# -*- coding: utf-8 -*-
import re
from datetime import datetime
# from django.core.cache import cache
from fuadmin.settings import DEMO, SECRET_KEY, WHITE_LIST
from ninja.security import HttpBearer
from system.models import MenuButton, Users
from .fu_jwt import FuJwt
from .fu_ninja import FuFilters
from .usual import get_dept, get_user_info_from_token
METHOD = {
'GET': 0,
'POST': 1,
'PUT': 2,
'DELETE': 3,
}
class GlobalAuth(HttpBearer):
def authenticate(self, request, token):
# 直接放行所有请求
return token
# jwt = FuJwt(SECRET_KEY)
# value = jwt.decode(SECRET_KEY, token)
# time_now = int(datetime.now().timestamp())
# # 判断token是否过期
# if value.valid_to >= time_now:
# token_user = value.payload
# token_user_id = token_user['id']
# user = Users.objects.get(id=token_user_id)
# request_path = request.path
# request_method = request.method
# if DEMO:
# # 判断是否在白名单中
# if request_path in WHITE_LIST:
# return token
# if request_method == 'GET':
# return token
# else:
# raise TimeoutError(403, '演示环境')
# else:
# # 判断是否是超级管理员
# if not token_user['is_superuser']:
# # 判断是path是否是/数字’结尾
# result = re.search(r'/\d+$', request_path)
# if result:
# match_value = result.group()
# # 将数字结尾的接口替换成.*? 因为接口中是/{id}
# request_path = request_path.replace(match_value, '/*')
# # 判断是否在白名单中
# if request_path in WHITE_LIST:
# return token
# else:
# menuIds = user.role.values_list('permission__id', flat=True)
# queryset = MenuButton.objects.filter(id__in=menuIds, api__regex=request_path,
# method=METHOD[request_method])
# if queryset.exists():
# return token
# else:
# raise TimeoutError(403, '没有权限')
# # cache_token = cache.get(token_user_id)
# # if token == cache_token:
# return token
# else:
# raise TimeoutError(401, 'token时间过期')
def data_permission(request, filters: FuFilters):
return filters # 直接返回不进行任何权限过滤
# user_info = get_user_info_from_token(request)
# if user_info['is_superuser']:
# return filters
# user = Users.objects.get(id=user_info['id'])
# data_range_qs = user.role.values_list('data_range', flat=True)
# dept_ids = user.role.values_list('dept__id', flat=True)
# # 如果有多个角色,取数据权限最大的角色
# data_range = max(list(data_range_qs))
# # 仅本人数据权限
# if data_range == 0:
# filters.creator_id = user_info['id']
# # 本部门数据权限
# if data_range == 1:
# filters.belong_dept = user_info['dept']
# # 本部门及以下数据权限
# if data_range == 2:
# dept_and_below_ids = get_dept(user_info['dept'])
# filters.belong_dept__in = dept_and_below_ids
# # 自定义数据权限
# if data_range == 3:
# filters.belong_dept__in = list(dept_ids)
# # 所有数据权限
# if data_range == 4:
# pass
# return filters

242
code/utils/fu_crud.py Normal file
View File

@@ -0,0 +1,242 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/6 14:22
# @Author : 臧成龙
# @FileName: usual.py
# @Software: PyCharm
# -*- coding: utf-8 -*-
import os
from datetime import datetime
from urllib.parse import unquote
import openpyxl
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from fuadmin.settings import BASE_DIR, STATIC_URL
from ninja import Schema
from openpyxl import load_workbook
from .fu_auth import data_permission
from .fu_ninja import FuFilters
from .fu_response import FuResponse
from .usual import get_user_info_from_token
class ImportSchema(Schema):
path: str
def create(request, data, model):
"""
创建新记录的函数。
参数:
- request: 请求对象,用于获取用户信息。
- data: 创建记录的数据可以是字典或者支持dict()方法的对象。
- model: Django ORM模型指定要创建的对象类型。
返回值:
- 创建成功的对象查询集合。
"""
if not isinstance(data, dict):
# 如果data不是字典类型则转换为字典
data = data.dict()
user_info = get_user_info_from_token(request)
# 从请求中提取用户信息,并添加到数据中作为创建人、修改者和所属部门信息
data['creator_id'] = user_info['id']
data['modifier'] = user_info['name']
data['belong_dept'] = user_info['dept']
# 使用提供的模型和数据创建新记录
query_set = model.objects.create(**data)
return query_set
def batch_create(request, data, model):
"""
批量创建模型实例。
参数:
- request: HTTP请求对象用于获取用户信息。
- data: 一个包含创建数据的列表每个元素可以是字典或者具有dict方法的对象。
- model: Django模型类用于实例化和批量创建。
返回值:
- query_set: 批量创建后的模型实例查询集。
"""
user_info = get_user_info_from_token(request) # 从请求中获取用户信息
data_list = []
for item in data:
if not isinstance(item, dict):
item = item.dict() # 如果item不是字典则转换为字典
# 为每个创建的数据项添加默认的创建人、修改者和所属部门信息
item['creator_id'] = user_info['id']
item['modifier'] = user_info['name']
item['belong_dept'] = user_info['dept']
data_list.append(model(**item)) # 根据字典内容实例化模型对象并添加到列表中
query_set = model.objects.bulk_create(data_list) # 批量创建模型实例
return query_set
def delete(id, model):
"""
根据提供的ID和模型删除对象实例。
参数:
- id: 要删除的对象的ID。
- model: 对象所属的模型类。
返回值:
- 无返回值。
"""
# 根据ID和模型获取对象实例如果找不到则返回404错误页面
instance = get_object_or_404(model, id=id)
# 删除对象实例
instance.delete()
pass
def update(request, id, data, model):
"""
更新给定模型实例的数据。
参数:
- request: HTTP请求对象用于获取用户信息。
- id: 要更新的模型实例的ID。
- data: 包含更新数据的实例,应能转换为字典格式。
- model: 要更新的模型类。
返回值:
- 更新后的模型实例。
"""
dict_data = data.dict() # 将data转换为字典格式
user_info = get_user_info_from_token(request) # 从请求中获取用户信息
# 为更新的数据添加修改者信息
dict_data['modifier'] = user_info['name']
instance = get_object_or_404(model, id=id) # 获取指定ID的模型实例
# 遍历字典,将更新的数据设置到模型实例上
for attr, value in dict_data.items():
setattr(instance, attr, value)
instance.save() # 保存更新
return instance # 返回更新后的实例
def retrieve(request, model, filters: FuFilters = FuFilters()):
"""
根据提供的过滤条件从数据库中检索模型实例。
参数:
- request: HttpRequest对象用于获取请求信息。
- model: Django模型类指定要检索的数据模型。
- filters: FuFilters类的实例包含过滤条件。默认为FuFilters(),即无条件过滤。
返回值:
- query_set: 一个Django QuerySet对象包含根据过滤条件检索到的模型实例。
"""
# 根据请求和过滤条件应用数据权限控制
filters = data_permission(request, filters)
if filters is not None:
# 将filters中的空字符串值转换为None
for attr, value in filters.__dict__.items():
if getattr(filters, attr) == '':
setattr(filters, attr, None)
# 使用过滤条件查询模型实例
query_set = model.objects.filter(**filters.dict(exclude_none=True))
else:
# 如果没有有效的过滤条件,则返回所有模型实例
query_set = model.objects.all()
return query_set
def export_data(request, model, scheme, export_fields):
"""
导出数据为Excel文件。
参数:
- request: HttpRequest对象表示客户端请求。
- model: Django模型类指定要导出数据的模型。
- scheme: 表示数据转换规则的对象用于将ORM对象转换为字典。
- export_fields: 包含要导出的字段名的列表。
返回值:
- FileResponse对象提供下载Excel文件。
"""
title_dict = {}
# 根据export_fields列表获取字段的显示名称
for field in export_fields:
field_obj = getattr(model, field).field
title_dict[field] = field_obj.help_text
qs = retrieve(request, model)
list_data = []
# 将查询集中的每一项转换为指定格式的字典
for qs_item in qs:
qs_item = scheme.from_orm(qs_item)
dict_data = {}
for item, value in title_dict.items():
dict_data[value] = getattr(qs_item, item)
list_data.append(dict_data)
# 创建并初始化Excel工作簿
wb = openpyxl.Workbook()
ws = wb.active
# 向Excel中写入数据
for index, data in enumerate(list_data):
if index == 0:
# 写入表头
ws.append(list(data.keys()))
ws.append(list(data.values()))
# 生成唯一的文件名并设置文件路径
file_name = datetime.now().strftime('%Y%m%d%H%M%S%f') + '.xlsx'
current_ymd = datetime.now().strftime('%Y%m%d')
file_path = os.path.join(STATIC_URL, current_ymd)
file_url = os.path.join(file_path, file_name)
# 确保导出文件的目录存在
if not os.path.exists(file_path):
os.makedirs(file_path)
# 保存Excel文件到指定路径
wb.save(file_url)
# 返回供下载的文件响应
return FileResponse(open(file_url, "rb"), as_attachment=True)
def import_data(request, model, scheme, data, import_fields):
"""
导入数据到指定模型
参数:
- request: HttpRequest对象表示客户端请求
- model: Django模型类数据将被导入到这个模型
- scheme: 一个函数,用于根据给定的数据字典创建模型实例
- data: 包含要导入文件信息的对象比如上传的Excel文件
- import_fields: 一个列表,指定模型中需要导入的字段名
返回值:
- FuResponse对象包含导入结果的消息
"""
title_dict = {} # 字段名与Excel列对应的字典
for field in import_fields:
field_obj = getattr(model, field).field
title_dict[field_obj.help_text] = field_obj.column
# 文件路径处理
file_path = str(BASE_DIR) + unquote(data.path)
# 加载Excel工作簿
wb = load_workbook(file_path)
ws = wb.active # 获取活动工作表
title_value = []
for index_row, row in enumerate(ws.values):
if index_row == 0:
title_value = row # 读取Excel表头
else:
dict_data = {} # 存储每一行数据转换后的字典
for index, cell in enumerate(row):
title_cell = title_value[index]
value = title_dict.get(title_cell) # 根据表头查找对应字段
if value is not None:
dict_data[value] = cell
print(dict_data) # 打印处理后的数据,用于调试
data = scheme(**dict_data) # 根据处理后的字典创建模型实例
create(request, data, model) # 在数据库中创建模型实例
return FuResponse(msg='导入成功') # 返回成功消息

Some files were not shown because too many files have changed in this diff Show More