diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..d66089246a819c25cad3ae53cf1dd67f34629a16 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [insistence] \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000000000000000000000000000000000..833cb15b98b5fc3fd812a5a5d7641878ca3fbc44 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: Tests + +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 + + - name: Run format check + run: | + ruff format ruoyi-fastapi-backend --check \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..446198b5769a9949b65c513120333e7297ff2c43 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,491 @@ +# 更新日志 + +## RuoYi-Vue-FastAPI v1.8.0 + +### 项目依赖 + +## 后端 + +1.后端依赖升级到最新版本,请升级依赖或重新创建环境。 + +### 新增功能 + +1.新增请求上下文管理类。 +2.新增`PreAuthDependency`、`CurrentUserDependency`、`DataScopeDependency`、`DBSessionDependency`、`UserInterfaceAuthDependency`和`RoleInterfaceAuthDependency`依赖函数。 +3.新增上下文清理中间件。 +4.新增公共vo模块。 +5.新增配置文档静态资源方法。 +6.新增自动注册路由功能。 +7.新增docker compose部署方式。 +8.网页标题设置新增SET_TITLE方法。 +9.菜单导航设置支持纯顶部。 + +### BUG修复 + +1.修复单账号登录模式下强退功能失效的问题。 +2.确保ApschedulerJobs字段类型与apscheduler默认创建的表字段类型一致。 +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.默认固定头部。 + +## RuoYi-Vue-FastAPI v1.7.1 + +### 项目依赖 + +1.后端依赖移除passlib,直接使用bcrypt。 + +### BUG修复 + +1.修复代码生成controller模板编辑接口异常生成字段的问题。 +2.移除passlib直接使用bcrypt修复密码校验异常的问题 #ID124V。 + +### 代码优化 + +1.代码生成do模板补充表描述。 + +## RuoYi-Vue-FastAPI v1.7.0 + +### 项目依赖 + +1.前后端依赖升级,请升级依赖或重新创建环境。 + +### 新增功能 + +1.新增alembic支持。 +2.文件&图片上传组件支持自定义地址&参数。 +3.显隐列组件支持全选/全不选。 +4.上传组件新增拖动排序属性。 +5.图片上传组件新增disabled属性。 +6.代码生成列支持拖动排序。 +7.新增页签图标显示开关功能。 +8.新增底部版权信息及开关。 +9.用户导入新增验证提示。 +10.菜单搜索支持键盘选择&悬浮主题背景。 +11.新增apscheduler_jobs表对应sqlalchemy模型类。 +12.初始密码支持自定义修改策略。 +13.账号密码支持自定义更新周期。 +14.注册账号设置默认密码最后更新时间。 +15.显示列信息支持对象格式。 + +### BUG修复 + +1.修复logout接口未按照app_same_time_login配置项动态判断的问题。 +2.修复上传组件被多次引用拖动仅对第一个有效的问题。 + +### 代码优化 + +1.优化接口耗时计算。 +2.优化启动信息显示。 +3.优化前端处理路由函数代码。 +4.登录页和注册页表头使用VUE_APP_TITLE配置值。 +5.优化角色禁用不允许分配。 +6.优化导航栏显示昵称&设置。 +7.优化服务监控和缓存监控页面,页边距保持一致。 + +### 代码重构 + +1.重构IP归属区域查询为异步调用 #7。 +2.调整do与sql使其相互适配以支持alembic。 +3.富文本复制粘贴图片上传至url。 + +## RuoYi-Vue-FastAPI v1.6.2 + +### 新增功能 + +1.文件上传组件新增disabled属性。 +2.文件上传组件新增类型。 + +### BUG修复 + +1.修复日志管理时间查询报错。 +2.修复定时任务状态暂停时执行单次任务会触发cron表达式的问题。 +3.修复修改字典类型时获取dict_code异常的问题。 +4.修复修改字典类型时字典数据更新时间异常的问题。 +5.修复代码生成模板时间查询问题。 +6.修复用户导出缺失部门名称的问题。 + +### 代码优化 + +1.优化代码生成新增和编辑字段显示和渲染。 +2.pagination更换成flex布局。 +3.优化代码生成vue模板。 + +## RuoYi-Vue-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-Vue-FastAPI v1.6.0 + +### 项目依赖 + +1.后端依赖升级到最新版本,请升级依赖或重新创建环境。 + +### 新增功能 + +1.新增代码生成功能,支持配置数据库表信息一键生成和下载前后端代码,需要重新执行sql文件,请先备份数据。 +2.用户头像新增支持http(s)链接。 +3.新增trace中间件强化日志链路追踪和响应头。 +4.用户管理支持分栏拖动。 +5.菜单面包屑导航支持多层级显示。 +6.白名单支持对通配符路径匹配。 + +### BUG修复 + +1.修复默认关闭Tags-Views时,内链页面打不开。 +2.修复删除当前登录用户拦截失效的问题。 +3.修复定时任务目标字符串规则校验不全的问题。 +4.修复执行单次任务时会覆盖已启用任务的问题。 +5.修复TopNav无法正确获取active的问题。 +6.修复个人中心特殊字符密码修改失败问题。 + +### 代码优化 + +1.优化导出方法。 +2.参数键值更换为多行文本。 +3.优化日志中操作方法显示。 +4.优化日志装饰器获取核心参数的方式。 +5.修改主题样式本地读取。 +6.用户管理过滤掉已禁用部门。 +7.优化菜单管理切换Mini布局错乱问题。 +8.优化TopNav内链菜单点击没有高亮。 +9.ResponseUtil补充完整参数。 + +## RuoYi-Vue-FastAPI v1.5.1 + +### 新增功能 + +1.定时任务新增支持调用异步函数。 + +### 代码优化 + +1.校检文件名是否包含特殊字符。 +2.移除已弃用的log_decorator装饰器。 + +## RuoYi-Vue-FastAPI v1.5.0 + +### 项目依赖 + +#### 前端 + +1.升级quill版本至2.0.2。 + +### 新增功能 + +1.新增对PostgreSQL数据库的支持。 + +### 代码回滚 + +1.因fastapi查询参数模型底层存在bug,回滚查询参数模型声明方式为as_query。 + +### 代码优化 + +1.优化CamelCaseUtil和SnakeCaseUtil以兼容更多转换场景。 +2.优化列表查询排序。 +3.优化上传图片带域名时不增加前缀。 + +## RuoYi-Vue-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-Vue-FastAPI v1.3.3 + +### 项目依赖 + +#### 后端 + +1.更新pydantic-validation-decorator版本为0.1.4,修复了一些底层bug。 + +### BUG修复 + +1.修复在线用户模块条件查询无效的问题。 + +### 代码优化 + +1.优化在线用户模块前后端字段描述一致。 +2.日志装饰器异常处理增加logger打印日志。 + +## RuoYi-Vue-FastAPI v1.3.2 + +### 新增功能 + +1.新增gzip压缩中间件。 + +### BUG修复 + +1.修复分页函数计算has_next错误的问题。 +2.修复定时任务监听函数中事件没有job_id报错的问题。 + +### 代码优化 + +1.优化添加中间件函数注释。 + +## RuoYi-Vue-FastAPI v1.3.1 + +### BUG修复 + +1.修复1.3.0版本采用新的异常处理机制后日志装饰器无法记录异常日志的问题。 + +### 代码优化 + +1.补充定时任务违规字符串。 + +## RuoYi-Vue-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.修复其他已知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-Vue-FastAPI v1.2.2 + +### BUG修复 + +1.修复删除定时任务时未移除调度中任务的问题。 +2.修复菜单生成路由时组件条件判断错误的问题。 + +## RuoYi-Vue-FastAPI v1.2.1 + +### BUG修复 + +1.修复各模块新增数据时创建时间记录异常的问题。 +2.修复菜单挂载到根目录时路由加载异常等一系列相关问题。 + +### 代码及性能优化 + +1.修改代理localhost为127.0.0.1以适配部分设备解析localhost异常的问题。 + +## RuoYi-Vue-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-Vue-FastAPI v1.1.3 + +### 项目依赖 + +#### 前端 + +1.前端更新compressionPlugin到6.1.2以兼容node18+。 + +### 新增功能 + +1.用户密码新增非法字符验证。 + +### BUG修复 + +1.修复通知公告列表查询前后端字段不一致的问题。 +2.修复个人中心修改基本资料后端异常的问题。 + +## RuoYi-Vue-FastAPI v1.1.2 + +### 新增功能 + +1.配置文件新增数据库连接池相关配置 #3。 + +### BUG修复 + +1.修复个人中心修改密码后端异常的问题 #3。 + +### 代码及性能优化 + +1.使用@lru_cache缓存ip归属区域查询结果,避免重复调用ip归属区域查询接口以优化性能。 + +## RuoYi-Vue-FastAPI v1.1.1 + +### BUG修复 + +1.修复编辑定时任务时更新的信息未同步至scheduler的问题。 +2.修复编辑角色数据权限时后端异常的问题。 +3.修复菜单配置路由参数不生效的问题。 +4.修复获取路由信息时菜单排序不生效的问题。 +5.修复添加菜单时是否外链和是否缓存回显异常的问题。 + +## RuoYi-Vue-FastAPI v1.1.0 + +### 新增功能 + +1.后端配置文件新增sqlalchemy日志开关配置。 +2.后端配置文件新增IP归属区域查询开关配置。 +3.后端配置文件新增账号同时登录开关配置。 + +### BUG修复 + +1.修复token本身过期时退出登录接口异常的问题。 +2.修复系统版本号或浏览器版本号无法获取时登录异常的问题。 + +## RuoYi-Vue-FastAPI v1.0.3 + +### 新增功能 + +1.账号密码登录新增IP黑名单校验。 + +### BUG修复 + +1.修复外链菜单无法打开的问题 #I95LBY。 +2.修复添加和编辑菜单页面中是否缓存和是否外链字段回显异常的问题 #I95LDI。 + +## RuoYi-Vue-FastAPI v1.0.2 + +### 新增功能 + +1.用户接口权限校验增加列表接收参数,实现同一接口支持多个权限标识校验。 +2.新增按角色校验接口权限依赖 + +### BUG修复 + +1.修复用户管理和部门管理模块数据权限异常的问题。 + +### 代码及性能优化 + +1.调整参数设置、部门管理、字典管理、定时任务、日志管理、角色管理、菜单管理模块部分接口权限标识。 + +## RuoYi-Vue-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-Vue-FastAPI v1.0.0 + +RuoYi-Vue-FastAPI第一个版本发布啦! +此版本功能如下: +1.用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2.角色管理:角色菜单权限分配。 +3.菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +4.部门管理:配置系统组织机构(公司、部门、小组)。 +5.岗位管理:配置系统用户所属担任职务。 +6.字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7.参数管理:对系统动态配置常用参数。 +8.通知公告:系统通知公告信息发布维护。 +9.操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10.登录日志:系统登录日志记录查询包含登录异常。 +11.在线用户:当前系统中活跃用户状态监控。 +12.定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13.服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +14.缓存监控:对系统的缓存信息查询,命令统计等。 +15.在线构建器:拖动表单元素生成相应的HTML代码。 +16.系统接口:根据业务代码自动生成相关的api接口文档。 diff --git a/README.md b/README.md index 188eb2f5b74985dc767f1748d7b5ea5ed9dd9201..10c53235231d194ba46c207e4fcd3c324c78fcc5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@

logo

-

RuoYi-Vue-FastAPI v1.7.1

+

RuoYi-Vue-FastAPI v1.8.0

基于RuoYi-Vue+FastAPI前后端分离的快速开发框架

