Compare commits

..
132 Commits
Author SHA1 Message Date
insistence 8cdf58310e chore: 发布1.8.1版本 2025-12-26 15:59:50 +08:00
insistence 75c49e89c2 perf: 优化pg版本的docker compose配置文件 (#61)
* perf: 优化pg版本的docker compose配置文件

* docs: 新增更新日志
2025-12-26 15:18:37 +08:00
insistence 8405471afd fix: 修复新增和编辑部门无法使用已删除的部门名称问题 (#60) 2025-12-26 11:50:48 +08:00
insistence 1e0b38451c fix: 修复DictTag组件渲染异常的问题 (#59)
* fix: 修复DictTag组件渲染异常的问题

* docs: 新增更新日志

* docs: 修复更新日志书写错误
2025-12-26 11:37:29 +08:00
insistence b84823610c feat: 新增E2E测试 (#57)
* feat: 新增E2E测试

* fix: 修复docker compose命令书写错误

* fix: 运行测试前执行`playwright install`

* fix: 增强检查以尝试修复3.11连接拒绝的问题

* fix: 增加mysql服务检查

* fix: 修复python 3.9兼容性问题

* perf: 优化禁用验证码脚本

* feat: 新增pg测试

* fix: 修复pg测试检查服务名书写错误

* perf: 优化测试

* style: 格式化代码
2025-12-26 11:09:51 +08:00
insistence 332ddbe0e8 perf: 动态导入定时任务函数,移除eval (#56) 2025-12-22 16:33:25 +08:00
insistence 9cfbf48ce9 perf: 优化数据权限依赖 (#55) 2025-12-22 11:30:03 +08:00
insistence 8f791ff6e5 !40 RuoYi-Vue3-FastAPI v1.8.0
Merge pull request !40 from insistence/develop
2025-12-19 08:32:34 +00:00
insistence 6cb2afca39 feat: 新增github actions配置文件 2025-12-19 16:30:24 +08:00
insistence d47613970b docs: 新增更新日志文档 2025-12-19 16:13:27 +08:00
insistence 8833980008 docs: 更新README文档 2025-12-19 11:20:19 +08:00
insistence 0067e66fc8 chore: 升级版本至1.8.0 2025-12-19 11:20:09 +08:00
insistence 39c4590c3f chore: 更新后端依赖 2025-12-19 11:05:48 +08:00
insistence 3196962197 fix: 修复代码生成v3模板时间控件between选择后清空报错问题 2025-12-19 10:31:52 +08:00
insistence 1ffaaf5225 perf: 优化topbar顶部菜单样式 2025-12-19 09:35:44 +08:00
insistence 4ad671023d perf: 默认固定头部 2025-12-19 09:30:05 +08:00
insistence 45bd2ddb21 perf: 优化字典组件值宽松匹配 2025-12-19 09:28:13 +08:00
insistence 267f76d6a2 perf: 优化字典组件数字类型值处理逻辑 2025-12-19 09:26:48 +08:00
insistence 67c09f3113 perf: 优化index页面标题读取配置 2025-12-19 09:25:32 +08:00
insistence c8da8d5406 perf: 优化操作日志详细请求参数显示 2025-12-19 09:24:40 +08:00
insistence 6ad0a63c68 feat: 菜单导航设置支持纯顶部 2025-12-19 09:23:19 +08:00
insistence ab4eb00bc1 行内表单默认设置固定宽度 2025-12-19 09:05:14 +08:00
insistence 954085666a perf: 优化表单构建关闭页签销毁复制插件 2025-12-19 09:02:58 +08:00
insistence 35aa26f8a9 perf: 优化生成代码下载的zip文件名 2025-12-19 09:01:01 +08:00
insistence 1e281508b2 perf: 登录/注册页面底部版权信息修改为读取配置 2025-12-19 08:45:10 +08:00
insistence a4e4c20c5e fix: 修复表单构建移除所有控件后切换路由回来空白问题 2025-12-19 08:42:46 +08:00
insistence d4b85f38d8 fix: 修复固定头部时出现的导航栏偏移问题 2025-12-19 08:38:41 +08:00
insistence ed88512bf8 feat: 代码生成controller模版使用APIRouterPro 2025-12-19 08:23:00 +08:00
insistence b9b2f713b9 feat: 新增docker compose部署方式 2025-12-18 16:03:44 +08:00
insistence b1563b0d39 fix: 移除代码生成表业务表外键,修复无法删除的问题 2025-12-17 17:02:42 +08:00
insistence 1857663c9f feat: 新增自动注册路由功能 2025-12-17 15:52:22 +08:00
insistence a28922acf0 style: 格式化代码 2025-12-17 15:51:36 +08:00
insistence cdacb1643c feat: 新增配置文档静态资源方法 2025-12-16 16:15:52 +08:00
insistence ad0327bf51 refactor: 重构app与server设计 2025-12-16 14:38:31 +08:00
insistence 50af1bf573 fix: 修复在线用户列表与PageResponseModel不兼容的问题 2025-12-15 17:08:43 +08:00
insistence d364394575 fix: 修复磁盘存在异常时服务监控无法正常运行的问题 2025-12-15 16:49:30 +08:00
insistence 98651f4d8f perf: 接口API文档的接口描述信息 2025-12-15 16:18:20 +08:00
insistence 40ccb84922 perf: 优化所有接口的API文档响应模型显示 2025-12-15 15:02:49 +08:00
insistence d9a19ce3ee perf: 操作响应模型改为使用公共vo模块的CrudResponseModel 2025-12-13 11:28:43 +08:00
insistence e216b97163 perf: 代码生成模板使用公共vo模块 2025-12-13 11:20:33 +08:00
insistence 1f51b628af perf: 优化API文档的响应模型显示 2025-12-12 17:44:54 +08:00
insistence 1656670203 perf: 分页模型改为使用公共vo模块的PageModel 2025-12-12 17:44:27 +08:00
insistence b8dcbdc448 feat: 新增公共vo模块 2025-12-12 17:42:26 +08:00
insistence dc2e255cd2 fix: 确保ApschedulerJobs字段类型与apscheduler默认创建的表字段类型一致 #53 2025-12-12 09:32:18 +08:00
insistence d9245ed2c9 fix: 修复单账号登录模式下强退功能失效的问题 #52 2025-12-12 08:49:55 +08:00
insistence 8f4058977d chore: fastapi -> 0.124.2 2025-12-11 17:01:53 +08:00
insistence 044fa0f30b feat: 新增上下文清理中间件 2025-12-11 17:00:33 +08:00
insistence 6565c50e08 perf: 当前用户信息使用上下文变量 2025-12-11 17:00:12 +08:00
insistence 184982eb08 perf: controller层全部使用新依赖项 2025-12-11 16:59:18 +08:00
insistence 9a1c53d9f4 feat: 新增UserInterfaceAuthDependency和RoleInterfaceAuthDependency依赖 2025-12-11 16:57:58 +08:00
insistence 44678985e9 feat: 新增DBSessionDependency依赖 2025-12-11 16:57:35 +08:00
insistence 077c8870b4 feat: 新增DataScopeDependency依赖 2025-12-11 16:57:10 +08:00
insistence d1fcb6dc11 feat: 新增依赖项工具类 2025-12-11 16:56:48 +08:00
insistence 9114d65308 feat: 新增PreAuthDependency和CurrentUserDependency依赖 2025-12-11 16:55:46 +08:00
insistence f5ec612850 feat: 新增请求上下文管理类 2025-12-11 16:54:09 +08:00
insistence a3355e2545 refactor&feat: 优化项目结构,新增common模块,原annotation、aspect、constant、enums模块移动至common模块下 2025-12-05 15:02:04 +08:00
insistence 1516c1a941 refactor&perf: 增强ruff规则,完善类型提示 2025-12-05 10:00:25 +08:00
insistence b974e8bd3d !38 RuoYi-Vue3-FastAPI v1.7.1
Merge pull request !38 from insistence/develop
2025-10-14 03:09:29 +00:00
insistence a389ef66bc docs: 更新README文档 2025-10-14 11:05:47 +08:00
insistence dcdeaa0394 chore: 升级版本至1.7.1 2025-10-14 11:05:39 +08:00
insistence f4d7d7a416 chore&fix: 移除passlib直接使用bcrypt修复密码校验异常的问题 报错 #48 #49 2025-10-14 11:05:20 +08:00
insistence f5a91c0ece perf: 代码生成do模板补充表描述 2025-09-05 09:21:40 +08:00
insistence 5ba6587b6c fix: 修复代码生成controller模板编辑接口异常生成字段的问题 2025-09-05 09:21:29 +08:00
insistence f2de18aa43 !36 docs: 更新README文档
Merge pull request !36 from insistence/develop
2025-09-04 02:10:06 +00:00
insistence 24cc7e360a docs: 更新README文档 2025-09-04 09:45:19 +08:00
insistence 3c5f6bf873 chore: 升级版本至1.7.0 2025-09-04 09:45:07 +08:00
insistence 81c9b82abd perf: 优化样式 2025-09-03 17:20:25 +08:00
insistence ac3056b9a7 feat: 显示列信息支持对象格式 2025-09-03 17:18:37 +08:00
insistence 10719a4067 feat: 注册账号设置默认密码最后更新时间 2025-09-03 17:01:25 +08:00
insistence 5597eb651f feat: 账号密码支持自定义更新周期 2025-09-03 16:58:30 +08:00
insistence 385013db9d feat: 初始密码支持自定义修改策略 2025-09-03 16:44:31 +08:00
insistence 2699039c0b perf: 优化布局显示 2025-09-03 16:44:01 +08:00
insistence 831768dafd feat: 新增apscheduler_jobs表对应sqlalchemy模型类 2025-09-03 16:37:41 +08:00
insistence 6ea37823c3 feat&refactor: 菜单搜索支持键盘选择&悬浮主题背景 2025-08-29 16:13:31 +08:00
insistence 67e9f6c98b perf: 优化导航栏显示昵称&设置 2025-08-29 15:52:06 +08:00
insistence f19f192170 feat: 用户导入新增验证提示 2025-08-29 15:51:07 +08:00
insistence 7eb4b75ab1 feat: 用户归属部门新增清除 2025-08-29 15:46:50 +08:00
insistence d0138516e7 perf: 优化checkbox废弃API 2025-08-29 15:44:33 +08:00
insistence ae4f37f1c5 perf: 消除控制台警告信息 2025-08-29 15:42:13 +08:00
insistence 41a9d1edf6 chore: 升级组件依赖 2025-08-29 15:39:07 +08:00
insistence 61fba98d89 feat: 新增底部版权信息及开关 2025-08-29 14:47:44 +08:00
insistence cc6a35d7c7 feat: 新增页签图标显示开关功能 2025-08-29 11:33:05 +08:00
insistence ea0303835f feat: 新增用户默认初始化密码 2025-08-29 08:29:05 +08:00
insistence 7b993655ba perf: 优化代码 2025-08-29 08:21:15 +08:00
insistence d9d7c94513 feat: 代码生成列支持拖动排序 2025-08-27 09:26:01 +08:00
insistence cc6b38acdd feat: 图片上传组件新增disabled属性 2025-08-27 09:25:38 +08:00
insistence 27d847fd25 fix: 修复上传组件被多次引用拖动仅对第一个有效的问题 2025-08-27 08:54:10 +08:00
insistence 9e76400c8e feat: 上传组件新增拖动排序属性 2025-08-27 08:50:07 +08:00
insistence c365459d88 feat: 外链加载时遮罩信息提示 2025-08-27 08:43:17 +08:00
insistence 079b2eefb6 feat: 添加页签openPage支持传递参数 2025-08-27 08:38:43 +08:00
insistence c6d4c6c1a9 refactor: 富文本复制粘贴图片上传至url 2025-08-27 08:33:38 +08:00
insistence 9b6355c68d perf: 优化富文本控制台警告异常 2025-08-27 08:28:55 +08:00
insistence ccacee5f95 perf: 优化代码 2025-08-27 08:25:35 +08:00
insistence 397a51bd96 feat: 显隐列组件支持全选/全不选 2025-08-26 15:07:51 +08:00
insistence f144945215 perf: 优化代码 2025-08-26 11:16:17 +08:00
insistence a1c78172ca feat: 新增默认打包配置项 2025-08-26 11:08:34 +08:00
insistence f03ab1a845 feat: 支持文件&图片组件自定义地址&参数 2025-08-26 11:04:37 +08:00
insistence a64eca3aeb perf: 优化角色禁用不允许分配 2025-08-26 10:51:27 +08:00
insistence bc547f90f9 perf: 登录页和注册页表头使用VITE_APP_BASE_API配置值 2025-08-26 10:42:31 +08:00
insistence 7a66854a22 perf: 优化顶部菜单搜索栏为多层级显示 2025-08-26 10:28:18 +08:00
insistence 795a9204ae perf: 优化前端处理路由函数代码 2025-08-26 10:23:49 +08:00
insistence 11debaf213 refactor: 调整do与sql使其相互适配 2025-08-22 16:17:46 +08:00
insistence 857b7dba80 chore: 更新依赖文件 2025-08-22 16:17:33 +08:00
insistence 5d6bd822d3 perf: 优化启动信息显示 2025-08-21 17:20:55 +08:00
insistence 0b7df2cfc1 feat: 集成alembic以支持数据迁移 2025-08-21 17:20:13 +08:00
insistence 20bc7ebaaa chore: 更新依赖文件 2025-08-04 10:56:14 +08:00
insistence c1a8060139 refactor: 重构IP归属区域查询为异步调用 2025-08-04 10:55:43 +08:00
insistence 5558dd181b perf: 优化接口耗时计算 2025-07-28 15:07:31 +08:00
insistence 0fd4fb283a fix: 修复logout接口未按照app_same_time_login配置项动态判断的问题 #IBZZ1S 2025-07-28 14:55:21 +08:00
insistence 00a7af2d5b !30 RuoYi-Vue3-FastAPI 1.6.2
Merge pull request !30 from insistence/develop
2025-03-19 07:59:40 +00:00
insistence e34a493b3e docs: 更新README文档 2025-03-19 15:55:10 +08:00
insistence a9df9ea56a chore: 升级版本至1.6.2 2025-03-19 15:54:12 +08:00
insistence d89c0df425 perf: 优化代码生成vue模板 #23 2025-03-19 15:46:28 +08:00
insistence d0730e7993 fix: 修复用户导出缺失部门名称的问题 2025-03-18 16:55:16 +08:00
insistence 5fe376f978 perf: pagination更换成flex布局 2025-03-18 08:28:46 +08:00
insistence dd5b1c55c9 feat: 文件上传组件新增类型 2025-03-18 08:12:13 +08:00
insistence 7645c6d9fd feat: 文件上传组件新增disabled属性 2025-03-18 08:11:18 +08:00
insistence 61073970fa fix: 修复代码生成模板时间查询问题 #28 2025-03-17 16:32:46 +08:00
insistence 9ae2ac02eb perf: 优化代码生成新增和编辑字段显示和渲染 2025-03-17 09:51:05 +08:00
insistence 6622c329fc fix: 修复修改字典类型时字典数据更新时间异常的问题 2025-03-17 08:27:39 +08:00
insistence b66d545985 fix: 修复修改字典类型时获取dict_code异常的问题 2025-03-17 08:25:39 +08:00
insistence 1789cb5a9b fix: 修复定时任务状态暂停时执行单次任务会触发cron表达式的问题 #31 2025-03-15 21:59:28 +08:00
insistence ee376c477d fix: 修复日志管理时间查询报错 #27 2025-03-09 18:43:57 +08:00
jiangbaihe 2b6f0905a9 chore: 调整.gitignore策略 2025-03-04 14:58:04 +08:00
insistence c953754bed chore: pg依赖文件新增sqlglot依赖 2025-03-03 17:13:37 +08:00
insistence cea4a10baa !29 RuoYi-Vue3-FastAPI v1.6.1
Merge pull request !29 from insistence/develop
2025-03-03 08:50:47 +00:00
insistence b708d86eff docs: 更新README文档 2025-03-03 16:44:31 +08:00
insistence 28aab8c7d4 chore: 升级版本至1.6.1 2025-03-03 16:43:47 +08:00
insistence 9412e3f344 fix: 修复代码生成主子表vo模板可能缺失NotBlank的问题 2025-02-28 10:46:45 +08:00
insistence b4d5619b1b fix: 引入泛型修复as_query和as_form装饰模型文档丢失的问题 2025-02-28 10:36:29 +08:00
insistence 8ce598ad54 fix: 修复代码生成字段唯一性校验dao层模板判断异常的问题 2025-02-26 09:00:35 +08:00
insistence ca641055e0 fix: 引入sqlglot修复sql语句解析异常的问题 2025-02-21 15:40:47 +08:00
249 changed files with 11971 additions and 3621 deletions
+1
View File
@@ -0,0 +1 @@
github: [insistence]
+170
View File
@@ -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
+33
View File
@@ -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
+7
View File
@@ -136,3 +136,10 @@ dmypy.json
# Cython debug symbols
cython_debug/
# PyCharm
.idea/
# VSCode
.vscode/
+510
View File
@@ -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接口文档。
+129 -40
View File
@@ -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>
+73
View File
@@ -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
+73
View File
@@ -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
+1 -1
View File
@@ -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归属区域查询
+66
View File
@@ -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
+66
View File
@@ -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
+1 -1
View File
@@ -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归属区域查询
+14
View File
@@ -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"]
+14
View File
@@ -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"]
+153
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
Generic single-database configuration with an async dbapi.
+125
View File
@@ -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"}
+4 -1
View File
@@ -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
@@ -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']
+100
View File
@@ -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': '登录令牌信息'}
+405
View File
@@ -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()
+105
View File
@@ -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='响应数据')
+4 -4
View File
@@ -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 = (
+32 -21
View File
@@ -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:
+9 -5
View File
@@ -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('✅️ 数据库连接成功')
+15 -12
View File
@@ -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:
"""
应用启动时缓存参数配置表
+63 -46
View File
@@ -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
+13 -12
View File
@@ -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压缩中间件
+5 -1
View File
@@ -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