项目初次提交

This commit is contained in:
sunyugang 2025-04-11 08:54:28 +08:00
commit 9e14a3256f
220 changed files with 15673 additions and 0 deletions

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
# Editor directories and files
.idea/
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
logs/*
!logs/.gitkeep
temp/*
!temp/.gitkeep
!alembic/versions/.gitkeep
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder business settings
.spyderproject
# Rope business settings
.ropeproject
*.db
.DS_Store
__pycache__
!migrations/__init__.py
*.pyc

373
README.md Normal file
View File

@ -0,0 +1,373 @@
# FastAPI 项目
fastapi Githubhttps://github.com/tiangolo/fastapi
fastapi 官方文档https://fastapi.tiangolo.com/zh/
fastapi 更新说明https://fastapi.tiangolo.com/zh/release-notes/
pydantic 官方文档https://pydantic-docs.helpmanual.io/
pydantic 数据模型代码生成器官方文档 Json -> Pydantichttps://koxudaxi.github.io/datamodel-code-generator/
SQLAlchemy-Utilshttps://sqlalchemy-utils.readthedocs.io/en/latest/
alembic 中文文档https://hellowac.github.io/alembic_doc/zh/_front_matter.html
Typer 官方文档https://typer.tiangolo.com/
SQLAlchemy 2.0 (官方): https://docs.sqlalchemy.org/en/20/intro.html#installation
SQLAlchemy 1.4 迁移到 2.0 官方https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#whatsnew-20-orm-declarative-typing
PEP 484 语法官方https://peps.python.org/pep-0484/
## 项目结构
使用的是仿照 Django 项目结构:
- alembic数据库迁移配置目录
- versions_dev开发环境数据库迁移文件目录
- versions_pro生产环境数据库迁移文件目录
- env.py映射类配置文件
- application主项目配置目录也存放了主路由文件
- config基础环境配置文件
- development.py开发环境
- production.py生产环境
- settings.py主项目配置文件
- urls.py主路由文件
- apps项目的app存放目录
- vadmin基础服务
- auth用户 - 角色 - 菜单接口服务
- modelsORM 模型目录
- params查询参数依赖项目录
- schemaspydantic 模型,用于数据库序列化操作目录
- utils登录认证功能接口服务
- curd.py数据库操作
- views.py视图函数
- core核心文件目录
- crud.py关系型数据库操作核心封装
- database.py关系型数据库核心配置
- data_types.py自定义数据类型
- exception.py异常处理
- logger日志处理核心配置
- middleware.py中间件核心配置
- dependencies.py常用依赖项
- event.py全局事件
- mongo_manage.pymongodb 数据库操作核心封装
- validator.pypydantic 模型重用验证器
- dbORM模型基类
- logs日志目录
- static静态资源存放目录
- utils封装的一些工具类目录
- main.py主程序入口文件
- alembic.ini数据库迁移配置文件
## 开发环境
开发语言Python 3.10
开发框架Fastapi 0.101.1
ORM 框架SQLAlchemy 2.0.20
## 开发工具
Pycharm 2022.3.2
推荐插件Chinese (Simplified) Language Pack / 中文语言包
代码样式配置:
![image-20230315194534959](https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/public/images/image-20230315194534959.png)
## 使用
```
source /opt/env/kinit-pro/bin/activate
# 安装依赖库
pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
# 第三方源:
1. 阿里源: https://mirrors.aliyun.com/pypi/simple/
# 线上安装更新依赖库
/opt/env/kinit-pro-310/bin/pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
```
### 数据初始化
```shell
# 项目根目录下执行,需提前创建好数据库,并且数据库应该为空
# 会自动将模型迁移到数据库,并生成初始化数据
# 在执行前一定要确认要操作的环境与application/settings.DEBUG 设置的环境是一致的,
# 不然会导致创建表和生成数据不在一个数据库中!!!!!!!!!!!!!!!!!!!!!!
# 比如要初始化开发环境那么env参数应该为 dev并且 application/settings.DEBUG 应该 = True
# 比如要初始化生产环境那么env参数应该为 pro并且 application/settings.DEBUG 应该 = False
# 生产环境
python main.py init
# 开发环境
python main.py init --env dev
```
### 运行启动
```shell
# 直接运行main文件
python main.py run
```
## 其他操作
在线文档地址(在配置文件里面设置路径或者关闭)
```
http://127.0.0.1:9000/docs
```
Git更新ignore文件直接修改gitignore是不会生效的需要先去掉已经托管的文件修改完成之后再重新添加并提交。
```
第一步:
git rm -r --cached .
去掉已经托管的文件
第二步:
修改自己的igonre文件内容
第三步:
git add .
git commit -m "clear cached"
```
## 新的数据迁移
- 新建模型:
- 在你的app目录下新建一个models目录`__init__.py`导入你需要迁移的models
```python
# app/.../your_app/models/__init__.py
from .your_model import YourModel,YourModel2
```
```python
# app/.../your_app/models/your_model.py
from db.db_base import BaseModel
class YourModel(BaseModel):
# 定义你的model
...
class YourModel2(BaseModel):
# 定义你的model
...
```
- 根据模型配置你的alembic
```
# alembic.ini
[dev]
...
sqlalchemy.url = mysql+pymysql://your_username:password@ip:port/kinit
...
```
```python
# alembic/env.py
# 导入项目中的基本映射类,与 需要迁移的 ORM 模型
from apps.vadmin.auth.models import *
...
from apps.xxx.your_app.models import *
```
- 执行数据库迁移命令(终端执行执行脚本):
```shell
# 执行命令(生产环境):
python main.py migrate
# 执行命令(开发环境):
python main.py migrate --env dev
# 开发环境的原命令
alembic --name dev revision --autogenerate -m 2.0
alembic --name dev upgrade head
```
生成迁移文件后会在alembic迁移目录中的version目录中多个迁移文件
## 新的CRUD
- 新的模型文件已经建好(上一步迁移时必须)
- 在 scripts/crud_generate/main.py 添加执行命令
```python
# scripts/crud_generate/main.py
if __name__ == '__main__':
from apps.xxx.your_app.models import YourModel
crud = CrudGenerate(YourModel, "中文名", "en_name")
# 只打印代码,不执行创建写入
crud.generate_codes()
# 创建并写入代码
crud.main()
```
- 生成后会自动创建crud, params,schema, views
## 新的路由配置
```python
# application/urls.py
from apps.xxx.your_app.views import app as your_app
urlpatterns = [
...,
{"ApiRouter": your_app, "prefix": "/your_router", "tags": ["your_tag"]},
]
```
完成后在 http://127.0.0.1:9000/docs 验证生成的接口
## 查询数据
### 自定义的一些查询过滤
```python
# 日期查询
# 值的类型str
# 值得格式:%Y-%m-%d2023-05-14
字段名称=("date", 值)
# 模糊查询
# 值的类型: str
字段名称=("like", 值)
# in 查询
# 值的类型list
字段名称=("in", 值)
# 时间区间查询
# 值的类型tuple 或者 list
字段名称=("between", 值)
# 月份查询
# 值的类型str
# 值的格式:%Y-%m2023-05
字段名称=("month", 值)
# 不等于查询
字段名称=("!=", 值)
# 大于查询
字段名称=(">", 值)
# 等于 None
字段名称=("None")
# 不等于 None
字段名称=("not None")
```
代码部分:
```python
def __dict_filter(self, **kwargs) -> list[BinaryExpression]:
"""
字典过滤
:param model:
:param kwargs:
"""
conditions = []
for field, value in kwargs.items():
if value is not None and value != "":
attr = getattr(self.model, field)
if isinstance(value, tuple):
if len(value) == 1:
if value[0] == "None":
conditions.append(attr.is_(None))
elif value[0] == "not None":
conditions.append(attr.isnot(None))
else:
raise CustomException("SQL查询语法错误")
elif len(value) == 2 and value[1] not in [None, [], ""]:
if value[0] == "date":
# 根据日期查询, 关键函数是func.time_format和func.date_format
conditions.append(func.date_format(attr, "%Y-%m-%d") == value[1])
elif value[0] == "like":
conditions.append(attr.like(f"%{value[1]}%"))
elif value[0] == "in":
conditions.append(attr.in_(value[1]))
elif value[0] == "between" and len(value[1]) == 2:
conditions.append(attr.between(value[1][0], value[1][1]))
elif value[0] == "month":
conditions.append(func.date_format(attr, "%Y-%m") == value[1])
elif value[0] == "!=":
conditions.append(attr != value[1])
elif value[0] == ">":
conditions.append(attr > value[1])
elif value[0] == "<=":
conditions.append(attr <= value[1])
else:
raise CustomException("SQL查询语法错误")
else:
conditions.append(attr == value)
return conditions
```
示例:
查询所有用户id为1或2或 4或6并且email不为空并且名称包括李
```python
users = UserDal(db).get_datas(limit=0, id=("in", [1,2,4,6]), email=("not None", ), name=("like", "李"))
# limit=0表示返回所有结果数据
# 这里的 get_datas 默认返回的是 pydantic 模型数据
# 如果需要返回用户对象列表,使用如下语句:
users = UserDal(db).get_datas(
limit=0,
id=("in", [1,2,4,6]),
email=("not None", ),
name=("like", "李"),
v_return_objs=True
)
```
查询所有用户id为1或2或 4或6并且email不为空并且名称包括李
查询第一页,每页两条数据,并返回总数,同样可以通过 `get_datas` 实现原始查询方式:
```python
v_where = [VadminUser.id.in_([1,2,4,6]), VadminUser.email.isnot(None), VadminUser.name.like(f"%李%")]
users, count = UserDal(db).get_datas(limit=2, v_where=v_where, v_return_count=True)
# 这里的 get_datas 默认返回的是 pydantic 模型数据
# 如果需要返回用户对象列表,使用如下语句:
users, count = UserDal(db).get_datas(
limit=2,
v_where=v_where,
v_return_count=True
v_return_objs=True
)
```
### 外键查询示例
以常见问题表为主表查询出创建用户名称为kinit的用户创建了哪些常见问题并加载出用户信息
```python
v_options = [joinedload(VadminIssue.create_user)]
v_join = [["create_user"]]
v_where = [VadminUser.name == "kinit"]
datas = await crud.IssueCategoryDal(auth.db).get_datas(
limit=0,
v_options=options,
v_join=v_join,
v_where=v_where,
v_return_objs=True
)
```

108
alembic.ini Normal file
View File

@ -0,0 +1,108 @@
# A generic, single database configuration.
[DEFAULT]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator"
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. Valid values are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
# mysql+pymysql://username:password@host:post/name
[dev]
version_locations = %(here)s/alembic/versions_dev
sqlalchemy.url = mysql+pymysql://root:root@127.0.0.1:3306/kinit
[pro]
version_locations = %(here)s/alembic/versions_pro
sqlalchemy.url = mysql+pymysql://root:123456@177.8.0.7:3306/kinit
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

1
alembic/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

95
alembic/env.py Normal file
View File

@ -0,0 +1,95 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import os
import sys
from core.database import Base
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# target_metadata = None
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
# 添加当前项目路径到环境变量
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
# 导入项目中的基本映射类,与 需要迁移的 ORM 模型
from apps.vadmin.auth.models import *
from apps.vadmin.system.models import *
from apps.vadmin.record.models import *
from apps.vadmin.help.models import *
from apps.vadmin.resource.models import *
from apps.business.project.models.project import *
from apps.business.train.models.train import *
from apps.business.detect.models.detect import *
# 修改配置中的参数
target_metadata = Base.metadata
def run_migrations_offline():
"""
脱机模式运行迁移
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True, # 是否检查字段类型,字段长度
compare_server_default=True # 是否比较在数据库中的默认值
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""
在线模式运行迁移
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True, # 是否检查字段类型,字段长度
compare_server_default=True # 是否比较在数据库中的默认值
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
print("offline")
run_migrations_offline()
else:
print("online")
run_migrations_online()

24
alembic/script.py.mako Normal file
View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

View File

@ -0,0 +1,468 @@
"""3.10.1
Revision ID: 655d3fa2c68d
Revises: e2dec87a3d12
Create Date: 2025-04-03 10:19:06.323166
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '655d3fa2c68d'
down_revision = 'e2dec87a3d12'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('project_detect',
sa.Column('project_id', sa.Integer(), nullable=False),
sa.Column('detect_name', sa.String(length=64), nullable=False),
sa.Column('detect_version', sa.Integer(), nullable=False),
sa.Column('detect_no', sa.String(length=32), nullable=False),
sa.Column('detect_status', sa.Integer(), nullable=False),
sa.Column('file_type', sa.String(length=10), nullable=False),
sa.Column('folder_url', sa.String(length=255), nullable=False),
sa.Column('rtsp_url', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='项目推理集合'
)
op.create_table('project_detect_img',
sa.Column('detect_id', sa.Integer(), nullable=False),
sa.Column('file_name', sa.String(length=64), nullable=False),
sa.Column('image_url', sa.String(length=255), nullable=False),
sa.Column('thumb_image_url', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='待推理图片'
)
op.create_table('project_detect_log',
sa.Column('detect_id', sa.Integer(), nullable=False),
sa.Column('detect_version', sa.String(length=10), nullable=False),
sa.Column('detect_name', sa.String(length=64), nullable=False),
sa.Column('train_id', sa.Integer(), nullable=False),
sa.Column('train_version', sa.String(length=10), nullable=False),
sa.Column('pt_type', sa.String(length=10), nullable=False),
sa.Column('pt_url', sa.String(length=255), nullable=False),
sa.Column('folder_url', sa.String(length=255), nullable=False),
sa.Column('detect_folder_url', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='项目推理记录'
)
op.create_table('project_detect_log_img',
sa.Column('log_id', sa.Integer(), nullable=False),
sa.Column('file_name', sa.String(length=64), nullable=False),
sa.Column('image_url', sa.String(length=255), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='项目训练版本信息表'
)
op.create_table('project_image',
sa.Column('img_type', sa.String(length=10), nullable=False),
sa.Column('file_name', sa.String(length=64), nullable=False),
sa.Column('image_url', sa.String(length=255), nullable=False),
sa.Column('thumb_image_url', sa.String(length=255), nullable=False),
sa.Column('project_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='项目图片表'
)
op.create_table('project_img_label',
sa.Column('image_id', sa.Integer(), nullable=False),
sa.Column('label_id', sa.Integer(), nullable=False),
sa.Column('mark_center_x', sa.String(length=64), nullable=False),
sa.Column('mark_center_y', sa.String(length=64), nullable=False),
sa.Column('mark_width', sa.String(length=64), nullable=False),
sa.Column('mark_height', sa.String(length=64), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='项目图片标签对应表一张图片对应多个label'
)
op.create_table('project_img_leafer',
sa.Column('image_id', sa.Integer(), nullable=False),
sa.Column('leafer', sa.JSON(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='项目图片leafer表'
)
op.create_table('project_info',
sa.Column('project_no', sa.String(length=32), nullable=False),
sa.Column('project_name', sa.String(length=32), nullable=False),
sa.Column('type_code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=255), nullable=False),
sa.Column('project_status', sa.String(length=10), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('dept_id', sa.Integer(), nullable=False),
sa.Column('train_version', sa.Integer(), nullable=False),
sa.Column('del_flag', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('project_name'),
sa.UniqueConstraint('project_no'),
comment='项目类别表 - 标识项目的类型目前存在的目标识别OCR识别瑕疵检测图像分类'
)
op.create_table('project_label',
sa.Column('label_name', sa.String(length=32), nullable=False),
sa.Column('project_id', sa.Integer(), nullable=False),
sa.Column('meta', sa.JSON(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('label_name'),
comment='项目标签表'
)
op.create_table('project_train',
sa.Column('project_id', sa.Integer(), nullable=False),
sa.Column('train_version', sa.String(length=32), nullable=False),
sa.Column('train_url', sa.String(length=255), nullable=False),
sa.Column('train_data', sa.String(length=255), nullable=False),
sa.Column('weights_id', sa.Integer(), nullable=False),
sa.Column('weights_name', sa.String(length=32), nullable=False),
sa.Column('epochs', sa.Integer(), nullable=False),
sa.Column('patience', sa.Integer(), nullable=False),
sa.Column('best_pt', sa.String(length=255), nullable=False),
sa.Column('last_pt', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='项目训练版本信息表'
)
op.create_table('project_type',
sa.Column('type_code', sa.String(length=20), nullable=False),
sa.Column('type_name', sa.String(length=20), nullable=False),
sa.Column('icon_path', sa.String(length=255), nullable=False),
sa.Column('description', sa.String(length=255), nullable=False),
sa.Column('type_status', sa.String(length=10), nullable=False),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('type_code'),
comment='项目类别表 - 标识项目的类型目前存在的目标识别OCR识别瑕疵检测图像分类'
)
op.alter_column('vadmin_auth_dept', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_dept', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings_tab', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings_tab', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('vadmin_system_settings_tab', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings_tab', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_dept', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_dept', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.drop_table('project_type')
op.drop_table('project_train')
op.drop_table('project_label')
op.drop_table('project_info')
op.drop_table('project_img_leafer')
op.drop_table('project_img_label')
op.drop_table('project_image')
op.drop_table('project_detect_log_img')
op.drop_table('project_detect_log')
op.drop_table('project_detect_img')
op.drop_table('project_detect')
# ### end Alembic commands ###

View File

@ -0,0 +1,335 @@
"""3.10.1
Revision ID: df17d7155cb6
Revises:
Create Date: 2025-04-03 09:36:50.773434
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'df17d7155cb6'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('vadmin_auth_dept',
sa.Column('name', sa.String(length=50), nullable=False, comment='部门名称'),
sa.Column('dept_key', sa.String(length=50), nullable=False, comment='部门标识'),
sa.Column('disabled', sa.Boolean(), nullable=False, comment='是否禁用'),
sa.Column('order', sa.Integer(), nullable=True, comment='显示排序'),
sa.Column('desc', sa.String(length=255), nullable=True, comment='描述'),
sa.Column('owner', sa.String(length=255), nullable=True, comment='负责人'),
sa.Column('phone', sa.String(length=255), nullable=True, comment='联系电话'),
sa.Column('email', sa.String(length=255), nullable=True, comment='邮箱'),
sa.Column('parent_id', sa.Integer(), nullable=True, comment='上级部门'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['parent_id'], ['vadmin_auth_dept.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
comment='部门表'
)
op.create_index(op.f('ix_vadmin_auth_dept_dept_key'), 'vadmin_auth_dept', ['dept_key'], unique=False)
op.create_index(op.f('ix_vadmin_auth_dept_name'), 'vadmin_auth_dept', ['name'], unique=False)
op.create_table('vadmin_auth_menu',
sa.Column('title', sa.String(length=50), nullable=False, comment='名称'),
sa.Column('icon', sa.String(length=50), nullable=True, comment='菜单图标'),
sa.Column('redirect', sa.String(length=100), nullable=True, comment='重定向地址'),
sa.Column('component', sa.String(length=255), nullable=True, comment='前端组件地址'),
sa.Column('path', sa.String(length=50), nullable=True, comment='前端路由地址'),
sa.Column('disabled', sa.Boolean(), nullable=False, comment='是否禁用'),
sa.Column('hidden', sa.Boolean(), nullable=False, comment='是否隐藏'),
sa.Column('order', sa.Integer(), nullable=False, comment='排序'),
sa.Column('menu_type', sa.String(length=8), nullable=False, comment='菜单类型'),
sa.Column('parent_id', sa.Integer(), nullable=True, comment='父菜单'),
sa.Column('perms', sa.String(length=50), nullable=True, comment='权限标识'),
sa.Column('noCache', sa.Boolean(), nullable=False, comment='如果设置为true则不会被 <keep-alive> 缓存(默认 false)'),
sa.Column('breadcrumb', sa.Boolean(), nullable=False, comment='如果设置为false则不会在breadcrumb面包屑中显示(默认 true)'),
sa.Column('affix', sa.Boolean(), nullable=False, comment='如果设置为true则会一直固定在tag项中(默认 false)'),
sa.Column('noTagsView', sa.Boolean(), nullable=False, comment='如果设置为true则不会出现在tag中(默认 false)'),
sa.Column('canTo', sa.Boolean(), nullable=False, comment='设置为true即使hidden为true也依然可以进行路由跳转(默认 false)'),
sa.Column('alwaysShow', sa.Boolean(), nullable=False, comment='当你一个路由下面的 children 声明的路由大于1个时自动会变成嵌套的模式\n 只有一个时,会将那个子路由当做根路由显示在侧边栏,若你想不管路由下面的 children 声明的个数都显示你的根路由,\n 你可以设置 alwaysShow: true这样它就会忽略之前定义的规则一直显示根路由(默认 true)'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['parent_id'], ['vadmin_auth_menu.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
comment='菜单表'
)
op.create_index(op.f('ix_vadmin_auth_menu_perms'), 'vadmin_auth_menu', ['perms'], unique=False)
op.create_table('vadmin_auth_role',
sa.Column('name', sa.String(length=50), nullable=False, comment='名称'),
sa.Column('role_key', sa.String(length=50), nullable=False, comment='权限字符'),
sa.Column('data_range', sa.Integer(), nullable=False, comment='数据权限范围'),
sa.Column('disabled', sa.Boolean(), nullable=False, comment='是否禁用'),
sa.Column('order', sa.Integer(), nullable=True, comment='排序'),
sa.Column('desc', sa.String(length=255), nullable=True, comment='描述'),
sa.Column('is_admin', sa.Boolean(), nullable=False, comment='是否为超级角色'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='角色表'
)
op.create_index(op.f('ix_vadmin_auth_role_name'), 'vadmin_auth_role', ['name'], unique=False)
op.create_index(op.f('ix_vadmin_auth_role_role_key'), 'vadmin_auth_role', ['role_key'], unique=False)
op.create_table('vadmin_auth_user',
sa.Column('avatar', sa.String(length=500), nullable=True, comment='头像'),
sa.Column('telephone', sa.String(length=11), nullable=False, comment='手机号'),
sa.Column('email', sa.String(length=50), nullable=True, comment='邮箱地址'),
sa.Column('name', sa.String(length=50), nullable=False, comment='姓名'),
sa.Column('nickname', sa.String(length=50), nullable=True, comment='昵称'),
sa.Column('password', sa.String(length=255), nullable=True, comment='密码'),
sa.Column('gender', sa.String(length=8), nullable=True, comment='性别'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否可用'),
sa.Column('is_reset_password', sa.Boolean(), nullable=False, comment='是否已经重置密码,没有重置的,登陆系统后必须重置密码'),
sa.Column('last_ip', sa.String(length=50), nullable=True, comment='最后一次登录IP'),
sa.Column('last_login', sa.DateTime(), nullable=True, comment='最近一次登录时间'),
sa.Column('is_staff', sa.Boolean(), nullable=False, comment='是否为工作人员'),
sa.Column('wx_server_openid', sa.String(length=255), nullable=True, comment='服务端微信平台openid'),
sa.Column('is_wx_server_openid', sa.Boolean(), nullable=False, comment='是否已有服务端微信平台openid'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='用户表'
)
op.create_index(op.f('ix_vadmin_auth_user_name'), 'vadmin_auth_user', ['name'], unique=False)
op.create_index(op.f('ix_vadmin_auth_user_telephone'), 'vadmin_auth_user', ['telephone'], unique=False)
op.create_table('vadmin_record_login',
sa.Column('telephone', sa.String(length=255), nullable=False, comment='手机号'),
sa.Column('status', sa.Boolean(), nullable=False, comment='是否登录成功'),
sa.Column('platform', sa.String(length=8), nullable=False, comment='登陆平台'),
sa.Column('login_method', sa.String(length=8), nullable=False, comment='认证方式'),
sa.Column('ip', sa.String(length=50), nullable=True, comment='登陆地址'),
sa.Column('address', sa.String(length=255), nullable=True, comment='登陆地点'),
sa.Column('country', sa.String(length=255), nullable=True, comment='国家'),
sa.Column('province', sa.String(length=255), nullable=True, comment=''),
sa.Column('city', sa.String(length=255), nullable=True, comment='城市'),
sa.Column('county', sa.String(length=255), nullable=True, comment='区/县'),
sa.Column('operator', sa.String(length=255), nullable=True, comment='运营商'),
sa.Column('postal_code', sa.String(length=255), nullable=True, comment='邮政编码'),
sa.Column('area_code', sa.String(length=255), nullable=True, comment='地区区号'),
sa.Column('browser', sa.String(length=50), nullable=True, comment='浏览器'),
sa.Column('system', sa.String(length=50), nullable=True, comment='操作系统'),
sa.Column('response', sa.Text(), nullable=True, comment='响应信息'),
sa.Column('request', sa.Text(), nullable=True, comment='请求信息'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='登录记录表'
)
op.create_index(op.f('ix_vadmin_record_login_telephone'), 'vadmin_record_login', ['telephone'], unique=False)
op.create_table('vadmin_system_dict_type',
sa.Column('dict_name', sa.String(length=50), nullable=False, comment='字典名称'),
sa.Column('dict_type', sa.String(length=50), nullable=False, comment='字典类型'),
sa.Column('disabled', sa.Boolean(), nullable=False, comment='字典状态,是否禁用'),
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='字典类型表'
)
op.create_index(op.f('ix_vadmin_system_dict_type_dict_name'), 'vadmin_system_dict_type', ['dict_name'], unique=False)
op.create_index(op.f('ix_vadmin_system_dict_type_dict_type'), 'vadmin_system_dict_type', ['dict_type'], unique=False)
op.create_table('vadmin_system_settings_tab',
sa.Column('title', sa.String(length=255), nullable=False, comment='标题'),
sa.Column('classify', sa.String(length=255), nullable=False, comment='分类键'),
sa.Column('tab_label', sa.String(length=255), nullable=False, comment='tab标题'),
sa.Column('tab_name', sa.String(length=255), nullable=False, comment='tab标识符'),
sa.Column('hidden', sa.Boolean(), nullable=False, comment='是否隐藏'),
sa.Column('disabled', sa.Boolean(), nullable=False, comment='是否禁用'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.PrimaryKeyConstraint('id'),
comment='系统配置分类表'
)
op.create_index(op.f('ix_vadmin_system_settings_tab_classify'), 'vadmin_system_settings_tab', ['classify'], unique=False)
op.create_index(op.f('ix_vadmin_system_settings_tab_tab_name'), 'vadmin_system_settings_tab', ['tab_name'], unique=True)
op.create_table('vadmin_auth_role_depts',
sa.Column('role_id', sa.Integer(), nullable=True),
sa.Column('dept_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['dept_id'], ['vadmin_auth_dept.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['role_id'], ['vadmin_auth_role.id'], ondelete='CASCADE')
)
op.create_table('vadmin_auth_role_menus',
sa.Column('role_id', sa.Integer(), nullable=True),
sa.Column('menu_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['menu_id'], ['vadmin_auth_menu.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['role_id'], ['vadmin_auth_role.id'], ondelete='CASCADE')
)
op.create_table('vadmin_auth_user_depts',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('dept_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['dept_id'], ['vadmin_auth_dept.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['vadmin_auth_user.id'], ondelete='CASCADE')
)
op.create_table('vadmin_auth_user_roles',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['vadmin_auth_role.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['vadmin_auth_user.id'], ondelete='CASCADE')
)
op.create_table('vadmin_help_issue_category',
sa.Column('name', sa.String(length=50), nullable=False, comment='类别名称'),
sa.Column('platform', sa.String(length=8), nullable=False, comment='展示平台'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否可见'),
sa.Column('create_user_id', sa.Integer(), nullable=False, comment='创建人'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['create_user_id'], ['vadmin_auth_user.id'], ondelete='RESTRICT'),
sa.PrimaryKeyConstraint('id'),
comment='常见问题类别表'
)
op.create_index(op.f('ix_vadmin_help_issue_category_name'), 'vadmin_help_issue_category', ['name'], unique=False)
op.create_index(op.f('ix_vadmin_help_issue_category_platform'), 'vadmin_help_issue_category', ['platform'], unique=False)
op.create_table('vadmin_record_sms_send',
sa.Column('user_id', sa.Integer(), nullable=False, comment='操作人'),
sa.Column('status', sa.Boolean(), nullable=False, comment='发送状态'),
sa.Column('content', sa.String(length=255), nullable=False, comment='发送内容'),
sa.Column('telephone', sa.String(length=11), nullable=False, comment='目标手机号'),
sa.Column('desc', sa.String(length=255), nullable=True, comment='失败描述'),
sa.Column('scene', sa.String(length=50), nullable=True, comment='发送场景'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['user_id'], ['vadmin_auth_user.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
comment='短信发送记录表'
)
op.create_table('vadmin_resource_images',
sa.Column('filename', sa.String(length=255), nullable=False, comment='原图片名称'),
sa.Column('image_url', sa.String(length=500), nullable=False, comment='图片链接'),
sa.Column('create_user_id', sa.Integer(), nullable=False, comment='创建人'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['create_user_id'], ['vadmin_auth_user.id'], ondelete='RESTRICT'),
sa.PrimaryKeyConstraint('id'),
comment='图片素材表'
)
op.create_table('vadmin_system_dict_details',
sa.Column('label', sa.String(length=50), nullable=False, comment='字典标签'),
sa.Column('value', sa.String(length=50), nullable=False, comment='字典键值'),
sa.Column('disabled', sa.Boolean(), nullable=False, comment='字典状态,是否禁用'),
sa.Column('is_default', sa.Boolean(), nullable=False, comment='是否默认'),
sa.Column('order', sa.Integer(), nullable=False, comment='字典排序'),
sa.Column('dict_type_id', sa.Integer(), nullable=False, comment='关联字典类型'),
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['dict_type_id'], ['vadmin_system_dict_type.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
comment='字典详情表'
)
op.create_index(op.f('ix_vadmin_system_dict_details_label'), 'vadmin_system_dict_details', ['label'], unique=False)
op.create_index(op.f('ix_vadmin_system_dict_details_value'), 'vadmin_system_dict_details', ['value'], unique=False)
op.create_table('vadmin_system_settings',
sa.Column('config_label', sa.String(length=255), nullable=False, comment='配置表标签'),
sa.Column('config_key', sa.String(length=255), nullable=False, comment='配置表键'),
sa.Column('config_value', sa.Text(), nullable=True, comment='配置表内容'),
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注信息'),
sa.Column('disabled', sa.Boolean(), nullable=False, comment='是否禁用'),
sa.Column('tab_id', sa.Integer(), nullable=False, comment='关联tab标签'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['tab_id'], ['vadmin_system_settings_tab.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
comment='系统配置表'
)
op.create_index(op.f('ix_vadmin_system_settings_config_key'), 'vadmin_system_settings', ['config_key'], unique=True)
op.create_table('vadmin_help_issue',
sa.Column('category_id', sa.Integer(), nullable=False, comment='类别'),
sa.Column('title', sa.String(length=255), nullable=False, comment='标题'),
sa.Column('content', sa.Text(), nullable=False, comment='内容'),
sa.Column('view_number', sa.Integer(), nullable=False, comment='查看次数'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否可见'),
sa.Column('create_user_id', sa.Integer(), nullable=False, comment='创建人'),
sa.Column('id', sa.Integer(), nullable=False, comment='主键ID'),
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='创建时间'),
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False, comment='更新时间'),
sa.Column('delete_datetime', sa.DateTime(), nullable=True, comment='删除时间'),
sa.Column('is_delete', sa.Boolean(), nullable=False, comment='是否软删除'),
sa.ForeignKeyConstraint(['category_id'], ['vadmin_help_issue_category.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['create_user_id'], ['vadmin_auth_user.id'], ondelete='RESTRICT'),
sa.PrimaryKeyConstraint('id'),
comment='常见问题记录表'
)
op.create_index(op.f('ix_vadmin_help_issue_title'), 'vadmin_help_issue', ['title'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_vadmin_help_issue_title'), table_name='vadmin_help_issue')
op.drop_table('vadmin_help_issue')
op.drop_index(op.f('ix_vadmin_system_settings_config_key'), table_name='vadmin_system_settings')
op.drop_table('vadmin_system_settings')
op.drop_index(op.f('ix_vadmin_system_dict_details_value'), table_name='vadmin_system_dict_details')
op.drop_index(op.f('ix_vadmin_system_dict_details_label'), table_name='vadmin_system_dict_details')
op.drop_table('vadmin_system_dict_details')
op.drop_table('vadmin_resource_images')
op.drop_table('vadmin_record_sms_send')
op.drop_index(op.f('ix_vadmin_help_issue_category_platform'), table_name='vadmin_help_issue_category')
op.drop_index(op.f('ix_vadmin_help_issue_category_name'), table_name='vadmin_help_issue_category')
op.drop_table('vadmin_help_issue_category')
op.drop_table('vadmin_auth_user_roles')
op.drop_table('vadmin_auth_user_depts')
op.drop_table('vadmin_auth_role_menus')
op.drop_table('vadmin_auth_role_depts')
op.drop_index(op.f('ix_vadmin_system_settings_tab_tab_name'), table_name='vadmin_system_settings_tab')
op.drop_index(op.f('ix_vadmin_system_settings_tab_classify'), table_name='vadmin_system_settings_tab')
op.drop_table('vadmin_system_settings_tab')
op.drop_index(op.f('ix_vadmin_system_dict_type_dict_type'), table_name='vadmin_system_dict_type')
op.drop_index(op.f('ix_vadmin_system_dict_type_dict_name'), table_name='vadmin_system_dict_type')
op.drop_table('vadmin_system_dict_type')
op.drop_index(op.f('ix_vadmin_record_login_telephone'), table_name='vadmin_record_login')
op.drop_table('vadmin_record_login')
op.drop_index(op.f('ix_vadmin_auth_user_telephone'), table_name='vadmin_auth_user')
op.drop_index(op.f('ix_vadmin_auth_user_name'), table_name='vadmin_auth_user')
op.drop_table('vadmin_auth_user')
op.drop_index(op.f('ix_vadmin_auth_role_role_key'), table_name='vadmin_auth_role')
op.drop_index(op.f('ix_vadmin_auth_role_name'), table_name='vadmin_auth_role')
op.drop_table('vadmin_auth_role')
op.drop_index(op.f('ix_vadmin_auth_menu_perms'), table_name='vadmin_auth_menu')
op.drop_table('vadmin_auth_menu')
op.drop_index(op.f('ix_vadmin_auth_dept_name'), table_name='vadmin_auth_dept')
op.drop_index(op.f('ix_vadmin_auth_dept_dept_key'), table_name='vadmin_auth_dept')
op.drop_table('vadmin_auth_dept')
# ### end Alembic commands ###

View File

@ -0,0 +1,286 @@
"""3.10.1
Revision ID: e2dec87a3d12
Revises: df17d7155cb6
Create Date: 2025-04-03 10:15:27.587731
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'e2dec87a3d12'
down_revision = 'df17d7155cb6'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('vadmin_auth_dept', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_dept', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings_tab', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings_tab', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('now()'),
existing_comment='更新时间',
existing_nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('vadmin_system_settings_tab', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings_tab', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_settings', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_type', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_system_dict_details', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_resource_images', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_sms_send', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_record_login', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue_category', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_help_issue', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_user', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_role', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_menu', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
op.alter_column('vadmin_auth_dept', 'update_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='更新时间',
existing_nullable=False)
op.alter_column('vadmin_auth_dept', 'create_datetime',
existing_type=mysql.DATETIME(),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_comment='创建时间',
existing_nullable=False)
# ### end Alembic commands ###

View File

0
application/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/19 15:47
# @File : development.py
# @IDE : PyCharm
# @desc : 数据库生产配置文件
"""
Mysql 数据库配置项
连接引擎官方文档https://www.osgeo.cn/sqlalchemy/core/engines.html
数据库链接配置说明mysql+asyncmy://数据库用户名:数据库密码@数据库地址:数据库端口/数据库名称
"""
SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:root@127.0.0.1:3306/kinit"
"""
Redis 数据库配置
格式"redis://:密码@地址:端口/数据库名称"
"""
REDIS_DB_ENABLE = True
REDIS_DB_URL = "redis://:sdust2020@127.0.0.1:6379/0"
"""
MongoDB 数据库配置
格式mongodb://用户名:密码@地址:端口/?authSource=数据库名称
"""
MONGO_DB_ENABLE = False
MONGO_DB_NAME = "kinit"
MONGO_DB_URL = f"mongodb://kinit:123456@177.8.0.6:27017/?authSource={MONGO_DB_NAME}"
"""
阿里云对象存储OSS配置
阿里云账号AccessKey拥有所有API的访问权限风险很高强烈建议您创建并使用RAM用户进行API访问或日常运维请登录RAM控制台创建RAM用户
yourEndpoint填写Bucket所在地域对应的Endpoint以华东1杭州为例Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com
* [accessKeyId] {String}通过阿里云控制台创建的AccessKey
* [accessKeySecret] {String}通过阿里云控制台创建的AccessSecret
* [bucket] {String}通过控制台或PutBucket创建的bucket
* [endpoint] {String}bucket所在的区域 默认oss-cn-hangzhou
"""
ALIYUN_OSS = {
"accessKeyId": "accessKeyId",
"accessKeySecret": "accessKeySecret",
"endpoint": "endpoint",
"bucket": "bucket",
"baseUrl": "baseUrl"
}
"""
获取IP地址归属地
文档https://user.ip138.com/ip/doc
"""
IP_PARSE_ENABLE = False
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
datasets_url = f'D:\syg\yolov5\datasets'
runs_url = f'D:\syg\yolov5\runs'
detect_url = f'D:\syg\yolov5\detect'
yolo_url = f'D:\syg\workspace\aicheckv2\yolov5'
images_url = f'D:\syg\images'

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/19 15:47
# @File : production.py
# @IDE : PyCharm
# @desc : 数据库开发配置文件
"""
Mysql 数据库配置项
连接引擎官方文档https://www.osgeo.cn/sqlalchemy/core/engines.html
数据库链接配置说明mysql+asyncmy://数据库用户名:数据库密码@数据库地址:数据库端口/数据库名称
"""
SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:123456@177.8.0.7:3306/kinit"
"""
Redis 数据库配置
格式"redis://:密码@地址:端口/数据库名称"
"""
REDIS_DB_ENABLE = True
REDIS_DB_URL = "redis://:123456@177.8.0.5:6379/1"
"""
MongoDB 数据库配置
格式mongodb://用户名:密码@地址:端口/?authSource=数据库名称
"""
MONGO_DB_ENABLE = True
MONGO_DB_NAME = "kinit"
MONGO_DB_URL = f"mongodb://kinit:123456@177.8.0.6:27017/?authSource={MONGO_DB_NAME}"
"""
阿里云对象存储OSS配置
阿里云账号AccessKey拥有所有API的访问权限风险很高强烈建议您创建并使用RAM用户进行API访问或日常运维请登录RAM控制台创建RAM用户
yourEndpoint填写Bucket所在地域对应的Endpoint以华东1杭州为例Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com
* [accessKeyId] {String}通过阿里云控制台创建的AccessKey
* [accessKeySecret] {String}通过阿里云控制台创建的AccessSecret
* [bucket] {String}通过控制台或PutBucket创建的bucket
* [endpoint] {String}bucket所在的区域 默认oss-cn-hangzhou
"""
ALIYUN_OSS = {
"accessKeyId": "accessKeyId",
"accessKeySecret": "accessKeySecret",
"endpoint": "endpoint",
"bucket": "bucket",
"baseUrl": "baseUrl"
}
"""
获取IP地址归属地
文档https://user.ip138.com/ip/doc
"""
IP_PARSE_ENABLE = False
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
datasets_url = f'/home/aicheckv2/yolov5/datasets'
runs_url = f'/home/aicheckv2/yolov5/runs'
detect_url = f'/home/aicheckv2/yolov5/detect'
yolo_url = f'/home/aicheckv2/backend/yolov5'
images_url = f'/home/aicheckv2/images'

150
application/settings.py Normal file
View File

@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/19 15:47
# @File : settings.py
# @IDE : PyCharm
# @desc : 主配置文件
import os
from fastapi.security import OAuth2PasswordBearer
"""
系统版本
"""
VERSION = "3.10.1"
"""安全警告: 不要在生产中打开调试运行!"""
DEBUG = True
"""是否开启演示功能取消所有POST,DELETE,PUT操作权限"""
DEMO = False
"""演示功能白名单"""
DEMO_WHITE_LIST_PATH = [
"/auth/login",
"/auth/token/refresh",
"/auth/wx/login",
"/vadmin/system/dict/types/details",
"/vadmin/system/settings/tabs",
"/vadmin/resource/images",
"/vadmin/auth/user/export/query/list/to/excel"
]
"""演示功能黑名单(触发异常 status_code=403黑名单优先级更高"""
DEMO_BLACK_LIST_PATH = [
"/auth/api/login"
]
"""
引入数据库配置
"""
if DEBUG:
from application.config.development import *
else:
from application.config.production import *
"""项目根目录"""
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
"""
是否开启登录认证
只适用于简单的接口
如果是与认证关联性比较强的接口则无法使用
"""
OAUTH_ENABLE = True
"""
配置 OAuth2 密码流认证方式
官方文档https://fastapi.tiangolo.com/zh/tutorial/security/first-steps/#fastapi-oauth2passwordbearer
auto_error:(bool) 可选参数默认为 True当验证失败时如果设置为 TrueFastAPI 将自动返回一个 401 未授权的响应如果设置为 False你需要自己处理身份验证失败的情况
这里的 auto_error 设置为 False 是因为存在 OpenAuth开放认证无认证也可以访问
如果设置为 True那么 FastAPI 会自动报错即无认证时 OpenAuth 会失效所以不能使用 True
"""
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/api/login", auto_error=False) if OAUTH_ENABLE else lambda: ""
"""安全的随机密钥,该密钥将用于对 JWT 令牌进行签名"""
SECRET_KEY = 'vgb0tnl9d58+6n-6h-ea&u^1#s0ccp!794=kbvqacjq75vzps$'
"""用于设定 JWT 令牌签名算法"""
ALGORITHM = "HS256"
"""access_token 过期时间,一天"""
ACCESS_TOKEN_EXPIRE_MINUTES = 1440
"""refresh_token 过期时间用于刷新token使用两天"""
REFRESH_TOKEN_EXPIRE_MINUTES = 1440 * 2
"""access_token 缓存时间用于刷新token使用30分钟"""
ACCESS_TOKEN_CACHE_MINUTES = 30
"""
挂载临时文件目录并添加路由访问此路由不会在接口文档中显示
TEMP_DIR临时文件目录绝对路径
官方文档https://fastapi.tiangolo.com/tutorial/static-files/
"""
TEMP_DIR = os.path.join(BASE_DIR, "temp")
"""
挂载静态目录并添加路由访问此路由不会在接口文档中显示
STATIC_ENABLE是否启用静态目录访问
STATIC_URL路由访问
STATIC_ROOT静态文件目录绝对路径
官方文档https://fastapi.tiangolo.com/tutorial/static-files/
"""
STATIC_ENABLE = True
STATIC_URL = "/media"
STATIC_DIR = "static"
STATIC_ROOT = os.path.join(BASE_DIR, STATIC_DIR)
"""
跨域解决
详细解释https://cloud.tencent.com/developer/article/1886114
官方文档https://fastapi.tiangolo.com/tutorial/cors/
"""
# 是否启用跨域
CORS_ORIGIN_ENABLE = True
# 只允许访问的域名列表,* 代表所有
ALLOW_ORIGINS = ["*"]
# 是否支持携带 cookie
ALLOW_CREDENTIALS = True
# 设置允许跨域的http方法比如 get、post、put等。
ALLOW_METHODS = ["*"]
# 允许携带的headers可以用来鉴别来源等作用。
ALLOW_HEADERS = ["*"]
"""
全局事件配置
"""
EVENTS = [
"core.event.connect_mongo" if MONGO_DB_ENABLE else None,
"core.event.connect_redis" if REDIS_DB_ENABLE else None,
]
"""
其他项目配置
"""
# 默认密码,"0" 默认为手机号后六位
DEFAULT_PASSWORD = "0"
# 默认头像
DEFAULT_AVATAR = "https://vv-reserve.oss-cn-hangzhou.aliyuncs.com/avatar/2023-01-27/1674820804e81e7631.png"
# 默认登陆时最大输入密码或验证码错误次数
DEFAULT_AUTH_ERROR_MAX_NUMBER = 5
# 是否开启保存登录日志
LOGIN_LOG_RECORD = True
# 是否开启保存每次请求日志到本地
REQUEST_LOG_RECORD = True
# 是否开启每次操作日志记录到MongoDB数据库
OPERATION_LOG_RECORD = True
# 只记录包括的请求方式操作到MongoDB数据库
OPERATION_RECORD_METHOD = ["POST", "PUT", "DELETE"]
# 忽略的操作接口函数名称,列表中的函数名称不会被记录到操作日志中
IGNORE_OPERATION_FUNCTION = ["post_dicts_details"]
"""
中间件配置
"""
MIDDLEWARES = [
"core.middleware.register_request_log_middleware" if REQUEST_LOG_RECORD else None,
"core.middleware.register_operation_record_middleware" if OPERATION_LOG_RECORD and MONGO_DB_ENABLE else None,
"core.middleware.register_demo_env_middleware" if DEMO else None,
"core.middleware.register_jwt_refresh_middleware"
]
"""
定时任务配置
"""
# 发布/订阅通道,与定时任务程序相互关联,请勿随意更改
SUBSCRIBE = 'kinit_queue'

30
application/urls.py Normal file
View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/19 15:47
# @File : urls.py
# @IDE : PyCharm
# @desc : 路由文件
from apps.vadmin.auth.utils.login import app as auth_app
from apps.vadmin.auth.views import app as vadmin_auth_app
from apps.vadmin.system.views import app as vadmin_system_app
from apps.vadmin.record.views import app as vadmin_record_app
from apps.vadmin.workplace.views import app as vadmin_workplace_app
from apps.vadmin.analysis.views import app as vadmin_analysis_app
from apps.vadmin.help.views import app as vadmin_help_app
from apps.vadmin.resource.views import app as vadmin_resource_app
from apps.business.project.views import app as project_app
# 引入应用中的路由
urlpatterns = [
{"ApiRouter": auth_app, "prefix": "/auth", "tags": ["系统认证"]},
{"ApiRouter": vadmin_auth_app, "prefix": "/vadmin/auth", "tags": ["权限管理"]},
{"ApiRouter": vadmin_system_app, "prefix": "/vadmin/system", "tags": ["系统管理"]},
{"ApiRouter": vadmin_record_app, "prefix": "/vadmin/record", "tags": ["记录管理"]},
{"ApiRouter": vadmin_workplace_app, "prefix": "/vadmin/workplace", "tags": ["工作区管理"]},
{"ApiRouter": vadmin_analysis_app, "prefix": "/vadmin/analysis", "tags": ["数据分析管理"]},
{"ApiRouter": vadmin_help_app, "prefix": "/vadmin/help", "tags": ["帮助中心管理"]},
{"ApiRouter": vadmin_resource_app, "prefix": "/vadmin/resource", "tags": ["资源管理"]},
{"ApiRouter": project_app, "prefix": "/business/project", "tags": ["项目管理"]},
]

7
apps/__init__.py Normal file
View File

@ -0,0 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/2/24 10:19
# @File : __init__.py
# @IDE : PyCharm
# @desc : 简要说明

View File

View File

View File

@ -0,0 +1,47 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:30
# @File : crud.py
# @IDE : PyCharm
# @desc : 数据访问层
from sqlalchemy.ext.asyncio import AsyncSession
from core.crud import DalBase
from . import schemas, models
class ProjectDetectDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectDetectDal, self).__init__()
self.db = db
self.model = models.ProjectDetect
self.schema = schemas.ProjectDetectSimpleOut
class ProjectDetectImgDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectDetectImgDal, self).__init__()
self.db = db
self.model = models.ProjectDetectImg
self.schema = schemas.ProjectDetectImgSimpleOut
class ProjectDetectLogDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectDetectLogDal, self).__init__()
self.db = db
self.model = models.ProjectDetectLog
self.schema = schemas.ProjectDetectLogSimpleOut
class ProjectDetectLogImgDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectDetectLogImgDal, self).__init__()
self.db = db
self.model = models.ProjectDetectLogImg
self.schema = schemas.ProjectDetectLogImgSimpleOut

View File

View File

@ -0,0 +1,67 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String, Integer
from db.db_base import BaseModel
class ProjectDetect(BaseModel):
"""
项目推理集合
"""
__tablename__ = "project_detect"
__table_args__ = ({'comment': '项目推理集合'})
project_id: Mapped[int] = mapped_column(Integer, nullable=False)
detect_name: Mapped[str] = mapped_column(String(64), nullable=False)
detect_version: Mapped[int] = mapped_column(Integer)
detect_no: Mapped[str] = mapped_column(String(32))
detect_status: Mapped[int] = mapped_column(Integer)
file_type: Mapped[str] = mapped_column(String(10))
folder_url: Mapped[str] = mapped_column(String(255))
rtsp_url: Mapped[str] = mapped_column(String(255))
user_id: Mapped[int] = mapped_column(Integer, nullable=False)
class ProjectDetectImg(BaseModel):
"""
待推理图片
"""
__tablename__ = "project_detect_img"
__table_args__ = ({'comment': '待推理图片'})
detect_id: Mapped[int] = mapped_column(Integer, nullable=False)
file_name: Mapped[str] = mapped_column(String(64), nullable=False)
image_url: Mapped[str] = mapped_column(String(255), nullable=False)
thumb_image_url: Mapped[str] = mapped_column(String(255), nullable=False)
user_id: Mapped[int] = mapped_column(Integer, nullable=False)
class ProjectDetectLog(BaseModel):
"""
项目推理记录
"""
__tablename__ = "project_detect_log"
__table_args__ = ({'comment': '项目推理记录'})
detect_id: Mapped[int] = mapped_column(Integer, nullable=False)
detect_version: Mapped[str] = mapped_column(String(10))
detect_name: Mapped[str] = mapped_column(String(64), nullable=False)
train_id: Mapped[int] = mapped_column(Integer, nullable=False)
train_version: Mapped[str] = mapped_column(String(10))
pt_type: Mapped[str] = mapped_column(String(10))
pt_url: Mapped[str] = mapped_column(String(255))
folder_url: Mapped[str] = mapped_column(String(255))
detect_folder_url: Mapped[str] = mapped_column(String(255))
user_id: Mapped[int] = mapped_column(Integer, nullable=False)
class ProjectDetectLogImg(BaseModel):
"""
推理完成的图片
"""
__tablename__ = "project_detect_log_img"
__table_args__ = ({'comment': '项目训练版本信息表'})
log_id: Mapped[int] = mapped_column(Integer, nullable=False)
file_name: Mapped[str] = mapped_column(String(64), nullable=False)
image_url: Mapped[str] = mapped_column(String(255), nullable=False)

View File

@ -0,0 +1,4 @@
from .project_detect import ProjectDetectParams
from .project_detect_img import ProjectDetectImgParams
from .project_detect_log import ProjectDetectLogParams
from .project_detect_log_img import ProjectDetectLogImgParams

View File

@ -0,0 +1,15 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:30
# @File : project_detect.py
# @IDE : PyCharm
# @desc : 项目推理集合信息
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class ProjectDetectParams(QueryParams):
def __init__(self, params: Paging = Depends()):
super().__init__(params)

View File

@ -0,0 +1,15 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:30
# @File : project_detect_img.py
# @IDE : PyCharm
# @desc : 项目推理集合图片信息
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class ProjectDetectImgParams(QueryParams):
def __init__(self, params: Paging = Depends()):
super().__init__(params)

View File

@ -0,0 +1,15 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:31
# @File : project_detect_log.py
# @IDE : PyCharm
# @desc : 项目推理记录信息
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class ProjectDetectLogParams(QueryParams):
def __init__(self, params: Paging = Depends()):
super().__init__(params)

View File

@ -0,0 +1,15 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:31
# @File : project_detect_log_img.py
# @IDE : PyCharm
# @desc : 项目推理记录图片信息
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class ProjectDetectLogImgParams(QueryParams):
def __init__(self, params: Paging = Depends()):
super().__init__(params)

View File

@ -0,0 +1,4 @@
from .project_detect import ProjectDetect, ProjectDetectSimpleOut
from .project_detect_img import ProjectDetectImg, ProjectDetectImgSimpleOut
from .project_detect_log import ProjectDetectLog, ProjectDetectLogSimpleOut
from .project_detect_log_img import ProjectDetectLogImg, ProjectDetectLogImgSimpleOut

View File

@ -0,0 +1,30 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:30
# @File : project_detect.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr
class ProjectDetect(BaseModel):
project_id: int = Field(..., title="None")
detect_name: str = Field(..., title="None")
detect_version: int = Field(..., title="None")
detect_no: str = Field(..., title="None")
detect_status: int = Field(..., title="None")
file_type: str = Field(..., title="None")
folder_url: str = Field(..., title="None")
rtsp_url: str = Field(..., title="None")
user_id: int = Field(..., title="None")
class ProjectDetectSimpleOut(ProjectDetect):
model_config = ConfigDict(from_attributes=True)
id: int = Field(..., title="编号")
create_datetime: DatetimeStr = Field(..., title="创建时间")
update_datetime: DatetimeStr = Field(..., title="更新时间")

View File

@ -0,0 +1,26 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:30
# @File : project_detect_img.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr
class ProjectDetectImg(BaseModel):
detect_id: int = Field(..., title="None")
file_name: str = Field(..., title="None")
image_url: str = Field(..., title="None")
thumb_image_url: str = Field(..., title="None")
user_id: int = Field(..., title="None")
class ProjectDetectImgSimpleOut(ProjectDetectImg):
model_config = ConfigDict(from_attributes=True)
id: int = Field(..., title="编号")
create_datetime: DatetimeStr = Field(..., title="创建时间")
update_datetime: DatetimeStr = Field(..., title="更新时间")

View File

@ -0,0 +1,31 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:31
# @File : project_detect_log.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr
class ProjectDetectLog(BaseModel):
detect_id: int = Field(..., title="None")
detect_version: str = Field(..., title="None")
detect_name: str = Field(..., title="None")
train_id: int = Field(..., title="None")
train_version: str = Field(..., title="None")
pt_type: str = Field(..., title="None")
pt_url: str = Field(..., title="None")
folder_url: str = Field(..., title="None")
detect_folder_url: str = Field(..., title="None")
user_id: int = Field(..., title="None")
class ProjectDetectLogSimpleOut(ProjectDetectLog):
model_config = ConfigDict(from_attributes=True)
id: int = Field(..., title="编号")
create_datetime: DatetimeStr = Field(..., title="创建时间")
update_datetime: DatetimeStr = Field(..., title="更新时间")

View File

@ -0,0 +1,24 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:31
# @File : project_detect_log_img.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr
class ProjectDetectLogImg(BaseModel):
log_id: int = Field(..., title="None")
file_name: str = Field(..., title="None")
image_url: str = Field(..., title="None")
class ProjectDetectLogImgSimpleOut(ProjectDetectLogImg):
model_config = ConfigDict(from_attributes=True)
id: int = Field(..., title="编号")
create_datetime: DatetimeStr = Field(..., title="创建时间")
update_datetime: DatetimeStr = Field(..., title="更新时间")

View File

@ -0,0 +1,150 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:30
# @File : views.py
# @IDE : PyCharm
# @desc : 路由,视图文件
from core.dependencies import IdList
from apps.vadmin.auth.utils.validation.auth import Auth
from sqlalchemy.ext.asyncio import AsyncSession
from apps.vadmin.auth.utils.current import AllUserAuth
from core.database import db_getter
from . import schemas, crud, models, params
from fastapi import Depends, APIRouter
from utils.response import SuccessResponse
app = APIRouter()
###########################################################
# 项目推理集合信息
###########################################################
@app.get("/project/detect", summary="获取项目推理集合信息列表", tags=["项目推理集合信息"])
async def get_project_detect_list(p: params.ProjectDetectParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas, count = await crud.ProjectDetectDal(auth.db).get_datas(**p.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.post("/project/detect", summary="创建项目推理集合信息", tags=["项目推理集合信息"])
async def create_project_detect(data: schemas.ProjectDetect, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectDal(auth.db).create_data(data=data))
@app.delete("/project/detect", summary="删除项目推理集合信息", description="硬删除", tags=["项目推理集合信息"])
async def delete_project_detect_list(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.ProjectDetectDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/project/detect/{data_id}", summary="更新项目推理集合信息", tags=["项目推理集合信息"])
async def put_project_detect(data_id: int, data: schemas.ProjectDetect, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectDal(auth.db).put_data(data_id, data))
@app.get("/project/detect/{data_id}", summary="获取项目推理集合信息信息", tags=["项目推理集合信息"])
async def get_project_detect(data_id: int, db: AsyncSession = Depends(db_getter)):
schema = schemas.ProjectDetectSimpleOut
return SuccessResponse(await crud.ProjectDetectDal(db).get_data(data_id, v_schema=schema))
###########################################################
# 项目推理集合图片信息
###########################################################
@app.get("/project/detect/img", summary="获取项目推理集合图片信息列表", tags=["项目推理集合图片信息"])
async def get_project_detect_img_list(p: params.ProjectDetectImgParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas, count = await crud.ProjectDetectImgDal(auth.db).get_datas(**p.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.post("/project/detect/img", summary="创建项目推理集合图片信息", tags=["项目推理集合图片信息"])
async def create_project_detect_img(data: schemas.ProjectDetectImg, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectImgDal(auth.db).create_data(data=data))
@app.delete("/project/detect/img", summary="删除项目推理集合图片信息", description="硬删除", tags=["项目推理集合图片信息"])
async def delete_project_detect_img_list(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.ProjectDetectImgDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/project/detect/img/{data_id}", summary="更新项目推理集合图片信息", tags=["项目推理集合图片信息"])
async def put_project_detect_img(data_id: int, data: schemas.ProjectDetectImg, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectImgDal(auth.db).put_data(data_id, data))
@app.get("/project/detect/img/{data_id}", summary="获取项目推理集合图片信息信息", tags=["项目推理集合图片信息"])
async def get_project_detect_img(data_id: int, db: AsyncSession = Depends(db_getter)):
schema = schemas.ProjectDetectImgSimpleOut
return SuccessResponse(await crud.ProjectDetectImgDal(db).get_data(data_id, v_schema=schema))
###########################################################
# 项目推理记录信息
###########################################################
@app.get("/project/detect/log", summary="获取项目推理记录信息列表", tags=["项目推理记录信息"])
async def get_project_detect_log_list(p: params.ProjectDetectLogParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas, count = await crud.ProjectDetectLogDal(auth.db).get_datas(**p.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.post("/project/detect/log", summary="创建项目推理记录信息", tags=["项目推理记录信息"])
async def create_project_detect_log(data: schemas.ProjectDetectLog, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectLogDal(auth.db).create_data(data=data))
@app.delete("/project/detect/log", summary="删除项目推理记录信息", description="硬删除", tags=["项目推理记录信息"])
async def delete_project_detect_log_list(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.ProjectDetectLogDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/project/detect/log/{data_id}", summary="更新项目推理记录信息", tags=["项目推理记录信息"])
async def put_project_detect_log(data_id: int, data: schemas.ProjectDetectLog, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectLogDal(auth.db).put_data(data_id, data))
@app.get("/project/detect/log/{data_id}", summary="获取项目推理记录信息信息", tags=["项目推理记录信息"])
async def get_project_detect_log(data_id: int, db: AsyncSession = Depends(db_getter)):
schema = schemas.ProjectDetectLogSimpleOut
return SuccessResponse(await crud.ProjectDetectLogDal(db).get_data(data_id, v_schema=schema))
###########################################################
# 项目推理记录图片信息
###########################################################
@app.get("/project/detect/log/img", summary="获取项目推理记录图片信息列表", tags=["项目推理记录图片信息"])
async def get_project_detect_log_img_list(p: params.ProjectDetectLogImgParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas, count = await crud.ProjectDetectLogImgDal(auth.db).get_datas(**p.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.post("/project/detect/log/img", summary="创建项目推理记录图片信息", tags=["项目推理记录图片信息"])
async def create_project_detect_log_img(data: schemas.ProjectDetectLogImg, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectLogImgDal(auth.db).create_data(data=data))
@app.delete("/project/detect/log/img", summary="删除项目推理记录图片信息", description="硬删除", tags=["项目推理记录图片信息"])
async def delete_project_detect_log_img_list(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.ProjectDetectLogImgDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/project/detect/log/img/{data_id}", summary="更新项目推理记录图片信息", tags=["项目推理记录图片信息"])
async def put_project_detect_log_img(data_id: int, data: schemas.ProjectDetectLogImg, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectDetectLogImgDal(auth.db).put_data(data_id, data))
@app.get("/project/detect/log/img/{data_id}", summary="获取项目推理记录图片信息信息", tags=["项目推理记录图片信息"])
async def get_project_detect_log_img(data_id: int, db: AsyncSession = Depends(db_getter)):
schema = schemas.ProjectDetectLogImgSimpleOut
return SuccessResponse(await crud.ProjectDetectLogImgDal(db).get_data(data_id, v_schema=schema))

View File

View File

@ -0,0 +1,156 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:25
# @File : crud.py
# @IDE : PyCharm
# @desc : 数据访问层
import application.settings
from . import schemas, models, params
from apps.vadmin.auth.utils.validation.auth import Auth
from utils import os_utils as os, random_utils as ru
if application.settings.DEBUG:
from application.config.development import datasets_url, runs_url, detect_url, yolo_url, images_url
else:
from application.config.production import datasets_url, runs_url, detect_url, yolo_url, images_url
from typing import Any, List
from core.crud import DalBase
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, case, and_
class ProjectInfoDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectInfoDal, self).__init__()
self.db = db
self.model = models.ProjectInfo
self.schema = schemas.ProjectInfoOut
async def get_project_pager(self, project: params.ProjectInfoParams, auth: Auth):
"""
分页查询项目列表
"""
# 定义子查询
subquery = (
select(
models.ProjectImage.project_id,
func.sum(case((models.ProjectImgLeafer.id.is_(None), 1), else_=0)).label('no_mark_count'),
func.sum(case((models.ProjectImgLeafer.id.isnot(None), 1), else_=0)).label('mark_count')
)
.outerjoin(models.ProjectImgLeafer, models.ProjectImage.id == models.ProjectImgLeafer.image_id)
.group_by(models.ProjectImage.project_id)
.subquery()
)
full_query = select(
models.ProjectInfo,
func.ifnull(subquery.c.mark_count, 0).label("mark_count"),
func.ifnull(subquery.c.no_mark_count, 0).label("no_mark_count")
).select_from(models.ProjectInfo).join(
subquery, models.ProjectInfo.id == subquery.c.project_id, isouter=True
)
v_where = [models.ProjectInfo.is_delete.is_(False)]
if '*' in auth.dept_ids:
v_where.append(models.ProjectInfo.dept_id.isnot(None))
else:
v_where.append(models.ProjectInfo.dept_id.in_(auth.dept_ids))
sql = await self.filter_core(
v_start_sql=full_query,
v_where=v_where,
v_return_sql=True,
v_order=project.v_order,
v_order_field=project.v_order_field
)
count = await self.get_count_sql(sql)
if project.limit != 0:
sql = sql.offset((project.page - 1) * project.limit).limit(project.limit)
queryset = await self.db.execute(sql)
result = queryset.all()
datas = []
for result in result:
data = schemas.ProjectInfoPagerOut.model_validate(result[0])
data.mark_count = int(result[1])
data.no_mark_count = int(result[2])
datas.append(data.model_dump())
return datas, count
async def check_name(self, project_name: str):
"""
校验项目名称是否重名
"""
count = await self.get_count(v_where=[models.ProjectInfo.project_name == project_name,
models.ProjectInfo.is_delete is False])
return count > 0
async def add_project(
self,
project: schemas.ProjectInfoIn,
auth: Auth
) -> Any:
obj = self.model(**project.model_dump())
obj.user_id = auth.user.id
obj.project_no = ru.random_str(6)
obj.project_status = "0"
obj.train_version = 0
obj.user_id = auth.user.id
if '*' in auth.dept_ids:
obj.dept_id = 0
else:
obj.dept_id = auth.dept_ids[0]
os.create_folder(datasets_url, obj.project_no)
os.create_folder(runs_url, obj.project_no)
await self.flush(obj)
return await self.out_dict(obj, None, False, schemas.ProjectInfoOut)
class ProjectImageDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectImageDal, self).__init__()
self.db = db
self.model = models.ProjectImage
self.schema = schemas.ProjectImageSimpleOut
class ProjectLabelDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectLabelDal, self).__init__()
self.db = db
self.model = models.ProjectLabel
self.schema = schemas.ProjectLabel
async def check_label_name(
self,
name: str,
pro_id: int,
label_id: int = None
):
wheres = [
models.ProjectLabel.project_id == pro_id,
models.ProjectLabel.label_name == name
]
if label_id:
wheres.append(models.ProjectLabel.id != label_id)
count = await self.get_count(v_where=wheres)
return count > 0
class ProjectImgLabelDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectImgLabelDal, self).__init__()
self.db = db
self.model = models.ProjectImgLabel
self.schema = schemas.ProjectImgLabelSimpleOut
class ProjectImgLeaferDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectImgLeaferDal, self).__init__()
self.db = db
self.model = models.ProjectImgLeafer
self.schema = schemas.ProjectImgLeaferSimpleOut

View File

@ -0,0 +1 @@
from .project import ProjectLabel, ProjectInfo, ProjectImgLabel, ProjectImgLeafer, ProjectImage

View File

@ -0,0 +1,73 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String, Integer, JSON
from db.db_base import BaseModel
class ProjectInfo(BaseModel):
"""
项目信息表
"""
__tablename__ = "project_info"
__table_args__ = ({'comment': '项目类别表 - 标识项目的类型目前存在的目标识别OCR识别瑕疵检测图像分类'})
project_no: Mapped[str] = mapped_column(String(32), unique=True, nullable=False)
project_name: Mapped[str] = mapped_column(String(32), unique=True, nullable=False)
type_code: Mapped[str] = mapped_column(String(10))
description: Mapped[str] = mapped_column(String(255))
project_status: Mapped[str] = mapped_column(String(10))
user_id: Mapped[int] = mapped_column(Integer)
dept_id: Mapped[int] = mapped_column(Integer)
train_version: Mapped[int] = mapped_column(Integer)
class ProjectLabel(BaseModel):
"""
项目标签表
"""
__tablename__ = "project_label"
__table_args__ = ({'comment': '项目标签表'})
label_name: Mapped[str] = mapped_column(String(32), unique=True, nullable=False)
project_id: Mapped[int] = mapped_column(Integer, nullable=False)
meta: Mapped[dict] = mapped_column(JSON)
class ProjectImage(BaseModel):
"""
项目图片表
"""
__tablename__ = "project_image"
__table_args__ = ({'comment': '项目图片表'})
img_type: Mapped[str] = mapped_column(String(10))
file_name: Mapped[str] = mapped_column(String(64), nullable=False)
image_url: Mapped[str] = mapped_column(String(255), nullable=False)
thumb_image_url: Mapped[str] = mapped_column(String(255), nullable=False)
project_id: Mapped[int] = mapped_column(Integer)
class ProjectImgLeafer(BaseModel):
"""
项目图片leafer表
"""
__tablename__ = "project_img_leafer"
__table_args__ = ({'comment': '项目图片leafer表'})
image_id: Mapped[int] = mapped_column(Integer, nullable=False)
leafer: Mapped[dict] = mapped_column(JSON)
class ProjectImgLabel(BaseModel):
"""
项目图片标签对应表一张图片对应多个label
"""
__tablename__ = "project_img_label"
__table_args__ = ({'comment': '项目图片标签对应表一张图片对应多个label'})
image_id: Mapped[int] = mapped_column(Integer, nullable=False)
label_id: Mapped[int] = mapped_column(Integer, nullable=False)
mark_center_x: Mapped[str] = mapped_column(String(64), nullable=False)
mark_center_y: Mapped[str] = mapped_column(String(64), nullable=False)
mark_width: Mapped[str] = mapped_column(String(64), nullable=False)
mark_height: Mapped[str] = mapped_column(String(64), nullable=False)

View File

@ -0,0 +1,5 @@
from .project_info import ProjectInfoParams
from .project_image import ProjectImageParams
from .project_label import ProjectLabelParams
from .project_img_label import ProjectImgLabelParams
from .project_img_leafer import ProjectImgLeaferParams

View File

@ -0,0 +1,25 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:27
# @File : project_image.py
# @IDE : PyCharm
# @desc : 项目图片信息
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class ProjectImageParams(QueryParams):
def __init__(
self,
img_type: str | None = Query(None, title="图片类别"),
project_id: int | None = Query(None, title="项目id"),
file_name: str | None = Query(None, title="文件名称"),
params: Paging = Depends()
):
super().__init__(params)
self.img_type = img_type
self.project_id = project_id
self.file_name = file_name

View File

@ -0,0 +1,20 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:29
# @File : project_img_label.py
# @IDE : PyCharm
# @desc : 项目图片标签信息
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class ProjectImgLabelParams(QueryParams):
def __init__(
self,
image_id: int | None = Query(None, title="图片id"),
params: Paging = Depends()
):
super().__init__(params)
self.image_id = image_id

View File

@ -0,0 +1,21 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:29
# @File : project_img_leafer.py
# @IDE : PyCharm
# @desc : 项目图片leafer信息
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class ProjectImgLeaferParams(QueryParams):
def __init__(
self,
image_id: int | None = Query(None, title="图片id"),
params: Paging = Depends()
):
super().__init__(params)
self.image_id = image_id

View File

@ -0,0 +1,28 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:25
# @File : project_info.py
# @IDE : PyCharm
# @desc : 项目信息
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class ProjectInfoParams(QueryParams):
def __init__(
self,
project_name: str | None = Query(None, title="项目名称"),
type_code: str | None = Query(None, title="项目类别"),
dept_id: str | None = Query(None, title="部门id"),
user_id: str | None = Query(None, title="用户id"),
params: Paging = Depends()
):
super().__init__(params)
self.project_name = ("like", project_name)
self.type_code = type_code
self.dept_id = dept_id
self.user_id = user_id

View File

@ -0,0 +1,20 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:28
# @File : project_label.py
# @IDE : PyCharm
# @desc : 项目标签信息
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class ProjectLabelParams(QueryParams):
def __init__(
self,
project_id: int | None = Query(None, title="项目id"),
params: Paging = Depends()
):
super().__init__(params)
self.project_id = project_id

View File

@ -0,0 +1,5 @@
from .project_info import ProjectInfoIn, ProjectInfoOut, ProjectInfoPagerOut
from .project_image import ProjectImage, ProjectImagePager, ProjectImageOut
from .project_label import ProjectLabel
from .project_img_label import ProjectImgLabelIn
from .project_img_leafer import ProjectImgLeaferLabel, ProjectImgLeaferOut

View File

@ -0,0 +1,38 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:27
# @File : project_image.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr
from typing import Optional
class ProjectImage(BaseModel):
id: Optional[int] = Field(None, description="id")
project_id: Optional[int] = Field(..., description="项目id")
img_type: Optional[str] = Field(None, description="图片类别")
file_name: Optional[str] = Field(None, description="文件名称")
create_time: DatetimeStr
model_config = ConfigDict(from_attributes=True)
class ProjectImageOut(BaseModel):
id: Optional[int] = Field(None, description="id")
project_id: Optional[int] = Field(..., description="项目id")
file_name: Optional[str] = Field(None, description="文件名称")
create_time: DatetimeStr
label_count: Optional[int]
model_config = ConfigDict(from_attributes=True)
class ProjectImagePager(BaseModel):
project_id: Optional[int] = Field(..., description="项目id")
img_type: Optional[str] = Field(None, description="图片类别")
pagerNum: Optional[int] = Field(None, description="当前页码")
pagerSize: Optional[int] = Field(None, description="每页数量")

View File

@ -0,0 +1,18 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:29
# @File : project_img_label.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel
class ProjectImgLabelIn(BaseModel):
label_id: int
mark_center_x: str
mark_center_y: str
mark_width: str
mark_height: str

View File

@ -0,0 +1,25 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:29
# @File : project_img_leafer.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
from . import ProjectImgLabelIn
class ProjectImgLeaferLabel(BaseModel):
image_id: Optional[int] = Field(..., description="图片id")
leafer: Optional[dict] = Field(..., description="保存的leafer")
label_infos: List[ProjectImgLabelIn] = Field(..., description="标签框选信息")
class ProjectImgLeaferOut(BaseModel):
image_id: Optional[int] = Field(..., description="图片id")
leafer: Optional[dict] = Field(..., description="保存的leafer")
model_config = ConfigDict(from_attributes=True)

View File

@ -0,0 +1,47 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:25
# @File : project_info.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
class ProjectInfoIn(BaseModel):
"""项目信息输入"""
project_name: Optional[str] = Field(..., description="项目名称")
type_code: Optional[str] = Field(..., description="项目类型编码")
description: Optional[str] = Field(None, description="项目描述")
model_config = ConfigDict(from_attributes=True)
class ProjectInfoOut(BaseModel):
"""项目信息输出"""
id: Optional[int] = Field(None, description="项目id")
project_no: Optional[str] = Field(..., description="项目编号")
project_name: Optional[str] = Field(..., description="项目名称")
type_code: Optional[str] = Field(..., description="项目类型编码")
description: Optional[str] = Field(None, description="项目描述")
train_version: Optional[int] = Field(None, description="训练版本号")
project_status: Optional[str] = Field(None, description="项目状态")
model_config = ConfigDict(from_attributes=True)
class ProjectInfoPagerOut(BaseModel):
"""项目信息输出"""
id: Optional[int] = Field(None, description="项目id")
project_no: Optional[str] = Field(None, description="项目编号")
project_name: Optional[str] = Field(None, description="项目名称")
type_code: Optional[str] = Field(None, description="项目类型编码")
description: Optional[str] = Field(None, description="项目描述")
train_version: Optional[int] = Field(None, description="训练版本号")
project_status: Optional[str] = Field(None, description="项目状态")
mark_count: Optional[int] = Field(0, description="已标记数量")
no_mark_count: Optional[int] = Field(0, description="未标记数量")
model_config = ConfigDict(from_attributes=True)

View File

@ -0,0 +1,20 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:28
# @File : project_label.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
class ProjectLabel(BaseModel):
"""项目标签输入输出"""
id: Optional[int] = Field(None, description="id")
project_id: Optional[int] = Field(None, description="项目id")
label_name: Optional[str] = Field(..., description="标签名称")
meta: Optional[dict] = Field(None, description="label属性")
model_config = ConfigDict(from_attributes=True)

View File

@ -0,0 +1,107 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:25
# @File : views.py
# @IDE : PyCharm
# @desc : 路由,视图文件
from utils.response import SuccessResponse, ErrorResponse
from . import params, schemas, crud, models
from core.dependencies import IdList
from fastapi import APIRouter, Depends
from apps.vadmin.auth.utils.current import FullAdminAuth
from apps.vadmin.auth.utils.validation.auth import Auth
app = APIRouter()
###########################################################
# 项目信息
###########################################################
@app.get("/list", summary="获取项目信息列表")
async def project_pager(
param: params.ProjectInfoParams = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
datas, count = await crud.ProjectInfoDal(auth.db).get_project_pager(project=param, auth=auth)
return SuccessResponse(datas, count=count)
@app.post("/info", summary="新建项目")
async def add_project(
pro_in: schemas.ProjectInfoIn,
auth: Auth = Depends(FullAdminAuth())
):
check = await crud.ProjectInfoDal(auth.db).check_name(project_name=pro_in.project_name)
if check:
return ErrorResponse(msg="存在相同名称的项目,不能创建")
result = await crud.ProjectInfoDal(auth.db).add_project(pro_in, auth)
return SuccessResponse(data=result)
@app.get("/info/{pro_id}", summary="查询项目信息")
async def project(
pro_id: int,
auth: Auth = Depends(FullAdminAuth())
):
result = await crud.ProjectInfoDal(auth.db).get_data(data_id=pro_id, v_schema=schemas.ProjectInfoOut)
return SuccessResponse(data=result)
@app.delete("/info", summary="删除项目")
async def del_project(
pro_ids: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
await crud.ProjectInfoDal(auth.db).delete_datas(ids=pro_ids.ids, v_soft=True)
return SuccessResponse(msg="删除成功")
@app.get("/label/{pro_id}", summary="查询标签列表")
async def label_list(
pro_id: int,
auth: Auth = Depends(FullAdminAuth())
):
result = await crud.ProjectLabelDal(auth.db).get_datas(v_where=[models.ProjectLabel.project_id == pro_id],
v_schema=schemas.ProjectLabel)
return SuccessResponse(data=result)
@app.post("/label", summary="新建标签")
async def add_label(
label_in: schemas.ProjectLabel,
auth: Auth = Depends(FullAdminAuth())
):
check = await crud.ProjectLabelDal(auth.db).check_label_name(label_in.label_name, label_in.project_id)
if check:
return ErrorResponse(msg="存在相同名称的标签,不能新建")
await crud.ProjectLabelDal(auth.db).create_data(data=label_in)
return SuccessResponse(msg="新建成功")
@app.put("/label", summary="修改标签")
async def update_label(
label_in: schemas.ProjectLabel,
auth: Auth = Depends(FullAdminAuth())
):
check = await crud.ProjectLabelDal(auth.db).check_label_name(label_in.label_name, label_in.project_id, label_in.id)
if check:
return ErrorResponse(msg="存在相同名称的标签,不能修改")
await crud.ProjectLabelDal(auth.db).put_data(data_id=label_in.id, data=label_in)
return SuccessResponse(msg="修改成功")
@app.delete("/label", summary="删除标签")
async def delete_label(
label_ids: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
await crud.ProjectLabelDal(auth.db).delete_datas(label_ids.ids)
return SuccessResponse(msg="删除成功")

View File

View File

@ -0,0 +1,20 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:32
# @File : crud.py
# @IDE : PyCharm
# @desc : 数据访问层
from sqlalchemy.ext.asyncio import AsyncSession
from core.crud import DalBase
from . import models, schemas
class ProjectTrainDal(DalBase):
def __init__(self, db: AsyncSession):
super(ProjectTrainDal, self).__init__()
self.db = db
self.model = models.ProjectTrain
self.schema = schemas.ProjectTrainSimpleOut

View File

View File

@ -0,0 +1,24 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String, Integer
from db.db_base import BaseModel
class ProjectTrain(BaseModel):
"""
项目训练版本信息表
"""
__tablename__ = "project_train"
__table_args__ = ({'comment': '项目训练版本信息表'})
project_id: Mapped[int] = mapped_column(Integer, nullable=False)
train_version: Mapped[str] = mapped_column(String(32), nullable=False)
train_url: Mapped[str] = mapped_column(String(255), nullable=False)
train_data: Mapped[str] = mapped_column(String(255), nullable=False)
weights_id: Mapped[int] = mapped_column(Integer)
weights_name: Mapped[str] = mapped_column(String(32))
epochs: Mapped[int] = mapped_column(Integer)
patience: Mapped[int] = mapped_column(Integer)
best_pt: Mapped[str] = mapped_column(String(255), nullable=False)
last_pt: Mapped[str] = mapped_column(String(255), nullable=False)
user_id: Mapped[int] = mapped_column(Integer, nullable=False)

View File

@ -0,0 +1 @@
from .project_train import ProjectTrainParams

View File

@ -0,0 +1,15 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:32
# @File : project_train.py
# @IDE : PyCharm
# @desc : 项目巡逻片信息
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class ProjectTrainParams(QueryParams):
def __init__(self, params: Paging = Depends()):
super().__init__(params)

View File

@ -0,0 +1 @@
from .project_train import ProjectTrain, ProjectTrainSimpleOut

View File

@ -0,0 +1,32 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:32
# @File : project_train.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr
class ProjectTrain(BaseModel):
project_id: int = Field(..., title="None")
train_version: str = Field(..., title="None")
train_url: str = Field(..., title="None")
train_data: str = Field(..., title="None")
weights_id: int = Field(..., title="None")
weights_name: str = Field(..., title="None")
epochs: int = Field(..., title="None")
patience: int = Field(..., title="None")
best_pt: str = Field(..., title="None")
last_pt: str = Field(..., title="None")
user_id: int = Field(..., title="None")
class ProjectTrainSimpleOut(ProjectTrain):
model_config = ConfigDict(from_attributes=True)
id: int = Field(..., title="编号")
create_datetime: DatetimeStr = Field(..., title="创建时间")
update_datetime: DatetimeStr = Field(..., title="更新时间")

View File

@ -0,0 +1,51 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2025/04/03 10:32
# @File : views.py
# @IDE : PyCharm
# @desc : 路由,视图文件
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends
from . import models, schemas, crud, params
from core.dependencies import IdList
from apps.vadmin.auth.utils.current import AllUserAuth
from utils.response import SuccessResponse
from apps.vadmin.auth.utils.validation.auth import Auth
from core.database import db_getter
app = APIRouter()
###########################################################
# 项目巡逻片信息
###########################################################
@app.get("/project/train", summary="获取项目巡逻片信息列表", tags=["项目巡逻片信息"])
async def get_project_train_list(p: params.ProjectTrainParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas, count = await crud.ProjectTrainDal(auth.db).get_datas(**p.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.post("/project/train", summary="创建项目巡逻片信息", tags=["项目巡逻片信息"])
async def create_project_train(data: schemas.ProjectTrain, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectTrainDal(auth.db).create_data(data=data))
@app.delete("/project/train", summary="删除项目巡逻片信息", description="硬删除", tags=["项目巡逻片信息"])
async def delete_project_train_list(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.ProjectTrainDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/project/train/{data_id}", summary="更新项目巡逻片信息", tags=["项目巡逻片信息"])
async def put_project_train(data_id: int, data: schemas.ProjectTrain, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.ProjectTrainDal(auth.db).put_data(data_id, data))
@app.get("/project/train/{data_id}", summary="获取项目巡逻片信息信息", tags=["项目巡逻片信息"])
async def get_project_train(data_id: int, db: AsyncSession = Depends(db_getter)):
schema = schemas.ProjectTrainSimpleOut
return SuccessResponse(await crud.ProjectTrainDal(db).get_data(data_id, v_schema=schema))

0
apps/vadmin/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,84 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/10/19 15:41
# @File : views.py
# @IDE : PyCharm
# @desc : 简要说明
from fastapi import APIRouter, Depends
from apps.vadmin.auth.utils.current import AllUserAuth
from utils.response import SuccessResponse
from apps.vadmin.auth.utils.validation.auth import Auth
import random
app = APIRouter()
###########################################################
# 图表数据
###########################################################
@app.get("/random/number", summary="获取随机整数")
async def get_random_number(auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(random.randint(500, 20000))
@app.get("/banners", summary="轮播图")
async def get_banners(auth: Auth = Depends(AllUserAuth())):
data = [
{
"id": 1, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-14/1.jpg"
},
{
"id": 2, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-09/banner1.png"
},
{
"id": 3, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-09/banner3.png"
},
]
return SuccessResponse(data)
@app.get("/user/access/source", summary="用户来源")
async def get_user_access_source(auth: Auth = Depends(AllUserAuth())):
data = [
{"value": 1000, "name": 'analysis.directAccess'},
{"value": 310, "name": 'analysis.mailMarketing'},
{"value": 234, "name": 'analysis.allianceAdvertising'},
{"value": 135, "name": 'analysis.videoAdvertising'},
{"value": 1548, "name": 'analysis.searchEngines'}
]
return SuccessResponse(data)
@app.get("/weekly/user/activity", summary="每周用户活跃量")
async def get_weekly_user_activity(auth: Auth = Depends(AllUserAuth())):
data = [
{"value": 13253, "name": 'analysis.monday'},
{"value": 34235, "name": 'analysis.tuesday'},
{"value": 26321, "name": 'analysis.wednesday'},
{"value": 12340, "name": 'analysis.thursday'},
{"value": 24643, "name": 'analysis.friday'},
{"value": 1322, "name": 'analysis.saturday'},
{"value": 1324, "name": 'analysis.sunday'}
]
return SuccessResponse(data)
@app.get("/monthly/sales", summary="每月销售额")
async def get_monthly_sales(auth: Auth = Depends(AllUserAuth())):
data = [
{"estimate": 100, "actual": 120, "name": 'analysis.january'},
{"estimate": 120, "actual": 82, "name": 'analysis.february'},
{"estimate": 161, "actual": 91, "name": 'analysis.march'},
{"estimate": 134, "actual": 154, "name": 'analysis.april'},
{"estimate": 105, "actual": 162, "name": 'analysis.may'},
{"estimate": 160, "actual": 140, "name": 'analysis.june'},
{"estimate": 165, "actual": 145, "name": 'analysis.july'},
{"estimate": 114, "actual": 250, "name": 'analysis.august'},
{"estimate": 163, "actual": 134, "name": 'analysis.september'},
{"estimate": 185, "actual": 56, "name": 'analysis.october'},
{"estimate": 118, "actual": 99, "name": 'analysis.november'},
{"estimate": 123, "actual": 123, "name": 'analysis.december'}
]
return SuccessResponse(data)

View File

1075
apps/vadmin/auth/crud.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : __init__.py
# @IDE : PyCharm
# @desc : 简要说明
from .m2m import vadmin_auth_user_roles, vadmin_auth_role_menus, vadmin_auth_user_depts, vadmin_auth_role_depts
from .menu import VadminMenu
from .role import VadminRole
from .user import VadminUser
from .dept import VadminDept

View File

@ -0,0 +1,31 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/10/23 13:41
# @File : dept.py
# @IDE : PyCharm
# @desc : 部门模型
from sqlalchemy.orm import Mapped, mapped_column
from db.db_base import BaseModel
from sqlalchemy import String, Boolean, Integer, ForeignKey
class VadminDept(BaseModel):
__tablename__ = "vadmin_auth_dept"
__table_args__ = ({'comment': '部门表'})
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="部门名称")
dept_key: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="部门标识")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
order: Mapped[int | None] = mapped_column(Integer, comment="显示排序")
desc: Mapped[str | None] = mapped_column(String(255), comment="描述")
owner: Mapped[str | None] = mapped_column(String(255), comment="负责人")
phone: Mapped[str | None] = mapped_column(String(255), comment="联系电话")
email: Mapped[str | None] = mapped_column(String(255), comment="邮箱")
parent_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("vadmin_auth_dept.id", ondelete='CASCADE'),
comment="上级部门"
)

View File

@ -0,0 +1,41 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : m2m.py
# @IDE : PyCharm
# @desc : 关联中间表
from db.db_base import Base
from sqlalchemy import ForeignKey, Column, Table, Integer
vadmin_auth_user_roles = Table(
"vadmin_auth_user_roles",
Base.metadata,
Column("user_id", Integer, ForeignKey("vadmin_auth_user.id", ondelete="CASCADE")),
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
)
vadmin_auth_role_menus = Table(
"vadmin_auth_role_menus",
Base.metadata,
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
Column("menu_id", Integer, ForeignKey("vadmin_auth_menu.id", ondelete="CASCADE")),
)
vadmin_auth_user_depts = Table(
"vadmin_auth_user_depts",
Base.metadata,
Column("user_id", Integer, ForeignKey("vadmin_auth_user.id", ondelete="CASCADE")),
Column("dept_id", Integer, ForeignKey("vadmin_auth_dept.id", ondelete="CASCADE")),
)
vadmin_auth_role_depts = Table(
"vadmin_auth_role_depts",
Base.metadata,
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
Column("dept_id", Integer, ForeignKey("vadmin_auth_dept.id", ondelete="CASCADE")),
)

View File

@ -0,0 +1,67 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : menu.py
# @IDE : PyCharm
# @desc : 菜单模型
from db.db_base import BaseModel
from sqlalchemy import String, Boolean, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
class VadminMenu(BaseModel):
__tablename__ = "vadmin_auth_menu"
__table_args__ = ({'comment': '菜单表'})
title: Mapped[str] = mapped_column(String(50), comment="名称")
icon: Mapped[str | None] = mapped_column(String(50), comment="菜单图标")
redirect: Mapped[str | None] = mapped_column(String(100), comment="重定向地址")
component: Mapped[str | None] = mapped_column(String(255), comment="前端组件地址")
path: Mapped[str | None] = mapped_column(String(50), comment="前端路由地址")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
hidden: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否隐藏")
order: Mapped[int] = mapped_column(Integer, comment="排序")
menu_type: Mapped[str] = mapped_column(String(8), comment="菜单类型")
parent_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("vadmin_auth_menu.id", ondelete='CASCADE'),
comment="父菜单"
)
perms: Mapped[str | None] = mapped_column(String(50), comment="权限标识", unique=False, index=True)
"""以下属性主要用于补全前端路由属性,"""
noCache: Mapped[bool] = mapped_column(
Boolean,
comment="如果设置为true则不会被 <keep-alive> 缓存(默认 false)",
default=False
)
breadcrumb: Mapped[bool] = mapped_column(
Boolean,
comment="如果设置为false则不会在breadcrumb面包屑中显示(默认 true)",
default=True
)
affix: Mapped[bool] = mapped_column(
Boolean,
comment="如果设置为true则会一直固定在tag项中(默认 false)",
default=False
)
noTagsView: Mapped[bool] = mapped_column(
Boolean,
comment="如果设置为true则不会出现在tag中(默认 false)",
default=False
)
canTo: Mapped[bool] = mapped_column(
Boolean,
comment="设置为true即使hidden为true也依然可以进行路由跳转(默认 false)",
default=False
)
alwaysShow: Mapped[bool] = mapped_column(
Boolean,
comment="""当你一个路由下面的 children 声明的路由大于1个时自动会变成嵌套的模式
只有一个时会将那个子路由当做根路由显示在侧边栏若你想不管路由下面的 children 声明的个数都显示你的根路由
你可以设置 alwaysShow: true这样它就会忽略之前定义的规则一直显示根路由(默认 true)""",
default=True
)

View File

@ -0,0 +1,30 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : role.py
# @IDE : PyCharm
# @desc : 角色模型
from sqlalchemy.orm import relationship, Mapped, mapped_column
from db.db_base import BaseModel
from sqlalchemy import String, Boolean, Integer
from .menu import VadminMenu
from .dept import VadminDept
from .m2m import vadmin_auth_role_menus, vadmin_auth_role_depts
class VadminRole(BaseModel):
__tablename__ = "vadmin_auth_role"
__table_args__ = ({'comment': '角色表'})
name: Mapped[str] = mapped_column(String(50), index=True, comment="名称")
role_key: Mapped[str] = mapped_column(String(50), index=True, comment="权限字符")
data_range: Mapped[int] = mapped_column(Integer, default=4, comment="数据权限范围")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
order: Mapped[int | None] = mapped_column(Integer, comment="排序")
desc: Mapped[str | None] = mapped_column(String(255), comment="描述")
is_admin: Mapped[bool] = mapped_column(Boolean, comment="是否为超级角色", default=False)
menus: Mapped[set[VadminMenu]] = relationship(secondary=vadmin_auth_role_menus)
depts: Mapped[set[VadminDept]] = relationship(secondary=vadmin_auth_role_depts)

View File

@ -0,0 +1,72 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : user.py
# @IDE : PyCharm
# @desc : 用户模型
from datetime import datetime
from sqlalchemy.orm import relationship, Mapped, mapped_column
from db.db_base import BaseModel
from sqlalchemy import String, Boolean, DateTime
from passlib.context import CryptContext
from .role import VadminRole
from .dept import VadminDept
from .m2m import vadmin_auth_user_roles, vadmin_auth_user_depts
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
class VadminUser(BaseModel):
__tablename__ = "vadmin_auth_user"
__table_args__ = ({'comment': '用户表'})
avatar: Mapped[str | None] = mapped_column(String(500), comment='头像')
telephone: Mapped[str] = mapped_column(String(11), nullable=False, index=True, comment="手机号", unique=False)
email: Mapped[str | None] = mapped_column(String(50), comment="邮箱地址")
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="姓名")
nickname: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="昵称")
password: Mapped[str] = mapped_column(String(255), nullable=True, comment="密码")
gender: Mapped[str | None] = mapped_column(String(8), nullable=True, comment="性别")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可用")
is_reset_password: Mapped[bool] = mapped_column(
Boolean,
default=False,
comment="是否已经重置密码,没有重置的,登陆系统后必须重置密码"
)
last_ip: Mapped[str | None] = mapped_column(String(50), comment="最后一次登录IP")
last_login: Mapped[datetime | None] = mapped_column(DateTime, comment="最近一次登录时间")
is_staff: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否为工作人员")
wx_server_openid: Mapped[str | None] = mapped_column(String(255), comment="服务端微信平台openid")
is_wx_server_openid: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否已有服务端微信平台openid")
roles: Mapped[set[VadminRole]] = relationship(secondary=vadmin_auth_user_roles)
depts: Mapped[set[VadminDept]] = relationship(secondary=vadmin_auth_user_depts)
@staticmethod
def get_password_hash(password: str) -> str:
"""
生成哈希密码
:param password: 原始密码
:return: 哈希密码
"""
return pwd_context.hash(password)
@staticmethod
def verify_password(password: str, hashed_password: str) -> bool:
"""
验证原始密码是否与哈希密码一致
:param password: 原始密码
:param hashed_password: 哈希密码
:return:
"""
return pwd_context.verify(password, hashed_password)
def is_admin(self) -> bool:
"""
获取该用户是否拥有最高权限
以最高权限为准
:return:
"""
return any([i.is_admin for i in self.roles])

View File

@ -0,0 +1,3 @@
from .user import UserParams
from .role import RoleParams
from .dept import DeptParams

View File

@ -0,0 +1,31 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/12/18 10:19
# @File : dept.py
# @IDE : PyCharm
# @desc : 查询参数-类依赖项
"""
类依赖项-官方文档https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
"""
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class DeptParams(QueryParams):
"""
列表分页
"""
def __init__(
self,
name: str | None = Query(None, title="部门名称"),
dept_key: str | None = Query(None, title="部门标识"),
disabled: bool | None = Query(None, title="是否禁用"),
params: Paging = Depends()
):
super().__init__(params)
self.name = ("like", name)
self.dept_key = ("like", dept_key)
self.disabled = disabled

View File

@ -0,0 +1,31 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : role.py
# @IDE : PyCharm
# @desc : 查询参数-类依赖项
"""
类依赖项-官方文档https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
"""
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class RoleParams(QueryParams):
"""
列表分页
"""
def __init__(
self,
name: str | None = Query(None, title="角色名称"),
role_key: str | None = Query(None, title="权限字符"),
disabled: bool | None = Query(None, title="是否禁用"),
params: Paging = Depends()
):
super().__init__(params)
self.name = ("like", name)
self.role_key = ("like", role_key)
self.disabled = disabled

View File

@ -0,0 +1,37 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : user.py
# @IDE : PyCharm
# @desc : 查询参数-类依赖项
"""
类依赖项-官方文档https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
"""
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class UserParams(QueryParams):
"""
列表分页
"""
def __init__(
self,
name: str | None = Query(None, title="用户名称"),
telephone: str | None = Query(None, title="手机号"),
email: str | None = Query(None, title="邮箱"),
is_active: bool | None = Query(None, title="是否可用"),
is_staff: bool | None = Query(None, title="是否为工作人员"),
params: Paging = Depends()
):
super().__init__(params)
self.name = ("like", name)
self.telephone = ("like", telephone)
self.email = ("like", email)
self.is_active = is_active
self.is_staff = is_staff

View File

@ -0,0 +1,4 @@
from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd, UserUpdateBaseInfo, UserPasswordOut
from .role import Role, RoleOut, RoleIn, RoleOptionsOut, RoleSimpleOut
from .menu import Menu, MenuSimpleOut, RouterOut, Meta, MenuTreeListOut
from .dept import Dept, DeptSimpleOut, DeptTreeListOut

View File

@ -0,0 +1,39 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/10/25 12:19
# @File : dept.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict
from core.data_types import DatetimeStr
class Dept(BaseModel):
name: str
dept_key: str
disabled: bool = False
order: int | None = None
desc: str | None = None
owner: str | None = None
phone: str | None = None
email: str | None = None
parent_id: int | None = None
class DeptSimpleOut(Dept):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr
class DeptTreeListOut(DeptSimpleOut):
model_config = ConfigDict(from_attributes=True)
children: list[dict] = []

View File

@ -0,0 +1,66 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : role.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict
from core.data_types import DatetimeStr
class Menu(BaseModel):
title: str
icon: str | None = None
component: str | None = None
redirect: str | None = None
path: str | None = None
disabled: bool = False
hidden: bool = False
order: int | None = None
perms: str | None = None
parent_id: int | None = None
menu_type: str
alwaysShow: bool | None = True
noCache: bool | None = False
class MenuSimpleOut(Menu):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr
class Meta(BaseModel):
title: str
icon: str | None = None
hidden: bool = False
noCache: bool | None = False
breadcrumb: bool | None = True
affix: bool | None = False
noTagsView: bool | None = False
canTo: bool | None = False
alwaysShow: bool | None = True
# 路由展示
class RouterOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str | None = None
component: str | None = None
path: str
redirect: str | None = None
meta: Meta | None = None
order: int | None = None
children: list[dict] = []
class MenuTreeListOut(MenuSimpleOut):
model_config = ConfigDict(from_attributes=True)
children: list[dict] = []

View File

@ -0,0 +1,52 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : role.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict, Field
from core.data_types import DatetimeStr
from .menu import MenuSimpleOut
from .dept import DeptSimpleOut
class Role(BaseModel):
name: str
disabled: bool = False
order: int | None = None
desc: str | None = None
data_range: int = 4
role_key: str
is_admin: bool = False
class RoleSimpleOut(Role):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr
class RoleOut(RoleSimpleOut):
model_config = ConfigDict(from_attributes=True)
menus: list[MenuSimpleOut] = []
depts: list[DeptSimpleOut] = []
class RoleIn(Role):
menu_ids: list[int] = []
dept_ids: list[int] = []
class RoleOptionsOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
label: str = Field(alias='name')
value: int = Field(alias='id')
disabled: bool

View File

@ -0,0 +1,98 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : user.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict, field_validator
from pydantic_core.core_schema import FieldValidationInfo
from core.data_types import Telephone, DatetimeStr, Email
from .role import RoleSimpleOut
from .dept import DeptSimpleOut
class User(BaseModel):
name: str
telephone: Telephone
email: Email | None = None
nickname: str | None = None
avatar: str | None = None
is_active: bool | None = True
is_staff: bool | None = True
gender: str | None = "0"
is_wx_server_openid: bool | None = False
class UserIn(User):
"""
创建用户
"""
role_ids: list[int] = []
dept_ids: list[int] = []
password: str | None = ""
class UserUpdateBaseInfo(BaseModel):
"""
更新用户基本信息
"""
name: str
telephone: Telephone
email: Email | None = None
nickname: str | None = None
gender: str | None = "0"
class UserUpdate(User):
"""
更新用户详细信息
"""
name: str | None = None
telephone: Telephone
email: Email | None = None
nickname: str | None = None
avatar: str | None = None
is_active: bool | None = True
is_staff: bool | None = False
gender: str | None = "0"
role_ids: list[int] = []
dept_ids: list[int] = []
class UserSimpleOut(User):
model_config = ConfigDict(from_attributes=True)
id: int
update_datetime: DatetimeStr
create_datetime: DatetimeStr
is_reset_password: bool | None = None
last_login: DatetimeStr | None = None
last_ip: str | None = None
class UserPasswordOut(UserSimpleOut):
model_config = ConfigDict(from_attributes=True)
password: str
class UserOut(UserSimpleOut):
model_config = ConfigDict(from_attributes=True)
roles: list[RoleSimpleOut] = []
depts: list[DeptSimpleOut] = []
class ResetPwd(BaseModel):
password: str
password_two: str
@field_validator('password_two')
def check_passwords_match(cls, v, info: FieldValidationInfo):
if 'password' in info.data and v != info.data['password']:
raise ValueError('两次密码不一致!')
return v

View File

@ -0,0 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/8/8 11:02
# @File : __init__.py
# @IDE : PyCharm
# @desc : 简要说明

View File

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/24 16:44
# @File : current.py
# @IDE : PyCharm
# @desc : 获取认证后的信息工具
from typing import Annotated
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from apps.vadmin.auth.crud import UserDal
from apps.vadmin.auth.models import VadminUser, VadminRole
from core.exception import CustomException
from utils import status
from .validation import AuthValidation
from fastapi import Request, Depends
from application import settings
from core.database import db_getter
from .validation.auth import Auth
class OpenAuth(AuthValidation):
"""
开放认证无认证也可以访问
认证了以后可以获取到用户信息无认证则获取不到
"""
async def __call__(
self,
request: Request,
token: Annotated[str, Depends(settings.oauth2_scheme)],
db: AsyncSession = Depends(db_getter)
):
"""
每次调用依赖此类的接口会执行该方法
"""
if not settings.OAUTH_ENABLE:
return Auth(db=db)
try:
telephone, password = self.validate_token(request, token)
user = await UserDal(db).get_data(telephone=telephone, password=password, v_return_none=True)
return await self.validate_user(request, user, db, is_all=True)
except CustomException:
return Auth(db=db)
class AllUserAuth(AuthValidation):
"""
支持所有用户认证
获取用户基本信息
"""
async def __call__(
self,
request: Request,
token: str = Depends(settings.oauth2_scheme),
db: AsyncSession = Depends(db_getter)
):
"""
每次调用依赖此类的接口会执行该方法
"""
if not settings.OAUTH_ENABLE:
return Auth(db=db)
telephone, password = self.validate_token(request, token)
user = await UserDal(db).get_data(telephone=telephone, password=password, v_return_none=True)
return await self.validate_user(request, user, db, is_all=True)
class FullAdminAuth(AuthValidation):
"""
只支持员工用户认证
获取员工用户完整信息
如果有权限那么会验证该用户是否包括权限列表中的其中一个权限
"""
def __init__(self, permissions: list[str] | None = None):
if permissions:
self.permissions = set(permissions)
else:
self.permissions = None
async def __call__(
self,
request: Request,
token: str = Depends(settings.oauth2_scheme),
db: AsyncSession = Depends(db_getter)
) -> Auth:
"""
每次调用依赖此类的接口会执行该方法
"""
if not settings.OAUTH_ENABLE:
return Auth(db=db)
telephone, password = self.validate_token(request, token)
options = [
joinedload(VadminUser.roles).subqueryload(VadminRole.menus),
joinedload(VadminUser.roles).subqueryload(VadminRole.depts),
joinedload(VadminUser.depts)
]
user = await UserDal(db).get_data(
telephone=telephone,
password=password,
v_return_none=True,
v_options=options,
is_staff=True
)
result = await self.validate_user(request, user, db, is_all=False)
permissions = self.get_user_permissions(user)
if permissions != {'*.*.*'} and self.permissions:
if not (self.permissions & permissions):
raise CustomException(msg="无权限操作", code=status.HTTP_403_FORBIDDEN)
return result

View File

@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/24 16:44
# @File : views.py
# @IDE : PyCharm
# @desc : 安全认证视图
"""
JWT 表示 JSON Web Tokenshttps://jwt.io/
它是一个将 JSON 对象编码为密集且没有空格的长字符串的标准
通过这种方式你可以创建一个有效期为 1 周的令牌然后当用户第二天使用令牌重新访问时你知道该用户仍然处于登入状态
一周后令牌将会过期用户将不会通过认证必须再次登录才能获得一个新令牌
我们需要安装 python-jose 以在 Python 中生成和校验 JWT 令牌pip install python-jose[cryptography]
PassLib 是一个用于处理哈希密码的很棒的 Python 它支持许多安全哈希算法以及配合算法使用的实用程序
推荐的算法是 Bcryptpip install passlib[bcrypt]
"""
from datetime import timedelta
from redis.asyncio import Redis
from fastapi import APIRouter, Depends, Request, Body
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
from core.database import db_getter, redis_getter
from core.exception import CustomException
from utils import status
from utils.response import SuccessResponse, ErrorResponse
from application import settings
from .login_manage import LoginManage
from .validation import LoginForm, WXLoginForm
from apps.vadmin.record.models import VadminLoginRecord
from apps.vadmin.auth.crud import MenuDal, UserDal
from apps.vadmin.auth.models import VadminUser
from .current import FullAdminAuth
from .validation.auth import Auth
from utils.wx.oauth import WXOAuth
import jwt
app = APIRouter()
@app.post("/api/login", summary="API 手机号密码登录", description="Swagger API 文档登录认证")
async def api_login_for_access_token(
request: Request,
data: OAuth2PasswordRequestForm = Depends(),
db: AsyncSession = Depends(db_getter)
):
user = await UserDal(db).get_data(telephone=data.username, v_return_none=True)
error_code = status.HTTP_401_UNAUTHORIZED
if not user:
raise CustomException(status_code=error_code, code=error_code, msg="该手机号不存在")
result = VadminUser.verify_password(data.password, user.password)
if not result:
raise CustomException(status_code=error_code, code=error_code, msg="手机号或密码错误")
if not user.is_active:
raise CustomException(status_code=error_code, code=error_code, msg="此手机号已被冻结")
elif not user.is_staff:
raise CustomException(status_code=error_code, code=error_code, msg="此手机号无权限")
access_token = LoginManage.create_token({"sub": user.telephone, "password": user.password})
record = LoginForm(platform='2', method='0', telephone=data.username, password=data.password)
resp = {"access_token": access_token, "token_type": "bearer"}
await VadminLoginRecord.create_login_record(db, record, True, request, resp)
return resp
@app.post("/login", summary="手机号密码登录", description="员工登录通道限制最多输错次数达到最大值后将is_active=False")
async def login_for_access_token(
request: Request,
data: LoginForm,
manage: LoginManage = Depends(),
db: AsyncSession = Depends(db_getter)
):
try:
result = await manage.password_login(data, db, request)
if not result.status:
raise ValueError(result.msg)
access_token = LoginManage.create_token(
{"sub": result.user.telephone, "is_refresh": False, "password": result.user.password}
)
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token(
payload={"sub": result.user.telephone, "is_refresh": True, "password": result.user.password},
expires=expires
)
resp = {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer",
"is_reset_password": result.user.is_reset_password,
"is_wx_server_openid": result.user.is_wx_server_openid
}
await VadminLoginRecord.create_login_record(db, data, True, request, resp)
return SuccessResponse(resp)
except ValueError as e:
await VadminLoginRecord.create_login_record(db, data, False, request, {"message": str(e)})
return ErrorResponse(msg=str(e))
@app.post("/wx/login", summary="微信服务端一键登录", description="员工登录通道")
async def wx_login_for_access_token(
request: Request,
data: WXLoginForm,
db: AsyncSession = Depends(db_getter),
rd: Redis = Depends(redis_getter)
):
try:
if data.platform != "1" or data.method != "2":
raise ValueError("无效参数")
wx = WXOAuth(rd, 0)
telephone = await wx.parsing_phone_number(data.code)
if not telephone:
raise ValueError("无效Code")
data.telephone = telephone
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
if not user:
raise ValueError("手机号不存在")
elif not user.is_active:
raise ValueError("手机号已被冻结")
except ValueError as e:
await VadminLoginRecord.create_login_record(db, data, False, request, {"message": str(e)})
return ErrorResponse(msg=str(e))
# 更新登录时间
await UserDal(db).update_login_info(user, request.client.host)
# 登录成功创建 token
access_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": False, "password": user.password})
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token(
payload={"sub": user.telephone, "is_refresh": True, "password": user.password},
expires=expires
)
resp = {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer",
"is_reset_password": user.is_reset_password,
"is_wx_server_openid": user.is_wx_server_openid
}
await VadminLoginRecord.create_login_record(db, data, True, request, resp)
return SuccessResponse(resp)
@app.get("/getMenuList", summary="获取当前用户菜单树")
async def get_menu_list(auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await MenuDal(auth.db).get_routers(auth.user))
@app.post("/token/refresh", summary="刷新Token")
async def token_refresh(refresh: str = Body(..., title="刷新Token")):
error_code = status.HTTP_401_UNAUTHORIZED
try:
payload = jwt.decode(refresh, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
telephone: str = payload.get("sub")
is_refresh: bool = payload.get("is_refresh")
password: str = payload.get("password")
if not telephone or not is_refresh or not password:
return ErrorResponse("未认证,请您重新登录", code=error_code, status=error_code)
except jwt.exceptions.InvalidSignatureError:
return ErrorResponse("无效认证,请您重新登录", code=error_code, status=error_code)
except jwt.exceptions.ExpiredSignatureError:
return ErrorResponse("登录已超时,请您重新登录", code=error_code, status=error_code)
access_token = LoginManage.create_token({"sub": telephone, "is_refresh": False, "password": password})
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token(
payload={"sub": telephone, "is_refresh": True, "password": password},
expires=expires
)
resp = {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer"
}
return SuccessResponse(resp)

View File

@ -0,0 +1,62 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/8/8 11:02
# @File : auth_util.py
# @IDE : PyCharm
# @desc : 简要说明
from datetime import datetime, timedelta
from fastapi import Request
from application import settings
import jwt
from apps.vadmin.auth import models
from core.database import redis_getter
from utils.sms.code import CodeSMS
from .validation import LoginValidation, LoginForm, LoginResult
class LoginManage:
"""
登录认证工具
"""
@LoginValidation
async def password_login(self, data: LoginForm, user: models.VadminUser, **kwargs) -> LoginResult:
"""
验证用户密码
"""
result = models.VadminUser.verify_password(data.password, user.password)
if result:
return LoginResult(status=True, msg="验证成功")
return LoginResult(status=False, msg="手机号或密码错误")
@LoginValidation
async def sms_login(self, data: LoginForm, request: Request, **kwargs) -> LoginResult:
"""
验证用户短信验证码
"""
rd = redis_getter(request)
sms = CodeSMS(data.telephone, rd)
result = await sms.check_sms_code(data.password)
if result:
return LoginResult(status=True, msg="验证成功")
return LoginResult(status=False, msg="验证码错误")
@staticmethod
def create_token(payload: dict, expires: timedelta = None):
"""
创建一个生成新的访问令牌的工具函数
pyjwthttps://github.com/jpadilla/pyjwt/blob/master/docs/usage.rst
jwt 博客https://geek-docs.com/python/python-tutorial/j_python-jwt.html
#TODO 传入的时间为UTC时间datetime.datetime类型但是在解码时获取到的是本机时间的时间戳
"""
if expires:
expire = datetime.utcnow() + expires
else:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
payload.update({"exp": expire})
encoded_jwt = jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt

View File

@ -0,0 +1,10 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/11/9 10:14
# @File : __init__.py.py
# @IDE : PyCharm
# @desc : 简要说明
from .auth import Auth, AuthValidation
from .login import LoginValidation, LoginForm, LoginResult, WXLoginForm

View File

@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/24 16:44
# @File : auth.py
# @IDE : PyCharm
# @desc : 用户凭证验证装饰器
from fastapi import Request
import jwt
from pydantic import BaseModel
from application import settings
from sqlalchemy.ext.asyncio import AsyncSession
from apps.vadmin.auth.models import VadminUser
from core.exception import CustomException
from utils import status
from datetime import timedelta, datetime
from apps.vadmin.auth.crud import UserDal
class Auth(BaseModel):
user: VadminUser = None
db: AsyncSession
data_range: int | None = None
dept_ids: list | None = []
class Config:
# 接收任意类型
arbitrary_types_allowed = True
class AuthValidation:
"""
用于用户每次调用接口时验证用户提交的token是否正确并从token中获取用户信息
"""
# status_code = 401 时表示强制要求重新登录因账号已冻结账号已过期手机号码错误刷新token无效等问题导致
# 只有 code = 401 时,表示 token 过期,要求刷新 token
# 只有 code = 错误值时,只是报错,不重新登陆
error_code = status.HTTP_401_UNAUTHORIZED
warning_code = status.HTTP_ERROR
# status_code = 403 时,表示强制要求重新登录,因无系统权限,而进入到系统访问等问题导致
@classmethod
def validate_token(cls, request: Request, token: str | None) -> tuple[str, bool]:
"""
验证用户 token
"""
if not token:
raise CustomException(
msg="请您先登录!",
code=status.HTTP_403_FORBIDDEN,
status_code=status.HTTP_403_FORBIDDEN
)
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
telephone: str = payload.get("sub")
exp: int = payload.get("exp")
is_refresh: bool = payload.get("is_refresh")
password: bool = payload.get("password")
if not telephone or is_refresh or not password:
raise CustomException(
msg="未认证,请您重新登录",
code=status.HTTP_403_FORBIDDEN,
status_code=status.HTTP_403_FORBIDDEN
)
# 计算当前时间 + 缓冲时间是否大于等于 JWT 过期时间
buffer_time = (datetime.now() + timedelta(minutes=settings.ACCESS_TOKEN_CACHE_MINUTES)).timestamp()
# print("过期时间", exp, datetime.fromtimestamp(exp))
# print("当前时间", buffer_time, datetime.fromtimestamp(buffer_time))
# print("剩余时间", exp - buffer_time)
if buffer_time >= exp:
request.scope["if-refresh"] = 1
else:
request.scope["if-refresh"] = 0
except (jwt.exceptions.InvalidSignatureError, jwt.exceptions.DecodeError):
raise CustomException(
msg="无效认证,请您重新登录",
code=status.HTTP_403_FORBIDDEN,
status_code=status.HTTP_403_FORBIDDEN
)
except jwt.exceptions.ExpiredSignatureError:
raise CustomException(msg="认证已失效,请您重新登录", code=cls.error_code, status_code=cls.error_code)
return telephone, password
@classmethod
async def validate_user(cls, request: Request, user: VadminUser, db: AsyncSession, is_all: bool = True) -> Auth:
"""
验证用户信息
:param request:
:param user:
:param db:
:param is_all: 是否所有人访问不加权限
:return:
"""
if user is None:
raise CustomException(msg="未认证,请您重新登陆", code=cls.error_code, status_code=cls.error_code)
elif not user.is_active:
raise CustomException(msg="用户已被冻结!", code=cls.error_code, status_code=cls.error_code)
request.scope["telephone"] = user.telephone
request.scope["user_id"] = user.id
request.scope["user_name"] = user.name
try:
request.scope["body"] = await request.body()
except RuntimeError:
request.scope["body"] = "获取失败"
if is_all:
return Auth(user=user, db=db)
data_range, dept_ids = await cls.get_user_data_range(user, db)
return Auth(user=user, db=db, data_range=data_range, dept_ids=dept_ids)
@classmethod
def get_user_permissions(cls, user: VadminUser) -> set:
"""
获取员工用户所有权限列表
:param user: 用户实例
:return:
"""
if user.is_admin():
return {'*.*.*'}
permissions = set()
for role_obj in user.roles:
for menu in role_obj.menus:
if menu.perms and not menu.disabled:
permissions.add(menu.perms)
return permissions
@classmethod
async def get_user_data_range(cls, user: VadminUser, db: AsyncSession) -> tuple:
"""
获取用户数据范围
0 仅本人数据权限 create_user_id 查询
1 本部门数据权限 部门 id 左连接查询
2 本部门及以下数据权限 部门 id 左连接查询
3 自定义数据权限 部门 id 左连接查询
4 全部数据权限
:param user:
:param db:
:return:
"""
if user.is_admin():
return 4, ["*"]
data_range = max([i.data_range for i in user.roles])
dept_ids = set()
if data_range == 0:
pass
elif data_range == 1:
for dept in user.depts:
dept_ids.add(dept.id)
elif data_range == 2:
# 递归获取部门列表
dept_ids = await UserDal(db).recursion_get_dept_ids(user)
elif data_range == 3:
for role_obj in user.roles:
for dept in role_obj.depts:
dept_ids.add(dept.id)
elif data_range == 4:
dept_ids.add("*")
return data_range, list(dept_ids)

View File

@ -0,0 +1,92 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/11/9 10:15
# @File : login.py
# @IDE : PyCharm
# @desc : 登录验证装饰器
from fastapi import Request
from pydantic import BaseModel, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
from application.settings import DEFAULT_AUTH_ERROR_MAX_NUMBER, DEMO, REDIS_DB_ENABLE
from apps.vadmin.auth import crud, schemas
from core.database import redis_getter
from core.validator import vali_telephone
from utils.count import Count
class LoginForm(BaseModel):
telephone: str
password: str
method: str = '0' # 认证方式0密码登录1短信登录2微信一键登录
platform: str = '0' # 登录平台0PC端管理系统1移动端管理系统
# 重用验证器https://docs.pydantic.dev/dev-v2/usage/validators/#reuse-validators
normalize_telephone = field_validator('telephone')(vali_telephone)
class WXLoginForm(BaseModel):
telephone: str | None = None
code: str
method: str = '2' # 认证方式0密码登录1短信登录2微信一键登录
platform: str = '1' # 登录平台0PC端管理系统1移动端管理系统
class LoginResult(BaseModel):
status: bool | None = False
user: schemas.UserPasswordOut | None = None
msg: str | None = None
class Config:
arbitrary_types_allowed = True
class LoginValidation:
"""
验证用户登录时提交的数据是否有效
"""
def __init__(self, func):
self.func = func
async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult:
self.result = LoginResult()
if data.platform not in ["0", "1"] or data.method not in ["0", "1"]:
self.result.msg = "无效参数"
return self.result
user = await crud.UserDal(db).get_data(telephone=data.telephone, v_return_none=True)
if not user:
self.result.msg = "该手机号不存在!"
return self.result
result = await self.func(self, data=data, user=user, request=request)
if REDIS_DB_ENABLE:
count_key = f"{data.telephone}_password_auth" if data.method == '0' else f"{data.telephone}_sms_auth"
count = Count(redis_getter(request), count_key)
else:
count = None
if not result.status:
self.result.msg = result.msg
if not DEMO and count:
number = await count.add(ex=86400)
if number >= DEFAULT_AUTH_ERROR_MAX_NUMBER:
await count.reset()
# 如果等于最大次数,那么就将用户 is_active=False
user.is_active = False
await db.flush()
elif not user.is_active:
self.result.msg = "此手机号已被冻结!"
elif data.platform in ["0", "1"] and not user.is_staff:
self.result.msg = "此手机号无权限!"
else:
if not DEMO and count:
await count.delete()
self.result.msg = "OK"
self.result.status = True
self.result.user = schemas.UserPasswordOut.model_validate(user)
await crud.UserDal(db).update_login_info(user, request.client.host)
return self.result

307
apps/vadmin/auth/views.py Normal file
View File

@ -0,0 +1,307 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/2/24 17:02
# @File : views.py
# @IDE : PyCharm
# @desc : 简要说明
from redis.asyncio import Redis
from fastapi import APIRouter, Depends, Body, UploadFile, Request
from sqlalchemy.orm import joinedload
from core.database import redis_getter
from utils.response import SuccessResponse, ErrorResponse
from . import schemas, crud, models
from core.dependencies import IdList
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth
from apps.vadmin.auth.utils.validation.auth import Auth
from .params import UserParams, RoleParams, DeptParams
app = APIRouter()
###########################################################
# 接口测试
###########################################################
@app.get("/test", summary="接口测试")
async def test(auth: Auth = Depends(OpenAuth())):
return SuccessResponse(await crud.TestDal(auth.db).relationship_where_operations_has())
###########################################################
# 用户管理
###########################################################
@app.get("/users", summary="获取用户列表")
async def get_users(
params: UserParams = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.list"]))
):
model = models.VadminUser
options = [joinedload(model.roles), joinedload(model.depts)]
schema = schemas.UserOut
datas, count = await crud.UserDal(auth.db).get_datas(
**params.dict(),
v_options=options,
v_schema=schema,
v_return_count=True
)
return SuccessResponse(datas, count=count)
@app.post("/users", summary="创建用户")
async def create_user(data: schemas.UserIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create"]))):
return SuccessResponse(await crud.UserDal(auth.db).create_data(data=data))
@app.delete("/users", summary="批量删除用户", description="软删除,删除后清空所关联的角色")
async def delete_users(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.delete"]))):
if auth.user.id in ids.ids:
return ErrorResponse("不能删除当前登录用户")
elif 1 in ids.ids:
return ErrorResponse("不能删除超级管理员用户")
await crud.UserDal(auth.db).delete_datas(ids=ids.ids, v_soft=True, is_active=False)
return SuccessResponse("删除成功")
@app.put("/users/{data_id}", summary="更新用户信息")
async def put_user(
data_id: int,
data: schemas.UserUpdate,
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.update"]))
):
return SuccessResponse(await crud.UserDal(auth.db).put_data(data_id, data))
@app.get("/users/{data_id}", summary="获取用户信息")
async def get_user(
data_id: int,
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.view", "auth.user.update"]))
):
model = models.VadminUser
options = [joinedload(model.roles), joinedload(model.depts)]
schema = schemas.UserOut
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
@app.post("/user/current/reset/password", summary="重置当前用户密码")
async def user_current_reset_password(data: schemas.ResetPwd, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).reset_current_password(auth.user, data))
@app.post("/user/current/update/info", summary="更新当前用户基本信息")
async def post_user_current_update_info(data: schemas.UserUpdateBaseInfo, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).update_current_info(auth.user, data))
@app.post("/user/current/update/avatar", summary="更新当前用户头像")
async def post_user_current_update_avatar(file: UploadFile, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).update_current_avatar(auth.user, file))
@app.get("/user/admin/current/info", summary="获取当前管理员信息")
async def get_user_admin_current_info(auth: Auth = Depends(FullAdminAuth())):
result = schemas.UserOut.model_validate(auth.user).model_dump()
result["permissions"] = list(FullAdminAuth.get_user_permissions(auth.user))
return SuccessResponse(result)
@app.post("/user/export/query/list/to/excel", summary="导出用户查询列表为excel")
async def post_user_export_query_list(
header: list = Body(..., title="表头与对应字段"),
params: UserParams = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.export"]))
):
return SuccessResponse(await crud.UserDal(auth.db).export_query_list(header, params))
@app.get("/user/download/import/template", summary="下载最新批量导入用户模板")
async def get_user_download_new_import_template(auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).download_import_template())
@app.post("/import/users", summary="批量导入用户")
async def post_import_users(file: UploadFile, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.import"]))):
return SuccessResponse(await crud.UserDal(auth.db).import_users(file))
@app.post("/users/init/password/send/sms", summary="初始化所选用户密码并发送通知短信")
async def post_users_init_password(
request: Request,
ids: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.reset"])),
rd: Redis = Depends(redis_getter)
):
return SuccessResponse(await crud.UserDal(auth.db).init_password_send_sms(ids.ids, rd))
@app.post("/users/init/password/send/email", summary="初始化所选用户密码并发送通知邮件")
async def post_users_init_password_send_email(
request: Request,
ids: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.reset"])),
rd: Redis = Depends(redis_getter)
):
return SuccessResponse(await crud.UserDal(auth.db).init_password_send_email(ids.ids, rd))
@app.put("/users/wx/server/openid", summary="更新当前用户服务端微信平台openid")
async def put_user_wx_server_openid(code: str, auth: Auth = Depends(AllUserAuth()), rd: Redis = Depends(redis_getter)):
result = await crud.UserDal(auth.db).update_wx_server_openid(code, auth.user, rd)
return SuccessResponse(result)
###########################################################
# 角色管理
###########################################################
@app.get("/roles", summary="获取角色列表")
async def get_roles(
params: RoleParams = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.list"]))
):
datas, count = await crud.RoleDal(auth.db).get_datas(**params.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.post("/roles", summary="创建角色信息")
async def create_role(role: schemas.RoleIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create"]))):
return SuccessResponse(await crud.RoleDal(auth.db).create_data(data=role))
@app.delete("/roles", summary="批量删除角色", description="硬删除, 如果存在用户关联则无法删除")
async def delete_roles(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.delete"]))):
if 1 in ids.ids:
return ErrorResponse("不能删除管理员角色")
await crud.RoleDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/roles/{data_id}", summary="更新角色信息")
async def put_role(
data_id: int,
data: schemas.RoleIn,
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.update"]))
):
if 1 == data_id:
return ErrorResponse("不能修改管理员角色")
return SuccessResponse(await crud.RoleDal(auth.db).put_data(data_id, data))
@app.get("/roles/options", summary="获取角色选择项")
async def get_role_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create", "auth.user.update"]))):
return SuccessResponse(await crud.RoleDal(auth.db).get_select_datas())
@app.get("/roles/{data_id}", summary="获取角色信息")
async def get_role(
data_id: int,
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.view", "auth.role.update"]))
):
model = models.VadminRole
options = [joinedload(model.menus), joinedload(model.depts)]
schema = schemas.RoleOut
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
###########################################################
# 菜单管理
###########################################################
@app.get("/menus", summary="获取菜单列表")
async def get_menus(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.list"]))):
datas = await crud.MenuDal(auth.db).get_tree_list(mode=1)
return SuccessResponse(datas)
@app.get("/menus/tree/options", summary="获取菜单树选择项,添加/修改菜单时使用")
async def get_menus_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create", "auth.menu.update"]))):
datas = await crud.MenuDal(auth.db).get_tree_list(mode=2)
return SuccessResponse(datas)
@app.get("/menus/role/tree/options", summary="获取菜单列表树信息,角色权限使用")
async def get_menus_treeselect(
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create", "auth.role.update"]))
):
return SuccessResponse(await crud.MenuDal(auth.db).get_tree_list(mode=3))
@app.post("/menus", summary="创建菜单信息")
async def create_menu(menu: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create"]))):
if menu.parent_id:
menu.alwaysShow = False
return SuccessResponse(await crud.MenuDal(auth.db).create_data(data=menu))
@app.delete("/menus", summary="批量删除菜单", description="硬删除, 如果存在角色关联则无法删除")
async def delete_menus(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.delete"]))):
await crud.MenuDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/menus/{data_id}", summary="更新菜单信息")
async def put_menus(
data_id: int,
data: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.update"]))
):
return SuccessResponse(await crud.MenuDal(auth.db).put_data(data_id, data))
@app.get("/menus/{data_id}", summary="获取菜单信息")
async def get_menus(
data_id: int,
auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.view", "auth.menu.update"]))
):
schema = schemas.MenuSimpleOut
return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, v_schema=schema))
@app.get("/role/menus/tree/{role_id}", summary="获取菜单列表树信息以及角色菜单权限ID角色权限使用")
async def get_role_menu_tree(
role_id: int,
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create", "auth.role.update"]))
):
tree_data = await crud.MenuDal(auth.db).get_tree_list(mode=3)
role_menu_tree = await crud.RoleDal(auth.db).get_role_menu_tree(role_id)
return SuccessResponse({"role_menu_tree": role_menu_tree, "menus": tree_data})
###########################################################
# 部门管理
###########################################################
@app.get("/depts", summary="获取部门列表")
async def get_depts(
params: DeptParams = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
datas = await crud.DeptDal(auth.db).get_tree_list(1)
return SuccessResponse(datas)
@app.get("/dept/tree/options", summary="获取部门树选择项,添加/修改部门时使用")
async def get_dept_options(auth: Auth = Depends(FullAdminAuth())):
datas = await crud.DeptDal(auth.db).get_tree_list(mode=2)
return SuccessResponse(datas)
@app.get("/dept/user/tree/options", summary="获取部门树选择项,添加/修改用户时使用")
async def get_dept_treeselect(auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.DeptDal(auth.db).get_tree_list(mode=3))
@app.post("/depts", summary="创建部门信息")
async def create_dept(data: schemas.Dept, auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.DeptDal(auth.db).create_data(data=data))
@app.delete("/depts", summary="批量删除部门", description="硬删除, 如果存在用户关联则无法删除")
async def delete_depts(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth())):
await crud.DeptDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/depts/{data_id}", summary="更新部门信息")
async def put_dept(
data_id: int,
data: schemas.Dept,
auth: Auth = Depends(FullAdminAuth())
):
return SuccessResponse(await crud.DeptDal(auth.db).put_data(data_id, data))

View File

@ -0,0 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023-02-15 20:03:49
# @File : __init__.py
# @IDE : PyCharm
# @desc : 帮助中心

37
apps/vadmin/help/crud.py Normal file
View File

@ -0,0 +1,37 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023-02-15 20:03:49
# @File : crud.py
# @IDE : PyCharm
# @desc : 帮助中心 - 增删改查
from sqlalchemy.ext.asyncio import AsyncSession
from core.crud import DalBase
from . import models, schemas
class IssueDal(DalBase):
def __init__(self, db: AsyncSession):
super(IssueDal, self).__init__()
self.db = db
self.model = models.VadminIssue
self.schema = schemas.IssueSimpleOut
async def add_view_number(self, data_id: int) -> None:
"""
更新常见问题查看次数+1
"""
obj: models.VadminIssue = await self.get_data(data_id)
obj.view_number = obj.view_number + 1 if obj.view_number else 1
await self.flush(obj)
class IssueCategoryDal(DalBase):
def __init__(self, db: AsyncSession):
super(IssueCategoryDal, self).__init__()
self.db = db
self.model = models.VadminIssueCategory
self.schema = schemas.IssueCategorySimpleOut

View File

@ -0,0 +1,10 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023-02-15 20:03:49
# @File : __init__.py
# @IDE : PyCharm
# @desc : 初始化文件
from .issue import VadminIssue, VadminIssueCategory

View File

@ -0,0 +1,54 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : issue.py
# @IDE : PyCharm
# @desc : 常见问题
from sqlalchemy.orm import relationship, Mapped, mapped_column
from apps.vadmin.auth.models import VadminUser
from db.db_base import BaseModel
from sqlalchemy import String, Boolean, Integer, ForeignKey, Text
class VadminIssueCategory(BaseModel):
__tablename__ = "vadmin_help_issue_category"
__table_args__ = ({'comment': '常见问题类别表'})
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="类别名称")
platform: Mapped[str] = mapped_column(String(8), index=True, nullable=False, comment="展示平台")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
issues: Mapped[list["VadminIssue"]] = relationship(back_populates='category')
create_user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vadmin_auth_user.id", ondelete='RESTRICT'),
comment="创建人"
)
create_user: Mapped[VadminUser] = relationship(foreign_keys=create_user_id)
class VadminIssue(BaseModel):
__tablename__ = "vadmin_help_issue"
__table_args__ = ({'comment': '常见问题记录表'})
category_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vadmin_help_issue_category.id", ondelete='CASCADE'),
comment="类别"
)
category: Mapped[list["VadminIssueCategory"]] = relationship(foreign_keys=category_id, back_populates='issues')
title: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="标题")
content: Mapped[str] = mapped_column(Text, comment="内容")
view_number: Mapped[int] = mapped_column(Integer, default=0, comment="查看次数")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
create_user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vadmin_auth_user.id", ondelete='RESTRICT'),
comment="创建人"
)
create_user: Mapped[VadminUser] = relationship(foreign_keys=create_user_id)

View File

@ -0,0 +1,10 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023-02-15 20:03:49
# @File : __init__.py
# @IDE : PyCharm
# @desc : 初始化文件
from .issue import IssueParams, IssueCategoryParams

View File

@ -0,0 +1,51 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : issue.py
# @IDE : PyCharm
# @desc : 常见问题
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class IssueParams(QueryParams):
"""
列表分页
"""
def __init__(
self,
params: Paging = Depends(),
is_active: bool = None,
title: str = None,
category_id: int = None
):
super().__init__(params)
self.v_order = "desc"
self.v_order_field = "create_datetime"
self.is_active = is_active
self.category_id = category_id
self.title = ("like", title)
class IssueCategoryParams(QueryParams):
"""
列表分页
"""
def __init__(
self,
params: Paging = Depends(),
is_active: bool = None,
platform: str = None,
name: str = None
):
super().__init__(params)
self.v_order = "desc"
self.v_order_field = "create_datetime"
self.is_active = is_active
self.platform = platform
self.name = ("like", name)

View File

@ -0,0 +1,12 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023-02-15 20:03:49
# @File : __init__.py
# @IDE : PyCharm
# @desc : 初始化文件
from .issue import Issue, IssueSimpleOut, IssueListOut
from .issue_category import IssueCategory, IssueCategorySimpleOut, IssueCategoryListOut, IssueCategoryOptionsOut
from .issue_m2m import IssueCategoryPlatformOut

View File

@ -0,0 +1,38 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : issue.py
# @IDE : PyCharm
# @desc : 常见问题
from typing import Optional
from pydantic import BaseModel, ConfigDict
from core.data_types import DatetimeStr
from apps.vadmin.auth.schemas import UserSimpleOut
from .issue_category import IssueCategorySimpleOut
class Issue(BaseModel):
category_id: int | None = None
create_user_id: int | None = None
title: str | None = None
content: str | None = None
view_number: int | None = None
is_active: bool | None = None
class IssueSimpleOut(Issue):
model_config = ConfigDict(from_attributes=True)
id: int
update_datetime: DatetimeStr
create_datetime: DatetimeStr
class IssueListOut(IssueSimpleOut):
model_config = ConfigDict(from_attributes=True)
create_user: UserSimpleOut
category: IssueCategorySimpleOut

View File

@ -0,0 +1,43 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : issue_category.py
# @IDE : PyCharm
# @desc : 常见问题类别
from typing import Optional
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr
from apps.vadmin.auth.schemas import UserSimpleOut
class IssueCategory(BaseModel):
name: str | None = None
platform: str | None = None
is_active: bool | None = None
create_user_id: int | None = None
class IssueCategorySimpleOut(IssueCategory):
model_config = ConfigDict(from_attributes=True)
id: int
update_datetime: DatetimeStr
create_datetime: DatetimeStr
class IssueCategoryListOut(IssueCategorySimpleOut):
model_config = ConfigDict(from_attributes=True)
create_user: UserSimpleOut
class IssueCategoryOptionsOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
label: str = Field(alias='name')
value: int = Field(alias='id')

View File

@ -0,0 +1,27 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/2/17 15:18
# @File : issue_m2m.py.py
# @IDE : PyCharm
# @desc : 简要说明
from pydantic import BaseModel, ConfigDict
from core.data_types import DatetimeStr
from .issue import IssueSimpleOut
class IssueCategoryPlatformOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str | None = None
platform: str | None = None
is_active: bool | None = None
create_user_id: int | None = None
id: int
update_datetime: DatetimeStr
create_datetime: DatetimeStr
issues: list[IssueSimpleOut] = None

125
apps/vadmin/help/views.py Normal file
View File

@ -0,0 +1,125 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023-02-15 20:03:49
# @File : views.py
# @IDE : PyCharm
# @desc : 帮助中心视图
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from core.database import db_getter
from utils.response import SuccessResponse
from . import schemas, crud, params, models
from core.dependencies import IdList
from apps.vadmin.auth.utils.current import AllUserAuth
from apps.vadmin.auth.utils.validation.auth import Auth
app = APIRouter()
###########################################################
# 类别管理
###########################################################
@app.get("/issue/categorys", summary="获取类别列表")
async def get_issue_categorys(p: params.IssueCategoryParams = Depends(), auth: Auth = Depends(AllUserAuth())):
model = models.VadminIssueCategory
options = [joinedload(model.create_user)]
schema = schemas.IssueCategoryListOut
datas, count = await crud.IssueCategoryDal(auth.db).get_datas(
**p.dict(),
v_options=options,
v_schema=schema,
v_return_count=True
)
return SuccessResponse(datas, count=count)
@app.get("/issue/categorys/options", summary="获取类别选择项")
async def get_issue_categorys_options(auth: Auth = Depends(AllUserAuth())):
schema = schemas.IssueCategoryOptionsOut
return SuccessResponse(await crud.IssueCategoryDal(auth.db).get_datas(limit=0, is_active=True, v_schema=schema))
@app.post("/issue/categorys", summary="创建类别")
async def create_issue_category(data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())):
data.create_user_id = auth.user.id
return SuccessResponse(await crud.IssueCategoryDal(auth.db).create_data(data=data))
@app.delete("/issue/categorys", summary="批量删除类别", description="硬删除")
async def delete_issue_categorys(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.IssueCategoryDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/issue/categorys/{data_id}", summary="更新类别信息")
async def put_issue_category(data_id: int, data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.IssueCategoryDal(auth.db).put_data(data_id, data))
@app.get("/issue/categorys/{data_id}", summary="获取类别信息")
async def get_issue_category(data_id: int, auth: Auth = Depends(AllUserAuth())):
schema = schemas.IssueCategorySimpleOut
return SuccessResponse(await crud.IssueCategoryDal(auth.db).get_data(data_id, v_schema=schema))
@app.get("/issue/categorys/platform/{platform}", summary="获取平台中的常见问题类别列表")
async def get_issue_category_platform(platform: str, db: AsyncSession = Depends(db_getter)):
model = models.VadminIssueCategory
options = [joinedload(model.issues)]
schema = schemas.IssueCategoryPlatformOut
result = await crud.IssueCategoryDal(db).get_datas(
limit=0,
platform=platform,
is_active=True,
v_schema=schema,
v_options=options
)
return SuccessResponse(result)
###########################################################
# 问题管理
###########################################################
@app.get("/issues", summary="获取问题列表")
async def get_issues(p: params.IssueParams = Depends(), auth: Auth = Depends(AllUserAuth())):
model = models.VadminIssue
options = [joinedload(model.create_user), joinedload(model.category)]
schema = schemas.IssueListOut
datas, count = await crud.IssueDal(auth.db).get_datas(
**p.dict(),
v_options=options,
v_schema=schema,
v_return_count=True
)
return SuccessResponse(datas, count=count)
@app.post("/issues", summary="创建问题")
async def create_issue(data: schemas.Issue, auth: Auth = Depends(AllUserAuth())):
data.create_user_id = auth.user.id
return SuccessResponse(await crud.IssueDal(auth.db).create_data(data=data))
@app.delete("/issues", summary="批量删除问题", description="硬删除")
async def delete_issues(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.IssueDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/issues/{data_id}", summary="更新问题信息")
async def put_issue(data_id: int, data: schemas.Issue, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.IssueDal(auth.db).put_data(data_id, data))
@app.get("/issues/{data_id}", summary="获取问题信息")
async def get_issue(data_id: int, db: AsyncSession = Depends(db_getter)):
schema = schemas.IssueSimpleOut
return SuccessResponse(await crud.IssueDal(db).get_data(data_id, v_schema=schema))
@app.get("/issues/add/view/number/{data_id}", summary="更新常见问题查看次数+1")
async def issue_add_view_number(data_id: int, db: AsyncSession = Depends(db_getter)):
return SuccessResponse(await crud.IssueDal(db).add_view_number(data_id))

View File

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