重大更新到2.0.0
This commit is contained in:
parent
45a8abe6bc
commit
09859d4d80
25
README.md
25
README.md
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
Kinit 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
Kinit 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||||
|
|
||||||
- 后端采用现代、快速(高性能) [FastAPI](https://fastapi.tiangolo.com/zh/) 异步框架 + 自动生成交互式API文档 + (强制类型约束)[Pydantic](https://docs.pydantic.dev/1.10/) + (高效率)[SQLAlchemy](https://www.sqlalchemy.org/) ;
|
- 后端采用现代、快速(高性能) [FastAPI](https://fastapi.tiangolo.com/zh/) 异步框架 + 自动生成交互式API文档 + (强制类型约束)[Pydantic](https://docs.pydantic.dev/1.10/) + (高效率)[SQLAlchemy 2.0]([SQLAlchemy Documentation — SQLAlchemy 2.0 Documentation](https://docs.sqlalchemy.org/en/20/index.html)) ;
|
||||||
- PC端采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 、[Vue3](https://cn.vuejs.org/guide/introduction.html)、[Element Plus](https://element-plus.gitee.io/zh-CN/guide/design.html)、[TypeScript](https://www.tslang.cn/)等主流技术开发;
|
- PC端采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 、[Vue3](https://cn.vuejs.org/guide/introduction.html)、[Element Plus](https://element-plus.gitee.io/zh-CN/guide/design.html)、[TypeScript](https://www.tslang.cn/)等主流技术开发;
|
||||||
- 移动端采用 [uni-app](https://uniapp.dcloud.net.cn/component/),[Vue2](https://v2.cn.vuejs.org/v2/guide/),[uView 2](https://www.uviewui.com/components/intro.html)为主要技术开发;
|
- 移动端采用 [uni-app](https://uniapp.dcloud.net.cn/component/),[Vue2](https://v2.cn.vuejs.org/v2/guide/),[uView 2](https://www.uviewui.com/components/intro.html)为主要技术开发;
|
||||||
- 后端加入 [Typer](https://typer.tiangolo.com/) 命令行应用,简单化数据初始化,数据表模型迁移等操作;
|
- 后端加入 [Typer](https://typer.tiangolo.com/) 命令行应用,简单化数据初始化,数据表模型迁移等操作;
|
||||||
@ -34,24 +34,14 @@ Kinit 是一套全部开源的快速开发平台,毫无保留给个人及企
|
|||||||
|
|
||||||
[vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin):一套基于vue3、element-plus、typescript4、vite3的后台集成方案
|
[vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin):一套基于vue3、element-plus、typescript4、vite3的后台集成方案
|
||||||
|
|
||||||
[RuoYi 若依官方网站](http://www.ruoyi.vip/):RuoYi 是一个后台管理系统,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf)主要目的让开发者注重专注业务,降低技术难度,从而节省人力成本,缩短项目周期,提高软件安全质量。
|
[RuoYi 若依官方网站](http://www.ruoyi.vip/):RuoYi 是一个优秀的 Java 后台管理系统
|
||||||
|
|
||||||
[django-vue-admin](https://gitee.com/liqianglog/django-vue-admin):基于RBAC模型的权限控制的一整套基础开发平台,前后端分离,后端采用 django+django-rest-framework,前端采用 vue+ElementUI。
|
[django-vue-admin](https://gitee.com/liqianglog/django-vue-admin):django-vue-admin 是一个优秀的基于 Django 开发后台管理系统
|
||||||
|
|
||||||
[Ant Design Pro](https://preview.pro.ant.design/dashboard/analysis):开箱即用的中台前端/设计解决方案
|
[Ant Design Pro](https://preview.pro.ant.design/dashboard/analysis):开箱即用的中台前端/设计解决方案
|
||||||
|
|
||||||
[Gin-Vue-Admin](https://demo.gin-vue-admin.com):基于vite+vue3+gin搭建的开发基础平台(支持TS,JS混用),集成jwt鉴权,权限管理,动态路由,显隐可控组件,分页封装,多点登录拦截,资源权限,上传下载,代码生成器,表单生成器等开发必备功能。
|
|
||||||
|
|
||||||
[Vben Admin](https://doc.vvbin.cn/guide/introduction.html):Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite2`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
|
|
||||||
|
|
||||||
[中华人民共和国行政区划 (github.com)](https://github.com/modood/Administrative-divisions-of-China):省级(省份)、 地级(城市)、 县级(区县)、 乡级(乡镇街道)、 村级(村委会居委会) ,中国省市区镇村二级三级四级五级联动地址数据。
|
|
||||||
|
|
||||||
[Vue Admin Plus](https://vue-admin-beautiful.com/admin-plus/#/index):vue-admin-better是github开源admin中最优秀的集成框架之一,它是国内首个基于vue3.0的开源admin项目,同时支持电脑,手机,平板,默认分支使用vue3.x+antdv开发,master分支使用的是vue2.x+element开发。
|
|
||||||
|
|
||||||
[小诺开源技术 (xiaonuo.vip)](https://www.xiaonuo.vip/):国内首个国密前后端分离快速开发平台
|
[小诺开源技术 (xiaonuo.vip)](https://www.xiaonuo.vip/):国内首个国密前后端分离快速开发平台
|
||||||
|
|
||||||
[my-web](https://gitee.com/newgateway/my-web):MyWeb 是一个企业级中后台前端/设计解决方案的的项目工程模板,它可以帮助你快速搭建企业级中后台产品原型
|
|
||||||
|
|
||||||
## 在线体验
|
## 在线体验
|
||||||
|
|
||||||
PC端演示地址:http://kinit.ktianc.top
|
PC端演示地址:http://kinit.ktianc.top
|
||||||
@ -134,18 +124,13 @@ github地址:https://github.com/vvandk/kinit
|
|||||||
|
|
||||||
- [x] 我的基础功能:编辑资料、头像修改、密码修改、常见问题、关于我们等
|
- [x] 我的基础功能:编辑资料、头像修改、密码修改、常见问题、关于我们等
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
- [ ] 多租户方案
|
|
||||||
- [ ] 自动化编排服务:使用docker-compose部署项目
|
|
||||||
- [ ] 可视化低代码表单:接入低代码表单,[vform3](https://vform666.com/vform3.html?from=element_plus)
|
|
||||||
|
|
||||||
## 前序准备
|
## 前序准备
|
||||||
|
|
||||||
### 后端技术
|
### 后端技术
|
||||||
|
|
||||||
- [Python3](https://www.python.org/downloads/windows/):熟悉 python3 基础语法
|
- [Python3](https://www.python.org/downloads/windows/):熟悉 python3 基础语法
|
||||||
- [FastAPI](https://fastapi.tiangolo.com/zh/) - 熟悉后台接口 Web 框架.
|
- [FastAPI](https://fastapi.tiangolo.com/zh/) - 熟悉后台接口 Web 框架
|
||||||
|
- [SQLAlchemy 2.0](https://docs.sqlalchemy.org/en/20/index.html) - 数据数据库操作
|
||||||
- [Typer](https://typer.tiangolo.com/) - 熟悉命令行工具的使用
|
- [Typer](https://typer.tiangolo.com/) - 熟悉命令行工具的使用
|
||||||
- [MySQL](https://www.mysql.com/) 和 [MongoDB](https://www.mongodb.com/) 和 [Redis](https://redis.io/) - 熟悉数据存储数据库
|
- [MySQL](https://www.mysql.com/) 和 [MongoDB](https://www.mongodb.com/) 和 [Redis](https://redis.io/) - 熟悉数据存储数据库
|
||||||
- [iP查询接口文档](https://user.ip138.com/ip/doc):IP查询第三方服务,有1000次的免费次数
|
- [iP查询接口文档](https://user.ip138.com/ip/doc):IP查询第三方服务,有1000次的免费次数
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { reactive, ref, unref, watch } from 'vue'
|
import { reactive, ref, unref, watch } from 'vue'
|
||||||
import { Form } from '@/components/Form'
|
import { Form } from '@/components/Form'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
|
import { ElButton, ElLink } from 'element-plus'
|
||||||
import { useForm } from '@/hooks/web/useForm'
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
import { getRoleMenusApi } from '@/api/login'
|
import { getRoleMenusApi } from '@/api/login'
|
||||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
@ -106,7 +106,7 @@ const schema = reactive<FormSchema[]>([
|
|||||||
])
|
])
|
||||||
|
|
||||||
const iconSize = 30
|
const iconSize = 30
|
||||||
const remember = ref(false)
|
// const remember = ref(false)
|
||||||
const { register, elFormRef, methods } = useForm()
|
const { register, elFormRef, methods } = useForm()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const iconColor = '#999'
|
const iconColor = '#999'
|
||||||
@ -189,7 +189,7 @@ const toTelephoneSignIn = () => {
|
|||||||
|
|
||||||
<template #tool>
|
<template #tool>
|
||||||
<div class="flex justify-between items-center w-[100%]">
|
<div class="flex justify-between items-center w-[100%]">
|
||||||
<ElCheckbox v-model="remember" :label="t('login.remember')" size="small" />
|
<!-- <ElCheckbox v-model="remember" :label="t('login.remember')" size="small" /> -->
|
||||||
<ElLink type="primary" :underline="false">{{ t('login.forgetPassword') }}</ElLink>
|
<ElLink type="primary" :underline="false">{{ t('login.forgetPassword') }}</ElLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -75,7 +75,7 @@ defineExpose({
|
|||||||
|
|
||||||
let selectAll = ref(false)
|
let selectAll = ref(false)
|
||||||
let defaultExpandAll = ref(true)
|
let defaultExpandAll = ref(true)
|
||||||
let checkStrictly = ref(true) // 父子联动
|
let checkStrictly = ref(false) // 父子联动,有bug
|
||||||
|
|
||||||
// 获取所有节点的key
|
// 获取所有节点的key
|
||||||
const getTreeNodeKeys = (nodes: Recordable[]): number[] => {
|
const getTreeNodeKeys = (nodes: Recordable[]): number[] => {
|
||||||
@ -119,7 +119,7 @@ function handleCheckedTreeNodeAll() {
|
|||||||
label="全选/全不选"
|
label="全选/全不选"
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
<ElCheckbox v-model="checkStrictly" label="父子联动" size="large" />
|
<!-- <ElCheckbox v-model="checkStrictly" label="父子联动" size="large" /> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="max-h-390px border p-10px overflow-auto">
|
<div class="max-h-390px border p-10px overflow-auto">
|
||||||
<ElTree
|
<ElTree
|
||||||
|
@ -74,7 +74,7 @@ getData()
|
|||||||
:editorConfig="editorConfig"
|
:editorConfig="editorConfig"
|
||||||
/>
|
/>
|
||||||
<div class="mt-10px" style="float: right">
|
<div class="mt-10px" style="float: right">
|
||||||
<ElButton type="primary" @click="save">立即保存</ElButton>
|
<ElButton :loading="loading" type="primary" @click="save">立即保存</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ getData()
|
|||||||
<template>
|
<template>
|
||||||
<Form @register="register">
|
<Form @register="register">
|
||||||
<template #active>
|
<template #active>
|
||||||
<ElButton type="primary" @click="save">立即提交</ElButton>
|
<ElButton :loading="loading" type="primary" @click="save">立即提交</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -152,7 +152,7 @@ getData()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #active>
|
<template #active>
|
||||||
<ElButton type="primary" @click="save">立即提交</ElButton>
|
<ElButton :loading="loading" type="primary" @click="save">立即提交</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -59,7 +59,7 @@ getData()
|
|||||||
<template>
|
<template>
|
||||||
<Form @register="register">
|
<Form @register="register">
|
||||||
<template #active>
|
<template #active>
|
||||||
<ElButton type="primary" @click="save">立即提交</ElButton>
|
<ElButton :loading="loading" type="primary" @click="save">立即提交</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -73,7 +73,7 @@ getData()
|
|||||||
:editorConfig="editorConfig"
|
:editorConfig="editorConfig"
|
||||||
/>
|
/>
|
||||||
<div class="mt-10px" style="float: right">
|
<div class="mt-10px" style="float: right">
|
||||||
<ElButton type="primary" @click="save">立即保存</ElButton>
|
<ElButton :loading="loading" type="primary" @click="save">立即保存</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ getData()
|
|||||||
<template>
|
<template>
|
||||||
<Form @register="register">
|
<Form @register="register">
|
||||||
<template #active>
|
<template #active>
|
||||||
<ElButton type="primary" @click="save">立即提交</ElButton>
|
<ElButton :loading="loading" type="primary" @click="save">立即提交</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# FastAPI 项目
|
# FastAPI 项目
|
||||||
|
|
||||||
fastapi 源代码:https://github.com/tiangolo/fastapi
|
fastapi Github:https://github.com/tiangolo/fastapi
|
||||||
|
|
||||||
fastapi 中文文档:https://fastapi.tiangolo.com/zh/
|
fastapi 官方文档:https://fastapi.tiangolo.com/zh/
|
||||||
|
|
||||||
fastapi 更新说明:https://fastapi.tiangolo.com/zh/release-notes/
|
fastapi 更新说明:https://fastapi.tiangolo.com/zh/release-notes/
|
||||||
|
|
||||||
@ -16,25 +16,50 @@ alembic 中文文档:https://hellowac.github.io/alembic_doc/zh/_front_matter.h
|
|||||||
|
|
||||||
Typer 官方文档:https://typer.tiangolo.com/
|
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 项目结构:
|
使用的是仿照 Django 项目结构:
|
||||||
|
|
||||||
- alembic:数据库迁移配置目录
|
- alembic:数据库迁移配置目录
|
||||||
|
- versions_dev:开发环境数据库迁移文件目录
|
||||||
|
- versions_pro:生产环境数据库迁移文件目录
|
||||||
|
- env.py:映射类配置文件
|
||||||
- application:主项目配置目录,也存放了主路由文件
|
- application:主项目配置目录,也存放了主路由文件
|
||||||
|
- config:基础环境配置文件
|
||||||
|
- development.py:开发环境
|
||||||
|
- production.py:生产环境
|
||||||
- settings.py:主项目配置文件
|
- settings.py:主项目配置文件
|
||||||
- urls.py:主路由文件
|
- urls.py:主路由文件
|
||||||
- apps:项目的app存放目录
|
- apps:项目的app存放目录
|
||||||
|
- vadmin:基础服务
|
||||||
|
- auth:用户 - 角色 - 菜单接口服务
|
||||||
|
- models:ORM 模型目录
|
||||||
|
- params:查询参数依赖项目录
|
||||||
|
- schemas:pydantic 模型,用于数据库序列化操作目录
|
||||||
|
- utils:登录认证功能接口服务
|
||||||
|
- curd.py:数据库操作
|
||||||
|
- views.py:视图函数
|
||||||
- core:核心文件目录
|
- core:核心文件目录
|
||||||
|
- crud.py:关系型数据库操作核心封装
|
||||||
- database.py:关系型数据库核心配置
|
- database.py:关系型数据库核心配置
|
||||||
|
- data_types.py:自定义数据类型
|
||||||
- exception.py:异常处理
|
- exception.py:异常处理
|
||||||
- logger:日志处理核心配置
|
- logger:日志处理核心配置
|
||||||
- middleware.py:中间件核心配置
|
- middleware.py:中间件核心配置
|
||||||
|
- dependencies.py:常用依赖项
|
||||||
|
- event.py:全局事件
|
||||||
|
- mongo_manage.py:mongodb 数据库操作核心封装
|
||||||
|
- validator.py:pydantic 模型重用验证器
|
||||||
- db:ORM模型基类
|
- db:ORM模型基类
|
||||||
- logs:日志目录
|
- logs:日志目录
|
||||||
- static:静态资源存放目录
|
- static:静态资源存放目录
|
||||||
- tests:测试接口文件目录
|
|
||||||
- utils:封装的一些工具类目录
|
- utils:封装的一些工具类目录
|
||||||
- main.py:主程序入口文件
|
- main.py:主程序入口文件
|
||||||
- alembic.ini:数据库迁移配置文件
|
- alembic.ini:数据库迁移配置文件
|
||||||
@ -43,7 +68,9 @@ Typer 官方文档:https://typer.tiangolo.com/
|
|||||||
|
|
||||||
开发语言:Python 3.10
|
开发语言:Python 3.10
|
||||||
|
|
||||||
开发框架:Fastapi 0.95.0
|
开发框架:Fastapi 0.101.1
|
||||||
|
|
||||||
|
ORM 框架:SQLAlchemy 2.0.20
|
||||||
|
|
||||||
## 开发工具
|
## 开发工具
|
||||||
|
|
||||||
@ -66,14 +93,28 @@ pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
|||||||
# 第三方源:
|
# 第三方源:
|
||||||
|
|
||||||
1. 阿里源: 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
|
```shell
|
||||||
# 项目根目录下执行,需提前创建好数据库
|
# 项目根目录下执行,需提前创建好数据库,并且数据库应该为空
|
||||||
# 会自动将模型迁移到数据库,并生成初始化数据
|
# 会自动将模型迁移到数据库,并生成初始化数据
|
||||||
|
|
||||||
|
# 在执行前一定要确认要操作的环境与application/settings.DEBUG 设置的环境是一致的,
|
||||||
|
# 不然会导致创建表和生成数据不在一个数据库中!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
# 比如要初始化开发环境,那么env参数应该为 dev,并且 application/settings.DEBUG 应该 = True
|
||||||
|
# 比如要初始化生产环境,那么env参数应该为 pro,并且 application/settings.DEBUG 应该 = False
|
||||||
|
|
||||||
|
# 生产环境
|
||||||
python main.py init
|
python main.py init
|
||||||
|
|
||||||
|
# 开发环境
|
||||||
|
python main.py init --env dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### 运行启动
|
### 运行启动
|
||||||
@ -92,7 +133,6 @@ http://127.0.0.1:9000/docs
|
|||||||
```
|
```
|
||||||
|
|
||||||
Git更新ignore文件直接修改gitignore是不会生效的,需要先去掉已经托管的文件,修改完成之后再重新添加并提交。
|
Git更新ignore文件直接修改gitignore是不会生效的,需要先去掉已经托管的文件,修改完成之后再重新添加并提交。
|
||||||
|
|
||||||
```
|
```
|
||||||
第一步:
|
第一步:
|
||||||
git rm -r --cached .
|
git rm -r --cached .
|
||||||
@ -112,15 +152,19 @@ git commit -m "clear cached"
|
|||||||
# 执行命令(生产环境):
|
# 执行命令(生产环境):
|
||||||
python main.py migrate
|
python main.py migrate
|
||||||
|
|
||||||
# 执行命令(测试环境):
|
# 执行命令(开发环境):
|
||||||
python main.py migrate --env dev
|
python main.py migrate --env dev
|
||||||
|
|
||||||
|
# 开发环境的原命令
|
||||||
|
alembic --name dev revision --autogenerate -m 2.0
|
||||||
|
alembic --name dev upgrade head
|
||||||
```
|
```
|
||||||
|
|
||||||
生成迁移文件后,会在alembic迁移目录中的version目录中多个迁移文件
|
生成迁移文件后,会在alembic迁移目录中的version目录中多个迁移文件
|
||||||
|
|
||||||
## 查询数据
|
## 查询数据
|
||||||
|
|
||||||
### 查询过滤
|
### 自定义的一些查询过滤
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 日期查询
|
# 日期查询
|
||||||
@ -160,7 +204,49 @@ python main.py migrate --env dev
|
|||||||
|
|
||||||
代码部分:
|
代码部分:
|
||||||
|
|
||||||

|
```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
|
||||||
|
```
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
@ -168,120 +254,51 @@ python main.py migrate --env dev
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
users = UserDal(db).get_datas(limit=0, id=("in", [1,2,4,6]), email=("not None"), name=("like", "李"))
|
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
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### v_join_query
|
查询所有用户id为1或2或 4或6,并且email不为空,并且名称包括李:
|
||||||
|
|
||||||
外键字段查询,内连接
|
查询第一页,每页两条数据,并返回总数,同样可以通过 `get_datas` 实现原始查询方式:
|
||||||
|
|
||||||
以常见问题类别表为例:
|
|
||||||
|
|
||||||
首先需要在 `crud.py/IssueCategoryDal` 的 `__init__` 方法中定义 `key_models`:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class IssueCategoryDal(DalBase):
|
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)
|
||||||
|
|
||||||
def __init__(self, db: AsyncSession):
|
# 这里的 get_datas 默认返回的是 pydantic 模型数据
|
||||||
key_models = {
|
# 如果需要返回用户对象列表,使用如下语句:
|
||||||
# 外键字段名,也可以自定义
|
users, count = UserDal(db).get_datas(
|
||||||
"create_user": {
|
limit=2,
|
||||||
# 外键对应的orm模型
|
v_where=v_where,
|
||||||
"model": vadminAuthModels.VadminUser,
|
v_return_count=True
|
||||||
# 如果对同一个模型只有一个外键关联时,下面这个 onclause 可以省略不写,一个以上时必须写,需要分清楚要查询的是哪个
|
v_return_objs=True
|
||||||
# 这里其实可以省略不写,但是为了演示这里写出来了
|
)
|
||||||
"onclause": models.VadminIssueCategory.create_user_id == vadminAuthModels.VadminUser.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super(IssueCategoryDal, self).__init__(
|
|
||||||
db,
|
|
||||||
models.VadminIssueCategory,
|
|
||||||
schemas.IssueCategorySimpleOut,
|
|
||||||
key_models
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
使用案例:
|
### 外键查询示例
|
||||||
|
|
||||||
|
以常见问题表为主表,查询出创建用户名称为kinit的用户,创建了哪些常见问题,并加载出用户信息:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def test(self):
|
v_options = [joinedload(VadminIssue.create_user)]
|
||||||
"""
|
v_join = [["create_user"]]
|
||||||
v_join_query 示例方法
|
v_where = [VadminUser.name == "kinit"]
|
||||||
获取用户名称包含李 创建出的常见问题类别
|
datas = await crud.IssueCategoryDal(auth.db).get_datas(
|
||||||
"""
|
limit=0,
|
||||||
v_join_query = {
|
v_options=options,
|
||||||
# 与 key_models 中定义的外键字段名定义的一样
|
v_join=v_join,
|
||||||
"create_user": {
|
v_where=v_where,
|
||||||
# 外键表字段名:查询值
|
v_return_objs=True
|
||||||
"name": ("like", "李")
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
v_options = [joinedload(self.model.create_user)]
|
|
||||||
datas = self.get_datas(limit=0, v_join_query=v_join_query, v_options=v_options)
|
|
||||||
```
|
|
||||||
|
|
||||||
完整案例:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class IssueCategoryDal(DalBase):
|
|
||||||
|
|
||||||
def __init__(self, db: AsyncSession):
|
|
||||||
key_models = {
|
|
||||||
# 外键字段名,也可以自定义
|
|
||||||
"create_user": {
|
|
||||||
# 外键对应的orm模型
|
|
||||||
"model": vadminAuthModels.VadminUser,
|
|
||||||
# 如果对同一个模型只有一个外键关联时,下面这个 onclause 可以省略不写,一个以上时必须写,需要分清楚要查询的是哪个
|
|
||||||
# 这里其实可以省略不写,但是为了演示这里写出来了
|
|
||||||
"onclause": models.VadminIssueCategory.create_user_id == vadminAuthModels.VadminUser.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super(IssueCategoryDal, self).__init__(
|
|
||||||
db,
|
|
||||||
models.VadminIssueCategory,
|
|
||||||
schemas.IssueCategorySimpleOut,
|
|
||||||
key_models
|
|
||||||
)
|
|
||||||
|
|
||||||
async def test(self):
|
|
||||||
"""
|
|
||||||
v_join_query 示例方法
|
|
||||||
获取用户名称包含李 创建出的常见问题类别
|
|
||||||
"""
|
|
||||||
v_join_query = {
|
|
||||||
# 与 key_models 中定义的外键字段名定义的一样
|
|
||||||
"create_user": {
|
|
||||||
# 外键表字段名:查询值
|
|
||||||
"name": ("like", "李")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v_options = [joinedload(self.model.create_user)]
|
|
||||||
datas = self.get_datas(limit=0, v_join_query=v_join_query, v_options=v_options)
|
|
||||||
```
|
|
||||||
|
|
||||||
### v_or
|
|
||||||
|
|
||||||
或逻辑运算查询
|
|
||||||
|
|
||||||
语法:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 普通查询
|
|
||||||
v_or = [(字段名称, 值), (字段名称, 值), ... ]
|
|
||||||
|
|
||||||
# 模糊查询
|
|
||||||
v_or = [(字段名称, ("like", 值)), (字段名称, ("like", 值)), ... ]
|
|
||||||
|
|
||||||
# 组合查询
|
|
||||||
v_or = [(字段名称, ("like", 值)), (字段名称, ("in", [值, 值, 值, ...])), ... ]
|
|
||||||
|
|
||||||
# 外键查询,需要先定义 key_models
|
|
||||||
v_or = [("fk", key_models 中定义的外键字段名, 外键表字段名称, ("like", 值)), ("fk", key_models 中定义的外键字段名, 外键表字段名称, ("like", 值)), ... ]
|
|
||||||
```
|
|
||||||
|
|
||||||
比如查询一个用户手机号为`13409090909`或者`15390909090`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
v_or = [("telephone", "13409090909"), ("telephone", "15390909090") ]
|
|
||||||
user = UserDal(db).get_data(v_or=v_or)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
|
|||||||
"""
|
"""
|
||||||
系统版本
|
系统版本
|
||||||
"""
|
"""
|
||||||
VERSION = "1.10.5"
|
VERSION = "2.0.0"
|
||||||
|
|
||||||
"""安全警告: 不要在生产中打开调试运行!"""
|
"""安全警告: 不要在生产中打开调试运行!"""
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
@ -44,8 +44,14 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||||||
如果是与认证关联性比较强的接口,则无法使用
|
如果是与认证关联性比较强的接口,则无法使用
|
||||||
"""
|
"""
|
||||||
OAUTH_ENABLE = True
|
OAUTH_ENABLE = True
|
||||||
"""登录认证视图"""
|
"""
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/api/login", auto_error=True) if OAUTH_ENABLE else lambda: ""
|
配置 OAuth2 密码流认证方式
|
||||||
|
官方文档:https://fastapi.tiangolo.com/zh/tutorial/security/first-steps/#fastapi-oauth2passwordbearer
|
||||||
|
auto_error:(bool) 可选参数,默认为 True。当验证失败时,如果设置为 True,FastAPI 将自动返回一个 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 令牌进行签名"""
|
"""安全的随机密钥,该密钥将用于对 JWT 令牌进行签名"""
|
||||||
SECRET_KEY = 'vgb0tnl9d58+6n-6h-ea&u^1#s0ccp!794=kbvqacjq75vzps$'
|
SECRET_KEY = 'vgb0tnl9d58+6n-6h-ea&u^1#s0ccp!794=kbvqacjq75vzps$'
|
||||||
"""用于设定 JWT 令牌签名算法"""
|
"""用于设定 JWT 令牌签名算法"""
|
||||||
|
@ -10,6 +10,7 @@ from typing import Any
|
|||||||
from aioredis import Redis
|
from aioredis import Redis
|
||||||
from fastapi import UploadFile
|
from fastapi import UploadFile
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
from sqlalchemy.orm.strategy_options import _AbstractLoad
|
||||||
from core.exception import CustomException
|
from core.exception import CustomException
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@ -26,10 +27,11 @@ from utils.tools import test_password
|
|||||||
from . import models, schemas
|
from . import models, schemas
|
||||||
from application import settings
|
from application import settings
|
||||||
from utils.excel.excel_manage import ExcelManage
|
from utils.excel.excel_manage import ExcelManage
|
||||||
from apps.vadmin.system import crud as vadminSystemCRUD
|
from apps.vadmin.system import crud as vadmin_system_crud
|
||||||
import copy
|
import copy
|
||||||
from utils import status
|
from utils import status
|
||||||
from utils.wx.oauth import WXOAuth
|
from utils.wx.oauth import WXOAuth
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class UserDal(DalBase):
|
class UserDal(DalBase):
|
||||||
@ -45,13 +47,24 @@ class UserDal(DalBase):
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
super(UserDal, self).__init__(db, models.VadminUser, schemas.UserSimpleOut)
|
super(UserDal, self).__init__(db, models.VadminUser, schemas.UserSimpleOut)
|
||||||
|
|
||||||
|
async def update_login_info(self, user: models.VadminUser, last_ip: str) -> None:
|
||||||
|
"""
|
||||||
|
更新当前登录信息
|
||||||
|
:param user: 用户对象
|
||||||
|
:param last_ip: 最近一次登录 IP
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
user.last_ip = last_ip
|
||||||
|
user.last_login = datetime.now()
|
||||||
|
await self.db.flush()
|
||||||
|
|
||||||
async def create_data(
|
async def create_data(
|
||||||
self,
|
self,
|
||||||
data: schemas.UserIn,
|
data: schemas.UserIn,
|
||||||
v_options: list = None,
|
v_options: list[_AbstractLoad] = None,
|
||||||
v_return_obj: bool = False,
|
v_return_obj: bool = False,
|
||||||
v_schema: Any = None
|
v_schema: Any = None
|
||||||
):
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
创建用户
|
创建用户
|
||||||
"""
|
"""
|
||||||
@ -65,7 +78,7 @@ class UserDal(DalBase):
|
|||||||
if data.role_ids:
|
if data.role_ids:
|
||||||
roles = await RoleDal(self.db).get_datas(limit=0, id=("in", data.role_ids), v_return_objs=True)
|
roles = await RoleDal(self.db).get_datas(limit=0, id=("in", data.role_ids), v_return_objs=True)
|
||||||
for role in roles:
|
for role in roles:
|
||||||
obj.roles.append(role)
|
obj.roles.add(role)
|
||||||
await self.flush(obj)
|
await self.flush(obj)
|
||||||
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
|
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
|
||||||
|
|
||||||
@ -73,10 +86,10 @@ class UserDal(DalBase):
|
|||||||
self,
|
self,
|
||||||
data_id: int,
|
data_id: int,
|
||||||
data: schemas.UserUpdate,
|
data: schemas.UserUpdate,
|
||||||
v_options: list = None,
|
v_options: list[_AbstractLoad] = None,
|
||||||
v_return_obj: bool = False,
|
v_return_obj: bool = False,
|
||||||
v_schema: Any = None
|
v_schema: Any = None
|
||||||
):
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
更新用户信息
|
更新用户信息
|
||||||
"""
|
"""
|
||||||
@ -84,18 +97,18 @@ class UserDal(DalBase):
|
|||||||
data_dict = jsonable_encoder(data)
|
data_dict = jsonable_encoder(data)
|
||||||
for key, value in data_dict.items():
|
for key, value in data_dict.items():
|
||||||
if key == "role_ids":
|
if key == "role_ids":
|
||||||
if obj.roles:
|
|
||||||
obj.roles.clear()
|
|
||||||
if value:
|
if value:
|
||||||
roles = await RoleDal(self.db).get_datas(limit=0, id=("in", value), v_return_objs=True)
|
roles = await RoleDal(self.db).get_datas(limit=0, id=("in", value), v_return_objs=True)
|
||||||
|
if obj.roles:
|
||||||
|
obj.roles.clear()
|
||||||
for role in roles:
|
for role in roles:
|
||||||
obj.roles.append(role)
|
obj.roles.add(role)
|
||||||
continue
|
continue
|
||||||
setattr(obj, key, value)
|
setattr(obj, key, value)
|
||||||
await self.flush(obj)
|
await self.flush(obj)
|
||||||
return await self.out_dict(obj, None, v_return_obj, v_schema)
|
return await self.out_dict(obj, None, v_return_obj, v_schema)
|
||||||
|
|
||||||
async def reset_current_password(self, user: models.VadminUser, data: schemas.ResetPwd):
|
async def reset_current_password(self, user: models.VadminUser, data: schemas.ResetPwd) -> None:
|
||||||
"""
|
"""
|
||||||
重置密码
|
重置密码
|
||||||
"""
|
"""
|
||||||
@ -107,9 +120,8 @@ class UserDal(DalBase):
|
|||||||
user.password = self.model.get_password_hash(data.password)
|
user.password = self.model.get_password_hash(data.password)
|
||||||
user.is_reset_password = True
|
user.is_reset_password = True
|
||||||
await self.flush(user)
|
await self.flush(user)
|
||||||
return True
|
|
||||||
|
|
||||||
async def update_current_info(self, user: models.VadminUser, data: schemas.UserUpdateBaseInfo):
|
async def update_current_info(self, user: models.VadminUser, data: schemas.UserUpdateBaseInfo) -> Any:
|
||||||
"""
|
"""
|
||||||
更新当前用户基本信息
|
更新当前用户基本信息
|
||||||
"""
|
"""
|
||||||
@ -125,7 +137,7 @@ class UserDal(DalBase):
|
|||||||
await self.flush(user)
|
await self.flush(user)
|
||||||
return await self.out_dict(user)
|
return await self.out_dict(user)
|
||||||
|
|
||||||
async def export_query_list(self, header: list, params: UserParams):
|
async def export_query_list(self, header: list, params: UserParams) -> dict:
|
||||||
"""
|
"""
|
||||||
导出用户查询列表为excel
|
导出用户查询列表为excel
|
||||||
"""
|
"""
|
||||||
@ -133,7 +145,7 @@ class UserDal(DalBase):
|
|||||||
# 获取表头
|
# 获取表头
|
||||||
row = list(map(lambda i: i.get("label"), header))
|
row = list(map(lambda i: i.get("label"), header))
|
||||||
rows = []
|
rows = []
|
||||||
options = await vadminSystemCRUD.DictTypeDal(self.db).get_dicts_details(["sys_vadmin_gender"])
|
options = await vadmin_system_crud.DictTypeDal(self.db).get_dicts_details(["sys_vadmin_gender"])
|
||||||
for user in datas:
|
for user in datas:
|
||||||
data = []
|
data = []
|
||||||
for item in header:
|
for item in header:
|
||||||
@ -142,6 +154,8 @@ class UserDal(DalBase):
|
|||||||
value = getattr(user, field, "")
|
value = getattr(user, field, "")
|
||||||
if field == "is_active":
|
if field == "is_active":
|
||||||
value = "可用" if value else "停用"
|
value = "可用" if value else "停用"
|
||||||
|
elif field == "is_staff":
|
||||||
|
value = "是" if value else "否"
|
||||||
elif field == "gender":
|
elif field == "gender":
|
||||||
result = list(filter(lambda i: i["value"] == value, options["sys_vadmin_gender"]))
|
result = list(filter(lambda i: i["value"] == value, options["sys_vadmin_gender"]))
|
||||||
value = result[0]["label"] if result else ""
|
value = result[0]["label"] if result else ""
|
||||||
@ -154,7 +168,7 @@ class UserDal(DalBase):
|
|||||||
em.close()
|
em.close()
|
||||||
return {"url": file_url, "filename": "用户列表.xlsx"}
|
return {"url": file_url, "filename": "用户列表.xlsx"}
|
||||||
|
|
||||||
async def get_import_headers_options(self):
|
async def get_import_headers_options(self) -> None:
|
||||||
"""
|
"""
|
||||||
补全表头数据选项
|
补全表头数据选项
|
||||||
"""
|
"""
|
||||||
@ -165,13 +179,13 @@ class UserDal(DalBase):
|
|||||||
role_options["options"] = [{"label": role.name, "value": role.id} for role in roles]
|
role_options["options"] = [{"label": role.name, "value": role.id} for role in roles]
|
||||||
|
|
||||||
# 性别选择项
|
# 性别选择项
|
||||||
dict_types = await vadminSystemCRUD.DictTypeDal(self.db).get_dicts_details(["sys_vadmin_gender"])
|
dict_types = await vadmin_system_crud.DictTypeDal(self.db).get_dicts_details(["sys_vadmin_gender"])
|
||||||
gender_options = self.import_headers[3]
|
gender_options = self.import_headers[3]
|
||||||
assert isinstance(gender_options, dict)
|
assert isinstance(gender_options, dict)
|
||||||
sys_vadmin_gender = dict_types.get("sys_vadmin_gender")
|
sys_vadmin_gender = dict_types.get("sys_vadmin_gender")
|
||||||
gender_options["options"] = [{"label": item["label"], "value": item["value"]} for item in sys_vadmin_gender]
|
gender_options["options"] = [{"label": item["label"], "value": item["value"]} for item in sys_vadmin_gender]
|
||||||
|
|
||||||
async def download_import_template(self):
|
async def download_import_template(self) -> dict:
|
||||||
"""
|
"""
|
||||||
下载用户最新版导入模板
|
下载用户最新版导入模板
|
||||||
"""
|
"""
|
||||||
@ -181,7 +195,7 @@ class UserDal(DalBase):
|
|||||||
em.close()
|
em.close()
|
||||||
return {"url": em.file_url, "filename": "用户导入模板.xlsx"}
|
return {"url": em.file_url, "filename": "用户导入模板.xlsx"}
|
||||||
|
|
||||||
async def import_users(self, file: UploadFile):
|
async def import_users(self, file: UploadFile) -> dict:
|
||||||
"""
|
"""
|
||||||
批量导入用户数据
|
批量导入用户数据
|
||||||
"""
|
"""
|
||||||
@ -206,7 +220,7 @@ class UserDal(DalBase):
|
|||||||
"error_url": im.generate_error_url()
|
"error_url": im.generate_error_url()
|
||||||
}
|
}
|
||||||
|
|
||||||
async def init_password(self, ids: list[int]):
|
async def init_password(self, ids: list[int]) -> list:
|
||||||
"""
|
"""
|
||||||
初始化所选用户密码
|
初始化所选用户密码
|
||||||
将用户密码改为系统默认密码,并将初始化密码状态改为false
|
将用户密码改为系统默认密码,并将初始化密码状态改为false
|
||||||
@ -226,7 +240,7 @@ class UserDal(DalBase):
|
|||||||
await self.db.flush()
|
await self.db.flush()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def init_password_send_sms(self, ids: list[int], rd: Redis):
|
async def init_password_send_sms(self, ids: list[int], rd: Redis) -> list:
|
||||||
"""
|
"""
|
||||||
初始化所选用户密码并发送通知短信
|
初始化所选用户密码并发送通知短信
|
||||||
将用户密码改为系统默认密码,并将初始化密码状态改为false
|
将用户密码改为系统默认密码,并将初始化密码状态改为false
|
||||||
@ -242,13 +256,13 @@ class UserDal(DalBase):
|
|||||||
try:
|
try:
|
||||||
send_result = (await sms.main_async(password=password))[0]
|
send_result = (await sms.main_async(password=password))[0]
|
||||||
user["send_sms_status"] = send_result
|
user["send_sms_status"] = send_result
|
||||||
user["send_sms_msg"] = "" if send_result else "发送失败,请联系管理员"
|
user["send_sms_msg"] = "" if send_result else "短信发送失败,请联系管理员"
|
||||||
except CustomException as e:
|
except CustomException as e:
|
||||||
user["send_sms_status"] = False
|
user["send_sms_status"] = False
|
||||||
user["send_sms_msg"] = e.msg
|
user["send_sms_msg"] = e.msg
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def init_password_send_email(self, ids: list[int], rd: Redis):
|
async def init_password_send_email(self, ids: list[int], rd: Redis) -> list:
|
||||||
"""
|
"""
|
||||||
初始化所选用户密码并发送通知邮件
|
初始化所选用户密码并发送通知邮件
|
||||||
将用户密码改为系统默认密码,并将初始化密码状态改为false
|
将用户密码改为系统默认密码,并将初始化密码状态改为false
|
||||||
@ -268,7 +282,7 @@ class UserDal(DalBase):
|
|||||||
try:
|
try:
|
||||||
send_result = await es.send_email([email], subject, body)
|
send_result = await es.send_email([email], subject, body)
|
||||||
user["send_sms_status"] = send_result
|
user["send_sms_status"] = send_result
|
||||||
user["send_sms_msg"] = "" if send_result else "发送失败,请联系管理员"
|
user["send_sms_msg"] = "" if send_result else "短信发送失败,请联系管理员"
|
||||||
except CustomException as e:
|
except CustomException as e:
|
||||||
user["send_sms_status"] = False
|
user["send_sms_status"] = False
|
||||||
user["send_sms_msg"] = e.msg
|
user["send_sms_msg"] = e.msg
|
||||||
@ -277,7 +291,7 @@ class UserDal(DalBase):
|
|||||||
user["send_sms_msg"] = "未获取到邮箱地址"
|
user["send_sms_msg"] = "未获取到邮箱地址"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def update_current_avatar(self, user: models.VadminUser, file: UploadFile):
|
async def update_current_avatar(self, user: models.VadminUser, file: UploadFile) -> str:
|
||||||
"""
|
"""
|
||||||
更新当前用户头像
|
更新当前用户头像
|
||||||
"""
|
"""
|
||||||
@ -286,7 +300,7 @@ class UserDal(DalBase):
|
|||||||
await self.flush(user)
|
await self.flush(user)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def update_wx_server_openid(self, code: str, user: models.VadminUser, redis: Redis):
|
async def update_wx_server_openid(self, code: str, user: models.VadminUser, redis: Redis) -> bool:
|
||||||
"""
|
"""
|
||||||
更新用户服务端微信平台openid
|
更新用户服务端微信平台openid
|
||||||
"""
|
"""
|
||||||
@ -299,7 +313,7 @@ class UserDal(DalBase):
|
|||||||
await self.flush(user)
|
await self.flush(user)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs):
|
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
删除多个用户,软删除
|
删除多个用户,软删除
|
||||||
删除后清空所关联的角色
|
删除后清空所关联的角色
|
||||||
@ -323,16 +337,16 @@ class RoleDal(DalBase):
|
|||||||
async def create_data(
|
async def create_data(
|
||||||
self,
|
self,
|
||||||
data: schemas.RoleIn,
|
data: schemas.RoleIn,
|
||||||
v_options: list = None,
|
v_options: list[_AbstractLoad] = None,
|
||||||
v_return_obj: bool = False,
|
v_return_obj: bool = False,
|
||||||
v_schema: Any = None
|
v_schema: Any = None
|
||||||
):
|
) -> Any:
|
||||||
"""创建数据"""
|
"""创建数据"""
|
||||||
obj = self.model(**data.model_dump(exclude={'menu_ids'}))
|
obj = self.model(**data.model_dump(exclude={'menu_ids'}))
|
||||||
menus = await MenuDal(db=self.db).get_datas(limit=0, id=("in", data.menu_ids), v_return_objs=True)
|
|
||||||
if data.menu_ids:
|
if data.menu_ids:
|
||||||
|
menus = await MenuDal(db=self.db).get_datas(limit=0, id=("in", data.menu_ids), v_return_objs=True)
|
||||||
for menu in menus:
|
for menu in menus:
|
||||||
obj.menus.append(menu)
|
obj.menus.add(menu)
|
||||||
await self.flush(obj)
|
await self.flush(obj)
|
||||||
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
|
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
|
||||||
|
|
||||||
@ -340,37 +354,37 @@ class RoleDal(DalBase):
|
|||||||
self,
|
self,
|
||||||
data_id: int,
|
data_id: int,
|
||||||
data: schemas.RoleIn,
|
data: schemas.RoleIn,
|
||||||
v_options: list = None,
|
v_options: list[_AbstractLoad] = None,
|
||||||
v_return_obj: bool = False,
|
v_return_obj: bool = False,
|
||||||
v_schema: Any = None
|
v_schema: Any = None
|
||||||
):
|
) -> Any:
|
||||||
"""更新单个数据"""
|
"""更新单个数据"""
|
||||||
obj = await self.get_data(data_id, v_options=[joinedload(self.model.menus)])
|
obj = await self.get_data(data_id, v_options=[joinedload(self.model.menus)])
|
||||||
obj_dict = jsonable_encoder(data)
|
obj_dict = jsonable_encoder(data)
|
||||||
for key, value in obj_dict.items():
|
for key, value in obj_dict.items():
|
||||||
if key == "menu_ids":
|
if key == "menu_ids":
|
||||||
if obj.menus:
|
|
||||||
obj.menus.clear()
|
|
||||||
if value:
|
if value:
|
||||||
menus = await MenuDal(db=self.db).get_datas(limit=0, id=("in", value), v_return_objs=True)
|
menus = await MenuDal(db=self.db).get_datas(limit=0, id=("in", value), v_return_objs=True)
|
||||||
|
if obj.menus:
|
||||||
|
obj.menus.clear()
|
||||||
for menu in menus:
|
for menu in menus:
|
||||||
obj.menus.append(menu)
|
obj.menus.add(menu)
|
||||||
continue
|
continue
|
||||||
setattr(obj, key, value)
|
setattr(obj, key, value)
|
||||||
await self.flush(obj)
|
await self.flush(obj)
|
||||||
return await self.out_dict(obj, None, v_return_obj, v_schema)
|
return await self.out_dict(obj, None, v_return_obj, v_schema)
|
||||||
|
|
||||||
async def get_role_menu_tree(self, role_id: int):
|
async def get_role_menu_tree(self, role_id: int) -> list:
|
||||||
role = await self.get_data(role_id, v_options=[joinedload(self.model.menus)])
|
role = await self.get_data(role_id, v_options=[joinedload(self.model.menus)])
|
||||||
return [i.id for i in role.menus]
|
return [i.id for i in role.menus]
|
||||||
|
|
||||||
async def get_select_datas(self):
|
async def get_select_datas(self) -> list:
|
||||||
"""获取选择数据,全部数据"""
|
"""获取选择数据,全部数据"""
|
||||||
sql = select(self.model)
|
sql = select(self.model)
|
||||||
queryset = await self.db.execute(sql)
|
queryset = await self.db.scalars(sql)
|
||||||
return [schemas.RoleSelectOut.model_validate(i).model_dump() for i in queryset.scalars().all()]
|
return [schemas.RoleSelectOut.model_validate(i).model_dump() for i in queryset.all()]
|
||||||
|
|
||||||
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs):
|
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
删除多个角色,硬删除
|
删除多个角色,硬删除
|
||||||
如果存在用户关联则无法删除
|
如果存在用户关联则无法删除
|
||||||
@ -378,8 +392,8 @@ class RoleDal(DalBase):
|
|||||||
:param v_soft: 是否执行软删除
|
:param v_soft: 是否执行软删除
|
||||||
:param kwargs: 其他更新字段
|
:param kwargs: 其他更新字段
|
||||||
"""
|
"""
|
||||||
objs = await self.get_datas(limit=0, id=("in", ids), user_total_number=(">", 0), v_return_objs=True)
|
user_count = await UserDal(self.db).get_count(v_join=[["roles"]], v_where=[models.VadminRole.id.in_(ids)])
|
||||||
if objs:
|
if user_count > 0:
|
||||||
raise CustomException("无法删除存在用户关联的角色", code=400)
|
raise CustomException("无法删除存在用户关联的角色", code=400)
|
||||||
return await super(RoleDal, self).delete_datas(ids, v_soft, **kwargs)
|
return await super(RoleDal, self).delete_datas(ids, v_soft, **kwargs)
|
||||||
|
|
||||||
@ -389,7 +403,7 @@ class MenuDal(DalBase):
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
super(MenuDal, self).__init__(db, models.VadminMenu, schemas.MenuSimpleOut)
|
super(MenuDal, self).__init__(db, models.VadminMenu, schemas.MenuSimpleOut)
|
||||||
|
|
||||||
async def get_tree_list(self, mode: int):
|
async def get_tree_list(self, mode: int) -> list:
|
||||||
"""
|
"""
|
||||||
1:获取菜单树列表
|
1:获取菜单树列表
|
||||||
2:获取菜单树选择项,添加/修改菜单时使用
|
2:获取菜单树选择项,添加/修改菜单时使用
|
||||||
@ -399,8 +413,8 @@ class MenuDal(DalBase):
|
|||||||
sql = select(self.model).where(self.model.disabled == 0, self.model.is_delete == False)
|
sql = select(self.model).where(self.model.disabled == 0, self.model.is_delete == False)
|
||||||
else:
|
else:
|
||||||
sql = select(self.model).where(self.model.is_delete == False)
|
sql = select(self.model).where(self.model.is_delete == False)
|
||||||
queryset = await self.db.execute(sql)
|
queryset = await self.db.scalars(sql)
|
||||||
datas = queryset.scalars().all()
|
datas = list(queryset.all())
|
||||||
roots = filter(lambda i: not i.parent_id, datas)
|
roots = filter(lambda i: not i.parent_id, datas)
|
||||||
if mode == 1:
|
if mode == 1:
|
||||||
menus = self.generate_tree_list(datas, roots)
|
menus = self.generate_tree_list(datas, roots)
|
||||||
@ -410,7 +424,7 @@ class MenuDal(DalBase):
|
|||||||
raise CustomException("获取菜单失败,无可用选项", code=400)
|
raise CustomException("获取菜单失败,无可用选项", code=400)
|
||||||
return self.menus_order(menus)
|
return self.menus_order(menus)
|
||||||
|
|
||||||
async def get_routers(self, user: models.VadminUser):
|
async def get_routers(self, user: models.VadminUser) -> list:
|
||||||
"""
|
"""
|
||||||
获取路由表
|
获取路由表
|
||||||
declare interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
declare interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||||
@ -423,12 +437,12 @@ class MenuDal(DalBase):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
if any([i.is_admin for i in user.roles]):
|
if any([i.is_admin for i in user.roles]):
|
||||||
sql = select(self.model)\
|
sql = select(self.model) \
|
||||||
.where(self.model.disabled == 0, self.model.menu_type != "2", self.model.is_delete == False)
|
.where(self.model.disabled == 0, self.model.menu_type != "2", self.model.is_delete == False)
|
||||||
queryset = await self.db.execute(sql)
|
queryset = await self.db.scalars(sql)
|
||||||
datas = queryset.scalars().all()
|
datas = list(queryset.all())
|
||||||
else:
|
else:
|
||||||
options = [joinedload(models.VadminUser.roles), joinedload("roles.menus")]
|
options = [joinedload(models.VadminUser.roles).subqueryload(models.VadminRole.menus)]
|
||||||
user = await UserDal(self.db).get_data(user.id, v_options=options)
|
user = await UserDal(self.db).get_data(user.id, v_options=options)
|
||||||
datas = set()
|
datas = set()
|
||||||
for role in user.roles:
|
for role in user.roles:
|
||||||
@ -492,7 +506,7 @@ class MenuDal(DalBase):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def menus_order(cls, datas: list, order: str = "order", children: str = "children"):
|
def menus_order(cls, datas: list, order: str = "order", children: str = "children") -> list:
|
||||||
"""
|
"""
|
||||||
菜单排序
|
菜单排序
|
||||||
"""
|
"""
|
||||||
@ -502,7 +516,7 @@ class MenuDal(DalBase):
|
|||||||
item[children] = sorted(item[children], key=lambda menu: menu[order])
|
item[children] = sorted(item[children], key=lambda menu: menu[order])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs):
|
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
删除多个菜单
|
删除多个菜单
|
||||||
如果存在角色关联则无法删除
|
如果存在角色关联则无法删除
|
||||||
@ -510,10 +524,30 @@ class MenuDal(DalBase):
|
|||||||
:param v_soft: 是否执行软删除
|
:param v_soft: 是否执行软删除
|
||||||
:param kwargs: 其他更新字段
|
:param kwargs: 其他更新字段
|
||||||
"""
|
"""
|
||||||
options = [joinedload(self.model.roles)]
|
count = await RoleDal(self.db).get_count(v_join=[["menus"]], v_where=[self.model.id.in_(ids)])
|
||||||
objs = await self.get_datas(limit=0, id=("in", ids), v_return_objs=True, v_options=options)
|
if count > 0:
|
||||||
for obj in objs:
|
raise CustomException("无法删除存在角色关联的菜单", code=400)
|
||||||
if obj.roles:
|
await super(MenuDal, self).delete_datas(ids, v_soft, **kwargs)
|
||||||
raise CustomException("无法删除存在角色关联的菜单", code=400)
|
|
||||||
return await super(MenuDal, self).delete_datas(ids, v_soft, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestDal(DalBase):
|
||||||
|
|
||||||
|
def __init__(self, db: AsyncSession):
|
||||||
|
super(TestDal, self).__init__(db, models.VadminUser, schemas.UserSimpleOut)
|
||||||
|
|
||||||
|
async def test(self):
|
||||||
|
# print("-----------------------开始------------------------")
|
||||||
|
options = [joinedload(self.model.roles)]
|
||||||
|
v_where = [self.model.id == 1, models.VadminRole.id == 1]
|
||||||
|
v_join = [[self.model.roles]]
|
||||||
|
v_start_sql = select(self.model)
|
||||||
|
result, count = await self.get_datas(
|
||||||
|
v_start_sql=v_start_sql,
|
||||||
|
v_join=v_join,
|
||||||
|
v_options=options,
|
||||||
|
v_where=v_where
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
print(result)
|
||||||
|
print(count)
|
||||||
|
# print("-----------------------结束------------------------")
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
# @desc : 简要说明
|
# @desc : 简要说明
|
||||||
|
|
||||||
|
|
||||||
from .m2m import vadmin_user_roles, vadmin_role_menus
|
from .m2m import vadmin_auth_user_roles, vadmin_auth_role_menus
|
||||||
from .menu import VadminMenu
|
from .menu import VadminMenu
|
||||||
from .role import VadminRole
|
from .role import VadminRole
|
||||||
from .user import VadminUser
|
from .user import VadminUser
|
||||||
|
@ -6,27 +6,22 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 关联中间表
|
# @desc : 关联中间表
|
||||||
|
|
||||||
"""
|
from db.db_base import Base
|
||||||
Table 操作博客:http://www.ttlsa.com/python/sqlalchemy-concise-guide/
|
from sqlalchemy import ForeignKey, Column, Table, Integer
|
||||||
"""
|
|
||||||
|
|
||||||
from db.db_base import Model
|
|
||||||
from sqlalchemy import Column, Table, Integer, ForeignKey, INT
|
|
||||||
|
|
||||||
|
|
||||||
vadmin_user_roles = Table(
|
vadmin_auth_user_roles = Table(
|
||||||
'vadmin_auth_user_roles',
|
"vadmin_auth_user_roles",
|
||||||
Model.metadata,
|
Base.metadata,
|
||||||
Column("id", INT, primary_key=True, unique=True, comment='主键ID', index=True, autoincrement=True),
|
Column("user_id", Integer, ForeignKey("vadmin_auth_user.id", ondelete="CASCADE")),
|
||||||
Column('user_id', Integer, ForeignKey('vadmin_auth_user.id', ondelete='CASCADE'), primary_key=True),
|
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
|
||||||
Column('role_id', Integer, ForeignKey('vadmin_auth_role.id', ondelete='CASCADE'), primary_key=True),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
vadmin_role_menus = Table(
|
vadmin_auth_role_menus = Table(
|
||||||
'vadmin_auth_role_menus',
|
"vadmin_auth_role_menus",
|
||||||
Model.metadata,
|
Base.metadata,
|
||||||
Column("id", INT, primary_key=True, unique=True, comment='主键ID', index=True, autoincrement=True),
|
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
|
||||||
Column('role_id', Integer, ForeignKey('vadmin_auth_role.id', ondelete='CASCADE'), primary_key=True),
|
Column("menu_id", Integer, ForeignKey("vadmin_auth_menu.id", ondelete="CASCADE")),
|
||||||
Column('menu_id', Integer, ForeignKey('vadmin_auth_menu.id', ondelete='CASCADE'), primary_key=True),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -7,39 +7,59 @@
|
|||||||
# @desc : 菜单模型
|
# @desc : 菜单模型
|
||||||
|
|
||||||
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from .m2m import vadmin_role_menus
|
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, Boolean, Integer, ForeignKey
|
from sqlalchemy import String, Boolean, Integer, ForeignKey
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
|
||||||
class VadminMenu(BaseModel):
|
class VadminMenu(BaseModel):
|
||||||
__tablename__ = "vadmin_auth_menu"
|
__tablename__ = "vadmin_auth_menu"
|
||||||
__table_args__ = ({'comment': '菜单表'})
|
__table_args__ = ({'comment': '菜单表'})
|
||||||
|
|
||||||
# class MenuTypes(Enum):
|
title: Mapped[str] = mapped_column(String(50), comment="名称")
|
||||||
# dir = "0"
|
icon: Mapped[str | None] = mapped_column(String(50), comment="菜单图标")
|
||||||
# menu = "1"
|
redirect: Mapped[str | None] = mapped_column(String(100), comment="重定向地址")
|
||||||
# button = "2"
|
component: Mapped[str | None] = mapped_column(String(50), comment="前端组件地址")
|
||||||
|
path: Mapped[str | None] = mapped_column(String(50), comment="前端路由地址")
|
||||||
title = Column(String(50), index=True, nullable=False, comment="名称")
|
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||||
icon = Column(String(50), comment="菜单图标")
|
hidden: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否隐藏")
|
||||||
redirect = Column(String(100), comment="重定向地址")
|
order: Mapped[int] = mapped_column(Integer, comment="排序")
|
||||||
component = Column(String(50), comment="前端组件地址")
|
menu_type: Mapped[str] = mapped_column(String(8), comment="菜单类型")
|
||||||
path = Column(String(50), comment="前端路由地址")
|
parent_id: Mapped[int | None] = mapped_column(
|
||||||
disabled = Column(Boolean, default=False, comment="是否禁用")
|
Integer,
|
||||||
hidden = Column(Boolean, default=False, comment="是否隐藏")
|
ForeignKey("vadmin_auth_menu.id", ondelete='CASCADE'),
|
||||||
order = Column(Integer, comment="排序")
|
comment="父菜单"
|
||||||
menu_type = Column(String(8), comment="菜单类型")
|
)
|
||||||
parent_id = Column(ForeignKey("vadmin_auth_menu.id", ondelete='CASCADE'), comment="父菜单")
|
perms: Mapped[str | None] = mapped_column(String(50), comment="权限标识", unique=False, index=True)
|
||||||
perms = Column(String(50), comment="权限标识", unique=False, nullable=True, index=True)
|
noCache: Mapped[bool] = mapped_column(
|
||||||
noCache = Column(Boolean, comment="如果设置为true,则不会被 <keep-alive> 缓存(默认 false)", default=False)
|
Boolean,
|
||||||
breadcrumb = Column(Boolean, comment="如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)", default=True)
|
comment="如果设置为true,则不会被 <keep-alive> 缓存(默认 false)",
|
||||||
affix = Column(Boolean, comment="如果设置为true,则会一直固定在tag项中(默认 false)", default=False)
|
default=False
|
||||||
noTagsView = Column(Boolean, comment="如果设置为true,则不会出现在tag中(默认 false)", default=False)
|
)
|
||||||
canTo = Column(Boolean, comment="设置为true即使hidden为true,也依然可以进行路由跳转(默认 false)", default=False)
|
breadcrumb: Mapped[bool] = mapped_column(
|
||||||
alwaysShow = Column(Boolean, comment="""当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式,
|
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 声明的个数都显示你的根路由,
|
只有一个时,会将那个子路由当做根路由显示在侧边栏,若你想不管路由下面的 children 声明的个数都显示你的根路由,
|
||||||
你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由(默认 true)""", default=True)
|
你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由(默认 true)""",
|
||||||
|
default=True
|
||||||
roles = relationship("VadminRole", back_populates='menus', secondary=vadmin_role_menus)
|
)
|
||||||
|
@ -6,28 +6,22 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 角色模型
|
# @desc : 角色模型
|
||||||
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
from sqlalchemy_utils import aggregated
|
|
||||||
from .user import VadminUser
|
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, Boolean, Integer, func
|
from sqlalchemy import String, Boolean, Integer
|
||||||
from .m2m import vadmin_user_roles, vadmin_role_menus
|
from .menu import VadminMenu
|
||||||
|
from .m2m import vadmin_auth_role_menus
|
||||||
|
|
||||||
|
|
||||||
class VadminRole(BaseModel):
|
class VadminRole(BaseModel):
|
||||||
__tablename__ = "vadmin_auth_role"
|
__tablename__ = "vadmin_auth_role"
|
||||||
__table_args__ = ({'comment': '角色表'})
|
__table_args__ = ({'comment': '角色表'})
|
||||||
|
|
||||||
name = Column(String(50), index=True, nullable=False, comment="名称")
|
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="名称")
|
||||||
role_key = Column(String(50), index=True, nullable=False, comment="权限字符")
|
role_key: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="权限字符")
|
||||||
disabled = Column(Boolean, default=False, comment="是否禁用")
|
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||||
order = Column(Integer, comment="排序")
|
order: Mapped[int | None] = mapped_column(Integer, comment="排序")
|
||||||
desc = Column(String(255), comment="描述")
|
desc: Mapped[str | None] = mapped_column(String(255), comment="描述")
|
||||||
is_admin = Column(Boolean, comment="是否为超级角色", default=False)
|
is_admin: Mapped[bool] = mapped_column(Boolean, comment="是否为超级角色", default=False)
|
||||||
|
|
||||||
users = relationship("VadminUser", back_populates='roles', secondary=vadmin_user_roles)
|
menus: Mapped[set[VadminMenu]] = relationship(secondary=vadmin_auth_role_menus)
|
||||||
menus = relationship("VadminMenu", back_populates='roles', secondary=vadmin_role_menus)
|
|
||||||
|
|
||||||
@aggregated('users', Column(Integer, default=0, comment="用户总数"))
|
|
||||||
def user_total_number(self):
|
|
||||||
return func.count(VadminUser.id)
|
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 用户模型
|
# @desc : 用户模型
|
||||||
|
|
||||||
import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, Boolean, DateTime
|
from sqlalchemy import String, Boolean, DateTime
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from .m2m import vadmin_user_roles
|
from .role import VadminRole
|
||||||
|
from .m2m import vadmin_auth_user_roles
|
||||||
|
|
||||||
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
||||||
|
|
||||||
@ -21,22 +21,26 @@ class VadminUser(BaseModel):
|
|||||||
__tablename__ = "vadmin_auth_user"
|
__tablename__ = "vadmin_auth_user"
|
||||||
__table_args__ = ({'comment': '用户表'})
|
__table_args__ = ({'comment': '用户表'})
|
||||||
|
|
||||||
avatar = Column(String(500), nullable=True, comment='头像')
|
avatar: Mapped[str | None] = mapped_column(String(500), comment='头像')
|
||||||
telephone = Column(String(11), nullable=False, index=True, comment="手机号", unique=False)
|
telephone: Mapped[str] = mapped_column(String(11), nullable=False, index=True, comment="手机号", unique=False)
|
||||||
email = Column(String(50), nullable=True, comment="邮箱地址")
|
email: Mapped[str | None] = mapped_column(String(50), comment="邮箱地址")
|
||||||
name = Column(String(50), index=True, nullable=False, comment="姓名")
|
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="姓名")
|
||||||
nickname = Column(String(50), nullable=True, comment="昵称")
|
nickname: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="昵称")
|
||||||
password = Column(String(255), nullable=True, comment="密码")
|
password: Mapped[str] = mapped_column(String(255), nullable=True, comment="密码")
|
||||||
gender = Column(String(8), nullable=True, comment="性别")
|
gender: Mapped[str | None] = mapped_column(String(8), nullable=True, comment="性别")
|
||||||
is_active = Column(Boolean, default=True, comment="是否可用")
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可用")
|
||||||
is_reset_password = Column(Boolean, default=False, comment="是否已经重置密码,没有重置的,登陆系统后必须重置密码")
|
is_reset_password: Mapped[bool] = mapped_column(
|
||||||
last_ip = Column(String(50), nullable=True, comment="最后一次登录IP")
|
Boolean,
|
||||||
last_login = Column(DateTime, nullable=True, comment="最近一次登录时间")
|
default=False,
|
||||||
is_staff = Column(Boolean, default=False, comment="是否为工作人员")
|
comment="是否已经重置密码,没有重置的,登陆系统后必须重置密码"
|
||||||
wx_server_openid = Column(String(255), comment="服务端微信平台openid")
|
)
|
||||||
is_wx_server_openid = Column(Boolean, default=False, comment="是否已有服务端微信平台openid")
|
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 = relationship("VadminRole", back_populates='users', secondary=vadmin_user_roles)
|
roles: Mapped[set[VadminRole]] = relationship(secondary=vadmin_auth_user_roles)
|
||||||
|
|
||||||
# generate hash password
|
# generate hash password
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -48,17 +52,6 @@ class VadminUser(BaseModel):
|
|||||||
def verify_password(password: str, hashed_password: str) -> bool:
|
def verify_password(password: str, hashed_password: str) -> bool:
|
||||||
return pwd_context.verify(password, hashed_password)
|
return pwd_context.verify(password, hashed_password)
|
||||||
|
|
||||||
async def update_login_info(self, db: AsyncSession, last_ip: str):
|
|
||||||
"""
|
|
||||||
更新当前登录信息
|
|
||||||
:param db: 数据库
|
|
||||||
:param last_ip: 最近一次登录 IP
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.last_ip = last_ip
|
|
||||||
self.last_login = datetime.datetime.now()
|
|
||||||
await db.flush()
|
|
||||||
|
|
||||||
def is_admin(self) -> bool:
|
def is_admin(self) -> bool:
|
||||||
"""
|
"""
|
||||||
获取该用户是否拥有最高权限
|
获取该用户是否拥有最高权限
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 获取认证后的信息工具
|
# @desc : 获取认证后的信息工具
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from apps.vadmin.auth.crud import UserDal
|
from apps.vadmin.auth.crud import UserDal
|
||||||
from apps.vadmin.auth.models import VadminUser
|
from apps.vadmin.auth.models import VadminUser, VadminRole
|
||||||
from core.exception import CustomException
|
from core.exception import CustomException
|
||||||
from utils import status
|
from utils import status
|
||||||
from .validation import AuthValidation
|
from .validation import AuthValidation
|
||||||
@ -28,7 +29,7 @@ class OpenAuth(AuthValidation):
|
|||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
request: Request,
|
request: Request,
|
||||||
token: str = Depends(settings.oauth2_scheme),
|
token: Annotated[str, Depends(settings.oauth2_scheme)],
|
||||||
db: AsyncSession = Depends(db_getter)
|
db: AsyncSession = Depends(db_getter)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -93,7 +94,7 @@ class FullAdminAuth(AuthValidation):
|
|||||||
if not settings.OAUTH_ENABLE:
|
if not settings.OAUTH_ENABLE:
|
||||||
return Auth(db=db)
|
return Auth(db=db)
|
||||||
telephone = self.validate_token(request, token)
|
telephone = self.validate_token(request, token)
|
||||||
options = [joinedload(VadminUser.roles), joinedload("roles.menus")]
|
options = [joinedload(VadminUser.roles).subqueryload(VadminRole.menus)]
|
||||||
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True, v_options=options, is_staff=True)
|
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True, v_options=options, is_staff=True)
|
||||||
result = await self.validate_user(request, user, db)
|
result = await self.validate_user(request, user, db)
|
||||||
permissions = self.get_user_permissions(user)
|
permissions = self.get_user_permissions(user)
|
||||||
|
@ -126,7 +126,7 @@ async def wx_login_for_access_token(
|
|||||||
return ErrorResponse(msg=str(e))
|
return ErrorResponse(msg=str(e))
|
||||||
|
|
||||||
# 更新登录时间
|
# 更新登录时间
|
||||||
await user.update_login_info(db, request.client.host)
|
await UserDal(db).update_login_info(user, request.client.host)
|
||||||
|
|
||||||
# 登录成功创建 token
|
# 登录成功创建 token
|
||||||
access_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": False})
|
access_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": False})
|
||||||
|
@ -7,13 +7,12 @@
|
|||||||
# @desc : 登录验证装饰器
|
# @desc : 登录验证装饰器
|
||||||
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from pydantic import BaseModel, validator, field_validator
|
from pydantic import BaseModel, field_validator
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from application.settings import DEFAULT_AUTH_ERROR_MAX_NUMBER, DEMO
|
from application.settings import DEFAULT_AUTH_ERROR_MAX_NUMBER, DEMO, REDIS_DB_ENABLE
|
||||||
from apps.vadmin.auth import crud, schemas
|
from apps.vadmin.auth import crud, schemas
|
||||||
from core.database import redis_getter
|
from core.database import redis_getter
|
||||||
from core.validator import vali_telephone
|
from core.validator import vali_telephone
|
||||||
from typing import Optional
|
|
||||||
from utils.count import Count
|
from utils.count import Count
|
||||||
|
|
||||||
|
|
||||||
@ -64,12 +63,15 @@ class LoginValidation:
|
|||||||
|
|
||||||
result = await self.func(self, data=data, user=user, request=request)
|
result = await self.func(self, data=data, user=user, request=request)
|
||||||
|
|
||||||
count_key = f"{data.telephone}_password_auth" if data.method == '0' else f"{data.telephone}_sms_auth"
|
if REDIS_DB_ENABLE:
|
||||||
count = Count(redis_getter(request), count_key)
|
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:
|
if not result.status:
|
||||||
self.result.msg = result.msg
|
self.result.msg = result.msg
|
||||||
if not DEMO:
|
if not DEMO and count:
|
||||||
number = await count.add(ex=86400)
|
number = await count.add(ex=86400)
|
||||||
if number >= DEFAULT_AUTH_ERROR_MAX_NUMBER:
|
if number >= DEFAULT_AUTH_ERROR_MAX_NUMBER:
|
||||||
await count.reset()
|
await count.reset()
|
||||||
@ -81,10 +83,10 @@ class LoginValidation:
|
|||||||
elif data.platform in ["0", "1"] and not user.is_staff:
|
elif data.platform in ["0", "1"] and not user.is_staff:
|
||||||
self.result.msg = "此手机号无权限!"
|
self.result.msg = "此手机号无权限!"
|
||||||
else:
|
else:
|
||||||
if not DEMO:
|
if not DEMO and count:
|
||||||
await count.delete()
|
await count.delete()
|
||||||
self.result.msg = "OK"
|
self.result.msg = "OK"
|
||||||
self.result.status = True
|
self.result.status = True
|
||||||
self.result.user = schemas.UserSimpleOut.model_validate(user)
|
self.result.user = schemas.UserSimpleOut.model_validate(user)
|
||||||
await user.update_login_info(db, request.client.host)
|
await crud.UserDal(db).update_login_info(user, request.client.host)
|
||||||
return self.result
|
return self.result
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
# @File : views.py
|
# @File : views.py
|
||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 简要说明
|
# @desc : 简要说明
|
||||||
|
|
||||||
from aioredis import Redis
|
from aioredis import Redis
|
||||||
from fastapi import APIRouter, Depends, Body, UploadFile, Request
|
from fastapi import APIRouter, Depends, Body, UploadFile, Request
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
@ -12,13 +13,22 @@ from core.database import redis_getter
|
|||||||
from utils.response import SuccessResponse, ErrorResponse
|
from utils.response import SuccessResponse, ErrorResponse
|
||||||
from . import schemas, crud, models
|
from . import schemas, crud, models
|
||||||
from core.dependencies import IdList
|
from core.dependencies import IdList
|
||||||
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth
|
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth
|
||||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||||
from .params import UserParams, RoleParams
|
from .params import UserParams, RoleParams
|
||||||
|
|
||||||
app = APIRouter()
|
app = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
# 接口测试
|
||||||
|
###########################################################
|
||||||
|
@app.get("/test", summary="接口测试")
|
||||||
|
async def test(auth: Auth = Depends(OpenAuth())):
|
||||||
|
await crud.TestDal(auth.db).test()
|
||||||
|
return SuccessResponse()
|
||||||
|
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
# 用户管理
|
# 用户管理
|
||||||
###########################################################
|
###########################################################
|
||||||
@ -30,8 +40,12 @@ async def get_users(
|
|||||||
model = models.VadminUser
|
model = models.VadminUser
|
||||||
options = [joinedload(model.roles)]
|
options = [joinedload(model.roles)]
|
||||||
schema = schemas.UserOut
|
schema = schemas.UserOut
|
||||||
datas = await crud.UserDal(auth.db).get_datas(**params.dict(), v_options=options, v_schema=schema)
|
datas, count = await crud.UserDal(auth.db).get_datas(
|
||||||
count = await crud.UserDal(auth.db).get_count(**params.to_count())
|
**params.dict(),
|
||||||
|
v_options=options,
|
||||||
|
v_schema=schema,
|
||||||
|
v_return_count=True
|
||||||
|
)
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
@ -67,7 +81,7 @@ async def get_user(
|
|||||||
model = models.VadminUser
|
model = models.VadminUser
|
||||||
options = [joinedload(model.roles)]
|
options = [joinedload(model.roles)]
|
||||||
schema = schemas.UserOut
|
schema = schemas.UserOut
|
||||||
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, options, v_schema=schema))
|
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/user/current/reset/password", summary="重置当前用户密码")
|
@app.post("/user/current/reset/password", summary="重置当前用户密码")
|
||||||
@ -145,8 +159,7 @@ async def get_roles(
|
|||||||
params: RoleParams = Depends(),
|
params: RoleParams = Depends(),
|
||||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.list"]))
|
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.list"]))
|
||||||
):
|
):
|
||||||
datas = await crud.RoleDal(auth.db).get_datas(**params.dict())
|
datas, count = await crud.RoleDal(auth.db).get_datas(**params.dict(), v_return_count=True)
|
||||||
count = await crud.RoleDal(auth.db).get_count(**params.to_count())
|
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
@ -187,7 +200,7 @@ async def get_role(
|
|||||||
model = models.VadminRole
|
model = models.VadminRole
|
||||||
options = [joinedload(model.menus)]
|
options = [joinedload(model.menus)]
|
||||||
schema = schemas.RoleOut
|
schema = schemas.RoleOut
|
||||||
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, options, v_schema=schema))
|
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
|
||||||
|
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
@ -239,7 +252,7 @@ async def put_menus(
|
|||||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.view", "auth.menu.update"]))
|
auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.view", "auth.menu.update"]))
|
||||||
):
|
):
|
||||||
schema = schemas.MenuSimpleOut
|
schema = schemas.MenuSimpleOut
|
||||||
return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, None, v_schema=schema))
|
return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, v_schema=schema))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/role/menus/tree/{role_id}", summary="获取菜单列表树信息以及角色菜单权限ID,角色权限使用")
|
@app.get("/role/menus/tree/{role_id}", summary="获取菜单列表树信息以及角色菜单权限ID,角色权限使用")
|
||||||
|
@ -7,11 +7,8 @@
|
|||||||
# @desc : 帮助中心 - 增删改查
|
# @desc : 帮助中心 - 增删改查
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import joinedload
|
|
||||||
|
|
||||||
from core.crud import DalBase
|
from core.crud import DalBase
|
||||||
from . import models, schemas
|
from . import models, schemas
|
||||||
from apps.vadmin.auth import models as vadminAuthModels
|
|
||||||
|
|
||||||
|
|
||||||
class IssueDal(DalBase):
|
class IssueDal(DalBase):
|
||||||
@ -19,47 +16,16 @@ class IssueDal(DalBase):
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
super(IssueDal, self).__init__(db, models.VadminIssue, schemas.IssueSimpleOut)
|
super(IssueDal, self).__init__(db, models.VadminIssue, schemas.IssueSimpleOut)
|
||||||
|
|
||||||
async def add_view_number(self, data_id: int):
|
async def add_view_number(self, data_id: int) -> None:
|
||||||
"""
|
"""
|
||||||
更新常见问题查看次数+1
|
更新常见问题查看次数+1
|
||||||
"""
|
"""
|
||||||
obj = await self.get_data(data_id)
|
obj: models.VadminIssue = await self.get_data(data_id)
|
||||||
obj.view_number = obj.view_number + 1 if obj.view_number else 1
|
obj.view_number = obj.view_number + 1 if obj.view_number else 1
|
||||||
await self.flush(obj)
|
await self.flush(obj)
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class IssueCategoryDal(DalBase):
|
class IssueCategoryDal(DalBase):
|
||||||
|
|
||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
key_models = {
|
super(IssueCategoryDal, self).__init__(db, models.VadminIssueCategory, schemas.IssueCategorySimpleOut)
|
||||||
# 外键字段名,也可以自定义
|
|
||||||
"create_user": {
|
|
||||||
# 外键对应的orm模型
|
|
||||||
"model": vadminAuthModels.VadminUser,
|
|
||||||
# 如果对同一个模型只有一个外键关联时,下面这个 onclause 可以省略不写,一个以上时必须写,需要分清楚要查询的是哪个
|
|
||||||
# 这里其实可以省略不写,但是为了演示这里写出来了
|
|
||||||
"onclause": models.VadminIssueCategory.create_user_id == vadminAuthModels.VadminUser.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super(IssueCategoryDal, self).__init__(
|
|
||||||
db,
|
|
||||||
models.VadminIssueCategory,
|
|
||||||
schemas.IssueCategorySimpleOut,
|
|
||||||
key_models
|
|
||||||
)
|
|
||||||
|
|
||||||
async def test(self):
|
|
||||||
"""
|
|
||||||
v_join_query 示例方法
|
|
||||||
获取用户名称包含李 创建出的常见问题类别
|
|
||||||
"""
|
|
||||||
v_join_query = {
|
|
||||||
# 与 key_models 中定义的外键字段名定义的一样
|
|
||||||
"create_user": {
|
|
||||||
# 外键表字段名:查询值
|
|
||||||
"name": ("like", "李")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v_options = [joinedload(self.model.create_user)]
|
|
||||||
datas = self.get_datas(limit=0, v_join_query=v_join_query, v_options=v_options)
|
|
||||||
|
@ -6,37 +6,49 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 常见问题
|
# @desc : 常见问题
|
||||||
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
|
from apps.vadmin.auth.models import VadminUser
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, Boolean, Integer, ForeignKey, Text
|
from sqlalchemy import String, Boolean, Integer, ForeignKey
|
||||||
|
|
||||||
|
|
||||||
class VadminIssueCategory(BaseModel):
|
class VadminIssueCategory(BaseModel):
|
||||||
__tablename__ = "vadmin_help_issue_category"
|
__tablename__ = "vadmin_help_issue_category"
|
||||||
__table_args__ = ({'comment': '常见问题类别表'})
|
__table_args__ = ({'comment': '常见问题类别表'})
|
||||||
|
|
||||||
name = Column(String(50), index=True, nullable=False, comment="类别名称")
|
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="类别名称")
|
||||||
platform = Column(String(8), index=True, nullable=False, comment="展示平台")
|
platform: Mapped[str] = mapped_column(String(8), index=True, nullable=False, comment="展示平台")
|
||||||
is_active = Column(Boolean, default=True, comment="是否可见")
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
|
||||||
|
|
||||||
issues = relationship("VadminIssue", back_populates='category')
|
issues: Mapped[list["VadminIssue"]] = relationship(back_populates='category')
|
||||||
|
|
||||||
create_user_id = Column(ForeignKey("vadmin_auth_user.id", ondelete='SET NULL'), comment="创建人")
|
create_user_id: Mapped[int] = mapped_column(
|
||||||
create_user = relationship("VadminUser", foreign_keys=create_user_id)
|
Integer,
|
||||||
|
ForeignKey("vadmin_auth_user.id", ondelete='RESTRICT'),
|
||||||
|
comment="创建人"
|
||||||
|
)
|
||||||
|
create_user: Mapped[VadminUser] = relationship(foreign_keys=create_user_id)
|
||||||
|
|
||||||
|
|
||||||
class VadminIssue(BaseModel):
|
class VadminIssue(BaseModel):
|
||||||
__tablename__ = "vadmin_help_issue"
|
__tablename__ = "vadmin_help_issue"
|
||||||
__table_args__ = ({'comment': '常见问题记录表'})
|
__table_args__ = ({'comment': '常见问题记录表'})
|
||||||
|
|
||||||
category_id = Column(ForeignKey("vadmin_help_issue_category.id", ondelete='CASCADE'), comment="类别")
|
category_id: Mapped[int] = mapped_column(
|
||||||
category = relationship("VadminIssueCategory", foreign_keys=category_id, back_populates='issues')
|
Integer,
|
||||||
|
ForeignKey("vadmin_help_issue_category.id", ondelete='CASCADE'),
|
||||||
|
comment="类别"
|
||||||
|
)
|
||||||
|
category: Mapped[list["VadminIssueCategory"]] = relationship(foreign_keys=category_id, back_populates='issues')
|
||||||
|
|
||||||
title = Column(String(255), index=True, nullable=False, comment="标题")
|
title: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="标题")
|
||||||
content = Column(Text, comment="内容")
|
content: Mapped[str] = mapped_column(String(5000), comment="内容")
|
||||||
view_number = Column(Integer, default=0, comment="查看次数")
|
view_number: Mapped[int] = mapped_column(Integer, default=0, comment="查看次数")
|
||||||
is_active = Column(Boolean, default=True, comment="是否可见")
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
|
||||||
|
|
||||||
create_user_id = Column(ForeignKey("vadmin_auth_user.id", ondelete='SET NULL'), comment="创建人")
|
|
||||||
create_user = relationship("VadminUser", foreign_keys=create_user_id)
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
@ -27,8 +27,12 @@ async def get_issue_categorys(p: params.IssueCategoryParams = Depends(), auth: A
|
|||||||
model = models.VadminIssueCategory
|
model = models.VadminIssueCategory
|
||||||
options = [joinedload(model.create_user)]
|
options = [joinedload(model.create_user)]
|
||||||
schema = schemas.IssueCategoryListOut
|
schema = schemas.IssueCategoryListOut
|
||||||
datas = await crud.IssueCategoryDal(auth.db).get_datas(**p.dict(), v_options=options, v_schema=schema)
|
datas, count = await crud.IssueCategoryDal(auth.db).get_datas(
|
||||||
count = await crud.IssueCategoryDal(auth.db).get_count(**p.to_count())
|
**p.dict(),
|
||||||
|
v_options=options,
|
||||||
|
v_schema=schema,
|
||||||
|
v_return_count=True
|
||||||
|
)
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
@ -67,6 +71,7 @@ async def get_issue_category_platform(platform: str, db: AsyncSession = Depends(
|
|||||||
options = [joinedload(model.issues)]
|
options = [joinedload(model.issues)]
|
||||||
schema = schemas.IssueCategoryPlatformOut
|
schema = schemas.IssueCategoryPlatformOut
|
||||||
result = await crud.IssueCategoryDal(db).get_datas(
|
result = await crud.IssueCategoryDal(db).get_datas(
|
||||||
|
limit=0,
|
||||||
platform=platform,
|
platform=platform,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
v_schema=schema,
|
v_schema=schema,
|
||||||
@ -83,8 +88,12 @@ async def get_issues(p: params.IssueParams = Depends(), auth: Auth = Depends(All
|
|||||||
model = models.VadminIssue
|
model = models.VadminIssue
|
||||||
options = [joinedload(model.create_user), joinedload(model.category)]
|
options = [joinedload(model.create_user), joinedload(model.category)]
|
||||||
schema = schemas.IssueListOut
|
schema = schemas.IssueListOut
|
||||||
datas = await crud.IssueDal(auth.db).get_datas(**p.dict(), v_options=options, v_schema=schema)
|
datas, count = await crud.IssueDal(auth.db).get_datas(
|
||||||
count = await crud.IssueDal(auth.db).get_count(**p.to_count())
|
**p.dict(),
|
||||||
|
v_options=options,
|
||||||
|
v_schema=schema,
|
||||||
|
v_return_count=True
|
||||||
|
)
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,10 +5,8 @@
|
|||||||
# @File : crud.py
|
# @File : crud.py
|
||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 数据库 增删改查操作
|
# @desc : 数据库 增删改查操作
|
||||||
|
|
||||||
import random
|
import random
|
||||||
# sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008
|
|
||||||
# sqlalchemy 关联查询:https://www.jianshu.com/p/dfad7c08c57a
|
|
||||||
# sqlalchemy 关联查询详细:https://blog.csdn.net/u012324798/article/details/103940527
|
|
||||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from . import models, schemas
|
from . import models, schemas
|
||||||
|
@ -6,12 +6,15 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 登录记录模型
|
# @desc : 登录记录模型
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from application.settings import LOGIN_LOG_RECORD
|
from application.settings import LOGIN_LOG_RECORD
|
||||||
from apps.vadmin.auth.utils.validation import LoginForm, WXLoginForm
|
from apps.vadmin.auth.utils.validation import LoginForm, WXLoginForm
|
||||||
from utils.ip_manage import IPManage
|
from utils.ip_manage import IPManage
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, Boolean, TEXT
|
from sqlalchemy import String, Boolean
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from starlette.requests import Request as StarletteRequest
|
from starlette.requests import Request as StarletteRequest
|
||||||
from user_agents import parse
|
from user_agents import parse
|
||||||
@ -21,23 +24,23 @@ class VadminLoginRecord(BaseModel):
|
|||||||
__tablename__ = "vadmin_record_login"
|
__tablename__ = "vadmin_record_login"
|
||||||
__table_args__ = ({'comment': '登录记录表'})
|
__table_args__ = ({'comment': '登录记录表'})
|
||||||
|
|
||||||
telephone = Column(String(255), index=True, nullable=False, comment="手机号")
|
telephone: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="手机号")
|
||||||
status = Column(Boolean, default=True, comment="是否登录成功")
|
status: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否登录成功")
|
||||||
platform = Column(String(8), comment="登陆平台")
|
platform: Mapped[str] = mapped_column(String(8), comment="登陆平台")
|
||||||
login_method = Column(String(8), comment="认证方式")
|
login_method: Mapped[str] = mapped_column(String(8), comment="认证方式")
|
||||||
ip = Column(String(50), comment="登陆地址")
|
ip: Mapped[str | None] = mapped_column(String(50), comment="登陆地址")
|
||||||
address = Column(String(255), comment="登陆地点")
|
address: Mapped[str | None] = mapped_column(String(255), comment="登陆地点")
|
||||||
country = Column(String(255), comment="国家")
|
country: Mapped[str | None] = mapped_column(String(255), comment="国家")
|
||||||
province = Column(String(255), comment="县")
|
province: Mapped[str | None] = mapped_column(String(255), comment="县")
|
||||||
city = Column(String(255), comment="城市")
|
city: Mapped[str | None] = mapped_column(String(255), comment="城市")
|
||||||
county = Column(String(255), comment="区/县")
|
county: Mapped[str | None] = mapped_column(String(255), comment="区/县")
|
||||||
operator = Column(String(255), comment="运营商")
|
operator: Mapped[str | None] = mapped_column(String(255), comment="运营商")
|
||||||
postal_code = Column(String(255), comment="邮政编码")
|
postal_code: Mapped[str | None] = mapped_column(String(255), comment="邮政编码")
|
||||||
area_code = Column(String(255), comment="地区区号")
|
area_code: Mapped[str | None] = mapped_column(String(255), comment="地区区号")
|
||||||
browser = Column(String(50), comment="浏览器")
|
browser: Mapped[str | None] = mapped_column(String(50), comment="浏览器")
|
||||||
system = Column(String(50), comment="操作系统")
|
system: Mapped[str | None] = mapped_column(String(50), comment="操作系统")
|
||||||
response = Column(TEXT, comment="响应信息")
|
response: Mapped[str | None] = mapped_column(String(5000), comment="响应信息")
|
||||||
request = Column(TEXT, comment="请求信息")
|
request: Mapped[str | None] = mapped_column(String(5000), comment="请求信息")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create_login_record(
|
async def create_login_record(
|
||||||
|
@ -6,18 +6,18 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 短信发送记录模型
|
# @desc : 短信发送记录模型
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, Boolean, ForeignKey
|
from sqlalchemy import Integer, String, Boolean, ForeignKey
|
||||||
|
|
||||||
|
|
||||||
class VadminSMSSendRecord(BaseModel):
|
class VadminSMSSendRecord(BaseModel):
|
||||||
__tablename__ = "vadmin_record_sms_send"
|
__tablename__ = "vadmin_record_sms_send"
|
||||||
__table_args__ = ({'comment': '短信发送记录表'})
|
__table_args__ = ({'comment': '短信发送记录表'})
|
||||||
|
|
||||||
user_id = Column(ForeignKey("vadmin_auth_user.id", ondelete='CASCADE'), comment="操作人")
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("vadmin_auth_user.id", ondelete='CASCADE'), comment="操作人")
|
||||||
status = Column(Boolean, default=True, comment="发送状态")
|
status: Mapped[bool] = mapped_column(Boolean, default=True, comment="发送状态")
|
||||||
content = Column(String(255), comment="发送内容")
|
content: Mapped[str] = mapped_column(String(255), comment="发送内容")
|
||||||
telephone = Column(String(11), comment="目标手机号")
|
telephone: Mapped[str] = mapped_column(String(11), comment="目标手机号")
|
||||||
desc = Column(String(255), comment="失败描述")
|
desc: Mapped[str | None] = mapped_column(String(255), comment="失败描述")
|
||||||
scene = Column(String(50), comment="发送场景")
|
scene: Mapped[str | None] = mapped_column(String(50), comment="发送场景")
|
||||||
|
@ -7,9 +7,8 @@
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||||
|
|
||||||
from utils.response import SuccessResponse
|
from utils.response import SuccessResponse
|
||||||
from . import crud, schemas
|
from . import crud
|
||||||
from apps.vadmin.auth.utils.current import AllUserAuth
|
from apps.vadmin.auth.utils.current import AllUserAuth
|
||||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||||
from .params import LoginParams, OperationParams, SMSParams
|
from .params import LoginParams, OperationParams, SMSParams
|
||||||
@ -23,8 +22,7 @@ app = APIRouter()
|
|||||||
###########################################################
|
###########################################################
|
||||||
@app.get("/logins", summary="获取登录日志列表")
|
@app.get("/logins", summary="获取登录日志列表")
|
||||||
async def get_record_login(p: LoginParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
async def get_record_login(p: LoginParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||||
datas = await crud.LoginRecordDal(auth.db).get_datas(**p.dict())
|
datas, count = await crud.LoginRecordDal(auth.db).get_datas(**p.dict(), v_return_count=True)
|
||||||
count = await crud.LoginRecordDal(auth.db).get_count(**p.to_count())
|
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
@ -41,8 +39,7 @@ async def get_record_operation(
|
|||||||
|
|
||||||
@app.get("/sms/send/list", summary="获取短信发送列表")
|
@app.get("/sms/send/list", summary="获取短信发送列表")
|
||||||
async def get_sms_send_list(p: SMSParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
async def get_sms_send_list(p: SMSParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||||
datas = await crud.SMSSendRecordDal(auth.db).get_datas(**p.dict())
|
datas, count = await crud.SMSSendRecordDal(auth.db).get_datas(**p.dict(), v_return_count=True)
|
||||||
count = await crud.SMSSendRecordDal(auth.db).get_count(**p.to_count())
|
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,9 +6,6 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 数据库 增删改查操作
|
# @desc : 数据库 增删改查操作
|
||||||
|
|
||||||
# sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008
|
|
||||||
# sqlalchemy 关联查询:https://www.jianshu.com/p/dfad7c08c57a
|
|
||||||
# sqlalchemy 关联查询详细:https://blog.csdn.net/u012324798/article/details/103940527
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@ -19,13 +16,15 @@ from motor.motor_asyncio import AsyncIOMotorDatabase
|
|||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from application.settings import STATIC_ROOT, SUBSCRIBE
|
from application.settings import STATIC_ROOT, SUBSCRIBE, REDIS_DB_ENABLE
|
||||||
|
from core.database import redis_getter
|
||||||
from core.mongo_manage import MongoManage
|
from core.mongo_manage import MongoManage
|
||||||
from utils.file.file_manage import FileManage
|
from utils.file.file_manage import FileManage
|
||||||
from . import models, schemas
|
from . import models, schemas
|
||||||
from core.crud import DalBase
|
from core.crud import DalBase
|
||||||
from core.exception import CustomException
|
from core.exception import CustomException
|
||||||
from utils import status
|
from utils import status
|
||||||
|
from fastapi import Request
|
||||||
|
|
||||||
|
|
||||||
class DictTypeDal(DalBase):
|
class DictTypeDal(DalBase):
|
||||||
@ -53,7 +52,7 @@ class DictTypeDal(DalBase):
|
|||||||
data[obj.dict_type] = [schemas.DictDetailsSimpleOut.model_validate(i).model_dump() for i in obj.details]
|
data[obj.dict_type] = [schemas.DictDetailsSimpleOut.model_validate(i).model_dump() for i in obj.details]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def get_select_datas(self):
|
async def get_select_datas(self) -> list:
|
||||||
"""获取选择数据,全部数据"""
|
"""获取选择数据,全部数据"""
|
||||||
sql = select(self.model)
|
sql = select(self.model)
|
||||||
queryset = await self.db.execute(sql)
|
queryset = await self.db.execute(sql)
|
||||||
@ -82,7 +81,7 @@ class SettingsDal(DalBase):
|
|||||||
result[data.config_key] = data.config_value
|
result[data.config_key] = data.config_value
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def update_datas(self, datas: dict, rd: Redis):
|
async def update_datas(self, datas: dict, request: Request) -> None:
|
||||||
"""
|
"""
|
||||||
更新系统配置信息
|
更新系统配置信息
|
||||||
|
|
||||||
@ -104,12 +103,13 @@ class SettingsDal(DalBase):
|
|||||||
sql = update(self.model).where(self.model.config_key == "web_ico").values(config_value=web_ico)
|
sql = update(self.model).where(self.model.config_key == "web_ico").values(config_value=web_ico)
|
||||||
await self.db.execute(sql)
|
await self.db.execute(sql)
|
||||||
else:
|
else:
|
||||||
sql = update(self.model).where(self.model.config_key == key).values(config_value=value)
|
sql = update(self.model).where(self.model.config_key == str(key)).values(config_value=value)
|
||||||
await self.db.execute(sql)
|
await self.db.execute(sql)
|
||||||
if "wx_server_app_id" in datas:
|
if "wx_server_app_id" in datas and REDIS_DB_ENABLE:
|
||||||
|
rd = redis_getter(request)
|
||||||
await rd.client().set("wx_server", json.dumps(datas))
|
await rd.client().set("wx_server", json.dumps(datas))
|
||||||
|
|
||||||
async def get_base_config(self):
|
async def get_base_config(self) -> dict:
|
||||||
"""
|
"""
|
||||||
获取系统基本信息
|
获取系统基本信息
|
||||||
"""
|
"""
|
||||||
@ -127,7 +127,7 @@ class SettingsTabDal(DalBase):
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
super(SettingsTabDal, self).__init__(db, models.VadminSystemSettingsTab, schemas.SettingsTabSimpleOut)
|
super(SettingsTabDal, self).__init__(db, models.VadminSystemSettingsTab, schemas.SettingsTabSimpleOut)
|
||||||
|
|
||||||
async def get_classify_tab_values(self, classify: list[str], hidden: bool | None = False):
|
async def get_classify_tab_values(self, classify: list[str], hidden: bool | None = False) -> dict:
|
||||||
"""
|
"""
|
||||||
获取系统配置分类下的标签信息
|
获取系统配置分类下的标签信息
|
||||||
"""
|
"""
|
||||||
@ -143,7 +143,7 @@ class SettingsTabDal(DalBase):
|
|||||||
)
|
)
|
||||||
return self.__generate_values(datas)
|
return self.__generate_values(datas)
|
||||||
|
|
||||||
async def get_tab_name_values(self, tab_names: list[str], hidden: bool | None = False):
|
async def get_tab_name_values(self, tab_names: list[str], hidden: bool | None = False) -> dict:
|
||||||
"""
|
"""
|
||||||
获取系统配置标签下的标签信息
|
获取系统配置标签下的标签信息
|
||||||
"""
|
"""
|
||||||
@ -160,7 +160,7 @@ class SettingsTabDal(DalBase):
|
|||||||
return self.__generate_values(datas)
|
return self.__generate_values(datas)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __generate_values(cls, datas: list[models.VadminSystemSettingsTab]):
|
def __generate_values(cls, datas: list[models.VadminSystemSettingsTab]) -> dict:
|
||||||
"""
|
"""
|
||||||
生成字典值
|
生成字典值
|
||||||
"""
|
"""
|
||||||
@ -281,7 +281,7 @@ class TaskDal(MongoManage):
|
|||||||
v_order: str = None,
|
v_order: str = None,
|
||||||
v_order_field: str = None,
|
v_order_field: str = None,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
) -> tuple:
|
||||||
"""
|
"""
|
||||||
获取任务信息列表
|
获取任务信息列表
|
||||||
|
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
from .dict import VadminDictType, VadminDictDetails
|
from .dict import VadminDictType, VadminDictDetails
|
||||||
from .settings import VadminSystemSettings
|
from .settings import VadminSystemSettings, VadminSystemSettingsTab
|
||||||
from .settings_tab import VadminSystemSettingsTab
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 系统字典模型
|
# @desc : 系统字典模型
|
||||||
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, Boolean, ForeignKey, Integer
|
from sqlalchemy import Column, String, Boolean, ForeignKey, Integer
|
||||||
|
|
||||||
@ -15,22 +15,26 @@ class VadminDictType(BaseModel):
|
|||||||
__tablename__ = "vadmin_system_dict_type"
|
__tablename__ = "vadmin_system_dict_type"
|
||||||
__table_args__ = ({'comment': '字典类型表'})
|
__table_args__ = ({'comment': '字典类型表'})
|
||||||
|
|
||||||
dict_name = Column(String(50), index=True, nullable=False, comment="字典名称")
|
dict_name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典名称")
|
||||||
dict_type = Column(String(50), index=True, nullable=False, comment="字典类型")
|
dict_type: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典类型")
|
||||||
disabled = Column(Boolean, default=False, comment="字典状态,是否禁用")
|
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="字典状态,是否禁用")
|
||||||
remark = Column(String(255), comment="备注")
|
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|
||||||
details = relationship("VadminDictDetails", back_populates="dict_type")
|
details: Mapped[list["VadminDictDetails"]] = relationship(back_populates="dict_type")
|
||||||
|
|
||||||
|
|
||||||
class VadminDictDetails(BaseModel):
|
class VadminDictDetails(BaseModel):
|
||||||
__tablename__ = "vadmin_system_dict_details"
|
__tablename__ = "vadmin_system_dict_details"
|
||||||
__table_args__ = ({'comment': '字典详情表'})
|
__table_args__ = ({'comment': '字典详情表'})
|
||||||
|
|
||||||
label = Column(String(50), index=True, nullable=False, comment="字典标签")
|
label: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典标签")
|
||||||
value = Column(String(50), index=True, nullable=False, comment="字典键值")
|
value: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典键值")
|
||||||
disabled = Column(Boolean, default=False, comment="字典状态,是否禁用")
|
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="字典状态,是否禁用")
|
||||||
is_default = Column(Boolean, default=False, comment="是否默认")
|
is_default: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否默认")
|
||||||
order = Column(Integer, comment="字典排序")
|
order: Mapped[int] = mapped_column(Integer, comment="字典排序")
|
||||||
dict_type_id = Column(Integer, ForeignKey("vadmin_system_dict_type.id", ondelete='CASCADE'), comment="关联字典类型")
|
dict_type_id: Mapped[int] = mapped_column(
|
||||||
dict_type = relationship("VadminDictType", foreign_keys=dict_type_id, back_populates="details")
|
Integer,
|
||||||
remark = Column(String(255), comment="备注")
|
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="备注")
|
||||||
|
@ -5,20 +5,38 @@
|
|||||||
# @File : settings.py
|
# @File : settings.py
|
||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 系统字典模型
|
# @desc : 系统字典模型
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import Column, String, TEXT, Integer, ForeignKey, Boolean
|
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):
|
class VadminSystemSettings(BaseModel):
|
||||||
__tablename__ = "vadmin_system_settings"
|
__tablename__ = "vadmin_system_settings"
|
||||||
__table_args__ = ({'comment': '系统配置表'})
|
__table_args__ = ({'comment': '系统配置表'})
|
||||||
|
|
||||||
config_label = Column(String(255), comment="配置表标签")
|
config_label: Mapped[str] = mapped_column(String(255), comment="配置表标签")
|
||||||
config_key = Column(String(255), index=True, nullable=False, unique=True, comment="配置表键")
|
config_key: Mapped[str] = mapped_column(String(255), index=True, nullable=False, unique=True, comment="配置表键")
|
||||||
config_value = Column(TEXT, comment="配置表内容")
|
config_value: Mapped[str | None] = mapped_column(Text, comment="配置表内容")
|
||||||
remark = Column(String(255), comment="备注信息")
|
remark: Mapped[str | None] = mapped_column(String(255), comment="备注信息")
|
||||||
disabled = Column(Boolean, default=False, comment="是否禁用")
|
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||||
|
|
||||||
tab_id = Column(Integer, ForeignKey("vadmin_system_settings_tab.id", ondelete='CASCADE'), comment="关联tab标签")
|
tab_id: Mapped[int] = mapped_column(
|
||||||
tab = relationship("VadminSystemSettingsTab", foreign_keys=tab_id, back_populates="settings")
|
Integer,
|
||||||
|
ForeignKey("vadmin_system_settings_tab.id", ondelete='CASCADE'),
|
||||||
|
comment="关联tab标签"
|
||||||
|
)
|
||||||
|
tab: Mapped[VadminSystemSettingsTab] = relationship(foreign_keys=tab_id, back_populates="settings")
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# @version : 1.0
|
|
||||||
# @Create Time : 2022/7/7 13:41
|
|
||||||
# @File : settings_tab.py
|
|
||||||
# @IDE : PyCharm
|
|
||||||
# @desc : 系统配置分类模型
|
|
||||||
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from db.db_base import BaseModel
|
|
||||||
from sqlalchemy import Column, String, Boolean
|
|
||||||
|
|
||||||
|
|
||||||
class VadminSystemSettingsTab(BaseModel):
|
|
||||||
__tablename__ = "vadmin_system_settings_tab"
|
|
||||||
__table_args__ = ({'comment': '系统配置分类表'})
|
|
||||||
|
|
||||||
title = Column(String(255), comment="标题")
|
|
||||||
classify = Column(String(255), index=True, nullable=False, comment="分类键")
|
|
||||||
tab_label = Column(String(255), comment="tab标题")
|
|
||||||
tab_name = Column(String(255), index=True, nullable=False, unique=True, comment="tab标识符")
|
|
||||||
hidden = Column(Boolean, default=False, comment="是否隐藏")
|
|
||||||
disabled = Column(Boolean, default=False, comment="是否禁用")
|
|
||||||
|
|
||||||
settings = relationship("VadminSystemSettings", back_populates="tab")
|
|
@ -5,9 +5,8 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 主要接口文件
|
# @desc : 主要接口文件
|
||||||
|
|
||||||
# UploadFile 库依赖:pip install python-multipart
|
|
||||||
from aioredis import Redis
|
from aioredis import Redis
|
||||||
from fastapi import APIRouter, Depends, Body, UploadFile, Form
|
from fastapi import APIRouter, Depends, Body, UploadFile, Form, Request
|
||||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from application.settings import ALIYUN_OSS
|
from application.settings import ALIYUN_OSS
|
||||||
@ -21,7 +20,7 @@ from core.dependencies import IdList
|
|||||||
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth
|
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth
|
||||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||||
from .params import DictTypeParams, DictDetailParams, TaskParams
|
from .params import DictTypeParams, DictDetailParams, TaskParams
|
||||||
from apps.vadmin.auth import crud as vadminAuthCRUD
|
from apps.vadmin.auth import crud as vadmin_auth_crud
|
||||||
from .params.task import TaskRecordParams
|
from .params.task import TaskRecordParams
|
||||||
|
|
||||||
app = APIRouter()
|
app = APIRouter()
|
||||||
@ -32,8 +31,7 @@ app = APIRouter()
|
|||||||
###########################################################
|
###########################################################
|
||||||
@app.get("/dict/types", summary="获取字典类型列表")
|
@app.get("/dict/types", summary="获取字典类型列表")
|
||||||
async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||||
datas = await crud.DictTypeDal(auth.db).get_datas(**p.dict())
|
datas, count = await crud.DictTypeDal(auth.db).get_datas(**p.dict(), v_return_count=True)
|
||||||
count = await crud.DictTypeDal(auth.db).get_count(**p.to_count())
|
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +68,7 @@ async def put_dict_types(data_id: int, data: schemas.DictType, auth: Auth = Depe
|
|||||||
@app.get("/dict/types/{data_id}", summary="获取字典类型详细")
|
@app.get("/dict/types/{data_id}", summary="获取字典类型详细")
|
||||||
async def get_dict_type(data_id: int, auth: Auth = Depends(AllUserAuth())):
|
async def get_dict_type(data_id: int, auth: Auth = Depends(AllUserAuth())):
|
||||||
schema = schemas.DictTypeSimpleOut
|
schema = schemas.DictTypeSimpleOut
|
||||||
return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, None, v_schema=schema))
|
return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, v_schema=schema))
|
||||||
|
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
@ -85,8 +83,7 @@ async def create_dict_details(data: schemas.DictDetails, auth: Auth = Depends(Al
|
|||||||
async def get_dict_details(params: DictDetailParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
async def get_dict_details(params: DictDetailParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||||
if not params.dict_type_id:
|
if not params.dict_type_id:
|
||||||
return ErrorResponse(msg="未获取到字典类型!")
|
return ErrorResponse(msg="未获取到字典类型!")
|
||||||
datas = await crud.DictDetailsDal(auth.db).get_datas(**params.dict())
|
datas, count = await crud.DictDetailsDal(auth.db).get_datas(**params.dict(), v_return_count=True)
|
||||||
count = await crud.DictDetailsDal(auth.db).get_count(**params.to_count())
|
|
||||||
return SuccessResponse(datas, count=count)
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
@ -104,7 +101,7 @@ async def put_dict_details(data_id: int, data: schemas.DictDetails, auth: Auth =
|
|||||||
@app.get("/dict/details/{data_id}", summary="获取字典元素详情")
|
@app.get("/dict/details/{data_id}", summary="获取字典元素详情")
|
||||||
async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())):
|
async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())):
|
||||||
schema = schemas.DictDetailsSimpleOut
|
schema = schemas.DictDetailsSimpleOut
|
||||||
return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, None, v_schema=schema))
|
return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, v_schema=schema))
|
||||||
|
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
@ -140,7 +137,7 @@ async def upload_image_to_local(file: UploadFile, path: str = Form(...)):
|
|||||||
###########################################################
|
###########################################################
|
||||||
@app.post("/sms/send", summary="发送短信验证码(阿里云服务)")
|
@app.post("/sms/send", summary="发送短信验证码(阿里云服务)")
|
||||||
async def sms_send(telephone: str, rd: Redis = Depends(redis_getter), auth: Auth = Depends(OpenAuth())):
|
async def sms_send(telephone: str, rd: Redis = Depends(redis_getter), auth: Auth = Depends(OpenAuth())):
|
||||||
user = await vadminAuthCRUD.UserDal(auth.db).get_data(telephone=telephone, v_return_none=True)
|
user = await vadmin_auth_crud.UserDal(auth.db).get_data(telephone=telephone, v_return_none=True)
|
||||||
if not user:
|
if not user:
|
||||||
return ErrorResponse("手机号不存在!")
|
return ErrorResponse("手机号不存在!")
|
||||||
sms = CodeSMS(telephone, rd)
|
sms = CodeSMS(telephone, rd)
|
||||||
@ -162,11 +159,11 @@ async def get_settings_tabs_values(tab_id: int, auth: Auth = Depends(FullAdminAu
|
|||||||
|
|
||||||
@app.put("/settings/tabs/values", summary="更新系统配置信息")
|
@app.put("/settings/tabs/values", summary="更新系统配置信息")
|
||||||
async def put_settings_tabs_values(
|
async def put_settings_tabs_values(
|
||||||
|
request: Request,
|
||||||
datas: dict = Body(...),
|
datas: dict = Body(...),
|
||||||
auth: Auth = Depends(FullAdminAuth()),
|
auth: Auth = Depends(FullAdminAuth())
|
||||||
rd: Redis = Depends(redis_getter)
|
|
||||||
):
|
):
|
||||||
return SuccessResponse(await crud.SettingsDal(auth.db).update_datas(datas, rd))
|
return SuccessResponse(await crud.SettingsDal(auth.db).update_datas(datas, request))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/settings/base/config", summary="获取系统基础配置", description="每次进入系统中时使用")
|
@app.get("/settings/base/config", summary="获取系统基础配置", description="每次进入系统中时使用")
|
||||||
|
@ -1,33 +1,26 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# @version : 1.0
|
# @version : 1.0
|
||||||
# @Create Time : 2021/10/18 22:18
|
# @Update Time : 2023/8/21 22:18
|
||||||
# @File : crud.py
|
# @File : crud.py
|
||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 数据库 增删改查操作
|
# @desc : 数据库 增删改查操作
|
||||||
|
|
||||||
# sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008
|
# sqlalchemy 官方文档:https://docs.sqlalchemy.org/en/20/index.html
|
||||||
# sqlalchemy 查询操作(官方文档): https://www.osgeo.cn/sqlalchemy/orm/queryguide.html
|
# sqlalchemy 查询操作(官方文档): https://docs.sqlalchemy.org/en/20/orm/queryguide/select.html
|
||||||
# sqlalchemy 增删改操作:https://www.osgeo.cn/sqlalchemy/tutorial/orm_data_manipulation.html#updating-orm-objects
|
# sqlalchemy 增删改操作:https://docs.sqlalchemy.org/en/20/orm/queryguide/dml.html
|
||||||
# SQLAlchemy lazy load和eager load: https://www.jianshu.com/p/dfad7c08c57a
|
# sqlalchemy 1.x 语法迁移到 2.x :https://docs.sqlalchemy.org/en/20/changelog/migration_20.html#migration-20-query-usage
|
||||||
# Mysql中内连接,左连接和右连接的区别总结:https://www.cnblogs.com/restartyang/articles/9080993.html
|
|
||||||
# SQLAlchemy INNER JOIN 内连接
|
|
||||||
# selectinload 官方文档:
|
|
||||||
# https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.selectinload
|
|
||||||
# SQLAlchemy LEFT OUTER JOIN 左连接
|
|
||||||
# joinedload 官方文档:
|
|
||||||
# https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.joinedload
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Set
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from sqlalchemy import func, delete, update, or_
|
from sqlalchemy import func, delete, update, BinaryExpression, ScalarResult, select
|
||||||
from sqlalchemy.future import select
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm import InstrumentedAttribute
|
||||||
|
from sqlalchemy.orm.strategy_options import _AbstractLoad
|
||||||
from starlette import status
|
from starlette import status
|
||||||
from core.exception import CustomException
|
from core.exception import CustomException
|
||||||
from sqlalchemy.sql.selectable import Select
|
from sqlalchemy.sql.selectable import Select as SelectType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
@ -35,124 +28,200 @@ class DalBase:
|
|||||||
# 倒叙
|
# 倒叙
|
||||||
ORDER_FIELD = ["desc", "descending"]
|
ORDER_FIELD = ["desc", "descending"]
|
||||||
|
|
||||||
def __init__(self, db: AsyncSession, model: Any, schema: Any, key_models: dict = None):
|
def __init__(self, db: AsyncSession, model: Any, schema: Any):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.model = model
|
self.model = model
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
self.key_models = key_models
|
|
||||||
|
|
||||||
async def get_data(
|
async def get_data(
|
||||||
self,
|
self,
|
||||||
data_id: int = None,
|
data_id: int = None,
|
||||||
v_options: list = None,
|
v_start_sql: SelectType = None,
|
||||||
v_join_query: dict = None,
|
v_select_from: list[Any] = None,
|
||||||
v_or: list[tuple] = None,
|
v_join: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_outerjoin: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_options: list[_AbstractLoad] = None,
|
||||||
|
v_where: list[BinaryExpression] = None,
|
||||||
v_order: str = None,
|
v_order: str = None,
|
||||||
v_order_field: str = None,
|
v_order_field: str = None,
|
||||||
v_return_none: bool = False,
|
v_return_none: bool = False,
|
||||||
v_schema: Any = None,
|
v_schema: Any = None,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
获取单个数据,默认使用 ID 查询,否则使用关键词查询
|
获取单个数据,默认使用 ID 查询,否则使用关键词查询
|
||||||
|
|
||||||
:param data_id: 数据 ID
|
:param data_id: 数据 ID
|
||||||
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
:param v_start_sql: 初始 sql
|
||||||
:param v_join_query: 外键字段查询,内连接
|
:param v_select_from: 用于指定查询从哪个表开始,通常与 .join() 等方法一起使用。
|
||||||
:param v_or: 或逻辑查询
|
:param v_join: 创建内连接(INNER JOIN)操作,返回两个表中满足连接条件的交集。
|
||||||
|
:param v_outerjoin: 用于创建外连接(OUTER JOIN)操作,返回两个表中满足连接条件的并集,包括未匹配的行,并用 NULL 值填充。
|
||||||
|
:param v_options: 用于为查询添加附加选项,如预加载、延迟加载等。
|
||||||
|
:param v_where: 当前表查询条件,原始表达式
|
||||||
:param v_order: 排序,默认正序,为 desc 是倒叙
|
:param v_order: 排序,默认正序,为 desc 是倒叙
|
||||||
:param v_order_field: 排序字段
|
:param v_order_field: 排序字段
|
||||||
:param v_return_none: 是否返回空 None,否认 抛出异常,默认抛出异常
|
:param v_return_none: 是否返回空 None,否认 抛出异常,默认抛出异常
|
||||||
:param v_schema: 指定使用的序列化对象
|
:param v_schema: 指定使用的序列化对象
|
||||||
:param kwargs: 查询参数
|
:param kwargs: 查询参数
|
||||||
|
:return: 默认返回 ORM 对象,如果存在 v_schema 则会返回 v_schema 结果
|
||||||
"""
|
"""
|
||||||
sql = select(self.model).where(self.model.is_delete == False)
|
if not isinstance(v_start_sql, SelectType):
|
||||||
|
v_start_sql = select(self.model).where(self.model.is_delete == False)
|
||||||
|
|
||||||
if data_id:
|
if data_id:
|
||||||
sql = sql.where(self.model.id == data_id)
|
v_start_sql = v_start_sql.where(self.model.id == data_id)
|
||||||
sql = self.add_filter_condition(sql, v_options, v_join_query, v_or, **kwargs)
|
|
||||||
if v_order_field and (v_order in self.ORDER_FIELD):
|
queryset: ScalarResult = await self.filter_core(
|
||||||
sql = sql.order_by(getattr(self.model, v_order_field).desc(), self.model.id.desc())
|
v_start_sql=v_start_sql,
|
||||||
elif v_order_field:
|
v_select_from=v_select_from,
|
||||||
sql = sql.order_by(getattr(self.model, v_order_field), self.model.id)
|
v_join=v_join,
|
||||||
elif v_order and (v_order in self.ORDER_FIELD):
|
v_outerjoin=v_outerjoin,
|
||||||
sql = sql.order_by(self.model.create_datetime.desc())
|
v_options=v_options,
|
||||||
queryset = await self.db.execute(sql)
|
v_where=v_where,
|
||||||
data = queryset.scalars().unique().first()
|
v_order=v_order,
|
||||||
|
v_order_field=v_order_field,
|
||||||
|
v_return_sql=False,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
if v_options:
|
||||||
|
data = queryset.unique().first()
|
||||||
|
else:
|
||||||
|
data = queryset.first()
|
||||||
|
|
||||||
if not data and v_return_none:
|
if not data and v_return_none:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if data and v_schema:
|
if data and v_schema:
|
||||||
return v_schema.model_validate(data).model_dump()
|
return v_schema.model_validate(data).model_dump()
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未找到此数据")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未找到此数据")
|
||||||
|
|
||||||
async def get_datas(
|
async def get_datas(
|
||||||
self,
|
self,
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
v_options: list = None,
|
v_start_sql: SelectType = None,
|
||||||
v_join_query: dict = None,
|
v_select_from: list[Any] = None,
|
||||||
v_or: list[tuple] = None,
|
v_join: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_outerjoin: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_options: list[_AbstractLoad] = None,
|
||||||
|
v_where: list[BinaryExpression] = None,
|
||||||
v_order: str = None,
|
v_order: str = None,
|
||||||
v_order_field: str = None,
|
v_order_field: str = None,
|
||||||
|
v_return_count: bool = False,
|
||||||
|
v_return_scalars: bool = False,
|
||||||
v_return_objs: bool = False,
|
v_return_objs: bool = False,
|
||||||
v_start_sql: Any = None,
|
|
||||||
v_schema: Any = None,
|
v_schema: Any = None,
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> list:
|
) -> list[Any] | ScalarResult | tuple:
|
||||||
"""
|
"""
|
||||||
获取数据列表
|
获取数据列表
|
||||||
|
|
||||||
:param page: 页码
|
:param page: 页码
|
||||||
:param limit: 当前页数据量
|
:param limit: 当前页数据量
|
||||||
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
:param v_start_sql: 初始 sql
|
||||||
:param v_join_query: 外键字段查询
|
:param v_select_from: 用于指定查询从哪个表开始,通常与 .join() 等方法一起使用。
|
||||||
:param v_or: 或逻辑查询
|
:param v_join: 创建内连接(INNER JOIN)操作,返回两个表中满足连接条件的交集。
|
||||||
|
:param v_outerjoin: 用于创建外连接(OUTER JOIN)操作,返回两个表中满足连接条件的并集,包括未匹配的行,并用 NULL 值填充。
|
||||||
|
:param v_options: 用于为查询添加附加选项,如预加载、延迟加载等。
|
||||||
|
:param v_where: 当前表查询条件,原始表达式
|
||||||
:param v_order: 排序,默认正序,为 desc 是倒叙
|
:param v_order: 排序,默认正序,为 desc 是倒叙
|
||||||
:param v_order_field: 排序字段
|
:param v_order_field: 排序字段
|
||||||
|
:param v_return_count: 默认为 False,是否返回 count 过滤后的数据总数,不会影响其他返回结果,会一起返回为一个数组
|
||||||
|
:param v_return_scalars: 返回scalars后的结果
|
||||||
:param v_return_objs: 是否返回对象
|
:param v_return_objs: 是否返回对象
|
||||||
:param v_start_sql: 初始 sql
|
|
||||||
:param v_schema: 指定使用的序列化对象
|
:param v_schema: 指定使用的序列化对象
|
||||||
:param kwargs: 查询参数
|
:param kwargs: 查询参数,使用的是自定义表达式
|
||||||
|
:return: 返回值优先级:v_return_scalars > v_return_objs > v_schema
|
||||||
"""
|
"""
|
||||||
if not isinstance(v_start_sql, Select):
|
sql: SelectType = await self.filter_core(
|
||||||
v_start_sql = select(self.model).where(self.model.is_delete == False)
|
v_start_sql=v_start_sql,
|
||||||
sql = self.add_filter_condition(v_start_sql, v_options, v_join_query, v_or, **kwargs)
|
v_select_from=v_select_from,
|
||||||
if v_order_field and (v_order in self.ORDER_FIELD):
|
v_join=v_join,
|
||||||
sql = sql.order_by(getattr(self.model, v_order_field).desc(), self.model.id.desc())
|
v_outerjoin=v_outerjoin,
|
||||||
elif v_order_field:
|
v_options=v_options,
|
||||||
sql = sql.order_by(getattr(self.model, v_order_field), self.model.id)
|
v_where=v_where,
|
||||||
elif v_order in self.ORDER_FIELD:
|
v_order=v_order,
|
||||||
sql = sql.order_by(self.model.id.desc())
|
v_order_field=v_order_field,
|
||||||
|
v_return_sql=True,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
if v_return_count:
|
||||||
|
count_sql = select(func.count()).select_from(sql.alias())
|
||||||
|
count_queryset = await self.db.execute(count_sql)
|
||||||
|
count = count_queryset.one()[0]
|
||||||
|
|
||||||
if limit != 0:
|
if limit != 0:
|
||||||
sql = sql.offset((page - 1) * limit).limit(limit)
|
sql = sql.offset((page - 1) * limit).limit(limit)
|
||||||
queryset = await self.db.execute(sql)
|
|
||||||
|
queryset = await self.db.scalars(sql)
|
||||||
|
|
||||||
|
if v_return_scalars:
|
||||||
|
if v_return_count:
|
||||||
|
return queryset, count
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
if v_options:
|
||||||
|
result = queryset.unique().all()
|
||||||
|
else:
|
||||||
|
result = queryset.all()
|
||||||
|
|
||||||
if v_return_objs:
|
if v_return_objs:
|
||||||
return queryset.scalars().unique().all()
|
if v_return_count:
|
||||||
return [await self.out_dict(i, v_schema=v_schema) for i in queryset.scalars().unique().all()]
|
return list(result), count
|
||||||
|
return list(result)
|
||||||
|
|
||||||
|
datas = [await self.out_dict(i, v_schema=v_schema) for i in result]
|
||||||
|
if v_return_count:
|
||||||
|
return datas, count
|
||||||
|
return datas
|
||||||
|
|
||||||
async def get_count(
|
async def get_count(
|
||||||
self,
|
self,
|
||||||
v_options: list = None,
|
v_select_from: list[Any] = None,
|
||||||
v_join_query: dict = None,
|
v_join: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
v_or: list[tuple] = None,
|
v_outerjoin: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_where: list[BinaryExpression] = None,
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
获取数据总数
|
获取数据总数
|
||||||
|
|
||||||
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
:param v_select_from: 用于指定查询从哪个表开始,通常与 .join() 等方法一起使用。
|
||||||
:param v_join_query: 外键字段查询
|
:param v_join: 创建内连接(INNER JOIN)操作,返回两个表中满足连接条件的交集。
|
||||||
:param v_or: 或逻辑查询
|
:param v_outerjoin: 用于创建外连接(OUTER JOIN)操作,返回两个表中满足连接条件的并集,包括未匹配的行,并用 NULL 值填充。
|
||||||
|
:param v_where: 当前表查询条件,原始表达式
|
||||||
:param kwargs: 查询参数
|
:param kwargs: 查询参数
|
||||||
"""
|
"""
|
||||||
sql = select(func.count(self.model.id).label('total')).where(self.model.is_delete == False)
|
v_start_sql = select(func.count(self.model.id))
|
||||||
sql = self.add_filter_condition(sql, v_options, v_join_query, v_or, **kwargs)
|
sql = await self.filter_core(
|
||||||
|
v_start_sql=v_start_sql,
|
||||||
|
v_select_from=v_select_from,
|
||||||
|
v_join=v_join,
|
||||||
|
v_outerjoin=v_outerjoin,
|
||||||
|
v_where=v_where,
|
||||||
|
v_return_sql=True,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
queryset = await self.db.execute(sql)
|
queryset = await self.db.execute(sql)
|
||||||
return queryset.one()['total']
|
return queryset.one()[0]
|
||||||
|
|
||||||
async def create_data(self, data, v_options: list = None, v_return_obj: bool = False, v_schema: Any = None):
|
async def create_data(
|
||||||
|
self,
|
||||||
|
data,
|
||||||
|
v_options: list[_AbstractLoad] = None,
|
||||||
|
v_return_obj: bool = False,
|
||||||
|
v_schema: Any = None
|
||||||
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
创建数据
|
创建单个数据
|
||||||
|
|
||||||
:param data: 创建数据
|
:param data: 创建数据
|
||||||
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
||||||
:param v_schema: ,指定使用的序列化对象
|
:param v_schema: ,指定使用的序列化对象
|
||||||
@ -165,14 +234,25 @@ class DalBase:
|
|||||||
await self.flush(obj)
|
await self.flush(obj)
|
||||||
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
|
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
|
||||||
|
|
||||||
|
# async def create_datas(self, datas: list[dict]) -> None:
|
||||||
|
# """
|
||||||
|
# 批量创建数据,暂不启用
|
||||||
|
# SQLAlchemy 2.0 批量插入不支持 MySQL 返回对象:
|
||||||
|
# https://docs.sqlalchemy.org/en/20/orm/queryguide/dml.html#getting-new-objects-with-returning
|
||||||
|
#
|
||||||
|
# :param datas: 字典数据列表
|
||||||
|
# """
|
||||||
|
# await self.db.execute(insert(self.model), datas)
|
||||||
|
# await self.db.flush()
|
||||||
|
|
||||||
async def put_data(
|
async def put_data(
|
||||||
self,
|
self,
|
||||||
data_id: int,
|
data_id: int,
|
||||||
data: Any,
|
data: Any,
|
||||||
v_options: list = None,
|
v_options: list[_AbstractLoad] = None,
|
||||||
v_return_obj: bool = False,
|
v_return_obj: bool = False,
|
||||||
v_schema: Any = None
|
v_schema: Any = None
|
||||||
):
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
更新单个数据
|
更新单个数据
|
||||||
:param data_id: 修改行数据的 ID
|
:param data_id: 修改行数据的 ID
|
||||||
@ -188,7 +268,7 @@ class DalBase:
|
|||||||
await self.flush(obj)
|
await self.flush(obj)
|
||||||
return await self.out_dict(obj, None, v_return_obj, v_schema)
|
return await self.out_dict(obj, None, v_return_obj, v_schema)
|
||||||
|
|
||||||
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs):
|
async def delete_datas(self, ids: list[int], v_soft: bool = False, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
删除多条数据
|
删除多条数据
|
||||||
:param ids: 数据集
|
:param ids: 数据集
|
||||||
@ -207,100 +287,152 @@ class DalBase:
|
|||||||
await self.db.execute(delete(self.model).where(self.model.id.in_(ids)))
|
await self.db.execute(delete(self.model).where(self.model.id.in_(ids)))
|
||||||
await self.flush()
|
await self.flush()
|
||||||
|
|
||||||
def add_filter_condition(
|
async def flush(self, obj: Any = None) -> Any:
|
||||||
self,
|
|
||||||
sql: select,
|
|
||||||
v_options: list = None,
|
|
||||||
v_join_query: dict = None,
|
|
||||||
v_or: list[tuple] = None,
|
|
||||||
**kwargs
|
|
||||||
) -> select:
|
|
||||||
"""
|
"""
|
||||||
添加过滤条件,以及内连接过滤条件
|
刷新到数据库
|
||||||
|
"""
|
||||||
|
if obj:
|
||||||
|
self.db.add(obj)
|
||||||
|
await self.db.flush()
|
||||||
|
if obj:
|
||||||
|
await self.db.refresh(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
当外键模型在查询模型中存在多个外键时,则需要添加onclause属性
|
async def out_dict(
|
||||||
:param sql:
|
self,
|
||||||
|
obj: Any,
|
||||||
|
v_options: list[_AbstractLoad] = None,
|
||||||
|
v_return_obj: bool = False,
|
||||||
|
v_schema: Any = None
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
序列化
|
||||||
|
:param obj:
|
||||||
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
||||||
:param v_join_query: 外键字段查询,内连接
|
:param v_return_obj: ,是否返回对象
|
||||||
:param v_or: 或逻辑
|
:param v_schema: ,指定使用的序列化对象
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if v_options:
|
||||||
|
obj = await self.get_data(obj.id, v_options=v_options)
|
||||||
|
if v_return_obj:
|
||||||
|
return obj
|
||||||
|
if v_schema:
|
||||||
|
return v_schema.model_validate(obj).model_dump()
|
||||||
|
return self.schema.model_validate(obj).model_dump()
|
||||||
|
|
||||||
|
async def filter_core(
|
||||||
|
self,
|
||||||
|
v_start_sql: SelectType = None,
|
||||||
|
v_select_from: list[Any] = None,
|
||||||
|
v_join: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_outerjoin: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_options: list[_AbstractLoad] = None,
|
||||||
|
v_where: list[BinaryExpression] = None,
|
||||||
|
v_order: str = None,
|
||||||
|
v_order_field: str = None,
|
||||||
|
v_return_sql: bool = False,
|
||||||
|
**kwargs
|
||||||
|
) -> ScalarResult | SelectType:
|
||||||
|
"""
|
||||||
|
数据过滤核心功能
|
||||||
|
|
||||||
|
:param v_start_sql: 初始 sql
|
||||||
|
:param v_select_from: 用于指定查询从哪个表开始,通常与 .join() 等方法一起使用。
|
||||||
|
:param v_join: 创建内连接(INNER JOIN)操作,返回两个表中满足连接条件的交集。
|
||||||
|
:param v_outerjoin: 用于创建外连接(OUTER JOIN)操作,返回两个表中满足连接条件的并集,包括未匹配的行,并用 NULL 值填充。
|
||||||
|
:param v_options: 用于为查询添加附加选项,如预加载、延迟加载等。
|
||||||
|
:param v_where: 当前表查询条件,原始表达式
|
||||||
|
:param v_order: 排序,默认正序,为 desc 是倒叙
|
||||||
|
:param v_order_field: 排序字段
|
||||||
|
:param v_return_sql: 是否直接返回 sql
|
||||||
|
:return: 返回过滤后的总数居 或 sql
|
||||||
|
"""
|
||||||
|
if not isinstance(v_start_sql, SelectType):
|
||||||
|
v_start_sql = select(self.model).where(self.model.is_delete == False)
|
||||||
|
|
||||||
|
sql = self.add_relation(
|
||||||
|
v_start_sql=v_start_sql,
|
||||||
|
v_select_from=v_select_from,
|
||||||
|
v_join=v_join,
|
||||||
|
v_outerjoin=v_outerjoin,
|
||||||
|
v_options=v_options
|
||||||
|
)
|
||||||
|
|
||||||
|
if v_where:
|
||||||
|
sql = sql.where(*v_where)
|
||||||
|
|
||||||
|
sql = self.add_filter_condition(sql, **kwargs)
|
||||||
|
|
||||||
|
if v_order_field and (v_order in self.ORDER_FIELD):
|
||||||
|
sql = sql.order_by(getattr(self.model, v_order_field).desc(), self.model.id.desc())
|
||||||
|
elif v_order_field:
|
||||||
|
sql = sql.order_by(getattr(self.model, v_order_field), self.model.id)
|
||||||
|
elif v_order in self.ORDER_FIELD:
|
||||||
|
sql = sql.order_by(self.model.id.desc())
|
||||||
|
|
||||||
|
if v_return_sql:
|
||||||
|
return sql
|
||||||
|
|
||||||
|
queryset = await self.db.scalars(sql)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def add_relation(
|
||||||
|
self,
|
||||||
|
v_start_sql: SelectType,
|
||||||
|
v_select_from: list[Any] = None,
|
||||||
|
v_join: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_outerjoin: list[list[str | InstrumentedAttribute, BinaryExpression | None]] = None,
|
||||||
|
v_options: list[_AbstractLoad] = None,
|
||||||
|
) -> SelectType:
|
||||||
|
"""
|
||||||
|
:param v_start_sql: 初始 sql
|
||||||
|
:param v_select_from: 用于指定查询从哪个表开始,通常与 .join() 等方法一起使用。
|
||||||
|
:param v_join: 创建内连接(INNER JOIN)操作,返回两个表中满足连接条件的交集。
|
||||||
|
:param v_outerjoin: 用于创建外连接(OUTER JOIN)操作,返回两个表中满足连接条件的并集,包括未匹配的行,并用 NULL 值填充。
|
||||||
|
:param v_options: 用于为查询添加附加选项,如预加载、延迟加载等。
|
||||||
|
"""
|
||||||
|
if v_select_from:
|
||||||
|
v_start_sql = v_start_sql.select_from(*v_select_from)
|
||||||
|
|
||||||
|
if v_join:
|
||||||
|
for relation in v_join:
|
||||||
|
table = relation[0]
|
||||||
|
if isinstance(table, str):
|
||||||
|
table = getattr(self.model, table)
|
||||||
|
if len(relation) == 2:
|
||||||
|
v_start_sql = v_start_sql.join(table, relation[1])
|
||||||
|
else:
|
||||||
|
v_start_sql = v_start_sql.join(table)
|
||||||
|
|
||||||
|
if v_outerjoin:
|
||||||
|
for relation in v_outerjoin:
|
||||||
|
table = relation[0]
|
||||||
|
if isinstance(table, str):
|
||||||
|
table = getattr(self.model, table)
|
||||||
|
if len(relation) == 2:
|
||||||
|
v_start_sql = v_start_sql.outerjoin(table, relation[1])
|
||||||
|
else:
|
||||||
|
v_start_sql = v_start_sql.outerjoin(table)
|
||||||
|
|
||||||
|
if v_options:
|
||||||
|
v_start_sql = v_start_sql.options(*v_options)
|
||||||
|
|
||||||
|
return v_start_sql
|
||||||
|
|
||||||
|
def add_filter_condition(self, sql: SelectType, **kwargs) -> SelectType:
|
||||||
|
"""
|
||||||
|
添加过滤条件
|
||||||
|
:param sql:
|
||||||
:param kwargs: 关键词参数
|
:param kwargs: 关键词参数
|
||||||
"""
|
"""
|
||||||
v_select_from: Set[str] = set()
|
conditions = self.__dict_filter(**kwargs)
|
||||||
v_join: Set[str] = set()
|
|
||||||
v_join_left: Set[str] = set()
|
|
||||||
if v_join_query:
|
|
||||||
for key, value in v_join_query.items():
|
|
||||||
foreign_key = self.key_models.get(key)
|
|
||||||
conditions = self.__dict_filter(foreign_key.get("model"), **value)
|
|
||||||
if conditions:
|
|
||||||
sql = sql.where(*conditions)
|
|
||||||
v_join.add(key)
|
|
||||||
|
|
||||||
if v_or:
|
|
||||||
sql = self.__or_filter(sql, v_or, v_join_left, v_join)
|
|
||||||
|
|
||||||
sql = self.__generate_join_conditions(sql, v_join, "join", v_select_from)
|
|
||||||
sql = self.__generate_join_conditions(sql, v_join_left, "outerjoin", v_select_from)
|
|
||||||
|
|
||||||
# 多对多关系查询使用
|
|
||||||
for item in v_select_from:
|
|
||||||
sql = sql.select_from(item)
|
|
||||||
|
|
||||||
conditions = self.__dict_filter(self.model, **kwargs)
|
|
||||||
if conditions:
|
if conditions:
|
||||||
sql = sql.where(*conditions)
|
sql = sql.where(*conditions)
|
||||||
if v_options:
|
|
||||||
sql = sql.options(*[load for load in v_options])
|
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
def __generate_join_conditions(self, sql, model_keys: Set[str], join_type: str, v_select_from: []):
|
def __dict_filter(self, **kwargs) -> list[BinaryExpression]:
|
||||||
"""
|
|
||||||
生成 join 条件
|
|
||||||
"""
|
|
||||||
for item in model_keys:
|
|
||||||
foreign_key = self.key_models.get(item)
|
|
||||||
join = foreign_key.get("join", None)
|
|
||||||
model = foreign_key.get("model")
|
|
||||||
if join:
|
|
||||||
v_select_from.add(model)
|
|
||||||
model = join
|
|
||||||
if join_type == "join":
|
|
||||||
sql = sql.join(model, onclause=foreign_key.get("onclause"))
|
|
||||||
elif join_type == "outerjoin":
|
|
||||||
sql = sql.outerjoin(model, onclause=foreign_key.get("onclause"))
|
|
||||||
return sql
|
|
||||||
|
|
||||||
def __or_filter(self, sql: select, v_or: list[tuple], v_join_left: Set[str], v_join: Set[str]):
|
|
||||||
"""
|
|
||||||
或逻辑操作
|
|
||||||
:param sql:
|
|
||||||
:param v_or: 或逻辑
|
|
||||||
:param v_join_left: 左连接
|
|
||||||
:param v_join: 内连接
|
|
||||||
"""
|
|
||||||
or_list = []
|
|
||||||
for item in v_or:
|
|
||||||
if len(item) == 2:
|
|
||||||
model = self.model
|
|
||||||
condition = {item[0]: item[1]}
|
|
||||||
or_list.extend(self.__dict_filter(model, **condition))
|
|
||||||
elif len(item) == 4 and item[0] == "fk":
|
|
||||||
model = self.key_models.get(item[1]).get("model")
|
|
||||||
condition = {item[2]: item[3]}
|
|
||||||
conditions = self.__dict_filter(model, **condition)
|
|
||||||
if conditions:
|
|
||||||
or_list.extend(conditions)
|
|
||||||
v_join_left.add(item[1])
|
|
||||||
if item[1] in v_join:
|
|
||||||
v_join.remove(item[1])
|
|
||||||
else:
|
|
||||||
raise CustomException(msg="v_or 获取查询属性失败,语法错误!")
|
|
||||||
if or_list:
|
|
||||||
sql = sql.where(or_(i for i in or_list))
|
|
||||||
return sql
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __dict_filter(model, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
字典过滤
|
字典过滤
|
||||||
:param model:
|
:param model:
|
||||||
@ -309,7 +441,7 @@ class DalBase:
|
|||||||
conditions = []
|
conditions = []
|
||||||
for field, value in kwargs.items():
|
for field, value in kwargs.items():
|
||||||
if value is not None and value != "":
|
if value is not None and value != "":
|
||||||
attr = getattr(model, field)
|
attr = getattr(self.model, field)
|
||||||
if isinstance(value, tuple):
|
if isinstance(value, tuple):
|
||||||
if len(value) == 1:
|
if len(value) == 1:
|
||||||
if value[0] == "None":
|
if value[0] == "None":
|
||||||
@ -341,31 +473,3 @@ class DalBase:
|
|||||||
else:
|
else:
|
||||||
conditions.append(attr == value)
|
conditions.append(attr == value)
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
async def flush(self, obj: Any = None):
|
|
||||||
"""
|
|
||||||
刷新到数据库
|
|
||||||
"""
|
|
||||||
if obj:
|
|
||||||
self.db.add(obj)
|
|
||||||
await self.db.flush()
|
|
||||||
if obj:
|
|
||||||
await self.db.refresh(obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
async def out_dict(self, obj: Any, v_options: list = None, v_return_obj: bool = False, v_schema: Any = None):
|
|
||||||
"""
|
|
||||||
序列化
|
|
||||||
:param obj:
|
|
||||||
:param v_options: 指示应使用select在预加载中加载给定的属性。
|
|
||||||
:param v_return_obj: ,是否返回对象
|
|
||||||
:param v_schema: ,指定使用的序列化对象
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if v_options:
|
|
||||||
obj = await self.get_data(obj.id, v_options=v_options)
|
|
||||||
if v_return_obj:
|
|
||||||
return obj
|
|
||||||
if v_schema:
|
|
||||||
return v_schema.model_validate(obj).model_dump()
|
|
||||||
return self.schema.model_validate(obj).model_dump()
|
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# @version : 1.0
|
# @version : 1.0
|
||||||
# @Create Time : 2021/10/18 22:19
|
# @Update Time : 2023/8/18 9:00
|
||||||
# @File : database.py
|
# @File : database.py
|
||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : SQLAlchemy 部分
|
# @desc : SQLAlchemy 部分
|
||||||
|
|
||||||
"""
|
"""
|
||||||
导入 SQLAlchemy 部分
|
导入 SQLAlchemy 部分
|
||||||
安装: pip install sqlalchemy
|
安装: pip install sqlalchemy[asyncio]
|
||||||
中文文档:https://www.osgeo.cn/sqlalchemy/
|
官方文档:https://docs.sqlalchemy.org/en/20/intro.html#installation
|
||||||
"""
|
"""
|
||||||
|
from typing import AsyncGenerator
|
||||||
from aioredis import Redis
|
from aioredis import Redis
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker, AsyncAttrs
|
||||||
from sqlalchemy.ext.declarative import declared_attr, declarative_base
|
from sqlalchemy.orm import DeclarativeBase, declared_attr
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
from application.settings import SQLALCHEMY_DATABASE_URL, REDIS_DB_ENABLE, MONGO_DB_ENABLE
|
from application.settings import SQLALCHEMY_DATABASE_URL, REDIS_DB_ENABLE, MONGO_DB_ENABLE
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from core.exception import CustomException
|
from core.exception import CustomException
|
||||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||||
|
|
||||||
|
|
||||||
def create_async_engine_session(database_url: str):
|
def create_async_engine_session(database_url: str) -> async_sessionmaker[AsyncSession]:
|
||||||
"""
|
"""
|
||||||
创建数据库会话
|
创建数据库会话
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ def create_async_engine_session(database_url: str):
|
|||||||
:param database_url: 数据库地址
|
:param database_url: 数据库地址
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
engine = create_async_engine(
|
async_engine = create_async_engine(
|
||||||
database_url,
|
database_url,
|
||||||
echo=False,
|
echo=False,
|
||||||
pool_pre_ping=True,
|
pool_pre_ping=True,
|
||||||
@ -44,18 +44,30 @@ def create_async_engine_session(database_url: str):
|
|||||||
max_overflow=5,
|
max_overflow=5,
|
||||||
connect_args={}
|
connect_args={}
|
||||||
)
|
)
|
||||||
return sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=True, class_=AsyncSession)
|
return async_sessionmaker(
|
||||||
|
autocommit=False,
|
||||||
|
autoflush=False,
|
||||||
|
bind=async_engine,
|
||||||
|
expire_on_commit=True,
|
||||||
|
class_=AsyncSession
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Base:
|
class Base(AsyncAttrs, DeclarativeBase):
|
||||||
"""将表名改为小写"""
|
"""
|
||||||
|
创建基本映射类
|
||||||
|
稍后,我们将继承该类,创建每个 ORM 模型
|
||||||
|
"""
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr.directive
|
||||||
def __tablename__(self):
|
def __tablename__(cls) -> str:
|
||||||
# 如果有自定义表名就取自定义,没有就取小写类名
|
"""
|
||||||
table_name = self.__tablename__
|
将表名改为小写
|
||||||
|
如果有自定义表名就取自定义,没有就取小写类名
|
||||||
|
"""
|
||||||
|
table_name = cls.__tablename__
|
||||||
if not table_name:
|
if not table_name:
|
||||||
model_name = self.__name__
|
model_name = cls.__name__
|
||||||
ls = []
|
ls = []
|
||||||
for index, char in enumerate(model_name):
|
for index, char in enumerate(model_name):
|
||||||
if char.isupper() and index != 0:
|
if char.isupper() and index != 0:
|
||||||
@ -65,29 +77,16 @@ class Base:
|
|||||||
return table_name
|
return table_name
|
||||||
|
|
||||||
|
|
||||||
"""
|
async def db_getter() -> AsyncGenerator[AsyncSession, None]:
|
||||||
创建基本映射类
|
|
||||||
稍后,我们将继承该类,创建每个 ORM 模型
|
|
||||||
"""
|
|
||||||
Model = declarative_base(name='Model', cls=Base)
|
|
||||||
|
|
||||||
""" 附上两个SQLAlchemy教程
|
|
||||||
|
|
||||||
Python3+SQLAlchemy+Sqlite3实现ORM教程
|
|
||||||
https://www.cnblogs.com/jiangxiaobo/p/12350561.html
|
|
||||||
|
|
||||||
SQLAlchemy基础知识 Autoflush和Autocommit
|
|
||||||
https://www.jianshu.com/p/b219c3dd4d1e
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
async def db_getter():
|
|
||||||
"""
|
"""
|
||||||
获取主数据库
|
获取主数据库
|
||||||
|
|
||||||
数据库依赖项,它将在单个请求中使用,然后在请求完成后将其关闭。
|
数据库依赖项,它将在单个请求中使用,然后在请求完成后将其关闭。
|
||||||
|
|
||||||
|
函数的返回类型被注解为 AsyncGenerator[int, None],其中 AsyncSession 是生成的值的类型,而 None 表示异步生成器没有终止条件。
|
||||||
"""
|
"""
|
||||||
async with create_async_engine_session(SQLALCHEMY_DATABASE_URL)() as session:
|
async with create_async_engine_session(SQLALCHEMY_DATABASE_URL)() as session:
|
||||||
|
# 创建一个新的事务,半自动 commit
|
||||||
async with session.begin():
|
async with session.begin():
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
|
@ -5,21 +5,27 @@
|
|||||||
# @IDE : PyCharm
|
# @IDE : PyCharm
|
||||||
# @desc : 数据库公共 ORM 模型
|
# @desc : 数据库公共 ORM 模型
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from core.database import Model
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
from sqlalchemy import Column, DateTime, Integer, func, Boolean
|
from core.database import Base
|
||||||
|
from sqlalchemy import DateTime, Integer, func, Boolean
|
||||||
|
|
||||||
|
|
||||||
# 使用命令:alembic init alembic 初始化迁移数据库环境
|
# 使用命令:alembic init alembic 初始化迁移数据库环境
|
||||||
# 这时会生成alembic文件夹 和 alembic.ini文件
|
# 这时会生成alembic文件夹 和 alembic.ini文件
|
||||||
class BaseModel(Model):
|
class BaseModel(Base):
|
||||||
"""
|
"""
|
||||||
公共 ORM 模型,基表
|
公共 ORM 模型,基表
|
||||||
"""
|
"""
|
||||||
__abstract__ = True
|
__abstract__ = True
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, unique=True, comment='主键ID', index=True, nullable=False)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, comment='主键ID')
|
||||||
create_datetime = Column(DateTime, server_default=func.now(), comment='创建时间')
|
create_datetime: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), comment='创建时间')
|
||||||
update_datetime = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')
|
update_datetime: Mapped[datetime] = mapped_column(
|
||||||
delete_datetime = Column(DateTime, nullable=True, comment='删除时间')
|
DateTime,
|
||||||
is_delete = Column(Boolean, default=False, comment="是否软删除")
|
server_default=func.now(),
|
||||||
|
onupdate=func.now(),
|
||||||
|
comment='更新时间'
|
||||||
|
)
|
||||||
|
delete_datetime: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, comment='删除时间')
|
||||||
|
is_delete: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否软删除")
|
||||||
|
Binary file not shown.
@ -18,7 +18,7 @@ class CreateApp:
|
|||||||
|
|
||||||
def __init__(self, path: str):
|
def __init__(self, path: str):
|
||||||
"""
|
"""
|
||||||
@params path: app 路径,根目录为apps,填写apps后面路径即可,例子:vadmin/auth
|
:param path: app 路径,根目录为apps,填写apps后面路径即可,例子:vadmin/auth
|
||||||
"""
|
"""
|
||||||
self.app_path = os.path.join(self.APPS_ROOT, path)
|
self.app_path = os.path.join(self.APPS_ROOT, path)
|
||||||
self.path = path
|
self.path = path
|
||||||
@ -46,7 +46,7 @@ class CreateApp:
|
|||||||
"""
|
"""
|
||||||
创建 python 包
|
创建 python 包
|
||||||
|
|
||||||
@params path: 绝对路径
|
:param path: 绝对路径
|
||||||
"""
|
"""
|
||||||
if self.exist(path):
|
if self.exist(path):
|
||||||
return
|
return
|
||||||
|
Binary file not shown.
@ -7,13 +7,14 @@
|
|||||||
# @desc : 简要说明
|
# @desc : 简要说明
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from sqlalchemy import insert
|
||||||
from core.database import db_getter
|
from core.database import db_getter
|
||||||
from utils.excel.excel_manage import ExcelManage
|
from utils.excel.excel_manage import ExcelManage
|
||||||
from application.settings import BASE_DIR, VERSION
|
from application.settings import BASE_DIR, VERSION
|
||||||
import os
|
import os
|
||||||
from apps.vadmin.auth import models as auth_models
|
from apps.vadmin.auth import models as auth_models
|
||||||
from apps.vadmin.system import models as system_models
|
from apps.vadmin.system import models as system_models
|
||||||
from sqlalchemy.sql.schema import Table
|
from apps.vadmin.help import models as help_models
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
@ -76,20 +77,16 @@ class InitializeData:
|
|||||||
"""
|
"""
|
||||||
生成数据
|
生成数据
|
||||||
|
|
||||||
@params table_name: 表名
|
:param table_name: 表名
|
||||||
@params model: 数据表模型
|
:param model: 数据表模型
|
||||||
"""
|
"""
|
||||||
async_session = db_getter()
|
async_session = db_getter()
|
||||||
db = await async_session.__anext__()
|
db = await async_session.__anext__()
|
||||||
if isinstance(model, Table):
|
datas = self.datas.get(table_name)
|
||||||
for data in self.datas.get(table_name):
|
await db.execute(insert(model), datas)
|
||||||
await db.execute(model.insert().values(**data))
|
|
||||||
else:
|
|
||||||
for data in self.datas.get(table_name):
|
|
||||||
db.add(model(**data))
|
|
||||||
print(f"{table_name} 表数据已生成")
|
|
||||||
await db.flush()
|
await db.flush()
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
print(f"{table_name} 表数据已生成")
|
||||||
|
|
||||||
async def generate_menu(self):
|
async def generate_menu(self):
|
||||||
"""
|
"""
|
||||||
@ -113,7 +110,7 @@ class InitializeData:
|
|||||||
"""
|
"""
|
||||||
生成用户
|
生成用户
|
||||||
"""
|
"""
|
||||||
await self.__generate_data("vadmin_auth_user_roles", auth_models.vadmin_user_roles)
|
await self.__generate_data("vadmin_auth_user_roles", auth_models.vadmin_auth_user_roles)
|
||||||
|
|
||||||
async def generate_system_tab(self):
|
async def generate_system_tab(self):
|
||||||
"""
|
"""
|
||||||
@ -139,6 +136,18 @@ class InitializeData:
|
|||||||
"""
|
"""
|
||||||
await self.__generate_data("vadmin_system_dict_details", system_models.VadminDictDetails)
|
await self.__generate_data("vadmin_system_dict_details", system_models.VadminDictDetails)
|
||||||
|
|
||||||
|
async def generate_help_issue_category(self):
|
||||||
|
"""
|
||||||
|
生成常见问题类别数据
|
||||||
|
"""
|
||||||
|
await self.__generate_data("vadmin_help_issue_category", help_models.VadminIssueCategory)
|
||||||
|
|
||||||
|
async def generate_help_issue(self):
|
||||||
|
"""
|
||||||
|
生成常见问题详情数据
|
||||||
|
"""
|
||||||
|
await self.__generate_data("vadmin_help_issue", help_models.VadminIssue)
|
||||||
|
|
||||||
async def run(self, env: Environment = Environment.pro):
|
async def run(self, env: Environment = Environment.pro):
|
||||||
"""
|
"""
|
||||||
执行初始化工作
|
执行初始化工作
|
||||||
@ -152,4 +161,6 @@ class InitializeData:
|
|||||||
await self.generate_dict_type()
|
await self.generate_dict_type()
|
||||||
await self.generate_system_config()
|
await self.generate_system_config()
|
||||||
await self.generate_dict_details()
|
await self.generate_dict_details()
|
||||||
|
await self.generate_help_issue_category()
|
||||||
|
await self.generate_help_issue()
|
||||||
print(f"环境:{env} {VERSION} 数据已初始化完成")
|
print(f"环境:{env} {VERSION} 数据已初始化完成")
|
||||||
|
@ -14,7 +14,8 @@ from openpyxl import load_workbook, Workbook
|
|||||||
from application.settings import TEMP_DIR, TEMP_URL
|
from application.settings import TEMP_DIR, TEMP_URL
|
||||||
import hashlib
|
import hashlib
|
||||||
import random
|
import random
|
||||||
from openpyxl.styles import Alignment
|
from openpyxl.styles import Alignment, Font, PatternFill, Border, Side
|
||||||
|
from .excel_schema import AlignmentModel, FontModel, PatternFillModel
|
||||||
|
|
||||||
|
|
||||||
class ExcelManage:
|
class ExcelManage:
|
||||||
@ -116,6 +117,9 @@ class ExcelManage:
|
|||||||
"""
|
"""
|
||||||
if header:
|
if header:
|
||||||
self.sheet.append(header)
|
self.sheet.append(header)
|
||||||
|
pattern_fill_style = PatternFillModel(start_color='D9D9D9', end_color='D9D9D9', fill_type='solid')
|
||||||
|
font_style = FontModel(bold=True)
|
||||||
|
self.__set_row_style(1, len(header), pattern_fill_style=pattern_fill_style, font_style=font_style)
|
||||||
for index, data in enumerate(rows):
|
for index, data in enumerate(rows):
|
||||||
format_columns = {
|
format_columns = {
|
||||||
"date_columns": []
|
"date_columns": []
|
||||||
@ -123,11 +127,12 @@ class ExcelManage:
|
|||||||
for i in range(0, len(data)):
|
for i in range(0, len(data)):
|
||||||
if isinstance(data[i], datetime.datetime):
|
if isinstance(data[i], datetime.datetime):
|
||||||
data[i] = data[i].strftime("%Y/%m/%d %H:%M:%S")
|
data[i] = data[i].strftime("%Y/%m/%d %H:%M:%S")
|
||||||
format_columns["date_columns"].append(i+1)
|
format_columns["date_columns"].append(i + 1)
|
||||||
self.sheet.append(data)
|
self.sheet.append(data)
|
||||||
self.__set_row_style(index+2, len(data)-1)
|
self.__set_row_style(index + 2, len(data))
|
||||||
self.__set_row_format(index+2, format_columns)
|
self.__set_row_format(index + 2, format_columns)
|
||||||
self.__auto_width()
|
self.__auto_width()
|
||||||
|
self.__set_row_border()
|
||||||
|
|
||||||
def save_excel(self, filename: str = None):
|
def save_excel(self, filename: str = None):
|
||||||
"""
|
"""
|
||||||
@ -148,22 +153,34 @@ class ExcelManage:
|
|||||||
# 返回访问路由
|
# 返回访问路由
|
||||||
return f"{TEMP_URL}/{date}/{name}"
|
return f"{TEMP_URL}/{date}/{name}"
|
||||||
|
|
||||||
def __set_row_style(self, row: int, max_column: int):
|
def __set_row_style(
|
||||||
|
self,
|
||||||
|
row: int,
|
||||||
|
max_column: int,
|
||||||
|
alignment_style: AlignmentModel = AlignmentModel(),
|
||||||
|
font_style: FontModel = FontModel(),
|
||||||
|
pattern_fill_style: PatternFillModel = PatternFillModel()
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
设置行样式
|
设置行样式
|
||||||
|
|
||||||
:param row: 行
|
:param row: 行
|
||||||
:param max_column: 最大列
|
:param max_column: 最大列
|
||||||
|
:param alignment_style: 单元格内容的对齐设置
|
||||||
|
:param font_style: 单元格内容的字体样式设置
|
||||||
|
:param pattern_fill_style: 单元格的填充模式设置
|
||||||
"""
|
"""
|
||||||
for index in range(0, max_column):
|
for index in range(0, max_column):
|
||||||
# 设置单元格对齐方式
|
alignment = Alignment(**alignment_style.model_dump())
|
||||||
# Alignment(horizontal=水平对齐模式,vertical=垂直对齐模式,text_rotation=旋转角度,wrap_text=是否自动换行)
|
font = Font(**font_style.model_dump())
|
||||||
alignment = Alignment(horizontal='center', vertical='center', text_rotation=0, wrap_text=False)
|
pattern_fill = PatternFill(**pattern_fill_style.model_dump())
|
||||||
self.sheet.cell(row=row, column=index+1).alignment = alignment
|
self.sheet.cell(row=row, column=index + 1).alignment = alignment
|
||||||
|
self.sheet.cell(row=row, column=index + 1).font = font
|
||||||
|
self.sheet.cell(row=row, column=index + 1).fill = pattern_fill
|
||||||
|
|
||||||
def __set_row_format(self, row: int, columns: dict):
|
def __set_row_format(self, row: int, columns: dict):
|
||||||
"""
|
"""
|
||||||
设置行样式
|
格式化行数据类型
|
||||||
|
|
||||||
:param row: 行
|
:param row: 行
|
||||||
:param columns: 列数据
|
:param columns: 列数据
|
||||||
@ -171,6 +188,22 @@ class ExcelManage:
|
|||||||
for index in columns.get("date_columns", []):
|
for index in columns.get("date_columns", []):
|
||||||
self.sheet.cell(row=row, column=index).number_format = "yyyy/mm/dd h:mm:ss"
|
self.sheet.cell(row=row, column=index).number_format = "yyyy/mm/dd h:mm:ss"
|
||||||
|
|
||||||
|
def __set_row_border(self):
|
||||||
|
"""
|
||||||
|
设置行边框
|
||||||
|
"""
|
||||||
|
# 创建 Border 对象并设置边框样式
|
||||||
|
border = Border(
|
||||||
|
left=Side(border_style="thin", color="000000"),
|
||||||
|
right=Side(border_style="thin", color="000000"),
|
||||||
|
top=Side(border_style="thin", color="000000"),
|
||||||
|
bottom=Side(border_style="thin", color="000000")
|
||||||
|
)
|
||||||
|
# 设置整个表格的边框
|
||||||
|
for row in self.sheet.iter_rows():
|
||||||
|
for cell in row:
|
||||||
|
cell.border = border
|
||||||
|
|
||||||
def __auto_width(self):
|
def __auto_width(self):
|
||||||
"""
|
"""
|
||||||
设置自适应列宽
|
设置自适应列宽
|
||||||
|
65
kinit-api/utils/excel/excel_schema.py
Normal file
65
kinit-api/utils/excel/excel_schema.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @version : 1.0
|
||||||
|
# @Create Time : 2023/08/24 22:19
|
||||||
|
# @File : excel_schema.py
|
||||||
|
# @IDE : PyCharm
|
||||||
|
# @desc :
|
||||||
|
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class AlignmentModel(BaseModel):
|
||||||
|
horizontal: str = Field('center', description="水平对齐方式。可选值:'left'、'center'、'right'、'justify'、'distributed'")
|
||||||
|
vertical: str = Field('center', description="垂直对齐方式。可选值:'top'、'center'、'bottom'、'justify'、'distributed'")
|
||||||
|
textRotation: int = Field(0, description="文本旋转角度(以度为单位)。默认为 0。")
|
||||||
|
wrapText: bool = Field(None, description="自动换行文本。设置为 True 时,如果文本内容超出单元格宽度,会自动换行显示。")
|
||||||
|
shrinkToFit: bool = Field(
|
||||||
|
None,
|
||||||
|
description="缩小字体以适应单元格。设置为 True 时,如果文本内容超出单元格宽度,会自动缩小字体大小以适应。"
|
||||||
|
)
|
||||||
|
indent: int = Field(0, description="文本缩进级别。默认为 0。")
|
||||||
|
relativeIndent: int = Field(0, description="相对缩进级别。默认为 0。")
|
||||||
|
justifyLastLine: bool = Field(
|
||||||
|
None,
|
||||||
|
description="对齐换行文本的末尾行。设置为 True 时,如果设置了文本换行,末尾的行也会被对齐。"
|
||||||
|
)
|
||||||
|
readingOrder: int = Field(0, description="阅读顺序。默认为 0。")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
title = "对齐设置模型"
|
||||||
|
description = "用于设置单元格内容的对齐样式。"
|
||||||
|
|
||||||
|
|
||||||
|
class FontModel(BaseModel):
|
||||||
|
name: str = Field(None, description="字体名称")
|
||||||
|
size: float = Field(None, description="字体大小(磅为单位)")
|
||||||
|
bold: bool = Field(None, description="是否加粗")
|
||||||
|
italic: bool = Field(None, description="是否斜体")
|
||||||
|
underline: str = Field(None, description="下划线样式。可选值:'single'、'double'、'singleAccounting'、'doubleAccounting'")
|
||||||
|
strikethrough: bool = Field(None, description="是否有删除线")
|
||||||
|
outline: bool = Field(None, description="是否轮廓字体")
|
||||||
|
shadow: bool = Field(None, description="是否阴影字体")
|
||||||
|
condense: bool = Field(None, description="是否压缩字体")
|
||||||
|
extend: bool = Field(None, description="是否扩展字体")
|
||||||
|
vertAlign: str = Field(None, description="垂直对齐方式。可选值:'superscript'、'subscript'、'baseline'")
|
||||||
|
color: dict = Field(None, description="字体颜色")
|
||||||
|
scheme: str = Field(None, description="字体方案。可选值:'major'、'minor'")
|
||||||
|
charset: int = Field(None, description="字符集编号")
|
||||||
|
family: int = Field(None, description="字体族编号")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
title = "字体设置模型"
|
||||||
|
description = "用于设置单元格内容的字体样式"
|
||||||
|
|
||||||
|
|
||||||
|
class PatternFillModel(BaseModel):
|
||||||
|
start_color: str = Field(None, description="起始颜色(RGB 值或颜色名称)")
|
||||||
|
end_color: str = Field(None, description="结束颜色(RGB 值或颜色名称)")
|
||||||
|
fill_type: str = Field("solid", description="填充类型('none'、'solid'、'darkDown' 等)")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
title = "填充模式设置模型"
|
||||||
|
description = "单元格的填充模式设置"
|
||||||
|
|
@ -43,7 +43,9 @@ class EmailSender:
|
|||||||
try:
|
try:
|
||||||
self.server.login(self.email, self.password)
|
self.server.login(self.email, self.password)
|
||||||
except smtplib.SMTPAuthenticationError:
|
except smtplib.SMTPAuthenticationError:
|
||||||
raise CustomException("邮箱服务器认证失败!")
|
raise CustomException("邮件发送失败,邮箱服务器认证失败!")
|
||||||
|
except AttributeError:
|
||||||
|
raise CustomException("邮件发送失败,邮箱服务器认证失败!")
|
||||||
|
|
||||||
async def send_email(self, to_emails: List[str], subject: str, body: str, attachments: List[str] = None):
|
async def send_email(self, to_emails: List[str], subject: str, body: str, attachments: List[str] = None):
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user