项目初次提交

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

View File

508
apps/vadmin/system/crud.py Normal file
View File

@ -0,0 +1,508 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:18
# @File : crud.py
# @IDE : PyCharm
# @desc : 数据库 增删改查操作
import json
import os
from enum import Enum
from typing import Any
from redis.asyncio import Redis
from fastapi.encoders import jsonable_encoder
from motor.motor_asyncio import AsyncIOMotorDatabase
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from application.settings import STATIC_ROOT, SUBSCRIBE, REDIS_DB_ENABLE
from core.database import redis_getter
from core.mongo_manage import MongoManage
from utils.file.file_manage import FileManage
from . import models, schemas
from core.crud import DalBase
from core.exception import CustomException
from utils import status
from fastapi import Request
class DictTypeDal(DalBase):
def __init__(self, db: AsyncSession):
super(DictTypeDal, self).__init__()
self.db = db
self.model = models.VadminDictType
self.schema = schemas.DictTypeSimpleOut
async def get_dicts_details(self, dict_types: list[str]) -> dict:
"""
获取多个字典类型下的字典元素列表
"""
data = {}
options = [joinedload(self.model.details)]
objs = await DictTypeDal(self.db).get_datas(
limit=0,
v_return_objs=True,
v_options=options,
dict_type=("in", dict_types)
)
for obj in objs:
if not obj:
data[obj.dict_type] = []
continue
else:
data[obj.dict_type] = [schemas.DictDetailsSimpleOut.model_validate(i).model_dump() for i in obj.details]
return data
async def get_select_datas(self) -> list:
"""获取选择数据,全部数据"""
sql = select(self.model)
queryset = await self.db.execute(sql)
return [schemas.DictTypeOptionsOut.model_validate(i).model_dump() for i in queryset.scalars().all()]
class DictDetailsDal(DalBase):
def __init__(self, db: AsyncSession):
super(DictDetailsDal, self).__init__()
self.db = db
self.model = models.VadminDictDetails
self.schema = schemas.DictDetailsSimpleOut
class SettingsDal(DalBase):
def __init__(self, db: AsyncSession):
super(SettingsDal, self).__init__()
self.db = db
self.model = models.VadminSystemSettings
self.schema = schemas.SettingsSimpleOut
async def get_tab_values(self, tab_id: int) -> dict:
"""
获取系统配置标签下的信息
"""
datas = await self.get_datas(limit=0, tab_id=tab_id, v_return_objs=True)
result = {}
for data in datas:
if not data.disabled:
result[data.config_key] = data.config_value
return result
async def update_datas(self, datas: dict, request: Request) -> None:
"""
更新系统配置信息
更新ico图标步骤先将文件上传到本地然后点击提交后获取到文件地址将上传的新文件覆盖原有文件
原因ico图标的路径是在前端的index.html中固定的所以目前只能改变图片不改变路径
"""
for key, value in datas.items():
if key == "web_ico":
continue
elif key == "web_ico_local_path":
if not value:
continue
ico = await self.get_data(config_key="web_ico", tab_id=1)
web_ico = datas.get("web_ico")
if ico.config_value == web_ico:
continue
# 将上传的ico路径替换到static/system/favicon.ico文件
await FileManage.async_copy_file(value, os.path.join(STATIC_ROOT, "system/favicon.ico"))
sql = update(self.model).where(self.model.config_key == "web_ico").values(config_value=web_ico)
await self.db.execute(sql)
else:
sql = update(self.model).where(self.model.config_key == str(key)).values(config_value=value)
await self.db.execute(sql)
if "wx_server_app_id" in datas and REDIS_DB_ENABLE:
rd = redis_getter(request)
await rd.client().set("wx_server", json.dumps(datas))
elif "sms_access_key" in datas and REDIS_DB_ENABLE:
rd = redis_getter(request)
await rd.client().set('aliyun_sms', json.dumps(datas))
async def get_base_config(self) -> dict:
"""
获取系统基本信息
"""
ignore_configs = ["wx_server_app_id", "wx_server_app_secret"]
datas = await self.get_datas(limit=0, tab_id=("in", ["1", "9"]), disabled=False, v_return_objs=True)
result = {}
for config in datas:
if config.config_key not in ignore_configs:
result[config.config_key] = config.config_value
return result
class SettingsTabDal(DalBase):
def __init__(self, db: AsyncSession):
super(SettingsTabDal, self).__init__(db, models.VadminSystemSettingsTab, schemas.SettingsTabSimpleOut)
async def get_classify_tab_values(self, classify: list[str], hidden: bool | None = False) -> dict:
"""
获取系统配置分类下的标签信息
"""
model = models.VadminSystemSettingsTab
options = [joinedload(model.settings)]
datas = await self.get_datas(
limit=0,
v_options=options,
classify=("in", classify),
disabled=False,
v_return_objs=True,
hidden=hidden
)
return self.__generate_values(datas)
async def get_tab_name_values(self, tab_names: list[str], hidden: bool | None = False) -> dict:
"""
获取系统配置标签下的标签信息
"""
model = models.VadminSystemSettingsTab
options = [joinedload(model.settings)]
datas = await self.get_datas(
limit=0,
v_options=options,
tab_name=("in", tab_names),
disabled=False,
v_return_objs=True,
hidden=hidden
)
return self.__generate_values(datas)
@classmethod
def __generate_values(cls, datas: list[models.VadminSystemSettingsTab]) -> dict:
"""
生成字典值
"""
return {
tab.tab_name: {
item.config_key: item.config_value
for item in tab.settings
if not item.disabled
}
for tab in datas
}
class TaskDal(MongoManage):
class JobOperation(Enum):
add = "add_job"
def __init__(self, db: AsyncIOMotorDatabase):
super(TaskDal, self).__init__(db, "vadmin_system_task", schemas.TaskSimpleOut)
async def get_task(
self,
_id: str = None,
v_return_none: bool = False,
v_schema: Any = None,
**kwargs
) -> dict | None:
"""
获取单个数据,默认使用 ID 查询,否则使用关键词查询
包括临时字段 last_run_datetimeis_active
is_active: 只有在 scheduler_task_jobs 任务运行表中存在相同 _id 才表示任务添加成功,任务状态才为 True
last_run_datetime: 在 scheduler_task_record 中获取该任务最近一次执行完成的时间
:param _id: 数据 ID
:param v_return_none: 是否返回空 None否则抛出异常默认抛出异常
:param v_schema: 指定使用的序列化对象
"""
if _id:
kwargs["_id"] = ("ObjectId", _id)
params = self.filter_condition(**kwargs)
pipeline = [
{
'$addFields': {
'str_id': {'$toString': '$_id'}
}
},
{
'$lookup': {
'from': 'scheduler_task_jobs',
'localField': 'str_id',
'foreignField': '_id',
'as': 'matched_jobs'
}
},
{
'$lookup': {
'from': 'scheduler_task_record',
'localField': 'str_id',
'foreignField': 'job_id',
'as': 'matched_records'
}
},
{
'$addFields': {
'is_active': {
'$cond': {
'if': {'$ne': ['$matched_jobs', []]},
'then': True,
'else': False
}
},
'last_run_datetime': {
'$ifNull': [
{'$arrayElemAt': ['$matched_records.create_datetime', -1]},
None
]
}
}
},
{
'$business': {
'matched_records': 0,
'matched_jobs': 0
}
},
{
'$match': params
},
{
'$facet': {
'documents': [
{'$limit': 1},
]
}
}
]
# 执行聚合查询
cursor = self.collection.aggregate(pipeline)
result = await cursor.to_list(length=None)
data = result[0]['documents']
if not data and v_return_none:
return None
elif not data:
raise CustomException("未查找到对应数据", code=status.HTTP_404_NOT_FOUND)
data = data[0]
if data and v_schema:
return jsonable_encoder(v_schema(**data))
return data
async def get_tasks(
self,
page: int = 1,
limit: int = 10,
v_schema: Any = None,
v_order: str = None,
v_order_field: str = None,
**kwargs
) -> tuple:
"""
获取任务信息列表
添加了两个临时字段
is_active: 只有在 scheduler_task_jobs 任务运行表中存在相同 _id 才表示任务添加成功,任务状态才为 True
last_run_datetime: 在 scheduler_task_record 中获取该任务最近一次执行完成的时间
"""
v_order_field = v_order_field if v_order_field else 'create_datetime'
v_order = -1 if v_order in self.ORDER_FIELD else 1
params = self.filter_condition(**kwargs)
pipeline = [
{
'$addFields': {
'str_id': {'$toString': '$_id'}
}
},
{
'$lookup': {
'from': 'scheduler_task_jobs',
'localField': 'str_id',
'foreignField': '_id',
'as': 'matched_jobs'
}
},
{
'$lookup': {
'from': 'scheduler_task_record',
'localField': 'str_id',
'foreignField': 'job_id',
'as': 'matched_records'
}
},
{
'$addFields': {
'is_active': {
'$cond': {
'if': {'$ne': ['$matched_jobs', []]},
'then': True,
'else': False
}
},
'last_run_datetime': {
'$ifNull': [
{'$arrayElemAt': ['$matched_records.create_datetime', -1]},
None
]
}
}
},
{
'$business': {
'matched_records': 0,
'matched_jobs': 0
}
},
{
'$match': params
},
{
'$facet': {
'documents': [
{'$sort': {v_order_field: v_order}},
{'$limit': limit},
{'$skip': (page - 1) * limit}
],
'count': [{'$count': 'total'}]
}
}
]
# 执行聚合查询
cursor = self.collection.aggregate(pipeline)
result = await cursor.to_list(length=None)
datas = result[0]['documents']
count = result[0]['count'][0]['total'] if result[0]['count'] else 0
if count == 0:
return [], 0
elif v_schema:
datas = [jsonable_encoder(v_schema(**data)) for data in datas]
elif self.schema:
datas = [jsonable_encoder(self.schema(**data)) for data in datas]
return datas, count
async def add_task(self, rd: Redis, data: dict) -> int:
"""
添加任务到消息队列
使用消息无保留策略:无保留是指当发送者向某个频道发送消息时,如果没有订阅该频道的调用方,就直接将该消息丢弃。
:param rd: redis 对象
:param data: 行数据字典
:return: 接收到消息的订阅者数量。
"""
exec_strategy = data.get("exec_strategy")
job_params = {
"name": data.get("_id"),
"job_class": data.get("job_class"),
"expression": data.get("expression")
}
if exec_strategy == "interval" or exec_strategy == "cron":
job_params["start_date"] = data.get("start_date")
job_params["end_date"] = data.get("end_date")
message = {
"operation": self.JobOperation.add.value,
"task": {
"exec_strategy": data.get("exec_strategy"),
"job_params": job_params
}
}
return await rd.publish(SUBSCRIBE, json.dumps(message).encode('utf-8'))
async def create_task(self, rd: Redis, data: schemas.Task) -> dict:
"""
创建任务
"""
data_dict = data.model_dump()
is_active = data_dict.pop('is_active')
insert_result = await super().create_data(data_dict)
obj = await self.get_task(insert_result.inserted_id, v_schema=schemas.TaskSimpleOut)
# 如果分组不存在则新增分组
group = await TaskGroupDal(self.db).get_data(value=data.group, v_return_none=True)
if not group:
await TaskGroupDal(self.db).create_data({"value": data.group})
result = {
"subscribe_number": 0,
"is_active": is_active
}
if is_active:
# 创建任务成功后, 如果任务状态为 True则向消息队列中发送任务
result['subscribe_number'] = await self.add_task(rd, obj)
return result
async def put_task(self, rd: Redis, _id: str, data: schemas.Task) -> dict:
"""
更新任务
"""
data_dict = data.model_dump()
is_active = data_dict.pop('is_active')
await super(TaskDal, self).put_data(_id, data)
obj: dict = await self.get_task(_id, v_schema=schemas.TaskSimpleOut)
# 如果分组不存在则新增分组
group = await TaskGroupDal(self.db).get_data(value=data.group, v_return_none=True)
if not group:
await TaskGroupDal(self.db).create_data({"value": data.group})
try:
# 删除正在运行中的 Job
await SchedulerTaskJobsDal(self.db).delete_data(_id)
except CustomException as e:
pass
result = {
"subscribe_number": 0,
"is_active": is_active
}
if is_active:
# 更新任务成功后, 如果任务状态为 True则向消息队列中发送任务
result['subscribe_number'] = await self.add_task(rd, obj)
return result
async def delete_task(self, _id: str) -> bool:
"""
删除任务
"""
result = await super(TaskDal, self).delete_data(_id)
try:
# 删除正在运行中的 Job
await SchedulerTaskJobsDal(self.db).delete_data(_id)
except CustomException as e:
pass
return result
async def run_once_task(self, rd: Redis, _id: str) -> int:
"""
执行一次任务
"""
obj: dict = await self.get_data(_id, v_schema=schemas.TaskSimpleOut)
message = {
"operation": self.JobOperation.add.value,
"task": {
"exec_strategy": "once",
"job_params": {
"name": obj.get("_id"),
"job_class": obj.get("job_class")
}
}
}
return await rd.publish(SUBSCRIBE, json.dumps(message).encode('utf-8'))
class TaskGroupDal(MongoManage):
def __init__(self, db: AsyncIOMotorDatabase):
super(TaskGroupDal, self).__init__(db, "vadmin_system_task_group")
class TaskRecordDal(MongoManage):
def __init__(self, db: AsyncIOMotorDatabase):
super(TaskRecordDal, self).__init__(db, "scheduler_task_record")
class SchedulerTaskJobsDal(MongoManage):
def __init__(self, db: AsyncIOMotorDatabase):
super(SchedulerTaskJobsDal, self).__init__(db, "scheduler_task_jobs", is_object_id=False)