- + diff --git a/docker-compose.my.yml b/docker-compose.my.yml new file mode 100644 index 0000000000000000000000000000000000000000..d54b0f29d8abf3e4a4633c7deda7669a9f17f3fb --- /dev/null +++ b/docker-compose.my.yml @@ -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 \ No newline at end of file diff --git a/docker-compose.pg.yml b/docker-compose.pg.yml new file mode 100644 index 0000000000000000000000000000000000000000..a8247da8d38019e3968118740d712af0c590cc37 --- /dev/null +++ b/docker-compose.pg.yml @@ -0,0 +1,66 @@ +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 + - ruoyi-redis + 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 + +# 网络配置 +networks: + ruoyi-network: + name: ruoyi-network + driver: bridge \ No newline at end of file diff --git a/ruoyi-fastapi-backend/.env.dev b/ruoyi-fastapi-backend/.env.dev index a792ea9629d86fd669023b8e6c04b036e1c361b9..db037c6bd5af3788f60b2524eae52db75ee212cb 100644 --- a/ruoyi-fastapi-backend/.env.dev +++ b/ruoyi-fastapi-backend/.env.dev @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.7.1' +APP_VERSION= '1.8.0' # 应用是否开启热重载 APP_RELOAD = true # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/.env.dockermy b/ruoyi-fastapi-backend/.env.dockermy new file mode 100644 index 0000000000000000000000000000000000000000..dc555220f73fe9afe99494f6b6f947c383225928 --- /dev/null +++ b/ruoyi-fastapi-backend/.env.dockermy @@ -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.0' +# 应用是否开启热重载 +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 \ No newline at end of file diff --git a/ruoyi-fastapi-backend/.env.dockerpg b/ruoyi-fastapi-backend/.env.dockerpg new file mode 100644 index 0000000000000000000000000000000000000000..6b1f31d9592426af492507f15d17f91e6dae5265 --- /dev/null +++ b/ruoyi-fastapi-backend/.env.dockerpg @@ -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.0' +# 应用是否开启热重载 +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 \ No newline at end of file diff --git a/ruoyi-fastapi-backend/.env.prod b/ruoyi-fastapi-backend/.env.prod index e932e397474a496dea499e496ce9368069dfb574..000682123b5aa2501341590dfe984cfa35c8a894 100644 --- a/ruoyi-fastapi-backend/.env.prod +++ b/ruoyi-fastapi-backend/.env.prod @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.7.1' +APP_VERSION= '1.8.0' # 应用是否开启热重载 APP_RELOAD = false # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/Dockerfile.my b/ruoyi-fastapi-backend/Dockerfile.my new file mode 100644 index 0000000000000000000000000000000000000000..0b308473b360287cf2a58769936d8366fc6f3606 --- /dev/null +++ b/ruoyi-fastapi-backend/Dockerfile.my @@ -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"] \ No newline at end of file diff --git a/ruoyi-fastapi-backend/Dockerfile.pg b/ruoyi-fastapi-backend/Dockerfile.pg new file mode 100644 index 0000000000000000000000000000000000000000..b01b03e5bd3df9e2d0f00055d7d159f88ab1f9a2 --- /dev/null +++ b/ruoyi-fastapi-backend/Dockerfile.pg @@ -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"] \ No newline at end of file diff --git a/ruoyi-fastapi-backend/alembic/env.py b/ruoyi-fastapi-backend/alembic/env.py index 98828e63be62774254d792119f838947dd6bb62f..88469e0e8b6e619cdabde7dfe93427f79e3a5635 100644 --- a/ruoyi-fastapi-backend/alembic/env.py +++ b/ruoyi-fastapi-backend/alembic/env.py @@ -1,13 +1,18 @@ import asyncio import os -from alembic import context +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 Base, ASYNC_SQLALCHEMY_DATABASE_URL -from utils.import_util import ImportUtil +from config.database import ASYNC_SQLALCHEMY_DATABASE_URL, Base +from utils.import_util import ImportUtil # 判断vesrions目录是否存在,如果不存在则创建 alembic_veresions_path = 'alembic/versions' @@ -60,7 +65,11 @@ def run_migrations_offline() -> None: def do_run_migrations(connection: Connection) -> None: - def process_revision_directives(context, revision, directives): + def process_revision_directives( + context: MigrationContext, + revision: Union[str, Iterable[Optional[str]], Iterable[str]], + directives: list[MigrationScript], + ) -> None: script = directives[0] # 检查所有操作集是否为空 diff --git a/ruoyi-fastapi-backend/app.py b/ruoyi-fastapi-backend/app.py index 1ee7695527ae397af30eec9d905ac7410da05295..2d2072dab8e94de7aaf3e37037c6a96760f86ec9 100644 --- a/ruoyi-fastapi-backend/app.py +++ b/ruoyi-fastapi-backend/app.py @@ -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( diff --git a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py b/ruoyi-fastapi-backend/common/annotation/log_annotation.py similarity index 41% rename from ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py rename to ruoyi-fastapi-backend/common/annotation/log_annotation.py index f7e938cf8fe54de1ef1314359ee2737ed01f7caa..59a0637909b6bd9de745543985ba7e58803ae7ee 100644 --- a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py +++ b/ruoyi-fastapi-backend/common/annotation/log_annotation.py @@ -1,25 +1,33 @@ -import httpx import inspect import json -import os import time -from async_lru import alru_cache +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 functools import wraps from sqlalchemy.ext.asyncio import AsyncSession -from typing import Any, Callable, Literal, Optional +from starlette.status import HTTP_200_OK +from typing_extensions import ParamSpec from user_agents import parse -from config.enums import BusinessType + +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 module_admin.service.login_service import LoginService +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: """ @@ -31,7 +39,7 @@ class Log: title: str, business_type: BusinessType, log_type: Optional[Literal['login', 'operation']] = 'operation', - ): + ) -> None: """ 日志装饰器 @@ -43,80 +51,40 @@ class Log: self.title = title self.business_type = business_type.value self.log_type = log_type + self._oper_param_len = 2000 - def __call__(self, func): + def __call__(self, func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]: @wraps(func) - async def wrapper(*args, **kwargs): + async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: start_time = time.perf_counter() - # 获取被装饰函数的文件路径 - 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__}()' + 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) - token = request.headers.get('Authorization') + 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 - 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 + # 获取操作类型 + operator_type = self._get_oper_type(user_agent) # 获取请求的url oper_url = request.url.path - # 获取请求的ip及ip归属区域 + # 获取请求ip oper_ip = request.headers.get('X-Forwarded-For') - oper_location = '内网IP' - if AppConfig.app_ip_location_query: - oper_location = await 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) + # 获取请求ip归属区域 + oper_location = await self._get_oper_location(oper_ip) + # 获取请求参数 + oper_param = await self._get_request_params(request) # 日志表请求参数字段长度最大为2000,因此在此处判断长度 - if len(oper_param) > 2000: + if len(oper_param) > self._oper_param_len: 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 + login_log = self._get_login_log(user_agent, oper_ip, oper_location, oper_time, kwargs) try: # 调用原始函数 result = await func(*args, **kwargs) @@ -132,35 +100,12 @@ class Log: # 获取请求耗时 cost_time = float(time.perf_counter() - start_time) * 1000 # 判断请求是否来自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 - ) + request_from_swagger, request_from_redoc = self._is_request_from_swagger_or_redoc(request) # 根据响应结果的类型使用不同的方法获取响应结果参数 - 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': '获取失败'} + result_dict = self._get_result_dict(result, request_from_swagger, request_from_redoc) 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') + status, error_msg = self._get_status_and_error_msg(result_dict) # 根据日志类型向对应的日志表插入数据 if self.log_type == 'login': # 登录请求来自于api文档时不记录登录日志,其余情况则记录 @@ -168,15 +113,18 @@ class Log: 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') + 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 = await LoginService.get_current_user(request, token, query_db) + 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( @@ -203,9 +151,203 @@ class Log: 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): +async def get_ip_location(oper_ip: str) -> str: """ 查询ip归属区域 @@ -214,11 +356,11 @@ async def get_ip_location(oper_ip: str): """ oper_location = '内网IP' try: - if oper_ip != '127.0.0.1' and oper_ip != 'localhost': + 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 == 200: + 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: @@ -229,7 +371,7 @@ async def get_ip_location(oper_ip: str): return oper_location -def get_function_parameters_name_by_type(func: Callable, param_type: Any): +def get_function_parameters_name_by_type(func: Callable, param_type: Any) -> list: """ 获取函数指定类型的参数名称 @@ -241,13 +383,23 @@ def get_function_parameters_name_by_type(func: Callable, param_type: Any): parameters = inspect.signature(func).parameters # 找到指定类型的参数名称 parameters_name_list = [] + # 遍历所有参数 for name, param in parameters.items(): - if param.annotation == param_type: + # 处理参数注解 + 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): +def get_function_parameters_value_by_name(func: Callable, name: str, *args, **kwargs) -> Any: """ 获取函数指定参数的值 diff --git a/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py b/ruoyi-fastapi-backend/common/annotation/pydantic_annotation.py similarity index 75% rename from ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py rename to ruoyi-fastapi-backend/common/annotation/pydantic_annotation.py index 11e8d7ff8ffdd7675521e2abe9e5abc003edd0ef..9dde679c3a8fa9a6dddd13880c092125f2d09f97 100644 --- a/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py +++ b/ruoyi-fastapi-backend/common/annotation/pydantic_annotation.py @@ -1,21 +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, TypeVar +if TYPE_CHECKING: + from pydantic.fields import FieldInfo BaseModelVar = TypeVar('BaseModelVar', bound=BaseModel) -def as_query(cls: Type[BaseModelVar]) -> Type[BaseModelVar]: +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( @@ -36,24 +38,24 @@ def as_query(cls: Type[BaseModelVar]) -> Type[BaseModelVar]: ) ) - 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[BaseModelVar]) -> Type[BaseModelVar]: +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( @@ -74,11 +76,11 @@ def as_form(cls: Type[BaseModelVar]) -> Type[BaseModelVar]: ) ) - 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 diff --git a/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py b/ruoyi-fastapi-backend/common/aspect/data_scope.py similarity index 72% rename from ruoyi-fastapi-backend/module_admin/aspect/data_scope.py rename to ruoyi-fastapi-backend/common/aspect/data_scope.py index 5a7afbb6d78f3695b3a1dfac9517d1f29ded957e..b4113f5684b4a7ee216cd7882e1c825be626a7bf 100644 --- a/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py +++ b/ruoyi-fastapi-backend/common/aspect/data_scope.py @@ -1,7 +1,9 @@ -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 + +from fastapi import Depends, Request, params + +from common.context import RequestContext +from utils.dependency_util import DependencyUtil class GetDataScope: @@ -21,7 +23,7 @@ class GetDataScope: db_alias: Optional[str] = 'db', user_alias: Optional[str] = 'user_id', dept_alias: Optional[str] = 'dept_id', - ): + ) -> None: """ 获取当前用户数据权限对应的查询sql语句 @@ -35,7 +37,9 @@ class GetDataScope: self.user_alias = user_alias self.dept_alias = dept_alias - def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + def __call__(self, request: Request) -> str: + 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 = [ @@ -46,7 +50,7 @@ class GetDataScope: 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 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" @@ -70,6 +74,24 @@ class GetDataScope: else: param_sql_list.append('1 == 0') param_sql_list = list(dict.fromkeys(param_sql_list)) - param_sql = f"or_({', '.join(param_sql_list)})" + param_sql = f'or_({", ".join(param_sql_list)})' return param_sql + + +def DataScopeDependency( # noqa: N802 + query_alias: Optional[str] = '', + db_alias: Optional[str] = 'db', + user_alias: Optional[str] = 'user_id', + dept_alias: Optional[str] = 'dept_id', +) -> params.Depends: + """ + 当前用户数据权限依赖 + + :param query_alias: 所要查询表对应的sqlalchemy模型名称,默认为'' + :param db_alias: orm对象别名,默认为'db' + :param user_alias: 用户id字段别名,默认为'user_id' + :param dept_alias: 部门id字段别名,默认为'dept_id' + :return: 当前用户数据权限依赖 + """ + return Depends(GetDataScope(query_alias, db_alias, user_alias, dept_alias)) diff --git a/ruoyi-fastapi-backend/common/aspect/db_seesion.py b/ruoyi-fastapi-backend/common/aspect/db_seesion.py new file mode 100644 index 0000000000000000000000000000000000000000..3c88fb9e49ea7be66b9135f7ed10773680967ee8 --- /dev/null +++ b/ruoyi-fastapi-backend/common/aspect/db_seesion.py @@ -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) diff --git a/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py b/ruoyi-fastapi-backend/common/aspect/interface_auth.py similarity index 40% rename from ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py rename to ruoyi-fastapi-backend/common/aspect/interface_auth.py index 8f8349dee84dfee54fcb5e4bab471df2ce5f5ffb..f8f286dbedb58263c8c22a76ee22c280d3e697b0 100644 --- a/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py +++ b/ruoyi-fastapi-backend/common/aspect/interface_auth.py @@ -1,8 +1,10 @@ -from fastapi import Depends -from typing import List, Union +from typing import Union + +from fastapi import Depends, Request, params + +from common.context import RequestContext from exceptions.exception import PermissionException -from module_admin.entity.vo.user_vo import CurrentUserModel -from module_admin.service.login_service import LoginService +from utils.dependency_util import DependencyUtil class CheckUserInterfaceAuth: @@ -10,7 +12,7 @@ class CheckUserInterfaceAuth: 校验当前用户是否具有相应的接口权限 """ - def __init__(self, perm: Union[str, List], is_strict: bool = False): + def __init__(self, perm: Union[str, list], is_strict: bool = False) -> None: """ 校验当前用户是否具有相应的接口权限 @@ -20,20 +22,22 @@ class CheckUserInterfaceAuth: self.perm = perm self.is_strict = is_strict - def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + 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): - if self.perm 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 - else: - if any([perm_str in user_auth_list for perm_str in self.perm]): + 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='该用户无此接口权限') @@ -42,7 +46,7 @@ class CheckRoleInterfaceAuth: 根据角色校验当前用户是否具有相应的接口权限 """ - def __init__(self, role_key: Union[str, List], is_strict: bool = False): + def __init__(self, role_key: Union[str, list], is_strict: bool = False) -> None: """ 根据角色校验当前用户是否具有相应的接口权限 @@ -52,17 +56,41 @@ class CheckRoleInterfaceAuth: self.role_key = role_key self.is_strict = is_strict - def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + 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): - if self.role_key in user_role_key_list: - return True + 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 - else: - if any([role_key_str in user_role_key_list for role_key_str in self.role_key]): + 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)) diff --git a/ruoyi-fastapi-backend/common/aspect/pre_auth.py b/ruoyi-fastapi-backend/common/aspect/pre_auth.py new file mode 100644 index 0000000000000000000000000000000000000000..b3c1856f685290a48f1acce830f32572ac9a4e52 --- /dev/null +++ b/ruoyi-fastapi-backend/common/aspect/pre_auth.py @@ -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) diff --git a/ruoyi-fastapi-backend/config/constant.py b/ruoyi-fastapi-backend/common/constant.py similarity index 99% rename from ruoyi-fastapi-backend/config/constant.py rename to ruoyi-fastapi-backend/common/constant.py index eb77464df2d9fe3ae54a2a2b1d5203c5eb45b699..da70e157d1b2f98a3c611414dc0202f8b74e782c 100644 --- a/ruoyi-fastapi-backend/config/constant.py +++ b/ruoyi-fastapi-backend/common/constant.py @@ -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://' diff --git a/ruoyi-fastapi-backend/common/context.py b/ruoyi-fastapi-backend/common/context.py new file mode 100644 index 0000000000000000000000000000000000000000..0e325a3f706965bbb38c9ce9e9d8a117ae657456 --- /dev/null +++ b/ruoyi-fastapi-backend/common/context.py @@ -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) diff --git a/ruoyi-fastapi-backend/config/enums.py b/ruoyi-fastapi-backend/common/enums.py similarity index 91% rename from ruoyi-fastapi-backend/config/enums.py rename to ruoyi-fastapi-backend/common/enums.py index 0df623899fb1ff67e136f329bc8dd793348b3c6a..b6c3fda93e9d6ca51c4d518af8499bc2eee84cd6 100644 --- a/ruoyi-fastapi-backend/config/enums.py +++ b/ruoyi-fastapi-backend/common/enums.py @@ -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': '登录令牌信息'} diff --git a/ruoyi-fastapi-backend/common/router.py b/ruoyi-fastapi-backend/common/router.py new file mode 100644 index 0000000000000000000000000000000000000000..5e413cd836ae4c897960f410d1c8c1f80fc97ab4 --- /dev/null +++ b/ruoyi-fastapi-backend/common/router.py @@ -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() diff --git a/ruoyi-fastapi-backend/common/vo.py b/ruoyi-fastapi-backend/common/vo.py new file mode 100644 index 0000000000000000000000000000000000000000..5a9b49ed9c5b5fc1a8f1d99843fac03f4696b95c --- /dev/null +++ b/ruoyi-fastapi-backend/common/vo.py @@ -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='响应数据') diff --git a/ruoyi-fastapi-backend/config/database.py b/ruoyi-fastapi-backend/config/database.py index 006b6a53dbc66f4c1b0331635cbc9849ee27b293..72e0ce45d7d320b82ad0801048602c30420e8962 100644 --- a/ruoyi-fastapi-backend/config/database.py +++ b/ruoyi-fastapi-backend/config/database.py @@ -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 = ( diff --git a/ruoyi-fastapi-backend/config/env.py b/ruoyi-fastapi-backend/config/env.py index 3a2b3af19ef8b3c0146d2ea0a14c1c5cc40e8b87..153b7a178cc3c2aee3549fd440cc30c2fe133970 100644 --- a/ruoyi-fastapi-backend/config/env.py +++ b/ruoyi-fastapi-backend/config/env.py @@ -2,11 +2,11 @@ import argparse import configparser import os import sys +from typing import Literal + from dotenv import load_dotenv -from functools import lru_cache from pydantic import computed_field from pydantic_settings import BaseSettings -from typing import Literal class AppSettings(BaseSettings): @@ -86,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) @@ -130,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): @@ -151,59 +151,53 @@ 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: """ 解析命令行参数 """ diff --git a/ruoyi-fastapi-backend/config/get_db.py b/ruoyi-fastapi-backend/config/get_db.py index e5930c7d11843e3362867dd4cf235714164260f4..16df0ab29053935d8ddc48079ff80be6e4931a11 100644 --- a/ruoyi-fastapi-backend/config/get_db.py +++ b/ruoyi-fastapi-backend/config/get_db.py @@ -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,7 +16,7 @@ async def get_db(): yield current_db -async def init_create_table(): +async def init_create_table() -> None: """ 应用启动时初始化数据库连接 diff --git a/ruoyi-fastapi-backend/config/get_redis.py b/ruoyi-fastapi-backend/config/get_redis.py index ee4b6bd37f3541169a9e28e04e3eccfbd08c02ce..e4777848855654179aff7d5dd7140316e54f0d72 100644 --- a/ruoyi-fastapi-backend/config/get_redis.py +++ b/ruoyi-fastapi-backend/config/get_redis.py @@ -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 @@ -37,14 +40,14 @@ class RedisUtil: logger.error('❌️ redis连接失败') except AuthenticationError as e: logger.error(f'❌️ redis用户名或密码错误,详细错误信息:{e}') - except TimeoutError as e: + except RedisTimeoutError as e: logger.error(f'❌️ redis连接超时,详细错误信息:{e}') except RedisError as 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连接 @@ -55,7 +58,7 @@ class RedisUtil: 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: """ 应用启动时缓存参数配置表 diff --git a/ruoyi-fastapi-backend/config/get_scheduler.py b/ruoyi-fastapi-backend/config/get_scheduler.py index fadcf02e849803dbeeee30535e7a4d1521040c9f..204adb476e9e562849230eb0ffda06e6a0b2e3b0 100644 --- a/ruoyi-fastapi-backend/config/get_scheduler.py +++ b/ruoyi-fastapi-backend/config/get_scheduler.py @@ -1,7 +1,12 @@ import json -from apscheduler.events import EVENT_ALL +from asyncio import iscoroutinefunction +from datetime import datetime, timedelta +from typing import 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 +14,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 +44,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 +56,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,19 +71,17 @@ 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: - return previous_day.day - else: - diff += 1 + diff = 1 + while True: + previous_day = date - timedelta(days=diff) + if previous_day.weekday() < cls.WEEKDAY_COUNT: + return previous_day.day + diff += 1 SQLALCHEMY_DATABASE_URL = ( @@ -100,18 +102,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,7 +126,7 @@ class SchedulerUtil: """ @classmethod - async def init_system_scheduler(cls): + async def init_system_scheduler(cls) -> None: """ 应用启动时初始化定时任务 @@ -142,7 +143,7 @@ class SchedulerUtil: logger.info('✅️ 系统初始定时任务加载成功') @classmethod - async def close_system_scheduler(cls): + async def close_system_scheduler(cls) -> None: """ 应用关闭时关闭定时任务 @@ -152,7 +153,7 @@ class SchedulerUtil: logger.info('✅️ 关闭定时任务成功') @classmethod - def get_scheduler_job(cls, job_id: Union[str, int]): + def get_scheduler_job(cls, job_id: Union[str, int]) -> Job: """ 根据任务id获取任务对象 @@ -164,7 +165,7 @@ class SchedulerUtil: return query_job @classmethod - def add_scheduler_job(cls, job_info: JobModel): + def add_scheduler_job(cls, job_info: JobModel) -> None: """ 根据输入的任务对象信息添加任务 @@ -183,14 +184,14 @@ class SchedulerUtil: 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: """ 根据输入的任务对象执行一次任务 @@ -212,14 +213,14 @@ class SchedulerUtil: 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移除任务 @@ -231,7 +232,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__ # 获取任务执行异常信息 @@ -260,7 +261,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, diff --git a/ruoyi-fastapi-backend/exceptions/exception.py b/ruoyi-fastapi-backend/exceptions/exception.py index b86f50d01aa2282f3dedebdeceb3992930cf4c3c..effaa9e7a04751febcde955926963a476e38ca73 100644 --- a/ruoyi-fastapi-backend/exceptions/exception.py +++ b/ruoyi-fastapi-backend/exceptions/exception.py @@ -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 diff --git a/ruoyi-fastapi-backend/exceptions/handle.py b/ruoyi-fastapi-backend/exceptions/handle.py index dec516a7e36683cb55f5a84a9477cca4464ddbe3..3e8e590ded0159b4b9a8d33f8debde2dd3e95fca 100644 --- a/ruoyi-fastapi-backend/exceptions/handle.py +++ b/ruoyi-fastapi-backend/exceptions/handle.py @@ -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)) diff --git a/ruoyi-fastapi-backend/middlewares/context_middleware.py b/ruoyi-fastapi-backend/middlewares/context_middleware.py new file mode 100644 index 0000000000000000000000000000000000000000..89c905454ddffc33b525977376e4a1972f549f5a --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/context_middleware.py @@ -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) diff --git a/ruoyi-fastapi-backend/middlewares/cors_middleware.py b/ruoyi-fastapi-backend/middlewares/cors_middleware.py index 55508e774f15d12ab4a599f954ec27d62240bca5..e82ca9c4d8a306513abc7cc7dd82b5959dcf8294 100644 --- a/ruoyi-fastapi-backend/middlewares/cors_middleware.py +++ b/ruoyi-fastapi-backend/middlewares/cors_middleware.py @@ -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: """ 添加跨域中间件 diff --git a/ruoyi-fastapi-backend/middlewares/gzip_middleware.py b/ruoyi-fastapi-backend/middlewares/gzip_middleware.py index eb371ceabbe0d7094cf26ec4751d90d99c4c5d04..867b7864a8d21fd478f311e1f5722230d94d8374 100644 --- a/ruoyi-fastapi-backend/middlewares/gzip_middleware.py +++ b/ruoyi-fastapi-backend/middlewares/gzip_middleware.py @@ -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压缩中间件 diff --git a/ruoyi-fastapi-backend/middlewares/handle.py b/ruoyi-fastapi-backend/middlewares/handle.py index abb2d0d1216fb5252c6748a97446203691b36122..235bc6ad05309b79fe7bd8769062e8fc4c029c40 100644 --- a/ruoyi-fastapi-backend/middlewares/handle.py +++ b/ruoyi-fastapi-backend/middlewares/handle.py @@ -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压缩中间件 diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py index 76f8d8557c64506f48bbbd5cc3f0666edea94655..c2ea302676934a25b70c2d6b6caf9109cb12775d 100644 --- a/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py @@ -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中间件 diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py index 558a5c9345e9084c89dd384fad5ace86cc746dba..4f9800841d5c169f96aac251f459779d46a109fb 100644 --- a/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py @@ -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() diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py index a071692af7b9837821773f4794a6beae5f52505b..42cd78cc4af200231044b8b8bc27911eec919fda 100644 --- a/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py @@ -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 diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py index 1e38eab1020987b7be46a23bd78a7c110d345c2a..ef6e46f5ba2bc086d513033ab3b8de7faa4273e0 100644 --- a/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py @@ -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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py b/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py index 9e72713e1f41b938919eb3e66f1e0ecc39302a74..e1f04e3900eb99a0ac24f8bbc178901dcac7a59f 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py @@ -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)]) +cache_controller = APIRouterPro( + prefix='/monitor/cache', order_num=15, tags=['系统监控-缓存监控'], dependencies=[PreAuthDependency()] +) -@cacheController.get( - '', response_model=CacheMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +@cache_controller.get( + '', + summary='获取缓存监控信息接口', + description='用于获取缓存监控信息', + response_model=DataResponseModel[CacheMonitorModel], + dependencies=[UserInterfaceAuthDependency('monitor:cache:list')], ) -async def get_monitor_cache_info(request: Request): +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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py b/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py index 83d35b817e1f66eb8e28d518e69c30251d867eab..e7f6cb5811be25c280f56d22c111fd88faa4055c 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py @@ -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 - -captchaController = APIRouter() +captcha_controller = APIRouterPro(order_num=2, tags=['验证码模块']) -@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() diff --git a/ruoyi-fastapi-backend/module_admin/controller/common_controller.py b/ruoyi-fastapi-backend/module_admin/controller/common_controller.py index d2fd621679886d812384cf4f658e1b9320dc9005..9e482527dccbdc01bbd69da3236b5db92d0fc6b1 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/common_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/common_controller.py @@ -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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py index 747dff2b583d3f853b67d4e5dbfa1b17b1147365..dddd29bc76136d0869986ab201d53b4cd10abd10 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py @@ -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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py index 29432069d955dd5881046099f5f278b8abe476c7..6aec56d51518bff617442d4bde4d5750bdc8f045 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py @@ -1,35 +1,42 @@ 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.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.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[str, 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 +44,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[str, 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 +85,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[str, 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 +114,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[str, 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 +143,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[str, 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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py index 27fa24cef71fd98f20f23c9bd722939e1dd6c168..f5b029784a8a8f141b32bdad527253a4981c5bcb 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py @@ -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 diff --git a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py index c930569daf8f20c78ee458b5b89bef265c5371a0..95891e6023cc2617784a705ad194287e1e57a4a2 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py @@ -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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/log_controller.py b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py index a01ba94617a074926d657d5c8da8cb5e0b930a46..fde3f9d6a9a5d0ff65167fab45b35c5be8738c69 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/log_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py @@ -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 diff --git a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py index 1a3c198aeecffcef95a9db0b0f3b47ff58e199c6..7262d2cab2cf7f387fd5d7c6b83c30ce7d42f3d7 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py @@ -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 +login_controller = APIRouterPro(order_num=1, tags=['登录模块']) -loginController = APIRouter() - -@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,8 +162,13 @@ 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} ) diff --git a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py index 0e3124ec8fcb762c5c840e6027b26c07d72c4fef..d5d8ffced970c56499e26ecb14e4117958c38133 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py @@ -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}的信息成功') diff --git a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py index e1e4aa169ec1c1f80a6e4510c6e82d3ec51706b1..ff904a6cdcb16b8d104b03dd3386302062461e63 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py @@ -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}的信息成功') diff --git a/ruoyi-fastapi-backend/module_admin/controller/online_controller.py b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py index bf65c8f5176e491ba197801929361c4c7b341428..ac51647cc4867b1a97cd16415b59d14323426bd5 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/online_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py @@ -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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/post_controler.py b/ruoyi-fastapi-backend/module_admin/controller/post_controller.py similarity index 45% rename from ruoyi-fastapi-backend/module_admin/controller/post_controler.py rename to ruoyi-fastapi-backend/module_admin/controller/post_controller.py index e9c69c4d8fd18430cdd8f3cde9b24ac3095101c8..6a6312113ff0b32e1138711e3a707fdf3c9ab232 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/post_controler.py +++ b/ruoyi-fastapi-backend/module_admin/controller/post_controller.py @@ -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 module_admin.service.login_service import LoginService -from module_admin.service.post_service import PostService + +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.page_util import PageResponseModel from utils.response_util import ResponseUtil - -postController = APIRouter(prefix='/system/post', dependencies=[Depends(LoginService.get_current_user)]) +post_controller = APIRouterPro( + prefix='/system/post', order_num=7, tags=['系统管理-岗位管理'], dependencies=[PreAuthDependency()] +) -@postController.get( - '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:list'))] +@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: PostPageQueryModel = Depends(PostPageQueryModel.as_query), - query_db: AsyncSession = Depends(get_db), -): + 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('获取成功') @@ -34,15 +44,21 @@ async def get_system_post_list( return ResponseUtil.success(model_content=post_page_query_result) -@postController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:add'))]) +@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: AsyncSession = Depends(get_db), - current_user: CurrentUserModel = Depends(LoginService.get_current_user), -): + 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 @@ -53,15 +69,21 @@ async def add_system_post( return ResponseUtil.success(msg=add_post_result.message) -@postController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:edit'))]) +@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: AsyncSession = Depends(get_db), - current_user: CurrentUserModel = Depends(LoginService.get_current_user), -): + 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) @@ -70,9 +92,19 @@ async def edit_system_post( return ResponseUtil.success(msg=edit_post_result.message) -@postController.delete('/{post_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:post:remove'))]) +@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: str, query_db: AsyncSession = Depends(get_db)): +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) @@ -80,23 +112,45 @@ async def delete_system_post(request: Request, post_ids: str, query_db: AsyncSes return ResponseUtil.success(msg=delete_post_result.message) -@postController.get( - '/{post_id}', response_model=PostModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:query'))] +@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: int, query_db: AsyncSession = Depends(get_db)): +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) -@postController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:post:export'))]) +@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: PostPageQueryModel = Form(), - query_db: AsyncSession = Depends(get_db), -): + 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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py index d4ab5311759428344d3434986aaeac149bd069aa..70dcf606cd2b08cc8ce099d52f54a607d7728bc5 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py @@ -1,36 +1,54 @@ 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.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.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[str, 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 +56,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[str, 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 +77,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 +102,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[str, 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 +129,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[str, 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 +161,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[str, 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 +189,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[str, 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 +211,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[str, 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 +243,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[str, 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 +274,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[str, 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 +295,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[str, 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 +316,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[str, 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 +339,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) diff --git a/ruoyi-fastapi-backend/module_admin/controller/server_controller.py b/ruoyi-fastapi-backend/module_admin/controller/server_controller.py index f63fdf8010e1bd16dc228c5e3ad4a306100b5d97..c75468951a72f23597d2ab6c35441b4211c6068b 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/server_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/server_controller.py @@ -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)]) +server_controller = APIRouterPro( + prefix='/monitor/server', order_num=14, tags=['系统监控-服务监控'], dependencies=[PreAuthDependency()] +) -@serverController.get( - '', response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))] +@server_controller.get( + '', + summary='获取服务器监控信息接口', + description='用于获取当前服务器的监控信息', + response_model=DataResponseModel[ServerMonitorModel], + dependencies=[UserInterfaceAuthDependency('monitor:server:list')], ) -async def get_monitor_server_info(request: Request): +async def get_monitor_server_info(request: Request) -> Response: # 获取全量数据 server_info_query_result = await ServerService.get_server_monitor_info() logger.info('获取成功') diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py index 91d661130b078d37c5764cc2c4114b0df7aea09b..e14c4425ce0ebc9a0e216a46999979a08c523cc8 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -1,18 +1,26 @@ 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.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.vo.dept_vo import DeptModel, DeptTreeModel from module_admin.entity.vo.user_vo import ( AddUserModel, + AvatarModel, CrudUserRoleModel, CurrentUserModel, DeleteUserModel, @@ -26,41 +34,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[str, 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[str, 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 +90,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[str, DataScopeDependency('SysDept')], + role_data_scope_sql: Annotated[str, 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 +123,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[str, DataScopeDependency('SysUser')], + dept_data_scope_sql: Annotated[str, DataScopeDependency('SysDept')], + role_data_scope_sql: Annotated[str, 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 +156,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[str, 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,15 +188,21 @@ 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[str, 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) @@ -176,15 +220,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[str, 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) @@ -201,31 +251,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[str, 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) @@ -234,14 +297,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")}' @@ -253,8 +321,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}', @@ -265,18 +333,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, @@ -293,14 +366,19 @@ 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, @@ -315,17 +393,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[str, DataScopeDependency('SysUser')], + dept_data_scope_sql: Annotated[str, 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 ) @@ -334,22 +418,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[str, DataScopeDependency('SysUser')], +) -> Response: # 获取全量数据 user_query_result = await UserService.get_user_list_services( query_db, user_page_query, data_scope_sql, is_page=False @@ -360,12 +474,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 @@ -375,21 +495,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[str, DataScopeDependency('SysUser')], + role_data_scope_sql: Annotated[str, 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) diff --git a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py index 4f34a2fa05acfdc4604f6ecb88837b34f8b97c2e..a12781c46787ed29ff4e49da81ae06e5b7bcc332 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py @@ -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: """ 删除参数配置数据库操作 diff --git a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py index f45021008ebde852a92f9caf055957183409e6b8..8fc7581edd0539507252130671c1fe9eefe0cf9a 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py @@ -1,7 +1,10 @@ +from collections.abc import Sequence +from typing import Union + from sqlalchemy import bindparam, func, or_, select, update # noqa: F401 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 @@ -14,7 +17,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 +30,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 +47,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]: """ 根据部门参数获取部门信息 @@ -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: str + ) -> Sequence[SysDept]: """ 获取部门编辑对应的在用部门列表信息 @@ -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: str + ) -> Sequence[SysDept]: """ 获取所有在用部门列表信息 @@ -146,7 +153,7 @@ 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: str) -> Sequence[SysDept]: """ 根据查询参数获取部门列表信息 @@ -177,7 +184,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 +199,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 +210,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 +232,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 +243,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 +258,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 +277,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 +297,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查询查询部门下的用户数量 diff --git a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py index c5a8ed5c855c1717c1470d1ed8b952559936b7b4..60841d1c8d0852b3a31c2656f26037d703257234 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py @@ -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]: """ 根据字典类型查询字典类型关联的字典数据数量 diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py index fc30048c501e800431414131dc8e0d1e640c0e72..a628b7be5b26cdff30931188c133594ced40d233 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py @@ -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,7 +113,7 @@ class JobDao: return db_job @classmethod - async def edit_job_dao(cls, db: AsyncSession, job: dict, old_job: JobModel): + async def edit_job_dao(cls, db: AsyncSession, job: dict, old_job: JobModel) -> None: """ 编辑定时任务数据库操作 @@ -124,7 +133,7 @@ class JobDao: ) @classmethod - async def delete_job_dao(cls, db: AsyncSession, job: JobModel): + async def delete_job_dao(cls, db: AsyncSession, job: JobModel) -> None: """ 删除定时任务数据库操作 diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py index 586a3d61adc42338a79f03dc07d0b010207e3f15..5aa1e7efe11eb140eaf4c656fe9e38e1c6879c50 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py @@ -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: """ 清除定时任务日志数据库操作 diff --git a/ruoyi-fastapi-backend/module_admin/dao/log_dao.py b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py index 684f3d79434392d1eae65bf64b9a3dfd524b8ab6..48d4ceceaf70e16939de98c9267a8f9d900ca708 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/log_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py @@ -1,6 +1,10 @@ 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 @@ -14,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]]]: """ 根据查询参数获取操作日志列表信息 @@ -48,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: """ 新增操作日志数据库操作 @@ -68,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: """ 删除操作日志数据库操作 @@ -79,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: """ 清除操作日志数据库操作 @@ -95,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]]]: """ 根据查询参数获取登录日志列表信息 @@ -130,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: """ 新增登录日志数据库操作 @@ -150,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: """ 删除登录日志数据库操作 @@ -161,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: """ 清除登录日志数据库操作 diff --git a/ruoyi-fastapi-backend/module_admin/dao/login_dao.py b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py index 9764a4a0ef1d5ca901ec58f347697f932943a3e9..322c1c80b857eb8655b70d0073e1aeb1863a7f3f 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/login_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py @@ -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]: """ 根据用户名查询用户信息 diff --git a/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py index 19831e015255114a46b390330591ebfa6c11e613..634360637fa9a0f68c28f58e707a4b3ef256aba1 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py @@ -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查询菜单关联角色数量 diff --git a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py index 9f48a14c3422a9568e517296fcc023845f905928..2dcce961114c4d4a934e4557a6d0eb5597b05669 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py @@ -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: """ 删除通知公告数据库操作 diff --git a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py index 7d90088b607b63c48ea292b534a7f6703c296b8a..39ec9aa8d12a94b73d756fcd1ba5bb4112a700aa 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py @@ -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查询岗位关联的用户数量 diff --git a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py index 534c7f34bd28a537e2c273083ed6b2f211d72547..0f5d560e0e632ae3c1a438aee4854e86b2e402a8 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py @@ -1,9 +1,14 @@ +from collections.abc import Sequence from datetime import datetime, time +from typing import Any, Union + from sqlalchemy import and_, delete, desc, func, or_, select, update # noqa: F401 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]: """ 获取编辑页面对应的在用角色列表信息 @@ -127,7 +132,7 @@ class RoleDao: @classmethod async def get_role_list( cls, db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 根据查询参数获取角色列表信息 @@ -159,12 +164,14 @@ class RoleDao: .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查询角色关联用户数量 diff --git a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py index cc5356626e735045c91158e755d7a69a72060f98..f052c7f2f6f44e2c50c6aed13fbbd2d655f741cb 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py @@ -1,6 +1,11 @@ +from collections.abc import Sequence from datetime import datetime, time +from typing import Any, Union + from sqlalchemy import 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 @@ -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 - ): + ) -> Union[PageModel, list[list[dict[str, Any]]]]: """ 根据查询参数获取用户列表信息 @@ -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获取用户已分配的角色列表信息数据库操作 @@ -400,7 +409,7 @@ 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 - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 根据角色id获取已分配的用户列表信息 @@ -424,14 +433,16 @@ class UserDao: ) .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 - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 根据角色id获取未分配的用户列表信息 @@ -463,14 +474,14 @@ class UserDao: ) .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 +493,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 +504,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 +520,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 +543,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 +555,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 +566,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( diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py index 8a3235dba9be60c24bc9199c3c9e2af9e9de8f4e..0c40b8d55f9a1ec072b214b75e438854fbeafffc 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py @@ -1,5 +1,7 @@ from datetime import datetime + from sqlalchemy import CHAR, Column, DateTime, Integer, String + from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py index 48baf8d6695252bdd76058f005ab69624b5194f7..c745b56d2191d65f3cc70aec0325043cdad7edc0 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py @@ -1,5 +1,7 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py index de6a9c4cfb1c9503e6aa0725d1d8621bc5c0ca7b..37b2b7bf5ec8a02656fd269e5629d1f7f6507442 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py @@ -1,5 +1,7 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py index b966066340d14bfe9f98d7f5d19a52671335ea68..82ce77b80b3cb31b175fb9be2219167a5098ef4a 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py @@ -1,5 +1,7 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, Column, DateTime, DOUBLE, Index, LargeBinary, 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 @@ -65,9 +67,11 @@ class ApschedulerJobs(Base): __tablename__ = 'apscheduler_jobs' - id = Column(String(191), primary_key=True, nullable=False) + id = Column(Unicode(191), primary_key=True, nullable=False) next_run_time = Column( - DOUBLE, nullable=True, server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False) + 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) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py index 981b02652d0ee7348f7ae81d51f9da851c205ffb..298921c27d692a0e6a6ef479de4b01458a033c58 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py @@ -1,5 +1,7 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, Column, DateTime, Index, Integer, String + +from sqlalchemy import CHAR, BigInteger, Column, DateTime, Index, Integer, String + from config.database import Base @@ -38,7 +40,9 @@ class SysOperLog(Base): 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手机端用户)') + 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') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py index 0a8a836f20d5d3e1964e3460d4579619fe0e2af6..b7aa4c75d4c74b33a8eff5cdb8b54ba5eeef04ca 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py @@ -1,5 +1,7 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py index 0d6085417f76abd1921e1f2293d7a2883d0478d5..468f599151f818f03b2f7a6f5d904e2a49fec150 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py @@ -1,6 +1,8 @@ from datetime import datetime + 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py index 442d0f3cbae3c7c7dec77f1e63ce6401173669fc..5b185967667db0f5d7f1de25f6816aac43d4d904 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py @@ -1,5 +1,7 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py index fecf9d198b68aa9757bafd3eeb4878f510a91dfc..39b08661cd5350258c286b17653d9946c24e6eff 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, SmallInteger, 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py index c71b5b7a04c00e067a3188a987c7dd057f21838e..4d1cfe180b56677678c3c0ad59efd831ef7fa430 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py @@ -1,5 +1,7 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, Column, DateTime, 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py index 79b49fb8161d379253cafbc4d1c25164633248d9..dfd4669759205c9f86a6cea42f206e54a94bb243 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py @@ -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信息') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py index 258be5d76394ff7dad5ef627c22ac2cd28edee65..c589cd1b0d7125adfada5f4e3313dc2722cadb0c 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py @@ -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): diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py index 917d7d66f5b25b1c6556e744a5f7e94a3a8630b0..395c608de9860f898968975e5158d682d69dfbcf 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py @@ -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): """ 参数配置管理分页查询模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py index dcad1177156a0698b540a382549d5f77bd80084a..7b7afa8ee7f83e6eb7d78f9fd25d0bd1c59c7e73 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py @@ -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): """ 删除部门模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py index 3251f08c517d24cfc1aaa526e1ee567208c67477..3d9a463841c0a6891edbf82b29863d16c54f056f 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py @@ -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): """ 字典数据管理分页查询模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py index 960a83762b028f699b084c2faf1462774ad78360..732ba8e31144765c21b2ba707213a801ea83a393 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py @@ -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): """ 定时任务日志管理分页查询模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py index 739ad6cf566c76dfd522afd91595371d7b3bcf1b..3ad871a41f6d50030c3d8fb2f5f059e0b0f3576f 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py @@ -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): """ 登录日志管理分页查询模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py index fbd6e07bb3e8902819bfb00b34ee3850acb14280..c86758b609b8ed76f9778355ed9971ebcae3067e 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py @@ -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,8 +33,7 @@ class UserRegister(BaseModel): pattern = r"""^[^<>"'|\\]+$""" if self.password is None or re.match(pattern, self.password): return self - else: - raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') class Token(BaseModel): @@ -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='子路由') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py index 9dc8d75434f4c53a7f2473de1f1f0c9ed490ea74..7b5212a0dbc89a39b0dcb67a517841c73b838455 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py @@ -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): """ 删除菜单模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py index 0b5d70c3a2741cee21a36048ff27e61eb32b7210..155530ae5e6e9d40abc01e6b9895e8833f8c240b 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py @@ -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): """ 通知公告管理分页查询模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py index 11bb4758ace3962e3568617be9f0c0030f0ee22a..074b1bbc1ea0ea453ba5c0918c33720b59f348d2 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py @@ -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): """ 强退在线用户模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py index 79390c3bc6d77b4e3c36fee31bfb802c8d02ebdf..dcb4f3b8d64ff804d393d9800207b8ef515d2255 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py @@ -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): """ 岗位管理分页查询模型 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py index a81f8d51cf08f28e6c8b6ea3108c92c40952af4a..5c80dd03fe11145e23e01ae5d6e432e54f1ee37a 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py @@ -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='更新时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py index 810ecac34ce770f175241fb296fbff982b2ac022..52c63bf5f3c93c3659d60fd57170b731f269d768 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py @@ -1,6 +1,7 @@ +from typing import Optional + from pydantic import BaseModel, ConfigDict, Field from pydantic.alias_generators import to_camel -from typing import List, Optional class CpuInfo(BaseModel): @@ -64,4 +65,4 @@ class ServerMonitorModel(BaseModel): py: Optional[PyInfo] = Field(description='Python相关信息') mem: Optional[MemoryInfo] = Field(description='內存相关信息') sys: Optional[SysInfo] = Field(description='服务器相关信息') - sys_files: Optional[List[SysFiles]] = Field(description='磁盘相关信息') + sys_files: Optional[list[SysFiles]] = Field(description='磁盘相关信息') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py index 0230882ba29713b8a3fb3f242a1b67098e6dd35c..0ac0f4e829e56c6ea53e48c29ecec1c5b6ce8558 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -1,11 +1,12 @@ import re from datetime import datetime +from typing import Literal, Optional, Union + from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic.alias_generators import to_camel from pydantic_validation_decorator import Network, NotBlank, Size, Xss -from typing import List, Literal, Optional, Union + from exceptions.exception import ModelValidatorException -from module_admin.annotation.pydantic_annotation import as_query from module_admin.entity.vo.dept_vo import DeptModel from module_admin.entity.vo.post_vo import PostModel from module_admin.entity.vo.role_vo import RoleModel @@ -53,8 +54,7 @@ class UserModel(BaseModel): pattern = r"""^[^<>"'|\\]+$""" if self.password is None or re.match(pattern, self.password): return self - else: - raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') @model_validator(mode='after') def check_admin(self) -> 'UserModel': @@ -67,30 +67,38 @@ class UserModel(BaseModel): @Xss(field_name='user_name', message='用户账号不能包含脚本字符') @NotBlank(field_name='user_name', message='用户账号不能为空') @Size(field_name='user_name', min_length=0, max_length=30, message='用户账号长度不能超过30个字符') - def get_user_name(self): + def get_user_name(self) -> Union[str, None]: return self.user_name @Xss(field_name='nick_name', message='用户昵称不能包含脚本字符') @Size(field_name='nick_name', min_length=0, max_length=30, message='用户昵称长度不能超过30个字符') - def get_nick_name(self): + def get_nick_name(self) -> Union[str, None]: return self.nick_name @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 @Size(field_name='phonenumber', min_length=0, max_length=11, message='手机号码长度不能超过11个字符') - def get_phonenumber(self): + def get_phonenumber(self) -> Union[str, None]: return self.phonenumber - def validate_fields(self): + def validate_fields(self) -> None: self.get_user_name() self.get_nick_name() self.get_email() self.get_phonenumber() +class UserRowModel(UserModel): + """ + 用户列表行数据模型 + """ + + dept: Optional[DeptModel] = Field(default=None, description='部门信息') + + class UserRoleModel(BaseModel): """ 用户和角色关联表对应pydantic模型 @@ -117,14 +125,14 @@ class UserInfoModel(UserModel): post_ids: Optional[Union[str, None]] = Field(default=None, description='岗位ID信息') role_ids: Optional[Union[str, None]] = Field(default=None, description='角色ID信息') dept: Optional[Union[DeptModel, None]] = Field(default=None, description='部门信息') - role: Optional[List[Union[RoleModel, None]]] = Field(default=[], description='角色信息') + role: Optional[list[Union[RoleModel, None]]] = Field(default=[], description='角色信息') class CurrentUserModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - permissions: List = Field(description='权限信息') - roles: List = Field(description='角色信息') + permissions: list = Field(description='权限信息') + roles: list = Field(description='角色信息') user: Union[UserInfoModel, None] = Field(description='用户信息') is_default_modify_pwd: bool = Field(default=False, description='是否初始密码修改提醒') is_password_expired: bool = Field(default=False, description='密码是否过期提醒') @@ -138,10 +146,10 @@ class UserDetailModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel) data: Optional[Union[UserInfoModel, None]] = Field(default=None, description='用户信息') - post_ids: Optional[List] = Field(default=None, description='岗位ID信息') - posts: List[Union[PostModel, None]] = Field(description='岗位信息') - role_ids: Optional[List] = Field(default=None, description='角色ID信息') - roles: List[Union[RoleModel, None]] = Field(description='角色信息') + post_ids: Optional[list] = Field(default=None, description='岗位ID信息') + posts: list[Union[PostModel, None]] = Field(description='岗位信息') + role_ids: Optional[list] = Field(default=None, description='角色ID信息') + roles: list[Union[RoleModel, None]] = Field(description='角色信息') class UserProfileModel(BaseModel): @@ -156,6 +164,16 @@ class UserProfileModel(BaseModel): role_group: Union[str, None] = Field(description='角色信息') +class AvatarModel(BaseModel): + """ + 上传头像响应模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + img_url: str = Field(description='头像地址') + + class UserQueryModel(UserModel): """ 用户管理不分页查询模型 @@ -165,7 +183,6 @@ class UserQueryModel(UserModel): end_time: Optional[str] = Field(default=None, description='结束时间') -@as_query class UserPageQueryModel(UserQueryModel): """ 用户管理分页查询模型 @@ -180,8 +197,8 @@ class AddUserModel(UserModel): 新增用户模型 """ - role_ids: Optional[List] = Field(default=[], description='角色ID信息') - post_ids: Optional[List] = Field(default=[], description='岗位ID信息') + role_ids: Optional[list] = Field(default=[], description='角色ID信息') + post_ids: Optional[list] = Field(default=[], description='岗位ID信息') type: Optional[str] = Field(default=None, description='操作类型') @@ -190,7 +207,7 @@ class EditUserModel(AddUserModel): 编辑用户模型 """ - role: Optional[List] = Field(default=[], description='角色信息') + role: Optional[list] = Field(default=[], description='角色信息') class ResetPasswordModel(BaseModel): @@ -208,8 +225,7 @@ class ResetPasswordModel(BaseModel): pattern = r"""^[^<>"'|\\]+$""" if self.new_password is None or re.match(pattern, self.new_password): return self - else: - raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') class ResetUserModel(UserModel): @@ -242,7 +258,6 @@ class UserRoleQueryModel(UserModel): role_id: Optional[int] = Field(default=None, description='角色ID') -@as_query class UserRolePageQueryModel(UserRoleQueryModel): """ 用户角色关联管理分页查询模型 @@ -267,11 +282,10 @@ class UserRoleResponseModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - roles: List[Union[SelectedRoleModel, None]] = Field(default=[], description='角色信息') + roles: list[Union[SelectedRoleModel, None]] = Field(default=[], description='角色信息') user: UserInfoModel = Field(description='用户信息') -@as_query class CrudUserRoleModel(BaseModel): """ 新增、删除用户关联角色及角色关联用户模型 diff --git a/ruoyi-fastapi-backend/module_admin/service/cache_service.py b/ruoyi-fastapi-backend/module_admin/service/cache_service.py index 72212cb34f77061d20ccb8b49e511cff0a21de46..410f6b8967433f2bc4a45da0c41fec2697403712 100644 --- a/ruoyi-fastapi-backend/module_admin/service/cache_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/cache_service.py @@ -1,8 +1,9 @@ from fastapi import Request -from config.enums import RedisInitKeyConfig + +from common.enums import RedisInitKeyConfig +from common.vo import CrudResponseModel from config.get_redis import RedisUtil from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel -from module_admin.entity.vo.common_vo import CrudResponseModel class CacheService: @@ -11,7 +12,7 @@ class CacheService: """ @classmethod - async def get_cache_monitor_statistical_info_services(cls, request: Request): + async def get_cache_monitor_statistical_info_services(cls, request: Request) -> CacheMonitorModel: """ 获取缓存监控信息service @@ -22,34 +23,33 @@ class CacheService: db_size = await request.app.state.redis.dbsize() command_stats_dict = await request.app.state.redis.info('commandstats') command_stats = [ - dict(name=key.split('_')[1], value=str(value.get('calls'))) for key, value in command_stats_dict.items() + {'name': key.split('_')[1], 'value': str(value.get('calls'))} for key, value in command_stats_dict.items() ] result = CacheMonitorModel(commandStats=command_stats, dbSize=db_size, info=info) return result @classmethod - async def get_cache_monitor_cache_name_services(cls): + async def get_cache_monitor_cache_name_services(cls) -> list[CacheInfoModel]: """ 获取缓存名称列表信息service :return: 缓存名称列表信息 """ - name_list = [] - for key_config in RedisInitKeyConfig: - name_list.append( - CacheInfoModel( - cacheKey='', - cacheName=key_config.key, - cacheValue='', - remark=key_config.remark, - ) + name_list = [ + CacheInfoModel( + cacheKey='', + cacheName=key_config.key, + cacheValue='', + remark=key_config.remark, ) + for key_config in RedisInitKeyConfig + ] return name_list @classmethod - async def get_cache_monitor_cache_key_services(cls, request: Request, cache_name: str): + async def get_cache_monitor_cache_key_services(cls, request: Request, cache_name: str) -> list[str]: """ 获取缓存键名列表信息service @@ -57,13 +57,15 @@ class CacheService: :param cache_name: 缓存名称 :return: 缓存键名列表信息 """ - cache_keys = await request.app.state.redis.keys(f'{cache_name}*') + cache_keys: list[str] = await request.app.state.redis.keys(f'{cache_name}*') cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f'{cache_name}:')] return cache_key_list @classmethod - async def get_cache_monitor_cache_value_services(cls, request: Request, cache_name: str, cache_key: str): + async def get_cache_monitor_cache_value_services( + cls, request: Request, cache_name: str, cache_key: str + ) -> CacheInfoModel: """ 获取缓存内容信息service @@ -77,7 +79,7 @@ class CacheService: return CacheInfoModel(cacheKey=cache_key, cacheName=cache_name, cacheValue=cache_value, remark='') @classmethod - async def clear_cache_monitor_cache_name_services(cls, request: Request, cache_name: str): + async def clear_cache_monitor_cache_name_services(cls, request: Request, cache_name: str) -> CrudResponseModel: """ 清除缓存名称对应所有键值service @@ -92,7 +94,7 @@ class CacheService: return CrudResponseModel(is_success=True, message=f'{cache_name}对应键值清除成功') @classmethod - async def clear_cache_monitor_cache_key_services(cls, request: Request, cache_key: str): + async def clear_cache_monitor_cache_key_services(cls, request: Request, cache_key: str) -> CrudResponseModel: """ 清除缓存名称对应所有键值service @@ -107,7 +109,7 @@ class CacheService: return CrudResponseModel(is_success=True, message=f'{cache_key}清除成功') @classmethod - async def clear_cache_monitor_all_services(cls, request: Request): + async def clear_cache_monitor_all_services(cls, request: Request) -> CrudResponseModel: """ 清除所有缓存service diff --git a/ruoyi-fastapi-backend/module_admin/service/captcha_service.py b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py index 1be8ffb4ea6d491a4735f252bbc8a098b41aa001..5187d743be78d3bb004afe541f7d6ee696261dbd 100644 --- a/ruoyi-fastapi-backend/module_admin/service/captcha_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py @@ -2,6 +2,7 @@ import base64 import io import os import random + from PIL import Image, ImageDraw, ImageFont @@ -11,7 +12,7 @@ class CaptchaService: """ @classmethod - async def create_captcha_image_service(cls): + async def create_captcha_image_service(cls) -> list[str, int]: # 创建空白图像 image = Image.new('RGB', (160, 60), color='#EAEAEA') diff --git a/ruoyi-fastapi-backend/module_admin/service/common_service.py b/ruoyi-fastapi-backend/module_admin/service/common_service.py index 20eb868de95f70fc1da498ee8bf4adf05e391a99..254ad9ffc5dc18a72a1ae3da8f4be642aa7ec617 100644 --- a/ruoyi-fastapi-backend/module_admin/service/common_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/common_service.py @@ -1,9 +1,13 @@ import os from datetime import datetime + +import aiofiles from fastapi import BackgroundTasks, Request, UploadFile + +from common.vo import CrudResponseModel from config.env import UploadConfig from exceptions.exception import ServiceException -from module_admin.entity.vo.common_vo import CrudResponseModel, UploadResponseModel +from module_admin.entity.vo.common_vo import UploadResponseModel from utils.upload_util import UploadUtil @@ -13,7 +17,7 @@ class CommonService: """ @classmethod - async def upload_service(cls, request: Request, file: UploadFile): + async def upload_service(cls, request: Request, file: UploadFile) -> CrudResponseModel: """ 通用上传service @@ -23,33 +27,39 @@ class CommonService: """ if not UploadUtil.check_file_extension(file): raise ServiceException(message='文件类型不合法') - else: - relative_path = f'upload/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}' - dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path) - try: - os.makedirs(dir_path) - except FileExistsError: - pass - filename = f'{file.filename.rsplit(".", 1)[0]}_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.{file.filename.rsplit(".")[-1]}' - filepath = os.path.join(dir_path, filename) - with open(filepath, 'wb') as f: - # 流式写出大型文件,这里的10代表10MB - for chunk in iter(lambda: file.file.read(1024 * 1024 * 10), b''): - f.write(chunk) + relative_path = ( + f'upload/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}' + ) + dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path) + try: + os.makedirs(dir_path) + except FileExistsError: + pass + filename = f'{file.filename.rsplit(".", 1)[0]}_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.{file.filename.rsplit(".")[-1]}' + filepath = os.path.join(dir_path, filename) + async with aiofiles.open(filepath, 'wb') as f: + # 流式写出大型文件,这里的10代表10MB + while True: + chunk = await file.read(1024 * 1024 * 10) + if not chunk: + break + await f.write(chunk) - return CrudResponseModel( - is_success=True, - result=UploadResponseModel( - fileName=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{filename}', - newFileName=filename, - originalFilename=file.filename, - url=f'{request.base_url}{UploadConfig.UPLOAD_PREFIX[1:]}/{relative_path}/{filename}', - ), - message='上传成功', - ) + return CrudResponseModel( + is_success=True, + result=UploadResponseModel( + fileName=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{filename}', + newFileName=filename, + originalFilename=file.filename, + url=f'{request.base_url}{UploadConfig.UPLOAD_PREFIX[1:]}/{relative_path}/{filename}', + ), + message='上传成功', + ) @classmethod - async def download_services(cls, background_tasks: BackgroundTasks, file_name, delete: bool): + async def download_services( + cls, background_tasks: BackgroundTasks, file_name: str, delete: bool + ) -> CrudResponseModel: """ 下载下载目录文件service @@ -61,15 +71,14 @@ class CommonService: filepath = os.path.join(UploadConfig.DOWNLOAD_PATH, file_name) if '..' in file_name: raise ServiceException(message='文件名称不合法') - elif not UploadUtil.check_file_exists(filepath): + if not UploadUtil.check_file_exists(filepath): raise ServiceException(message='文件不存在') - else: - if delete: - background_tasks.add_task(UploadUtil.delete_file, filepath) - return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') + if delete: + background_tasks.add_task(UploadUtil.delete_file, filepath) + return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') @classmethod - async def download_resource_services(cls, resource: str): + async def download_resource_services(cls, resource: str) -> CrudResponseModel: """ 下载上传目录文件service @@ -85,7 +94,6 @@ class CommonService: or not UploadUtil.check_file_random_code(filename) ): raise ServiceException(message='文件名称不合法') - elif not UploadUtil.check_file_exists(filepath): + if not UploadUtil.check_file_exists(filepath): raise ServiceException(message='文件不存在') - else: - return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') + return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') diff --git a/ruoyi-fastapi-backend/module_admin/service/config_service.py b/ruoyi-fastapi-backend/module_admin/service/config_service.py index 312006dbbca6baaa4559ac7b60baced8ae9ee19d..a16828d05feb98b1c4d85f00c5c087dafed515f3 100644 --- a/ruoyi-fastapi-backend/module_admin/service/config_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/config_service.py @@ -1,11 +1,14 @@ +from typing import Any, Union + from fastapi import Request +from redis import asyncio as aioredis from sqlalchemy.ext.asyncio import AsyncSession -from typing import List -from config.constant import CommonConstant -from config.enums import RedisInitKeyConfig + +from common.constant import CommonConstant +from common.enums import RedisInitKeyConfig +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException from module_admin.dao.config_dao import ConfigDao -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel from utils.common_util import CamelCaseUtil from utils.excel_util import ExcelUtil @@ -19,7 +22,7 @@ class ConfigService: @classmethod async def get_config_list_services( cls, query_db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取参数配置列表信息service @@ -33,7 +36,7 @@ class ConfigService: return config_list_result @classmethod - async def init_cache_sys_config_services(cls, query_db: AsyncSession, redis): + async def init_cache_sys_config_services(cls, query_db: AsyncSession, redis: aioredis.Redis) -> None: """ 应用初始化:获取所有参数配置对应的键值对信息并缓存service @@ -46,15 +49,15 @@ class ConfigService: # 删除匹配的键 if keys: await redis.delete(*keys) - config_all = await ConfigDao.get_config_list(query_db, ConfigPageQueryModel(**dict()), is_page=False) + config_all = await ConfigDao.get_config_list(query_db, ConfigPageQueryModel(), is_page=False) for config_obj in config_all: await redis.set( - f"{RedisInitKeyConfig.SYS_CONFIG.key}:{config_obj.get('configKey')}", + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_obj.get("configKey")}', config_obj.get('configValue'), ) @classmethod - async def query_config_list_from_cache_services(cls, redis, config_key: str): + async def query_config_list_from_cache_services(cls, redis: aioredis.Redis, config_key: str) -> Any: """ 从缓存获取参数键名对应值service @@ -67,7 +70,7 @@ class ConfigService: return result @classmethod - async def check_config_key_unique_services(cls, query_db: AsyncSession, page_object: ConfigModel): + async def check_config_key_unique_services(cls, query_db: AsyncSession, page_object: ConfigModel) -> bool: """ 校验参数键名是否唯一service @@ -82,7 +85,9 @@ class ConfigService: return CommonConstant.UNIQUE @classmethod - async def add_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): + async def add_config_services( + cls, request: Request, query_db: AsyncSession, page_object: ConfigModel + ) -> CrudResponseModel: """ 新增参数配置信息service @@ -93,20 +98,21 @@ class ConfigService: """ if not await cls.check_config_key_unique_services(query_db, page_object): raise ServiceException(message=f'新增参数{page_object.config_name}失败,参数键名已存在') - else: - try: - await ConfigDao.add_config_dao(query_db, page_object) - await query_db.commit() - await request.app.state.redis.set( - f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value - ) - return CrudResponseModel(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await ConfigDao.add_config_dao(query_db, page_object) + await query_db.commit() + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value + ) + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - async def edit_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): + async def edit_config_services( + cls, request: Request, query_db: AsyncSession, page_object: ConfigModel + ) -> CrudResponseModel: """ 编辑参数配置信息service @@ -120,26 +126,27 @@ class ConfigService: if config_info.config_id: if not await cls.check_config_key_unique_services(query_db, page_object): raise ServiceException(message=f'修改参数{page_object.config_name}失败,参数键名已存在') - else: - try: - await ConfigDao.edit_config_dao(query_db, edit_config) - await query_db.commit() - if config_info.config_key != page_object.config_key: - await request.app.state.redis.delete( - f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}' - ) - await request.app.state.redis.set( - f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value + try: + await ConfigDao.edit_config_dao(query_db, edit_config) + await query_db.commit() + if config_info.config_key != page_object.config_key: + await request.app.state.redis.delete( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}' ) - return CrudResponseModel(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: raise ServiceException(message='参数配置不存在') @classmethod - async def delete_config_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteConfigModel): + async def delete_config_services( + cls, request: Request, query_db: AsyncSession, page_object: DeleteConfigModel + ) -> CrudResponseModel: """ 删除参数配置信息service @@ -156,9 +163,8 @@ class ConfigService: config_info = await cls.config_detail_services(query_db, int(config_id)) if config_info.config_type == CommonConstant.YES: raise ServiceException(message=f'内置参数{config_info.config_key}不能删除') - else: - await ConfigDao.delete_config_dao(query_db, ConfigModel(configId=int(config_id))) - delete_config_key_list.append(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}') + await ConfigDao.delete_config_dao(query_db, ConfigModel(configId=int(config_id))) + delete_config_key_list.append(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}') await query_db.commit() if delete_config_key_list: await request.app.state.redis.delete(*delete_config_key_list) @@ -170,7 +176,7 @@ class ConfigService: raise ServiceException(message='传入参数配置id为空') @classmethod - async def config_detail_services(cls, query_db: AsyncSession, config_id: int): + async def config_detail_services(cls, query_db: AsyncSession, config_id: int) -> ConfigModel: """ 获取参数配置详细信息service @@ -179,15 +185,12 @@ class ConfigService: :return: 参数配置id对应的信息 """ config = await ConfigDao.get_config_detail_by_id(query_db, config_id=config_id) - if config: - result = ConfigModel(**CamelCaseUtil.transform_result(config)) - else: - result = ConfigModel(**dict()) + result = ConfigModel(**CamelCaseUtil.transform_result(config)) if config else ConfigModel() return result @staticmethod - async def export_config_list_services(config_list: List): + async def export_config_list_services(config_list: list) -> bytes: """ 导出参数配置信息service @@ -218,7 +221,7 @@ class ConfigService: return binary_data @classmethod - async def refresh_sys_config_services(cls, request: Request, query_db: AsyncSession): + async def refresh_sys_config_services(cls, request: Request, query_db: AsyncSession) -> CrudResponseModel: """ 刷新字典缓存信息service diff --git a/ruoyi-fastapi-backend/module_admin/service/dept_service.py b/ruoyi-fastapi-backend/module_admin/service/dept_service.py index 50594c85220a9dbb5385f6d8f3373b54c5474261..c9c9a19165d729b1d9a776b8fac0fd7119b8931c 100644 --- a/ruoyi-fastapi-backend/module_admin/service/dept_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/dept_service.py @@ -1,9 +1,14 @@ +from collections.abc import Sequence +from typing import Any + from sqlalchemy.ext.asyncio import AsyncSession -from config.constant import CommonConstant + +from common.constant import CommonConstant +from common.vo import CrudResponseModel from exceptions.exception import ServiceException, ServiceWarning from module_admin.dao.dept_dao import DeptDao -from module_admin.entity.vo.common_vo import CrudResponseModel -from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel +from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptTreeModel from utils.common_util import CamelCaseUtil @@ -13,7 +18,9 @@ class DeptService: """ @classmethod - async def get_dept_tree_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): + async def get_dept_tree_services( + cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str + ) -> list[dict[str, Any]]: """ 获取部门树信息service @@ -23,14 +30,15 @@ class DeptService: :return: 部门树信息对象 """ dept_list_result = await DeptDao.get_dept_list_for_tree(query_db, page_object, data_scope_sql) - dept_tree_result = cls.list_to_tree(dept_list_result) + dept_tree_model_result = cls.list_to_tree(dept_list_result) + dept_tree_result = [dept.model_dump(exclude_unset=True, by_alias=True) for dept in dept_tree_model_result] return dept_tree_result @classmethod async def get_dept_for_edit_option_services( cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str - ): + ) -> list[dict[str, Any]]: """ 获取部门编辑部门树信息service @@ -44,7 +52,9 @@ class DeptService: return CamelCaseUtil.transform_result(dept_list_result) @classmethod - async def get_dept_list_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): + async def get_dept_list_services( + cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str + ) -> list[dict[str, Any]]: """ 获取部门列表信息service @@ -58,7 +68,9 @@ class DeptService: return CamelCaseUtil.transform_result(dept_list_result) @classmethod - async def check_dept_data_scope_services(cls, query_db: AsyncSession, dept_id: int, data_scope_sql: str): + async def check_dept_data_scope_services( + cls, query_db: AsyncSession, dept_id: int, data_scope_sql: str + ) -> CrudResponseModel: """ 校验部门是否有数据权限service @@ -70,11 +82,10 @@ class DeptService: depts = await DeptDao.get_dept_list(query_db, DeptModel(deptId=dept_id), data_scope_sql) if depts: return CrudResponseModel(is_success=True, message='校验通过') - else: - raise ServiceException(message='没有权限访问部门数据') + raise ServiceException(message='没有权限访问部门数据') @classmethod - async def check_dept_name_unique_services(cls, query_db: AsyncSession, page_object: DeptModel): + async def check_dept_name_unique_services(cls, query_db: AsyncSession, page_object: DeptModel) -> bool: """ 校验部门名称是否唯一service @@ -91,7 +102,7 @@ class DeptService: return CommonConstant.UNIQUE @classmethod - async def add_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): + async def add_dept_services(cls, query_db: AsyncSession, page_object: DeptModel) -> CrudResponseModel: """ 新增部门信息service @@ -114,7 +125,7 @@ class DeptService: raise e @classmethod - async def edit_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): + async def edit_dept_services(cls, query_db: AsyncSession, page_object: DeptModel) -> CrudResponseModel: """ 编辑部门信息service @@ -124,9 +135,9 @@ class DeptService: """ if not await cls.check_dept_name_unique_services(query_db, page_object): raise ServiceException(message=f'修改部门{page_object.dept_name}失败,部门名称已存在') - elif page_object.dept_id == page_object.parent_id: + if page_object.dept_id == page_object.parent_id: raise ServiceException(message=f'修改部门{page_object.dept_name}失败,上级部门不能是自己') - elif ( + if ( page_object.status == CommonConstant.DEPT_DISABLE and (await DeptDao.count_normal_children_dept_dao(query_db, page_object.dept_id)) > 0 ): @@ -154,7 +165,7 @@ class DeptService: raise e @classmethod - async def delete_dept_services(cls, query_db: AsyncSession, page_object: DeleteDeptModel): + async def delete_dept_services(cls, query_db: AsyncSession, page_object: DeleteDeptModel) -> CrudResponseModel: """ 删除部门信息service @@ -168,7 +179,7 @@ class DeptService: for dept_id in dept_id_list: if (await DeptDao.count_children_dept_dao(query_db, int(dept_id))) > 0: raise ServiceWarning(message='存在下级部门,不允许删除') - elif (await DeptDao.count_dept_user_dao(query_db, int(dept_id))) > 0: + if (await DeptDao.count_dept_user_dao(query_db, int(dept_id))) > 0: raise ServiceWarning(message='部门存在用户,不允许删除') await DeptDao.delete_dept_dao(query_db, DeptModel(deptId=dept_id)) @@ -181,7 +192,7 @@ class DeptService: raise ServiceException(message='传入部门id为空') @classmethod - async def dept_detail_services(cls, query_db: AsyncSession, dept_id: int): + async def dept_detail_services(cls, query_db: AsyncSession, dept_id: int) -> DeptModel: """ 获取部门详细信息service @@ -190,46 +201,43 @@ class DeptService: :return: 部门id对应的信息 """ dept = await DeptDao.get_dept_detail_by_id(query_db, dept_id=dept_id) - if dept: - result = DeptModel(**CamelCaseUtil.transform_result(dept)) - else: - result = DeptModel(**dict()) + result = DeptModel(**CamelCaseUtil.transform_result(dept)) if dept else DeptModel() return result @classmethod - def list_to_tree(cls, permission_list: list) -> list: + def list_to_tree(cls, permission_list: Sequence[SysDept]) -> list[DeptTreeModel]: """ 工具方法:根据部门列表信息生成树形嵌套数据 :param permission_list: 部门列表信息 :return: 部门树形嵌套数据 """ - permission_list = [ - dict(id=item.dept_id, label=item.dept_name, parentId=item.parent_id) for item in permission_list + _permission_list = [ + DeptTreeModel(id=item.dept_id, label=item.dept_name, parentId=item.parent_id) for item in permission_list ] # 转成id为key的字典 - mapping: dict = dict(zip([i['id'] for i in permission_list], permission_list)) + mapping: dict[int, DeptTreeModel] = dict(zip([i.id for i in _permission_list], _permission_list)) # 树容器 - container: list = [] + container: list[DeptTreeModel] = [] - for d in permission_list: + for d in _permission_list: # 如果找不到父级项,则是根节点 - parent: dict = mapping.get(d['parentId']) + parent = mapping.get(d.parent_id) if parent is None: container.append(d) else: - children: list = parent.get('children') + children: list[DeptTreeModel] = parent.children if not children: children = [] children.append(d) - parent.update({'children': children}) + parent.children = children return container @classmethod - async def replace_first(cls, original_str: str, old_str: str, new_str: str): + async def replace_first(cls, original_str: str, old_str: str, new_str: str) -> str: """ 工具方法:替换字符串 @@ -240,11 +248,10 @@ class DeptService: """ if original_str.startswith(old_str): return original_str.replace(old_str, new_str, 1) - else: - return original_str + return original_str @classmethod - async def update_parent_dept_status_normal(cls, query_db: AsyncSession, dept: DeptModel): + async def update_parent_dept_status_normal(cls, query_db: AsyncSession, dept: DeptModel) -> None: """ 更新父部门状态为正常 @@ -256,7 +263,9 @@ class DeptService: await DeptDao.update_dept_status_normal_dao(query_db, list(map(int, dept_id_list))) @classmethod - async def update_dept_children(cls, query_db: AsyncSession, dept_id: int, new_ancestors: str, old_ancestors: str): + async def update_dept_children( + cls, query_db: AsyncSession, dept_id: int, new_ancestors: str, old_ancestors: str + ) -> None: """ 更新子部门信息 diff --git a/ruoyi-fastapi-backend/module_admin/service/dict_service.py b/ruoyi-fastapi-backend/module_admin/service/dict_service.py index 0acfd738df81e35b6d05c3453816201152e09aae..9717ac5b22fbda223a10aab7b2484fdf10b9b7e8 100644 --- a/ruoyi-fastapi-backend/module_admin/service/dict_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/dict_service.py @@ -1,12 +1,17 @@ import json +from collections.abc import Sequence +from typing import Any, Union + from fastapi import Request +from redis import asyncio as aioredis from sqlalchemy.ext.asyncio import AsyncSession -from typing import List -from config.constant import CommonConstant -from config.enums import RedisInitKeyConfig + +from common.constant import CommonConstant +from common.enums import RedisInitKeyConfig +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException from module_admin.dao.dict_dao import DictDataDao, DictTypeDao -from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.do.dict_do import SysDictData from module_admin.entity.vo.dict_vo import ( DeleteDictDataModel, DeleteDictTypeModel, @@ -27,7 +32,7 @@ class DictTypeService: @classmethod async def get_dict_type_list_services( cls, query_db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取字典类型列表信息service @@ -41,7 +46,7 @@ class DictTypeService: return dict_type_list_result @classmethod - async def check_dict_type_unique_services(cls, query_db: AsyncSession, page_object: DictTypeModel): + async def check_dict_type_unique_services(cls, query_db: AsyncSession, page_object: DictTypeModel) -> bool: """ 校验字典类型称是否唯一service @@ -58,7 +63,9 @@ class DictTypeService: return CommonConstant.UNIQUE @classmethod - async def add_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): + async def add_dict_type_services( + cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel + ) -> CrudResponseModel: """ 新增字典类型信息service @@ -69,20 +76,21 @@ class DictTypeService: """ if not await cls.check_dict_type_unique_services(query_db, page_object): raise ServiceException(message=f'新增字典{page_object.dict_name}失败,字典类型已存在') - else: - try: - await DictTypeDao.add_dict_type_dao(query_db, page_object) - await query_db.commit() - await request.app.state.redis.set(f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', '') - result = dict(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await DictTypeDao.add_dict_type_dao(query_db, page_object) + await query_db.commit() + await request.app.state.redis.set(f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', '') + result = {'is_success': True, 'message': '新增成功'} + except Exception as e: + await query_db.rollback() + raise e return CrudResponseModel(**result) @classmethod - async def edit_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): + async def edit_dict_type_services( + cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel + ) -> CrudResponseModel: """ 编辑字典类型信息service @@ -96,38 +104,37 @@ class DictTypeService: if dict_type_info.dict_id: if not await cls.check_dict_type_unique_services(query_db, page_object): raise ServiceException(message=f'修改字典{page_object.dict_name}失败,字典类型已存在') - else: - try: - query_dict_data = DictDataPageQueryModel(dictType=dict_type_info.dict_type) - dict_data_list = await DictDataDao.get_dict_data_list(query_db, query_dict_data, is_page=False) - if dict_type_info.dict_type != page_object.dict_type: - for dict_data in dict_data_list: - edit_dict_data = DictDataModel( - dictCode=dict_data.get('dict_code'), - dictType=page_object.dict_type, - updateBy=page_object.update_by, - updateTime=page_object.update_time, - ).model_dump(exclude_unset=True) - await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data) - await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type) - await query_db.commit() - if dict_type_info.dict_type != page_object.dict_type: - dict_data = [CamelCaseUtil.transform_result(row) for row in dict_data_list if row] - await request.app.state.redis.set( - f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', - json.dumps(dict_data, ensure_ascii=False, default=str), - ) - return CrudResponseModel(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + try: + query_dict_data = DictDataPageQueryModel(dictType=dict_type_info.dict_type) + dict_data_list = await DictDataDao.get_dict_data_list(query_db, query_dict_data, is_page=False) + if dict_type_info.dict_type != page_object.dict_type: + for dict_data in dict_data_list: + edit_dict_data = DictDataModel( + dictCode=dict_data.get('dict_code'), + dictType=page_object.dict_type, + updateBy=page_object.update_by, + updateTime=page_object.update_time, + ).model_dump(exclude_unset=True) + await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data) + await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type) + await query_db.commit() + if dict_type_info.dict_type != page_object.dict_type: + dict_data = [CamelCaseUtil.transform_result(row) for row in dict_data_list if row] + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps(dict_data, ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: raise ServiceException(message='字典类型不存在') @classmethod async def delete_dict_type_services( cls, request: Request, query_db: AsyncSession, page_object: DeleteDictTypeModel - ): + ) -> CrudResponseModel: """ 删除字典类型信息service @@ -157,7 +164,7 @@ class DictTypeService: raise ServiceException(message='传入字典类型id为空') @classmethod - async def dict_type_detail_services(cls, query_db: AsyncSession, dict_id: int): + async def dict_type_detail_services(cls, query_db: AsyncSession, dict_id: int) -> DictTypeModel: """ 获取字典类型详细信息service @@ -166,15 +173,12 @@ class DictTypeService: :return: 字典类型id对应的信息 """ dict_type = await DictTypeDao.get_dict_type_detail_by_id(query_db, dict_id=dict_id) - if dict_type: - result = DictTypeModel(**CamelCaseUtil.transform_result(dict_type)) - else: - result = DictTypeModel(**dict()) + result = DictTypeModel(**CamelCaseUtil.transform_result(dict_type)) if dict_type else DictTypeModel() return result @staticmethod - async def export_dict_type_list_services(dict_type_list: List): + async def export_dict_type_list_services(dict_type_list: list) -> bytes: """ 导出字典类型信息service @@ -204,7 +208,7 @@ class DictTypeService: return binary_data @classmethod - async def refresh_sys_dict_services(cls, request: Request, query_db: AsyncSession): + async def refresh_sys_dict_services(cls, request: Request, query_db: AsyncSession) -> CrudResponseModel: """ 刷新字典缓存信息service @@ -213,7 +217,7 @@ class DictTypeService: :return: 刷新字典缓存校验结果 """ await DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='刷新成功') + result = {'is_success': True, 'message': '刷新成功'} return CrudResponseModel(**result) @@ -226,7 +230,7 @@ class DictDataService: @classmethod async def get_dict_data_list_services( cls, query_db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取字典数据列表信息service @@ -240,7 +244,7 @@ class DictDataService: return dict_data_list_result @classmethod - async def query_dict_data_list_services(cls, query_db: AsyncSession, dict_type: str): + async def query_dict_data_list_services(cls, query_db: AsyncSession, dict_type: str) -> Sequence[SysDictData]: """ 获取字典数据列表信息service @@ -253,7 +257,7 @@ class DictDataService: return dict_data_list_result @classmethod - async def init_cache_sys_dict_services(cls, query_db: AsyncSession, redis): + async def init_cache_sys_dict_services(cls, query_db: AsyncSession, redis: aioredis.Redis) -> None: """ 应用初始化:获取所有字典类型对应的字典数据信息并缓存service @@ -277,7 +281,9 @@ class DictDataService: ) @classmethod - async def query_dict_data_list_from_cache_services(cls, redis, dict_type: str): + async def query_dict_data_list_from_cache_services( + cls, redis: aioredis.Redis, dict_type: str + ) -> list[dict[str, Any]]: """ 从缓存获取字典数据列表信息service @@ -293,7 +299,7 @@ class DictDataService: return CamelCaseUtil.transform_result(result) @classmethod - async def check_dict_data_unique_services(cls, query_db: AsyncSession, page_object: DictDataModel): + async def check_dict_data_unique_services(cls, query_db: AsyncSession, page_object: DictDataModel) -> bool: """ 校验字典数据是否唯一service @@ -308,7 +314,9 @@ class DictDataService: return CommonConstant.UNIQUE @classmethod - async def add_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): + async def add_dict_data_services( + cls, request: Request, query_db: AsyncSession, page_object: DictDataModel + ) -> CrudResponseModel: """ 新增字典数据信息service @@ -321,22 +329,23 @@ class DictDataService: raise ServiceException( message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据' ) - else: - try: - await DictDataDao.add_dict_data_dao(query_db, page_object) - await query_db.commit() - dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) - await request.app.state.redis.set( - f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', - json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str), - ) - return CrudResponseModel(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await DictDataDao.add_dict_data_dao(query_db, page_object) + await query_db.commit() + dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - async def edit_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): + async def edit_dict_data_services( + cls, request: Request, query_db: AsyncSession, page_object: DictDataModel + ) -> CrudResponseModel: """ 编辑字典数据信息service @@ -352,26 +361,25 @@ class DictDataService: raise ServiceException( message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据' ) - else: - try: - await DictDataDao.edit_dict_data_dao(query_db, edit_data_type) - await query_db.commit() - dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) - await request.app.state.redis.set( - f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', - json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str), - ) - return CrudResponseModel(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await DictDataDao.edit_dict_data_dao(query_db, edit_data_type) + await query_db.commit() + dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps(CamelCaseUtil.transform_result(dict_data_list), ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: raise ServiceException(message='字典数据不存在') @classmethod async def delete_dict_data_services( cls, request: Request, query_db: AsyncSession, page_object: DeleteDictDataModel - ): + ) -> CrudResponseModel: """ 删除字典数据信息service @@ -403,7 +411,7 @@ class DictDataService: raise ServiceException(message='传入字典数据id为空') @classmethod - async def dict_data_detail_services(cls, query_db: AsyncSession, dict_code: int): + async def dict_data_detail_services(cls, query_db: AsyncSession, dict_code: int) -> DictDataModel: """ 获取字典数据详细信息service @@ -412,15 +420,12 @@ class DictDataService: :return: 字典数据id对应的信息 """ dict_data = await DictDataDao.get_dict_data_detail_by_id(query_db, dict_code=dict_code) - if dict_data: - result = DictDataModel(**CamelCaseUtil.transform_result(dict_data)) - else: - result = DictDataModel(**dict()) + result = DictDataModel(**CamelCaseUtil.transform_result(dict_data)) if dict_data else DictDataModel() return result @staticmethod - async def export_dict_data_list_services(dict_data_list: List): + async def export_dict_data_list_services(dict_data_list: list) -> bytes: """ 导出字典数据信息service diff --git a/ruoyi-fastapi-backend/module_admin/service/job_log_service.py b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py index 596abe7b235ee486cb2bfc8bb53c6b166af6bced..45f046e64a8af7b62788e4171509baa0d5411910 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_log_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py @@ -1,9 +1,11 @@ +from typing import Any, Union + from fastapi import Request from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session -from typing import List + +from common.vo import CrudResponseModel, PageModel from module_admin.dao.job_log_dao import JobLogDao -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.job_vo import DeleteJobLogModel, JobLogModel, JobLogPageQueryModel from module_admin.service.dict_service import DictDataService from utils.excel_util import ExcelUtil @@ -17,7 +19,7 @@ class JobLogService: @classmethod async def get_job_log_list_services( cls, query_db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取定时任务日志列表信息service @@ -31,7 +33,7 @@ class JobLogService: return job_log_list_result @classmethod - def add_job_log_services(cls, query_db: Session, page_object: JobLogModel): + def add_job_log_services(cls, query_db: Session, page_object: JobLogModel) -> CrudResponseModel: """ 新增定时任务日志信息service @@ -42,15 +44,15 @@ class JobLogService: try: JobLogDao.add_job_log_dao(query_db, page_object) query_db.commit() - result = dict(is_success=True, message='新增成功') + result = {'is_success': True, 'message': '新增成功'} except Exception as e: query_db.rollback() - result = dict(is_success=False, message=str(e)) + result = {'is_success': False, 'message': str(e)} return CrudResponseModel(**result) @classmethod - async def delete_job_log_services(cls, query_db: AsyncSession, page_object: DeleteJobLogModel): + async def delete_job_log_services(cls, query_db: AsyncSession, page_object: DeleteJobLogModel) -> CrudResponseModel: """ 删除定时任务日志信息service @@ -64,16 +66,16 @@ class JobLogService: for job_log_id in job_log_id_list: await JobLogDao.delete_job_log_dao(query_db, JobLogModel(jobLogId=job_log_id)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + result = {'is_success': True, 'message': '删除成功'} except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入定时任务日志id为空') + result = {'is_success': False, 'message': '传入定时任务日志id为空'} return CrudResponseModel(**result) @classmethod - async def clear_job_log_services(cls, query_db: AsyncSession): + async def clear_job_log_services(cls, query_db: AsyncSession) -> CrudResponseModel: """ 清除定时任务日志信息service @@ -83,7 +85,7 @@ class JobLogService: try: await JobLogDao.clear_job_log_dao(query_db) await query_db.commit() - result = dict(is_success=True, message='清除成功') + result = {'is_success': True, 'message': '清除成功'} except Exception as e: await query_db.rollback() raise e @@ -91,7 +93,7 @@ class JobLogService: return CrudResponseModel(**result) @staticmethod - async def export_job_log_list_services(request: Request, job_log_list: List): + async def export_job_log_list_services(request: Request, job_log_list: list) -> bytes: """ 导出定时任务日志信息service @@ -118,13 +120,13 @@ class JobLogService: job_group_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type='sys_job_group' ) - job_group_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_group_list] + job_group_option = [{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_group_list] job_group_option_dict = {item.get('value'): item for item in job_group_option} job_executor_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type='sys_job_executor' ) job_executor_option = [ - dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_executor_list + {'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_executor_list ] job_executor_option_dict = {item.get('value'): item for item in job_executor_option} @@ -133,9 +135,9 @@ class JobLogService: item['status'] = '正常' else: item['status'] = '暂停' - if str(item.get('jobGroup')) in job_group_option_dict.keys(): + if str(item.get('jobGroup')) in job_group_option_dict: item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label') - if str(item.get('jobExecutor')) in job_executor_option_dict.keys(): + if str(item.get('jobExecutor')) in job_executor_option_dict: item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label') binary_data = ExcelUtil.export_list2excel(job_log_list, mapping_dict) diff --git a/ruoyi-fastapi-backend/module_admin/service/job_service.py b/ruoyi-fastapi-backend/module_admin/service/job_service.py index 2b783f0a0be34277384af1683ee87967ec285961..7f327bfe0642768ad0b9a41ba6b989edab06a246 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -1,11 +1,13 @@ +from typing import Any, Union + from fastapi import Request from sqlalchemy.ext.asyncio import AsyncSession -from typing import List -from config.constant import CommonConstant, JobConstant + +from common.constant import CommonConstant, JobConstant +from common.vo import CrudResponseModel, PageModel from config.get_scheduler import SchedulerUtil from exceptions.exception import ServiceException from module_admin.dao.job_dao import JobDao -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel from module_admin.service.dict_service import DictDataService from utils.common_util import CamelCaseUtil @@ -22,7 +24,7 @@ class JobService: @classmethod async def get_job_list_services( cls, query_db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取定时任务列表信息service @@ -36,7 +38,7 @@ class JobService: return job_list_result @classmethod - async def check_job_unique_services(cls, query_db: AsyncSession, page_object: JobModel): + async def check_job_unique_services(cls, query_db: AsyncSession, page_object: JobModel) -> bool: """ 校验定时任务是否存在service @@ -51,7 +53,7 @@ class JobService: return CommonConstant.UNIQUE @classmethod - async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel): + async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel) -> CrudResponseModel: """ 新增定时任务信息service @@ -61,38 +63,46 @@ class JobService: """ if not CronUtil.validate_cron_expression(page_object.cron_expression): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,Cron表达式不正确') - elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): + if StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') - elif StringUtil.contains_any_ignore_case( + if StringUtil.contains_any_ignore_case( page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS] ): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用') - elif StringUtil.contains_any_ignore_case( - page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS] - ): + if StringUtil.contains_any_ignore_case(page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS]): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用') - elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): + if StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串存在违规') - elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): + if not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不在白名单内') - elif not await cls.check_job_unique_services(query_db, page_object): + if not await cls.check_job_unique_services(query_db, page_object): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,定时任务已存在') - else: - try: - add_job = await JobDao.add_job_dao(query_db, page_object) - job_info = await cls.job_detail_services(query_db, add_job.job_id) - if job_info.status == '0': - SchedulerUtil.add_scheduler_job(job_info=job_info) - await query_db.commit() - result = dict(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + add_job = await JobDao.add_job_dao(query_db, page_object) + job_info = await cls.job_detail_services(query_db, add_job.job_id) + if job_info.status == '0': + SchedulerUtil.add_scheduler_job(job_info=job_info) + await query_db.commit() + result = {'is_success': True, 'message': '新增成功'} + except Exception as e: + await query_db.rollback() + raise e return CrudResponseModel(**result) @classmethod - async def edit_job_services(cls, query_db: AsyncSession, page_object: EditJobModel): + def _deal_edit_job(cls, page_object: EditJobModel, edit_job: dict[str, Any]) -> None: + """ + 处理编辑定时任务字典 + + :param page_object: 编辑定时任务对象 + :param edit_job: 编辑定时任务字典 + """ + if page_object.type == 'status': + del edit_job['type'] + + @classmethod + async def edit_job_services(cls, query_db: AsyncSession, page_object: EditJobModel) -> CrudResponseModel: """ 编辑定时任务信息service @@ -101,32 +111,31 @@ class JobService: :return: 编辑定时任务校验结果 """ edit_job = page_object.model_dump(exclude_unset=True) - if page_object.type == 'status': - del edit_job['type'] + cls._deal_edit_job(page_object, edit_job) job_info = await cls.job_detail_services(query_db, page_object.job_id) if job_info: if page_object.type != 'status': if not CronUtil.validate_cron_expression(page_object.cron_expression): raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,Cron表达式不正确') - elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): + if StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') - elif StringUtil.contains_any_ignore_case( + if StringUtil.contains_any_ignore_case( page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS] ): raise ServiceException( message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用' ) - elif StringUtil.contains_any_ignore_case( + if StringUtil.contains_any_ignore_case( page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS] ): raise ServiceException( message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用' ) - elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): + if StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串存在违规') - elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): + if not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不在白名单内') - elif not await cls.check_job_unique_services(query_db, page_object): + if not await cls.check_job_unique_services(query_db, page_object): raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在') try: await JobDao.edit_job_dao(query_db, edit_job, job_info) @@ -143,7 +152,7 @@ class JobService: raise ServiceException(message='定时任务不存在') @classmethod - async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel): + async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel) -> CrudResponseModel: """ 执行一次定时任务service @@ -156,11 +165,10 @@ class JobService: if job_info: SchedulerUtil.execute_scheduler_job_once(job_info=job_info) return CrudResponseModel(is_success=True, message='执行成功') - else: - raise ServiceException(message='定时任务不存在') + raise ServiceException(message='定时任务不存在') @classmethod - async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel): + async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel) -> CrudResponseModel: """ 删除定时任务信息service @@ -183,7 +191,7 @@ class JobService: raise ServiceException(message='传入定时任务id为空') @classmethod - async def job_detail_services(cls, query_db: AsyncSession, job_id: int): + async def job_detail_services(cls, query_db: AsyncSession, job_id: int) -> JobModel: """ 获取定时任务详细信息service @@ -192,15 +200,12 @@ class JobService: :return: 定时任务id对应的信息 """ job = await JobDao.get_job_detail_by_id(query_db, job_id=job_id) - if job: - result = JobModel(**CamelCaseUtil.transform_result(job)) - else: - result = JobModel(**dict()) + result = JobModel(**CamelCaseUtil.transform_result(job)) if job else JobModel() return result @staticmethod - async def export_job_list_services(request: Request, job_list: List): + async def export_job_list_services(request: Request, job_list: list) -> bytes: """ 导出定时任务信息service @@ -231,13 +236,13 @@ class JobService: job_group_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type='sys_job_group' ) - job_group_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_group_list] + job_group_option = [{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_group_list] job_group_option_dict = {item.get('value'): item for item in job_group_option} job_executor_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type='sys_job_executor' ) job_executor_option = [ - dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in job_executor_list + {'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in job_executor_list ] job_executor_option_dict = {item.get('value'): item for item in job_executor_option} @@ -246,9 +251,9 @@ class JobService: item['status'] = '正常' else: item['status'] = '暂停' - if str(item.get('jobGroup')) in job_group_option_dict.keys(): + if str(item.get('jobGroup')) in job_group_option_dict: item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label') - if str(item.get('jobExecutor')) in job_executor_option_dict.keys(): + if str(item.get('jobExecutor')) in job_executor_option_dict: item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label') if item.get('misfirePolicy') == '1': item['misfirePolicy'] = '立即执行' diff --git a/ruoyi-fastapi-backend/module_admin/service/log_service.py b/ruoyi-fastapi-backend/module_admin/service/log_service.py index 0983b1a271ad77e11a718244e10187d5256dc2fd..4e89c540da2fd305c2dbd28899b5493507f234cc 100644 --- a/ruoyi-fastapi-backend/module_admin/service/log_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/log_service.py @@ -1,9 +1,11 @@ +from typing import Any, Union + from fastapi import Request from sqlalchemy.ext.asyncio import AsyncSession -from typing import List + +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException from module_admin.dao.log_dao import LoginLogDao, OperationLogDao -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.log_vo import ( DeleteLoginLogModel, DeleteOperLogModel, @@ -25,7 +27,7 @@ class OperationLogService: @classmethod async def get_operation_log_list_services( cls, query_db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取操作日志列表信息service @@ -39,7 +41,7 @@ class OperationLogService: return operation_log_list_result @classmethod - async def add_operation_log_services(cls, query_db: AsyncSession, page_object: OperLogModel): + async def add_operation_log_services(cls, query_db: AsyncSession, page_object: OperLogModel) -> CrudResponseModel: """ 新增操作日志service @@ -56,7 +58,9 @@ class OperationLogService: raise e @classmethod - async def delete_operation_log_services(cls, query_db: AsyncSession, page_object: DeleteOperLogModel): + async def delete_operation_log_services( + cls, query_db: AsyncSession, page_object: DeleteOperLogModel + ) -> CrudResponseModel: """ 删除操作日志信息service @@ -78,7 +82,7 @@ class OperationLogService: raise ServiceException(message='传入操作日志id为空') @classmethod - async def clear_operation_log_services(cls, query_db: AsyncSession): + async def clear_operation_log_services(cls, query_db: AsyncSession) -> CrudResponseModel: """ 清除操作日志信息service @@ -94,7 +98,7 @@ class OperationLogService: raise e @classmethod - async def export_operation_log_list_services(cls, request: Request, operation_log_list: List): + async def export_operation_log_list_services(cls, request: Request, operation_log_list: list) -> bytes: """ 导出操作日志信息service @@ -126,7 +130,7 @@ class OperationLogService: request.app.state.redis, dict_type='sys_oper_type' ) operation_type_option = [ - dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in operation_type_list + {'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in operation_type_list ] operation_type_option_dict = {item.get('value'): item for item in operation_type_option} @@ -135,7 +139,7 @@ class OperationLogService: item['status'] = '成功' else: item['status'] = '失败' - if str(item.get('businessType')) in operation_type_option_dict.keys(): + if str(item.get('businessType')) in operation_type_option_dict: item['businessType'] = operation_type_option_dict.get(str(item.get('businessType'))).get('label') binary_data = ExcelUtil.export_list2excel(operation_log_list, mapping_dict) @@ -150,7 +154,7 @@ class LoginLogService: @classmethod async def get_login_log_list_services( cls, query_db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取登录日志列表信息service @@ -164,7 +168,7 @@ class LoginLogService: return operation_log_list_result @classmethod - async def add_login_log_services(cls, query_db: AsyncSession, page_object: LogininforModel): + async def add_login_log_services(cls, query_db: AsyncSession, page_object: LogininforModel) -> CrudResponseModel: """ 新增登录日志service @@ -181,7 +185,9 @@ class LoginLogService: raise e @classmethod - async def delete_login_log_services(cls, query_db: AsyncSession, page_object: DeleteLoginLogModel): + async def delete_login_log_services( + cls, query_db: AsyncSession, page_object: DeleteLoginLogModel + ) -> CrudResponseModel: """ 删除操作日志信息service @@ -203,7 +209,7 @@ class LoginLogService: raise ServiceException(message='传入登录日志id为空') @classmethod - async def clear_login_log_services(cls, query_db: AsyncSession): + async def clear_login_log_services(cls, query_db: AsyncSession) -> CrudResponseModel: """ 清除操作日志信息service @@ -219,16 +225,15 @@ class LoginLogService: raise e @classmethod - async def unlock_user_services(cls, request: Request, unlock_user: UnlockUser): + async def unlock_user_services(cls, request: Request, unlock_user: UnlockUser) -> CrudResponseModel: locked_user = await request.app.state.redis.get(f'account_lock:{unlock_user.user_name}') if locked_user: await request.app.state.redis.delete(f'account_lock:{unlock_user.user_name}') return CrudResponseModel(is_success=True, message='解锁成功') - else: - raise ServiceException(message='该用户未锁定') + raise ServiceException(message='该用户未锁定') @staticmethod - async def export_login_log_list_services(login_log_list: List): + async def export_login_log_list_services(login_log_list: list) -> bytes: """ 导出登录日志信息service diff --git a/ruoyi-fastapi-backend/module_admin/service/login_service.py b/ruoyi-fastapi-backend/module_admin/service/login_service.py index 81c894a74640a65b3cc892bfb1be9296ad229168..d982925b82601c620413224f96a6ea946da41ced 100644 --- a/ruoyi-fastapi-backend/module_admin/service/login_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/login_service.py @@ -1,21 +1,27 @@ -import jwt import random import uuid from datetime import datetime, timedelta, timezone +from typing import Any, Optional, Union + +import jwt from fastapi import Depends, Form, Request from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jwt.exceptions import InvalidTokenError +from sqlalchemy import Row from sqlalchemy.ext.asyncio import AsyncSession -from typing import Dict, List, Optional, Union -from config.constant import CommonConstant, MenuConstant -from config.enums import RedisInitKeyConfig + +from common.constant import CommonConstant, MenuConstant +from common.context import RequestContext +from common.enums import RedisInitKeyConfig +from common.vo import CrudResponseModel from config.env import AppConfig, JwtConfig from config.get_db import get_db -from exceptions.exception import LoginException, AuthException, ServiceException +from exceptions.exception import AuthException, LoginException, ServiceException from module_admin.dao.login_dao import login_by_account from module_admin.dao.user_dao import UserDao +from module_admin.entity.do.dept_do import SysDept from module_admin.entity.do.menu_do import SysMenu -from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.do.user_do import SysUser from module_admin.entity.vo.login_vo import MenuTreeModel, MetaModel, RouterModel, SmsCode, UserLogin, UserRegister from module_admin.entity.vo.user_vo import AddUserModel, CurrentUserModel, ResetUserModel, TokenData, UserInfoModel from module_admin.service.user_service import UserService @@ -42,8 +48,8 @@ class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm): client_secret: Optional[str] = Form(default=None), code: Optional[str] = Form(default=''), uuid: Optional[str] = Form(default=''), - login_info: Optional[Dict[str, str]] = Form(default=None), - ): + login_info: Optional[dict[str, str]] = Form(default=None), + ) -> None: super().__init__( grant_type=grant_type, username=username, @@ -63,7 +69,9 @@ class LoginService: """ @classmethod - async def authenticate_user(cls, request: Request, query_db: AsyncSession, login_user: UserLogin): + async def authenticate_user( + cls, request: Request, query_db: AsyncSession, login_user: UserLogin + ) -> Row[tuple[SysUser, SysDept]]: """ 根据用户名密码校验用户登录 @@ -110,7 +118,7 @@ class LoginService: password_error_count, ex=timedelta(minutes=10), ) - if password_error_count > 5: + if password_error_count > CommonConstant.PASSWORD_ERROR_COUNT: await request.app.state.redis.delete( f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}' ) @@ -130,7 +138,7 @@ class LoginService: return user @classmethod - async def __check_login_ip(cls, request: Request): + async def __check_login_ip(cls, request: Request) -> bool: """ 校验用户登录ip是否在黑名单内 @@ -145,7 +153,7 @@ class LoginService: return True @classmethod - async def __check_login_captcha(cls, request: Request, login_user: UserLogin): + async def __check_login_captcha(cls, request: Request, login_user: UserLogin) -> bool: """ 校验用户登录验证码 @@ -163,7 +171,7 @@ class LoginService: return True @classmethod - async def create_access_token(cls, data: dict, expires_delta: Union[timedelta, None] = None): + async def create_access_token(cls, data: dict, expires_delta: Union[timedelta, None] = None) -> str: """ 根据登录信息创建当前用户token @@ -183,7 +191,7 @@ class LoginService: @classmethod async def get_current_user( cls, request: Request = Request, token: str = Depends(oauth2_scheme), query_db: AsyncSession = Depends(get_db) - ): + ) -> CurrentUserModel: """ 根据token获取当前用户信息 @@ -206,9 +214,9 @@ class LoginService: logger.warning('用户token不合法') raise AuthException(data='', message='用户token不合法') token_data = TokenData(user_id=int(user_id)) - except InvalidTokenError: + except InvalidTokenError as e: logger.warning('用户token已失效,请重新登录') - raise AuthException(data='', message='用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') from e query_user = await UserDao.get_user_by_id(query_db, user_id=token_data.user_id) if query_user.get('user_basic_info') is None: logger.warning('用户token不合法') @@ -235,7 +243,7 @@ class LoginService: ) role_id_list = [item.role_id for item in query_user.get('user_role_info')] - if 1 in role_id_list: + if 1 in role_id_list: # noqa: SIM108 permissions = ['*:*:*'] else: permissions = [row.perms for row in query_user.get('user_menu_info')] @@ -262,13 +270,14 @@ class LoginService: isDefaultModifyPwd=is_default_modify_pwd, isPasswordExpired=is_password_expired, ) + # 设置当前用户信息到上下文 + RequestContext.set_current_user(current_user) return current_user - else: - logger.warning('用户token已失效,请重新登录') - raise AuthException(data='', message='用户token已失效,请重新登录') + logger.warning('用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') @classmethod - async def __init_password_is_modify(cls, request: Request, pwd_update_date: datetime): + async def __init_password_is_modify(cls, request: Request, pwd_update_date: datetime) -> bool: """ 判断当前用户是否初始密码登录 @@ -282,7 +291,7 @@ class LoginService: return init_password_is_modify == '1' and pwd_update_date is None @classmethod - async def __password_is_expired(cls, request: Request, pwd_update_date: datetime): + async def __password_is_expired(cls, request: Request, pwd_update_date: datetime) -> bool: """ 判断当前用户密码是否过期 @@ -302,7 +311,7 @@ class LoginService: return False @classmethod - async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession): + async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession) -> list[dict[str, Any]]: """ 根据用户id获取当前用户路由信息 @@ -324,7 +333,7 @@ class LoginService: return [router.model_dump(exclude_unset=True, by_alias=True) for router in user_router] @classmethod - def __generate_menus(cls, pid: int, permission_list: List[SysMenu]): + def __generate_menus(cls, pid: int, permission_list: list[SysMenu]) -> list[MenuTreeModel]: """ 工具方法:根据菜单信息生成菜单信息树形嵌套数据 @@ -332,7 +341,7 @@ class LoginService: :param permission_list: 菜单列表信息 :return: 菜单信息树形嵌套数据 """ - menu_list: List[MenuTreeModel] = [] + menu_list: list[MenuTreeModel] = [] for permission in permission_list: if permission.parent_id == pid: children = cls.__generate_menus(permission.menu_id, permission_list) @@ -344,17 +353,17 @@ class LoginService: return menu_list @classmethod - def __generate_user_router_menu(cls, permission_list: List[MenuTreeModel]): + def __generate_user_router_menu(cls, permission_list: list[MenuTreeModel]) -> list[RouterModel]: """ 工具方法:根据菜单树信息生成路由信息树形嵌套数据 :param permission_list: 菜单树列表信息 :return: 路由信息树形嵌套数据 """ - router_list: List[RouterModel] = [] + router_list: list[RouterModel] = [] for permission in permission_list: router = RouterModel( - hidden=True if permission.visible == '1' else False, + hidden=permission.visible == '1', name=RouterUtil.get_router_name(permission), path=RouterUtil.get_router_path(permission), component=RouterUtil.get_component(permission), @@ -362,7 +371,7 @@ class LoginService: meta=MetaModel( title=permission.menu_name, icon=permission.icon, - noCache=True if permission.is_cache == 1 else False, + noCache=permission.is_cache == 1, link=permission.path if RouterUtil.is_http(permission.path) else None, ), ) @@ -373,7 +382,7 @@ class LoginService: router.children = cls.__generate_user_router_menu(c_menus) elif RouterUtil.is_menu_frame(permission): router.meta = None - children_list: List[RouterModel] = [] + children_list: list[RouterModel] = [] children = RouterModel( path=permission.path, component=permission.component, @@ -381,7 +390,7 @@ class LoginService: meta=MetaModel( title=permission.menu_name, icon=permission.icon, - noCache=True if permission.is_cache == 1 else False, + noCache=permission.is_cache == 1, link=permission.path if RouterUtil.is_http(permission.path) else None, ), query=permission.query, @@ -391,7 +400,7 @@ class LoginService: elif permission.parent_id == 0 and RouterUtil.is_inner_link(permission): router.meta = MetaModel(title=permission.menu_name, icon=permission.icon) router.path = '/' - children_list: List[RouterModel] = [] + children_list: list[RouterModel] = [] router_path = RouterUtil.inner_link_replace_each(permission.path) children = RouterModel( path=router_path, @@ -411,7 +420,9 @@ class LoginService: return router_list @classmethod - async def register_user_services(cls, request: Request, query_db: AsyncSession, user_register: UserRegister): + async def register_user_services( + cls, request: Request, query_db: AsyncSession, user_register: UserRegister + ) -> CrudResponseModel: """ 用户注册services @@ -421,16 +432,11 @@ class LoginService: :return: 注册结果 """ 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' ) captcha_enabled = ( - True - if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') == 'true' - else False ) if user_register.password == user_register.confirm_password: if register_enabled: @@ -440,7 +446,7 @@ class LoginService: ) if not captcha_value: raise ServiceException(message='验证码已失效') - elif user_register.code != str(captcha_value): + if user_register.code != str(captcha_value): raise ServiceException(message='验证码错误') add_user = AddUserModel( userName=user_register.username, @@ -450,13 +456,11 @@ class LoginService: ) result = await UserService.add_user_services(query_db, add_user) return result - else: - raise ServiceException(message='注册程序已关闭,禁止注册') - else: - raise ServiceException(message='两次输入的密码不一致') + raise ServiceException(message='注册程序已关闭,禁止注册') + raise ServiceException(message='两次输入的密码不一致') @classmethod - async def get_sms_code_services(cls, request: Request, query_db: AsyncSession, user: ResetUserModel): + async def get_sms_code_services(cls, request: Request, query_db: AsyncSession, user: ResetUserModel) -> SmsCode: """ 获取短信验证码service @@ -467,7 +471,7 @@ class LoginService: """ redis_sms_result = await request.app.state.redis.get(f'{RedisInitKeyConfig.SMS_CODE.key}:{user.session_id}') if redis_sms_result: - return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='短信验证码仍在有效期内')) + return SmsCode(is_success=False, sms_code='', session_id='', message='短信验证码仍在有效期内') is_user = await UserDao.get_user_by_name(query_db, user.user_name) if is_user: sms_code = str(random.randint(100000, 999999)) @@ -478,12 +482,14 @@ class LoginService: # 此处模拟调用短信服务 message_service(sms_code) - return SmsCode(**dict(is_success=True, sms_code=sms_code, session_id=session_id, message='获取成功')) + return SmsCode(is_success=True, sms_code=sms_code, session_id=session_id, message='获取成功') - return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='用户不存在')) + return SmsCode(is_success=False, sms_code='', session_id='', message='用户不存在') @classmethod - async def forget_user_services(cls, request: Request, query_db: AsyncSession, forget_user: ResetUserModel): + async def forget_user_services( + cls, request: Request, query_db: AsyncSession, forget_user: ResetUserModel + ) -> CrudResponseModel: """ 用户忘记密码services @@ -501,15 +507,15 @@ class LoginService: edit_result = await UserService.reset_user_services(query_db, forget_user) result = edit_result.dict() elif not redis_sms_result: - result = dict(is_success=False, message='短信验证码已过期') + result = {'is_success': False, 'message': '短信验证码已过期'} else: await request.app.state.redis.delete(f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}') - result = dict(is_success=False, message='短信验证码不正确') + result = {'is_success': False, 'message': '短信验证码不正确'} return CrudResponseModel(**result) @classmethod - async def logout_services(cls, request: Request, token_id: str): + async def logout_services(cls, request: Request, token_id: str) -> bool: """ 退出登录services @@ -530,7 +536,7 @@ class RouterUtil: """ @classmethod - def get_router_name(cls, menu: MenuTreeModel): + def get_router_name(cls, menu: MenuTreeModel) -> str: """ 获取路由名称 @@ -544,7 +550,7 @@ class RouterUtil: return cls.get_route_name(menu.route_name, menu.path) @classmethod - def get_route_name(cls, name: str, path: str): + def get_route_name(cls, name: str, path: str) -> str: """ 获取路由名称,如没有配置路由名称则取路由地址 @@ -556,7 +562,7 @@ class RouterUtil: return router_name.capitalize() @classmethod - def get_router_path(cls, menu: MenuTreeModel): + def get_router_path(cls, menu: MenuTreeModel) -> Union[str, None]: """ 获取路由地址 @@ -576,7 +582,7 @@ class RouterUtil: return router_path @classmethod - def get_component(cls, menu: MenuTreeModel): + def get_component(cls, menu: MenuTreeModel) -> str: """ 获取组件信息 @@ -593,7 +599,7 @@ class RouterUtil: return component @classmethod - def is_menu_frame(cls, menu: MenuTreeModel): + def is_menu_frame(cls, menu: MenuTreeModel) -> bool: """ 判断是否为菜单内部跳转 @@ -605,7 +611,7 @@ class RouterUtil: ) @classmethod - def is_inner_link(cls, menu: MenuTreeModel): + def is_inner_link(cls, menu: MenuTreeModel) -> bool: """ 判断是否为内链组件 @@ -615,7 +621,7 @@ class RouterUtil: return menu.is_frame == MenuConstant.NO_FRAME and cls.is_http(menu.path) @classmethod - def is_parent_view(cls, menu: MenuTreeModel): + def is_parent_view(cls, menu: MenuTreeModel) -> bool: """ 判断是否为parent_view组件 @@ -625,17 +631,17 @@ class RouterUtil: return menu.parent_id != 0 and menu.menu_type == MenuConstant.TYPE_DIR @classmethod - def is_http(cls, link: str): + def is_http(cls, link: str) -> bool: """ 判断是否为http(s)://开头 :param link: 链接 :return: 是否为http(s)://开头 """ - return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) + return link.startswith((CommonConstant.HTTP, CommonConstant.HTTPS)) @classmethod - def inner_link_replace_each(cls, path: str): + def inner_link_replace_each(cls, path: str) -> str: """ 内链域名特殊字符替换 diff --git a/ruoyi-fastapi-backend/module_admin/service/menu_service.py b/ruoyi-fastapi-backend/module_admin/service/menu_service.py index cb6368ab609c0a892042b25451c1694504e88877..224e384de1431f9fdef540d2a4d267ed3a62e416 100644 --- a/ruoyi-fastapi-backend/module_admin/service/menu_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/menu_service.py @@ -1,11 +1,15 @@ +from collections.abc import Sequence +from typing import Any, Optional + from sqlalchemy.ext.asyncio import AsyncSession -from typing import Optional -from config.constant import CommonConstant, MenuConstant + +from common.constant import CommonConstant, MenuConstant +from common.vo import CrudResponseModel from exceptions.exception import ServiceException, ServiceWarning from module_admin.dao.menu_dao import MenuDao from module_admin.dao.role_dao import RoleDao -from module_admin.entity.vo.common_vo import CrudResponseModel -from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuQueryModel, MenuModel +from module_admin.entity.do.menu_do import SysMenu +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 utils.common_util import CamelCaseUtil @@ -18,7 +22,9 @@ class MenuService: """ @classmethod - async def get_menu_tree_services(cls, query_db: AsyncSession, current_user: Optional[CurrentUserModel] = None): + async def get_menu_tree_services( + cls, query_db: AsyncSession, current_user: Optional[CurrentUserModel] = None + ) -> list[dict[str, Any]]: """ 获取菜单树信息service @@ -29,14 +35,15 @@ class MenuService: menu_list_result = await MenuDao.get_menu_list_for_tree( query_db, current_user.user.user_id, current_user.user.role ) - menu_tree_result = cls.list_to_tree(menu_list_result) + menu_tree_model_result = cls.list_to_tree(menu_list_result) + menu_tree_result = [menu.model_dump(exclude_unset=True, by_alias=True) for menu in menu_tree_model_result] return menu_tree_result @classmethod async def get_role_menu_tree_services( cls, query_db: AsyncSession, role_id: int, current_user: Optional[CurrentUserModel] = None - ): + ) -> RoleMenuQueryModel: """ 根据角色id获取菜单树信息service @@ -59,7 +66,7 @@ class MenuService: @classmethod async def get_menu_list_services( cls, query_db: AsyncSession, page_object: MenuQueryModel, current_user: Optional[CurrentUserModel] = None - ): + ) -> list[dict[str, Any]]: """ 获取菜单列表信息service @@ -75,7 +82,7 @@ class MenuService: return CamelCaseUtil.transform_result(menu_list_result) @classmethod - async def check_menu_name_unique_services(cls, query_db: AsyncSession, page_object: MenuModel): + async def check_menu_name_unique_services(cls, query_db: AsyncSession, page_object: MenuModel) -> bool: """ 校验菜单名称是否唯一service @@ -90,7 +97,7 @@ class MenuService: return CommonConstant.UNIQUE @classmethod - async def add_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): + async def add_menu_services(cls, query_db: AsyncSession, page_object: MenuModel) -> CrudResponseModel: """ 新增菜单信息service @@ -100,19 +107,18 @@ class MenuService: """ if not await cls.check_menu_name_unique_services(query_db, page_object): raise ServiceException(message=f'新增菜单{page_object.menu_name}失败,菜单名称已存在') - elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + if page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): raise ServiceException(message=f'新增菜单{page_object.menu_name}失败,地址必须以http(s)://开头') - else: - try: - await MenuDao.add_menu_dao(query_db, page_object) - await query_db.commit() - return CrudResponseModel(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await MenuDao.add_menu_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - async def edit_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): + async def edit_menu_services(cls, query_db: AsyncSession, page_object: MenuModel) -> CrudResponseModel: """ 编辑菜单信息service @@ -125,23 +131,22 @@ class MenuService: if menu_info.menu_id: if not await cls.check_menu_name_unique_services(query_db, page_object): raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,菜单名称已存在') - elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + if page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,地址必须以http(s)://开头') - elif page_object.menu_id == page_object.parent_id: + if page_object.menu_id == page_object.parent_id: raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,上级菜单不能选择自己') - else: - try: - await MenuDao.edit_menu_dao(query_db, edit_menu) - await query_db.commit() - return CrudResponseModel(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await MenuDao.edit_menu_dao(query_db, edit_menu) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: raise ServiceException(message='菜单不存在') @classmethod - async def delete_menu_services(cls, query_db: AsyncSession, page_object: DeleteMenuModel): + async def delete_menu_services(cls, query_db: AsyncSession, page_object: DeleteMenuModel) -> CrudResponseModel: """ 删除菜单信息service @@ -155,7 +160,7 @@ class MenuService: for menu_id in menu_id_list: if (await MenuDao.has_child_by_menu_id_dao(query_db, int(menu_id))) > 0: raise ServiceWarning(message='存在子菜单,不允许删除') - elif (await MenuDao.check_menu_exist_role_dao(query_db, int(menu_id))) > 0: + if (await MenuDao.check_menu_exist_role_dao(query_db, int(menu_id))) > 0: raise ServiceWarning(message='菜单已分配,不允许删除') await MenuDao.delete_menu_dao(query_db, MenuModel(menuId=menu_id)) await query_db.commit() @@ -167,7 +172,7 @@ class MenuService: raise ServiceException(message='传入菜单id为空') @classmethod - async def menu_detail_services(cls, query_db: AsyncSession, menu_id: int): + async def menu_detail_services(cls, query_db: AsyncSession, menu_id: int) -> MenuModel: """ 获取菜单详细信息service @@ -176,40 +181,37 @@ class MenuService: :return: 菜单id对应的信息 """ menu = await MenuDao.get_menu_detail_by_id(query_db, menu_id=menu_id) - if menu: - result = MenuModel(**CamelCaseUtil.transform_result(menu)) - else: - result = MenuModel(**dict()) + result = MenuModel(**CamelCaseUtil.transform_result(menu)) if menu else MenuModel() return result @classmethod - def list_to_tree(cls, permission_list: list) -> list: + def list_to_tree(cls, permission_list: Sequence[SysMenu]) -> list[MenuTreeModel]: """ 工具方法:根据菜单列表信息生成树形嵌套数据 :param permission_list: 菜单列表信息 :return: 菜单树形嵌套数据 """ - permission_list = [ - dict(id=item.menu_id, label=item.menu_name, parentId=item.parent_id) for item in permission_list + _permission_list = [ + MenuTreeModel(id=item.menu_id, label=item.menu_name, parentId=item.parent_id) for item in permission_list ] # 转成id为key的字典 - mapping: dict = dict(zip([i['id'] for i in permission_list], permission_list)) + mapping: dict[int, MenuTreeModel] = dict(zip([i.id for i in _permission_list], _permission_list)) # 树容器 - container: list = [] + container: list[MenuTreeModel] = [] - for d in permission_list: + for d in _permission_list: # 如果找不到父级项,则是根节点 - parent: dict = mapping.get(d['parentId']) + parent = mapping.get(d.parent_id) if parent is None: container.append(d) else: - children: list = parent.get('children') + children: list[MenuTreeModel] = parent.children if not children: children = [] children.append(d) - parent.update({'children': children}) + parent.children = children return container diff --git a/ruoyi-fastapi-backend/module_admin/service/notice_service.py b/ruoyi-fastapi-backend/module_admin/service/notice_service.py index 4671703eb5a0710d6e4b1d3f84e86f12d9bb88af..4c210a880dca0f6610a21fe19cefd28ffbea5a01 100644 --- a/ruoyi-fastapi-backend/module_admin/service/notice_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/notice_service.py @@ -1,8 +1,11 @@ +from typing import Any, Union + from sqlalchemy.ext.asyncio import AsyncSession -from config.constant import CommonConstant + +from common.constant import CommonConstant +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException from module_admin.dao.notice_dao import NoticeDao -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel from utils.common_util import CamelCaseUtil @@ -15,7 +18,7 @@ class NoticeService: @classmethod async def get_notice_list_services( cls, query_db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = True - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取通知公告列表信息service @@ -29,7 +32,7 @@ class NoticeService: return notice_list_result @classmethod - async def check_notice_unique_services(cls, query_db: AsyncSession, page_object: NoticeModel): + async def check_notice_unique_services(cls, query_db: AsyncSession, page_object: NoticeModel) -> bool: """ 校验通知公告是否存在service @@ -44,7 +47,7 @@ class NoticeService: return CommonConstant.UNIQUE @classmethod - async def add_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): + async def add_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel) -> CrudResponseModel: """ 新增通知公告信息service @@ -54,17 +57,16 @@ class NoticeService: """ if not await cls.check_notice_unique_services(query_db, page_object): raise ServiceException(message=f'新增通知公告{page_object.notice_title}失败,通知公告已存在') - else: - try: - await NoticeDao.add_notice_dao(query_db, page_object) - await query_db.commit() - return CrudResponseModel(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await NoticeDao.add_notice_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - async def edit_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): + async def edit_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel) -> CrudResponseModel: """ 编辑通知公告信息service @@ -77,19 +79,18 @@ class NoticeService: if notice_info.notice_id: if not await cls.check_notice_unique_services(query_db, page_object): raise ServiceException(message=f'修改通知公告{page_object.notice_title}失败,通知公告已存在') - else: - try: - await NoticeDao.edit_notice_dao(query_db, edit_notice) - await query_db.commit() - return CrudResponseModel(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await NoticeDao.edit_notice_dao(query_db, edit_notice) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: raise ServiceException(message='通知公告不存在') @classmethod - async def delete_notice_services(cls, query_db: AsyncSession, page_object: DeleteNoticeModel): + async def delete_notice_services(cls, query_db: AsyncSession, page_object: DeleteNoticeModel) -> CrudResponseModel: """ 删除通知公告信息service @@ -111,7 +112,7 @@ class NoticeService: raise ServiceException(message='传入通知公告id为空') @classmethod - async def notice_detail_services(cls, query_db: AsyncSession, notice_id: int): + async def notice_detail_services(cls, query_db: AsyncSession, notice_id: int) -> NoticeModel: """ 获取通知公告详细信息service @@ -120,9 +121,6 @@ class NoticeService: :return: 通知公告id对应的信息 """ notice = await NoticeDao.get_notice_detail_by_id(query_db, notice_id=notice_id) - if notice: - result = NoticeModel(**CamelCaseUtil.transform_result(notice)) - else: - result = NoticeModel(**dict()) + result = NoticeModel(**CamelCaseUtil.transform_result(notice)) if notice else NoticeModel() return result diff --git a/ruoyi-fastapi-backend/module_admin/service/online_service.py b/ruoyi-fastapi-backend/module_admin/service/online_service.py index 56742b06a19c2f43b06e3b49266bb184aed7e833..6ed507023d0d1686e88ba9f1e12ef3484baa920d 100644 --- a/ruoyi-fastapi-backend/module_admin/service/online_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/online_service.py @@ -1,9 +1,12 @@ +from typing import Any + import jwt from fastapi import Request -from config.enums import RedisInitKeyConfig -from config.env import JwtConfig + +from common.enums import RedisInitKeyConfig +from common.vo import CrudResponseModel +from config.env import AppConfig, JwtConfig from exceptions.exception import ServiceException -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlineQueryModel from utils.common_util import CamelCaseUtil @@ -14,7 +17,7 @@ class OnlineService: """ @classmethod - async def get_online_list_services(cls, request: Request, query_object: OnlineQueryModel): + async def get_online_list_services(cls, request: Request, query_object: OnlineQueryModel) -> list[dict[str, Any]]: """ 获取在线用户表信息service @@ -29,16 +32,16 @@ class OnlineService: online_info_list = [] for item in access_token_values_list: payload = jwt.decode(item, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) - online_dict = dict( - token_id=payload.get('session_id'), - user_name=payload.get('user_name'), - dept_name=payload.get('dept_name'), - ipaddr=payload.get('login_info').get('ipaddr'), - login_location=payload.get('login_info').get('loginLocation'), - browser=payload.get('login_info').get('browser'), - os=payload.get('login_info').get('os'), - login_time=payload.get('login_info').get('loginTime'), - ) + online_dict = { + 'token_id': payload.get('session_id') if AppConfig.app_same_time_login else payload.get('user_id'), + 'user_name': payload.get('user_name'), + 'dept_name': payload.get('dept_name'), + 'ipaddr': payload.get('login_info').get('ipaddr'), + 'login_location': payload.get('login_info').get('loginLocation'), + 'browser': payload.get('login_info').get('browser'), + 'os': payload.get('login_info').get('os'), + 'login_time': payload.get('login_info').get('loginTime'), + } if query_object.user_name and not query_object.ipaddr: if query_object.user_name == payload.get('user_name'): online_info_list = [online_dict] @@ -59,7 +62,7 @@ class OnlineService: return CamelCaseUtil.transform_result(online_info_list) @classmethod - async def delete_online_services(cls, request: Request, page_object: DeleteOnlineModel): + async def delete_online_services(cls, request: Request, page_object: DeleteOnlineModel) -> CrudResponseModel: """ 强退在线用户信息service @@ -72,5 +75,4 @@ class OnlineService: for token_id in token_id_list: await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}') return CrudResponseModel(is_success=True, message='强退成功') - else: - raise ServiceException(message='传入session_id为空') + raise ServiceException(message='传入session_id为空') diff --git a/ruoyi-fastapi-backend/module_admin/service/post_service.py b/ruoyi-fastapi-backend/module_admin/service/post_service.py index 9338a9fc3041bd8aa45e4fd8cc1d4285b9a93888..c25d462c09e72f1e280a8f3bf4c4ccfcaa11767d 100644 --- a/ruoyi-fastapi-backend/module_admin/service/post_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/post_service.py @@ -1,9 +1,11 @@ +from typing import Any, Union + from sqlalchemy.ext.asyncio import AsyncSession -from typing import List -from config.constant import CommonConstant + +from common.constant import CommonConstant +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException from module_admin.dao.post_dao import PostDao -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel from utils.common_util import CamelCaseUtil from utils.excel_util import ExcelUtil @@ -17,7 +19,7 @@ class PostService: @classmethod async def get_post_list_services( cls, query_db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取岗位列表信息service @@ -31,7 +33,7 @@ class PostService: return post_list_result @classmethod - async def check_post_name_unique_services(cls, query_db: AsyncSession, page_object: PostModel): + async def check_post_name_unique_services(cls, query_db: AsyncSession, page_object: PostModel) -> bool: """ 检查岗位名称是否唯一service @@ -46,7 +48,7 @@ class PostService: return CommonConstant.UNIQUE @classmethod - async def check_post_code_unique_services(cls, query_db: AsyncSession, page_object: PostModel): + async def check_post_code_unique_services(cls, query_db: AsyncSession, page_object: PostModel) -> bool: """ 检查岗位编码是否唯一service @@ -61,7 +63,7 @@ class PostService: return CommonConstant.UNIQUE @classmethod - async def add_post_services(cls, query_db: AsyncSession, page_object: PostModel): + async def add_post_services(cls, query_db: AsyncSession, page_object: PostModel) -> CrudResponseModel: """ 新增岗位信息service @@ -71,19 +73,18 @@ class PostService: """ if not await cls.check_post_name_unique_services(query_db, page_object): raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位名称已存在') - elif not await cls.check_post_code_unique_services(query_db, page_object): + if not await cls.check_post_code_unique_services(query_db, page_object): raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位编码已存在') - else: - try: - await PostDao.add_post_dao(query_db, page_object) - await query_db.commit() - return CrudResponseModel(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await PostDao.add_post_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - async def edit_post_services(cls, query_db: AsyncSession, page_object: PostModel): + async def edit_post_services(cls, query_db: AsyncSession, page_object: PostModel) -> CrudResponseModel: """ 编辑岗位信息service @@ -96,21 +97,20 @@ class PostService: if post_info.post_id: if not await cls.check_post_name_unique_services(query_db, page_object): raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位名称已存在') - elif not await cls.check_post_code_unique_services(query_db, page_object): + if not await cls.check_post_code_unique_services(query_db, page_object): raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位编码已存在') - else: - try: - await PostDao.edit_post_dao(query_db, edit_post) - await query_db.commit() - return CrudResponseModel(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + try: + await PostDao.edit_post_dao(query_db, edit_post) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: raise ServiceException(message='岗位不存在') @classmethod - async def delete_post_services(cls, query_db: AsyncSession, page_object: DeletePostModel): + async def delete_post_services(cls, query_db: AsyncSession, page_object: DeletePostModel) -> CrudResponseModel: """ 删除岗位信息service @@ -135,7 +135,7 @@ class PostService: raise ServiceException(message='传入岗位id为空') @classmethod - async def post_detail_services(cls, query_db: AsyncSession, post_id: int): + async def post_detail_services(cls, query_db: AsyncSession, post_id: int) -> PostModel: """ 获取岗位详细信息service @@ -144,15 +144,12 @@ class PostService: :return: 岗位id对应的信息 """ post = await PostDao.get_post_detail_by_id(query_db, post_id=post_id) - if post: - result = PostModel(**CamelCaseUtil.transform_result(post)) - else: - result = PostModel(**dict()) + result = PostModel(**CamelCaseUtil.transform_result(post)) if post else PostModel() return result @staticmethod - async def export_post_list_services(post_list: List): + async def export_post_list_services(post_list: list) -> bytes: """ 导出岗位信息service diff --git a/ruoyi-fastapi-backend/module_admin/service/role_service.py b/ruoyi-fastapi-backend/module_admin/service/role_service.py index 4b633de4755396caa622bb06827d18da2eac632d..e41c53a85b23336072c24ae10342ea67bb2ea670 100644 --- a/ruoyi-fastapi-backend/module_admin/service/role_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/role_service.py @@ -1,8 +1,12 @@ +from typing import Any, Union + from sqlalchemy.ext.asyncio import AsyncSession -from typing import List -from config.constant import CommonConstant + +from common.constant import CommonConstant +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException -from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.role_dao import RoleDao +from module_admin.dao.user_dao import UserDao from module_admin.entity.vo.role_vo import ( AddRoleModel, DeleteRoleModel, @@ -13,11 +17,8 @@ from module_admin.entity.vo.role_vo import ( RolePageQueryModel, ) from module_admin.entity.vo.user_vo import UserInfoModel, UserRolePageQueryModel -from module_admin.dao.role_dao import RoleDao -from module_admin.dao.user_dao import UserDao from utils.common_util import CamelCaseUtil from utils.excel_util import ExcelUtil -from utils.page_util import PageResponseModel class RoleService: @@ -26,7 +27,7 @@ class RoleService: """ @classmethod - async def get_role_select_option_services(cls, query_db: AsyncSession): + async def get_role_select_option_services(cls, query_db: AsyncSession) -> list[dict[str, Any]]: """ 获取角色列表不分页信息service @@ -38,7 +39,7 @@ class RoleService: return CamelCaseUtil.transform_result(role_list_result) @classmethod - async def get_role_dept_tree_services(cls, query_db: AsyncSession, role_id: int): + async def get_role_dept_tree_services(cls, query_db: AsyncSession, role_id: int) -> RoleDeptQueryModel: """ 根据角色id获取部门树信息service @@ -56,7 +57,7 @@ class RoleService: @classmethod async def get_role_list_services( cls, query_db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取角色列表信息service @@ -71,7 +72,7 @@ class RoleService: return role_list_result @classmethod - async def check_role_allowed_services(cls, check_role: RoleModel): + async def check_role_allowed_services(cls, check_role: RoleModel) -> CrudResponseModel: """ 校验角色是否允许操作service @@ -80,11 +81,10 @@ class RoleService: """ if check_role.admin: raise ServiceException(message='不允许操作超级管理员角色') - else: - return CrudResponseModel(is_success=True, message='校验通过') + return CrudResponseModel(is_success=True, message='校验通过') @classmethod - async def check_role_data_scope_services(cls, query_db: AsyncSession, role_ids: str, data_scope_sql: str): + async def check_role_data_scope_services(cls, query_db: AsyncSession, role_ids: str, data_scope_sql: str) -> None: """ 校验角色是否有数据权限service @@ -101,11 +101,10 @@ class RoleService: ) if roles: continue - else: - raise ServiceException(message='没有权限访问角色数据') + raise ServiceException(message='没有权限访问角色数据') @classmethod - async def check_role_name_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + async def check_role_name_unique_services(cls, query_db: AsyncSession, page_object: RoleModel) -> bool: """ 校验角色名称是否唯一service @@ -120,7 +119,7 @@ class RoleService: return CommonConstant.UNIQUE @classmethod - async def check_role_key_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + async def check_role_key_unique_services(cls, query_db: AsyncSession, page_object: RoleModel) -> bool: """ 校验角色权限字符是否唯一service @@ -135,7 +134,7 @@ class RoleService: return CommonConstant.UNIQUE @classmethod - async def add_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): + async def add_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel) -> CrudResponseModel: """ 新增角色信息service @@ -146,23 +145,22 @@ class RoleService: add_role = RoleModel(**page_object.model_dump(by_alias=True)) if not await cls.check_role_name_unique_services(query_db, page_object): raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色名称已存在') - elif not await cls.check_role_key_unique_services(query_db, page_object): + if not await cls.check_role_key_unique_services(query_db, page_object): raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色权限已存在') - else: - try: - add_result = await RoleDao.add_role_dao(query_db, add_role) - role_id = add_result.role_id - if page_object.menu_ids: - for menu in page_object.menu_ids: - await RoleDao.add_role_menu_dao(query_db, RoleMenuModel(roleId=role_id, menuId=menu)) - await query_db.commit() - return CrudResponseModel(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + try: + add_result = await RoleDao.add_role_dao(query_db, add_role) + role_id = add_result.role_id + if page_object.menu_ids: + for menu in page_object.menu_ids: + await RoleDao.add_role_menu_dao(query_db, RoleMenuModel(roleId=role_id, menuId=menu)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - async def edit_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): + async def edit_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel) -> CrudResponseModel: """ 编辑角色信息service @@ -180,7 +178,7 @@ class RoleService: if page_object.type != 'status': if not await cls.check_role_name_unique_services(query_db, page_object): raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色名称已存在') - elif not await cls.check_role_key_unique_services(query_db, page_object): + if not await cls.check_role_key_unique_services(query_db, page_object): raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色权限已存在') try: await RoleDao.edit_role_dao(query_db, edit_role) @@ -200,7 +198,7 @@ class RoleService: raise ServiceException(message='角色不存在') @classmethod - async def role_datascope_services(cls, query_db: AsyncSession, page_object: AddRoleModel): + async def role_datascope_services(cls, query_db: AsyncSession, page_object: AddRoleModel) -> CrudResponseModel: """ 分配角色数据权限service @@ -228,7 +226,7 @@ class RoleService: raise ServiceException(message='角色不存在') @classmethod - async def delete_role_services(cls, query_db: AsyncSession, page_object: DeleteRoleModel): + async def delete_role_services(cls, query_db: AsyncSession, page_object: DeleteRoleModel) -> CrudResponseModel: """ 删除角色信息service @@ -243,9 +241,11 @@ class RoleService: role = await cls.role_detail_services(query_db, int(role_id)) if (await RoleDao.count_user_role_dao(query_db, int(role_id))) > 0: raise ServiceException(message=f'角色{role.role_name}已分配,不能删除') - role_id_dict = dict( - roleId=role_id, updateBy=page_object.update_by, updateTime=page_object.update_time - ) + role_id_dict = { + 'roleId': role_id, + 'updateBy': page_object.update_by, + 'updateTime': page_object.update_time, + } await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(**role_id_dict)) await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(**role_id_dict)) await RoleDao.delete_role_dao(query_db, RoleModel(**role_id_dict)) @@ -258,7 +258,7 @@ class RoleService: raise ServiceException(message='传入角色id为空') @classmethod - async def role_detail_services(cls, query_db: AsyncSession, role_id: int): + async def role_detail_services(cls, query_db: AsyncSession, role_id: int) -> RoleModel: """ 获取角色详细信息service @@ -267,15 +267,12 @@ class RoleService: :return: 角色id对应的信息 """ role = await RoleDao.get_role_detail_by_id(query_db, role_id=role_id) - if role: - result = RoleModel(**CamelCaseUtil.transform_result(role)) - else: - result = RoleModel(**dict()) + result = RoleModel(**CamelCaseUtil.transform_result(role)) if role else RoleModel() return result @staticmethod - async def export_role_list_services(role_list: List): + async def export_role_list_services(role_list: list) -> bytes: """ 导出角色列表信息service @@ -308,7 +305,7 @@ class RoleService: @classmethod async def get_role_user_allocated_list_services( cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False - ): + ) -> PageModel: """ 根据角色id获取已分配用户列表 @@ -321,7 +318,7 @@ class RoleService: query_user_list = await UserDao.get_user_role_allocated_list_by_role_id( query_db, page_object, data_scope_sql, is_page ) - allocated_list = PageResponseModel( + allocated_list = PageModel[UserInfoModel]( **{ **query_user_list.model_dump(by_alias=True), 'rows': [UserInfoModel(**row) for row in query_user_list.rows], @@ -333,7 +330,7 @@ class RoleService: @classmethod async def get_role_user_unallocated_list_services( cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False - ): + ) -> PageModel: """ 根据角色id获取未分配用户列表 @@ -346,7 +343,7 @@ class RoleService: query_user_list = await UserDao.get_user_role_unallocated_list_by_role_id( query_db, page_object, data_scope_sql, is_page ) - unallocated_list = PageResponseModel( + unallocated_list = PageModel[UserInfoModel]( **{ **query_user_list.model_dump(by_alias=True), 'rows': [UserInfoModel(**row) for row in query_user_list.rows], diff --git a/ruoyi-fastapi-backend/module_admin/service/server_service.py b/ruoyi-fastapi-backend/module_admin/service/server_service.py index 2f9a53fecf012c1d80f4d246119ed562b33cc568..5a396987b8e040a033e370f60a88eee7ff38ed2d 100644 --- a/ruoyi-fastapi-backend/module_admin/service/server_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/server_service.py @@ -1,8 +1,10 @@ import os import platform -import psutil import socket import time + +import psutil + from module_admin.entity.vo.server_vo import CpuInfo, MemoryInfo, PyInfo, ServerMonitorModel, SysFiles, SysInfo from utils.common_util import bytes2human @@ -13,7 +15,7 @@ class ServerService: """ @staticmethod - async def get_server_monitor_info(): + async def get_server_monitor_info() -> ServerMonitorModel: # CPU信息 # 获取CPU总核心数 cpu_num = psutil.cpu_count(logical=True) @@ -79,17 +81,21 @@ class ServerService: io = psutil.disk_partitions() sys_files = [] for i in io: - o = psutil.disk_usage(i.device) - disk_data = SysFiles( - dirName=i.device, - sysTypeName=i.fstype, - typeName='本地固定磁盘(' + i.mountpoint.replace('\\', '') + ')', - total=bytes2human(o.total), - used=bytes2human(o.used), - free=bytes2human(o.free), - usage=f'{psutil.disk_usage(i.device).percent}%', - ) - sys_files.append(disk_data) + try: + o = psutil.disk_usage(i.device) + disk_data = SysFiles( + dirName=i.device, + sysTypeName=i.fstype, + typeName='本地固定磁盘(' + i.mountpoint.replace('\\', '') + ')', + total=bytes2human(o.total), + used=bytes2human(o.used), + free=bytes2human(o.free), + usage=f'{psutil.disk_usage(i.device).percent}%', + ) + sys_files.append(disk_data) + except Exception: # noqa: PERF203 + # 忽略所有异常,跳过有问题的磁盘 + continue result = ServerMonitorModel(cpu=cpu, mem=mem, sys=sys, py=py, sysFiles=sys_files) diff --git a/ruoyi-fastapi-backend/module_admin/service/user_service.py b/ruoyi-fastapi-backend/module_admin/service/user_service.py index 60f77c6a727a3dab5e35af488f3f9e61dc905f27..1d88d03b1ddbf33eb8a0b49c91862a5fc1a0ee6f 100644 --- a/ruoyi-fastapi-backend/module_admin/service/user_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/user_service.py @@ -1,13 +1,16 @@ import io -import pandas as pd from datetime import datetime +from typing import Any, Union + +import pandas as pd from fastapi import Request, UploadFile from sqlalchemy.ext.asyncio import AsyncSession -from typing import List, Union -from config.constant import CommonConstant + +from common.constant import CommonConstant +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException from module_admin.dao.user_dao import UserDao -from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.do.user_do import SysUserRole from module_admin.entity.vo.post_vo import PostPageQueryModel from module_admin.entity.vo.user_vo import ( AddUserModel, @@ -26,6 +29,7 @@ from module_admin.entity.vo.user_vo import ( UserRoleModel, UserRoleQueryModel, UserRoleResponseModel, + UserRowModel, ) from module_admin.service.config_service import ConfigService from module_admin.service.dept_service import DeptService @@ -33,7 +37,6 @@ from module_admin.service.post_service import PostService from module_admin.service.role_service import RoleService from utils.common_util import CamelCaseUtil from utils.excel_util import ExcelUtil -from utils.page_util import PageResponseModel from utils.pwd_util import PwdUtil @@ -45,7 +48,7 @@ class UserService: @classmethod async def get_user_list_services( cls, query_db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False - ): + ) -> Union[PageModel[UserRowModel], list[dict[str, Any]]]: """ 获取用户列表信息service @@ -57,7 +60,7 @@ class UserService: """ query_result = await UserDao.get_user_list(query_db, query_object, data_scope_sql, is_page) if is_page: - user_list_result = PageResponseModel( + user_list_result = PageModel[UserRowModel]( **{ **query_result.model_dump(by_alias=True), 'rows': [{**row[0], 'dept': row[1]} for row in query_result.rows], @@ -71,7 +74,7 @@ class UserService: return user_list_result @classmethod - async def check_user_allowed_services(cls, check_user: UserModel): + async def check_user_allowed_services(cls, check_user: UserModel) -> CrudResponseModel: """ 校验用户是否允许操作service @@ -80,11 +83,12 @@ class UserService: """ if check_user.admin: raise ServiceException(message='不允许操作超级管理员用户') - else: - return CrudResponseModel(is_success=True, message='校验通过') + return CrudResponseModel(is_success=True, message='校验通过') @classmethod - async def check_user_data_scope_services(cls, query_db: AsyncSession, user_id: int, data_scope_sql: str): + async def check_user_data_scope_services( + cls, query_db: AsyncSession, user_id: int, data_scope_sql: str + ) -> CrudResponseModel: """ 校验用户数据权限service @@ -96,11 +100,10 @@ class UserService: users = await UserDao.get_user_list(query_db, UserPageQueryModel(userId=user_id), data_scope_sql, is_page=False) if users: return CrudResponseModel(is_success=True, message='校验通过') - else: - raise ServiceException(message='没有权限访问用户数据') + raise ServiceException(message='没有权限访问用户数据') @classmethod - async def check_user_name_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + async def check_user_name_unique_services(cls, query_db: AsyncSession, page_object: UserModel) -> bool: """ 校验用户名是否唯一service @@ -115,7 +118,7 @@ class UserService: return CommonConstant.UNIQUE @classmethod - async def check_phonenumber_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + async def check_phonenumber_unique_services(cls, query_db: AsyncSession, page_object: UserModel) -> bool: """ 校验用户手机号是否唯一service @@ -130,7 +133,7 @@ class UserService: return CommonConstant.UNIQUE @classmethod - async def check_email_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + async def check_email_unique_services(cls, query_db: AsyncSession, page_object: UserModel) -> bool: """ 校验用户邮箱是否唯一service @@ -145,7 +148,7 @@ class UserService: return CommonConstant.UNIQUE @classmethod - async def add_user_services(cls, query_db: AsyncSession, page_object: AddUserModel): + async def add_user_services(cls, query_db: AsyncSession, page_object: AddUserModel) -> CrudResponseModel: """ 新增用户信息service @@ -156,28 +159,43 @@ class UserService: add_user = UserModel(**page_object.model_dump(by_alias=True)) if not await cls.check_user_name_unique_services(query_db, page_object): raise ServiceException(message=f'新增用户{page_object.user_name}失败,登录账号已存在') - elif page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): + if page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): raise ServiceException(message=f'新增用户{page_object.user_name}失败,手机号码已存在') - elif page_object.email and not await cls.check_email_unique_services(query_db, page_object): + if page_object.email and not await cls.check_email_unique_services(query_db, page_object): raise ServiceException(message=f'新增用户{page_object.user_name}失败,邮箱账号已存在') + try: + add_result = await UserDao.add_user_dao(query_db, add_user) + user_id = add_result.user_id + if page_object.role_ids: + for role in page_object.role_ids: + await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=role)) + if page_object.post_ids: + for post in page_object.post_ids: + await UserDao.add_user_post_dao(query_db, UserPostModel(userId=user_id, postId=post)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + def _deal_edit_user(cls, page_object: EditUserModel, edit_user: dict[str, Any]) -> None: + """ + 处理编辑用户字典 + + :param page_object: 编辑用户对象 + :param edit_user: 编辑用户字典 + :return: None + """ + if page_object.type not in ['status', 'avatar', 'pwd']: + del edit_user['role_ids'] + del edit_user['post_ids'] + del edit_user['role'] else: - try: - add_result = await UserDao.add_user_dao(query_db, add_user) - user_id = add_result.user_id - if page_object.role_ids: - for role in page_object.role_ids: - await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=role)) - if page_object.post_ids: - for post in page_object.post_ids: - await UserDao.add_user_post_dao(query_db, UserPostModel(userId=user_id, postId=post)) - await query_db.commit() - return CrudResponseModel(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e + del edit_user['type'] @classmethod - async def edit_user_services(cls, query_db: AsyncSession, page_object: EditUserModel): + async def edit_user_services(cls, query_db: AsyncSession, page_object: EditUserModel) -> CrudResponseModel: """ 编辑用户信息service @@ -186,24 +204,19 @@ class UserService: :return: 编辑用户校验结果 """ edit_user = page_object.model_dump(exclude_unset=True, exclude={'admin'}) - if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': - del edit_user['role_ids'] - del edit_user['post_ids'] - del edit_user['role'] - if page_object.type == 'status' or page_object.type == 'avatar' or page_object.type == 'pwd': - del edit_user['type'] + cls._deal_edit_user(page_object, edit_user) user_info = await cls.user_detail_services(query_db, edit_user.get('user_id')) if user_info.data and user_info.data.user_id: - if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + if page_object.type not in ['status', 'avatar', 'pwd']: if not await cls.check_user_name_unique_services(query_db, page_object): raise ServiceException(message=f'修改用户{page_object.user_name}失败,登录账号已存在') - elif page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): + if page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): raise ServiceException(message=f'修改用户{page_object.user_name}失败,手机号码已存在') - elif page_object.email and not await cls.check_email_unique_services(query_db, page_object): + if page_object.email and not await cls.check_email_unique_services(query_db, page_object): raise ServiceException(message=f'修改用户{page_object.user_name}失败,邮箱账号已存在') try: await UserDao.edit_user_dao(query_db, edit_user) - if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + if page_object.type not in {'status', 'avatar', 'pwd'}: await UserDao.delete_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) await UserDao.delete_user_post_dao(query_db, UserPostModel(userId=page_object.user_id)) if page_object.role_ids: @@ -225,7 +238,7 @@ class UserService: raise ServiceException(message='用户不存在') @classmethod - async def delete_user_services(cls, query_db: AsyncSession, page_object: DeleteUserModel): + async def delete_user_services(cls, query_db: AsyncSession, page_object: DeleteUserModel) -> CrudResponseModel: """ 删除用户信息service @@ -237,9 +250,11 @@ class UserService: user_id_list = page_object.user_ids.split(',') try: for user_id in user_id_list: - user_id_dict = dict( - userId=user_id, updateBy=page_object.update_by, updateTime=page_object.update_time - ) + user_id_dict = { + 'userId': user_id, + 'updateBy': page_object.update_by, + 'updateTime': page_object.update_time, + } await UserDao.delete_user_role_dao(query_db, UserRoleModel(**user_id_dict)) await UserDao.delete_user_post_dao(query_db, UserPostModel(**user_id_dict)) await UserDao.delete_user_dao(query_db, UserModel(**user_id_dict)) @@ -252,7 +267,7 @@ class UserService: raise ServiceException(message='传入用户id为空') @classmethod - async def user_detail_services(cls, query_db: AsyncSession, user_id: Union[int, str]): + async def user_detail_services(cls, query_db: AsyncSession, user_id: Union[int, str]) -> UserDetailModel: """ 获取用户详细信息service @@ -260,7 +275,7 @@ class UserService: :param user_id: 用户id :return: 用户id对应的信息 """ - posts = await PostService.get_post_list_services(query_db, PostPageQueryModel(**{}), is_page=False) + posts = await PostService.get_post_list_services(query_db, PostPageQueryModel(), is_page=False) roles = await RoleService.get_role_select_option_services(query_db) if user_id != '': query_user = await UserDao.get_user_detail_by_id(query_db, user_id=user_id) @@ -286,7 +301,7 @@ class UserService: return UserDetailModel(posts=posts, roles=roles) @classmethod - async def user_profile_services(cls, query_db: AsyncSession, user_id: int): + async def user_profile_services(cls, query_db: AsyncSession, user_id: int) -> UserProfileModel: """ 获取用户个人详细信息service @@ -313,7 +328,7 @@ class UserService: ) @classmethod - async def reset_user_services(cls, query_db: AsyncSession, page_object: ResetUserModel): + async def reset_user_services(cls, query_db: AsyncSession, page_object: ResetUserModel) -> CrudResponseModel: """ 重置用户密码service @@ -326,10 +341,9 @@ class UserService: user = (await UserDao.get_user_detail_by_id(query_db, user_id=page_object.user_id)).get('user_basic_info') if not PwdUtil.verify_password(page_object.old_password, user.password): raise ServiceException(message='修改密码失败,旧密码错误') - elif PwdUtil.verify_password(page_object.password, user.password): + if PwdUtil.verify_password(page_object.password, user.password): raise ServiceException(message='新密码不能与旧密码相同') - else: - del reset_user['old_password'] + del reset_user['old_password'] if page_object.sms_code and page_object.session_id: del reset_user['sms_code'] del reset_user['session_id'] @@ -342,6 +356,34 @@ class UserService: await query_db.rollback() raise e + @classmethod + def _set_row_sex_value(cls, row: pd.Series) -> None: + """ + 设置行性别值 + + :param row: 行数据 + :return: None + """ + if row['sex'] == '男': + row['sex'] = '0' + if row['sex'] == '女': + row['sex'] = '1' + if row['sex'] == '未知': + row['sex'] = '2' + + @classmethod + def _set_row_status_value(cls, row: pd.Series) -> None: + """ + 设置行状态值 + + :param row: 行数据 + :return: None + """ + if row['status'] == '正常': + row['status'] = '0' + if row['status'] == '停用': + row['status'] = '1' + @classmethod async def batch_import_user_services( cls, @@ -352,7 +394,7 @@ class UserService: current_user: CurrentUserModel, user_data_scope_sql: str, dept_data_scope_sql: str, - ): + ) -> CrudResponseModel: """ 批量导入用户service @@ -381,18 +423,10 @@ class UserService: add_error_result = [] count = 0 try: - for index, row in df.iterrows(): + for _index, row in df.iterrows(): count = count + 1 - if row['sex'] == '男': - row['sex'] = '0' - if row['sex'] == '女': - row['sex'] = '1' - if row['sex'] == '未知': - row['sex'] = '2' - if row['status'] == '正常': - row['status'] = '0' - if row['status'] == '停用': - row['status'] = '1' + cls._set_row_sex_value(row) + cls._set_row_status_value(row) add_user = UserModel( deptId=row['dept_id'], userName=row['user_name'], @@ -438,7 +472,7 @@ class UserService: edit_user = edit_user_model.model_dump(exclude_unset=True) await UserDao.edit_user_dao(query_db, edit_user) else: - add_error_result.append(f"{count}.用户账号{row['user_name']}已存在") + add_error_result.append(f'{count}.用户账号{row["user_name"]}已存在') else: add_user.validate_fields() if not current_user.user.admin: @@ -453,7 +487,7 @@ class UserService: raise e @staticmethod - async def get_user_import_template_services(): + async def get_user_import_template_services() -> bytes: """ 获取用户导入模板service @@ -469,7 +503,7 @@ class UserService: return binary_data @staticmethod - async def export_user_list_services(user_list: List): + async def export_user_list_services(user_list: list) -> bytes: """ 导出用户信息service @@ -510,7 +544,9 @@ class UserService: return binary_data @classmethod - async def get_user_role_allocated_list_services(cls, query_db: AsyncSession, page_object: UserRoleQueryModel): + async def get_user_role_allocated_list_services( + cls, query_db: AsyncSession, page_object: UserRoleQueryModel + ) -> UserRoleResponseModel: """ 根据用户id获取已分配角色列表 @@ -540,7 +576,7 @@ class UserService: return result @classmethod - async def add_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): + async def add_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel) -> CrudResponseModel: """ 新增用户关联角色信息service @@ -576,10 +612,7 @@ class UserService: ) if user_role: continue - else: - await UserDao.add_user_role_dao( - query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id) - ) + await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)) await query_db.commit() return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: @@ -589,7 +622,9 @@ class UserService: raise ServiceException(message='不满足新增条件') @classmethod - async def delete_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): + async def delete_user_role_services( + cls, query_db: AsyncSession, page_object: CrudUserRoleModel + ) -> CrudResponseModel: """ 删除用户关联角色信息service @@ -626,7 +661,9 @@ class UserService: raise ServiceException(message='传入用户角色关联信息为空') @classmethod - async def detail_user_role_services(cls, query_db: AsyncSession, page_object: UserRoleModel): + async def detail_user_role_services( + cls, query_db: AsyncSession, page_object: UserRoleModel + ) -> Union[SysUserRole, None]: """ 获取用户关联角色详细信息service diff --git a/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py index 4e227c1a1bfe5a7b8c299baad47edd910cabf5b1..997428ab251cda679f825cb02579290f35c617e4 100644 --- a/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py +++ b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py @@ -1,33 +1,48 @@ from datetime import datetime -from fastapi import APIRouter, Depends, Query, Request +from typing import Annotated + +from fastapi import 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 common.annotation.log_annotation import Log +from common.aspect.db_seesion import DBSessionDependency +from common.aspect.interface_auth import RoleInterfaceAuthDependency, 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 config.env import GenConfig -from config.get_db import get_db -from module_admin.annotation.log_annotation import Log -from module_admin.aspect.interface_auth import CheckRoleInterfaceAuth, CheckUserInterfaceAuth -from module_admin.service.login_service import LoginService from module_admin.entity.vo.user_vo import CurrentUserModel -from module_generator.entity.vo.gen_vo import DeleteGenTableModel, EditGenTableModel, GenTablePageQueryModel +from module_generator.entity.vo.gen_vo import ( + DeleteGenTableModel, + EditGenTableModel, + GenTableDbRowModel, + GenTableDetailModel, + GenTablePageQueryModel, + GenTableRowModel, +) from module_generator.service.gen_service import GenTableColumnService, GenTableService 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 +gen_controller = APIRouterPro(prefix='/tool/gen', order_num=17, tags=['代码生成'], dependencies=[PreAuthDependency()]) -genController = APIRouter(prefix='/tool/gen', dependencies=[Depends(LoginService.get_current_user)]) - -@genController.get( - '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))] +@gen_controller.get( + '/list', + summary='获取代码生成表分页列表接口', + description='用于获取代码生成表分页列表', + response_model=PageResponseModel[GenTableRowModel], + dependencies=[UserInterfaceAuthDependency('tool:gen:list')], ) async def get_gen_table_list( request: Request, - gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query), - query_db: AsyncSession = Depends(get_db), -): + gen_page_query: Annotated[GenTablePageQueryModel, Query()], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: # 获取分页数据 gen_page_query_result = await GenTableService.get_gen_table_list_services(query_db, gen_page_query, is_page=True) logger.info('获取成功') @@ -35,14 +50,18 @@ async def get_gen_table_list( return ResponseUtil.success(model_content=gen_page_query_result) -@genController.get( - '/db/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))] +@gen_controller.get( + '/db/list', + summary='获取数据库表分页列表接口', + description='用于获取数据库表分页列表', + response_model=PageResponseModel[GenTableDbRowModel], + dependencies=[UserInterfaceAuthDependency('tool:gen:list')], ) async def get_gen_db_table_list( request: Request, - gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query), - query_db: AsyncSession = Depends(get_db), -): + gen_page_query: Annotated[GenTablePageQueryModel, Query()], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: # 获取分页数据 gen_page_query_result = await GenTableService.get_gen_db_table_list_services(query_db, gen_page_query, is_page=True) logger.info('获取成功') @@ -50,14 +69,20 @@ async def get_gen_db_table_list( return ResponseUtil.success(model_content=gen_page_query_result) -@genController.post('/importTable', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:import'))]) +@gen_controller.post( + '/importTable', + summary='导入数据库表接口', + description='用于导入数据库表', + response_model=ResponseBaseModel, + dependencies=[UserInterfaceAuthDependency('tool:gen:import')], +) @Log(title='代码生成', business_type=BusinessType.IMPORT) async def import_gen_table( request: Request, - tables: str = Query(), - query_db: AsyncSession = Depends(get_db), - current_user: CurrentUserModel = Depends(LoginService.get_current_user), -): + tables: Annotated[str, Query()], + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: table_names = tables.split(',') if tables else [] add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(query_db, table_names) add_gen_table_result = await GenTableService.import_gen_table_services(query_db, add_gen_table_list, current_user) @@ -66,15 +91,21 @@ async def import_gen_table( return ResponseUtil.success(msg=add_gen_table_result.message) -@genController.put('', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))]) +@gen_controller.put( + '', + summary='编辑代码生成表接口', + description='用于编辑代码生成表', + response_model=ResponseBaseModel, + dependencies=[UserInterfaceAuthDependency('tool:gen:edit')], +) @ValidateFields(validate_model='edit_gen_table') @Log(title='代码生成', business_type=BusinessType.UPDATE) async def edit_gen_table( request: Request, edit_gen_table: EditGenTableModel, - 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_gen_table.update_by = current_user.user.user_name edit_gen_table.update_time = datetime.now() await GenTableService.validate_edit(edit_gen_table) @@ -84,9 +115,19 @@ async def edit_gen_table( return ResponseUtil.success(msg=edit_gen_result.message) -@genController.delete('/{table_ids}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:remove'))]) +@gen_controller.delete( + '/{table_ids}', + summary='删除代码生成表接口', + description='用于删除代码生成表', + response_model=ResponseBaseModel, + dependencies=[UserInterfaceAuthDependency('tool:gen:remove')], +) @Log(title='代码生成', business_type=BusinessType.DELETE) -async def delete_gen_table(request: Request, table_ids: str, query_db: AsyncSession = Depends(get_db)): +async def delete_gen_table( + request: Request, + table_ids: Annotated[str, Path(description='需要删除的代码生成业务表ID')], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: delete_gen_table = DeleteGenTableModel(tableIds=table_ids) delete_gen_table_result = await GenTableService.delete_gen_table_services(query_db, delete_gen_table) logger.info(delete_gen_table_result.message) @@ -94,23 +135,47 @@ async def delete_gen_table(request: Request, table_ids: str, query_db: AsyncSess return ResponseUtil.success(msg=delete_gen_table_result.message) -@genController.post('/createTable', dependencies=[Depends(CheckRoleInterfaceAuth('admin'))]) +@gen_controller.post( + '/createTable', + summary='创建数据库表接口', + description='用于创建数据库表', + response_model=ResponseBaseModel, + dependencies=[RoleInterfaceAuthDependency('admin')], +) @Log(title='创建表', business_type=BusinessType.OTHER) async def create_table( request: Request, - sql: str = Query(), - query_db: AsyncSession = Depends(get_db), - current_user: CurrentUserModel = Depends(LoginService.get_current_user), -): + sql: Annotated[str, Query()], + query_db: Annotated[AsyncSession, DBSessionDependency()], + current_user: Annotated[CurrentUserModel, CurrentUserDependency()], +) -> Response: create_table_result = await GenTableService.create_table_services(query_db, sql, current_user) logger.info(create_table_result.message) return ResponseUtil.success(msg=create_table_result.message) -@genController.get('/batchGenCode', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))]) +@gen_controller.get( + '/batchGenCode', + summary='生成代码文件接口', + description='用于生成代码文件', + response_class=StreamingResponse, + responses={ + 200: { + 'description': '流式返回生成的代码文件', + 'content': { + 'application/octet-stream': {}, + }, + } + }, + dependencies=[UserInterfaceAuthDependency('tool:gen:code')], +) @Log(title='代码生成', business_type=BusinessType.GENCODE) -async def batch_gen_code(request: Request, tables: str = Query(), query_db: AsyncSession = Depends(get_db)): +async def batch_gen_code( + request: Request, + tables: Annotated[str, Query()], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: table_names = tables.split(',') if tables else [] batch_gen_code_result = await GenTableService.batch_gen_code_services(query_db, table_names) logger.info('生成代码成功') @@ -118,9 +183,19 @@ async def batch_gen_code(request: Request, tables: str = Query(), query_db: Asyn return ResponseUtil.streaming(data=bytes2file_response(batch_gen_code_result)) -@genController.get('/genCode/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))]) +@gen_controller.get( + '/genCode/{table_name}', + summary='生成代码文件到本地接口', + description='用于生成代码文件到本地', + response_model=ResponseBaseModel, + dependencies=[UserInterfaceAuthDependency('tool:gen:code')], +) @Log(title='代码生成', business_type=BusinessType.GENCODE) -async def gen_code_local(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)): +async def gen_code_local( + request: Request, + table_name: Annotated[str, Path(description='表名称')], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: if not GenConfig.allow_overwrite: logger.error('【系统预设】不允许生成文件覆盖到本地') return ResponseUtil.error('【系统预设】不允许生成文件覆盖到本地') @@ -130,28 +205,58 @@ async def gen_code_local(request: Request, table_name: str, query_db: AsyncSessi return ResponseUtil.success(msg=gen_code_local_result.message) -@genController.get('/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:query'))]) -async def query_detail_gen_table(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)): +@gen_controller.get( + '/{table_id}', + summary='获取代码生成表详情接口', + description='用于获取指定代码生成表的详细信息', + response_model=DataResponseModel[GenTableDetailModel], + dependencies=[UserInterfaceAuthDependency('tool:gen:query')], +) +async def query_detail_gen_table( + request: Request, + table_id: Annotated[int, Path(description='表编号')], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: gen_table = await GenTableService.get_gen_table_by_id_services(query_db, table_id) gen_tables = await GenTableService.get_gen_table_all_services(query_db) gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(query_db, table_id) - gen_table_detail_result = dict(info=gen_table, rows=gen_columns, tables=gen_tables) + gen_table_detail_result = {'info': gen_table, 'rows': gen_columns, 'tables': gen_tables} logger.info(f'获取table_id为{table_id}的信息成功') return ResponseUtil.success(data=gen_table_detail_result) -@genController.get('/preview/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:preview'))]) -async def preview_code(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)): +@gen_controller.get( + '/preview/{table_id}', + summary='预览生成的代码接口', + description='用于预览指定代码生成表生成的代码', + response_model=DataResponseModel[dict[str, str]], + dependencies=[UserInterfaceAuthDependency('tool:gen:preview')], +) +async def preview_code( + request: Request, + table_id: Annotated[int, Path(description='表编号')], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: preview_code_result = await GenTableService.preview_code_services(query_db, table_id) logger.info('获取预览代码成功') return ResponseUtil.success(data=preview_code_result) -@genController.get('/synchDb/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))]) +@gen_controller.get( + '/synchDb/{table_name}', + summary='同步数据库接口', + description='用于同步指定数据库信息到指定代码生成表', + response_model=DataResponseModel[str], + dependencies=[UserInterfaceAuthDependency('tool:gen:edit')], +) @Log(title='代码生成', business_type=BusinessType.UPDATE) -async def sync_db(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)): +async def sync_db( + request: Request, + table_name: Annotated[str, Path(description='表名称')], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: sync_db_result = await GenTableService.sync_db_services(query_db, table_name) logger.info(sync_db_result.message) diff --git a/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py index 937668a8ba1e4a84dd9918f002ab8bcdaeadc999..d490ca217ec068bdc2e028485a6b208c64ebcd5d 100644 --- a/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py +++ b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py @@ -1,9 +1,13 @@ +from collections.abc import Sequence from datetime import datetime, time -from sqlalchemy import delete, func, select, text, update +from typing import Any, Union + +from sqlalchemy import Row, delete, func, select, text, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from sqlglot.expressions import Expression -from typing import List + +from common.vo import PageModel from config.env import DataBaseConfig from module_generator.entity.do.gen_do import GenTable, GenTableColumn from module_generator.entity.vo.gen_vo import ( @@ -22,7 +26,7 @@ class GenTableDao: """ @classmethod - async def get_gen_table_by_id(cls, db: AsyncSession, table_id: int): + async def get_gen_table_by_id(cls, db: AsyncSession, table_id: int) -> Union[GenTable, None]: """ 根据业务表id获取需要生成的业务表信息 @@ -43,7 +47,7 @@ class GenTableDao: return gen_table_info @classmethod - async def get_gen_table_by_name(cls, db: AsyncSession, table_name: str): + async def get_gen_table_by_name(cls, db: AsyncSession, table_name: str) -> Union[GenTable, None]: """ 根据业务表名称获取需要生成的业务表信息 @@ -64,7 +68,7 @@ class GenTableDao: return gen_table_info @classmethod - async def get_gen_table_all(cls, db: AsyncSession): + async def get_gen_table_all(cls, db: AsyncSession) -> Sequence[GenTable]: """ 获取所有业务表信息 @@ -76,7 +80,7 @@ class GenTableDao: return gen_table_all @classmethod - async def create_table_by_sql_dao(cls, db: AsyncSession, sql_statements: List[Expression]): + async def create_table_by_sql_dao(cls, db: AsyncSession, sql_statements: list[Expression]) -> None: """ 根据sql语句创建表结构 @@ -89,7 +93,9 @@ class GenTableDao: await db.execute(text(sql)) @classmethod - async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False): + async def get_gen_table_list( + cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False + ) -> Union[PageModel, list[dict[str, Any]]]: """ 根据查询参数获取代码生成业务表列表信息 @@ -117,12 +123,16 @@ class GenTableDao: ) .distinct() ) - gen_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + gen_table_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate( + db, query, query_object.page_num, query_object.page_size, is_page + ) return gen_table_list @classmethod - async def get_gen_db_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False): + async def get_gen_db_table_list( + cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False + ) -> Union[PageModel, list[dict[str, Any]]]: """ 根据查询参数获取数据库列表信息 @@ -133,28 +143,28 @@ class GenTableDao: """ if DataBaseConfig.db_type == 'postgresql': query_sql = """ - table_name as table_name, - table_comment as table_comment, - create_time as create_time, + table_name as table_name, + table_comment as table_comment, + create_time as create_time, update_time as update_time - from + from list_table - where - table_name not like 'apscheduler_%' + where + table_name not like 'apscheduler_%' and table_name not like 'gen_%' and table_name not in (select table_name from gen_table) """ else: - query_sql = """ - table_name as table_name, - table_comment as table_comment, - create_time as create_time, + query_sql = r""" + table_name as table_name, + table_comment as table_comment, + create_time as create_time, update_time as update_time - from + from information_schema.tables - where + where table_schema = (select database()) - and table_name not like 'apscheduler\_%' + and table_name not like 'apscheduler\_%' and table_name not like 'gen\_%' and table_name not in (select table_name from gen_table) """ @@ -174,19 +184,16 @@ class GenTableDao: query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:end_time, '%Y%m%d')""" query_sql += """order by create_time desc""" query = select( - text(query_sql).bindparams( - **{ - k: v - for k, v in query_object.model_dump(exclude_none=True, exclude={'page_num', 'page_size'}).items() - } - ) + text(query_sql).bindparams(**query_object.model_dump(exclude_none=True, exclude={'page_num', 'page_size'})) + ) + gen_db_table_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate( + db, query, query_object.page_num, query_object.page_size, is_page ) - gen_db_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return gen_db_table_list @classmethod - async def get_gen_db_table_list_by_names(cls, db: AsyncSession, table_names: List[str]): + async def get_gen_db_table_list_by_names(cls, db: AsyncSession, table_names: list[str]) -> Sequence[Row]: """ 根据业务表名称组获取数据库列表信息 @@ -197,29 +204,29 @@ class GenTableDao: if DataBaseConfig.db_type == 'postgresql': query_sql = """ select - table_name as table_name, - table_comment as table_comment, - create_time as create_time, - update_time as update_time - from + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from list_table - where - table_name not like 'qrtz_%' - and table_name not like 'gen_%' + where + table_name not like 'qrtz_%' + and table_name not like 'gen_%' and table_name = any(:table_names) """ else: - query_sql = """ + query_sql = r""" select - table_name as table_name, - table_comment as table_comment, - create_time as create_time, - update_time as update_time - from + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from information_schema.tables - where - table_name not like 'qrtz\_%' - and table_name not like 'gen\_%' + where + table_name not like 'qrtz\_%' + and table_name not like 'gen\_%' and table_schema = (select database()) and table_name in :table_names """ @@ -229,7 +236,7 @@ class GenTableDao: return gen_db_table_list @classmethod - async def add_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel): + async def add_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel) -> GenTable: """ 新增业务表数据库操作 @@ -244,7 +251,7 @@ class GenTableDao: return db_gen_table @classmethod - async def edit_gen_table_dao(cls, db: AsyncSession, gen_table: dict): + async def edit_gen_table_dao(cls, db: AsyncSession, gen_table: dict) -> None: """ 编辑业务表数据库操作 @@ -255,7 +262,7 @@ class GenTableDao: await db.execute(update(GenTable), [GenTableBaseModel(**gen_table).model_dump()]) @classmethod - async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel): + async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel) -> None: """ 删除业务表数据库操作 @@ -272,7 +279,7 @@ class GenTableColumnDao: """ @classmethod - async def get_gen_table_column_list_by_table_id(cls, db: AsyncSession, table_id: int): + async def get_gen_table_column_list_by_table_id(cls, db: AsyncSession, table_id: int) -> GenTableColumn: """ 根据业务表id获取需要生成的业务表字段列表信息 @@ -293,7 +300,7 @@ class GenTableColumnDao: return gen_table_column_list @classmethod - async def get_gen_db_table_columns_by_name(cls, db: AsyncSession, table_name: str): + async def get_gen_db_table_columns_by_name(cls, db: AsyncSession, table_name: str) -> Sequence[Row]: """ 根据业务表名称获取业务表字段列表信息 @@ -312,29 +319,29 @@ class GenTableColumnDao: """ else: query_sql = """ - select + select column_name as column_name, - case - when is_nullable = 'no' and column_key != 'PRI' then '1' - else '0' + case + when is_nullable = 'no' and column_key != 'PRI' then '1' + else '0' end as is_required, - case - when column_key = 'PRI' then '1' - else '0' + case + when column_key = 'PRI' then '1' + else '0' end as is_pk, ordinal_position as sort, column_comment as column_comment, - case - when extra = 'auto_increment' then '1' - else '0' + case + when extra = 'auto_increment' then '1' + else '0' end as is_increment, column_type as column_type - from + from information_schema.columns - where - table_schema = (select database()) + where + table_schema = (select database()) and table_name = :table_name - order by + order by ordinal_position """ query = text(query_sql).bindparams(table_name=table_name) @@ -343,7 +350,7 @@ class GenTableColumnDao: return gen_db_table_columns @classmethod - async def add_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + async def add_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel) -> GenTableColumn: """ 新增业务表字段数据库操作 @@ -360,7 +367,7 @@ class GenTableColumnDao: return db_gen_table_column @classmethod - async def edit_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: dict): + async def edit_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: dict) -> None: """ 编辑业务表字段数据库操作 @@ -371,7 +378,9 @@ class GenTableColumnDao: await db.execute(update(GenTableColumn), [GenTableColumnBaseModel(**gen_table_column).model_dump()]) @classmethod - async def delete_gen_table_column_by_table_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + async def delete_gen_table_column_by_table_id_dao( + cls, db: AsyncSession, gen_table_column: GenTableColumnModel + ) -> None: """ 通过业务表id删除业务表字段数据库操作 @@ -382,7 +391,9 @@ class GenTableColumnDao: await db.execute(delete(GenTableColumn).where(GenTableColumn.table_id.in_([gen_table_column.table_id]))) @classmethod - async def delete_gen_table_column_by_column_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + async def delete_gen_table_column_by_column_id_dao( + cls, db: AsyncSession, gen_table_column: GenTableColumnModel + ) -> None: """ 通过业务字段id删除业务表字段数据库操作 diff --git a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py index edd62f67d2594ff1ce194777ba94883b296de61c..90b25e06b2167b0bf06b17e7689e936e68502a07 100644 --- a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py +++ b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import BigInteger, CHAR, Column, DateTime, ForeignKey, Integer, String -from sqlalchemy.orm import relationship + +from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String +from sqlalchemy.orm import foreign, relationship + from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil @@ -55,7 +57,12 @@ class GenTable(Base): comment='备注', ) - columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables') + columns = relationship( + 'GenTableColumn', + primaryjoin=lambda: GenTable.table_id == foreign(GenTableColumn.table_id), + order_by='GenTableColumn.sort', + back_populates='tables', + ) class GenTableColumn(Base): @@ -67,7 +74,7 @@ class GenTableColumn(Base): __table_args__ = {'comment': '代码生成业务表字段'} column_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='编号') - table_id = Column(BigInteger, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号') + table_id = Column(BigInteger, nullable=True, comment='归属表编号') column_name = Column(String(200), nullable=True, comment='列名称') column_comment = Column(String(500), nullable=True, comment='列描述') column_type = Column(String(100), nullable=True, comment='列类型') @@ -94,4 +101,6 @@ class GenTableColumn(Base): update_by = Column(String(64), server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - tables = relationship('GenTable', back_populates='columns') + tables = relationship( + 'GenTable', primaryjoin=lambda: foreign(GenTableColumn.table_id) == GenTable.table_id, back_populates='columns' + ) diff --git a/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py b/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py index e5d7917ef07da349a9fe2ad59fbd2de3d39b9920..4e4a67ea6913938cc12117a25c19a281e90c33b8 100644 --- a/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py +++ b/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py @@ -1,10 +1,11 @@ from datetime import datetime +from typing import Literal, Optional, Union + from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic.alias_generators import to_camel from pydantic_validation_decorator import NotBlank -from typing import List, Literal, Optional -from config.constant import GenConstant -from module_admin.annotation.pydantic_annotation import as_query + +from common.constant import GenConstant from utils.string_util import StringUtil @@ -38,38 +39,38 @@ class GenTableBaseModel(BaseModel): remark: Optional[str] = Field(default=None, description='备注') @NotBlank(field_name='table_name', message='表名称不能为空') - def get_table_name(self): + def get_table_name(self) -> Union[str, None]: return self.table_name @NotBlank(field_name='table_comment', message='表描述不能为空') - def get_table_comment(self): + def get_table_comment(self) -> Union[str, None]: return self.table_comment @NotBlank(field_name='class_name', message='实体类名称不能为空') - def get_class_name(self): + def get_class_name(self) -> Union[str, None]: return self.class_name @NotBlank(field_name='package_name', message='生成包路径不能为空') - def get_package_name(self): + def get_package_name(self) -> Union[str, None]: return self.package_name @NotBlank(field_name='module_name', message='生成模块名不能为空') - def get_module_name(self): + def get_module_name(self) -> Union[str, None]: return self.module_name @NotBlank(field_name='business_name', message='生成业务名不能为空') - def get_business_name(self): + def get_business_name(self) -> Union[str, None]: return self.business_name @NotBlank(field_name='function_name', message='生成功能名不能为空') - def get_function_name(self): + def get_function_name(self) -> Union[str, None]: return self.function_name @NotBlank(field_name='function_author', message='生成功能作者不能为空') - def get_function_author(self): + def get_function_author(self) -> Union[str, None]: return self.function_author - def validate_fields(self): + def validate_fields(self) -> None: self.get_table_name() self.get_table_comment() self.get_class_name() @@ -80,6 +81,27 @@ class GenTableBaseModel(BaseModel): self.get_function_author() +class GenTableRowModel(GenTableBaseModel): + """ + 代码生成业务表行数据模型 + """ + + columns: Optional[list['GenTableColumnBaseModel']] = Field(default=None, description='表列信息') + + +class GenTableDbRowModel(BaseModel): + """ + 代码生成业务表数据库行数据模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + table_name: Optional[str] = Field(default=None, description='表名称') + table_comment: Optional[str] = Field(default=None, description='表描述') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + + class GenTableModel(GenTableBaseModel): """ 代码生成业务表模型 @@ -87,7 +109,7 @@ class GenTableModel(GenTableBaseModel): pk_column: Optional['GenTableColumnModel'] = Field(default=None, description='主键信息') sub_table: Optional['GenTableModel'] = Field(default=None, description='子表信息') - columns: Optional[List['GenTableColumnModel']] = Field(default=None, description='表列信息') + columns: Optional[list['GenTableColumnModel']] = Field(default=None, description='表列信息') tree_code: Optional[str] = Field(default=None, description='树编码字段') tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段') tree_name: Optional[str] = Field(default=None, description='树名称字段') @@ -99,9 +121,9 @@ class GenTableModel(GenTableBaseModel): @model_validator(mode='after') def check_some_is(self) -> 'GenTableModel': - self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False - self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False - self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False + self.sub = bool(self.tpl_category and self.tpl_category == GenConstant.TPL_SUB) + self.tree = bool(self.tpl_category and self.tpl_category == GenConstant.TPL_TREE) + self.crud = bool(self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD) return self @@ -135,7 +157,6 @@ class GenTableQueryModel(GenTableBaseModel): end_time: Optional[str] = Field(default=None, description='结束时间') -@as_query class GenTablePageQueryModel(GenTableQueryModel): """ 代码生成业务表分页查询模型 @@ -145,6 +166,18 @@ class GenTablePageQueryModel(GenTableQueryModel): page_size: int = Field(default=10, description='每页记录数') +class GenTableDetailModel(BaseModel): + """ + 代码生成业务表详情模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + info: Optional[GenTableModel] = Field(default=None, description='业务表信息') + rows: Optional[list['GenTableColumnModel']] = Field(default=None, description='表列信息') + tables: Optional[list['GenTableModel']] = Field(default=None, description='所有业务表信息') + + class DeleteGenTableModel(BaseModel): """ 删除代码生成业务表模型 @@ -189,10 +222,10 @@ class GenTableColumnBaseModel(BaseModel): update_time: Optional[datetime] = Field(default=None, description='更新时间') @NotBlank(field_name='python_field', message='Python属性不能为空') - def get_python_field(self): + def get_python_field(self) -> Union[str, None]: return self.python_field - def validate_fields(self): + def validate_fields(self) -> None: self.get_python_field() @@ -216,21 +249,19 @@ class GenTableColumnModel(GenTableColumnBaseModel): @model_validator(mode='after') def check_some_is(self) -> 'GenTableModel': self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None - self.pk = True if self.is_pk and self.is_pk == '1' else False - self.increment = True if self.is_increment and self.is_increment == '1' else False - self.required = True if self.is_required and self.is_required == '1' else False - self.unique = True if self.is_unique and self.is_unique == '1' else False - self.insert = True if self.is_insert and self.is_insert == '1' else False - self.edit = True if self.is_edit and self.is_edit == '1' else False - self.list = True if self.is_list and self.is_list == '1' else False - self.query = True if self.is_query and self.is_query == '1' else False - self.super_column = ( - True - if StringUtil.equals_any_ignore_case(self.python_field, GenConstant.TREE_ENTITY + GenConstant.BASE_ENTITY) - else False + self.pk = self.is_pk and self.is_pk == '1' + self.increment = bool(self.is_increment and self.is_increment == '1') + self.required = bool(self.is_required and self.is_required == '1') + self.unique = bool(self.is_unique and self.is_unique == '1') + self.insert = bool(self.is_insert and self.is_insert == '1') + self.edit = bool(self.is_edit and self.is_edit == '1') + self.list = bool(self.is_list and self.is_list == '1') + self.query = bool(self.is_query and self.is_query == '1') + self.super_column = bool( + StringUtil.equals_any_ignore_case(self.python_field, GenConstant.TREE_ENTITY + GenConstant.BASE_ENTITY) ) - self.usable_column = ( - True if StringUtil.equals_any_ignore_case(self.python_field, ['parentId', 'orderNum', 'remark']) else False + self.usable_column = bool( + StringUtil.equals_any_ignore_case(self.python_field, ['parentId', 'orderNum', 'remark']) ) return self @@ -244,7 +275,6 @@ class GenTableColumnQueryModel(GenTableColumnBaseModel): end_time: Optional[str] = Field(default=None, description='结束时间') -@as_query class GenTableColumnPageQueryModel(GenTableColumnQueryModel): """ 代码生成业务表字段分页查询模型 diff --git a/ruoyi-fastapi-backend/module_generator/service/gen_service.py b/ruoyi-fastapi-backend/module_generator/service/gen_service.py index aec0bbf0ced2847eb1bcdc62e59dec2dd3fefefc..4918d00cd7abc9feef6e6fdb1e81c87cdd123c1e 100644 --- a/ruoyi-fastapi-backend/module_generator/service/gen_service.py +++ b/ruoyi-fastapi-backend/module_generator/service/gen_service.py @@ -3,15 +3,19 @@ import json import os import zipfile from datetime import datetime +from typing import Any, Union + +import aiofiles from sqlalchemy.ext.asyncio import AsyncSession from sqlglot import parse as sqlglot_parse from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update -from typing import List -from config.constant import GenConstant + +from common.constant import GenConstant +from common.vo import CrudResponseModel, PageModel from config.env import DataBaseConfig, GenConfig from exceptions.exception import ServiceException -from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.user_vo import CurrentUserModel +from module_generator.dao.gen_dao import GenTableColumnDao, GenTableDao from module_generator.entity.vo.gen_vo import ( DeleteGenTableModel, EditGenTableModel, @@ -19,7 +23,6 @@ from module_generator.entity.vo.gen_vo import ( GenTableModel, GenTablePageQueryModel, ) -from module_generator.dao.gen_dao import GenTableColumnDao, GenTableDao from utils.common_util import CamelCaseUtil from utils.gen_util import GenUtils from utils.template_util import TemplateInitializer, TemplateUtils @@ -33,7 +36,7 @@ class GenTableService: @classmethod async def get_gen_table_list_services( cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取代码生成业务表列表信息service @@ -49,7 +52,7 @@ class GenTableService: @classmethod async def get_gen_db_table_list_services( cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取数据库列表信息service @@ -63,7 +66,9 @@ class GenTableService: return gen_db_table_list_result @classmethod - async def get_gen_db_table_list_by_name_services(cls, query_db: AsyncSession, table_names: List[str]): + async def get_gen_db_table_list_by_name_services( + cls, query_db: AsyncSession, table_names: list[str] + ) -> list[GenTableModel]: """ 根据表名称组获取数据库列表信息service @@ -77,8 +82,8 @@ class GenTableService: @classmethod async def import_gen_table_services( - cls, query_db: AsyncSession, gen_table_list: List[GenTableModel], current_user: CurrentUserModel - ): + cls, query_db: AsyncSession, gen_table_list: list[GenTableModel], current_user: CurrentUserModel + ) -> CrudResponseModel: """ 导入表结构service @@ -105,10 +110,10 @@ class GenTableService: return CrudResponseModel(is_success=True, message='导入成功') except Exception as e: await query_db.rollback() - raise ServiceException(message=f'导入失败, {str(e)}') + raise ServiceException(message=f'导入失败, {e}') from e @classmethod - async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: EditGenTableModel): + async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: EditGenTableModel) -> CrudResponseModel: """ 编辑业务表信息service @@ -137,7 +142,9 @@ class GenTableService: raise ServiceException(message='业务表不存在') @classmethod - async def delete_gen_table_services(cls, query_db: AsyncSession, page_object: DeleteGenTableModel): + async def delete_gen_table_services( + cls, query_db: AsyncSession, page_object: DeleteGenTableModel + ) -> CrudResponseModel: """ 删除业务表信息service @@ -162,7 +169,7 @@ class GenTableService: raise ServiceException(message='传入业务表id为空') @classmethod - async def get_gen_table_by_id_services(cls, query_db: AsyncSession, table_id: int): + async def get_gen_table_by_id_services(cls, query_db: AsyncSession, table_id: int) -> GenTableModel: """ 获取需要生成的业务表详细信息service @@ -176,7 +183,7 @@ class GenTableService: return result @classmethod - async def get_gen_table_all_services(cls, query_db: AsyncSession): + async def get_gen_table_all_services(cls, query_db: AsyncSession) -> list[GenTableModel]: """ 获取所有业务表信息service @@ -189,7 +196,9 @@ class GenTableService: return result @classmethod - async def create_table_services(cls, query_db: AsyncSession, sql: str, current_user: CurrentUserModel): + async def create_table_services( + cls, query_db: AsyncSession, sql: str, current_user: CurrentUserModel + ) -> CrudResponseModel: """ 创建表结构service @@ -208,12 +217,12 @@ class GenTableService: return CrudResponseModel(is_success=True, message='创建表结构成功') except Exception as e: - raise ServiceException(message=f'创建表结构异常,详细错误信息:{str(e)}') + raise ServiceException(message=f'创建表结构异常,详细错误信息:{e}') from e else: raise ServiceException(message='建表语句不合法') @classmethod - def __is_valid_create_table(cls, sql_statements: List[Expression]): + def __is_valid_create_table(cls, sql_statements: list[Expression]) -> bool: """ 校验sql语句是否为合法的建表语句 @@ -228,26 +237,24 @@ class GenTableService: ) for sql_statement in sql_statements ] - if not any(validate_create) or any(validate_forbidden_keywords): - return False - return True + return not (not any(validate_create) or any(validate_forbidden_keywords)) @classmethod - def __get_table_names(cls, sql_statements: List[Expression]): + def __get_table_names(cls, sql_statements: list[Expression]) -> list[str]: """ 获取sql语句中所有的建表表名 :param sql_statements: sql语句的ast列表 :return: 建表表名列表 """ - table_names = [] - for sql_statement in sql_statements: - if isinstance(sql_statement, Create): - table_names.append(sql_statement.find(Table).name) + table_names = [ + sql_statement.find(Table).name for sql_statement in sql_statements if isinstance(sql_statement, Create) + ] + return table_names @classmethod - async def preview_code_services(cls, query_db: AsyncSession, table_id: int): + async def preview_code_services(cls, query_db: AsyncSession, table_id: int) -> dict[str, str]: """ 预览代码service @@ -270,7 +277,7 @@ class GenTableService: return preview_code_result @classmethod - async def generate_code_services(cls, query_db: AsyncSession, table_name: str): + async def generate_code_services(cls, query_db: AsyncSession, table_name: str) -> CrudResponseModel: """ 生成代码至指定路径service @@ -280,22 +287,20 @@ class GenTableService: """ env = TemplateInitializer.init_jinja2() render_info = await cls.__get_gen_render_info(query_db, table_name) - for template in render_info[0]: - try: + try: + for template in render_info[0]: render_content = env.get_template(template).render(**render_info[2]) gen_path = cls.__get_gen_path(render_info[3], template) os.makedirs(os.path.dirname(gen_path), exist_ok=True) - with open(gen_path, 'w', encoding='utf-8') as f: - f.write(render_content) - except Exception as e: - raise ServiceException( - message=f'渲染模板失败,表名:{render_info[3].table_name},详细错误信息:{str(e)}' - ) + async with aiofiles.open(gen_path, 'w', encoding='utf-8') as f: + await f.write(render_content) + except Exception as e: + raise ServiceException(message=f'渲染模板失败,表名:{render_info[3].table_name},详细错误信息:{e}') from e return CrudResponseModel(is_success=True, message='生成代码成功') @classmethod - async def batch_gen_code_services(cls, query_db: AsyncSession, table_names: List[str]): + async def batch_gen_code_services(cls, query_db: AsyncSession, table_names: list[str]) -> bytes: """ 批量生成代码service @@ -317,7 +322,7 @@ class GenTableService: return zip_data @classmethod - async def __get_gen_render_info(cls, query_db: AsyncSession, table_name: str): + async def __get_gen_render_info(cls, query_db: AsyncSession, table_name: str) -> list: """ 获取生成代码渲染模板相关信息 @@ -337,7 +342,7 @@ class GenTableService: return [template_list, output_files, context, gen_table] @classmethod - def __get_gen_path(cls, gen_table: GenTableModel, template: str): + def __get_gen_path(cls, gen_table: GenTableModel, template: str) -> str: """ 根据GenTableModel对象和模板名称生成路径 @@ -348,11 +353,11 @@ class GenTableService: gen_path = gen_table.gen_path if gen_path == '/': return os.path.join(os.getcwd(), GenConfig.GEN_PATH, TemplateUtils.get_file_name(template, gen_table)) - else: - return os.path.join(gen_path, TemplateUtils.get_file_name(template, gen_table)) + + return os.path.join(gen_path, TemplateUtils.get_file_name(template, gen_table)) @classmethod - async def sync_db_services(cls, query_db: AsyncSession, table_name: str): + async def sync_db_services(cls, query_db: AsyncSession, table_name: str) -> CrudResponseModel: """ 同步数据库service @@ -402,7 +407,7 @@ class GenTableService: raise e @classmethod - async def set_sub_table(cls, query_db: AsyncSession, gen_table: GenTableModel): + async def set_sub_table(cls, query_db: AsyncSession, gen_table: GenTableModel) -> None: """ 设置主子表信息 @@ -415,7 +420,7 @@ class GenTableService: gen_table.sub_table = GenTableModel(**CamelCaseUtil.transform_result(sub_table)) @classmethod - async def set_pk_column(cls, gen_table: GenTableModel): + async def set_pk_column(cls, gen_table: GenTableModel) -> None: """ 设置主键列信息 @@ -437,7 +442,7 @@ class GenTableService: gen_table.sub_table.pk_column = gen_table.sub_table.columns[0] @classmethod - async def set_table_from_options(cls, gen_table: GenTableModel): + async def set_table_from_options(cls, gen_table: GenTableModel) -> GenTableModel: """ 设置代码生成其他选项值 @@ -455,7 +460,7 @@ class GenTableService: return gen_table @classmethod - async def validate_edit(cls, edit_gen_table: EditGenTableModel): + async def validate_edit(cls, edit_gen_table: EditGenTableModel) -> None: """ 编辑保存参数校验 @@ -466,14 +471,14 @@ class GenTableService: if GenConstant.TREE_CODE not in params_obj: raise ServiceException(message='树编码字段不能为空') - elif GenConstant.TREE_PARENT_CODE not in params_obj: + if GenConstant.TREE_PARENT_CODE not in params_obj: raise ServiceException(message='树父编码字段不能为空') - elif GenConstant.TREE_NAME not in params_obj: + if GenConstant.TREE_NAME not in params_obj: raise ServiceException(message='树名称字段不能为空') - elif edit_gen_table.tpl_category == GenConstant.TPL_SUB: + if edit_gen_table.tpl_category == GenConstant.TPL_SUB: if not edit_gen_table.sub_table_name: raise ServiceException(message='关联子表的表名不能为空') - elif not edit_gen_table.sub_table_fk_name: + if not edit_gen_table.sub_table_fk_name: raise ServiceException(message='子表关联的外键名不能为空') @@ -483,7 +488,9 @@ class GenTableColumnService: """ @classmethod - async def get_gen_table_column_list_by_table_id_services(cls, query_db: AsyncSession, table_id: int): + async def get_gen_table_column_list_by_table_id_services( + cls, query_db: AsyncSession, table_id: int + ) -> list[GenTableColumnModel]: """ 获取业务表字段列表信息service diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 index 13cc466f5895b400b82b5b2a0e618125b96e4a05..1c78e62ae821133b1292dd1f5542aad0f5237353 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 @@ -1,5 +1,7 @@ {% set pkField = pkColumn.python_field %} {% set pk_field = pkColumn.python_field | camel_to_snake %} +{% set pkParentheseIndex = pkColumn.column_comment.find("(") %} +{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} {% set need_import_datetime = namespace(has_datetime=False) %} {% for column in columns %} {% if column.python_field in ["createTime", "updatetime"] %} @@ -9,34 +11,45 @@ {% if need_import_datetime.has_datetime %} from datetime import datetime {% endif %} -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.user_vo import CurrentUserModel -from module_admin.service.login_service import LoginService from {{ packageName }}.service.{{ businessName }}_service import {{ BusinessName }}Service from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel 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 -{{ businessName }}Controller = APIRouter(prefix='/{{ moduleName }}/{{ businessName }}', dependencies=[Depends(LoginService.get_current_user)]) +{{ businessName }}_controller = APIRouterPro( + prefix='/{{ moduleName }}/{{ businessName }}', order_num=50, tags=['{{ functionName }}'], dependencies=[PreAuthDependency()] +) -@{{ businessName }}Controller.get( - '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:list'))] +@{{ businessName }}_controller.get( + '/list', + summary='获取{{ functionName }}分页列表接口', + description='用于获取{{ functionName }}分页列表', + response_model=PageResponseModel[{{ BusinessName }}Model], + dependencies=[UserInterfaceAuthDependency('{{ permissionPrefix }}:list')], ) async def get_{{ moduleName }}_{{ businessName }}_list( request: Request, - {% if table.crud or table.sub %}{{ businessName }}_page_query{% elif table.tree %}{{ businessName }}_query{% endif %}: {{ BusinessName }}PageQueryModel = Depends({{ BusinessName }}PageQueryModel.as_query), - query_db: AsyncSession = Depends(get_db), -): + {% if table.crud or table.sub %}{{ businessName }}_page_query{% elif table.tree %}{{ businessName }}_query{% endif %}: Annotated[{{ BusinessName }}PageQueryModel, Query()], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: {% if table.crud or table.sub %} # 获取分页数据 {{ businessName }}_page_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=True) @@ -51,15 +64,21 @@ async def get_{{ moduleName }}_{{ businessName }}_list( {% endif %} -@{{ businessName }}Controller.post('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:add'))]) +@{{ businessName }}_controller.post( + '', + summary='新增{{ functionName }}接口', + description='用于新增{{ functionName }}', + response_model=ResponseBaseModel, + dependencies=[UserInterfaceAuthDependency('{{ permissionPrefix }}:add')], +) @ValidateFields(validate_model='add_{{ businessName }}') @Log(title='{{ functionName }}', business_type=BusinessType.INSERT) async def add_{{ moduleName }}_{{ businessName }}( request: Request, add_{{ businessName }}: {{ BusinessName }}Model, - 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: {% for column in columns %} {% if column.python_field == "createBy" %} add_{{ businessName }}.create_by = current_user.user.user_name @@ -77,15 +96,21 @@ async def add_{{ moduleName }}_{{ businessName }}( return ResponseUtil.success(msg=add_{{ businessName }}_result.message) -@{{ businessName }}Controller.put('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:edit'))]) +@{{ businessName }}_controller.put( + '', + summary='编辑{{ functionName }}接口', + description='用于编辑{{ functionName }}', + response_model=ResponseBaseModel, + dependencies=[UserInterfaceAuthDependency('{{ permissionPrefix }}:edit')], +) @ValidateFields(validate_model='edit_{{ businessName }}') @Log(title='{{ functionName }}', business_type=BusinessType.UPDATE) async def edit_{{ moduleName }}_{{ businessName }}( request: Request, edit_{{ businessName }}: {{ BusinessName }}Model, - 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: {% for column in columns %} {% if column.python_field == "updateBy" %} edit_{{ businessName }}.update_by = current_user.user.user_name @@ -99,9 +124,19 @@ async def edit_{{ moduleName }}_{{ businessName }}( return ResponseUtil.success(msg=edit_{{ businessName }}_result.message) -@{{ businessName }}Controller.delete('/{% raw %}{{% endraw %}{{ pk_field }}s{% raw %}}{% endraw %}', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:remove'))]) +@{{ businessName }}_controller.delete( + '/{% raw %}{{% endraw %}{{ pk_field }}s{% raw %}}{% endraw %}', + summary='删除{{ functionName }}接口', + description='用于删除{{ functionName }}', + response_model=ResponseBaseModel, + dependencies=[UserInterfaceAuthDependency('{{ permissionPrefix }}:remove')], +) @Log(title='{{ functionName }}', business_type=BusinessType.DELETE) -async def delete_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}s: str, query_db: AsyncSession = Depends(get_db)): +async def delete_{{ moduleName }}_{{ businessName }}( + request: Request, + {{ pk_field }}s: Annotated[str, Path(description='需要删除的{{ pk_field_comment }}')], + Annotated[AsyncSession, DBSessionDependency()], +) -> Response: delete_{{ businessName }} = Delete{{ BusinessName }}Model({{ pkField }}s={{ pk_field }}s) delete_{{ businessName }}_result = await {{ BusinessName }}Service.delete_{{ businessName }}_services(query_db, delete_{{ businessName }}) logger.info(delete_{{ businessName }}_result.message) @@ -109,23 +144,45 @@ async def delete_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_fie return ResponseUtil.success(msg=delete_{{ businessName }}_result.message) -@{{ businessName }}Controller.get( - '/{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}', response_model={{ BusinessName }}Model, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:query'))] +@{{ businessName }}_controller.get( + '/{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}', + summary='获取{{ functionName }}详情接口', + description='用于获取指定{{ functionName }}的详细信息', + response_model=DataResponseModel[{{ BusinessName }}Model], + dependencies=[UserInterfaceAuthDependency('{{ permissionPrefix }}:query')] ) -async def query_detail_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}: int, query_db: AsyncSession = Depends(get_db)): +async def query_detail_{{ moduleName }}_{{ businessName }}( + request: Request, + {{ pk_field }}: Annotated[{{ pkColumn.python_type }}, Path(description='{{ pk_field_comment }}')], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: {{ businessName }}_detail_result = await {{ BusinessName }}Service.{{ businessName }}_detail_services(query_db, {{ pk_field }}) logger.info(f'获取{{ pk_field }}为{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}的信息成功') return ResponseUtil.success(data={{ businessName }}_detail_result) -@{{ businessName }}Controller.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:export'))]) +@{{ businessName }}_controller.post( + '/export', + summary='导出{{ functionName }}列表接口', + description='用于导出当前符合查询条件的{{ functionName }}列表数据', + response_class=StreamingResponse, + responses={ + 200: { + 'description': '流式返回{{ functionName }}列表excel文件', + 'content': { + 'application/octet-stream': {}, + }, + } + }, + dependencies=[UserInterfaceAuthDependency('{{ permissionPrefix }}:export')], +) @Log(title='{{ functionName }}', business_type=BusinessType.EXPORT) async def export_{{ moduleName }}_{{ businessName }}_list( request: Request, - {{ businessName }}_page_query: {{ BusinessName }}PageQueryModel = Form(), - query_db: AsyncSession = Depends(get_db), -): + {{ businessName }}_page_query: Annotated[{{ BusinessName }}PageQueryModel, Form()], + query_db: Annotated[AsyncSession, DBSessionDependency()], +) -> Response: # 获取全量数据 {{ businessName }}_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=False) {{ businessName }}_export_result = await {{ BusinessName }}Service.export_{{ businessName }}_list_services({% if dicts %}request, {% endif %}{{ businessName }}_query_result) diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 index 3b186c52ad1a580fca2db790b219f7bb1721ab15..60222c40ccecee6b2d14818ad91ff6b882496d19 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 @@ -7,11 +7,15 @@ from datetime import datetime, time {% endif %} {% endfor %} +from typing import Any, Union + from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession {% if table.sub %} from sqlalchemy.orm import selectinload {% endif %} + +from common.vo import PageModel {% if table.sub %} from {{ packageName }}.entity.do.{{ businessName }}_do import {{ ClassName }}, {{ subClassName }} from {{ packageName }}.entity.vo.{{ businessName }}_vo import {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel, {{ subTable.business_name | capitalize }}Model @@ -28,7 +32,7 @@ class {{ BusinessName }}Dao: """ @classmethod - async def get_{{ businessName }}_detail_by_id(cls, db: AsyncSession, {{ pk_field }}: int): + async def get_{{ businessName }}_detail_by_id(cls, db: AsyncSession, {{ pk_field }}: int) -> Union[{{ ClassName }}, None]: """ 根据{{ pk_field_comment }}获取{{ functionName }}详细信息 @@ -57,7 +61,7 @@ class {{ BusinessName }}Dao: return {{ businessName }}_info @classmethod - async def get_{{ businessName }}_detail_by_info(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + async def get_{{ businessName }}_detail_by_info(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model) -> Union[{{ ClassName }}, None]: """ 根据{{ functionName }}参数获取{{ functionName }}信息 @@ -84,7 +88,9 @@ class {{ BusinessName }}Dao: return {{ businessName }}_info @classmethod - async def get_{{ businessName }}_list(cls, db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False): + async def get_{{ businessName }}_list( + cls, db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False + ) -> Union[PageModel, list[dict[str, Any]]]: """ 根据查询参数获取{{ functionName }}列表信息 @@ -132,12 +138,14 @@ class {{ BusinessName }}Dao: .order_by({{ ClassName }}.{{ pk_field }}) .distinct() ) - {{ businessName }}_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + {{ businessName }}_list: Union[PageModel, list[dict[str, Any]]] = await PageUtil.paginate( + db, query, query_object.page_num, query_object.page_size, is_page + ) return {{ businessName }}_list @classmethod - async def add_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + async def add_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model) -> {{ ClassName }}: """ 新增{{ functionName }}数据库操作 @@ -152,7 +160,7 @@ class {{ BusinessName }}Dao: return db_{{ businessName }} @classmethod - async def edit_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: dict): + async def edit_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: dict) -> None: """ 编辑{{ functionName }}数据库操作 @@ -163,7 +171,7 @@ class {{ BusinessName }}Dao: await db.execute(update({{ ClassName }}), [{{ businessName }}]) @classmethod - async def delete_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + async def delete_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model) -> None: """ 删除{{ functionName }}数据库操作 @@ -175,7 +183,7 @@ class {{ BusinessName }}Dao: {% if table.sub %} @classmethod - async def add_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model): + async def add_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model) -> {{ subClassName }}: """ 新增{{ subTable.function_name }}数据库操作 @@ -190,7 +198,7 @@ class {{ BusinessName }}Dao: return db_{{ subTable.business_name }} @classmethod - async def edit_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: dict): + async def edit_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: dict) -> None: """ 编辑{{ subTable.function_name }}数据库操作 @@ -201,7 +209,7 @@ class {{ BusinessName }}Dao: await db.execute(update({{ subClassName }}), [{{ subTable.business_name }}]) @classmethod - async def delete_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model): + async def delete_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model) -> None: """ 删除{{ subTable.function_name }}数据库操作 diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 index c7ea75791d61ebc82a3f98ac15b79be00f3036ee..76137e3c75da8955556c3ce0c2e837bb1e28c559 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 @@ -4,6 +4,7 @@ {% if table.sub %} from sqlalchemy.orm import relationship {% endif %} + from config.database import Base diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 index 5726c5d1240b7457f5f4d96a7d28c42aa2733791..c13d8cdc278d0b20a0811992718ae8dbbd1a87c9 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 @@ -2,19 +2,21 @@ {% set pk_field = pkColumn.python_field | camel_to_snake %} {% set pkParentheseIndex = pkColumn.column_comment.find("(") %} {% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} +from typing import Any, Union + {% if dicts %} from fastapi import Request {% endif %} from sqlalchemy.ext.asyncio import AsyncSession -from typing import List -from config.constant import CommonConstant + +from common.constant import CommonConstant +from common.vo import CrudResponseModel, PageModel from exceptions.exception import ServiceException -from module_admin.entity.vo.common_vo import CrudResponseModel +from {{ packageName }}.dao.{{ businessName }}_dao import {{ BusinessName }}Dao +from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel {% if dicts %} from module_admin.service.dict_service import DictDataService {% endif %} -from {{ packageName }}.dao.{{ businessName }}_dao import {{ BusinessName }}Dao -from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel from utils.common_util import CamelCaseUtil from utils.excel_util import ExcelUtil @@ -27,7 +29,7 @@ class {{ BusinessName }}Service: @classmethod async def get_{{ businessName }}_list_services( cls, query_db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False - ): + ) -> Union[PageModel, list[dict[str, Any]]]: """ 获取{{ functionName }}列表信息service @@ -45,7 +47,7 @@ class {{ BusinessName }}Service: {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} {% if column.unique %} @classmethod - async def check_{{ column.python_field | camel_to_snake }}_unique_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + async def check_{{ column.python_field | camel_to_snake }}_unique_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model) -> bool: """ 检查{{ comment }}是否唯一service @@ -63,7 +65,7 @@ class {{ BusinessName }}Service: {% endfor %} @classmethod - async def add_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + async def add_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model) -> CrudResponseModel: """ 新增{{ functionName }}信息service @@ -95,7 +97,7 @@ class {{ BusinessName }}Service: raise e @classmethod - async def edit_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + async def edit_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model) -> CrudResponseModel: """ 编辑{{ functionName }}信息service @@ -131,7 +133,7 @@ class {{ BusinessName }}Service: raise ServiceException(message='{{ functionName }}不存在') @classmethod - async def delete_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: Delete{{ BusinessName }}Model): + async def delete_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: Delete{{ BusinessName }}Model) -> CrudResponseModel: """ 删除{{ functionName }}信息service @@ -158,7 +160,7 @@ class {{ BusinessName }}Service: raise ServiceException(message='传入{{ pk_field_comment }}为空') @classmethod - async def {{ businessName }}_detail_services(cls, query_db: AsyncSession, {{ pk_field }}: int): + async def {{ businessName }}_detail_services(cls, query_db: AsyncSession, {{ pk_field }}: int) -> {{ BusinessName }}Model: """ 获取{{ functionName }}详细信息service @@ -167,15 +169,12 @@ class {{ BusinessName }}Service: :return: {{ pk_field_comment }}对应的信息 """ {{ businessName }} = await {{ BusinessName }}Dao.get_{{ businessName }}_detail_by_id(query_db, {{ pk_field }}={{ pk_field }}) - if {{ businessName }}: - result = {{ BusinessName }}Model(**CamelCaseUtil.transform_result({{ businessName }})) - else: - result = {{ BusinessName }}Model(**dict()) + result = {{ BusinessName }}Model(**CamelCaseUtil.transform_result({{ businessName }})) if {{ businessName }} else {{ BusinessName }}Model() return result @staticmethod - async def export_{{ businessName }}_list_services({% if dicts %}request: Request, {% endif %}{{ businessName }}_list: List): + async def export_{{ businessName }}_list_services({% if dicts %}request: Request, {% endif %}{{ businessName }}_list: list) -> bytes: """ 导出{{ functionName }}信息service @@ -195,13 +194,13 @@ class {{ BusinessName }}Service: {{ dict_type[1:-1] }}_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type={{ dict_type }} ) - {{ dict_type[1:-1] }}_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in {{ dict_type[1:-1] }}_list] + {{ dict_type[1:-1] }}_option = [{'label': item.get('dictLabel'), 'value': item.get('dictValue')} for item in {{ dict_type[1:-1] }}_list] {{ dict_type[1:-1] }}_option_dict = {item.get('value'): item for item in {{ dict_type[1:-1] }}_option} {% endfor %} for item in {{ businessName }}_list: {% for column in columns %} {% if column.dict_type %} - if str(item.get('{{ column.python_field }}')) in {{ column.dict_type }}_option_dict.keys(): + if str(item.get('{{ column.python_field }}')) in {{ column.dict_type }}_option_dict: item['{{ column.python_field }}'] = {{ column.dict_type }}_option_dict.get(str(item.get('{{ column.python_field }}'))).get('label') {% endif %} {% endfor %} diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 index 47abbf597a37583717098ef2cc4f37aa7358942e..f525d5496ae8ce9ffeabb422c51acd68469d314e 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 @@ -23,17 +23,17 @@ {% for vo_import in voImportList %} {{ vo_import }} {% endfor %} +{% if table.sub %} +from typing import List, Optional +{% else %} +from typing import Optional, Union +{% endif %} + from pydantic import BaseModel, ConfigDict, Field from pydantic.alias_generators import to_camel {% if vo_field_required.has_required or sub_vo_field_required.has_required %} from pydantic_validation_decorator import NotBlank {% endif %} -{% if table.sub %} -from typing import List, Optional -{% else %} -from typing import Optional -{% endif %} -from module_admin.annotation.pydantic_annotation import as_query {% if table.sub %} @@ -53,14 +53,14 @@ class {{ BusinessName }}BaseModel(BaseModel): {% set parentheseIndex = column.column_comment.find("(") %} {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} @NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空') - def get_{{ column.column_name }}(self): + def get_{{ column.column_name }}(self) -> Union[{{ column.python_type }}, None]: return self.{{ column.column_name }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endif %} {% endfor %} {% if vo_field_required.has_required %} - def validate_fields(self): + def validate_fields(self) -> None: {% for column in columns %} {% if column.required %} self.get_{{ column.column_name }}() @@ -91,14 +91,14 @@ class {{ BusinessName }}Model({% if table.sub %}{{ BusinessName }}BaseModel{% el {% set parentheseIndex = column.column_comment.find("(") %} {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} @NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空') - def get_{{ column.column_name }}(self): + def get_{{ column.column_name }}(self) -> Union[{{ column.python_type }}, None]: return self.{{ column.column_name }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endif %} {% endfor %} {% if vo_field_required.has_required %} - def validate_fields(self): + def validate_fields(self) -> None: {% for column in columns %} {% if column.required %} self.get_{{ column.column_name }}() @@ -125,14 +125,14 @@ class {{ subTable.business_name | capitalize }}Model(BaseModel): {% set parentheseIndex = sub_column.column_comment.find("(") %} {% set comment = sub_column.column_comment[:parentheseIndex] if parentheseIndex != -1 else sub_column.column_comment %} @NotBlank(field_name='{{ sub_column.column_name }}', message='{{ comment }}不能为空') - def get_{{ sub_column.column_name }}(self): + def get_{{ sub_column.column_name }}(self) -> Union[{{ column.python_type }}, None]: return self.{{ sub_column.column_name }} {% if not loop.last %}{{ "\n" }}{% endif %} {% endif %} {% endfor %} {% if sub_vo_field_required.has_required %} - def validate_fields(self): + def validate_fields(self) -> None: {% for sub_column in subTable.columns %} {% if sub_column.required %} self.get_{{ sub_column.column_name }}() @@ -158,7 +158,6 @@ class {{ BusinessName }}QueryModel({% if table.sub %}{{ BusinessName }}BaseModel {% endif %} -@as_query class {{ BusinessName }}PageQueryModel({{ BusinessName }}QueryModel): """ {{ functionName }}分页查询模型 diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 index 52eeda9afc679ec5069f871e2527e87c2f44683b..79fc220e9d568ac436b665a9c53890119198810c 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 @@ -318,7 +318,7 @@ function getList() { {% for column in columns %} {% if column.html_type == "datetime" and column.query_type == "BETWEEN" %} {% set AttrName = column.python_field[0] | upper + column.python_field[1:] %} - if (null != daterange{{ AttrName }} && '' != daterange{{ AttrName }}) { + if (null != daterange{{ AttrName }}.value && '' != daterange{{ AttrName }}.value) { queryParams.value.begin{{ AttrName }} = daterange{{ AttrName }}.value[0]; queryParams.value.end{{ AttrName }} = daterange{{ AttrName }}.value[1]; } diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 index 73dae7d52ac3937f6be36ccabc59adfc8bdf8978..8e2c886af3f92d4e3f190d40de352f4337408c90 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 @@ -400,7 +400,7 @@ function getList() { {% for column in columns %} {% if column.html_type == "datetime" and column.query_type == "BETWEEN" %} {% set AttrName = column.python_field[0] | upper + column.python_field[1:] %} - if (null != daterange{{ AttrName }} && '' != daterange{{ AttrName }}) { + if (null != daterange{{ AttrName }}.value && '' != daterange{{ AttrName }}.value) { queryParams.value.begin{{ AttrName }} = daterange{{ AttrName }}.value[0]; queryParams.value.end{{ AttrName }} = daterange{{ AttrName }}.value[1]; } diff --git a/ruoyi-fastapi-backend/module_task/scheduler_test.py b/ruoyi-fastapi-backend/module_task/scheduler_test.py index b282732db1cee848b8c23ee782c2f3b5709ca131..7862c6db36fbf582a6bdb3adf7fab7cbe4104369 100644 --- a/ruoyi-fastapi-backend/module_task/scheduler_test.py +++ b/ruoyi-fastapi-backend/module_task/scheduler_test.py @@ -1,7 +1,7 @@ from datetime import datetime -def job(*args, **kwargs): +def job(*args, **kwargs) -> None: """ 定时任务执行同步函数示例 """ @@ -10,7 +10,7 @@ def job(*args, **kwargs): print(f'{datetime.now()}同步函数执行了') -async def async_job(*args, **kwargs): +async def async_job(*args, **kwargs) -> None: """ 定时任务执行异步函数示例 """ diff --git a/ruoyi-fastapi-backend/requirements-pg.txt b/ruoyi-fastapi-backend/requirements-pg.txt index 9dbf1fd609569dd2f915f32ed4c1e2267e27e408..2317cb2e010e2b3b8d6d4603b59c6cacfbc20a24 100644 --- a/ruoyi-fastapi-backend/requirements-pg.txt +++ b/ruoyi-fastapi-backend/requirements-pg.txt @@ -1,19 +1,20 @@ -alembic==1.16.4 -APScheduler==3.11.0 +aiofiles==25.1.0 +alembic==1.16.5 +APScheduler==3.11.1 async-lru==2.0.5 -asyncpg==0.30.0 +asyncpg==0.31.0 bcrypt==5.0.0 -DateTime==5.5 -fastapi[all]==0.116.1 +fastapi[all]==0.125.0 loguru==0.7.3 openpyxl==3.1.5 -pandas==2.3.2 +pandas==2.3.3 Pillow==11.3.0 -psutil==7.0.0 -pydantic-validation-decorator==0.1.4 +psutil==7.1.3 +pydantic-validation-decorator==0.1.5 PyJWT[crypto]==2.10.1 -psycopg2==2.9.10 +psycopg2==2.9.11 redis==6.4.0 -SQLAlchemy[asyncio]==2.0.43 -sqlglot[rs]==27.8.0 +ruff==0.14.10 +SQLAlchemy[asyncio]==2.0.45 +sqlglot[rs]==28.5.0 user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/requirements.txt b/ruoyi-fastapi-backend/requirements.txt index af66bc709c7afd598784c62a403c9c7bc30ace3a..19084a02af42b520a25dabccea8969e1742e6be7 100644 --- a/ruoyi-fastapi-backend/requirements.txt +++ b/ruoyi-fastapi-backend/requirements.txt @@ -1,19 +1,20 @@ -alembic==1.16.4 -APScheduler==3.11.0 +aiofiles==25.1.0 +alembic==1.16.5 +APScheduler==3.11.1 async-lru==2.0.5 asyncmy==0.2.10 bcrypt==5.0.0 -DateTime==5.5 -fastapi[all]==0.116.1 +fastapi[all]==0.125.0 loguru==0.7.3 openpyxl==3.1.5 -pandas==2.3.2 +pandas==2.3.3 Pillow==11.3.0 -psutil==7.0.0 -pydantic-validation-decorator==0.1.4 +psutil==7.1.3 +pydantic-validation-decorator==0.1.5 PyJWT[crypto]==2.10.1 -PyMySQL==1.1.1 +PyMySQL==1.1.2 redis==6.4.0 -SQLAlchemy[asyncio]==2.0.43 -sqlglot[rs]==27.8.0 +ruff==0.14.10 +SQLAlchemy[asyncio]==2.0.45 +sqlglot[rs]==28.5.0 user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/ruff.toml b/ruoyi-fastapi-backend/ruff.toml index ea953686cf07cd98f8382ffc6e5dd54f2b183c4c..61be1f79e0d220595e3d0d62f10abbcaed3d4c2b 100644 --- a/ruoyi-fastapi-backend/ruff.toml +++ b/ruoyi-fastapi-backend/ruff.toml @@ -1,4 +1,68 @@ line-length = 120 +show-fixes = true +target-version = "py39" +unsafe-fixes = true + +[lint] +select = [ + "FAST", # fastapi + "ANN", # flake8-annotations + "ASYNC", # flake8-async + "B", # flake8-bugbear + "A", # flake8-builtins + "COM", # flake8-commas + "C4", # flake8-comprehensions + "ISC", # flake8-implicit-str-concat + "ICN", # flake8-import-conventions + "PIE", # flake8-pie + "RSE", # flake8-raise + "RET", # flake8-return + "SIM", # flake8-simplify + "TC", # flake8-type-checking + "FLY", # flynt + "I", # isort + "N", # pep8-naming + "PERF", # perflint + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "PGH", # pygrep-hooks + "PL", # pylint + "UP", # pyupgrade + "RUF", # Ruff-specific rules +] +ignore = [ + "ANN002", # missing type annotation for *{name} + "ANN003", # missing type annotation for **{name} + "ANN401", # dynamically typed expressions (typing.Any) are disallowed in {name} + "B008", # do not perform function calls in argument defaults + "COM812", # trailing comma missing + "RET504", # unnecessary assignment to {name} before return statement + "SIM105", # use contextlib.suppress({exception}) instead of try-except-pass + "C901", # too complex + "N818", # exception name {name} should be named with an Error suffix + "E501", # line too long + "W191", # indentation contains tabs + "RUF001", # string contains ambiguous + "RUF002", # docstring contains ambiguous + "RUF003", # comment contains ambiguous + "RUF012", # mutable class attributes should be annotated with typing.ClassVar +] + +[lint.flake8-type-checking] +runtime-evaluated-base-classes = ["pydantic.BaseModel", "sqlalchemy.orm.DeclarativeBase"] + +[lint.isort] +known-third-party = ["alembic"] + +[lint.pylint] +max-args = 10 +max-returns = 10 + +[lint.pyupgrade] +# Preserve types, even if a file imports `from __future__ import annotations`. +keep-runtime-typing = true [format] +docstring-code-format = true quote-style = "single" \ No newline at end of file diff --git a/ruoyi-fastapi-backend/server.py b/ruoyi-fastapi-backend/server.py index 7a46e775f950099dba644ef7478d523548bfa461..724d8b51471732f09dd96645e2c827e3cb27667c 100644 --- a/ruoyi-fastapi-backend/server.py +++ b/ruoyi-fastapi-backend/server.py @@ -1,28 +1,17 @@ +from collections.abc import AsyncGenerator from contextlib import asynccontextmanager -from fastapi import FastAPI + +from fastapi import FastAPI, applications +from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html +from fastapi.responses import HTMLResponse + +from common.router import auto_register_routers from config.env import AppConfig from config.get_db import init_create_table from config.get_redis import RedisUtil from config.get_scheduler import SchedulerUtil from exceptions.handle import handle_exception from middlewares.handle import handle_middleware -from module_admin.controller.cache_controller import cacheController -from module_admin.controller.captcha_controller import captchaController -from module_admin.controller.common_controller import commonController -from module_admin.controller.config_controller import configController -from module_admin.controller.dept_controller import deptController -from module_admin.controller.dict_controller import dictController -from module_admin.controller.log_controller import logController -from module_admin.controller.login_controller import loginController -from module_admin.controller.job_controller import jobController -from module_admin.controller.menu_controller import menuController -from module_admin.controller.notice_controller import noticeController -from module_admin.controller.online_controller import onlineController -from module_admin.controller.post_controler import postController -from module_admin.controller.role_controller import roleController -from module_admin.controller.server_controller import serverController -from module_admin.controller.user_controller import userController -from module_generator.controller.gen_controller import genController from sub_applications.handle import handle_sub_applications from utils.common_util import worship from utils.log_util import logger @@ -30,7 +19,7 @@ from utils.log_util import logger # 生命周期事件 @asynccontextmanager -async def lifespan(app: FastAPI): +async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: logger.info(f'⏰️ {AppConfig.app_name}开始启动') worship() await init_create_table() @@ -44,42 +33,68 @@ async def lifespan(app: FastAPI): await SchedulerUtil.close_system_scheduler() -# 初始化FastAPI对象 -app = FastAPI( - title=AppConfig.app_name, - description=f'{AppConfig.app_name}接口文档', - version=AppConfig.app_version, - lifespan=lifespan, -) +def setup_docs_static_resources( + redoc_js_url: str = 'https://registry.npmmirror.com/redoc/2/files/bundles/redoc.standalone.js', + redoc_favicon_url: str = 'https://fastapi.tiangolo.com/img/favicon.png', + swagger_js_url: str = 'https://registry.npmmirror.com/swagger-ui-dist/5/files/swagger-ui-bundle.js', + swagger_css_url: str = 'https://registry.npmmirror.com/swagger-ui-dist/5/files/swagger-ui.css', + swagger_favicon_url: str = 'https://fastapi.tiangolo.com/img/favicon.png', +) -> None: + """ + 配置文档静态资源 + + :param redoc_js_url: 用于加载ReDoc JavaScript的URL + :param redoc_favicon_url: ReDoc要使用的favicon的URL + :param swagger_js_url: 用于加载Swagger UI JavaScript的URL + :param swagger_css_url: 用于加载Swagger UI CSS的URL + :param swagger_favicon_url: Swagger UI要使用的favicon的URL + :return: + """ + + def redoc_monkey_patch(*args, **kwargs) -> HTMLResponse: + return get_redoc_html( + *args, + **kwargs, + redoc_js_url=redoc_js_url, + redoc_favicon_url=redoc_favicon_url, + ) + + def swagger_ui_monkey_patch(*args, **kwargs) -> HTMLResponse: + return get_swagger_ui_html( + *args, + **kwargs, + swagger_js_url=swagger_js_url, + swagger_css_url=swagger_css_url, + swagger_favicon_url=swagger_favicon_url, + ) + + applications.get_redoc_html = redoc_monkey_patch + applications.get_swagger_ui_html = swagger_ui_monkey_patch + -# 挂载子应用 -handle_sub_applications(app) -# 加载中间件处理方法 -handle_middleware(app) -# 加载全局异常处理方法 -handle_exception(app) +def create_app() -> FastAPI: + """ + 创建FastAPI应用 + :return: FastAPI对象 + """ + # 配置文档静态资源 + setup_docs_static_resources() + # 初始化FastAPI对象 + app = FastAPI( + title=AppConfig.app_name, + description=f'{AppConfig.app_name}接口文档', + version=AppConfig.app_version, + lifespan=lifespan, + ) -# 加载路由列表 -controller_list = [ - {'router': loginController, 'tags': ['登录模块']}, - {'router': captchaController, 'tags': ['验证码模块']}, - {'router': userController, 'tags': ['系统管理-用户管理']}, - {'router': roleController, 'tags': ['系统管理-角色管理']}, - {'router': menuController, 'tags': ['系统管理-菜单管理']}, - {'router': deptController, 'tags': ['系统管理-部门管理']}, - {'router': postController, 'tags': ['系统管理-岗位管理']}, - {'router': dictController, 'tags': ['系统管理-字典管理']}, - {'router': configController, 'tags': ['系统管理-参数管理']}, - {'router': noticeController, 'tags': ['系统管理-通知公告管理']}, - {'router': logController, 'tags': ['系统管理-日志管理']}, - {'router': onlineController, 'tags': ['系统监控-在线用户']}, - {'router': jobController, 'tags': ['系统监控-定时任务']}, - {'router': serverController, 'tags': ['系统监控-菜单管理']}, - {'router': cacheController, 'tags': ['系统监控-缓存监控']}, - {'router': commonController, 'tags': ['通用模块']}, - {'router': genController, 'tags': ['代码生成']}, -] + # 挂载子应用 + handle_sub_applications(app) + # 加载中间件处理方法 + handle_middleware(app) + # 加载全局异常处理方法 + handle_exception(app) + # 自动注册路由 + auto_register_routers(app) -for controller in controller_list: - app.include_router(router=controller.get('router'), tags=controller.get('tags')) + return app diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql index 18446b0b658a572c6ed2360832b070e57f77f340..4b117aa499e6bb25b96bf287212c64bda4ffb768 100644 --- a/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql @@ -936,8 +936,7 @@ create table gen_table_column ( create_time timestamp(0), update_by varchar(64) default '', update_time timestamp(0), - primary key (column_id), - constraint fk_gen_table_column_table_id foreign key (table_id) references gen_table(table_id) + primary key (column_id) ); comment on column gen_table_column.column_id is '编号'; comment on column gen_table_column.table_id is '归属表编号'; diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql index e48900788a8efb1db20c87b37a55062f7682cfbd..7bc6f3c7b059f758438bf358612b06f9ab67466b 100644 --- a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql @@ -712,6 +712,5 @@ create table gen_table_column ( create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', - primary key (column_id), - foreign key (table_id) references gen_table(table_id) + primary key (column_id) ) engine=innodb auto_increment=1 comment = '代码生成业务表字段'; \ No newline at end of file diff --git a/ruoyi-fastapi-backend/sub_applications/handle.py b/ruoyi-fastapi-backend/sub_applications/handle.py index df2a5f481a56afa59be84e9aa9490b794600dd60..325193e90185878013b515e5b5fce7a0efde313c 100644 --- a/ruoyi-fastapi-backend/sub_applications/handle.py +++ b/ruoyi-fastapi-backend/sub_applications/handle.py @@ -1,8 +1,9 @@ from fastapi import FastAPI + from sub_applications.staticfiles import mount_staticfiles -def handle_sub_applications(app: FastAPI): +def handle_sub_applications(app: FastAPI) -> None: """ 全局处理子应用挂载 """ diff --git a/ruoyi-fastapi-backend/sub_applications/staticfiles.py b/ruoyi-fastapi-backend/sub_applications/staticfiles.py index c481d729f7e6beb81ebbe8357303b113b530ded0..5e5d4e5aec8505c66418198c0f277dab36f13e77 100644 --- a/ruoyi-fastapi-backend/sub_applications/staticfiles.py +++ b/ruoyi-fastapi-backend/sub_applications/staticfiles.py @@ -1,9 +1,10 @@ from fastapi import FastAPI from fastapi.staticfiles import StaticFiles + from config.env import UploadConfig -def mount_staticfiles(app: FastAPI): +def mount_staticfiles(app: FastAPI) -> None: """ 挂载静态文件 """ diff --git a/ruoyi-fastapi-backend/utils/common_util.py b/ruoyi-fastapi-backend/utils/common_util.py index 0ccb546998516ff6f8a4d1baa1d1d646daee7dfa..43ab2d281c824817d778abbde2650cb854ffd17c 100644 --- a/ruoyi-fastapi-backend/utils/common_util.py +++ b/ruoyi-fastapi-backend/utils/common_util.py @@ -1,7 +1,10 @@ import io import os -import pandas as pd import re +from collections.abc import Generator, Sequence +from typing import Any, Literal, Union, overload + +import pandas as pd from openpyxl import Workbook from openpyxl.styles import Alignment, PatternFill from openpyxl.utils import get_column_letter @@ -9,13 +12,13 @@ from openpyxl.worksheet.datavalidation import DataValidation from sqlalchemy.engine.row import Row from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.sql.expression import TextClause, null -from typing import Any, Dict, List, Literal, Union + from config.database import Base from config.env import CachePathConfig -def worship(): - print(""" +def worship() -> None: + print(r""" //////////////////////////////////////////////////////////////////// // _ooOoo_ // // o8888888o // @@ -36,7 +39,7 @@ def worship(): // ========`-.____`-.___\_____/___.-`____.-'======== // // `=---=' // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // -// 佛祖保佑 永不宕机 永无BUG // +// 佛祖保佑 永不宕机 永无BUG // //////////////////////////////////////////////////////////////////// """) @@ -48,8 +51,8 @@ class SqlalchemyUtil: @classmethod def base_to_dict( - cls, obj: Union[Base, Dict], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' - ): + cls, obj: Union[Base, dict], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> dict: """ 将sqlalchemy模型对象转换为字典 @@ -67,15 +70,63 @@ class SqlalchemyUtil: base_dict = obj.copy() if transform_case == 'snake_to_camel': return {CamelCaseUtil.snake_to_camel(k): v for k, v in base_dict.items()} - elif transform_case == 'camel_to_snake': + if transform_case == 'camel_to_snake': return {SnakeCaseUtil.camel_to_snake(k): v for k, v in base_dict.items()} return base_dict + @classmethod + @overload + def serialize_result( + cls, result: Base, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> dict[str, Any]: ... + + @classmethod + @overload + def serialize_result( + cls, result: dict, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> dict[Any, Any]: ... + + @classmethod + @overload + def serialize_result( + cls, result: Row, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> Union[dict[str, Any], list[dict[Any, Any]]]: ... + + @classmethod + @overload + def serialize_result( + cls, result: Sequence[Base], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> list[dict[str, Any]]: ... + + @classmethod + @overload + def serialize_result( + cls, result: Sequence[dict], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> list[dict[Any, Any]]: ... + + @classmethod + @overload + def serialize_result( + cls, result: Sequence[Row], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> list[Union[dict[str, Any], list[dict[Any, Any]]]]: ... + + @classmethod + @overload + def serialize_result( + cls, result: Sequence[Any], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> list[Any]: ... + + @classmethod + @overload + def serialize_result( + cls, result: Any, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + ) -> Any: ... + @classmethod def serialize_result( cls, result: Any, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' - ): + ) -> Any: """ 将sqlalchemy查询结果序列化 @@ -85,20 +136,19 @@ class SqlalchemyUtil: """ if isinstance(result, (Base, dict)): return cls.base_to_dict(result, transform_case) - elif isinstance(result, list): + if isinstance(result, list): return [cls.serialize_result(row, transform_case) for row in result] - elif isinstance(result, Row): - if all([isinstance(row, Base) for row in result]): + if isinstance(result, Row): + if all(isinstance(row, Base) for row in result): return [cls.base_to_dict(row, transform_case) for row in result] - elif any([isinstance(row, Base) for row in result]): + if any(isinstance(row, Base) for row in result): return [cls.serialize_result(row, transform_case) for row in result] - else: - result_dict = result._asdict() - if transform_case == 'snake_to_camel': - return {CamelCaseUtil.snake_to_camel(k): v for k, v in result_dict.items()} - elif transform_case == 'camel_to_snake': - return {SnakeCaseUtil.camel_to_snake(k): v for k, v in result_dict.items()} - return result_dict + result_dict = result._asdict() + if transform_case == 'snake_to_camel': + return {CamelCaseUtil.snake_to_camel(k): v for k, v in result_dict.items()} + if transform_case == 'camel_to_snake': + return {SnakeCaseUtil.camel_to_snake(k): v for k, v in result_dict.items()} + return result_dict return result @classmethod @@ -121,7 +171,7 @@ class CamelCaseUtil: """ @classmethod - def snake_to_camel(cls, snake_str: str): + def snake_to_camel(cls, snake_str: str) -> str: """ 下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase) @@ -134,7 +184,39 @@ class CamelCaseUtil: return words[0] + ''.join(word.capitalize() for word in words[1:]) @classmethod - def transform_result(cls, result: Any): + @overload + def transform_result(cls, result: Base) -> dict[str, Any]: ... + + @classmethod + @overload + def transform_result(cls, result: dict) -> dict[Any, Any]: ... + + @classmethod + @overload + def transform_result(cls, result: Row) -> Union[dict[str, Any], list[dict[Any, Any]]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[Base]) -> list[dict[str, Any]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[dict]) -> list[dict[Any, Any]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[Row]) -> list[Union[dict[str, Any], list[dict[Any, Any]]]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[Any]) -> list[Any]: ... + + @classmethod + @overload + def transform_result(cls, result: Any) -> Any: ... + + @classmethod + def transform_result(cls, result: Any) -> Any: """ 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 @@ -150,7 +232,7 @@ class SnakeCaseUtil: """ @classmethod - def camel_to_snake(cls, camel_str: str): + def camel_to_snake(cls, camel_str: str) -> str: """ 小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case) @@ -162,17 +244,49 @@ class SnakeCaseUtil: return re.sub('([a-z0-9])([A-Z])', r'\1_\2', words).lower() @classmethod - def transform_result(cls, result: Any): + @overload + def transform_result(cls, result: Base) -> dict[str, Any]: ... + + @classmethod + @overload + def transform_result(cls, result: dict) -> dict[Any, Any]: ... + + @classmethod + @overload + def transform_result(cls, result: Row) -> Union[dict[str, Any], list[dict[Any, Any]]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[Base]) -> list[dict[str, Any]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[dict]) -> list[dict[Any, Any]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[Row]) -> list[Union[dict[str, Any], list[dict[Any, Any]]]]: ... + + @classmethod + @overload + def transform_result(cls, result: Sequence[Any]) -> list[Any]: ... + + @classmethod + @overload + def transform_result(cls, result: Any) -> Any: ... + + @classmethod + def transform_result(cls, result: Any) -> Any: """ - 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + 针对不同类型将小驼峰形式(camelCase)批量转换为下划线形式(snake_case)方法 :param result: 输入数据 - :return: 小驼峰形式结果 + :return: 下划线形式结果 """ return SqlalchemyUtil.serialize_result(result=result, transform_case='camel_to_snake') -def bytes2human(n, format_str='%(value).1f%(symbol)s'): +def bytes2human(n: int, format_str: str = '%(value).1f%(symbol)s') -> str: """Used by various scripts. See: http://goo.gl/zeJZl @@ -189,14 +303,14 @@ def bytes2human(n, format_str='%(value).1f%(symbol)s'): if n >= prefix[symbol]: value = float(n) / prefix[symbol] return format_str % locals() - return format_str % dict(symbol=symbols[0], value=n) + return format_str % {'symbol': symbols[0], 'value': n} -def bytes2file_response(bytes_info): +def bytes2file_response(bytes_info: bytes) -> Generator[bytes]: yield bytes_info -def export_list2excel(list_data: List): +def export_list2excel(list_data: list) -> bytes: """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 @@ -211,7 +325,7 @@ def export_list2excel(list_data: List): return binary_data -def get_excel_template(header_list: List, selector_header_list: List, option_list: List[dict]): +def get_excel_template(header_list: list, selector_header_list: list, option_list: list[dict]) -> bytes: """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 @@ -272,7 +386,7 @@ def get_excel_template(header_list: List, selector_header_list: List, option_lis return excel_data -def get_filepath_from_url(url: str): +def get_filepath_from_url(url: str) -> str: """ 工具方法:根据请求参数获取文件路径 diff --git a/ruoyi-fastapi-backend/utils/cron_util.py b/ruoyi-fastapi-backend/utils/cron_util.py index 62329627d27fd547e215276c8885f074ac12749a..045f4ea44a2f135dbb2f78f57f4716fcf35f81f0 100644 --- a/ruoyi-fastapi-backend/utils/cron_util.py +++ b/ruoyi-fastapi-backend/utils/cron_util.py @@ -7,8 +7,13 @@ class CronUtil: Cron表达式工具类 """ + ONE_YEAR_LENGTH = 4 + MAX_YEAR = 2099 + CRON_EXPRESSION_LENGTH_MIN = 6 + CRON_EXPRESSION_LENGTH_MAX = 7 + @classmethod - def __valid_range(cls, search_str: str, start_range: int, end_range: int): + def __valid_range(cls, search_str: str, start_range: int, end_range: int) -> bool: match = re.match(r'^(\d+)-(\d+)$', search_str) if match: start, end = int(match.group(1)), int(match.group(2)) @@ -18,7 +23,7 @@ class CronUtil: @classmethod def __valid_sum( cls, search_str: str, start_range_a: int, start_range_b: int, end_range_a: int, end_range_b: int, sum_range: int - ): + ) -> bool: match = re.match(r'^(\d+)/(\d+)$', search_str) if match: start, end = int(match.group(1)), int(match.group(2)) @@ -30,94 +35,84 @@ class CronUtil: return False @classmethod - def validate_second_or_minute(cls, second_or_minute: str): + def validate_second_or_minute(cls, second_or_minute: str) -> bool: """ 校验秒或分钟值是否正确 :param second_or_minute: 秒或分钟值 :return: 校验结果 """ - if ( + return bool( second_or_minute == '*' or ('-' in second_or_minute and cls.__valid_range(second_or_minute, 0, 59)) or ('/' in second_or_minute and cls.__valid_sum(second_or_minute, 0, 58, 1, 59, 59)) or re.match(r'^(?:[0-5]?\d|59)(?:,[0-5]?\d|59)*$', second_or_minute) - ): - return True - return False + ) @classmethod - def validate_hour(cls, hour: str): + def validate_hour(cls, hour: str) -> bool: """ 校验小时值是否正确 :param hour: 小时值 :return: 校验结果 """ - if ( + return bool( hour == '*' or ('-' in hour and cls.__valid_range(hour, 0, 23)) or ('/' in hour and cls.__valid_sum(hour, 0, 22, 1, 23, 23)) or re.match(r'^(?:0|[1-9]|1\d|2[0-3])(?:,(?:0|[1-9]|1\d|2[0-3]))*$', hour) - ): - return True - return False + ) @classmethod - def validate_day(cls, day: str): + def validate_day(cls, day: str) -> bool: """ 校验日值是否正确 :param day: 日值 :return: 校验结果 """ - if ( + return bool( day in ['*', '?', 'L'] or ('-' in day and cls.__valid_range(day, 1, 31)) or ('/' in day and cls.__valid_sum(day, 1, 30, 1, 30, 31)) or ('W' in day and re.match(r'^(?:[1-9]|1\d|2\d|3[01])W$', day)) or re.match(r'^(?:0|[1-9]|1\d|2[0-9]|3[0-1])(?:,(?:0|[1-9]|1\d|2[0-9]|3[0-1]))*$', day) - ): - return True - return False + ) @classmethod - def validate_month(cls, month: str): + def validate_month(cls, month: str) -> bool: """ 校验月值是否正确 :param month: 月值 :return: 校验结果 """ - if ( + return bool( month == '*' or ('-' in month and cls.__valid_range(month, 1, 12)) or ('/' in month and cls.__valid_sum(month, 1, 11, 1, 11, 12)) or re.match(r'^(?:0|[1-9]|1[0-2])(?:,(?:0|[1-9]|1[0-2]))*$', month) - ): - return True - return False + ) @classmethod - def validate_week(cls, week: str): + def validate_week(cls, week: str) -> bool: """ 校验周值是否正确 :param week: 周值 :return: 校验结果 """ - if ( + return bool( week in ['*', '?'] or ('-' in week and cls.__valid_range(week, 1, 7)) or ('#' in week and re.match(r'^[1-7]#[1-4]$', week)) or ('L' in week and re.match(r'^[1-7]L$', week)) or re.match(r'^[1-7](?:(,[1-7]))*$', week) - ): - return True - return False + ) @classmethod - def validate_year(cls, year: str): + def validate_year(cls, year: str) -> bool: """ 校验年值是否正确 @@ -126,22 +121,22 @@ class CronUtil: """ current_year = int(datetime.now().year) future_years = [current_year + i for i in range(9)] - if ( + return bool( year == '*' or ('-' in year and cls.__valid_range(year, current_year, 2099)) or ('/' in year and cls.__valid_sum(year, current_year, 2098, 1, 2099 - current_year, 2099)) or ('#' in year and re.match(r'^[1-7]#[1-4]$', year)) or ('L' in year and re.match(r'^[1-7]L$', year)) or ( - (len(year) == 4 or ',' in year) - and all(int(item) in future_years and current_year <= int(item) <= 2099 for item in year.split(',')) + (len(year) == cls.ONE_YEAR_LENGTH or ',' in year) + and all( + int(item) in future_years and current_year <= int(item) <= cls.MAX_YEAR for item in year.split(',') + ) ) - ): - return True - return False + ) @classmethod - def validate_cron_expression(cls, cron_expression: str): + def validate_cron_expression(cls, cron_expression: str) -> bool: """ 校验Cron表达式是否正确 @@ -149,7 +144,7 @@ class CronUtil: :return: 校验结果 """ values = cron_expression.split() - if len(values) != 6 and len(values) != 7: + if len(values) != cls.CRON_EXPRESSION_LENGTH_MIN and len(values) != cls.CRON_EXPRESSION_LENGTH_MAX: return False second_validation = cls.validate_second_or_minute(values[0]) minute_validation = cls.validate_second_or_minute(values[1]) @@ -165,8 +160,7 @@ class CronUtil: and month_validation and week_validation ) - if len(values) == 6: + if len(values) == cls.CRON_EXPRESSION_LENGTH_MIN: return validation - if len(values) == 7: - year_validation = cls.validate_year(values[6]) - return validation and year_validation + year_validation = cls.validate_year(values[6]) + return validation and year_validation diff --git a/ruoyi-fastapi-backend/utils/dependency_util.py b/ruoyi-fastapi-backend/utils/dependency_util.py new file mode 100644 index 0000000000000000000000000000000000000000..23ace9d27675d35f81571b3d4c79371d84e859f2 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/dependency_util.py @@ -0,0 +1,49 @@ +from fastapi import Request + +from common.context import RequestContext +from config.env import AppConfig +from exceptions.exception import PermissionException + + +class DependencyUtil: + """ + 依赖项工具类 + """ + + @classmethod + def check_exclude_routes(cls, request: Request, err_msg: str = '当前路由不在认证规则内,不可使用该依赖项') -> None: + """ + 检查路径和方法是否匹配排除路由模式 + + :param request: 请求对象 + :param err_msg: 错误信息 + :return: None + """ + # 获取当前请求路径和方法 + 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) :] + + # 获取编译后的排除路由模式列表 + exclude_patterns = RequestContext.get_current_exclude_patterns() + + # 检查当前路由是否在排除路由列表中 + if path and method and exclude_patterns: + for item in 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): + raise PermissionException(data='', message=err_msg) diff --git a/ruoyi-fastapi-backend/utils/excel_util.py b/ruoyi-fastapi-backend/utils/excel_util.py index 875a41d92f1fa3b262772401793fca452a9a7d99..07168d10baf1d64173ced3553e4069f8c3b08c1e 100644 --- a/ruoyi-fastapi-backend/utils/excel_util.py +++ b/ruoyi-fastapi-backend/utils/excel_util.py @@ -1,10 +1,10 @@ import io + import pandas as pd from openpyxl import Workbook from openpyxl.styles import Alignment, PatternFill from openpyxl.utils import get_column_letter from openpyxl.worksheet.datavalidation import DataValidation -from typing import Dict, List class ExcelUtil: @@ -13,7 +13,7 @@ class ExcelUtil: """ @classmethod - def __mapping_list(cls, list_data: List, mapping_dict: Dict): + def __mapping_list(cls, list_data: list, mapping_dict: dict) -> list[dict]: """ 工具方法:将list数据中的字段名映射为对应的中文字段名 @@ -26,7 +26,7 @@ class ExcelUtil: return mapping_data @classmethod - def export_list2excel(cls, list_data: List, mapping_dict: Dict): + def export_list2excel(cls, list_data: list, mapping_dict: dict) -> bytes: """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 @@ -43,7 +43,7 @@ class ExcelUtil: return binary_data @classmethod - def get_excel_template(cls, header_list: List, selector_header_list: List, option_list: List[Dict]): + def get_excel_template(cls, header_list: list, selector_header_list: list, option_list: list[dict]) -> bytes: """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 diff --git a/ruoyi-fastapi-backend/utils/gen_util.py b/ruoyi-fastapi-backend/utils/gen_util.py index 355e5d022557c757507c95c81c585e9e3b2f7647..c08ceec455c21fd471ca35bedbe6d644c6a1064f 100644 --- a/ruoyi-fastapi-backend/utils/gen_util.py +++ b/ruoyi-fastapi-backend/utils/gen_util.py @@ -1,7 +1,7 @@ import re from datetime import datetime -from typing import List -from config.constant import GenConstant + +from common.constant import GenConstant from config.env import GenConfig from module_generator.entity.vo.gen_vo import GenTableColumnModel, GenTableModel from utils.string_util import StringUtil @@ -10,6 +10,8 @@ from utils.string_util import StringUtil class GenUtils: """代码生成器工具类""" + TEXTAREA_COLUMN_LENGTH = 500 + @classmethod def init_table(cls, gen_table: GenTableModel, oper_name: str) -> None: """ @@ -58,7 +60,8 @@ class GenUtils: column_length = cls.get_column_length(column.column_type) html_type = ( GenConstant.HTML_TEXTAREA - if column_length >= 500 or cls.arrays_contains(GenConstant.COLUMNTYPE_TEXT, data_type) + if column_length >= cls.TEXTAREA_COLUMN_LENGTH + or cls.arrays_contains(GenConstant.COLUMNTYPE_TEXT, data_type) else GenConstant.HTML_INPUT ) column.html_type = html_type @@ -98,14 +101,14 @@ class GenUtils: # 内容字段设置富文本控件 elif column_name.lower().endswith('content'): column.html_type = GenConstant.HTML_EDITOR - + column.create_by = table.create_by column.create_time = datetime.now() column.update_by = table.update_by column.update_time = datetime.now() @classmethod - def arrays_contains(cls, arr: List[str], target_value: str) -> bool: + def arrays_contains(cls, arr: list[str], target_value: str) -> bool: """ 校验数组是否包含指定值 @@ -151,7 +154,7 @@ class GenUtils: return StringUtil.convert_to_camel_case(table_name) @classmethod - def replace_first(cls, replacement: str, search_list: List[str]) -> str: + def replace_first(cls, replacement: str, search_list: list[str]) -> str: """ 批量替换前缀 @@ -200,7 +203,7 @@ class GenUtils: return 0 @classmethod - def split_column_type(cls, column_type: str) -> List[str]: + def split_column_type(cls, column_type: str) -> list[str]: """ 拆分列类型 diff --git a/ruoyi-fastapi-backend/utils/import_util.py b/ruoyi-fastapi-backend/utils/import_util.py index 4bb2a63d7fcb12f862993a6496451a6147217ced..c349e9c58d7aa1f7f9bb1bc38653d58bd566a59f 100644 --- a/ruoyi-fastapi-backend/utils/import_util.py +++ b/ruoyi-fastapi-backend/utils/import_util.py @@ -1,11 +1,13 @@ import importlib import inspect import os -from pathlib import Path import sys from functools import lru_cache +from pathlib import Path +from typing import Any + from sqlalchemy import inspect as sa_inspect -from typing import Any, List + from config.database import Base @@ -49,7 +51,7 @@ class ImportUtil: @classmethod @lru_cache(maxsize=256) - def find_models(cls, base_class: Base) -> List[Base]: + def find_models(cls, base_class: Base) -> list[Base]: """ 查找并过滤有效的模型类,避免重复和无效定义 @@ -64,7 +66,7 @@ class ImportUtil: project_root = cls.find_project_root() sys.path.append(str(project_root)) - print(f"⏰️ 开始在项目根目录 {project_root} 中查找模型...") + print(f'⏰️ 开始在项目根目录 {project_root} 中查找模型...') # 排除目录扩展 exclude_dirs = { @@ -87,13 +89,13 @@ class ImportUtil: for file in files: if file.endswith('.py') and not file.startswith('__'): relative_path = Path(root).relative_to(project_root) - module_parts = list(relative_path.parts) + [file[:-3]] + module_parts = [*list(relative_path.parts), file[:-3]] module_name = '.'.join(module_parts) try: module = importlib.import_module(module_name) - for name, obj in inspect.getmembers(module, inspect.isclass): + for _name, obj in inspect.getmembers(module, inspect.isclass): # 验证模型有效性 if not cls.is_valid_model(obj, base_class): continue diff --git a/ruoyi-fastapi-backend/utils/log_util.py b/ruoyi-fastapi-backend/utils/log_util.py index f953f55159b10b686e84975964fd9e7f7a761cf6..8996306a674f1325ddb8e1b6ebf5b1582a905166 100644 --- a/ruoyi-fastapi-backend/utils/log_util.py +++ b/ruoyi-fastapi-backend/utils/log_util.py @@ -1,18 +1,20 @@ import os import sys import time + from loguru import logger as _logger -from typing import Dict +from loguru._logger import Logger + from middlewares.trace_middleware import TraceCtx class LoggerInitializer: - def __init__(self): + def __init__(self) -> None: self.log_path = os.path.join(os.getcwd(), 'logs') self.__ensure_log_directory_exists() self.log_path_error = os.path.join(self.log_path, f'{time.strftime("%Y-%m-%d")}_error.log') - def __ensure_log_directory_exists(self): + def __ensure_log_directory_exists(self) -> None: """ 确保日志目录存在,如果不存在则创建 """ @@ -20,14 +22,14 @@ class LoggerInitializer: os.mkdir(self.log_path) @staticmethod - def __filter(log: Dict): + def __filter(log: dict) -> dict: """ 自定义日志过滤器,添加trace_id """ log['trace_id'] = TraceCtx.get_id() return log - def init_log(self): + def init_log(self) -> Logger: """ 初始化日志配置 """ diff --git a/ruoyi-fastapi-backend/utils/message_util.py b/ruoyi-fastapi-backend/utils/message_util.py index 3d3eb51aebd51cb1f97b5a756e3a4bf493c1ead4..b425d3b57c92a2b9f1f176dcc7e743d0080261f2 100644 --- a/ruoyi-fastapi-backend/utils/message_util.py +++ b/ruoyi-fastapi-backend/utils/message_util.py @@ -1,5 +1,5 @@ from utils.log_util import logger -def message_service(sms_code: str): +def message_service(sms_code: str) -> None: logger.info(f'短信验证码为{sms_code}') diff --git a/ruoyi-fastapi-backend/utils/page_util.py b/ruoyi-fastapi-backend/utils/page_util.py index dfe8a31cf132c81728da77116f214191bc99daab..ba75f70ab52012c28c339c9a3ace4152b553098a 100644 --- a/ruoyi-fastapi-backend/utils/page_util.py +++ b/ruoyi-fastapi-backend/utils/page_util.py @@ -1,24 +1,11 @@ import math -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from sqlalchemy import func, select, Select -from sqlalchemy.ext.asyncio import AsyncSession -from typing import Optional, List -from utils.common_util import CamelCaseUtil - - -class PageResponseModel(BaseModel): - """ - 列表分页查询返回模型 - """ +from typing import Any, Union - model_config = ConfigDict(alias_generator=to_camel) +from sqlalchemy import Row, Select, func, select +from sqlalchemy.ext.asyncio import AsyncSession - rows: List = [] - page_num: Optional[int] = None - page_size: Optional[int] = None - total: int - has_next: Optional[bool] = None +from common.vo import PageModel +from utils.common_util import CamelCaseUtil class PageUtil: @@ -27,7 +14,7 @@ class PageUtil: """ @classmethod - def get_page_obj(cls, data_list: List, page_num: int, page_size: int): + def get_page_obj(cls, data_list: list, page_num: int, page_size: int) -> PageModel: """ 输入数据列表data_list和分页信息,返回分页数据列表结果 @@ -42,16 +29,18 @@ class PageUtil: # 根据计算得到的起始索引和结束索引对数据列表进行切片 paginated_data = data_list[start:end] - has_next = True if math.ceil(len(data_list) / page_size) > page_num else False + has_next = math.ceil(len(data_list) / page_size) > page_num - result = PageResponseModel( + result = PageModel[Any]( rows=paginated_data, pageNum=page_num, pageSize=page_size, total=len(data_list), hasNext=has_next ) return result @classmethod - async def paginate(cls, db: AsyncSession, query: Select, page_num: int, page_size: int, is_page: bool = False): + async def paginate( + cls, db: AsyncSession, query: Select, page_num: int, page_size: int, is_page: bool = False + ) -> Union[PageModel, list[Union[dict[str, Any], list[dict[Any, Any]]]]]: """ 输入查询语句和分页信息,返回分页数据列表结果 @@ -65,14 +54,14 @@ class PageUtil: if is_page: total = (await db.execute(select(func.count('*')).select_from(query.subquery()))).scalar() query_result = await db.execute(query.offset((page_num - 1) * page_size).limit(page_size)) - paginated_data = [] + paginated_data: list[Row] = [] for row in query_result: if row and len(row) == 1: paginated_data.append(row[0]) else: paginated_data.append(row) has_next = math.ceil(total / page_size) > page_num - result = PageResponseModel( + result = PageModel[Any]( rows=CamelCaseUtil.transform_result(paginated_data), pageNum=page_num, pageSize=page_size, @@ -81,7 +70,7 @@ class PageUtil: ) else: query_result = await db.execute(query) - no_paginated_data = [] + no_paginated_data: list[Row] = [] for row in query_result: if row and len(row) == 1: no_paginated_data.append(row[0]) @@ -92,7 +81,7 @@ class PageUtil: return result -def get_page_obj(data_list: List, page_num: int, page_size: int): +def get_page_obj(data_list: list, page_num: int, page_size: int) -> PageModel: """ 输入数据列表data_list和分页信息,返回分页数据列表结果 @@ -107,9 +96,9 @@ def get_page_obj(data_list: List, page_num: int, page_size: int): # 根据计算得到的起始索引和结束索引对数据列表进行切片 paginated_data = data_list[start:end] - has_next = True if math.ceil(len(data_list) / page_size) > page_num else False + has_next = math.ceil(len(data_list) / page_size) > page_num - result = PageResponseModel( + result = PageModel[Any]( rows=paginated_data, pageNum=page_num, pageSize=page_size, total=len(data_list), hasNext=has_next ) diff --git a/ruoyi-fastapi-backend/utils/pwd_util.py b/ruoyi-fastapi-backend/utils/pwd_util.py index c3f91ce76fe4d9c7080fe3496ddc7b42b53bb460..9fa592f44b3c46d8f3093d737ff89a9df1eba0cc 100644 --- a/ruoyi-fastapi-backend/utils/pwd_util.py +++ b/ruoyi-fastapi-backend/utils/pwd_util.py @@ -20,7 +20,7 @@ class PwdUtil: ) @classmethod - def get_password_hash(cls, input_password): + def get_password_hash(cls, input_password: str) -> str: """ 工具方法:对当前输入的密码进行加密 diff --git a/ruoyi-fastapi-backend/utils/response_util.py b/ruoyi-fastapi-backend/utils/response_util.py index 01d463281c510a162df8713162b2206f1b808f0f..ebf85c32a31c69719899575331421687b78e86bc 100644 --- a/ruoyi-fastapi-backend/utils/response_util.py +++ b/ruoyi-fastapi-backend/utils/response_util.py @@ -1,11 +1,14 @@ +from collections.abc import Mapping from datetime import datetime +from typing import Any, Optional + from fastapi import status from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse, Response, StreamingResponse from pydantic import BaseModel from starlette.background import BackgroundTask -from typing import Any, Dict, Mapping, Optional -from config.constant import HttpStatusConstant + +from common.constant import HttpStatusConstant class ResponseUtil: @@ -19,7 +22,7 @@ class ResponseUtil: msg: str = '操作成功', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, + dict_content: Optional[dict] = None, model_content: Optional[BaseModel] = None, headers: Optional[Mapping[str, str]] = None, media_type: Optional[str] = None, @@ -65,7 +68,7 @@ class ResponseUtil: msg: str = '操作失败', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, + dict_content: Optional[dict] = None, model_content: Optional[BaseModel] = None, headers: Optional[Mapping[str, str]] = None, media_type: Optional[str] = None, @@ -111,7 +114,7 @@ class ResponseUtil: msg: str = '登录信息已过期,访问系统资源失败', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, + dict_content: Optional[dict] = None, model_content: Optional[BaseModel] = None, headers: Optional[Mapping[str, str]] = None, media_type: Optional[str] = None, @@ -157,7 +160,7 @@ class ResponseUtil: msg: str = '该用户无此接口权限', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, + dict_content: Optional[dict] = None, model_content: Optional[BaseModel] = None, headers: Optional[Mapping[str, str]] = None, media_type: Optional[str] = None, @@ -203,7 +206,7 @@ class ResponseUtil: msg: str = '接口异常', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, + dict_content: Optional[dict] = None, model_content: Optional[BaseModel] = None, headers: Optional[Mapping[str, str]] = None, media_type: Optional[str] = None, diff --git a/ruoyi-fastapi-backend/utils/string_util.py b/ruoyi-fastapi-backend/utils/string_util.py index 7196bcf53de6530c8a3f760aee40abc893c1baa3..d14d6609519da91d02c5c71722f71c3551338bd1 100644 --- a/ruoyi-fastapi-backend/utils/string_util.py +++ b/ruoyi-fastapi-backend/utils/string_util.py @@ -1,5 +1,6 @@ -from typing import Dict, List -from config.constant import CommonConstant +from typing import Union + +from common.constant import CommonConstant class StringUtil: @@ -8,7 +9,7 @@ class StringUtil: """ @classmethod - def is_blank(cls, string: str) -> bool: + def is_blank(cls, string: Union[str, None]) -> bool: """ 校验字符串是否为''或全空格 @@ -20,14 +21,10 @@ class StringUtil: str_len = len(string) if str_len == 0: return True - else: - for i in range(str_len): - if string[i] != ' ': - return False - return True + return all(string[i] == ' ' for i in range(str_len)) @classmethod - def is_empty(cls, string) -> bool: + def is_empty(cls, string: str) -> bool: """ 校验字符串是否为''或None @@ -47,17 +44,17 @@ class StringUtil: return not cls.is_empty(string) @classmethod - def is_http(cls, link: str): + def is_http(cls, link: str) -> bool: """ 判断是否为http(s)://开头 :param link: 链接 :return: 是否为http(s)://开头 """ - return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) + return link.startswith((CommonConstant.HTTP, CommonConstant.HTTPS)) @classmethod - def contains_ignore_case(cls, search_str: str, compare_str: str): + def contains_ignore_case(cls, search_str: str, compare_str: str) -> bool: """ 查找指定字符串是否包含指定字符串同时忽略大小写 @@ -70,7 +67,7 @@ class StringUtil: return False @classmethod - def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + def contains_any_ignore_case(cls, search_str: str, compare_str_list: list[str]) -> bool: """ 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时忽略大小写 @@ -79,11 +76,11 @@ class StringUtil: :return: 查找结果 """ if search_str and compare_str_list: - return any([cls.contains_ignore_case(search_str, compare_str) for compare_str in compare_str_list]) + return any(cls.contains_ignore_case(search_str, compare_str) for compare_str in compare_str_list) return False @classmethod - def equals_ignore_case(cls, search_str: str, compare_str: str): + def equals_ignore_case(cls, search_str: str, compare_str: str) -> bool: """ 比较两个字符串是否相等同时忽略大小写 @@ -96,7 +93,7 @@ class StringUtil: return False @classmethod - def equals_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + def equals_any_ignore_case(cls, search_str: str, compare_str_list: list[str]) -> bool: """ 比较指定字符串是否与指定字符串列表中的任意一个字符串相等同时忽略大小写 @@ -105,11 +102,11 @@ class StringUtil: :return: 比较结果 """ if search_str and compare_str_list: - return any([cls.equals_ignore_case(search_str, compare_str) for compare_str in compare_str_list]) + return any(cls.equals_ignore_case(search_str, compare_str) for compare_str in compare_str_list) return False @classmethod - def startswith_case(cls, search_str: str, compare_str: str): + def startswith_case(cls, search_str: str, compare_str: str) -> bool: """ 查找指定字符串是否以指定字符串开头 @@ -122,7 +119,7 @@ class StringUtil: return False @classmethod - def startswith_any_case(cls, search_str: str, compare_str_list: List[str]): + def startswith_any_case(cls, search_str: str, compare_str_list: list[str]) -> bool: """ 查找指定字符串是否以指定字符串列表中的任意一个字符串开头 @@ -131,7 +128,7 @@ class StringUtil: :return: 查找结果 """ if search_str and compare_str_list: - return any([cls.startswith_case(search_str, compare_str) for compare_str in compare_str_list]) + return any(cls.startswith_case(search_str, compare_str) for compare_str in compare_str_list) return False @classmethod @@ -155,7 +152,7 @@ class StringUtil: return ''.join(result) @classmethod - def get_mapping_value_by_key_ignore_case(cls, mapping: Dict[str, str], key: str) -> str: + def get_mapping_value_by_key_ignore_case(cls, mapping: dict[str, str], key: str) -> str: """ 根据忽略大小写的键获取字典中的对应的值 @@ -166,5 +163,5 @@ class StringUtil: for k, v in mapping.items(): if key.lower() == k.lower(): return v - + return '' diff --git a/ruoyi-fastapi-backend/utils/template_util.py b/ruoyi-fastapi-backend/utils/template_util.py index afe5c41438cbb973b4c8d0904ceaee482fc9ee2a..9983fa530f19627954f9f522dd5a3b6e3ad039b2 100644 --- a/ruoyi-fastapi-backend/utils/template_util.py +++ b/ruoyi-fastapi-backend/utils/template_util.py @@ -1,12 +1,14 @@ import json import os from datetime import datetime +from typing import Any + from jinja2 import Environment, FileSystemLoader -from typing import Dict, List, Set -from config.constant import GenConstant + +from common.constant import GenConstant from config.env import DataBaseConfig from exceptions.exception import ServiceWarning -from module_generator.entity.vo.gen_vo import GenTableModel, GenTableColumnModel +from module_generator.entity.vo.gen_vo import GenTableColumnModel, GenTableModel from utils.common_util import CamelCaseUtil, SnakeCaseUtil from utils.string_util import StringUtil @@ -17,7 +19,7 @@ class TemplateInitializer: """ @classmethod - def init_jinja2(cls): + def init_jinja2(cls) -> Environment: """ 初始化 Jinja2 模板引擎 @@ -40,7 +42,7 @@ class TemplateInitializer: ) return env except Exception as e: - raise RuntimeError(f'初始化Jinja2模板引擎失败: {e}') + raise RuntimeError(f'初始化Jinja2模板引擎失败: {e}') from e class TemplateUtils: @@ -54,7 +56,7 @@ class TemplateUtils: DEFAULT_PARENT_MENU_ID = '3' @classmethod - def prepare_context(cls, gen_table: GenTableModel): + def prepare_context(cls, gen_table: GenTableModel) -> dict[str, Any]: """ 准备模板变量 @@ -106,7 +108,7 @@ class TemplateUtils: return context @classmethod - def set_menu_context(cls, context: Dict, gen_table: GenTableModel): + def set_menu_context(cls, context: dict, gen_table: GenTableModel) -> None: """ 设置菜单上下文 @@ -119,7 +121,7 @@ class TemplateUtils: context['parentMenuId'] = cls.get_parent_menu_id(params_obj) @classmethod - def set_tree_context(cls, context: Dict, gen_table: GenTableModel): + def set_tree_context(cls, context: dict, gen_table: GenTableModel) -> None: """ 设置树形结构上下文 @@ -135,7 +137,7 @@ class TemplateUtils: context['expandColumn'] = cls.get_expand_column(gen_table) @classmethod - def set_sub_context(cls, context: Dict, gen_table: GenTableModel): + def set_sub_context(cls, context: dict, gen_table: GenTableModel) -> None: """ 设置子表上下文 @@ -157,7 +159,7 @@ class TemplateUtils: context['subclassName'] = sub_class_name.lower() @classmethod - def get_template_list(cls, tpl_category: str, tpl_web_type: str): + def get_template_list(cls, tpl_category: str, tpl_web_type: str) -> list[str]: """ 获取模板列表 @@ -187,7 +189,7 @@ class TemplateUtils: return templates @classmethod - def get_file_name(cls, template: List[str], gen_table: GenTableModel): + def get_file_name(cls, template: list[str], gen_table: GenTableModel) -> str: """ 根据模板生成文件名 @@ -204,24 +206,24 @@ class TemplateUtils: if 'controller.py.jinja2' in template: return f'{python_path}/controller/{business_name}_controller.py' - elif 'dao.py.jinja2' in template: + if 'dao.py.jinja2' in template: return f'{python_path}/dao/{business_name}_dao.py' - elif 'do.py.jinja2' in template: + if 'do.py.jinja2' in template: return f'{python_path}/entity/do/{business_name}_do.py' - elif 'service.py.jinja2' in template: + if 'service.py.jinja2' in template: return f'{python_path}/service/{business_name}_service.py' - elif 'vo.py.jinja2' in template: + if 'vo.py.jinja2' in template: return f'{python_path}/entity/vo/{business_name}_vo.py' - elif 'sql.jinja2' in template: + if 'sql.jinja2' in template: return f'{cls.BACKEND_PROJECT_PATH}/sql/{business_name}_menu.sql' - elif 'api.js.jinja2' in template: + if 'api.js.jinja2' in template: return f'{vue_path}/api/{module_name}/{business_name}.js' - elif 'index.vue.jinja2' in template or 'index-tree.vue.jinja2' in template: + if 'index.vue.jinja2' in template or 'index-tree.vue.jinja2' in template: return f'{vue_path}/views/{module_name}/{business_name}/index.vue' return '' @classmethod - def get_package_prefix(cls, package_name: str): + def get_package_prefix(cls, package_name: str) -> str: """ 获取包前缀 @@ -231,7 +233,7 @@ class TemplateUtils: return package_name[: package_name.rfind('.')] @classmethod - def get_vo_import_list(cls, gen_table: GenTableModel): + def get_vo_import_list(cls, gen_table: GenTableModel) -> list[str]: """ 获取vo模板导入包列表 @@ -255,7 +257,7 @@ class TemplateUtils: return cls.merge_same_imports(list(import_list), 'from datetime import') @classmethod - def get_do_import_list(cls, gen_table: GenTableModel): + def get_do_import_list(cls, gen_table: GenTableModel) -> list[str]: """ 获取do模板导入包列表 @@ -295,7 +297,7 @@ class TemplateUtils: return column_type @classmethod - def merge_same_imports(cls, imports: List[str], import_start: str) -> List[str]: + def merge_same_imports(cls, imports: list[str], import_start: str) -> list[str]: """ 合并相同的导入语句 @@ -319,7 +321,7 @@ class TemplateUtils: return merged_imports @classmethod - def get_dicts(cls, gen_table: GenTableModel): + def get_dicts(cls, gen_table: GenTableModel) -> str: """ 获取字典列表 @@ -334,7 +336,7 @@ class TemplateUtils: return ', '.join(dicts) @classmethod - def add_dicts(cls, dicts: Set[str], columns: List[GenTableColumnModel]): + def add_dicts(cls, dicts: set[str], columns: list[GenTableColumnModel]) -> None: """ 添加字典列表 @@ -353,7 +355,7 @@ class TemplateUtils: dicts.add(f"'{column.dict_type}'") @classmethod - def get_permission_prefix(cls, module_name: str, business_name: str): + def get_permission_prefix(cls, module_name: str, business_name: str) -> str: """ 获取权限前缀 @@ -364,7 +366,7 @@ class TemplateUtils: return f'{module_name}:{business_name}' @classmethod - def get_parent_menu_id(cls, params_obj: Dict): + def get_parent_menu_id(cls, params_obj: dict) -> str: """ 获取上级菜单ID @@ -376,7 +378,7 @@ class TemplateUtils: return cls.DEFAULT_PARENT_MENU_ID @classmethod - def get_tree_code(cls, params_obj: Dict): + def get_tree_code(cls, params_obj: dict) -> str: """ 获取树编码 @@ -388,7 +390,7 @@ class TemplateUtils: return '' @classmethod - def get_tree_parent_code(cls, params_obj: Dict): + def get_tree_parent_code(cls, params_obj: dict) -> str: """ 获取树父编码 @@ -400,7 +402,7 @@ class TemplateUtils: return '' @classmethod - def get_tree_name(cls, params_obj: Dict): + def get_tree_name(cls, params_obj: dict) -> str: """ 获取树名称 @@ -412,7 +414,7 @@ class TemplateUtils: return '' @classmethod - def get_expand_column(cls, gen_table: GenTableModel): + def get_expand_column(cls, gen_table: GenTableModel) -> int: """ 获取展开列 @@ -442,7 +444,7 @@ class TemplateUtils: return parts[0] + ''.join(word.capitalize() for word in parts[1:]) @classmethod - def get_sqlalchemy_type(cls, column_type: str): + def get_sqlalchemy_type(cls, column_type: str) -> str: """ 获取SQLAlchemy类型 diff --git a/ruoyi-fastapi-backend/utils/time_format_util.py b/ruoyi-fastapi-backend/utils/time_format_util.py index bfb048156ff98f4fe19086cac687567e7d4d0b90..71e9f6af4404da881ad087d9f31555485a5518a8 100644 --- a/ruoyi-fastapi-backend/utils/time_format_util.py +++ b/ruoyi-fastapi-backend/utils/time_format_util.py @@ -1,10 +1,11 @@ from copy import deepcopy -from datetime import datetime +from datetime import date, datetime +from typing import Any, Union + from dateutil.parser import parse -from typing import Dict, List, Union -def object_format_datetime(obj): +def object_format_datetime(obj: Any) -> Any: """ :param obj: 输入一个对象 :return:对目标对象所有datetime类型的属性格式化 @@ -16,7 +17,7 @@ def object_format_datetime(obj): return obj -def list_format_datetime(lst): +def list_format_datetime(lst: list[Any]) -> list[Any]: """ :param lst: 输入一个嵌套对象的列表 :return: 对目标列表中所有对象的datetime类型的属性格式化 @@ -29,7 +30,7 @@ def list_format_datetime(lst): return lst -def format_datetime_dict_list(dicts): +def format_datetime_dict_list(dicts: list[dict]) -> list[dict]: """ 递归遍历嵌套字典,并将 datetime 值转换为字符串格式 @@ -61,27 +62,27 @@ class TimeFormatUtil: """ @classmethod - def format_time(cls, time_info: Union[str, datetime], format: str = '%Y-%m-%d %H:%M:%S'): + def format_time(cls, time_info: Union[str, datetime], fmt: str = '%Y-%m-%d %H:%M:%S') -> str: """ 格式化时间字符串或datetime对象为指定格式 :param time_info: 时间字符串或datetime对象 - :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :param fmt: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' :return: 格式化后的时间字符串 """ if isinstance(time_info, datetime): - format_date = time_info.strftime(format) + format_date = time_info.strftime(fmt) else: try: date = parse(time_info) - format_date = date.strftime(format) + format_date = date.strftime(fmt) except Exception: format_date = time_info return format_date @classmethod - def parse_date(cls, time_str: str): + def parse_date(cls, time_str: str) -> Union[date, str]: """ 解析时间字符串提取日期部分 @@ -95,44 +96,44 @@ class TimeFormatUtil: return time_str @classmethod - def format_time_dict(cls, time_dict: Dict, format: str = '%Y-%m-%d %H:%M:%S'): + def format_time_dict(cls, time_dict: dict, fmt: str = '%Y-%m-%d %H:%M:%S') -> dict: """ 格式化时间字典 :param time_dict: 时间字典 - :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :param fmt: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' :return: 格式化后的时间字典 """ copy_time_dict = deepcopy(time_dict) for k, v in copy_time_dict.items(): if isinstance(v, (str, datetime)): - copy_time_dict[k] = cls.format_time(v, format) + copy_time_dict[k] = cls.format_time(v, fmt) elif isinstance(v, dict): - copy_time_dict[k] = cls.format_time_dict(v, format) + copy_time_dict[k] = cls.format_time_dict(v, fmt) elif isinstance(v, list): - copy_time_dict[k] = cls.format_time_list(v, format) + copy_time_dict[k] = cls.format_time_list(v, fmt) else: copy_time_dict[k] = v return copy_time_dict @classmethod - def format_time_list(cls, time_list: List, format: str = '%Y-%m-%d %H:%M:%S'): + def format_time_list(cls, time_list: list, fmt: str = '%Y-%m-%d %H:%M:%S') -> list: """ 格式化时间列表 :param time_list: 时间列表 - :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :param fmt: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' :return: 格式化后的时间列表 """ format_time_list = [] for item in time_list: if isinstance(item, (str, datetime)): - format_item = cls.format_time(item, format) + format_item = cls.format_time(item, fmt) elif isinstance(item, dict): - format_item = cls.format_time_dict(item, format) + format_item = cls.format_time_dict(item, fmt) elif isinstance(item, list): - format_item = cls.format_time_list(item, format) + format_item = cls.format_time_list(item, fmt) else: format_item = item diff --git a/ruoyi-fastapi-backend/utils/upload_util.py b/ruoyi-fastapi-backend/utils/upload_util.py index 726789edaa965fcfe99412f3c5763011143ba1cd..c979a14caa2e1cf2a50bed4a3d50871138f07acd 100644 --- a/ruoyi-fastapi-backend/utils/upload_util.py +++ b/ruoyi-fastapi-backend/utils/upload_util.py @@ -1,7 +1,11 @@ import os import random +from collections.abc import AsyncGenerator from datetime import datetime + +import aiofiles from fastapi import UploadFile + from config.env import UploadConfig @@ -11,7 +15,7 @@ class UploadUtil: """ @classmethod - def generate_random_number(cls): + def generate_random_number(cls) -> str: """ 生成3位数字构成的字符串 @@ -22,7 +26,7 @@ class UploadUtil: return f'{random_number:03}' @classmethod - def check_file_exists(cls, filepath: str): + def check_file_exists(cls, filepath: str) -> bool: """ 检查文件是否存在 @@ -32,7 +36,7 @@ class UploadUtil: return os.path.exists(filepath) @classmethod - def check_file_extension(cls, file: UploadFile): + def check_file_extension(cls, file: UploadFile) -> bool: """ 检查文件后缀是否合法 @@ -40,12 +44,11 @@ class UploadUtil: :return: 校验结果 """ file_extension = file.filename.rsplit('.', 1)[-1] - if file_extension in UploadConfig.DEFAULT_ALLOWED_EXTENSION: - return True - return False + + return file_extension in UploadConfig.DEFAULT_ALLOWED_EXTENSION @classmethod - def check_file_timestamp(cls, filename: str): + def check_file_timestamp(cls, filename: str) -> bool: """ 校验文件时间戳是否合法 @@ -60,19 +63,17 @@ class UploadUtil: return False @classmethod - def check_file_machine(cls, filename: str): + def check_file_machine(cls, filename: str) -> bool: """ 校验文件机器码是否合法 :param filename: 文件名称 :return: 校验结果 """ - if filename.rsplit('.', 1)[0][-4] == UploadConfig.UPLOAD_MACHINE: - return True - return False + return filename.rsplit('.', 1)[0][-4] == UploadConfig.UPLOAD_MACHINE @classmethod - def check_file_random_code(cls, filename: str): + def check_file_random_code(cls, filename: str) -> bool: """ 校验文件随机码是否合法 @@ -80,23 +81,23 @@ class UploadUtil: :return: 校验结果 """ valid_code_list = [f'{i:03}' for i in range(1, 999)] - if filename.rsplit('.', 1)[0][-3:] in valid_code_list: - return True - return False + + return filename.rsplit('.', 1)[0][-3:] in valid_code_list @classmethod - def generate_file(cls, filepath: str): + async def generate_file(cls, filepath: str) -> AsyncGenerator[bytes, None]: """ 根据文件生成二进制数据 :param filepath: 文件路径 :yield: 二进制数据 """ - with open(filepath, 'rb') as response_file: - yield from response_file + async with aiofiles.open(filepath, 'rb') as response_file: + async for chunk in response_file: + yield chunk @classmethod - def delete_file(cls, filepath: str): + def delete_file(cls, filepath: str) -> None: """ 根据文件路径删除对应文件 diff --git a/ruoyi-fastapi-frontend/.env.docker b/ruoyi-fastapi-frontend/.env.docker new file mode 100644 index 0000000000000000000000000000000000000000..5b4b425b6ae745b5bd8451feb3332f9756d3175e --- /dev/null +++ b/ruoyi-fastapi-frontend/.env.docker @@ -0,0 +1,12 @@ +# 页面标题 +VUE_APP_TITLE = vfadmin管理系统 + +BABEL_ENV = production + +NODE_ENV = production + +# 测试环境配置 +ENV = 'docker' + +# vfadmin管理系统/测试环境 +VUE_APP_BASE_API = '/docker-api' diff --git a/ruoyi-fastapi-frontend/Dockerfile b/ruoyi-fastapi-frontend/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4235ee57245a0b9d5df2f3ddbe2a09b4e88baa80 --- /dev/null +++ b/ruoyi-fastapi-frontend/Dockerfile @@ -0,0 +1,28 @@ +# 构建阶段 +FROM node:18-slim AS builder +WORKDIR /app + +# 复制源代码 +COPY . . + +# 设置npm镜像源 +RUN npm config set registry https://registry.npmmirror.com + +# 安装依赖 +RUN npm install + +# 执行docker构建命令 +RUN npm run build:docker + +# 运行阶段 +FROM nginx:latest +WORKDIR /usr/share/nginx/html + +# 复制构建产物 +COPY --from=builder /app/dist . + +# 暴露端口 +EXPOSE 80 + +# 启动nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/bin/nginx.dockermy.conf b/ruoyi-fastapi-frontend/bin/nginx.dockermy.conf new file mode 100644 index 0000000000000000000000000000000000000000..c7855c90101f457764b49eab495ba4de2aaff3f7 --- /dev/null +++ b/ruoyi-fastapi-frontend/bin/nginx.dockermy.conf @@ -0,0 +1,24 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # API代理配置(MySQL版本) + location /docker-api/ { + proxy_pass http://ruoyi-backend-my:9099/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/bin/nginx.dockerpg.conf b/ruoyi-fastapi-frontend/bin/nginx.dockerpg.conf new file mode 100644 index 0000000000000000000000000000000000000000..35564d77fbd90a882fec34ba12f38f8414f0e964 --- /dev/null +++ b/ruoyi-fastapi-frontend/bin/nginx.dockerpg.conf @@ -0,0 +1,24 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # API代理配置(PostgreSQL版本) + location /docker-api/ { + proxy_pass http://ruoyi-backend-pg:9099/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json index 0bffa57492c2f4214ecec33b0c2ca507022c4033..0e6c2aa9aef46793a4a2eb5606822f6a86af6eb0 100644 --- a/ruoyi-fastapi-frontend/package.json +++ b/ruoyi-fastapi-frontend/package.json @@ -1,12 +1,13 @@ { "name": "vfadmin", - "version": "1.7.1", + "version": "1.8.0", "description": "vfadmin管理系统", "author": "insistence", "license": "MIT", "scripts": { "dev": "vue-cli-service serve", "build:prod": "vue-cli-service build", + "build:docker": "vue-cli-service build --mode docker", "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview" }, diff --git a/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss index ab4a1d2d6ba4f0d7266e2e8b6a0b1c319ea889a9..e1ba08258fd3781b63c97e67134d1d1c47bfbdab 100644 --- a/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss @@ -130,6 +130,16 @@ border-radius: 4px; } +/* horizontal el menu */ +.el-menu--horizontal .el-menu-item .svg-icon + span, +.el-menu--horizontal .el-submenu__title .svg-icon + span { + margin-left: 3px; +} + +.el-menu--horizontal .el-menu--popup { + min-width: 120px !important; +} + @media (max-width: 768px) { .pagination-container .el-pagination > .el-pagination__jump { display: none !important; diff --git a/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss b/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss index 355829331b84dd7433a616e96261402018018cc5..5950ba9172c8eaafca58b1a5984ae7dc330722ee 100644 --- a/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss @@ -61,7 +61,7 @@ } .svg-icon { - margin-right: 16px; + margin-right: 10px !important; } .el-menu { @@ -116,15 +116,17 @@ margin-left: 54px; } - .submenu-title-noDropdown { - padding: 0 !important; - position: relative; - - .el-tooltip { + .el-menu:not(.el-menu--horizontal) { + .submenu-title-noDropdown { padding: 0 !important; + position: relative; - .svg-icon { - margin-left: 20px; + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } } } } diff --git a/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue b/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue index 080595a4ec14290cc2cfa2d4b066453a20414fae..84f4831af7f244ef33c43c18ab0fb8b37585d009 100644 --- a/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue +++ b/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue @@ -94,7 +94,6 @@ export default { display: inline-block; font-size: 14px; line-height: 50px; - margin-left: 8px; .no-redirect { color: #97a8be; cursor: text; diff --git a/ruoyi-fastapi-frontend/src/components/DictTag/index.vue b/ruoyi-fastapi-frontend/src/components/DictTag/index.vue index 6b5b230f5c52393267f9d2b27fea218c87248eb4..84b54cd3c418fa609be1f8e4144035b254d46b1c 100644 --- a/ruoyi-fastapi-frontend/src/components/DictTag/index.vue +++ b/ruoyi-fastapi-frontend/src/components/DictTag/index.vue @@ -1,7 +1,7 @@