完成项目信息管理的迁移

This commit is contained in:
sunyugang 2025-04-11 14:30:48 +08:00
parent 9e14a3256f
commit 4439687870
41 changed files with 276 additions and 1224 deletions

374
README.md
View File

@ -1,373 +1 @@
# 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
)
```
基于aicheckv2改写的项目增加后台管理用户部门权限

View File

@ -38,7 +38,6 @@ 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 *

View File

@ -47,6 +47,13 @@ ALIYUN_OSS = {
"baseUrl": "baseUrl"
}
HUAWEI_OBS = {
"AccessKeyID": "HPUAICKSMPQP4XSATCLX",
"SecretAccessKey": "DoAL1RWydNwSQRsodS4c34nC0XyXy6TAsetVMasy",
"server": "https://obs.cn-north-4.myhuaweicloud.com",
"bucketName": "aicheckv2"
}
"""
获取IP地址归属地
文档https://user.ip138.com/ip/doc

View File

@ -46,6 +46,13 @@ ALIYUN_OSS = {
"baseUrl": "baseUrl"
}
HUAWEI_OBS = {
"AccessKeyID": "HPUAICKSMPQP4XSATCLX",
"SecretAccessKey": "DoAL1RWydNwSQRsodS4c34nC0XyXy6TAsetVMasy",
"server": "https://obs.cn-north-4.myhuaweicloud.com",
"bucketName": "aicheckv2"
}
"""
获取IP地址归属地
文档https://user.ip138.com/ip/doc

View File

@ -9,10 +9,7 @@ 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
@ -22,9 +19,6 @@ urlpatterns = [
{"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": ["项目管理"]},
]

View File

@ -9,18 +9,25 @@ 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
from utils.huawei_obs import ObsClient
from utils import status
from core.exception import CustomException
if application.settings.DEBUG:
from application.config.development import datasets_url, runs_url, detect_url, yolo_url, images_url
from application.config.development import datasets_url, runs_url, images_url
else:
from application.config.production import datasets_url, runs_url, detect_url, yolo_url, images_url
from application.config.production import datasets_url, runs_url, images_url
from typing import Any, List
from core.crud import DalBase
from fastapi import UploadFile
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__()
@ -106,15 +113,112 @@ class ProjectInfoDal(DalBase):
class ProjectImageDal(DalBase):
"""
项目图片
"""
def __init__(self, db: AsyncSession):
super(ProjectImageDal, self).__init__()
self.db = db
self.model = models.ProjectImage
self.schema = schemas.ProjectImageSimpleOut
self.schema = schemas.ProjectImageOut
async def img_page(self, param: params.ProjectImageParams):
"""
分页查询图片信息然后关联一个图片的标签数量
"""
subquery = (
select(
models.ProjectImgLabel.image_id,
func.ifnull(func.count(models.ProjectImgLabel.id), 0).label('label_count')
)
.group_by(models.ProjectImgLabel.image_id)
.subquery()
)
# 2 主查询
query = (
select(
models.ProjectImage,
func.ifnull(subquery.c.label_count, 0).label('label_count')
)
.outerjoin(subquery, models.ProjectImage.id == subquery.c.image_id)
)
v_where = [models.ProjectImage.project_id == param.project_id, models.ProjectImage.img_type == param.img_type]
sql = await self.filter_core(
v_start_sql=query,
v_where=v_where,
v_return_sql=True,
v_order=param.v_order,
v_order_field=param.v_order_field
)
count = await self.get_count_sql(sql)
if param.limit != 0:
sql = sql.offset((param.page - 1) * param.limit).limit(param.limit)
queryset = await self.db.execute(sql)
result = queryset.all()
datas = []
for result in result:
data = schemas.ProjectImageOut.model_validate(result[0])
data.label_count = int(result[1])
datas.append(data.model_dump())
return datas, count
async def upload_imgs(self, files: List[UploadFile], pro: schemas.ProjectInfoOut, img_type: str) -> int:
"""
上传项目图片
"""
image_models = []
for file in files:
image = models.ProjectImage()
image.project_id = pro.id
image.file_name = file.filename
image.img_type = img_type
# 保存原图
path = os.save_images(images_url, pro.project_no, file=file)
image.image_url = path
# 上传图片到obs
object_key = pro.project_no + '/' + img_type + '/' + file.filename
success, key, url = ObsClient.put_file(object_key=object_key, file_path=path)
if success:
image.object_key = object_key
image.thumb_image_url = url
else:
raise CustomException("obs上传失败", code=status.HTTP_ERROR)
image_models.append(image)
await self.create_datas(datas=image_models)
return len(image_models)
async def check_img_name(self, file_name: str, project_id: int, img_type: str):
"""
校验相同的项目相同的文件类型是否有同名的文件
"""
count = await self.get_count(v_where=[
models.ProjectImage.file_name == file_name,
models.ProjectImage.project_id == project_id,
models.ProjectImage.img_type == img_type
])
return count > 0
async def del_img(self, ids: List[int]):
"""
删除图片删除数据库数据删除本地的文件删除obs中的文件
"""
file_urls = []
object_keys = []
for img_id in ids:
image = self.get_data(data_id=img_id)
if image:
file_urls.append(image.image_url)
object_keys.append(image.object_key)
os.delete_file_if_exists(**file_urls)
ObsClient.del_objects(object_keys)
await self.delete_datas(ids)
class ProjectLabelDal(DalBase):
"""
项目标签
"""
def __init__(self, db: AsyncSession):
super(ProjectLabelDal, self).__init__()
@ -139,18 +243,37 @@ class ProjectLabelDal(DalBase):
class ProjectImgLabelDal(DalBase):
"""
图片标签信息
"""
def __init__(self, db: AsyncSession):
super(ProjectImgLabelDal, self).__init__()
self.db = db
self.model = models.ProjectImgLabel
self.schema = schemas.ProjectImgLabelSimpleOut
async def add_img_label(self, img_label_in: schemas.ProjectImgLeaferLabel):
# 先把历史数据都删掉,然后再保存
image_id = img_label_in.image_id
await self.delete_datas(image_id=image_id)
img_labels = [self.model(**i.model_dump()) for i in img_label_in.label_infos]
for img in img_labels:
img.image_id = image_id
await self.create_datas(img_labels)
class ProjectImgLeaferDal(DalBase):
"""
图片标注信息-leafer.js
"""
def __init__(self, db: AsyncSession):
super(ProjectImgLeaferDal, self).__init__()
self.db = db
self.model = models.ProjectImgLeafer
self.schema = schemas.ProjectImgLeaferSimpleOut
self.schema = schemas.ProjectImgLeaferOut
async def add_leafer(self, img_label_in: schemas.ProjectImgLeaferLabel):
# 先把历史数据都删掉,然后再保存
image_id = img_label_in.image_id
await self.delete_datas(image_id=image_id)
await self.create_data(data=self.model(**img_label_in.model_dump()))

View File

@ -43,6 +43,7 @@ class ProjectImage(BaseModel):
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)
object_key: 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)

View File

@ -15,11 +15,9 @@ class ProjectImageParams(QueryParams):
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

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

View File

@ -14,8 +14,8 @@ 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="文件名称")
thumb_image_url: Optional[str] = Field(None, description="图片在obs上的链接")
create_time: DatetimeStr
model_config = ConfigDict(from_attributes=True)
@ -25,14 +25,8 @@ class ProjectImageOut(BaseModel):
id: Optional[int] = Field(None, description="id")
project_id: Optional[int] = Field(..., description="项目id")
file_name: Optional[str] = Field(None, description="文件名称")
thumb_image_url: Optional[str] = Field(None, description="图片在obs上的链接")
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

@ -5,12 +5,11 @@
# @File : project_img_leafer.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from . import ProjectImgLabelIn
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
from . import ProjectImgLabelIn
class ProjectImgLeaferLabel(BaseModel):
image_id: Optional[int] = Field(..., description="图片id")

View File

@ -4,12 +4,13 @@
# @Create Time : 2025/04/03 10:25
# @File : views.py
# @IDE : PyCharm
# @desc : 路由,视图文件
# @desc : 路由,项目信息管理,包括项目主体,项目图片,项目标签和图片的标注信息
from utils.response import SuccessResponse, ErrorResponse
from . import params, schemas, crud, models
from core.dependencies import IdList
from fastapi import APIRouter, Depends
from typing import List
from fastapi import APIRouter, Depends, UploadFile, File, Form
from apps.vadmin.auth.utils.current import FullAdminAuth
from apps.vadmin.auth.utils.validation.auth import Auth
@ -42,7 +43,7 @@ async def add_project(
@app.get("/info/{pro_id}", summary="查询项目信息")
async def project(
async def project_info(
pro_id: int,
auth: Auth = Depends(FullAdminAuth())
):
@ -98,10 +99,64 @@ async def delete_label(
label_ids: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
# 删除标签信息,然后删除图片标签关联表
await crud.ProjectLabelDal(auth.db).delete_datas(label_ids.ids)
for label_id in label_ids.ids:
await crud.ProjectImgLabelDal(auth.db).delete_datas(label_id=label_id)
return SuccessResponse(msg="删除成功")
@app.get("/img", summary="获取图片列表,分两种情况,一个带分页的,一个不带分页的")
async def project_pager(
param: params.ProjectImageParams = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
if param.limit:
# 分页查询,关联一个标签数量
datas, count = await crud.ProjectImageDal(auth.db).img_page(param)
return SuccessResponse(data=datas, count=count)
else:
# 直接查询
datas = await crud.ProjectImageDal(auth.db).get_datas(v_schema=schemas.ProjectImage, **param.dict())
return SuccessResponse(data=datas)
@app.post("/img", summary="上传图片")
async def up_img(
project_id: int = Form(...),
files: List[UploadFile] = File(...),
img_type: str = Form(...),
auth: Auth = Depends(FullAdminAuth())
):
pro = await crud.ProjectInfoDal(auth.db).get_data(data_id=project_id)
if pro is None:
return ErrorResponse(msg="项目查询失败,请稍后再试")
count = await crud.ProjectImageDal(auth.db).upload_imgs(files=files, pro=pro, img_type=img_type)
if count > 0:
return SuccessResponse(msg="上传成功")
@app.delete("/img", summary="删除照片")
async def del_img(
img_id: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
await crud.ProjectImageDal(auth.db).del_img(img_id.ids)
return SuccessResponse(msg="删除成功")
@app.post("/img_label", summary="保存图片的标注信息")
async def add_img_label(
img_label_in: schemas.ProjectImgLeaferLabel,
auth: Auth = Depends(FullAdminAuth())
):
await crud.ProjectImgLabelDal(auth.db).add_img_label(img_label_in)
await crud.ProjectImgLeaferDal(auth.db).add_leafer(img_label_in)
return SuccessResponse(msg="保存成功")

View File

@ -1,84 +0,0 @@
#!/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

@ -18,7 +18,7 @@ from sqlalchemy import select, false, and_
from core.crud import DalBase
from sqlalchemy.ext.asyncio import AsyncSession
from core.validator import vali_telephone
from utils.file.aliyun_oss import AliyunOSS, BucketConf
from utils.huawei_obs import ObsClient
from utils.excel.import_manage import ImportManage, FieldType
from utils.excel.write_xlsx import WriteXlsx
from utils.send_email import EmailSender
@ -397,10 +397,10 @@ class UserDal(DalBase):
:param file:
:return:
"""
result = await AliyunOSS(BucketConf(**settings.ALIYUN_OSS)).upload_image("avatar", file)
user.avatar = result
success, key, url = await ObsClient().put_object("avatar"+"/"+user.name+file.filename, file)
user.avatar = url
await self.flush(user)
return result
return url
async def update_wx_server_openid(self, code: str, user: models.VadminUser, redis: Redis) -> bool:
"""

View File

@ -1,20 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/8/25 13:15
# @File : crud.py
# @IDE : PyCharm
# @desc : 简要说明
from sqlalchemy.ext.asyncio import AsyncSession
from core.crud import DalBase
from . import models, schemas
class ImagesDal(DalBase):
def __init__(self, db: AsyncSession):
super(ImagesDal, self).__init__()
self.db = db
self.model = models.VadminImages
self.schema = schemas.ImagesSimpleOut

View File

@ -1 +0,0 @@
from .images import VadminImages

View File

@ -1,27 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/8/25 13:41
# @File : images.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, ForeignKey, Integer
class VadminImages(BaseModel):
__tablename__ = "vadmin_resource_images"
__table_args__ = ({'comment': '图片素材表'})
filename: Mapped[str] = mapped_column(String(255), nullable=False, comment="原图片名称")
image_url: Mapped[str] = mapped_column(String(500), nullable=False, 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

@ -1 +0,0 @@
from .images import ImagesParams

View File

@ -1,27 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/8/25 14:59
# @File : images.py
# @IDE : PyCharm
# @desc : 简要说明
from fastapi import Depends
from core.dependencies import Paging, QueryParams
class ImagesParams(QueryParams):
"""
列表分页
"""
def __init__(
self,
filename: str = None,
params: Paging = Depends()
):
super().__init__(params)
self.filename = ('like', filename)
self.v_order = "desc"
self.v_order_field = "create_datetime"

View File

@ -1 +0,0 @@
from .images import Images, ImagesOut, ImagesSimpleOut

View File

@ -1,33 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/8/25 14:49
# @File : images.py
# @IDE : PyCharm
# @desc : 简要说明
from pydantic import BaseModel, ConfigDict
from core.data_types import DatetimeStr
from apps.vadmin.auth.schemas import UserSimpleOut
class Images(BaseModel):
filename: str
image_url: str
create_user_id: int
class ImagesSimpleOut(Images):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr
class ImagesOut(ImagesSimpleOut):
model_config = ConfigDict(from_attributes=True)
create_user: UserSimpleOut

View File

@ -1,60 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/8/25 9:29
# @File : views.py
# @IDE : PyCharm
# @desc : 简要说明
from fastapi import APIRouter, Depends, UploadFile
from sqlalchemy.orm import joinedload
from core.dependencies import IdList
from utils.file.aliyun_oss import AliyunOSS, BucketConf
from utils.response import SuccessResponse
from . import schemas, crud, params, models
from apps.vadmin.auth.utils.current import FullAdminAuth
from apps.vadmin.auth.utils.validation.auth import Auth
from application.settings import ALIYUN_OSS
app = APIRouter()
###########################################################
# 图片资源管理
###########################################################
@app.get("/images", summary="获取图片列表")
async def get_images_list(p: params.ImagesParams = Depends(), auth: Auth = Depends(FullAdminAuth())):
model = models.VadminImages
v_options = [joinedload(model.create_user)]
v_schema = schemas.ImagesOut
datas, count = await crud.ImagesDal(auth.db).get_datas(
**p.dict(),
v_options=v_options,
v_schema=v_schema,
v_return_count=True
)
return SuccessResponse(datas, count=count)
@app.post("/images", summary="创建图片")
async def create_images(file: UploadFile, auth: Auth = Depends(FullAdminAuth())):
filepath = f"/resource/images/"
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(filepath, file)
data = schemas.Images(
filename=file.filename,
image_url=result,
create_user_id=auth.user.id
)
return SuccessResponse(await crud.ImagesDal(auth.db).create_data(data=data))
@app.delete("/images", summary="删除图片", description="硬删除")
async def delete_images(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth())):
await crud.ImagesDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.get("/images/{data_id}", summary="获取图片信息")
async def get_images(data_id: int, auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.ImagesDal(auth.db).get_data(data_id, v_schema=schemas.ImagesSimpleOut))

View File

@ -7,24 +7,20 @@
# @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 import select
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 application.settings import SUBSCRIBE
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):
@ -71,121 +67,6 @@ class DictDetailsDal(DalBase):
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):

View File

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

View File

@ -1,42 +0,0 @@
#!/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

@ -1,4 +1,2 @@
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

@ -1,29 +0,0 @@
#!/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

@ -1,28 +0,0 @@
#!/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

@ -12,6 +12,7 @@ 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.huawei_obs import ObsClient
from utils.file.file_manage import FileManage
from utils.response import SuccessResponse, ErrorResponse
from utils.sms.code import CodeSMS
@ -102,34 +103,6 @@ async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())):
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)
###########################################################
# 短信服务管理
###########################################################
@ -142,43 +115,6 @@ async def sms_send(telephone: str, rd: Redis = Depends(redis_getter), auth: Auth
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)
###########################################################
# 定时任务管理
###########################################################

View File

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

View File

@ -1,159 +0,0 @@
#!/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 apps.vadmin.auth.utils.validation.auth import Auth
from utils.response import SuccessResponse
import datetime
from apps.vadmin.record.crud import LoginRecordDal
app = APIRouter()
###########################################################
# 工作区管理
###########################################################
@app.get("/project", summary="获取项目")
async def get_project():
data = [
{
"name": 'Mysql',
"icon": 'vscode-icons:file-type-mysql',
"message": '最流行的关系型数据库管理系统',
"personal": 'kinit',
"link": "https://www.mysql.com/",
"time": datetime.datetime.now().strftime("%Y-%m-%d")
},
{
"name": 'FastAPI',
"icon": 'simple-icons:fastapi',
"message": '一个现代、快速(高性能)的 web 框架',
"personal": 'kinit',
"link": "https://fastapi.tiangolo.com/zh/",
"time": datetime.datetime.now().strftime("%Y-%m-%d")
},
{
"name": 'Vue',
"icon": 'logos:vue',
"message": '渐进式 JavaScript 框架',
"personal": 'kinit',
"link": "https://cn.vuejs.org/",
"time": datetime.datetime.now().strftime("%Y-%m-%d")
},
{
"name": 'Element-plus',
"icon": 'logos:element',
"message": '面向设计师和开发者的组件库',
"personal": 'kinit',
"link": "https://element-plus.org/zh-CN/",
"time": datetime.datetime.now().strftime("%Y-%m-%d")
},
{
"name": 'Typescript',
"icon": 'vscode-icons:file-type-typescript-official',
"message": 'TypeScript是JavaScript类型的超集',
"personal": 'kinit',
"link": "https://www.typescriptlang.org/",
"time": datetime.datetime.now().strftime("%Y-%m-%d")
},
{
"name": 'Vite',
"icon": 'vscode-icons:file-type-vite',
"message": 'Vite 下一代的前端工具链',
"personal": 'kinit',
"link": "https://cn.vitejs.dev/",
"time": datetime.datetime.now().strftime("%Y-%m-%d")
}
]
return SuccessResponse(data)
@app.get("/dynamic", summary="获取动态")
async def get_dynamic():
data = [
{
"keys": ['workplace.push', 'Github'],
"time": datetime.datetime.now().strftime("%Y-%m-%d")
},
{
"keys": ['workplace.push', 'Github'],
"time": datetime.datetime.now().strftime("%Y-%m-%d")
}
]
return SuccessResponse(data)
@app.get("/team", summary="获取团队信息")
async def get_team():
data = [
{
"name": 'Mysql',
"icon": 'vscode-icons:file-type-mysql'
},
{
"name": 'Vue',
"icon": 'logos:vue'
},
{
"name": 'Element-plus',
"icon": 'logos:element'
},
{
"name": 'Fastapi',
"icon": 'simple-icons:fastapi'
},
{
"name": 'Typescript',
"icon": 'vscode-icons:file-type-typescript-official'
},
{
"name": 'Vite',
"icon": 'vscode-icons:file-type-vite'
}
]
return SuccessResponse(data)
@app.get("/shortcuts", summary="获取快捷操作")
async def get_shortcuts():
data = [
{
"name": "Gitee 项目仓库",
"link": "https://gitee.com/ktianc/kinit"
},
{
"name": "GitHub 项目仓库",
"link": "https://github.com/vvandk/kinit"
},
{
"name": "前端文档",
"link": "https://element-plus-admin-doc.cn/"
},
{
"name": "Swagger UI 接口文档",
"link": "http://kinit.ktianc.top/api/docs"
},
{
"name": "Redoc 接口文档",
"link": "http://kinit.ktianc.top/api/redoc"
},
{
"name": "UnoCSS 中文文档",
"link": "https://unocss.nodejs.cn/guide/"
},
{
"name": "Iconify 文档",
"link": "https://icon-sets.iconify.design/"
},
{
"name": "echarts 文档",
"link": "https://echarts.apache.org/zh/index.html"
},
]
return SuccessResponse(data)

View File

@ -10,13 +10,10 @@
from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient
from application.settings import REDIS_DB_URL, MONGO_DB_URL, MONGO_DB_NAME, EVENTS
from utils.cache import Cache
from redis import asyncio as aioredis
from redis.exceptions import AuthenticationError, TimeoutError, RedisError
from contextlib import asynccontextmanager
from utils.tools import import_modules_async
from sqlalchemy.exc import ProgrammingError
from core.logger import logger
@asynccontextmanager
@ -79,11 +76,6 @@ async def connect_redis(app: FastAPI, status: bool):
raise TimeoutError(f"Redis 连接超时,地址或者端口错误: {e}")
except RedisError as e:
raise RedisError(f"Redis 连接失败: {e}")
try:
await Cache(app.state.redis).cache_tab_names()
except ProgrammingError as e:
logger.error(f"sqlalchemy.exc.ProgrammingError: {e}")
print(f"sqlalchemy.exc.ProgrammingError: {e}")
else:
print("Redis 连接关闭")
await app.state.redis.close()

Binary file not shown.

Binary file not shown.

View File

@ -1,89 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2022/3/21 11:03
# @File : cache.py
# @IDE : PyCharm
# @desc : 缓存
from typing import List
from sqlalchemy import false
from sqlalchemy.future import select
from sqlalchemy.orm import joinedload
from core.logger import logger # 注意:报错就在这里,如果只写 core.logger 会写入日志报错,很难排查
from core.database import db_getter
from apps.vadmin.system.models import VadminSystemSettingsTab
import json
from redis.asyncio.client import Redis
from core.exception import CustomException
from utils import status
class Cache:
DEFAULT_TAB_NAMES = ["wx_server", "aliyun_sms", "aliyun_oss", "web_email"]
def __init__(self, rd: Redis):
self.rd = rd
async def __get_tab_name_values(self, tab_names: List[str]):
"""
获取系统配置标签下的标签信息
"""
async_session = db_getter()
session = await async_session.__anext__()
model = VadminSystemSettingsTab
v_options = [joinedload(model.settings)]
sql = select(model).where(
model.is_delete == false(),
model.tab_name.in_(tab_names),
model.disabled == false()
).options(*[load for load in v_options])
queryset = await session.execute(sql)
datas = queryset.scalars().unique().all()
return self.__generate_values(datas)
@classmethod
def __generate_values(cls, datas: List[VadminSystemSettingsTab]):
"""
生成字典值
"""
return {
tab.tab_name: {
item.config_key: item.config_value
for item in tab.settings
if not item.disabled
}
for tab in datas
}
async def cache_tab_names(self, tab_names: List[str] = None):
"""
缓存系统配置
如果手动修改了mysql数据库中的配置
那么需要在redis中将对应的tab_name删除
"""
if not tab_names:
tab_names = self.DEFAULT_TAB_NAMES
datas = await self.__get_tab_name_values(tab_names)
for k, v in datas.items():
await self.rd.client().set(k, json.dumps(v))
async def get_tab_name(self, tab_name: str, retry: int = 3):
"""
获取系统配置
:param tab_name: 配置表标签名称
:param retry: 重试次数
"""
result = await self.rd.get(tab_name)
if not result and retry > 0:
logger.error(f"未从Redis中获取到{tab_name}配置信息,正在重新更新配置信息,重试次数:{retry}")
await self.cache_tab_names([tab_name])
return await self.get_tab_name(tab_name, retry - 1)
elif not result and retry == 0:
raise CustomException(f"获取{tab_name}配置信息失败,请联系管理员!", code=status.HTTP_ERROR)
else:
return json.loads(result)

59
utils/huawei_obs.py Normal file
View File

@ -0,0 +1,59 @@
from application.settings import HUAWEI_OBS
from obs import ObsClient
from obs import DeleteObjectsRequest
from obs import Object
class ObsClient:
def __int__(self):
self.obsClient = ObsClient(
access_key_id=HUAWEI_OBS['AccessKeyID'],
secret_access_key=HUAWEI_OBS['SecretAccessKey'],
server=HUAWEI_OBS["server"])
async def put_file(self, objectKey: str, file_path: str):
resp = await self.obsClient.putFile(
bucketName=HUAWEI_OBS["bucketName"],
objectKey=objectKey,
file_path=file_path)
if resp.status < 300:
print("objectKey", objectKey)
print("url", resp.body.objectUrl)
return True, objectKey, resp.body.objectUrl
else:
return False, None, None
async def put_object(self, objectKey: str, file_content):
resp = await self.obsClient.put_object(
bucketName=HUAWEI_OBS["bucketName"],
objectKey=objectKey,
file_cotent=file_content)
if resp.status < 300:
print("objectKey", objectKey)
print("url", resp.body.objectUrl)
return True, objectKey, resp.body.objectUrl
else:
return False, None, None
async def del_objects(self, object_keys: []):
objects = []
for object_key in object_keys:
object_one = Object(key=object_key, versionId=None)
objects.append(object_one)
encoding_type = 'url'
resp = await self.obsClient.deleteObjects(
bucketName=HUAWEI_OBS["bucketName"],
deleteObjectsRequest=DeleteObjectsRequest(quiet=False, objects=objects, encoding_type=encoding_type))
if resp.status < 300:
return True
else:
return False
async def close(self):
await self.obsClient.close()

View File

@ -15,7 +15,6 @@ from typing import List
from redis.asyncio import Redis
from core.exception import CustomException
from utils.cache import Cache
class EmailSender:
@ -32,7 +31,7 @@ class EmailSender:
"""
获取配置信息
"""
web_email = await Cache(self.rd).get_tab_name("web_email", retry)
web_email = []
self.email = web_email.get("email_access")
self.password = web_email.get("email_password")
self.smtp_server = web_email.get("email_server")
@ -84,10 +83,3 @@ class EmailSender:
print('邮件发送失败!错误信息:', e)
return False
# if __name__ == '__main__':
# sender = EmailSender()
# to_emails = ['ktianc2001@163.com', '2445667550@qq.com']
# subject = 'Test email'
# body = 'This is a test email'
# sender.send_email(to_emails, subject, body)

View File

@ -28,7 +28,6 @@ from alibabacloud_tea_util import models as util_models
from core.logger import logger
import datetime
from redis.asyncio.client import Redis
from utils.cache import Cache
from utils.db_getter import DBGetter
@ -86,7 +85,7 @@ class AliyunSMS(DBGetter):
raise ValueError("缺少 redis 对象参数!")
elif not self.sign_conf or not self.template_code_conf:
raise ValueError("缺少短信签名信息和短信模板ID")
aliyun_sms = await Cache(self.rd).get_tab_name("aliyun_sms", retry)
aliyun_sms = []
self.access_key = aliyun_sms.get("sms_access_key")
self.access_key_secret = aliyun_sms.get("sms_access_key_secret")
self.send_interval = int(aliyun_sms.get("sms_send_interval"))

View File

@ -8,7 +8,6 @@
import requests
from core.logger import logger
from utils.cache import Cache
from utils.wx.wx_access_token import WxAccessToken
from redis.asyncio import Redis
@ -35,7 +34,7 @@ class WXOAuth:
"""
if not self.tab_name:
logger.error(f"请选择认证的微信平台")
wx_config = await Cache(self.rd).get_tab_name(self.tab_name, retry)
wx_config = []
self.appid = wx_config.get("wx_server_app_id")
self.secret = wx_config.get("wx_server_app_secret")