View File

@ -0,0 +1,2 @@
from .dict import VadminDictType, VadminDictDetails
from .settings import VadminSystemSettings, VadminSystemSettingsTab

View File

@ -0,0 +1,40 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : user.py
# @IDE : PyCharm
# @desc : 系统字典模型
from sqlalchemy.orm import relationship, Mapped, mapped_column
from db.db_base import BaseModel
from sqlalchemy import Column, String, Boolean, ForeignKey, Integer
class VadminDictType(BaseModel):
__tablename__ = "vadmin_system_dict_type"
__table_args__ = ({'comment': '字典类型表'})
dict_name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典名称")
dict_type: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典类型")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="字典状态,是否禁用")
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
details: Mapped[list["VadminDictDetails"]] = relationship(back_populates="dict_type")
class VadminDictDetails(BaseModel):
__tablename__ = "vadmin_system_dict_details"
__table_args__ = ({'comment': '字典详情表'})
label: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典标签")
value: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典键值")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="字典状态,是否禁用")
is_default: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否默认")
order: Mapped[int] = mapped_column(Integer, comment="字典排序")
dict_type_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vadmin_system_dict_type.id", ondelete='CASCADE'),
comment="关联字典类型"
)
dict_type: Mapped[VadminDictType] = relationship(foreign_keys=dict_type_id, back_populates="details")
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")

