diff --git a/README.md b/README.md index 6d8252d8aab694561e3b7739bb684a0ac5f021d2..6dfa2e659feec20cf821844eeb47e768ec7dd1b1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@

logo

-

RuoYi-Vue-FastAPI v1.2.2

+

RuoYi-Vue-FastAPI v1.3.0

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

- + diff --git a/ruoyi-fastapi-backend/.env.dev b/ruoyi-fastapi-backend/.env.dev index aa4241c9eceace020063a57677aa00d1d58740ec..344c0274dd536f5b5cc046eaa5de5900844e946a 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.2.2' +APP_VERSION= '1.3.0' # 应用是否开启热重载 APP_RELOAD = true # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/.env.prod b/ruoyi-fastapi-backend/.env.prod index b41b5b8ce6b6727534f6f48fd3c57ffbfb253a8c..b2d2d579e440f21899f21c52ea996ceb6158d8bd 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.2.2' +APP_VERSION= '1.3.0' # 应用是否开启热重载 APP_RELOAD = false # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/app.py b/ruoyi-fastapi-backend/app.py index 7797fa934cb5641a857724ae64d0307221ac97f1..1ee7695527ae397af30eec9d905ac7410da05295 100644 --- a/ruoyi-fastapi-backend/app.py +++ b/ruoyi-fastapi-backend/app.py @@ -1,5 +1,5 @@ import uvicorn -from server import app, AppConfig +from server import app, AppConfig # noqa: F401 if __name__ == '__main__': @@ -8,5 +8,5 @@ if __name__ == '__main__': host=AppConfig.app_host, port=AppConfig.app_port, root_path=AppConfig.app_root_path, - reload=AppConfig.app_reload + reload=AppConfig.app_reload, ) diff --git a/ruoyi-fastapi-backend/config/constant.py b/ruoyi-fastapi-backend/config/constant.py new file mode 100644 index 0000000000000000000000000000000000000000..9129fe06ef5afecf0a7cd6f03f618af7d9b6e94e --- /dev/null +++ b/ruoyi-fastapi-backend/config/constant.py @@ -0,0 +1,117 @@ +class CommonConstant: + """ + 常用常量 + + WWW: www主域 + HTTP: http请求 + HTTPS: https请求 + LOOKUP_RMI: RMI远程方法调用 + LOOKUP_LDAP: LDAP远程方法调用 + LOOKUP_LDAPS: LDAPS远程方法调用 + YES: 是否为系统默认(是) + NO: 是否为系统默认(否) + DEPT_NORMAL: 部门正常状态 + DEPT_DISABLE: 部门停用状态 + UNIQUE: 校验是否唯一的返回标识(是) + NOT_UNIQUE: 校验是否唯一的返回标识(否) + """ + + WWW = 'www.' + HTTP = 'http://' + HTTPS = 'https://' + LOOKUP_RMI = 'rmi:' + LOOKUP_LDAP = 'ldap:' + LOOKUP_LDAPS = 'ldaps:' + YES = 'Y' + NO = 'N' + DEPT_NORMAL = '0' + DEPT_DISABLE = '1' + UNIQUE = True + NOT_UNIQUE = False + + +class HttpStatusConstant: + """ + 返回状态码 + + SUCCESS: 操作成功 + CREATED: 对象创建成功 + ACCEPTED: 请求已经被接受 + NO_CONTENT: 操作已经执行成功,但是没有返回数据 + MOVED_PERM: 资源已被移除 + SEE_OTHER: 重定向 + NOT_MODIFIED: 资源没有被修改 + BAD_REQUEST: 参数列表错误(缺少,格式不匹配) + UNAUTHORIZED: 未授权 + FORBIDDEN: 访问受限,授权过期 + NOT_FOUND: 资源,服务未找到 + BAD_METHOD: 不允许的http方法 + CONFLICT: 资源冲突,或者资源被锁 + UNSUPPORTED_TYPE: 不支持的数据,媒体类型 + ERROR: 系统内部错误 + NOT_IMPLEMENTED: 接口未实现 + WARN: 系统警告消息 + """ + + SUCCESS = 200 + CREATED = 201 + ACCEPTED = 202 + NO_CONTENT = 204 + MOVED_PERM = 301 + SEE_OTHER = 303 + NOT_MODIFIED = 304 + BAD_REQUEST = 400 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + BAD_METHOD = 405 + CONFLICT = 409 + UNSUPPORTED_TYPE = 415 + ERROR = 500 + NOT_IMPLEMENTED = 501 + WARN = 601 + + +class JobConstant: + """ + 定时任务常量 + + JOB_ERROR_LIST: 定时任务禁止调用模块列表 + JOB_WHITE_LIST: 定时任务允许调用模块列表 + """ + + JOB_ERROR_LIST = [ + 'app', + 'config', + 'exceptions', + 'middlewares', + 'module_admin', + 'server', + 'sub_applications', + 'utils', + ] + JOB_WHITE_LIST = ['module_task'] + + +class MenuConstant: + """ + 菜单常量 + + TYPE_DIR: 菜单类型(目录) + TYPE_MENU: 菜单类型(菜单) + TYPE_BUTTON: 菜单类型(按钮) + YES_FRAME: 是否菜单外链(是) + NO_FRAME: 是否菜单外链(否) + LAYOUT: Layout组件标识 + PARENT_VIEW: ParentView组件标识 + INNER_LINK: InnerLink组件标识 + """ + + TYPE_DIR = 'M' + TYPE_MENU = 'C' + TYPE_BUTTON = 'F' + YES_FRAME = 0 + NO_FRAME = 1 + LAYOUT = 'Layout' + PARENT_VIEW = 'ParentView' + INNER_LINK = 'InnerLink' diff --git a/ruoyi-fastapi-backend/config/database.py b/ruoyi-fastapi-backend/config/database.py index 512ae1be0dbf7b240c4a5206016e09f9cef0478a..14c5a80cb8131737f2083116d8fdfe2235cdf9df 100644 --- a/ruoyi-fastapi-backend/config/database.py +++ b/ruoyi-fastapi-backend/config/database.py @@ -5,8 +5,10 @@ from sqlalchemy.orm import DeclarativeBase from urllib.parse import quote_plus from config.env import DataBaseConfig -ASYNC_SQLALCHEMY_DATABASE_URL = f"mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@" \ - f"{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}" +ASYNC_SQLALCHEMY_DATABASE_URL = ( + f'mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' +) async_engine = create_async_engine( ASYNC_SQLALCHEMY_DATABASE_URL, @@ -14,7 +16,7 @@ async_engine = create_async_engine( max_overflow=DataBaseConfig.db_max_overflow, pool_size=DataBaseConfig.db_pool_size, pool_recycle=DataBaseConfig.db_pool_recycle, - pool_timeout=DataBaseConfig.db_pool_timeout + pool_timeout=DataBaseConfig.db_pool_timeout, ) AsyncSessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=async_engine) diff --git a/ruoyi-fastapi-backend/config/enums.py b/ruoyi-fastapi-backend/config/enums.py new file mode 100644 index 0000000000000000000000000000000000000000..0df623899fb1ff67e136f329bc8dd793348b3c6a --- /dev/null +++ b/ruoyi-fastapi-backend/config/enums.py @@ -0,0 +1,51 @@ +from enum import Enum + + +class BusinessType(Enum): + """ + 业务操作类型 + + OTHER: 其它 + INSERT: 新增 + UPDATE: 修改 + DELETE: 删除 + GRANT: 授权 + EXPORT: 导出 + IMPORT: 导入 + FORCE: 强退 + GENCODE: 生成代码 + CLEAN: 清空数据 + """ + + OTHER = 0 + INSERT = 1 + UPDATE = 2 + DELETE = 3 + GRANT = 4 + EXPORT = 5 + IMPORT = 6 + FORCE = 7 + GENCODE = 8 + CLEAN = 9 + + +class RedisInitKeyConfig(Enum): + """ + 系统内置Redis键名 + """ + + @property + def key(self): + return self.value.get('key') + + @property + def remark(self): + return self.value.get('remark') + + ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'} + SYS_DICT = {'key': 'sys_dict', 'remark': '数据字典'} + SYS_CONFIG = {'key': 'sys_config', 'remark': '配置信息'} + CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'} + ACCOUNT_LOCK = {'key': 'account_lock', 'remark': '用户锁定'} + PASSWORD_ERROR_COUNT = {'key': 'password_error_count', 'remark': '密码错误次数'} + SMS_CODE = {'key': 'sms_code', 'remark': '短信验证码'} diff --git a/ruoyi-fastapi-backend/config/env.py b/ruoyi-fastapi-backend/config/env.py index 71718f926d1e6eb17fc7d33aafe133a5291f0761..9b91b25bc3b2175f0c4fc6bc77106f783a1edf0b 100644 --- a/ruoyi-fastapi-backend/config/env.py +++ b/ruoyi-fastapi-backend/config/env.py @@ -1,15 +1,16 @@ +import argparse import os import sys -import argparse -from pydantic_settings import BaseSettings -from functools import lru_cache from dotenv import load_dotenv +from functools import lru_cache +from pydantic_settings import BaseSettings class AppSettings(BaseSettings): """ 应用配置 """ + app_env: str = 'dev' app_name: str = 'RuoYi-FasAPI' app_root_path: str = '/dev-api' @@ -25,6 +26,7 @@ class JwtSettings(BaseSettings): """ Jwt配置 """ + jwt_secret_key: str = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' jwt_algorithm: str = 'HS256' jwt_expire_minutes: int = 1440 @@ -35,6 +37,7 @@ class DataBaseSettings(BaseSettings): """ 数据库配置 """ + db_host: str = '127.0.0.1' db_port: int = 3306 db_username: str = 'root' @@ -51,6 +54,7 @@ class RedisSettings(BaseSettings): """ Redis配置 """ + redis_host: str = '127.0.0.1' redis_port: int = 6379 redis_username: str = '' @@ -62,20 +66,38 @@ class UploadSettings: """ 上传配置 """ + UPLOAD_PREFIX = '/profile' UPLOAD_PATH = 'vf_admin/upload_path' UPLOAD_MACHINE = 'A' DEFAULT_ALLOWED_EXTENSION = [ # 图片 - "bmp", "gif", "jpg", "jpeg", "png", + 'bmp', + 'gif', + 'jpg', + 'jpeg', + 'png', # word excel powerpoint - "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + 'doc', + 'docx', + 'xls', + 'xlsx', + 'ppt', + 'pptx', + 'html', + 'htm', + 'txt', # 压缩文件 - "rar", "zip", "gz", "bz2", + 'rar', + 'zip', + 'gz', + 'bz2', # 视频格式 - "mp4", "avi", "rmvb", + 'mp4', + 'avi', + 'rmvb', # pdf - "pdf" + 'pdf', ] DOWNLOAD_PATH = 'vf_admin/download_path' @@ -90,23 +112,11 @@ class CachePathConfig: """ 缓存目录配置 """ + PATH = os.path.join(os.path.abspath(os.getcwd()), 'caches') PATHSTR = 'caches' -class RedisInitKeyConfig: - """ - 系统内置Redis键名 - """ - ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'} - SYS_DICT = {'key': 'sys_dict', 'remark': '数据字典'} - SYS_CONFIG = {'key': 'sys_config', 'remark': '配置信息'} - CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'} - ACCOUNT_LOCK = {'key': 'account_lock', 'remark': '用户锁定'} - PASSWORD_ERROR_COUNT = {'key': 'password_error_count', 'remark': '密码错误次数'} - SMS_CODE = {'key': 'sms_code', 'remark': '短信验证码'} - - class GetConfig: """ 获取配置 diff --git a/ruoyi-fastapi-backend/config/get_db.py b/ruoyi-fastapi-backend/config/get_db.py index 9c12561fb5583c2d01a1a75afff6ea8f19271038..20986aeebe8dedcfa9327dd569ca535ad0ef3ee2 100644 --- a/ruoyi-fastapi-backend/config/get_db.py +++ b/ruoyi-fastapi-backend/config/get_db.py @@ -1,10 +1,11 @@ -from config.database import * +from config.database import async_engine, AsyncSessionLocal, Base from utils.log_util import logger async def get_db(): """ 每一个请求处理完毕后会关闭当前连接,不同的请求使用不同的连接 + :return: """ async with AsyncSessionLocal() as current_db: @@ -14,9 +15,10 @@ async def get_db(): async def init_create_table(): """ 应用启动时初始化数据库连接 + :return: """ - logger.info("初始化数据库连接...") + logger.info('初始化数据库连接...') async with async_engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) - logger.info("数据库连接成功") + logger.info('数据库连接成功') diff --git a/ruoyi-fastapi-backend/config/get_redis.py b/ruoyi-fastapi-backend/config/get_redis.py index aa78f4ce52e6b7c8563289af65fba09758ff6651..9d78cad0c935cf9fc91a6f3b67aecff132174178 100644 --- a/ruoyi-fastapi-backend/config/get_redis.py +++ b/ruoyi-fastapi-backend/config/get_redis.py @@ -1,9 +1,9 @@ from redis import asyncio as aioredis from redis.exceptions import AuthenticationError, TimeoutError, RedisError -from module_admin.service.dict_service import DictDataService -from module_admin.service.config_service import ConfigService -from config.env import RedisConfig from config.database import AsyncSessionLocal +from config.env import RedisConfig +from module_admin.service.config_service import ConfigService +from module_admin.service.dict_service import DictDataService from utils.log_util import logger @@ -16,46 +16,49 @@ class RedisUtil: async def create_redis_pool(cls) -> aioredis.Redis: """ 应用启动时初始化redis连接 + :return: Redis连接对象 """ - logger.info("开始连接redis...") + logger.info('开始连接redis...') redis = await aioredis.from_url( - url=f"redis://{RedisConfig.redis_host}", + url=f'redis://{RedisConfig.redis_host}', port=RedisConfig.redis_port, username=RedisConfig.redis_username, password=RedisConfig.redis_password, db=RedisConfig.redis_database, - encoding="utf-8", - decode_responses=True + encoding='utf-8', + decode_responses=True, ) try: connection = await redis.ping() if connection: - logger.info("redis连接成功") + logger.info('redis连接成功') else: - logger.error("redis连接失败") + logger.error('redis连接失败') except AuthenticationError as e: - logger.error(f"redis用户名或密码错误,详细错误信息:{e}") + logger.error(f'redis用户名或密码错误,详细错误信息:{e}') except TimeoutError as e: - logger.error(f"redis连接超时,详细错误信息:{e}") + logger.error(f'redis连接超时,详细错误信息:{e}') except RedisError as e: - logger.error(f"redis连接错误,详细错误信息:{e}") + logger.error(f'redis连接错误,详细错误信息:{e}') return redis @classmethod async def close_redis_pool(cls, app): """ 应用关闭时关闭redis连接 + :param app: fastapi对象 :return: """ await app.state.redis.close() - logger.info("关闭redis连接成功") + logger.info('关闭redis连接成功') @classmethod async def init_sys_dict(cls, redis): """ 应用启动时缓存字典表 + :param redis: redis对象 :return: """ @@ -66,6 +69,7 @@ class RedisUtil: async def init_sys_config(cls, redis): """ 应用启动时缓存参数配置表 + :param redis: redis对象 :return: """ diff --git a/ruoyi-fastapi-backend/config/get_scheduler.py b/ruoyi-fastapi-backend/config/get_scheduler.py index ae4f9320cca8059840ba5ddd8695aec551a071b6..5c5809cc2ef50fb62db830a88cb85e1aab599995 100644 --- a/ruoyi-fastapi-backend/config/get_scheduler.py +++ b/ruoyi-fastapi-backend/config/get_scheduler.py @@ -1,26 +1,28 @@ import json +from apscheduler.events import EVENT_ALL +from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.jobstores.redis import RedisJobStore -from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor +from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.triggers.cron import CronTrigger -from apscheduler.events import EVENT_ALL +from datetime import datetime, timedelta from sqlalchemy.engine import create_engine from sqlalchemy.orm import sessionmaker -from datetime import datetime, timedelta -from config.database import quote_plus, AsyncSessionLocal +from typing import Union +from config.database import AsyncSessionLocal, quote_plus from config.env import DataBaseConfig, RedisConfig -from module_admin.service.job_log_service import JobLogService, JobLogModel 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 +import module_task # noqa: F401 # 重写Cron定时 class MyCronTrigger(CronTrigger): @classmethod - def from_crontab(cls, expr, timezone=None): + def from_crontab(cls, expr: str, timezone=None): 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))) @@ -48,11 +50,20 @@ class MyCronTrigger(CronTrigger): else: day_of_week = None year = values[6] if len(values) == 7 else None - return cls(second=second, minute=minute, hour=hour, day=day, month=month, week=week, - day_of_week=day_of_week, year=year, timezone=timezone) + return cls( + second=second, + minute=minute, + hour=hour, + day=day, + month=month, + week=week, + day_of_week=day_of_week, + year=year, + timezone=timezone, + ) @classmethod - def __find_recent_workday(cls, day): + def __find_recent_workday(cls, day: int): now = datetime.now() date = datetime(now.year, now.month, day) if date.weekday() < 5: @@ -67,15 +78,17 @@ class MyCronTrigger(CronTrigger): diff += 1 -SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@" \ - f"{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}" +SQLALCHEMY_DATABASE_URL = ( + f'mysql+pymysql://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' +) engine = create_engine( SQLALCHEMY_DATABASE_URL, echo=DataBaseConfig.db_echo, max_overflow=DataBaseConfig.db_max_overflow, pool_size=DataBaseConfig.db_pool_size, pool_recycle=DataBaseConfig.db_pool_recycle, - pool_timeout=DataBaseConfig.db_pool_timeout + pool_timeout=DataBaseConfig.db_pool_timeout, ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) job_stores = { @@ -87,18 +100,12 @@ job_stores = { port=RedisConfig.redis_port, username=RedisConfig.redis_username, password=RedisConfig.redis_password, - db=RedisConfig.redis_database + db=RedisConfig.redis_database, ) - ) -} -executors = { - 'default': ThreadPoolExecutor(20), - 'processpool': ProcessPoolExecutor(5) -} -job_defaults = { - 'coalesce': False, - 'max_instance': 1 + ), } +executors = {'default': ThreadPoolExecutor(20), 'processpool': ProcessPoolExecutor(5)} +job_defaults = {'coalesce': False, 'max_instance': 1} scheduler = BackgroundScheduler() scheduler.configure(jobstores=job_stores, executors=executors, job_defaults=job_defaults) @@ -112,9 +119,10 @@ class SchedulerUtil: async def init_system_scheduler(cls): """ 应用启动时初始化定时任务 + :return: """ - logger.info("开始启动定时任务...") + logger.info('开始启动定时任务...') scheduler.start() async with AsyncSessionLocal() as session: job_list = await JobDao.get_job_list_for_scheduler(session) @@ -124,21 +132,23 @@ class SchedulerUtil: cls.remove_scheduler_job(job_id=str(item.job_id)) cls.add_scheduler_job(item) scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL) - logger.info("系统初始定时任务加载成功") + logger.info('系统初始定时任务加载成功') @classmethod async def close_system_scheduler(cls): """ 应用关闭时关闭定时任务 + :return: """ scheduler.shutdown() - logger.info("关闭定时任务成功") + logger.info('关闭定时任务成功') @classmethod - def get_scheduler_job(cls, job_id): + def get_scheduler_job(cls, job_id: Union[str, int]): """ 根据任务id获取任务对象 + :param job_id: 任务id :return: 任务对象 """ @@ -147,9 +157,10 @@ class SchedulerUtil: return query_job @classmethod - def add_scheduler_job(cls, job_info): + def add_scheduler_job(cls, job_info: JobModel): """ 根据输入的任务对象信息添加任务 + :param job_info: 任务对象信息 :return: """ @@ -164,13 +175,14 @@ class SchedulerUtil: coalesce=True if job_info.misfire_policy == '2' else False, max_instances=3 if job_info.concurrent == '0' else 1, jobstore=job_info.job_group, - executor=job_info.job_executor + executor=job_info.job_executor, ) @classmethod - def execute_scheduler_job_once(cls, job_info): + def execute_scheduler_job_once(cls, job_info: JobModel): """ 根据输入的任务对象执行一次任务 + :param job_info: 任务对象信息 :return: """ @@ -186,13 +198,14 @@ class SchedulerUtil: coalesce=True if job_info.misfire_policy == '2' else False, max_instances=3 if job_info.concurrent == '0' else 1, jobstore=job_info.job_group, - executor=job_info.job_executor + executor=job_info.job_executor, ) @classmethod - def remove_scheduler_job(cls, job_id): + def remove_scheduler_job(cls, job_id: Union[str, int]): """ 根据任务id移除任务 + :param job_id: 任务id :return: """ @@ -239,7 +252,7 @@ class SchedulerUtil: jobMessage=job_message, status=status, exceptionInfo=exception_info, - createTime=datetime.now() + createTime=datetime.now(), ) session = SessionLocal() JobLogService.add_job_log_services(session, job_log) diff --git a/ruoyi-fastapi-backend/exceptions/exception.py b/ruoyi-fastapi-backend/exceptions/exception.py index 28c39a673a1ba1d6010033a8924f8c1a447a6323..b86f50d01aa2282f3dedebdeceb3992930cf4c3c 100644 --- a/ruoyi-fastapi-backend/exceptions/exception.py +++ b/ruoyi-fastapi-backend/exceptions/exception.py @@ -28,6 +28,26 @@ class PermissionException(Exception): self.message = message +class ServiceException(Exception): + """ + 自定义服务异常ServiceException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ServiceWarning(Exception): + """ + 自定义服务警告ServiceWarning + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + class ModelValidatorException(Exception): """ 自定义模型校验异常ModelValidatorException diff --git a/ruoyi-fastapi-backend/exceptions/handle.py b/ruoyi-fastapi-backend/exceptions/handle.py index 040a11fb71d3a151d85061ec0276624b555d4ab0..dec516a7e36683cb55f5a84a9477cca4464ddbe3 100644 --- a/ruoyi-fastapi-backend/exceptions/handle.py +++ b/ruoyi-fastapi-backend/exceptions/handle.py @@ -1,32 +1,71 @@ from fastapi import FastAPI, Request from fastapi.exceptions import HTTPException -from exceptions.exception import AuthException, PermissionException, ModelValidatorException -from utils.response_util import ResponseUtil, JSONResponse, jsonable_encoder +from pydantic_validation_decorator import FieldValidationError +from exceptions.exception import ( + AuthException, + LoginException, + ModelValidatorException, + PermissionException, + ServiceException, + ServiceWarning, +) +from utils.log_util import logger +from utils.response_util import jsonable_encoder, JSONResponse, ResponseUtil def handle_exception(app: FastAPI): """ 全局异常处理 """ + # 自定义token检验异常 @app.exception_handler(AuthException) async def auth_exception_handler(request: Request, exc: AuthException): return ResponseUtil.unauthorized(data=exc.data, msg=exc.message) + # 自定义登录检验异常 + @app.exception_handler(LoginException) + async def login_exception_handler(request: Request, exc: LoginException): + return ResponseUtil.failure(data=exc.data, msg=exc.message) + + # 自定义模型检验异常 + @app.exception_handler(ModelValidatorException) + async def model_validator_exception_handler(request: Request, exc: ModelValidatorException): + 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): + logger.warning(exc.message) + return ResponseUtil.failure(msg=exc.message) + # 自定义权限检验异常 @app.exception_handler(PermissionException) async def permission_exception_handler(request: Request, exc: PermissionException): return ResponseUtil.forbidden(data=exc.data, msg=exc.message) - # 自定义模型检验异常 - @app.exception_handler(ModelValidatorException) - async def model_validator_exception_handler(request: Request, exc: ModelValidatorException): + # 自定义服务异常 + @app.exception_handler(ServiceException) + async def service_exception_handler(request: Request, exc: ServiceException): + 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): + 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): return JSONResponse( - content=jsonable_encoder({"code": exc.status_code, "msg": exc.detail}), - status_code=exc.status_code + 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): + logger.exception(exc) + return ResponseUtil.error(msg=str(exc)) diff --git a/ruoyi-fastapi-backend/middlewares/cors_middleware.py b/ruoyi-fastapi-backend/middlewares/cors_middleware.py index 4b78db9fc1f66cd449810a626cbbc3d4b88b49b7..754fc1ad427126abb8dffb2e9251d15c198c2658 100644 --- a/ruoyi-fastapi-backend/middlewares/cors_middleware.py +++ b/ruoyi-fastapi-backend/middlewares/cors_middleware.py @@ -5,8 +5,8 @@ from fastapi.middleware.cors import CORSMiddleware def add_cors_middleware(app: FastAPI): # 前端页面url origins = [ - "http://localhost:80", - "http://127.0.0.1:80", + 'http://localhost:80', + 'http://127.0.0.1:80', ] # 后台api允许跨域 @@ -14,6 +14,6 @@ def add_cors_middleware(app: FastAPI): CORSMiddleware, allow_origins=origins, allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], + allow_methods=['*'], + allow_headers=['*'], ) diff --git a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py index fa7c1b3c275cacff7cda42268e758cadc46ccb7a..4d0737e280b7bfee4bd7546458acac3fd5f2e7f1 100644 --- a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py +++ b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py @@ -1,28 +1,215 @@ -from functools import wraps, lru_cache -from fastapi import Request -from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse import inspect -import os import json +import os +import requests import time +import warnings from datetime import datetime -import requests +from fastapi import Request +from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse +from functools import lru_cache, wraps +from typing import Literal, Optional, Union from user_agents import parse -from typing import Optional +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 module_admin.service.log_service import OperationLogService, LoginLogService -from module_admin.entity.vo.log_vo import OperLogModel, LogininforModel +from config.enums import BusinessType from config.env import AppConfig -def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'operation'): +class Log: """ 日志装饰器 - :param log_type: 日志类型(login表示登录日志,为空表示为操作日志) + """ + + def __init__( + self, + title: str, + business_type: BusinessType, + log_type: Optional[Literal['login', 'operation']] = 'operation', + ): + """ + 日志装饰器 + + :param title: 当前日志装饰器装饰的模块标题 + :param business_type: 业务类型(OTHER其它 INSERT新增 UPDATE修改 DELETE删除 GRANT授权 EXPORT导出 IMPORT导入 FORCE强退 GENCODE生成代码 CLEAN清空数据) + :param log_type: 日志类型(login表示登录日志,operation表示为操作日志) + :return: + """ + self.title = title + self.business_type = business_type.value + self.log_type = log_type + + def __call__(self, func): + @wraps(func) + async def wrapper(*args, **kwargs): + start_time = time.time() + # 获取被装饰函数的文件路径 + file_path = inspect.getfile(func) + # 获取项目根路径 + project_root = os.getcwd() + # 处理文件路径,去除项目根路径部分 + relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.') + # 获取当前被装饰函数所在路径 + func_path = f'{relative_path}{func.__name__}()' + # 获取上下文信息 + request: Request = kwargs.get('request') + token = request.headers.get('Authorization') + query_db = kwargs.get('query_db') + request_method = request.method + operator_type = 0 + user_agent = request.headers.get('User-Agent') + if 'Windows' in user_agent or 'Macintosh' in user_agent or 'Linux' in user_agent: + operator_type = 1 + if 'Mobile' in user_agent or 'Android' in user_agent or 'iPhone' in user_agent: + operator_type = 2 + # 获取请求的url + oper_url = request.url.path + # 获取请求的ip及ip归属区域 + oper_ip = request.headers.get('X-Forwarded-For') + oper_location = '内网IP' + if AppConfig.app_ip_location_query: + oper_location = get_ip_location(oper_ip) + # 根据不同的请求类型使用不同的方法获取请求参数 + content_type = request.headers.get('Content-Type') + if content_type and ( + 'multipart/form-data' in content_type or 'application/x-www-form-urlencoded' in content_type + ): + payload = await request.form() + oper_param = '\n'.join([f'{key}: {value}' for key, value in payload.items()]) + else: + payload = await request.body() + # 通过 request.path_params 直接访问路径参数 + path_params = request.path_params + oper_param = {} + if payload: + oper_param.update(json.loads(str(payload, 'utf-8'))) + if path_params: + oper_param.update(path_params) + oper_param = json.dumps(oper_param, ensure_ascii=False) + # 日志表请求参数字段长度最大为2000,因此在此处判断长度 + if len(oper_param) > 2000: + oper_param = '请求参数过长' + + # 获取操作时间 + oper_time = datetime.now() + # 此处在登录之前向原始函数传递一些登录信息,用于监测在线用户的相关信息 + login_log = {} + if self.log_type == 'login': + user_agent_info = parse(user_agent) + browser = f'{user_agent_info.browser.family}' + system_os = f'{user_agent_info.os.family}' + if user_agent_info.browser.version != (): + browser += f' {user_agent_info.browser.version[0]}' + if user_agent_info.os.version != (): + system_os += f' {user_agent_info.os.version[0]}' + login_log = dict( + ipaddr=oper_ip, + loginLocation=oper_location, + browser=browser, + os=system_os, + loginTime=oper_time.strftime('%Y-%m-%d %H:%M:%S'), + ) + kwargs['form_data'].login_info = login_log + # 调用原始函数 + result = await func(*args, **kwargs) + # 获取请求耗时 + cost_time = float(time.time() - start_time) * 100 + # 判断请求是否来自api文档 + request_from_swagger = ( + request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + ) + request_from_redoc = ( + request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + ) + # 根据响应结果的类型使用不同的方法获取响应结果参数 + if ( + isinstance(result, JSONResponse) + or isinstance(result, ORJSONResponse) + or isinstance(result, UJSONResponse) + ): + result_dict = json.loads(str(result.body, 'utf-8')) + else: + if request_from_swagger or request_from_redoc: + result_dict = {} + else: + if result.status_code == 200: + result_dict = {'code': result.status_code, 'message': '获取成功'} + else: + result_dict = {'code': result.status_code, 'message': '获取失败'} + json_result = json.dumps(result_dict, ensure_ascii=False) + # 根据响应结果获取响应状态及异常信息 + status = 1 + error_msg = '' + if result_dict.get('code') == 200: + status = 0 + else: + error_msg = result_dict.get('msg') + # 根据日志类型向对应的日志表插入数据 + if self.log_type == 'login': + # 登录请求来自于api文档时不记录登录日志,其余情况则记录 + if request_from_swagger or request_from_redoc: + pass + else: + user = kwargs.get('form_data') + user_name = user.username + login_log['loginTime'] = oper_time + login_log['userName'] = user_name + login_log['status'] = str(status) + login_log['msg'] = result_dict.get('msg') + + await LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log)) + else: + current_user = await LoginService.get_current_user(request, token, query_db) + oper_name = current_user.user.user_name + dept_name = current_user.user.dept.dept_name if current_user.user.dept else None + operation_log = OperLogModel( + title=self.title, + businessType=self.business_type, + method=func_path, + requestMethod=request_method, + operatorType=operator_type, + operName=oper_name, + deptName=dept_name, + operUrl=oper_url, + operIp=oper_ip, + operLocation=oper_location, + operParam=oper_param, + jsonResult=json_result, + status=status, + errorMsg=error_msg, + operTime=oper_time, + costTime=int(cost_time), + ) + await OperationLogService.add_operation_log_services(query_db, operation_log) + + return result + + return wrapper + + +def log_decorator( + title: str, + business_type: Union[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], BusinessType], + log_type: Optional[Literal['login', 'operation']] = 'operation', +): + """ + 日志装饰器 + :param title: 当前日志装饰器装饰的模块标题 :param business_type: 业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据) + :param log_type: 日志类型(login表示登录日志,operation表示为操作日志) :return: """ + warnings.simplefilter('always', category=DeprecationWarning) + if isinstance(business_type, BusinessType): + business_type = business_type.value + warnings.warn( + '未来版本将会移除@log_decorator装饰器,请使用@Log装饰器', + category=DeprecationWarning, + stacklevel=2, + ) + def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): @@ -42,22 +229,24 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope 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: + 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: + if 'Mobile' in user_agent or 'Android' in user_agent or 'iPhone' in user_agent: operator_type = 2 # 获取请求的url oper_url = request.url.path # 获取请求的ip及ip归属区域 - oper_ip = request.headers.get("X-Forwarded-For") + oper_ip = request.headers.get('X-Forwarded-For') oper_location = '内网IP' if AppConfig.app_ip_location_query: oper_location = get_ip_location(oper_ip) # 根据不同的请求类型使用不同的方法获取请求参数 - content_type = request.headers.get("Content-Type") - if content_type and ("multipart/form-data" in content_type or 'application/x-www-form-urlencoded' in content_type): + 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()]) + oper_param = '\n'.join([f'{key}: {value}' for key, value in payload.items()]) else: payload = await request.body() # 通过 request.path_params 直接访问路径参数 @@ -89,7 +278,7 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope loginLocation=oper_location, browser=browser, os=system_os, - loginTime=oper_time.strftime('%Y-%m-%d %H:%M:%S') + loginTime=oper_time.strftime('%Y-%m-%d %H:%M:%S'), ) kwargs['form_data'].login_info = login_log # 调用原始函数 @@ -97,10 +286,18 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope # 获取请求耗时 cost_time = float(time.time() - start_time) * 100 # 判断请求是否来自api文档 - request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False - request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + request_from_swagger = ( + request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + ) + request_from_redoc = ( + request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + ) # 根据响应结果的类型使用不同的方法获取响应结果参数 - if isinstance(result, JSONResponse) or isinstance(result, ORJSONResponse) or isinstance(result, UJSONResponse): + 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: @@ -152,7 +349,7 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope status=status, errorMsg=error_msg, operTime=oper_time, - costTime=int(cost_time) + costTime=int(cost_time), ) await OperationLogService.add_operation_log_services(query_db, operation_log) @@ -167,6 +364,7 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope def get_ip_location(oper_ip: str): """ 查询ip归属区域 + :param oper_ip: 需要查询的ip :return: ip归属区域 """ diff --git a/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py index 306901568a12df48ef422799e0921e8d58718cef..0799f66ad9e39c13646ef0fae5ffd0113a8da4a9 100644 --- a/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py +++ b/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py @@ -1,9 +1,8 @@ import inspect -from typing import Type - -from fastapi import Query, Form +from fastapi import Form, Query from pydantic import BaseModel from pydantic.fields import FieldInfo +from typing import Type def as_query(cls: Type[BaseModel]): @@ -20,8 +19,8 @@ def as_query(cls: Type[BaseModel]): inspect.Parameter( model_field.alias, inspect.Parameter.POSITIONAL_ONLY, - default=Query(model_field.default), - annotation=model_field.annotation + default=Query(default=model_field.default, description=model_field.description), + annotation=model_field.annotation, ) ) else: @@ -29,8 +28,8 @@ def as_query(cls: Type[BaseModel]): inspect.Parameter( model_field.alias, inspect.Parameter.POSITIONAL_ONLY, - default=Query(...), - annotation=model_field.annotation + default=Query(..., description=model_field.description), + annotation=model_field.annotation, ) ) @@ -58,8 +57,8 @@ def as_form(cls: Type[BaseModel]): inspect.Parameter( model_field.alias, inspect.Parameter.POSITIONAL_ONLY, - default=Form(model_field.default), - annotation=model_field.annotation + default=Form(default=model_field.default, description=model_field.description), + annotation=model_field.annotation, ) ) else: @@ -67,8 +66,8 @@ def as_form(cls: Type[BaseModel]): inspect.Parameter( model_field.alias, inspect.Parameter.POSITIONAL_ONLY, - default=Form(...), - annotation=model_field.annotation + default=Form(..., description=model_field.description), + annotation=model_field.annotation, ) ) diff --git a/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py b/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py index 598170e2e985ae7fa0a092071d4d4d702f7a9a0f..5a7afbb6d78f3695b3a1dfac9517d1f29ded957e 100644 --- a/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py +++ b/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py @@ -1,14 +1,35 @@ 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 typing import Optional class GetDataScope: """ 获取当前用户数据权限对应的查询sql语句 """ - def __init__(self, query_alias: Optional[str] = '', db_alias: Optional[str] = 'db', user_alias: Optional[str] = 'user_id', dept_alias: Optional[str] = 'dept_id'): + + DATA_SCOPE_ALL = '1' + DATA_SCOPE_CUSTOM = '2' + DATA_SCOPE_DEPT = '3' + DATA_SCOPE_DEPT_AND_CHILD = '4' + DATA_SCOPE_SELF = '5' + + def __init__( + self, + query_alias: Optional[str] = '', + db_alias: Optional[str] = 'db', + user_alias: Optional[str] = 'user_id', + dept_alias: Optional[str] = 'dept_id', + ): + """ + 获取当前用户数据权限对应的查询sql语句 + + :param query_alias: 所要查询表对应的sqlalchemy模型名称,默认为'' + :param db_alias: orm对象别名,默认为'db' + :param user_alias: 用户id字段别名,默认为'user_id' + :param dept_alias: 部门id字段别名,默认为'dept_id' + """ self.query_alias = query_alias self.db_alias = db_alias self.user_alias = user_alias @@ -17,21 +38,38 @@ class GetDataScope: def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): user_id = current_user.user.user_id dept_id = current_user.user.dept_id - role_datascope_list = [dict(role_id=item.role_id, data_scope=int(item.data_scope)) for item in current_user.user.role] - max_data_scope_dict = min(role_datascope_list, key=lambda x: x['data_scope']) - max_role_id = max_data_scope_dict['role_id'] - max_data_scope = max_data_scope_dict['data_scope'] - if self.query_alias == '' or max_data_scope == 1 or user_id == 1: - param_sql = '1 == 1' - elif max_data_scope == 2: - param_sql = f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id == {max_role_id})) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 1" - elif max_data_scope == 3: - param_sql = f"{self.query_alias}.{self.dept_alias} == {dept_id} if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 1" - elif max_data_scope == 4: - param_sql = f"{self.query_alias}.{self.dept_alias}.in_(select(SysDept.dept_id).where(or_(SysDept.dept_id == {dept_id}, func.find_in_set({dept_id}, SysDept.ancestors)))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 1" - elif max_data_scope == 5: - param_sql = f"{self.query_alias}.{self.user_alias} == {user_id} if hasattr({self.query_alias}, '{self.user_alias}') else 1 == 1" - else: - param_sql = '1 == 0' + custom_data_scope_role_id_list = [ + item.role_id for item in current_user.user.role if item.data_scope == self.DATA_SCOPE_CUSTOM + ] + param_sql_list = [] + for role in current_user.user.role: + if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL: + param_sql_list = ['1 == 1'] + break + elif role.data_scope == self.DATA_SCOPE_CUSTOM: + if len(custom_data_scope_role_id_list) > 1: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_({custom_data_scope_role_id_list}))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + else: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id == {role.role_id})) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_DEPT: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias} == {dept_id} if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysDept.dept_id).where(or_(SysDept.dept_id == {dept_id}, func.find_in_set({dept_id}, SysDept.ancestors)))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_SELF: + param_sql_list.append( + f"{self.query_alias}.{self.user_alias} == {user_id} if hasattr({self.query_alias}, '{self.user_alias}') else 1 == 0" + ) + else: + param_sql_list.append('1 == 0') + param_sql_list = list(dict.fromkeys(param_sql_list)) + param_sql = f"or_({', '.join(param_sql_list)})" return param_sql diff --git a/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py b/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py index e688ad82ae289cc01def20f368abe9a24df2d691..8f8349dee84dfee54fcb5e4bab471df2ce5f5ffb 100644 --- a/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py +++ b/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py @@ -1,17 +1,22 @@ from fastapi import Depends -from typing import Union, List +from typing import List, Union +from exceptions.exception import PermissionException from module_admin.entity.vo.user_vo import CurrentUserModel from module_admin.service.login_service import LoginService -from exceptions.exception import PermissionException class CheckUserInterfaceAuth: """ 校验当前用户是否具有相应的接口权限 - :param perm: 权限标识 - :param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过 """ + def __init__(self, perm: Union[str, List], is_strict: bool = False): + """ + 校验当前用户是否具有相应的接口权限 + + :param perm: 权限标识 + :param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过 + """ self.perm = perm self.is_strict = is_strict @@ -29,16 +34,21 @@ class CheckUserInterfaceAuth: else: if any([perm_str in user_auth_list for perm_str in self.perm]): return True - raise PermissionException(data="", message="该用户无此接口权限") + raise PermissionException(data='', message='该用户无此接口权限') class CheckRoleInterfaceAuth: """ 根据角色校验当前用户是否具有相应的接口权限 - :param role_key: 角色标识 - :param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过 """ + def __init__(self, role_key: Union[str, List], is_strict: bool = False): + """ + 根据角色校验当前用户是否具有相应的接口权限 + + :param role_key: 角色标识 + :param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过 + """ self.role_key = role_key self.is_strict = is_strict @@ -55,5 +65,4 @@ class CheckRoleInterfaceAuth: else: if any([role_key_str in user_role_key_list for role_key_str in self.role_key]): return True - raise PermissionException(data="", message="该用户无此接口权限") - + raise PermissionException(data='', message='该用户无此接口权限') diff --git a/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py b/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py index b33a2873918fb1878731dfda54366c891b147c0e..9e72713e1f41b938919eb3e66f1e0ecc39302a74 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py @@ -1,94 +1,89 @@ -from fastapi import APIRouter -from fastapi import Depends -from module_admin.service.login_service import LoginService -from module_admin.service.cache_service import * -from utils.response_util import * -from utils.log_util import * +from fastapi import APIRouter, Depends, Request +from typing import List from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel +from module_admin.service.cache_service import CacheService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil cacheController = APIRouter(prefix='/monitor/cache', dependencies=[Depends(LoginService.get_current_user)]) -@cacheController.get("", response_model=CacheMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) +@cacheController.get( + '', response_model=CacheMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) async def get_monitor_cache_info(request: Request): - try: - # 获取全量数据 - cache_info_query_result = await CacheService.get_cache_monitor_statistical_info_services(request) - logger.info('获取成功') - return ResponseUtil.success(data=cache_info_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + # 获取全量数据 + cache_info_query_result = await CacheService.get_cache_monitor_statistical_info_services(request) + logger.info('获取成功') + + return ResponseUtil.success(data=cache_info_query_result) -@cacheController.get("/getNames", response_model=List[CacheInfoModel], dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) +@cacheController.get( + '/getNames', + response_model=List[CacheInfoModel], + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) async def get_monitor_cache_name(request: Request): - try: - # 获取全量数据 - cache_name_list_result = await CacheService.get_cache_monitor_cache_name_services() - logger.info('获取成功') - return ResponseUtil.success(data=cache_name_list_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + # 获取全量数据 + cache_name_list_result = await CacheService.get_cache_monitor_cache_name_services() + logger.info('获取成功') + return ResponseUtil.success(data=cache_name_list_result) -@cacheController.get("/getKeys/{cache_name}", response_model=List[str], dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) + +@cacheController.get( + '/getKeys/{cache_name}', + response_model=List[str], + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) async def get_monitor_cache_key(request: Request, cache_name: str): - try: - # 获取全量数据 - cache_key_list_result = await CacheService.get_cache_monitor_cache_key_services(request, cache_name) - logger.info('获取成功') - return ResponseUtil.success(data=cache_key_list_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + # 获取全量数据 + cache_key_list_result = await CacheService.get_cache_monitor_cache_key_services(request, cache_name) + logger.info('获取成功') + + return ResponseUtil.success(data=cache_key_list_result) -@cacheController.get("/getValue/{cache_name}/{cache_key}", response_model=CacheInfoModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) +@cacheController.get( + '/getValue/{cache_name}/{cache_key}', + response_model=CacheInfoModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) async def get_monitor_cache_value(request: Request, cache_name: str, cache_key: str): - try: - # 获取全量数据 - cache_value_list_result = await CacheService.get_cache_monitor_cache_value_services(request, cache_name, cache_key) - logger.info('获取成功') - return ResponseUtil.success(data=cache_value_list_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + # 获取全量数据 + cache_value_list_result = await CacheService.get_cache_monitor_cache_value_services(request, cache_name, cache_key) + logger.info('获取成功') + return ResponseUtil.success(data=cache_value_list_result) -@cacheController.delete("/clearCacheName/{cache_name}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) + +@cacheController.delete( + '/clearCacheName/{cache_name}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) async def clear_monitor_cache_name(request: Request, cache_name: str): - try: - clear_cache_name_result = await CacheService.clear_cache_monitor_cache_name_services(request, cache_name) - if clear_cache_name_result.is_success: - logger.info(clear_cache_name_result.message) - return ResponseUtil.success(msg=clear_cache_name_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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'))]) +@cacheController.delete( + '/clearCacheKey/{cache_key}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) async def clear_monitor_cache_key(request: Request, cache_key: str): - try: - clear_cache_key_result = await CacheService.clear_cache_monitor_cache_key_services(request, cache_key) - if clear_cache_key_result.is_success: - logger.info(clear_cache_key_result.message) - return ResponseUtil.success(msg=clear_cache_key_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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'))]) + +@cacheController.delete('/clearCacheAll', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) async def clear_monitor_cache_all(request: Request): - try: - clear_cache_all_result = await CacheService.clear_cache_monitor_all_services(request) - if clear_cache_all_result.is_success: - logger.info(clear_cache_all_result.message) - return ResponseUtil.success(msg=clear_cache_all_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + clear_cache_all_result = await CacheService.clear_cache_monitor_all_services(request) + logger.info(clear_cache_all_result.message) + + return ResponseUtil.success(msg=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 9d245ccc195ed7863555d047a6539badfec8b72f..83d35b817e1f66eb8e28d518e69c30251d867eab 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py @@ -1,31 +1,40 @@ import uuid +from datetime import timedelta from fastapi import APIRouter, Request -from config.env import RedisInitKeyConfig -from module_admin.service.captcha_service import * +from config.enums import RedisInitKeyConfig from module_admin.entity.vo.login_vo import CaptchaCode -from utils.response_util import * -from utils.log_util import * -from datetime import timedelta +from module_admin.service.captcha_service import CaptchaService +from utils.response_util import ResponseUtil +from utils.log_util import logger captchaController = APIRouter() -@captchaController.get("/captchaImage") +@captchaController.get('/captchaImage') async def get_captcha_image(request: Request): - try: - captcha_enabled = True if await request.app.state.redis.get(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.account.captchaEnabled") == 'true' else False - register_enabled = True if await request.app.state.redis.get( - f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.account.registerUser") == 'true' else False - session_id = str(uuid.uuid4()) - captcha_result = await CaptchaService.create_captcha_image_service() - image = captcha_result[0] - computed_result = captcha_result[1] - await request.app.state.redis.set(f"{RedisInitKeyConfig.CAPTCHA_CODES.get('key')}:{session_id}", computed_result, ex=timedelta(minutes=2)) - logger.info(f'编号为{session_id}的会话获取图片验证码成功') - return ResponseUtil.success( - model_content=CaptchaCode(captchaEnabled=captcha_enabled, registerEnabled=register_enabled, img=image, uuid=session_id) + captcha_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + == 'true' + else False + ) + register_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true' + else False + ) + session_id = str(uuid.uuid4()) + captcha_result = await CaptchaService.create_captcha_image_service() + image = captcha_result[0] + computed_result = captcha_result[1] + await request.app.state.redis.set( + f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{session_id}', computed_result, ex=timedelta(minutes=2) + ) + logger.info(f'编号为{session_id}的会话获取图片验证码成功') + + return ResponseUtil.success( + model_content=CaptchaCode( + captchaEnabled=captcha_enabled, registerEnabled=register_enabled, img=image, uuid=session_id ) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + ) diff --git a/ruoyi-fastapi-backend/module_admin/controller/common_controller.py b/ruoyi-fastapi-backend/module_admin/controller/common_controller.py index 5744f7075ae78932ee2d8bfe1552028291e7acd5..d2fd621679886d812384cf4f658e1b9320dc9005 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/common_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/common_controller.py @@ -1,53 +1,36 @@ -from fastapi import APIRouter -from fastapi import Depends, File, Query +from fastapi import APIRouter, BackgroundTasks, Depends, File, Query, Request, UploadFile +from module_admin.service.common_service import CommonService from module_admin.service.login_service import LoginService -from module_admin.service.common_service import * -from utils.response_util import * -from utils.log_util import * +from utils.log_util import logger +from utils.response_util import ResponseUtil commonController = APIRouter(prefix='/common', dependencies=[Depends(LoginService.get_current_user)]) -@commonController.post("/upload") +@commonController.post('/upload') async def common_upload(request: Request, file: UploadFile = File(...)): - try: - upload_result = await CommonService.upload_service(request, file) - if upload_result.is_success: - logger.info('上传成功') - return ResponseUtil.success(model_content=upload_result.result) - else: - logger.warning('上传失败') - return ResponseUtil.failure(msg=upload_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@commonController.get("/download") -async def common_download(request: Request, background_tasks: BackgroundTasks, file_name: str = Query(alias='fileName'), delete: bool = Query()): - try: - download_result = await CommonService.download_services(background_tasks, file_name, delete) - if download_result.is_success: - logger.info(download_result.message) - return ResponseUtil.streaming(data=download_result.result) - else: - logger.warning(download_result.message) - return ResponseUtil.failure(msg=download_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@commonController.get("/download/resource") -async def common_download(request: Request, resource: str = Query()): - try: - download_resource_result = await CommonService.download_resource_services(resource) - if download_resource_result.is_success: - logger.info(download_resource_result.message) - return ResponseUtil.streaming(data=download_resource_result.result) - else: - logger.warning(download_resource_result.message) - return ResponseUtil.failure(msg=download_resource_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + upload_result = await CommonService.upload_service(request, file) + logger.info('上传成功') + + return ResponseUtil.success(model_content=upload_result.result) + + +@commonController.get('/download') +async def common_download( + request: Request, + background_tasks: BackgroundTasks, + file_name: str = Query(alias='fileName'), + delete: bool = Query(), +): + 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()): + download_resource_result = await CommonService.download_resource_services(resource) + logger.info(download_resource_result.message) + + return ResponseUtil.streaming(data=download_resource_result.result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py index bc442d05a6e0186cc2e0740b05b700247353163e..d594da85df0d374227623f9acefff4697d38a67c 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py @@ -1,134 +1,123 @@ -from fastapi import APIRouter -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import LoginService, CurrentUserModel -from module_admin.service.config_service import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import * -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +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)]) -@configController.get("/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - config_page_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=config_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@configController.post("", dependencies=[Depends(CheckUserInterfaceAuth('system:config:add'))]) -@log_decorator(title='参数管理', business_type=1) -async def add_system_config(request: Request, add_config: ConfigModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_config.create_by = current_user.user.user_name - add_config.create_time = datetime.now() - add_config.update_by = current_user.user.user_name - add_config.update_time = datetime.now() - add_config_result = await ConfigService.add_config_services(request, query_db, add_config) - if add_config_result.is_success: - logger.info(add_config_result.message) - return ResponseUtil.success(msg=add_config_result.message) - else: - logger.warning(add_config_result.message) - return ResponseUtil.failure(msg=add_config_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@configController.put("", dependencies=[Depends(CheckUserInterfaceAuth('system:config:edit'))]) -@log_decorator(title='参数管理', business_type=2) -async def edit_system_config(request: Request, edit_config: ConfigModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - 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) - if edit_config_result.is_success: - logger.info(edit_config_result.message) - return ResponseUtil.success(msg=edit_config_result.message) - else: - logger.warning(edit_config_result.message) - return ResponseUtil.failure(msg=edit_config_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@configController.delete("/refreshCache", dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) -@log_decorator(title='参数管理', business_type=2) +@configController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=config_page_query_result) + + +@configController.post('', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + add_config.create_by = current_user.user.user_name + add_config.create_time = datetime.now() + add_config.update_by = current_user.user.user_name + add_config.update_time = datetime.now() + add_config_result = await ConfigService.add_config_services(request, query_db, add_config) + logger.info(add_config_result.message) + + return ResponseUtil.success(msg=add_config_result.message) + + +@configController.put('', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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) + logger.info(edit_config_result.message) + + return ResponseUtil.success(msg=edit_config_result.message) + + +@configController.delete('/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) +@Log(title='参数管理', business_type=BusinessType.UPDATE) async def refresh_system_config(request: Request, query_db: AsyncSession = Depends(get_db)): - try: - refresh_config_result = await ConfigService.refresh_sys_config_services(request, query_db) - if refresh_config_result.is_success: - logger.info(refresh_config_result.message) - return ResponseUtil.success(msg=refresh_config_result.message) - else: - logger.warning(refresh_config_result.message) - return ResponseUtil.failure(msg=refresh_config_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@configController.delete("/{config_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) -@log_decorator(title='参数管理', business_type=3) + 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'))]) +@Log(title='参数管理', business_type=BusinessType.DELETE) async def delete_system_config(request: Request, config_ids: str, query_db: AsyncSession = Depends(get_db)): - try: - delete_config = DeleteConfigModel(configIds=config_ids) - delete_config_result = await ConfigService.delete_config_services(request, query_db, delete_config) - if delete_config_result.is_success: - logger.info(delete_config_result.message) - return ResponseUtil.success(msg=delete_config_result.message) - else: - logger.warning(delete_config_result.message) - return ResponseUtil.failure(msg=delete_config_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@configController.get("/{config_id}", response_model=ConfigModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:query'))]) + 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) + + return ResponseUtil.success(msg=delete_config_result.message) + + +@configController.get( + '/{config_id}', response_model=ConfigModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:query'))] +) async def query_detail_system_config(request: Request, config_id: int, query_db: AsyncSession = Depends(get_db)): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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}") +@configController.get('/configKey/{config_key}') async def query_system_config(request: Request, config_key: str): - try: - # 获取全量数据 - config_query_result = await ConfigService.query_config_list_from_cache_services(request.app.state.redis, config_key) - logger.info('获取成功') - return ResponseUtil.success(msg=config_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@configController.post("/export", dependencies=[Depends(CheckUserInterfaceAuth('system:config:export'))]) -@log_decorator(title='参数管理', business_type=5) -async def export_system_config_list(request: Request, config_page_query: ConfigPageQueryModel = Depends(ConfigPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - 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) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(config_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + # 获取全量数据 + config_query_result = await ConfigService.query_config_list_from_cache_services(request.app.state.redis, config_key) + logger.info('获取成功') + + return ResponseUtil.success(msg=config_query_result) + + +@configController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:config:export'))]) +@Log(title='参数管理', business_type=BusinessType.EXPORT) +async def export_system_config_list( + request: Request, + config_page_query: ConfigPageQueryModel = Depends(ConfigPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + 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) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(config_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py index 476ca3a20866516d1c69c041e0a03595098ee50e..29432069d955dd5881046099f5f278b8abe476c7 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py @@ -1,104 +1,132 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +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.service.login_service import LoginService, CurrentUserModel -from module_admin.service.dept_service import * -from utils.response_util import * -from utils.log_util import * -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import Log from module_admin.aspect.data_scope import GetDataScope -from module_admin.annotation.log_annotation import log_decorator +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +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)]) -@deptController.get("/list/exclude/{dept_id}", response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('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'))): - try: - 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('获取成功') - return ResponseUtil.success(data=dept_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@deptController.get("/list", response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('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'))): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@deptController.post("", dependencies=[Depends(CheckUserInterfaceAuth('system:dept:add'))]) -@log_decorator(title='部门管理', business_type=1) -async def add_system_dept(request: Request, add_dept: DeptModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_dept.create_by = current_user.user.user_name - add_dept.create_time = datetime.now() - add_dept.update_by = current_user.user.user_name - add_dept.update_time = datetime.now() - add_dept_result = await DeptService.add_dept_services(query_db, add_dept) - if add_dept_result.is_success: - logger.info(add_dept_result.message) - return ResponseUtil.success(data=add_dept_result) - else: - logger.warning(add_dept_result.message) - return ResponseUtil.failure(msg=add_dept_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@deptController.put("", dependencies=[Depends(CheckUserInterfaceAuth('system:dept:edit'))]) -@log_decorator(title='部门管理', business_type=2) -async def edit_system_dept(request: Request, edit_dept: DeptModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - edit_dept.update_by = current_user.user.user_name - edit_dept.update_time = datetime.now() - edit_dept_result = await DeptService.edit_dept_services(query_db, edit_dept) - if edit_dept_result.is_success: - logger.info(edit_dept_result.message) - return ResponseUtil.success(msg=edit_dept_result.message) - else: - logger.warning(edit_dept_result.message) - return ResponseUtil.failure(msg=edit_dept_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@deptController.delete("/{dept_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:dept:remove'))]) -@log_decorator(title='部门管理', business_type=3) -async def delete_system_dept(request: Request, dept_ids: str, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - delete_dept = DeleteDeptModel(deptIds=dept_ids) - delete_dept.update_by = current_user.user.user_name - delete_dept.update_time = datetime.now() - delete_dept_result = await DeptService.delete_dept_services(query_db, delete_dept) - if delete_dept_result.is_success: - logger.info(delete_dept_result.message) - return ResponseUtil.success(msg=delete_dept_result.message) - else: - logger.warning(delete_dept_result.message) - return ResponseUtil.failure(msg=delete_dept_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@deptController.get("/{dept_id}", response_model=DeptModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:query'))]) -async def query_detail_system_dept(request: Request, dept_id: int, query_db: AsyncSession = Depends(get_db)): - try: - detail_dept_result = await DeptService.dept_detail_services(query_db, dept_id) - logger.info(f'获取dept_id为{dept_id}的信息成功') - return ResponseUtil.success(data=detail_dept_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) +@deptController.get( + '/list/exclude/{dept_id}', + response_model=List[DeptModel], + dependencies=[Depends(CheckUserInterfaceAuth('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_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('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@deptController.get( + '/list', response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('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_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'))]) +@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), +): + add_dept.create_by = current_user.user.user_name + add_dept.create_time = datetime.now() + add_dept.update_by = current_user.user.user_name + add_dept.update_time = datetime.now() + 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) + + +@deptController.put('', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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 + edit_dept.update_time = datetime.now() + edit_dept_result = await DeptService.edit_dept_services(query_db, edit_dept) + logger.info(edit_dept_result.message) + + return ResponseUtil.success(msg=edit_dept_result.message) + + +@deptController.delete('/{dept_ids}', dependencies=[Depends(CheckUserInterfaceAuth('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_id_list = dept_ids.split(',') if dept_ids else [] + if dept_id_list: + for dept_id in dept_id_list: + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, int(dept_id), data_scope_sql) + delete_dept = DeleteDeptModel(deptIds=dept_ids) + delete_dept.update_by = current_user.user.user_name + delete_dept.update_time = datetime.now() + delete_dept_result = await DeptService.delete_dept_services(query_db, delete_dept) + logger.info(delete_dept_result.message) + + return ResponseUtil.success(msg=delete_dept_result.message) + + +@deptController.get( + '/{dept_id}', response_model=DeptModel, dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + logger.info(f'获取dept_id为{dept_id}的信息成功') + + return ResponseUtil.success(data=detail_dept_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py index a686ef8638d6c226f72647b06ca75648beadf50d..f86dbac3ac39eec4ad89acddd9a61194c18c0ff8 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py @@ -1,237 +1,239 @@ -from fastapi import APIRouter -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +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.service.login_service import LoginService, CurrentUserModel -from module_admin.service.dict_service import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import PageResponseModel -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +from module_admin.entity.vo.dict_vo import ( + DeleteDictDataModel, + DeleteDictTypeModel, + DictDataModel, + DictDataPageQueryModel, + DictTypeModel, + DictTypePageQueryModel, +) +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)]) -@dictController.get("/type/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - dict_type_page_query_result = await DictTypeService.get_dict_type_list_services(query_db, dict_type_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=dict_type_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.post("/type", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) -@log_decorator(title='字典管理', business_type=1) -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)): - try: - 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 - add_dict_type.update_time = datetime.now() - add_dict_type_result = await DictTypeService.add_dict_type_services(request, query_db, add_dict_type) - if add_dict_type_result.is_success: - logger.info(add_dict_type_result.message) - return ResponseUtil.success(msg=add_dict_type_result.message) - else: - logger.warning(add_dict_type_result.message) - return ResponseUtil.failure(msg=add_dict_type_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.put("/type", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) -@log_decorator(title='字典管理', business_type=2) -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)): - try: - 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) - if edit_dict_type_result.is_success: - logger.info(edit_dict_type_result.message) - return ResponseUtil.success(msg=edit_dict_type_result.message) - else: - logger.warning(edit_dict_type_result.message) - return ResponseUtil.failure(msg=edit_dict_type_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.delete("/type/refreshCache", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) -@log_decorator(title='字典管理', business_type=2) +@dictController.get( + '/type/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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_result = await DictTypeService.get_dict_type_list_services( + query_db, dict_type_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=dict_type_page_query_result) + + +@dictController.post('/type', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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 + add_dict_type.update_time = datetime.now() + add_dict_type_result = await DictTypeService.add_dict_type_services(request, query_db, add_dict_type) + logger.info(add_dict_type_result.message) + + return ResponseUtil.success(msg=add_dict_type_result.message) + + +@dictController.put('/type', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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) + logger.info(edit_dict_type_result.message) + + return ResponseUtil.success(msg=edit_dict_type_result.message) + + +@dictController.delete('/type/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) +@Log(title='字典类型', business_type=BusinessType.UPDATE) async def refresh_system_dict(request: Request, query_db: AsyncSession = Depends(get_db)): - try: - refresh_dict_result = await DictTypeService.refresh_sys_dict_services(request, query_db) - if refresh_dict_result.is_success: - logger.info(refresh_dict_result.message) - return ResponseUtil.success(msg=refresh_dict_result.message) - else: - logger.warning(refresh_dict_result.message) - return ResponseUtil.failure(msg=refresh_dict_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.delete("/type/{dict_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) -@log_decorator(title='字典管理', business_type=3) + 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'))]) +@Log(title='字典类型', business_type=BusinessType.DELETE) async def delete_system_dict_type(request: Request, dict_ids: str, query_db: AsyncSession = Depends(get_db)): - try: - delete_dict_type = DeleteDictTypeModel(dictIds=dict_ids) - delete_dict_type_result = await DictTypeService.delete_dict_type_services(request, query_db, delete_dict_type) - if delete_dict_type_result.is_success: - logger.info(delete_dict_type_result.message) - return ResponseUtil.success(msg=delete_dict_type_result.message) - else: - logger.warning(delete_dict_type_result.message) - return ResponseUtil.failure(msg=delete_dict_type_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.get("/type/optionselect", response_model=List[DictTypeModel]) + 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) + + 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)): - try: - dict_type_query_result = await DictTypeService.get_dict_type_list_services(query_db, DictTypePageQueryModel(**dict()), is_page=False) - logger.info(f'获取成功') - return ResponseUtil.success(data=dict_type_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + dict_type_query_result = await DictTypeService.get_dict_type_list_services( + query_db, DictTypePageQueryModel(**dict()), 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'))]) + +@dictController.get( + '/type/{dict_id}', response_model=DictTypeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))] +) async def query_detail_system_dict_type(request: Request, dict_id: int, query_db: AsyncSession = Depends(get_db)): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.post("/type/export", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) -@log_decorator(title='字典管理', business_type=5) -async def export_system_dict_type_list(request: Request, dict_type_page_query: DictTypePageQueryModel = Depends(DictTypePageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - dict_type_query_result = await DictTypeService.get_dict_type_list_services(query_db, dict_type_page_query, is_page=False) - dict_type_export_result = await DictTypeService.export_dict_type_list_services(dict_type_query_result) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(dict_type_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.get("/data/type/{dict_type}") + 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'))]) +@Log(title='字典类型', business_type=BusinessType.EXPORT) +async def export_system_dict_type_list( + request: Request, + dict_type_page_query: DictTypePageQueryModel = Depends(DictTypePageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + dict_type_query_result = await DictTypeService.get_dict_type_list_services( + query_db, dict_type_page_query, is_page=False + ) + dict_type_export_result = await DictTypeService.export_dict_type_list_services(dict_type_query_result) + logger.info('导出成功') + + 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)): - try: - # 获取全量数据 - dict_data_query_result = await DictDataService.query_dict_data_list_from_cache_services(request.app.state.redis, dict_type) - logger.info('获取成功') - return ResponseUtil.success(data=dict_data_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.get("/data/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - dict_data_page_query_result = await DictDataService.get_dict_data_list_services(query_db, dict_data_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=dict_data_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.post("/data", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) -@log_decorator(title='字典管理', business_type=1) -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)): - try: - 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 - add_dict_data.update_time = datetime.now() - add_dict_data_result = await DictDataService.add_dict_data_services(request, query_db, add_dict_data) - if add_dict_data_result.is_success: - logger.info(add_dict_data_result.message) - return ResponseUtil.success(msg=add_dict_data_result.message) - else: - logger.warning(add_dict_data_result.message) - return ResponseUtil.failure(msg=add_dict_data_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.put("/data", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) -@log_decorator(title='字典管理', business_type=2) -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)): - try: - 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) - if edit_dict_data_result.is_success: - logger.info(edit_dict_data_result.message) - return ResponseUtil.success(msg=edit_dict_data_result.message) - else: - logger.warning(edit_dict_data_result.message) - return ResponseUtil.failure(msg=edit_dict_data_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.delete("/data/{dict_codes}", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) -@log_decorator(title='字典管理', business_type=3) + # 获取全量数据 + dict_data_query_result = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type + ) + logger.info('获取成功') + + return ResponseUtil.success(data=dict_data_query_result) + + +@dictController.get( + '/data/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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_result = await DictDataService.get_dict_data_list_services( + query_db, dict_data_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=dict_data_page_query_result) + + +@dictController.post('/data', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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 + add_dict_data.update_time = datetime.now() + add_dict_data_result = await DictDataService.add_dict_data_services(request, query_db, add_dict_data) + logger.info(add_dict_data_result.message) + + return ResponseUtil.success(msg=add_dict_data_result.message) + + +@dictController.put('/data', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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) + logger.info(edit_dict_data_result.message) + + return ResponseUtil.success(msg=edit_dict_data_result.message) + + +@dictController.delete('/data/{dict_codes}', dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - delete_dict_data = DeleteDictDataModel(dictCodes=dict_codes) - delete_dict_data_result = await DictDataService.delete_dict_data_services(request, query_db, delete_dict_data) - if delete_dict_data_result.is_success: - logger.info(delete_dict_data_result.message) - return ResponseUtil.success(msg=delete_dict_data_result.message) - else: - logger.warning(delete_dict_data_result.message) - return ResponseUtil.failure(msg=delete_dict_data_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.get("/data/{dict_code}", response_model=DictDataModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))]) + 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) + + return ResponseUtil.success(msg=delete_dict_data_result.message) + + +@dictController.get( + '/data/{dict_code}', + response_model=DictDataModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))], +) async def query_detail_system_dict_data(request: Request, dict_code: int, query_db: AsyncSession = Depends(get_db)): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@dictController.post("/data/export", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) -@log_decorator(title='字典管理', business_type=5) -async def export_system_dict_data_list(request: Request, dict_data_page_query: DictDataPageQueryModel = Depends(DictDataPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - dict_data_query_result = await DictDataService.get_dict_data_list_services(query_db, dict_data_page_query, is_page=False) - dict_data_export_result = await DictDataService.export_dict_data_list_services(dict_data_query_result) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(dict_data_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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'))]) +@Log(title='字典数据', business_type=BusinessType.EXPORT) +async def export_system_dict_data_list( + request: Request, + dict_data_page_query: DictDataPageQueryModel = Depends(DictDataPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + dict_data_query_result = await DictDataService.get_dict_data_list_services( + query_db, dict_data_page_query, is_page=False + ) + dict_data_export_result = await DictDataService.export_dict_data_list_services(dict_data_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(dict_data_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py index 04cc6e06afcfd69f9e2e2e2123204e879570b744..0c08fffbfe3ef32baa1e9096cd98fe923f2d46dd 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py @@ -1,201 +1,194 @@ -from fastapi import APIRouter -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import LoginService, CurrentUserModel -from module_admin.service.job_service import * -from module_admin.service.job_log_service import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import * -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +from module_admin.entity.vo.job_vo import ( + DeleteJobLogModel, + DeleteJobModel, + EditJobModel, + JobLogPageQueryModel, + JobModel, + JobPageQueryModel, +) +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)]) -@jobController.get("/job/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - notice_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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.post("/job", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:add'))]) -@log_decorator(title='定时任务管理', business_type=1) -async def add_system_job(request: Request, add_job: JobModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_job.create_by = current_user.user.user_name - add_job.create_time = datetime.now() - add_job.update_by = current_user.user.user_name - add_job.update_time = datetime.now() - add_job_result = await JobService.add_job_services(query_db, add_job) - if add_job_result.is_success: - logger.info(add_job_result.message) - return ResponseUtil.success(msg=add_job_result.message) - else: - logger.warning(add_job_result.message) - return ResponseUtil.failure(msg=add_job_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.put("/job", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))]) -@log_decorator(title='定时任务管理', business_type=2) -async def edit_system_job(request: Request, edit_job: EditJobModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - 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) - if edit_job_result.is_success: - logger.info(edit_job_result.message) - return ResponseUtil.success(msg=edit_job_result.message) - else: - logger.warning(edit_job_result.message) - return ResponseUtil.failure(msg=edit_job_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.put("/job/changeStatus", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) -@log_decorator(title='定时任务管理', business_type=2) -async def edit_system_job(request: Request, edit_job: EditJobModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - edit_job.update_by = current_user.user.user_name - edit_job.update_time = datetime.now() - edit_job.type = 'status' - edit_job_result = await JobService.edit_job_services(query_db, edit_job) - if edit_job_result.is_success: - logger.info(edit_job_result.message) - return ResponseUtil.success(msg=edit_job_result.message) - else: - logger.warning(edit_job_result.message) - return ResponseUtil.failure(msg=edit_job_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.put("/job/run", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) -@log_decorator(title='定时任务管理', business_type=2) +@jobController.get( + '/job/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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), +): + # 获取分页数据 + notice_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) + + +@jobController.post('/job', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + add_job.create_by = current_user.user.user_name + add_job.create_time = datetime.now() + add_job.update_by = current_user.user.user_name + add_job.update_time = datetime.now() + add_job_result = await JobService.add_job_services(query_db, add_job) + logger.info(add_job_result.message) + + return ResponseUtil.success(msg=add_job_result.message) + + +@jobController.put('/job', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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) + logger.info(edit_job_result.message) + + return ResponseUtil.success(msg=edit_job_result.message) + + +@jobController.put('/job/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + edit_job = EditJobModel( + jobId=change_job.job_id, + status=change_job.status, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='status', + ) + edit_job_result = await JobService.edit_job_services(query_db, edit_job) + logger.info(edit_job_result.message) + + return ResponseUtil.success(msg=edit_job_result.message) + + +@jobController.put('/job/run', dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - execute_job_result = await JobService.execute_job_once_services(query_db, execute_job) - if execute_job_result.is_success: - logger.info(execute_job_result.message) - return ResponseUtil.success(msg=execute_job_result.message) - else: - logger.warning(execute_job_result.message) - return ResponseUtil.failure(msg=execute_job_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.delete("/job/{job_ids}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) -@log_decorator(title='定时任务管理', business_type=3) + 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'))]) +@Log(title='定时任务', business_type=BusinessType.DELETE) async def delete_system_job(request: Request, job_ids: str, query_db: AsyncSession = Depends(get_db)): - try: - delete_job = DeleteJobModel(jobIds=job_ids) - delete_job_result = await JobService.delete_job_services(query_db, delete_job) - if delete_job_result.is_success: - logger.info(delete_job_result.message) - return ResponseUtil.success(msg=delete_job_result.message) - else: - logger.warning(delete_job_result.message) - return ResponseUtil.failure(msg=delete_job_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.get("/job/{job_id}", response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))]) + delete_job = DeleteJobModel(jobIds=job_ids) + delete_job_result = await JobService.delete_job_services(query_db, delete_job) + logger.info(delete_job_result.message) + + return ResponseUtil.success(msg=delete_job_result.message) + + +@jobController.get( + '/job/{job_id}', response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))] +) async def query_detail_system_job(request: Request, job_id: int, query_db: AsyncSession = Depends(get_db)): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.post("/job/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) -@log_decorator(title='定时任务管理', business_type=5) -async def export_system_job_list(request: Request, job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - 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) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(job_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.get("/jobLog/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - job_log_page_query_result = await JobLogService.get_job_log_list_services(query_db, job_log_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=job_log_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.delete("/jobLog/clean", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) -@log_decorator(title='定时任务日志管理', business_type=9) + 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'))]) +@Log(title='定时任务', business_type=BusinessType.EXPORT) +async def export_system_job_list( + request: Request, + job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + 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) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(job_export_result)) + + +@jobController.get( + '/jobLog/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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_result = await JobLogService.get_job_log_list_services( + query_db, job_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=job_log_page_query_result) + + +@jobController.delete('/jobLog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.CLEAN) async def clear_system_job_log(request: Request, query_db: AsyncSession = Depends(get_db)): - try: - clear_job_log_result = await JobLogService.clear_job_log_services(query_db) - if clear_job_log_result.is_success: - logger.info(clear_job_log_result.message) - return ResponseUtil.success(msg=clear_job_log_result.message) - else: - logger.warning(clear_job_log_result.message) - return ResponseUtil.failure(msg=clear_job_log_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.delete("/jobLog/{job_log_ids}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) -@log_decorator(title='定时任务日志管理', business_type=3) + 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'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.DELETE) async def delete_system_job_log(request: Request, job_log_ids: str, query_db: AsyncSession = Depends(get_db)): - try: - delete_job_log = DeleteJobLogModel(jobLogIds=job_log_ids) - delete_job_log_result = await JobLogService.delete_job_log_services(query_db, delete_job_log) - if delete_job_log_result.is_success: - logger.info(delete_job_log_result.message) - return ResponseUtil.success(msg=delete_job_log_result.message) - else: - logger.warning(delete_job_log_result.message) - return ResponseUtil.failure(msg=delete_job_log_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@jobController.post("/jobLog/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) -@log_decorator(title='定时任务日志管理', business_type=5) -async def export_system_job_log_list(request: Request, job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - 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) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(job_log_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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) + + return ResponseUtil.success(msg=delete_job_log_result.message) + + +@jobController.post('/jobLog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.EXPORT) +async def export_system_job_log_list( + request: Request, + job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + 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) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(job_log_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/log_controller.py b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py index a0a18c5567bbb3bfcd9ec02310bad312d85d48ce..4d0b55cdece5ce9a2da774bbf3527cbf0a79fbe2 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/log_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py @@ -1,149 +1,150 @@ -from fastapi import APIRouter -from fastapi import Depends +from fastapi import APIRouter, Depends, Request +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.log_vo import ( + DeleteLoginLogModel, + DeleteOperLogModel, + LoginLogPageQueryModel, + OperLogPageQueryModel, + UnlockUser, +) +from module_admin.service.log_service import LoginLogService, OperationLogService from module_admin.service.login_service import LoginService -from module_admin.service.log_service import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import * from utils.common_util import bytes2file_response -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +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)]) -@logController.get("/operlog/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - operation_log_page_query_result = await OperationLogService.get_operation_log_list_services(query_db, operation_log_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=operation_log_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) +@logController.get( + '/operlog/list', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('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_result = await OperationLogService.get_operation_log_list_services( + query_db, operation_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=operation_log_page_query_result) -@logController.delete("/operlog/clean", dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) -@log_decorator(title='操作日志管理', business_type=9) +@logController.delete('/operlog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) +@Log(title='操作日志', business_type=BusinessType.CLEAN) async def clear_system_operation_log(request: Request, query_db: AsyncSession = Depends(get_db)): - try: - clear_operation_log_result = await OperationLogService.clear_operation_log_services(query_db) - if clear_operation_log_result.is_success: - logger.info(clear_operation_log_result.message) - return ResponseUtil.success(msg=clear_operation_log_result.message) - else: - logger.warning(clear_operation_log_result.message) - return ResponseUtil.failure(msg=clear_operation_log_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@logController.delete("/operlog/{oper_ids}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) -@log_decorator(title='操作日志管理', business_type=3) + 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(title='操作日志', business_type=BusinessType.DELETE) async def delete_system_operation_log(request: Request, oper_ids: str, query_db: AsyncSession = Depends(get_db)): - try: - delete_operation_log = DeleteOperLogModel(operIds=oper_ids) - delete_operation_log_result = await OperationLogService.delete_operation_log_services(query_db, delete_operation_log) - if delete_operation_log_result.is_success: - logger.info(delete_operation_log_result.message) - return ResponseUtil.success(msg=delete_operation_log_result.message) - else: - logger.warning(delete_operation_log_result.message) - return ResponseUtil.failure(msg=delete_operation_log_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@logController.post("/operlog/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:export'))]) -@log_decorator(title='操作日志管理', business_type=5) -async def export_system_operation_log_list(request: Request, operation_log_page_query: OperLogPageQueryModel = Depends(OperLogPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - operation_log_query_result = await OperationLogService.get_operation_log_list_services(query_db, operation_log_page_query, is_page=False) - operation_log_export_result = await OperationLogService.export_operation_log_list_services(request, operation_log_query_result) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@logController.get("/logininfor/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - login_log_page_query_result = await LoginLogService.get_login_log_list_services(query_db, login_log_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=login_log_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@logController.delete("/logininfor/clean", dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]) -@log_decorator(title='登录日志管理', business_type=9) + delete_operation_log = DeleteOperLogModel(operIds=oper_ids) + delete_operation_log_result = await OperationLogService.delete_operation_log_services( + query_db, delete_operation_log + ) + logger.info(delete_operation_log_result.message) + + return ResponseUtil.success(msg=delete_operation_log_result.message) + + +@logController.post('/operlog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:export'))]) +@Log(title='操作日志', business_type=BusinessType.EXPORT) +async def export_system_operation_log_list( + request: Request, + operation_log_page_query: OperLogPageQueryModel = Depends(OperLogPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + operation_log_query_result = await OperationLogService.get_operation_log_list_services( + query_db, operation_log_page_query, is_page=False + ) + operation_log_export_result = await OperationLogService.export_operation_log_list_services( + request, operation_log_query_result + ) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result)) + + +@logController.get( + '/logininfor/list', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('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_result = await LoginLogService.get_login_log_list_services( + query_db, login_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=login_log_page_query_result) + + +@logController.delete('/logininfor/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]) +@Log(title='登录日志', business_type=BusinessType.CLEAN) async def clear_system_login_log(request: Request, query_db: AsyncSession = Depends(get_db)): - try: - clear_login_log_result = await LoginLogService.clear_login_log_services(query_db) - if clear_login_log_result.is_success: - logger.info(clear_login_log_result.message) - return ResponseUtil.success(msg=clear_login_log_result.message) - else: - logger.warning(clear_login_log_result.message) - return ResponseUtil.failure(msg=clear_login_log_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@logController.delete("/logininfor/{info_ids}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]) -@log_decorator(title='登录日志管理', business_type=3) + 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(title='登录日志', business_type=BusinessType.DELETE) async def delete_system_login_log(request: Request, info_ids: str, query_db: AsyncSession = Depends(get_db)): - try: - delete_login_log = DeleteLoginLogModel(infoIds=info_ids) - delete_login_log_result = await LoginLogService.delete_login_log_services(query_db, delete_login_log) - if delete_login_log_result.is_success: - logger.info(delete_login_log_result.message) - return ResponseUtil.success(msg=delete_login_log_result.message) - else: - logger.warning(delete_login_log_result.message) - return ResponseUtil.failure(msg=delete_login_log_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@logController.get("/logininfor/unlock/{user_name}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:unlock'))]) -@log_decorator(title='登录日志管理', business_type=0) -async def clear_system_login_log(request: Request, user_name: str, query_db: AsyncSession = Depends(get_db)): - try: - unlock_user = UnlockUser(userName=user_name) - unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user) - if unlock_user_result.is_success: - logger.info(unlock_user_result.message) - return ResponseUtil.success(msg=unlock_user_result.message) - else: - logger.warning(unlock_user_result.message) - return ResponseUtil.failure(msg=unlock_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@logController.post("/logininfor/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:export'))]) -@log_decorator(title='登录日志管理', business_type=5) -async def export_system_login_log_list(request: Request, login_log_page_query: LoginLogPageQueryModel = Depends(LoginLogPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - login_log_query_result = await LoginLogService.get_login_log_list_services(query_db, login_log_page_query, is_page=False) - login_log_export_result = await LoginLogService.export_login_log_list_services(login_log_query_result) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(login_log_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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) + + return ResponseUtil.success(msg=delete_login_log_result.message) + + +@logController.get( + '/logininfor/unlock/{user_name}', dependencies=[Depends(CheckUserInterfaceAuth('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)): + unlock_user = UnlockUser(userName=user_name) + unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user) + logger.info(unlock_user_result.message) + + return ResponseUtil.success(msg=unlock_user_result.message) + + +@logController.post('/logininfor/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:export'))]) +@Log(title='登录日志', business_type=BusinessType.EXPORT) +async def export_system_login_log_list( + request: Request, + login_log_page_query: LoginLogPageQueryModel = Depends(LoginLogPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + login_log_query_result = await LoginLogService.get_login_log_list_services( + query_db, login_log_page_query, is_page=False + ) + login_log_export_result = await LoginLogService.export_login_log_list_services(login_log_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(login_log_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py index 74b495ce310cbb2af1aea2840b56421a009fea4e..6d13fcc82133850d7ab9330c40538c8f4e505483 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py @@ -1,103 +1,109 @@ -from fastapi import APIRouter -from module_admin.service.login_service import * -from module_admin.entity.vo.login_vo import * -from module_admin.dao.login_dao import * -from module_admin.annotation.log_annotation import log_decorator -from config.env import JwtConfig, RedisInitKeyConfig +import jwt +import uuid +from datetime import datetime, timedelta +from fastapi import APIRouter, Depends, Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional +from config.enums import BusinessType, RedisInitKeyConfig +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.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 -from utils.log_util import * -from datetime import timedelta loginController = APIRouter() -@loginController.post("/login", response_model=Token) -@log_decorator(title='用户登录', business_type=0, log_type='login') -async def login(request: Request, form_data: CustomOAuth2PasswordRequestForm = Depends(), query_db: AsyncSession = Depends(get_db)): - captcha_enabled = True if await request.app.state.redis.get(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.account.captchaEnabled") == 'true' else False +@loginController.post('/login', response_model=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) +): + captcha_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + == 'true' + else False + ) user = UserLogin( userName=form_data.username, password=form_data.password, code=form_data.code, uuid=form_data.uuid, loginInfo=form_data.login_info, - captchaEnabled=captcha_enabled + captchaEnabled=captcha_enabled, + ) + result = await LoginService.authenticate_user(request, query_db, user) + access_token_expires = timedelta(minutes=JwtConfig.jwt_expire_minutes) + session_id = str(uuid.uuid4()) + access_token = await LoginService.create_access_token( + data={ + 'user_id': str(result[0].user_id), + 'user_name': result[0].user_name, + 'dept_name': result[1].dept_name if result[1] else None, + 'session_id': session_id, + 'login_info': user.login_info, + }, + expires_delta=access_token_expires, ) - try: - result = await LoginService.authenticate_user(request, query_db, user) - except LoginException as e: - return ResponseUtil.failure(msg=e.message) - try: - access_token_expires = timedelta(minutes=JwtConfig.jwt_expire_minutes) - session_id = str(uuid.uuid4()) - access_token = await LoginService.create_access_token( - data={ - "user_id": str(result[0].user_id), - "user_name": result[0].user_name, - "dept_name": result[1].dept_name if result[1] else None, - "session_id": session_id, - "login_info": user.login_info - }, - expires_delta=access_token_expires + if AppConfig.app_same_time_login: + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}', + access_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), ) - if AppConfig.app_same_time_login: - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", access_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) - else: - # 此方法可实现同一账号同一时间只能登录一次 - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{result[0].user_id}", access_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) - await UserService.edit_user_services(query_db, EditUserModel(userId=result[0].user_id, loginDate=datetime.now(), type='status')) - logger.info('登录成功') - # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug - request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False - request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False - if request_from_swagger or request_from_redoc: - return {'access_token': access_token, 'token_type': 'Bearer'} - return ResponseUtil.success( - msg='登录成功', - dict_content={'token': access_token} + else: + # 此方法可实现同一账号同一时间只能登录一次 + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{result[0].user_id}', + access_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), ) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + await UserService.edit_user_services( + query_db, EditUserModel(userId=result[0].user_id, loginDate=datetime.now(), type='status') + ) + logger.info('登录成功') + # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug + request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + if request_from_swagger or request_from_redoc: + return {'access_token': access_token, 'token_type': 'Bearer'} + return ResponseUtil.success(msg='登录成功', dict_content={'token': access_token}) + +@loginController.get('/getInfo', response_model=CurrentUserModel) +async def get_login_user_info( + request: Request, current_user: CurrentUserModel = Depends(LoginService.get_current_user) +): + logger.info('获取成功') -@loginController.get("/getInfo", response_model=CurrentUserModel) -async def get_login_user_info(request: Request, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - logger.info('获取成功') - return ResponseUtil.success(model_content=current_user) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + return ResponseUtil.success(model_content=current_user) -@loginController.get("/getRouters") -async def get_login_user_routers(request: Request, current_user: CurrentUserModel = Depends(LoginService.get_current_user), query_db: AsyncSession = Depends(get_db)): - try: - logger.info('获取成功') - user_routers = await LoginService.get_current_user_routers(current_user.user.user_id, query_db) - return ResponseUtil.success(data=user_routers) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) +@loginController.get('/getRouters') +async def get_login_user_routers( + request: Request, + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + query_db: AsyncSession = Depends(get_db), +): + 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) + +@loginController.post('/register', response_model=CrudResponseModel) async def register_user(request: Request, user_register: UserRegister, query_db: AsyncSession = Depends(get_db)): - try: - user_register_result = await LoginService.register_user_services(request, query_db, user_register) - if user_register_result.is_success: - logger.info(user_register_result.message) - return ResponseUtil.success(data=user_register_result, msg=user_register_result.message) - else: - logger.warning(user_register_result.message) - return ResponseUtil.failure(msg=user_register_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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) @@ -130,14 +136,13 @@ async def register_user(request: Request, user_register: UserRegister, query_db: # return ResponseUtil.error(msg=str(e)) -@loginController.post("/logout") +@loginController.post('/logout') async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme)): - try: - payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False}) - session_id: str = payload.get("session_id") - await LoginService.logout_services(request, session_id) - logger.info('退出成功') - return ResponseUtil.success(msg="退出成功") - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + payload = jwt.decode( + token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False} + ) + session_id: str = payload.get('session_id') + await LoginService.logout_services(request, session_id) + logger.info('退出成功') + + return ResponseUtil.success(msg='退出成功') diff --git a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py index 949fd1a7a71d5641ae97cd4a6b5631df8452dc81..0e3124ec8fcb762c5c840e6027b26c07d72c4fef 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py @@ -1,111 +1,114 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +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.service.login_service import LoginService -from module_admin.service.menu_service import * -from utils.response_util import * -from utils.log_util import * +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel +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)]) -@menuController.get("/treeselect") -async def get_system_menu_tree(request: Request, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - menu_query_result = await MenuService.get_menu_tree_services(query_db, current_user) - logger.info('获取成功') - return ResponseUtil.success(data=menu_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@menuController.get("/roleMenuTreeselect/{role_id}") -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)): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@menuController.get("/list", response_model=List[MenuModel], dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - menu_query_result = await MenuService.get_menu_list_services(query_db, menu_query, current_user) - logger.info('获取成功') - return ResponseUtil.success(data=menu_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@menuController.post("", dependencies=[Depends(CheckUserInterfaceAuth('system:menu:add'))]) -@log_decorator(title='菜单管理', business_type=1) -async def add_system_menu(request: Request, add_menu: MenuModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_menu.create_by = current_user.user.user_name - add_menu.create_time = datetime.now() - add_menu.update_by = current_user.user.user_name - add_menu.update_time = datetime.now() - add_menu_result = await MenuService.add_menu_services(query_db, add_menu) - if add_menu_result.is_success: - logger.info(add_menu_result.message) - return ResponseUtil.success(msg=add_menu_result.message) - else: - logger.warning(add_menu_result.message) - return ResponseUtil.failure(msg=add_menu_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@menuController.put("", dependencies=[Depends(CheckUserInterfaceAuth('system:menu:edit'))]) -@log_decorator(title='菜单管理', business_type=2) -async def edit_system_menu(request: Request, edit_menu: MenuModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - 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) - if edit_menu_result.is_success: - logger.info(edit_menu_result.message) - return ResponseUtil.success(msg=edit_menu_result.message) - else: - logger.warning(edit_menu_result.message) - return ResponseUtil.failure(msg=edit_menu_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@menuController.delete("/{menu_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:menu:remove'))]) -@log_decorator(title='菜单管理', business_type=3) +@menuController.get('/treeselect') +async def get_system_menu_tree( + request: Request, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + 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}') +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_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'))] +) +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_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'))]) +@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), +): + add_menu.create_by = current_user.user.user_name + add_menu.create_time = datetime.now() + add_menu.update_by = current_user.user.user_name + add_menu.update_time = datetime.now() + add_menu_result = await MenuService.add_menu_services(query_db, add_menu) + logger.info(add_menu_result.message) + + return ResponseUtil.success(msg=add_menu_result.message) + + +@menuController.put('', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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) + logger.info(edit_menu_result.message) + + return ResponseUtil.success(msg=edit_menu_result.message) + + +@menuController.delete('/{menu_ids}', dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - delete_menu = DeleteMenuModel(menuIds=menu_ids) - delete_menu_result = await MenuService.delete_menu_services(query_db, delete_menu) - if delete_menu_result.is_success: - logger.info(delete_menu_result.message) - return ResponseUtil.success(msg=delete_menu_result.message) - else: - logger.warning(delete_menu_result.message) - return ResponseUtil.failure(msg=delete_menu_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@menuController.get("/{menu_id}", response_model=MenuModel, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:query'))]) + delete_menu = DeleteMenuModel(menuIds=menu_ids) + delete_menu_result = await MenuService.delete_menu_services(query_db, delete_menu) + logger.info(delete_menu_result.message) + + return ResponseUtil.success(msg=delete_menu_result.message) + + +@menuController.get( + '/{menu_id}', response_model=MenuModel, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:query'))] +) async def query_detail_system_menu(request: Request, menu_id: int, query_db: AsyncSession = Depends(get_db)): - try: - menu_detail_result = await MenuService.menu_detail_services(query_db, menu_id) - logger.info(f'获取menu_id为{menu_id}的信息成功') - return ResponseUtil.success(data=menu_detail_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + menu_detail_result = await MenuService.menu_detail_services(query_db, menu_id) + logger.info(f'获取menu_id为{menu_id}的信息成功') + + return ResponseUtil.success(data=menu_detail_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py index 0e2c0d7c0d1f0d4df864395e9a69df09aada44f9..e1e4aa169ec1c1f80a6e4510c6e82d3ec51706b1 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py @@ -1,91 +1,89 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import LoginService, CurrentUserModel -from module_admin.service.notice_service import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import * +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +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)]) -@noticeController.get("/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - # 获取分页数据 - notice_page_query_result = await NoticeService.get_notice_list_services(query_db, notice_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=notice_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@noticeController.post("", dependencies=[Depends(CheckUserInterfaceAuth('system:notice:add'))]) -@log_decorator(title='通知公告管理', business_type=1) -async def add_system_notice(request: Request, add_notice: NoticeModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_notice.create_by = current_user.user.user_name - add_notice.create_time = datetime.now() - add_notice.update_by = current_user.user.user_name - add_notice.update_time = datetime.now() - add_notice_result = await NoticeService.add_notice_services(query_db, add_notice) - if add_notice_result.is_success: - logger.info(add_notice_result.message) - return ResponseUtil.success(msg=add_notice_result.message) - else: - logger.warning(add_notice_result.message) - return ResponseUtil.failure(msg=add_notice_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@noticeController.put("", dependencies=[Depends(CheckUserInterfaceAuth('system:notice:edit'))]) -@log_decorator(title='通知公告管理', business_type=2) -async def edit_system_notice(request: Request, edit_notice: NoticeModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - 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) - if edit_notice_result.is_success: - logger.info(edit_notice_result.message) - return ResponseUtil.success(msg=edit_notice_result.message) - else: - logger.warning(edit_notice_result.message) - return ResponseUtil.failure(msg=edit_notice_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@noticeController.delete("/{notice_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:notice:remove'))]) -@log_decorator(title='通知公告管理', business_type=3) +@noticeController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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_result = await NoticeService.get_notice_list_services(query_db, notice_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=notice_page_query_result) + + +@noticeController.post('', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + add_notice.create_by = current_user.user.user_name + add_notice.create_time = datetime.now() + add_notice.update_by = current_user.user.user_name + add_notice.update_time = datetime.now() + add_notice_result = await NoticeService.add_notice_services(query_db, add_notice) + logger.info(add_notice_result.message) + + return ResponseUtil.success(msg=add_notice_result.message) + + +@noticeController.put('', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + 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) + logger.info(edit_notice_result.message) + + return ResponseUtil.success(msg=edit_notice_result.message) + + +@noticeController.delete('/{notice_ids}', dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - delete_notice = DeleteNoticeModel(noticeIds=notice_ids) - delete_notice_result = await NoticeService.delete_notice_services(query_db, delete_notice) - if delete_notice_result.is_success: - logger.info(delete_notice_result.message) - return ResponseUtil.success(msg=delete_notice_result.message) - else: - logger.warning(delete_notice_result.message) - return ResponseUtil.failure(msg=delete_notice_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@noticeController.get("/{notice_id}", response_model=NoticeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:query'))]) + delete_notice = DeleteNoticeModel(noticeIds=notice_ids) + delete_notice_result = await NoticeService.delete_notice_services(query_db, delete_notice) + logger.info(delete_notice_result.message) + + return ResponseUtil.success(msg=delete_notice_result.message) + + +@noticeController.get( + '/{notice_id}', response_model=NoticeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:query'))] +) async def query_detail_system_post(request: Request, notice_id: int, query_db: AsyncSession = Depends(get_db)): - try: - notice_detail_result = await NoticeService.notice_detail_services(query_db, notice_id) - logger.info(f'获取notice_id为{notice_id}的信息成功') - return ResponseUtil.success(data=notice_detail_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + notice_detail_result = await NoticeService.notice_detail_services(query_db, notice_id) + logger.info(f'获取notice_id为{notice_id}的信息成功') + + return ResponseUtil.success(data=notice_detail_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/online_controller.py b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py index 747cc13c7eb3fb50dbe1e2e5fd511aca0dafdb91..bf65c8f5176e491ba197801929361c4c7b341428 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/online_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py @@ -1,42 +1,40 @@ -from fastapi import APIRouter -from fastapi import Depends +from fastapi import APIRouter, Depends, Request +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import LoginService, AsyncSession -from module_admin.service.online_service import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import * +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlineQueryModel +from module_admin.service.login_service import LoginService +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)]) -@onlineController.get("/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))]) -async def get_monitor_online_list(request: Request, online_page_query: OnlineQueryModel = Depends(OnlineQueryModel.as_query)): - try: - # 获取全量数据 - 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))) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) +@onlineController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))] +) +async def get_monitor_online_list( + request: Request, online_page_query: OnlineQueryModel = Depends(OnlineQueryModel.as_query) +): + # 获取全量数据 + 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)) + ) -@onlineController.delete("/{token_ids}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:forceLogout'))]) -@log_decorator(title='在线用户', business_type=7) + +@onlineController.delete('/{token_ids}', dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - delete_online = DeleteOnlineModel(tokenIds=token_ids) - delete_online_result = await OnlineService.delete_online_services(request, delete_online) - if delete_online_result.is_success: - logger.info(delete_online_result.message) - return ResponseUtil.success(msg=delete_online_result.message) - else: - logger.warning(delete_online_result.message) - return ResponseUtil.failure(msg=delete_online_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + delete_online = DeleteOnlineModel(tokenIds=token_ids) + delete_online_result = await OnlineService.delete_online_services(request, delete_online) + logger.info(delete_online_result.message) + + return ResponseUtil.success(msg=delete_online_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/post_controler.py b/ruoyi-fastapi-backend/module_admin/controller/post_controler.py index 2128a68295bb20c52f85d9c58f29751bd7019f05..42190763c66e75fbad16dc2626f72bac5188efdd 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/post_controler.py +++ b/ruoyi-fastapi-backend/module_admin/controller/post_controler.py @@ -1,107 +1,105 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import LoginService, CurrentUserModel -from module_admin.service.post_service import * -from module_admin.entity.vo.post_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import * -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator +from module_admin.service.login_service import LoginService +from module_admin.service.post_service import PostService +from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil postController = APIRouter(prefix='/system/post', dependencies=[Depends(LoginService.get_current_user)]) -@postController.get("/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:list'))]) -async def get_system_post_list(request: Request, post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_query), query_db: AsyncSession = Depends(get_db)): - try: - # 获取分页数据 - post_page_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=post_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@postController.post("", dependencies=[Depends(CheckUserInterfaceAuth('system:post:add'))]) -@log_decorator(title='岗位管理', business_type=1) -async def add_system_post(request: Request, add_post: PostModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_post.create_by = current_user.user.user_name - add_post.create_time = datetime.now() - add_post.update_by = current_user.user.user_name - add_post.update_time = datetime.now() - add_post_result = await PostService.add_post_services(query_db, add_post) - if add_post_result.is_success: - logger.info(add_post_result.message) - return ResponseUtil.success(msg=add_post_result.message) - else: - logger.warning(add_post_result.message) - return ResponseUtil.failure(msg=add_post_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@postController.put("", dependencies=[Depends(CheckUserInterfaceAuth('system:post:edit'))]) -@log_decorator(title='岗位管理', business_type=2) -async def edit_system_post(request: Request, edit_post: PostModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - 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) - if edit_post_result.is_success: - logger.info(edit_post_result.message) - return ResponseUtil.success(msg=edit_post_result.message) - else: - logger.warning(edit_post_result.message) - return ResponseUtil.failure(msg=edit_post_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@postController.delete("/{post_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:post:remove'))]) -@log_decorator(title='岗位管理', business_type=3) +@postController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:list'))] +) +async def get_system_post_list( + request: Request, + post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + post_page_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=post_page_query_result) + + +@postController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:add'))]) +@ValidateFields(validate_model='add_post') +@Log(title='岗位管理', business_type=BusinessType.INSERT) +async def add_system_post( + request: Request, + add_post: PostModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_post.create_by = current_user.user.user_name + add_post.create_time = datetime.now() + add_post.update_by = current_user.user.user_name + add_post.update_time = datetime.now() + add_post_result = await PostService.add_post_services(query_db, add_post) + logger.info(add_post_result.message) + + return ResponseUtil.success(msg=add_post_result.message) + + +@postController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:edit'))]) +@ValidateFields(validate_model='edit_post') +@Log(title='岗位管理', business_type=BusinessType.UPDATE) +async def edit_system_post( + request: Request, + edit_post: PostModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_post.update_by = current_user.user.user_name + edit_post.update_time = datetime.now() + edit_post_result = await PostService.edit_post_services(query_db, edit_post) + logger.info(edit_post_result.message) + + return ResponseUtil.success(msg=edit_post_result.message) + + +@postController.delete('/{post_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:post:remove'))]) +@Log(title='岗位管理', business_type=BusinessType.DELETE) async def delete_system_post(request: Request, post_ids: str, query_db: AsyncSession = Depends(get_db)): - try: - delete_post = DeletePostModel(postIds=post_ids) - delete_post_result = await PostService.delete_post_services(query_db, delete_post) - if delete_post_result.is_success: - logger.info(delete_post_result.message) - return ResponseUtil.success(msg=delete_post_result.message) - else: - logger.warning(delete_post_result.message) - return ResponseUtil.failure(msg=delete_post_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@postController.get("/{post_id}", response_model=PostModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:query'))]) + delete_post = DeletePostModel(postIds=post_ids) + delete_post_result = await PostService.delete_post_services(query_db, delete_post) + logger.info(delete_post_result.message) + + return ResponseUtil.success(msg=delete_post_result.message) + + +@postController.get( + '/{post_id}', response_model=PostModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:query'))] +) async def query_detail_system_post(request: Request, post_id: int, query_db: AsyncSession = Depends(get_db)): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@postController.post("/export", dependencies=[Depends(CheckUserInterfaceAuth('system:post:export'))]) -@log_decorator(title='岗位管理', business_type=5) -async def export_system_post_list(request: Request, post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - post_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=False) - post_export_result = await PostService.export_post_list_services(post_query_result) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(post_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + post_detail_result = await PostService.post_detail_services(query_db, post_id) + logger.info(f'获取post_id为{post_id}的信息成功') + + return ResponseUtil.success(data=post_detail_result) + + +@postController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:post:export'))]) +@Log(title='岗位管理', business_type=BusinessType.EXPORT) +async def export_system_post_list( + request: Request, + post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + post_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=False) + post_export_result = await PostService.export_post_list_services(post_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(post_export_result)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py index 068d94fdb670f4bf4b26f77a8e0d5c54139e004e..e092bcc94a65a8bdb4b288824bcbecfd1261e8d4 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py @@ -1,232 +1,283 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import LoginService, CurrentUserModel -from module_admin.service.role_service import * -from module_admin.service.dept_service import DeptService, DeptModel -from module_admin.service.user_service import UserService, UserRoleQueryModel, UserRolePageQueryModel, CrudUserRoleModel -from utils.response_util import * -from utils.log_util import * -from utils.page_util import PageResponseModel -from utils.common_util import bytes2file_response -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import Log from module_admin.aspect.data_scope import GetDataScope -from module_admin.annotation.log_annotation import log_decorator +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +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.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)]) -@roleController.get("/deptTree/{role_id}", dependencies=[Depends(CheckUserInterfaceAuth('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'))): - try: - 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('获取成功') - return ResponseUtil.success(model_content=role_dept_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.get("/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - role_page_query_result = await RoleService.get_role_list_services(query_db, role_page_query, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=role_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.post("", dependencies=[Depends(CheckUserInterfaceAuth('system:role:add'))]) -@log_decorator(title='角色管理', business_type=1) -async def add_system_role(request: Request, add_role: AddRoleModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_role.create_by = current_user.user.user_name - add_role.create_time = datetime.now() - add_role.update_by = current_user.user.user_name - add_role.update_time = datetime.now() - add_role_result = await RoleService.add_role_services(query_db, add_role) - if add_role_result.is_success: - logger.info(add_role_result.message) - return ResponseUtil.success(msg=add_role_result.message) - else: - logger.warning(add_role_result.message) - return ResponseUtil.failure(msg=add_role_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.put("", dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=2) -async def edit_system_role(request: Request, edit_role: AddRoleModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - edit_role.update_by = current_user.user.user_name - edit_role.update_time = datetime.now() - edit_role_result = await RoleService.edit_role_services(query_db, edit_role) - if edit_role_result.is_success: - logger.info(edit_role_result.message) - return ResponseUtil.success(msg=edit_role_result.message) - else: - logger.warning(edit_role_result.message) - return ResponseUtil.failure(msg=edit_role_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.put("/dataScope", dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=4) -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)): - try: - role_data_scope.update_by = current_user.user.user_name - role_data_scope.update_time = datetime.now() - role_data_scope_result = await RoleService.role_datascope_services(query_db, role_data_scope) - if role_data_scope_result.is_success: - logger.info(role_data_scope_result.message) - return ResponseUtil.success(msg=role_data_scope_result.message) - else: - logger.warning(role_data_scope_result.message) - return ResponseUtil.failure(msg=role_data_scope_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.delete("/{role_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:role:remove'))]) -@log_decorator(title='角色管理', business_type=3) -async def delete_system_role(request: Request, role_ids: str, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - delete_role = DeleteRoleModel( - roleIds=role_ids, - updateBy=current_user.user.user_name, - updateTime=datetime.now() - ) - delete_role_result = await RoleService.delete_role_services(query_db, delete_role) - if delete_role_result.is_success: - logger.info(delete_role_result.message) - return ResponseUtil.success(msg=delete_role_result.message) - else: - logger.warning(delete_role_result.message) - return ResponseUtil.failure(msg=delete_role_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.get("/{role_id}", response_model=RoleModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))]) -async def query_detail_system_role(request: Request, role_id: int, query_db: AsyncSession = Depends(get_db)): - try: - role_detail_result = await RoleService.role_detail_services(query_db, role_id) - logger.info(f'获取role_id为{role_id}的信息成功') - return ResponseUtil.success(data=role_detail_result.model_dump(by_alias=True)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.post("/export", dependencies=[Depends(CheckUserInterfaceAuth('system:role:export'))]) -@log_decorator(title='角色管理', business_type=5) -async def export_system_role_list(request: Request, role_page_query: RolePageQueryModel = Depends(RolePageQueryModel.as_form), query_db: AsyncSession = Depends(get_db)): - try: - # 获取全量数据 - role_query_result = await RoleService.get_role_list_services(query_db, role_page_query, is_page=False) - role_export_result = await RoleService.export_role_list_services(role_query_result) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(role_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.put("/changeStatus", dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=2) -async def reset_system_role_status(request: Request, edit_role: AddRoleModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - edit_role.update_by = current_user.user.user_name - edit_role.update_time = datetime.now() - edit_role.type = 'status' - edit_role_result = await RoleService.edit_role_services(query_db, edit_role) - if edit_role_result.is_success: - logger.info(edit_role_result.message) - return ResponseUtil.success(msg=edit_role_result.message) - else: - logger.warning(edit_role_result.message) - return ResponseUtil.failure(msg=edit_role_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.get("/authUser/allocatedList", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - role_user_allocated_page_query_result = await RoleService.get_role_user_allocated_list_services(query_db, user_role, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=role_user_allocated_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.get("/authUser/unallocatedList", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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)): - try: - role_user_unallocated_page_query_result = await RoleService.get_role_user_unallocated_list_services(query_db, user_role, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=role_user_unallocated_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.put("/authUser/selectAll", dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=4) -async def add_system_role_user(request: Request, add_role_user: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query), query_db: AsyncSession = Depends(get_db)): - try: - add_role_user_result = await UserService.add_user_role_services(query_db, add_role_user) - if add_role_user_result.is_success: - logger.info(add_role_user_result.message) - return ResponseUtil.success(msg=add_role_user_result.message) - else: - logger.warning(add_role_user_result.message) - return ResponseUtil.failure(msg=add_role_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.put("/authUser/cancel", dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=4) -async def cancel_system_role_user(request: Request, cancel_user_role: CrudUserRoleModel, query_db: AsyncSession = Depends(get_db)): - try: - cancel_user_role_result = await UserService.delete_user_role_services(query_db, cancel_user_role) - if cancel_user_role_result.is_success: - logger.info(cancel_user_role_result.message) - return ResponseUtil.success(msg=cancel_user_role_result.message) - else: - logger.warning(cancel_user_role_result.message) - return ResponseUtil.failure(msg=cancel_user_role_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@roleController.put("/authUser/cancelAll", dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=4) -async def batch_cancel_system_role_user(request: Request, batch_cancel_user_role: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query), query_db: AsyncSession = Depends(get_db)): - try: - batch_cancel_user_role_result = await UserService.delete_user_role_services(query_db, batch_cancel_user_role) - if batch_cancel_user_role_result.is_success: - logger.info(batch_cancel_user_role_result.message) - return ResponseUtil.success(msg=batch_cancel_user_role_result.message) - else: - logger.warning(batch_cancel_user_role_result.message) - return ResponseUtil.failure(msg=batch_cancel_user_role_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) +@roleController.get('/deptTree/{role_id}', dependencies=[Depends(CheckUserInterfaceAuth('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_dept_query_result = await RoleService.get_role_dept_tree_services(query_db, role_id) + role_dept_query_result.depts = dept_query_result + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_dept_query_result) + + +@roleController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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_result = await RoleService.get_role_list_services( + query_db, role_page_query, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_page_query_result) + + +@roleController.post('', dependencies=[Depends(CheckUserInterfaceAuth('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), +): + add_role.create_by = current_user.user.user_name + add_role.create_time = datetime.now() + add_role.update_by = current_user.user.user_name + add_role.update_time = datetime.now() + add_role_result = await RoleService.add_role_services(query_db, add_role) + logger.info(add_role_result.message) + + return ResponseUtil.success(msg=add_role_result.message) + + +@roleController.put('', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + edit_role.update_by = current_user.user.user_name + edit_role.update_time = datetime.now() + edit_role_result = await RoleService.edit_role_services(query_db, edit_role) + logger.info(edit_role_result.message) + + return ResponseUtil.success(msg=edit_role_result.message) + + +@roleController.put('/dataScope', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + edit_role = AddRoleModel( + roleId=role_data_scope.role_id, + dataScope=role_data_scope.data_scope, + deptIds=role_data_scope.dept_ids, + deptCheckStrictly=role_data_scope.dept_check_strictly, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + ) + role_data_scope_result = await RoleService.role_datascope_services(query_db, edit_role) + logger.info(role_data_scope_result.message) + + return ResponseUtil.success(msg=role_data_scope_result.message) + + +@roleController.delete('/{role_ids}', dependencies=[Depends(CheckUserInterfaceAuth('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_id_list = role_ids.split(',') if role_ids else [] + if role_id_list: + for role_id in role_id_list: + await RoleService.check_role_allowed_services(RoleModel(roleId=int(role_id))) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, role_id, data_scope_sql) + delete_role = DeleteRoleModel(roleIds=role_ids, updateBy=current_user.user.user_name, updateTime=datetime.now()) + delete_role_result = await RoleService.delete_role_services(query_db, delete_role) + logger.info(delete_role_result.message) + + return ResponseUtil.success(msg=delete_role_result.message) + + +@roleController.get( + '/{role_id}', response_model=RoleModel, dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + logger.info(f'获取role_id为{role_id}的信息成功') + + return ResponseUtil.success(data=role_detail_result.model_dump(by_alias=True)) + + +@roleController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:role:export'))]) +@Log(title='角色管理', business_type=BusinessType.EXPORT) +async def export_system_role_list( + request: Request, + role_page_query: RolePageQueryModel = Depends(RolePageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + # 获取全量数据 + role_query_result = await RoleService.get_role_list_services( + query_db, role_page_query, data_scope_sql, is_page=False + ) + role_export_result = await RoleService.export_role_list_services(role_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(role_export_result)) + + +@roleController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + edit_role = AddRoleModel( + roleId=change_role.role_id, + status=change_role.status, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='status', + ) + edit_role_result = await RoleService.edit_role_services(query_db, edit_role) + logger.info(edit_role_result.message) + + return ResponseUtil.success(msg=edit_role_result.message) + + +@roleController.get( + '/authUser/allocatedList', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + role_user_allocated_page_query_result = await RoleService.get_role_user_allocated_list_services( + query_db, user_role, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_user_allocated_page_query_result) + + +@roleController.get( + '/authUser/unallocatedList', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + role_user_unallocated_page_query_result = await RoleService.get_role_user_unallocated_list_services( + query_db, user_role, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_user_unallocated_page_query_result) + + +@roleController.put('/authUser/selectAll', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + logger.info(add_role_user_result.message) + + return ResponseUtil.success(msg=add_role_user_result.message) + + +@roleController.put('/authUser/cancel', dependencies=[Depends(CheckUserInterfaceAuth('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) +): + 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'))]) +@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_result = await UserService.delete_user_role_services(query_db, batch_cancel_user_role) + logger.info(batch_cancel_user_role_result.message) + + return ResponseUtil.success(msg=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 8d040ff67014171d10e1acfec1510ea818f9dc94..f63fdf8010e1bd16dc228c5e3ad4a306100b5d97 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/server_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/server_controller.py @@ -1,22 +1,21 @@ -from fastapi import APIRouter, Request -from fastapi import Depends -from module_admin.service.login_service import LoginService -from module_admin.service.server_service import * -from utils.response_util import * -from utils.log_util import * +from fastapi import APIRouter, Depends, Request from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +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 serverController = APIRouter(prefix='/monitor/server', dependencies=[Depends(LoginService.get_current_user)]) -@serverController.get("", response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))]) +@serverController.get( + '', response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))] +) async def get_monitor_server_info(request: Request): - try: - # 获取全量数据 - server_info_query_result = await ServerService.get_server_monitor_info() - logger.info('获取成功') - return ResponseUtil.success(data=server_info_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + # 获取全量数据 + server_info_query_result = await ServerService.get_server_monitor_info() + logger.info('获取成功') + + return ResponseUtil.success(data=server_info_query_result) diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py index 93c13ca8be85599351208a87b308ca635da42afc..253535c4b0479e727ded50b689e6c934888379e9 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -1,173 +1,250 @@ -from fastapi import APIRouter, Request -from fastapi import Depends, File, Query +import os +from datetime import datetime +from fastapi import APIRouter, Depends, File, Query, Request, UploadFile +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional, Union +from pydantic_validation_decorator import ValidateFields from config.get_db import get_db 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.user_vo import ( + AddUserModel, + CrudUserRoleModel, + CurrentUserModel, + DeleteUserModel, + EditUserModel, + ResetPasswordModel, + ResetUserModel, + UserDetailModel, + UserInfoModel, + UserModel, + UserPageQueryModel, + UserProfileModel, + UserRoleQueryModel, + UserRoleResponseModel, +) from module_admin.service.login_service import LoginService -from module_admin.service.user_service import * +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 utils.page_util import PageResponseModel -from utils.response_util import * -from utils.log_util import * +from config.enums import BusinessType 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 -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.aspect.data_scope import GetDataScope -from module_admin.annotation.log_annotation import log_decorator userController = APIRouter(prefix='/system/user', dependencies=[Depends(LoginService.get_current_user)]) -@userController.get("/deptTree", dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))]) -async def get_system_dept_tree(request: Request, query_db: AsyncSession = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): - try: - dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql) - logger.info('获取成功') - return ResponseUtil.success(data=dept_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.get("/list", response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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'))): - try: - # 获取分页数据 - user_page_query_result = await UserService.get_user_list_services(query_db, user_page_query, data_scope_sql, is_page=True) - logger.info('获取成功') - return ResponseUtil.success(model_content=user_page_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.post("", dependencies=[Depends(CheckUserInterfaceAuth('system:user:add'))]) -@log_decorator(title='用户管理', business_type=1) -async def add_system_user(request: Request, add_user: AddUserModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - add_user.password = PwdUtil.get_password_hash(add_user.password) - add_user.create_by = current_user.user.user_name - add_user.create_time = datetime.now() - add_user.update_by = current_user.user.user_name - add_user.update_time = datetime.now() - add_user_result = await UserService.add_user_services(query_db, add_user) - if add_user_result.is_success: - logger.info(add_user_result.message) - return ResponseUtil.success(msg=add_user_result.message) - else: - logger.warning(add_user_result.message) - return ResponseUtil.failure(msg=add_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.put("", dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) -@log_decorator(title='用户管理', business_type=2) -async def edit_system_user(request: Request, edit_user: EditUserModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now() - edit_user_result = await UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return ResponseUtil.success(msg=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return ResponseUtil.failure(msg=edit_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.delete("/{user_ids}", dependencies=[Depends(CheckUserInterfaceAuth('system:user:remove'))]) -@log_decorator(title='用户管理', business_type=3) -async def delete_system_user(request: Request, user_ids: str, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - delete_user = DeleteUserModel( - userIds=user_ids, - updateBy=current_user.user.user_name, - updateTime=datetime.now() +@userController.get('/deptTree', dependencies=[Depends(CheckUserInterfaceAuth('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) + logger.info('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@userController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('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_result = await UserService.get_user_list_services( + query_db, user_page_query, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=user_page_query_result) + + +@userController.post('', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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( + query_db, ','.join([str(item) for item in add_user.role_ids]), role_data_scope_sql + ) + add_user.password = PwdUtil.get_password_hash(add_user.password) + add_user.create_by = current_user.user.user_name + add_user.create_time = datetime.now() + add_user.update_by = current_user.user.user_name + add_user.update_time = datetime.now() + add_user_result = await UserService.add_user_services(query_db, add_user) + logger.info(add_user_result.message) + + return ResponseUtil.success(msg=add_user_result.message) + + +@userController.put('', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + await DeptService.check_dept_data_scope_services(query_db, edit_user.dept_id, dept_data_scope_sql) + await RoleService.check_role_data_scope_services( + query_db, ','.join([str(item) for item in edit_user.role_ids]), role_data_scope_sql + ) + edit_user.update_by = current_user.user.user_name + edit_user.update_time = datetime.now() + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.delete('/{user_ids}', dependencies=[Depends(CheckUserInterfaceAuth('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_id_list = user_ids.split(',') if user_ids else [] + if user_id_list: + if current_user.user.user_id in user_id_list: + logger.warning('当前登录用户不能删除') + + return ResponseUtil.failure(msg='当前登录用户不能删除') + for user_id in user_id_list: + await UserService.check_user_allowed_services(UserModel(userId=int(user_id))) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, int(user_id), data_scope_sql) + delete_user = DeleteUserModel(userIds=user_ids, updateBy=current_user.user.user_name, updateTime=datetime.now()) + delete_user_result = await UserService.delete_user_services(query_db, delete_user) + logger.info(delete_user_result.message) + + return ResponseUtil.success(msg=delete_user_result.message) + + +@userController.put('/resetPwd', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + await UserService.check_user_allowed_services(reset_user) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, reset_user.user_id, data_scope_sql) + edit_user = EditUserModel( + userId=reset_user.user_id, + password=PwdUtil.get_password_hash(reset_user.password), + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='pwd', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + edit_user = EditUserModel( + userId=change_user.user_id, + status=change_user.status, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='status', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.get('/profile', response_model=UserProfileModel) +async def query_detail_system_user_profile( + request: Request, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + 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'))] +) +@userController.get( + '/', response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))] +) +async def query_detail_system_user( + request: Request, + user_id: Optional[Union[int, str]] = '', + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + 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) + logger.info(f'获取user_id为{user_id}的信息成功') + + return ResponseUtil.success(model_content=detail_user_result) + + +@userController.post('/profile/avatar') +@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), +): + if avatarfile: + relative_path = ( + f'avatar/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}' ) - delete_user_result = await UserService.delete_user_services(query_db, delete_user) - if delete_user_result.is_success: - logger.info(delete_user_result.message) - return ResponseUtil.success(msg=delete_user_result.message) - else: - logger.warning(delete_user_result.message) - return ResponseUtil.failure(msg=delete_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.put("/resetPwd", dependencies=[Depends(CheckUserInterfaceAuth('system:user:resetPwd'))]) -@log_decorator(title='用户管理', business_type=2) -async def reset_system_user_pwd(request: Request, edit_user: EditUserModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - edit_user.password = PwdUtil.get_password_hash(edit_user.password) - edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now() - edit_user.type = 'pwd' - edit_user_result = await UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return ResponseUtil.success(msg=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return ResponseUtil.failure(msg=edit_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.put("/changeStatus", dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) -@log_decorator(title='用户管理', business_type=2) -async def change_system_user_status(request: Request, edit_user: EditUserModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now() - edit_user.type = 'status' - edit_user_result = await UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return ResponseUtil.success(msg=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return ResponseUtil.failure(msg=edit_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.get("/profile", response_model=UserProfileModel) -async def query_detail_system_user(request: Request, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - 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) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.get("/{user_id}", response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))]) -@userController.get("/", response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))]) -async def query_detail_system_user(request: Request, user_id: Optional[Union[int, str]] = '', query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - detail_user_result = await UserService.user_detail_services(query_db, user_id) - logger.info(f'获取user_id为{user_id}的信息成功') - return ResponseUtil.success(model_content=detail_user_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.post("/profile/avatar") -@log_decorator(title='个人信息', business_type=2) -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)): - try: - relative_path = f'avatar/{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) @@ -182,136 +259,141 @@ async def change_system_user_profile_avatar(request: Request, avatarfile: bytes avatar=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{avatar_name}', updateBy=current_user.user.user_name, updateTime=datetime.now(), - type='avatar' - ) - edit_user_result = await UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return ResponseUtil.success(dict_content={'imgUrl': edit_user.avatar}, msg=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return ResponseUtil.failure(msg=edit_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.put("/profile") -@log_decorator(title='个人信息', business_type=2) -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)): - try: - edit_user = EditUserModel( - **user_info.model_dump( - exclude_unset=True, - by_alias=True, - exclude={'role_ids', 'post_ids'} - ), - userId=current_user.user.user_id, - userName=current_user.user.user_name, - updateBy=current_user.user.user_name, - updateTime=datetime.now(), - roleIds=current_user.user.role_ids.split(',') if current_user.user.role_ids else [], - postIds=current_user.user.post_ids.split(',') if current_user.user.post_ids else [], - role=current_user.user.role + type='avatar', ) edit_user_result = await UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return ResponseUtil.success(msg=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return ResponseUtil.failure(msg=edit_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.put("/profile/updatePwd") -@log_decorator(title='个人信息', business_type=2) -async def reset_system_user_password(request: Request, reset_password: ResetPasswordModel = Depends(ResetPasswordModel.as_query), query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): - try: - reset_user = ResetUserModel( - userId=current_user.user.user_id, - oldPassword=reset_password.old_password, - password=PwdUtil.get_password_hash(reset_password.new_password), - updateBy=current_user.user.user_name, - updateTime=datetime.now() - ) - reset_user_result = await UserService.reset_user_services(query_db, reset_user) - if reset_user_result.is_success: - logger.info(reset_user_result.message) - return ResponseUtil.success(msg=reset_user_result.message) - else: - logger.warning(reset_user_result.message) - return ResponseUtil.failure(msg=reset_user_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.post("/importData", dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) -@log_decorator(title='用户管理', business_type=6) -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)): - try: - batch_import_result = await UserService.batch_import_user_services(query_db, file, update_support, current_user) - if batch_import_result.is_success: - logger.info(batch_import_result.message) - return ResponseUtil.success(msg=batch_import_result.message) - else: - logger.warning(batch_import_result.message) - return ResponseUtil.failure(msg=batch_import_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.post("/importTemplate", dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) + logger.info(edit_user_result.message) + + return ResponseUtil.success(dict_content={'imgUrl': edit_user.avatar}, msg=edit_user_result.message) + return ResponseUtil.failure(msg='上传图片异常,请联系管理员') + + +@userController.put('/profile') +@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), +): + edit_user = EditUserModel( + **user_info.model_dump(exclude_unset=True, by_alias=True, exclude={'role_ids', 'post_ids'}), + userId=current_user.user.user_id, + userName=current_user.user.user_name, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + roleIds=current_user.user.role_ids.split(',') if current_user.user.role_ids else [], + postIds=current_user.user.post_ids.split(',') if current_user.user.post_ids else [], + role=current_user.user.role, + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.put('/profile/updatePwd') +@Log(title='个人信息', business_type=BusinessType.UPDATE) +async def reset_system_user_password( + request: Request, + reset_password: ResetPasswordModel = Depends(ResetPasswordModel.as_query), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + reset_user = ResetUserModel( + userId=current_user.user.user_id, + oldPassword=reset_password.old_password, + password=reset_password.new_password, + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + ) + reset_user_result = await UserService.reset_user_services(query_db, reset_user) + logger.info(reset_user_result.message) + + return ResponseUtil.success(msg=reset_user_result.message) + + +@userController.post('/importData', dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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 + ) + logger.info(batch_import_result.message) + + 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)): - try: - user_import_template_result = await UserService.get_user_import_template_services() - logger.info('获取成功') - return ResponseUtil.streaming(data=bytes2file_response(user_import_template_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.post("/export", dependencies=[Depends(CheckUserInterfaceAuth('system:user:export'))]) -@log_decorator(title='用户管理', business_type=5) -async def export_system_user_list(request: Request, user_page_query: UserPageQueryModel = Depends(UserPageQueryModel.as_form), query_db: AsyncSession = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysUser'))): - try: - # 获取全量数据 - user_query_result = await UserService.get_user_list_services(query_db, user_page_query, data_scope_sql, is_page=False) - user_export_result = await UserService.export_user_list_services(user_query_result) - logger.info('导出成功') - return ResponseUtil.streaming(data=bytes2file_response(user_export_result)) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.get("/authRole/{user_id}", response_model=UserRoleResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))]) + 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'))]) +@Log(title='用户管理', business_type=BusinessType.EXPORT) +async def export_system_user_list( + request: Request, + user_page_query: UserPageQueryModel = Depends(UserPageQueryModel.as_form), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + # 获取全量数据 + user_query_result = await UserService.get_user_list_services( + query_db, user_page_query, data_scope_sql, is_page=False + ) + user_export_result = await UserService.export_user_list_services(user_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(user_export_result)) + + +@userController.get( + '/authRole/{user_id}', + response_model=UserRoleResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))], +) async def get_system_allocated_role_list(request: Request, user_id: int, query_db: AsyncSession = Depends(get_db)): - try: - 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) - logger.info('获取成功') - return ResponseUtil.success(model_content=user_role_allocated_query_result) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) - - -@userController.put("/authRole", response_model=UserRoleResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) -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)): - try: - add_user_role_result = await UserService.add_user_role_services(query_db, CrudUserRoleModel(userId=user_id, roleIds=role_ids)) - if add_user_role_result.is_success: - logger.info(add_user_role_result.message) - return ResponseUtil.success(msg=add_user_role_result.message) - else: - logger.warning(add_user_role_result.message) - return ResponseUtil.failure(msg=add_user_role_result.message) - except Exception as e: - logger.exception(e) - return ResponseUtil.error(msg=str(e)) + 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 + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=user_role_allocated_query_result) + + +@userController.put( + '/authRole', + response_model=UserRoleResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('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')), +): + 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) + add_user_role_result = await UserService.add_user_role_services( + query_db, CrudUserRoleModel(userId=user_id, roleIds=role_ids) + ) + logger.info(add_user_role_result.message) + + return ResponseUtil.success(msg=add_user_role_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py index 79304c29f357f0cf83ffbc0f17d472fb2028970e..7b3c067847d85abd4cf3ba05e084dc3973c12c6c 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py @@ -1,9 +1,9 @@ -from sqlalchemy import select, update, delete +from datetime import datetime, time +from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.config_do import SysConfig -from module_admin.entity.vo.config_vo import * +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel from utils.page_util import PageUtil -from datetime import datetime, time class ConfigDao: @@ -15,14 +15,12 @@ class ConfigDao: async def get_config_detail_by_id(cls, db: AsyncSession, config_id: int): """ 根据参数配置id获取参数配置详细信息 + :param db: orm对象 :param config_id: 参数配置id :return: 参数配置信息对象 """ - config_info = (await db.execute( - select(SysConfig) - .where(SysConfig.config_id == config_id) - )).scalars().first() + config_info = (await db.execute(select(SysConfig).where(SysConfig.config_id == config_id))).scalars().first() return config_info @@ -30,15 +28,23 @@ class ConfigDao: async def get_config_detail_by_info(cls, db: AsyncSession, config: ConfigModel): """ 根据参数配置参数获取参数配置信息 + :param db: orm对象 :param config: 参数配置参数对象 :return: 参数配置信息对象 """ - config_info = (await db.execute( - select(SysConfig) - .where(SysConfig.config_key == config.config_key if config.config_key else True, - SysConfig.config_value == config.config_value if config.config_value else True) - )).scalars().first() + config_info = ( + ( + await db.execute( + select(SysConfig).where( + SysConfig.config_key == config.config_key if config.config_key else True, + SysConfig.config_value == config.config_value if config.config_value else True, + ) + ) + ) + .scalars() + .first() + ) return config_info @@ -46,20 +52,27 @@ class ConfigDao: async def get_config_list(cls, db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False): """ 根据查询参数获取参数配置列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 :return: 参数配置列表信息对象 """ - query = select(SysConfig) \ - .where(SysConfig.config_name.like(f'%{query_object.config_name}%') if query_object.config_name else True, - SysConfig.config_key.like(f'%{query_object.config_key}%') if query_object.config_key else True, - SysConfig.config_type == query_object.config_type if query_object.config_type else True, - SysConfig.create_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True) \ + query = ( + select(SysConfig) + .where( + SysConfig.config_name.like(f'%{query_object.config_name}%') if query_object.config_name else True, + SysConfig.config_key.like(f'%{query_object.config_key}%') if query_object.config_key else True, + SysConfig.config_type == query_object.config_type if query_object.config_type else True, + SysConfig.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) .distinct() + ) config_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return config_list @@ -68,6 +81,7 @@ class ConfigDao: async def add_config_dao(cls, db: AsyncSession, config: ConfigModel): """ 新增参数配置数据库操作 + :param db: orm对象 :param config: 参数配置对象 :return: @@ -82,24 +96,20 @@ class ConfigDao: async def edit_config_dao(cls, db: AsyncSession, config: dict): """ 编辑参数配置数据库操作 + :param db: orm对象 :param config: 需要更新的参数配置字典 :return: """ - await db.execute( - update(SysConfig), - [config] - ) + await db.execute(update(SysConfig), [config]) @classmethod async def delete_config_dao(cls, db: AsyncSession, config: ConfigModel): """ 删除参数配置数据库操作 + :param db: orm对象 :param config: 参数配置对象 :return: """ - await db.execute( - delete(SysConfig) - .where(SysConfig.config_id.in_([config.config_id])) - ) + await db.execute(delete(SysConfig).where(SysConfig.config_id.in_([config.config_id]))) diff --git a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py index c5169a511cf08e5d53522560dc53db89455f7879..f45021008ebde852a92f9caf055957183409e6b8 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py @@ -1,9 +1,11 @@ -from sqlalchemy import select, update, delete +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 -from module_admin.entity.vo.dept_vo import * -from utils.time_format_util import list_format_datetime +from module_admin.entity.do.role_do import SysRoleDept # noqa: F401 +from module_admin.entity.do.user_do import SysUser +from module_admin.entity.vo.dept_vo import DeptModel class DeptDao: @@ -15,32 +17,12 @@ class DeptDao: async def get_dept_by_id(cls, db: AsyncSession, dept_id: int): """ 根据部门id获取在用部门信息 - :param db: orm对象 - :param dept_id: 部门id - :return: 在用部门信息对象 - """ - dept_info = (await db.execute( - select(SysDept) - .where(SysDept.dept_id == dept_id, - SysDept.status == 0, - SysDept.del_flag == 0) - )).scalars().first() - return dept_info - - @classmethod - async def get_dept_by_id_for_list(cls, db: AsyncSession, dept_id: int): - """ - 用于获取部门列表的工具方法 :param db: orm对象 :param dept_id: 部门id - :return: 部门id对应的信息对象 + :return: 在用部门信息对象 """ - dept_info = (await db.execute( - select(SysDept) - .where(SysDept.dept_id == dept_id, - SysDept.del_flag == 0) - )).scalars().first() + dept_info = (await db.execute(select(SysDept).where(SysDept.dept_id == dept_id))).scalars().first() return dept_info @@ -48,15 +30,16 @@ class DeptDao: async def get_dept_detail_by_id(cls, db: AsyncSession, dept_id: int): """ 根据部门id获取部门详细信息 + :param db: orm对象 :param dept_id: 部门id :return: 部门信息对象 """ - dept_info = (await db.execute( - select(SysDept) - .where(SysDept.dept_id == dept_id, - SysDept.del_flag == 0) - )).scalars().first() + dept_info = ( + (await db.execute(select(SysDept).where(SysDept.dept_id == dept_id, SysDept.del_flag == '0'))) + .scalars() + .first() + ) return dept_info @@ -64,15 +47,23 @@ class DeptDao: async def get_dept_detail_by_info(cls, db: AsyncSession, dept: DeptModel): """ 根据部门参数获取部门信息 + :param db: orm对象 :param dept: 部门参数对象 :return: 部门信息对象 """ - dept_info = (await db.execute( - select(SysDept) - .where(SysDept.parent_id == dept.parent_id if dept.parent_id else True, - SysDept.dept_name == dept.dept_name if dept.dept_name else True) - )).scalars().first() + dept_info = ( + ( + await db.execute( + select(SysDept).where( + SysDept.parent_id == dept.parent_id if dept.parent_id else True, + SysDept.dept_name == dept.dept_name if dept.dept_name else True, + ) + ) + ) + .scalars() + .first() + ) return dept_info @@ -80,71 +71,77 @@ class DeptDao: async def get_dept_info_for_edit_option(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str): """ 获取部门编辑对应的在用部门列表信息 + :param db: orm对象 :param dept_info: 部门对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 部门列表信息 """ - dept_result = (await db.execute( - select(SysDept) - .where(SysDept.dept_id != dept_info.dept_id, - SysDept.parent_id != dept_info.dept_id, - SysDept.del_flag == 0, SysDept.status == 0, - eval(data_scope_sql)) - .order_by(SysDept.order_num) - .distinct() - )).scalars().all() + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.dept_id != dept_info.dept_id, + ~SysDept.dept_id.in_( + select(SysDept.dept_id).where(func.find_in_set(dept_info.dept_id, SysDept.ancestors)) + ), + SysDept.del_flag == '0', + SysDept.status == '0', + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) - return list_format_datetime(dept_result) + return dept_result @classmethod - async def get_children_dept(cls, db: AsyncSession, dept_id: int): + async def get_children_dept_dao(cls, db: AsyncSession, dept_id: int): """ 根据部门id查询当前部门的子部门列表信息 + :param db: orm对象 :param dept_id: 部门id :return: 子部门信息列表 """ - dept_result = (await db.execute( - select(SysDept) - .where(SysDept.parent_id == dept_id, - SysDept.del_flag == 0) - )).scalars().all() - - return list_format_datetime(dept_result) - - @classmethod - async def get_dept_all_ancestors(cls, db: AsyncSession): - """ - 获取所有部门的ancestors信息 - :param db: orm对象 - :return: ancestors信息列表 - """ - ancestors = (await db.execute( - select(SysDept.ancestors) - .where(SysDept.del_flag == 0) - )).scalars().all() + dept_result = ( + (await db.execute(select(SysDept).where(func.find_in_set(dept_id, SysDept.ancestors)))).scalars().all() + ) - return ancestors + return dept_result @classmethod async def get_dept_list_for_tree(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str): """ 获取所有在用部门列表信息 + :param db: orm对象 :param dept_info: 部门对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 在用部门列表信息 """ - dept_result = (await db.execute( - select(SysDept) - .where(SysDept.status == 0, - SysDept.del_flag == 0, - SysDept.dept_name.like(f'%{dept_info.dept_name}%') if dept_info.dept_name else True, - eval(data_scope_sql)) - .order_by(SysDept.order_num) - .distinct() - )).scalars().all() + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.status == '0', + SysDept.del_flag == '0', + SysDept.dept_name.like(f'%{dept_info.dept_name}%') if dept_info.dept_name else True, + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) return dept_result @@ -152,20 +149,30 @@ class DeptDao: async def get_dept_list(cls, db: AsyncSession, page_object: DeptModel, data_scope_sql: str): """ 根据查询参数获取部门列表信息 + :param db: orm对象 :param page_object: 不分页查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 部门列表信息对象 """ - dept_result = (await db.execute( - select(SysDept) - .where(SysDept.del_flag == 0, - SysDept.status == page_object.status if page_object.status else True, - SysDept.dept_name.like(f'%{page_object.dept_name}%') if page_object.dept_name else True, - eval(data_scope_sql)) - .order_by(SysDept.order_num) - .distinct() - )).scalars().all() + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.del_flag == '0', + SysDept.dept_id == page_object.dept_id if page_object.dept_id is not None else True, + SysDept.status == page_object.status if page_object.status else True, + SysDept.dept_name.like(f'%{page_object.dept_name}%') if page_object.dept_name else True, + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) return dept_result @@ -173,6 +180,7 @@ class DeptDao: async def add_dept_dao(cls, db: AsyncSession, dept: DeptModel): """ 新增部门数据库操作 + :param db: orm对象 :param dept: 部门对象 :return: 新增校验结果 @@ -187,25 +195,113 @@ class DeptDao: async def edit_dept_dao(cls, db: AsyncSession, dept: dict): """ 编辑部门数据库操作 + :param db: orm对象 :param dept: 需要更新的部门字典 :return: 编辑校验结果 """ + await db.execute(update(SysDept), [dept]) + + @classmethod + async def update_dept_children_dao(cls, db: AsyncSession, update_dept: List): + """ + 更新子部门信息 + + :param db: orm对象 + :param update_dept: 需要更新的部门列表 + :return: + """ await db.execute( - update(SysDept), - [dept] + update(SysDept) + .where(SysDept.dept_id == bindparam('dept_id')) + .values( + { + 'dept_id': bindparam('dept_id'), + 'ancestors': bindparam('ancestors'), + } + ), + update_dept, + execution_options=immutabledict({'synchronize_session': None}), ) + @classmethod + async def update_dept_status_normal_dao(cls, db: AsyncSession, dept_id_list: List): + """ + 批量更新部门状态为正常 + + :param db: orm对象 + :param dept_id_list: 部门id列表 + :return: + """ + 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): """ 删除部门数据库操作 + :param db: orm对象 :param dept: 部门对象 :return: """ await db.execute( update(SysDept) - .where(SysDept.dept_id == dept.dept_id) - .values(del_flag='2', update_by=dept.update_by, update_time=dept.update_time) + .where(SysDept.dept_id == dept.dept_id) + .values(del_flag='2', update_by=dept.update_by, update_time=dept.update_time) ) + + @classmethod + async def count_normal_children_dept_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询所有子部门(正常状态)的数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 所有子部门(正常状态)的数量 + """ + normal_children_dept_count = ( + await db.execute( + select(func.count('*')) + .select_from(SysDept) + .where(SysDept.status == '0', SysDept.del_flag == '0', func.find_in_set(dept_id, SysDept.ancestors)) + ) + ).scalar() + + return normal_children_dept_count + + @classmethod + async def count_children_dept_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询所有子部门(所有状态)的数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 所有子部门(所有状态)的数量 + """ + children_dept_count = ( + await db.execute( + select(func.count('*')) + .select_from(SysDept) + .where(SysDept.del_flag == '0', SysDept.parent_id == dept_id) + .limit(1) + ) + ).scalar() + + return children_dept_count + + @classmethod + async def count_dept_user_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询部门下的用户数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 部门下的用户数量 + """ + dept_user_count = ( + await db.execute( + select(func.count('*')).select_from(SysUser).where(SysUser.dept_id == dept_id, SysUser.del_flag == '0') + ) + ).scalar() + + return dept_user_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py index fd98c959a6881c84a3f52312408405322adcb19c..8f4aab221714345d545dee6edd4f981be81631c1 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py @@ -1,10 +1,10 @@ -from sqlalchemy import select, update, delete, and_ +from datetime import datetime, time +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 module_admin.entity.vo.dict_vo import * -from utils.time_format_util import list_format_datetime +from module_admin.entity.vo.dict_vo import DictDataModel, DictDataPageQueryModel, DictTypeModel, DictTypePageQueryModel from utils.page_util import PageUtil -from datetime import datetime, time +from utils.time_format_util import list_format_datetime class DictTypeDao: @@ -16,14 +16,12 @@ class DictTypeDao: async def get_dict_type_detail_by_id(cls, db: AsyncSession, dict_id: int): """ 根据字典类型id获取字典类型详细信息 + :param db: orm对象 :param dict_id: 字典类型id :return: 字典类型信息对象 """ - dict_type_info = (await db.execute( - select(SysDictType) - .where(SysDictType.dict_id == dict_id) - )).scalars().first() + dict_type_info = (await db.execute(select(SysDictType).where(SysDictType.dict_id == dict_id))).scalars().first() return dict_type_info @@ -31,15 +29,23 @@ class DictTypeDao: async def get_dict_type_detail_by_info(cls, db: AsyncSession, dict_type: DictTypeModel): """ 根据字典类型参数获取字典类型信息 + :param db: orm对象 :param dict_type: 字典类型参数对象 :return: 字典类型信息对象 """ - dict_type_info = (await db.execute( - select(SysDictType) - .where(SysDictType.dict_type == dict_type.dict_type if dict_type.dict_type else True, - SysDictType.dict_name == dict_type.dict_name if dict_type.dict_name else True) - )).scalars().first() + dict_type_info = ( + ( + await db.execute( + select(SysDictType).where( + SysDictType.dict_type == dict_type.dict_type if dict_type.dict_type else True, + SysDictType.dict_name == dict_type.dict_name if dict_type.dict_name else True, + ) + ) + ) + .scalars() + .first() + ) return dict_type_info @@ -47,12 +53,11 @@ class DictTypeDao: async def get_all_dict_type(cls, db: AsyncSession): """ 获取所有的字典类型信息 + :param db: orm对象 :return: 字典类型信息列表对象 """ - dict_type_info = (await db.execute( - select(SysDictType) - )).scalars().all() + dict_type_info = (await db.execute(select(SysDictType))).scalars().all() return list_format_datetime(dict_type_info) @@ -60,20 +65,27 @@ class DictTypeDao: async def get_dict_type_list(cls, db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False): """ 根据查询参数获取字典类型列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 :return: 字典类型列表信息对象 """ - query = select(SysDictType) \ - .where(SysDictType.dict_name.like(f'%{query_object.dict_name}%') if query_object.dict_name else True, - SysDictType.dict_type.like(f'%{query_object.dict_type}%') if query_object.dict_type else True, - SysDictType.status == query_object.status if query_object.status else True, - SysDictType.create_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True) \ + query = ( + select(SysDictType) + .where( + SysDictType.dict_name.like(f'%{query_object.dict_name}%') if query_object.dict_name else True, + SysDictType.dict_type.like(f'%{query_object.dict_type}%') if query_object.dict_type else True, + SysDictType.status == query_object.status if query_object.status else True, + SysDictType.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) .distinct() + ) dict_type_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return dict_type_list @@ -82,6 +94,7 @@ class DictTypeDao: async def add_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel): """ 新增字典类型数据库操作 + :param db: orm对象 :param dict_type: 字典类型对象 :return: @@ -96,27 +109,23 @@ class DictTypeDao: async def edit_dict_type_dao(cls, db: AsyncSession, dict_type: dict): """ 编辑字典类型数据库操作 + :param db: orm对象 :param dict_type: 需要更新的字典类型字典 :return: """ - await db.execute( - update(SysDictType), - [dict_type] - ) + await db.execute(update(SysDictType), [dict_type]) @classmethod async def delete_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel): """ 删除字典类型数据库操作 + :param db: orm对象 :param dict_type: 字典类型对象 :return: """ - await db.execute( - delete(SysDictType) - .where(SysDictType.dict_id.in_([dict_type.dict_id])) - ) + await db.execute(delete(SysDictType).where(SysDictType.dict_id.in_([dict_type.dict_id]))) class DictDataDao: @@ -128,14 +137,14 @@ class DictDataDao: async def get_dict_data_detail_by_id(cls, db: AsyncSession, dict_code: int): """ 根据字典数据id获取字典数据详细信息 + :param db: orm对象 :param dict_code: 字典数据id :return: 字典数据信息对象 """ - dict_data_info = (await db.execute( - select(SysDictData) - .where(SysDictData.dict_code == dict_code) - )).scalars().first() + dict_data_info = ( + (await db.execute(select(SysDictData).where(SysDictData.dict_code == dict_code))).scalars().first() + ) return dict_data_info @@ -143,16 +152,24 @@ class DictDataDao: async def get_dict_data_detail_by_info(cls, db: AsyncSession, dict_data: DictDataModel): """ 根据字典数据参数获取字典数据信息 + :param db: orm对象 :param dict_data: 字典数据参数对象 :return: 字典数据信息对象 """ - dict_data_info = (await db.execute( - select(SysDictData) - .where(SysDictData.dict_type == dict_data.dict_type if dict_data.dict_type else True, - SysDictData.dict_label == dict_data.dict_label if dict_data.dict_label else True, - SysDictData.dict_value == dict_data.dict_value if dict_data.dict_value else True) - )).scalars().first() + dict_data_info = ( + ( + await db.execute( + select(SysDictData).where( + SysDictData.dict_type == dict_data.dict_type, + SysDictData.dict_label == dict_data.dict_label, + SysDictData.dict_value == dict_data.dict_value, + ) + ) + ) + .scalars() + .first() + ) return dict_data_info @@ -160,17 +177,22 @@ class DictDataDao: async def get_dict_data_list(cls, db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False): """ 根据查询参数获取字典数据列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 :return: 字典数据列表信息对象 """ - query = select(SysDictData) \ - .where(SysDictData.dict_type == query_object.dict_type if query_object.dict_type else True, - SysDictData.dict_label.like(f'%{query_object.dict_label}%') if query_object.dict_label else True, - SysDictData.status == query_object.status if query_object.status else True)\ - .order_by(SysDictData.dict_sort)\ + query = ( + select(SysDictData) + .where( + SysDictData.dict_type == query_object.dict_type if query_object.dict_type else True, + SysDictData.dict_label.like(f'%{query_object.dict_label}%') if query_object.dict_label else True, + SysDictData.status == query_object.status if query_object.status else True, + ) + .order_by(SysDictData.dict_sort) .distinct() + ) dict_data_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return dict_data_list @@ -179,18 +201,29 @@ class DictDataDao: async def query_dict_data_list(cls, db: AsyncSession, dict_type: str): """ 根据查询参数获取字典数据列表信息 + :param db: orm对象 :param dict_type: 字典类型 :return: 字典数据列表信息对象 """ - dict_data_list = (await db.execute( - select(SysDictData) - .select_from(SysDictType) - .where(SysDictType.dict_type == dict_type if dict_type else True, SysDictType.status == 0) - .join(SysDictData, and_(SysDictType.dict_type == SysDictData.dict_type, SysDictData.status == 0), isouter=True) - .order_by(SysDictData.dict_sort) - .distinct() - )).scalars().all() + dict_data_list = ( + ( + await db.execute( + select(SysDictData) + .select_from(SysDictType) + .where(SysDictType.dict_type == dict_type if dict_type else True, SysDictType.status == '0') + .join( + SysDictData, + and_(SysDictType.dict_type == SysDictData.dict_type, SysDictData.status == '0'), + isouter=True, + ) + .order_by(SysDictData.dict_sort) + .distinct() + ) + ) + .scalars() + .all() + ) return dict_data_list @@ -198,11 +231,12 @@ class DictDataDao: async def add_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel): """ 新增字典数据数据库操作 + :param db: orm对象 :param dict_data: 字典数据对象 :return: """ - db_data_type = SysDictData(**dict_data.dict()) + db_data_type = SysDictData(**dict_data.model_dump()) db.add(db_data_type) await db.flush() @@ -212,24 +246,35 @@ class DictDataDao: async def edit_dict_data_dao(cls, db: AsyncSession, dict_data: dict): """ 编辑字典数据数据库操作 + :param db: orm对象 :param dict_data: 需要更新的字典数据字典 :return: """ - await db.execute( - update(SysDictData), - [dict_data] - ) + await db.execute(update(SysDictData), [dict_data]) @classmethod async def delete_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel): """ 删除字典数据数据库操作 + :param db: orm对象 :param dict_data: 字典数据对象 :return: """ - await db.execute( - delete(SysDictData) - .where(SysDictData.dict_code.in_([dict_data.dict_code])) - ) + 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): + """ + 根据字典类型查询字典类型关联的字典数据数量 + + :param db: orm对象 + :param dict_type: 字典类型 + :return: 字典类型关联的字典数据数量 + """ + dict_data_count = ( + await db.execute(select(func.count('*')).select_from(SysDictData).where(SysDictData.dict_type == dict_type)) + ).scalar() + + return dict_data_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py index cfd6982f6eaf27ecb6b2049a3c7ccb5ae3b55744..7f4f4a34e99bbd84b08c2ae32bad9f7fc2eee3a7 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py @@ -1,7 +1,7 @@ -from sqlalchemy import select, update, delete +from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.job_do import SysJob -from module_admin.entity.vo.job_vo import * +from module_admin.entity.vo.job_vo import JobModel, JobPageQueryModel from utils.page_util import PageUtil @@ -14,14 +14,12 @@ class JobDao: async def get_job_detail_by_id(cls, db: AsyncSession, job_id: int): """ 根据定时任务id获取定时任务详细信息 + :param db: orm对象 :param job_id: 定时任务id :return: 定时任务信息对象 """ - job_info = (await db.execute( - select(SysJob) - .where(SysJob.job_id == job_id) - )).scalars().first() + job_info = (await db.execute(select(SysJob).where(SysJob.job_id == job_id))).scalars().first() return job_info @@ -29,17 +27,28 @@ class JobDao: async def get_job_detail_by_info(cls, db: AsyncSession, job: JobModel): """ 根据定时任务参数获取定时任务信息 + :param db: orm对象 :param job: 定时任务参数对象 :return: 定时任务信息对象 """ - job_info = (await db.execute( - select(SysJob) - .where(SysJob.job_name == job.job_name if job.job_name else True, - SysJob.job_group == job.job_group if job.job_group else True, - SysJob.invoke_target == job.invoke_target if job.invoke_target else True, - SysJob.cron_expression == job.cron_expression if job.cron_expression else True) - )).scalars().first() + job_info = ( + ( + await db.execute( + select(SysJob).where( + SysJob.job_name == job.job_name, + SysJob.job_group == job.job_group, + SysJob.job_executor == job.job_executor, + SysJob.invoke_target == job.invoke_target, + SysJob.job_args == job.job_args, + SysJob.job_kwargs == job.job_kwargs, + SysJob.cron_expression == job.cron_expression, + ) + ) + ) + .scalars() + .first() + ) return job_info @@ -47,16 +56,21 @@ class JobDao: async def get_job_list(cls, db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False): """ 根据查询参数获取定时任务列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 :return: 定时任务列表信息对象 """ - query = select(SysJob) \ - .where(SysJob.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, - SysJob.job_group == query_object.job_group if query_object.job_group else True, - SysJob.status == query_object.status if query_object.status else True) \ + query = ( + select(SysJob) + .where( + SysJob.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, + SysJob.job_group == query_object.job_group if query_object.job_group else True, + SysJob.status == query_object.status if query_object.status else True, + ) .distinct() + ) job_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return job_list @@ -65,14 +79,11 @@ class JobDao: async def get_job_list_for_scheduler(cls, db: AsyncSession): """ 获取定时任务列表信息 + :param db: orm对象 :return: 定时任务列表信息对象 """ - job_list = (await db.execute( - select(SysJob) - .where(SysJob.status == 0) - .distinct() - )).scalars().all() + job_list = (await db.execute(select(SysJob).where(SysJob.status == '0').distinct())).scalars().all() return job_list @@ -80,6 +91,7 @@ class JobDao: async def add_job_dao(cls, db: AsyncSession, job: JobModel): """ 新增定时任务数据库操作 + :param db: orm对象 :param job: 定时任务对象 :return: @@ -94,24 +106,20 @@ class JobDao: async def edit_job_dao(cls, db: AsyncSession, job: dict): """ 编辑定时任务数据库操作 + :param db: orm对象 :param job: 需要更新的定时任务字典 :return: """ - await db.execute( - update(SysJob), - [job] - ) + await db.execute(update(SysJob), [job]) @classmethod async def delete_job_dao(cls, db: AsyncSession, job: JobModel): """ 删除定时任务数据库操作 + :param db: orm对象 :param job: 定时任务对象 :return: """ - await db.execute( - delete(SysJob) - .where(SysJob.job_id.in_([job.job_id])) - ) + await db.execute(delete(SysJob).where(SysJob.job_id.in_([job.job_id]))) 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 7bc8abe3871a32a20f180a073ca79cf0299af4db..730be5aa285daae3aef29dade1bee41315cec3e8 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py @@ -1,10 +1,10 @@ -from sqlalchemy import select, delete +from datetime import datetime, time +from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session from module_admin.entity.do.job_do import SysJobLog -from module_admin.entity.vo.job_vo import * +from module_admin.entity.vo.job_vo import JobLogModel, JobLogPageQueryModel from utils.page_util import PageUtil -from datetime import datetime, time class JobLogDao: @@ -16,20 +16,27 @@ class JobLogDao: async def get_job_log_list(cls, db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False): """ 根据查询参数获取定时任务日志列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 :return: 定时任务日志列表信息对象 """ - query = select(SysJobLog) \ - .where(SysJobLog.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, - SysJobLog.job_group == query_object.job_group if query_object.job_group else True, - SysJobLog.status == query_object.status if query_object.status else True, - SysJobLog.create_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True) \ + query = ( + select(SysJobLog) + .where( + SysJobLog.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, + SysJobLog.job_group == query_object.job_group if query_object.job_group else True, + SysJobLog.status == query_object.status if query_object.status else True, + SysJobLog.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) .distinct() + ) job_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return job_log_list @@ -38,6 +45,7 @@ class JobLogDao: def add_job_log_dao(cls, db: Session, job_log: JobLogModel): """ 新增定时任务日志数据库操作 + :param db: orm对象 :param job_log: 定时任务日志对象 :return: @@ -52,22 +60,19 @@ class JobLogDao: async def delete_job_log_dao(cls, db: AsyncSession, job_log: JobLogModel): """ 删除定时任务日志数据库操作 + :param db: orm对象 :param job_log: 定时任务日志对象 :return: """ - await db.execute( - delete(SysJobLog) - .where(SysJobLog.job_log_id.in_([job_log.job_log_id])) - ) + 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): """ 清除定时任务日志数据库操作 + :param db: orm对象 :return: """ - await db.execute( - delete(SysJobLog) - ) + await db.execute(delete(SysJobLog)) diff --git a/ruoyi-fastapi-backend/module_admin/dao/log_dao.py b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py index 5161bea3eec4b77022522ff32485285db367765d..1e4ad41eb21dd11f40422a0759d6f6ea21e9bd97 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/log_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py @@ -1,10 +1,10 @@ -from sqlalchemy import select, update, delete, asc, desc +from datetime import datetime, time +from sqlalchemy import asc, delete, desc, select from sqlalchemy.ext.asyncio import AsyncSession -from module_admin.entity.do.log_do import SysOperLog, SysLogininfor -from module_admin.entity.vo.log_vo import * -from utils.page_util import PageUtil +from module_admin.entity.do.log_do import SysLogininfor, SysOperLog +from module_admin.entity.vo.log_vo import LogininforModel, LoginLogPageQueryModel, OperLogModel, OperLogPageQueryModel from utils.common_util import SnakeCaseUtil -from datetime import datetime, time +from utils.page_util import PageUtil class OperationLogDao: @@ -16,6 +16,7 @@ class OperationLogDao: async def get_operation_log_list(cls, db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False): """ 根据查询参数获取操作日志列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -25,20 +26,27 @@ class OperationLogDao: order_by_column = asc(getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)) elif query_object.is_asc == 'descending': order_by_column = desc( - getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)) + getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) else: order_by_column = desc(SysOperLog.oper_time) - query = select(SysOperLog) \ - .where(SysOperLog.title.like(f'%{query_object.title}%') if query_object.title else True, - SysOperLog.oper_name.like(f'%{query_object.oper_name}%') if query_object.oper_name else True, - SysOperLog.business_type == query_object.business_type if query_object.business_type else True, - SysOperLog.status == query_object.status if query_object.status else True, - SysOperLog.oper_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True) \ - .distinct() \ + query = ( + select(SysOperLog) + .where( + SysOperLog.title.like(f'%{query_object.title}%') if query_object.title else True, + SysOperLog.oper_name.like(f'%{query_object.oper_name}%') if query_object.oper_name else True, + SysOperLog.business_type == query_object.business_type if query_object.business_type else True, + SysOperLog.status == query_object.status if query_object.status else True, + SysOperLog.oper_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() .order_by(order_by_column) + ) operation_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return operation_log_list @@ -47,6 +55,7 @@ class OperationLogDao: async def add_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel): """ 新增操作日志数据库操作 + :param db: orm对象 :param operation_log: 操作日志对象 :return: 新增校验结果 @@ -61,25 +70,22 @@ class OperationLogDao: async def delete_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel): """ 删除操作日志数据库操作 + :param db: orm对象 :param operation_log: 操作日志对象 :return: """ - await db.execute( - delete(SysOperLog) - .where(SysOperLog.oper_id.in_([operation_log.oper_id])) - ) + await db.execute(delete(SysOperLog).where(SysOperLog.oper_id.in_([operation_log.oper_id]))) @classmethod async def clear_operation_log_dao(cls, db: AsyncSession): """ 清除操作日志数据库操作 + :param db: orm对象 :return: """ - await db.execute( - delete(SysOperLog) - ) + await db.execute(delete(SysOperLog)) class LoginLogDao: @@ -91,6 +97,7 @@ class LoginLogDao: async def get_login_log_list(cls, db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False): """ 根据查询参数获取登录日志列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -98,22 +105,30 @@ class LoginLogDao: """ if query_object.is_asc == 'ascending': order_by_column = asc( - getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)) + getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) elif query_object.is_asc == 'descending': order_by_column = desc( - getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)) + getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) else: order_by_column = desc(SysLogininfor.login_time) - query = select(SysLogininfor) \ - .where(SysLogininfor.ipaddr.like(f'%{query_object.ipaddr}%') if query_object.ipaddr else True, - SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, - SysLogininfor.status == query_object.status if query_object.status else True, - SysLogininfor.login_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True) \ - .distinct() \ + query = ( + select(SysLogininfor) + .where( + SysLogininfor.ipaddr.like(f'%{query_object.ipaddr}%') if query_object.ipaddr else True, + SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, + SysLogininfor.status == query_object.status if query_object.status else True, + SysLogininfor.login_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() .order_by(order_by_column) + ) login_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return login_log_list @@ -122,6 +137,7 @@ class LoginLogDao: async def add_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel): """ 新增登录日志数据库操作 + :param db: orm对象 :param login_log: 登录日志对象 :return: 新增校验结果 @@ -136,22 +152,19 @@ class LoginLogDao: async def delete_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel): """ 删除登录日志数据库操作 + :param db: orm对象 :param login_log: 登录日志对象 :return: """ - await db.execute( - delete(SysLogininfor) - .where(SysLogininfor.info_id.in_([login_log.info_id])) - ) + await db.execute(delete(SysLogininfor).where(SysLogininfor.info_id.in_([login_log.info_id]))) @classmethod async def clear_login_log_dao(cls, db: AsyncSession): """ 清除登录日志数据库操作 + :param db: orm对象 :return: """ - await db.execute( - delete(SysLogininfor) - ) + await db.execute(delete(SysLogininfor)) diff --git a/ruoyi-fastapi-backend/module_admin/dao/login_dao.py b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py index 2c80c7fc07503b94a5702ad7c4e029c700c8f9fb..9764a4a0ef1d5ca901ec58f347697f932943a3e9 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/login_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py @@ -1,21 +1,28 @@ -from sqlalchemy import select, and_ +from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession -from module_admin.entity.do.user_do import SysUser 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): """ 根据用户名查询用户信息 + :param db: orm对象 :param user_name: 用户名 :return: 用户对象 """ - user = (await db.execute( - select(SysUser, SysDept) + user = ( + await db.execute( + select(SysUser, SysDept) .where(SysUser.user_name == user_name, SysUser.del_flag == '0') - .join(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0), isouter=True) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + isouter=True, + ) .distinct() - )).first() + ) + ).first() return user diff --git a/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py index 7f8545b64db5be5c53e7ea920b1dc4ca9083fe94..19831e015255114a46b390330591ebfa6c11e613 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py @@ -1,9 +1,9 @@ -from sqlalchemy import select, update, delete, and_ +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.user_do import SysUser, SysUserRole from module_admin.entity.do.role_do import SysRole, SysRoleMenu -from module_admin.entity.vo.menu_vo import * +from module_admin.entity.do.user_do import SysUser, SysUserRole +from module_admin.entity.vo.menu_vo import MenuModel, MenuQueryModel class MenuDao: @@ -15,14 +15,12 @@ class MenuDao: async def get_menu_detail_by_id(cls, db: AsyncSession, menu_id: int): """ 根据菜单id获取菜单详细信息 + :param db: orm对象 :param menu_id: 菜单id :return: 菜单信息对象 """ - menu_info = (await db.execute( - select(SysMenu) - .where(SysMenu.menu_id == menu_id) - )).scalars().first() + menu_info = (await db.execute(select(SysMenu).where(SysMenu.menu_id == menu_id))).scalars().first() return menu_info @@ -30,16 +28,24 @@ class MenuDao: async def get_menu_detail_by_info(cls, db: AsyncSession, menu: MenuModel): """ 根据菜单参数获取菜单信息 + :param db: orm对象 :param menu: 菜单参数对象 :return: 菜单信息对象 """ - menu_info = (await db.execute( - select(SysMenu) - .where(SysMenu.parent_id == menu.parent_id if menu.parent_id else True, - SysMenu.menu_name == menu.menu_name if menu.menu_name else True, - SysMenu.menu_type == menu.menu_type if menu.menu_type else True) - )).scalars().first() + menu_info = ( + ( + await db.execute( + select(SysMenu).where( + SysMenu.parent_id == menu.parent_id if menu.parent_id else True, + SysMenu.menu_name == menu.menu_name if menu.menu_name else True, + SysMenu.menu_type == menu.menu_type if menu.menu_type else True, + ) + ) + ) + .scalars() + .first() + ) return menu_info @@ -47,6 +53,7 @@ class MenuDao: async def get_menu_list_for_tree(cls, db: AsyncSession, user_id: int, role: list): """ 根据角色信息获取所有在用菜单列表信息 + :param db: orm对象 :param user_id: 用户id :param role: 用户角色列表信息 @@ -54,26 +61,35 @@ class MenuDao: """ role_id_list = [item.role_id for item in role] if 1 in role_id_list: - menu_query_all = (await db.execute( - select(SysMenu) - .where(SysMenu.status == 0) - .order_by(SysMenu.order_num) - .distinct() - )).scalars().all() + menu_query_all = ( + (await db.execute(select(SysMenu).where(SysMenu.status == '0').order_by(SysMenu.order_num).distinct())) + .scalars() + .all() + ) else: - menu_query_all = (await db.execute( - select(SysMenu) - .select_from(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) - .join(SysRole, - and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0), - isouter=True) - .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) - .order_by(SysMenu.order_num) - .distinct() - )).scalars().all() + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) return menu_query_all @@ -81,6 +97,7 @@ class MenuDao: async def get_menu_list(cls, db: AsyncSession, page_object: MenuQueryModel, user_id: int, role: list): """ 根据查询参数获取菜单列表信息 + :param db: orm对象 :param page_object: 不分页查询参数对象 :param user_id: 用户id @@ -89,31 +106,52 @@ class MenuDao: """ role_id_list = [item.role_id for item in role] if 1 in role_id_list: - menu_query_all = (await db.execute( - select(SysMenu) - .where(SysMenu.status == page_object.status if page_object.status else True, - SysMenu.menu_name.like( - f'%{page_object.menu_name}%') if page_object.menu_name else True) - .order_by(SysMenu.order_num) - .distinct() - )).scalars().all() + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .where( + SysMenu.status == page_object.status if page_object.status else True, + SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True, + ) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) else: - menu_query_all = (await db.execute( - select(SysMenu) - .select_from(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) - .join(SysRole, - and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0), - isouter=True) - .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, - SysMenu.status == page_object.status if page_object.status else True, - SysMenu.menu_name.like( - f'%{page_object.menu_name}%') if page_object.menu_name else True)) - .order_by(SysMenu.order_num) - .distinct() - )).scalars().all() + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join( + SysMenu, + and_( + SysRoleMenu.menu_id == SysMenu.menu_id, + SysMenu.status == page_object.status if page_object.status else True, + SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True, + ), + ) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) return menu_query_all @@ -121,6 +159,7 @@ class MenuDao: async def add_menu_dao(cls, db: AsyncSession, menu: MenuModel): """ 新增菜单数据库操作 + :param db: orm对象 :param menu: 菜单对象 :return: @@ -135,24 +174,50 @@ class MenuDao: async def edit_menu_dao(cls, db: AsyncSession, menu: dict): """ 编辑菜单数据库操作 + :param db: orm对象 :param menu: 需要更新的菜单字典 :return: """ - await db.execute( - update(SysMenu), - [menu] - ) + await db.execute(update(SysMenu), [menu]) @classmethod async def delete_menu_dao(cls, db: AsyncSession, menu: MenuModel): """ 删除菜单数据库操作 + :param db: orm对象 :param menu: 菜单对象 :return: """ - await db.execute( - delete(SysMenu) - .where(SysMenu.menu_id.in_([menu.menu_id])) - ) + 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): + """ + 根据菜单id查询菜单关联子菜单的数量 + + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单关联子菜单的数量 + """ + menu_count = ( + await db.execute(select(func.count('*')).select_from(SysMenu).where(SysMenu.parent_id == menu_id)) + ).scalar() + + return menu_count + + @classmethod + async def check_menu_exist_role_dao(cls, db: AsyncSession, menu_id: int): + """ + 根据菜单id查询菜单关联角色数量 + + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单关联角色数量 + """ + role_count = ( + await db.execute(select(func.count('*')).select_from(SysRoleMenu).where(SysRoleMenu.menu_id == menu_id)) + ).scalar() + + return role_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py index fd1846f9dc0eeb6d9968a9caa23b72d028ca44e9..961a9921c160a00ce5a1f4084b525018fc33bf49 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py @@ -1,9 +1,9 @@ -from sqlalchemy import select, update, delete +from datetime import datetime, time +from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.notice_do import SysNotice -from module_admin.entity.vo.notice_vo import * +from module_admin.entity.vo.notice_vo import NoticeModel, NoticePageQueryModel from utils.page_util import PageUtil -from datetime import datetime, time class NoticeDao: @@ -15,14 +15,12 @@ class NoticeDao: async def get_notice_detail_by_id(cls, db: AsyncSession, notice_id: int): """ 根据通知公告id获取通知公告详细信息 + :param db: orm对象 :param notice_id: 通知公告id :return: 通知公告信息对象 """ - notice_info = (await db.execute( - select(SysNotice) - .where(SysNotice.notice_id == notice_id) - )).scalars().first() + notice_info = (await db.execute(select(SysNotice).where(SysNotice.notice_id == notice_id))).scalars().first() return notice_info @@ -30,16 +28,24 @@ class NoticeDao: async def get_notice_detail_by_info(cls, db: AsyncSession, notice: NoticeModel): """ 根据通知公告参数获取通知公告信息 + :param db: orm对象 :param notice: 通知公告参数对象 :return: 通知公告信息对象 """ - notice_info = (await db.execute( - select(SysNotice) - .where(SysNotice.notice_title == notice.notice_title if notice.notice_title else True, - SysNotice.notice_type == notice.notice_type if notice.notice_type else True, - SysNotice.notice_content == notice.notice_content if notice.notice_content else True) - )).scalars().first() + notice_info = ( + ( + await db.execute( + select(SysNotice).where( + SysNotice.notice_title == notice.notice_title, + SysNotice.notice_type == notice.notice_type, + SysNotice.notice_content == notice.notice_content, + ) + ) + ) + .scalars() + .first() + ) return notice_info @@ -47,20 +53,27 @@ class NoticeDao: async def get_notice_list(cls, db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = False): """ 根据查询参数获取通知公告列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 :return: 通知公告列表信息对象 """ - query = select(SysNotice) \ - .where(SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True, - SysNotice.create_by.like(f'%{query_object.create_by}%') if query_object.create_by else True, - SysNotice.notice_type == query_object.notice_type if query_object.notice_type else True, - SysNotice.create_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True) \ + query = ( + select(SysNotice) + .where( + SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True, + SysNotice.create_by.like(f'%{query_object.create_by}%') if query_object.create_by else True, + SysNotice.notice_type == query_object.notice_type if query_object.notice_type else True, + SysNotice.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) .distinct() + ) notice_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return notice_list @@ -69,6 +82,7 @@ class NoticeDao: async def add_notice_dao(cls, db: AsyncSession, notice: NoticeModel): """ 新增通知公告数据库操作 + :param db: orm对象 :param notice: 通知公告对象 :return: @@ -83,24 +97,20 @@ class NoticeDao: async def edit_notice_dao(cls, db: AsyncSession, notice: dict): """ 编辑通知公告数据库操作 + :param db: orm对象 :param notice: 需要更新的通知公告字典 :return: """ - await db.execute( - update(SysNotice), - [notice] - ) + await db.execute(update(SysNotice), [notice]) @classmethod async def delete_notice_dao(cls, db: AsyncSession, notice: NoticeModel): """ 删除通知公告数据库操作 + :param db: orm对象 :param notice: 通知公告对象 :return: """ - await db.execute( - delete(SysNotice) - .where(SysNotice.notice_id.in_([notice.notice_id])) - ) + await db.execute(delete(SysNotice).where(SysNotice.notice_id.in_([notice.notice_id]))) diff --git a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py index d485d38c2357be10c646a83c782611fd31ff2826..7d90088b607b63c48ea292b534a7f6703c296b8a 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py @@ -1,7 +1,8 @@ -from sqlalchemy import select, update, delete +from sqlalchemy import delete, func, select, update from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.post_do import SysPost -from module_admin.entity.vo.post_vo import * +from module_admin.entity.do.user_do import SysUserPost +from module_admin.entity.vo.post_vo import PostModel, PostPageQueryModel from utils.page_util import PageUtil @@ -14,15 +15,16 @@ class PostDao: async def get_post_by_id(cls, db: AsyncSession, post_id: int): """ 根据岗位id获取在用岗位详细信息 + :param db: orm对象 :param post_id: 岗位id :return: 在用岗位信息对象 """ - post_info = (await db.execute( - select(SysPost) - .where(SysPost.post_id == post_id, - SysPost.status == 0) - )).scalars().first() + post_info = ( + (await db.execute(select(SysPost).where(SysPost.post_id == post_id, SysPost.status == '0'))) + .scalars() + .first() + ) return post_info @@ -30,14 +32,12 @@ class PostDao: async def get_post_detail_by_id(cls, db: AsyncSession, post_id: int): """ 根据岗位id获取岗位详细信息 + :param db: orm对象 :param post_id: 岗位id :return: 岗位信息对象 """ - post_info = (await db.execute( - select(SysPost) - .where(SysPost.post_id == post_id) - )).scalars().first() + post_info = (await db.execute(select(SysPost).where(SysPost.post_id == post_id))).scalars().first() return post_info @@ -45,16 +45,24 @@ class PostDao: async def get_post_detail_by_info(cls, db: AsyncSession, post: PostModel): """ 根据岗位参数获取岗位信息 + :param db: orm对象 :param post: 岗位参数对象 :return: 岗位信息对象 """ - post_info = (await db.execute( - select(SysPost) - .where(SysPost.post_name == post.post_name if post.post_name else True, - SysPost.post_code == post.post_code if post.post_code else True, - SysPost.post_sort == post.post_sort if post.post_sort else True) - )).scalars().first() + post_info = ( + ( + await db.execute( + select(SysPost).where( + SysPost.post_name == post.post_name if post.post_name else True, + SysPost.post_code == post.post_code if post.post_code else True, + SysPost.post_sort == post.post_sort if post.post_sort else True, + ) + ) + ) + .scalars() + .first() + ) return post_info @@ -62,17 +70,22 @@ class PostDao: async def get_post_list(cls, db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False): """ 根据查询参数获取岗位列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 :return: 岗位列表信息对象 """ - query = select(SysPost) \ - .where(SysPost.post_code.like(f'%{query_object.post_code}%') if query_object.post_code else True, - SysPost.post_name.like(f'%{query_object.post_name}%') if query_object.post_name else True, - SysPost.status == query_object.status if query_object.status else True) \ - .order_by(SysPost.post_sort) \ + query = ( + select(SysPost) + .where( + SysPost.post_code.like(f'%{query_object.post_code}%') if query_object.post_code else True, + SysPost.post_name.like(f'%{query_object.post_name}%') if query_object.post_name else True, + SysPost.status == query_object.status if query_object.status else True, + ) + .order_by(SysPost.post_sort) .distinct() + ) post_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return post_list @@ -81,6 +94,7 @@ class PostDao: async def add_post_dao(cls, db: AsyncSession, post: PostModel): """ 新增岗位数据库操作 + :param db: orm对象 :param post: 岗位对象 :return: @@ -95,24 +109,35 @@ class PostDao: async def edit_post_dao(cls, db: AsyncSession, post: dict): """ 编辑岗位数据库操作 + :param db: orm对象 :param post: 需要更新的岗位字典 :return: """ - await db.execute( - update(SysPost), - [post] - ) + await db.execute(update(SysPost), [post]) @classmethod async def delete_post_dao(cls, db: AsyncSession, post: PostModel): """ 删除岗位数据库操作 + :param db: orm对象 :param post: 岗位对象 :return: """ - await db.execute( - delete(SysPost) - .where(SysPost.post_id.in_([post.post_id])) - ) + 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): + """ + 根据岗位id查询岗位关联的用户数量 + + :param db: orm对象 + :param post_id: 岗位id + :return: 岗位关联的用户数量 + """ + user_post_count = ( + await db.execute(select(func.count('*')).select_from(SysUserPost).where(SysUserPost.post_id == post_id)) + ).scalar() + + return user_post_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py index 9c435506719ab9b812c989069d45312381cbe978..534c7f34bd28a537e2c273083ed6b2f211d72547 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py @@ -1,10 +1,12 @@ -from sqlalchemy import select, update, delete, desc, func +from datetime import datetime, time +from sqlalchemy import and_, delete, desc, func, or_, select, update # noqa: F401 from sqlalchemy.ext.asyncio import AsyncSession -from module_admin.entity.do.role_do import SysRole, SysRoleMenu, SysRoleDept from module_admin.entity.do.dept_do import SysDept -from module_admin.entity.vo.role_vo import * +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.user_do import SysUser, SysUserRole +from module_admin.entity.vo.role_vo import RoleDeptModel, RoleMenuModel, RoleModel, RolePageQueryModel from utils.page_util import PageUtil -from datetime import datetime, time class RoleDao: @@ -16,16 +18,23 @@ class RoleDao: async def get_role_by_name(cls, db: AsyncSession, role_name: str): """ 根据角色名获取在用角色信息 + :param db: orm对象 :param role_name: 角色名 :return: 当前角色名的角色信息对象 """ - query_role_info = (await db.execute( - select(SysRole) - .where(SysRole.status == 0, SysRole.del_flag == 0, SysRole.role_name == role_name) - .order_by(desc(SysRole.create_time)) - .distinct() - )).scalars().first() + query_role_info = ( + ( + await db.execute( + select(SysRole) + .where(SysRole.status == '0', SysRole.del_flag == '0', SysRole.role_name == role_name) + .order_by(desc(SysRole.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_role_info @@ -33,18 +42,27 @@ class RoleDao: async def get_role_by_info(cls, db: AsyncSession, role: RoleModel): """ 根据角色参数获取角色信息 + :param db: orm对象 :param role: 角色参数 :return: 当前角色参数的角色信息对象 """ - query_role_info = (await db.execute( - select(SysRole) - .where(SysRole.del_flag == 0, - SysRole.role_name == role.role_name if role.role_name else True, - SysRole.role_key == role.role_key if role.role_key else True) - .order_by(desc(SysRole.create_time)) - .distinct() - )).scalars().first() + query_role_info = ( + ( + await db.execute( + select(SysRole) + .where( + SysRole.del_flag == '0', + SysRole.role_name == role.role_name if role.role_name else True, + SysRole.role_key == role.role_key if role.role_key else True, + ) + .order_by(desc(SysRole.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_role_info @@ -52,16 +70,20 @@ class RoleDao: async def get_role_by_id(cls, db: AsyncSession, role_id: int): """ 根据角色id获取在用角色信息 + :param db: orm对象 :param role_id: 角色id :return: 当前角色id的角色信息对象 """ - role_info = (await db.execute( - select(SysRole) - .where(SysRole.role_id == role_id, - SysRole.status == 0, - SysRole.del_flag == 0) - )).scalars().first() + role_info = ( + ( + await db.execute( + select(SysRole).where(SysRole.role_id == role_id, SysRole.status == '0', SysRole.del_flag == '0') + ) + ) + .scalars() + .first() + ) return role_info @@ -69,15 +91,16 @@ class RoleDao: async def get_role_detail_by_id(cls, db: AsyncSession, role_id: int): """ 根据role_id获取角色详细信息 + :param db: orm对象 :param role_id: 角色id :return: 当前role_id的角色信息对象 """ - query_role_info = (await db.execute( - select(SysRole) - .where(SysRole.del_flag == 0, SysRole.role_id == role_id) - .distinct() - )).scalars().first() + query_role_info = ( + (await db.execute(select(SysRole).where(SysRole.del_flag == '0', SysRole.role_id == role_id).distinct())) + .scalars() + .first() + ) return query_role_info @@ -85,36 +108,57 @@ class RoleDao: async def get_role_select_option_dao(cls, db: AsyncSession): """ 获取编辑页面对应的在用角色列表信息 + :param db: orm对象 :return: 角色列表信息 """ - role_info = (await db.execute( - select(SysRole) - .where(SysRole.role_id != 1, SysRole.status == 0, SysRole.del_flag == 0) - )).scalars().all() + role_info = ( + ( + await db.execute( + select(SysRole).where(SysRole.role_id != 1, SysRole.status == '0', SysRole.del_flag == '0') + ) + ) + .scalars() + .all() + ) return role_info @classmethod - async def get_role_list(cls, db: AsyncSession, query_object: RolePageQueryModel, is_page: bool = False): + async def get_role_list( + cls, db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据查询参数获取角色列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 :param is_page: 是否开启分页 :return: 角色列表信息对象 """ - query = select(SysRole) \ - .where(SysRole.del_flag == 0, - SysRole.role_name.like(f'%{query_object.role_name}%') if query_object.role_name else True, - SysRole.role_key.like(f'%{query_object.role_key}%') if query_object.role_key else True, - SysRole.status == query_object.status if query_object.status else True, - SysRole.create_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True) \ - .order_by(SysRole.role_sort) \ + query = ( + select(SysRole) + .join(SysUserRole, SysUserRole.role_id == SysRole.role_id, isouter=True) + .join(SysUser, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .where( + SysRole.del_flag == '0', + SysRole.role_id == query_object.role_id if query_object.role_id is not None else True, + SysRole.role_name.like(f'%{query_object.role_name}%') if query_object.role_name else True, + SysRole.role_key.like(f'%{query_object.role_key}%') if query_object.role_key else True, + SysRole.status == query_object.status if query_object.status else True, + SysRole.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + eval(data_scope_sql), + ) + .order_by(SysRole.role_sort) .distinct() + ) role_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return role_list @@ -123,6 +167,7 @@ class RoleDao: async def add_role_dao(cls, db: AsyncSession, role: RoleModel): """ 新增角色数据库操作 + :param db: orm对象 :param role: 角色对象 :return: @@ -137,42 +182,61 @@ class RoleDao: async def edit_role_dao(cls, db: AsyncSession, role: dict): """ 编辑角色数据库操作 + :param db: orm对象 :param role: 需要更新的角色字典 :return: """ - await db.execute( - update(SysRole), - [role] - ) + await db.execute(update(SysRole), [role]) @classmethod async def delete_role_dao(cls, db: AsyncSession, role: RoleModel): """ 删除角色数据库操作 + :param db: orm对象 :param role: 角色对象 :return: """ await db.execute( update(SysRole) - .where(SysRole.role_id == role.role_id) - .values(del_flag='2', update_by=role.update_by, update_time=role.update_time) + .where(SysRole.role_id == role.role_id) + .values(del_flag='2', update_by=role.update_by, update_time=role.update_time) ) @classmethod - async def get_role_menu_dao(cls, db: AsyncSession, role_id: int): + async def get_role_menu_dao(cls, db: AsyncSession, role: RoleModel): """ 根据角色id获取角色菜单关联列表信息 + :param db: orm对象 - :param role_id: 角色id + :param role: 角色对象 :return: 角色菜单关联列表信息 """ - role_menu_query_all = (await db.execute( - select(SysRoleMenu) - .where(SysRoleMenu.role_id == role_id) - .distinct() - )).scalars().all() + role_menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .join(SysRoleMenu, SysRoleMenu.menu_id == SysMenu.menu_id) + .where( + SysRoleMenu.role_id == role.role_id, + ~SysMenu.menu_id.in_( + select(SysMenu.parent_id) + .select_from(SysMenu) + .join( + SysRoleMenu, + and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysRoleMenu.role_id == role.role_id), + ) + ) + if role.menu_check_strictly + else True, + ) + .order_by(SysMenu.parent_id, SysMenu.order_num) + ) + ) + .scalars() + .all() + ) return role_menu_query_all @@ -180,6 +244,7 @@ class RoleDao: async def add_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel): """ 新增角色菜单关联信息数据库操作 + :param db: orm对象 :param role_menu: 用户角色菜单关联对象 :return: @@ -191,29 +256,46 @@ class RoleDao: async def delete_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel): """ 删除角色菜单关联信息数据库操作 + :param db: orm对象 :param role_menu: 角色菜单关联对象 :return: """ - await db.execute( - delete(SysRoleMenu) - .where(SysRoleMenu.role_id.in_([role_menu.role_id])) - ) + 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_id: int): + async def get_role_dept_dao(cls, db: AsyncSession, role: RoleModel): """ 根据角色id获取角色部门关联列表信息 + :param db: orm对象 - :param role_id: 角色id + :param role: 角色对象 :return: 角色部门关联列表信息 """ - role_dept_query_all = (await db.execute( - select(SysRoleDept) - .where(SysRoleDept.role_id == role_id, - ~select(SysDept).where(func.find_in_set(SysRoleDept.dept_id, SysDept.ancestors)).exists()) - .distinct() - )).scalars().all() + role_dept_query_all = ( + ( + await db.execute( + select(SysDept) + .join(SysRoleDept, SysRoleDept.dept_id == SysDept.dept_id) + .where( + SysRoleDept.role_id == role.role_id, + ~SysDept.dept_id.in_( + select(SysDept.parent_id) + .select_from(SysDept) + .join( + SysRoleDept, + and_(SysRoleDept.dept_id == SysDept.dept_id, SysRoleDept.role_id == role.role_id), + ) + ) + if role.dept_check_strictly + else True, + ) + .order_by(SysDept.parent_id, SysDept.order_num) + ) + ) + .scalars() + .all() + ) return role_dept_query_all @@ -221,6 +303,7 @@ class RoleDao: async def add_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel): """ 新增角色部门关联信息数据库操作 + :param db: orm对象 :param role_dept: 用户角色部门关联对象 :return: @@ -232,11 +315,24 @@ class RoleDao: async def delete_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel): """ 删除角色部门关联信息数据库操作 + :param db: orm对象 :param role_dept: 角色部门关联对象 :return: """ - await db.execute( - delete(SysRoleDept) - .where(SysRoleDept.role_id.in_([role_dept.role_id])) - ) + 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): + """ + 根据角色id查询角色关联用户数量 + + :param db: orm对象 + :param role_id: 角色id + :return: 角色关联用户数量 + """ + user_count = ( + await db.execute(select(func.count('*')).select_from(SysUserRole).where(SysUserRole.role_id == role_id)) + ).scalar() + + return user_count diff --git a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py index d73c6f08fccfac1b2f5154ce63ba653e296f8020..52a088c8fa4c43e9e201cc50c613ddc8d7fe8c2d 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py @@ -1,13 +1,20 @@ -from sqlalchemy import select, update, delete, and_, or_, desc, func +from datetime import datetime, time +from sqlalchemy import and_, delete, desc, func, or_, select, update from sqlalchemy.ext.asyncio import AsyncSession -from module_admin.entity.do.user_do import SysUser, SysUserRole, SysUserPost -from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu from module_admin.entity.do.dept_do import SysDept -from module_admin.entity.do.post_do import SysPost from module_admin.entity.do.menu_do import SysMenu -from module_admin.entity.vo.user_vo import * +from module_admin.entity.do.post_do import SysPost +from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu # noqa: F401 +from module_admin.entity.do.user_do import SysUser, SysUserPost, SysUserRole +from module_admin.entity.vo.user_vo import ( + UserModel, + UserPageQueryModel, + UserPostModel, + UserRoleModel, + UserRolePageQueryModel, + UserRoleQueryModel, +) from utils.page_util import PageUtil -from datetime import datetime, time class UserDao: @@ -19,16 +26,23 @@ class UserDao: async def get_user_by_name(cls, db: AsyncSession, user_name: str): """ 根据用户名获取用户信息 + :param db: orm对象 :param user_name: 用户名 :return: 当前用户名的用户信息对象 """ - query_user_info = (await db.execute( - select(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_name == user_name) - .order_by(desc(SysUser.create_time)) - .distinct() - )).scalars().first() + query_user_info = ( + ( + await db.execute( + select(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_name == user_name) + .order_by(desc(SysUser.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_user_info @@ -36,16 +50,28 @@ class UserDao: async def get_user_by_info(cls, db: AsyncSession, user: UserModel): """ 根据用户参数获取用户信息 + :param db: orm对象 :param user: 用户参数 :return: 当前用户参数的用户信息对象 """ - query_user_info = (await db.execute( - select(SysUser) - .where(SysUser.del_flag == 0, SysUser.user_name == user.user_name) - .order_by(desc(SysUser.create_time)) - .distinct() - )).scalars().first() + query_user_info = ( + ( + await db.execute( + select(SysUser) + .where( + SysUser.del_flag == '0', + SysUser.user_name == user.user_name if user.user_name else True, + SysUser.phonenumber == user.phonenumber if user.phonenumber else True, + SysUser.email == user.email if user.email else True, + ) + .order_by(desc(SysUser.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_user_info @@ -53,66 +79,105 @@ class UserDao: async def get_user_by_id(cls, db: AsyncSession, user_id: int): """ 根据user_id获取用户信息 + :param db: orm对象 :param user_id: 用户id :return: 当前user_id的用户信息对象 """ - query_user_basic_info = (await db.execute( - select(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) - .distinct() - )).scalars().first() - query_user_dept_info = (await db.execute( - select(SysDept) - .select_from(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) - .distinct() - )).scalars().first() - query_user_role_info = (await db.execute( - select(SysRole) - .select_from(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) - .join(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) - .distinct() - )).scalars().all() - query_user_post_info = (await db.execute( - select(SysPost) - .select_from(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) - .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == 0)) - .distinct() - )).scalars().all() - role_id_list = [item.role_id for item in query_user_role_info] - if 1 in role_id_list: - query_user_menu_info = (await db.execute( - select(SysMenu) - .where(SysMenu.status == 0) + query_user_basic_info = ( + ( + await db.execute( + select(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) .distinct() - )).scalars().all() - else: - query_user_menu_info = (await db.execute( - select(SysMenu) + ) + ) + .scalars() + .first() + ) + query_user_dept_info = ( + ( + await db.execute( + select(SysDept) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_role_info = ( + ( + await db.execute( + select(SysRole) .select_from(SysUser) - .where(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) - .join(SysRole, - and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0), - isouter=True) - .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) - .order_by(SysMenu.order_num) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_post_info = ( + ( + await db.execute( + select(SysPost) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0')) .distinct() - )).scalars().all() + ) + ) + .scalars() + .all() + ) + role_id_list = [item.role_id for item in query_user_role_info] + if 1 in role_id_list: + query_user_menu_info = ( + (await db.execute(select(SysMenu).where(SysMenu.status == '0').distinct())).scalars().all() + ) + else: + query_user_menu_info = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .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 + user_menu_info=query_user_menu_info, ) return results @@ -121,90 +186,140 @@ class UserDao: async def get_user_detail_by_id(cls, db: AsyncSession, user_id: int): """ 根据user_id获取用户详细信息 + :param db: orm对象 :param user_id: 用户id :return: 当前user_id的用户信息对象 """ - query_user_basic_info = (await db.execute( - select(SysUser) - .where(SysUser.del_flag == 0, SysUser.user_id == user_id) - .distinct() - )).scalars().first() - query_user_dept_info = (await db.execute( - select(SysDept) - .select_from(SysUser) - .where(SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) - .distinct() - )).scalars().first() - query_user_role_info = (await db.execute( - select(SysRole) - .select_from(SysUser) - .where(SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) - .join(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) - .distinct() - )).scalars().all() - query_user_post_info = (await db.execute( - select(SysPost) - .select_from(SysUser) - .where(SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) - .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == 0)) - .distinct() - )).scalars().all() - query_user_menu_info = (await db.execute( - select(SysMenu) - .select_from(SysUser) - .where(SysUser.del_flag == 0, SysUser.user_id == user_id) - .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) - .join(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0), - isouter=True) - .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) - .distinct() - )).scalars().all() + query_user_basic_info = ( + (await db.execute(select(SysUser).where(SysUser.del_flag == '0', SysUser.user_id == user_id).distinct())) + .scalars() + .first() + ) + query_user_dept_info = ( + ( + await db.execute( + select(SysDept) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_role_info = ( + ( + await db.execute( + select(SysRole) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_post_info = ( + ( + await db.execute( + select(SysPost) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0')) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_menu_info = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .distinct() + ) + ) + .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 + 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): + async def get_user_list( + cls, db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据查询参数获取用户列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 :param is_page: 是否开启分页 :return: 用户列表信息对象 """ - query = select(SysUser, SysDept) \ - .where(SysUser.del_flag == 0, - or_(SysUser.dept_id == query_object.dept_id, SysUser.dept_id.in_( - select(SysDept.dept_id).where(func.find_in_set(query_object.dept_id, SysDept.ancestors)) - )) if query_object.dept_id else True, - SysUser.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, - SysUser.nick_name.like(f'%{query_object.nick_name}%') if query_object.nick_name else True, - SysUser.email.like(f'%{query_object.email}%') if query_object.email else True, - SysUser.phonenumber.like(f'%{query_object.phonenumber}%') if query_object.phonenumber else True, - SysUser.status == query_object.status if query_object.status else True, - SysUser.sex == query_object.sex if query_object.sex else True, - SysUser.create_time.between( - datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.begin_time and query_object.end_time else True, - eval(data_scope_sql) - ) \ - .join(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0), - isouter=True) \ + query = ( + select(SysUser, SysDept) + .where( + SysUser.del_flag == '0', + or_( + SysUser.dept_id == query_object.dept_id, + SysUser.dept_id.in_( + select(SysDept.dept_id).where(func.find_in_set(query_object.dept_id, SysDept.ancestors)) + ), + ) + if query_object.dept_id + else True, + SysUser.user_id == query_object.user_id if query_object.user_id is not None else True, + SysUser.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, + SysUser.nick_name.like(f'%{query_object.nick_name}%') if query_object.nick_name else True, + SysUser.email.like(f'%{query_object.email}%') if query_object.email else True, + SysUser.phonenumber.like(f'%{query_object.phonenumber}%') if query_object.phonenumber else True, + SysUser.status == query_object.status if query_object.status else True, + SysUser.sex == query_object.sex if query_object.sex else True, + SysUser.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + eval(data_scope_sql), + ) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + isouter=True, + ) .distinct() + ) user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) return user_list @@ -213,6 +328,7 @@ class UserDao: async def add_user_dao(cls, db: AsyncSession, user: UserModel): """ 新增用户数据库操作 + :param db: orm对象 :param user: 用户对象 :return: 新增校验结果 @@ -227,95 +343,128 @@ class UserDao: async def edit_user_dao(cls, db: AsyncSession, user: dict): """ 编辑用户数据库操作 + :param db: orm对象 :param user: 需要更新的用户字典 :return: 编辑校验结果 """ - await db.execute( - update(SysUser), - [user] - ) + await db.execute(update(SysUser), [user]) @classmethod async def delete_user_dao(cls, db: AsyncSession, user: UserModel): """ 删除用户数据库操作 + :param db: orm对象 :param user: 用户对象 :return: """ await db.execute( update(SysUser) - .where(SysUser.user_id == user.user_id) - .values(del_flag='2', update_by=user.update_by, update_time=user.update_time) + .where(SysUser.user_id == user.user_id) + .values(del_flag='2', update_by=user.update_by, update_time=user.update_time) ) @classmethod async def get_user_role_allocated_list_by_user_id(cls, db: AsyncSession, query_object: UserRoleQueryModel): """ 根据用户id获取用户已分配的角色列表信息数据库操作 + :param db: orm对象 :param query_object: 用户角色查询对象 :return: 用户已分配的角色列表信息 """ - allocated_role_list = (await db.execute( - select(SysRole) - .where(SysRole.del_flag == 0, - SysRole.role_id != 1, - SysRole.role_name == query_object.role_name if query_object.role_name else True, - SysRole.role_key == query_object.role_key if query_object.role_key else True, - SysRole.role_id.in_( - select(SysUserRole.role_id).where(SysUserRole.user_id == query_object.user_id) - )) - .distinct() - )).scalars().all() + allocated_role_list = ( + ( + await db.execute( + select(SysRole) + .where( + SysRole.del_flag == '0', + SysRole.role_id != 1, + SysRole.role_name == query_object.role_name if query_object.role_name else True, + SysRole.role_key == query_object.role_key if query_object.role_key else True, + SysRole.role_id.in_( + select(SysUserRole.role_id).where(SysUserRole.user_id == query_object.user_id) + ), + ) + .distinct() + ) + ) + .scalars() + .all() + ) return allocated_role_list @classmethod - async def get_user_role_allocated_list_by_role_id(cls, db: AsyncSession, query_object: UserRolePageQueryModel, - is_page: bool = False): + async def get_user_role_allocated_list_by_role_id( + cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据角色id获取已分配的用户列表信息 + :param db: orm对象 :param query_object: 用户角色查询对象 + :param data_scope_sql: 数据权限对应的查询sql语句 :param is_page: 是否开启分页 :return: 角色已分配的用户列表信息 """ - query = select(SysUser) \ - .where(SysUser.del_flag == 0, - SysUser.user_id != 1, - SysUser.user_name == query_object.user_name if query_object.user_name else True, - SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, - SysUser.user_id.in_( - select(SysUserRole.user_id).where(SysUserRole.role_id == query_object.role_id) - )) \ + query = ( + select(SysUser) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True) + .join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True) + .where( + SysUser.del_flag == '0', + SysUser.user_name == query_object.user_name if query_object.user_name else True, + SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, + SysRole.role_id == query_object.role_id, + eval(data_scope_sql), + ) .distinct() + ) allocated_user_list = 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, - is_page: bool = False): + async def get_user_role_unallocated_list_by_role_id( + cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据角色id获取未分配的用户列表信息 + :param db: orm对象 :param query_object: 用户角色查询对象 + :param data_scope_sql: 数据权限对应的查询sql语句 :param is_page: 是否开启分页 :return: 角色未分配的用户列表信息 """ - query = select(SysUser) \ - .where(SysUser.del_flag == 0, - SysUser.user_id != 1, - SysUser.user_name == query_object.user_name if query_object.user_name else True, - SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, - ~SysUser.user_id.in_( - select(SysUserRole.user_id).where(SysUserRole.role_id == query_object.role_id) - )) \ + query = ( + select(SysUser) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True) + .join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True) + .where( + SysUser.del_flag == '0', + SysUser.user_name == query_object.user_name if query_object.user_name else True, + SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, + or_(SysRole.role_id != query_object.role_id, SysRole.role_id.is_(None)), + ~SysUser.user_id.in_( + select(SysUser.user_id) + .select_from(SysUser) + .join( + SysUserRole, + and_(SysUserRole.user_id == SysUser.user_id, SysUserRole.role_id == query_object.role_id), + ) + ), + eval(data_scope_sql), + ) .distinct() - unallocated_user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, - is_page) + ) + unallocated_user_list = await PageUtil.paginate( + db, query, query_object.page_num, query_object.page_size, is_page + ) return unallocated_user_list @@ -323,6 +472,7 @@ class UserDao: async def add_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): """ 新增用户角色关联信息数据库操作 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: @@ -334,42 +484,49 @@ class UserDao: async def delete_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): """ 删除用户角色关联信息数据库操作 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: """ - await db.execute( - delete(SysUserRole) - .where(SysUserRole.user_id.in_([user_role.user_id])) - ) + 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): """ 根据用户id及角色id删除用户角色关联信息数据库操作 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: """ await db.execute( - delete(SysUserRole) - .where(SysUserRole.user_id.in_([user_role.user_id]), - SysUserRole.role_id == user_role.role_id if user_role.role_id else True) + delete(SysUserRole).where( + SysUserRole.user_id == user_role.user_id if user_role.user_id else True, + SysUserRole.role_id == user_role.role_id if user_role.role_id else True, + ) ) @classmethod async def get_user_role_detail(cls, db: AsyncSession, user_role: UserRoleModel): """ 根据用户角色关联获取用户角色关联详细信息 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: 用户角色关联信息 """ - user_role_info = (await db.execute( - select(SysUserRole) - .where(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id) - .distinct() - )).scalars().first() + user_role_info = ( + ( + await db.execute( + select(SysUserRole) + .where(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id) + .distinct() + ) + ) + .scalars() + .first() + ) return user_role_info @@ -377,6 +534,7 @@ class UserDao: async def add_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel): """ 新增用户岗位关联信息数据库操作 + :param db: orm对象 :param user_post: 用户岗位关联对象 :return: @@ -388,21 +546,22 @@ class UserDao: async def delete_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel): """ 删除用户岗位关联信息数据库操作 + :param db: orm对象 :param user_post: 用户岗位关联对象 :return: """ - await db.execute( - delete(SysUserPost) - .where(SysUserPost.user_id.in_([user_post.user_id])) - ) + 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): - dept_basic_info = (await db.execute( - select(SysDept) - .where(SysDept.dept_id == dept_id, - SysDept.status == 0, - SysDept.del_flag == 0) - )).scalars().first() + dept_basic_info = ( + ( + await db.execute( + select(SysDept).where(SysDept.dept_id == dept_id, SysDept.status == '0', SysDept.del_flag == '0') + ) + ) + .scalars() + .first() + ) return dept_basic_info 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 5fb6476005cd3f45aeed01c695e30fc8f5609fd6..32af3b0f737766a5f1f37da5e8d7c415beb88cb0 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysConfig(Base): """ 参数配置表 """ + __tablename__ = 'sys_config' config_id = Column(Integer, primary_key=True, autoincrement=True, comment='参数主键') @@ -18,4 +19,4 @@ class SysConfig(Base): create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, default='', comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default='', comment='备注') \ No newline at end of file + remark = Column(String(500), nullable=True, default='', comment='备注') 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 05729270974100678e662fbb1d7689309677e0bc..96ac8daf6e999a8de60e9f654ef6b77899ba9371 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysDept(Base): """ 部门表 """ + __tablename__ = 'sys_dept' dept_id = Column(Integer, primary_key=True, autoincrement=True, comment='部门id') 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 a7d630efad527c3748133ba961f4bc9027805280..061c88f2b2352a233a275c36700603f7bf6b1ae1 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime, UniqueConstraint -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint +from config.database import Base class SysDictType(Base): """ 字典类型表 """ + __tablename__ = 'sys_dict_type' dict_id = Column(Integer, primary_key=True, autoincrement=True, comment='字典主键') @@ -19,15 +20,14 @@ class SysDictType(Base): update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column(String(500), nullable=True, default='', comment='备注') - __table_args__ = ( - UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'), - ) + __table_args__ = (UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'),) class SysDictData(Base): """ 字典数据表 """ + __tablename__ = 'sys_dict_data' dict_code = Column(Integer, primary_key=True, autoincrement=True, comment='字典编码') 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 aef168faca0943a40797944c9c9b1c63e7fc8f11..c6d671b18a27ca86e13edc729b23f417602bf112 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py @@ -1,24 +1,36 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysJob(Base): """ 定时任务调度表 """ + __tablename__ = 'sys_job' job_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务ID') job_name = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务名称') job_group = Column(String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务组名') - job_executor = Column(String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器') + job_executor = Column( + String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器' + ) invoke_target = Column(String(500, collation='utf8_general_ci'), nullable=False, comment='调用目标字符串') job_args = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='位置参数') job_kwargs = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='关键字参数') - cron_expression = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='cron执行表达式') - misfire_policy = Column(String(20, collation='utf8_general_ci'), nullable=True, default='3', comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)') - concurrent = Column(String(1, collation='utf8_general_ci'), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)') + cron_expression = Column( + String(255, collation='utf8_general_ci'), nullable=True, default='', comment='cron执行表达式' + ) + misfire_policy = Column( + String(20, collation='utf8_general_ci'), + nullable=True, + default='3', + comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + ) + concurrent = Column( + String(1, collation='utf8_general_ci'), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)' + ) status = Column(String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='状态(0正常 1暂停)') create_by = Column(String(64, collation='utf8_general_ci'), nullable=True, default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') @@ -31,17 +43,22 @@ class SysJobLog(Base): """ 定时任务调度日志表 """ + __tablename__ = 'sys_job_log' job_log_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务日志ID') job_name = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务名称') job_group = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务组名') - job_executor = Column(String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器') + job_executor = Column( + String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器' + ) invoke_target = Column(String(500, collation='utf8_general_ci'), nullable=False, comment='调用目标字符串') job_args = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='位置参数') job_kwargs = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='关键字参数') job_trigger = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='任务触发器') job_message = Column(String(500, collation='utf8_general_ci'), nullable=True, default='', comment='日志信息') - status = Column(String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='执行状态(0正常 1失败)') + status = Column( + String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='执行状态(0正常 1失败)' + ) exception_info = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='异常信息') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') 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 19d2b187d8c579777fec80b996a29ab51ec3714a..f915207cf984f4c663ea4cb818fa5c99f9a8610d 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime, Text, BigInteger, Index -from config.database import Base from datetime import datetime +from sqlalchemy import BigInteger, Column, DateTime, Index, Integer, String +from config.database import Base class SysLogininfor(Base): """ 系统访问记录 """ + __tablename__ = 'sys_logininfor' info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID') @@ -15,7 +16,9 @@ class SysLogininfor(Base): login_location = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='登录地点') browser = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='浏览器类型') os = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='操作系统') - status = Column(String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='登录状态(0成功 1失败)') + status = Column( + String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='登录状态(0成功 1失败)' + ) msg = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='提示消息') login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间') @@ -27,6 +30,7 @@ class SysOperLog(Base): """ 操作日志记录 """ + __tablename__ = 'sys_oper_log' oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键') 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 8f98be48e0b75542fe209893747cce903506503e..d3ccffa5537b8b31ae73c7977dd6d444f49c2844 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysMenu(Base): """ 菜单权限表 """ + __tablename__ = 'sys_menu' menu_id = Column(Integer, primary_key=True, autoincrement=True, comment='菜单ID') @@ -16,6 +17,7 @@ class SysMenu(Base): path = Column(String(200), nullable=True, default='', comment='路由地址') component = Column(String(255), nullable=True, default=None, comment='组件路径') query = Column(String(255), nullable=True, default=None, comment='路由参数') + route_name = Column(String(50), nullable=True, default='', comment='路由名称') is_frame = Column(Integer, default=1, comment='是否为外链(0是 1否)') is_cache = Column(Integer, default=0, comment='是否缓存(0缓存 1不缓存)') menu_type = Column(String(1), nullable=True, default='', comment='菜单类型(M目录 C菜单 F按钮)') 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 38566584178aaa0a3105e497bc7159ea3935c839..125a40a8ef38766307ccd210e00bd1ad9fa5e9c1 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime, LargeBinary -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, LargeBinary, String +from config.database import Base class SysNotice(Base): """ 通知公告表 """ + __tablename__ = 'sys_notice' notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID') 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 c6b189b6cd9fe1d5adcf6a8e02e30979a85c3cb4..54ab38dcb0d3ee77b500286f6e7d731ac092dd78 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysPost(Base): """ 岗位信息表 """ + __tablename__ = 'sys_post' post_id = Column(Integer, primary_key=True, autoincrement=True, comment='岗位ID') 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 db29244e11f24e330683b8e10d992b9c4318593c..fc2e34d593ac8eb624f9288a9a2be32a7d8c3303 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py @@ -1,19 +1,24 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysRole(Base): """ 角色信息表 """ + __tablename__ = 'sys_role' role_id = Column(Integer, primary_key=True, autoincrement=True, comment='角色ID') role_name = Column(String(30, collation='utf8_general_ci'), nullable=False, comment='角色名称') role_key = Column(String(100, collation='utf8_general_ci'), nullable=False, comment='角色权限字符串') role_sort = Column(Integer, nullable=False, comment='显示顺序') - data_scope = Column(String(1, collation='utf8_general_ci'), default='1', comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)') + data_scope = Column( + String(1, collation='utf8_general_ci'), + default='1', + comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + ) menu_check_strictly = Column(Integer, default=1, comment='菜单树选择项是否关联显示') dept_check_strictly = Column(Integer, default=1, comment='部门树选择项是否关联显示') status = Column(String(1, collation='utf8_general_ci'), nullable=False, comment='角色状态(0正常 1停用)') @@ -29,6 +34,7 @@ class SysRoleDept(Base): """ 角色和部门关联表 """ + __tablename__ = 'sys_role_dept' role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') @@ -39,6 +45,7 @@ class SysRoleMenu(Base): """ 角色和菜单关联表 """ + __tablename__ = 'sys_role_menu' role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') 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 93515472f905e58d79b28ccc3f0aa9ffa24779ed..21bba84a7bdeab34378dd39d70439859c4cc8d1d 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysUser(Base): """ 用户信息表 """ + __tablename__ = 'sys_user' user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID') @@ -34,6 +35,7 @@ class SysUserRole(Base): """ 用户和角色关联表 """ + __tablename__ = 'sys_user_role' user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') @@ -44,6 +46,7 @@ class SysUserPost(Base): """ 用户与岗位关联表 """ + __tablename__ = 'sys_user_post' user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') 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 f2fc9ff24024e8e210cc657720b7e0f81f799c75..79b49fb8161d379253cafbc4d1c25164633248d9 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py @@ -1,26 +1,28 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from pydantic.alias_generators import to_camel -from typing import Optional, List, Any +from typing import Any, List, Optional class CacheMonitorModel(BaseModel): """ 缓存监控信息对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel) - command_stats: Optional[List] = [] - db_size: Optional[int] = None - info: Optional[dict] = {} + command_stats: Optional[List] = Field(default=[], description='命令统计') + db_size: Optional[int] = Field(default=None, description='Key数量') + info: Optional[dict] = Field(default={}, description='Redis信息') class CacheInfoModel(BaseModel): """ 缓存监控对象对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel) - cache_key: Optional[str] = None - cache_name: Optional[str] = None - cache_value: Optional[Any] = None - remark: Optional[str] = None + cache_key: Optional[str] = Field(default=None, description='缓存键名') + cache_name: Optional[str] = Field(default=None, description='缓存名称') + cache_value: Optional[Any] = Field(default=None, description='缓存内容') + remark: Optional[str] = Field(default=None, description='备注') 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 64e929ee5074f102e180bca45edb45c9b8c259d1..258be5d76394ff7dad5ef627c22ac2cd28edee65 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py @@ -1,24 +1,26 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from pydantic.alias_generators import to_camel -from typing import Optional, Any +from typing import Any, Optional class CrudResponseModel(BaseModel): """ 操作响应模型 """ - is_success: bool - message: str - result: Optional[Any] = None + + is_success: bool = Field(description='操作是否成功') + message: str = Field(description='响应信息') + result: Optional[Any] = Field(default=None, description='响应结果') class UploadResponseModel(BaseModel): """ 上传响应模型 """ + model_config = ConfigDict(alias_generator=to_camel) - file_name: Optional[str] = None - new_file_name: Optional[str] = None - original_filename: Optional[str] = None - url: Optional[str] = None + file_name: Optional[str] = Field(default=None, description='新文件映射路径') + new_file_name: Optional[str] = Field(default=None, description='新文件名称') + original_filename: Optional[str] = Field(default=None, description='原文件名称') + url: Optional[str] = Field(default=None, description='新文件url') 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 6c7ada7c49b4e0c17484eee2e31c9f95be569f8d..c398d238dc159ada74a65fe0cbfd37e51f5c8911 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py @@ -1,34 +1,57 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.annotation.pydantic_annotation import as_query, as_form +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_form, as_query class ConfigModel(BaseModel): """ 参数配置表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - config_id: Optional[int] = None - config_name: Optional[str] = None - config_key: Optional[str] = None - config_value: Optional[str] = None - config_type: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None + config_id: Optional[int] = Field(default=None, description='参数主键') + config_name: Optional[str] = Field(default=None, description='参数名称') + config_key: Optional[str] = Field(default=None, description='参数键名') + config_value: Optional[str] = Field(default=None, description='参数键值') + config_type: Optional[Literal['Y', 'N']] = Field(default=None, description='系统内置(Y是 N否)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='config_key', message='参数名称不能为空') + @Size(field_name='config_key', min_length=0, max_length=100, message='参数名称长度不能超过100个字符') + def get_config_key(self): + 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): + 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): + return self.config_value + + def validate_fields(self): + self.get_config_key() + self.get_config_name() + self.get_config_value() class ConfigQueryModel(ConfigModel): """ 参数配置管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -37,14 +60,16 @@ class ConfigPageQueryModel(ConfigQueryModel): """ 参数配置管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteConfigModel(BaseModel): """ 删除参数配置模型 """ + model_config = ConfigDict(alias_generator=to_camel) - config_ids: str + config_ids: str = Field(description='需要删除的参数主键') 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 9a8eb3e7fa50630f0416e950117d4df8374d8aef..dcad1177156a0698b540a382549d5f77bd80084a 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py @@ -1,7 +1,8 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime +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 @@ -9,22 +10,47 @@ class DeptModel(BaseModel): """ 部门表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - dept_id: Optional[int] = None - parent_id: Optional[int] = None - ancestors: Optional[str] = None - dept_name: Optional[str] = None - order_num: Optional[int] = None - leader: Optional[str] = None - phone: Optional[str] = None - email: Optional[str] = None - status: Optional[str] = None - del_flag: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None + dept_id: Optional[int] = Field(default=None, description='部门id') + parent_id: Optional[int] = Field(default=None, description='父部门id') + ancestors: Optional[str] = Field(default=None, description='祖级列表') + dept_name: Optional[str] = Field(default=None, description='部门名称') + order_num: Optional[int] = Field(default=None, description='显示顺序') + leader: Optional[str] = Field(default=None, description='负责人') + phone: Optional[str] = Field(default=None, description='联系电话') + email: Optional[str] = Field(default=None, description='邮箱') + status: Optional[Literal['0', '1']] = Field(default=None, description='部门状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + + @NotBlank(field_name='dept_name', message='部门名称不能为空') + @Size(field_name='dept_name', min_length=0, max_length=30, message='部门名称长度不能超过30个字符') + def get_dept_name(self): + return self.dept_name + + @NotBlank(field_name='order_num', message='显示顺序不能为空') + def get_order_num(self): + return self.order_num + + @Size(field_name='phone', min_length=0, max_length=11, message='联系电话长度不能超过11个字符') + def get_phone(self): + 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): + return self.email + + def validate_fields(self): + self.get_dept_name() + self.get_order_num() + self.get_phone() + self.get_email() @as_query @@ -32,16 +58,18 @@ class DeptQueryModel(DeptModel): """ 部门管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') class DeleteDeptModel(BaseModel): """ 删除部门模型 """ + model_config = ConfigDict(alias_generator=to_camel) - dept_ids: str - update_by: Optional[str] = None - update_time: Optional[str] = None + dept_ids: str = Field(default=None, description='需要删除的部门id') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[str] = Field(default=None, description='更新时间') 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 e4cda55c5c29ee5c0a110db16004ed0b8c69888a..49616749799379ffa917997aa5b228634ff33e7c 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py @@ -1,55 +1,103 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.annotation.pydantic_annotation import as_query, as_form +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_form, as_query class DictTypeModel(BaseModel): """ 字典类型表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - dict_id: Optional[int] = None - dict_name: Optional[str] = None - dict_type: Optional[str] = None - status: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None + dict_id: Optional[int] = Field(default=None, description='字典主键') + dict_name: Optional[str] = Field(default=None, description='字典名称') + dict_type: Optional[str] = Field(default=None, description='字典类型') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='dict_name', message='字典名称不能为空') + @Size(field_name='dict_name', min_length=0, max_length=100, message='字典类型名称长度不能超过100个字符') + def get_dict_name(self): + return self.dict_name + + @NotBlank(field_name='dict_type', message='字典类型不能为空') + @Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型类型长度不能超过100个字符') + @Pattern( + field_name='dict_type', + regexp='^[a-z][a-z0-9_]*$', + message='字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)', + ) + def get_dict_type(self): + return self.dict_type + + def validate_fields(self): + self.get_dict_name() + self.get_dict_type() class DictDataModel(BaseModel): """ 字典数据表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - dict_code: Optional[int] = None - dict_sort: Optional[int] = None - dict_label: Optional[str] = None - dict_value: Optional[str] = None - dict_type: Optional[str] = None - css_class: Optional[str] = None - list_class: Optional[str] = None - is_default: Optional[str] = None - status: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None + dict_code: Optional[int] = Field(default=None, description='字典编码') + dict_sort: Optional[int] = Field(default=None, description='字典排序') + dict_label: Optional[str] = Field(default=None, description='字典标签') + dict_value: Optional[str] = Field(default=None, description='字典键值') + dict_type: Optional[str] = Field(default=None, description='字典类型') + css_class: Optional[str] = Field(default=None, description='样式属性(其他样式扩展)') + list_class: Optional[str] = Field(default=None, description='表格回显样式') + is_default: Optional[Literal['Y', 'N']] = Field(default=None, description='是否默认(Y是 N否)') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='dict_label', message='字典标签不能为空') + @Size(field_name='dict_label', min_length=0, max_length=100, message='字典标签长度不能超过100个字符') + def get_dict_label(self): + 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): + 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): + return self.dict_type + + @Size(field_name='css_class', min_length=0, max_length=100, message='样式属性长度不能超过100个字符') + def get_css_class(self): + return self.css_class + + def validate_fields(self): + self.get_dict_label() + self.get_dict_value() + self.get_dict_type() + self.get_css_class() class DictTypeQueryModel(DictTypeModel): """ 字典类型管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -58,25 +106,28 @@ class DictTypePageQueryModel(DictTypeQueryModel): """ 字典类型管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteDictTypeModel(BaseModel): """ 删除字典类型模型 """ + model_config = ConfigDict(alias_generator=to_camel) - dict_ids: str + dict_ids: str = Field(description='需要删除的字典主键') class DictDataQueryModel(DictDataModel): """ 字典数据管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -85,14 +136,16 @@ class DictDataPageQueryModel(DictDataQueryModel): """ 字典数据管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteDictDataModel(BaseModel): """ 删除字典数据模型 """ + model_config = ConfigDict(alias_generator=to_camel) - dict_codes: str + dict_codes: str = Field(description='需要删除的字典编码') 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 f6f91d3967337e1495fd1c4e4453096434f0fd4b..4dd9e38a0ab8e13ab3ed5b47b084aaf38a690749 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py @@ -1,60 +1,80 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.annotation.pydantic_annotation import as_query, as_form +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_form, as_query class JobModel(BaseModel): """ 定时任务调度表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - job_id: Optional[int] = None - job_name: Optional[str] = None - job_group: Optional[str] = None - job_executor: Optional[str] = None - invoke_target: Optional[str] = None - job_args: Optional[str] = None - job_kwargs: Optional[str] = None - cron_expression: Optional[str] = None - misfire_policy: Optional[str] = None - concurrent: Optional[str] = None - status: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None + job_id: Optional[int] = Field(default=None, description='任务ID') + job_name: Optional[str] = Field(default=None, description='任务名称') + job_group: Optional[str] = Field(default=None, description='任务组名') + job_executor: Optional[str] = Field(default=None, description='任务执行器') + invoke_target: Optional[str] = Field(default=None, description='调用目标字符串') + job_args: Optional[str] = Field(default=None, description='位置参数') + job_kwargs: Optional[str] = Field(default=None, description='关键字参数') + cron_expression: Optional[str] = Field(default=None, description='cron执行表达式') + misfire_policy: Optional[Literal['1', '2', '3']] = Field( + default=None, description='计划执行错误策略(1立即执行 2执行一次 3放弃执行)' + ) + concurrent: Optional[Literal['0', '1']] = Field(default=None, description='是否并发执行(0允许 1禁止)') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1暂停)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注信息') + + @NotBlank(field_name='invoke_target', message='调用目标字符串不能为空') + @Size(field_name='invoke_target', min_length=0, max_length=500, message='调用目标字符串长度不能超过500个字符') + def get_invoke_target(self): + 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): + return self.cron_expression + + def validate_fields(self): + self.get_invoke_target() + self.get_cron_expression() class JobLogModel(BaseModel): """ 定时任务调度日志表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - job_log_id: Optional[int] = None - job_name: Optional[str] = None - job_group: Optional[str] = None - job_executor: Optional[str] = None - invoke_target: Optional[str] = None - job_args: Optional[str] = None - job_kwargs: Optional[str] = None - job_trigger: Optional[str] = None - job_message: Optional[str] = None - status: Optional[str] = None - exception_info: Optional[str] = None - create_time: Optional[datetime] = None + job_log_id: Optional[int] = Field(default=None, description='任务日志ID') + job_name: Optional[str] = Field(default=None, description='任务名称') + job_group: Optional[str] = Field(default=None, description='任务组名') + job_executor: Optional[str] = Field(default=None, description='任务执行器') + invoke_target: Optional[str] = Field(default=None, description='调用目标字符串') + job_args: Optional[str] = Field(default=None, description='位置参数') + job_kwargs: Optional[str] = Field(default=None, description='关键字参数') + job_trigger: Optional[str] = Field(default=None, description='任务触发器') + job_message: Optional[str] = Field(default=None, description='日志信息') + status: Optional[Literal['0', '1']] = Field(default=None, description='执行状态(0正常 1失败)') + exception_info: Optional[str] = Field(default=None, description='异常信息') + create_time: Optional[datetime] = Field(default=None, description='创建时间') class JobQueryModel(JobModel): """ 定时任务管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -63,32 +83,36 @@ class JobPageQueryModel(JobQueryModel): """ 定时任务管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class EditJobModel(JobModel): """ 编辑定时任务模型 """ - type: Optional[str] = None + + type: Optional[str] = Field(default=None, description='操作类型') class DeleteJobModel(BaseModel): """ 删除定时任务模型 """ + model_config = ConfigDict(alias_generator=to_camel) - job_ids: str + job_ids: str = Field(description='需要删除的定时任务ID') class JobLogQueryModel(JobLogModel): """ 定时任务日志不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -97,14 +121,16 @@ class JobLogPageQueryModel(JobLogQueryModel): """ 定时任务日志管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteJobLogModel(BaseModel): """ 删除定时任务日志模型 """ + model_config = ConfigDict(alias_generator=to_camel) - job_log_ids: str + job_log_ids: str = Field(description='需要删除的定时任务日志ID') 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 604bb5dff5ac488ecde8d125b2c9f018d7a7a1cc..18665132425f975f393de7ce456c0fc205488397 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py @@ -1,60 +1,71 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.annotation.pydantic_annotation import as_query, as_form +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_form, as_query class OperLogModel(BaseModel): """ 操作日志表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - oper_id: Optional[int] = None - title: Optional[str] = None - business_type: Optional[int] = None - method: Optional[str] = None - request_method: Optional[str] = None - operator_type: Optional[int] = None - oper_name: Optional[str] = None - dept_name: Optional[str] = None - oper_url: Optional[str] = None - oper_ip: Optional[str] = None - oper_location: Optional[str] = None - oper_param: Optional[str] = None - json_result: Optional[str] = None - status: Optional[int] = None - error_msg: Optional[str] = None - oper_time: Optional[datetime] = None - cost_time: Optional[int] = None + oper_id: Optional[int] = Field(default=None, description='日志主键') + title: Optional[str] = Field(default=None, description='模块标题') + business_type: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']] = ( + Field( + default=None, description='业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据)' + ) + ) + method: Optional[str] = Field(default=None, description='方法名称') + request_method: Optional[str] = Field(default=None, description='请求方式') + operator_type: Optional[Literal[0, 1, 2]] = Field( + default=None, description='操作类别(0其它 1后台用户 2手机端用户)' + ) + oper_name: Optional[str] = Field(default=None, description='操作人员') + dept_name: Optional[str] = Field(default=None, description='部门名称') + oper_url: Optional[str] = Field(default=None, description='请求URL') + oper_ip: Optional[str] = Field(default=None, description='主机地址') + oper_location: Optional[str] = Field(default=None, description='操作地点') + oper_param: Optional[str] = Field(default=None, description='请求参数') + json_result: Optional[str] = Field(default=None, description='返回参数') + status: Optional[Literal[0, 1, '0', '1']] = Field(default=None, description='操作状态(0正常 1异常)') + error_msg: Optional[str] = Field(default=None, description='错误消息') + oper_time: Optional[datetime] = Field(default=None, description='操作时间') + cost_time: Optional[int] = Field(default=None, description='消耗时间') class LogininforModel(BaseModel): """ 登录日志表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - info_id: Optional[int] = None - user_name: Optional[str] = None - ipaddr: Optional[str] = None - login_location: Optional[str] = None - browser: Optional[str] = None - os: Optional[str] = None - status: Optional[str] = None - msg: Optional[str] = None - login_time: Optional[datetime] = None + info_id: Optional[int] = Field(default=None, description='访问ID') + user_name: Optional[str] = Field(default=None, description='用户账号') + ipaddr: Optional[str] = Field(default=None, description='登录IP地址') + login_location: Optional[str] = Field(default=None, description='登录地点') + browser: Optional[str] = Field(default=None, description='浏览器类型') + os: Optional[str] = Field(default=None, description='操作系统') + status: Optional[Literal['0', '1']] = Field(default=None, description='登录状态(0成功 1失败)') + msg: Optional[str] = Field(default=None, description='提示消息') + login_time: Optional[datetime] = Field(default=None, description='访问时间') class OperLogQueryModel(OperLogModel): """ 操作日志管理不分页查询模型 """ - order_by_column: Optional[str] = None - is_asc: Optional[str] = None - begin_time: Optional[str] = None - end_time: Optional[str] = None + + order_by_column: Optional[str] = Field(default=None, description='排序的字段名称') + is_asc: Optional[Literal['ascending', 'descending']] = Field( + default=None, description='排序方式(ascending升序 descending降序)' + ) + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -63,28 +74,32 @@ class OperLogPageQueryModel(OperLogQueryModel): """ 操作日志管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteOperLogModel(BaseModel): """ 删除操作日志模型 """ + model_config = ConfigDict(alias_generator=to_camel) - oper_ids: str + oper_ids: str = Field(description='需要删除的日志主键') class LoginLogQueryModel(LogininforModel): """ 登录日志管理不分页查询模型 """ - order_by_column: Optional[str] = None - is_asc: Optional[str] = None - begin_time: Optional[str] = None - end_time: Optional[str] = None + order_by_column: Optional[str] = Field(default=None, description='排序的字段名称') + is_asc: Optional[Literal['ascending', 'descending']] = Field( + default=None, description='排序方式(ascending升序 descending降序)' + ) + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -93,23 +108,26 @@ class LoginLogPageQueryModel(LoginLogQueryModel): """ 登录日志管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteLoginLogModel(BaseModel): """ 删除登录日志模型 """ + model_config = ConfigDict(alias_generator=to_camel) - info_ids: str + info_ids: str = Field(description='需要删除的访问ID') class UnlockUser(BaseModel): """ 解锁用户模型 """ + model_config = ConfigDict(alias_generator=to_camel) - user_name: str + user_name: str = Field(description='用户名称') 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 76bb7b8fc19e133068fbf5772deaa2825b712798..fbd6e07bb3e8902819bfb00b34ee3850acb14280 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,7 @@ import re -from pydantic import BaseModel, ConfigDict, model_validator +from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic.alias_generators import to_camel -from typing import Optional, List, Union +from typing import List, Optional, Union from exceptions.exception import ModelValidatorException from module_admin.entity.vo.menu_vo import MenuModel @@ -9,75 +9,79 @@ from module_admin.entity.vo.menu_vo import MenuModel class UserLogin(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - user_name: str - password: str - code: Optional[str] = None - uuid: Optional[str] = None - login_info: Optional[dict] = None - captcha_enabled: Optional[bool] = None + user_name: str = Field(description='用户名称') + password: str = Field(description='用户密码') + code: Optional[str] = Field(default=None, description='验证码') + uuid: Optional[str] = Field(default=None, description='会话编号') + login_info: Optional[dict] = Field(default=None, description='登录信息,前端无需传递') + captcha_enabled: Optional[bool] = Field(default=None, description='是否启用验证码,前端无需传递') class UserRegister(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - username: str - password: str - confirm_password: str - code: Optional[str] = None - uuid: Optional[str] = None + username: str = Field(description='用户名称') + password: str = Field(description='用户密码') + confirm_password: str = Field(description='用户二次确认密码') + code: Optional[str] = Field(default=None, description='验证码') + uuid: Optional[str] = Field(default=None, description='会话编号') @model_validator(mode='after') def check_password(self) -> 'UserRegister': - pattern = r'''^[^<>"'|\\]+$''' + 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): - access_token: str - token_type: str + access_token: str = Field(description='token信息') + token_type: str = Field(description='token类型') class CaptchaCode(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - captcha_enabled: bool - register_enabled: bool - img: str - uuid: str + captcha_enabled: bool = Field(description='是否启用验证码') + register_enabled: bool = Field(description='是否启用注册') + img: str = Field(description='验证码图片') + uuid: str = Field(description='会话编号') class SmsCode(BaseModel): - is_success: Optional[bool] = None - sms_code: str - session_id: str - message: Optional[str] = None + is_success: Optional[bool] = Field(default=None, description='操作是否成功') + sms_code: str = Field(description='短信验证码') + session_id: str = Field(description='会话编号') + message: Optional[str] = Field(default=None, description='响应信息') class MenuTreeModel(MenuModel): - children: Optional[Union[List['MenuTreeModel'], None]] = None + children: Optional[Union[List['MenuTreeModel'], None]] = Field(default=None, description='子菜单') class MetaModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - title: Optional[str] = None - icon: Optional[str] = None - no_cache: Optional[bool] = None - link: Optional[str] = None + title: Optional[str] = Field(default=None, description='设置路由在侧边栏和面包屑中展示的名字') + icon: Optional[str] = Field(default=None, description='设置路由的图标') + no_cache: Optional[bool] = Field(default=None, description='设置为true,则不会被 缓存') + link: Optional[str] = Field(default=None, description='内链地址(http(s)://开头)') class RouterModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - name: Optional[str] = None - path: Optional[str] = None - hidden: Optional[bool] = None - redirect: Optional[str] = None - component: Optional[str] = None - query: Optional[str] = None - always_show: Optional[bool] = None - meta: Optional[MetaModel] = None - children: Optional[Union[List['RouterModel'], None]] = None + name: Optional[str] = Field(default=None, description='路由名称') + path: Optional[str] = Field(default=None, description='路由地址') + hidden: Optional[bool] = Field(default=None, description='是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现') + redirect: Optional[str] = Field( + default=None, description='重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击' + ) + component: Optional[str] = Field(default=None, description='组件地址') + query: Optional[str] = Field(default=None, description='路由参数:如 {"id": 1, "name": "ry"}') + always_show: Optional[bool] = Field( + default=None, description='当你一个路由下面的children声明的路由大于1个时,自动会变成嵌套的模式--如组件页面' + ) + meta: Optional[MetaModel] = 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 08f22e187b26a12cab6b726ef4ee06214f3905db..9dc8d75434f4c53a7f2473de1f1f0c9ed490ea74 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py @@ -1,7 +1,8 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel from datetime import datetime -from typing import Union, Optional, List +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 @@ -9,27 +10,62 @@ class MenuModel(BaseModel): """ 菜单表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - menu_id: Optional[int] = None - menu_name: Optional[str] = None - parent_id: Optional[int] = None - order_num: Optional[int] = None - path: Optional[str] = None - component: Optional[str] = None - query: Optional[str] = None - is_frame: Optional[int] = None - is_cache: Optional[int] = None - menu_type: Optional[str] = None - visible: Optional[str] = None - status: Optional[str] = None - perms: Optional[str] = None - icon: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None + menu_id: Optional[int] = Field(default=None, description='菜单ID') + menu_name: Optional[str] = Field(default=None, description='菜单名称') + parent_id: Optional[int] = Field(default=None, description='父菜单ID') + order_num: Optional[int] = Field(default=None, description='显示顺序') + path: Optional[str] = Field(default=None, description='路由地址') + component: Optional[str] = Field(default=None, description='组件路径') + query: Optional[str] = Field(default=None, description='路由参数') + route_name: Optional[str] = Field(default=None, description='路由名称') + is_frame: Optional[Literal[0, 1]] = Field(default=None, description='是否为外链(0是 1否)') + is_cache: Optional[Literal[0, 1]] = Field(default=None, description='是否缓存(0缓存 1不缓存)') + menu_type: Optional[Literal['M', 'C', 'F']] = Field(default=None, description='菜单类型(M目录 C菜单 F按钮)') + visible: Optional[Literal['0', '1']] = Field(default=None, description='菜单状态(0显示 1隐藏)') + status: Optional[Literal['0', '1']] = Field(default=None, description='菜单状态(0正常 1停用)') + perms: Optional[str] = Field(default=None, description='权限标识') + icon: Optional[str] = Field(default=None, description='菜单图标') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='menu_name', message='菜单名称不能为空') + @Size(field_name='menu_name', min_length=0, max_length=50, message='菜单名称长度不能超过50个字符') + def get_menu_name(self): + return self.menu_name + + @NotBlank(field_name='order_num', message='显示顺序不能为空') + def get_order_num(self): + return self.order_num + + @Size(field_name='path', min_length=0, max_length=200, message='路由地址长度不能超过200个字符') + def get_path(self): + return self.path + + @Size(field_name='component', min_length=0, max_length=255, message='组件路径长度不能超过255个字符') + def get_component(self): + return self.component + + @NotBlank(field_name='menu_type', message='菜单类型不能为空') + def get_menu_type(self): + return self.menu_type + + @Size(field_name='perms', min_length=0, max_length=100, message='权限标识长度不能超过100个字符') + def get_perms(self): + return self.perms + + def validate_fields(self): + self.get_menu_name() + self.get_order_num() + self.get_path() + self.get_component() + self.get_menu_type() + self.get_perms() @as_query @@ -37,14 +73,16 @@ class MenuQueryModel(MenuModel): """ 菜单管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') class DeleteMenuModel(BaseModel): """ 删除菜单模型 """ + model_config = ConfigDict(alias_generator=to_camel) - menu_ids: str + menu_ids: str = Field(description='需要删除的菜单ID') 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 8403fa24a4736d75a9e5a50448caa9218c9b8b17..bbf0a07d6162b8d86130973d68d384cf29969408 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py @@ -1,34 +1,46 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.annotation.pydantic_annotation import as_query, as_form +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_form, as_query class NoticeModel(BaseModel): """ 通知公告表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - notice_id: Optional[int] = None - notice_title: Optional[str] = None - notice_type: Optional[str] = None - notice_content: Optional[bytes] = None - status: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None + notice_id: Optional[int] = Field(default=None, description='公告ID') + notice_title: Optional[str] = Field(default=None, description='公告标题') + notice_type: Optional[Literal['1', '2']] = Field(default=None, description='公告类型(1通知 2公告)') + notice_content: Optional[bytes] = Field(default=None, description='公告内容') + status: Optional[Literal['0', '1']] = Field(default=None, description='公告状态(0正常 1关闭)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @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): + return self.notice_title + + def validate_fields(self): + self.get_notice_title() class NoticeQueryModel(NoticeModel): """ 通知公告管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -37,14 +49,16 @@ class NoticePageQueryModel(NoticeQueryModel): """ 通知公告管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteNoticeModel(BaseModel): """ 删除通知公告模型 """ + model_config = ConfigDict(alias_generator=to_camel) - notice_ids: str + notice_ids: str = Field(description='需要删除的公告ID') 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 616fd2a33b3531779767787c4ac6aa0b1fd6a264..ca68f1ec878a6c5c87b759d00c8bd15a05ee5271 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py @@ -1,7 +1,7 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime +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 @@ -9,16 +9,17 @@ class OnlineModel(BaseModel): """ 在线用户对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel) - token_id: Optional[str] = None - user_name: Optional[str] = None - dept_name: Optional[str] = None - ipaddr: Optional[str] = None - login_location: Optional[str] = None - browser: Optional[str] = None - os: Optional[str] = None - login_time: Optional[datetime] = None + token_id: Optional[str] = Field(default=None, description='会话编号') + user_name: Optional[str] = Field(default=None, description='部门名称') + dept_name: Optional[str] = Field(default=None, description='用户名称') + ipaddr: Optional[str] = Field(default=None, description='登录IP地址') + login_location: Optional[str] = Field(default=None, description='登录地址') + browser: Optional[str] = Field(default=None, description='浏览器类型') + os: Optional[str] = Field(default=None, description='操作系统') + login_time: Optional[datetime] = Field(default=None, description='登录时间') @as_query @@ -26,14 +27,16 @@ class OnlineQueryModel(OnlineModel): """ 岗位管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') class DeleteOnlineModel(BaseModel): """ 强退在线用户模型 """ + model_config = ConfigDict(alias_generator=to_camel) - token_ids: str + token_ids: str = Field(description='需要强退的会话编号') 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 8ba34958fcf78c2d4f2f82b36fcfd5d6da8b18e4..ab85103884d8490d758c3dd904d987df29028d1a 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py @@ -1,34 +1,56 @@ -from pydantic import BaseModel, ConfigDict -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.annotation.pydantic_annotation import as_query, as_form +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_form, as_query class PostModel(BaseModel): """ 岗位信息表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - post_id: Optional[int] = None - post_code: Optional[str] = None - post_name: Optional[str] = None - post_sort: Optional[int] = None - status: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None + post_id: Optional[int] = Field(default=None, description='岗位ID') + post_code: Optional[str] = Field(default=None, description='岗位编码') + post_name: Optional[str] = Field(default=None, description='岗位名称') + post_sort: Optional[int] = Field(default=None, description='显示顺序') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='post_code', message='岗位编码不能为空') + @Size(field_name='post_code', min_length=0, max_length=64, message='岗位编码长度不能超过64个字符') + def get_post_code(self): + 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): + return self.post_name + + @NotBlank(field_name='post_sort', message='显示顺序不能为空') + def get_post_sort(self): + return self.post_sort + + def validate_fields(self): + self.get_post_code() + self.get_post_name() + self.get_post_sort() class PostQueryModel(PostModel): """ 岗位管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -37,14 +59,16 @@ class PostPageQueryModel(PostQueryModel): """ 岗位管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeletePostModel(BaseModel): """ 删除岗位模型 """ + model_config = ConfigDict(alias_generator=to_camel) - post_ids: str + post_ids: str = Field(description='需要删除的岗位ID') 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 5ec58f11e923a9b8d3a5b94da4728549f2aafe14..d6f60bc7c384c7956e97f5b6ce64d1ded792417f 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py @@ -1,31 +1,36 @@ -from pydantic import BaseModel, ConfigDict, field_validator, model_validator -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.annotation.pydantic_annotation import as_query, as_form +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_form, as_query class RoleModel(BaseModel): """ 角色表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - role_id: Optional[int] = None - role_name: Optional[str] = None - role_key: Optional[str] = None - role_sort: Optional[int] = None - data_scope: Optional[str] = None - menu_check_strictly: Optional[Union[int, bool]] = None - dept_check_strictly: Optional[Union[int, bool]] = None - status: Optional[str] = None - del_flag: Optional[str] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None - admin: Optional[bool] = False + role_id: Optional[int] = Field(default=None, description='角色ID') + role_name: Optional[str] = Field(default=None, description='角色名称') + role_key: Optional[str] = Field(default=None, description='角色权限字符串') + role_sort: Optional[int] = Field(default=None, description='显示顺序') + data_scope: Optional[Literal['1', '2', '3', '4', '5']] = Field( + default=None, + description='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限)', + ) + menu_check_strictly: Optional[Union[int, bool]] = Field(default=None, description='菜单树选择项是否关联显示') + dept_check_strictly: Optional[Union[int, bool]] = Field(default=None, description='部门树选择项是否关联显示') + status: Optional[Literal['0', '1']] = Field(default=None, description='角色状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + admin: Optional[bool] = Field(default=False, description='是否为admin') @field_validator('menu_check_strictly', 'dept_check_strictly') @classmethod @@ -48,33 +53,55 @@ class RoleModel(BaseModel): self.admin = False return self + @NotBlank(field_name='role_name', message='角色名称不能为空') + @Size(field_name='role_name', min_length=0, max_length=30, message='角色名称长度不能超过30个字符') + def get_role_name(self): + 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): + return self.role_key + + @NotBlank(field_name='role_sort', message='显示顺序不能为空') + def get_role_sort(self): + return self.role_sort + + def validate_fields(self): + self.get_role_name() + self.get_role_key() + self.get_role_sort() + class RoleMenuModel(BaseModel): """ 角色和菜单关联表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - role_id: Optional[int] = None - menu_id: Optional[int] = None + role_id: Optional[int] = Field(default=None, description='角色ID') + menu_id: Optional[int] = Field(default=None, description='菜单ID') class RoleDeptModel(BaseModel): """ 角色和部门关联表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - role_id: Optional[int] = None - dept_id: Optional[int] = None + role_id: Optional[int] = Field(default=None, description='角色ID') + dept_id: Optional[int] = Field(default=None, description='部门ID') class RoleQueryModel(RoleModel): """ 角色管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -83,45 +110,50 @@ class RolePageQueryModel(RoleQueryModel): """ 角色管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class RoleMenuQueryModel(BaseModel): """ 角色菜单查询模型 """ + model_config = ConfigDict(alias_generator=to_camel) - menus: List = [] - checked_keys: List[int] = [] + menus: List = Field(default=[], description='菜单信息') + checked_keys: List[int] = Field(default=[], description='已选择的菜单ID信息') class RoleDeptQueryModel(BaseModel): """ 角色部门查询模型 """ + model_config = ConfigDict(alias_generator=to_camel) - depts: List = [] - checked_keys: List[int] = [] + depts: List = Field(default=[], description='部门信息') + checked_keys: List[int] = Field(default=[], description='已选择的部门ID信息') class AddRoleModel(RoleModel): """ 新增角色模型 """ - dept_ids: List = [] - menu_ids: List = [] - type: Optional[str] = None + + dept_ids: List = Field(default=[], description='部门ID信息') + menu_ids: List = Field(default=[], description='菜单ID信息') + type: Optional[str] = Field(default=None, description='操作类型') class DeleteRoleModel(BaseModel): """ 删除角色模型 """ + model_config = ConfigDict(alias_generator=to_camel) - role_ids: str - update_by: Optional[str] = None - update_time: Optional[datetime] = None + 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 4323ddfab2e7b6eb2b56dc52f83fe25fb8e65a0a..810ecac34ce770f175241fb296fbff982b2ac022 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py @@ -1,66 +1,67 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from pydantic.alias_generators import to_camel -from typing import Optional, List +from typing import List, Optional class CpuInfo(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - cpu_num: Optional[int] = None - used: Optional[float] = None - sys: Optional[float] = None - free: Optional[float] = None + cpu_num: Optional[int] = Field(default=None, description='核心数') + used: Optional[float] = Field(default=None, description='CPU用户使用率') + sys: Optional[float] = Field(default=None, description='CPU系统使用率') + free: Optional[float] = Field(default=None, description='CPU当前空闲率') class MemoryInfo(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - total: Optional[str] = None - used: Optional[str] = None - free: Optional[str] = None - usage: Optional[float] = None + total: Optional[str] = Field(default=None, description='内存总量') + used: Optional[str] = Field(default=None, description='已用内存') + free: Optional[str] = Field(default=None, description='剩余内存') + usage: Optional[float] = Field(default=None, description='使用率') class SysInfo(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - computer_ip: Optional[str] = None - computer_name: Optional[str] = None - os_arch: Optional[str] = None - os_name: Optional[str] = None - user_dir: Optional[str] = None + computer_ip: Optional[str] = Field(default=None, description='服务器IP') + computer_name: Optional[str] = Field(default=None, description='服务器名称') + os_arch: Optional[str] = Field(default=None, description='系统架构') + os_name: Optional[str] = Field(default=None, description='操作系统') + user_dir: Optional[str] = Field(default=None, description='项目路径') class PyInfo(MemoryInfo): model_config = ConfigDict(alias_generator=to_camel) - name: Optional[str] = None - version: Optional[str] = None - start_time: Optional[str] = None - run_time: Optional[str] = None - home: Optional[str] = None + name: Optional[str] = Field(default=None, description='Python名称') + version: Optional[str] = Field(default=None, description='Python版本') + start_time: Optional[str] = Field(default=None, description='启动时间') + run_time: Optional[str] = Field(default=None, description='运行时长') + home: Optional[str] = Field(default=None, description='安装路径') class SysFiles(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - dir_name: Optional[str] = None - sys_type_name: Optional[str] = None - type_name: Optional[str] = None - total: Optional[str] = None - used: Optional[str] = None - free: Optional[str] = None - usage: Optional[str] = None + dir_name: Optional[str] = Field(default=None, description='盘符路径') + sys_type_name: Optional[str] = Field(default=None, description='盘符类型') + type_name: Optional[str] = Field(default=None, description='文件类型') + total: Optional[str] = Field(default=None, description='总大小') + used: Optional[str] = Field(default=None, description='已经使用量') + free: Optional[str] = Field(default=None, description='剩余大小') + usage: Optional[str] = Field(default=None, description='资源的使用率') class ServerMonitorModel(BaseModel): """ 服务监控对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel) - cpu: Optional[CpuInfo] - py: Optional[PyInfo] - mem: Optional[MemoryInfo] - sys: Optional[SysInfo] - sys_files: Optional[List[SysFiles]] + cpu: Optional[CpuInfo] = Field(description='CPU相关信息') + py: Optional[PyInfo] = Field(description='Python相关信息') + mem: Optional[MemoryInfo] = Field(description='內存相关信息') + sys: Optional[SysInfo] = 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 fa761ef5048da3482dfd9977ce2a61ea020398fa..67a1d14162538399a7777d696e226044b76298e7 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -1,56 +1,59 @@ import re -from pydantic import BaseModel, ConfigDict, model_validator -from pydantic.alias_generators import to_camel -from typing import Union, Optional, List from datetime import datetime -from module_admin.entity.vo.role_vo import RoleModel +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_form, as_query from module_admin.entity.vo.dept_vo import DeptModel from module_admin.entity.vo.post_vo import PostModel -from module_admin.annotation.pydantic_annotation import as_query, as_form -from exceptions.exception import ModelValidatorException +from module_admin.entity.vo.role_vo import RoleModel class TokenData(BaseModel): """ token解析结果 """ - user_id: Union[int, None] = None + + user_id: Union[int, None] = Field(default=None, description='用户ID') class UserModel(BaseModel): """ 用户表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - user_id: Optional[int] = None - dept_id: Optional[int] = None - user_name: Optional[str] = None - nick_name: Optional[str] = None - user_type: Optional[str] = None - email: Optional[str] = None - phonenumber: Optional[str] = None - sex: Optional[str] = None - avatar: Optional[str] = None - password: Optional[str] = None - status: Optional[str] = None - del_flag: Optional[str] = None - login_ip: Optional[str] = None - login_date: Optional[datetime] = None - create_by: Optional[str] = None - create_time: Optional[datetime] = None - update_by: Optional[str] = None - update_time: Optional[datetime] = None - remark: Optional[str] = None - admin: Optional[bool] = False + user_id: Optional[int] = Field(default=None, description='用户ID') + dept_id: Optional[int] = Field(default=None, description='部门ID') + user_name: Optional[str] = Field(default=None, description='用户账号') + nick_name: Optional[str] = Field(default=None, description='用户昵称') + user_type: Optional[str] = Field(default=None, description='用户类型(00系统用户)') + email: Optional[str] = Field(default=None, description='用户邮箱') + phonenumber: Optional[str] = Field(default=None, description='手机号码') + sex: Optional[Literal['0', '1', '2']] = Field(default=None, description='用户性别(0男 1女 2未知)') + avatar: Optional[str] = Field(default=None, description='头像地址') + password: Optional[str] = Field(default=None, description='密码') + status: Optional[Literal['0', '1']] = Field(default=None, description='帐号状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + login_ip: Optional[str] = Field(default=None, description='最后登录IP') + login_date: Optional[datetime] = Field(default=None, description='最后登录时间') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + admin: Optional[bool] = Field(default=False, description='是否为admin') @model_validator(mode='after') def check_password(self) -> 'UserModel': - pattern = r'''^[^<>"'|\\]+$''' + 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': @@ -60,72 +63,103 @@ class UserModel(BaseModel): self.admin = False return self + @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): + 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): + 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): + return self.email + + @Size(field_name='phonenumber', min_length=0, max_length=11, message='手机号码长度不能超过11个字符') + def get_phonenumber(self): + return self.phonenumber + + def validate_fields(self): + self.get_user_name() + self.get_nick_name() + self.get_email() + self.get_phonenumber() + class UserRoleModel(BaseModel): """ 用户和角色关联表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - user_id: Optional[int] = None - role_id: Optional[int] = None + user_id: Optional[int] = Field(default=None, description='用户ID') + role_id: Optional[int] = Field(default=None, description='角色ID') class UserPostModel(BaseModel): """ 用户与岗位关联表对应pydantic模型 """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - user_id: Optional[int] = None - post_id: Optional[int] = None + user_id: Optional[int] = Field(default=None, description='用户ID') + post_id: Optional[int] = Field(default=None, description='岗位ID') class UserInfoModel(UserModel): - post_ids: Optional[Union[str, None]] = None - role_ids: Optional[Union[str, None]] = None - dept: Optional[Union[DeptModel, None]] = None - role: Optional[List[Union[RoleModel, None]]] = [] + 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='角色信息') class CurrentUserModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel) - permissions: List - roles: List - user: Union[UserInfoModel, None] + permissions: List = Field(description='权限信息') + roles: List = Field(description='角色信息') + user: Union[UserInfoModel, None] = Field(description='用户信息') class UserDetailModel(BaseModel): """ 获取用户详情信息响应模型 """ + model_config = ConfigDict(alias_generator=to_camel) - data: Optional[Union[UserInfoModel, None]] = None - post_ids: Optional[List] = None - posts: List[Union[PostModel, None]] - role_ids: Optional[List] = None - roles: List[Union[RoleModel, None]] + 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='角色信息') class UserProfileModel(BaseModel): """ 获取个人信息响应模型 """ + model_config = ConfigDict(alias_generator=to_camel) - data: Union[UserInfoModel, None] - post_group: Union[str, None] - role_group: Union[str, None] + data: Union[UserInfoModel, None] = Field(description='用户信息') + post_group: Union[str, None] = Field(description='岗位信息') + role_group: Union[str, None] = Field(description='角色信息') class UserQueryModel(UserModel): """ 用户管理不分页查询模型 """ - begin_time: Optional[str] = None - end_time: Optional[str] = None + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') @as_query @@ -134,24 +168,27 @@ class UserPageQueryModel(UserQueryModel): """ 用户管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class AddUserModel(UserModel): """ 新增用户模型 """ - role_ids: Optional[List] = [] - post_ids: Optional[List] = [] - type: Optional[str] = None + + role_ids: Optional[List] = Field(default=[], description='角色ID信息') + post_ids: Optional[List] = Field(default=[], description='岗位ID信息') + type: Optional[str] = Field(default=None, description='操作类型') class EditUserModel(AddUserModel): """ 编辑用户模型 """ - role: Optional[List] = [] + + role: Optional[List] = Field(default=[], description='角色信息') @as_query @@ -159,45 +196,49 @@ class ResetPasswordModel(BaseModel): """ 重置密码模型 """ + model_config = ConfigDict(alias_generator=to_camel) - old_password: Optional[str] = None - new_password: Optional[str] = None + old_password: Optional[str] = Field(default=None, description='旧密码') + new_password: Optional[str] = Field(default=None, description='新密码') @model_validator(mode='after') def check_new_password(self) -> 'ResetPasswordModel': - pattern = r'''^[^<>"'|\\]+$''' + 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): """ 重置用户密码模型 """ - old_password: Optional[str] = None - sms_code: Optional[str] = None - session_id: Optional[str] = None + + old_password: Optional[str] = Field(default=None, description='旧密码') + sms_code: Optional[str] = Field(default=None, description='验证码') + session_id: Optional[str] = Field(default=None, description='会话id') class DeleteUserModel(BaseModel): """ 删除用户模型 """ + model_config = ConfigDict(alias_generator=to_camel) - user_ids: str - update_by: Optional[str] = None - update_time: Optional[datetime] = None + user_ids: str = Field(description='需要删除的用户ID') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') class UserRoleQueryModel(UserModel): """ 用户角色关联管理不分页查询模型 """ - role_id: Optional[int] = None + + role_id: Optional[int] = Field(default=None, description='角色ID') @as_query @@ -205,25 +246,28 @@ class UserRolePageQueryModel(UserRoleQueryModel): """ 用户角色关联管理分页查询模型 """ - page_num: int = 1 - page_size: int = 10 + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class SelectedRoleModel(RoleModel): """ 是否选择角色模型 """ - flag: Optional[bool] = False + + flag: Optional[bool] = Field(default=False, description='选择标识') class UserRoleResponseModel(BaseModel): """ 用户角色关联管理列表返回模型 """ + model_config = ConfigDict(alias_generator=to_camel) - roles: List[Union[SelectedRoleModel, None]] = [] - user: UserInfoModel + roles: List[Union[SelectedRoleModel, None]] = Field(default=[], description='角色信息') + user: UserInfoModel = Field(description='用户信息') @as_query @@ -231,9 +275,10 @@ class CrudUserRoleModel(BaseModel): """ 新增、删除用户关联角色及角色关联用户模型 """ + model_config = ConfigDict(alias_generator=to_camel) - user_id: Optional[int] = None - user_ids: Optional[str] = None - role_id: Optional[int] = None - role_ids: Optional[str] = None + user_id: Optional[int] = Field(default=None, description='用户ID') + user_ids: Optional[str] = Field(default=None, description='用户ID信息') + role_id: Optional[int] = Field(default=None, description='角色ID') + role_ids: Optional[str] = Field(default=None, description='角色ID信息') diff --git a/ruoyi-fastapi-backend/module_admin/service/cache_service.py b/ruoyi-fastapi-backend/module_admin/service/cache_service.py index 960b9cd498733ecc26d2e30b941b8ac740a1a452..72212cb34f77061d20ccb8b49e511cff0a21de46 100644 --- a/ruoyi-fastapi-backend/module_admin/service/cache_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/cache_service.py @@ -1,7 +1,7 @@ from fastapi import Request -from module_admin.entity.vo.cache_vo import * -from config.env import RedisInitKeyConfig +from config.enums import RedisInitKeyConfig 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 @@ -14,19 +14,17 @@ class CacheService: async def get_cache_monitor_statistical_info_services(cls, request: Request): """ 获取缓存监控信息service + :param request: Request对象 :return: 缓存监控信息 """ info = await request.app.state.redis.info() 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()] - result = CacheMonitorModel( - commandStats=command_stats, - dbSize=db_size, - info=info - ) + command_stats = [ + dict(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 @@ -34,19 +32,19 @@ class CacheService: async def get_cache_monitor_cache_name_services(cls): """ 获取缓存名称列表信息service + :return: 缓存名称列表信息 """ name_list = [] - for attr_name in dir(RedisInitKeyConfig): - if not attr_name.startswith('__') and isinstance(getattr(RedisInitKeyConfig, attr_name), dict): - name_list.append( - CacheInfoModel( - cacheKey="", - cacheName=getattr(RedisInitKeyConfig, attr_name).get('key'), - cacheValue="", - remark=getattr(RedisInitKeyConfig, attr_name).get('remark') - ) + for key_config in RedisInitKeyConfig: + name_list.append( + CacheInfoModel( + cacheKey='', + cacheName=key_config.key, + cacheValue='', + remark=key_config.remark, ) + ) return name_list @@ -54,12 +52,13 @@ class CacheService: async def get_cache_monitor_cache_key_services(cls, request: Request, cache_name: str): """ 获取缓存键名列表信息service + :param request: Request对象 :param cache_name: 缓存名称 :return: 缓存键名列表信息 """ - cache_keys = 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}:")] + cache_keys = 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 @@ -67,49 +66,51 @@ class CacheService: async def get_cache_monitor_cache_value_services(cls, request: Request, cache_name: str, cache_key: str): """ 获取缓存内容信息service + :param request: Request对象 :param cache_name: 缓存名称 :param cache_key: 缓存键名 :return: 缓存内容信息 """ - cache_value = await request.app.state.redis.get(f"{cache_name}:{cache_key}") + cache_value = await request.app.state.redis.get(f'{cache_name}:{cache_key}') - return CacheInfoModel(cacheKey=cache_key, cacheName=cache_name, cacheValue=cache_value, remark="") + 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): """ 清除缓存名称对应所有键值service + :param request: Request对象 :param cache_name: 缓存名称 :return: 操作缓存响应信息 """ - cache_keys = await request.app.state.redis.keys(f"{cache_name}*") + cache_keys = await request.app.state.redis.keys(f'{cache_name}*') if cache_keys: await request.app.state.redis.delete(*cache_keys) - result = dict(is_success=True, message=f"{cache_name}对应键值清除成功") - return CrudResponseModel(**result) + return CrudResponseModel(is_success=True, message=f'{cache_name}对应键值清除成功') @classmethod async def clear_cache_monitor_cache_key_services(cls, request: Request, cache_key: str): """ 清除缓存名称对应所有键值service + :param request: Request对象 :param cache_key: 缓存键名 :return: 操作缓存响应信息 """ - cache_keys = await request.app.state.redis.keys(f"*{cache_key}") + cache_keys = await request.app.state.redis.keys(f'*{cache_key}') if cache_keys: await request.app.state.redis.delete(*cache_keys) - result = dict(is_success=True, message=f"{cache_key}清除成功") - return CrudResponseModel(**result) + return CrudResponseModel(is_success=True, message=f'{cache_key}清除成功') @classmethod async def clear_cache_monitor_all_services(cls, request: Request): """ 清除所有缓存service + :param request: Request对象 :return: 操作缓存响应信息 """ @@ -117,8 +118,7 @@ class CacheService: if cache_keys: await request.app.state.redis.delete(*cache_keys) - result = dict(is_success=True, message="所有缓存清除成功") await RedisUtil.init_sys_dict(request.app.state.redis) await RedisUtil.init_sys_config(request.app.state.redis) - return CrudResponseModel(**result) + return CrudResponseModel(is_success=True, message='所有缓存清除成功') diff --git a/ruoyi-fastapi-backend/module_admin/service/captcha_service.py b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py index 85b09123a6fb7c4e622a8f0c36ddf02e14e06929..1be8ffb4ea6d491a4735f252bbc8a098b41aa001 100644 --- a/ruoyi-fastapi-backend/module_admin/service/captcha_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py @@ -1,8 +1,8 @@ -from PIL import Image, ImageDraw, ImageFont +import base64 import io import os import random -import base64 +from PIL import Image, ImageDraw, ImageFont class CaptchaService: @@ -35,7 +35,7 @@ class CaptchaService: else: result = num1 * num2 # 绘制文本 - text = f"{num1} {operational_character} {num2} = ?" + text = f'{num1} {operational_character} {num2} = ?' draw.text((25, 15), text, fill='blue', font=font) # 将图像数据保存到内存中 diff --git a/ruoyi-fastapi-backend/module_admin/service/common_service.py b/ruoyi-fastapi-backend/module_admin/service/common_service.py index 883f18cf729263256b3449ee1b10f8b536ef39f4..20eb868de95f70fc1da498ee8bf4adf05e391a99 100644 --- a/ruoyi-fastapi-backend/module_admin/service/common_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/common_service.py @@ -1,9 +1,9 @@ -from fastapi import Request, BackgroundTasks import os -from fastapi import UploadFile from datetime import datetime +from fastapi import BackgroundTasks, Request, UploadFile from config.env import UploadConfig -from module_admin.entity.vo.common_vo import * +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel, UploadResponseModel from utils.upload_util import UploadUtil @@ -16,12 +16,13 @@ class CommonService: async def upload_service(cls, request: Request, file: UploadFile): """ 通用上传service + :param request: Request对象 :param file: 上传文件对象 :return: 上传结果 """ if not UploadUtil.check_file_extension(file): - result = dict(is_success=False, message='文件类型不合法') + 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) @@ -36,23 +37,22 @@ class CommonService: for chunk in iter(lambda: file.file.read(1024 * 1024 * 10), b''): f.write(chunk) - result = dict( + 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}' + url=f'{request.base_url}{UploadConfig.UPLOAD_PREFIX[1:]}/{relative_path}/{filename}', ), - message='上传成功' + message='上传成功', ) - return CrudResponseModel(**result) - @classmethod async def download_services(cls, background_tasks: BackgroundTasks, file_name, delete: bool): """ 下载下载目录文件service + :param background_tasks: 后台任务对象 :param file_name: 下载的文件名称 :param delete: 是否在下载完成后删除文件 @@ -60,28 +60,32 @@ class CommonService: """ filepath = os.path.join(UploadConfig.DOWNLOAD_PATH, file_name) if '..' in file_name: - result = dict(is_success=False, message='文件名称不合法') + raise ServiceException(message='文件名称不合法') elif not UploadUtil.check_file_exists(filepath): - result = dict(is_success=False, message='文件不存在') + raise ServiceException(message='文件不存在') else: - result = dict(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') if delete: background_tasks.add_task(UploadUtil.delete_file, filepath) - return CrudResponseModel(**result) + return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') @classmethod async def download_resource_services(cls, resource: str): """ 下载上传目录文件service + :param resource: 下载的文件名称 :return: 上传结果 """ filepath = os.path.join(resource.replace(UploadConfig.UPLOAD_PREFIX, UploadConfig.UPLOAD_PATH)) - filename = resource.rsplit("/", 1)[-1] - if '..' in filename or not UploadUtil.check_file_timestamp(filename) or not UploadUtil.check_file_machine(filename) or not UploadUtil.check_file_random_code(filename): - result = dict(is_success=False, message='文件名称不合法') + filename = resource.rsplit('/', 1)[-1] + if ( + '..' in filename + or not UploadUtil.check_file_timestamp(filename) + or not UploadUtil.check_file_machine(filename) + or not UploadUtil.check_file_random_code(filename) + ): + raise ServiceException(message='文件名称不合法') elif not UploadUtil.check_file_exists(filepath): - result = dict(is_success=False, message='文件不存在') + raise ServiceException(message='文件不存在') else: - result = dict(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') - return CrudResponseModel(**result) + 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 e4700609b23c2d2c61312b9d97910f2a8ce60259..1d8ec7645ed553068d2d0bcb84d62434f5fc57d3 100644 --- a/ruoyi-fastapi-backend/module_admin/service/config_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/config_service.py @@ -1,8 +1,13 @@ from fastapi import Request -from config.env import RedisInitKeyConfig -from module_admin.dao.config_dao import * +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from config.enums import RedisInitKeyConfig +from exceptions.exception import ServiceException +from module_admin.dao.config_dao import ConfigDao from module_admin.entity.vo.common_vo import CrudResponseModel -from utils.common_util import export_list2excel, CamelCaseUtil +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel +from utils.common_util import CamelCaseUtil, export_list2excel class ConfigService: @@ -11,9 +16,12 @@ class ConfigService: """ @classmethod - async def get_config_list_services(cls, query_db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False): + async def get_config_list_services( + cls, query_db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False + ): """ 获取参数配置列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -27,120 +35,153 @@ class ConfigService: async def init_cache_sys_config_services(cls, query_db: AsyncSession, redis): """ 应用初始化:获取所有参数配置对应的键值对信息并缓存service + :param query_db: orm对象 :param redis: redis对象 :return: """ # 获取以sys_config:开头的键列表 - keys = await redis.keys(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:*") + keys = await redis.keys(f'{RedisInitKeyConfig.SYS_CONFIG.key}:*') # 删除匹配的键 if keys: await redis.delete(*keys) config_all = await ConfigDao.get_config_list(query_db, ConfigPageQueryModel(**dict()), is_page=False) for config_obj in config_all: - if config_obj.get('configType') == 'Y': - await redis.set(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:{config_obj.get('configKey')}", config_obj.get('configValue')) + await redis.set( + 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): """ 从缓存获取参数键名对应值service + :param redis: redis对象 :param config_key: 参数键名 :return: 参数键名对应值 """ - result = await redis.get(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:{config_key}") + result = await redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_key}') return result + @classmethod + async def check_config_key_unique_services(cls, query_db: AsyncSession, page_object: ConfigModel): + """ + 校验参数键名是否唯一service + + :param query_db: orm对象 + :param page_object: 参数配置对象 + :return: 校验结果 + """ + config_id = -1 if page_object.config_id is None else page_object.config_id + config = await ConfigDao.get_config_detail_by_info(query_db, ConfigModel(configKey=page_object.config_key)) + if config and config.config_id != config_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): """ 新增参数配置信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 新增参数配置对象 :return: 新增参数配置校验结果 """ - config = await ConfigDao.get_config_detail_by_info(query_db, ConfigModel(configKey=page_object.config_key)) - if config: - result = dict(is_success=False, message='参数键名已存在') + 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 cls.init_cache_sys_config_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='新增成功') + 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 - return CrudResponseModel(**result) - @classmethod async def edit_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): """ 编辑参数配置信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 编辑参数配置对象 :return: 编辑参数配置校验结果 """ edit_config = page_object.model_dump(exclude_unset=True) - config_info = await cls.config_detail_services(query_db, edit_config.get('config_id')) - if config_info: - if config_info.config_key != page_object.config_key or config_info.config_value != page_object.config_value: - config = await ConfigDao.get_config_detail_by_info(query_db, page_object) - if config: - result = dict(is_success=False, message='参数配置已存在') - return CrudResponseModel(**result) - try: - await ConfigDao.edit_config_dao(query_db, edit_config) - await query_db.commit() - await cls.init_cache_sys_config_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + config_info = await cls.config_detail_services(query_db, page_object.config_id) + 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 + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='参数配置不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='参数配置不存在') @classmethod async def delete_config_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteConfigModel): """ 删除参数配置信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 删除参数配置对象 :return: 删除参数配置校验结果 """ - if page_object.config_ids.split(','): + if page_object.config_ids: config_id_list = page_object.config_ids.split(',') try: + delete_config_key_list = [] for config_id in config_id_list: - await ConfigDao.delete_config_dao(query_db, ConfigModel(configId=config_id)) + 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 query_db.commit() - await cls.init_cache_sys_config_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='删除成功') + if delete_config_key_list: + await request.app.state.redis.delete(*delete_config_key_list) + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入字典数据id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入参数配置id为空') @classmethod async def config_detail_services(cls, query_db: AsyncSession, config_id: int): """ 获取参数配置详细信息service + :param query_db: orm对象 :param config_id: 参数配置id :return: 参数配置id对应的信息 """ config = await ConfigDao.get_config_detail_by_id(query_db, config_id=config_id) - result = ConfigModel(**CamelCaseUtil.transform_result(config)) + if config: + result = ConfigModel(**CamelCaseUtil.transform_result(config)) + else: + result = ConfigModel(**dict()) return result @@ -148,21 +189,22 @@ class ConfigService: async def export_config_list_services(config_list: List): """ 导出参数配置信息service + :param config_list: 参数配置信息列表 :return: 参数配置信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "configId": "参数主键", - "configName": "参数名称", - "configKey": "参数键名", - "configValue": "参数键值", - "configType": "系统内置", - "createBy": "创建者", - "createTime": "创建时间", - "updateBy": "更新者", - "updateTime": "更新时间", - "remark": "备注", + 'configId': '参数主键', + 'configName': '参数名称', + 'configKey': '参数键名', + 'configValue': '参数键值', + 'configType': '系统内置', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', } data = config_list @@ -172,7 +214,9 @@ class ConfigService: item['configType'] = '是' else: item['configType'] = '否' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @@ -181,11 +225,11 @@ class ConfigService: async def refresh_sys_config_services(cls, request: Request, query_db: AsyncSession): """ 刷新字典缓存信息service + :param request: Request对象 :param query_db: orm对象 :return: 刷新字典缓存校验结果 """ await cls.init_cache_sys_config_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='刷新成功') - return CrudResponseModel(**result) + return CrudResponseModel(is_success=True, message='刷新成功') diff --git a/ruoyi-fastapi-backend/module_admin/service/dept_service.py b/ruoyi-fastapi-backend/module_admin/service/dept_service.py index 5d67412c97c38d17fea224000a32359c3ed6da16..a0e49e59a1bf0dee4c94a01eb10516348fae8616 100644 --- a/ruoyi-fastapi-backend/module_admin/service/dept_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/dept_service.py @@ -1,5 +1,9 @@ -from module_admin.dao.dept_dao import * +from sqlalchemy.ext.asyncio import AsyncSession +from config.constant import CommonConstant +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 utils.common_util import CamelCaseUtil @@ -12,6 +16,7 @@ class DeptService: async def get_dept_tree_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): """ 获取部门树信息service + :param query_db: orm对象 :param page_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 @@ -23,10 +28,12 @@ class DeptService: return dept_tree_result @classmethod - async def get_dept_for_edit_option_services(cls, query_db: AsyncSession, page_object: DeptModel, - data_scope_sql: str): + async def get_dept_for_edit_option_services( + cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str + ): """ 获取部门编辑部门树信息service + :param query_db: orm对象 :param page_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 @@ -40,6 +47,7 @@ class DeptService: async def get_dept_list_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): """ 获取部门列表信息service + :param query_db: orm对象 :param page_object: 分页查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 @@ -49,113 +57,143 @@ 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): + """ + 校验部门是否有数据权限service + + :param query_db: orm对象 + :param dept_id: 部门id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + 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='没有权限访问部门数据') + + @classmethod + async def check_dept_name_unique_services(cls, query_db: AsyncSession, page_object: DeptModel): + """ + 校验部门名称是否唯一service + + :param query_db: orm对象 + :param page_object: 部门对象 + :return: 校验结果 + """ + dept_id = -1 if page_object.dept_id is None else page_object.dept_id + dept = await DeptDao.get_dept_detail_by_info( + query_db, DeptModel(deptName=page_object.dept_name, parentId=page_object.parent_id) + ) + if dept and dept.dept_id != dept_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): """ 新增部门信息service + :param query_db: orm对象 :param page_object: 新增部门对象 :return: 新增部门校验结果 """ + if not await cls.check_dept_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增部门{page_object.dept_name}失败,部门名称已存在') parent_info = await DeptDao.get_dept_by_id(query_db, page_object.parent_id) - if parent_info: - page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}' - else: - page_object.ancestors = '0' - dept = await DeptDao.get_dept_detail_by_info(query_db, DeptModel(parentId=page_object.parent_id, - deptName=page_object.dept_name)) - if dept: - result = dict(is_success=False, message='同一部门下不允许存在同名的部门') - else: - try: - await DeptDao.add_dept_dao(query_db, page_object) - await query_db.commit() - result = dict(is_success=True, message='新增成功') - except Exception as e: - await query_db.rollback() - raise e - - return CrudResponseModel(**result) + if parent_info.status != CommonConstant.DEPT_NORMAL: + raise ServiceException(message=f'部门{parent_info.dept_name}停用,不允许新增') + page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}' + try: + await DeptDao.add_dept_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_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): """ 编辑部门信息service + :param query_db: orm对象 :param page_object: 编辑部门对象 :return: 编辑部门校验结果 """ - parent_info = await DeptDao.get_dept_by_id(query_db, page_object.parent_id) - if parent_info: - page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}' - else: - page_object.ancestors = '0' - edit_dept = page_object.model_dump(exclude_unset=True) - dept_info = await cls.dept_detail_services(query_db, edit_dept.get('dept_id')) - if dept_info: - if dept_info.parent_id != page_object.parent_id or dept_info.dept_name != page_object.dept_name: - dept = await DeptDao.get_dept_detail_by_info(query_db, DeptModel(parentId=page_object.parent_id, - deptName=page_object.dept_name)) - if dept: - result = dict(is_success=False, message='同一部门下不允许存在同名的部门') - return CrudResponseModel(**result) - try: - await DeptDao.edit_dept_dao(query_db, edit_dept) - await cls.update_children_info(query_db, DeptModel(deptId=page_object.dept_id, - ancestors=page_object.ancestors, - updateBy=page_object.update_by, - updateTime=page_object.update_time - ) - ) - await query_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e - else: - result = dict(is_success=False, message='部门不存在') - - return CrudResponseModel(**result) + 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: + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,上级部门不能是自己') + elif ( + page_object.status == CommonConstant.DEPT_DISABLE + and (await DeptDao.count_normal_children_dept_dao(query_db, page_object.dept_id)) > 0 + ): + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,该部门包含未停用的子部门') + new_parent_dept = await DeptDao.get_dept_by_id(query_db, page_object.parent_id) + old_dept = await DeptDao.get_dept_by_id(query_db, page_object.dept_id) + try: + if new_parent_dept and old_dept: + new_ancestors = f'{new_parent_dept.ancestors},{new_parent_dept.dept_id}' + old_ancestors = old_dept.ancestors + page_object.ancestors = new_ancestors + await cls.update_dept_children(query_db, page_object.dept_id, new_ancestors, old_ancestors) + edit_dept = page_object.model_dump(exclude_unset=True) + await DeptDao.edit_dept_dao(query_db, edit_dept) + if ( + page_object.status == CommonConstant.DEPT_NORMAL + and page_object.ancestors + and page_object.ancestors != 0 + ): + await cls.update_parent_dept_status_normal(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 delete_dept_services(cls, query_db: AsyncSession, page_object: DeleteDeptModel): """ 删除部门信息service + :param query_db: orm对象 :param page_object: 删除部门对象 :return: 删除部门校验结果 """ - if page_object.dept_ids.split(','): + if page_object.dept_ids: dept_id_list = page_object.dept_ids.split(',') - ancestors = await DeptDao.get_dept_all_ancestors(query_db) try: for dept_id in dept_id_list: - for ancestor in ancestors: - if dept_id in ancestor[0]: - result = dict(is_success=False, message='该部门下有子部门,不允许删除') - - return CrudResponseModel(**result) + 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: + raise ServiceWarning(message='部门存在用户,不允许删除') await DeptDao.delete_dept_dao(query_db, DeptModel(deptId=dept_id)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入部门id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入部门id为空') @classmethod async def dept_detail_services(cls, query_db: AsyncSession, dept_id: int): """ 获取部门详细信息service + :param query_db: orm对象 :param dept_id: 部门id :return: 部门id对应的信息 """ dept = await DeptDao.get_dept_detail_by_id(query_db, dept_id=dept_id) - result = DeptModel(**CamelCaseUtil.transform_result(dept)) + if dept: + result = DeptModel(**CamelCaseUtil.transform_result(dept)) + else: + result = DeptModel(**dict()) return result @@ -163,11 +201,13 @@ class DeptService: def list_to_tree(cls, permission_list: list) -> list: """ 工具方法:根据部门列表信息生成树形嵌套数据 + :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 = [ + dict(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)) @@ -189,26 +229,47 @@ class DeptService: return container @classmethod - async def update_children_info(cls, query_db, page_object): + async def replace_first(cls, original_str: str, old_str: str, new_str: str): + """ + 工具方法:替换字符串 + + :param original_str: 需要替换的原始字符串 + :param old_str: 用于匹配的字符串 + :param new_str: 替换的字符串 + :return: 替换后的字符串 + """ + if original_str.startswith(old_str): + return original_str.replace(old_str, new_str, 1) + else: + return original_str + + @classmethod + async def update_parent_dept_status_normal(cls, query_db: AsyncSession, dept: DeptModel): """ - 工具方法:递归更新子部门信息 + 更新父部门状态为正常 + :param query_db: orm对象 - :param page_object: 编辑部门对象 + :param dept: 部门对象 + :return: + """ + dept_id_list = dept.ancestors.split(',') + await DeptDao.update_dept_status_normal_dao(query_db, dept_id_list) + + @classmethod + async def update_dept_children(cls, query_db: AsyncSession, dept_id: int, new_ancestors: str, old_ancestors: str): + """ + 更新子部门信息 + + :param query_db: orm对象 + :param dept_id: 部门id + :param new_ancestors: 新的祖先 + :param old_ancestors: 旧的祖先 :return: """ - children_info = await DeptDao.get_children_dept(query_db, page_object.dept_id) - if children_info: - for child in children_info: - child.ancestors = f'{page_object.ancestors},{page_object.dept_id}' - await DeptDao.edit_dept_dao(query_db, - dict(dept_id=child.dept_id, - ancestors=child.ancestors, - update_by=page_object.update_by, - update_time=page_object.update_time - ) - ) - await cls.update_children_info(query_db, DeptModel(dept_id=child.dept_id, - ancestors=child.ancestors, - update_by=page_object.update_by, - update_time=page_object.update_time - )) + children = await DeptDao.get_children_dept_dao(query_db, dept_id) + update_children = [] + for child in children: + child_ancestors = await cls.replace_first(child.ancestors, old_ancestors, new_ancestors) + update_children.append({'dept_id': child.dept_id, 'ancestors': child_ancestors}) + if children: + await DeptDao.update_dept_children_dao(query_db, update_children) diff --git a/ruoyi-fastapi-backend/module_admin/service/dict_service.py b/ruoyi-fastapi-backend/module_admin/service/dict_service.py index efcd3b7dc42d0459147352704114ecd9017eb8b3..53540833f38376d3425223331c5b4355bcbc6e40 100644 --- a/ruoyi-fastapi-backend/module_admin/service/dict_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/dict_service.py @@ -1,9 +1,21 @@ -from fastapi import Request import json -from config.env import RedisInitKeyConfig -from module_admin.dao.dict_dao import * +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from config.enums import RedisInitKeyConfig +from exceptions.exception import ServiceException +from module_admin.dao.dict_dao import DictDataDao, DictTypeDao from module_admin.entity.vo.common_vo import CrudResponseModel -from utils.common_util import export_list2excel, CamelCaseUtil +from module_admin.entity.vo.dict_vo import ( + DeleteDictDataModel, + DeleteDictTypeModel, + DictDataModel, + DictDataPageQueryModel, + DictTypeModel, + DictTypePageQueryModel, +) +from utils.common_util import CamelCaseUtil, export_list2excel class DictTypeService: @@ -12,9 +24,12 @@ class DictTypeService: """ @classmethod - async def get_dict_type_list_services(cls, query_db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False): + async def get_dict_type_list_services( + cls, query_db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False + ): """ 获取字典类型列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -24,23 +39,40 @@ class DictTypeService: return dict_type_list_result + @classmethod + async def check_dict_type_unique_services(cls, query_db: AsyncSession, page_object: DictTypeModel): + """ + 校验字典类型称是否唯一service + + :param query_db: orm对象 + :param page_object: 字典类型对象 + :return: 校验结果 + """ + dict_id = -1 if page_object.dict_id is None else page_object.dict_id + dict_type = await DictTypeDao.get_dict_type_detail_by_info( + query_db, DictTypeModel(dictType=page_object.dict_type) + ) + if dict_type and dict_type.dict_id != dict_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): """ 新增字典类型信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 新增岗位对象 :return: 新增字典类型校验结果 """ - dict_type = await DictTypeDao.get_dict_type_detail_by_info(query_db, DictTypeModel(dictType=page_object.dict_type)) - if dict_type: - result = dict(is_success=False, message='字典类型已存在') + 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 DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis) + 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() @@ -52,72 +84,90 @@ class DictTypeService: async def edit_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): """ 编辑字典类型信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 编辑字典类型对象 :return: 编辑字典类型校验结果 """ edit_dict_type = page_object.model_dump(exclude_unset=True) - dict_type_info = await cls.dict_type_detail_services(query_db, edit_dict_type.get('dict_id')) - if dict_type_info: - if dict_type_info.dict_type != page_object.dict_type or dict_type_info.dict_name != page_object.dict_name: - dict_type = await DictTypeDao.get_dict_type_detail_by_info(query_db, DictTypeModel(dictType=page_object.dict_type)) - if dict_type: - result = dict(is_success=False, message='字典类型已存在') - return CrudResponseModel(**result) - try: - if dict_type_info.dict_type != page_object.dict_type: - query_dict_data = await DictDataModel(dictType=dict_type_info.dict_type) - dict_data_list = await DictDataDao.get_dict_data_list(query_db, query_dict_data) - for dict_data in dict_data_list: - edit_dict_data = DictDataModel(dictCode=dict_data.dict_code, dictType=page_object.dict_type, updateBy=page_object.update_by).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() - await DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + dict_type_info = await cls.dict_type_detail_services(query_db, page_object.dict_id) + 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.dict_code, + dictType=page_object.dict_type, + updateBy=page_object.update_by, + ).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: - result = dict(is_success=False, message='字典类型不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='字典类型不存在') @classmethod - async def delete_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteDictTypeModel): + async def delete_dict_type_services( + cls, request: Request, query_db: AsyncSession, page_object: DeleteDictTypeModel + ): """ 删除字典类型信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 删除字典类型对象 :return: 删除字典类型校验结果 """ - if page_object.dict_ids.split(','): + if page_object.dict_ids: dict_id_list = page_object.dict_ids.split(',') try: + delete_dict_type_list = [] for dict_id in dict_id_list: - await DictTypeDao.delete_dict_type_dao(query_db, DictTypeModel(dictId=dict_id)) + dict_type_into = await cls.dict_type_detail_services(query_db, int(dict_id)) + if (await DictDataDao.count_dict_data_dao(query_db, dict_type_into.dict_type)) > 0: + raise ServiceException(message=f'{dict_type_into.dict_name}已分配,不能删除') + await DictTypeDao.delete_dict_type_dao(query_db, DictTypeModel(dictId=int(dict_id))) + delete_dict_type_list.append(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type_into.dict_type}') await query_db.commit() - await DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='删除成功') + if delete_dict_type_list: + await request.app.state.redis.delete(*delete_dict_type_list) + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入字典类型id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入字典类型id为空') @classmethod async def dict_type_detail_services(cls, query_db: AsyncSession, dict_id: int): """ 获取字典类型详细信息service + :param query_db: orm对象 :param dict_id: 字典类型id :return: 字典类型id对应的信息 """ dict_type = await DictTypeDao.get_dict_type_detail_by_id(query_db, dict_id=dict_id) - result = DictTypeModel(**CamelCaseUtil.transform_result(dict_type)) + if dict_type: + result = DictTypeModel(**CamelCaseUtil.transform_result(dict_type)) + else: + result = DictTypeModel(**dict()) return result @@ -125,20 +175,21 @@ class DictTypeService: async def export_dict_type_list_services(dict_type_list: List): """ 导出字典类型信息service + :param dict_type_list: 字典信息列表 :return: 字典信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "dictId": "字典编号", - "dictName": "字典名称", - "dictType": "字典类型", - "status": "状态", - "createBy": "创建者", - "createTime": "创建时间", - "updateBy": "更新者", - "updateTime": "更新时间", - "remark": "备注", + 'dictId': '字典编号', + 'dictName': '字典名称', + 'dictType': '字典类型', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', } data = dict_type_list @@ -148,7 +199,9 @@ class DictTypeService: item['status'] = '正常' else: item['status'] = '停用' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @@ -157,6 +210,7 @@ class DictTypeService: async def refresh_sys_dict_services(cls, request: Request, query_db: AsyncSession): """ 刷新字典缓存信息service + :param request: Request对象 :param query_db: orm对象 :return: 刷新字典缓存校验结果 @@ -173,9 +227,12 @@ class DictDataService: """ @classmethod - async def get_dict_data_list_services(cls, query_db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False): + async def get_dict_data_list_services( + cls, query_db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False + ): """ 获取字典数据列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -189,6 +246,7 @@ class DictDataService: async def query_dict_data_list_services(cls, query_db: AsyncSession, dict_type: str): """ 获取字典数据列表信息service + :param query_db: orm对象 :param dict_type: 字典类型 :return: 字典数据列表信息对象 @@ -201,12 +259,13 @@ class DictDataService: async def init_cache_sys_dict_services(cls, query_db: AsyncSession, redis): """ 应用初始化:获取所有字典类型对应的字典数据信息并缓存service + :param query_db: orm对象 :param redis: redis对象 :return: """ # 获取以sys_dict:开头的键列表 - keys = await redis.keys(f"{RedisInitKeyConfig.SYS_DICT.get('key')}:*") + keys = await redis.keys(f'{RedisInitKeyConfig.SYS_DICT.key}:*') # 删除匹配的键 if keys: await redis.delete(*keys) @@ -215,111 +274,151 @@ class DictDataService: dict_type = dict_type_obj.dict_type dict_data_list = await DictDataDao.query_dict_data_list(query_db, dict_type) dict_data = [CamelCaseUtil.transform_result(row) for row in dict_data_list if row] - await redis.set(f"{RedisInitKeyConfig.SYS_DICT.get('key')}:{dict_type}", json.dumps(dict_data, ensure_ascii=False, default=str)) + await redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}', + json.dumps(dict_data, ensure_ascii=False, default=str), + ) @classmethod async def query_dict_data_list_from_cache_services(cls, redis, dict_type: str): """ 从缓存获取字典数据列表信息service + :param redis: redis对象 :param dict_type: 字典类型 :return: 字典数据列表信息对象 """ result = [] - dict_data_list_result = await redis.get(f"{RedisInitKeyConfig.SYS_DICT.get('key')}:{dict_type}") + dict_data_list_result = await redis.get(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}') if dict_data_list_result: result = json.loads(dict_data_list_result) return CamelCaseUtil.transform_result(result) + @classmethod + async def check_dict_data_unique_services(cls, query_db: AsyncSession, page_object: DictDataModel): + """ + 校验字典数据是否唯一service + + :param query_db: orm对象 + :param page_object: 字典数据对象 + :return: 校验结果 + """ + dict_code = -1 if page_object.dict_code is None else page_object.dict_code + dict_data = await DictDataDao.get_dict_data_detail_by_info(query_db, page_object) + if dict_data and dict_data.dict_code != dict_code: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): """ 新增字典数据信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 新增岗位对象 :return: 新增字典数据校验结果 """ - dict_data = await DictDataDao.get_dict_data_detail_by_info(query_db, page_object) - if dict_data: - result = dict(is_success=False, message='字典数据已存在') + if not await cls.check_dict_data_unique_services(query_db, page_object): + 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() - await cls.init_cache_sys_dict_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='新增成功') + 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 - return CrudResponseModel(**result) - @classmethod async def edit_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): """ 编辑字典数据信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 编辑字典数据对象 :return: 编辑字典数据校验结果 """ edit_data_type = page_object.model_dump(exclude_unset=True) - dict_data_info = await cls.dict_data_detail_services(query_db, edit_data_type.get('dict_code')) - if dict_data_info: - if dict_data_info.dict_type != page_object.dict_type or dict_data_info.dict_label != page_object.dict_label or dict_data_info.dict_value != page_object.dict_value: - dict_data = await DictDataDao.get_dict_data_detail_by_info(query_db, page_object) - if dict_data: - result = dict(is_success=False, message='字典数据已存在') - return CrudResponseModel(**result) - try: - await DictDataDao.edit_dict_data_dao(query_db, edit_data_type) - await query_db.commit() - await cls.init_cache_sys_dict_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + dict_data_info = await cls.dict_data_detail_services(query_db, page_object.dict_code) + if dict_data_info.dict_code: + if not await cls.check_dict_data_unique_services(query_db, page_object): + 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 else: - result = dict(is_success=False, message='字典数据不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='字典数据不存在') @classmethod - async def delete_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteDictDataModel): + async def delete_dict_data_services( + cls, request: Request, query_db: AsyncSession, page_object: DeleteDictDataModel + ): """ 删除字典数据信息service + :param request: Request对象 :param query_db: orm对象 :param page_object: 删除字典数据对象 :return: 删除字典数据校验结果 """ - if page_object.dict_codes.split(','): + if page_object.dict_codes: dict_code_list = page_object.dict_codes.split(',') try: + delete_dict_type_list = [] for dict_code in dict_code_list: + dict_data = await cls.dict_data_detail_services(query_db, int(dict_code)) await DictDataDao.delete_dict_data_dao(query_db, DictDataModel(dictCode=dict_code)) + delete_dict_type_list.append(dict_data.dict_type) await query_db.commit() - await cls.init_cache_sys_dict_services(query_db, request.app.state.redis) - result = dict(is_success=True, message='删除成功') + for dict_type in list(set(delete_dict_type_list)): + dict_data_list = await cls.query_dict_data_list_services(query_db, dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{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: - result = dict(is_success=False, message='传入字典数据id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入字典数据id为空') @classmethod async def dict_data_detail_services(cls, query_db: AsyncSession, dict_code: int): """ 获取字典数据详细信息service + :param query_db: orm对象 :param dict_code: 字典数据id :return: 字典数据id对应的信息 """ dict_data = await DictDataDao.get_dict_data_detail_by_id(query_db, dict_code=dict_code) - result = DictDataModel(**CamelCaseUtil.transform_result(dict_data)) + if dict_data: + result = DictDataModel(**CamelCaseUtil.transform_result(dict_data)) + else: + result = DictDataModel(**dict()) return result @@ -327,25 +426,26 @@ class DictDataService: async def export_dict_data_list_services(dict_data_list: List): """ 导出字典数据信息service + :param dict_data_list: 字典数据信息列表 :return: 字典数据信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "dictCode": "字典编码", - "dictSort": "字典标签", - "dictLabel": "字典键值", - "dictValue": "字典排序", - "dictType": "字典类型", - "cssClass": "样式属性", - "listClass": "表格回显样式", - "isDefault": "是否默认", - "status": "状态", - "createBy": "创建者", - "createTime": "创建时间", - "updateBy": "更新者", - "updateTime": "更新时间", - "remark": "备注", + 'dictCode': '字典编码', + 'dictSort': '字典标签', + 'dictLabel': '字典键值', + 'dictValue': '字典排序', + 'dictType': '字典类型', + 'cssClass': '样式属性', + 'listClass': '表格回显样式', + 'isDefault': '是否默认', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', } data = dict_data_list @@ -359,7 +459,9 @@ class DictDataService: item['isDefault'] = '是' else: item['isDefault'] = '否' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data 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 99d22318f3dc1bb683fbe87574b613a399f6aee1..f4c9f376b920f7929cf3aa00dd10466c08d6af7f 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_log_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py @@ -1,6 +1,11 @@ -from module_admin.dao.job_log_dao import * -from module_admin.service.dict_service import Request, DictDataService +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Session +from typing import List +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.common_util import export_list2excel @@ -10,9 +15,12 @@ class JobLogService: """ @classmethod - async def get_job_log_list_services(cls, query_db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False): + async def get_job_log_list_services( + cls, query_db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False + ): """ 获取定时任务日志列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -26,6 +34,7 @@ class JobLogService: def add_job_log_services(cls, query_db: Session, page_object: JobLogModel): """ 新增定时任务日志信息service + :param query_db: orm对象 :param page_object: 新增定时任务日志对象 :return: 新增定时任务日志校验结果 @@ -44,11 +53,12 @@ class JobLogService: async def delete_job_log_services(cls, query_db: AsyncSession, page_object: DeleteJobLogModel): """ 删除定时任务日志信息service + :param query_db: orm对象 :param page_object: 删除定时任务日志对象 :return: 删除定时任务日志校验结果 """ - if page_object.job_log_ids.split(','): + if page_object.job_log_ids: job_log_id_list = page_object.job_log_ids.split(',') try: for job_log_id in job_log_id_list: @@ -66,6 +76,7 @@ class JobLogService: async def clear_job_log_services(cls, query_db: AsyncSession): """ 清除定时任务日志信息service + :param query_db: orm对象 :return: 清除定时任务日志校验结果 """ @@ -83,32 +94,39 @@ class JobLogService: async def export_job_log_list_services(request: Request, job_log_list: List): """ 导出定时任务日志信息service + :param request: Request对象 :param job_log_list: 定时任务日志信息列表 :return: 定时任务日志信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "jobLogId": "任务日志编码", - "jobName": "任务名称", - "jobGroup": "任务组名", - "jobExecutor": "任务执行器", - "invokeTarget": "调用目标字符串", - "jobArgs": "位置参数", - "jobKwargs": "关键字参数", - "jobTrigger": "任务触发器", - "jobMessage": "日志信息", - "status": "执行状态", - "exceptionInfo": "异常信息", - "createTime": "创建时间", + 'jobLogId': '任务日志编码', + 'jobName': '任务名称', + 'jobGroup': '任务组名', + 'jobExecutor': '任务执行器', + 'invokeTarget': '调用目标字符串', + 'jobArgs': '位置参数', + 'jobKwargs': '关键字参数', + 'jobTrigger': '任务触发器', + 'jobMessage': '日志信息', + 'status': '执行状态', + 'exceptionInfo': '异常信息', + 'createTime': '创建时间', } data = job_log_list - job_group_list = await DictDataService.query_dict_data_list_from_cache_services(request.app.state.redis, dict_type='sys_job_group') + 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_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] + 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 + ] job_executor_option_dict = {item.get('value'): item for item in job_executor_option} for item in data: @@ -120,8 +138,9 @@ class JobLogService: item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label') if str(item.get('jobExecutor')) in job_executor_option_dict.keys(): item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label') - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in - data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/job_service.py b/ruoyi-fastapi-backend/module_admin/service/job_service.py index 78775d47334cc398f417a0fda68eb0008dd88245..2c4c5bb3538e38155e612eaf17004dff8d2b0c61 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -1,8 +1,16 @@ -from module_admin.dao.job_dao import * -from module_admin.service.dict_service import Request, DictDataService -from module_admin.entity.vo.common_vo import CrudResponseModel -from utils.common_util import export_list2excel, CamelCaseUtil +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant, JobConstant 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, export_list2excel +from utils.cron_util import CronUtil +from utils.string_util import StringUtil class JobService: @@ -11,9 +19,12 @@ class JobService: """ @classmethod - async def get_job_list_services(cls, query_db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False): + async def get_job_list_services( + cls, query_db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False + ): """ 获取定时任务列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -23,21 +34,52 @@ class JobService: return job_list_result + @classmethod + async def check_job_unique_services(cls, query_db: AsyncSession, page_object: JobModel): + """ + 校验定时任务是否存在service + + :param query_db: orm对象 + :param page_object: 定时任务对象 + :return: 校验结果 + """ + job_id = -1 if page_object.job_id is None else page_object.job_id + job = await JobDao.get_job_detail_by_info(query_db, page_object) + if job and job.job_id != job_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel): """ 新增定时任务信息service + :param query_db: orm对象 :param page_object: 新增定时任务对象 :return: 新增定时任务校验结果 """ - job = await JobDao.get_job_detail_by_info(query_db, page_object) - if job: - result = dict(is_success=False, message='定时任务已存在') + 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): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') + elif 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] + ): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用') + elif 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): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不在白名单内') + elif not await cls.check_job_unique_services(query_db, page_object): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,定时任务已存在') else: try: - await JobDao.add_job_dao(query_db, page_object) - job_info = await JobDao.get_job_detail_by_info(query_db, page_object) + 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() @@ -52,6 +94,7 @@ class JobService: async def edit_job_services(cls, query_db: AsyncSession, page_object: EditJobModel): """ 编辑定时任务信息service + :param query_db: orm对象 :param page_object: 编辑定时任务对象 :return: 编辑定时任务校验结果 @@ -59,13 +102,31 @@ class JobService: edit_job = page_object.model_dump(exclude_unset=True) if page_object.type == 'status': del edit_job['type'] - job_info = await cls.job_detail_services(query_db, edit_job.get('job_id')) + job_info = await cls.job_detail_services(query_db, page_object.job_id) if job_info: - if page_object.type != 'status' and (job_info.job_name != page_object.job_name or job_info.job_group != page_object.job_group or job_info.invoke_target != page_object.invoke_target or job_info.cron_expression != page_object.cron_expression): - job = await JobDao.get_job_detail_by_info(query_db, page_object) - if job: - result = dict(is_success=False, message='定时任务已存在') - return CrudResponseModel(**result) + 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): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') + elif 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] + ): + raise ServiceException( + message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用' + ) + elif 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): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不在白名单内') + elif 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) query_job = SchedulerUtil.get_scheduler_job(job_id=edit_job.get('job_id')) @@ -75,19 +136,18 @@ class JobService: job_info = await cls.job_detail_services(query_db, edit_job.get('job_id')) SchedulerUtil.add_scheduler_job(job_info=job_info) await query_db.commit() - result = dict(is_success=True, message='更新成功') + return CrudResponseModel(is_success=True, message='更新成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='定时任务不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='定时任务不存在') @classmethod async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel): """ 执行一次定时任务service + :param query_db: orm对象 :param page_object: 定时任务对象 :return: 执行一次定时任务结果 @@ -98,21 +158,20 @@ class JobService: job_info = await cls.job_detail_services(query_db, page_object.job_id) if job_info: SchedulerUtil.execute_scheduler_job_once(job_info=job_info) - result = dict(is_success=True, message='执行成功') + return CrudResponseModel(is_success=True, message='执行成功') else: - result = dict(is_success=False, message='定时任务不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='定时任务不存在') @classmethod async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel): """ 删除定时任务信息service + :param query_db: orm对象 :param page_object: 删除定时任务对象 :return: 删除定时任务校验结果 """ - if page_object.job_ids.split(','): + if page_object.job_ids: job_id_list = page_object.job_ids.split(',') try: for job_id in job_id_list: @@ -121,24 +180,27 @@ class JobService: if query_job: SchedulerUtil.remove_scheduler_job(job_id=job_id) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入定时任务id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入定时任务id为空') @classmethod async def job_detail_services(cls, query_db: AsyncSession, job_id: int): """ 获取定时任务详细信息service + :param query_db: orm对象 :param job_id: 定时任务id :return: 定时任务id对应的信息 """ job = await JobDao.get_job_detail_by_id(query_db, job_id=job_id) - result = JobModel(**CamelCaseUtil.transform_result(job)) + if job: + result = JobModel(**CamelCaseUtil.transform_result(job)) + else: + result = JobModel(**dict()) return result @@ -146,36 +208,43 @@ class JobService: async def export_job_list_services(request: Request, job_list: List): """ 导出定时任务信息service + :param request: Request对象 :param job_list: 定时任务信息列表 :return: 定时任务信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "jobId": "任务编码", - "jobName": "任务名称", - "jobGroup": "任务组名", - "jobExecutor": "任务执行器", - "invokeTarget": "调用目标字符串", - "jobArgs": "位置参数", - "jobKwargs": "关键字参数", - "cronExpression": "cron执行表达式", - "misfirePolicy": "计划执行错误策略", - "concurrent": "是否并发执行", - "status": "状态", - "createBy": "创建者", - "createTime": "创建时间", - "updateBy": "更新者", - "updateTime": "更新时间", - "remark": "备注", + 'jobId': '任务编码', + 'jobName': '任务名称', + 'jobGroup': '任务组名', + 'jobExecutor': '任务执行器', + 'invokeTarget': '调用目标字符串', + 'jobArgs': '位置参数', + 'jobKwargs': '关键字参数', + 'cronExpression': 'cron执行表达式', + 'misfirePolicy': '计划执行错误策略', + 'concurrent': '是否并发执行', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', } data = job_list - job_group_list = await DictDataService.query_dict_data_list_from_cache_services(request.app.state.redis, dict_type='sys_job_group') + 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_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] + 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 + ] job_executor_option_dict = {item.get('value'): item for item in job_executor_option} for item in data: @@ -197,7 +266,9 @@ class JobService: item['concurrent'] = '允许' else: item['concurrent'] = '禁止' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/log_service.py b/ruoyi-fastapi-backend/module_admin/service/log_service.py index 4e15d2c81435c3c93243cefed8d13d7fec8a1194..0c80a60c45fb1cd10fc102152beda41be8f2c2c9 100644 --- a/ruoyi-fastapi-backend/module_admin/service/log_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/log_service.py @@ -1,7 +1,20 @@ -from module_admin.dao.log_dao import * -from module_admin.service.dict_service import Request, DictDataService +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from exceptions.exception import ServiceException +from module_admin.dao.log_dao import LoginLogDao, OperationLogDao from module_admin.entity.vo.common_vo import CrudResponseModel -from utils.common_util import export_list2excel, CamelCaseUtil +from module_admin.entity.vo.log_vo import ( + DeleteLoginLogModel, + DeleteOperLogModel, + LogininforModel, + LoginLogPageQueryModel, + OperLogModel, + OperLogPageQueryModel, + UnlockUser, +) +from module_admin.service.dict_service import DictDataService +from utils.common_util import export_list2excel class OperationLogService: @@ -10,9 +23,12 @@ class OperationLogService: """ @classmethod - async def get_operation_log_list_services(cls, query_db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False): + async def get_operation_log_list_services( + cls, query_db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False + ): """ 获取操作日志列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -26,6 +42,7 @@ class OperationLogService: async def add_operation_log_services(cls, query_db: AsyncSession, page_object: OperLogModel): """ 新增操作日志service + :param query_db: orm对象 :param page_object: 新增操作日志对象 :return: 新增操作日志校验结果 @@ -33,83 +50,85 @@ class OperationLogService: try: await OperationLogDao.add_operation_log_dao(query_db, page_object) await query_db.commit() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudResponseModel(**result) + raise e @classmethod async def delete_operation_log_services(cls, query_db: AsyncSession, page_object: DeleteOperLogModel): """ 删除操作日志信息service + :param query_db: orm对象 :param page_object: 删除操作日志对象 :return: 删除操作日志校验结果 """ - if page_object.oper_ids.split(','): + if page_object.oper_ids: oper_id_list = page_object.oper_ids.split(',') try: for oper_id in oper_id_list: await OperationLogDao.delete_operation_log_dao(query_db, OperLogModel(operId=oper_id)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入操作日志id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入操作日志id为空') @classmethod async def clear_operation_log_services(cls, query_db: AsyncSession): """ 清除操作日志信息service + :param query_db: orm对象 :return: 清除操作日志校验结果 """ try: await OperationLogDao.clear_operation_log_dao(query_db) await query_db.commit() - result = dict(is_success=True, message='清除成功') + return CrudResponseModel(is_success=True, message='清除成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod async def export_operation_log_list_services(cls, request: Request, operation_log_list: List): """ 导出操作日志信息service + :param request: Request对象 :param operation_log_list: 操作日志信息列表 :return: 操作日志信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "operId": "日志编号", - "title": "系统模块", - "businessType": "操作类型", - "method": "方法名称", - "requestMethod": "请求方式", - "operName": "操作人员", - "deptName": "部门名称", - "operUrl": "请求URL", - "operIp": "操作地址", - "operLocation": "操作地点", - "operParam": "请求参数", - "jsonResult": "返回参数", - "status": "操作状态", - "error_msg": "错误消息", - "operTime": "操作日期", - "costTime": "消耗时间(毫秒)" + 'operId': '日志编号', + 'title': '系统模块', + 'businessType': '操作类型', + 'method': '方法名称', + 'requestMethod': '请求方式', + 'operName': '操作人员', + 'deptName': '部门名称', + 'operUrl': '请求URL', + 'operIp': '操作地址', + 'operLocation': '操作地点', + 'operParam': '请求参数', + 'jsonResult': '返回参数', + 'status': '操作状态', + 'error_msg': '错误消息', + 'operTime': '操作日期', + 'costTime': '消耗时间(毫秒)', } data = operation_log_list - operation_type_list = await DictDataService.query_dict_data_list_from_cache_services(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] + operation_type_list = await DictDataService.query_dict_data_list_from_cache_services( + 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 + ] operation_type_option_dict = {item.get('value'): item for item in operation_type_option} for item in data: @@ -120,7 +139,9 @@ class OperationLogService: if str(item.get('businessType')) in operation_type_option_dict.keys(): item['businessType'] = operation_type_option_dict.get(str(item.get('businessType'))).get('label') - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @@ -132,9 +153,12 @@ class LoginLogService: """ @classmethod - async def get_login_log_list_services(cls, query_db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False): + async def get_login_log_list_services( + cls, query_db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False + ): """ 获取登录日志列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -148,6 +172,7 @@ class LoginLogService: async def add_login_log_services(cls, query_db: AsyncSession, page_object: LogininforModel): """ 新增登录日志service + :param query_db: orm对象 :param page_object: 新增登录日志对象 :return: 新增登录日志校验结果 @@ -155,80 +180,77 @@ class LoginLogService: try: await LoginLogDao.add_login_log_dao(query_db, page_object) await query_db.commit() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudResponseModel(**result) + raise e @classmethod async def delete_login_log_services(cls, query_db: AsyncSession, page_object: DeleteLoginLogModel): """ 删除操作日志信息service + :param query_db: orm对象 :param page_object: 删除操作日志对象 :return: 删除操作日志校验结果 """ - if page_object.info_ids.split(','): + if page_object.info_ids: info_id_list = page_object.info_ids.split(',') try: for info_id in info_id_list: await LoginLogDao.delete_login_log_dao(query_db, LogininforModel(infoId=info_id)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入登录日志id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入登录日志id为空') @classmethod async def clear_login_log_services(cls, query_db: AsyncSession): """ 清除操作日志信息service + :param query_db: orm对象 :return: 清除操作日志校验结果 """ try: await LoginLogDao.clear_login_log_dao(query_db) await query_db.commit() - result = dict(is_success=True, message='清除成功') + return CrudResponseModel(is_success=True, message='清除成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod async def unlock_user_services(cls, request: Request, unlock_user: UnlockUser): - locked_user = await request.app.state.redis.get(f"account_lock:{unlock_user.user_name}") + 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}") - result = dict(is_success=True, message='解锁成功') + await request.app.state.redis.delete(f'account_lock:{unlock_user.user_name}') + return CrudResponseModel(is_success=True, message='解锁成功') else: - result = dict(is_success=False, message='该用户未锁定') - return CrudResponseModel(**result) + raise ServiceException(message='该用户未锁定') @staticmethod async def export_login_log_list_services(login_log_list: List): """ 导出登录日志信息service + :param login_log_list: 登录日志信息列表 :return: 登录日志信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "infoId": "访问编号", - "userName": "用户名称", - "ipaddr": "登录地址", - "loginLocation": "登录地点", - "browser": "浏览器", - "os": "操作系统", - "status": "登录状态", - "msg": "操作信息", - "loginTime": "登录日期" + 'infoId': '访问编号', + 'userName': '用户名称', + 'ipaddr': '登录地址', + 'loginLocation': '登录地点', + 'browser': '浏览器', + 'os': '操作系统', + 'status': '登录状态', + 'msg': '操作信息', + 'loginTime': '登录日期', } data = login_log_list @@ -238,7 +260,9 @@ class LoginLogService: item['status'] = '成功' else: item['status'] = '失败' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/login_service.py b/ruoyi-fastapi-backend/module_admin/service/login_service.py index baeb7aa0e154fa8949e0993a6ac4bcd1ff252c46..5579f0da08a55c6b7152d912a5f5f2ea207bcc90 100644 --- a/ruoyi-fastapi-backend/module_admin/service/login_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/login_service.py @@ -1,23 +1,30 @@ -from fastapi import Request, Form -from fastapi import Depends -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jose import JWTError, jwt +import jwt import random import uuid -from datetime import timedelta -from module_admin.service.user_service import * -from module_admin.entity.vo.login_vo import * -from module_admin.entity.vo.common_vo import CrudResponseModel -from module_admin.dao.login_dao import * -from exceptions.exception import LoginException, AuthException -from config.env import AppConfig, JwtConfig, RedisInitKeyConfig +from datetime import datetime, timedelta, timezone +from fastapi import Depends, Form, Request +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from jwt.exceptions import InvalidTokenError +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 config.env import AppConfig, JwtConfig from config.get_db import get_db +from exceptions.exception import LoginException, AuthException, ServiceException +from module_admin.dao.login_dao import login_by_account +from module_admin.dao.user_dao import UserDao +from module_admin.entity.do.menu_do import SysMenu +from module_admin.entity.vo.common_vo import CrudResponseModel +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 from utils.common_util import CamelCaseUtil -from utils.pwd_util import * -from utils.response_util import * -from utils.message_util import * +from utils.log_util import logger +from utils.message_util import message_service +from utils.pwd_util import PwdUtil -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl='login') class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm): @@ -26,19 +33,25 @@ class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm): """ def __init__( - self, - grant_type: str = Form(default=None, regex="password"), - username: str = Form(), - password: str = Form(), - scope: str = Form(default=""), - client_id: Optional[str] = Form(default=None), - 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) + self, + grant_type: str = Form(default=None, regex='password'), + username: str = Form(), + password: str = Form(), + scope: str = Form(default=''), + client_id: Optional[str] = Form(default=None), + 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), ): - super().__init__(grant_type=grant_type, username=username, password=password, - scope=scope, client_id=client_id, client_secret=client_secret) + super().__init__( + grant_type=grant_type, + username=username, + password=password, + scope=scope, + client_id=client_id, + client_secret=client_secret, + ) self.code = code self.uuid = uuid self.login_info = login_info @@ -53,6 +66,7 @@ class LoginService: async def authenticate_user(cls, request: Request, query_db: AsyncSession, login_user: UserLogin): """ 根据用户名密码校验用户登录 + :param request: Request对象 :param query_db: orm对象 :param login_user: 登录用户对象 @@ -60,105 +74,119 @@ class LoginService: """ await cls.__check_login_ip(request) account_lock = await request.app.state.redis.get( - f"{RedisInitKeyConfig.ACCOUNT_LOCK.get('key')}:{login_user.user_name}") + f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}' + ) if login_user.user_name == account_lock: - logger.warning("账号已锁定,请稍后再试") - raise LoginException(data="", message="账号已锁定,请稍后再试") + logger.warning('账号已锁定,请稍后再试') + raise LoginException(data='', message='账号已锁定,请稍后再试') # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug - 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.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 + ) # 判断是否开启验证码,开启则验证,否则不验证(dev模式下来自API文档的登录请求不检验) - if not login_user.captcha_enabled or ((request_from_swagger or request_from_redoc) and AppConfig.app_env == 'dev'): + if not login_user.captcha_enabled or ( + (request_from_swagger or request_from_redoc) and AppConfig.app_env == 'dev' + ): pass else: await cls.__check_login_captcha(request, login_user) user = await login_by_account(query_db, login_user.user_name) - print(user) if not user: - logger.warning("用户不存在") - raise LoginException(data="", message="用户不存在") + logger.warning('用户不存在') + raise LoginException(data='', message='用户不存在') if not PwdUtil.verify_password(login_user.password, user[0].password): cache_password_error_count = await request.app.state.redis.get( - f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}") + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}' + ) password_error_counted = 0 if cache_password_error_count: password_error_counted = cache_password_error_count password_error_count = int(password_error_counted) + 1 await request.app.state.redis.set( - f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}", password_error_count, - ex=timedelta(minutes=10)) + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}', + password_error_count, + ex=timedelta(minutes=10), + ) if password_error_count > 5: await request.app.state.redis.delete( - f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}") + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}' + ) await request.app.state.redis.set( - f"{RedisInitKeyConfig.ACCOUNT_LOCK.get('key')}:{login_user.user_name}", login_user.user_name, - ex=timedelta(minutes=10)) - logger.warning("10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试") - raise LoginException(data="", message="10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试") - logger.warning("密码错误") - raise LoginException(data="", message="密码错误") + f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}', + login_user.user_name, + ex=timedelta(minutes=10), + ) + logger.warning('10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试') + raise LoginException(data='', message='10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试') + logger.warning('密码错误') + raise LoginException(data='', message='密码错误') if user[0].status == '1': - logger.warning("用户已停用") - raise LoginException(data="", message="用户已停用") - await request.app.state.redis.delete( - f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}") + logger.warning('用户已停用') + raise LoginException(data='', message='用户已停用') + await request.app.state.redis.delete(f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}') return user @classmethod async def __check_login_ip(cls, request: Request): """ 校验用户登录ip是否在黑名单内 + :param request: Request对象 :return: 校验结果 """ - black_ip_value = await request.app.state.redis.get( - f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.login.blackIPList") + black_ip_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.login.blackIPList') black_ip_list = black_ip_value.split(',') if black_ip_value else [] if request.headers.get('X-Forwarded-For') in black_ip_list: - logger.warning("当前IP禁止登录") - raise LoginException(data="", message="当前IP禁止登录") + logger.warning('当前IP禁止登录') + raise LoginException(data='', message='当前IP禁止登录') return True @classmethod async def __check_login_captcha(cls, request: Request, login_user: UserLogin): """ 校验用户登录验证码 + :param request: Request对象 :param login_user: 登录用户对象 :return: 校验结果 """ - captcha_value = await request.app.state.redis.get( - f"{RedisInitKeyConfig.CAPTCHA_CODES.get('key')}:{login_user.uuid}") + captcha_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{login_user.uuid}') if not captcha_value: - logger.warning("验证码已失效") - raise LoginException(data="", message="验证码已失效") + logger.warning('验证码已失效') + raise LoginException(data='', message='验证码已失效') if login_user.code != str(captcha_value): - logger.warning("验证码错误") - raise LoginException(data="", message="验证码错误") + logger.warning('验证码错误') + raise LoginException(data='', message='验证码错误') return True @classmethod async def create_access_token(cls, data: dict, expires_delta: Union[timedelta, None] = None): """ 根据登录信息创建当前用户token + :param data: 登录信息 :param expires_delta: token有效期 :return: token """ to_encode = data.copy() if expires_delta: - expire = datetime.utcnow() + expires_delta + expire = datetime.now(timezone.utc) + expires_delta else: - expire = datetime.utcnow() + timedelta(minutes=30) - to_encode.update({"exp": expire}) + expire = datetime.now(timezone.utc) + timedelta(minutes=30) + to_encode.update({'exp': expire}) encoded_jwt = jwt.encode(to_encode, JwtConfig.jwt_secret_key, algorithm=JwtConfig.jwt_algorithm) return encoded_jwt @classmethod - async def get_current_user(cls, request: Request = Request, token: str = Depends(oauth2_scheme), - query_db: AsyncSession = Depends(get_db)): + async def get_current_user( + cls, request: Request = Request, token: str = Depends(oauth2_scheme), query_db: AsyncSession = Depends(get_db) + ): """ 根据token获取当前用户信息 + :param request: Request对象 :param token: 用户token :param query_db: orm对象 @@ -172,31 +200,39 @@ class LoginService: if token.startswith('Bearer'): token = token.split(' ')[1] payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) - user_id: str = payload.get("user_id") - session_id: str = payload.get("session_id") - if user_id is None: - logger.warning("用户token不合法") - raise AuthException(data="", message="用户token不合法") + user_id: str = payload.get('user_id') + session_id: str = payload.get('session_id') + if not user_id: + logger.warning('用户token不合法') + raise AuthException(data='', message='用户token不合法') token_data = TokenData(user_id=int(user_id)) - except JWTError: - logger.warning("用户token已失效,请重新登录") - raise AuthException(data="", message="用户token已失效,请重新登录") + except InvalidTokenError: + logger.warning('用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') 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不合法") - raise AuthException(data="", message="用户token不合法") + logger.warning('用户token不合法') + raise AuthException(data='', message='用户token不合法') if AppConfig.app_same_time_login: - redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}") + redis_token = await request.app.state.redis.get(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}') else: # 此方法可实现同一账号同一时间只能登录一次 - redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{query_user.get('user_basic_info').user_id}") + redis_token = await request.app.state.redis.get( + f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}" + ) if token == redis_token: if AppConfig.app_same_time_login: - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", redis_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}', + redis_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) else: - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{query_user.get('user_basic_info').user_id}", redis_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) + await request.app.state.redis.set( + f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}", + redis_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) role_id_list = [item.role_id for item in query_user.get('user_role_info')] if 1 in role_id_list: @@ -215,24 +251,32 @@ class LoginService: postIds=post_ids, roleIds=role_ids, dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), - role=CamelCaseUtil.transform_result(query_user.get('user_role_info')) - ) + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), + ), ) return current_user else: - logger.warning("用户token已失效,请重新登录") - raise AuthException(data="", message="用户token已失效,请重新登录") + logger.warning('用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') @classmethod async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession): """ 根据用户id获取当前用户路由信息 + :param user_id: 用户id :param query_db: orm对象 :return: 当前用户路由信息对象 """ query_user = await UserDao.get_user_by_id(query_db, user_id=user_id) - user_router_menu = sorted([row for row in query_user.get('user_menu_info') if row.menu_type in ['M', 'C']], key=lambda x: x.order_num) + user_router_menu = sorted( + [ + row + for row in query_user.get('user_menu_info') + if row.menu_type in [MenuConstant.TYPE_DIR, MenuConstant.TYPE_MENU] + ], + key=lambda x: x.order_num, + ) menus = cls.__generate_menus(0, user_router_menu) user_router = cls.__generate_user_router_menu(menus) return [router.model_dump(exclude_unset=True, by_alias=True) for router in user_router] @@ -241,6 +285,7 @@ class LoginService: def __generate_menus(cls, pid: int, permission_list: List[SysMenu]): """ 工具方法:根据菜单信息生成菜单信息树形嵌套数据 + :param pid: 菜单id :param permission_list: 菜单列表信息 :return: 菜单信息树形嵌套数据 @@ -260,6 +305,7 @@ class LoginService: def __generate_user_router_menu(cls, permission_list: List[MenuTreeModel]): """ 工具方法:根据菜单树信息生成路由信息树形嵌套数据 + :param permission_list: 菜单树列表信息 :return: 路由信息树形嵌套数据 """ @@ -275,11 +321,11 @@ class LoginService: title=permission.menu_name, icon=permission.icon, noCache=True if permission.is_cache == 1 else False, - link=permission.path if RouterUtil.is_http(permission.path) else None - ) + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), ) c_menus = permission.children - if c_menus and permission.menu_type == 'M': + if c_menus and permission.menu_type == MenuConstant.TYPE_DIR: router.always_show = True router.redirect = 'noRedirect' router.children = cls.__generate_user_router_menu(c_menus) @@ -289,34 +335,31 @@ class LoginService: children = RouterModel( path=permission.path, component=permission.component, - name=permission.path.capitalize(), + name=RouterUtil.get_route_name(permission.route_name, permission.path), meta=MetaModel( title=permission.menu_name, icon=permission.icon, noCache=True if permission.is_cache == 1 else False, - link=permission.path if RouterUtil.is_http(permission.path) else None + link=permission.path if RouterUtil.is_http(permission.path) else None, ), - query=permission.query + query=permission.query, ) children_list.append(children) router.children = children_list elif permission.parent_id == 0 and RouterUtil.is_inner_link(permission): - router.meta = MetaModel( - title=permission.menu_name, - icon=permission.icon - ) + router.meta = MetaModel(title=permission.menu_name, icon=permission.icon) router.path = '/' children_list: List[RouterModel] = [] router_path = RouterUtil.inner_link_replace_each(permission.path) children = RouterModel( path=router_path, - component='InnerLink', - name=router_path.capitalize(), + component=MenuConstant.INNER_LINK, + name=RouterUtil.get_route_name(permission.route_name, permission.path), meta=MetaModel( title=permission.menu_name, icon=permission.icon, - link=permission.path if RouterUtil.is_http(permission.path) else None - ) + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), ) children_list.append(children) router.children = children_list @@ -329,59 +372,66 @@ class LoginService: async def register_user_services(cls, request: Request, query_db: AsyncSession, user_register: UserRegister): """ 用户注册services + :param request: Request对象 :param query_db: orm对象 :param user_register: 注册用户对象 :return: 注册结果 """ - register_enabled = True if await request.app.state.redis.get( - f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.account.registerUser") == 'true' else False - captcha_enabled = True if await request.app.state.redis.get( - f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.account.captchaEnabled") == 'true' else False + register_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') + == 'true' + else False + ) + captcha_enabled = ( + True + if 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: if captcha_enabled: captcha_value = await request.app.state.redis.get( - f"{RedisInitKeyConfig.CAPTCHA_CODES.get('key')}:{user_register.uuid}") + f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{user_register.uuid}' + ) if not captcha_value: - logger.warning("验证码已失效") - return CrudResponseModel(is_success=False, message='验证码已失效') + raise ServiceException(message='验证码已失效') elif user_register.code != str(captcha_value): - logger.warning("验证码错误") - return CrudResponseModel(is_success=False, message='验证码错误') + raise ServiceException(message='验证码错误') add_user = AddUserModel( userName=user_register.username, nickName=user_register.username, - password=PwdUtil.get_password_hash(user_register.password) + password=PwdUtil.get_password_hash(user_register.password), ) result = await UserService.add_user_services(query_db, add_user) return result else: - result = dict(is_success=False, message='注册程序已关闭,禁止注册') + raise ServiceException(message='注册程序已关闭,禁止注册') else: - result = dict(is_success=False, message='两次输入的密码不一致') - - return CrudResponseModel(**result) + raise ServiceException(message='两次输入的密码不一致') @classmethod async def get_sms_code_services(cls, request: Request, query_db: AsyncSession, user: ResetUserModel): """ 获取短信验证码service + :param request: Request对象 :param query_db: orm对象 :param user: 用户对象 :return: 短信验证码对象 """ - redis_sms_result = await request.app.state.redis.get( - f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{user.session_id}") + 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='短信验证码仍在有效期内')) is_user = await UserDao.get_user_by_name(query_db, user.user_name) if is_user: sms_code = str(random.randint(100000, 999999)) session_id = str(uuid.uuid4()) - await request.app.state.redis.set(f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{session_id}", sms_code, - ex=timedelta(minutes=2)) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SMS_CODE.key}:{session_id}', sms_code, ex=timedelta(minutes=2) + ) # 此处模拟调用短信服务 message_service(sms_code) @@ -393,13 +443,15 @@ class LoginService: async def forget_user_services(cls, request: Request, query_db: AsyncSession, forget_user: ResetUserModel): """ 用户忘记密码services + :param request: Request对象 :param query_db: orm对象 :param forget_user: 重置用户对象 :return: 重置结果 """ redis_sms_result = await request.app.state.redis.get( - f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{forget_user.session_id}") + f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}' + ) if forget_user.sms_code == redis_sms_result: forget_user.password = PwdUtil.get_password_hash(forget_user.password) forget_user.user_id = (await UserDao.get_user_by_name(query_db, forget_user.user_name)).user_id @@ -408,7 +460,7 @@ class LoginService: elif not redis_sms_result: result = dict(is_success=False, message='短信验证码已过期') else: - await request.app.state.redis.delete(f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{forget_user.session_id}") + await request.app.state.redis.delete(f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}') result = dict(is_success=False, message='短信验证码不正确') return CrudResponseModel(**result) @@ -417,11 +469,12 @@ class LoginService: async def logout_services(cls, request: Request, session_id: str): """ 退出登录services + :param request: Request对象 :param session_id: 会话编号 :return: 退出登录结果 """ - await request.app.state.redis.delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}") + await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}') # await request.app.state.redis.delete(f'{current_user.user.user_id}_access_token') # await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id') @@ -437,19 +490,33 @@ class RouterUtil: def get_router_name(cls, menu: MenuTreeModel): """ 获取路由名称 + :param menu: 菜单数对象 :return: 路由名称 """ - router_name = menu.path.capitalize() + # 非外链并且是一级目录(类型为目录) if cls.is_menu_frame(menu): - router_name = '' + return '' - return router_name + return cls.get_route_name(menu.route_name, menu.path) + + @classmethod + def get_route_name(cls, name: str, path: str): + """ + 获取路由名称,如没有配置路由名称则取路由地址 + + :param name: 路由名称 + :param path: 路由地址 + :return: 路由名称(驼峰格式) + """ + router_name = name if name else path + return router_name.capitalize() @classmethod def get_router_path(cls, menu: MenuTreeModel): """ 获取路由地址 + :param menu: 菜单数对象 :return: 路由地址 """ @@ -458,7 +525,7 @@ class RouterUtil: if menu.parent_id != 0 and cls.is_inner_link(menu): router_path = cls.inner_link_replace_each(router_path) # 非外链并且是一级目录(类型为目录) - if menu.parent_id == 0 and menu.menu_type == 'M' and menu.is_frame == 1: + if menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_DIR and menu.is_frame == MenuConstant.NO_FRAME: router_path = f'/{menu.path}' # 非外链并且是一级目录(类型为菜单) elif cls.is_menu_frame(menu): @@ -469,63 +536,71 @@ class RouterUtil: def get_component(cls, menu: MenuTreeModel): """ 获取组件信息 + :param menu: 菜单数对象 :return: 组件信息 """ - component = 'Layout' + component = MenuConstant.LAYOUT if menu.component and not cls.is_menu_frame(menu): component = menu.component elif (menu.component is None or menu.component == '') and menu.parent_id != 0 and cls.is_inner_link(menu): - component = 'InnerLink' + component = MenuConstant.INNER_LINK elif (menu.component is None or menu.component == '') and cls.is_parent_view(menu): - component = 'ParentView' + component = MenuConstant.PARENT_VIEW return component @classmethod def is_menu_frame(cls, menu: MenuTreeModel): """ 判断是否为菜单内部跳转 + :param menu: 菜单数对象 :return: 是否为菜单内部跳转 """ - return menu.parent_id == 0 and menu.menu_type == 'C' and menu.is_frame == 1 + return ( + menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_MENU and menu.is_frame == MenuConstant.NO_FRAME + ) @classmethod def is_inner_link(cls, menu: MenuTreeModel): """ 判断是否为内链组件 + :param menu: 菜单数对象 :return: 是否为内链组件 """ - return menu.is_frame == 1 and cls.is_http(menu.path) + return menu.is_frame == MenuConstant.NO_FRAME and cls.is_http(menu.path) @classmethod def is_parent_view(cls, menu: MenuTreeModel): """ 判断是否为parent_view组件 + :param menu: 菜单数对象 :return: 是否为parent_view组件 """ - return menu.parent_id != 0 and menu.menu_type == 'M' + return menu.parent_id != 0 and menu.menu_type == MenuConstant.TYPE_DIR @classmethod def is_http(cls, link: str): """ 判断是否为http(s)://开头 + :param link: 链接 :return: 是否为http(s)://开头 """ - return link.startswith('http://') or link.startswith('https://') + return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) @classmethod def inner_link_replace_each(cls, path: str): """ 内链域名特殊字符替换 + :param path: 内链域名 :return: 替换后的内链域名 """ - old_values = ["http://", "https://", "www.", ".", ":"] - new_values = ["", "", "", "/", "/"] + old_values = [CommonConstant.HTTP, CommonConstant.HTTPS, CommonConstant.WWW, '.', ':'] + new_values = ['', '', '', '/', '/'] for old, new in zip(old_values, new_values): path = path.replace(old, new) return path diff --git a/ruoyi-fastapi-backend/module_admin/service/menu_service.py b/ruoyi-fastapi-backend/module_admin/service/menu_service.py index 5f89c90ed4ba4f245b06f57e382a5bee2ab6986c..2852d6cf9894af68d88353d36d71f531d317df24 100644 --- a/ruoyi-fastapi-backend/module_admin/service/menu_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/menu_service.py @@ -1,9 +1,15 @@ -from module_admin.entity.vo.user_vo import CurrentUserModel -from module_admin.entity.vo.role_vo import RoleMenuQueryModel -from module_admin.entity.vo.common_vo import CrudResponseModel +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional +from config.constant import CommonConstant, MenuConstant +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.dao.menu_dao import * +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuQueryModel, MenuModel +from module_admin.entity.vo.role_vo import RoleMenuQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel from utils.common_util import CamelCaseUtil +from utils.string_util import StringUtil class MenuService: @@ -15,130 +21,165 @@ class MenuService: async def get_menu_tree_services(cls, query_db: AsyncSession, current_user: Optional[CurrentUserModel] = None): """ 获取菜单树信息service + :param query_db: orm对象 :param current_user: 当前用户对象 :return: 菜单树信息对象 """ - menu_list_result = await MenuDao.get_menu_list_for_tree(query_db, current_user.user.user_id, current_user.user.role) + 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) return menu_tree_result @classmethod - async def get_role_menu_tree_services(cls, query_db: AsyncSession, role_id: int, current_user: Optional[CurrentUserModel] = None): + async def get_role_menu_tree_services( + cls, query_db: AsyncSession, role_id: int, current_user: Optional[CurrentUserModel] = None + ): """ 根据角色id获取菜单树信息service + :param query_db: orm对象 :param role_id: 角色id :param current_user: 当前用户对象 :return: 当前角色id的菜单树信息对象 """ - menu_list_result = await MenuDao.get_menu_list_for_tree(query_db, current_user.user.user_id, current_user.user.role) + 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) - role_menu_list = await RoleDao.get_role_menu_dao(query_db, role_id) + role = await RoleDao.get_role_detail_by_id(query_db, role_id) + role_menu_list = await RoleDao.get_role_menu_dao(query_db, role) checked_keys = [row.menu_id for row in role_menu_list] - result = RoleMenuQueryModel( - menus=menu_tree_result, - checkedKeys=checked_keys - ) + result = RoleMenuQueryModel(menus=menu_tree_result, checkedKeys=checked_keys) return result @classmethod - async def get_menu_list_services(cls, query_db: AsyncSession, page_object: MenuQueryModel, current_user: Optional[CurrentUserModel] = None): + async def get_menu_list_services( + cls, query_db: AsyncSession, page_object: MenuQueryModel, current_user: Optional[CurrentUserModel] = None + ): """ 获取菜单列表信息service + :param query_db: orm对象 :param page_object: 分页查询参数对象 :param current_user: 当前用户对象 :return: 菜单列表信息对象 """ - menu_list_result = await MenuDao.get_menu_list(query_db, page_object, current_user.user.user_id, current_user.user.role) + menu_list_result = await MenuDao.get_menu_list( + query_db, page_object, current_user.user.user_id, current_user.user.role + ) return CamelCaseUtil.transform_result(menu_list_result) + @classmethod + async def check_menu_name_unique_services(cls, query_db: AsyncSession, page_object: MenuModel): + """ + 校验菜单名称是否唯一service + + :param query_db: orm对象 + :param page_object: 菜单对象 + :return: 校验结果 + """ + menu_id = -1 if page_object.menu_id is None else page_object.menu_id + menu = await MenuDao.get_menu_detail_by_info(query_db, MenuModel(menuName=page_object.menu_name)) + if menu and menu.menu_id != menu_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): """ 新增菜单信息service + :param query_db: orm对象 :param page_object: 新增菜单对象 :return: 新增菜单校验结果 """ - menu = await MenuDao.get_menu_detail_by_info(query_db, MenuModel(parentId=page_object.parent_id, menuName=page_object.menu_name, menuType=page_object.menu_type)) - if menu: - result = dict(is_success=False, message='同一目录下不允许存在同名同类型的菜单') + if not await cls.check_menu_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增菜单{page_object.post_name}失败,菜单名称已存在') + elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + raise ServiceException(message=f'新增菜单{page_object.post_name}失败,地址必须以http(s)://开头') else: try: await MenuDao.add_menu_dao(query_db, page_object) await query_db.commit() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod async def edit_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): """ 编辑菜单信息service + :param query_db: orm对象 :param page_object: 编辑部门对象 :return: 编辑菜单校验结果 """ edit_menu = page_object.model_dump(exclude_unset=True) - menu_info = await cls.menu_detail_services(query_db, edit_menu.get('menu_id')) - if menu_info: - if menu_info.parent_id != page_object.parent_id or menu_info.menu_name != page_object.menu_name or menu_info.menu_type != page_object.menu_type: - menu = await MenuDao.get_menu_detail_by_info(query_db, MenuModel(parentId=page_object.parent_id, menuName=page_object.menu_name, menuType=page_object.menu_type)) - if menu: - result = dict(is_success=False, message='同一目录下不允许存在同名同类型的菜单') - return CrudResponseModel(**result) - try: - await MenuDao.edit_menu_dao(query_db, edit_menu) - await query_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + menu_info = await cls.menu_detail_services(query_db, page_object.menu_id) + if menu_info.menu_id: + if not await cls.check_menu_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改菜单{page_object.post_name}失败,菜单名称已存在') + elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + raise ServiceException(message=f'修改菜单{page_object.post_name}失败,地址必须以http(s)://开头') + elif page_object.menu_id == page_object.parent_id: + raise ServiceException(message=f'修改菜单{page_object.post_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 else: - result = dict(is_success=False, message='菜单不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='菜单不存在') @classmethod async def delete_menu_services(cls, query_db: AsyncSession, page_object: DeleteMenuModel): """ 删除菜单信息service + :param query_db: orm对象 :param page_object: 删除菜单对象 :return: 删除菜单校验结果 """ - if page_object.menu_ids.split(','): + if page_object.menu_ids: menu_id_list = page_object.menu_ids.split(',') try: 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: + raise ServiceWarning(message='菜单已分配,不允许删除') await MenuDao.delete_menu_dao(query_db, MenuModel(menuId=menu_id)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入菜单id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入菜单id为空') @classmethod async def menu_detail_services(cls, query_db: AsyncSession, menu_id: int): """ 获取菜单详细信息service + :param query_db: orm对象 :param menu_id: 菜单id :return: 菜单id对应的信息 """ menu = await MenuDao.get_menu_detail_by_id(query_db, menu_id=menu_id) - result = MenuModel(**CamelCaseUtil.transform_result(menu)) + if menu: + result = MenuModel(**CamelCaseUtil.transform_result(menu)) + else: + result = MenuModel(**dict()) return result @@ -146,10 +187,13 @@ class MenuService: def list_to_tree(cls, permission_list: list) -> list: """ 工具方法:根据菜单列表信息生成树形嵌套数据 + :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 = [ + dict(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)) diff --git a/ruoyi-fastapi-backend/module_admin/service/notice_service.py b/ruoyi-fastapi-backend/module_admin/service/notice_service.py index 573fc39fd7ba97944f2f35d18710b1ffba8b45c9..4671703eb5a0710d6e4b1d3f84e86f12d9bb88af 100644 --- a/ruoyi-fastapi-backend/module_admin/service/notice_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/notice_service.py @@ -1,6 +1,10 @@ -from module_admin.dao.notice_dao import * +from sqlalchemy.ext.asyncio import AsyncSession +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.notice_dao import NoticeDao from module_admin.entity.vo.common_vo import CrudResponseModel -from utils.common_util import export_list2excel, CamelCaseUtil +from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel +from utils.common_util import CamelCaseUtil class NoticeService: @@ -9,9 +13,12 @@ class NoticeService: """ @classmethod - async def get_notice_list_services(cls, query_db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = True): + async def get_notice_list_services( + cls, query_db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = True + ): """ 获取通知公告列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -21,87 +28,101 @@ class NoticeService: return notice_list_result + @classmethod + async def check_notice_unique_services(cls, query_db: AsyncSession, page_object: NoticeModel): + """ + 校验通知公告是否存在service + + :param query_db: orm对象 + :param page_object: 通知公告对象 + :return: 校验结果 + """ + notice_id = -1 if page_object.notice_id is None else page_object.notice_id + notice = await NoticeDao.get_notice_detail_by_info(query_db, page_object) + if notice and notice.notice_id != notice_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): """ 新增通知公告信息service + :param query_db: orm对象 :param page_object: 新增通知公告对象 :return: 新增通知公告校验结果 """ - notice = await NoticeDao.get_notice_detail_by_info(query_db, page_object) - if notice: - result = dict(is_success=False, message='通知公告已存在') + 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() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod async def edit_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): """ 编辑通知公告信息service + :param query_db: orm对象 :param page_object: 编辑通知公告对象 :return: 编辑通知公告校验结果 """ edit_notice = page_object.model_dump(exclude_unset=True) - notice_info = await cls.notice_detail_services(query_db, edit_notice.get('notice_id')) - if notice_info: - if notice_info.notice_title != page_object.notice_title or notice_info.notice_type != page_object.notice_type or notice_info.notice_content != page_object.notice_content: - notice = await NoticeDao.get_notice_detail_by_info(query_db, page_object) - if notice: - result = dict(is_success=False, message='通知公告已存在') - return CrudResponseModel(**result) - try: - await NoticeDao.edit_notice_dao(query_db, edit_notice) - await query_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + notice_info = await cls.notice_detail_services(query_db, page_object.notice_id) + 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 else: - result = dict(is_success=False, message='通知公告不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='通知公告不存在') @classmethod async def delete_notice_services(cls, query_db: AsyncSession, page_object: DeleteNoticeModel): """ 删除通知公告信息service + :param query_db: orm对象 :param page_object: 删除通知公告对象 :return: 删除通知公告校验结果 """ - if page_object.notice_ids.split(','): + if page_object.notice_ids: notice_id_list = page_object.notice_ids.split(',') try: for notice_id in notice_id_list: await NoticeDao.delete_notice_dao(query_db, NoticeModel(noticeId=notice_id)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入通知公告id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入通知公告id为空') @classmethod async def notice_detail_services(cls, query_db: AsyncSession, notice_id: int): """ 获取通知公告详细信息service + :param query_db: orm对象 :param notice_id: 通知公告id :return: 通知公告id对应的信息 """ notice = await NoticeDao.get_notice_detail_by_id(query_db, notice_id=notice_id) - result = NoticeModel(**CamelCaseUtil.transform_result(notice)) + if notice: + result = NoticeModel(**CamelCaseUtil.transform_result(notice)) + else: + result = NoticeModel(**dict()) 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 968aacb14dfa01a92d069ac3dc3a86513931e046..0d1c913ecfc9de9b3c272cd0c92c80e2e4bb54b8 100644 --- a/ruoyi-fastapi-backend/module_admin/service/online_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/online_service.py @@ -1,8 +1,10 @@ +import jwt from fastapi import Request -from jose import jwt -from config.env import JwtConfig, RedisInitKeyConfig -from module_admin.entity.vo.online_vo import * +from config.enums import RedisInitKeyConfig +from config.env import 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 @@ -15,11 +17,12 @@ class OnlineService: async def get_online_list_services(cls, request: Request, query_object: OnlineQueryModel): """ 获取在线用户表信息service + :param request: Request对象 :param query_object: 查询参数对象 :return: 在线用户列表信息 """ - access_token_keys = await request.app.state.redis.keys(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}*") + access_token_keys = await request.app.state.redis.keys(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}*") if not access_token_keys: access_token_keys = [] access_token_values_list = [await request.app.state.redis.get(key) for key in access_token_keys] @@ -34,7 +37,7 @@ class OnlineService: 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') + 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('login_info').get('ipaddr'): @@ -45,7 +48,9 @@ class OnlineService: online_info_list = [online_dict] break elif query_object.user_name and query_object.ipaddr: - if query_object.user_name == payload.get('user_name') and query_object.ipaddr == payload.get('login_info').get('ipaddr'): + if query_object.user_name == payload.get('user_name') and query_object.ipaddr == payload.get( + 'login_info' + ).get('ipaddr'): online_info_list = [online_dict] break else: @@ -57,15 +62,15 @@ class OnlineService: async def delete_online_services(cls, request: Request, page_object: DeleteOnlineModel): """ 强退在线用户信息service + :param request: Request对象 :param page_object: 强退在线用户对象 :return: 强退在线用户校验结果 """ - if page_object.token_ids.split(','): + if page_object.token_ids: token_id_list = page_object.token_ids.split(',') for token_id in token_id_list: - await request.app.state.redis.delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{token_id}") - result = dict(is_success=True, message='强退成功') + await request.app.state.redis.delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}") + return CrudResponseModel(is_success=True, message='强退成功') else: - result = dict(is_success=False, message='传入session_id为空') - return CrudResponseModel(**result) + 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 13882abb906a6a34f82f32ede089af762118e85e..52155395cf962e35dc82fdce1dc597207eab9373 100644 --- a/ruoyi-fastapi-backend/module_admin/service/post_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/post_service.py @@ -1,16 +1,25 @@ -from module_admin.dao.post_dao import * +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.post_dao import PostDao from module_admin.entity.vo.common_vo import CrudResponseModel -from utils.common_util import export_list2excel, CamelCaseUtil +from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel +from utils.common_util import CamelCaseUtil, export_list2excel class PostService: """ 岗位管理模块服务层 """ + @classmethod - async def get_post_list_services(cls, query_db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False): + async def get_post_list_services( + cls, query_db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False + ): """ 获取岗位列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param is_page: 是否开启分页 @@ -20,88 +29,124 @@ class PostService: return post_list_result + @classmethod + async def check_post_name_unique_services(cls, query_db: AsyncSession, page_object: PostModel): + """ + 检查岗位名称是否唯一service + + :param query_db: orm对象 + :param page_object: 岗位对象 + :return: 校验结果 + """ + post_id = -1 if page_object.post_id is None else page_object.post_id + post = await PostDao.get_post_detail_by_info(query_db, PostModel(postName=page_object.post_name)) + if post and post.post_id != post_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_post_code_unique_services(cls, query_db: AsyncSession, page_object: PostModel): + """ + 检查岗位编码是否唯一service + + :param query_db: orm对象 + :param page_object: 岗位对象 + :return: 校验结果 + """ + post_id = -1 if page_object.post_id is None else page_object.post_id + post = await PostDao.get_post_detail_by_info(query_db, PostModel(postCode=page_object.post_code)) + if post and post.post_id != post_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_post_services(cls, query_db: AsyncSession, page_object: PostModel): """ 新增岗位信息service + :param query_db: orm对象 :param page_object: 新增岗位对象 :return: 新增岗位校验结果 """ - post = await PostDao.get_post_detail_by_info(query_db, PostModel(postName=page_object.post_name)) - if post: - result = dict(is_success=False, message='岗位名称已存在') + 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): + raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位编码已存在') else: try: await PostDao.add_post_dao(query_db, page_object) await query_db.commit() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod async def edit_post_services(cls, query_db: AsyncSession, page_object: PostModel): """ 编辑岗位信息service + :param query_db: orm对象 :param page_object: 编辑岗位对象 :return: 编辑岗位校验结果 """ edit_post = page_object.model_dump(exclude_unset=True) - post_info = await cls.post_detail_services(query_db, edit_post.get('post_id')) - if post_info: - if post_info.post_name != page_object.post_name: - post = await PostDao.get_post_detail_by_info(query_db, PostModel(postName=page_object.post_name)) - if post: - result = dict(is_success=False, message='岗位名称已存在') - return CrudResponseModel(**result) - try: - await PostDao.edit_post_dao(query_db, edit_post) - await query_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - await query_db.rollback() - raise e + post_info = await cls.post_detail_services(query_db, page_object.post_id) + 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): + 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 else: - result = dict(is_success=False, message='岗位不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='岗位不存在') @classmethod async def delete_post_services(cls, query_db: AsyncSession, page_object: DeletePostModel): """ 删除岗位信息service + :param query_db: orm对象 :param page_object: 删除岗位对象 :return: 删除岗位校验结果 """ - if page_object.post_ids.split(','): + if page_object.post_ids: post_id_list = page_object.post_ids.split(',') try: for post_id in post_id_list: + post = await cls.post_detail_services(query_db, int(post_id)) + if (await PostDao.count_user_post_dao(query_db, int(post_id))) > 0: + raise ServiceException(message=f'{post.post_name}已分配,不能删除') await PostDao.delete_post_dao(query_db, PostModel(postId=post_id)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入岗位id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入岗位id为空') @classmethod async def post_detail_services(cls, query_db: AsyncSession, post_id: int): """ 获取岗位详细信息service + :param query_db: orm对象 :param post_id: 岗位id :return: 岗位id对应的信息 """ post = await PostDao.get_post_detail_by_id(query_db, post_id=post_id) - result = PostModel(**CamelCaseUtil.transform_result(post)) + if post: + result = PostModel(**CamelCaseUtil.transform_result(post)) + else: + result = PostModel(**dict()) return result @@ -109,21 +154,22 @@ class PostService: async def export_post_list_services(post_list: List): """ 导出岗位信息service + :param post_list: 岗位信息列表 :return: 岗位信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "postId": "岗位编号", - "postCode": "岗位编码", - "postName": "岗位名称", - "postSort": "显示顺序", - "status": "状态", - "createBy": "创建者", - "createTime": "创建时间", - "updateBy": "更新者", - "updateTime": "更新时间", - "remark": "备注", + 'postId': '岗位编号', + 'postCode': '岗位编码', + 'postName': '岗位名称', + 'postSort': '显示顺序', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', } data = post_list @@ -133,7 +179,9 @@ class PostService: item['status'] = '正常' else: item['status'] = '停用' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/role_service.py b/ruoyi-fastapi-backend/module_admin/service/role_service.py index 745d3dbd3248add51b85b2194c3c0d979702dffe..7939781bfa479b07cfce923b1e8a225cd00710ad 100644 --- a/ruoyi-fastapi-backend/module_admin/service/role_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/role_service.py @@ -1,9 +1,22 @@ -from module_admin.entity.vo.user_vo import UserInfoModel, UserRolePageQueryModel +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.role_vo import ( + AddRoleModel, + DeleteRoleModel, + RoleDeptModel, + RoleDeptQueryModel, + RoleMenuModel, + RoleModel, + 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 module_admin.dao.role_dao import * +from utils.common_util import CamelCaseUtil, export_list2excel from utils.page_util import PageResponseModel -from utils.common_util import export_list2excel, CamelCaseUtil class RoleService: @@ -15,6 +28,7 @@ class RoleService: async def get_role_select_option_services(cls, query_db: AsyncSession): """ 获取角色列表不分页信息service + :param query_db: orm对象 :return: 角色列表不分页信息对象 """ @@ -26,46 +40,113 @@ class RoleService: async def get_role_dept_tree_services(cls, query_db: AsyncSession, role_id: int): """ 根据角色id获取部门树信息service + :param query_db: orm对象 :param role_id: 角色id :return: 当前角色id的部门树信息对象 """ - role_dept_list = await RoleDao.get_role_dept_dao(query_db, role_id) + role = await cls.role_detail_services(query_db, role_id) + role_dept_list = await RoleDao.get_role_dept_dao(query_db, role) checked_keys = [row.dept_id for row in role_dept_list] - result = RoleDeptQueryModel( - checkedKeys=checked_keys - ) + result = RoleDeptQueryModel(checkedKeys=checked_keys) return result @classmethod - async def get_role_list_services(cls, query_db: AsyncSession, query_object: RolePageQueryModel, is_page: bool = False): + async def get_role_list_services( + cls, query_db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 获取角色列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 :param is_page: 是否开启分页 :return: 角色列表信息对象 """ - role_list_result = await RoleDao.get_role_list(query_db, query_object, is_page) + role_list_result = await RoleDao.get_role_list(query_db, query_object, data_scope_sql, is_page) return role_list_result + @classmethod + async def check_role_allowed_services(cls, check_role: RoleModel): + """ + 校验角色是否允许操作service + + :param check_role: 角色信息 + :return: 校验结果 + """ + if check_role.admin: + raise ServiceException(message='不允许操作超级管理员角色') + else: + 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): + """ + 校验角色是否有数据权限service + + :param query_db: orm对象 + :param role_ids: 角色id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + role_id_list = role_ids.split(',') if role_ids else [] + if role_id_list: + for role_id in role_id_list: + roles = await RoleDao.get_role_list( + query_db, RolePageQueryModel(roleId=int(role_id)), data_scope_sql, is_page=False + ) + if roles: + continue + else: + raise ServiceException(message='没有权限访问角色数据') + + @classmethod + async def check_role_name_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 校验角色名称是否唯一service + + :param query_db: orm对象 + :param page_object: 角色对象 + :return: 校验结果 + """ + role_id = -1 if page_object.role_id is None else page_object.role_id + role = await RoleDao.get_role_by_info(query_db, RoleModel(roleName=page_object.role_name)) + if role and role.role_id != role_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_role_key_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 校验角色权限字符是否唯一service + + :param query_db: orm对象 + :param page_object: 角色对象 + :return: 校验结果 + """ + role_id = -1 if page_object.role_id is None else page_object.role_id + role = await RoleDao.get_role_by_info(query_db, RoleModel(roleKey=page_object.role_key)) + if role and role.role_id != role_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): """ 新增角色信息service + :param query_db: orm对象 :param page_object: 新增角色对象 :return: 新增角色校验结果 """ add_role = RoleModel(**page_object.model_dump(by_alias=True)) - role_name = await RoleDao.get_role_by_info(query_db, RoleModel(roleName=page_object.role_name)) - role_key = await RoleDao.get_role_by_info(query_db, RoleModel(roleKey=page_object.role_key)) - if role_name: - result = dict(is_success=False, message='角色名称已存在') - elif role_key: - result = dict(is_success=False, message='权限字符已存在') + if not await cls.check_role_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增角色{page_object.post_name}失败,角色名称已存在') + elif not await cls.check_role_key_unique_services(query_db, page_object): + raise ServiceException(message=f'新增角色{page_object.post_name}失败,角色权限已存在') else: try: add_result = await RoleDao.add_role_dao(query_db, add_role) @@ -74,17 +155,16 @@ class RoleService: 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() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod async def edit_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): """ 编辑角色信息service + :param query_db: orm对象 :param page_object: 编辑角色对象 :return: 编辑角色校验结果 @@ -96,105 +176,100 @@ class RoleService: del edit_role['type'] role_info = await cls.role_detail_services(query_db, edit_role.get('role_id')) if role_info: - if page_object.type != 'status' and role_info.role_name != page_object.role_name: - role_name = await RoleDao.get_role_by_info(query_db, RoleModel(roleName=page_object.role_name)) - if role_name: - result = dict(is_success=False, message='角色名称已存在') - return CrudResponseModel(**result) - elif page_object.type != 'status' and role_info.role_key != page_object.role_key: - role_key = await RoleDao.get_role_by_info(query_db, RoleModel(roleKey=page_object.role_key)) - if role_key: - result = dict(is_success=False, message='权限字符已存在') - return CrudResponseModel(**result) + if page_object.type != 'status': + if not await cls.check_role_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改角色{page_object.post_name}失败,角色名称已存在') + elif not await cls.check_role_key_unique_services(query_db, page_object): + raise ServiceException(message=f'修改角色{page_object.post_name}失败,角色权限已存在') try: await RoleDao.edit_role_dao(query_db, edit_role) if page_object.type != 'status': await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(roleId=page_object.role_id)) if page_object.menu_ids: for menu in page_object.menu_ids: - await RoleDao.add_role_menu_dao(query_db, RoleMenuModel(roleId=page_object.role_id, menuId=menu)) + await RoleDao.add_role_menu_dao( + query_db, RoleMenuModel(roleId=page_object.role_id, menuId=menu) + ) await query_db.commit() - result = dict(is_success=True, message='更新成功') + return CrudResponseModel(is_success=True, message='更新成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='角色不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='角色不存在') @classmethod async def role_datascope_services(cls, query_db: AsyncSession, page_object: AddRoleModel): """ 分配角色数据权限service + :param query_db: orm对象 :param page_object: 角色数据权限对象 :return: 分配角色数据权限结果 """ - edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin'}) - del edit_role['dept_ids'] - role_info = await cls.role_detail_services(query_db, edit_role.get('role_id')) - if role_info: - if role_info.role_name != page_object.role_name: - role_name = await RoleDao.get_role_by_info(query_db, RoleModel(roleName=page_object.role_name)) - if role_name: - result = dict(is_success=False, message='角色名称已存在') - return CrudResponseModel(**result) - elif role_info.role_key != page_object.role_key: - role_key = await RoleDao.get_role_by_info(query_db, RoleModel(roleKey=page_object.role_key)) - if role_key: - result = dict(is_success=False, message='权限字符已存在') - return CrudResponseModel(**result) + edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin', 'dept_ids'}) + role_info = await cls.role_detail_services(query_db, page_object.role_id) + if role_info.role_id: try: await RoleDao.edit_role_dao(query_db, edit_role) await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(roleId=page_object.role_id)) if page_object.dept_ids and page_object.data_scope == '2': for dept in page_object.dept_ids: - await RoleDao.add_role_dept_dao(query_db, RoleDeptModel(roleId=page_object.role_id, deptId=dept)) + await RoleDao.add_role_dept_dao( + query_db, RoleDeptModel(roleId=page_object.role_id, deptId=dept) + ) await query_db.commit() - result = dict(is_success=True, message='分配成功') + return CrudResponseModel(is_success=True, message='分配成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='角色不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='角色不存在') @classmethod async def delete_role_services(cls, query_db: AsyncSession, page_object: DeleteRoleModel): """ 删除角色信息service + :param query_db: orm对象 :param page_object: 删除角色对象 :return: 删除角色校验结果 """ - if page_object.role_ids.split(','): + if page_object.role_ids: role_id_list = page_object.role_ids.split(',') try: for role_id in role_id_list: - role_id_dict = dict(roleId=role_id, updateBy=page_object.update_by, updateTime=page_object.update_time) + 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 + ) 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)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入角色id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入角色id为空') @classmethod async def role_detail_services(cls, query_db: AsyncSession, role_id: int): """ 获取角色详细信息service + :param query_db: orm对象 :param role_id: 角色id :return: 角色id对应的信息 """ role = await RoleDao.get_role_detail_by_id(query_db, role_id=role_id) - result = RoleModel(**CamelCaseUtil.transform_result(role)) + if role: + result = RoleModel(**CamelCaseUtil.transform_result(role)) + else: + result = RoleModel(**dict()) return result @@ -202,21 +277,22 @@ class RoleService: async def export_role_list_services(role_list: List): """ 导出角色列表信息service + :param role_list: 角色信息列表 :return: 角色列表信息对象 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "roleId": "角色编号", - "roleName": "角色名称", - "roleKey": "权限字符", - "roleSort": "显示顺序", - "status": "状态", - "createBy": "创建者", - "createTime": "创建时间", - "updateBy": "更新者", - "updateTime": "更新时间", - "remark": "备注", + 'roleId': '角色编号', + 'roleName': '角色名称', + 'roleKey': '权限字符', + 'roleSort': '显示顺序', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', } data = role_list @@ -226,44 +302,58 @@ class RoleService: item['status'] = '正常' else: item['status'] = '停用' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @classmethod - async def get_role_user_allocated_list_services(cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, is_page: bool = False): + async def get_role_user_allocated_list_services( + cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据角色id获取已分配用户列表 + :param query_db: orm对象 :param page_object: 用户关联角色对象 + :param data_scope_sql: 数据权限对应的查询sql语句 :param is_page: 是否开启分页 :return: 已分配用户列表 """ - query_user_list = await UserDao.get_user_role_allocated_list_by_role_id(query_db, page_object, is_page) + 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( **{ **query_user_list.model_dump(by_alias=True), - 'rows': [UserInfoModel(**row) for row in query_user_list.rows] + 'rows': [UserInfoModel(**row) for row in query_user_list.rows], } ) return allocated_list @classmethod - async def get_role_user_unallocated_list_services(cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, is_page: bool = False): + async def get_role_user_unallocated_list_services( + cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据角色id获取未分配用户列表 + :param query_db: orm对象 :param page_object: 用户关联角色对象 + :param data_scope_sql: 数据权限对应的查询sql语句 :param is_page: 是否开启分页 :return: 未分配用户列表 """ - query_user_list = await UserDao.get_user_role_unallocated_list_by_role_id(query_db, page_object, is_page) + 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( **{ **query_user_list.model_dump(by_alias=True), - 'rows': [UserInfoModel(**row) for row in query_user_list.rows] + '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 da6e2a2ff70e00ebcd53661f8fa010b7e72f4a93..2f9a53fecf012c1d80f4d246119ed562b33cc568 100644 --- a/ruoyi-fastapi-backend/module_admin/service/server_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/server_service.py @@ -1,10 +1,10 @@ -import psutil -from utils.common_util import bytes2human +import os import platform +import psutil import socket -import os import time -from module_admin.entity.vo.server_vo import * +from module_admin.entity.vo.server_vo import CpuInfo, MemoryInfo, PyInfo, ServerMonitorModel, SysFiles, SysInfo +from utils.common_util import bytes2human class ServerService: @@ -40,7 +40,9 @@ class ServerService: computer_name = platform.node() os_arch = platform.machine() user_dir = os.path.abspath(os.getcwd()) - sys = SysInfo(computerIp=computer_ip, computerName=computer_name, osArch=os_arch, osName=os_name, userDir=user_dir) + sys = SysInfo( + computerIp=computer_ip, computerName=computer_name, osArch=os_arch, osName=os_name, userDir=user_dir + ) # python解释器信息 current_pid = os.getpid() @@ -49,14 +51,14 @@ class ServerService: python_version = platform.python_version() python_home = current_process.exe() start_time_stamp = current_process.create_time() - start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time_stamp)) + start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time_stamp)) current_time_stamp = time.time() difference = current_time_stamp - start_time_stamp # 将时间差转换为天、小时和分钟数 days = int(difference // (24 * 60 * 60)) # 每天的秒数 hours = int((difference % (24 * 60 * 60)) // (60 * 60)) # 每小时的秒数 minutes = int((difference % (60 * 60)) // 60) # 每分钟的秒数 - run_time = f"{days}天{hours}小时{minutes}分钟" + run_time = f'{days}天{hours}小时{minutes}分钟' # 获取当前Python程序的pid pid = os.getpid() # 获取该进程的内存信息 @@ -70,7 +72,7 @@ class ServerService: total=bytes2human(memory_info.available), used=bytes2human(current_process_memory_info.rss), free=bytes2human(memory_info.available - current_process_memory_info.rss), - usage=round((current_process_memory_info.rss / memory_info.available) * 100, 2) + usage=round((current_process_memory_info.rss / memory_info.available) * 100, 2), ) # 磁盘信息 @@ -81,11 +83,11 @@ class ServerService: disk_data = SysFiles( dirName=i.device, sysTypeName=i.fstype, - typeName="本地固定磁盘(" + i.mountpoint.replace('\\', '') + ")", + typeName='本地固定磁盘(' + i.mountpoint.replace('\\', '') + ')', total=bytes2human(o.total), used=bytes2human(o.used), free=bytes2human(o.free), - usage=f'{psutil.disk_usage(i.device).percent}%' + usage=f'{psutil.disk_usage(i.device).percent}%', ) sys_files.append(disk_data) diff --git a/ruoyi-fastapi-backend/module_admin/service/user_service.py b/ruoyi-fastapi-backend/module_admin/service/user_service.py index d1132de209d76084b38725ba142fa4a472a7997e..dc7943027e17c4052f642debae9af0020ec3fc70 100644 --- a/ruoyi-fastapi-backend/module_admin/service/user_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/user_service.py @@ -1,11 +1,39 @@ -from fastapi import UploadFile -from module_admin.service.role_service import RoleService -from module_admin.service.post_service import PostService, PostPageQueryModel +import io +import pandas as pd +from datetime import datetime +from fastapi import Request, UploadFile +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List, Union +from config.constant import CommonConstant +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.dao.user_dao import * +from module_admin.entity.vo.post_vo import PostPageQueryModel +from module_admin.entity.vo.user_vo import ( + AddUserModel, + CrudUserRoleModel, + CurrentUserModel, + DeleteUserModel, + EditUserModel, + ResetUserModel, + SelectedRoleModel, + UserDetailModel, + UserInfoModel, + UserModel, + UserPageQueryModel, + UserPostModel, + UserProfileModel, + UserRoleModel, + UserRoleQueryModel, + UserRoleResponseModel, +) +from module_admin.service.config_service import ConfigService +from module_admin.service.dept_service import DeptService +from module_admin.service.post_service import PostService +from module_admin.service.role_service import RoleService +from utils.common_util import CamelCaseUtil, export_list2excel, get_excel_template from utils.page_util import PageResponseModel -from utils.pwd_util import * -from utils.common_util import * +from utils.pwd_util import PwdUtil class UserService: @@ -14,9 +42,12 @@ class UserService: """ @classmethod - async def get_user_list_services(cls, query_db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False): + async def get_user_list_services( + cls, query_db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 获取用户列表信息service + :param query_db: orm对象 :param query_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 @@ -28,7 +59,7 @@ class UserService: user_list_result = PageResponseModel( **{ **query_result.model_dump(by_alias=True), - 'rows': [{**row[0], 'dept': row[1]} for row in query_result.rows] + 'rows': [{**row[0], 'dept': row[1]} for row in query_result.rows], } ) else: @@ -38,18 +69,96 @@ class UserService: return user_list_result + @classmethod + async def check_user_allowed_services(cls, check_user: UserModel): + """ + 校验用户是否允许操作service + + :param check_user: 用户信息 + :return: 校验结果 + """ + if check_user.admin: + raise ServiceException(message='不允许操作超级管理员用户') + else: + 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): + """ + 校验用户数据权限service + + :param query_db: orm对象 + :param user_id: 用户id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + 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='没有权限访问用户数据') + + @classmethod + async def check_user_name_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户名是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(userName=page_object.user_name)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_phonenumber_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户手机号是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(phonenumber=page_object.phonenumber)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_email_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户邮箱是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(email=page_object.email)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + @classmethod async def add_user_services(cls, query_db: AsyncSession, page_object: AddUserModel): """ 新增用户信息service + :param query_db: orm对象 :param page_object: 新增用户对象 :return: 新增用户校验结果 """ add_user = UserModel(**page_object.model_dump(by_alias=True)) - user = await UserDao.get_user_by_info(query_db, UserModel(userName=page_object.user_name)) - if user: - result = dict(is_success=False, message='用户名已存在') + 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): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,手机号码已存在') + elif page_object.email and not await cls.check_email_unique_services(query_db, page_object): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,邮箱账号已存在') else: try: add_result = await UserDao.add_user_dao(query_db, add_user) @@ -61,17 +170,16 @@ class UserService: 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() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod async def edit_user_services(cls, query_db: AsyncSession, page_object: EditUserModel): """ 编辑用户信息service + :param query_db: orm对象 :param page_object: 编辑用户对象 :return: 编辑用户校验结果 @@ -84,12 +192,14 @@ class UserService: if page_object.type == 'status' or page_object.type == 'avatar' or page_object.type == 'pwd': del edit_user['type'] user_info = await cls.user_detail_services(query_db, edit_user.get('user_id')) - if user_info: - if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type == 'pwd' and user_info.data.user_name != page_object.user_name: - user = await UserDao.get_user_by_info(query_db, UserModel(userName=page_object.user_name)) - if user: - result = dict(is_success=False, message='用户名已存在') - return CrudResponseModel(**result) + 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 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): + raise ServiceException(message=f'修改用户{page_object.user_name}失败,手机号码已存在') + elif 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': @@ -97,49 +207,54 @@ class UserService: await UserDao.delete_user_post_dao(query_db, UserPostModel(userId=page_object.user_id)) if page_object.role_ids: for role in page_object.role_ids: - await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=role)) + await UserDao.add_user_role_dao( + query_db, UserRoleModel(userId=page_object.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=page_object.user_id, postId=post)) + await UserDao.add_user_post_dao( + query_db, UserPostModel(userId=page_object.user_id, postId=post) + ) await query_db.commit() - result = dict(is_success=True, message='更新成功') + return CrudResponseModel(is_success=True, message='更新成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='用户不存在') - - return CrudResponseModel(**result) + raise ServiceException(message='用户不存在') @classmethod async def delete_user_services(cls, query_db: AsyncSession, page_object: DeleteUserModel): """ 删除用户信息service + :param query_db: orm对象 :param page_object: 删除用户对象 :return: 删除用户校验结果 """ - if page_object.user_ids.split(','): + if page_object.user_ids: 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 = 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)) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='传入用户id为空') - return CrudResponseModel(**result) + raise ServiceException(message='传入用户id为空') @classmethod async def user_detail_services(cls, query_db: AsyncSession, user_id: Union[int, str]): """ 获取用户详细信息service + :param query_db: orm对象 :param user_id: 用户id :return: 用户id对应的信息 @@ -159,23 +274,21 @@ class UserService: postIds=post_ids, roleIds=role_ids, dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), - role=CamelCaseUtil.transform_result(query_user.get('user_role_info')) + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), ), postIds=post_ids_list, posts=posts, roleIds=role_ids_list, - roles=roles + roles=roles, ) - return UserDetailModel( - posts=posts, - roles=roles - ) + return UserDetailModel(posts=posts, roles=roles) @classmethod async def user_profile_services(cls, query_db: AsyncSession, user_id: int): """ - 获取用户详细信息service + 获取用户个人详细信息service + :param query_db: orm对象 :param user_id: 用户id :return: 用户id对应的信息 @@ -192,16 +305,17 @@ class UserService: postIds=post_ids, roleIds=role_ids, dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), - role=CamelCaseUtil.transform_result(query_user.get('user_role_info')) + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), ), postGroup=post_group, - roleGroup=role_group + roleGroup=role_group, ) @classmethod async def reset_user_services(cls, query_db: AsyncSession, page_object: ResetUserModel): """ 重置用户密码service + :param query_db: orm对象 :param page_object: 重置用户对象 :return: 重置用户校验结果 @@ -210,41 +324,54 @@ class UserService: if page_object.old_password: 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): - result = dict(is_success=False, message='旧密码不正确') - return CrudResponseModel(**result) + raise ServiceException(message='修改密码失败,旧密码错误') + elif PwdUtil.verify_password(page_object.password, user.password): + raise ServiceException(message='新密码不能与旧密码相同') else: 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'] try: + reset_user['password'] = PwdUtil.get_password_hash(page_object.password) await UserDao.edit_user_dao(query_db, reset_user) await query_db.commit() - result = dict(is_success=True, message='重置成功') + return CrudResponseModel(is_success=True, message='重置成功') except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @classmethod - async def batch_import_user_services(cls, query_db: AsyncSession, file: UploadFile, update_support: bool, current_user: CurrentUserModel): + async def batch_import_user_services( + cls, + request: Request, + query_db: AsyncSession, + file: UploadFile, + update_support: bool, + current_user: CurrentUserModel, + user_data_scope_sql: str, + dept_data_scope_sql: str, + ): """ 批量导入用户service + + :param request: Request对象 :param query_db: orm对象 :param file: 用户导入文件对象 :param update_support: 用户存在时是否更新 :param current_user: 当前用户对象 + :param user_data_scope_sql: 用户数据权限sql + :param dept_data_scope_sql: 部门数据权限sql :return: 批量导入用户结果 """ header_dict = { - "部门编号": "dept_id", - "登录名称": "user_name", - "用户名称": "nick_name", - "用户邮箱": "email", - "手机号码": "phonenumber", - "用户性别": "sex", - "帐号状态": "status" + '部门编号': 'dept_id', + '登录名称': 'user_name', + '用户名称': 'nick_name', + '用户邮箱': 'email', + '手机号码': 'phonenumber', + '用户性别': 'sex', + '帐号状态': 'status', } contents = await file.read() df = pd.read_excel(io.BytesIO(contents)) @@ -268,7 +395,11 @@ class UserService: add_user = UserModel( deptId=row['dept_id'], userName=row['user_name'], - password=PwdUtil.get_password_hash('123456'), + password=PwdUtil.get_password_hash( + await ConfigService.query_config_list_from_cache_services( + request.app.state.redis, 'sys.user.initPassword' + ) + ), nickName=row['nick_name'], email=row['email'], phonenumber=str(row['phonenumber']), @@ -277,12 +408,12 @@ class UserService: createBy=current_user.user.user_name, createTime=datetime.now(), updateBy=current_user.user.user_name, - updateTime=datetime.now() + updateTime=datetime.now(), ) user_info = await UserDao.get_user_by_info(query_db, UserModel(userName=row['user_name'])) if user_info: if update_support: - edit_user = UserModel( + edit_user_model = UserModel( userId=user_info.user_id, deptId=row['dept_id'], userName=row['user_name'], @@ -292,31 +423,47 @@ class UserService: sex=row['sex'], status=row['status'], updateBy=current_user.user.user_name, - updateTime=datetime.now() - ).model_dump(exclude_unset=True) + updateTime=datetime.now(), + ) + edit_user_model.validate_fields() + await cls.check_user_allowed_services(edit_user_model) + if not current_user.user.admin: + await cls.check_user_data_scope_services( + query_db, edit_user_model.user_id, user_data_scope_sql + ) + await DeptService.check_dept_data_scope_services( + query_db, edit_user_model.dept_id, dept_data_scope_sql + ) + 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']}已存在") else: + add_user.validate_fields() + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services( + query_db, add_user.dept_id, dept_data_scope_sql + ) await UserDao.add_user_dao(query_db, add_user) await query_db.commit() - result = dict(is_success=True, message='\n'.join(add_error_result)) + return CrudResponseModel(is_success=True, message='\n'.join(add_error_result)) except Exception as e: await query_db.rollback() raise e - return CrudResponseModel(**result) - @staticmethod async def get_user_import_template_services(): """ 获取用户导入模板service + :return: 用户导入模板excel的二进制数据 """ - header_list = ["部门编号", "登录名称", "用户名称", "用户邮箱", "手机号码", "用户性别", "帐号状态"] - selector_header_list = ["用户性别", "帐号状态"] - option_list = [{"用户性别": ["男", "女", "未知"]}, {"帐号状态": ["正常", "停用"]}] - binary_data = get_excel_template(header_list=header_list, selector_header_list=selector_header_list, option_list=option_list) + header_list = ['部门编号', '登录名称', '用户名称', '用户邮箱', '手机号码', '用户性别', '帐号状态'] + selector_header_list = ['用户性别', '帐号状态'] + option_list = [{'用户性别': ['男', '女', '未知']}, {'帐号状态': ['正常', '停用']}] + binary_data = get_excel_template( + header_list=header_list, selector_header_list=selector_header_list, option_list=option_list + ) return binary_data @@ -324,24 +471,25 @@ class UserService: async def export_user_list_services(user_list: List): """ 导出用户信息service + :param user_list: 用户信息列表 :return: 用户信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "userId": "用户编号", - "userName": "用户名称", - "nickName": "用户昵称", - "deptName": "部门", - "email": "邮箱地址", - "phonenumber": "手机号码", - "sex": "性别", - "status": "状态", - "createBy": "创建者", - "createTime": "创建时间", - "updateBy": "更新者", - "updateTime": "更新时间", - "remark": "备注", + 'userId': '用户编号', + 'userName': '用户名称', + 'nickName': '用户昵称', + 'deptName': '部门', + 'email': '邮箱地址', + 'phonenumber': '手机号码', + 'sex': '性别', + 'status': '状态', + 'createBy': '创建者', + 'createTime': '创建时间', + 'updateBy': '更新者', + 'updateTime': '更新时间', + 'remark': '备注', } data = user_list @@ -357,7 +505,9 @@ class UserService: item['sex'] = '女' else: item['sex'] = '未知' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @@ -366,6 +516,7 @@ class UserService: async def get_user_role_allocated_list_services(cls, query_db: AsyncSession, page_object: UserRoleQueryModel): """ 根据用户id获取已分配角色列表 + :param query_db: orm对象 :param page_object: 用户关联角色对象 :return: 已分配角色列表 @@ -378,17 +529,16 @@ class UserService: postIds=post_ids, roleIds=role_ids, dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), - role=CamelCaseUtil.transform_result(query_user.get('user_role_info')) + role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), ) - query_role_list = [SelectedRoleModel(**row) for row in await RoleService.get_role_select_option_services(query_db)] + query_role_list = [ + SelectedRoleModel(**row) for row in await RoleService.get_role_select_option_services(query_db) + ] for model_a in query_role_list: for model_b in user.role: if model_a.role_id == model_b.role_id: model_a.flag = True - result = UserRoleResponseModel( - roles=query_role_list, - user=user - ) + result = UserRoleResponseModel(roles=query_role_list, user=user) return result @@ -396,6 +546,7 @@ class UserService: async def add_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): """ 新增用户关联角色信息service + :param query_db: orm对象 :param page_object: 新增用户关联角色对象 :return: 新增用户关联角色校验结果 @@ -403,14 +554,11 @@ class UserService: if page_object.user_id and page_object.role_ids: role_id_list = page_object.role_ids.split(',') try: + await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) for role_id in role_id_list: - user_role = await cls.detail_user_role_services(query_db, UserRoleModel(userId=page_object.user_id, roleId=role_id)) - if user_role: - continue - else: - await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=role_id)) + await UserDao.add_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=role_id)) await query_db.commit() - result = dict(is_success=True, message='分配成功') + return CrudResponseModel(is_success=True, message='分配成功') except Exception as e: await query_db.rollback() raise e @@ -418,7 +566,7 @@ class UserService: try: await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) await query_db.commit() - result = dict(is_success=True, message='分配成功') + return CrudResponseModel(is_success=True, message='分配成功') except Exception as e: await query_db.rollback() raise e @@ -426,25 +574,28 @@ class UserService: user_id_list = page_object.user_ids.split(',') try: for user_id in user_id_list: - user_role = await cls.detail_user_role_services(query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)) + user_role = await cls.detail_user_role_services( + query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id) + ) 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() - result = dict(is_success=True, message='新增成功') + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='不满足新增条件') - - return CrudResponseModel(**result) + raise ServiceException(message='不满足新增条件') @classmethod async def delete_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): """ 删除用户关联角色信息service + :param query_db: orm对象 :param page_object: 删除用户关联角色对象 :return: 删除用户关联角色校验结果 @@ -452,9 +603,11 @@ class UserService: if (page_object.user_id and page_object.role_id) or (page_object.user_ids and page_object.role_id): if page_object.user_id and page_object.role_id: try: - await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=page_object.role_id)) + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(userId=page_object.user_id, roleId=page_object.role_id) + ) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e @@ -462,23 +615,24 @@ class UserService: user_id_list = page_object.user_ids.split(',') try: for user_id in user_id_list: - await UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)) + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id) + ) await query_db.commit() - result = dict(is_success=True, message='删除成功') + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: await query_db.rollback() raise e else: - result = dict(is_success=False, message='不满足删除条件') + raise ServiceException(message='不满足删除条件') else: - result = dict(is_success=False, message='传入用户角色关联信息为空') - - return CrudResponseModel(**result) + raise ServiceException(message='传入用户角色关联信息为空') @classmethod async def detail_user_role_services(cls, query_db: AsyncSession, page_object: UserRoleModel): """ 获取用户关联角色详细信息service + :param query_db: orm对象 :param page_object: 用户关联角色对象 :return: 用户关联角色详细信息 diff --git a/ruoyi-fastapi-backend/module_task/__init__.py b/ruoyi-fastapi-backend/module_task/__init__.py index 36fefe9bdc99a4966a3627472fb980bff6c96a39..1f4b41292aae3b1b4b030a58a1ccf4fbaf99a696 100644 --- a/ruoyi-fastapi-backend/module_task/__init__.py +++ b/ruoyi-fastapi-backend/module_task/__init__.py @@ -1 +1 @@ -from . import scheduler_test +from . import scheduler_test # noqa: F401 diff --git a/ruoyi-fastapi-backend/module_task/scheduler_test.py b/ruoyi-fastapi-backend/module_task/scheduler_test.py index b22dc6fc37a56bd4c4ae2d95b47d767a7bf62a8b..1c8441f9d6ccad2742d4b7c67d81a4220a5bdf29 100644 --- a/ruoyi-fastapi-backend/module_task/scheduler_test.py +++ b/ruoyi-fastapi-backend/module_task/scheduler_test.py @@ -4,4 +4,4 @@ from datetime import datetime def job(*args, **kwargs): print(args) print(kwargs) - print(f"{datetime.now()}执行了") + print(f'{datetime.now()}执行了') diff --git a/ruoyi-fastapi-backend/requirements.txt b/ruoyi-fastapi-backend/requirements.txt index a0ee800e2cf4b23ce79adcb5456661a7e12468cb..1a7fc30e3854a425e146cb700a6ff26c991fe0dc 100644 --- a/ruoyi-fastapi-backend/requirements.txt +++ b/ruoyi-fastapi-backend/requirements.txt @@ -1,16 +1,17 @@ APScheduler==3.10.4 asyncmy==0.2.9 -DateTime==5.4 -fastapi[all]==0.109.1 +DateTime==5.5 +fastapi[all]==0.111.1 loguru==0.7.2 -openpyxl==3.1.2 -pandas==2.1.4 +openpyxl==3.1.5 +pandas==2.2.2 passlib[bcrypt]==1.7.4 -Pillow==10.2.0 -psutil==5.9.7 -PyMySQL==1.1.0 -python-jose[cryptography]==3.3.0 -redis==5.0.1 -requests==2.31.0 -SQLAlchemy[asyncio]==2.0.25 +Pillow==10.4.0 +psutil==6.0.0 +pydantic-validation-decorator==0.1.2 +PyJWT[crypto]==2.8.0 +PyMySQL==1.1.1 +redis==5.0.7 +requests==2.32.3 +SQLAlchemy[asyncio]==2.0.31 user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/ruff.toml b/ruoyi-fastapi-backend/ruff.toml new file mode 100644 index 0000000000000000000000000000000000000000..ea953686cf07cd98f8382ffc6e5dd54f2b183c4c --- /dev/null +++ b/ruoyi-fastapi-backend/ruff.toml @@ -0,0 +1,4 @@ +line-length = 120 + +[format] +quote-style = "single" \ No newline at end of file diff --git a/ruoyi-fastapi-backend/server.py b/ruoyi-fastapi-backend/server.py index f7f85c20d6fc98ad3f5a1719f68ae1a20af9272c..00b4661dfc132653e08a1cfe648abf124f6af057 100644 --- a/ruoyi-fastapi-backend/server.py +++ b/ruoyi-fastapi-backend/server.py @@ -1,43 +1,43 @@ -from fastapi import FastAPI from contextlib import asynccontextmanager -from sub_applications.handle import handle_sub_applications -from middlewares.handle import handle_middleware +from fastapi import FastAPI +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 module_admin.controller.login_controller import loginController +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.user_controller import userController -from module_admin.controller.menu_controller import menuController +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.role_controller import roleController -from module_admin.controller.post_controler import postController from module_admin.controller.dict_controller import dictController -from module_admin.controller.config_controller import configController -from module_admin.controller.notice_controller import noticeController from module_admin.controller.log_controller import logController -from module_admin.controller.online_controller import onlineController +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.cache_controller import cacheController -from module_admin.controller.common_controller import commonController -from config.env import AppConfig -from config.get_redis import RedisUtil -from config.get_db import init_create_table -from config.get_scheduler import SchedulerUtil -from utils.log_util import logger +from module_admin.controller.user_controller import userController +from sub_applications.handle import handle_sub_applications from utils.common_util import worship +from utils.log_util import logger # 生命周期事件 @asynccontextmanager async def lifespan(app: FastAPI): - logger.info(f"{AppConfig.app_name}开始启动") + logger.info(f'{AppConfig.app_name}开始启动') worship() await init_create_table() app.state.redis = await RedisUtil.create_redis_pool() await RedisUtil.init_sys_dict(app.state.redis) await RedisUtil.init_sys_config(app.state.redis) await SchedulerUtil.init_system_scheduler() - logger.info(f"{AppConfig.app_name}启动成功") + logger.info(f'{AppConfig.app_name}启动成功') yield await RedisUtil.close_redis_pool(app) await SchedulerUtil.close_system_scheduler() @@ -48,7 +48,7 @@ app = FastAPI( title=AppConfig.app_name, description=f'{AppConfig.app_name}接口文档', version=AppConfig.app_version, - lifespan=lifespan + lifespan=lifespan, ) # 挂载子应用 @@ -76,7 +76,7 @@ controller_list = [ {'router': jobController, 'tags': ['系统监控-定时任务']}, {'router': serverController, 'tags': ['系统监控-菜单管理']}, {'router': cacheController, 'tags': ['系统监控-缓存监控']}, - {'router': commonController, 'tags': ['通用模块']} + {'router': commonController, 'tags': ['通用模块']}, ] for controller in controller_list: diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql index 0541431652bc754f3b27e7ca44b6d3dd242d25ac..ef5a8debddd0c2f695424ce2ca201ab6ab217c9f 100644 --- a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql @@ -138,6 +138,7 @@ create table sys_menu ( path varchar(200) default '' comment '路由地址', component varchar(255) default null comment '组件路径', query varchar(255) default null comment '路由参数', + route_name varchar(50) default '' comment '路由名称', is_frame int(1) default 1 comment '是否为外链(0是 1否)', is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)', menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)', @@ -157,106 +158,106 @@ create table sys_menu ( -- 初始化-菜单信息表数据 -- ---------------------------- -- 一级菜单 -insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录'); -insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录'); -insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录'); -insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, '若依官网地址'); +insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录'); +insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录'); +insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录'); +insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, '若依官网地址'); -- 二级菜单 -insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单'); -insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单'); -insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单'); -insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单'); -insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单'); -insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单'); -insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单'); -insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单'); -insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单'); -insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单'); -insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单'); -insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单'); -insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单'); -insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单'); -insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单'); -insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单'); -insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单'); -insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', sysdate(), '', null, '系统接口菜单'); +insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单'); +insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单'); +insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单'); +insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单'); +insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单'); +insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单'); +insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单'); +insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单'); +insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单'); +insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单'); +insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单'); +insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单'); +insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单'); +insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单'); +insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单'); +insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单'); +insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单'); +insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', sysdate(), '', null, '系统接口菜单'); -- 三级菜单 -insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单'); -insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', sysdate(), '', null, '登录日志菜单'); +insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单'); +insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', sysdate(), '', null, '登录日志菜单'); -- 用户管理按钮 -insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); -- 角色管理按钮 -insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); -- 菜单管理按钮 -insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); -- 部门管理按钮 -insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); -- 岗位管理按钮 -insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); -- 字典管理按钮 -insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); -- 参数设置按钮 -insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); -- 通知公告按钮 -insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); -- 操作日志按钮 -insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); -- 登录日志按钮 -insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); -- 在线用户按钮 -insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); -- 定时任务按钮 -insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); -- 代码生成按钮 -insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); -insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); -- ---------------------------- diff --git a/ruoyi-fastapi-backend/sub_applications/staticfiles.py b/ruoyi-fastapi-backend/sub_applications/staticfiles.py index 6899035d82bdc211c9a3942847a918f969e8c17f..c481d729f7e6beb81ebbe8357303b113b530ded0 100644 --- a/ruoyi-fastapi-backend/sub_applications/staticfiles.py +++ b/ruoyi-fastapi-backend/sub_applications/staticfiles.py @@ -7,4 +7,4 @@ def mount_staticfiles(app: FastAPI): """ 挂载静态文件 """ - app.mount(f"{UploadConfig.UPLOAD_PREFIX}", StaticFiles(directory=f"{UploadConfig.UPLOAD_PATH}"), name="profile") + app.mount(f'{UploadConfig.UPLOAD_PREFIX}', StaticFiles(directory=f'{UploadConfig.UPLOAD_PATH}'), name='profile') diff --git a/ruoyi-fastapi-backend/utils/common_util.py b/ruoyi-fastapi-backend/utils/common_util.py index 1dd8c9370ab05d8ea7c97dc666a9b2f59c746a75..853cfe37ecaf7226d2ec9543ebafbea4c5880e67 100644 --- a/ruoyi-fastapi-backend/utils/common_util.py +++ b/ruoyi-fastapi-backend/utils/common_util.py @@ -1,6 +1,6 @@ -import pandas as pd import io import os +import pandas as pd import re from openpyxl import Workbook from openpyxl.styles import Alignment, PatternFill @@ -33,7 +33,7 @@ def worship(): // ========`-.____`-.___\_____/___.-`____.-'======== // // `=---=' // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // -// 佛祖保佑 永不宕机 永无BUG // +// 佛祖保佑 永不宕机 永无BUG // //////////////////////////////////////////////////////////////////// """) @@ -42,10 +42,12 @@ class CamelCaseUtil: """ 下划线形式(snake_case)转小驼峰形式(camelCase)工具方法 """ + @classmethod def snake_to_camel(cls, snake_str): """ 下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase) + :param snake_str: 下划线形式字符串 :return: 小驼峰形式字符串 """ @@ -58,6 +60,7 @@ class CamelCaseUtil: def transform_result(cls, result): """ 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + :param result: 输入数据 :return: 小驼峰形式结果 """ @@ -68,10 +71,24 @@ class CamelCaseUtil: return {cls.snake_to_camel(k): v for k, v in result.items()} # 如果是一组字典或其他类型的列表,遍历列表进行转换 elif isinstance(result, list): - return [cls.transform_result(row) if isinstance(row, (dict, Row)) else (cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in result] + return [ + cls.transform_result(row) + if isinstance(row, (dict, Row)) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] # 如果是sqlalchemy的Row实例,遍历Row进行转换 elif isinstance(result, Row): - return [cls.transform_result(row) if isinstance(row, dict) else (cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in result] + return [ + cls.transform_result(row) + if isinstance(row, dict) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] # 如果是其他类型,如模型实例,先转换为字典 else: return cls.transform_result({c.name: getattr(result, c.name) for c in result.__table__.columns}) @@ -81,10 +98,12 @@ class SnakeCaseUtil: """ 小驼峰形式(camelCase)转下划线形式(snake_case)工具方法 """ + @classmethod def camel_to_snake(cls, camel_str): """ 小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case) + :param camel_str: 小驼峰形式字符串 :return: 下划线形式字符串 """ @@ -96,6 +115,7 @@ class SnakeCaseUtil: def transform_result(cls, result): """ 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + :param result: 输入数据 :return: 小驼峰形式结果 """ @@ -106,16 +126,30 @@ class SnakeCaseUtil: return {cls.camel_to_snake(k): v for k, v in result.items()} # 如果是一组字典或其他类型的列表,遍历列表进行转换 elif isinstance(result, list): - return [cls.transform_result(row) if isinstance(row, (dict, Row)) else (cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in result] + return [ + cls.transform_result(row) + if isinstance(row, (dict, Row)) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] # 如果是sqlalchemy的Row实例,遍历Row进行转换 elif isinstance(result, Row): - return [cls.transform_result(row) if isinstance(row, dict) else (cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in result] + return [ + cls.transform_result(row) + if isinstance(row, dict) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] # 如果是其他类型,如模型实例,先转换为字典 else: return cls.transform_result({c.name: getattr(result, c.name) for c in result.__table__.columns}) -def bytes2human(n, format_str="%(value).1f%(symbol)s"): +def bytes2human(n, format_str='%(value).1f%(symbol)s'): """Used by various scripts. See: http://goo.gl/zeJZl @@ -142,6 +176,7 @@ def bytes2file_response(bytes_info): def export_list2excel(list_data: List): """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + :param list_data: 数据列表 :return: 字典信息对应excel的二进制数据 """ @@ -156,6 +191,7 @@ def export_list2excel(list_data: List): def get_excel_template(header_list: List, selector_header_list: List, option_list: List[dict]): """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + :param header_list: 表头数据列表 :param selector_header_list: 需要设置为选择器格式的表头数据列表 :param option_list: 选择器格式的表头预设的选项列表 @@ -170,7 +206,7 @@ def get_excel_template(header_list: List, selector_header_list: List, option_lis headers = header_list # 设置表头背景样式为灰色,前景色为白色 - header_fill = PatternFill(start_color="ababab", end_color="ababab", fill_type="solid") + header_fill = PatternFill(start_color='ababab', end_color='ababab', fill_type='solid') # 将表头写入第一行 for col_num, header in enumerate(headers, 1): @@ -194,10 +230,11 @@ def get_excel_template(header_list: List, selector_header_list: List, option_lis for option in options: if option.get(selector_header): header_option = option.get(selector_header) - dv = DataValidation(type="list", formula1=f'"{",".join(header_option)}"') + dv = DataValidation(type='list', formula1=f'"{",".join(header_option)}"') # 设置数据有效性规则的起始单元格和结束单元格 dv.add( - f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576') + f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576' + ) # 添加数据有效性规则到工作表 ws.add_data_validation(dv) @@ -215,13 +252,14 @@ def get_excel_template(header_list: List, selector_header_list: List, option_lis def get_filepath_from_url(url: str): """ 工具方法:根据请求参数获取文件路径 + :param url: 请求参数中的url参数 :return: 文件路径 """ - file_info = url.split("?")[1].split("&") - task_id = file_info[0].split("=")[1] - file_name = file_info[1].split("=")[1] - task_path = file_info[2].split("=")[1] + file_info = url.split('?')[1].split('&') + task_id = file_info[0].split('=')[1] + file_name = file_info[1].split('=')[1] + task_path = file_info[2].split('=')[1] filepath = os.path.join(CachePathConfig.PATH, task_path, task_id, file_name) return filepath diff --git a/ruoyi-fastapi-backend/utils/cron_util.py b/ruoyi-fastapi-backend/utils/cron_util.py new file mode 100644 index 0000000000000000000000000000000000000000..62329627d27fd547e215276c8885f074ac12749a --- /dev/null +++ b/ruoyi-fastapi-backend/utils/cron_util.py @@ -0,0 +1,172 @@ +import re +from datetime import datetime + + +class CronUtil: + """ + Cron表达式工具类 + """ + + @classmethod + def __valid_range(cls, search_str: str, start_range: int, end_range: int): + match = re.match(r'^(\d+)-(\d+)$', search_str) + if match: + start, end = int(match.group(1)), int(match.group(2)) + return start_range <= start < end <= end_range + return False + + @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 + ): + match = re.match(r'^(\d+)/(\d+)$', search_str) + if match: + start, end = int(match.group(1)), int(match.group(2)) + return ( + start_range_a <= start <= start_range_b + and end_range_a <= end <= end_range_b + and start + end <= sum_range + ) + return False + + @classmethod + def validate_second_or_minute(cls, second_or_minute: str): + """ + 校验秒或分钟值是否正确 + + :param second_or_minute: 秒或分钟值 + :return: 校验结果 + """ + if ( + 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): + """ + 校验小时值是否正确 + + :param hour: 小时值 + :return: 校验结果 + """ + if ( + 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): + """ + 校验日值是否正确 + + :param day: 日值 + :return: 校验结果 + """ + if ( + 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): + """ + 校验月值是否正确 + + :param month: 月值 + :return: 校验结果 + """ + if ( + 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): + """ + 校验周值是否正确 + + :param week: 周值 + :return: 校验结果 + """ + if ( + 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): + """ + 校验年值是否正确 + + :param year: 年值 + :return: 校验结果 + """ + current_year = int(datetime.now().year) + future_years = [current_year + i for i in range(9)] + if ( + 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(',')) + ) + ): + return True + return False + + @classmethod + def validate_cron_expression(cls, cron_expression: str): + """ + 校验Cron表达式是否正确 + + :param cron_expression: Cron表达式 + :return: 校验结果 + """ + values = cron_expression.split() + if len(values) != 6 and len(values) != 7: + return False + second_validation = cls.validate_second_or_minute(values[0]) + minute_validation = cls.validate_second_or_minute(values[1]) + hour_validation = cls.validate_hour(values[2]) + day_validation = cls.validate_day(values[3]) + month_validation = cls.validate_month(values[4]) + week_validation = cls.validate_week(values[5]) + validation = ( + second_validation + and minute_validation + and hour_validation + and day_validation + and month_validation + and week_validation + ) + if len(values) == 6: + return validation + if len(values) == 7: + year_validation = cls.validate_year(values[6]) + return validation and year_validation diff --git a/ruoyi-fastapi-backend/utils/log_util.py b/ruoyi-fastapi-backend/utils/log_util.py index e9046538f001eb13338a754d7356a60edecb3280..e42f3938cc8435e49f02c17aac4bfa215ed866a7 100644 --- a/ruoyi-fastapi-backend/utils/log_util.py +++ b/ruoyi-fastapi-backend/utils/log_util.py @@ -8,4 +8,4 @@ if not os.path.exists(log_path): log_path_error = os.path.join(log_path, f'{time.strftime("%Y-%m-%d")}_error.log') -logger.add(log_path_error, rotation="50MB", encoding="utf-8", enqueue=True, compression="zip") +logger.add(log_path_error, rotation='50MB', encoding='utf-8', enqueue=True, compression='zip') diff --git a/ruoyi-fastapi-backend/utils/message_util.py b/ruoyi-fastapi-backend/utils/message_util.py index 6a21f88d0e714cd89425c011150122301f9d4e19..3d3eb51aebd51cb1f97b5a756e3a4bf493c1ead4 100644 --- a/ruoyi-fastapi-backend/utils/message_util.py +++ b/ruoyi-fastapi-backend/utils/message_util.py @@ -2,4 +2,4 @@ from utils.log_util import logger def message_service(sms_code: str): - logger.info(f"短信验证码为{sms_code}") + logger.info(f'短信验证码为{sms_code}') diff --git a/ruoyi-fastapi-backend/utils/page_util.py b/ruoyi-fastapi-backend/utils/page_util.py index d46d26042a7e1787ed520d86b55e71876f6a9c45..6e7bfe221e678f0fde8156eea5bc426b20ff2d8d 100644 --- a/ruoyi-fastapi-backend/utils/page_util.py +++ b/ruoyi-fastapi-backend/utils/page_util.py @@ -1,9 +1,9 @@ import math -from typing import Optional, List -from sqlalchemy import Select, select, func -from sqlalchemy.ext.asyncio import AsyncSession 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 @@ -11,6 +11,7 @@ class PageResponseModel(BaseModel): """ 列表分页查询返回模型 """ + model_config = ConfigDict(alias_generator=to_camel) rows: List = [] @@ -29,6 +30,7 @@ class PageUtil: def get_page_obj(cls, data_list: List, page_num: int, page_size: int): """ 输入数据列表data_list和分页信息,返回分页数据列表结果 + :param data_list: 原始数据列表 :param page_num: 当前页码 :param page_size: 当前页面数据量 @@ -43,11 +45,7 @@ class PageUtil: has_next = True if math.ceil(len(data_list) / page_size) > page_num else False result = PageResponseModel( - rows=paginated_data, - pageNum=page_num, - pageSize=page_size, - total=len(data_list), - hasNext=has_next + rows=paginated_data, pageNum=page_num, pageSize=page_size, total=len(data_list), hasNext=has_next ) return result @@ -56,6 +54,7 @@ class PageUtil: async def paginate(cls, db: AsyncSession, query: Select, page_num: int, page_size: int, is_page: bool = False): """ 输入查询语句和分页信息,返回分页数据列表结果 + :param db: orm对象 :param query: sqlalchemy查询语句 :param page_num: 当前页码 @@ -65,7 +64,7 @@ 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))) + query_result = await db.execute(query.offset((page_num - 1) * page_size).limit(page_size)) paginated_data = [] for row in query_result: if row and len(row) == 1: @@ -78,7 +77,7 @@ class PageUtil: pageNum=page_num, pageSize=page_size, total=total, - hasNext=has_next + hasNext=has_next, ) else: query_result = await db.execute(query) @@ -96,6 +95,7 @@ class PageUtil: def get_page_obj(data_list: List, page_num: int, page_size: int): """ 输入数据列表data_list和分页信息,返回分页数据列表结果 + :param data_list: 原始数据列表 :param page_num: 当前页码 :param page_size: 当前页面数据量 @@ -110,11 +110,7 @@ def get_page_obj(data_list: List, page_num: int, page_size: int): has_next = True if math.ceil(len(data_list) / page_size) > page_num else False result = PageResponseModel( - rows=paginated_data, - pageNum=page_num, - pageSize=page_size, - total=len(data_list), - hasNext=has_next + rows=paginated_data, pageNum=page_num, pageSize=page_size, total=len(data_list), hasNext=has_next ) return result diff --git a/ruoyi-fastapi-backend/utils/pwd_util.py b/ruoyi-fastapi-backend/utils/pwd_util.py index 2e9a2f9a72f69f20e819c206ea8eb61258d5de54..86e9c271e6a0afb0e217e04264f5f92e28546443 100644 --- a/ruoyi-fastapi-backend/utils/pwd_util.py +++ b/ruoyi-fastapi-backend/utils/pwd_util.py @@ -1,6 +1,6 @@ from passlib.context import CryptContext -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') class PwdUtil: @@ -12,6 +12,7 @@ class PwdUtil: def verify_password(cls, plain_password, hashed_password): """ 工具方法:校验当前输入的密码与数据库存储的密码是否一致 + :param plain_password: 当前输入的密码 :param hashed_password: 数据库存储的密码 :return: 校验结果 @@ -22,6 +23,7 @@ class PwdUtil: def get_password_hash(cls, input_password): """ 工具方法:对当前输入的密码进行加密 + :param input_password: 输入的密码 :return: 加密成功的密码 """ diff --git a/ruoyi-fastapi-backend/utils/response_util.py b/ruoyi-fastapi-backend/utils/response_util.py index a741074ad03ef7e93e659c877b7acf301ae2b8fb..88d2e37bfa72db23ecc3e88967ed521348e7c68c 100644 --- a/ruoyi-fastapi-backend/utils/response_util.py +++ b/ruoyi-fastapi-backend/utils/response_util.py @@ -1,9 +1,10 @@ +from datetime import datetime from fastapi import status -from fastapi.responses import JSONResponse, Response, StreamingResponse from fastapi.encoders import jsonable_encoder -from typing import Any, Dict, Optional +from fastapi.responses import JSONResponse, Response, StreamingResponse from pydantic import BaseModel -from datetime import datetime +from typing import Any, Dict, Optional +from config.constant import HttpStatusConstant class ResponseUtil: @@ -12,10 +13,17 @@ class ResponseUtil: """ @classmethod - def success(cls, msg: str = '操作成功', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None) -> Response: + def success( + cls, + msg: str = '操作成功', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: """ 成功响应方法 + :param msg: 可选,自定义成功响应信息 :param data: 可选,成功响应结果中属性为data的值 :param rows: 可选,成功响应结果中属性为rows的值 @@ -23,10 +31,7 @@ class ResponseUtil: :param model_content: 可选,BaseModel类型,成功响应结果中自定义属性的值 :return: 成功响应结果 """ - result = { - 'code': 200, - 'msg': msg - } + result = {'code': HttpStatusConstant.SUCCESS, 'msg': msg} if data is not None: result['data'] = data @@ -39,16 +44,20 @@ class ResponseUtil: result.update({'success': True, 'time': datetime.now()}) - return JSONResponse( - status_code=status.HTTP_200_OK, - content=jsonable_encoder(result) - ) + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) @classmethod - def failure(cls, msg: str = '操作失败', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None) -> Response: + def failure( + cls, + msg: str = '操作失败', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: """ 失败响应方法 + :param msg: 可选,自定义失败响应信息 :param data: 可选,失败响应结果中属性为data的值 :param rows: 可选,失败响应结果中属性为rows的值 @@ -56,10 +65,7 @@ class ResponseUtil: :param model_content: 可选,BaseModel类型,失败响应结果中自定义属性的值 :return: 失败响应结果 """ - result = { - 'code': 601, - 'msg': msg - } + result = {'code': HttpStatusConstant.WARN, 'msg': msg} if data is not None: result['data'] = data @@ -72,16 +78,20 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse( - status_code=status.HTTP_200_OK, - content=jsonable_encoder(result) - ) + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) @classmethod - def unauthorized(cls, msg: str = '登录信息已过期,访问系统资源失败', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None) -> Response: + def unauthorized( + cls, + msg: str = '登录信息已过期,访问系统资源失败', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: """ 未认证响应方法 + :param msg: 可选,自定义未认证响应信息 :param data: 可选,未认证响应结果中属性为data的值 :param rows: 可选,未认证响应结果中属性为rows的值 @@ -89,10 +99,7 @@ class ResponseUtil: :param model_content: 可选,BaseModel类型,未认证响应结果中自定义属性的值 :return: 未认证响应结果 """ - result = { - 'code': 401, - 'msg': msg - } + result = {'code': HttpStatusConstant.UNAUTHORIZED, 'msg': msg} if data is not None: result['data'] = data @@ -105,27 +112,28 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse( - status_code=status.HTTP_200_OK, - content=jsonable_encoder(result) - ) + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) @classmethod - def forbidden(cls, msg: str = '该用户无此接口权限', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None) -> Response: + def forbidden( + cls, + msg: str = '该用户无此接口权限', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: """ - 未认证响应方法 - :param msg: 可选,自定义未认证响应信息 - :param data: 可选,未认证响应结果中属性为data的值 - :param rows: 可选,未认证响应结果中属性为rows的值 - :param dict_content: 可选,dict类型,未认证响应结果中自定义属性的值 - :param model_content: 可选,BaseModel类型,未认证响应结果中自定义属性的值 - :return: 未认证响应结果 + 未授权响应方法 + + :param msg: 可选,自定义未授权响应信息 + :param data: 可选,未授权响应结果中属性为data的值 + :param rows: 可选,未授权响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,未授权响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,未授权响应结果中自定义属性的值 + :return: 未授权响应结果 """ - result = { - 'code': 403, - 'msg': msg - } + result = {'code': HttpStatusConstant.FORBIDDEN, 'msg': msg} if data is not None: result['data'] = data @@ -138,16 +146,20 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse( - status_code=status.HTTP_200_OK, - content=jsonable_encoder(result) - ) + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) @classmethod - def error(cls, msg: str = '接口异常', data: Optional[Any] = None, rows: Optional[Any] = None, - dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None) -> Response: + def error( + cls, + msg: str = '接口异常', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: """ 错误响应方法 + :param msg: 可选,自定义错误响应信息 :param data: 可选,错误响应结果中属性为data的值 :param rows: 可选,错误响应结果中属性为rows的值 @@ -155,10 +167,7 @@ class ResponseUtil: :param model_content: 可选,BaseModel类型,错误响应结果中自定义属性的值 :return: 错误响应结果 """ - result = { - 'code': 500, - 'msg': msg - } + result = {'code': HttpStatusConstant.ERROR, 'msg': msg} if data is not None: result['data'] = data @@ -171,19 +180,14 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse( - status_code=status.HTTP_200_OK, - content=jsonable_encoder(result) - ) + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) @classmethod def streaming(cls, *, data: Any = None): """ 流式响应方法 + :param data: 流式传输的内容 :return: 流式响应结果 """ - return StreamingResponse( - status_code=status.HTTP_200_OK, - content=data - ) + return StreamingResponse(status_code=status.HTTP_200_OK, content=data) diff --git a/ruoyi-fastapi-backend/utils/string_util.py b/ruoyi-fastapi-backend/utils/string_util.py new file mode 100644 index 0000000000000000000000000000000000000000..0be9e653bffdd63dace928ed6d95059dabf74ce8 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/string_util.py @@ -0,0 +1,101 @@ +from typing import List +from config.constant import CommonConstant + + +class StringUtil: + """ + 字符串工具类 + """ + + @classmethod + def is_blank(cls, string: str) -> bool: + """ + 校验字符串是否为''或全空格 + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + if string is None: + return False + str_len = len(string) + if str_len == 0: + return True + else: + for i in range(str_len): + if string[i] != ' ': + return False + return True + + @classmethod + def is_empty(cls, string) -> bool: + """ + 校验字符串是否为''或None + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + return string is None or len(string) == 0 + + @classmethod + def is_http(cls, link: str): + """ + 判断是否为http(s)://开头 + + :param link: 链接 + :return: 是否为http(s)://开头 + """ + return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) + + @classmethod + def contains_ignore_case(cls, search_str: str, compare_str: str): + """ + 查找指定字符串是否包含指定字符串同时串忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 查找结果 + """ + if compare_str and search_str: + return compare_str.lower() in search_str.lower() + return False + + @classmethod + def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + """ + 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 查找结果 + """ + if search_str and compare_str_list: + for compare_str in compare_str_list: + return cls.contains_ignore_case(search_str, compare_str) + return False + + @classmethod + def startswith_case(cls, search_str: str, compare_str: str): + """ + 查找指定字符串是否以指定字符串开头 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 查找结果 + """ + if compare_str and search_str: + return search_str.startswith(compare_str) + return False + + @classmethod + def startswith_any_case(cls, search_str: str, compare_str_list: List[str]): + """ + 查找指定字符串是否以指定字符串列表中的任意一个字符串开头 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 查找结果 + """ + if search_str and compare_str_list: + for compare_str in compare_str_list: + return cls.startswith_case(search_str, compare_str) + return False diff --git a/ruoyi-fastapi-backend/utils/time_format_util.py b/ruoyi-fastapi-backend/utils/time_format_util.py index ba19489f64f3f825b41077509d66fc2b980978de..380537ebaa28be2ed2e0d291d87847cd5c651ac4 100644 --- a/ruoyi-fastapi-backend/utils/time_format_util.py +++ b/ruoyi-fastapi-backend/utils/time_format_util.py @@ -29,6 +29,7 @@ def list_format_datetime(lst): def format_datetime_dict_list(dicts): """ 递归遍历嵌套字典,并将 datetime 值转换为字符串格式 + :param dicts: 输入一个嵌套字典的列表 :return: 对目标列表中所有字典的datetime类型的属性格式化 """ diff --git a/ruoyi-fastapi-backend/utils/upload_util.py b/ruoyi-fastapi-backend/utils/upload_util.py index dc71255a931bf0b3894ec8d39a2c8f1b8bce36ac..726789edaa965fcfe99412f3c5763011143ba1cd 100644 --- a/ruoyi-fastapi-backend/utils/upload_util.py +++ b/ruoyi-fastapi-backend/utils/upload_util.py @@ -1,7 +1,7 @@ -import random import os -from fastapi import UploadFile +import random from datetime import datetime +from fastapi import UploadFile from config.env import UploadConfig @@ -14,15 +14,20 @@ class UploadUtil: def generate_random_number(cls): """ 生成3位数字构成的字符串 + + :return: 3位数字构成的字符串 """ random_number = random.randint(1, 999) return f'{random_number:03}' @classmethod - def check_file_exists(cls, filepath): + def check_file_exists(cls, filepath: str): """ 检查文件是否存在 + + :param filepath: 文件路径 + :return: 校验结果 """ return os.path.exists(filepath) @@ -30,6 +35,9 @@ class UploadUtil: def check_file_extension(cls, file: UploadFile): """ 检查文件后缀是否合法 + + :param file: 文件对象 + :return: 校验结果 """ file_extension = file.filename.rsplit('.', 1)[-1] if file_extension in UploadConfig.DEFAULT_ALLOWED_EXTENSION: @@ -37,9 +45,12 @@ class UploadUtil: return False @classmethod - def check_file_timestamp(cls, filename): + def check_file_timestamp(cls, filename: str): """ 校验文件时间戳是否合法 + + :param filename: 文件名称 + :return: 校验结果 """ timestamp = filename.rsplit('.', 1)[0].split('_')[-1].split(UploadConfig.UPLOAD_MACHINE)[0] try: @@ -49,28 +60,37 @@ class UploadUtil: return False @classmethod - def check_file_machine(cls, filename): + def check_file_machine(cls, filename: str): """ 校验文件机器码是否合法 + + :param filename: 文件名称 + :return: 校验结果 """ if filename.rsplit('.', 1)[0][-4] == UploadConfig.UPLOAD_MACHINE: return True return False @classmethod - def check_file_random_code(cls, filename): + def check_file_random_code(cls, filename: str): """ 校验文件随机码是否合法 + + :param filename: 文件名称 + :return: 校验结果 """ - valid_code_list = [f"{i:03}" for i in range(1, 999)] + 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 @classmethod - def generate_file(cls, filepath): + def generate_file(cls, filepath: str): """ 根据文件生成二进制数据 + + :param filepath: 文件路径 + :yield: 二进制数据 """ with open(filepath, 'rb') as response_file: yield from response_file @@ -79,5 +99,7 @@ class UploadUtil: def delete_file(cls, filepath: str): """ 根据文件路径删除对应文件 + + :param filepath: 文件路径 """ os.remove(filepath) diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json index 78ad6e6fdcee05f8694f9b11b92e7c466d6e4137..be30e981e054eedc361d9f0aaba749bae9bf036b 100644 --- a/ruoyi-fastapi-frontend/package.json +++ b/ruoyi-fastapi-frontend/package.json @@ -1,6 +1,6 @@ { "name": "vfadmin", - "version": "1.2.2", + "version": "1.3.0", "description": "vfadmin管理系统", "author": "insistence", "license": "MIT", @@ -39,9 +39,9 @@ "@antv/g2plot": "^2.4.31", "@riophae/vue-treeselect": "0.4.0", "ant-design-vue": "^1.7.8", - "axios": "0.24.0", + "axios": "0.28.1", "clipboard": "2.0.8", - "core-js": "3.25.3", + "core-js": "3.37.1", "echarts": "5.4.0", "element-ui": "2.15.14", "file-saver": "2.0.5", diff --git a/ruoyi-fastapi-frontend/src/api/login.js b/ruoyi-fastapi-frontend/src/api/login.js index d3a8e0b645cbc0a8d3eab649269143e2fcd8248d..8f6699b60bde4ea29e29862a81212f4fea21d8c4 100644 --- a/ruoyi-fastapi-frontend/src/api/login.js +++ b/ruoyi-fastapi-frontend/src/api/login.js @@ -11,7 +11,8 @@ export function login(username, password, code, uuid) { url: '/login', headers: { isToken: false, - repeatSubmit: false + repeatSubmit: false, + 'Content-Type': 'application/x-www-form-urlencoded' }, method: 'post', data: data diff --git a/ruoyi-fastapi-frontend/src/api/system/user.js b/ruoyi-fastapi-frontend/src/api/system/user.js index f2f76ef9fed4310e3028afd0fd17f269b2178a54..9b0211a5fe3db21e68c659a9e9c0dfd539b677a7 100644 --- a/ruoyi-fastapi-frontend/src/api/system/user.js +++ b/ruoyi-fastapi-frontend/src/api/system/user.js @@ -105,6 +105,7 @@ export function uploadAvatar(data) { return request({ url: '/system/user/profile/avatar', method: 'post', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: data }) } diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/hour.vue b/ruoyi-fastapi-frontend/src/components/Crontab/hour.vue index 4b1f1fcdb3705554e6844c7cdf0a572f3f247fd2..3216c33f8749495544d538ff2b2eed8918d4a507 100644 --- a/ruoyi-fastapi-frontend/src/components/Crontab/hour.vue +++ b/ruoyi-fastapi-frontend/src/components/Crontab/hour.vue @@ -51,10 +51,16 @@ export default { methods: { // 单选按钮值变化时 radioChange() { + if (this.cron.min === '*') { + this.$emit('update', 'min', '0', 'hour'); + } + if (this.cron.second === '*') { + this.$emit('update', 'second', '0', 'hour'); + } switch (this.radioValue) { case 1: - this.$emit('update', 'hour', '*') - break; + this.$emit('update', 'hour', '*') + break; case 2: this.$emit('update', 'hour', this.cycleTotal); break; diff --git a/ruoyi-fastapi-frontend/src/views/monitor/job/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/job/index.vue index 4939d57edbe4dc9d7998747e3a07f5a6538994e9..8703d941dd77b9318fed0b3eb7dc7f654e3a35c6 100644 --- a/ruoyi-fastapi-frontend/src/views/monitor/job/index.vue +++ b/ruoyi-fastapi-frontend/src/views/monitor/job/index.vue @@ -415,7 +415,7 @@ export default { cronExpression: undefined, misfirePolicy: "1", concurrent: "1", - status: "0" + status: "1" }; this.resetForm("form"); }, diff --git a/ruoyi-fastapi-frontend/src/views/system/menu/index.vue b/ruoyi-fastapi-frontend/src/views/system/menu/index.vue index d2ad50a44dcf4dab0932b217f86a5018f6c7ab62..619c9e2dac937932b66494146ce022cbeb2f0109 100644 --- a/ruoyi-fastapi-frontend/src/views/system/menu/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/menu/index.vue @@ -126,7 +126,7 @@ - + + + + + + - - - + + + + + + + 路由名称 + + @@ -369,6 +380,7 @@ export default { menuId: undefined, parentId: 0, menuName: undefined, + routeName: undefined, icon: undefined, menuType: "M", orderNum: undefined, diff --git a/ruoyi-fastapi-frontend/src/views/system/role/selectUser.vue b/ruoyi-fastapi-frontend/src/views/system/role/selectUser.vue index b2b072f9c8b3112226804564d5b7045f75ab0207..afd019c9745bcce1b1cfe2fa8f1ef38a1946a18b 100644 --- a/ruoyi-fastapi-frontend/src/views/system/role/selectUser.vue +++ b/ruoyi-fastapi-frontend/src/views/system/role/selectUser.vue @@ -127,10 +127,8 @@ export default { } authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => { this.$modal.msgSuccess(res.msg); - if (res.code === 200) { - this.visible = false; - this.$emit("ok"); - } + this.visible = false; + this.$emit("ok"); }); } } diff --git a/ruoyi-fastapi-frontend/src/views/system/user/index.vue b/ruoyi-fastapi-frontend/src/views/system/user/index.vue index 1666cf09c5248d0aa7fc71a94093e22d62ba070d..793409559a3446db36baef9f27d906b9a43a195c 100644 --- a/ruoyi-fastapi-frontend/src/views/system/user/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/user/index.vue @@ -582,7 +582,7 @@ export default { this.$set(this.form, "roleIds", response.roleIds); this.open = true; this.title = "修改用户"; - this.form.password = ""; + this.form.password = undefined; }); }, /** 重置密码按钮操作 */