mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-09-20 20:39:55 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cdf58310e | ||
|
|
75c49e89c2 | ||
|
|
8405471afd | ||
|
|
1e0b38451c | ||
|
|
b84823610c | ||
|
|
332ddbe0e8 | ||
|
|
9cfbf48ce9 | ||
|
|
8f791ff6e5 | ||
|
|
6cb2afca39 | ||
|
|
d47613970b | ||
|
|
8833980008 | ||
|
|
0067e66fc8 | ||
|
|
39c4590c3f | ||
|
|
3196962197 | ||
|
|
1ffaaf5225 | ||
|
|
4ad671023d | ||
|
|
45bd2ddb21 | ||
|
|
267f76d6a2 | ||
|
|
67c09f3113 | ||
|
|
c8da8d5406 | ||
|
|
6ad0a63c68 | ||
|
|
ab4eb00bc1 | ||
|
|
954085666a | ||
|
|
35aa26f8a9 | ||
|
|
1e281508b2 | ||
|
|
a4e4c20c5e | ||
|
|
d4b85f38d8 | ||
|
|
ed88512bf8 | ||
|
|
b9b2f713b9 | ||
|
|
b1563b0d39 | ||
|
|
1857663c9f | ||
|
|
a28922acf0 | ||
|
|
cdacb1643c | ||
|
|
ad0327bf51 | ||
|
|
50af1bf573 | ||
|
|
d364394575 | ||
|
|
98651f4d8f | ||
|
|
40ccb84922 | ||
|
|
d9a19ce3ee | ||
|
|
e216b97163 | ||
|
|
1f51b628af | ||
|
|
1656670203 | ||
|
|
b8dcbdc448 | ||
|
|
dc2e255cd2 | ||
|
|
d9245ed2c9 | ||
|
|
8f4058977d | ||
|
|
044fa0f30b | ||
|
|
6565c50e08 | ||
|
|
184982eb08 | ||
|
|
9a1c53d9f4 | ||
|
|
44678985e9 | ||
|
|
077c8870b4 | ||
|
|
d1fcb6dc11 | ||
|
|
9114d65308 | ||
|
|
f5ec612850 | ||
|
|
a3355e2545 | ||
|
|
1516c1a941 | ||
|
|
b974e8bd3d | ||
|
|
a389ef66bc | ||
|
|
dcdeaa0394 | ||
|
|
f4d7d7a416 | ||
|
|
f5a91c0ece | ||
|
|
5ba6587b6c | ||
|
|
f2de18aa43 | ||
|
|
24cc7e360a | ||
|
|
3c5f6bf873 | ||
|
|
81c9b82abd | ||
|
|
ac3056b9a7 | ||
|
|
10719a4067 | ||
|
|
5597eb651f | ||
|
|
385013db9d | ||
|
|
2699039c0b | ||
|
|
831768dafd | ||
|
|
6ea37823c3 | ||
|
|
67e9f6c98b | ||
|
|
f19f192170 | ||
|
|
7eb4b75ab1 | ||
|
|
d0138516e7 | ||
|
|
ae4f37f1c5 | ||
|
|
41a9d1edf6 | ||
|
|
61fba98d89 | ||
|
|
cc6a35d7c7 | ||
|
|
ea0303835f | ||
|
|
7b993655ba | ||
|
|
d9d7c94513 | ||
|
|
cc6b38acdd | ||
|
|
27d847fd25 | ||
|
|
9e76400c8e | ||
|
|
c365459d88 | ||
|
|
079b2eefb6 | ||
|
|
c6d4c6c1a9 | ||
|
|
9b6355c68d | ||
|
|
ccacee5f95 | ||
|
|
397a51bd96 | ||
|
|
f144945215 | ||
|
|
a1c78172ca | ||
|
|
f03ab1a845 | ||
|
|
a64eca3aeb | ||
|
|
bc547f90f9 | ||
|
|
7a66854a22 | ||
|
|
795a9204ae | ||
|
|
11debaf213 | ||
|
|
857b7dba80 | ||
|
|
5d6bd822d3 | ||
|
|
0b7df2cfc1 | ||
|
|
20bc7ebaaa | ||
|
|
c1a8060139 | ||
|
|
5558dd181b | ||
|
|
0fd4fb283a | ||
|
|
00a7af2d5b | ||
|
|
e34a493b3e | ||
|
|
a9df9ea56a | ||
|
|
d89c0df425 | ||
|
|
d0730e7993 | ||
|
|
5fe376f978 | ||
|
|
dd5b1c55c9 | ||
|
|
7645c6d9fd | ||
|
|
61073970fa | ||
|
|
9ae2ac02eb | ||
|
|
6622c329fc | ||
|
|
b66d545985 | ||
|
|
1789cb5a9b | ||
|
|
ee376c477d | ||
|
|
2b6f0905a9 | ||
|
|
c953754bed | ||
|
|
cea4a10baa | ||
|
|
b708d86eff | ||
|
|
28aab8c7d4 | ||
|
|
9412e3f344 | ||
|
|
b4d5619b1b | ||
|
|
8ce598ad54 | ||
|
|
ca641055e0 |
@@ -0,0 +1 @@
|
||||
github: [insistence]
|
||||
@@ -0,0 +1,170 @@
|
||||
name: Playwright Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
mysql-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: /tmp/.docker_cache
|
||||
key: ${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('**/Dockerfile*', '**/requirements.txt') }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create temporary Dockerfile with Python ${{ matrix.python-version }}
|
||||
run: |
|
||||
# Save original Dockerfile.my
|
||||
cp ruoyi-fastapi-backend/Dockerfile.my ruoyi-fastapi-backend/Dockerfile.my.bak
|
||||
# Create temporary Dockerfile with target Python version
|
||||
sed "s/FROM python:3.10/FROM python:${{ matrix.python-version }}/g" ruoyi-fastapi-backend/Dockerfile.my.bak > ruoyi-fastapi-backend/Dockerfile.my
|
||||
|
||||
- name: Start services with docker-compose
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
docker compose -f docker-compose.test.my.yml up -d --build
|
||||
|
||||
- name: Wait for services to be ready
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
# Wait for backend to be running
|
||||
timeout 180 bash -c 'until docker compose -f docker-compose.test.my.yml ps ruoyi-backend-my | grep -q "Up"; do sleep 5; done'
|
||||
# Wait for frontend to be running
|
||||
timeout 120 bash -c 'until docker compose -f docker-compose.test.my.yml ps ruoyi-frontend | grep -q "Up"; do sleep 5; done'
|
||||
# Additional wait for services to be fully ready and listening on ports
|
||||
sleep 30
|
||||
# Check that backend is actually responding on the API endpoint
|
||||
echo "Checking if backend service is ready..."
|
||||
for i in {1..30}; do
|
||||
if curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||
echo "Backend service is ready!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for backend service to be ready... ($i/30)"
|
||||
sleep 5
|
||||
done
|
||||
# Final check
|
||||
if ! curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||
echo "Backend service failed to start properly. Checking logs..."
|
||||
docker logs ruoyi-backend-my-test
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
pip install -r requirements.txt
|
||||
playwright install
|
||||
pytest -v
|
||||
|
||||
- name: Stop services
|
||||
if: always()
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
docker compose -f docker-compose.test.my.yml down
|
||||
|
||||
- name: Restore original Dockerfile.my
|
||||
if: always()
|
||||
run: |
|
||||
# Restore original Dockerfile.my after test
|
||||
mv ruoyi-fastapi-backend/Dockerfile.my.bak ruoyi-fastapi-backend/Dockerfile.my
|
||||
|
||||
pg-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: /tmp/.docker_cache
|
||||
key: ${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('**/Dockerfile*', '**/requirements.txt') }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Create temporary Dockerfile with Python ${{ matrix.python-version }}
|
||||
run: |
|
||||
# Save original Dockerfile.my
|
||||
cp ruoyi-fastapi-backend/Dockerfile.my ruoyi-fastapi-backend/Dockerfile.my.bak
|
||||
# Create temporary Dockerfile with target Python version
|
||||
sed "s/FROM python:3.10/FROM python:${{ matrix.python-version }}/g" ruoyi-fastapi-backend/Dockerfile.my.bak > ruoyi-fastapi-backend/Dockerfile.my
|
||||
|
||||
- name: Start services with docker-compose
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
docker compose -f docker-compose.test.pg.yml up -d --build
|
||||
|
||||
- name: Wait for services to be ready
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
# Wait for backend to be running
|
||||
timeout 180 bash -c 'until docker compose -f docker-compose.test.pg.yml ps ruoyi-backend-pg | grep -q "Up"; do sleep 5; done'
|
||||
# Wait for frontend to be running
|
||||
timeout 120 bash -c 'until docker compose -f docker-compose.test.pg.yml ps ruoyi-frontend | grep -q "Up"; do sleep 5; done'
|
||||
# Additional wait for services to be fully ready and listening on ports
|
||||
sleep 30
|
||||
# Check that backend is actually responding on the API endpoint
|
||||
echo "Checking if backend service is ready..."
|
||||
for i in {1..30}; do
|
||||
if curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||
echo "Backend service is ready!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for backend service to be ready... ($i/30)"
|
||||
sleep 5
|
||||
done
|
||||
# Final check
|
||||
if ! curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||
echo "Backend service failed to start properly. Checking logs..."
|
||||
docker logs ruoyi-backend-pg-test
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
pip install -r requirements.txt
|
||||
playwright install
|
||||
pytest -v
|
||||
|
||||
- name: Stop services
|
||||
if: always()
|
||||
run: |
|
||||
cd ruoyi-fastapi-test
|
||||
docker compose -f docker-compose.test.pg.yml down
|
||||
|
||||
- name: Restore original Dockerfile.my
|
||||
if: always()
|
||||
run: |
|
||||
# Restore original Dockerfile.my after test
|
||||
mv ruoyi-fastapi-backend/Dockerfile.my.bak ruoyi-fastapi-backend/Dockerfile.my
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Ruff Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
lint-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff
|
||||
|
||||
- name: Run linting
|
||||
run: |
|
||||
ruff check ruoyi-fastapi-backend
|
||||
ruff check ruoyi-fastapi-test
|
||||
|
||||
- name: Run format check
|
||||
run: |
|
||||
ruff format ruoyi-fastapi-backend --check
|
||||
ruff format ruoyi-fastapi-test --check
|
||||
@@ -136,3 +136,10 @@ dmypy.json
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
|
||||
+510
@@ -0,0 +1,510 @@
|
||||
# 更新日志
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.8.1
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增E2E测试 (#57)。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复DictTag组件渲染异常的问题 (#59)。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化数据权限依赖 (#55)。
|
||||
2.动态导入定时任务函数,移除eval (#56)。
|
||||
3.优化pg版本的docker compose配置文件 (#61)。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.8.0
|
||||
|
||||
### 项目依赖
|
||||
|
||||
#### 后端
|
||||
|
||||
1.后端依赖升级到最新版本,请升级依赖或重新创建环境。
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增请求上下文管理类。
|
||||
2.新增`PreAuthDependency`、`CurrentUserDependency`、`DataScopeDependency`、`DBSessionDependency`、`UserInterfaceAuthDependency`和`RoleInterfaceAuthDependency`依赖函数。
|
||||
3.新增上下文清理中间件。
|
||||
4.新增公共vo模块。
|
||||
5.新增配置文档静态资源方法。
|
||||
6.新增自动注册路由功能。
|
||||
7.新增docker compose部署方式。
|
||||
8.菜单导航设置支持纯顶部。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复单账号登录模式下强退功能失效的问题 #52。
|
||||
2.确保ApschedulerJobs字段类型与apscheduler默认创建的表字段类型一致 #53。
|
||||
3.修复磁盘存在异常时服务监控无法正常运行的问题。
|
||||
4.移除代码生成表业务表外键,修复无法删除的问题。
|
||||
5.修复固定头部时出现的导航栏偏移问题。
|
||||
6.修复表单构建移除所有控件后切换路由回来空白问题。
|
||||
7.修复代码生成v3模板时间控件between选择后清空报错问题。
|
||||
|
||||
### 代码重构
|
||||
|
||||
1.增强ruff规则,完善类型提示。
|
||||
2.优化项目结构,新增common模块,原annotation、aspect、constant、enums模块移动至common模块下。
|
||||
3.重构app与server设计。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.controller层全部使用新依赖项。
|
||||
2.当前用户信息使用上下文变量。
|
||||
3.分页模型改为使用公共vo模块的PageModel。
|
||||
4.优化API文档的响应模型显示。
|
||||
5.操作响应模型改为使用公共vo模块的CrudResponseModel。
|
||||
6.优化API文档的接口描述信息。
|
||||
7.登录/注册页面底部版权信息修改为读取配置。
|
||||
8.优化生成代码下载的zip文件名。
|
||||
9.优化表单构建关闭页签销毁复制插件。
|
||||
10.行内表单默认设置固定宽度。
|
||||
11.优化操作日志详细请求参数显示。
|
||||
12.优化index页面标题读取配置。
|
||||
13.优化字典组件数字类型值处理逻辑。
|
||||
14.优化字典组件值宽松匹配。
|
||||
15.默认固定头部。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.7.1
|
||||
|
||||
### 项目依赖
|
||||
|
||||
1.后端依赖移除passlib,直接使用bcrypt。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复代码生成controller模板编辑接口异常生成字段的问题。
|
||||
2.移除passlib直接使用bcrypt修复密码校验异常的问题 #48 #49。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.代码生成do模板补充表描述。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.7.0
|
||||
|
||||
### 项目依赖
|
||||
|
||||
1.前后端依赖升级,请升级依赖或重新创建环境。
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增alembic支持。
|
||||
2.文件&图片上传组件支持自定义地址&参数。
|
||||
3.新增默认打包配置项。
|
||||
4.显隐列组件支持全选/全不选。
|
||||
5.添加页签openPage支持传递参数。
|
||||
6.外链加载时遮罩信息提示。
|
||||
7.上传组件新增拖动排序属性。
|
||||
8.图片上传组件新增disabled属性。
|
||||
9.代码生成列支持拖动排序。
|
||||
10.新增用户默认初始化密码。
|
||||
11.新增页签图标显示开关功能。
|
||||
12.新增底部版权信息及开关。
|
||||
13.用户归属部门新增清除。
|
||||
14.用户导入新增验证提示。
|
||||
15.菜单搜索支持键盘选择&悬浮主题背景。
|
||||
16.新增apscheduler_jobs表对应sqlalchemy模型类。
|
||||
17.初始密码支持自定义修改策略。
|
||||
18.账号密码支持自定义更新周期。
|
||||
19.注册账号设置默认密码最后更新时间。
|
||||
20.显示列信息支持对象格式。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复logout接口未按照app_same_time_login配置项动态判断的问题 #IBZZ1S。
|
||||
2.修复上传组件被多次引用拖动仅对第一个有效的问题。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化接口耗时计算。
|
||||
2.优化启动信息显示。
|
||||
3.优化前端处理路由函数代码。
|
||||
4.登录页和注册页表头使用VITE_APP_BASE_API配置值。
|
||||
5.优化角色禁用不允许分配。
|
||||
6.优化富文本控制台警告异常。
|
||||
7.优化checkbox废弃API。
|
||||
8.优化导航栏显示昵称&设置。
|
||||
|
||||
### 代码重构
|
||||
|
||||
1.重构IP归属区域查询为异步调用。
|
||||
2.调整do与sql使其相互适配以支持alembic。
|
||||
3.富文本复制粘贴图片上传至url。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.6.2
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.文件上传组件新增disabled属性。
|
||||
2.文件上传组件新增类型。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复日志管理时间查询报错 #27。
|
||||
2.修复定时任务状态暂停时执行单次任务会触发cron表达式的问题 #31。
|
||||
3.修复修改字典类型时获取dict_code异常的问题。
|
||||
4.修复修改字典类型时字典数据更新时间异常的问题。
|
||||
5.修复代码生成模板时间查询问题 #28。
|
||||
6.修复用户导出缺失部门名称的问题。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化代码生成新增和编辑字段显示和渲染。
|
||||
2.pagination更换成flex布局。
|
||||
3.优化代码生成vue模板 #23。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.6.1
|
||||
|
||||
### 项目依赖
|
||||
|
||||
#### 后端
|
||||
|
||||
1.新增sqlglot依赖
|
||||
|
||||
```bash
|
||||
pip install sqlglot[rs]==26.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
```
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.引入sqlglot修复sql语句解析异常的问题。
|
||||
2.修复代码生成字段唯一性校验dao层模板判断异常的问题。
|
||||
3.引入泛型修复as_query和as_form装饰模型文档丢失的问题。
|
||||
4.修复代码生成主子表vo模板可能缺失NotBlank的问题。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.6.0
|
||||
|
||||
### 项目依赖
|
||||
|
||||
1.后端依赖升级到最新版本,请升级依赖或重新创建环境。
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增代码生成功能,支持配置数据库表信息一键生成和下载前后端代码,需要重新执行sql文件,请先备份数据。
|
||||
2.新增表单构建功能。
|
||||
3.用户头像新增支持http(s)链接。
|
||||
4.新增trace中间件强化日志链路追踪和响应头 [@y1ren](https://gitee.com/y1ren)。
|
||||
5.用户管理支持分栏拖动。
|
||||
6.菜单面包屑导航支持多层级显示。
|
||||
7.白名单支持对通配符路径匹配。
|
||||
8.支持开启暗黑模式。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复默认关闭Tags-Views时,内链页面打不开。
|
||||
2.修复删除当前登录用户拦截失效的问题。
|
||||
3.修复定时任务目标字符串规则校验不全的问题。
|
||||
4.修复执行单次任务时会覆盖已启用任务的问题 #IBEKD2。
|
||||
5.修复个人中心特殊字符密码修改失败问题。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化导出方法。
|
||||
2.参数键值更换为多行文本。
|
||||
3.优化日志中操作方法显示。
|
||||
4.优化日志装饰器获取核心参数的方式。
|
||||
5.用户管理过滤掉已禁用部门。
|
||||
6.优化TopNav内链菜单点击没有高亮。
|
||||
7.ResponseUtil补充完整参数。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.5.1
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.定时任务新增支持调用异步函数。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化字典数组条件判断。
|
||||
2.校检文件名是否包含特殊字符。
|
||||
3.移除已弃用的log_decorator装饰器。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.5.0
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增对PostgreSQL数据库的支持。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复DictTag组件控制台抛异常的问题 #IAYSVZ。
|
||||
2.修复登录日志导出文件名称错误的问题。
|
||||
|
||||
### 代码回滚
|
||||
|
||||
1.因fastapi查询参数模型底层存在bug,回滚查询参数模型声明方式为as_query。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化CamelCaseUtil和SnakeCaseUtil以兼容更多转换场景。
|
||||
2.优化列表查询排序。
|
||||
3.优化参数设置页面。
|
||||
4.优化上传图片带域名时不增加前缀。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.4.0
|
||||
|
||||
### 项目依赖
|
||||
|
||||
#### 后端
|
||||
|
||||
1.更新fastapi版本为0.115.0
|
||||
|
||||
```bash
|
||||
pip install fastapi[all]==0.115.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
```
|
||||
|
||||
### 代码重构
|
||||
|
||||
1.基于fastapi 0.115.0版本新特性,直接使用pydantic模型接收查询参数和表单数据,移除原有as_query和as_form使用方式。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复角色管理service书写错误。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化前端登录请求方法。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.3.3
|
||||
|
||||
### 项目依赖
|
||||
|
||||
#### 后端
|
||||
|
||||
1.更新pydantic-validation-decorator版本为0.1.4,修复了一些底层bug。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复在线用户模块条件查询无效的问题。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化在线用户模块前后端字段描述一致。
|
||||
2.日志装饰器异常处理增加logger打印日志。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.3.2
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增gzip压缩中间件。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复分页函数计算has_next错误的问题 #10。
|
||||
2.修复定时任务监听函数中事件没有job_id报错的问题。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.优化添加中间件函数注释。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.3.1
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复1.3.0版本采用新的异常处理机制后日志装饰器无法记录异常日志的问题。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.补充定时任务违规字符串。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.3.0
|
||||
|
||||
### 项目依赖
|
||||
|
||||
1.前后端依赖均升级到最新版本,请升级依赖或重新创建环境。
|
||||
2.使用`PyJWT`替换`python-jose`以解决一些安全性问题。
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增字段校验装饰器,支持手动触发校验,已封装为`pydantic-validation-decorator`库。
|
||||
2.各模块`service`层新增字段唯一性校验。
|
||||
3.全局新增`ServiceException`自定义服务异常和`ServiceWarning`自定义服务警告,无需在接口中写大量的异常捕获。
|
||||
4.菜单管理新增路由名称,请执行以下sql为数据库新增字段:
|
||||
|
||||
```sql
|
||||
ALTER TABLE sys_menu ADD COLUMN route_name varchar(50) DEFAULT '';
|
||||
```
|
||||
|
||||
5.新增`constant`常量配置及`enums`枚举类型配置。
|
||||
6.新增`StringUtil`、`CronUtil`工具类。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复用户管理、角色管理、部门管理越权漏洞。
|
||||
2.修复各模块`dao`层`status`、`del_flag`类型与数据库不一致的问题。
|
||||
3.修复移动端左侧菜单无法显示的问题。
|
||||
4.修复其他已知BUG。
|
||||
|
||||
### 代码重构
|
||||
|
||||
1.重构日志装饰器为`Log`,未来版本将删除`log_decorator`装饰器,请尽快迁移。
|
||||
2.重构`RedisInitKeyConfig`为枚举类型,现在可通过以下方式获取对应的`key`和`remark`
|
||||
`RedisInitKeyConfig.ACCESS_TOKEN.key`、`RedisInitKeyConfig.ACCESS_TOKEN.remark`。
|
||||
3.重构数据权限逻辑,底层进行优化,使用方法与之前相同。
|
||||
|
||||
### 代码优化
|
||||
|
||||
1.引入`ruff`对后端代码进行格式化及检测修复,优化导入。
|
||||
2.各模块基于`ServiceException`自定义服务异常和`ServiceWarning`自定义服务警告优化了异常处理逻辑。
|
||||
3.各模块`vo`层使用`Field`声明字段。
|
||||
4.优化API文档字段描述显示。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.2.2
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复删除定时任务时未移除调度中任务的问题。
|
||||
2.修复菜单生成路由时组件条件判断错误的问题。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.2.1
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复各模块新增数据时创建时间记录异常的问题。
|
||||
2.修复菜单挂载到根目录时路由加载异常等一系列相关问题。
|
||||
|
||||
### 代码及性能优化
|
||||
|
||||
1.修改代理localhost为127.0.0.1以适配部分设备解析localhost异常的问题。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.2.0
|
||||
|
||||
### 重要说明
|
||||
|
||||
本次更新为 **_破坏性更新_** ,重构数据库orm为异步,代码改动很大,请谨慎升级。
|
||||
1.原有的Session类型声明统一变更为AsyncSession。
|
||||
2.service层和dao层的函数修改为异步函数,请使用await调用。
|
||||
3.orm查询不再支持query,请使用select、update、delete等语句,具体使用方法请参考[https://docs.sqlalchemy.org/en/20/orm/queryguide/index.html](https://docs.sqlalchemy.org/en/20/orm/queryguide/index.html)。
|
||||
|
||||
### 项目依赖
|
||||
|
||||
#### 后端
|
||||
|
||||
1.增加asyncmy依赖用于支持orm异步操作mysql,请重新安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。
|
||||
```
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.新增SnakeCaseUtil工具类,将原CamelCaseUtil工具类的camel_to_snake函数迁移至SnakeCaseUtil工具类。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复用户管理模块重置用户密码时会异常重置用户岗位和角色的问题。
|
||||
2.修复清空定时任务日志异常的问题。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.1.3
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.用户密码新增非法字符验证。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复通知公告列表查询前后端字段不一致的问题。
|
||||
2.修复个人中心修改基本资料后端异常的问题。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.1.2
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.配置文件新增数据库连接池相关配置。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复个人中心修改密码后端异常的问题。
|
||||
|
||||
### 代码及性能优化
|
||||
|
||||
1.使用@lru_cache缓存ip归属区域查询结果,避免重复调用ip归属区域查询接口以优化性能。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.1.1
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复编辑定时任务时更新的信息未同步至scheduler的问题 #I9EK56。
|
||||
2.修复编辑角色数据权限时后端异常的问题 #I9ENQN。
|
||||
3.修复菜单配置路由参数不生效的问题。
|
||||
4.修复获取路由信息时菜单排序不生效的问题。
|
||||
5.修复添加菜单时是否外链和是否缓存回显异常的问题。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.1.0
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.后端配置文件新增sqlalchemy日志开关配置。
|
||||
2.后端配置文件新增IP归属区域查询开关配置。
|
||||
3.后端配置文件新增账号同时登录开关配置。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复token本身过期时退出登录接口异常的问题 #I9CBWT。
|
||||
2.修复系统版本号或浏览器版本号无法获取时登录异常的问题 #I9CYNM。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.0.3
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.账号密码登录新增IP黑名单校验。
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复外链菜单无法打开的问题 #I95KBK。
|
||||
2.修复添加和编辑菜单页面中是否缓存和是否外链字段回显异常的问题 #I95KBK。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.0.2
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.用户接口权限校验增加列表接收参数,实现同一接口支持多个权限标识校验。
|
||||
2.新增按角色校验接口权限依赖
|
||||
|
||||
### BUG修复
|
||||
|
||||
1.修复用户管理和部门管理模块数据权限异常的问题。
|
||||
|
||||
### 代码及性能优化
|
||||
|
||||
1.调整参数设置、部门管理、字典管理、定时任务、日志管理、角色管理、菜单管理模块部分接口权限标识。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.0.1
|
||||
|
||||
### 项目依赖
|
||||
|
||||
#### 后端
|
||||
|
||||
1.更新fastapi版本为0.109.1,修复一些安全性问题,命令:
|
||||
|
||||
```bash
|
||||
pip install fastapi[all]==0.109.1 -i https://mirrors.aliyun.com/pypi/simple/
|
||||
```
|
||||
|
||||
### 新增功能
|
||||
|
||||
1.日志管理模块新增字段排序查询。
|
||||
|
||||
## RuoYi-Vue3-FastAPI v1.0.0
|
||||
|
||||
RuoYi-Vue3-FastAPI第一个版本发布啦!
|
||||
此版本功能如下:
|
||||
1.用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2.角色管理:角色菜单权限分配。
|
||||
3.菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
4.部门管理:配置系统组织机构(公司、部门、小组)。
|
||||
5.岗位管理:配置系统用户所属担任职务。
|
||||
6.字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7.参数管理:对系统动态配置常用参数。
|
||||
8.通知公告:系统通知公告信息发布维护。
|
||||
9.操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
10.登录日志:系统登录日志记录查询包含登录异常。
|
||||
11.在线用户:当前系统中活跃用户状态监控。
|
||||
12.定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
||||
13.服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
|
||||
14.缓存监控:对系统的缓存信息查询,命令统计等。
|
||||
15.系统接口:根据业务代码自动生成相关的api接口文档。
|
||||
@@ -1,15 +1,33 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
|
||||
</p>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.6.0</h1>
|
||||
</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI</h1>
|
||||
<h4 align="center">基于RuoYi-Vue3+FastAPI前后端分离的快速开发框架</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers"><img src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark"></a>
|
||||
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social"></a>
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.6.0-brightgreen.svg"></a>
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
|
||||
<img src="https://img.shields.io/badge/python-≥3.9-blue">
|
||||
<img src="https://img.shields.io/badge/MySQL-≥5.7-blue">
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers">
|
||||
<img alt="Gitee" src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark">
|
||||
</a>
|
||||
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI">
|
||||
<img alt="Github" src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social">
|
||||
</a>
|
||||
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22%22Playwright+Tests%22%22">
|
||||
<img alt="Playwright Tests" src="https://github.com/insistence/RuoYi-Vue3-FastAPI/workflows/Playwright Tests/badge.svg">
|
||||
</a>
|
||||
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22%22Ruff+Check%22%22">
|
||||
<img alt="Ruff Check" src="https://github.com/insistence/RuoYi-Vue3-FastAPI/workflows/Ruff Check/badge.svg">
|
||||
</a>
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI">
|
||||
<img alt="project version" src="https://img.shields.io/badge/version-1.8.1-brightgreen.svg">
|
||||
</a>
|
||||
<a href="https://github.com/astral-sh/ruff">
|
||||
<img alt="Ruff" src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json">
|
||||
</a>
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE">
|
||||
<img alt="LICENSE" src="https://img.shields.io/github/license/mashape/apistatus.svg">
|
||||
</a>
|
||||
<img alt="node version" src="https://img.shields.io/badge/node-≥18-blue">
|
||||
<img alt="python version" src="https://img.shields.io/badge/python-≥3.9-blue">
|
||||
<img alt="mysql version" src="https://img.shields.io/badge/MySQL-≥5.7-blue">
|
||||
</p>
|
||||
|
||||
## 平台简介
|
||||
@@ -21,11 +39,11 @@ RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台,毫无保留给
|
||||
* 权限认证使用OAuth2 & Jwt,支持多终端认证系统。
|
||||
* 支持加载动态权限菜单,多方式轻松权限控制。
|
||||
* Vue2版本:
|
||||
- Gitte仓库地址:https://gitee.com/insistence2022/RuoYi-Vue-FastAPI
|
||||
- GitHub仓库地址:https://github.com/insistence/RuoYi-Vue-FastAPI
|
||||
* Gitte仓库地址:<https://gitee.com/insistence2022/RuoYi-Vue-FastAPI>
|
||||
* GitHub仓库地址:<https://github.com/insistence/RuoYi-Vue-FastAPI>
|
||||
* 纯Python版本:
|
||||
- Gitte仓库地址:https://gitee.com/insistence2022/dash-fastapi-admin
|
||||
- GitHub仓库地址:https://github.com/insistence/Dash-FastAPI-Admin
|
||||
* Gitte仓库地址:<https://gitee.com/insistence2022/dash-fastapi-admin>
|
||||
* GitHub仓库地址:<https://github.com/insistence/Dash-FastAPI-Admin>
|
||||
* 特别鸣谢:<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>
|
||||
|
||||
## 内置功能
|
||||
@@ -52,54 +70,97 @@ RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台,毫无保留给
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/login.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dashboard.png"/></td>
|
||||
<td>
|
||||
<img alt="login" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/login.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="dashboard" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dashboard.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/user.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/role.png"/></td>
|
||||
<td>
|
||||
<img alt="user" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/user.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="role" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/role.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/menu.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dept.png"/></td>
|
||||
<td>
|
||||
<img alt="menu" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/menu.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="dept" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dept.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/post.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dict.png"/></td>
|
||||
<td>
|
||||
<img alt=""post src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/post.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="dict" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dict.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/config.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/notice.png"/></td>
|
||||
<td>
|
||||
<img alt="config" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/config.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="notice" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/notice.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/operLog.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/loginLog.png"/></td>
|
||||
<td>
|
||||
<img alt="operLog" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/operLog.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="loginLog" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/loginLog.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/online.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/job.png"/></td>
|
||||
<td>
|
||||
<img alt="online" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/online.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="job" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/job.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/server.png"/></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/cache.png"/></td>
|
||||
<td>
|
||||
<img alt="server" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/server.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="cache" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/cache.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/cacheList.png"></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/form.png"></td>
|
||||
<td>
|
||||
<img alt="cacheList" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/cacheList.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="form" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/form.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/api.png"></td>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/gen.png"/></td>
|
||||
<td>
|
||||
<img alt="api" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/api.png">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="gen" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/gen.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/profile.png"/></td>
|
||||
<td>
|
||||
<img alt="profile" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/profile.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 在线体验
|
||||
- *账号:admin*
|
||||
- *密码:admin123*
|
||||
- 演示地址:<a href="https://vfadmin.insistence.tech">vfadmin管理系统<a>
|
||||
|
||||
* *账号:admin*
|
||||
* *密码:admin123*
|
||||
* 演示地址:<a href="https://vfadmin.insistence.tech">vfadmin管理系统<a>
|
||||
|
||||
## 项目开发及发布相关
|
||||
|
||||
@@ -114,6 +175,7 @@ cd RuoYi-Vue3-FastAPI
|
||||
```
|
||||
|
||||
#### 前端
|
||||
|
||||
```bash
|
||||
# 进入前端目录
|
||||
cd ruoyi-fastapi-frontend
|
||||
@@ -129,6 +191,7 @@ npm run dev 或 yarn dev
|
||||
```
|
||||
|
||||
#### 后端
|
||||
|
||||
```bash
|
||||
# 进入后端目录
|
||||
cd ruoyi-fastapi-backend
|
||||
@@ -150,6 +213,7 @@ python3 app.py --env=dev
|
||||
```
|
||||
|
||||
#### 访问
|
||||
|
||||
```bash
|
||||
# 默认账号密码
|
||||
账号:admin
|
||||
@@ -162,6 +226,7 @@ python3 app.py --env=dev
|
||||
### 发布
|
||||
|
||||
#### 前端
|
||||
|
||||
```bash
|
||||
# 构建测试环境
|
||||
npm run build:stage 或 yarn build:stage
|
||||
@@ -171,6 +236,7 @@ npm run build:prod 或 yarn build:prod
|
||||
```
|
||||
|
||||
#### 后端
|
||||
|
||||
```bash
|
||||
# 配置环境
|
||||
在.env.prod文件中配置生产环境的数据库和redis
|
||||
@@ -179,14 +245,37 @@ npm run build:prod 或 yarn build:prod
|
||||
python3 app.py --env=prod
|
||||
```
|
||||
|
||||
### Docker Compose部署方式
|
||||
|
||||
> ⚠️ **警告:** 默认未做数据持久化配置,请注意数据备份或自行配置持久化
|
||||
|
||||
#### MySQL版本
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.my.yml up -d --build
|
||||
```
|
||||
|
||||
#### PostgreSQL版本
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.pg.yml up -d --build
|
||||
```
|
||||
|
||||
## 交流与赞助
|
||||
|
||||
如果有对本项目及FastAPI感兴趣的朋友,欢迎加入知识星球一起交流学习,让我们一起变得更强。如果你觉得这个项目帮助到了你,你可以请作者喝杯咖啡表示鼓励☕。扫描下面微信二维码添加微信备注VF-Admin即可进群。
|
||||
<table>
|
||||
<tr>
|
||||
<td><img alt="zsxq" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/zsxq.jpg"></td>
|
||||
<td><img alt="zanzhu" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/zanzhu.jpg"></td>
|
||||
<td>
|
||||
<img alt="zsxq" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/zsxq.jpg">
|
||||
</td>
|
||||
<td>
|
||||
<img alt="zanzhu" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/zanzhu.jpg">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img alt="wxcode" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/wxcode.jpg"></td>
|
||||
<td>
|
||||
<img alt="wxcode" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/wxcode.jpg">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,73 @@
|
||||
services:
|
||||
# 前端服务
|
||||
ruoyi-frontend:
|
||||
build:
|
||||
context: ./ruoyi-fastapi-frontend
|
||||
dockerfile: Dockerfile
|
||||
image: ruoyi-frontend:latest
|
||||
container_name: ruoyi-frontend
|
||||
ports:
|
||||
- "12580:80"
|
||||
volumes:
|
||||
- ./ruoyi-fastapi-frontend/bin/nginx.dockermy.conf:/etc/nginx/conf.d/default.conf
|
||||
depends_on:
|
||||
- ruoyi-backend-my
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# 后端服务(MySQL版本)
|
||||
ruoyi-backend-my:
|
||||
build:
|
||||
context: ./ruoyi-fastapi-backend
|
||||
dockerfile: Dockerfile.my
|
||||
image: ruoyi-backend-my:latest
|
||||
container_name: ruoyi-backend-my
|
||||
ports:
|
||||
- "19099:9099"
|
||||
depends_on:
|
||||
ruoyi-mysql:
|
||||
condition: service_healthy
|
||||
ruoyi-redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# MySQL服务
|
||||
ruoyi-mysql:
|
||||
image: mysql:8.0
|
||||
container_name: ruoyi-mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: ruoyi-fastapi
|
||||
ports:
|
||||
- "13306:3306"
|
||||
volumes:
|
||||
- ./ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql:/docker-entrypoint-initdb.d/ruoyi-fastapi.sql
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --skip-character-set-client-handshake=1
|
||||
networks:
|
||||
- ruoyi-network
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
# Redis服务
|
||||
ruoyi-redis:
|
||||
image: redis:latest
|
||||
container_name: ruoyi-redis
|
||||
ports:
|
||||
- "16379:6379"
|
||||
networks:
|
||||
- ruoyi-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
# 网络配置
|
||||
networks:
|
||||
ruoyi-network:
|
||||
name: ruoyi-network
|
||||
driver: bridge
|
||||
@@ -0,0 +1,73 @@
|
||||
services:
|
||||
# 前端服务
|
||||
ruoyi-frontend:
|
||||
build:
|
||||
context: ./ruoyi-fastapi-frontend
|
||||
dockerfile: Dockerfile
|
||||
image: ruoyi-frontend:latest
|
||||
container_name: ruoyi-frontend
|
||||
ports:
|
||||
- "12580:80"
|
||||
volumes:
|
||||
- ./ruoyi-fastapi-frontend/bin/nginx.dockerpg.conf:/etc/nginx/conf.d/default.conf
|
||||
depends_on:
|
||||
- ruoyi-backend-pg
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# 后端服务(PostgreSQL版本)
|
||||
ruoyi-backend-pg:
|
||||
build:
|
||||
context: ./ruoyi-fastapi-backend
|
||||
dockerfile: Dockerfile.pg
|
||||
image: ruoyi-backend-pg:latest
|
||||
container_name: ruoyi-backend-pg
|
||||
ports:
|
||||
- "19099:9099"
|
||||
depends_on:
|
||||
ruoyi-pg:
|
||||
condition: service_healthy
|
||||
ruoyi-redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# PostgreSQL服务
|
||||
ruoyi-pg:
|
||||
image: postgres:14
|
||||
container_name: ruoyi-pg
|
||||
environment:
|
||||
POSTGRES_PASSWORD: root
|
||||
POSTGRES_DB: ruoyi-fastapi
|
||||
POSTGRES_INITDB_ARGS: --encoding=UTF8 --lc-collate=C --lc-ctype=C
|
||||
ports:
|
||||
- "15432:5432"
|
||||
volumes:
|
||||
- ./ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql:/docker-entrypoint-initdb.d/ruoyi-fastapi-pg.sql
|
||||
networks:
|
||||
- ruoyi-network
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
# Redis服务
|
||||
ruoyi-redis:
|
||||
image: redis:latest
|
||||
container_name: ruoyi-redis
|
||||
ports:
|
||||
- "16379:6379"
|
||||
networks:
|
||||
- ruoyi-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
|
||||
# 网络配置
|
||||
networks:
|
||||
ruoyi-network:
|
||||
name: ruoyi-network
|
||||
driver: bridge
|
||||
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.6.0'
|
||||
APP_VERSION= '1.8.1'
|
||||
# 应用是否开启热重载
|
||||
APP_RELOAD = true
|
||||
# 应用是否开启IP归属区域查询
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# -------- 应用配置 --------
|
||||
# 应用运行环境
|
||||
APP_ENV = 'prod'
|
||||
# 应用名称
|
||||
APP_NAME = 'RuoYi-FastAPI'
|
||||
# 应用代理路径
|
||||
APP_ROOT_PATH = '/docker-api'
|
||||
# 应用主机
|
||||
APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.8.1'
|
||||
# 应用是否开启热重载
|
||||
APP_RELOAD = false
|
||||
# 应用是否开启IP归属区域查询
|
||||
APP_IP_LOCATION_QUERY = true
|
||||
# 应用是否允许账号同时登录
|
||||
APP_SAME_TIME_LOGIN = true
|
||||
|
||||
# -------- Jwt配置 --------
|
||||
# Jwt秘钥
|
||||
JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55'
|
||||
# Jwt算法
|
||||
JWT_ALGORITHM = 'HS256'
|
||||
# 令牌过期时间
|
||||
JWT_EXPIRE_MINUTES = 1440
|
||||
# redis中令牌过期时间
|
||||
JWT_REDIS_EXPIRE_MINUTES = 30
|
||||
|
||||
|
||||
# -------- 数据库配置 --------
|
||||
# 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql'
|
||||
DB_TYPE = 'mysql'
|
||||
# 数据库主机
|
||||
DB_HOST = 'ruoyi-mysql'
|
||||
# 数据库端口
|
||||
DB_PORT = 3306
|
||||
# 数据库用户名
|
||||
DB_USERNAME = 'root'
|
||||
# 数据库密码
|
||||
DB_PASSWORD = 'root'
|
||||
# 数据库名称
|
||||
DB_DATABASE = 'ruoyi-fastapi'
|
||||
# 是否开启sqlalchemy日志
|
||||
DB_ECHO = true
|
||||
# 允许溢出连接池大小的最大连接数
|
||||
DB_MAX_OVERFLOW = 10
|
||||
# 连接池大小,0表示连接数无限制
|
||||
DB_POOL_SIZE = 50
|
||||
# 连接回收时间(单位:秒)
|
||||
DB_POOL_RECYCLE = 3600
|
||||
# 连接池中没有线程可用时,最多等待的时间(单位:秒)
|
||||
DB_POOL_TIMEOUT = 30
|
||||
|
||||
# -------- Redis配置 --------
|
||||
# Redis主机
|
||||
REDIS_HOST = 'ruoyi-redis'
|
||||
# Redis端口
|
||||
REDIS_PORT = 6379
|
||||
# Redis用户名
|
||||
REDIS_USERNAME = ''
|
||||
# Redis密码
|
||||
REDIS_PASSWORD = ''
|
||||
# Redis数据库
|
||||
REDIS_DATABASE = 2
|
||||
@@ -0,0 +1,66 @@
|
||||
# -------- 应用配置 --------
|
||||
# 应用运行环境
|
||||
APP_ENV = 'prod'
|
||||
# 应用名称
|
||||
APP_NAME = 'RuoYi-FastAPI'
|
||||
# 应用代理路径
|
||||
APP_ROOT_PATH = '/docker-api'
|
||||
# 应用主机
|
||||
APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.8.1'
|
||||
# 应用是否开启热重载
|
||||
APP_RELOAD = false
|
||||
# 应用是否开启IP归属区域查询
|
||||
APP_IP_LOCATION_QUERY = true
|
||||
# 应用是否允许账号同时登录
|
||||
APP_SAME_TIME_LOGIN = true
|
||||
|
||||
# -------- Jwt配置 --------
|
||||
# Jwt秘钥
|
||||
JWT_SECRET_KEY = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55'
|
||||
# Jwt算法
|
||||
JWT_ALGORITHM = 'HS256'
|
||||
# 令牌过期时间
|
||||
JWT_EXPIRE_MINUTES = 1440
|
||||
# redis中令牌过期时间
|
||||
JWT_REDIS_EXPIRE_MINUTES = 30
|
||||
|
||||
|
||||
# -------- 数据库配置 --------
|
||||
# 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql'
|
||||
DB_TYPE = 'postgresql'
|
||||
# 数据库主机
|
||||
DB_HOST = 'ruoyi-pg'
|
||||
# 数据库端口
|
||||
DB_PORT = 5432
|
||||
# 数据库用户名
|
||||
DB_USERNAME = 'postgres'
|
||||
# 数据库密码
|
||||
DB_PASSWORD = 'root'
|
||||
# 数据库名称
|
||||
DB_DATABASE = 'ruoyi-fastapi'
|
||||
# 是否开启sqlalchemy日志
|
||||
DB_ECHO = true
|
||||
# 允许溢出连接池大小的最大连接数
|
||||
DB_MAX_OVERFLOW = 10
|
||||
# 连接池大小,0表示连接数无限制
|
||||
DB_POOL_SIZE = 50
|
||||
# 连接回收时间(单位:秒)
|
||||
DB_POOL_RECYCLE = 3600
|
||||
# 连接池中没有线程可用时,最多等待的时间(单位:秒)
|
||||
DB_POOL_TIMEOUT = 30
|
||||
|
||||
# -------- Redis配置 --------
|
||||
# Redis主机
|
||||
REDIS_HOST = 'ruoyi-redis'
|
||||
# Redis端口
|
||||
REDIS_PORT = 6379
|
||||
# Redis用户名
|
||||
REDIS_USERNAME = ''
|
||||
# Redis密码
|
||||
REDIS_PASSWORD = ''
|
||||
# Redis数据库
|
||||
REDIS_DATABASE = 2
|
||||
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.6.0'
|
||||
APP_VERSION= '1.8.1'
|
||||
# 应用是否开启热重载
|
||||
APP_RELOAD = false
|
||||
# 应用是否开启IP归属区域查询
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM python:3.10
|
||||
WORKDIR /app
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 9099
|
||||
|
||||
# 启动命令
|
||||
CMD ["python", "app.py", "--env=dockermy"]
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM python:3.10
|
||||
WORKDIR /app
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -r requirements-pg.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 9099
|
||||
|
||||
# 启动命令
|
||||
CMD ["python", "app.py", "--env=dockerpg"]
|
||||
@@ -0,0 +1,153 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts.
|
||||
# this is typically a path given in POSIX (e.g. forward slashes)
|
||||
# format, relative to the token %(here)s which refers to the location of this
|
||||
# ini file
|
||||
script_location = %(here)s/alembic
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
||||
# for all available tokens
|
||||
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory. for multiple paths, the path separator
|
||||
# is defined by "path_separator" below.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to ZoneInfo()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to <script_location>/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "path_separator"
|
||||
# below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
|
||||
|
||||
# path_separator; This indicates what character is used to split lists of file
|
||||
# paths, including version_locations and prepend_sys_path within configparser
|
||||
# files such as alembic.ini.
|
||||
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
|
||||
# to provide os-dependent path splitting.
|
||||
#
|
||||
# Note that in order to support legacy alembic.ini files, this default does NOT
|
||||
# take place if path_separator is not present in alembic.ini. If this
|
||||
# option is omitted entirely, fallback logic is as follows:
|
||||
#
|
||||
# 1. Parsing of the version_locations option falls back to using the legacy
|
||||
# "version_path_separator" key, which if absent then falls back to the legacy
|
||||
# behavior of splitting on spaces and/or commas.
|
||||
# 2. Parsing of the prepend_sys_path option falls back to the legacy
|
||||
# behavior of splitting on spaces, commas, or colons.
|
||||
#
|
||||
# Valid values for path_separator are:
|
||||
#
|
||||
# path_separator = :
|
||||
# path_separator = ;
|
||||
# path_separator = space
|
||||
# path_separator = newline
|
||||
#
|
||||
# Use os.pathsep. Default configuration used for new projects.
|
||||
path_separator = os
|
||||
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
# database URL. This is consumed by the user-maintained env.py script only.
|
||||
# other means of configuring database URLs may be customized within the env.py
|
||||
# file.
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
|
||||
# hooks = ruff
|
||||
# ruff.type = module
|
||||
# ruff.module = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Alternatively, use the exec runner to execute a binary found on your PATH
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration. This is also consumed by the user-maintained
|
||||
# env.py script only.
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARNING
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
|
||||
[settings]
|
||||
# This section is used to set environment variables for the alembic.
|
||||
# The env option is used to specify the environment for the alembic.
|
||||
# It can be set to dev or prod.
|
||||
env = dev
|
||||
@@ -0,0 +1 @@
|
||||
Generic single-database configuration with an async dbapi.
|
||||
@@ -0,0 +1,125 @@
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import Iterable
|
||||
from logging.config import fileConfig
|
||||
from typing import Optional, Union
|
||||
|
||||
from alembic import context
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations.ops import MigrationScript
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
|
||||
from config.database import ASYNC_SQLALCHEMY_DATABASE_URL, Base
|
||||
from utils.import_util import ImportUtil
|
||||
|
||||
# 判断vesrions目录是否存在,如果不存在则创建
|
||||
alembic_veresions_path = 'alembic/versions'
|
||||
if not os.path.exists(alembic_veresions_path):
|
||||
os.makedirs(alembic_veresions_path)
|
||||
|
||||
|
||||
# 自动查找所有模型
|
||||
found_models = ImportUtil.find_models(Base)
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
alembic_config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if alembic_config.config_file_name is not None:
|
||||
fileConfig(alembic_config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
target_metadata = Base.metadata
|
||||
# ASYNC_SQLALCHEMY_DATABASE_URL = 'mysql+asyncmy://root:mysqlroot@127.0.0.1:3306/ruoyi-fastapi'
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
alembic_config.set_main_option('sqlalchemy.url', ASYNC_SQLALCHEMY_DATABASE_URL)
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = alembic_config.get_main_option('sqlalchemy.url')
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={'paramstyle': 'named'},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def do_run_migrations(connection: Connection) -> None:
|
||||
def process_revision_directives(
|
||||
context: MigrationContext,
|
||||
revision: Union[str, Iterable[Optional[str]], Iterable[str]],
|
||||
directives: list[MigrationScript],
|
||||
) -> None:
|
||||
script = directives[0]
|
||||
|
||||
# 检查所有操作集是否为空
|
||||
all_empty = all(ops.is_empty() for ops in script.upgrade_ops_list)
|
||||
|
||||
if all_empty:
|
||||
# 如果没有实际变更,不生成迁移文件
|
||||
directives[:] = []
|
||||
print('❎️ 未检测到模型变更,不生成迁移文件')
|
||||
else:
|
||||
print('✅️ 检测到模型变更,生成迁移文件')
|
||||
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True,
|
||||
compare_server_default=True,
|
||||
transaction_per_migration=True,
|
||||
process_revision_directives=process_revision_directives,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_async_migrations() -> None:
|
||||
"""In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
connectable = async_engine_from_config(
|
||||
alembic_config.get_section(alembic_config.config_ini_section, {}),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode."""
|
||||
|
||||
asyncio.run(run_async_migrations())
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -0,0 +1,28 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -1,6 +1,9 @@
|
||||
import uvicorn
|
||||
from server import app, AppConfig # noqa: F401
|
||||
|
||||
from config.env import AppConfig
|
||||
from server import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
import inspect
|
||||
import json
|
||||
import time
|
||||
from collections.abc import Awaitable
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Literal, Optional, TypeVar
|
||||
|
||||
import httpx
|
||||
from async_lru import alru_cache
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette.status import HTTP_200_OK
|
||||
from typing_extensions import ParamSpec
|
||||
from user_agents import parse
|
||||
|
||||
from common.context import RequestContext
|
||||
from common.enums import BusinessType
|
||||
from config.env import AppConfig
|
||||
from exceptions.exception import LoginException, ServiceException, ServiceWarning
|
||||
from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel
|
||||
from module_admin.service.log_service import LoginLogService, OperationLogService
|
||||
from utils.dependency_util import DependencyUtil
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
P = ParamSpec('P')
|
||||
R = TypeVar('R')
|
||||
|
||||
|
||||
class Log:
|
||||
"""
|
||||
日志装饰器
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
business_type: BusinessType,
|
||||
log_type: Optional[Literal['login', 'operation']] = 'operation',
|
||||
) -> None:
|
||||
"""
|
||||
日志装饰器
|
||||
|
||||
:param title: 当前日志装饰器装饰的模块标题
|
||||
:param business_type: 业务类型(OTHER其它 INSERT新增 UPDATE修改 DELETE删除 GRANT授权 EXPORT导出 IMPORT导入 FORCE强退 GENCODE生成代码 CLEAN清空数据)
|
||||
:param log_type: 日志类型(login表示登录日志,operation表示为操作日志)
|
||||
:return:
|
||||
"""
|
||||
self.title = title
|
||||
self.business_type = business_type.value
|
||||
self.log_type = log_type
|
||||
self._oper_param_len = 2000
|
||||
|
||||
def __call__(self, func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
|
||||
@wraps(func)
|
||||
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
start_time = time.perf_counter()
|
||||
# 获取当前被装饰函数所在路径
|
||||
func_path = self._get_decorator_func_path(func)
|
||||
# 获取上下文信息
|
||||
request_name_list = get_function_parameters_name_by_type(func, Request)
|
||||
request = get_function_parameters_value_by_name(func, request_name_list[0], *args, **kwargs)
|
||||
DependencyUtil.check_exclude_routes(request, err_msg='当前路由不在认证规则内,不可使用Log装饰器')
|
||||
session_name_list = get_function_parameters_name_by_type(func, AsyncSession)
|
||||
query_db = get_function_parameters_value_by_name(func, session_name_list[0], *args, **kwargs)
|
||||
request_method = request.method
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
# 获取操作类型
|
||||
operator_type = self._get_oper_type(user_agent)
|
||||
# 获取请求的url
|
||||
oper_url = request.url.path
|
||||
# 获取请求ip
|
||||
oper_ip = request.headers.get('X-Forwarded-For')
|
||||
# 获取请求ip归属区域
|
||||
oper_location = await self._get_oper_location(oper_ip)
|
||||
# 获取请求参数
|
||||
oper_param = await self._get_request_params(request)
|
||||
# 日志表请求参数字段长度最大为2000,因此在此处判断长度
|
||||
if len(oper_param) > self._oper_param_len:
|
||||
oper_param = '请求参数过长'
|
||||
|
||||
# 获取操作时间
|
||||
oper_time = datetime.now()
|
||||
# 此处在登录之前向原始函数传递一些登录信息,用于监测在线用户的相关信息
|
||||
login_log = self._get_login_log(user_agent, oper_ip, oper_location, oper_time, kwargs)
|
||||
try:
|
||||
# 调用原始函数
|
||||
result = await func(*args, **kwargs)
|
||||
except (LoginException, ServiceWarning) as e:
|
||||
logger.warning(e.message)
|
||||
result = ResponseUtil.failure(data=e.data, msg=e.message)
|
||||
except ServiceException as e:
|
||||
logger.error(e.message)
|
||||
result = ResponseUtil.error(data=e.data, msg=e.message)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
result = ResponseUtil.error(msg=str(e))
|
||||
# 获取请求耗时
|
||||
cost_time = float(time.perf_counter() - start_time) * 1000
|
||||
# 判断请求是否来自api文档
|
||||
request_from_swagger, request_from_redoc = self._is_request_from_swagger_or_redoc(request)
|
||||
# 根据响应结果的类型使用不同的方法获取响应结果参数
|
||||
result_dict = self._get_result_dict(result, request_from_swagger, request_from_redoc)
|
||||
json_result = json.dumps(result_dict, ensure_ascii=False)
|
||||
# 根据响应结果获取响应状态及异常信息
|
||||
status, error_msg = self._get_status_and_error_msg(result_dict)
|
||||
# 根据日志类型向对应的日志表插入数据
|
||||
if self.log_type == 'login':
|
||||
# 登录请求来自于api文档时不记录登录日志,其余情况则记录
|
||||
if request_from_swagger or request_from_redoc:
|
||||
pass
|
||||
else:
|
||||
user = kwargs.get('form_data')
|
||||
login_log.update(
|
||||
{
|
||||
'loginTime': oper_time,
|
||||
'userName': user.username,
|
||||
'status': str(status),
|
||||
'msg': result_dict.get('msg'),
|
||||
}
|
||||
)
|
||||
|
||||
await LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log))
|
||||
else:
|
||||
current_user = RequestContext.get_current_user()
|
||||
oper_name = current_user.user.user_name
|
||||
dept_name = current_user.user.dept.dept_name if current_user.user.dept else None
|
||||
operation_log = OperLogModel(
|
||||
title=self.title,
|
||||
businessType=self.business_type,
|
||||
method=func_path,
|
||||
requestMethod=request_method,
|
||||
operatorType=operator_type,
|
||||
operName=oper_name,
|
||||
deptName=dept_name,
|
||||
operUrl=oper_url,
|
||||
operIp=oper_ip,
|
||||
operLocation=oper_location,
|
||||
operParam=oper_param,
|
||||
jsonResult=json_result,
|
||||
status=status,
|
||||
errorMsg=error_msg,
|
||||
operTime=oper_time,
|
||||
costTime=int(cost_time),
|
||||
)
|
||||
await OperationLogService.add_operation_log_services(query_db, operation_log)
|
||||
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
def _get_decorator_func_path(self, func: Callable) -> str:
|
||||
"""
|
||||
获取被装饰函数所在路径
|
||||
|
||||
:param func: 被装饰函数
|
||||
:return: 被装饰函数所在路径
|
||||
"""
|
||||
# 获取被装饰函数所在的模块
|
||||
module = inspect.getmodule(func)
|
||||
# 获取完整模块路径
|
||||
module_path = module.__name__ if module else ''
|
||||
# 获取当前被装饰函数所在路径
|
||||
func_path = f'{module_path}.{func.__name__}()'
|
||||
|
||||
return func_path
|
||||
|
||||
def _get_oper_type(self, user_agent: Any) -> int:
|
||||
"""
|
||||
获取操作类型
|
||||
|
||||
:param user_agent: 用户代理字符串
|
||||
:return: 操作类型
|
||||
"""
|
||||
operator_type = 0
|
||||
if 'Windows' in user_agent or 'Macintosh' in user_agent or 'Linux' in user_agent:
|
||||
operator_type = 1
|
||||
if 'Mobile' in user_agent or 'Android' in user_agent or 'iPhone' in user_agent:
|
||||
operator_type = 2
|
||||
|
||||
return operator_type
|
||||
|
||||
async def _get_oper_location(self, oper_ip: str) -> str:
|
||||
"""
|
||||
获取请求IP归属区域
|
||||
|
||||
:param oper_ip: 请求IP
|
||||
:return: 请求IP归属区域
|
||||
"""
|
||||
oper_location = '内网IP'
|
||||
if AppConfig.app_ip_location_query:
|
||||
oper_location = await get_ip_location(oper_ip)
|
||||
|
||||
return oper_location
|
||||
|
||||
async def _get_request_params(self, request: Request) -> str:
|
||||
"""
|
||||
获取请求参数
|
||||
|
||||
:param request: Request对象
|
||||
:return: 格式化后的请求参数字符串
|
||||
"""
|
||||
params = {}
|
||||
|
||||
# 路径和查询参数
|
||||
path_params = dict(request.path_params)
|
||||
query_params = dict(request.query_params)
|
||||
params.update({k: v for k, v in {'path_params': path_params, 'query_params': query_params}.items() if v})
|
||||
|
||||
# 请求体处理
|
||||
content_type = request.headers.get('Content-Type', '')
|
||||
|
||||
# JSON请求
|
||||
if 'application/json' in content_type:
|
||||
json_body = await request.json()
|
||||
if json_body:
|
||||
params['json_body'] = json_body
|
||||
|
||||
# 表单数据
|
||||
elif 'multipart/form-data' in content_type or 'application/x-www-form-urlencoded' in content_type:
|
||||
form_data = await request.form()
|
||||
if form_data:
|
||||
# 过滤掉文件对象,只保留普通表单字段
|
||||
form_dict = {key: value for key, value in form_data.items() if not hasattr(value, 'filename')}
|
||||
if form_dict:
|
||||
params['form_data'] = form_dict
|
||||
|
||||
# 仅在multipart时尝试处理文件
|
||||
if 'multipart/form-data' in content_type:
|
||||
file_info = {}
|
||||
for key, value in form_data.items():
|
||||
if hasattr(value, 'filename'):
|
||||
file_info[key] = {
|
||||
'filename': value.filename,
|
||||
'content_type': value.content_type,
|
||||
'size': value.size,
|
||||
'headers': dict(value.headers),
|
||||
}
|
||||
if file_info:
|
||||
params['files'] = file_info
|
||||
|
||||
# 其他文本请求
|
||||
elif 'application/octet-stream' not in content_type:
|
||||
body = await request.body()
|
||||
if body:
|
||||
params['raw_body'] = body.decode('utf-8')
|
||||
|
||||
return json.dumps(params, ensure_ascii=False, indent=2) if params else ''
|
||||
|
||||
def _get_login_log(
|
||||
self, user_agent: Any, oper_ip: str, oper_location: str, oper_time: datetime, origin_kwargs: dict
|
||||
) -> dict:
|
||||
"""
|
||||
获取登录日志信息
|
||||
|
||||
:param user_agent: 用户代理字符串
|
||||
:param oper_ip: 操作ip
|
||||
:param oper_location: 操作区域
|
||||
:param oper_time: 操作时间
|
||||
:param origin_kwargs: 原始函数参数
|
||||
:return: 登录日志信息
|
||||
"""
|
||||
login_log = {}
|
||||
if self.log_type == 'login':
|
||||
user_agent_info = parse(user_agent)
|
||||
browser = f'{user_agent_info.browser.family}'
|
||||
system_os = f'{user_agent_info.os.family}'
|
||||
if user_agent_info.browser.version != ():
|
||||
browser += f' {user_agent_info.browser.version[0]}'
|
||||
if user_agent_info.os.version != ():
|
||||
system_os += f' {user_agent_info.os.version[0]}'
|
||||
login_log = {
|
||||
'ipaddr': oper_ip,
|
||||
'loginLocation': oper_location,
|
||||
'browser': browser,
|
||||
'os': system_os,
|
||||
'loginTime': oper_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
}
|
||||
self._set_login_data(login_log, origin_kwargs)
|
||||
|
||||
return login_log
|
||||
|
||||
def _set_login_data(self, login_log: dict, origin_kwargs: dict) -> None:
|
||||
"""
|
||||
设置登录日志数据
|
||||
|
||||
:param login_log: 登录日志信息
|
||||
:param origin_kwargs: 原始函数参数
|
||||
:return: None
|
||||
"""
|
||||
if 'form_data' in origin_kwargs:
|
||||
origin_kwargs['form_data'].login_info = login_log
|
||||
|
||||
def _get_status_and_error_msg(self, result_dict: dict) -> tuple[int, str]:
|
||||
"""
|
||||
获取操作状态和错误信息
|
||||
|
||||
:param result_dict: 操作结果字典
|
||||
:return: 操作状态和错误信息元组
|
||||
"""
|
||||
status = 1
|
||||
error_msg = ''
|
||||
if result_dict.get('code') == HTTP_200_OK:
|
||||
status = 0
|
||||
else:
|
||||
error_msg = result_dict.get('msg')
|
||||
|
||||
return status, error_msg
|
||||
|
||||
def _is_request_from_swagger_or_redoc(self, request: Request) -> tuple[bool, bool]:
|
||||
"""
|
||||
判断请求是否来自swagger或redoc
|
||||
|
||||
:param request: Request对象
|
||||
:return: 是否来自swagger请求和是否来自redoc请求元组
|
||||
"""
|
||||
request_from_swagger = (
|
||||
request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
|
||||
)
|
||||
request_from_redoc = (
|
||||
request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False
|
||||
)
|
||||
|
||||
return request_from_swagger, request_from_redoc
|
||||
|
||||
def _get_result_dict(self, result: Any, request_from_swagger: bool, request_from_redoc: bool) -> dict:
|
||||
"""
|
||||
获取操作结果字典
|
||||
|
||||
:param result: 操作结果
|
||||
:param request_from_swagger: 是否来自swagger请求
|
||||
:param request_from_redoc: 是否来自redoc请求
|
||||
:return: 操作结果字典
|
||||
"""
|
||||
if isinstance(result, (JSONResponse, ORJSONResponse, UJSONResponse)):
|
||||
result_dict = json.loads(str(result.body, 'utf-8'))
|
||||
elif request_from_swagger or request_from_redoc:
|
||||
result_dict = {}
|
||||
elif result.status_code == HTTP_200_OK:
|
||||
result_dict = {'code': result.status_code, 'message': '获取成功'}
|
||||
else:
|
||||
result_dict = {'code': result.status_code, 'message': '获取失败'}
|
||||
|
||||
return result_dict
|
||||
|
||||
|
||||
@alru_cache()
|
||||
async def get_ip_location(oper_ip: str) -> str:
|
||||
"""
|
||||
查询ip归属区域
|
||||
|
||||
:param oper_ip: 需要查询的ip
|
||||
:return: ip归属区域
|
||||
"""
|
||||
oper_location = '内网IP'
|
||||
try:
|
||||
if oper_ip not in ['127.0.0.1', 'localhost']:
|
||||
oper_location = '未知'
|
||||
async with httpx.AsyncClient() as client:
|
||||
ip_result = await client.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}')
|
||||
if ip_result.status_code == HTTP_200_OK:
|
||||
prov = ip_result.json().get('data', {}).get('prov')
|
||||
city = ip_result.json().get('data', {}).get('city')
|
||||
if prov or city:
|
||||
oper_location = f'{prov}-{city}'
|
||||
except Exception as e:
|
||||
oper_location = '未知'
|
||||
print(e)
|
||||
return oper_location
|
||||
|
||||
|
||||
def get_function_parameters_name_by_type(func: Callable, param_type: Any) -> list:
|
||||
"""
|
||||
获取函数指定类型的参数名称
|
||||
|
||||
:param func: 函数
|
||||
:param arg_type: 参数类型
|
||||
:return: 函数指定类型的参数名称
|
||||
"""
|
||||
# 获取函数的参数信息
|
||||
parameters = inspect.signature(func).parameters
|
||||
# 找到指定类型的参数名称
|
||||
parameters_name_list = []
|
||||
# 遍历所有参数
|
||||
for name, param in parameters.items():
|
||||
# 处理参数注解
|
||||
annotation = param.annotation
|
||||
# 检查参数类型是否匹配
|
||||
# 1. 直接匹配
|
||||
# 2. 检查是否为Annotated类型(通过类型名称判断)
|
||||
if annotation == param_type or (
|
||||
hasattr(annotation, '__class__')
|
||||
and annotation.__class__.__name__ == '_AnnotatedAlias'
|
||||
and annotation.__origin__ == param_type
|
||||
):
|
||||
parameters_name_list.append(name)
|
||||
return parameters_name_list
|
||||
|
||||
|
||||
def get_function_parameters_value_by_name(func: Callable, name: str, *args, **kwargs) -> Any:
|
||||
"""
|
||||
获取函数指定参数的值
|
||||
|
||||
:param func: 函数
|
||||
:param name: 参数名
|
||||
:return: 参数值
|
||||
"""
|
||||
# 获取参数值
|
||||
bound_parameters = inspect.signature(func).bind(*args, **kwargs)
|
||||
bound_parameters.apply_defaults()
|
||||
parameters_value = bound_parameters.arguments.get(name)
|
||||
|
||||
return parameters_value
|
||||
+19
-14
@@ -1,18 +1,23 @@
|
||||
import inspect
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
from fastapi import Form, Query
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import FieldInfo
|
||||
from typing import Type
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
BaseModelVar = TypeVar('BaseModelVar', bound=BaseModel)
|
||||
|
||||
|
||||
def as_query(cls: Type[BaseModel]):
|
||||
def as_query(cls: type[BaseModelVar]) -> type[BaseModelVar]:
|
||||
"""
|
||||
pydantic模型查询参数装饰器,将pydantic模型用于接收查询参数
|
||||
"""
|
||||
new_parameters = []
|
||||
|
||||
for field_name, model_field in cls.model_fields.items():
|
||||
model_field: FieldInfo # type: ignore
|
||||
for model_field in cls.model_fields.values():
|
||||
model_field: FieldInfo
|
||||
|
||||
if not model_field.is_required():
|
||||
new_parameters.append(
|
||||
@@ -33,24 +38,24 @@ def as_query(cls: Type[BaseModel]):
|
||||
)
|
||||
)
|
||||
|
||||
async def as_query_func(**data):
|
||||
async def as_query_func(**data) -> type[BaseModelVar]:
|
||||
return cls(**data)
|
||||
|
||||
sig = inspect.signature(as_query_func)
|
||||
sig = sig.replace(parameters=new_parameters)
|
||||
as_query_func.__signature__ = sig # type: ignore
|
||||
setattr(cls, 'as_query', as_query_func)
|
||||
as_query_func.__signature__ = sig
|
||||
cls.as_query = as_query_func
|
||||
return cls
|
||||
|
||||
|
||||
def as_form(cls: Type[BaseModel]):
|
||||
def as_form(cls: type[BaseModelVar]) -> type[BaseModelVar]:
|
||||
"""
|
||||
pydantic模型表单参数装饰器,将pydantic模型用于接收表单参数
|
||||
"""
|
||||
new_parameters = []
|
||||
|
||||
for field_name, model_field in cls.model_fields.items():
|
||||
model_field: FieldInfo # type: ignore
|
||||
for model_field in cls.model_fields.values():
|
||||
model_field: FieldInfo
|
||||
|
||||
if not model_field.is_required():
|
||||
new_parameters.append(
|
||||
@@ -71,11 +76,11 @@ def as_form(cls: Type[BaseModel]):
|
||||
)
|
||||
)
|
||||
|
||||
async def as_form_func(**data):
|
||||
async def as_form_func(**data) -> type[BaseModelVar]:
|
||||
return cls(**data)
|
||||
|
||||
sig = inspect.signature(as_form_func)
|
||||
sig = sig.replace(parameters=new_parameters)
|
||||
as_form_func.__signature__ = sig # type: ignore
|
||||
setattr(cls, 'as_form', as_form_func)
|
||||
as_form_func.__signature__ = sig
|
||||
cls.as_form = as_form_func
|
||||
return cls
|
||||
@@ -0,0 +1,114 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Depends, Request, params
|
||||
from sqlalchemy import ColumnElement, func, or_, select
|
||||
|
||||
from common.context import RequestContext
|
||||
from config.database import Base
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.role_do import SysRoleDept
|
||||
from utils.dependency_util import DependencyUtil
|
||||
|
||||
|
||||
class GetDataScope:
|
||||
"""
|
||||
获取当前用户数据权限对应的查询sql语句
|
||||
"""
|
||||
|
||||
DATA_SCOPE_ALL = '1'
|
||||
DATA_SCOPE_CUSTOM = '2'
|
||||
DATA_SCOPE_DEPT = '3'
|
||||
DATA_SCOPE_DEPT_AND_CHILD = '4'
|
||||
DATA_SCOPE_SELF = '5'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
query_alias: Base,
|
||||
user_alias: Optional[str] = 'user_id',
|
||||
dept_alias: Optional[str] = 'dept_id',
|
||||
) -> None:
|
||||
"""
|
||||
获取当前用户数据权限对应的查询sql语句
|
||||
|
||||
:param query_alias: 所要查询表对应的sqlalchemy模型类,不可为空
|
||||
:param user_alias: 用户id字段别名,默认为'user_id'
|
||||
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
||||
"""
|
||||
self.query_alias = query_alias
|
||||
self.user_alias = user_alias
|
||||
self.dept_alias = dept_alias
|
||||
|
||||
def __call__(self, request: Request) -> ColumnElement:
|
||||
DependencyUtil.check_exclude_routes(request, err_msg='当前路由不在认证规则内,不可使用GetDataScope依赖项')
|
||||
current_user = RequestContext.get_current_user()
|
||||
user_id = current_user.user.user_id
|
||||
dept_id = current_user.user.dept_id
|
||||
custom_data_scope_role_id_list = [
|
||||
item.role_id for item in current_user.user.role if item.data_scope == self.DATA_SCOPE_CUSTOM
|
||||
]
|
||||
param_sql_list = []
|
||||
for role in current_user.user.role:
|
||||
if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL:
|
||||
param_sql_list = [True]
|
||||
break
|
||||
if role.data_scope == self.DATA_SCOPE_CUSTOM:
|
||||
if len(custom_data_scope_role_id_list) > 1:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias).in_(
|
||||
select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_(custom_data_scope_role_id_list))
|
||||
)
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
else:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias).in_(
|
||||
select(SysRoleDept.dept_id).where(SysRoleDept.role_id == role.role_id)
|
||||
)
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_DEPT:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias) == dept_id
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias).in_(
|
||||
select(SysDept.dept_id).where(
|
||||
or_(SysDept.dept_id == dept_id, func.find_in_set(dept_id, SysDept.ancestors))
|
||||
)
|
||||
)
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_SELF:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.user_alias) == user_id
|
||||
if hasattr(self.query_alias, self.user_alias)
|
||||
else False
|
||||
)
|
||||
else:
|
||||
param_sql_list.append(False)
|
||||
param_sql_list = list(dict.fromkeys(param_sql_list))
|
||||
param_sql = or_(*param_sql_list)
|
||||
|
||||
return param_sql
|
||||
|
||||
|
||||
def DataScopeDependency( # noqa: N802
|
||||
query_alias: Base,
|
||||
user_alias: Optional[str] = 'user_id',
|
||||
dept_alias: Optional[str] = 'dept_id',
|
||||
) -> params.Depends:
|
||||
"""
|
||||
当前用户数据权限依赖
|
||||
|
||||
:param query_alias: 所要查询表对应的sqlalchemy模型类,不可为空
|
||||
:param user_alias: 用户id字段别名,默认为'user_id'
|
||||
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
||||
:return: 当前用户数据权限依赖
|
||||
"""
|
||||
return Depends(GetDataScope(query_alias, user_alias, dept_alias))
|
||||
@@ -0,0 +1,12 @@
|
||||
from fastapi import Depends, params
|
||||
|
||||
from config.get_db import get_db
|
||||
|
||||
|
||||
def DBSessionDependency() -> params.Depends: # noqa: N802
|
||||
"""
|
||||
数据库会话依赖
|
||||
|
||||
:return: 数据库会话依赖
|
||||
"""
|
||||
return Depends(get_db)
|
||||
@@ -0,0 +1,96 @@
|
||||
from typing import Union
|
||||
|
||||
from fastapi import Depends, Request, params
|
||||
|
||||
from common.context import RequestContext
|
||||
from exceptions.exception import PermissionException
|
||||
from utils.dependency_util import DependencyUtil
|
||||
|
||||
|
||||
class CheckUserInterfaceAuth:
|
||||
"""
|
||||
校验当前用户是否具有相应的接口权限
|
||||
"""
|
||||
|
||||
def __init__(self, perm: Union[str, list], is_strict: bool = False) -> None:
|
||||
"""
|
||||
校验当前用户是否具有相应的接口权限
|
||||
|
||||
:param perm: 权限标识
|
||||
:param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过
|
||||
"""
|
||||
self.perm = perm
|
||||
self.is_strict = is_strict
|
||||
|
||||
def __call__(self, request: Request) -> bool:
|
||||
DependencyUtil.check_exclude_routes(
|
||||
request, err_msg='当前路由不在认证规则内,不可使用CheckUserInterfaceAuth依赖项'
|
||||
)
|
||||
current_user = RequestContext.get_current_user()
|
||||
user_auth_list = current_user.permissions
|
||||
if '*:*:*' in user_auth_list:
|
||||
return True
|
||||
if isinstance(self.perm, str) and self.perm in user_auth_list:
|
||||
return True
|
||||
if isinstance(self.perm, list):
|
||||
if self.is_strict:
|
||||
if all(perm_str in user_auth_list for perm_str in self.perm):
|
||||
return True
|
||||
elif any(perm_str in user_auth_list for perm_str in self.perm):
|
||||
return True
|
||||
raise PermissionException(data='', message='该用户无此接口权限')
|
||||
|
||||
|
||||
class CheckRoleInterfaceAuth:
|
||||
"""
|
||||
根据角色校验当前用户是否具有相应的接口权限
|
||||
"""
|
||||
|
||||
def __init__(self, role_key: Union[str, list], is_strict: bool = False) -> None:
|
||||
"""
|
||||
根据角色校验当前用户是否具有相应的接口权限
|
||||
|
||||
:param role_key: 角色标识
|
||||
:param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过
|
||||
"""
|
||||
self.role_key = role_key
|
||||
self.is_strict = is_strict
|
||||
|
||||
def __call__(self, request: Request) -> bool:
|
||||
DependencyUtil.check_exclude_routes(
|
||||
request, err_msg='当前路由不在认证规则内,不可使用CheckRoleInterfaceAuth依赖项'
|
||||
)
|
||||
current_user = RequestContext.get_current_user()
|
||||
user_role_list = current_user.user.role
|
||||
user_role_key_list = [role.role_key for role in user_role_list]
|
||||
if isinstance(self.role_key, str) and self.role_key in user_role_key_list:
|
||||
return True
|
||||
if isinstance(self.role_key, list):
|
||||
if self.is_strict:
|
||||
if all(role_key_str in user_role_key_list for role_key_str in self.role_key):
|
||||
return True
|
||||
elif any(role_key_str in user_role_key_list for role_key_str in self.role_key):
|
||||
return True
|
||||
raise PermissionException(data='', message='该用户无此接口权限')
|
||||
|
||||
|
||||
def UserInterfaceAuthDependency(perm: Union[str, list], is_strict: bool = False) -> params.Depends: # noqa: N802
|
||||
"""
|
||||
根据权限标识校验当前用户接口权限依赖
|
||||
|
||||
:param perm: 权限标识
|
||||
:param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过
|
||||
:return: 根据权限标识校验当前用户接口权限依赖
|
||||
"""
|
||||
return Depends(CheckUserInterfaceAuth(perm, is_strict))
|
||||
|
||||
|
||||
def RoleInterfaceAuthDependency(role_key: Union[str, list], is_strict: bool = False) -> params.Depends: # noqa: N802
|
||||
"""
|
||||
根据角色校验当前用户接口权限依赖
|
||||
|
||||
:param role_key: 角色标识
|
||||
:param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过
|
||||
:return: 根据角色校验当前用户接口权限依赖
|
||||
"""
|
||||
return Depends(CheckRoleInterfaceAuth(role_key, is_strict))
|
||||
@@ -0,0 +1,146 @@
|
||||
import re
|
||||
from typing import Literal, Optional, TypedDict, Union
|
||||
|
||||
from fastapi import Depends, Request, params
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.context import RequestContext
|
||||
from config.env import AppConfig
|
||||
from config.get_db import get_db
|
||||
from exceptions.exception import AuthException
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
|
||||
|
||||
# 定义排除路由的字典结构
|
||||
class ExcludeRoute(TypedDict, total=False):
|
||||
"""
|
||||
排除路由的字典结构
|
||||
|
||||
:param path: 路由路径(必填)
|
||||
:param methods: HTTP方法列表,空列表表示所有方法(可选,默认为[])
|
||||
:param ignore_paths: 需要忽略的特定路径列表,即使匹配通配符也不排除(可选,默认为[])
|
||||
"""
|
||||
|
||||
path: str
|
||||
methods: list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']]
|
||||
ignore_paths: list[str]
|
||||
|
||||
|
||||
# 创建OAuth2PasswordBearer对象
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/login')
|
||||
|
||||
|
||||
class PreAuth:
|
||||
"""
|
||||
登录认证前置校验依赖类
|
||||
"""
|
||||
|
||||
def __init__(self, exclude_routes: Optional[list[ExcludeRoute]] = None) -> None:
|
||||
"""
|
||||
初始化登录认证前置校验依赖
|
||||
|
||||
:param exclude_routes: 需要排除的路由列表,格式为:
|
||||
[{'path': '/path1', 'methods': ['GET', 'POST']}, {'path': '/path2/{param}', 'methods': ['GET']}]
|
||||
methods 可以是字符串或列表,空列表表示所有方法
|
||||
"""
|
||||
self.exclude_routes = exclude_routes or []
|
||||
# 编译排除路径为正则表达式模式,并存储方法信息
|
||||
self.exclude_patterns = []
|
||||
|
||||
for route in self.exclude_routes:
|
||||
# 使用TypedDict,确保路由字典包含path字段
|
||||
path = route.get('path', '')
|
||||
methods = route.get('methods', [])
|
||||
ignore_paths = route.get('ignore_paths', [])
|
||||
|
||||
# 编译路径为正则表达式
|
||||
pattern = self._compile_path_pattern(path)
|
||||
# 存储编译后的模式和方法信息
|
||||
self.exclude_patterns.append(
|
||||
{
|
||||
'pattern': pattern,
|
||||
'methods': [method.upper() for method in methods],
|
||||
'original_path': path,
|
||||
'ignore_paths': ignore_paths,
|
||||
}
|
||||
)
|
||||
|
||||
def _compile_path_pattern(self, path: str) -> re.Pattern:
|
||||
"""
|
||||
将FastAPI路径转换为正则表达式模式
|
||||
|
||||
:param path: FastAPI路径(如 /configKey/{config_key})
|
||||
:return: 编译后的正则表达式模式
|
||||
"""
|
||||
# 将FastAPI路径参数转换为正则表达式
|
||||
# 例如:/configKey/{config_key} -> /configKey/[^/]+
|
||||
pattern_str = re.sub(r'\{[^}]+\}', r'[^/]+', path)
|
||||
# 添加开始和结束锚点,确保精确匹配
|
||||
return re.compile(f'^{pattern_str}$')
|
||||
|
||||
async def __call__(self, request: Request, db: AsyncSession = Depends(get_db)) -> Union[CurrentUserModel, None]:
|
||||
"""
|
||||
执行登录认证校验
|
||||
|
||||
:param request: 当前请求对象
|
||||
:param db: 数据库会话
|
||||
:return: 当前用户信息
|
||||
"""
|
||||
# 获取当前请求路径和方法
|
||||
path = request.url.path
|
||||
method = request.method.upper()
|
||||
|
||||
# 从配置中获取APP_ROOT_PATH
|
||||
app_root_path = AppConfig.app_root_path
|
||||
|
||||
# 去掉APP_ROOT_PATH前缀
|
||||
if app_root_path and path.startswith(app_root_path):
|
||||
path = path[len(app_root_path) :]
|
||||
|
||||
# 设置上下文变量
|
||||
RequestContext.set_current_exclude_patterns(self.exclude_patterns)
|
||||
|
||||
# 检查路径和方法是否匹配排除模式
|
||||
for item in self.exclude_patterns:
|
||||
pattern = item['pattern']
|
||||
exclude_methods = item['methods']
|
||||
ignore_paths = item['ignore_paths']
|
||||
|
||||
# 检查当前路径是否在忽略列表中
|
||||
if path in ignore_paths:
|
||||
continue
|
||||
|
||||
# 检查路径是否匹配,并且methods为空列表(匹配所有方法)或者当前方法在允许列表中
|
||||
if pattern.match(path) and (not exclude_methods or method in exclude_methods):
|
||||
# 跳过认证
|
||||
return None
|
||||
|
||||
# 否则执行正常认证
|
||||
token = request.headers.get('Authorization')
|
||||
if not token:
|
||||
raise AuthException(data='', message='用户未登录,请先完成登录')
|
||||
current_user = await LoginService.get_current_user(request, token, db)
|
||||
return current_user
|
||||
|
||||
|
||||
def PreAuthDependency(exclude_routes: Optional[list[ExcludeRoute]] = None) -> params.Depends: # noqa: N802
|
||||
"""
|
||||
登录认证前置校验依赖
|
||||
|
||||
:param exclude_routes: 需要排除的路由列表,格式为:
|
||||
[{'path': '/path1', 'methods': ['GET', 'POST']}, {'path': '/path2/{param}', 'methods': ['GET']}]
|
||||
methods 可以是字符串或列表,空列表表示所有方法
|
||||
:return: 登录认证前置校验依赖
|
||||
"""
|
||||
return Depends(PreAuth(exclude_routes))
|
||||
|
||||
|
||||
def CurrentUserDependency() -> params.Depends: # noqa: N802
|
||||
"""
|
||||
当前登录用户信息依赖
|
||||
|
||||
:return: 当前登录用户信息依赖
|
||||
"""
|
||||
return Depends(LoginService.get_current_user)
|
||||
@@ -5,6 +5,7 @@ class CommonConstant:
|
||||
"""
|
||||
常用常量
|
||||
|
||||
PASSWORD_ERROR_COUNT: 密码错误次数
|
||||
WWW: www主域
|
||||
HTTP: http请求
|
||||
HTTPS: https请求
|
||||
@@ -19,6 +20,7 @@ class CommonConstant:
|
||||
NOT_UNIQUE: 校验是否唯一的返回标识(否)
|
||||
"""
|
||||
|
||||
PASSWORD_ERROR_COUNT = 5
|
||||
WWW = 'www.'
|
||||
HTTP = 'http://'
|
||||
HTTPS = 'https://'
|
||||
@@ -252,6 +254,8 @@ class GenConstant:
|
||||
'double',
|
||||
'decimal',
|
||||
]
|
||||
COLUMNNAME_NOT_ADD_SHOW = ['create_by', 'create_time']
|
||||
COLUMNNAME_NOT_EDIT_SHOW = ['update_by', 'update_time']
|
||||
COLUMNNAME_NOT_EDIT = ['id', 'create_by', 'create_time', 'del_flag']
|
||||
COLUMNNAME_NOT_LIST = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time']
|
||||
COLUMNNAME_NOT_QUERY = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time', 'remark']
|
||||
@@ -0,0 +1,100 @@
|
||||
import re
|
||||
from contextvars import ContextVar, Token
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from exceptions.exception import LoginException
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
|
||||
# 定义上下文变量
|
||||
# 存储当前请求的编译后的排除路由模式列表
|
||||
current_exclude_patterns: ContextVar[
|
||||
Optional[
|
||||
list[
|
||||
dict[str, Union[str, list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']], re.Pattern]]
|
||||
]
|
||||
]
|
||||
] = ContextVar('current_exclude_patterns', default=None)
|
||||
# 存储当前用户信息
|
||||
current_user: ContextVar[Optional[CurrentUserModel]] = ContextVar('current_user', default=None)
|
||||
|
||||
|
||||
class RequestContext:
|
||||
"""
|
||||
请求上下文管理类,用于设置和清理上下文变量
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def set_current_exclude_patterns(
|
||||
exclude_patterns: list[
|
||||
dict[str, Union[str, list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']], re.Pattern]]
|
||||
],
|
||||
) -> Token:
|
||||
"""
|
||||
设置当前请求的编译后的排除路由模式列表
|
||||
|
||||
:param exclude_patterns: 编译后的排除路由模式列表
|
||||
:return: 上下文变量令牌,用于重置
|
||||
"""
|
||||
return current_exclude_patterns.set(exclude_patterns)
|
||||
|
||||
@staticmethod
|
||||
def get_current_exclude_patterns() -> list[
|
||||
dict[str, Union[str, list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']], re.Pattern]]
|
||||
]:
|
||||
"""
|
||||
获取当前请求的编译后的排除路由模式列表
|
||||
|
||||
:return: 编译后的排除路由模式列表
|
||||
"""
|
||||
_exclude_patterns = current_exclude_patterns.get()
|
||||
if _exclude_patterns is None:
|
||||
_exclude_patterns = []
|
||||
return _exclude_patterns
|
||||
|
||||
@staticmethod
|
||||
def set_current_user(user: CurrentUserModel) -> Token:
|
||||
"""
|
||||
设置当前用户信息
|
||||
|
||||
:param user: 用户信息
|
||||
:return: 上下文变量令牌,用于重置
|
||||
"""
|
||||
return current_user.set(user)
|
||||
|
||||
@staticmethod
|
||||
def get_current_user() -> CurrentUserModel:
|
||||
"""
|
||||
获取当前用户信息
|
||||
|
||||
:return: 用户信息
|
||||
"""
|
||||
_current_user = current_user.get()
|
||||
if _current_user is None:
|
||||
raise LoginException(data='', message='当前用户信息为空,请检查是否已登录')
|
||||
return _current_user
|
||||
|
||||
@staticmethod
|
||||
def reset_current_exclude_patterns(token: Token) -> None:
|
||||
"""
|
||||
重置当前请求的编译后的排除路由模式列表
|
||||
|
||||
:param token: 设置编译后的排除路由模式列表时返回的令牌
|
||||
"""
|
||||
current_exclude_patterns.reset(token)
|
||||
|
||||
@staticmethod
|
||||
def reset_current_user(token: Token) -> None:
|
||||
"""
|
||||
重置当前用户信息
|
||||
|
||||
:param token: 设置用户信息时返回的令牌
|
||||
"""
|
||||
current_user.reset(token)
|
||||
|
||||
@staticmethod
|
||||
def clear_all() -> None:
|
||||
"""
|
||||
清除所有上下文变量
|
||||
"""
|
||||
current_exclude_patterns.set(None)
|
||||
current_user.set(None)
|
||||
@@ -1,4 +1,5 @@
|
||||
from enum import Enum
|
||||
from typing import Union
|
||||
|
||||
|
||||
class BusinessType(Enum):
|
||||
@@ -35,11 +36,11 @@ class RedisInitKeyConfig(Enum):
|
||||
"""
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
def key(self) -> Union[str, None]:
|
||||
return self.value.get('key')
|
||||
|
||||
@property
|
||||
def remark(self):
|
||||
def remark(self) -> Union[str, None]:
|
||||
return self.value.get('remark')
|
||||
|
||||
ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'}
|
||||
@@ -0,0 +1,405 @@
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from enum import Enum
|
||||
from typing import Annotated, Any, Callable, Literal, Optional, Union
|
||||
|
||||
from annotated_doc import Doc
|
||||
from fastapi import FastAPI, params
|
||||
from fastapi.datastructures import Default
|
||||
from fastapi.routing import APIRoute, APIRouter
|
||||
from fastapi.utils import generate_unique_id
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.routing import BaseRoute
|
||||
from starlette.types import ASGIApp, Lifespan
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
class APIRouterPro(APIRouter):
|
||||
"""
|
||||
`APIRouterPro` class, inherited from the `APIRouter` class, it has all the functions of `APIRouter` and provides some additional parameter settings.
|
||||
`APIRouter` class, used to group *path operations*, for example to structure
|
||||
an app in multiple files. It would then be included in the `FastAPI` app, or
|
||||
in another `APIRouter` (ultimately included in the app).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/).
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from common.router import APIRouterPro, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
router = APIRouterPro()
|
||||
|
||||
|
||||
@router.get('/users/', tags=['users'])
|
||||
async def read_users():
|
||||
return [{'username': 'Rick'}, {'username': 'Morty'}]
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
*,
|
||||
prefix: Annotated[str, Doc('An optional path prefix for the router.')] = '',
|
||||
order_num: Annotated[int, Doc('An optional order number for the router.')] = 100,
|
||||
auto_register: Annotated[bool, Doc('An optional auto register flag for the router.')] = True,
|
||||
tags: Annotated[
|
||||
Optional[list[Union[str, Enum]]],
|
||||
Doc(
|
||||
"""
|
||||
A list of tags to be applied to all the *path operations* in this
|
||||
router.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
dependencies: Annotated[
|
||||
Optional[Sequence[params.Depends]],
|
||||
Doc(
|
||||
"""
|
||||
A list of dependencies (using `Depends()`) to be applied to all the
|
||||
*path operations* in this router.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
default_response_class: Annotated[
|
||||
type[Response],
|
||||
Doc(
|
||||
"""
|
||||
The default response class to be used.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).
|
||||
"""
|
||||
),
|
||||
] = Default(JSONResponse),
|
||||
responses: Annotated[
|
||||
Optional[dict[Union[int, str], dict[str, Any]]],
|
||||
Doc(
|
||||
"""
|
||||
Additional responses to be shown in OpenAPI.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).
|
||||
|
||||
And in the
|
||||
[FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
callbacks: Annotated[
|
||||
Optional[list[BaseRoute]],
|
||||
Doc(
|
||||
"""
|
||||
OpenAPI callbacks that should apply to all *path operations* in this
|
||||
router.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
routes: Annotated[
|
||||
Optional[list[BaseRoute]],
|
||||
Doc(
|
||||
"""
|
||||
**Note**: you probably shouldn't use this parameter, it is inherited
|
||||
from Starlette and supported for compatibility.
|
||||
|
||||
---
|
||||
|
||||
A list of routes to serve incoming HTTP and WebSocket requests.
|
||||
"""
|
||||
),
|
||||
deprecated(
|
||||
"""
|
||||
You normally wouldn't use this parameter with FastAPI, it is inherited
|
||||
from Starlette and supported for compatibility.
|
||||
|
||||
In FastAPI, you normally would use the *path operation methods*,
|
||||
like `router.get()`, `router.post()`, etc.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
redirect_slashes: Annotated[
|
||||
bool,
|
||||
Doc(
|
||||
"""
|
||||
Whether to detect and redirect slashes in URLs when the client doesn't
|
||||
use the same format.
|
||||
"""
|
||||
),
|
||||
] = True,
|
||||
default: Annotated[
|
||||
Optional[ASGIApp],
|
||||
Doc(
|
||||
"""
|
||||
Default function handler for this router. Used to handle
|
||||
404 Not Found errors.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
dependency_overrides_provider: Annotated[
|
||||
Optional[Any],
|
||||
Doc(
|
||||
"""
|
||||
Only used internally by FastAPI to handle dependency overrides.
|
||||
|
||||
You shouldn't need to use it. It normally points to the `FastAPI` app
|
||||
object.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
route_class: Annotated[
|
||||
type[APIRoute],
|
||||
Doc(
|
||||
"""
|
||||
Custom route (*path operation*) class to be used by this router.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Custom Request and APIRoute class](https://fastapi.tiangolo.com/how-to/custom-request-and-route/#custom-apiroute-class-in-a-router).
|
||||
"""
|
||||
),
|
||||
] = APIRoute,
|
||||
on_startup: Annotated[
|
||||
Optional[Sequence[Callable[[], Any]]],
|
||||
Doc(
|
||||
"""
|
||||
A list of startup event handler functions.
|
||||
|
||||
You should instead use the `lifespan` handlers.
|
||||
|
||||
Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
on_shutdown: Annotated[
|
||||
Optional[Sequence[Callable[[], Any]]],
|
||||
Doc(
|
||||
"""
|
||||
A list of shutdown event handler functions.
|
||||
|
||||
You should instead use the `lifespan` handlers.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
# the generic to Lifespan[AppType] is the type of the top level application
|
||||
# which the router cannot know statically, so we use typing.Any
|
||||
lifespan: Annotated[
|
||||
Optional[Lifespan[Any]],
|
||||
Doc(
|
||||
"""
|
||||
A `Lifespan` context manager handler. This replaces `startup` and
|
||||
`shutdown` functions with a single context manager.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
deprecated: Annotated[
|
||||
Optional[bool],
|
||||
Doc(
|
||||
"""
|
||||
Mark all *path operations* in this router as deprecated.
|
||||
|
||||
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
include_in_schema: Annotated[
|
||||
bool,
|
||||
Doc(
|
||||
"""
|
||||
To include (or not) all the *path operations* in this router in the
|
||||
generated OpenAPI.
|
||||
|
||||
This affects the generated OpenAPI (e.g. visible at `/docs`).
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
|
||||
"""
|
||||
),
|
||||
] = True,
|
||||
generate_unique_id_function: Annotated[
|
||||
Callable[[APIRoute], str],
|
||||
Doc(
|
||||
"""
|
||||
Customize the function used to generate unique IDs for the *path
|
||||
operations* shown in the generated OpenAPI.
|
||||
|
||||
This is particularly useful when automatically generating clients or
|
||||
SDKs for your API.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
|
||||
"""
|
||||
),
|
||||
] = Default(generate_unique_id),
|
||||
) -> None:
|
||||
self.order_num = order_num
|
||||
self.auto_register = auto_register
|
||||
super().__init__(
|
||||
prefix=prefix,
|
||||
tags=tags,
|
||||
dependencies=dependencies,
|
||||
default_response_class=default_response_class,
|
||||
responses=responses,
|
||||
callbacks=callbacks,
|
||||
routes=routes,
|
||||
redirect_slashes=redirect_slashes,
|
||||
default=default,
|
||||
dependency_overrides_provider=dependency_overrides_provider,
|
||||
route_class=route_class,
|
||||
on_startup=on_startup,
|
||||
on_shutdown=on_shutdown,
|
||||
lifespan=lifespan,
|
||||
deprecated=deprecated,
|
||||
include_in_schema=include_in_schema,
|
||||
generate_unique_id_function=generate_unique_id_function,
|
||||
)
|
||||
|
||||
|
||||
class RouterRegister:
|
||||
"""
|
||||
路由注册器,用于自动注册所有controller目录下的路由
|
||||
"""
|
||||
|
||||
def __init__(self, app: FastAPI) -> None:
|
||||
"""
|
||||
初始化路由注册器
|
||||
|
||||
:param app: FastAPI对象
|
||||
"""
|
||||
self.app = app
|
||||
# 获取项目根目录
|
||||
self.project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
sys.path.insert(0, self.project_root)
|
||||
|
||||
def _find_controller_files(self) -> list[str]:
|
||||
"""
|
||||
查找所有controller目录下的py文件
|
||||
|
||||
:return: py文件路径列表
|
||||
"""
|
||||
controller_files = []
|
||||
# 遍历所有目录,查找controller目录
|
||||
for root, _dirs, files in os.walk(self.project_root):
|
||||
# 检查当前目录是否为controller目录
|
||||
if os.path.basename(root) == 'controller':
|
||||
# 遍历controller目录下的所有py文件
|
||||
for file in files:
|
||||
if file.endswith('.py') and not file.startswith('__'):
|
||||
file_path = os.path.join(root, file)
|
||||
controller_files.append(file_path)
|
||||
return controller_files
|
||||
|
||||
def _import_module_and_get_routers(self, controller_files: list[str]) -> list[tuple[str, APIRouter]]:
|
||||
"""
|
||||
导入模块并获取路由实例
|
||||
|
||||
:param controller_files: controller目录下的py文件路径列表
|
||||
:return: 路由实例列表
|
||||
"""
|
||||
routers = []
|
||||
for file_path in controller_files:
|
||||
# 计算模块路径
|
||||
relative_path = os.path.relpath(file_path, self.project_root)
|
||||
module_name = relative_path.replace(os.sep, '.')[:-3]
|
||||
|
||||
try:
|
||||
# 动态导入模块
|
||||
module = importlib.import_module(module_name)
|
||||
# 遍历模块属性,寻找APIRouter和APIRouterPro实例
|
||||
for attr_name in dir(module):
|
||||
attr = getattr(module, attr_name)
|
||||
# 对于APIRouterPro实例,只有当auto_register=True时才添加
|
||||
if isinstance(attr, APIRouterPro):
|
||||
if attr.auto_register:
|
||||
routers.append((attr_name, attr))
|
||||
# 对于APIRouter实例,直接添加
|
||||
elif isinstance(attr, APIRouter):
|
||||
routers.append((attr_name, attr))
|
||||
except Exception as e:
|
||||
print(f'Error importing module {module_name}: {e}')
|
||||
return routers
|
||||
|
||||
def _sort_routers(self, routers: list[tuple[str, APIRouter]]) -> list[tuple[str, APIRouter]]:
|
||||
"""
|
||||
按规则排序路由
|
||||
|
||||
:param routers: 路由实例列表
|
||||
:return: 排序后的路由实例列表
|
||||
"""
|
||||
|
||||
# 按规则排序路由
|
||||
def sort_key(item: tuple[str, APIRouter]) -> Union[tuple[Literal[0], int, str], tuple[Literal[1], str]]:
|
||||
attr_name, router = item
|
||||
# APIRouterPro实例按order_num排序,序号越小越靠前
|
||||
if isinstance(router, APIRouterPro):
|
||||
return (0, router.order_num, attr_name)
|
||||
# APIRouter实例按变量名首字母排序
|
||||
return (1, attr_name)
|
||||
|
||||
return sorted(routers, key=sort_key)
|
||||
|
||||
def _register_routers_to_app(self, routers: list[tuple[str, APIRouter]]) -> None:
|
||||
"""
|
||||
将路由注册到FastAPI应用
|
||||
|
||||
:param routers: 排序后的路由实例列表
|
||||
:return: None
|
||||
"""
|
||||
for _attr_name, router in routers:
|
||||
self.app.include_router(router=router)
|
||||
|
||||
def register_routers(self) -> None:
|
||||
"""
|
||||
自动注册所有controller目录下的路由
|
||||
|
||||
:return: None
|
||||
"""
|
||||
# 查找所有controller目录下的py文件
|
||||
controller_files = self._find_controller_files()
|
||||
# 导入模块并获取路由实例
|
||||
routers = self._import_module_and_get_routers(controller_files)
|
||||
# 按规则排序路由
|
||||
sorted_routers = self._sort_routers(routers)
|
||||
# 注册路由到FastAPI应用
|
||||
self._register_routers_to_app(sorted_routers)
|
||||
|
||||
|
||||
def auto_register_routers(app: FastAPI) -> None:
|
||||
"""
|
||||
自动注册所有controller目录下的路由
|
||||
|
||||
:param app: FastAPI对象
|
||||
:return: None
|
||||
"""
|
||||
# 使用路由注册器进行注册
|
||||
router_register = RouterRegister(app)
|
||||
router_register.register_routers()
|
||||
@@ -0,0 +1,105 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Generic, Optional, TypeVar, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, create_model
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing_extensions import Self
|
||||
|
||||
from common.constant import HttpStatusConstant
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class CrudResponseModel(BaseModel):
|
||||
"""
|
||||
操作响应模型
|
||||
"""
|
||||
|
||||
is_success: bool = Field(description='操作是否成功')
|
||||
message: str = Field(description='响应信息')
|
||||
result: Optional[Any] = Field(default=None, description='响应结果')
|
||||
|
||||
|
||||
class ResponseBaseModel(BaseModel):
|
||||
"""
|
||||
响应模型
|
||||
"""
|
||||
|
||||
code: int = Field(default=HttpStatusConstant.SUCCESS, description='响应码')
|
||||
msg: str = Field(default='操作成功', description='响应信息')
|
||||
success: bool = Field(default=True, description='响应是否成功')
|
||||
time: datetime = Field(default_factory=datetime.now, description='响应时间')
|
||||
|
||||
|
||||
class DynamicResponseModel(ResponseBaseModel, Generic[T]):
|
||||
"""
|
||||
动态响应模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
def __class_getitem__(cls, item: Any) -> Union[Any, Self]:
|
||||
"""
|
||||
当使用 DynamicResponseModel[Item] 语法时,动态创建一个包含所有字段的新模型
|
||||
"""
|
||||
# 检查是否已经为该类型创建了模型
|
||||
if not hasattr(cls, '_cached_models'):
|
||||
cls._cached_models = {}
|
||||
|
||||
if item in cls._cached_models:
|
||||
return cls._cached_models[item]
|
||||
|
||||
# 检查item是否为Pydantic模型
|
||||
if not hasattr(item, 'model_fields'):
|
||||
raise TypeError(f'{item} 不是一个Pydantic模型,请使用Pydantic模型作为泛型参数')
|
||||
|
||||
# 获取ResponseBaseModel的字段
|
||||
base_fields = {}
|
||||
for field_name, field in cls.model_fields.items():
|
||||
base_fields[field_name] = (field.annotation, field)
|
||||
|
||||
# 获取泛型类型的字段
|
||||
item_fields = {}
|
||||
for field_name, field in item.model_fields.items():
|
||||
item_fields[field_name] = (field.annotation, field)
|
||||
|
||||
# 合并所有字段
|
||||
all_fields = {**base_fields, **item_fields}
|
||||
|
||||
# 动态创建新模型
|
||||
new_model = create_model(
|
||||
f'DynamicResponseModel[{item.__name__}]', __base__=cls, __config__=cls.model_config, **all_fields
|
||||
)
|
||||
|
||||
# 缓存模型
|
||||
cls._cached_models[item] = new_model
|
||||
|
||||
return new_model
|
||||
|
||||
|
||||
class PageModel(BaseModel, Generic[T]):
|
||||
"""
|
||||
分页模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
rows: list[T] = Field(description='记录列表')
|
||||
page_num: int = Field(description='当前页码')
|
||||
page_size: int = Field(description='每页记录数')
|
||||
total: int = Field(description='总记录数')
|
||||
has_next: bool = Field(description='是否有下一页')
|
||||
|
||||
|
||||
class PageResponseModel(PageModel, ResponseBaseModel, Generic[T]):
|
||||
"""
|
||||
分页响应模型
|
||||
"""
|
||||
|
||||
|
||||
class DataResponseModel(ResponseBaseModel, Generic[T]):
|
||||
"""
|
||||
数据响应模型
|
||||
"""
|
||||
|
||||
data: T = Field(description='响应数据')
|
||||
@@ -1,8 +1,8 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
from config.env import DataBaseConfig
|
||||
|
||||
ASYNC_SQLALCHEMY_DATABASE_URL = (
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import argparse
|
||||
import configparser
|
||||
import os
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
from functools import lru_cache
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Literal
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import computed_field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class AppSettings(BaseSettings):
|
||||
"""
|
||||
@@ -51,6 +53,13 @@ class DataBaseSettings(BaseSettings):
|
||||
db_pool_recycle: int = 3600
|
||||
db_pool_timeout: int = 30
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def sqlglot_parse_dialect(self) -> str:
|
||||
if self.db_type == 'postgresql':
|
||||
return 'postgres'
|
||||
return self.db_type
|
||||
|
||||
|
||||
class RedisSettings(BaseSettings):
|
||||
"""
|
||||
@@ -77,7 +86,7 @@ class GenSettings:
|
||||
|
||||
GEN_PATH = 'vf_admin/gen_path'
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
if not os.path.exists(self.GEN_PATH):
|
||||
os.makedirs(self.GEN_PATH)
|
||||
|
||||
@@ -121,7 +130,7 @@ class UploadSettings:
|
||||
]
|
||||
DOWNLOAD_PATH = 'vf_admin/download_path'
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
if not os.path.exists(self.UPLOAD_PATH):
|
||||
os.makedirs(self.UPLOAD_PATH)
|
||||
if not os.path.exists(self.DOWNLOAD_PATH):
|
||||
@@ -142,63 +151,65 @@ class GetConfig:
|
||||
获取配置
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.parse_cli_args()
|
||||
|
||||
@lru_cache()
|
||||
def get_app_config(self):
|
||||
def get_app_config(self) -> AppSettings:
|
||||
"""
|
||||
获取应用配置
|
||||
"""
|
||||
# 实例化应用配置模型
|
||||
return AppSettings()
|
||||
|
||||
@lru_cache()
|
||||
def get_jwt_config(self):
|
||||
def get_jwt_config(self) -> JwtSettings:
|
||||
"""
|
||||
获取Jwt配置
|
||||
"""
|
||||
# 实例化Jwt配置模型
|
||||
return JwtSettings()
|
||||
|
||||
@lru_cache()
|
||||
def get_database_config(self):
|
||||
def get_database_config(self) -> DataBaseSettings:
|
||||
"""
|
||||
获取数据库配置
|
||||
"""
|
||||
# 实例化数据库配置模型
|
||||
return DataBaseSettings()
|
||||
|
||||
@lru_cache()
|
||||
def get_redis_config(self):
|
||||
def get_redis_config(self) -> RedisSettings:
|
||||
"""
|
||||
获取Redis配置
|
||||
"""
|
||||
# 实例化Redis配置模型
|
||||
return RedisSettings()
|
||||
|
||||
@lru_cache()
|
||||
def get_gen_config(self):
|
||||
def get_gen_config(self) -> GenSettings:
|
||||
"""
|
||||
获取代码生成配置
|
||||
"""
|
||||
# 实例化代码生成配置
|
||||
return GenSettings()
|
||||
|
||||
@lru_cache()
|
||||
def get_upload_config(self):
|
||||
def get_upload_config(self) -> UploadSettings:
|
||||
"""
|
||||
获取数据库配置
|
||||
获取上传配置
|
||||
"""
|
||||
# 实例上传配置
|
||||
return UploadSettings()
|
||||
|
||||
@staticmethod
|
||||
def parse_cli_args():
|
||||
def parse_cli_args() -> None:
|
||||
"""
|
||||
解析命令行参数
|
||||
"""
|
||||
if 'uvicorn' in sys.argv[0]:
|
||||
# 检查是否在alembic环境中运行,如果是则跳过参数解析
|
||||
if 'alembic' in sys.argv[0] or any('alembic' in arg for arg in sys.argv):
|
||||
ini_config = configparser.ConfigParser()
|
||||
ini_config.read('alembic.ini', encoding='utf-8')
|
||||
if 'settings' in ini_config:
|
||||
# 获取env选项
|
||||
env_value = ini_config['settings'].get('env')
|
||||
os.environ['APP_ENV'] = env_value if env_value else 'dev'
|
||||
elif 'uvicorn' in sys.argv[0]:
|
||||
# 使用uvicorn启动时,命令行参数需要按照uvicorn的文档进行配置,无法自定义参数
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from config.database import async_engine, AsyncSessionLocal, Base
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config.database import AsyncSessionLocal, Base, async_engine
|
||||
from utils.log_util import logger
|
||||
|
||||
|
||||
async def get_db():
|
||||
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""
|
||||
每一个请求处理完毕后会关闭当前连接,不同的请求使用不同的连接
|
||||
|
||||
@@ -12,13 +16,13 @@ async def get_db():
|
||||
yield current_db
|
||||
|
||||
|
||||
async def init_create_table():
|
||||
async def init_create_table() -> None:
|
||||
"""
|
||||
应用启动时初始化数据库连接
|
||||
|
||||
:return:
|
||||
"""
|
||||
logger.info('初始化数据库连接...')
|
||||
logger.info('🔎 初始化数据库连接...')
|
||||
async with async_engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
logger.info('数据库连接成功')
|
||||
logger.info('✅️ 数据库连接成功')
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
from redis import asyncio as aioredis
|
||||
from redis.exceptions import AuthenticationError, TimeoutError, RedisError
|
||||
from redis.exceptions import AuthenticationError, RedisError
|
||||
from redis.exceptions import TimeoutError as RedisTimeoutError
|
||||
|
||||
from config.database import AsyncSessionLocal
|
||||
from config.env import RedisConfig
|
||||
from module_admin.service.config_service import ConfigService
|
||||
@@ -19,7 +22,7 @@ class RedisUtil:
|
||||
|
||||
:return: Redis连接对象
|
||||
"""
|
||||
logger.info('开始连接redis...')
|
||||
logger.info('🔎 开始连接redis...')
|
||||
redis = await aioredis.from_url(
|
||||
url=f'redis://{RedisConfig.redis_host}',
|
||||
port=RedisConfig.redis_port,
|
||||
@@ -32,19 +35,19 @@ class RedisUtil:
|
||||
try:
|
||||
connection = await redis.ping()
|
||||
if connection:
|
||||
logger.info('redis连接成功')
|
||||
logger.info('✅️ redis连接成功')
|
||||
else:
|
||||
logger.error('redis连接失败')
|
||||
logger.error('❌️ redis连接失败')
|
||||
except AuthenticationError as e:
|
||||
logger.error(f'redis用户名或密码错误,详细错误信息:{e}')
|
||||
except TimeoutError as e:
|
||||
logger.error(f'redis连接超时,详细错误信息:{e}')
|
||||
logger.error(f'❌️ redis用户名或密码错误,详细错误信息:{e}')
|
||||
except RedisTimeoutError as e:
|
||||
logger.error(f'❌️ redis连接超时,详细错误信息:{e}')
|
||||
except RedisError as e:
|
||||
logger.error(f'redis连接错误,详细错误信息:{e}')
|
||||
logger.error(f'❌️ redis连接错误,详细错误信息:{e}')
|
||||
return redis
|
||||
|
||||
@classmethod
|
||||
async def close_redis_pool(cls, app):
|
||||
async def close_redis_pool(cls, app: FastAPI) -> None:
|
||||
"""
|
||||
应用关闭时关闭redis连接
|
||||
|
||||
@@ -52,10 +55,10 @@ class RedisUtil:
|
||||
:return:
|
||||
"""
|
||||
await app.state.redis.close()
|
||||
logger.info('关闭redis连接成功')
|
||||
logger.info('✅️ 关闭redis连接成功')
|
||||
|
||||
@classmethod
|
||||
async def init_sys_dict(cls, redis):
|
||||
async def init_sys_dict(cls, redis: FastAPI) -> None:
|
||||
"""
|
||||
应用启动时缓存字典表
|
||||
|
||||
@@ -66,7 +69,7 @@ class RedisUtil:
|
||||
await DictDataService.init_cache_sys_dict_services(session, redis)
|
||||
|
||||
@classmethod
|
||||
async def init_sys_config(cls, redis):
|
||||
async def init_sys_config(cls, redis: aioredis.Redis) -> None:
|
||||
"""
|
||||
应用启动时缓存参数配置表
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import importlib
|
||||
import json
|
||||
from apscheduler.events import EVENT_ALL
|
||||
from asyncio import iscoroutinefunction
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Callable, Optional, Union
|
||||
|
||||
from apscheduler.events import EVENT_ALL, SchedulerEvent
|
||||
from apscheduler.executors.asyncio import AsyncIOExecutor
|
||||
from apscheduler.executors.pool import ProcessPoolExecutor
|
||||
from apscheduler.job import Job
|
||||
from apscheduler.jobstores.memory import MemoryJobStore
|
||||
from apscheduler.jobstores.redis import RedisJobStore
|
||||
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
||||
@@ -9,27 +15,29 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.combining import OrTrigger
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.date import DateTrigger
|
||||
from asyncio import iscoroutinefunction
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.engine import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from typing import Union
|
||||
|
||||
import module_task # noqa: F401
|
||||
from config.database import AsyncSessionLocal, quote_plus
|
||||
from config.env import DataBaseConfig, RedisConfig
|
||||
from module_admin.dao.job_dao import JobDao
|
||||
from module_admin.entity.vo.job_vo import JobLogModel, JobModel
|
||||
from module_admin.service.job_log_service import JobLogService
|
||||
from utils.log_util import logger
|
||||
import module_task # noqa: F401
|
||||
|
||||
|
||||
# 重写Cron定时
|
||||
class MyCronTrigger(CronTrigger):
|
||||
CRON_EXPRESSION_LENGTH_MIN = 6
|
||||
CRON_EXPRESSION_LENGTH_MAX = 7
|
||||
WEEKDAY_COUNT = 5
|
||||
|
||||
@classmethod
|
||||
def from_crontab(cls, expr: str, timezone=None):
|
||||
def from_crontab(cls, expr: str, timezone: Optional[str] = None) -> 'MyCronTrigger':
|
||||
values = expr.split()
|
||||
if len(values) != 6 and len(values) != 7:
|
||||
raise ValueError('Wrong number of fields; got {}, expected 6 or 7'.format(len(values)))
|
||||
if len(values) != cls.CRON_EXPRESSION_LENGTH_MIN and len(values) != cls.CRON_EXPRESSION_LENGTH_MAX:
|
||||
raise ValueError(f'Wrong number of fields; got {len(values)}, expected 6 or 7')
|
||||
|
||||
second = values[0]
|
||||
minute = values[1]
|
||||
@@ -37,7 +45,7 @@ class MyCronTrigger(CronTrigger):
|
||||
if '?' in values[3]:
|
||||
day = None
|
||||
elif 'L' in values[5]:
|
||||
day = f"last {values[5].replace('L', '')}"
|
||||
day = f'last {values[5].replace("L", "")}'
|
||||
elif 'W' in values[3]:
|
||||
day = cls.__find_recent_workday(int(values[3].split('W')[0]))
|
||||
else:
|
||||
@@ -49,11 +57,8 @@ class MyCronTrigger(CronTrigger):
|
||||
week = int(values[5].split('#')[1])
|
||||
else:
|
||||
week = values[5]
|
||||
if '#' in values[5]:
|
||||
day_of_week = int(values[5].split('#')[0]) - 1
|
||||
else:
|
||||
day_of_week = None
|
||||
year = values[6] if len(values) == 7 else None
|
||||
day_of_week = int(values[5].split('#')[0]) - 1 if '#' in values[5] else None
|
||||
year = values[6] if len(values) == cls.CRON_EXPRESSION_LENGTH_MAX else None
|
||||
return cls(
|
||||
second=second,
|
||||
minute=minute,
|
||||
@@ -67,18 +72,16 @@ class MyCronTrigger(CronTrigger):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def __find_recent_workday(cls, day: int):
|
||||
def __find_recent_workday(cls, day: int) -> int:
|
||||
now = datetime.now()
|
||||
date = datetime(now.year, now.month, day)
|
||||
if date.weekday() < 5:
|
||||
if date.weekday() < cls.WEEKDAY_COUNT:
|
||||
return date.day
|
||||
else:
|
||||
diff = 1
|
||||
while True:
|
||||
previous_day = date - timedelta(days=diff)
|
||||
if previous_day.weekday() < 5:
|
||||
if previous_day.weekday() < cls.WEEKDAY_COUNT:
|
||||
return previous_day.day
|
||||
else:
|
||||
diff += 1
|
||||
|
||||
|
||||
@@ -100,18 +103,17 @@ engine = create_engine(
|
||||
pool_timeout=DataBaseConfig.db_pool_timeout,
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
redis_config = {
|
||||
'host': RedisConfig.redis_host,
|
||||
'port': RedisConfig.redis_port,
|
||||
'username': RedisConfig.redis_username,
|
||||
'password': RedisConfig.redis_password,
|
||||
'db': RedisConfig.redis_database,
|
||||
}
|
||||
job_stores = {
|
||||
'default': MemoryJobStore(),
|
||||
'sqlalchemy': SQLAlchemyJobStore(url=SQLALCHEMY_DATABASE_URL, engine=engine),
|
||||
'redis': RedisJobStore(
|
||||
**dict(
|
||||
host=RedisConfig.redis_host,
|
||||
port=RedisConfig.redis_port,
|
||||
username=RedisConfig.redis_username,
|
||||
password=RedisConfig.redis_password,
|
||||
db=RedisConfig.redis_database,
|
||||
)
|
||||
),
|
||||
'redis': RedisJobStore(**redis_config),
|
||||
}
|
||||
executors = {'default': AsyncIOExecutor(), 'processpool': ProcessPoolExecutor(5)}
|
||||
job_defaults = {'coalesce': False, 'max_instance': 1}
|
||||
@@ -125,13 +127,13 @@ class SchedulerUtil:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def init_system_scheduler(cls):
|
||||
async def init_system_scheduler(cls) -> None:
|
||||
"""
|
||||
应用启动时初始化定时任务
|
||||
|
||||
:return:
|
||||
"""
|
||||
logger.info('开始启动定时任务...')
|
||||
logger.info('🔎 开始启动定时任务...')
|
||||
scheduler.start()
|
||||
async with AsyncSessionLocal() as session:
|
||||
job_list = await JobDao.get_job_list_for_scheduler(session)
|
||||
@@ -139,20 +141,32 @@ class SchedulerUtil:
|
||||
cls.remove_scheduler_job(job_id=str(item.job_id))
|
||||
cls.add_scheduler_job(item)
|
||||
scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL)
|
||||
logger.info('系统初始定时任务加载成功')
|
||||
logger.info('✅️ 系统初始定时任务加载成功')
|
||||
|
||||
@classmethod
|
||||
async def close_system_scheduler(cls):
|
||||
async def close_system_scheduler(cls) -> None:
|
||||
"""
|
||||
应用关闭时关闭定时任务
|
||||
|
||||
:return:
|
||||
"""
|
||||
scheduler.shutdown()
|
||||
logger.info('关闭定时任务成功')
|
||||
logger.info('✅️ 关闭定时任务成功')
|
||||
|
||||
@classmethod
|
||||
def get_scheduler_job(cls, job_id: Union[str, int]):
|
||||
def _import_function(cls, func_path: str) -> Callable[..., Any]:
|
||||
"""
|
||||
动态导入函数
|
||||
|
||||
:param func_path: 函数字符串,如module_task.scheduler_test.job
|
||||
:return: 导入的函数对象
|
||||
"""
|
||||
module_path, func_name = func_path.rsplit('.', 1)
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, func_name)
|
||||
|
||||
@classmethod
|
||||
def get_scheduler_job(cls, job_id: Union[str, int]) -> Job:
|
||||
"""
|
||||
根据任务id获取任务对象
|
||||
|
||||
@@ -164,59 +178,62 @@ class SchedulerUtil:
|
||||
return query_job
|
||||
|
||||
@classmethod
|
||||
def add_scheduler_job(cls, job_info: JobModel):
|
||||
def add_scheduler_job(cls, job_info: JobModel) -> None:
|
||||
"""
|
||||
根据输入的任务对象信息添加任务
|
||||
|
||||
:param job_info: 任务对象信息
|
||||
:return:
|
||||
"""
|
||||
job_func = eval(job_info.invoke_target)
|
||||
job_func = cls._import_function(job_info.invoke_target)
|
||||
job_executor = job_info.job_executor
|
||||
if iscoroutinefunction(job_func):
|
||||
job_executor = 'default'
|
||||
scheduler.add_job(
|
||||
func=eval(job_info.invoke_target),
|
||||
func=job_func,
|
||||
trigger=MyCronTrigger.from_crontab(job_info.cron_expression),
|
||||
args=job_info.job_args.split(',') if job_info.job_args else None,
|
||||
kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None,
|
||||
id=str(job_info.job_id),
|
||||
name=job_info.job_name,
|
||||
misfire_grace_time=1000000000000 if job_info.misfire_policy == '3' else None,
|
||||
coalesce=True if job_info.misfire_policy == '2' else False,
|
||||
coalesce=job_info.misfire_policy == '2',
|
||||
max_instances=3 if job_info.concurrent == '0' else 1,
|
||||
jobstore=job_info.job_group,
|
||||
executor=job_executor,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute_scheduler_job_once(cls, job_info: JobModel):
|
||||
def execute_scheduler_job_once(cls, job_info: JobModel) -> None:
|
||||
"""
|
||||
根据输入的任务对象执行一次任务
|
||||
|
||||
:param job_info: 任务对象信息
|
||||
:return:
|
||||
"""
|
||||
job_func = eval(job_info.invoke_target)
|
||||
job_func = cls._import_function(job_info.invoke_target)
|
||||
job_executor = job_info.job_executor
|
||||
if iscoroutinefunction(job_func):
|
||||
job_executor = 'default'
|
||||
job_trigger = DateTrigger()
|
||||
if job_info.status == '0':
|
||||
job_trigger = OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)])
|
||||
scheduler.add_job(
|
||||
func=eval(job_info.invoke_target),
|
||||
trigger=OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)]),
|
||||
func=job_func,
|
||||
trigger=job_trigger,
|
||||
args=job_info.job_args.split(',') if job_info.job_args else None,
|
||||
kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None,
|
||||
id=str(job_info.job_id),
|
||||
name=job_info.job_name,
|
||||
misfire_grace_time=1000000000000 if job_info.misfire_policy == '3' else None,
|
||||
coalesce=True if job_info.misfire_policy == '2' else False,
|
||||
coalesce=job_info.misfire_policy == '2',
|
||||
max_instances=3 if job_info.concurrent == '0' else 1,
|
||||
jobstore=job_info.job_group,
|
||||
executor=job_executor,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def remove_scheduler_job(cls, job_id: Union[str, int]):
|
||||
def remove_scheduler_job(cls, job_id: Union[str, int]) -> None:
|
||||
"""
|
||||
根据任务id移除任务
|
||||
|
||||
@@ -228,7 +245,7 @@ class SchedulerUtil:
|
||||
scheduler.remove_job(job_id=str(job_id))
|
||||
|
||||
@classmethod
|
||||
def scheduler_event_listener(cls, event):
|
||||
def scheduler_event_listener(cls, event: SchedulerEvent) -> None:
|
||||
# 获取事件类型和任务ID
|
||||
event_type = event.__class__.__name__
|
||||
# 获取任务执行异常信息
|
||||
@@ -257,7 +274,7 @@ class SchedulerUtil:
|
||||
# 获取任务触发器
|
||||
job_trigger = str(query_job_info.get('trigger'))
|
||||
# 构造日志消息
|
||||
job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
job_message = f'事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
|
||||
job_log = JobLogModel(
|
||||
jobName=job_name,
|
||||
jobGroup=job_group,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class LoginException(Exception):
|
||||
"""
|
||||
自定义登录异常LoginException
|
||||
"""
|
||||
|
||||
def __init__(self, data: str = None, message: str = None):
|
||||
def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None:
|
||||
self.data = data
|
||||
self.message = message
|
||||
|
||||
@@ -13,7 +16,7 @@ class AuthException(Exception):
|
||||
自定义令牌异常AuthException
|
||||
"""
|
||||
|
||||
def __init__(self, data: str = None, message: str = None):
|
||||
def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None:
|
||||
self.data = data
|
||||
self.message = message
|
||||
|
||||
@@ -23,7 +26,7 @@ class PermissionException(Exception):
|
||||
自定义权限异常PermissionException
|
||||
"""
|
||||
|
||||
def __init__(self, data: str = None, message: str = None):
|
||||
def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None:
|
||||
self.data = data
|
||||
self.message = message
|
||||
|
||||
@@ -33,7 +36,7 @@ class ServiceException(Exception):
|
||||
自定义服务异常ServiceException
|
||||
"""
|
||||
|
||||
def __init__(self, data: str = None, message: str = None):
|
||||
def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None:
|
||||
self.data = data
|
||||
self.message = message
|
||||
|
||||
@@ -43,7 +46,7 @@ class ServiceWarning(Exception):
|
||||
自定义服务警告ServiceWarning
|
||||
"""
|
||||
|
||||
def __init__(self, data: str = None, message: str = None):
|
||||
def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None:
|
||||
self.data = data
|
||||
self.message = message
|
||||
|
||||
@@ -53,6 +56,6 @@ class ModelValidatorException(Exception):
|
||||
自定义模型校验异常ModelValidatorException
|
||||
"""
|
||||
|
||||
def __init__(self, data: str = None, message: str = None):
|
||||
def __init__(self, data: Optional[str] = None, message: Optional[str] = None) -> None:
|
||||
self.data = data
|
||||
self.message = message
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi import FastAPI, Request, Response
|
||||
from fastapi.exceptions import HTTPException
|
||||
from pydantic_validation_decorator import FieldValidationError
|
||||
|
||||
from exceptions.exception import (
|
||||
AuthException,
|
||||
LoginException,
|
||||
@@ -10,62 +11,62 @@ from exceptions.exception import (
|
||||
ServiceWarning,
|
||||
)
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import jsonable_encoder, JSONResponse, ResponseUtil
|
||||
from utils.response_util import JSONResponse, ResponseUtil, jsonable_encoder
|
||||
|
||||
|
||||
def handle_exception(app: FastAPI):
|
||||
def handle_exception(app: FastAPI) -> None:
|
||||
"""
|
||||
全局异常处理
|
||||
"""
|
||||
|
||||
# 自定义token检验异常
|
||||
@app.exception_handler(AuthException)
|
||||
async def auth_exception_handler(request: Request, exc: AuthException):
|
||||
async def auth_exception_handler(request: Request, exc: AuthException) -> Response:
|
||||
return ResponseUtil.unauthorized(data=exc.data, msg=exc.message)
|
||||
|
||||
# 自定义登录检验异常
|
||||
@app.exception_handler(LoginException)
|
||||
async def login_exception_handler(request: Request, exc: LoginException):
|
||||
async def login_exception_handler(request: Request, exc: LoginException) -> Response:
|
||||
return ResponseUtil.failure(data=exc.data, msg=exc.message)
|
||||
|
||||
# 自定义模型检验异常
|
||||
@app.exception_handler(ModelValidatorException)
|
||||
async def model_validator_exception_handler(request: Request, exc: ModelValidatorException):
|
||||
async def model_validator_exception_handler(request: Request, exc: ModelValidatorException) -> Response:
|
||||
logger.warning(exc.message)
|
||||
return ResponseUtil.failure(data=exc.data, msg=exc.message)
|
||||
|
||||
# 自定义字段检验异常
|
||||
@app.exception_handler(FieldValidationError)
|
||||
async def field_validation_error_handler(request: Request, exc: FieldValidationError):
|
||||
async def field_validation_error_handler(request: Request, exc: FieldValidationError) -> Response:
|
||||
logger.warning(exc.message)
|
||||
return ResponseUtil.failure(msg=exc.message)
|
||||
|
||||
# 自定义权限检验异常
|
||||
@app.exception_handler(PermissionException)
|
||||
async def permission_exception_handler(request: Request, exc: PermissionException):
|
||||
async def permission_exception_handler(request: Request, exc: PermissionException) -> Response:
|
||||
return ResponseUtil.forbidden(data=exc.data, msg=exc.message)
|
||||
|
||||
# 自定义服务异常
|
||||
@app.exception_handler(ServiceException)
|
||||
async def service_exception_handler(request: Request, exc: ServiceException):
|
||||
async def service_exception_handler(request: Request, exc: ServiceException) -> Response:
|
||||
logger.error(exc.message)
|
||||
return ResponseUtil.error(data=exc.data, msg=exc.message)
|
||||
|
||||
# 自定义服务警告
|
||||
@app.exception_handler(ServiceWarning)
|
||||
async def service_warning_handler(request: Request, exc: ServiceWarning):
|
||||
async def service_warning_handler(request: Request, exc: ServiceWarning) -> Response:
|
||||
logger.warning(exc.message)
|
||||
return ResponseUtil.failure(data=exc.data, msg=exc.message)
|
||||
|
||||
# 处理其他http请求异常
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_exception_handler(request: Request, exc: HTTPException):
|
||||
async def http_exception_handler(request: Request, exc: HTTPException) -> Response:
|
||||
return JSONResponse(
|
||||
content=jsonable_encoder({'code': exc.status_code, 'msg': exc.detail}), status_code=exc.status_code
|
||||
)
|
||||
|
||||
# 处理其他异常
|
||||
@app.exception_handler(Exception)
|
||||
async def exception_handler(request: Request, exc: Exception):
|
||||
async def exception_handler(request: Request, exc: Exception) -> Response:
|
||||
logger.exception(exc)
|
||||
return ResponseUtil.error(msg=str(exc))
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
||||
from starlette.responses import Response
|
||||
|
||||
from common.context import RequestContext
|
||||
|
||||
|
||||
class ContextCleanupMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
上下文清理中间件
|
||||
"""
|
||||
|
||||
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
||||
"""
|
||||
在每个请求处理完成后清理上下文信息
|
||||
"""
|
||||
response = await call_next(request)
|
||||
# 请求处理完成后清理所有上下文变量
|
||||
RequestContext.clear_all()
|
||||
return response
|
||||
|
||||
|
||||
def add_context_cleanup_middleware(app: FastAPI) -> None:
|
||||
"""
|
||||
添加上下文清理中间件
|
||||
|
||||
:param app: FastAPI对象
|
||||
"""
|
||||
app.add_middleware(ContextCleanupMiddleware)
|
||||
@@ -2,7 +2,7 @@ from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
|
||||
def add_cors_middleware(app: FastAPI):
|
||||
def add_cors_middleware(app: FastAPI) -> None:
|
||||
"""
|
||||
添加跨域中间件
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from fastapi import FastAPI
|
||||
from starlette.middleware.gzip import GZipMiddleware
|
||||
|
||||
|
||||
def add_gzip_middleware(app: FastAPI):
|
||||
def add_gzip_middleware(app: FastAPI) -> None:
|
||||
"""
|
||||
添加gzip压缩中间件
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from middlewares.context_middleware import add_context_cleanup_middleware
|
||||
from middlewares.cors_middleware import add_cors_middleware
|
||||
from middlewares.gzip_middleware import add_gzip_middleware
|
||||
from middlewares.trace_middleware import add_trace_middleware
|
||||
|
||||
|
||||
def handle_middleware(app: FastAPI):
|
||||
def handle_middleware(app: FastAPI) -> None:
|
||||
"""
|
||||
全局中间件处理
|
||||
"""
|
||||
# 加载上下文清理中间件
|
||||
add_context_cleanup_middleware(app)
|
||||
# 加载跨域中间件
|
||||
add_cors_middleware(app)
|
||||
# 加载gzip压缩中间件
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from .ctx import TraceCtx
|
||||
from .middle import TraceASGIMiddleware
|
||||
|
||||
@@ -7,7 +8,7 @@ __all__ = ('TraceASGIMiddleware', 'TraceCtx')
|
||||
__version__ = '0.1.0'
|
||||
|
||||
|
||||
def add_trace_middleware(app: FastAPI):
|
||||
def add_trace_middleware(app: FastAPI) -> None:
|
||||
"""
|
||||
添加trace中间件
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@author: peng
|
||||
@file: ctx.py
|
||||
@time: 2025/1/17 16:57
|
||||
"""
|
||||
|
||||
import contextvars
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -13,11 +6,11 @@ CTX_REQUEST_ID: contextvars.ContextVar[str] = contextvars.ContextVar('request-id
|
||||
|
||||
class TraceCtx:
|
||||
@staticmethod
|
||||
def set_id():
|
||||
def set_id() -> str:
|
||||
_id = uuid4().hex
|
||||
CTX_REQUEST_ID.set(_id)
|
||||
return _id
|
||||
|
||||
@staticmethod
|
||||
def get_id():
|
||||
def get_id() -> str:
|
||||
return CTX_REQUEST_ID.get()
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@author: peng
|
||||
@file: middle.py
|
||||
@time: 2025/1/17 16:57
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
||||
from .span import get_current_span, Span
|
||||
|
||||
from .span import Span, get_current_span
|
||||
|
||||
|
||||
class TraceASGIMiddleware:
|
||||
@@ -21,11 +16,11 @@ class TraceASGIMiddleware:
|
||||
self.app = app
|
||||
|
||||
@staticmethod
|
||||
async def my_receive(receive: Receive, span: Span):
|
||||
async def my_receive(receive: Receive, span: Span) -> Receive:
|
||||
await span.request_before()
|
||||
|
||||
@wraps(receive)
|
||||
async def my_receive():
|
||||
async def my_receive() -> Message:
|
||||
message = await receive()
|
||||
await span.request_after(message)
|
||||
return message
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@author: peng
|
||||
@file: span.py
|
||||
@time: 2025/1/17 16:57
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from starlette.types import Scope, Message
|
||||
|
||||
from starlette.types import Message, Scope
|
||||
|
||||
from .ctx import TraceCtx
|
||||
|
||||
|
||||
@@ -16,16 +12,16 @@ class Span:
|
||||
request(before) --> request(after) --> response(before) --> response(after)
|
||||
"""
|
||||
|
||||
def __init__(self, scope: Scope):
|
||||
def __init__(self, scope: Scope) -> None:
|
||||
self.scope = scope
|
||||
|
||||
async def request_before(self):
|
||||
async def request_before(self) -> None:
|
||||
"""
|
||||
request_before: 处理header信息等, 如记录请求体信息
|
||||
"""
|
||||
TraceCtx.set_id()
|
||||
|
||||
async def request_after(self, message: Message):
|
||||
async def request_after(self, message: Message) -> Message:
|
||||
"""
|
||||
request_after: 处理请求bytes, 如记录请求参数
|
||||
|
||||
@@ -34,7 +30,7 @@ class Span:
|
||||
"""
|
||||
return message
|
||||
|
||||
async def response(self, message: Message):
|
||||
async def response(self, message: Message) -> Message:
|
||||
"""
|
||||
if message['type'] == "http.response.start": -----> request-before
|
||||
pass
|
||||
@@ -48,5 +44,5 @@ class Span:
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_current_span(scope: Scope):
|
||||
async def get_current_span(scope: Scope) -> AsyncGenerator[Span, None]:
|
||||
yield Span(scope)
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
from datetime import datetime
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
|
||||
from functools import lru_cache, wraps
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Any, Callable, Literal, Optional
|
||||
from user_agents import parse
|
||||
from config.enums import BusinessType
|
||||
from config.env import AppConfig
|
||||
from exceptions.exception import LoginException, ServiceException, ServiceWarning
|
||||
from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel
|
||||
from module_admin.service.log_service import LoginLogService, OperationLogService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
class Log:
|
||||
"""
|
||||
日志装饰器
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
business_type: BusinessType,
|
||||
log_type: Optional[Literal['login', 'operation']] = 'operation',
|
||||
):
|
||||
"""
|
||||
日志装饰器
|
||||
|
||||
:param title: 当前日志装饰器装饰的模块标题
|
||||
:param business_type: 业务类型(OTHER其它 INSERT新增 UPDATE修改 DELETE删除 GRANT授权 EXPORT导出 IMPORT导入 FORCE强退 GENCODE生成代码 CLEAN清空数据)
|
||||
:param log_type: 日志类型(login表示登录日志,operation表示为操作日志)
|
||||
:return:
|
||||
"""
|
||||
self.title = title
|
||||
self.business_type = business_type.value
|
||||
self.log_type = log_type
|
||||
|
||||
def __call__(self, func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
# 获取被装饰函数的文件路径
|
||||
file_path = inspect.getfile(func)
|
||||
# 获取项目根路径
|
||||
project_root = os.getcwd()
|
||||
# 处理文件路径,去除项目根路径部分
|
||||
relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.').replace('/', '.')
|
||||
# 获取当前被装饰函数所在路径
|
||||
func_path = f'{relative_path}{func.__name__}()'
|
||||
# 获取上下文信息
|
||||
request_name_list = get_function_parameters_name_by_type(func, Request)
|
||||
request = get_function_parameters_value_by_name(func, request_name_list[0], *args, **kwargs)
|
||||
token = request.headers.get('Authorization')
|
||||
session_name_list = get_function_parameters_name_by_type(func, AsyncSession)
|
||||
query_db = get_function_parameters_value_by_name(func, session_name_list[0], *args, **kwargs)
|
||||
request_method = request.method
|
||||
operator_type = 0
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
if 'Windows' in user_agent or 'Macintosh' in user_agent or 'Linux' in user_agent:
|
||||
operator_type = 1
|
||||
if 'Mobile' in user_agent or 'Android' in user_agent or 'iPhone' in user_agent:
|
||||
operator_type = 2
|
||||
# 获取请求的url
|
||||
oper_url = request.url.path
|
||||
# 获取请求的ip及ip归属区域
|
||||
oper_ip = request.headers.get('X-Forwarded-For')
|
||||
oper_location = '内网IP'
|
||||
if AppConfig.app_ip_location_query:
|
||||
oper_location = get_ip_location(oper_ip)
|
||||
# 根据不同的请求类型使用不同的方法获取请求参数
|
||||
content_type = request.headers.get('Content-Type')
|
||||
if content_type and (
|
||||
'multipart/form-data' in content_type or 'application/x-www-form-urlencoded' in content_type
|
||||
):
|
||||
payload = await request.form()
|
||||
oper_param = '\n'.join([f'{key}: {value}' for key, value in payload.items()])
|
||||
else:
|
||||
payload = await request.body()
|
||||
# 通过 request.path_params 直接访问路径参数
|
||||
path_params = request.path_params
|
||||
oper_param = {}
|
||||
if payload:
|
||||
oper_param.update(json.loads(str(payload, 'utf-8')))
|
||||
if path_params:
|
||||
oper_param.update(path_params)
|
||||
oper_param = json.dumps(oper_param, ensure_ascii=False)
|
||||
# 日志表请求参数字段长度最大为2000,因此在此处判断长度
|
||||
if len(oper_param) > 2000:
|
||||
oper_param = '请求参数过长'
|
||||
|
||||
# 获取操作时间
|
||||
oper_time = datetime.now()
|
||||
# 此处在登录之前向原始函数传递一些登录信息,用于监测在线用户的相关信息
|
||||
login_log = {}
|
||||
if self.log_type == 'login':
|
||||
user_agent_info = parse(user_agent)
|
||||
browser = f'{user_agent_info.browser.family}'
|
||||
system_os = f'{user_agent_info.os.family}'
|
||||
if user_agent_info.browser.version != ():
|
||||
browser += f' {user_agent_info.browser.version[0]}'
|
||||
if user_agent_info.os.version != ():
|
||||
system_os += f' {user_agent_info.os.version[0]}'
|
||||
login_log = dict(
|
||||
ipaddr=oper_ip,
|
||||
loginLocation=oper_location,
|
||||
browser=browser,
|
||||
os=system_os,
|
||||
loginTime=oper_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
)
|
||||
kwargs['form_data'].login_info = login_log
|
||||
try:
|
||||
# 调用原始函数
|
||||
result = await func(*args, **kwargs)
|
||||
except (LoginException, ServiceWarning) as e:
|
||||
logger.warning(e.message)
|
||||
result = ResponseUtil.failure(data=e.data, msg=e.message)
|
||||
except ServiceException as e:
|
||||
logger.error(e.message)
|
||||
result = ResponseUtil.error(data=e.data, msg=e.message)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
result = ResponseUtil.error(msg=str(e))
|
||||
# 获取请求耗时
|
||||
cost_time = float(time.time() - start_time) * 100
|
||||
# 判断请求是否来自api文档
|
||||
request_from_swagger = (
|
||||
request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
|
||||
)
|
||||
request_from_redoc = (
|
||||
request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False
|
||||
)
|
||||
# 根据响应结果的类型使用不同的方法获取响应结果参数
|
||||
if (
|
||||
isinstance(result, JSONResponse)
|
||||
or isinstance(result, ORJSONResponse)
|
||||
or isinstance(result, UJSONResponse)
|
||||
):
|
||||
result_dict = json.loads(str(result.body, 'utf-8'))
|
||||
else:
|
||||
if request_from_swagger or request_from_redoc:
|
||||
result_dict = {}
|
||||
else:
|
||||
if result.status_code == 200:
|
||||
result_dict = {'code': result.status_code, 'message': '获取成功'}
|
||||
else:
|
||||
result_dict = {'code': result.status_code, 'message': '获取失败'}
|
||||
json_result = json.dumps(result_dict, ensure_ascii=False)
|
||||
# 根据响应结果获取响应状态及异常信息
|
||||
status = 1
|
||||
error_msg = ''
|
||||
if result_dict.get('code') == 200:
|
||||
status = 0
|
||||
else:
|
||||
error_msg = result_dict.get('msg')
|
||||
# 根据日志类型向对应的日志表插入数据
|
||||
if self.log_type == 'login':
|
||||
# 登录请求来自于api文档时不记录登录日志,其余情况则记录
|
||||
if request_from_swagger or request_from_redoc:
|
||||
pass
|
||||
else:
|
||||
user = kwargs.get('form_data')
|
||||
user_name = user.username
|
||||
login_log['loginTime'] = oper_time
|
||||
login_log['userName'] = user_name
|
||||
login_log['status'] = str(status)
|
||||
login_log['msg'] = result_dict.get('msg')
|
||||
|
||||
await LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log))
|
||||
else:
|
||||
current_user = await LoginService.get_current_user(request, token, query_db)
|
||||
oper_name = current_user.user.user_name
|
||||
dept_name = current_user.user.dept.dept_name if current_user.user.dept else None
|
||||
operation_log = OperLogModel(
|
||||
title=self.title,
|
||||
businessType=self.business_type,
|
||||
method=func_path,
|
||||
requestMethod=request_method,
|
||||
operatorType=operator_type,
|
||||
operName=oper_name,
|
||||
deptName=dept_name,
|
||||
operUrl=oper_url,
|
||||
operIp=oper_ip,
|
||||
operLocation=oper_location,
|
||||
operParam=oper_param,
|
||||
jsonResult=json_result,
|
||||
status=status,
|
||||
errorMsg=error_msg,
|
||||
operTime=oper_time,
|
||||
costTime=int(cost_time),
|
||||
)
|
||||
await OperationLogService.add_operation_log_services(query_db, operation_log)
|
||||
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_ip_location(oper_ip: str):
|
||||
"""
|
||||
查询ip归属区域
|
||||
|
||||
:param oper_ip: 需要查询的ip
|
||||
:return: ip归属区域
|
||||
"""
|
||||
oper_location = '内网IP'
|
||||
try:
|
||||
if oper_ip != '127.0.0.1' and oper_ip != 'localhost':
|
||||
oper_location = '未知'
|
||||
ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}')
|
||||
if ip_result.status_code == 200:
|
||||
prov = ip_result.json().get('data').get('prov')
|
||||
city = ip_result.json().get('data').get('city')
|
||||
if prov or city:
|
||||
oper_location = f'{prov}-{city}'
|
||||
except Exception as e:
|
||||
oper_location = '未知'
|
||||
print(e)
|
||||
return oper_location
|
||||
|
||||
|
||||
def get_function_parameters_name_by_type(func: Callable, param_type: Any):
|
||||
"""
|
||||
获取函数指定类型的参数名称
|
||||
|
||||
:param func: 函数
|
||||
:param arg_type: 参数类型
|
||||
:return: 函数指定类型的参数名称
|
||||
"""
|
||||
# 获取函数的参数信息
|
||||
parameters = inspect.signature(func).parameters
|
||||
# 找到指定类型的参数名称
|
||||
parameters_name_list = []
|
||||
for name, param in parameters.items():
|
||||
if param.annotation == param_type:
|
||||
parameters_name_list.append(name)
|
||||
return parameters_name_list
|
||||
|
||||
|
||||
def get_function_parameters_value_by_name(func: Callable, name: str, *args, **kwargs):
|
||||
"""
|
||||
获取函数指定参数的值
|
||||
|
||||
:param func: 函数
|
||||
:param name: 参数名
|
||||
:return: 参数值
|
||||
"""
|
||||
# 获取参数值
|
||||
bound_parameters = inspect.signature(func).bind(*args, **kwargs)
|
||||
bound_parameters.apply_defaults()
|
||||
parameters_value = bound_parameters.arguments.get(name)
|
||||
|
||||
return parameters_value
|
||||
@@ -1,75 +0,0 @@
|
||||
from fastapi import Depends
|
||||
from typing import Optional
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
|
||||
|
||||
class GetDataScope:
|
||||
"""
|
||||
获取当前用户数据权限对应的查询sql语句
|
||||
"""
|
||||
|
||||
DATA_SCOPE_ALL = '1'
|
||||
DATA_SCOPE_CUSTOM = '2'
|
||||
DATA_SCOPE_DEPT = '3'
|
||||
DATA_SCOPE_DEPT_AND_CHILD = '4'
|
||||
DATA_SCOPE_SELF = '5'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
query_alias: Optional[str] = '',
|
||||
db_alias: Optional[str] = 'db',
|
||||
user_alias: Optional[str] = 'user_id',
|
||||
dept_alias: Optional[str] = 'dept_id',
|
||||
):
|
||||
"""
|
||||
获取当前用户数据权限对应的查询sql语句
|
||||
|
||||
:param query_alias: 所要查询表对应的sqlalchemy模型名称,默认为''
|
||||
:param db_alias: orm对象别名,默认为'db'
|
||||
:param user_alias: 用户id字段别名,默认为'user_id'
|
||||
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
||||
"""
|
||||
self.query_alias = query_alias
|
||||
self.db_alias = db_alias
|
||||
self.user_alias = user_alias
|
||||
self.dept_alias = dept_alias
|
||||
|
||||
def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
|
||||
user_id = current_user.user.user_id
|
||||
dept_id = current_user.user.dept_id
|
||||
custom_data_scope_role_id_list = [
|
||||
item.role_id for item in current_user.user.role if item.data_scope == self.DATA_SCOPE_CUSTOM
|
||||
]
|
||||
param_sql_list = []
|
||||
for role in current_user.user.role:
|
||||
if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL:
|
||||
param_sql_list = ['1 == 1']
|
||||
break
|
||||
elif role.data_scope == self.DATA_SCOPE_CUSTOM:
|
||||
if len(custom_data_scope_role_id_list) > 1:
|
||||
param_sql_list.append(
|
||||
f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_({custom_data_scope_role_id_list}))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
||||
)
|
||||
else:
|
||||
param_sql_list.append(
|
||||
f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id == {role.role_id})) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_DEPT:
|
||||
param_sql_list.append(
|
||||
f"{self.query_alias}.{self.dept_alias} == {dept_id} if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD:
|
||||
param_sql_list.append(
|
||||
f"{self.query_alias}.{self.dept_alias}.in_(select(SysDept.dept_id).where(or_(SysDept.dept_id == {dept_id}, func.find_in_set({dept_id}, SysDept.ancestors)))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_SELF:
|
||||
param_sql_list.append(
|
||||
f"{self.query_alias}.{self.user_alias} == {user_id} if hasattr({self.query_alias}, '{self.user_alias}') else 1 == 0"
|
||||
)
|
||||
else:
|
||||
param_sql_list.append('1 == 0')
|
||||
param_sql_list = list(dict.fromkeys(param_sql_list))
|
||||
param_sql = f"or_({', '.join(param_sql_list)})"
|
||||
|
||||
return param_sql
|
||||
@@ -1,68 +0,0 @@
|
||||
from fastapi import Depends
|
||||
from typing import List, Union
|
||||
from exceptions.exception import PermissionException
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
|
||||
|
||||
class CheckUserInterfaceAuth:
|
||||
"""
|
||||
校验当前用户是否具有相应的接口权限
|
||||
"""
|
||||
|
||||
def __init__(self, perm: Union[str, List], is_strict: bool = False):
|
||||
"""
|
||||
校验当前用户是否具有相应的接口权限
|
||||
|
||||
:param perm: 权限标识
|
||||
:param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过
|
||||
"""
|
||||
self.perm = perm
|
||||
self.is_strict = is_strict
|
||||
|
||||
def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
|
||||
user_auth_list = current_user.permissions
|
||||
if '*:*:*' in user_auth_list:
|
||||
return True
|
||||
if isinstance(self.perm, str):
|
||||
if self.perm in user_auth_list:
|
||||
return True
|
||||
if isinstance(self.perm, list):
|
||||
if self.is_strict:
|
||||
if all([perm_str in user_auth_list for perm_str in self.perm]):
|
||||
return True
|
||||
else:
|
||||
if any([perm_str in user_auth_list for perm_str in self.perm]):
|
||||
return True
|
||||
raise PermissionException(data='', message='该用户无此接口权限')
|
||||
|
||||
|
||||
class CheckRoleInterfaceAuth:
|
||||
"""
|
||||
根据角色校验当前用户是否具有相应的接口权限
|
||||
"""
|
||||
|
||||
def __init__(self, role_key: Union[str, List], is_strict: bool = False):
|
||||
"""
|
||||
根据角色校验当前用户是否具有相应的接口权限
|
||||
|
||||
:param role_key: 角色标识
|
||||
:param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过
|
||||
"""
|
||||
self.role_key = role_key
|
||||
self.is_strict = is_strict
|
||||
|
||||
def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
|
||||
user_role_list = current_user.user.role
|
||||
user_role_key_list = [role.role_key for role in user_role_list]
|
||||
if isinstance(self.role_key, str):
|
||||
if self.role_key in user_role_key_list:
|
||||
return True
|
||||
if isinstance(self.role_key, list):
|
||||
if self.is_strict:
|
||||
if all([role_key_str in user_role_key_list for role_key_str in self.role_key]):
|
||||
return True
|
||||
else:
|
||||
if any([role_key_str in user_role_key_list for role_key_str in self.role_key]):
|
||||
return True
|
||||
raise PermissionException(data='', message='该用户无此接口权限')
|
||||
@@ -1,20 +1,29 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from typing import List
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Path, Request, Response
|
||||
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import PreAuthDependency
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel
|
||||
from module_admin.service.cache_service import CacheService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
cacheController = APIRouter(prefix='/monitor/cache', dependencies=[Depends(LoginService.get_current_user)])
|
||||
|
||||
|
||||
@cacheController.get(
|
||||
'', response_model=CacheMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]
|
||||
cache_controller = APIRouterPro(
|
||||
prefix='/monitor/cache', order_num=15, tags=['系统监控-缓存监控'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
async def get_monitor_cache_info(request: Request):
|
||||
|
||||
|
||||
@cache_controller.get(
|
||||
'',
|
||||
summary='获取缓存监控信息接口',
|
||||
description='用于获取缓存监控信息',
|
||||
response_model=DataResponseModel[CacheMonitorModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
|
||||
)
|
||||
async def get_monitor_cache_info(request: Request) -> Response:
|
||||
# 获取全量数据
|
||||
cache_info_query_result = await CacheService.get_cache_monitor_statistical_info_services(request)
|
||||
logger.info('获取成功')
|
||||
@@ -22,12 +31,14 @@ async def get_monitor_cache_info(request: Request):
|
||||
return ResponseUtil.success(data=cache_info_query_result)
|
||||
|
||||
|
||||
@cacheController.get(
|
||||
@cache_controller.get(
|
||||
'/getNames',
|
||||
response_model=List[CacheInfoModel],
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))],
|
||||
summary='获取缓存名称列表接口',
|
||||
description='用于获取缓存名称列表',
|
||||
response_model=DataResponseModel[list[CacheInfoModel]],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
|
||||
)
|
||||
async def get_monitor_cache_name(request: Request):
|
||||
async def get_monitor_cache_name(request: Request) -> Response:
|
||||
# 获取全量数据
|
||||
cache_name_list_result = await CacheService.get_cache_monitor_cache_name_services()
|
||||
logger.info('获取成功')
|
||||
@@ -35,12 +46,14 @@ async def get_monitor_cache_name(request: Request):
|
||||
return ResponseUtil.success(data=cache_name_list_result)
|
||||
|
||||
|
||||
@cacheController.get(
|
||||
@cache_controller.get(
|
||||
'/getKeys/{cache_name}',
|
||||
response_model=List[str],
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))],
|
||||
summary='获取缓存键列表接口',
|
||||
description='用于获取指定缓存名称下的所有缓存键列表',
|
||||
response_model=DataResponseModel[list[str]],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
|
||||
)
|
||||
async def get_monitor_cache_key(request: Request, cache_name: str):
|
||||
async def get_monitor_cache_key(request: Request, cache_name: Annotated[str, Path(description='缓存名称')]) -> Response:
|
||||
# 获取全量数据
|
||||
cache_key_list_result = await CacheService.get_cache_monitor_cache_key_services(request, cache_name)
|
||||
logger.info('获取成功')
|
||||
@@ -48,12 +61,18 @@ async def get_monitor_cache_key(request: Request, cache_name: str):
|
||||
return ResponseUtil.success(data=cache_key_list_result)
|
||||
|
||||
|
||||
@cacheController.get(
|
||||
@cache_controller.get(
|
||||
'/getValue/{cache_name}/{cache_key}',
|
||||
response_model=CacheInfoModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))],
|
||||
summary='获取缓存值接口',
|
||||
description='用于获取指定缓存名称下的指定缓存键对应的值',
|
||||
response_model=DataResponseModel[CacheInfoModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
|
||||
)
|
||||
async def get_monitor_cache_value(request: Request, cache_name: str, cache_key: str):
|
||||
async def get_monitor_cache_value(
|
||||
request: Request,
|
||||
cache_name: Annotated[str, Path(description='缓存名称')],
|
||||
cache_key: Annotated[str, Path(description='缓存键')],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
cache_value_list_result = await CacheService.get_cache_monitor_cache_value_services(request, cache_name, cache_key)
|
||||
logger.info('获取成功')
|
||||
@@ -61,28 +80,44 @@ async def get_monitor_cache_value(request: Request, cache_name: str, cache_key:
|
||||
return ResponseUtil.success(data=cache_value_list_result)
|
||||
|
||||
|
||||
@cacheController.delete(
|
||||
'/clearCacheName/{cache_name}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]
|
||||
@cache_controller.delete(
|
||||
'/clearCacheName/{cache_name}',
|
||||
summary='清除缓存名称接口',
|
||||
description='用于清除指定缓存名称下的所有缓存键值对',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
|
||||
)
|
||||
async def clear_monitor_cache_name(request: Request, cache_name: str):
|
||||
async def clear_monitor_cache_name(
|
||||
request: Request, cache_name: Annotated[str, Path(description='缓存名称')]
|
||||
) -> Response:
|
||||
clear_cache_name_result = await CacheService.clear_cache_monitor_cache_name_services(request, cache_name)
|
||||
logger.info(clear_cache_name_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=clear_cache_name_result.message)
|
||||
|
||||
|
||||
@cacheController.delete(
|
||||
'/clearCacheKey/{cache_key}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]
|
||||
@cache_controller.delete(
|
||||
'/clearCacheKey/{cache_key}',
|
||||
summary='清除缓存键接口',
|
||||
description='用于清除指定缓存键对应的值',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
|
||||
)
|
||||
async def clear_monitor_cache_key(request: Request, cache_key: str):
|
||||
async def clear_monitor_cache_key(request: Request, cache_key: Annotated[str, Path(description='缓存键')]) -> Response:
|
||||
clear_cache_key_result = await CacheService.clear_cache_monitor_cache_key_services(request, cache_key)
|
||||
logger.info(clear_cache_key_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=clear_cache_key_result.message)
|
||||
|
||||
|
||||
@cacheController.delete('/clearCacheAll', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))])
|
||||
async def clear_monitor_cache_all(request: Request):
|
||||
@cache_controller.delete(
|
||||
'/clearCacheAll',
|
||||
summary='清除所有缓存接口',
|
||||
description='用于清除所有缓存键值对',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:cache:list')],
|
||||
)
|
||||
async def clear_monitor_cache_all(request: Request) -> Response:
|
||||
clear_cache_all_result = await CacheService.clear_cache_monitor_all_services(request)
|
||||
logger.info(clear_cache_all_result.message)
|
||||
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from fastapi import APIRouter, Request
|
||||
from config.enums import RedisInitKeyConfig
|
||||
|
||||
from fastapi import Request, Response
|
||||
|
||||
from common.enums import RedisInitKeyConfig
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DynamicResponseModel
|
||||
from module_admin.entity.vo.login_vo import CaptchaCode
|
||||
from module_admin.service.captcha_service import CaptchaService
|
||||
from utils.response_util import ResponseUtil
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
captcha_controller = APIRouterPro(order_num=2, tags=['验证码模块'])
|
||||
|
||||
|
||||
captchaController = APIRouter()
|
||||
|
||||
|
||||
@captchaController.get('/captchaImage')
|
||||
async def get_captcha_image(request: Request):
|
||||
@captcha_controller.get(
|
||||
'/captchaImage',
|
||||
summary='获取图片验证码接口',
|
||||
description='用于获取图片验证码',
|
||||
response_model=DynamicResponseModel[CaptchaCode],
|
||||
)
|
||||
async def get_captcha_image(request: Request) -> Response:
|
||||
captcha_enabled = (
|
||||
True
|
||||
if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled')
|
||||
== 'true'
|
||||
else False
|
||||
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') == 'true'
|
||||
)
|
||||
register_enabled = (
|
||||
True
|
||||
if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true'
|
||||
else False
|
||||
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true'
|
||||
)
|
||||
session_id = str(uuid.uuid4())
|
||||
captcha_result = await CaptchaService.create_captcha_image_service()
|
||||
|
||||
@@ -1,35 +1,73 @@
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, Query, Request, UploadFile
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import BackgroundTasks, File, Query, Request, Response, UploadFile
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from common.aspect.pre_auth import PreAuthDependency
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DynamicResponseModel
|
||||
from module_admin.entity.vo.common_vo import UploadResponseModel
|
||||
from module_admin.service.common_service import CommonService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
commonController = APIRouter(prefix='/common', dependencies=[Depends(LoginService.get_current_user)])
|
||||
common_controller = APIRouterPro(prefix='/common', order_num=16, tags=['通用模块'], dependencies=[PreAuthDependency()])
|
||||
|
||||
|
||||
@commonController.post('/upload')
|
||||
async def common_upload(request: Request, file: UploadFile = File(...)):
|
||||
@common_controller.post(
|
||||
'/upload',
|
||||
summary='通用文件上传接口',
|
||||
description='用于上传文件',
|
||||
response_model=DynamicResponseModel[UploadResponseModel],
|
||||
)
|
||||
async def common_upload(request: Request, file: Annotated[UploadFile, File(...)]) -> Response:
|
||||
upload_result = await CommonService.upload_service(request, file)
|
||||
logger.info('上传成功')
|
||||
|
||||
return ResponseUtil.success(model_content=upload_result.result)
|
||||
|
||||
|
||||
@commonController.get('/download')
|
||||
@common_controller.get(
|
||||
'/download',
|
||||
summary='通用文件下载接口',
|
||||
description='用于下载下载目录中的文件',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
async def common_download(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
file_name: str = Query(alias='fileName'),
|
||||
delete: bool = Query(),
|
||||
):
|
||||
file_name: Annotated[str, Query(alias='fileName')],
|
||||
delete: Annotated[bool, Query()],
|
||||
) -> Response:
|
||||
download_result = await CommonService.download_services(background_tasks, file_name, delete)
|
||||
logger.info(download_result.message)
|
||||
|
||||
return ResponseUtil.streaming(data=download_result.result)
|
||||
|
||||
|
||||
@commonController.get('/download/resource')
|
||||
async def common_download_resource(request: Request, resource: str = Query()):
|
||||
@common_controller.get(
|
||||
'/download/resource',
|
||||
summary='通用资源文件下载接口',
|
||||
description='用于下载上传目录中的资源文件',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
async def common_download_resource(request: Request, resource: Annotated[str, Query()]) -> Response:
|
||||
download_resource_result = await CommonService.download_resource_services(resource)
|
||||
logger.info(download_resource_result.message)
|
||||
|
||||
|
||||
@@ -1,32 +1,42 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Form, Path, Query, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.config_service import ConfigService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
configController = APIRouter(prefix='/system/config', dependencies=[Depends(LoginService.get_current_user)])
|
||||
config_controller = APIRouterPro(
|
||||
prefix='/system/config', order_num=9, tags=['系统管理-参数管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@configController.get(
|
||||
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:list'))]
|
||||
@config_controller.get(
|
||||
'/list',
|
||||
summary='获取参数分页列表接口',
|
||||
description='用于获取参数分页列表',
|
||||
response_model=PageResponseModel[ConfigModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:config:list')],
|
||||
)
|
||||
async def get_system_config_list(
|
||||
request: Request,
|
||||
config_page_query: ConfigPageQueryModel = Depends(ConfigPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
config_page_query: Annotated[ConfigPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
config_page_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=True)
|
||||
logger.info('获取成功')
|
||||
@@ -34,15 +44,21 @@ async def get_system_config_list(
|
||||
return ResponseUtil.success(model_content=config_page_query_result)
|
||||
|
||||
|
||||
@configController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:config:add'))])
|
||||
@config_controller.post(
|
||||
'',
|
||||
summary='新增参数接口',
|
||||
description='用于新增参数',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:config:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_config')
|
||||
@Log(title='参数管理', business_type=BusinessType.INSERT)
|
||||
async def add_system_config(
|
||||
request: Request,
|
||||
add_config: ConfigModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_config.create_by = current_user.user.user_name
|
||||
add_config.create_time = datetime.now()
|
||||
add_config.update_by = current_user.user.user_name
|
||||
@@ -53,15 +69,21 @@ async def add_system_config(
|
||||
return ResponseUtil.success(msg=add_config_result.message)
|
||||
|
||||
|
||||
@configController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:config:edit'))])
|
||||
@config_controller.put(
|
||||
'',
|
||||
summary='编辑参数接口',
|
||||
description='用于编辑参数',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:config:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_config')
|
||||
@Log(title='参数管理', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_config(
|
||||
request: Request,
|
||||
edit_config: ConfigModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_config.update_by = current_user.user.user_name
|
||||
edit_config.update_time = datetime.now()
|
||||
edit_config_result = await ConfigService.edit_config_services(request, query_db, edit_config)
|
||||
@@ -70,18 +92,37 @@ async def edit_system_config(
|
||||
return ResponseUtil.success(msg=edit_config_result.message)
|
||||
|
||||
|
||||
@configController.delete('/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))])
|
||||
@config_controller.delete(
|
||||
'/refreshCache',
|
||||
summary='刷新参数缓存接口',
|
||||
description='用于刷新参数缓存',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:config:remove')],
|
||||
)
|
||||
@Log(title='参数管理', business_type=BusinessType.UPDATE)
|
||||
async def refresh_system_config(request: Request, query_db: AsyncSession = Depends(get_db)):
|
||||
async def refresh_system_config(
|
||||
request: Request,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
refresh_config_result = await ConfigService.refresh_sys_config_services(request, query_db)
|
||||
logger.info(refresh_config_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=refresh_config_result.message)
|
||||
|
||||
|
||||
@configController.delete('/{config_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))])
|
||||
@config_controller.delete(
|
||||
'/{config_ids}',
|
||||
summary='删除参数接口',
|
||||
description='用于删除参数',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:config:remove')],
|
||||
)
|
||||
@Log(title='参数管理', business_type=BusinessType.DELETE)
|
||||
async def delete_system_config(request: Request, config_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_config(
|
||||
request: Request,
|
||||
config_ids: Annotated[str, Path(description='需要删除的参数主键')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_config = DeleteConfigModel(configIds=config_ids)
|
||||
delete_config_result = await ConfigService.delete_config_services(request, query_db, delete_config)
|
||||
logger.info(delete_config_result.message)
|
||||
@@ -89,18 +130,31 @@ async def delete_system_config(request: Request, config_ids: str, query_db: Asyn
|
||||
return ResponseUtil.success(msg=delete_config_result.message)
|
||||
|
||||
|
||||
@configController.get(
|
||||
'/{config_id}', response_model=ConfigModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:query'))]
|
||||
@config_controller.get(
|
||||
'/{config_id}',
|
||||
summary='获取参数详情接口',
|
||||
description='用于获取指定参数的详细信息',
|
||||
response_model=DataResponseModel[ConfigModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:config:query')],
|
||||
)
|
||||
async def query_detail_system_config(request: Request, config_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
async def query_detail_system_config(
|
||||
request: Request,
|
||||
config_id: Annotated[int, Path(description='参数主键')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
config_detail_result = await ConfigService.config_detail_services(query_db, config_id)
|
||||
logger.info(f'获取config_id为{config_id}的信息成功')
|
||||
|
||||
return ResponseUtil.success(data=config_detail_result)
|
||||
|
||||
|
||||
@configController.get('/configKey/{config_key}')
|
||||
async def query_system_config(request: Request, config_key: str):
|
||||
@config_controller.get(
|
||||
'/configKey/{config_key}',
|
||||
summary='根据参数键查询参数值接口',
|
||||
description='用于根据参数键从缓存中查询参数值',
|
||||
response_model=ResponseBaseModel,
|
||||
)
|
||||
async def query_system_config(request: Request, config_key: str) -> Response:
|
||||
# 获取全量数据
|
||||
config_query_result = await ConfigService.query_config_list_from_cache_services(request.app.state.redis, config_key)
|
||||
logger.info('获取成功')
|
||||
@@ -108,13 +162,27 @@ async def query_system_config(request: Request, config_key: str):
|
||||
return ResponseUtil.success(msg=config_query_result)
|
||||
|
||||
|
||||
@configController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:config:export'))])
|
||||
@config_controller.post(
|
||||
'/export',
|
||||
summary='导出参数列表接口',
|
||||
description='用于导出当前符合查询条件的参数列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回参数列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('system:config:export')],
|
||||
)
|
||||
@Log(title='参数管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_config_list(
|
||||
request: Request,
|
||||
config_page_query: ConfigPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
config_page_query: Annotated[ConfigPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
config_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=False)
|
||||
config_export_result = await ConfigService.export_config_list_services(config_query_result)
|
||||
|
||||
@@ -1,35 +1,44 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Path, Query, Request, Response
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy import ColumnElement
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.data_scope import GetDataScope
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.data_scope import DataScopeDependency
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptQueryModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.dept_service import DeptService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
deptController = APIRouter(prefix='/system/dept', dependencies=[Depends(LoginService.get_current_user)])
|
||||
dept_controller = APIRouterPro(
|
||||
prefix='/system/dept', order_num=6, tags=['系统管理-部门管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@deptController.get(
|
||||
@dept_controller.get(
|
||||
'/list/exclude/{dept_id}',
|
||||
response_model=List[DeptModel],
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))],
|
||||
summary='获取编辑部门的下拉树接口',
|
||||
description='用于获取部门下拉树,不包含指定部门及其子部门',
|
||||
response_model=DataResponseModel[list[DeptModel]],
|
||||
dependencies=[UserInterfaceAuthDependency('system:dept:list')],
|
||||
)
|
||||
async def get_system_dept_tree_for_edit_option(
|
||||
request: Request,
|
||||
dept_id: int,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
dept_id: Annotated[int, Path(description='部门id')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
dept_query = DeptModel(deptId=dept_id)
|
||||
dept_query_result = await DeptService.get_dept_for_edit_option_services(query_db, dept_query, data_scope_sql)
|
||||
logger.info('获取成功')
|
||||
@@ -37,30 +46,40 @@ async def get_system_dept_tree_for_edit_option(
|
||||
return ResponseUtil.success(data=dept_query_result)
|
||||
|
||||
|
||||
@deptController.get(
|
||||
'/list', response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))]
|
||||
@dept_controller.get(
|
||||
'/list',
|
||||
summary='获取部门列表接口',
|
||||
description='用于获取部门列表',
|
||||
response_model=DataResponseModel[list[DeptModel]],
|
||||
dependencies=[UserInterfaceAuthDependency('system:dept:list')],
|
||||
)
|
||||
async def get_system_dept_list(
|
||||
request: Request,
|
||||
dept_query: DeptQueryModel = Depends(DeptQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
dept_query: Annotated[DeptQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
dept_query_result = await DeptService.get_dept_list_services(query_db, dept_query, data_scope_sql)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(data=dept_query_result)
|
||||
|
||||
|
||||
@deptController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:add'))])
|
||||
@dept_controller.post(
|
||||
'',
|
||||
summary='新增部门接口',
|
||||
description='用于新增部门',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dept:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_dept')
|
||||
@Log(title='部门管理', business_type=BusinessType.INSERT)
|
||||
async def add_system_dept(
|
||||
request: Request,
|
||||
add_dept: DeptModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_dept.create_by = current_user.user.user_name
|
||||
add_dept.create_time = datetime.now()
|
||||
add_dept.update_by = current_user.user.user_name
|
||||
@@ -68,19 +87,25 @@ async def add_system_dept(
|
||||
add_dept_result = await DeptService.add_dept_services(query_db, add_dept)
|
||||
logger.info(add_dept_result.message)
|
||||
|
||||
return ResponseUtil.success(data=add_dept_result)
|
||||
return ResponseUtil.success(msg=add_dept_result.message)
|
||||
|
||||
|
||||
@deptController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:edit'))])
|
||||
@dept_controller.put(
|
||||
'',
|
||||
summary='编辑部门接口',
|
||||
description='用于编辑部门',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dept:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_dept')
|
||||
@Log(title='部门管理', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_dept(
|
||||
request: Request,
|
||||
edit_dept: DeptModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
if not current_user.user.admin:
|
||||
await DeptService.check_dept_data_scope_services(query_db, edit_dept.dept_id, data_scope_sql)
|
||||
edit_dept.update_by = current_user.user.user_name
|
||||
@@ -91,15 +116,21 @@ async def edit_system_dept(
|
||||
return ResponseUtil.success(msg=edit_dept_result.message)
|
||||
|
||||
|
||||
@deptController.delete('/{dept_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:remove'))])
|
||||
@dept_controller.delete(
|
||||
'/{dept_ids}',
|
||||
summary='删除部门接口',
|
||||
description='用于删除部门',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dept:remove')],
|
||||
)
|
||||
@Log(title='部门管理', business_type=BusinessType.DELETE)
|
||||
async def delete_system_dept(
|
||||
request: Request,
|
||||
dept_ids: str,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
dept_ids: Annotated[str, Path(description='需要删除的部门id')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
dept_id_list = dept_ids.split(',') if dept_ids else []
|
||||
if dept_id_list:
|
||||
for dept_id in dept_id_list:
|
||||
@@ -114,16 +145,20 @@ async def delete_system_dept(
|
||||
return ResponseUtil.success(msg=delete_dept_result.message)
|
||||
|
||||
|
||||
@deptController.get(
|
||||
'/{dept_id}', response_model=DeptModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:query'))]
|
||||
@dept_controller.get(
|
||||
'/{dept_id}',
|
||||
summary='获取部门详情接口',
|
||||
description='用于获取指定部门的详情信息',
|
||||
response_model=DataResponseModel[DeptModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:dept:query')],
|
||||
)
|
||||
async def query_detail_system_dept(
|
||||
request: Request,
|
||||
dept_id: int,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
dept_id: Annotated[int, Path(description='部门id')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
if not current_user.user.admin:
|
||||
await DeptService.check_dept_data_scope_services(query_db, dept_id, data_scope_sql)
|
||||
detail_dept_result = await DeptService.dept_detail_services(query_db, dept_id)
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Form, Path, Query, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.dict_vo import (
|
||||
DeleteDictDataModel,
|
||||
DeleteDictTypeModel,
|
||||
@@ -17,24 +23,27 @@ from module_admin.entity.vo.dict_vo import (
|
||||
)
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.dict_service import DictDataService, DictTypeService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
dictController = APIRouter(prefix='/system/dict', dependencies=[Depends(LoginService.get_current_user)])
|
||||
dict_controller = APIRouterPro(
|
||||
prefix='/system/dict', order_num=8, tags=['系统管理-字典管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@dictController.get(
|
||||
'/type/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]
|
||||
@dict_controller.get(
|
||||
'/type/list',
|
||||
summary='获取字典类型分页列表接口',
|
||||
description='用于获取字典类型分页列表',
|
||||
response_model=PageResponseModel[DictTypeModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:list')],
|
||||
)
|
||||
async def get_system_dict_type_list(
|
||||
request: Request,
|
||||
dict_type_page_query: DictTypePageQueryModel = Depends(DictTypePageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
dict_type_page_query: Annotated[DictTypePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
dict_type_page_query_result = await DictTypeService.get_dict_type_list_services(
|
||||
query_db, dict_type_page_query, is_page=True
|
||||
@@ -44,15 +53,21 @@ async def get_system_dict_type_list(
|
||||
return ResponseUtil.success(model_content=dict_type_page_query_result)
|
||||
|
||||
|
||||
@dictController.post('/type', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))])
|
||||
@dict_controller.post(
|
||||
'/type',
|
||||
summary='新增字典类型接口',
|
||||
description='用于新增字典类型',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_dict_type')
|
||||
@Log(title='字典类型', business_type=BusinessType.INSERT)
|
||||
async def add_system_dict_type(
|
||||
request: Request,
|
||||
add_dict_type: DictTypeModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_dict_type.create_by = current_user.user.user_name
|
||||
add_dict_type.create_time = datetime.now()
|
||||
add_dict_type.update_by = current_user.user.user_name
|
||||
@@ -63,15 +78,21 @@ async def add_system_dict_type(
|
||||
return ResponseUtil.success(msg=add_dict_type_result.message)
|
||||
|
||||
|
||||
@dictController.put('/type', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))])
|
||||
@dict_controller.put(
|
||||
'/type',
|
||||
summary='编辑字典类型接口',
|
||||
description='用于编辑字典类型',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_dict_type')
|
||||
@Log(title='字典类型', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_dict_type(
|
||||
request: Request,
|
||||
edit_dict_type: DictTypeModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_dict_type.update_by = current_user.user.user_name
|
||||
edit_dict_type.update_time = datetime.now()
|
||||
edit_dict_type_result = await DictTypeService.edit_dict_type_services(request, query_db, edit_dict_type)
|
||||
@@ -80,18 +101,34 @@ async def edit_system_dict_type(
|
||||
return ResponseUtil.success(msg=edit_dict_type_result.message)
|
||||
|
||||
|
||||
@dictController.delete('/type/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))])
|
||||
@dict_controller.delete(
|
||||
'/type/refreshCache',
|
||||
summary='刷新字典缓存接口',
|
||||
description='用于刷新字典缓存',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:remove')],
|
||||
)
|
||||
@Log(title='字典类型', business_type=BusinessType.UPDATE)
|
||||
async def refresh_system_dict(request: Request, query_db: AsyncSession = Depends(get_db)):
|
||||
async def refresh_system_dict(request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]) -> Response:
|
||||
refresh_dict_result = await DictTypeService.refresh_sys_dict_services(request, query_db)
|
||||
logger.info(refresh_dict_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=refresh_dict_result.message)
|
||||
|
||||
|
||||
@dictController.delete('/type/{dict_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))])
|
||||
@dict_controller.delete(
|
||||
'/type/{dict_ids}',
|
||||
summary='删除字典类型接口',
|
||||
description='用于删除字典类型',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:remove')],
|
||||
)
|
||||
@Log(title='字典类型', business_type=BusinessType.DELETE)
|
||||
async def delete_system_dict_type(request: Request, dict_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_dict_type(
|
||||
request: Request,
|
||||
dict_ids: Annotated[str, Path(description='需要删除的字典主键')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_dict_type = DeleteDictTypeModel(dictIds=dict_ids)
|
||||
delete_dict_type_result = await DictTypeService.delete_dict_type_services(request, query_db, delete_dict_type)
|
||||
logger.info(delete_dict_type_result.message)
|
||||
@@ -99,33 +136,62 @@ async def delete_system_dict_type(request: Request, dict_ids: str, query_db: Asy
|
||||
return ResponseUtil.success(msg=delete_dict_type_result.message)
|
||||
|
||||
|
||||
@dictController.get('/type/optionselect', response_model=List[DictTypeModel])
|
||||
async def query_system_dict_type_options(request: Request, query_db: AsyncSession = Depends(get_db)):
|
||||
@dict_controller.get(
|
||||
'/type/optionselect',
|
||||
summary='获取字典类型下拉列表接口',
|
||||
description='用于获取字典类型下拉列表',
|
||||
response_model=DataResponseModel[list[DictTypeModel]],
|
||||
)
|
||||
async def query_system_dict_type_options(
|
||||
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
|
||||
) -> Response:
|
||||
dict_type_query_result = await DictTypeService.get_dict_type_list_services(
|
||||
query_db, DictTypePageQueryModel(**dict()), is_page=False
|
||||
query_db, DictTypePageQueryModel(), is_page=False
|
||||
)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(data=dict_type_query_result)
|
||||
|
||||
|
||||
@dictController.get(
|
||||
'/type/{dict_id}', response_model=DictTypeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))]
|
||||
@dict_controller.get(
|
||||
'/type/{dict_id}',
|
||||
summary='获取字典类型详情接口',
|
||||
description='用于获取指定字典类型的详细信息',
|
||||
response_model=DataResponseModel[DictTypeModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:query')],
|
||||
)
|
||||
async def query_detail_system_dict_type(request: Request, dict_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
async def query_detail_system_dict_type(
|
||||
request: Request,
|
||||
dict_id: Annotated[int, Path(description='字典主键')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
dict_type_detail_result = await DictTypeService.dict_type_detail_services(query_db, dict_id)
|
||||
logger.info(f'获取dict_id为{dict_id}的信息成功')
|
||||
|
||||
return ResponseUtil.success(data=dict_type_detail_result)
|
||||
|
||||
|
||||
@dictController.post('/type/export', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))])
|
||||
@dict_controller.post(
|
||||
'/type/export',
|
||||
summary='导出字典类型列表接口',
|
||||
description='用于导出当前符合查询条件的字典类型列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回字典类型列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:export')],
|
||||
)
|
||||
@Log(title='字典类型', business_type=BusinessType.EXPORT)
|
||||
async def export_system_dict_type_list(
|
||||
request: Request,
|
||||
dict_type_page_query: DictTypePageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
dict_type_page_query: Annotated[DictTypePageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
dict_type_query_result = await DictTypeService.get_dict_type_list_services(
|
||||
query_db, dict_type_page_query, is_page=False
|
||||
@@ -136,8 +202,17 @@ async def export_system_dict_type_list(
|
||||
return ResponseUtil.streaming(data=bytes2file_response(dict_type_export_result))
|
||||
|
||||
|
||||
@dictController.get('/data/type/{dict_type}')
|
||||
async def query_system_dict_type_data(request: Request, dict_type: str, query_db: AsyncSession = Depends(get_db)):
|
||||
@dict_controller.get(
|
||||
'/data/type/{dict_type}',
|
||||
summary='获取指定字典类型的数据列表接口',
|
||||
description='用于从缓存中获取指定字典类型的所有数据项',
|
||||
response_model=DataResponseModel[list[DictDataModel]],
|
||||
)
|
||||
async def query_system_dict_type_data(
|
||||
request: Request,
|
||||
dict_type: Annotated[str, Path(description='字典类型')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
dict_data_query_result = await DictDataService.query_dict_data_list_from_cache_services(
|
||||
request.app.state.redis, dict_type
|
||||
@@ -147,14 +222,18 @@ async def query_system_dict_type_data(request: Request, dict_type: str, query_db
|
||||
return ResponseUtil.success(data=dict_data_query_result)
|
||||
|
||||
|
||||
@dictController.get(
|
||||
'/data/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]
|
||||
@dict_controller.get(
|
||||
'/data/list',
|
||||
summary='获取字典数据分页列表接口',
|
||||
description='用于获取字典数据分页列表',
|
||||
response_model=PageResponseModel[DictDataModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:list')],
|
||||
)
|
||||
async def get_system_dict_data_list(
|
||||
request: Request,
|
||||
dict_data_page_query: DictDataPageQueryModel = Depends(DictDataPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
dict_data_page_query: Annotated[DictDataPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
dict_data_page_query_result = await DictDataService.get_dict_data_list_services(
|
||||
query_db, dict_data_page_query, is_page=True
|
||||
@@ -164,15 +243,21 @@ async def get_system_dict_data_list(
|
||||
return ResponseUtil.success(model_content=dict_data_page_query_result)
|
||||
|
||||
|
||||
@dictController.post('/data', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))])
|
||||
@dict_controller.post(
|
||||
'/data',
|
||||
summary='新增字典数据接口',
|
||||
description='用于新增字典数据',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_dict_data')
|
||||
@Log(title='字典数据', business_type=BusinessType.INSERT)
|
||||
async def add_system_dict_data(
|
||||
request: Request,
|
||||
add_dict_data: DictDataModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_dict_data.create_by = current_user.user.user_name
|
||||
add_dict_data.create_time = datetime.now()
|
||||
add_dict_data.update_by = current_user.user.user_name
|
||||
@@ -183,15 +268,21 @@ async def add_system_dict_data(
|
||||
return ResponseUtil.success(msg=add_dict_data_result.message)
|
||||
|
||||
|
||||
@dictController.put('/data', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))])
|
||||
@dict_controller.put(
|
||||
'/data',
|
||||
summary='编辑字典数据接口',
|
||||
description='用于编辑字典数据',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_dict_data')
|
||||
@Log(title='字典数据', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_dict_data(
|
||||
request: Request,
|
||||
edit_dict_data: DictDataModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_dict_data.update_by = current_user.user.user_name
|
||||
edit_dict_data.update_time = datetime.now()
|
||||
edit_dict_data_result = await DictDataService.edit_dict_data_services(request, query_db, edit_dict_data)
|
||||
@@ -200,9 +291,19 @@ async def edit_system_dict_data(
|
||||
return ResponseUtil.success(msg=edit_dict_data_result.message)
|
||||
|
||||
|
||||
@dictController.delete('/data/{dict_codes}', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))])
|
||||
@dict_controller.delete(
|
||||
'/data/{dict_codes}',
|
||||
summary='删除字典数据接口',
|
||||
description='用于删除字典数据',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:remove')],
|
||||
)
|
||||
@Log(title='字典数据', business_type=BusinessType.DELETE)
|
||||
async def delete_system_dict_data(request: Request, dict_codes: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_dict_data(
|
||||
request: Request,
|
||||
dict_codes: Annotated[str, Path(description='需要删除的字典编码')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_dict_data = DeleteDictDataModel(dictCodes=dict_codes)
|
||||
delete_dict_data_result = await DictDataService.delete_dict_data_services(request, query_db, delete_dict_data)
|
||||
logger.info(delete_dict_data_result.message)
|
||||
@@ -210,25 +311,45 @@ async def delete_system_dict_data(request: Request, dict_codes: str, query_db: A
|
||||
return ResponseUtil.success(msg=delete_dict_data_result.message)
|
||||
|
||||
|
||||
@dictController.get(
|
||||
@dict_controller.get(
|
||||
'/data/{dict_code}',
|
||||
response_model=DictDataModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))],
|
||||
summary='获取字典数据详情接口',
|
||||
description='用于获取指定字典数据的详细信息',
|
||||
response_model=DataResponseModel[DictDataModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:query')],
|
||||
)
|
||||
async def query_detail_system_dict_data(request: Request, dict_code: int, query_db: AsyncSession = Depends(get_db)):
|
||||
async def query_detail_system_dict_data(
|
||||
request: Request,
|
||||
dict_code: Annotated[int, Path(description='字典编码')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
detail_dict_data_result = await DictDataService.dict_data_detail_services(query_db, dict_code)
|
||||
logger.info(f'获取dict_code为{dict_code}的信息成功')
|
||||
|
||||
return ResponseUtil.success(data=detail_dict_data_result)
|
||||
|
||||
|
||||
@dictController.post('/data/export', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))])
|
||||
@dict_controller.post(
|
||||
'/data/export',
|
||||
summary='导出字典数据列表接口',
|
||||
description='用于导出当前符合查询条件的字典数据列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回字典数据列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('system:dict:export')],
|
||||
)
|
||||
@Log(title='字典数据', business_type=BusinessType.EXPORT)
|
||||
async def export_system_dict_data_list(
|
||||
request: Request,
|
||||
dict_data_page_query: DictDataPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
dict_data_page_query: Annotated[DictDataPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
dict_data_query_result = await DictDataService.get_dict_data_list_services(
|
||||
query_db, dict_data_page_query, is_page=False
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Form, Path, Query, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.job_vo import (
|
||||
DeleteJobLogModel,
|
||||
DeleteJobModel,
|
||||
EditJobModel,
|
||||
JobLogModel,
|
||||
JobLogPageQueryModel,
|
||||
JobModel,
|
||||
JobPageQueryModel,
|
||||
@@ -17,40 +25,49 @@ from module_admin.entity.vo.job_vo import (
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.job_log_service import JobLogService
|
||||
from module_admin.service.job_service import JobService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
jobController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.get_current_user)])
|
||||
job_controller = APIRouterPro(
|
||||
prefix='/monitor', order_num=13, tags=['系统监控-定时任务'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@jobController.get(
|
||||
'/job/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))]
|
||||
@job_controller.get(
|
||||
'/job/list',
|
||||
summary='获取定时任务分页列表接口',
|
||||
description='用于获取定时任务分页列表',
|
||||
response_model=PageResponseModel[JobModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:list')],
|
||||
)
|
||||
async def get_system_job_list(
|
||||
request: Request,
|
||||
job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
job_page_query: Annotated[JobPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
notice_page_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=True)
|
||||
job_page_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=True)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(model_content=notice_page_query_result)
|
||||
return ResponseUtil.success(model_content=job_page_query_result)
|
||||
|
||||
|
||||
@jobController.post('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:add'))])
|
||||
@job_controller.post(
|
||||
'/job',
|
||||
summary='新增定时任务接口',
|
||||
description='用于新增定时任务',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_job')
|
||||
@Log(title='定时任务', business_type=BusinessType.INSERT)
|
||||
async def add_system_job(
|
||||
request: Request,
|
||||
add_job: JobModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_job.create_by = current_user.user.user_name
|
||||
add_job.create_time = datetime.now()
|
||||
add_job.update_by = current_user.user.user_name
|
||||
@@ -61,15 +78,21 @@ async def add_system_job(
|
||||
return ResponseUtil.success(msg=add_job_result.message)
|
||||
|
||||
|
||||
@jobController.put('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))])
|
||||
@job_controller.put(
|
||||
'/job',
|
||||
summary='编辑定时任务接口',
|
||||
description='用于编辑定时任务',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_job')
|
||||
@Log(title='定时任务', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_job(
|
||||
request: Request,
|
||||
edit_job: EditJobModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_job.update_by = current_user.user.user_name
|
||||
edit_job.update_time = datetime.now()
|
||||
edit_job_result = await JobService.edit_job_services(query_db, edit_job)
|
||||
@@ -78,14 +101,20 @@ async def edit_system_job(
|
||||
return ResponseUtil.success(msg=edit_job_result.message)
|
||||
|
||||
|
||||
@jobController.put('/job/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))])
|
||||
@job_controller.put(
|
||||
'/job/changeStatus',
|
||||
summary='修改定时任务状态接口',
|
||||
description='用于修改定时任务状态',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:changeStatus')],
|
||||
)
|
||||
@Log(title='定时任务', business_type=BusinessType.UPDATE)
|
||||
async def change_system_job_status(
|
||||
request: Request,
|
||||
change_job: EditJobModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_job = EditJobModel(
|
||||
jobId=change_job.job_id,
|
||||
status=change_job.status,
|
||||
@@ -99,18 +128,38 @@ async def change_system_job_status(
|
||||
return ResponseUtil.success(msg=edit_job_result.message)
|
||||
|
||||
|
||||
@jobController.put('/job/run', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))])
|
||||
@job_controller.put(
|
||||
'/job/run',
|
||||
summary='执行定时任务接口',
|
||||
description='用于执行指定的定时任务',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:changeStatus')],
|
||||
)
|
||||
@Log(title='定时任务', business_type=BusinessType.UPDATE)
|
||||
async def execute_system_job(request: Request, execute_job: JobModel, query_db: AsyncSession = Depends(get_db)):
|
||||
async def execute_system_job(
|
||||
request: Request,
|
||||
execute_job: JobModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
execute_job_result = await JobService.execute_job_once_services(query_db, execute_job)
|
||||
logger.info(execute_job_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=execute_job_result.message)
|
||||
|
||||
|
||||
@jobController.delete('/job/{job_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))])
|
||||
@job_controller.delete(
|
||||
'/job/{job_ids}',
|
||||
summary='删除定时任务接口',
|
||||
description='用于删除定时任务',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:remove')],
|
||||
)
|
||||
@Log(title='定时任务', business_type=BusinessType.DELETE)
|
||||
async def delete_system_job(request: Request, job_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_job(
|
||||
request: Request,
|
||||
job_ids: Annotated[str, Path(description='需要删除的定时任务ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_job = DeleteJobModel(jobIds=job_ids)
|
||||
delete_job_result = await JobService.delete_job_services(query_db, delete_job)
|
||||
logger.info(delete_job_result.message)
|
||||
@@ -118,23 +167,45 @@ async def delete_system_job(request: Request, job_ids: str, query_db: AsyncSessi
|
||||
return ResponseUtil.success(msg=delete_job_result.message)
|
||||
|
||||
|
||||
@jobController.get(
|
||||
'/job/{job_id}', response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))]
|
||||
@job_controller.get(
|
||||
'/job/{job_id}',
|
||||
summary='获取定时任务详情接口',
|
||||
description='用于获取指定定时任务的详情信息',
|
||||
response_model=DataResponseModel[JobModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:query')],
|
||||
)
|
||||
async def query_detail_system_job(request: Request, job_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
async def query_detail_system_job(
|
||||
request: Request,
|
||||
job_id: Annotated[int, Path(description='任务ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
job_detail_result = await JobService.job_detail_services(query_db, job_id)
|
||||
logger.info(f'获取job_id为{job_id}的信息成功')
|
||||
|
||||
return ResponseUtil.success(data=job_detail_result)
|
||||
|
||||
|
||||
@jobController.post('/job/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))])
|
||||
@job_controller.post(
|
||||
'/job/export',
|
||||
summary='导出定时任务列表接口',
|
||||
description='用于导出当前符合查询条件的定时任务列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回定时任务列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:export')],
|
||||
)
|
||||
@Log(title='定时任务', business_type=BusinessType.EXPORT)
|
||||
async def export_system_job_list(
|
||||
request: Request,
|
||||
job_page_query: JobPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
job_page_query: Annotated[JobPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
job_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=False)
|
||||
job_export_result = await JobService.export_job_list_services(request, job_query_result)
|
||||
@@ -143,14 +214,18 @@ async def export_system_job_list(
|
||||
return ResponseUtil.streaming(data=bytes2file_response(job_export_result))
|
||||
|
||||
|
||||
@jobController.get(
|
||||
'/jobLog/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))]
|
||||
@job_controller.get(
|
||||
'/jobLog/list',
|
||||
summary='获取定时任务调度日志分页列表接口',
|
||||
description='用于获取定时任务调度日志分页列表',
|
||||
response_model=PageResponseModel[JobLogModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:list')],
|
||||
)
|
||||
async def get_system_job_log_list(
|
||||
request: Request,
|
||||
job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
job_log_page_query: Annotated[JobLogPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
job_log_page_query_result = await JobLogService.get_job_log_list_services(
|
||||
query_db, job_log_page_query, is_page=True
|
||||
@@ -160,18 +235,37 @@ async def get_system_job_log_list(
|
||||
return ResponseUtil.success(model_content=job_log_page_query_result)
|
||||
|
||||
|
||||
@jobController.delete('/jobLog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))])
|
||||
@job_controller.delete(
|
||||
'/jobLog/clean',
|
||||
summary='清空定时任务调度日志接口',
|
||||
description='用于清空所有定时任务调度日志',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:remove')],
|
||||
)
|
||||
@Log(title='定时任务调度日志', business_type=BusinessType.CLEAN)
|
||||
async def clear_system_job_log(request: Request, query_db: AsyncSession = Depends(get_db)):
|
||||
async def clear_system_job_log(
|
||||
request: Request,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
clear_job_log_result = await JobLogService.clear_job_log_services(query_db)
|
||||
logger.info(clear_job_log_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=clear_job_log_result.message)
|
||||
|
||||
|
||||
@jobController.delete('/jobLog/{job_log_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))])
|
||||
@job_controller.delete(
|
||||
'/jobLog/{job_log_ids}',
|
||||
summary='删除定时任务调度日志接口',
|
||||
description='用于删除定时任务调度日志',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:remove')],
|
||||
)
|
||||
@Log(title='定时任务调度日志', business_type=BusinessType.DELETE)
|
||||
async def delete_system_job_log(request: Request, job_log_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_job_log(
|
||||
request: Request,
|
||||
job_log_ids: Annotated[str, Path(description='需要删除的定时任务日志ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_job_log = DeleteJobLogModel(jobLogIds=job_log_ids)
|
||||
delete_job_log_result = await JobLogService.delete_job_log_services(query_db, delete_job_log)
|
||||
logger.info(delete_job_log_result.message)
|
||||
@@ -179,13 +273,27 @@ async def delete_system_job_log(request: Request, job_log_ids: str, query_db: As
|
||||
return ResponseUtil.success(msg=delete_job_log_result.message)
|
||||
|
||||
|
||||
@jobController.post('/jobLog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))])
|
||||
@job_controller.post(
|
||||
'/jobLog/export',
|
||||
summary='导出定时任务调度日志列表接口',
|
||||
description='用于导出当前符合查询条件的定时任务调度日志列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回定时任务日志列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:job:export')],
|
||||
)
|
||||
@Log(title='定时任务调度日志', business_type=BusinessType.EXPORT)
|
||||
async def export_system_job_log_list(
|
||||
request: Request,
|
||||
job_log_page_query: JobLogPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
job_log_page_query: Annotated[JobLogPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
job_log_query_result = await JobLogService.get_job_log_list_services(query_db, job_log_page_query, is_page=False)
|
||||
job_log_export_result = await JobLogService.export_job_log_list_services(request, job_log_query_result)
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Form, Path, Query, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import PageResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.log_vo import (
|
||||
DeleteLoginLogModel,
|
||||
DeleteOperLogModel,
|
||||
LogininforModel,
|
||||
LoginLogPageQueryModel,
|
||||
OperLogModel,
|
||||
OperLogPageQueryModel,
|
||||
UnlockUser,
|
||||
)
|
||||
from module_admin.service.log_service import LoginLogService, OperationLogService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
logController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.get_current_user)])
|
||||
log_controller = APIRouterPro(
|
||||
prefix='/monitor', order_num=11, tags=['系统管理-日志管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@logController.get(
|
||||
@log_controller.get(
|
||||
'/operlog/list',
|
||||
response_model=PageResponseModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:list'))],
|
||||
summary='获取操作日志分页列表接口',
|
||||
description='用于获取操作日志分页列表',
|
||||
response_model=PageResponseModel[OperLogModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:operlog:list')],
|
||||
)
|
||||
async def get_system_operation_log_list(
|
||||
request: Request,
|
||||
operation_log_page_query: OperLogPageQueryModel = Depends(OperLogPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
operation_log_page_query: Annotated[OperLogPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
operation_log_page_query_result = await OperationLogService.get_operation_log_list_services(
|
||||
query_db, operation_log_page_query, is_page=True
|
||||
@@ -41,18 +51,36 @@ async def get_system_operation_log_list(
|
||||
return ResponseUtil.success(model_content=operation_log_page_query_result)
|
||||
|
||||
|
||||
@logController.delete('/operlog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))])
|
||||
@log_controller.delete(
|
||||
'/operlog/clean',
|
||||
summary='清空操作日志接口',
|
||||
description='用于清空所有操作日志',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:operlog:remove')],
|
||||
)
|
||||
@Log(title='操作日志', business_type=BusinessType.CLEAN)
|
||||
async def clear_system_operation_log(request: Request, query_db: AsyncSession = Depends(get_db)):
|
||||
async def clear_system_operation_log(
|
||||
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
|
||||
) -> Response:
|
||||
clear_operation_log_result = await OperationLogService.clear_operation_log_services(query_db)
|
||||
logger.info(clear_operation_log_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=clear_operation_log_result.message)
|
||||
|
||||
|
||||
@logController.delete('/operlog/{oper_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))])
|
||||
@log_controller.delete(
|
||||
'/operlog/{oper_ids}',
|
||||
summary='删除操作日志接口',
|
||||
description='用于删除操作日志',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:operlog:remove')],
|
||||
)
|
||||
@Log(title='操作日志', business_type=BusinessType.DELETE)
|
||||
async def delete_system_operation_log(request: Request, oper_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_operation_log(
|
||||
request: Request,
|
||||
oper_ids: Annotated[str, Path(description='需要删除的日志主键')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_operation_log = DeleteOperLogModel(operIds=oper_ids)
|
||||
delete_operation_log_result = await OperationLogService.delete_operation_log_services(
|
||||
query_db, delete_operation_log
|
||||
@@ -62,13 +90,27 @@ async def delete_system_operation_log(request: Request, oper_ids: str, query_db:
|
||||
return ResponseUtil.success(msg=delete_operation_log_result.message)
|
||||
|
||||
|
||||
@logController.post('/operlog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:export'))])
|
||||
@log_controller.post(
|
||||
'/operlog/export',
|
||||
summary='导出操作日志接口',
|
||||
description='用于导出当前符合查询条件的操作日志数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回操作日志列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:operlog:export')],
|
||||
)
|
||||
@Log(title='操作日志', business_type=BusinessType.EXPORT)
|
||||
async def export_system_operation_log_list(
|
||||
request: Request,
|
||||
operation_log_page_query: OperLogPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
operation_log_page_query: Annotated[OperLogPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
operation_log_query_result = await OperationLogService.get_operation_log_list_services(
|
||||
query_db, operation_log_page_query, is_page=False
|
||||
@@ -81,16 +123,18 @@ async def export_system_operation_log_list(
|
||||
return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result))
|
||||
|
||||
|
||||
@logController.get(
|
||||
@log_controller.get(
|
||||
'/logininfor/list',
|
||||
response_model=PageResponseModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:list'))],
|
||||
summary='获取登录日志分页列表接口',
|
||||
description='用于获取登录日志分页列表',
|
||||
response_model=PageResponseModel[LogininforModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:list')],
|
||||
)
|
||||
async def get_system_login_log_list(
|
||||
request: Request,
|
||||
login_log_page_query: LoginLogPageQueryModel = Depends(LoginLogPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
login_log_page_query: Annotated[LoginLogPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
login_log_page_query_result = await LoginLogService.get_login_log_list_services(
|
||||
query_db, login_log_page_query, is_page=True
|
||||
@@ -100,20 +144,36 @@ async def get_system_login_log_list(
|
||||
return ResponseUtil.success(model_content=login_log_page_query_result)
|
||||
|
||||
|
||||
@logController.delete('/logininfor/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))])
|
||||
@log_controller.delete(
|
||||
'/logininfor/clean',
|
||||
summary='清空登录日志接口',
|
||||
description='用于清空所有登录日志',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:remove')],
|
||||
)
|
||||
@Log(title='登录日志', business_type=BusinessType.CLEAN)
|
||||
async def clear_system_login_log(request: Request, query_db: AsyncSession = Depends(get_db)):
|
||||
async def clear_system_login_log(
|
||||
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
|
||||
) -> Response:
|
||||
clear_login_log_result = await LoginLogService.clear_login_log_services(query_db)
|
||||
logger.info(clear_login_log_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=clear_login_log_result.message)
|
||||
|
||||
|
||||
@logController.delete(
|
||||
'/logininfor/{info_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]
|
||||
@log_controller.delete(
|
||||
'/logininfor/{info_ids}',
|
||||
summary='删除登录日志接口',
|
||||
description='用于删除登录日志',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:remove')],
|
||||
)
|
||||
@Log(title='登录日志', business_type=BusinessType.DELETE)
|
||||
async def delete_system_login_log(request: Request, info_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_login_log(
|
||||
request: Request,
|
||||
info_ids: Annotated[str, Path(description='需要删除的访问ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_login_log = DeleteLoginLogModel(infoIds=info_ids)
|
||||
delete_login_log_result = await LoginLogService.delete_login_log_services(query_db, delete_login_log)
|
||||
logger.info(delete_login_log_result.message)
|
||||
@@ -121,11 +181,19 @@ async def delete_system_login_log(request: Request, info_ids: str, query_db: Asy
|
||||
return ResponseUtil.success(msg=delete_login_log_result.message)
|
||||
|
||||
|
||||
@logController.get(
|
||||
'/logininfor/unlock/{user_name}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:unlock'))]
|
||||
@log_controller.get(
|
||||
'/logininfor/unlock/{user_name}',
|
||||
summary='解锁账户接口',
|
||||
description='用于解锁指定用户账户',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:unlock')],
|
||||
)
|
||||
@Log(title='账户解锁', business_type=BusinessType.OTHER)
|
||||
async def unlock_system_user(request: Request, user_name: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def unlock_system_user(
|
||||
request: Request,
|
||||
user_name: Annotated[str, Path(description='用户名称')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
unlock_user = UnlockUser(userName=user_name)
|
||||
unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user)
|
||||
logger.info(unlock_user_result.message)
|
||||
@@ -133,13 +201,27 @@ async def unlock_system_user(request: Request, user_name: str, query_db: AsyncSe
|
||||
return ResponseUtil.success(msg=unlock_user_result.message)
|
||||
|
||||
|
||||
@logController.post('/logininfor/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:export'))])
|
||||
@log_controller.post(
|
||||
'/logininfor/export',
|
||||
summary='导出登录日志接口',
|
||||
description='用于导出当前符合查询条件的登录日志数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回登录日志列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:logininfor:export')],
|
||||
)
|
||||
@Log(title='登录日志', business_type=BusinessType.EXPORT)
|
||||
async def export_system_login_log_list(
|
||||
request: Request,
|
||||
login_log_page_query: LoginLogPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
login_log_page_query: Annotated[LoginLogPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
login_log_query_result = await LoginLogService.get_login_log_list_services(
|
||||
query_db, login_log_page_query, is_page=False
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
import jwt
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from typing import Annotated, Optional
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, Request, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Optional
|
||||
from config.enums import BusinessType, RedisInitKeyConfig
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency
|
||||
from common.enums import BusinessType, RedisInitKeyConfig
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import CrudResponseModel, DataResponseModel, DynamicResponseModel, ResponseBaseModel
|
||||
from config.env import AppConfig, JwtConfig
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.entity.vo.common_vo import CrudResponseModel
|
||||
from module_admin.entity.vo.login_vo import UserLogin, UserRegister, Token
|
||||
from module_admin.entity.vo.login_vo import RouterModel, Token, UserLogin, UserRegister
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel, EditUserModel
|
||||
from module_admin.service.login_service import CustomOAuth2PasswordRequestForm, LoginService, oauth2_scheme
|
||||
from module_admin.service.user_service import UserService
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
loginController = APIRouter()
|
||||
login_controller = APIRouterPro(order_num=1, tags=['登录模块'])
|
||||
|
||||
|
||||
@loginController.post('/login', response_model=Token)
|
||||
@login_controller.post(
|
||||
'/login',
|
||||
summary='登录接口',
|
||||
description='用于用户登录',
|
||||
response_model=DynamicResponseModel[Token],
|
||||
)
|
||||
@Log(title='用户登录', business_type=BusinessType.OTHER, log_type='login')
|
||||
async def login(
|
||||
request: Request, form_data: CustomOAuth2PasswordRequestForm = Depends(), query_db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
request: Request,
|
||||
form_data: Annotated[CustomOAuth2PasswordRequestForm, Depends()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
captcha_enabled = (
|
||||
True
|
||||
if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled')
|
||||
== 'true'
|
||||
else False
|
||||
await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') == 'true'
|
||||
)
|
||||
user = UserLogin(
|
||||
userName=form_data.username,
|
||||
@@ -77,37 +84,56 @@ async def login(
|
||||
return ResponseUtil.success(msg='登录成功', dict_content={'token': access_token})
|
||||
|
||||
|
||||
@loginController.get('/getInfo', response_model=CurrentUserModel)
|
||||
@login_controller.get(
|
||||
'/getInfo',
|
||||
summary='获取用户信息接口',
|
||||
description='用于获取当前登录用户的信息',
|
||||
response_model=DynamicResponseModel[CurrentUserModel],
|
||||
)
|
||||
async def get_login_user_info(
|
||||
request: Request, current_user: CurrentUserModel = Depends(LoginService.get_current_user)
|
||||
):
|
||||
request: Request, current_user: Annotated[CurrentUserModel, CurrentUserDependency()]
|
||||
) -> Response:
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(model_content=current_user)
|
||||
|
||||
|
||||
@loginController.get('/getRouters')
|
||||
@login_controller.get(
|
||||
'/getRouters',
|
||||
summary='获取用户路由接口',
|
||||
description='用于获取当前登录用户的路由信息',
|
||||
response_model=DataResponseModel[list[RouterModel]],
|
||||
)
|
||||
async def get_login_user_routers(
|
||||
request: Request,
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
logger.info('获取成功')
|
||||
user_routers = await LoginService.get_current_user_routers(current_user.user.user_id, query_db)
|
||||
|
||||
return ResponseUtil.success(data=user_routers)
|
||||
|
||||
|
||||
@loginController.post('/register', response_model=CrudResponseModel)
|
||||
async def register_user(request: Request, user_register: UserRegister, query_db: AsyncSession = Depends(get_db)):
|
||||
@login_controller.post(
|
||||
'/register',
|
||||
summary='注册接口',
|
||||
description='用于用户注册',
|
||||
response_model=DataResponseModel[CrudResponseModel],
|
||||
)
|
||||
async def register_user(
|
||||
request: Request,
|
||||
user_register: UserRegister,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
user_register_result = await LoginService.register_user_services(request, query_db, user_register)
|
||||
logger.info(user_register_result.message)
|
||||
|
||||
return ResponseUtil.success(data=user_register_result, msg=user_register_result.message)
|
||||
|
||||
|
||||
# @loginController.post("/getSmsCode", response_model=SmsCode)
|
||||
# async def get_sms_code(request: Request, user: ResetUserModel, query_db: AsyncSession = Depends(get_db)):
|
||||
# @login_controller.post("/getSmsCode", response_model=SmsCode)
|
||||
# async def get_sms_code(request: Request, user: ResetUserModel, query_db: AsyncSession = DBSessionDependency()):
|
||||
# try:
|
||||
# sms_result = await LoginService.get_sms_code_services(request, query_db, user)
|
||||
# if sms_result.is_success:
|
||||
@@ -121,8 +147,8 @@ async def register_user(request: Request, user_register: UserRegister, query_db:
|
||||
# return ResponseUtil.error(msg=str(e))
|
||||
#
|
||||
#
|
||||
# @loginController.post("/forgetPwd", response_model=CrudResponseModel)
|
||||
# async def forget_user_pwd(request: Request, forget_user: ResetUserModel, query_db: AsyncSession = Depends(get_db)):
|
||||
# @login_controller.post("/forgetPwd", response_model=CrudResponseModel)
|
||||
# async def forget_user_pwd(request: Request, forget_user: ResetUserModel, query_db: AsyncSession = DBSessionDependency()):
|
||||
# try:
|
||||
# forget_user_result = await LoginService.forget_user_services(request, query_db, forget_user)
|
||||
# if forget_user_result.is_success:
|
||||
@@ -136,13 +162,21 @@ async def register_user(request: Request, user_register: UserRegister, query_db:
|
||||
# return ResponseUtil.error(msg=str(e))
|
||||
|
||||
|
||||
@loginController.post('/logout')
|
||||
async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme)):
|
||||
@login_controller.post(
|
||||
'/logout',
|
||||
summary='退出登录接口',
|
||||
description='用于用户退出登录',
|
||||
response_model=ResponseBaseModel,
|
||||
)
|
||||
async def logout(request: Request, token: Annotated[Optional[str], Depends(oauth2_scheme)]) -> Response:
|
||||
payload = jwt.decode(
|
||||
token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False}
|
||||
)
|
||||
session_id: str = payload.get('session_id')
|
||||
await LoginService.logout_services(request, session_id)
|
||||
if AppConfig.app_same_time_login:
|
||||
token_id: str = payload.get('session_id')
|
||||
else:
|
||||
token_id: str = payload.get('user_id')
|
||||
await LoginService.logout_services(request, token_id)
|
||||
logger.info('退出成功')
|
||||
|
||||
return ResponseUtil.success(msg='退出成功')
|
||||
|
||||
@@ -1,72 +1,98 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Path, Query, Request, Response
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, DynamicResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel, MenuTreeModel
|
||||
from module_admin.entity.vo.role_vo import RoleMenuQueryModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_admin.service.menu_service import MenuService
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
menuController = APIRouter(prefix='/system/menu', dependencies=[Depends(LoginService.get_current_user)])
|
||||
menu_controller = APIRouterPro(
|
||||
prefix='/system/menu', order_num=5, tags=['系统管理-菜单管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@menuController.get('/treeselect')
|
||||
@menu_controller.get(
|
||||
'/treeselect',
|
||||
summary='获取菜单树接口',
|
||||
description='用于获取当前用户可见的菜单树',
|
||||
response_model=DataResponseModel[list[MenuTreeModel]],
|
||||
)
|
||||
async def get_system_menu_tree(
|
||||
request: Request,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
menu_query_result = await MenuService.get_menu_tree_services(query_db, current_user)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(data=menu_query_result)
|
||||
|
||||
|
||||
@menuController.get('/roleMenuTreeselect/{role_id}')
|
||||
@menu_controller.get(
|
||||
'/roleMenuTreeselect/{role_id}',
|
||||
summary='获取角色菜单树接口',
|
||||
description='用于获取指定角色可见的菜单树',
|
||||
response_model=DynamicResponseModel[RoleMenuQueryModel],
|
||||
)
|
||||
async def get_system_role_menu_tree(
|
||||
request: Request,
|
||||
role_id: int,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
role_id: Annotated[int, Path(description='角色ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
role_menu_query_result = await MenuService.get_role_menu_tree_services(query_db, role_id, current_user)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(model_content=role_menu_query_result)
|
||||
|
||||
|
||||
@menuController.get(
|
||||
'/list', response_model=List[MenuModel], dependencies=[Depends(CheckUserInterfaceAuth('system:menu:list'))]
|
||||
@menu_controller.get(
|
||||
'/list',
|
||||
summary='获取菜单列表接口',
|
||||
description='用于获取当前用户可见的菜单列表',
|
||||
response_model=DataResponseModel[list[MenuModel]],
|
||||
dependencies=[UserInterfaceAuthDependency('system:menu:list')],
|
||||
)
|
||||
async def get_system_menu_list(
|
||||
request: Request,
|
||||
menu_query: MenuQueryModel = Depends(MenuQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
menu_query: Annotated[MenuQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
menu_query_result = await MenuService.get_menu_list_services(query_db, menu_query, current_user)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(data=menu_query_result)
|
||||
|
||||
|
||||
@menuController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:add'))])
|
||||
@menu_controller.post(
|
||||
'',
|
||||
summary='新增菜单接口',
|
||||
description='用于新增菜单',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:menu:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_menu')
|
||||
@Log(title='菜单管理', business_type=BusinessType.INSERT)
|
||||
async def add_system_menu(
|
||||
request: Request,
|
||||
add_menu: MenuModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_menu.create_by = current_user.user.user_name
|
||||
add_menu.create_time = datetime.now()
|
||||
add_menu.update_by = current_user.user.user_name
|
||||
@@ -77,15 +103,21 @@ async def add_system_menu(
|
||||
return ResponseUtil.success(msg=add_menu_result.message)
|
||||
|
||||
|
||||
@menuController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:edit'))])
|
||||
@menu_controller.put(
|
||||
'',
|
||||
summary='编辑菜单接口',
|
||||
description='用于编辑菜单',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:menu:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_menu')
|
||||
@Log(title='菜单管理', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_menu(
|
||||
request: Request,
|
||||
edit_menu: MenuModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_menu.update_by = current_user.user.user_name
|
||||
edit_menu.update_time = datetime.now()
|
||||
edit_menu_result = await MenuService.edit_menu_services(query_db, edit_menu)
|
||||
@@ -94,9 +126,19 @@ async def edit_system_menu(
|
||||
return ResponseUtil.success(msg=edit_menu_result.message)
|
||||
|
||||
|
||||
@menuController.delete('/{menu_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:remove'))])
|
||||
@menu_controller.delete(
|
||||
'/{menu_ids}',
|
||||
summary='删除菜单接口',
|
||||
description='用于删除菜单',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:menu:remove')],
|
||||
)
|
||||
@Log(title='菜单管理', business_type=BusinessType.DELETE)
|
||||
async def delete_system_menu(request: Request, menu_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_menu(
|
||||
request: Request,
|
||||
menu_ids: Annotated[str, Path(description='需要删除的菜单ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_menu = DeleteMenuModel(menuIds=menu_ids)
|
||||
delete_menu_result = await MenuService.delete_menu_services(query_db, delete_menu)
|
||||
logger.info(delete_menu_result.message)
|
||||
@@ -104,10 +146,18 @@ async def delete_system_menu(request: Request, menu_ids: str, query_db: AsyncSes
|
||||
return ResponseUtil.success(msg=delete_menu_result.message)
|
||||
|
||||
|
||||
@menuController.get(
|
||||
'/{menu_id}', response_model=MenuModel, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:query'))]
|
||||
@menu_controller.get(
|
||||
'/{menu_id}',
|
||||
summary='获取菜单详情接口',
|
||||
description='用于获取指定菜单的详情信息',
|
||||
response_model=DataResponseModel[MenuModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:menu:query')],
|
||||
)
|
||||
async def query_detail_system_menu(request: Request, menu_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
async def query_detail_system_menu(
|
||||
request: Request,
|
||||
menu_id: Annotated[int, Path(description='菜单ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
menu_detail_result = await MenuService.menu_detail_services(query_db, menu_id)
|
||||
logger.info(f'获取menu_id为{menu_id}的信息成功')
|
||||
|
||||
|
||||
@@ -1,31 +1,40 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Path, Query, Request, Response
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_admin.service.notice_service import NoticeService
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
noticeController = APIRouter(prefix='/system/notice', dependencies=[Depends(LoginService.get_current_user)])
|
||||
notice_controller = APIRouterPro(
|
||||
prefix='/system/notice', order_num=10, tags=['系统管理-通知公告管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@noticeController.get(
|
||||
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:list'))]
|
||||
@notice_controller.get(
|
||||
'/list',
|
||||
summary='获取通知公告分页列表接口',
|
||||
description='用于获取通知公告分页列表',
|
||||
response_model=PageResponseModel[NoticeModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:notice:list')],
|
||||
)
|
||||
async def get_system_notice_list(
|
||||
request: Request,
|
||||
notice_page_query: NoticePageQueryModel = Depends(NoticePageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
notice_page_query: Annotated[NoticePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
notice_page_query_result = await NoticeService.get_notice_list_services(query_db, notice_page_query, is_page=True)
|
||||
logger.info('获取成功')
|
||||
@@ -33,15 +42,21 @@ async def get_system_notice_list(
|
||||
return ResponseUtil.success(model_content=notice_page_query_result)
|
||||
|
||||
|
||||
@noticeController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:add'))])
|
||||
@notice_controller.post(
|
||||
'',
|
||||
summary='新增通知公告接口',
|
||||
description='用于新增通知公告',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:notice:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_notice')
|
||||
@Log(title='通知公告', business_type=BusinessType.INSERT)
|
||||
async def add_system_notice(
|
||||
request: Request,
|
||||
add_notice: NoticeModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_notice.create_by = current_user.user.user_name
|
||||
add_notice.create_time = datetime.now()
|
||||
add_notice.update_by = current_user.user.user_name
|
||||
@@ -52,15 +67,21 @@ async def add_system_notice(
|
||||
return ResponseUtil.success(msg=add_notice_result.message)
|
||||
|
||||
|
||||
@noticeController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:edit'))])
|
||||
@notice_controller.put(
|
||||
'',
|
||||
summary='编辑通知公告接口',
|
||||
description='用于编辑通知公告',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:notice:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_notice')
|
||||
@Log(title='通知公告', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_notice(
|
||||
request: Request,
|
||||
edit_notice: NoticeModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_notice.update_by = current_user.user.user_name
|
||||
edit_notice.update_time = datetime.now()
|
||||
edit_notice_result = await NoticeService.edit_notice_services(query_db, edit_notice)
|
||||
@@ -69,9 +90,19 @@ async def edit_system_notice(
|
||||
return ResponseUtil.success(msg=edit_notice_result.message)
|
||||
|
||||
|
||||
@noticeController.delete('/{notice_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:remove'))])
|
||||
@notice_controller.delete(
|
||||
'/{notice_ids}',
|
||||
summary='删除通知公告接口',
|
||||
description='用于删除通知公告',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:notice:remove')],
|
||||
)
|
||||
@Log(title='通知公告', business_type=BusinessType.DELETE)
|
||||
async def delete_system_notice(request: Request, notice_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_system_notice(
|
||||
request: Request,
|
||||
notice_ids: Annotated[str, Path(description='需要删除的公告ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_notice = DeleteNoticeModel(noticeIds=notice_ids)
|
||||
delete_notice_result = await NoticeService.delete_notice_services(query_db, delete_notice)
|
||||
logger.info(delete_notice_result.message)
|
||||
@@ -79,10 +110,18 @@ async def delete_system_notice(request: Request, notice_ids: str, query_db: Asyn
|
||||
return ResponseUtil.success(msg=delete_notice_result.message)
|
||||
|
||||
|
||||
@noticeController.get(
|
||||
'/{notice_id}', response_model=NoticeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:query'))]
|
||||
@notice_controller.get(
|
||||
'/{notice_id}',
|
||||
summary='获取通知公告详情接口',
|
||||
description='用于获取指定通知公告的详细信息',
|
||||
response_model=DataResponseModel[NoticeModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:notice:query')],
|
||||
)
|
||||
async def query_detail_system_post(request: Request, notice_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
async def query_detail_system_post(
|
||||
request: Request,
|
||||
notice_id: Annotated[int, Path(description='公告ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
notice_detail_result = await NoticeService.notice_detail_services(query_db, notice_id)
|
||||
logger.info(f'获取notice_id为{notice_id}的信息成功')
|
||||
|
||||
|
||||
@@ -1,38 +1,58 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Path, Query, Request, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlineQueryModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import ResponseBaseModel
|
||||
from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlinePageResponseModel, OnlineQueryModel
|
||||
from module_admin.service.online_service import OnlineService
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
onlineController = APIRouter(prefix='/monitor/online', dependencies=[Depends(LoginService.get_current_user)])
|
||||
online_controller = APIRouterPro(
|
||||
prefix='/monitor/online', order_num=12, tags=['系统监控-在线用户'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@onlineController.get(
|
||||
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))]
|
||||
@online_controller.get(
|
||||
'/list',
|
||||
summary='获取在线用户分页列表接口',
|
||||
description='用于获取在线用户分页列表',
|
||||
response_model=OnlinePageResponseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:online:list')],
|
||||
)
|
||||
async def get_monitor_online_list(
|
||||
request: Request, online_page_query: OnlineQueryModel = Depends(OnlineQueryModel.as_query)
|
||||
):
|
||||
request: Request,
|
||||
online_page_query: Annotated[OnlineQueryModel, Query()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
online_query_result = await OnlineService.get_online_list_services(request, online_page_query)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(
|
||||
model_content=PageResponseModel(rows=online_query_result, total=len(online_query_result))
|
||||
model_content=OnlinePageResponseModel(rows=online_query_result, total=len(online_query_result))
|
||||
)
|
||||
|
||||
|
||||
@onlineController.delete('/{token_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:forceLogout'))])
|
||||
@online_controller.delete(
|
||||
'/{token_ids}',
|
||||
summary='强退在线用户接口',
|
||||
description='用于强退指定会话编号的在线用户',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:online:forceLogout')],
|
||||
)
|
||||
@Log(title='在线用户', business_type=BusinessType.FORCE)
|
||||
async def delete_monitor_online(request: Request, token_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
async def delete_monitor_online(
|
||||
request: Request,
|
||||
token_ids: Annotated[str, Path(description='需要强退的会话编号')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_online = DeleteOnlineModel(tokenIds=token_ids)
|
||||
delete_online_result = await OnlineService.delete_online_services(request, delete_online)
|
||||
logger.info(delete_online_result.message)
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_admin.service.post_service import PostService
|
||||
from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
postController = APIRouter(prefix='/system/post', dependencies=[Depends(LoginService.get_current_user)])
|
||||
|
||||
|
||||
@postController.get(
|
||||
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:list'))]
|
||||
)
|
||||
async def get_system_post_list(
|
||||
request: Request,
|
||||
post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取分页数据
|
||||
post_page_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=True)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(model_content=post_page_query_result)
|
||||
|
||||
|
||||
@postController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:add'))])
|
||||
@ValidateFields(validate_model='add_post')
|
||||
@Log(title='岗位管理', business_type=BusinessType.INSERT)
|
||||
async def add_system_post(
|
||||
request: Request,
|
||||
add_post: PostModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
add_post.create_by = current_user.user.user_name
|
||||
add_post.create_time = datetime.now()
|
||||
add_post.update_by = current_user.user.user_name
|
||||
add_post.update_time = datetime.now()
|
||||
add_post_result = await PostService.add_post_services(query_db, add_post)
|
||||
logger.info(add_post_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=add_post_result.message)
|
||||
|
||||
|
||||
@postController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:edit'))])
|
||||
@ValidateFields(validate_model='edit_post')
|
||||
@Log(title='岗位管理', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_post(
|
||||
request: Request,
|
||||
edit_post: PostModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
edit_post.update_by = current_user.user.user_name
|
||||
edit_post.update_time = datetime.now()
|
||||
edit_post_result = await PostService.edit_post_services(query_db, edit_post)
|
||||
logger.info(edit_post_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=edit_post_result.message)
|
||||
|
||||
|
||||
@postController.delete('/{post_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:post:remove'))])
|
||||
@Log(title='岗位管理', business_type=BusinessType.DELETE)
|
||||
async def delete_system_post(request: Request, post_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
delete_post = DeletePostModel(postIds=post_ids)
|
||||
delete_post_result = await PostService.delete_post_services(query_db, delete_post)
|
||||
logger.info(delete_post_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=delete_post_result.message)
|
||||
|
||||
|
||||
@postController.get(
|
||||
'/{post_id}', response_model=PostModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:query'))]
|
||||
)
|
||||
async def query_detail_system_post(request: Request, post_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
post_detail_result = await PostService.post_detail_services(query_db, post_id)
|
||||
logger.info(f'获取post_id为{post_id}的信息成功')
|
||||
|
||||
return ResponseUtil.success(data=post_detail_result)
|
||||
|
||||
|
||||
@postController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:post:export'))])
|
||||
@Log(title='岗位管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_post_list(
|
||||
request: Request,
|
||||
post_page_query: PostPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
post_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=False)
|
||||
post_export_result = await PostService.export_post_list_services(post_query_result)
|
||||
logger.info('导出成功')
|
||||
|
||||
return ResponseUtil.streaming(data=bytes2file_response(post_export_result))
|
||||
@@ -0,0 +1,159 @@
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Form, Path, Query, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, PageResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.post_service import PostService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
post_controller = APIRouterPro(
|
||||
prefix='/system/post', order_num=7, tags=['系统管理-岗位管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@post_controller.get(
|
||||
'/list',
|
||||
summary='获取岗位分页列表接口',
|
||||
description='用于获取岗位分页列表',
|
||||
response_model=PageResponseModel[PostModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:post:list')],
|
||||
)
|
||||
async def get_system_post_list(
|
||||
request: Request,
|
||||
post_page_query: Annotated[PostPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
post_page_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=True)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(model_content=post_page_query_result)
|
||||
|
||||
|
||||
@post_controller.post(
|
||||
'',
|
||||
summary='新增岗位接口',
|
||||
description='用于新增岗位',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:post:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_post')
|
||||
@Log(title='岗位管理', business_type=BusinessType.INSERT)
|
||||
async def add_system_post(
|
||||
request: Request,
|
||||
add_post: PostModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_post.create_by = current_user.user.user_name
|
||||
add_post.create_time = datetime.now()
|
||||
add_post.update_by = current_user.user.user_name
|
||||
add_post.update_time = datetime.now()
|
||||
add_post_result = await PostService.add_post_services(query_db, add_post)
|
||||
logger.info(add_post_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=add_post_result.message)
|
||||
|
||||
|
||||
@post_controller.put(
|
||||
'',
|
||||
summary='编辑岗位接口',
|
||||
description='用于编辑岗位',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:post:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_post')
|
||||
@Log(title='岗位管理', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_post(
|
||||
request: Request,
|
||||
edit_post: PostModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_post.update_by = current_user.user.user_name
|
||||
edit_post.update_time = datetime.now()
|
||||
edit_post_result = await PostService.edit_post_services(query_db, edit_post)
|
||||
logger.info(edit_post_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=edit_post_result.message)
|
||||
|
||||
|
||||
@post_controller.delete(
|
||||
'/{post_ids}',
|
||||
summary='删除岗位接口',
|
||||
description='用于删除岗位',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:post:remove')],
|
||||
)
|
||||
@Log(title='岗位管理', business_type=BusinessType.DELETE)
|
||||
async def delete_system_post(
|
||||
request: Request,
|
||||
post_ids: Annotated[str, Path(description='需要删除的岗位ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
delete_post = DeletePostModel(postIds=post_ids)
|
||||
delete_post_result = await PostService.delete_post_services(query_db, delete_post)
|
||||
logger.info(delete_post_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=delete_post_result.message)
|
||||
|
||||
|
||||
@post_controller.get(
|
||||
'/{post_id}',
|
||||
summary='获取岗位详情接口',
|
||||
description='用于获取指定岗位的详细信息',
|
||||
response_model=DataResponseModel[PostModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:post:query')],
|
||||
)
|
||||
async def query_detail_system_post(
|
||||
request: Request,
|
||||
post_id: Annotated[int, Path(description='岗位ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
post_detail_result = await PostService.post_detail_services(query_db, post_id)
|
||||
logger.info(f'获取post_id为{post_id}的信息成功')
|
||||
|
||||
return ResponseUtil.success(data=post_detail_result)
|
||||
|
||||
|
||||
@post_controller.post(
|
||||
'/export',
|
||||
summary='导出岗位列表接口',
|
||||
description='用于导出当前符合查询条件的岗位列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回岗位列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('system:post:export')],
|
||||
)
|
||||
@Log(title='岗位管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_post_list(
|
||||
request: Request,
|
||||
post_page_query: Annotated[PostPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
post_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=False)
|
||||
post_export_result = await PostService.export_post_list_services(post_query_result)
|
||||
logger.info('导出成功')
|
||||
|
||||
return ResponseUtil.streaming(data=bytes2file_response(post_export_result))
|
||||
@@ -1,36 +1,57 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Form, Path, Query, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy import ColumnElement
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.data_scope import GetDataScope
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.data_scope import DataScopeDependency
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, DynamicResponseModel, PageResponseModel, ResponseBaseModel
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.user_do import SysUser
|
||||
from module_admin.entity.vo.dept_vo import DeptModel
|
||||
from module_admin.entity.vo.role_vo import AddRoleModel, DeleteRoleModel, RoleModel, RolePageQueryModel
|
||||
from module_admin.entity.vo.user_vo import CrudUserRoleModel, CurrentUserModel, UserRolePageQueryModel
|
||||
from module_admin.entity.vo.role_vo import (
|
||||
AddRoleModel,
|
||||
DeleteRoleModel,
|
||||
RoleDeptQueryModel,
|
||||
RoleModel,
|
||||
RolePageQueryModel,
|
||||
)
|
||||
from module_admin.entity.vo.user_vo import CrudUserRoleModel, CurrentUserModel, UserInfoModel, UserRolePageQueryModel
|
||||
from module_admin.service.dept_service import DeptService
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_admin.service.role_service import RoleService
|
||||
from module_admin.service.user_service import UserService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
roleController = APIRouter(prefix='/system/role', dependencies=[Depends(LoginService.get_current_user)])
|
||||
role_controller = APIRouterPro(
|
||||
prefix='/system/role', order_num=4, tags=['系统管理-角色管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@roleController.get('/deptTree/{role_id}', dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))])
|
||||
@role_controller.get(
|
||||
'/deptTree/{role_id}',
|
||||
summary='获取自定义数据权限时可见的部门树接口',
|
||||
description='用于自定义数据权限时获取当前用户可见的部门树',
|
||||
response_model=DynamicResponseModel[RoleDeptQueryModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:query')],
|
||||
)
|
||||
async def get_system_role_dept_tree(
|
||||
request: Request,
|
||||
role_id: int,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql)
|
||||
role_id: Annotated[int, Path(description='角色ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(), data_scope_sql)
|
||||
role_dept_query_result = await RoleService.get_role_dept_tree_services(query_db, role_id)
|
||||
role_dept_query_result.depts = dept_query_result
|
||||
logger.info('获取成功')
|
||||
@@ -38,15 +59,19 @@ async def get_system_role_dept_tree(
|
||||
return ResponseUtil.success(model_content=role_dept_query_result)
|
||||
|
||||
|
||||
@roleController.get(
|
||||
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))]
|
||||
@role_controller.get(
|
||||
'/list',
|
||||
summary='获取角色分页列表接口',
|
||||
description='用于获取角色分页列表',
|
||||
response_model=PageResponseModel[RoleModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:list')],
|
||||
)
|
||||
async def get_system_role_list(
|
||||
request: Request,
|
||||
role_page_query: RolePageQueryModel = Depends(RolePageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
role_page_query: Annotated[RolePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
role_page_query_result = await RoleService.get_role_list_services(
|
||||
query_db, role_page_query, data_scope_sql, is_page=True
|
||||
)
|
||||
@@ -55,15 +80,21 @@ async def get_system_role_list(
|
||||
return ResponseUtil.success(model_content=role_page_query_result)
|
||||
|
||||
|
||||
@roleController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:add'))])
|
||||
@role_controller.post(
|
||||
'',
|
||||
summary='新增角色接口',
|
||||
description='用于新增角色',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_role')
|
||||
@Log(title='角色管理', business_type=BusinessType.INSERT)
|
||||
async def add_system_role(
|
||||
request: Request,
|
||||
add_role: AddRoleModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
add_role.create_by = current_user.user.user_name
|
||||
add_role.create_time = datetime.now()
|
||||
add_role.update_by = current_user.user.user_name
|
||||
@@ -74,16 +105,22 @@ async def add_system_role(
|
||||
return ResponseUtil.success(msg=add_role_result.message)
|
||||
|
||||
|
||||
@roleController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))])
|
||||
@role_controller.put(
|
||||
'',
|
||||
summary='编辑角色接口',
|
||||
description='用于编辑角色',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_role')
|
||||
@Log(title='角色管理', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_role(
|
||||
request: Request,
|
||||
edit_role: AddRoleModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
await RoleService.check_role_allowed_services(edit_role)
|
||||
if not current_user.user.admin:
|
||||
await RoleService.check_role_data_scope_services(query_db, str(edit_role.role_id), data_scope_sql)
|
||||
@@ -95,15 +132,21 @@ async def edit_system_role(
|
||||
return ResponseUtil.success(msg=edit_role_result.message)
|
||||
|
||||
|
||||
@roleController.put('/dataScope', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))])
|
||||
@role_controller.put(
|
||||
'/dataScope',
|
||||
summary='编辑角色数据权限接口',
|
||||
description='用于编辑角色数据权限',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
|
||||
)
|
||||
@Log(title='角色管理', business_type=BusinessType.GRANT)
|
||||
async def edit_system_role_datascope(
|
||||
request: Request,
|
||||
role_data_scope: AddRoleModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
await RoleService.check_role_allowed_services(role_data_scope)
|
||||
if not current_user.user.admin:
|
||||
await RoleService.check_role_data_scope_services(query_db, str(role_data_scope.role_id), data_scope_sql)
|
||||
@@ -121,15 +164,21 @@ async def edit_system_role_datascope(
|
||||
return ResponseUtil.success(msg=role_data_scope_result.message)
|
||||
|
||||
|
||||
@roleController.delete('/{role_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:role:remove'))])
|
||||
@role_controller.delete(
|
||||
'/{role_ids}',
|
||||
summary='删除角色接口',
|
||||
description='用于删除角色',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:remove')],
|
||||
)
|
||||
@Log(title='角色管理', business_type=BusinessType.DELETE)
|
||||
async def delete_system_role(
|
||||
request: Request,
|
||||
role_ids: str,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
role_ids: Annotated[str, Path(description='需要删除的角色ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
role_id_list = role_ids.split(',') if role_ids else []
|
||||
if role_id_list:
|
||||
for role_id in role_id_list:
|
||||
@@ -143,16 +192,20 @@ async def delete_system_role(
|
||||
return ResponseUtil.success(msg=delete_role_result.message)
|
||||
|
||||
|
||||
@roleController.get(
|
||||
'/{role_id}', response_model=RoleModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))]
|
||||
@role_controller.get(
|
||||
'/{role_id}',
|
||||
summary='获取角色详情接口',
|
||||
description='用于获取指定角色的详细信息',
|
||||
response_model=DataResponseModel[RoleModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:query')],
|
||||
)
|
||||
async def query_detail_system_role(
|
||||
request: Request,
|
||||
role_id: int,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
role_id: Annotated[int, Path(description='角色ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
if not current_user.user.admin:
|
||||
await RoleService.check_role_data_scope_services(query_db, str(role_id), data_scope_sql)
|
||||
role_detail_result = await RoleService.role_detail_services(query_db, role_id)
|
||||
@@ -161,14 +214,28 @@ async def query_detail_system_role(
|
||||
return ResponseUtil.success(data=role_detail_result.model_dump(by_alias=True))
|
||||
|
||||
|
||||
@roleController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:role:export'))])
|
||||
@role_controller.post(
|
||||
'/export',
|
||||
summary='导出角色列表接口',
|
||||
description='用于导出当前符合查询条件的角色列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回角色列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:export')],
|
||||
)
|
||||
@Log(title='角色管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_role_list(
|
||||
request: Request,
|
||||
role_page_query: RolePageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
role_page_query: Annotated[RolePageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
role_query_result = await RoleService.get_role_list_services(
|
||||
query_db, role_page_query, data_scope_sql, is_page=False
|
||||
@@ -179,15 +246,21 @@ async def export_system_role_list(
|
||||
return ResponseUtil.streaming(data=bytes2file_response(role_export_result))
|
||||
|
||||
|
||||
@roleController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))])
|
||||
@role_controller.put(
|
||||
'/changeStatus',
|
||||
summary='修改角色状态接口',
|
||||
description='用于修改角色状态',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
|
||||
)
|
||||
@Log(title='角色管理', business_type=BusinessType.UPDATE)
|
||||
async def reset_system_role_status(
|
||||
request: Request,
|
||||
change_role: AddRoleModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
await RoleService.check_role_allowed_services(change_role)
|
||||
if not current_user.user.admin:
|
||||
await RoleService.check_role_data_scope_services(query_db, str(change_role.role_id), data_scope_sql)
|
||||
@@ -204,17 +277,19 @@ async def reset_system_role_status(
|
||||
return ResponseUtil.success(msg=edit_role_result.message)
|
||||
|
||||
|
||||
@roleController.get(
|
||||
@role_controller.get(
|
||||
'/authUser/allocatedList',
|
||||
response_model=PageResponseModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))],
|
||||
summary='获取已分配用户分页列表接口',
|
||||
description='用于获取指定角色已分配的用户分页列表',
|
||||
response_model=PageResponseModel[UserInfoModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:list')],
|
||||
)
|
||||
async def get_system_allocated_user_list(
|
||||
request: Request,
|
||||
user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
user_role: Annotated[UserRolePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
) -> Response:
|
||||
role_user_allocated_page_query_result = await RoleService.get_role_user_allocated_list_services(
|
||||
query_db, user_role, data_scope_sql, is_page=True
|
||||
)
|
||||
@@ -223,17 +298,19 @@ async def get_system_allocated_user_list(
|
||||
return ResponseUtil.success(model_content=role_user_allocated_page_query_result)
|
||||
|
||||
|
||||
@roleController.get(
|
||||
@role_controller.get(
|
||||
'/authUser/unallocatedList',
|
||||
response_model=PageResponseModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))],
|
||||
summary='获取未分配用户分页列表接口',
|
||||
description='用于获取指定角色未分配的用户分页列表',
|
||||
response_model=PageResponseModel[UserInfoModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:list')],
|
||||
)
|
||||
async def get_system_unallocated_user_list(
|
||||
request: Request,
|
||||
user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
user_role: Annotated[UserRolePageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
) -> Response:
|
||||
role_user_unallocated_page_query_result = await RoleService.get_role_user_unallocated_list_services(
|
||||
query_db, user_role, data_scope_sql, is_page=True
|
||||
)
|
||||
@@ -242,15 +319,21 @@ async def get_system_unallocated_user_list(
|
||||
return ResponseUtil.success(model_content=role_user_unallocated_page_query_result)
|
||||
|
||||
|
||||
@roleController.put('/authUser/selectAll', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))])
|
||||
@role_controller.put(
|
||||
'/authUser/selectAll',
|
||||
summary='分配用户给角色接口',
|
||||
description='用于给指定角色分配用户',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
|
||||
)
|
||||
@Log(title='角色管理', business_type=BusinessType.GRANT)
|
||||
async def add_system_role_user(
|
||||
request: Request,
|
||||
add_role_user: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
add_role_user: Annotated[CrudUserRoleModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
if not current_user.user.admin:
|
||||
await RoleService.check_role_data_scope_services(query_db, str(add_role_user.role_id), data_scope_sql)
|
||||
add_role_user_result = await UserService.add_user_role_services(query_db, add_role_user)
|
||||
@@ -259,24 +342,38 @@ async def add_system_role_user(
|
||||
return ResponseUtil.success(msg=add_role_user_result.message)
|
||||
|
||||
|
||||
@roleController.put('/authUser/cancel', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))])
|
||||
@role_controller.put(
|
||||
'/authUser/cancel',
|
||||
summary='取消分配用户给角色接口',
|
||||
description='用于取消指定用户分配给角色',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
|
||||
)
|
||||
@Log(title='角色管理', business_type=BusinessType.GRANT)
|
||||
async def cancel_system_role_user(
|
||||
request: Request, cancel_user_role: CrudUserRoleModel, query_db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
request: Request,
|
||||
cancel_user_role: CrudUserRoleModel,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
cancel_user_role_result = await UserService.delete_user_role_services(query_db, cancel_user_role)
|
||||
logger.info(cancel_user_role_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=cancel_user_role_result.message)
|
||||
|
||||
|
||||
@roleController.put('/authUser/cancelAll', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))])
|
||||
@role_controller.put(
|
||||
'/authUser/cancelAll',
|
||||
summary='批量取消分配用户给角色接口',
|
||||
description='用于批量取消用户分配给角色',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:role:edit')],
|
||||
)
|
||||
@Log(title='角色管理', business_type=BusinessType.GRANT)
|
||||
async def batch_cancel_system_role_user(
|
||||
request: Request,
|
||||
batch_cancel_user_role: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
batch_cancel_user_role: Annotated[CrudUserRoleModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
batch_cancel_user_role_result = await UserService.delete_user_role_services(query_db, batch_cancel_user_role)
|
||||
logger.info(batch_cancel_user_role_result.message)
|
||||
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
from fastapi import Request, Response
|
||||
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import PreAuthDependency
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel
|
||||
from module_admin.entity.vo.server_vo import ServerMonitorModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_admin.service.server_service import ServerService
|
||||
from utils.response_util import ResponseUtil
|
||||
from utils.log_util import logger
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
serverController = APIRouter(prefix='/monitor/server', dependencies=[Depends(LoginService.get_current_user)])
|
||||
|
||||
|
||||
@serverController.get(
|
||||
'', response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))]
|
||||
server_controller = APIRouterPro(
|
||||
prefix='/monitor/server', order_num=14, tags=['系统监控-服务监控'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
async def get_monitor_server_info(request: Request):
|
||||
|
||||
|
||||
@server_controller.get(
|
||||
'',
|
||||
summary='获取服务器监控信息接口',
|
||||
description='用于获取当前服务器的监控信息',
|
||||
response_model=DataResponseModel[ServerMonitorModel],
|
||||
dependencies=[UserInterfaceAuthDependency('monitor:server:list')],
|
||||
)
|
||||
async def get_monitor_server_info(request: Request) -> Response:
|
||||
# 获取全量数据
|
||||
server_info_query_result = await ServerService.get_server_monitor_info()
|
||||
logger.info('获取成功')
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Literal, Optional, Union
|
||||
from typing import Annotated, Literal, Optional, Union
|
||||
|
||||
import aiofiles
|
||||
from fastapi import File, Form, Path, Query, Request, Response, UploadFile
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from config.get_db import get_db
|
||||
from config.enums import BusinessType
|
||||
from sqlalchemy import ColumnElement
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.annotation.log_annotation import Log
|
||||
from common.aspect.data_scope import DataScopeDependency
|
||||
from common.aspect.db_seesion import DBSessionDependency
|
||||
from common.aspect.interface_auth import UserInterfaceAuthDependency
|
||||
from common.aspect.pre_auth import CurrentUserDependency, PreAuthDependency
|
||||
from common.enums import BusinessType
|
||||
from common.router import APIRouterPro
|
||||
from common.vo import DataResponseModel, DynamicResponseModel, PageResponseModel, ResponseBaseModel
|
||||
from config.env import UploadConfig
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.data_scope import GetDataScope
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
from module_admin.entity.vo.dept_vo import DeptModel
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.user_do import SysUser
|
||||
from module_admin.entity.vo.dept_vo import DeptModel, DeptTreeModel
|
||||
from module_admin.entity.vo.user_vo import (
|
||||
AddUserModel,
|
||||
AvatarModel,
|
||||
CrudUserRoleModel,
|
||||
CurrentUserModel,
|
||||
DeleteUserModel,
|
||||
@@ -26,41 +37,53 @@ from module_admin.entity.vo.user_vo import (
|
||||
UserProfileModel,
|
||||
UserRoleQueryModel,
|
||||
UserRoleResponseModel,
|
||||
UserRowModel,
|
||||
)
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_admin.service.user_service import UserService
|
||||
from module_admin.service.role_service import RoleService
|
||||
from module_admin.service.dept_service import DeptService
|
||||
from module_admin.service.role_service import RoleService
|
||||
from module_admin.service.user_service import UserService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.pwd_util import PwdUtil
|
||||
from utils.response_util import ResponseUtil
|
||||
from utils.upload_util import UploadUtil
|
||||
|
||||
|
||||
userController = APIRouter(prefix='/system/user', dependencies=[Depends(LoginService.get_current_user)])
|
||||
user_controller = APIRouterPro(
|
||||
prefix='/system/user', order_num=3, tags=['系统管理-用户管理'], dependencies=[PreAuthDependency()]
|
||||
)
|
||||
|
||||
|
||||
@userController.get('/deptTree', dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))])
|
||||
@user_controller.get(
|
||||
'/deptTree',
|
||||
summary='获取部门树接口',
|
||||
description='用于获取当前登录用户可见的部门树',
|
||||
response_model=DataResponseModel[list[DeptTreeModel]],
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:list')],
|
||||
)
|
||||
async def get_system_dept_tree(
|
||||
request: Request, query_db: AsyncSession = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))
|
||||
):
|
||||
dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql)
|
||||
request: Request,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(), data_scope_sql)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(data=dept_query_result)
|
||||
|
||||
|
||||
@userController.get(
|
||||
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))]
|
||||
@user_controller.get(
|
||||
'/list',
|
||||
summary='获取用户分页列表接口',
|
||||
description='用于获取用户分页列表',
|
||||
response_model=PageResponseModel[UserRowModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:list')],
|
||||
)
|
||||
async def get_system_user_list(
|
||||
request: Request,
|
||||
user_page_query: UserPageQueryModel = Depends(UserPageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
user_page_query: Annotated[UserPageQueryModel, Query()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
) -> Response:
|
||||
# 获取分页数据
|
||||
user_page_query_result = await UserService.get_user_list_services(
|
||||
query_db, user_page_query, data_scope_sql, is_page=True
|
||||
@@ -70,17 +93,23 @@ async def get_system_user_list(
|
||||
return ResponseUtil.success(model_content=user_page_query_result)
|
||||
|
||||
|
||||
@userController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:user:add'))])
|
||||
@user_controller.post(
|
||||
'',
|
||||
summary='新增用户接口',
|
||||
description='用于新增用户',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:add')],
|
||||
)
|
||||
@ValidateFields(validate_model='add_user')
|
||||
@Log(title='用户管理', business_type=BusinessType.INSERT)
|
||||
async def add_system_user(
|
||||
request: Request,
|
||||
add_user: AddUserModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
dept_data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
role_data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
dept_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
role_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
if not current_user.user.admin:
|
||||
await DeptService.check_dept_data_scope_services(query_db, add_user.dept_id, dept_data_scope_sql)
|
||||
await RoleService.check_role_data_scope_services(
|
||||
@@ -97,18 +126,24 @@ async def add_system_user(
|
||||
return ResponseUtil.success(msg=add_user_result.message)
|
||||
|
||||
|
||||
@userController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))])
|
||||
@user_controller.put(
|
||||
'',
|
||||
summary='编辑用户接口',
|
||||
description='用于编辑用户',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:edit')],
|
||||
)
|
||||
@ValidateFields(validate_model='edit_user')
|
||||
@Log(title='用户管理', business_type=BusinessType.UPDATE)
|
||||
async def edit_system_user(
|
||||
request: Request,
|
||||
edit_user: EditUserModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
user_data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
dept_data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
role_data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
user_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
dept_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
role_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
await UserService.check_user_allowed_services(edit_user)
|
||||
if not current_user.user.admin:
|
||||
await UserService.check_user_data_scope_services(query_db, edit_user.user_id, user_data_scope_sql)
|
||||
@@ -124,15 +159,21 @@ async def edit_system_user(
|
||||
return ResponseUtil.success(msg=edit_user_result.message)
|
||||
|
||||
|
||||
@userController.delete('/{user_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:user:remove'))])
|
||||
@user_controller.delete(
|
||||
'/{user_ids}',
|
||||
summary='删除用户接口',
|
||||
description='用于删除用户',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:remove')],
|
||||
)
|
||||
@Log(title='用户管理', business_type=BusinessType.DELETE)
|
||||
async def delete_system_user(
|
||||
request: Request,
|
||||
user_ids: str,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
user_ids: Annotated[str, Path(description='需要删除的用户ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
) -> Response:
|
||||
user_id_list = user_ids.split(',') if user_ids else []
|
||||
if user_id_list:
|
||||
if current_user.user.user_id in list(map(int, user_id_list)):
|
||||
@@ -150,21 +191,28 @@ async def delete_system_user(
|
||||
return ResponseUtil.success(msg=delete_user_result.message)
|
||||
|
||||
|
||||
@userController.put('/resetPwd', dependencies=[Depends(CheckUserInterfaceAuth('system:user:resetPwd'))])
|
||||
@user_controller.put(
|
||||
'/resetPwd',
|
||||
summary='重置用户密码接口',
|
||||
description='用于重置用户密码',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:resetPwd')],
|
||||
)
|
||||
@Log(title='用户管理', business_type=BusinessType.UPDATE)
|
||||
async def reset_system_user_pwd(
|
||||
request: Request,
|
||||
reset_user: EditUserModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
) -> Response:
|
||||
await UserService.check_user_allowed_services(reset_user)
|
||||
if not current_user.user.admin:
|
||||
await UserService.check_user_data_scope_services(query_db, reset_user.user_id, data_scope_sql)
|
||||
edit_user = EditUserModel(
|
||||
userId=reset_user.user_id,
|
||||
password=PwdUtil.get_password_hash(reset_user.password),
|
||||
pwdUpdateDate=datetime.now(),
|
||||
updateBy=current_user.user.user_name,
|
||||
updateTime=datetime.now(),
|
||||
type='pwd',
|
||||
@@ -175,15 +223,21 @@ async def reset_system_user_pwd(
|
||||
return ResponseUtil.success(msg=edit_user_result.message)
|
||||
|
||||
|
||||
@userController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))])
|
||||
@user_controller.put(
|
||||
'/changeStatus',
|
||||
summary='修改用户状态接口',
|
||||
description='用于修改用户状态',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:edit')],
|
||||
)
|
||||
@Log(title='用户管理', business_type=BusinessType.UPDATE)
|
||||
async def change_system_user_status(
|
||||
request: Request,
|
||||
change_user: EditUserModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
) -> Response:
|
||||
await UserService.check_user_allowed_services(change_user)
|
||||
if not current_user.user.admin:
|
||||
await UserService.check_user_data_scope_services(query_db, change_user.user_id, data_scope_sql)
|
||||
@@ -200,31 +254,44 @@ async def change_system_user_status(
|
||||
return ResponseUtil.success(msg=edit_user_result.message)
|
||||
|
||||
|
||||
@userController.get('/profile', response_model=UserProfileModel)
|
||||
@user_controller.get(
|
||||
'/profile',
|
||||
summary='获取用户个人信息接口',
|
||||
description='用于获取当前登录用户的个人信息',
|
||||
response_model=DynamicResponseModel[UserProfileModel],
|
||||
)
|
||||
async def query_detail_system_user_profile(
|
||||
request: Request,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
profile_user_result = await UserService.user_profile_services(query_db, current_user.user.user_id)
|
||||
logger.info(f'获取user_id为{current_user.user.user_id}的信息成功')
|
||||
|
||||
return ResponseUtil.success(model_content=profile_user_result)
|
||||
|
||||
|
||||
@userController.get(
|
||||
'/{user_id}', response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))]
|
||||
@user_controller.get(
|
||||
'/{user_id}',
|
||||
summary='获取用户详情接口',
|
||||
description='用于获取指定用户的详情信息',
|
||||
response_model=DynamicResponseModel[UserDetailModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:query')],
|
||||
)
|
||||
@userController.get(
|
||||
'/', response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))]
|
||||
@user_controller.get(
|
||||
'/',
|
||||
summary='获取用户岗位和角色列表接口',
|
||||
description='用于获取当前登录用户可见的岗位和角色列表',
|
||||
response_model=DynamicResponseModel[UserDetailModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:query')],
|
||||
)
|
||||
async def query_detail_system_user(
|
||||
request: Request,
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
user_id: Optional[Union[int, Literal['']]] = '',
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
) -> Response:
|
||||
if user_id and not current_user.user.admin:
|
||||
await UserService.check_user_data_scope_services(query_db, user_id, data_scope_sql)
|
||||
detail_user_result = await UserService.user_detail_services(query_db, user_id)
|
||||
@@ -233,14 +300,19 @@ async def query_detail_system_user(
|
||||
return ResponseUtil.success(model_content=detail_user_result)
|
||||
|
||||
|
||||
@userController.post('/profile/avatar')
|
||||
@user_controller.post(
|
||||
'/profile/avatar',
|
||||
summary='修改用户头像接口',
|
||||
description='用于修改当前登录用户的头像',
|
||||
response_model=DynamicResponseModel[AvatarModel],
|
||||
)
|
||||
@Log(title='个人信息', business_type=BusinessType.UPDATE)
|
||||
async def change_system_user_profile_avatar(
|
||||
request: Request,
|
||||
avatarfile: bytes = File(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
avatarfile: Annotated[bytes, File()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
if avatarfile:
|
||||
relative_path = (
|
||||
f'avatar/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}'
|
||||
@@ -252,8 +324,8 @@ async def change_system_user_profile_avatar(
|
||||
pass
|
||||
avatar_name = f'avatar_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.png'
|
||||
avatar_path = os.path.join(dir_path, avatar_name)
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatarfile)
|
||||
async with aiofiles.open(avatar_path, 'wb') as f:
|
||||
await f.write(avatarfile)
|
||||
edit_user = EditUserModel(
|
||||
userId=current_user.user.user_id,
|
||||
avatar=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{avatar_name}',
|
||||
@@ -264,18 +336,23 @@ async def change_system_user_profile_avatar(
|
||||
edit_user_result = await UserService.edit_user_services(query_db, edit_user)
|
||||
logger.info(edit_user_result.message)
|
||||
|
||||
return ResponseUtil.success(dict_content={'imgUrl': edit_user.avatar}, msg=edit_user_result.message)
|
||||
return ResponseUtil.success(model_content=AvatarModel(imgUrl=edit_user.avatar), msg=edit_user_result.message)
|
||||
return ResponseUtil.failure(msg='上传图片异常,请联系管理员')
|
||||
|
||||
|
||||
@userController.put('/profile')
|
||||
@user_controller.put(
|
||||
'/profile',
|
||||
summary='修改用户个人信息接口',
|
||||
description='用于修改当前登录用户的个人信息',
|
||||
response_model=ResponseBaseModel,
|
||||
)
|
||||
@Log(title='个人信息', business_type=BusinessType.UPDATE)
|
||||
async def change_system_user_profile_info(
|
||||
request: Request,
|
||||
user_info: UserInfoModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
edit_user = EditUserModel(
|
||||
**user_info.model_dump(exclude_unset=True, by_alias=True, exclude={'role_ids', 'post_ids'}),
|
||||
userId=current_user.user.user_id,
|
||||
@@ -292,18 +369,24 @@ async def change_system_user_profile_info(
|
||||
return ResponseUtil.success(msg=edit_user_result.message)
|
||||
|
||||
|
||||
@userController.put('/profile/updatePwd')
|
||||
@user_controller.put(
|
||||
'/profile/updatePwd',
|
||||
summary='修改用户密码接口',
|
||||
description='用于修改当前登录用户的密码',
|
||||
response_model=ResponseBaseModel,
|
||||
)
|
||||
@Log(title='个人信息', business_type=BusinessType.UPDATE)
|
||||
async def reset_system_user_password(
|
||||
request: Request,
|
||||
reset_password: ResetPasswordModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
) -> Response:
|
||||
reset_user = ResetUserModel(
|
||||
userId=current_user.user.user_id,
|
||||
oldPassword=reset_password.old_password,
|
||||
password=reset_password.new_password,
|
||||
pwdUpdateDate=datetime.now(),
|
||||
updateBy=current_user.user.user_name,
|
||||
updateTime=datetime.now(),
|
||||
)
|
||||
@@ -313,17 +396,23 @@ async def reset_system_user_password(
|
||||
return ResponseUtil.success(msg=reset_user_result.message)
|
||||
|
||||
|
||||
@userController.post('/importData', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))])
|
||||
@user_controller.post(
|
||||
'/importData',
|
||||
summary='批量导入用户接口',
|
||||
description='用于批量导入用户数据',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:import')],
|
||||
)
|
||||
@Log(title='用户管理', business_type=BusinessType.IMPORT)
|
||||
async def batch_import_system_user(
|
||||
request: Request,
|
||||
file: UploadFile = File(...),
|
||||
update_support: bool = Query(alias='updateSupport'),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
user_data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
dept_data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
file: Annotated[UploadFile, File(...)],
|
||||
update_support: Annotated[bool, Query(alias='updateSupport')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
user_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
dept_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
batch_import_result = await UserService.batch_import_user_services(
|
||||
request, query_db, file, update_support, current_user, user_data_scope_sql, dept_data_scope_sql
|
||||
)
|
||||
@@ -332,22 +421,52 @@ async def batch_import_system_user(
|
||||
return ResponseUtil.success(msg=batch_import_result.message)
|
||||
|
||||
|
||||
@userController.post('/importTemplate', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))])
|
||||
async def export_system_user_template(request: Request, query_db: AsyncSession = Depends(get_db)):
|
||||
@user_controller.post(
|
||||
'/importTemplate',
|
||||
summary='获取用户导入模板接口',
|
||||
description='用于获取用户导入模板excel文件',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回导入用户模板excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:import')],
|
||||
)
|
||||
async def export_system_user_template(
|
||||
request: Request, query_db: Annotated[AsyncSession, DBSessionDependency()]
|
||||
) -> Response:
|
||||
user_import_template_result = await UserService.get_user_import_template_services()
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.streaming(data=bytes2file_response(user_import_template_result))
|
||||
|
||||
|
||||
@userController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:user:export'))])
|
||||
@user_controller.post(
|
||||
'/export',
|
||||
summary='导出用户列表接口',
|
||||
description='用于导出当前符合查询条件的用户列表数据',
|
||||
response_class=StreamingResponse,
|
||||
responses={
|
||||
200: {
|
||||
'description': '流式返回用户列表excel文件',
|
||||
'content': {
|
||||
'application/octet-stream': {},
|
||||
},
|
||||
}
|
||||
},
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:export')],
|
||||
)
|
||||
@Log(title='用户管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_user_list(
|
||||
request: Request,
|
||||
user_page_query: UserPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
user_page_query: Annotated[UserPageQueryModel, Form()],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
) -> Response:
|
||||
# 获取全量数据
|
||||
user_query_result = await UserService.get_user_list_services(
|
||||
query_db, user_page_query, data_scope_sql, is_page=False
|
||||
@@ -358,12 +477,18 @@ async def export_system_user_list(
|
||||
return ResponseUtil.streaming(data=bytes2file_response(user_export_result))
|
||||
|
||||
|
||||
@userController.get(
|
||||
@user_controller.get(
|
||||
'/authRole/{user_id}',
|
||||
response_model=UserRoleResponseModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))],
|
||||
summary='获取用户已分配角色列表接口',
|
||||
description='用于获取指定用户已分配的角色列表',
|
||||
response_model=DynamicResponseModel[UserRoleResponseModel],
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:query')],
|
||||
)
|
||||
async def get_system_allocated_role_list(request: Request, user_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
async def get_system_allocated_role_list(
|
||||
request: Request,
|
||||
user_id: Annotated[int, Path(description='用户ID')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
) -> Response:
|
||||
user_role_query = UserRoleQueryModel(userId=user_id)
|
||||
user_role_allocated_query_result = await UserService.get_user_role_allocated_list_services(
|
||||
query_db, user_role_query
|
||||
@@ -373,21 +498,23 @@ async def get_system_allocated_role_list(request: Request, user_id: int, query_d
|
||||
return ResponseUtil.success(model_content=user_role_allocated_query_result)
|
||||
|
||||
|
||||
@userController.put(
|
||||
@user_controller.put(
|
||||
'/authRole',
|
||||
response_model=UserRoleResponseModel,
|
||||
dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))],
|
||||
summary='给用户分配角色接口',
|
||||
description='用于给指定用户分配角色',
|
||||
response_model=ResponseBaseModel,
|
||||
dependencies=[UserInterfaceAuthDependency('system:user:edit')],
|
||||
)
|
||||
@Log(title='用户管理', business_type=BusinessType.GRANT)
|
||||
async def update_system_role_user(
|
||||
request: Request,
|
||||
user_id: int = Query(alias='userId'),
|
||||
role_ids: str = Query(alias='roleIds'),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
user_data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
role_data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
user_id: Annotated[int, Query(alias='userId')],
|
||||
role_ids: Annotated[str, Query(alias='roleIds')],
|
||||
query_db: Annotated[AsyncSession, DBSessionDependency()],
|
||||
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
|
||||
user_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysUser)],
|
||||
role_data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
|
||||
) -> Response:
|
||||
if not current_user.user.admin:
|
||||
await UserService.check_user_data_scope_services(query_db, user_id, user_data_scope_sql)
|
||||
await RoleService.check_role_data_scope_services(query_db, role_ids, role_data_scope_sql)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime, time
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import delete, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.config_do import SysConfig
|
||||
from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel
|
||||
from utils.page_util import PageUtil
|
||||
@@ -12,7 +16,7 @@ class ConfigDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_config_detail_by_id(cls, db: AsyncSession, config_id: int):
|
||||
async def get_config_detail_by_id(cls, db: AsyncSession, config_id: int) -> Union[SysConfig, None]:
|
||||
"""
|
||||
根据参数配置id获取参数配置详细信息
|
||||
|
||||
@@ -25,7 +29,7 @@ class ConfigDao:
|
||||
return config_info
|
||||
|
||||
@classmethod
|
||||
async def get_config_detail_by_info(cls, db: AsyncSession, config: ConfigModel):
|
||||
async def get_config_detail_by_info(cls, db: AsyncSession, config: ConfigModel) -> Union[SysConfig, None]:
|
||||
"""
|
||||
根据参数配置参数获取参数配置信息
|
||||
|
||||
@@ -49,7 +53,9 @@ class ConfigDao:
|
||||
return config_info
|
||||
|
||||
@classmethod
|
||||
async def get_config_list(cls, db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False):
|
||||
async def get_config_list(
|
||||
cls, db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取参数配置列表信息
|
||||
|
||||
@@ -74,12 +80,14 @@ class ConfigDao:
|
||||
.order_by(SysConfig.config_id)
|
||||
.distinct()
|
||||
)
|
||||
config_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
config_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return config_list
|
||||
|
||||
@classmethod
|
||||
async def add_config_dao(cls, db: AsyncSession, config: ConfigModel):
|
||||
async def add_config_dao(cls, db: AsyncSession, config: ConfigModel) -> SysConfig:
|
||||
"""
|
||||
新增参数配置数据库操作
|
||||
|
||||
@@ -94,7 +102,7 @@ class ConfigDao:
|
||||
return db_config
|
||||
|
||||
@classmethod
|
||||
async def edit_config_dao(cls, db: AsyncSession, config: dict):
|
||||
async def edit_config_dao(cls, db: AsyncSession, config: dict) -> None:
|
||||
"""
|
||||
编辑参数配置数据库操作
|
||||
|
||||
@@ -105,7 +113,7 @@ class ConfigDao:
|
||||
await db.execute(update(SysConfig), [config])
|
||||
|
||||
@classmethod
|
||||
async def delete_config_dao(cls, db: AsyncSession, config: ConfigModel):
|
||||
async def delete_config_dao(cls, db: AsyncSession, config: ConfigModel) -> None:
|
||||
"""
|
||||
删除参数配置数据库操作
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from sqlalchemy import bindparam, func, or_, select, update # noqa: F401
|
||||
from collections.abc import Sequence
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import ColumnElement, bindparam, func, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.util import immutabledict
|
||||
from typing import List
|
||||
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.role_do import SysRoleDept # noqa: F401
|
||||
from module_admin.entity.do.user_do import SysUser
|
||||
from module_admin.entity.vo.dept_vo import DeptModel
|
||||
|
||||
@@ -14,7 +16,7 @@ class DeptDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_dept_by_id(cls, db: AsyncSession, dept_id: int):
|
||||
async def get_dept_by_id(cls, db: AsyncSession, dept_id: int) -> Union[SysDept, None]:
|
||||
"""
|
||||
根据部门id获取在用部门信息
|
||||
|
||||
@@ -27,7 +29,7 @@ class DeptDao:
|
||||
return dept_info
|
||||
|
||||
@classmethod
|
||||
async def get_dept_detail_by_id(cls, db: AsyncSession, dept_id: int):
|
||||
async def get_dept_detail_by_id(cls, db: AsyncSession, dept_id: int) -> Union[SysDept, None]:
|
||||
"""
|
||||
根据部门id获取部门详细信息
|
||||
|
||||
@@ -44,7 +46,7 @@ class DeptDao:
|
||||
return dept_info
|
||||
|
||||
@classmethod
|
||||
async def get_dept_detail_by_info(cls, db: AsyncSession, dept: DeptModel):
|
||||
async def get_dept_detail_by_info(cls, db: AsyncSession, dept: DeptModel) -> Union[SysDept, None]:
|
||||
"""
|
||||
根据部门参数获取部门信息
|
||||
|
||||
@@ -58,6 +60,7 @@ class DeptDao:
|
||||
select(SysDept).where(
|
||||
SysDept.parent_id == dept.parent_id if dept.parent_id else True,
|
||||
SysDept.dept_name == dept.dept_name if dept.dept_name else True,
|
||||
SysDept.del_flag == '0',
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -68,7 +71,9 @@ class DeptDao:
|
||||
return dept_info
|
||||
|
||||
@classmethod
|
||||
async def get_dept_info_for_edit_option(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str):
|
||||
async def get_dept_info_for_edit_option(
|
||||
cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: ColumnElement
|
||||
) -> Sequence[SysDept]:
|
||||
"""
|
||||
获取部门编辑对应的在用部门列表信息
|
||||
|
||||
@@ -88,7 +93,7 @@ class DeptDao:
|
||||
),
|
||||
SysDept.del_flag == '0',
|
||||
SysDept.status == '0',
|
||||
eval(data_scope_sql),
|
||||
data_scope_sql,
|
||||
)
|
||||
.order_by(SysDept.order_num)
|
||||
.distinct()
|
||||
@@ -101,7 +106,7 @@ class DeptDao:
|
||||
return dept_result
|
||||
|
||||
@classmethod
|
||||
async def get_children_dept_dao(cls, db: AsyncSession, dept_id: int):
|
||||
async def get_children_dept_dao(cls, db: AsyncSession, dept_id: int) -> Sequence[SysDept]:
|
||||
"""
|
||||
根据部门id查询当前部门的子部门列表信息
|
||||
|
||||
@@ -116,7 +121,9 @@ class DeptDao:
|
||||
return dept_result
|
||||
|
||||
@classmethod
|
||||
async def get_dept_list_for_tree(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str):
|
||||
async def get_dept_list_for_tree(
|
||||
cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: ColumnElement
|
||||
) -> Sequence[SysDept]:
|
||||
"""
|
||||
获取所有在用部门列表信息
|
||||
|
||||
@@ -133,7 +140,7 @@ class DeptDao:
|
||||
SysDept.status == '0',
|
||||
SysDept.del_flag == '0',
|
||||
SysDept.dept_name.like(f'%{dept_info.dept_name}%') if dept_info.dept_name else True,
|
||||
eval(data_scope_sql),
|
||||
data_scope_sql,
|
||||
)
|
||||
.order_by(SysDept.order_num)
|
||||
.distinct()
|
||||
@@ -146,7 +153,9 @@ class DeptDao:
|
||||
return dept_result
|
||||
|
||||
@classmethod
|
||||
async def get_dept_list(cls, db: AsyncSession, page_object: DeptModel, data_scope_sql: str):
|
||||
async def get_dept_list(
|
||||
cls, db: AsyncSession, page_object: DeptModel, data_scope_sql: ColumnElement
|
||||
) -> Sequence[SysDept]:
|
||||
"""
|
||||
根据查询参数获取部门列表信息
|
||||
|
||||
@@ -164,7 +173,7 @@ class DeptDao:
|
||||
SysDept.dept_id == page_object.dept_id if page_object.dept_id is not None else True,
|
||||
SysDept.status == page_object.status if page_object.status else True,
|
||||
SysDept.dept_name.like(f'%{page_object.dept_name}%') if page_object.dept_name else True,
|
||||
eval(data_scope_sql),
|
||||
data_scope_sql,
|
||||
)
|
||||
.order_by(SysDept.order_num)
|
||||
.distinct()
|
||||
@@ -177,7 +186,7 @@ class DeptDao:
|
||||
return dept_result
|
||||
|
||||
@classmethod
|
||||
async def add_dept_dao(cls, db: AsyncSession, dept: DeptModel):
|
||||
async def add_dept_dao(cls, db: AsyncSession, dept: DeptModel) -> SysDept:
|
||||
"""
|
||||
新增部门数据库操作
|
||||
|
||||
@@ -192,7 +201,7 @@ class DeptDao:
|
||||
return db_dept
|
||||
|
||||
@classmethod
|
||||
async def edit_dept_dao(cls, db: AsyncSession, dept: dict):
|
||||
async def edit_dept_dao(cls, db: AsyncSession, dept: dict) -> None:
|
||||
"""
|
||||
编辑部门数据库操作
|
||||
|
||||
@@ -203,7 +212,7 @@ class DeptDao:
|
||||
await db.execute(update(SysDept), [dept])
|
||||
|
||||
@classmethod
|
||||
async def update_dept_children_dao(cls, db: AsyncSession, update_dept: List):
|
||||
async def update_dept_children_dao(cls, db: AsyncSession, update_dept: list) -> None:
|
||||
"""
|
||||
更新子部门信息
|
||||
|
||||
@@ -225,7 +234,7 @@ class DeptDao:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def update_dept_status_normal_dao(cls, db: AsyncSession, dept_id_list: List):
|
||||
async def update_dept_status_normal_dao(cls, db: AsyncSession, dept_id_list: list) -> None:
|
||||
"""
|
||||
批量更新部门状态为正常
|
||||
|
||||
@@ -236,7 +245,7 @@ class DeptDao:
|
||||
await db.execute(update(SysDept).where(SysDept.dept_id.in_(dept_id_list)).values(status='0'))
|
||||
|
||||
@classmethod
|
||||
async def delete_dept_dao(cls, db: AsyncSession, dept: DeptModel):
|
||||
async def delete_dept_dao(cls, db: AsyncSession, dept: DeptModel) -> None:
|
||||
"""
|
||||
删除部门数据库操作
|
||||
|
||||
@@ -251,7 +260,7 @@ class DeptDao:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def count_normal_children_dept_dao(cls, db: AsyncSession, dept_id: int):
|
||||
async def count_normal_children_dept_dao(cls, db: AsyncSession, dept_id: int) -> Union[int, None]:
|
||||
"""
|
||||
根据部门id查询查询所有子部门(正常状态)的数量
|
||||
|
||||
@@ -270,7 +279,7 @@ class DeptDao:
|
||||
return normal_children_dept_count
|
||||
|
||||
@classmethod
|
||||
async def count_children_dept_dao(cls, db: AsyncSession, dept_id: int):
|
||||
async def count_children_dept_dao(cls, db: AsyncSession, dept_id: int) -> Union[int, None]:
|
||||
"""
|
||||
根据部门id查询查询所有子部门(所有状态)的数量
|
||||
|
||||
@@ -290,7 +299,7 @@ class DeptDao:
|
||||
return children_dept_count
|
||||
|
||||
@classmethod
|
||||
async def count_dept_user_dao(cls, db: AsyncSession, dept_id: int):
|
||||
async def count_dept_user_dao(cls, db: AsyncSession, dept_id: int) -> Union[int, None]:
|
||||
"""
|
||||
根据部门id查询查询部门下的用户数量
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime, time
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import and_, delete, func, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from module_admin.entity.do.dict_do import SysDictType, SysDictData
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.dict_do import SysDictData, SysDictType
|
||||
from module_admin.entity.vo.dict_vo import DictDataModel, DictDataPageQueryModel, DictTypeModel, DictTypePageQueryModel
|
||||
from utils.page_util import PageUtil
|
||||
from utils.time_format_util import list_format_datetime
|
||||
@@ -13,7 +18,7 @@ class DictTypeDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_dict_type_detail_by_id(cls, db: AsyncSession, dict_id: int):
|
||||
async def get_dict_type_detail_by_id(cls, db: AsyncSession, dict_id: int) -> Union[SysDictType, None]:
|
||||
"""
|
||||
根据字典类型id获取字典类型详细信息
|
||||
|
||||
@@ -26,7 +31,7 @@ class DictTypeDao:
|
||||
return dict_type_info
|
||||
|
||||
@classmethod
|
||||
async def get_dict_type_detail_by_info(cls, db: AsyncSession, dict_type: DictTypeModel):
|
||||
async def get_dict_type_detail_by_info(cls, db: AsyncSession, dict_type: DictTypeModel) -> Union[SysDictType, None]:
|
||||
"""
|
||||
根据字典类型参数获取字典类型信息
|
||||
|
||||
@@ -50,7 +55,7 @@ class DictTypeDao:
|
||||
return dict_type_info
|
||||
|
||||
@classmethod
|
||||
async def get_all_dict_type(cls, db: AsyncSession):
|
||||
async def get_all_dict_type(cls, db: AsyncSession) -> list[Any]:
|
||||
"""
|
||||
获取所有的字典类型信息
|
||||
|
||||
@@ -62,7 +67,9 @@ class DictTypeDao:
|
||||
return list_format_datetime(dict_type_info)
|
||||
|
||||
@classmethod
|
||||
async def get_dict_type_list(cls, db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False):
|
||||
async def get_dict_type_list(
|
||||
cls, db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取字典类型列表信息
|
||||
|
||||
@@ -87,12 +94,14 @@ class DictTypeDao:
|
||||
.order_by(SysDictType.dict_id)
|
||||
.distinct()
|
||||
)
|
||||
dict_type_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
dict_type_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return dict_type_list
|
||||
|
||||
@classmethod
|
||||
async def add_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel):
|
||||
async def add_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel) -> SysDictType:
|
||||
"""
|
||||
新增字典类型数据库操作
|
||||
|
||||
@@ -107,7 +116,7 @@ class DictTypeDao:
|
||||
return db_dict_type
|
||||
|
||||
@classmethod
|
||||
async def edit_dict_type_dao(cls, db: AsyncSession, dict_type: dict):
|
||||
async def edit_dict_type_dao(cls, db: AsyncSession, dict_type: dict) -> None:
|
||||
"""
|
||||
编辑字典类型数据库操作
|
||||
|
||||
@@ -118,7 +127,7 @@ class DictTypeDao:
|
||||
await db.execute(update(SysDictType), [dict_type])
|
||||
|
||||
@classmethod
|
||||
async def delete_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel):
|
||||
async def delete_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel) -> None:
|
||||
"""
|
||||
删除字典类型数据库操作
|
||||
|
||||
@@ -135,7 +144,7 @@ class DictDataDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_dict_data_detail_by_id(cls, db: AsyncSession, dict_code: int):
|
||||
async def get_dict_data_detail_by_id(cls, db: AsyncSession, dict_code: int) -> Union[SysDictData, None]:
|
||||
"""
|
||||
根据字典数据id获取字典数据详细信息
|
||||
|
||||
@@ -150,7 +159,7 @@ class DictDataDao:
|
||||
return dict_data_info
|
||||
|
||||
@classmethod
|
||||
async def get_dict_data_detail_by_info(cls, db: AsyncSession, dict_data: DictDataModel):
|
||||
async def get_dict_data_detail_by_info(cls, db: AsyncSession, dict_data: DictDataModel) -> Union[SysDictData, None]:
|
||||
"""
|
||||
根据字典数据参数获取字典数据信息
|
||||
|
||||
@@ -175,7 +184,9 @@ class DictDataDao:
|
||||
return dict_data_info
|
||||
|
||||
@classmethod
|
||||
async def get_dict_data_list(cls, db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False):
|
||||
async def get_dict_data_list(
|
||||
cls, db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取字典数据列表信息
|
||||
|
||||
@@ -194,12 +205,14 @@ class DictDataDao:
|
||||
.order_by(SysDictData.dict_sort)
|
||||
.distinct()
|
||||
)
|
||||
dict_data_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
dict_data_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return dict_data_list
|
||||
|
||||
@classmethod
|
||||
async def query_dict_data_list(cls, db: AsyncSession, dict_type: str):
|
||||
async def query_dict_data_list(cls, db: AsyncSession, dict_type: str) -> Sequence[SysDictData]:
|
||||
"""
|
||||
根据查询参数获取字典数据列表信息
|
||||
|
||||
@@ -229,7 +242,7 @@ class DictDataDao:
|
||||
return dict_data_list
|
||||
|
||||
@classmethod
|
||||
async def add_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel):
|
||||
async def add_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel) -> SysDictData:
|
||||
"""
|
||||
新增字典数据数据库操作
|
||||
|
||||
@@ -244,7 +257,7 @@ class DictDataDao:
|
||||
return db_data_type
|
||||
|
||||
@classmethod
|
||||
async def edit_dict_data_dao(cls, db: AsyncSession, dict_data: dict):
|
||||
async def edit_dict_data_dao(cls, db: AsyncSession, dict_data: dict) -> None:
|
||||
"""
|
||||
编辑字典数据数据库操作
|
||||
|
||||
@@ -255,7 +268,7 @@ class DictDataDao:
|
||||
await db.execute(update(SysDictData), [dict_data])
|
||||
|
||||
@classmethod
|
||||
async def delete_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel):
|
||||
async def delete_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel) -> None:
|
||||
"""
|
||||
删除字典数据数据库操作
|
||||
|
||||
@@ -266,7 +279,7 @@ class DictDataDao:
|
||||
await db.execute(delete(SysDictData).where(SysDictData.dict_code.in_([dict_data.dict_code])))
|
||||
|
||||
@classmethod
|
||||
async def count_dict_data_dao(cls, db: AsyncSession, dict_type: str):
|
||||
async def count_dict_data_dao(cls, db: AsyncSession, dict_type: str) -> Union[int, None]:
|
||||
"""
|
||||
根据字典类型查询字典类型关联的字典数据数量
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import delete, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.job_do import SysJob
|
||||
from module_admin.entity.vo.job_vo import JobModel, JobPageQueryModel
|
||||
from utils.page_util import PageUtil
|
||||
@@ -11,7 +16,7 @@ class JobDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_job_detail_by_id(cls, db: AsyncSession, job_id: int):
|
||||
async def get_job_detail_by_id(cls, db: AsyncSession, job_id: int) -> Union[SysJob, None]:
|
||||
"""
|
||||
根据定时任务id获取定时任务详细信息
|
||||
|
||||
@@ -24,7 +29,7 @@ class JobDao:
|
||||
return job_info
|
||||
|
||||
@classmethod
|
||||
async def get_job_detail_by_info(cls, db: AsyncSession, job: JobModel):
|
||||
async def get_job_detail_by_info(cls, db: AsyncSession, job: JobModel) -> Union[SysJob, None]:
|
||||
"""
|
||||
根据定时任务参数获取定时任务信息
|
||||
|
||||
@@ -53,7 +58,9 @@ class JobDao:
|
||||
return job_info
|
||||
|
||||
@classmethod
|
||||
async def get_job_list(cls, db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False):
|
||||
async def get_job_list(
|
||||
cls, db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取定时任务列表信息
|
||||
|
||||
@@ -72,12 +79,14 @@ class JobDao:
|
||||
.order_by(SysJob.job_id)
|
||||
.distinct()
|
||||
)
|
||||
job_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
job_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return job_list
|
||||
|
||||
@classmethod
|
||||
async def get_job_list_for_scheduler(cls, db: AsyncSession):
|
||||
async def get_job_list_for_scheduler(cls, db: AsyncSession) -> Sequence[SysJob]:
|
||||
"""
|
||||
获取定时任务列表信息
|
||||
|
||||
@@ -89,7 +98,7 @@ class JobDao:
|
||||
return job_list
|
||||
|
||||
@classmethod
|
||||
async def add_job_dao(cls, db: AsyncSession, job: JobModel):
|
||||
async def add_job_dao(cls, db: AsyncSession, job: JobModel) -> SysJob:
|
||||
"""
|
||||
新增定时任务数据库操作
|
||||
|
||||
@@ -104,18 +113,27 @@ class JobDao:
|
||||
return db_job
|
||||
|
||||
@classmethod
|
||||
async def edit_job_dao(cls, db: AsyncSession, job: dict):
|
||||
async def edit_job_dao(cls, db: AsyncSession, job: dict, old_job: JobModel) -> None:
|
||||
"""
|
||||
编辑定时任务数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param job: 需要更新的定时任务字典
|
||||
:param old_job: 原定时任务对象
|
||||
:return:
|
||||
"""
|
||||
await db.execute(update(SysJob), [job])
|
||||
await db.execute(
|
||||
update(SysJob)
|
||||
.where(
|
||||
SysJob.job_id == old_job.job_id,
|
||||
SysJob.job_name == old_job.job_name,
|
||||
SysJob.job_group == old_job.job_group,
|
||||
)
|
||||
.values(**job)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def delete_job_dao(cls, db: AsyncSession, job: JobModel):
|
||||
async def delete_job_dao(cls, db: AsyncSession, job: JobModel) -> None:
|
||||
"""
|
||||
删除定时任务数据库操作
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from datetime import datetime, time
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import delete, desc, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.job_do import SysJobLog
|
||||
from module_admin.entity.vo.job_vo import JobLogModel, JobLogPageQueryModel
|
||||
from utils.page_util import PageUtil
|
||||
@@ -13,7 +17,9 @@ class JobLogDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_job_log_list(cls, db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False):
|
||||
async def get_job_log_list(
|
||||
cls, db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取定时任务日志列表信息
|
||||
|
||||
@@ -38,12 +44,14 @@ class JobLogDao:
|
||||
.order_by(desc(SysJobLog.create_time))
|
||||
.distinct()
|
||||
)
|
||||
job_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
job_log_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return job_log_list
|
||||
|
||||
@classmethod
|
||||
def add_job_log_dao(cls, db: Session, job_log: JobLogModel):
|
||||
def add_job_log_dao(cls, db: Session, job_log: JobLogModel) -> SysJobLog:
|
||||
"""
|
||||
新增定时任务日志数据库操作
|
||||
|
||||
@@ -58,7 +66,7 @@ class JobLogDao:
|
||||
return db_job_log
|
||||
|
||||
@classmethod
|
||||
async def delete_job_log_dao(cls, db: AsyncSession, job_log: JobLogModel):
|
||||
async def delete_job_log_dao(cls, db: AsyncSession, job_log: JobLogModel) -> None:
|
||||
"""
|
||||
删除定时任务日志数据库操作
|
||||
|
||||
@@ -69,7 +77,7 @@ class JobLogDao:
|
||||
await db.execute(delete(SysJobLog).where(SysJobLog.job_log_id.in_([job_log.job_log_id])))
|
||||
|
||||
@classmethod
|
||||
async def clear_job_log_dao(cls, db: AsyncSession):
|
||||
async def clear_job_log_dao(cls, db: AsyncSession) -> None:
|
||||
"""
|
||||
清除定时任务日志数据库操作
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from datetime import datetime, time
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import asc, delete, desc, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.log_do import SysLogininfor, SysOperLog
|
||||
from module_admin.entity.vo.log_vo import LogininforModel, LoginLogPageQueryModel, OperLogModel, OperLogPageQueryModel
|
||||
from utils.common_util import SnakeCaseUtil
|
||||
from utils.page_util import PageUtil
|
||||
from utils.time_format_util import TimeFormatUtil
|
||||
|
||||
|
||||
class OperationLogDao:
|
||||
@@ -13,7 +18,9 @@ class OperationLogDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_operation_log_list(cls, db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False):
|
||||
async def get_operation_log_list(
|
||||
cls, db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取操作日志列表信息
|
||||
|
||||
@@ -38,8 +45,8 @@ class OperationLogDao:
|
||||
SysOperLog.business_type == query_object.business_type if query_object.business_type else True,
|
||||
SysOperLog.status == query_object.status if query_object.status else True,
|
||||
SysOperLog.oper_time.between(
|
||||
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
||||
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
else True,
|
||||
@@ -47,12 +54,14 @@ class OperationLogDao:
|
||||
.distinct()
|
||||
.order_by(order_by_column)
|
||||
)
|
||||
operation_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
operation_log_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return operation_log_list
|
||||
|
||||
@classmethod
|
||||
async def add_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel):
|
||||
async def add_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel) -> SysOperLog:
|
||||
"""
|
||||
新增操作日志数据库操作
|
||||
|
||||
@@ -67,7 +76,7 @@ class OperationLogDao:
|
||||
return db_operation_log
|
||||
|
||||
@classmethod
|
||||
async def delete_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel):
|
||||
async def delete_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel) -> None:
|
||||
"""
|
||||
删除操作日志数据库操作
|
||||
|
||||
@@ -78,7 +87,7 @@ class OperationLogDao:
|
||||
await db.execute(delete(SysOperLog).where(SysOperLog.oper_id.in_([operation_log.oper_id])))
|
||||
|
||||
@classmethod
|
||||
async def clear_operation_log_dao(cls, db: AsyncSession):
|
||||
async def clear_operation_log_dao(cls, db: AsyncSession) -> None:
|
||||
"""
|
||||
清除操作日志数据库操作
|
||||
|
||||
@@ -94,7 +103,9 @@ class LoginLogDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_login_log_list(cls, db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False):
|
||||
async def get_login_log_list(
|
||||
cls, db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取登录日志列表信息
|
||||
|
||||
@@ -120,8 +131,8 @@ class LoginLogDao:
|
||||
SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True,
|
||||
SysLogininfor.status == query_object.status if query_object.status else True,
|
||||
SysLogininfor.login_time.between(
|
||||
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
||||
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
else True,
|
||||
@@ -129,12 +140,14 @@ class LoginLogDao:
|
||||
.distinct()
|
||||
.order_by(order_by_column)
|
||||
)
|
||||
login_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
login_log_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return login_log_list
|
||||
|
||||
@classmethod
|
||||
async def add_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel):
|
||||
async def add_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel) -> SysLogininfor:
|
||||
"""
|
||||
新增登录日志数据库操作
|
||||
|
||||
@@ -149,7 +162,7 @@ class LoginLogDao:
|
||||
return db_login_log
|
||||
|
||||
@classmethod
|
||||
async def delete_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel):
|
||||
async def delete_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel) -> None:
|
||||
"""
|
||||
删除登录日志数据库操作
|
||||
|
||||
@@ -160,7 +173,7 @@ class LoginLogDao:
|
||||
await db.execute(delete(SysLogininfor).where(SysLogininfor.info_id.in_([login_log.info_id])))
|
||||
|
||||
@classmethod
|
||||
async def clear_login_log_dao(cls, db: AsyncSession):
|
||||
async def clear_login_log_dao(cls, db: AsyncSession) -> None:
|
||||
"""
|
||||
清除登录日志数据库操作
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from sqlalchemy import and_, select
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import Row, and_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.user_do import SysUser
|
||||
|
||||
|
||||
async def login_by_account(db: AsyncSession, user_name: str):
|
||||
async def login_by_account(db: AsyncSession, user_name: str) -> Union[Row[tuple[SysUser, SysDept]], None]:
|
||||
"""
|
||||
根据用户名查询用户信息
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from collections.abc import Sequence
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import and_, delete, func, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from module_admin.entity.do.menu_do import SysMenu
|
||||
from module_admin.entity.do.role_do import SysRole, SysRoleMenu
|
||||
from module_admin.entity.do.user_do import SysUser, SysUserRole
|
||||
@@ -12,7 +16,7 @@ class MenuDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_menu_detail_by_id(cls, db: AsyncSession, menu_id: int):
|
||||
async def get_menu_detail_by_id(cls, db: AsyncSession, menu_id: int) -> Union[SysMenu, None]:
|
||||
"""
|
||||
根据菜单id获取菜单详细信息
|
||||
|
||||
@@ -25,7 +29,7 @@ class MenuDao:
|
||||
return menu_info
|
||||
|
||||
@classmethod
|
||||
async def get_menu_detail_by_info(cls, db: AsyncSession, menu: MenuModel):
|
||||
async def get_menu_detail_by_info(cls, db: AsyncSession, menu: MenuModel) -> Union[SysMenu, None]:
|
||||
"""
|
||||
根据菜单参数获取菜单信息
|
||||
|
||||
@@ -50,7 +54,7 @@ class MenuDao:
|
||||
return menu_info
|
||||
|
||||
@classmethod
|
||||
async def get_menu_list_for_tree(cls, db: AsyncSession, user_id: int, role: list):
|
||||
async def get_menu_list_for_tree(cls, db: AsyncSession, user_id: int, role: list) -> Sequence[SysMenu]:
|
||||
"""
|
||||
根据角色信息获取所有在用菜单列表信息
|
||||
|
||||
@@ -94,7 +98,9 @@ class MenuDao:
|
||||
return menu_query_all
|
||||
|
||||
@classmethod
|
||||
async def get_menu_list(cls, db: AsyncSession, page_object: MenuQueryModel, user_id: int, role: list):
|
||||
async def get_menu_list(
|
||||
cls, db: AsyncSession, page_object: MenuQueryModel, user_id: int, role: list
|
||||
) -> Sequence[SysMenu]:
|
||||
"""
|
||||
根据查询参数获取菜单列表信息
|
||||
|
||||
@@ -156,7 +162,7 @@ class MenuDao:
|
||||
return menu_query_all
|
||||
|
||||
@classmethod
|
||||
async def add_menu_dao(cls, db: AsyncSession, menu: MenuModel):
|
||||
async def add_menu_dao(cls, db: AsyncSession, menu: MenuModel) -> SysMenu:
|
||||
"""
|
||||
新增菜单数据库操作
|
||||
|
||||
@@ -171,7 +177,7 @@ class MenuDao:
|
||||
return db_menu
|
||||
|
||||
@classmethod
|
||||
async def edit_menu_dao(cls, db: AsyncSession, menu: dict):
|
||||
async def edit_menu_dao(cls, db: AsyncSession, menu: dict) -> None:
|
||||
"""
|
||||
编辑菜单数据库操作
|
||||
|
||||
@@ -182,7 +188,7 @@ class MenuDao:
|
||||
await db.execute(update(SysMenu), [menu])
|
||||
|
||||
@classmethod
|
||||
async def delete_menu_dao(cls, db: AsyncSession, menu: MenuModel):
|
||||
async def delete_menu_dao(cls, db: AsyncSession, menu: MenuModel) -> None:
|
||||
"""
|
||||
删除菜单数据库操作
|
||||
|
||||
@@ -193,7 +199,7 @@ class MenuDao:
|
||||
await db.execute(delete(SysMenu).where(SysMenu.menu_id.in_([menu.menu_id])))
|
||||
|
||||
@classmethod
|
||||
async def has_child_by_menu_id_dao(cls, db: AsyncSession, menu_id: int):
|
||||
async def has_child_by_menu_id_dao(cls, db: AsyncSession, menu_id: int) -> Union[int, None]:
|
||||
"""
|
||||
根据菜单id查询菜单关联子菜单的数量
|
||||
|
||||
@@ -208,7 +214,7 @@ class MenuDao:
|
||||
return menu_count
|
||||
|
||||
@classmethod
|
||||
async def check_menu_exist_role_dao(cls, db: AsyncSession, menu_id: int):
|
||||
async def check_menu_exist_role_dao(cls, db: AsyncSession, menu_id: int) -> Union[int, None]:
|
||||
"""
|
||||
根据菜单id查询菜单关联角色数量
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime, time
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import delete, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.notice_do import SysNotice
|
||||
from module_admin.entity.vo.notice_vo import NoticeModel, NoticePageQueryModel
|
||||
from utils.page_util import PageUtil
|
||||
@@ -12,7 +16,7 @@ class NoticeDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_notice_detail_by_id(cls, db: AsyncSession, notice_id: int):
|
||||
async def get_notice_detail_by_id(cls, db: AsyncSession, notice_id: int) -> Union[SysNotice, None]:
|
||||
"""
|
||||
根据通知公告id获取通知公告详细信息
|
||||
|
||||
@@ -25,7 +29,7 @@ class NoticeDao:
|
||||
return notice_info
|
||||
|
||||
@classmethod
|
||||
async def get_notice_detail_by_info(cls, db: AsyncSession, notice: NoticeModel):
|
||||
async def get_notice_detail_by_info(cls, db: AsyncSession, notice: NoticeModel) -> Union[SysNotice, None]:
|
||||
"""
|
||||
根据通知公告参数获取通知公告信息
|
||||
|
||||
@@ -50,7 +54,9 @@ class NoticeDao:
|
||||
return notice_info
|
||||
|
||||
@classmethod
|
||||
async def get_notice_list(cls, db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = False):
|
||||
async def get_notice_list(
|
||||
cls, db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取通知公告列表信息
|
||||
|
||||
@@ -75,12 +81,14 @@ class NoticeDao:
|
||||
.order_by(SysNotice.notice_id)
|
||||
.distinct()
|
||||
)
|
||||
notice_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
notice_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return notice_list
|
||||
|
||||
@classmethod
|
||||
async def add_notice_dao(cls, db: AsyncSession, notice: NoticeModel):
|
||||
async def add_notice_dao(cls, db: AsyncSession, notice: NoticeModel) -> SysNotice:
|
||||
"""
|
||||
新增通知公告数据库操作
|
||||
|
||||
@@ -95,7 +103,7 @@ class NoticeDao:
|
||||
return db_notice
|
||||
|
||||
@classmethod
|
||||
async def edit_notice_dao(cls, db: AsyncSession, notice: dict):
|
||||
async def edit_notice_dao(cls, db: AsyncSession, notice: dict) -> None:
|
||||
"""
|
||||
编辑通知公告数据库操作
|
||||
|
||||
@@ -106,7 +114,7 @@ class NoticeDao:
|
||||
await db.execute(update(SysNotice), [notice])
|
||||
|
||||
@classmethod
|
||||
async def delete_notice_dao(cls, db: AsyncSession, notice: NoticeModel):
|
||||
async def delete_notice_dao(cls, db: AsyncSession, notice: NoticeModel) -> None:
|
||||
"""
|
||||
删除通知公告数据库操作
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import delete, func, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.post_do import SysPost
|
||||
from module_admin.entity.do.user_do import SysUserPost
|
||||
from module_admin.entity.vo.post_vo import PostModel, PostPageQueryModel
|
||||
@@ -12,7 +16,7 @@ class PostDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_post_by_id(cls, db: AsyncSession, post_id: int):
|
||||
async def get_post_by_id(cls, db: AsyncSession, post_id: int) -> Union[SysPost, None]:
|
||||
"""
|
||||
根据岗位id获取在用岗位详细信息
|
||||
|
||||
@@ -29,7 +33,7 @@ class PostDao:
|
||||
return post_info
|
||||
|
||||
@classmethod
|
||||
async def get_post_detail_by_id(cls, db: AsyncSession, post_id: int):
|
||||
async def get_post_detail_by_id(cls, db: AsyncSession, post_id: int) -> Union[SysPost, None]:
|
||||
"""
|
||||
根据岗位id获取岗位详细信息
|
||||
|
||||
@@ -42,7 +46,7 @@ class PostDao:
|
||||
return post_info
|
||||
|
||||
@classmethod
|
||||
async def get_post_detail_by_info(cls, db: AsyncSession, post: PostModel):
|
||||
async def get_post_detail_by_info(cls, db: AsyncSession, post: PostModel) -> Union[SysPost, None]:
|
||||
"""
|
||||
根据岗位参数获取岗位信息
|
||||
|
||||
@@ -67,7 +71,9 @@ class PostDao:
|
||||
return post_info
|
||||
|
||||
@classmethod
|
||||
async def get_post_list(cls, db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False):
|
||||
async def get_post_list(
|
||||
cls, db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取岗位列表信息
|
||||
|
||||
@@ -86,12 +92,14 @@ class PostDao:
|
||||
.order_by(SysPost.post_sort)
|
||||
.distinct()
|
||||
)
|
||||
post_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
post_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return post_list
|
||||
|
||||
@classmethod
|
||||
async def add_post_dao(cls, db: AsyncSession, post: PostModel):
|
||||
async def add_post_dao(cls, db: AsyncSession, post: PostModel) -> SysPost:
|
||||
"""
|
||||
新增岗位数据库操作
|
||||
|
||||
@@ -106,7 +114,7 @@ class PostDao:
|
||||
return db_post
|
||||
|
||||
@classmethod
|
||||
async def edit_post_dao(cls, db: AsyncSession, post: dict):
|
||||
async def edit_post_dao(cls, db: AsyncSession, post: dict) -> None:
|
||||
"""
|
||||
编辑岗位数据库操作
|
||||
|
||||
@@ -117,7 +125,7 @@ class PostDao:
|
||||
await db.execute(update(SysPost), [post])
|
||||
|
||||
@classmethod
|
||||
async def delete_post_dao(cls, db: AsyncSession, post: PostModel):
|
||||
async def delete_post_dao(cls, db: AsyncSession, post: PostModel) -> None:
|
||||
"""
|
||||
删除岗位数据库操作
|
||||
|
||||
@@ -128,7 +136,7 @@ class PostDao:
|
||||
await db.execute(delete(SysPost).where(SysPost.post_id.in_([post.post_id])))
|
||||
|
||||
@classmethod
|
||||
async def count_user_post_dao(cls, db: AsyncSession, post_id: int):
|
||||
async def count_user_post_dao(cls, db: AsyncSession, post_id: int) -> Union[int, None]:
|
||||
"""
|
||||
根据岗位id查询岗位关联的用户数量
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime, time
|
||||
from sqlalchemy import and_, delete, desc, func, or_, select, update # noqa: F401
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import ColumnElement, and_, delete, desc, func, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.menu_do import SysMenu
|
||||
from module_admin.entity.do.role_do import SysRole, SysRoleMenu, SysRoleDept
|
||||
from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu
|
||||
from module_admin.entity.do.user_do import SysUser, SysUserRole
|
||||
from module_admin.entity.vo.role_vo import RoleDeptModel, RoleMenuModel, RoleModel, RolePageQueryModel
|
||||
from utils.page_util import PageUtil
|
||||
@@ -15,7 +20,7 @@ class RoleDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_role_by_name(cls, db: AsyncSession, role_name: str):
|
||||
async def get_role_by_name(cls, db: AsyncSession, role_name: str) -> Union[SysRole, None]:
|
||||
"""
|
||||
根据角色名获取在用角色信息
|
||||
|
||||
@@ -39,7 +44,7 @@ class RoleDao:
|
||||
return query_role_info
|
||||
|
||||
@classmethod
|
||||
async def get_role_by_info(cls, db: AsyncSession, role: RoleModel):
|
||||
async def get_role_by_info(cls, db: AsyncSession, role: RoleModel) -> Union[SysRole, None]:
|
||||
"""
|
||||
根据角色参数获取角色信息
|
||||
|
||||
@@ -67,7 +72,7 @@ class RoleDao:
|
||||
return query_role_info
|
||||
|
||||
@classmethod
|
||||
async def get_role_by_id(cls, db: AsyncSession, role_id: int):
|
||||
async def get_role_by_id(cls, db: AsyncSession, role_id: int) -> Union[SysRole, None]:
|
||||
"""
|
||||
根据角色id获取在用角色信息
|
||||
|
||||
@@ -88,7 +93,7 @@ class RoleDao:
|
||||
return role_info
|
||||
|
||||
@classmethod
|
||||
async def get_role_detail_by_id(cls, db: AsyncSession, role_id: int):
|
||||
async def get_role_detail_by_id(cls, db: AsyncSession, role_id: int) -> Union[SysRole, None]:
|
||||
"""
|
||||
根据role_id获取角色详细信息
|
||||
|
||||
@@ -105,7 +110,7 @@ class RoleDao:
|
||||
return query_role_info
|
||||
|
||||
@classmethod
|
||||
async def get_role_select_option_dao(cls, db: AsyncSession):
|
||||
async def get_role_select_option_dao(cls, db: AsyncSession) -> Sequence[SysRole]:
|
||||
"""
|
||||
获取编辑页面对应的在用角色列表信息
|
||||
|
||||
@@ -126,8 +131,8 @@ class RoleDao:
|
||||
|
||||
@classmethod
|
||||
async def get_role_list(
|
||||
cls, db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False
|
||||
):
|
||||
cls, db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: ColumnElement, is_page: bool = False
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据查询参数获取角色列表信息
|
||||
|
||||
@@ -154,17 +159,19 @@ class RoleDao:
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
else True,
|
||||
eval(data_scope_sql),
|
||||
data_scope_sql,
|
||||
)
|
||||
.order_by(SysRole.role_sort)
|
||||
.distinct()
|
||||
)
|
||||
role_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
role_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return role_list
|
||||
|
||||
@classmethod
|
||||
async def add_role_dao(cls, db: AsyncSession, role: RoleModel):
|
||||
async def add_role_dao(cls, db: AsyncSession, role: RoleModel) -> SysRole:
|
||||
"""
|
||||
新增角色数据库操作
|
||||
|
||||
@@ -179,7 +186,7 @@ class RoleDao:
|
||||
return db_role
|
||||
|
||||
@classmethod
|
||||
async def edit_role_dao(cls, db: AsyncSession, role: dict):
|
||||
async def edit_role_dao(cls, db: AsyncSession, role: dict) -> None:
|
||||
"""
|
||||
编辑角色数据库操作
|
||||
|
||||
@@ -190,7 +197,7 @@ class RoleDao:
|
||||
await db.execute(update(SysRole), [role])
|
||||
|
||||
@classmethod
|
||||
async def delete_role_dao(cls, db: AsyncSession, role: RoleModel):
|
||||
async def delete_role_dao(cls, db: AsyncSession, role: RoleModel) -> None:
|
||||
"""
|
||||
删除角色数据库操作
|
||||
|
||||
@@ -205,7 +212,7 @@ class RoleDao:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_role_menu_dao(cls, db: AsyncSession, role: RoleModel):
|
||||
async def get_role_menu_dao(cls, db: AsyncSession, role: RoleModel) -> Sequence[SysMenu]:
|
||||
"""
|
||||
根据角色id获取角色菜单关联列表信息
|
||||
|
||||
@@ -241,7 +248,7 @@ class RoleDao:
|
||||
return role_menu_query_all
|
||||
|
||||
@classmethod
|
||||
async def add_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel):
|
||||
async def add_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel) -> None:
|
||||
"""
|
||||
新增角色菜单关联信息数据库操作
|
||||
|
||||
@@ -253,7 +260,7 @@ class RoleDao:
|
||||
db.add(db_role_menu)
|
||||
|
||||
@classmethod
|
||||
async def delete_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel):
|
||||
async def delete_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel) -> None:
|
||||
"""
|
||||
删除角色菜单关联信息数据库操作
|
||||
|
||||
@@ -264,7 +271,7 @@ class RoleDao:
|
||||
await db.execute(delete(SysRoleMenu).where(SysRoleMenu.role_id.in_([role_menu.role_id])))
|
||||
|
||||
@classmethod
|
||||
async def get_role_dept_dao(cls, db: AsyncSession, role: RoleModel):
|
||||
async def get_role_dept_dao(cls, db: AsyncSession, role: RoleModel) -> Sequence[SysDept]:
|
||||
"""
|
||||
根据角色id获取角色部门关联列表信息
|
||||
|
||||
@@ -300,7 +307,7 @@ class RoleDao:
|
||||
return role_dept_query_all
|
||||
|
||||
@classmethod
|
||||
async def add_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel):
|
||||
async def add_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel) -> None:
|
||||
"""
|
||||
新增角色部门关联信息数据库操作
|
||||
|
||||
@@ -312,7 +319,7 @@ class RoleDao:
|
||||
db.add(db_role_dept)
|
||||
|
||||
@classmethod
|
||||
async def delete_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel):
|
||||
async def delete_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel) -> None:
|
||||
"""
|
||||
删除角色部门关联信息数据库操作
|
||||
|
||||
@@ -323,7 +330,7 @@ class RoleDao:
|
||||
await db.execute(delete(SysRoleDept).where(SysRoleDept.role_id.in_([role_dept.role_id])))
|
||||
|
||||
@classmethod
|
||||
async def count_user_role_dao(cls, db: AsyncSession, role_id: int):
|
||||
async def count_user_role_dao(cls, db: AsyncSession, role_id: int) -> Union[int, None]:
|
||||
"""
|
||||
根据角色id查询角色关联用户数量
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime, time
|
||||
from sqlalchemy import and_, delete, desc, func, or_, select, update
|
||||
from typing import Any, Union
|
||||
|
||||
from sqlalchemy import ColumnElement, and_, delete, desc, func, or_, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.vo import PageModel
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.menu_do import SysMenu
|
||||
from module_admin.entity.do.post_do import SysPost
|
||||
from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu # noqa: F401
|
||||
from module_admin.entity.do.role_do import SysRole, SysRoleMenu
|
||||
from module_admin.entity.do.user_do import SysUser, SysUserPost, SysUserRole
|
||||
from module_admin.entity.vo.user_vo import (
|
||||
UserModel,
|
||||
@@ -23,7 +28,7 @@ class UserDao:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_user_by_name(cls, db: AsyncSession, user_name: str):
|
||||
async def get_user_by_name(cls, db: AsyncSession, user_name: str) -> Union[SysUser, None]:
|
||||
"""
|
||||
根据用户名获取用户信息
|
||||
|
||||
@@ -47,7 +52,7 @@ class UserDao:
|
||||
return query_user_info
|
||||
|
||||
@classmethod
|
||||
async def get_user_by_info(cls, db: AsyncSession, user: UserModel):
|
||||
async def get_user_by_info(cls, db: AsyncSession, user: UserModel) -> Union[SysUser, None]:
|
||||
"""
|
||||
根据用户参数获取用户信息
|
||||
|
||||
@@ -76,7 +81,7 @@ class UserDao:
|
||||
return query_user_info
|
||||
|
||||
@classmethod
|
||||
async def get_user_by_id(cls, db: AsyncSession, user_id: int):
|
||||
async def get_user_by_id(cls, db: AsyncSession, user_id: int) -> dict[str, Any]:
|
||||
"""
|
||||
根据user_id获取用户信息
|
||||
|
||||
@@ -172,18 +177,18 @@ class UserDao:
|
||||
.all()
|
||||
)
|
||||
|
||||
results = dict(
|
||||
user_basic_info=query_user_basic_info,
|
||||
user_dept_info=query_user_dept_info,
|
||||
user_role_info=query_user_role_info,
|
||||
user_post_info=query_user_post_info,
|
||||
user_menu_info=query_user_menu_info,
|
||||
)
|
||||
results = {
|
||||
'user_basic_info': query_user_basic_info,
|
||||
'user_dept_info': query_user_dept_info,
|
||||
'user_role_info': query_user_role_info,
|
||||
'user_post_info': query_user_post_info,
|
||||
'user_menu_info': query_user_menu_info,
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
async def get_user_detail_by_id(cls, db: AsyncSession, user_id: int):
|
||||
async def get_user_detail_by_id(cls, db: AsyncSession, user_id: int) -> dict[str, Any]:
|
||||
"""
|
||||
根据user_id获取用户详细信息
|
||||
|
||||
@@ -263,20 +268,20 @@ class UserDao:
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
results = dict(
|
||||
user_basic_info=query_user_basic_info,
|
||||
user_dept_info=query_user_dept_info,
|
||||
user_role_info=query_user_role_info,
|
||||
user_post_info=query_user_post_info,
|
||||
user_menu_info=query_user_menu_info,
|
||||
)
|
||||
results = {
|
||||
'user_basic_info': query_user_basic_info,
|
||||
'user_dept_info': query_user_dept_info,
|
||||
'user_role_info': query_user_role_info,
|
||||
'user_post_info': query_user_post_info,
|
||||
'user_menu_info': query_user_menu_info,
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
async def get_user_list(
|
||||
cls, db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False
|
||||
):
|
||||
cls, db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: ColumnElement, is_page: bool = False
|
||||
) -> Union[PageModel, list[list[dict[str, Any]]]]:
|
||||
"""
|
||||
根据查询参数获取用户列表信息
|
||||
|
||||
@@ -311,7 +316,7 @@ class UserDao:
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
else True,
|
||||
eval(data_scope_sql),
|
||||
data_scope_sql,
|
||||
)
|
||||
.join(
|
||||
SysDept,
|
||||
@@ -321,12 +326,14 @@ class UserDao:
|
||||
.order_by(SysUser.user_id)
|
||||
.distinct()
|
||||
)
|
||||
user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
user_list: Union[PageModel, list[list[dict[str, Any]]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return user_list
|
||||
|
||||
@classmethod
|
||||
async def add_user_dao(cls, db: AsyncSession, user: UserModel):
|
||||
async def add_user_dao(cls, db: AsyncSession, user: UserModel) -> SysUser:
|
||||
"""
|
||||
新增用户数据库操作
|
||||
|
||||
@@ -341,7 +348,7 @@ class UserDao:
|
||||
return db_user
|
||||
|
||||
@classmethod
|
||||
async def edit_user_dao(cls, db: AsyncSession, user: dict):
|
||||
async def edit_user_dao(cls, db: AsyncSession, user: dict) -> None:
|
||||
"""
|
||||
编辑用户数据库操作
|
||||
|
||||
@@ -352,7 +359,7 @@ class UserDao:
|
||||
await db.execute(update(SysUser), [user])
|
||||
|
||||
@classmethod
|
||||
async def delete_user_dao(cls, db: AsyncSession, user: UserModel):
|
||||
async def delete_user_dao(cls, db: AsyncSession, user: UserModel) -> None:
|
||||
"""
|
||||
删除用户数据库操作
|
||||
|
||||
@@ -367,7 +374,9 @@ class UserDao:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_user_role_allocated_list_by_user_id(cls, db: AsyncSession, query_object: UserRoleQueryModel):
|
||||
async def get_user_role_allocated_list_by_user_id(
|
||||
cls, db: AsyncSession, query_object: UserRoleQueryModel
|
||||
) -> Sequence[SysRole]:
|
||||
"""
|
||||
根据用户id获取用户已分配的角色列表信息数据库操作
|
||||
|
||||
@@ -399,8 +408,12 @@ class UserDao:
|
||||
|
||||
@classmethod
|
||||
async def get_user_role_allocated_list_by_role_id(
|
||||
cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False
|
||||
):
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: UserRolePageQueryModel,
|
||||
data_scope_sql: ColumnElement,
|
||||
is_page: bool = False,
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据角色id获取已分配的用户列表信息
|
||||
|
||||
@@ -420,18 +433,24 @@ class UserDao:
|
||||
SysUser.user_name == query_object.user_name if query_object.user_name else True,
|
||||
SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True,
|
||||
SysRole.role_id == query_object.role_id,
|
||||
eval(data_scope_sql),
|
||||
data_scope_sql,
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
allocated_user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
allocated_user_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return allocated_user_list
|
||||
|
||||
@classmethod
|
||||
async def get_user_role_unallocated_list_by_role_id(
|
||||
cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False
|
||||
):
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
query_object: UserRolePageQueryModel,
|
||||
data_scope_sql: ColumnElement,
|
||||
is_page: bool = False,
|
||||
) -> Union[PageModel, list[dict[str, Any]]]:
|
||||
"""
|
||||
根据角色id获取未分配的用户列表信息
|
||||
|
||||
@@ -459,18 +478,18 @@ class UserDao:
|
||||
and_(SysUserRole.user_id == SysUser.user_id, SysUserRole.role_id == query_object.role_id),
|
||||
)
|
||||
),
|
||||
eval(data_scope_sql),
|
||||
data_scope_sql,
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
unallocated_user_list = await PageUtil.paginate(
|
||||
unallocated_user_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate(
|
||||
db, query, query_object.page_num, query_object.page_size, is_page
|
||||
)
|
||||
|
||||
return unallocated_user_list
|
||||
|
||||
@classmethod
|
||||
async def add_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel):
|
||||
async def add_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel) -> None:
|
||||
"""
|
||||
新增用户角色关联信息数据库操作
|
||||
|
||||
@@ -482,7 +501,7 @@ class UserDao:
|
||||
db.add(db_user_role)
|
||||
|
||||
@classmethod
|
||||
async def delete_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel):
|
||||
async def delete_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel) -> None:
|
||||
"""
|
||||
删除用户角色关联信息数据库操作
|
||||
|
||||
@@ -493,7 +512,7 @@ class UserDao:
|
||||
await db.execute(delete(SysUserRole).where(SysUserRole.user_id.in_([user_role.user_id])))
|
||||
|
||||
@classmethod
|
||||
async def delete_user_role_by_user_and_role_dao(cls, db: AsyncSession, user_role: UserRoleModel):
|
||||
async def delete_user_role_by_user_and_role_dao(cls, db: AsyncSession, user_role: UserRoleModel) -> None:
|
||||
"""
|
||||
根据用户id及角色id删除用户角色关联信息数据库操作
|
||||
|
||||
@@ -509,7 +528,7 @@ class UserDao:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_user_role_detail(cls, db: AsyncSession, user_role: UserRoleModel):
|
||||
async def get_user_role_detail(cls, db: AsyncSession, user_role: UserRoleModel) -> Union[SysUserRole, None]:
|
||||
"""
|
||||
根据用户角色关联获取用户角色关联详细信息
|
||||
|
||||
@@ -532,7 +551,7 @@ class UserDao:
|
||||
return user_role_info
|
||||
|
||||
@classmethod
|
||||
async def add_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel):
|
||||
async def add_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel) -> None:
|
||||
"""
|
||||
新增用户岗位关联信息数据库操作
|
||||
|
||||
@@ -544,7 +563,7 @@ class UserDao:
|
||||
db.add(db_user_post)
|
||||
|
||||
@classmethod
|
||||
async def delete_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel):
|
||||
async def delete_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel) -> None:
|
||||
"""
|
||||
删除用户岗位关联信息数据库操作
|
||||
|
||||
@@ -555,7 +574,7 @@ class UserDao:
|
||||
await db.execute(delete(SysUserPost).where(SysUserPost.user_id.in_([user_post.user_id])))
|
||||
|
||||
@classmethod
|
||||
async def get_user_dept_info(cls, db: AsyncSession, dept_id: int):
|
||||
async def get_user_dept_info(cls, db: AsyncSession, dept_id: int) -> Union[SysDept, None]:
|
||||
dept_basic_info = (
|
||||
(
|
||||
await db.execute(
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, Column, DateTime, Integer, String
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysConfig(Base):
|
||||
@@ -9,14 +13,20 @@ class SysConfig(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_config'
|
||||
__table_args__ = {'comment': '参数配置表'}
|
||||
|
||||
config_id = Column(Integer, primary_key=True, autoincrement=True, comment='参数主键')
|
||||
config_name = Column(String(100), nullable=True, default='', comment='参数名称')
|
||||
config_key = Column(String(100), nullable=True, default='', comment='参数键名')
|
||||
config_value = Column(String(500), nullable=True, default='', comment='参数键值')
|
||||
config_type = Column(String(1), nullable=True, default='N', comment='系统内置(Y是 N否)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
config_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='参数主键')
|
||||
config_name = Column(String(100), nullable=True, server_default="''", comment='参数名称')
|
||||
config_key = Column(String(100), nullable=True, server_default="''", comment='参数键名')
|
||||
config_value = Column(String(500), nullable=True, server_default="''", comment='参数键值')
|
||||
config_type = Column(CHAR(1), nullable=True, server_default='N', comment='系统内置(Y是 N否)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysDept(Base):
|
||||
@@ -9,18 +13,34 @@ class SysDept(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_dept'
|
||||
__table_args__ = {'comment': '部门表'}
|
||||
|
||||
dept_id = Column(Integer, primary_key=True, autoincrement=True, comment='部门id')
|
||||
parent_id = Column(Integer, default=0, comment='父部门id')
|
||||
ancestors = Column(String(50), nullable=True, default='', comment='祖级列表')
|
||||
dept_name = Column(String(30), nullable=True, default='', comment='部门名称')
|
||||
order_num = Column(Integer, default=0, comment='显示顺序')
|
||||
leader = Column(String(20), nullable=True, default=None, comment='负责人')
|
||||
phone = Column(String(11), nullable=True, default=None, comment='联系电话')
|
||||
email = Column(String(50), nullable=True, default=None, comment='邮箱')
|
||||
status = Column(String(1), nullable=True, default='0', comment='部门状态(0正常 1停用)')
|
||||
del_flag = Column(String(1), nullable=True, default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
dept_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='部门id')
|
||||
parent_id = Column(BigInteger, server_default='0', comment='父部门id')
|
||||
ancestors = Column(String(50), nullable=True, server_default="''", comment='祖级列表')
|
||||
dept_name = Column(String(30), nullable=True, server_default="''", comment='部门名称')
|
||||
order_num = Column(Integer, server_default='0', comment='显示顺序')
|
||||
leader = Column(
|
||||
String(20),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='负责人',
|
||||
)
|
||||
phone = Column(
|
||||
String(11),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='联系电话',
|
||||
)
|
||||
email = Column(
|
||||
String(50),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='邮箱',
|
||||
)
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='部门状态(0正常 1停用)')
|
||||
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint
|
||||
|
||||
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysDictType(Base):
|
||||
@@ -9,18 +13,22 @@ class SysDictType(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_dict_type'
|
||||
__table_args__ = {'comment': '字典类型表'}
|
||||
|
||||
dict_id = Column(Integer, primary_key=True, autoincrement=True, comment='字典主键')
|
||||
dict_name = Column(String(100), nullable=True, default='', comment='字典名称')
|
||||
dict_type = Column(String(100), nullable=True, default='', comment='字典类型')
|
||||
status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
dict_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典主键')
|
||||
dict_name = Column(String(100), nullable=True, server_default="''", comment='字典名称')
|
||||
dict_type = Column(String(100), unique=True, nullable=True, server_default="''", comment='字典类型')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
|
||||
__table_args__ = (UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'),)
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
|
||||
class SysDictData(Base):
|
||||
@@ -29,18 +37,34 @@ class SysDictData(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_dict_data'
|
||||
__table_args__ = {'comment': '字典数据表'}
|
||||
|
||||
dict_code = Column(Integer, primary_key=True, autoincrement=True, comment='字典编码')
|
||||
dict_sort = Column(Integer, nullable=True, default=0, comment='字典排序')
|
||||
dict_label = Column(String(100), nullable=True, default='', comment='字典标签')
|
||||
dict_value = Column(String(100), nullable=True, default='', comment='字典键值')
|
||||
dict_type = Column(String(100), nullable=True, default='', comment='字典类型')
|
||||
css_class = Column(String(100), nullable=True, default=None, comment='样式属性(其他样式扩展)')
|
||||
list_class = Column(String(100), nullable=True, default=None, comment='表格回显样式')
|
||||
is_default = Column(String(1), nullable=True, default='N', comment='是否默认(Y是 N否)')
|
||||
status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
dict_code = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典编码')
|
||||
dict_sort = Column(Integer, nullable=True, server_default='0', comment='字典排序')
|
||||
dict_label = Column(String(100), nullable=True, server_default="''", comment='字典标签')
|
||||
dict_value = Column(String(100), nullable=True, server_default="''", comment='字典键值')
|
||||
dict_type = Column(String(100), nullable=True, server_default="''", comment='字典类型')
|
||||
css_class = Column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='样式属性(其他样式扩展)',
|
||||
)
|
||||
list_class = Column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='表格回显样式',
|
||||
)
|
||||
is_default = Column(CHAR(1), nullable=True, server_default='N', comment='是否默认(Y是 N否)')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, DOUBLE, BigInteger, Column, DateTime, Float, Index, LargeBinary, String, Unicode
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysJob(Base):
|
||||
@@ -9,28 +13,29 @@ class SysJob(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_job'
|
||||
__table_args__ = {'comment': '定时任务调度表'}
|
||||
|
||||
job_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务ID')
|
||||
job_name = Column(String(64), nullable=True, default='', comment='任务名称')
|
||||
job_group = Column(String(64), nullable=True, default='default', comment='任务组名')
|
||||
job_executor = Column(String(64), nullable=True, default='default', comment='任务执行器')
|
||||
job_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务ID')
|
||||
job_name = Column(String(64), primary_key=True, nullable=False, server_default="''", comment='任务名称')
|
||||
job_group = Column(String(64), primary_key=True, nullable=False, server_default='default', comment='任务组名')
|
||||
job_executor = Column(String(64), nullable=True, server_default='default', comment='任务执行器')
|
||||
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
|
||||
job_args = Column(String(255), nullable=True, default='', comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数')
|
||||
cron_expression = Column(String(255), nullable=True, default='', comment='cron执行表达式')
|
||||
job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数')
|
||||
cron_expression = Column(String(255), nullable=True, server_default="''", comment='cron执行表达式')
|
||||
misfire_policy = Column(
|
||||
String(20),
|
||||
nullable=True,
|
||||
default='3',
|
||||
server_default='3',
|
||||
comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
|
||||
)
|
||||
concurrent = Column(String(1), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)')
|
||||
status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1暂停)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
concurrent = Column(CHAR(1), nullable=True, server_default='1', comment='是否并发执行(0允许 1禁止)')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1暂停)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default='', comment='备注信息')
|
||||
remark = Column(String(500), nullable=True, server_default="''", comment='备注信息')
|
||||
|
||||
|
||||
class SysJobLog(Base):
|
||||
@@ -39,16 +44,35 @@ class SysJobLog(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_job_log'
|
||||
__table_args__ = {'comment': '定时任务调度日志表'}
|
||||
|
||||
job_log_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务日志ID')
|
||||
job_log_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务日志ID')
|
||||
job_name = Column(String(64), nullable=False, comment='任务名称')
|
||||
job_group = Column(String(64), nullable=False, comment='任务组名')
|
||||
job_executor = Column(String(64), nullable=False, comment='任务执行器')
|
||||
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
|
||||
job_args = Column(String(255), nullable=True, default='', comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数')
|
||||
job_trigger = Column(String(255), nullable=True, default='', comment='任务触发器')
|
||||
job_message = Column(String(500), nullable=True, default='', comment='日志信息')
|
||||
status = Column(String(1), nullable=True, default='0', comment='执行状态(0正常 1失败)')
|
||||
exception_info = Column(String(2000), nullable=True, default='', comment='异常信息')
|
||||
job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数')
|
||||
job_trigger = Column(String(255), nullable=True, server_default="''", comment='任务触发器')
|
||||
job_message = Column(String(500), nullable=True, comment='日志信息')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='执行状态(0正常 1失败)')
|
||||
exception_info = Column(String(2000), nullable=True, server_default="''", comment='异常信息')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
|
||||
|
||||
class ApschedulerJobs(Base):
|
||||
"""
|
||||
定时任务调度任务表
|
||||
"""
|
||||
|
||||
__tablename__ = 'apscheduler_jobs'
|
||||
|
||||
id = Column(Unicode(191), primary_key=True, nullable=False)
|
||||
next_run_time = Column(
|
||||
DOUBLE if DataBaseConfig.db_type == 'mysql' else Float(25),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False),
|
||||
)
|
||||
job_state = Column(LargeBinary, nullable=False)
|
||||
|
||||
idx_sys_logininfor_s = Index('ix_apscheduler_jobs_next_run_time', next_run_time)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import BigInteger, Column, DateTime, Index, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Index, Integer, String
|
||||
|
||||
from config.database import Base
|
||||
|
||||
|
||||
@@ -9,15 +11,16 @@ class SysLogininfor(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_logininfor'
|
||||
__table_args__ = {'comment': '系统访问记录'}
|
||||
|
||||
info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID')
|
||||
user_name = Column(String(50), nullable=True, default='', comment='用户账号')
|
||||
ipaddr = Column(String(128), nullable=True, default='', comment='登录IP地址')
|
||||
login_location = Column(String(255), nullable=True, default='', comment='登录地点')
|
||||
browser = Column(String(50), nullable=True, default='', comment='浏览器类型')
|
||||
os = Column(String(50), nullable=True, default='', comment='操作系统')
|
||||
status = Column(String(1), nullable=True, default='0', comment='登录状态(0成功 1失败)')
|
||||
msg = Column(String(255), nullable=True, default='', comment='提示消息')
|
||||
info_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='访问ID')
|
||||
user_name = Column(String(50), nullable=True, server_default="''", comment='用户账号')
|
||||
ipaddr = Column(String(128), nullable=True, server_default="''", comment='登录IP地址')
|
||||
login_location = Column(String(255), nullable=True, server_default="''", comment='登录地点')
|
||||
browser = Column(String(50), nullable=True, server_default="''", comment='浏览器类型')
|
||||
os = Column(String(50), nullable=True, server_default="''", comment='操作系统')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='登录状态(0成功 1失败)')
|
||||
msg = Column(String(255), nullable=True, server_default="''", comment='提示消息')
|
||||
login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间')
|
||||
|
||||
idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status)
|
||||
@@ -30,24 +33,27 @@ class SysOperLog(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_oper_log'
|
||||
__table_args__ = {'comment': '操作日志记录'}
|
||||
|
||||
oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键')
|
||||
title = Column(String(50), nullable=True, default='', comment='模块标题')
|
||||
business_type = Column(Integer, default=0, comment='业务类型(0其它 1新增 2修改 3删除)')
|
||||
method = Column(String(100), nullable=True, default='', comment='方法名称')
|
||||
request_method = Column(String(10), nullable=True, default='', comment='请求方式')
|
||||
operator_type = Column(Integer, default=0, comment='操作类别(0其它 1后台用户 2手机端用户)')
|
||||
oper_name = Column(String(50), nullable=True, default='', comment='操作人员')
|
||||
dept_name = Column(String(50), nullable=True, default='', comment='部门名称')
|
||||
oper_url = Column(String(255), nullable=True, default='', comment='请求URL')
|
||||
oper_ip = Column(String(128), nullable=True, default='', comment='主机地址')
|
||||
oper_location = Column(String(255), nullable=True, default='', comment='操作地点')
|
||||
oper_param = Column(String(2000), nullable=True, default='', comment='请求参数')
|
||||
json_result = Column(String(2000), nullable=True, default='', comment='返回参数')
|
||||
status = Column(Integer, default=0, comment='操作状态(0正常 1异常)')
|
||||
error_msg = Column(String(2000), nullable=True, default='', comment='错误消息')
|
||||
oper_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='日志主键')
|
||||
title = Column(String(50), nullable=True, server_default="''", comment='模块标题')
|
||||
business_type = Column(Integer, nullable=True, server_default='0', comment='业务类型(0其它 1新增 2修改 3删除)')
|
||||
method = Column(String(100), nullable=True, server_default="''", comment='方法名称')
|
||||
request_method = Column(String(10), nullable=True, server_default="''", comment='请求方式')
|
||||
operator_type = Column(
|
||||
Integer, nullable=True, server_default='0', comment='操作类别(0其它 1后台用户 2手机端用户)'
|
||||
)
|
||||
oper_name = Column(String(50), nullable=True, server_default="''", comment='操作人员')
|
||||
dept_name = Column(String(50), nullable=True, server_default="''", comment='部门名称')
|
||||
oper_url = Column(String(255), nullable=True, server_default="''", comment='请求URL')
|
||||
oper_ip = Column(String(128), nullable=True, server_default="''", comment='主机地址')
|
||||
oper_location = Column(String(255), nullable=True, server_default="''", comment='操作地点')
|
||||
oper_param = Column(String(2000), nullable=True, server_default="''", comment='请求参数')
|
||||
json_result = Column(String(2000), nullable=True, server_default="''", comment='返回参数')
|
||||
status = Column(Integer, nullable=True, server_default='0', comment='操作状态(0正常 1异常)')
|
||||
error_msg = Column(String(2000), nullable=True, server_default="''", comment='错误消息')
|
||||
oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间')
|
||||
cost_time = Column(BigInteger, default=0, comment='消耗时间')
|
||||
cost_time = Column(BigInteger, nullable=True, server_default='0', comment='消耗时间')
|
||||
|
||||
idx_sys_oper_log_bt = Index('idx_sys_oper_log_bt', business_type)
|
||||
idx_sys_oper_log_s = Index('idx_sys_oper_log_s', status)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysMenu(Base):
|
||||
@@ -9,24 +13,40 @@ class SysMenu(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_menu'
|
||||
__table_args__ = {'comment': '菜单权限表'}
|
||||
|
||||
menu_id = Column(Integer, primary_key=True, autoincrement=True, comment='菜单ID')
|
||||
menu_name = Column(String(50), nullable=False, default='', comment='菜单名称')
|
||||
parent_id = Column(Integer, default=0, comment='父菜单ID')
|
||||
order_num = Column(Integer, default=0, comment='显示顺序')
|
||||
path = Column(String(200), nullable=True, default='', comment='路由地址')
|
||||
component = Column(String(255), nullable=True, default=None, comment='组件路径')
|
||||
query = Column(String(255), nullable=True, default=None, comment='路由参数')
|
||||
route_name = Column(String(50), nullable=True, default='', comment='路由名称')
|
||||
is_frame = Column(Integer, default=1, comment='是否为外链(0是 1否)')
|
||||
is_cache = Column(Integer, default=0, comment='是否缓存(0缓存 1不缓存)')
|
||||
menu_type = Column(String(1), nullable=True, default='', comment='菜单类型(M目录 C菜单 F按钮)')
|
||||
visible = Column(String(1), nullable=True, default='0', comment='菜单状态(0显示 1隐藏)')
|
||||
status = Column(String(1), nullable=True, default='0', comment='菜单状态(0正常 1停用)')
|
||||
perms = Column(String(100), nullable=True, default=None, comment='权限标识')
|
||||
icon = Column(String(100), nullable=True, default='#', comment='菜单图标')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
menu_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='菜单ID')
|
||||
menu_name = Column(String(50), nullable=False, comment='菜单名称')
|
||||
parent_id = Column(BigInteger, nullable=True, server_default='0', comment='父菜单ID')
|
||||
order_num = Column(Integer, server_default='0', comment='显示顺序')
|
||||
path = Column(String(200), nullable=True, server_default="''", comment='路由地址')
|
||||
component = Column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='组件路径',
|
||||
)
|
||||
query = Column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='路由参数',
|
||||
)
|
||||
route_name = Column(String(50), nullable=True, server_default="''", comment='路由名称')
|
||||
is_frame = Column(Integer, nullable=True, server_default='1', comment='是否为外链(0是 1否)')
|
||||
is_cache = Column(Integer, nullable=True, server_default='0', comment='是否缓存(0缓存 1不缓存)')
|
||||
menu_type = Column(CHAR(1), nullable=True, server_default="''", comment='菜单类型(M目录 C菜单 F按钮)')
|
||||
visible = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0显示 1隐藏)')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0正常 1停用)')
|
||||
perms = Column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='权限标识',
|
||||
)
|
||||
icon = Column(String(100), nullable=True, server_default='#', comment='菜单图标')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default='', comment='备注')
|
||||
remark = Column(String(500), nullable=True, server_default="''", comment='备注')
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, LargeBinary, String
|
||||
|
||||
from sqlalchemy import CHAR, Column, DateTime, Integer, LargeBinary, String
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysNotice(Base):
|
||||
@@ -9,14 +14,25 @@ class SysNotice(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_notice'
|
||||
__table_args__ = {'comment': '通知公告表'}
|
||||
|
||||
notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID')
|
||||
notice_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='公告ID')
|
||||
notice_title = Column(String(50), nullable=False, comment='公告标题')
|
||||
notice_type = Column(String(1), nullable=False, comment='公告类型(1通知 2公告)')
|
||||
notice_content = Column(LargeBinary, comment='公告内容')
|
||||
status = Column(String(1), default='0', comment='公告状态(0正常 1关闭)')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, comment='更新时间', default=datetime.now())
|
||||
remark = Column(String(255), comment='备注')
|
||||
notice_type = Column(CHAR(1), nullable=False, comment='公告类型(1通知 2公告)')
|
||||
notice_content = Column(
|
||||
mysql.LONGBLOB if DataBaseConfig.db_type == 'mysql' else LargeBinary,
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False),
|
||||
comment='公告内容',
|
||||
)
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='公告状态(0正常 1关闭)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now())
|
||||
remark = Column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysPost(Base):
|
||||
@@ -9,14 +13,20 @@ class SysPost(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_post'
|
||||
__table_args__ = {'comment': '岗位信息表'}
|
||||
|
||||
post_id = Column(Integer, primary_key=True, autoincrement=True, comment='岗位ID')
|
||||
post_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='岗位ID')
|
||||
post_code = Column(String(64), nullable=False, comment='岗位编码')
|
||||
post_name = Column(String(50), nullable=False, comment='岗位名称')
|
||||
post_sort = Column(Integer, nullable=False, comment='显示顺序')
|
||||
status = Column(String(1), nullable=False, default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
status = Column(CHAR(1), nullable=False, comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, SmallInteger, String
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysRole(Base):
|
||||
@@ -9,25 +14,42 @@ class SysRole(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_role'
|
||||
__table_args__ = {'comment': '角色信息表'}
|
||||
|
||||
role_id = Column(Integer, primary_key=True, autoincrement=True, comment='角色ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='角色ID')
|
||||
role_name = Column(String(30), nullable=False, comment='角色名称')
|
||||
role_key = Column(String(100), nullable=False, comment='角色权限字符串')
|
||||
role_sort = Column(Integer, nullable=False, comment='显示顺序')
|
||||
data_scope = Column(
|
||||
String(1),
|
||||
default='1',
|
||||
CHAR(1),
|
||||
nullable=True,
|
||||
server_default='1',
|
||||
comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
|
||||
)
|
||||
menu_check_strictly = Column(Integer, default=1, comment='菜单树选择项是否关联显示')
|
||||
dept_check_strictly = Column(Integer, default=1, comment='部门树选择项是否关联显示')
|
||||
status = Column(String(1), nullable=False, default='0', comment='角色状态(0正常 1停用)')
|
||||
del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), default=None, comment='备注')
|
||||
menu_check_strictly = Column(
|
||||
mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger,
|
||||
nullable=True,
|
||||
server_default='1',
|
||||
comment='菜单树选择项是否关联显示',
|
||||
)
|
||||
dept_check_strictly = Column(
|
||||
mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger,
|
||||
nullable=True,
|
||||
server_default='1',
|
||||
comment='部门树选择项是否关联显示',
|
||||
)
|
||||
status = Column(CHAR(1), nullable=False, comment='角色状态(0正常 1停用)')
|
||||
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
|
||||
class SysRoleDept(Base):
|
||||
@@ -36,9 +58,10 @@ class SysRoleDept(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_role_dept'
|
||||
__table_args__ = {'comment': '角色和部门关联表'}
|
||||
|
||||
role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID')
|
||||
dept_id = Column(Integer, primary_key=True, nullable=False, comment='部门ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
|
||||
dept_id = Column(BigInteger, primary_key=True, nullable=False, comment='部门ID')
|
||||
|
||||
|
||||
class SysRoleMenu(Base):
|
||||
@@ -47,6 +70,7 @@ class SysRoleMenu(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_role_menu'
|
||||
__table_args__ = {'comment': '角色和菜单关联表'}
|
||||
|
||||
role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID')
|
||||
menu_id = Column(Integer, primary_key=True, nullable=False, comment='菜单ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
|
||||
menu_id = Column(BigInteger, primary_key=True, nullable=False, comment='菜单ID')
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
|
||||
from sqlalchemy import CHAR, BigInteger, Column, DateTime, String
|
||||
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysUser(Base):
|
||||
@@ -9,26 +13,38 @@ class SysUser(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_user'
|
||||
__table_args__ = {'comment': '用户信息表'}
|
||||
|
||||
user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID')
|
||||
dept_id = Column(Integer, default=None, comment='部门ID')
|
||||
user_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='用户ID')
|
||||
dept_id = Column(
|
||||
BigInteger,
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False),
|
||||
comment='部门ID',
|
||||
)
|
||||
user_name = Column(String(30), nullable=False, comment='用户账号')
|
||||
nick_name = Column(String(30), nullable=False, comment='用户昵称')
|
||||
user_type = Column(String(2), default='00', comment='用户类型(00系统用户)')
|
||||
email = Column(String(50), default='', comment='用户邮箱')
|
||||
phonenumber = Column(String(11), default='', comment='手机号码')
|
||||
sex = Column(String(1), default='0', comment='用户性别(0男 1女 2未知)')
|
||||
avatar = Column(String(100), default='', comment='头像地址')
|
||||
password = Column(String(100), default='', comment='密码')
|
||||
status = Column(String(1), default='0', comment='帐号状态(0正常 1停用)')
|
||||
del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
login_ip = Column(String(128), default='', comment='最后登录IP')
|
||||
login_date = Column(DateTime, comment='最后登录时间')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, comment='更新时间', default=datetime.now())
|
||||
remark = Column(String(500), default=None, comment='备注')
|
||||
user_type = Column(String(2), nullable=True, server_default='00', comment='用户类型(00系统用户)')
|
||||
email = Column(String(50), nullable=True, server_default="''", comment='用户邮箱')
|
||||
phonenumber = Column(String(11), nullable=True, server_default="''", comment='手机号码')
|
||||
sex = Column(CHAR(1), nullable=True, server_default='0', comment='用户性别(0男 1女 2未知)')
|
||||
avatar = Column(String(100), nullable=True, server_default="''", comment='头像地址')
|
||||
password = Column(String(100), nullable=True, server_default="''", comment='密码')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='帐号状态(0正常 1停用)')
|
||||
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
login_ip = Column(String(128), nullable=True, server_default="''", comment='最后登录IP')
|
||||
login_date = Column(DateTime, nullable=True, comment='最后登录时间')
|
||||
pwd_update_date = Column(DateTime, nullable=True, comment='密码最后更新时间')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now())
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
|
||||
class SysUserRole(Base):
|
||||
@@ -37,9 +53,10 @@ class SysUserRole(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_user_role'
|
||||
__table_args__ = {'comment': '用户和角色关联表'}
|
||||
|
||||
user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID')
|
||||
role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID')
|
||||
user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
|
||||
|
||||
|
||||
class SysUserPost(Base):
|
||||
@@ -48,6 +65,7 @@ class SysUserPost(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_user_post'
|
||||
__table_args__ = {'comment': '用户与岗位关联表'}
|
||||
|
||||
user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID')
|
||||
post_id = Column(Integer, primary_key=True, nullable=False, comment='岗位ID')
|
||||
user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID')
|
||||
post_id = Column(BigInteger, primary_key=True, nullable=False, comment='岗位ID')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import Any, List, Optional
|
||||
|
||||
|
||||
class CacheMonitorModel(BaseModel):
|
||||
@@ -10,7 +11,7 @@ class CacheMonitorModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
command_stats: Optional[List] = Field(default=[], description='命令统计')
|
||||
command_stats: Optional[list] = Field(default=[], description='命令统计')
|
||||
db_size: Optional[int] = Field(default=None, description='Key数量')
|
||||
info: Optional[dict] = Field(default={}, description='Redis信息')
|
||||
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class CrudResponseModel(BaseModel):
|
||||
"""
|
||||
操作响应模型
|
||||
"""
|
||||
|
||||
is_success: bool = Field(description='操作是否成功')
|
||||
message: str = Field(description='响应信息')
|
||||
result: Optional[Any] = Field(default=None, description='响应结果')
|
||||
|
||||
|
||||
class UploadResponseModel(BaseModel):
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank, Size
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class ConfigModel(BaseModel):
|
||||
@@ -26,20 +26,20 @@ class ConfigModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='config_key', message='参数名称不能为空')
|
||||
@Size(field_name='config_key', min_length=0, max_length=100, message='参数名称长度不能超过100个字符')
|
||||
def get_config_key(self):
|
||||
def get_config_key(self) -> Union[str, None]:
|
||||
return self.config_key
|
||||
|
||||
@NotBlank(field_name='config_name', message='参数键名不能为空')
|
||||
@Size(field_name='config_name', min_length=0, max_length=100, message='参数键名长度不能超过100个字符')
|
||||
def get_config_name(self):
|
||||
def get_config_name(self) -> Union[str, None]:
|
||||
return self.config_name
|
||||
|
||||
@NotBlank(field_name='config_value', message='参数键值不能为空')
|
||||
@Size(field_name='config_value', min_length=0, max_length=500, message='参数键值长度不能超过500个字符')
|
||||
def get_config_value(self):
|
||||
def get_config_value(self) -> Union[str, None]:
|
||||
return self.config_value
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_config_key()
|
||||
self.get_config_name()
|
||||
self.get_config_value()
|
||||
@@ -54,7 +54,6 @@ class ConfigQueryModel(ConfigModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class ConfigPageQueryModel(ConfigQueryModel):
|
||||
"""
|
||||
参数配置管理分页查询模型
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import Network, NotBlank, Size
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class DeptModel(BaseModel):
|
||||
@@ -30,30 +30,29 @@ class DeptModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='dept_name', message='部门名称不能为空')
|
||||
@Size(field_name='dept_name', min_length=0, max_length=30, message='部门名称长度不能超过30个字符')
|
||||
def get_dept_name(self):
|
||||
def get_dept_name(self) -> Union[str, None]:
|
||||
return self.dept_name
|
||||
|
||||
@NotBlank(field_name='order_num', message='显示顺序不能为空')
|
||||
def get_order_num(self):
|
||||
def get_order_num(self) -> Union[int, None]:
|
||||
return self.order_num
|
||||
|
||||
@Size(field_name='phone', min_length=0, max_length=11, message='联系电话长度不能超过11个字符')
|
||||
def get_phone(self):
|
||||
def get_phone(self) -> Union[str, None]:
|
||||
return self.phone
|
||||
|
||||
@Network(field_name='email', field_type='EmailStr', message='邮箱格式不正确')
|
||||
@Size(field_name='email', min_length=0, max_length=50, message='邮箱长度不能超过50个字符')
|
||||
def get_email(self):
|
||||
def get_email(self) -> Union[str, None]:
|
||||
return self.email
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_dept_name()
|
||||
self.get_order_num()
|
||||
self.get_phone()
|
||||
self.get_email()
|
||||
|
||||
|
||||
@as_query
|
||||
class DeptQueryModel(DeptModel):
|
||||
"""
|
||||
部门管理不分页查询模型
|
||||
@@ -63,6 +62,19 @@ class DeptQueryModel(DeptModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
class DeptTreeModel(BaseModel):
|
||||
"""
|
||||
部门树模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
id: int = Field(description='部门id')
|
||||
label: str = Field(description='部门名称')
|
||||
parent_id: int = Field(description='父部门id')
|
||||
children: Optional[list['DeptTreeModel']] = Field(default=None, description='子部门树')
|
||||
|
||||
|
||||
class DeleteDeptModel(BaseModel):
|
||||
"""
|
||||
删除部门模型
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank, Pattern, Size
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class DictTypeModel(BaseModel):
|
||||
@@ -25,7 +25,7 @@ class DictTypeModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='dict_name', message='字典名称不能为空')
|
||||
@Size(field_name='dict_name', min_length=0, max_length=100, message='字典类型名称长度不能超过100个字符')
|
||||
def get_dict_name(self):
|
||||
def get_dict_name(self) -> Union[str, None]:
|
||||
return self.dict_name
|
||||
|
||||
@NotBlank(field_name='dict_type', message='字典类型不能为空')
|
||||
@@ -35,10 +35,10 @@ class DictTypeModel(BaseModel):
|
||||
regexp='^[a-z][a-z0-9_]*$',
|
||||
message='字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)',
|
||||
)
|
||||
def get_dict_type(self):
|
||||
def get_dict_type(self) -> Union[str, None]:
|
||||
return self.dict_type
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_dict_name()
|
||||
self.get_dict_type()
|
||||
|
||||
@@ -67,24 +67,24 @@ class DictDataModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='dict_label', message='字典标签不能为空')
|
||||
@Size(field_name='dict_label', min_length=0, max_length=100, message='字典标签长度不能超过100个字符')
|
||||
def get_dict_label(self):
|
||||
def get_dict_label(self) -> Union[str, None]:
|
||||
return self.dict_label
|
||||
|
||||
@NotBlank(field_name='dict_value', message='字典键值不能为空')
|
||||
@Size(field_name='dict_value', min_length=0, max_length=100, message='字典键值长度不能超过100个字符')
|
||||
def get_dict_value(self):
|
||||
def get_dict_value(self) -> Union[str, None]:
|
||||
return self.dict_value
|
||||
|
||||
@NotBlank(field_name='dict_type', message='字典类型不能为空')
|
||||
@Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型长度不能超过100个字符')
|
||||
def get_dict_type(self):
|
||||
def get_dict_type(self) -> Union[str, None]:
|
||||
return self.dict_type
|
||||
|
||||
@Size(field_name='css_class', min_length=0, max_length=100, message='样式属性长度不能超过100个字符')
|
||||
def get_css_class(self):
|
||||
def get_css_class(self) -> Union[str, None]:
|
||||
return self.css_class
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_dict_label()
|
||||
self.get_dict_value()
|
||||
self.get_dict_type()
|
||||
@@ -100,7 +100,6 @@ class DictTypeQueryModel(DictTypeModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class DictTypePageQueryModel(DictTypeQueryModel):
|
||||
"""
|
||||
字典类型管理分页查询模型
|
||||
@@ -129,7 +128,6 @@ class DictDataQueryModel(DictDataModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class DictDataPageQueryModel(DictDataQueryModel):
|
||||
"""
|
||||
字典数据管理分页查询模型
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank, Size
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class JobModel(BaseModel):
|
||||
@@ -34,15 +34,15 @@ class JobModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='invoke_target', message='调用目标字符串不能为空')
|
||||
@Size(field_name='invoke_target', min_length=0, max_length=500, message='调用目标字符串长度不能超过500个字符')
|
||||
def get_invoke_target(self):
|
||||
def get_invoke_target(self) -> Union[str, None]:
|
||||
return self.invoke_target
|
||||
|
||||
@NotBlank(field_name='cron_expression', message='Cron执行表达式不能为空')
|
||||
@Size(field_name='cron_expression', min_length=0, max_length=255, message='Cron执行表达式不能超过255个字符')
|
||||
def get_cron_expression(self):
|
||||
def get_cron_expression(self) -> Union[str, None]:
|
||||
return self.cron_expression
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_invoke_target()
|
||||
self.get_cron_expression()
|
||||
|
||||
@@ -77,7 +77,6 @@ class JobQueryModel(JobModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class JobPageQueryModel(JobQueryModel):
|
||||
"""
|
||||
定时任务管理分页查询模型
|
||||
@@ -114,7 +113,6 @@ class JobLogQueryModel(JobLogModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class JobLogPageQueryModel(JobLogQueryModel):
|
||||
"""
|
||||
定时任务日志管理分页查询模型
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class OperLogModel(BaseModel):
|
||||
@@ -68,7 +68,6 @@ class OperLogQueryModel(OperLogModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class OperLogPageQueryModel(OperLogQueryModel):
|
||||
"""
|
||||
操作日志管理分页查询模型
|
||||
@@ -101,7 +100,6 @@ class LoginLogQueryModel(LogininforModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class LoginLogPageQueryModel(LoginLogQueryModel):
|
||||
"""
|
||||
登录日志管理分页查询模型
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import re
|
||||
from typing import Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from exceptions.exception import ModelValidatorException
|
||||
from module_admin.entity.vo.menu_vo import MenuModel
|
||||
|
||||
@@ -31,7 +33,6 @@ class UserRegister(BaseModel):
|
||||
pattern = r"""^[^<>"'|\\]+$"""
|
||||
if self.password is None or re.match(pattern, self.password):
|
||||
return self
|
||||
else:
|
||||
raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |')
|
||||
|
||||
|
||||
@@ -57,7 +58,7 @@ class SmsCode(BaseModel):
|
||||
|
||||
|
||||
class MenuTreeModel(MenuModel):
|
||||
children: Optional[Union[List['MenuTreeModel'], None]] = Field(default=None, description='子菜单')
|
||||
children: Optional[Union[list['MenuTreeModel'], None]] = Field(default=None, description='子菜单')
|
||||
|
||||
|
||||
class MetaModel(BaseModel):
|
||||
@@ -84,4 +85,4 @@ class RouterModel(BaseModel):
|
||||
default=None, description='当你一个路由下面的children声明的路由大于1个时,自动会变成嵌套的模式--如组件页面'
|
||||
)
|
||||
meta: Optional[MetaModel] = Field(default=None, description='其他元素')
|
||||
children: Optional[Union[List['RouterModel'], None]] = Field(default=None, description='子路由')
|
||||
children: Optional[Union[list['RouterModel'], None]] = Field(default=None, description='子路由')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank, Size
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class MenuModel(BaseModel):
|
||||
@@ -36,30 +36,30 @@ class MenuModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='menu_name', message='菜单名称不能为空')
|
||||
@Size(field_name='menu_name', min_length=0, max_length=50, message='菜单名称长度不能超过50个字符')
|
||||
def get_menu_name(self):
|
||||
def get_menu_name(self) -> Union[str, None]:
|
||||
return self.menu_name
|
||||
|
||||
@NotBlank(field_name='order_num', message='显示顺序不能为空')
|
||||
def get_order_num(self):
|
||||
def get_order_num(self) -> Union[int, None]:
|
||||
return self.order_num
|
||||
|
||||
@Size(field_name='path', min_length=0, max_length=200, message='路由地址长度不能超过200个字符')
|
||||
def get_path(self):
|
||||
def get_path(self) -> Union[str, None]:
|
||||
return self.path
|
||||
|
||||
@Size(field_name='component', min_length=0, max_length=255, message='组件路径长度不能超过255个字符')
|
||||
def get_component(self):
|
||||
def get_component(self) -> Union[str, None]:
|
||||
return self.component
|
||||
|
||||
@NotBlank(field_name='menu_type', message='菜单类型不能为空')
|
||||
def get_menu_type(self):
|
||||
def get_menu_type(self) -> Union[Literal['M', 'C', 'F'], None]:
|
||||
return self.menu_type
|
||||
|
||||
@Size(field_name='perms', min_length=0, max_length=100, message='权限标识长度不能超过100个字符')
|
||||
def get_perms(self):
|
||||
def get_perms(self) -> Union[str, None]:
|
||||
return self.perms
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_menu_name()
|
||||
self.get_order_num()
|
||||
self.get_path()
|
||||
@@ -68,7 +68,6 @@ class MenuModel(BaseModel):
|
||||
self.get_perms()
|
||||
|
||||
|
||||
@as_query
|
||||
class MenuQueryModel(MenuModel):
|
||||
"""
|
||||
菜单管理不分页查询模型
|
||||
@@ -78,6 +77,19 @@ class MenuQueryModel(MenuModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
class MenuTreeModel(BaseModel):
|
||||
"""
|
||||
菜单树模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
id: int = Field(description='菜单id')
|
||||
label: str = Field(description='菜单名称')
|
||||
parent_id: int = Field(description='父菜单id')
|
||||
children: Optional[list['MenuTreeModel']] = Field(default=None, description='子菜单树')
|
||||
|
||||
|
||||
class DeleteMenuModel(BaseModel):
|
||||
"""
|
||||
删除菜单模型
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank, Size, Xss
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class NoticeModel(BaseModel):
|
||||
@@ -27,10 +27,10 @@ class NoticeModel(BaseModel):
|
||||
@Xss(field_name='notice_title', message='公告标题不能包含脚本字符')
|
||||
@NotBlank(field_name='notice_title', message='公告标题不能为空')
|
||||
@Size(field_name='notice_title', min_length=0, max_length=50, message='公告标题不能超过50个字符')
|
||||
def get_notice_title(self):
|
||||
def get_notice_title(self) -> Union[str, None]:
|
||||
return self.notice_title
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_notice_title()
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ class NoticeQueryModel(NoticeModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class NoticePageQueryModel(NoticeQueryModel):
|
||||
"""
|
||||
通知公告管理分页查询模型
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class OnlineModel(BaseModel):
|
||||
@@ -22,7 +22,6 @@ class OnlineModel(BaseModel):
|
||||
login_time: Optional[datetime] = Field(default=None, description='登录时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class OnlineQueryModel(OnlineModel):
|
||||
"""
|
||||
岗位管理不分页查询模型
|
||||
@@ -32,6 +31,17 @@ class OnlineQueryModel(OnlineModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
class OnlinePageResponseModel(BaseModel):
|
||||
"""
|
||||
在线用户分页响应模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
rows: list[OnlineModel] = Field(description='在线用户记录列表')
|
||||
total: int = Field(description='总记录数')
|
||||
|
||||
|
||||
class DeleteOnlineModel(BaseModel):
|
||||
"""
|
||||
强退在线用户模型
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank, Size
|
||||
from typing import Literal, Optional
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class PostModel(BaseModel):
|
||||
@@ -26,19 +26,19 @@ class PostModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='post_code', message='岗位编码不能为空')
|
||||
@Size(field_name='post_code', min_length=0, max_length=64, message='岗位编码长度不能超过64个字符')
|
||||
def get_post_code(self):
|
||||
def get_post_code(self) -> Union[str, None]:
|
||||
return self.post_code
|
||||
|
||||
@NotBlank(field_name='post_name', message='岗位名称不能为空')
|
||||
@Size(field_name='post_name', min_length=0, max_length=50, message='岗位名称长度不能超过50个字符')
|
||||
def get_post_name(self):
|
||||
def get_post_name(self) -> Union[str, None]:
|
||||
return self.post_name
|
||||
|
||||
@NotBlank(field_name='post_sort', message='显示顺序不能为空')
|
||||
def get_post_sort(self):
|
||||
def get_post_sort(self) -> Union[int, None]:
|
||||
return self.post_sort
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_post_code()
|
||||
self.get_post_name()
|
||||
self.get_post_sort()
|
||||
@@ -53,7 +53,6 @@ class PostQueryModel(PostModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class PostPageQueryModel(PostQueryModel):
|
||||
"""
|
||||
岗位管理分页查询模型
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank, Size
|
||||
from typing import List, Literal, Optional, Union
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class RoleModel(BaseModel):
|
||||
@@ -55,19 +55,19 @@ class RoleModel(BaseModel):
|
||||
|
||||
@NotBlank(field_name='role_name', message='角色名称不能为空')
|
||||
@Size(field_name='role_name', min_length=0, max_length=30, message='角色名称长度不能超过30个字符')
|
||||
def get_role_name(self):
|
||||
def get_role_name(self) -> Union[str, None]:
|
||||
return self.role_name
|
||||
|
||||
@NotBlank(field_name='role_key', message='权限字符不能为空')
|
||||
@Size(field_name='role_key', min_length=0, max_length=100, message='权限字符长度不能超过100个字符')
|
||||
def get_role_key(self):
|
||||
def get_role_key(self) -> Union[str, None]:
|
||||
return self.role_key
|
||||
|
||||
@NotBlank(field_name='role_sort', message='显示顺序不能为空')
|
||||
def get_role_sort(self):
|
||||
def get_role_sort(self) -> Union[int, None]:
|
||||
return self.role_sort
|
||||
|
||||
def validate_fields(self):
|
||||
def validate_fields(self) -> None:
|
||||
self.get_role_name()
|
||||
self.get_role_key()
|
||||
self.get_role_sort()
|
||||
@@ -104,7 +104,6 @@ class RoleQueryModel(RoleModel):
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class RolePageQueryModel(RoleQueryModel):
|
||||
"""
|
||||
角色管理分页查询模型
|
||||
@@ -121,8 +120,8 @@ class RoleMenuQueryModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
menus: List = Field(default=[], description='菜单信息')
|
||||
checked_keys: List[int] = Field(default=[], description='已选择的菜单ID信息')
|
||||
menus: list = Field(default=[], description='菜单信息')
|
||||
checked_keys: list[int] = Field(default=[], description='已选择的菜单ID信息')
|
||||
|
||||
|
||||
class RoleDeptQueryModel(BaseModel):
|
||||
@@ -132,8 +131,8 @@ class RoleDeptQueryModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
depts: List = Field(default=[], description='部门信息')
|
||||
checked_keys: List[int] = Field(default=[], description='已选择的部门ID信息')
|
||||
depts: list = Field(default=[], description='部门信息')
|
||||
checked_keys: list[int] = Field(default=[], description='已选择的部门ID信息')
|
||||
|
||||
|
||||
class AddRoleModel(RoleModel):
|
||||
@@ -141,8 +140,8 @@ class AddRoleModel(RoleModel):
|
||||
新增角色模型
|
||||
"""
|
||||
|
||||
dept_ids: List = Field(default=[], description='部门ID信息')
|
||||
menu_ids: List = Field(default=[], description='菜单ID信息')
|
||||
dept_ids: list = Field(default=[], description='部门ID信息')
|
||||
menu_ids: list = Field(default=[], description='菜单ID信息')
|
||||
type: Optional[str] = Field(default=None, description='操作类型')
|
||||
|
||||
|
||||
@@ -153,6 +152,6 @@ class DeleteRoleModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
role_ids: str = Field(description='需要删除的菜单ID')
|
||||
role_ids: str = Field(description='需要删除的角色ID')
|
||||
update_by: Optional[str] = Field(default=None, description='更新者')
|
||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user