View File

@ -0,0 +1,42 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/7/7 13:41
# @File : settings.py
# @IDE : PyCharm
# @desc : 系统字典模型
from sqlalchemy.orm import relationship, Mapped, mapped_column
from db.db_base import BaseModel
from sqlalchemy import String, Integer, ForeignKey, Boolean, Text
class VadminSystemSettingsTab(BaseModel):
__tablename__ = "vadmin_system_settings_tab"
__table_args__ = ({'comment': '系统配置分类表'})
title: Mapped[str] = mapped_column(String(255), comment="标题")
classify: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="分类键")
tab_label: Mapped[str] = mapped_column(String(255), comment="tab标题")
tab_name: Mapped[str] = mapped_column(String(255), index=True, nullable=False, unique=True, comment="tab标识符")
hidden: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否隐藏")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
settings: Mapped[list["VadminSystemSettings"]] = relationship(back_populates="tab")
class VadminSystemSettings(BaseModel):
__tablename__ = "vadmin_system_settings"
__table_args__ = ({'comment': '系统配置表'})
config_label: Mapped[str] = mapped_column(String(255), comment="配置表标签")
config_key: Mapped[str] = mapped_column(String(255), index=True, nullable=False, unique=True, comment="配置表键")
config_value: Mapped[str | None] = mapped_column(Text, comment="配置表内容")
remark: Mapped[str | None] = mapped_column(String(255), comment="备注信息")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
tab_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vadmin_system_settings_tab.id", ondelete='CASCADE'),
comment="关联tab标签"
)
tab: Mapped[VadminSystemSettingsTab] = relationship(foreign_keys=tab_id, back_populates="settings")

View File

@ -0,0 +1,3 @@
from .dict_type import DictTypeParams
from .dict_detail import DictDetailParams
from .task import TaskParams

View File

@ -0,0 +1,23 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : dict_type.py
# @IDE : PyCharm
# @desc : 查询参数-类依赖项
"""
类依赖项-官方文档https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
"""
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class DictDetailParams(QueryParams):
"""
列表分页
"""
def __init__(self, dict_type_id: int = None, label: str = None, params: Paging = Depends()):
super().__init__(params)
self.dict_type_id = dict_type_id
self.label = ("like", label)

View File

@ -0,0 +1,23 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : dict_type.py
# @IDE : PyCharm
# @desc : 查询参数-类依赖项
"""
类依赖项-官方文档https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
"""
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class DictTypeParams(QueryParams):
"""
列表分页
"""
def __init__(self, dict_name: str = None, dict_type: str = None, params: Paging = Depends()):
super().__init__(params)
self.dict_name = ("like", dict_name)
self.dict_type = ("like", dict_type)

View File

@ -0,0 +1,32 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/6/25 14:50
# @File : task.py
# @IDE : PyCharm
# @desc : 简要说明
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class TaskParams(QueryParams):
"""
列表分页
"""
def __init__(self, name: str = None, _id: str = None, group: str = None, params: Paging = Depends()):
super().__init__(params)
self.name = ("like", name)
self.group = group
self._id = ("ObjectId", _id)
self.v_order = "desc"
class TaskRecordParams(QueryParams):
"""
列表分页
"""
def __init__(self, job_id: str = None, name: str = None, params: Paging = Depends()):
super().__init__(params)
self.job_id = ("like", job_id)
self.name = ("like", name)
self.v_order = "desc"

View File

@ -0,0 +1,4 @@
from .dict import DictType, DictDetails, DictTypeSimpleOut, DictDetailsSimpleOut, DictTypeOptionsOut
from .settings_tab import SettingsTab, SettingsTabSimpleOut
from .settings import Settings, SettingsSimpleOut
from .task import Task, TaskSimpleOut

View File

@ -0,0 +1,53 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : dict.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict, Field
from core.data_types import DatetimeStr
class DictType(BaseModel):
dict_name: str
dict_type: str
disabled: bool | None = False
remark: str | None = None
class DictTypeSimpleOut(DictType):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr
class DictTypeOptionsOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
label: str = Field(alias='dict_name')
value: int = Field(alias='id')
disabled: bool
class DictDetails(BaseModel):
label: str
value: str
disabled: bool | None = False
is_default: bool | None = False
remark: str | None = None
order: int | None = None
dict_type_id: int
class DictDetailsSimpleOut(DictDetails):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr

View File

@ -0,0 +1,29 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : settings.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict
from core.data_types import DatetimeStr
class Settings(BaseModel):
config_label: str | None = None
config_key: str
config_value: str | None = None
remark: str | None = None
disabled: bool | None = None
tab_id: int
class SettingsSimpleOut(Settings):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr

View File

@ -0,0 +1,28 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/18 22:19
# @File : settings_tab.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict
from core.data_types import DatetimeStr
class SettingsTab(BaseModel):
title: str
classify: str
tab_label: str
tab_name: str
hidden: bool
class SettingsTabSimpleOut(SettingsTab):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr

View File

@ -0,0 +1,32 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/6/25 15:08
# @File : task.py
# @IDE : PyCharm
# @desc : 简要说明
from pydantic import BaseModel, Field, ConfigDict
from core.data_types import DatetimeStr, ObjectIdStr
class Task(BaseModel):
name: str
group: str | None = None
job_class: str
exec_strategy: str
expression: str
is_active: bool | None = True # 临时字段,不在表中创建
remark: str | None = None
start_date: DatetimeStr | None = None
end_date: DatetimeStr | None = None
class TaskSimpleOut(Task):
model_config = ConfigDict(from_attributes=True)
id: ObjectIdStr = Field(..., alias='_id')
create_datetime: DatetimeStr
update_datetime: DatetimeStr
last_run_datetime: DatetimeStr | None = None # 临时字段,不在表中创建

263
apps/vadmin/system/views.py Normal file
View File

@ -0,0 +1,263 @@
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2021/10/24 16:44
# @File : views.py
# @IDE : PyCharm
# @desc : 主要接口文件
from redis.asyncio import Redis
from fastapi import APIRouter, Depends, Body, UploadFile, Form, Request
from motor.motor_asyncio import AsyncIOMotorDatabase
from sqlalchemy.ext.asyncio import AsyncSession
from application.settings import ALIYUN_OSS
from core.database import db_getter, redis_getter, mongo_getter
from utils.file.aliyun_oss import AliyunOSS, BucketConf
from utils.file.file_manage import FileManage
from utils.response import SuccessResponse, ErrorResponse
from utils.sms.code import CodeSMS
from . import schemas, crud
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 DictTypeParams, DictDetailParams, TaskParams
from apps.vadmin.auth import crud as vadmin_auth_crud
from .params.task import TaskRecordParams
app = APIRouter()
###########################################################
# 字典类型管理
###########################################################
@app.get("/dict/types", summary="获取字典类型列表")
async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas, count = await crud.DictTypeDal(auth.db).get_datas(**p.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.post("/dict/types", summary="创建字典类型")
async def create_dict_types(data: schemas.DictType, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictTypeDal(auth.db).create_data(data=data))
@app.delete("/dict/types", summary="批量删除字典类型")
async def delete_dict_types(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.DictTypeDal(auth.db).delete_datas(ids=ids.ids)
return SuccessResponse("删除成功")
@app.post("/dict/types/details", summary="获取多个字典类型下的字典元素列表")
async def post_dicts_details(
auth: Auth = Depends(AllUserAuth()),
dict_types: list[str] = Body(None, title="字典元素列表", description="查询字典元素列表")
):
datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types)
return SuccessResponse(datas)
@app.get("/dict/types/options", summary="获取字典类型选择项")
async def get_dicts_options(auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictTypeDal(auth.db).get_select_datas())
@app.put("/dict/types/{data_id}", summary="更新字典类型")
async def put_dict_types(data_id: int, data: schemas.DictType, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictTypeDal(auth.db).put_data(data_id, data))
@app.get("/dict/types/{data_id}", summary="获取字典类型详细")
async def get_dict_type(data_id: int, auth: Auth = Depends(AllUserAuth())):
schema = schemas.DictTypeSimpleOut
return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, v_schema=schema))
###########################################################
# 字典元素管理
###########################################################
@app.post("/dict/details", summary="创建字典元素")
async def create_dict_details(data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictDetailsDal(auth.db).create_data(data=data))
@app.get("/dict/details", summary="获取单个字典类型下的字典元素列表,分页")
async def get_dict_details(params: DictDetailParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas, count = await crud.DictDetailsDal(auth.db).get_datas(**params.dict(), v_return_count=True)
return SuccessResponse(datas, count=count)
@app.delete("/dict/details", summary="批量删除字典元素", description="硬删除")
async def delete_dict_details(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.DictDetailsDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/dict/details/{data_id}", summary="更新字典元素")
async def put_dict_details(data_id: int, data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictDetailsDal(auth.db).put_data(data_id, data))
@app.get("/dict/details/{data_id}", summary="获取字典元素详情")
async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())):
schema = schemas.DictDetailsSimpleOut
return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, v_schema=schema))
###########################################################
# 文件上传管理
###########################################################
@app.post("/upload/image/to/oss", summary="上传图片到阿里云OSS")
async def upload_image_to_oss(file: UploadFile, path: str = Form(...)):
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(path, file)
return SuccessResponse(result)
@app.post("/upload/video/to/oss", summary="上传视频到阿里云OSS")
async def upload_video_to_oss(file: UploadFile, path: str = Form(...)):
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_video(path, file)
return SuccessResponse(result)
@app.post("/upload/file/to/oss", summary="上传文件到阿里云OSS")
async def upload_file_to_oss(file: UploadFile, path: str = Form(...)):
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_file(path, file)
return SuccessResponse(result)
@app.post("/upload/image/to/local", summary="上传图片到本地")
async def upload_image_to_local(file: UploadFile, path: str = Form(...)):
manage = FileManage(file, path)
path = await manage.save_image_local()
return SuccessResponse(path)
###########################################################
# 短信服务管理
###########################################################
@app.post("/sms/send", summary="发送短信验证码(阿里云服务)")
async def sms_send(telephone: str, rd: Redis = Depends(redis_getter), auth: Auth = Depends(OpenAuth())):
user = await vadmin_auth_crud.UserDal(auth.db).get_data(telephone=telephone, v_return_none=True)
if not user:
return ErrorResponse("手机号不存在!")
sms = CodeSMS(telephone, rd)
return SuccessResponse(await sms.main_async())
###########################################################
# 系统配置管理
###########################################################
@app.post("/settings/tabs", summary="获取系统配置标签列表")
async def get_settings_tabs(classifys: list[str] = Body(...), auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.SettingsTabDal(auth.db).get_datas(limit=0, classify=("in", classifys)))
@app.get("/settings/tabs/values", summary="获取系统配置标签下的信息")
async def get_settings_tabs_values(tab_id: int, auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.SettingsDal(auth.db).get_tab_values(tab_id=tab_id))
@app.put("/settings/tabs/values", summary="更新系统配置信息")
async def put_settings_tabs_values(
request: Request,
datas: dict = Body(...),
auth: Auth = Depends(FullAdminAuth())
):
return SuccessResponse(await crud.SettingsDal(auth.db).update_datas(datas, request))
@app.get("/settings/base/config", summary="获取系统基础配置", description="每次进入系统中时使用")
async def get_setting_base_config(db: AsyncSession = Depends(db_getter)):
return SuccessResponse(await crud.SettingsDal(db).get_base_config())
@app.get("/settings/privacy", summary="获取隐私协议")
async def get_settings_privacy(auth: Auth = Depends(OpenAuth())):
return SuccessResponse((await crud.SettingsDal(auth.db).get_data(config_key="web_privacy")).config_value)
@app.get("/settings/agreement", summary="获取用户协议")
async def get_settings_agreement(auth: Auth = Depends(OpenAuth())):
return SuccessResponse((await crud.SettingsDal(auth.db).get_data(config_key="web_agreement")).config_value)
###########################################################
# 定时任务管理
###########################################################
@app.get("/tasks", summary="获取定时任务列表")
async def get_tasks(
p: TaskParams = Depends(),
db: AsyncIOMotorDatabase = Depends(mongo_getter),
auth: Auth = Depends(AllUserAuth())
):
datas, count = await crud.TaskDal(db).get_tasks(**p.dict())
return SuccessResponse(datas, count=count)
@app.post("/tasks", summary="添加定时任务")
async def post_tasks(
data: schemas.Task,
db: AsyncIOMotorDatabase = Depends(mongo_getter),
rd: Redis = Depends(redis_getter),
auth: Auth = Depends(AllUserAuth())
):
return SuccessResponse(await crud.TaskDal(db).create_task(rd, data))
@app.put("/tasks", summary="更新定时任务")
async def put_tasks(
_id: str,
data: schemas.Task,
db: AsyncIOMotorDatabase = Depends(mongo_getter),
rd: Redis = Depends(redis_getter),
auth: Auth = Depends(AllUserAuth())
):
return SuccessResponse(await crud.TaskDal(db).put_task(rd, _id, data))
@app.delete("/tasks", summary="删除单个定时任务")
async def delete_task(
_id: str,
db: AsyncIOMotorDatabase = Depends(mongo_getter),
auth: Auth = Depends(AllUserAuth())
):
return SuccessResponse(await crud.TaskDal(db).delete_task(_id))
@app.get("/task", summary="获取定时任务详情")
async def get_task(
_id: str,
db: AsyncIOMotorDatabase = Depends(mongo_getter),
auth: Auth = Depends(AllUserAuth())
):
return SuccessResponse(await crud.TaskDal(db).get_task(_id, v_schema=schemas.TaskSimpleOut))
@app.post("/task", summary="执行一次定时任务")
async def run_once_task(
_id: str,
db: AsyncIOMotorDatabase = Depends(mongo_getter),
rd: Redis = Depends(redis_getter),
auth: Auth = Depends(AllUserAuth())
):
return SuccessResponse(await crud.TaskDal(db).run_once_task(rd, _id))
###########################################################
# 定时任务分组管理
###########################################################
@app.get("/task/group/options", summary="获取定时任务分组选择项列表")
async def get_task_group_options(db: AsyncIOMotorDatabase = Depends(mongo_getter), auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.TaskGroupDal(db).get_datas(limit=0))
###########################################################
# 定时任务调度日志
###########################################################
@app.get("/task/records", summary="获取定时任务调度日志列表")
async def get_task_records(
p: TaskRecordParams = Depends(),
db: AsyncIOMotorDatabase = Depends(mongo_getter),
auth: Auth = Depends(AllUserAuth())
):
count = await crud.TaskRecordDal(db).get_count(**p.to_count())
datas = await crud.TaskRecordDal(db).get_datas(**p.dict())
return SuccessResponse(datas, count=count)