diff --git a/ruoyi-fastapi-backend/app.py b/ruoyi-fastapi-backend/app.py new file mode 100644 index 0000000..364d6b8 --- /dev/null +++ b/ruoyi-fastapi-backend/app.py @@ -0,0 +1,112 @@ +from fastapi import FastAPI, Request +import uvicorn +from fastapi.exceptions import HTTPException +from fastapi.middleware.cors import CORSMiddleware +from contextlib import asynccontextmanager +from module_admin.controller.login_controller import loginController +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.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.job_controller import jobController +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.get_redis import RedisUtil +from config.get_db import init_create_table +from config.get_scheduler import SchedulerUtil +from utils.response_util import * +from utils.log_util import logger +from utils.common_util import worship + + +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info("RuoYi-FastAPI开始启动") + 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("RuoYi-FastAPI启动成功") + yield + await RedisUtil.close_redis_pool(app) + await SchedulerUtil.close_system_scheduler() + + +app = FastAPI( + title='RuoYi-FastAPI', + description='RuoYi-FastAPI接口文档', + version='1.0.0', + lifespan=lifespan, + root_path='/dev-api' +) + +# 前端页面url +origins = [ + "http://localhost:81", + "http://127.0.0.1:81", +] + +# 后台api允许跨域 +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +# 自定义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(PermissionException) +async def permission_exception_handler(request: Request, exc: PermissionException): + return ResponseUtil.forbidden(data=exc.data, msg=exc.message) + + +@app.exception_handler(HTTPException) +async def http_exception_handler(request: Request, exc: HTTPException): + return JSONResponse( + content=jsonable_encoder({"message": exc.detail, "code": exc.status_code}), + status_code=exc.status_code + ) + + +controller_list = [ + {'router': loginController, 'tags': ['登录模块']}, + {'router': captchaController, 'tags': ['验证码模块']}, + {'router': userController, 'tags': ['系统管理-用户管理']}, + {'router': roleController, 'tags': ['系统管理-角色管理']}, + {'router': menuController, 'tags': ['系统管理-菜单管理']}, + {'router': deptController, 'tags': ['系统管理-部门管理']}, + {'router': postController, 'tags': ['系统管理-岗位管理']}, + {'router': dictController, 'tags': ['系统管理-字典管理']}, + {'router': configController, 'tags': ['系统管理-参数管理']}, + {'router': noticeController, 'tags': ['系统管理-通知公告管理']}, + {'router': logController, 'tags': ['系统管理-日志管理']}, + {'router': onlineController, 'tags': ['系统监控-在线用户']}, + {'router': jobController, 'tags': ['系统监控-定时任务']}, + {'router': serverController, 'tags': ['系统监控-菜单管理']}, + {'router': cacheController, 'tags': ['系统监控-缓存监控']}, + {'router': commonController, 'tags': ['通用模块']} +] + +for controller in controller_list: + app.include_router(router=controller.get('router'), tags=controller.get('tags')) + +if __name__ == '__main__': + uvicorn.run(app='app:app', host="0.0.0.0", port=9099, reload=True) diff --git a/ruoyi-fastapi-backend/assets/font/Arial.ttf b/ruoyi-fastapi-backend/assets/font/Arial.ttf new file mode 100644 index 0000000..9512aea Binary files /dev/null and b/ruoyi-fastapi-backend/assets/font/Arial.ttf differ diff --git a/ruoyi-fastapi-backend/caches/profile/avatar/blob_20240119210836.jpeg b/ruoyi-fastapi-backend/caches/profile/avatar/blob_20240119210836.jpeg new file mode 100644 index 0000000..98516d2 Binary files /dev/null and b/ruoyi-fastapi-backend/caches/profile/avatar/blob_20240119210836.jpeg differ diff --git a/ruoyi-fastapi-backend/config/database.py b/ruoyi-fastapi-backend/config/database.py new file mode 100644 index 0000000..a9acc4c --- /dev/null +++ b/ruoyi-fastapi-backend/config/database.py @@ -0,0 +1,14 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from urllib.parse import quote_plus +from config.env import DataBaseConfig + +SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{DataBaseConfig.USERNAME}:{quote_plus(DataBaseConfig.PASSWORD)}@" \ + f"{DataBaseConfig.HOST}:{DataBaseConfig.PORT}/{DataBaseConfig.DB}" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, echo=True +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() diff --git a/ruoyi-fastapi-backend/config/env.py b/ruoyi-fastapi-backend/config/env.py new file mode 100644 index 0000000..3f104db --- /dev/null +++ b/ruoyi-fastapi-backend/config/env.py @@ -0,0 +1,54 @@ +import os + + +class JwtConfig: + """ + Jwt配置 + """ + SECRET_KEY = "b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55" + ALGORITHM = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES = 1440 + REDIS_TOKEN_EXPIRE_MINUTES = 30 + + +class DataBaseConfig: + """ + 数据库配置 + """ + HOST = "127.0.0.1" + PORT = 3306 + USERNAME = 'root' + PASSWORD = 'mysqlroot' + DB = 'ruoyi-fastapi' + + +class RedisConfig: + """ + Redis配置 + """ + HOST = "127.0.0.1" + PORT = 6379 + USERNAME = '' + PASSWORD = '' + DB = 2 + + +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': '短信验证码'} diff --git a/ruoyi-fastapi-backend/config/get_db.py b/ruoyi-fastapi-backend/config/get_db.py new file mode 100644 index 0000000..6d3a9cf --- /dev/null +++ b/ruoyi-fastapi-backend/config/get_db.py @@ -0,0 +1,27 @@ +from config.database import * +from utils.log_util import logger + + +def get_db_pro(): + """ + 每一个请求处理完毕后会关闭当前连接,不同的请求使用不同的连接 + :return: + """ + current_db = SessionLocal() + try: + yield current_db + finally: + current_db.close() + + +async def init_create_table(): + """ + 应用启动时初始化数据库连接 + :return: + """ + logger.info("初始化数据库连接...") + Base.metadata.create_all(bind=engine) + logger.info("数据库连接成功") + + +get_db = get_db_pro diff --git a/ruoyi-fastapi-backend/config/get_redis.py b/ruoyi-fastapi-backend/config/get_redis.py new file mode 100644 index 0000000..c7bc8ec --- /dev/null +++ b/ruoyi-fastapi-backend/config/get_redis.py @@ -0,0 +1,65 @@ +import aioredis +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 SessionLocal +from utils.log_util import logger + + +class RedisUtil: + """ + Redis相关方法 + """ + + @classmethod + async def create_redis_pool(cls) -> aioredis.Redis: + """ + 应用启动时初始化redis连接 + :return: Redis连接对象 + """ + logger.info("开始连接redis...") + redis = await aioredis.from_url( + url=f"redis://{RedisConfig.HOST}", + port=RedisConfig.PORT, + username=RedisConfig.USERNAME, + password=RedisConfig.PASSWORD, + db=RedisConfig.DB, + encoding="utf-8", + decode_responses=True + ) + logger.info("redis连接成功") + return redis + + @classmethod + async def close_redis_pool(cls, app): + """ + 应用关闭时关闭redis连接 + :param app: fastapi对象 + :return: + """ + await app.state.redis.close() + logger.info("关闭redis连接成功") + + @classmethod + async def init_sys_dict(cls, redis): + """ + 应用启动时缓存字典表 + :param redis: redis对象 + :return: + """ + session = SessionLocal() + await DictDataService.init_cache_sys_dict_services(session, redis) + + session.close() + + @classmethod + async def init_sys_config(cls, redis): + """ + 应用启动时缓存参数配置表 + :param redis: redis对象 + :return: + """ + session = SessionLocal() + await ConfigService.init_cache_sys_config_services(session, redis) + + session.close() diff --git a/ruoyi-fastapi-backend/config/get_scheduler.py b/ruoyi-fastapi-backend/config/get_scheduler.py new file mode 100644 index 0000000..3910ebe --- /dev/null +++ b/ruoyi-fastapi-backend/config/get_scheduler.py @@ -0,0 +1,232 @@ +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.triggers.cron import CronTrigger +from apscheduler.events import EVENT_ALL +import json +from datetime import datetime, timedelta +from config.database import engine, SQLALCHEMY_DATABASE_URL, SessionLocal +from config.env import RedisConfig +from module_admin.service.job_log_service import JobLogService, JobLogModel +from module_admin.dao.job_dao import Session, JobDao +from utils.log_util import logger +import module_task + + +# 重写Cron定时 +class MyCronTrigger(CronTrigger): + @classmethod + def from_crontab(cls, expr, 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))) + + second = values[0] + minute = values[1] + hour = values[2] + if '?' in values[3]: + day = None + elif 'L' in values[5]: + day = f"last {values[5].replace('L', '')}" + elif 'W' in values[3]: + day = cls.__find_recent_workday(int(values[3].split('W')[0])) + else: + day = values[3].replace('L', 'last') + month = values[4] + if '?' in values[5] or 'L' in values[5]: + week = None + elif '#' in values[5]: + week = int(values[5].split('#')[1]) + else: + week = values[5] + if '#' in values[5]: + day_of_week = int(values[5].split('#')[0]) - 1 + else: + day_of_week = None + year = values[6] if len(values) == 7 else None + 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): + now = datetime.now() + date = datetime(now.year, now.month, day) + if date.weekday() < 5: + return date.day + else: + diff = 1 + while True: + previous_day = date - timedelta(days=diff) + if previous_day.weekday() < 5: + return previous_day.day + else: + diff += 1 + + +job_stores = { + 'default': MemoryJobStore(), + 'sqlalchemy': SQLAlchemyJobStore(url=SQLALCHEMY_DATABASE_URL, engine=engine), + 'redis': RedisJobStore( + **dict( + host=RedisConfig.HOST, + port=RedisConfig.PORT, + username=RedisConfig.USERNAME, + password=RedisConfig.PASSWORD, + db=RedisConfig.DB + ) + ) +} +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) + + +class SchedulerUtil: + """ + 定时任务相关方法 + """ + + @classmethod + async def init_system_scheduler(cls, query_db: Session = SessionLocal()): + """ + 应用启动时初始化定时任务 + :return: + """ + logger.info("开始启动定时任务...") + scheduler.start() + job_list = JobDao.get_job_list_for_scheduler(query_db) + for item in job_list: + query_job = cls.get_scheduler_job(job_id=str(item.job_id)) + if query_job: + cls.remove_scheduler_job(job_id=str(item.job_id)) + cls.add_scheduler_job(item) + query_db.close() + scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL) + logger.info("系统初始定时任务加载成功") + + @classmethod + async def close_system_scheduler(cls): + """ + 应用关闭时关闭定时任务 + :return: + """ + scheduler.shutdown() + logger.info("关闭定时任务成功") + + @classmethod + def get_scheduler_job(cls, job_id): + """ + 根据任务id获取任务对象 + :param job_id: 任务id + :return: 任务对象 + """ + query_job = scheduler.get_job(job_id=str(job_id)) + + return query_job + + @classmethod + def add_scheduler_job(cls, job_info): + """ + 根据输入的任务对象信息添加任务 + :param job_info: 任务对象信息 + :return: + """ + scheduler.add_job( + func=eval(job_info.invoke_target), + trigger=MyCronTrigger.from_crontab(job_info.cron_expression), + args=job_info.job_args.split(',') if job_info.job_args else None, + kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None, + id=str(job_info.job_id), + name=job_info.job_name, + misfire_grace_time=1000000000000 if job_info.misfire_policy == '3' else None, + coalesce=True if job_info.misfire_policy == '2' else False, + max_instances=3 if job_info.concurrent == '0' else 1, + jobstore=job_info.job_group, + executor=job_info.job_executor + ) + + @classmethod + def execute_scheduler_job_once(cls, job_info): + """ + 根据输入的任务对象执行一次任务 + :param job_info: 任务对象信息 + :return: + """ + scheduler.add_job( + func=eval(job_info.invoke_target), + trigger='date', + run_date=datetime.now() + timedelta(seconds=1), + args=job_info.job_args.split(',') if job_info.job_args else None, + kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None, + id=str(job_info.job_id), + name=job_info.job_name, + misfire_grace_time=1000000000000 if job_info.misfire_policy == '3' else None, + coalesce=True if job_info.misfire_policy == '2' else False, + max_instances=3 if job_info.concurrent == '0' else 1, + jobstore=job_info.job_group, + executor=job_info.job_executor + ) + + @classmethod + def remove_scheduler_job(cls, job_id): + """ + 根据任务id移除任务 + :param job_id: 任务id + :return: + """ + scheduler.remove_job(job_id=str(job_id)) + + @classmethod + def scheduler_event_listener(cls, event): + # 获取事件类型和任务ID + event_type = event.__class__.__name__ + # 获取任务执行异常信息 + status = '0' + exception_info = '' + if event_type == 'JobExecutionEvent' and event.exception: + exception_info = str(event.exception) + status = '1' + job_id = event.job_id + query_job = cls.get_scheduler_job(job_id=job_id) + if query_job: + query_job_info = query_job.__getstate__() + # 获取任务名称 + job_name = query_job_info.get('name') + # 获取任务组名 + job_group = query_job._jobstore_alias + # 获取任务执行器 + job_executor = query_job_info.get('executor') + # 获取调用目标字符串 + invoke_target = query_job_info.get('func') + # 获取调用函数位置参数 + job_args = ','.join(query_job_info.get('args')) + # 获取调用函数关键字参数 + job_kwargs = json.dumps(query_job_info.get('kwargs')) + # 获取任务触发器 + job_trigger = str(query_job_info.get('trigger')) + # 构造日志消息 + job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + job_log = JobLogModel( + jobName=job_name, + jobGroup=job_group, + jobExecutor=job_executor, + invokeTarget=invoke_target, + jobArgs=job_args, + jobKwargs=job_kwargs, + jobTrigger=job_trigger, + jobMessage=job_message, + status=status, + exceptionInfo=exception_info + ) + session = SessionLocal() + JobLogService.add_job_log_services(session, job_log) + session.close() diff --git a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py new file mode 100644 index 0000000..13131f8 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py @@ -0,0 +1,172 @@ +from functools import wraps +from fastapi import Request +from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse +import inspect +import os +import json +import time +from datetime import datetime +import requests +from user_agents import parse +from typing import Optional +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 + + +def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'operation'): + """ + 日志装饰器 + :param log_type: 日志类型(login表示登录日志,为空表示为操作日志) + :param title: 当前日志装饰器装饰的模块标题 + :param business_type: 业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据) + :return: + """ + def decorator(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' + try: + if oper_ip != '127.0.0.1' and oper_ip != 'localhost': + ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}') + if ip_result.status_code == 200: + prov = ip_result.json().get('data').get('prov') + city = ip_result.json().get('data').get('city') + if prov or city: + oper_location = f'{prov}-{city}' + else: + oper_location = '未知' + else: + oper_location = '未知' + except Exception as e: + oper_location = '未知' + print(e) + finally: + # 根据不同的请求类型使用不同的方法获取请求参数 + 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 log_type == 'login': + user_agent_info = parse(user_agent) + browser = f'{user_agent_info.browser.family} {user_agent_info.browser.version[0]}' + system_os = f'{user_agent_info.os.family} {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 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') + + 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=title, + businessType=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) + ) + OperationLogService.add_operation_log_services(query_db, operation_log) + + return result + + return wrapper + + return decorator diff --git a/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py new file mode 100644 index 0000000..3069015 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/annotation/pydantic_annotation.py @@ -0,0 +1,82 @@ +import inspect +from typing import Type + +from fastapi import Query, Form +from pydantic import BaseModel +from pydantic.fields import FieldInfo + + +def as_query(cls: Type[BaseModel]): + """ + pydantic模型查询参数装饰器,将pydantic模型用于接收查询参数 + """ + new_parameters = [] + + for field_name, model_field in cls.model_fields.items(): + model_field: FieldInfo # type: ignore + + if not model_field.is_required(): + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Query(model_field.default), + annotation=model_field.annotation + ) + ) + else: + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Query(...), + annotation=model_field.annotation + ) + ) + + async def as_query_func(**data): + return cls(**data) + + sig = inspect.signature(as_query_func) + sig = sig.replace(parameters=new_parameters) + as_query_func.__signature__ = sig # type: ignore + setattr(cls, 'as_query', as_query_func) + return cls + + +def as_form(cls: Type[BaseModel]): + """ + pydantic模型表单参数装饰器,将pydantic模型用于接收表单参数 + """ + new_parameters = [] + + for field_name, model_field in cls.model_fields.items(): + model_field: FieldInfo # type: ignore + + if not model_field.is_required(): + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Form(model_field.default), + annotation=model_field.annotation + ) + ) + else: + new_parameters.append( + inspect.Parameter( + model_field.alias, + inspect.Parameter.POSITIONAL_ONLY, + default=Form(...), + annotation=model_field.annotation + ) + ) + + async def as_form_func(**data): + return cls(**data) + + sig = inspect.signature(as_form_func) + sig = sig.replace(parameters=new_parameters) + as_form_func.__signature__ = sig # type: ignore + setattr(cls, 'as_form', as_form_func) + return cls diff --git a/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py b/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py new file mode 100644 index 0000000..3e03918 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/aspect/data_scope.py @@ -0,0 +1,37 @@ +from fastapi import Depends +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'): + self.query_alias = query_alias + self.db_alias = db_alias + self.user_alias = user_alias + self.dept_alias = dept_alias + + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + user_id = current_user.user.user_id + dept_id = current_user.user.dept_id + 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_({self.db_alias}.query(SysRoleDept.dept_id).filter(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_({self.db_alias}.query(SysDept.dept_id).filter(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' + + 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 new file mode 100644 index 0000000..876670a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/aspect/interface_auth.py @@ -0,0 +1,19 @@ +from fastapi import Depends +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from utils.response_util import PermissionException + + +class CheckUserInterfaceAuth: + """ + 校验当前用户是否具有相应的接口权限 + """ + def __init__(self, perm_str: str = 'common'): + self.perm_str = perm_str + + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + user_auth_list = current_user.permissions + user_auth_list.append('common') + if '*:*:*' in user_auth_list or self.perm_str in user_auth_list: + return True + 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 new file mode 100644 index 0000000..7b7da4b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/cache_controller.py @@ -0,0 +1,94 @@ +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 module_admin.aspect.interface_auth import CheckUserInterfaceAuth + + +cacheController = APIRouter(prefix='/monitor/cache', dependencies=[Depends(LoginService.get_current_user)]) + + +@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)) + + +@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 = 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)) + + +@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)) + + +@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)) + + +@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)) + + +@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)) + + +@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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py b/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py new file mode 100644 index 0000000..8028ece --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/captcha_controller.py @@ -0,0 +1,29 @@ +import uuid +from fastapi import APIRouter, Request +from config.env import RedisInitKeyConfig +from module_admin.service.captcha_service import * +from module_admin.entity.vo.login_vo import CaptchaCode +from utils.response_util import * +from utils.log_util import * +from datetime import timedelta + + +captchaController = APIRouter() + + +@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 + session_id = str(uuid.uuid4()) + captcha_result = 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, 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 new file mode 100644 index 0000000..73aa890 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/common_controller.py @@ -0,0 +1,87 @@ +from fastapi import APIRouter, Request +from fastapi import Depends, File, Form, Query +from sqlalchemy.orm import Session +from config.env import CachePathConfig +from config.get_db import get_db +from module_admin.service.login_service import LoginService +from module_admin.service.common_service import * +from module_admin.service.config_service import ConfigService +from utils.response_util import * +from utils.log_util import * +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from typing import Optional + + +commonController = APIRouter(prefix='/common') + + +@commonController.post("/upload", dependencies=[Depends(LoginService.get_current_user), Depends(CheckUserInterfaceAuth('common'))]) +async def common_upload(request: Request, taskPath: str = Form(), uploadId: str = Form(), file: UploadFile = File(...)): + try: + try: + os.makedirs(os.path.join(CachePathConfig.PATH, taskPath, uploadId)) + except FileExistsError: + pass + CommonService.upload_service(CachePathConfig.PATH, taskPath, uploadId, file) + logger.info('上传成功') + return response_200(data={'filename': file.filename, 'path': f'/common/{CachePathConfig.PATHSTR}?taskPath={taskPath}&taskId={uploadId}&filename={file.filename}'}, message="上传成功") + except Exception as e: + logger.exception(e) + return response_500(data="", message=str(e)) + + +@commonController.post("/uploadForEditor", dependencies=[Depends(LoginService.get_current_user), Depends(CheckUserInterfaceAuth('common'))]) +async def editor_upload(request: Request, baseUrl: str = Form(), uploadId: str = Form(), taskPath: str = Form(), file: UploadFile = File(...)): + try: + try: + os.makedirs(os.path.join(CachePathConfig.PATH, taskPath, uploadId)) + except FileExistsError: + pass + CommonService.upload_service(CachePathConfig.PATH, taskPath, uploadId, file) + logger.info('上传成功') + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder( + { + 'errno': 0, + 'data': { + 'url': f'{baseUrl}/common/{CachePathConfig.PATHSTR}?taskPath={taskPath}&taskId={uploadId}&filename={file.filename}' + }, + } + ) + ) + except Exception as e: + logger.exception(e) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=jsonable_encoder( + { + 'errno': 1, + 'message': str(e), + } + ) + ) + + +@commonController.get(f"/{CachePathConfig.PATHSTR}") +async def common_download(request: Request, task_path: str = Query(alias='taskPath'), task_id: str = Query(alias='taskId'), filename: str = Query()): + try: + def generate_file(): + with open(os.path.join(CachePathConfig.PATH, task_path, task_id, filename), 'rb') as response_file: + yield from response_file + return streaming_response_200(data=generate_file()) + except Exception as e: + logger.exception(e) + return response_500(data="", message=str(e)) + + +@commonController.get("/config/query/{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 response_200(data=config_query_result, message="获取成功") + except Exception as e: + logger.exception(e) + return response_500(data="", message=str(e)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py new file mode 100644 index 0000000..65f1b6c --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py @@ -0,0 +1,136 @@ +from fastapi import APIRouter +from fastapi import Depends +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.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import log_decorator + + +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: Session = Depends(get_db)): + try: + config_query = ConfigQueryModel(**config_page_query.model_dump(by_alias=True)) + # 获取全量数据 + config_query_result = ConfigService.get_config_list_services(query_db, config_query) + # 分页操作 + config_page_query_result = get_page_obj(config_query_result, config_page_query.page_num, config_page_query.page_size) + 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + add_config.create_by = current_user.user.user_name + add_config.update_by = current_user.user.user_name + 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: Session = 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:edit'))]) +@log_decorator(title='参数管理', business_type=2) +async def refresh_system_config(request: Request, query_db: Session = 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) +async def delete_system_config(request: Request, config_ids: str, query_db: Session = 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'))]) +async def query_detail_system_config(request: Request, config_id: int, query_db: Session = Depends(get_db)): + try: + config_detail_result = 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)) + + +@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: Session = Depends(get_db)): + try: + config_query = ConfigQueryModel(**config_page_query.model_dump(by_alias=True)) + # 获取全量数据 + config_query_result = ConfigService.get_config_list_services(query_db, config_query) + config_export_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py new file mode 100644 index 0000000..a39b7ad --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py @@ -0,0 +1,102 @@ +from fastapi import APIRouter, Request +from fastapi import Depends +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.aspect.data_scope import GetDataScope +from module_admin.annotation.log_annotation import log_decorator + + +deptController = APIRouter(prefix='/system/dept', dependencies=[Depends(LoginService.get_current_user)]) + + +@deptController.get("/list/exclude/{dept_id}", response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('common'))]) +async def get_system_dept_tree_for_edit_option(request: Request, dept_id: int, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): + try: + dept_query = DeptModel(deptId=dept_id) + dept_query_result = 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: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): + try: + dept_query_result = 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + add_dept.create_by = current_user.user.user_name + add_dept.update_by = current_user.user.user_name + add_dept_result = 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: Session = 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 = 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: Session = 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 = 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: Session = Depends(get_db)): + try: + detail_dept_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py new file mode 100644 index 0000000..d69a839 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py @@ -0,0 +1,241 @@ +from fastapi import APIRouter +from fastapi import Depends +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 * +from utils.common_util import bytes2file_response +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import log_decorator + + +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: Session = Depends(get_db)): + try: + dict_type_query = DictTypeQueryModel(**dict_type_page_query.model_dump(by_alias=True)) + # 获取全量数据 + dict_type_query_result = DictTypeService.get_dict_type_list_services(query_db, dict_type_query) + # 分页操作 + dict_type_page_query_result = get_page_obj(dict_type_query_result, dict_type_page_query.page_num, dict_type_page_query.page_size) + 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: Session = 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.update_by = current_user.user.user_name + 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: Session = 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:edit'))]) +@log_decorator(title='字典管理', business_type=2) +async def refresh_system_dict(request: Request, query_db: Session = 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) +async def delete_system_dict_type(request: Request, dict_ids: str, query_db: Session = 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], dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))]) +async def query_system_dict_type_options(request: Request, query_db: Session = Depends(get_db)): + try: + dict_type_query_result = DictTypeService.get_dict_type_list_services(query_db, DictTypeQueryModel(**dict())) + logger.info(f'获取成功') + return ResponseUtil.success(data=dict_type_query_result) + except Exception as e: + logger.exception(e) + return ResponseUtil.error(msg=str(e)) + + +@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: Session = Depends(get_db)): + try: + dict_type_detail_result = 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: Session = Depends(get_db)): + try: + dict_type_query = DictTypeQueryModel(**dict_type_page_query.model_dump(by_alias=True)) + # 获取全量数据 + dict_type_query_result = DictTypeService.get_dict_type_list_services(query_db, dict_type_query) + dict_type_export_result = 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}", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]) +async def query_system_dict_type_data(request: Request, dict_type: str, query_db: Session = 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: Session = Depends(get_db)): + try: + dict_data_query = DictDataModel(**dict_data_page_query.model_dump(by_alias=True)) + # 获取全量数据 + dict_data_query_result = DictDataService.get_dict_data_list_services(query_db, dict_data_query) + # 分页操作 + dict_data_page_query_result = get_page_obj(dict_data_query_result, dict_data_page_query.page_num, dict_data_page_query.page_size) + 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: Session = 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.update_by = current_user.user.user_name + 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: Session = 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) +async def delete_system_dict_data(request: Request, dict_codes: str, query_db: Session = 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'))]) +async def query_detail_system_dict_data(request: Request, dict_code: int, query_db: Session = Depends(get_db)): + try: + detail_dict_data_result = 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: Session = Depends(get_db)): + try: + dict_data_query = DictDataModel(**dict_data_page_query.model_dump(by_alias=True)) + # 获取全量数据 + dict_data_query_result = DictDataService.get_dict_data_list_services(query_db, dict_data_query) + dict_data_export_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py new file mode 100644 index 0000000..dce5501 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py @@ -0,0 +1,207 @@ +from fastapi import APIRouter +from fastapi import Depends +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.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import log_decorator + + +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: Session = Depends(get_db)): + try: + job_query = JobModel(**job_page_query.model_dump(by_alias=True)) + # 获取全量数据 + job_query_result = JobService.get_job_list_services(query_db, job_query) + # 分页操作 + notice_page_query_result = get_page_obj(job_query_result, job_page_query.page_num, job_page_query.page_size) + 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + add_job.create_by = current_user.user.user_name + add_job.update_by = current_user.user.user_name + add_job_result = 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: Session = 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 = 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:edit'))]) +@log_decorator(title='定时任务管理', business_type=2) +async def edit_system_job(request: Request, edit_job: EditJobModel, query_db: Session = 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 = 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) +async def execute_system_job(request: Request, execute_job: JobModel, query_db: Session = Depends(get_db)): + try: + execute_job_result = 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) +async def delete_system_job(request: Request, job_ids: str, query_db: Session = Depends(get_db)): + try: + delete_job = DeleteJobModel(jobIds=job_ids) + delete_job_result = 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'))]) +async def query_detail_system_job(request: Request, job_id: int, query_db: Session = Depends(get_db)): + try: + job_detail_result = 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: Session = Depends(get_db)): + try: + job_query = JobModel(**job_page_query.model_dump(by_alias=True)) + # 获取全量数据 + job_query_result = JobService.get_job_list_services(query_db, job_query) + 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: Session = Depends(get_db)): + try: + job_log_query = JobLogQueryModel(**job_log_page_query.model_dump(by_alias=True)) + # 获取全量数据 + job_log_query_result = JobLogService.get_job_log_list_services(query_db, job_log_query) + # 分页操作 + notice_page_query_result = get_page_obj(job_log_query_result, job_log_page_query.page_num, job_log_page_query.page_size) + 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.delete("/jobLog/{job_log_ids}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@log_decorator(title='定时任务日志管理', business_type=3) +async def delete_system_job_log(request: Request, job_log_ids: str, query_db: Session = Depends(get_db)): + try: + delete_job_log = DeleteJobLogModel(jobLogIds=job_log_ids) + delete_job_log_result = 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/clean", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@log_decorator(title='定时任务日志管理', business_type=9) +async def clear_system_job_log(request: Request, query_db: Session = Depends(get_db)): + try: + clear_job_log_result = 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.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: Session = Depends(get_db)): + try: + job_log_query = JobLogQueryModel(**job_log_page_query.model_dump(by_alias=True)) + # 获取全量数据 + job_log_query_result = JobLogService.get_job_log_list_services(query_db, job_log_query) + job_log_export_result = JobLogService.export_job_log_list_services(query_db, 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/log_controller.py b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py new file mode 100644 index 0000000..bf00fd8 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/log_controller.py @@ -0,0 +1,157 @@ +from fastapi import APIRouter +from fastapi import Depends +from config.get_db import get_db +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 + + +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: Session = Depends(get_db)): + try: + operation_log_query = OperLogQueryModel(**operation_log_page_query.model_dump(by_alias=True)) + # 获取全量数据 + operation_log_query_result = OperationLogService.get_operation_log_list_services(query_db, operation_log_query) + # 分页操作 + operation_log_page_query_result = get_page_obj(operation_log_query_result, operation_log_page_query.page_num, operation_log_page_query.page_size) + 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.delete("/operlog/clean", dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) +@log_decorator(title='操作日志管理', business_type=9) +async def clear_system_operation_log(request: Request, query_db: Session = Depends(get_db)): + try: + clear_operation_log_result = 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) +async def delete_system_operation_log(request: Request, oper_ids: str, query_db: Session = Depends(get_db)): + try: + delete_operation_log = DeleteOperLogModel(operIds=oper_ids) + delete_operation_log_result = 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: Session = Depends(get_db)): + try: + operation_log_query = OperLogQueryModel(**operation_log_page_query.model_dump(by_alias=True)) + # 获取全量数据 + operation_log_query_result = OperationLogService.get_operation_log_list_services(query_db, operation_log_query) + 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 response_500(data="", message=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: Session = Depends(get_db)): + try: + login_log_query = LoginLogQueryModel(**login_log_page_query.model_dump(by_alias=True)) + # 获取全量数据 + login_log_query_result = LoginLogService.get_login_log_list_services(query_db, login_log_query) + # 分页操作 + login_log_page_query_result = get_page_obj(login_log_query_result, login_log_page_query.page_num, login_log_page_query.page_size) + 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) +async def clear_system_login_log(request: Request, query_db: Session = Depends(get_db)): + try: + clear_login_log_result = 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) +async def delete_system_login_log(request: Request, info_ids: str, query_db: Session = Depends(get_db)): + try: + delete_login_log = DeleteLoginLogModel(infoIds=info_ids) + delete_login_log_result = 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: Session = 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: Session = Depends(get_db)): + try: + login_log_query = LoginLogQueryModel(**login_log_page_query.model_dump(by_alias=True)) + # 获取全量数据 + login_log_query_result = LoginLogService.get_login_log_list_services(query_db, login_log_query) + login_log_export_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py new file mode 100644 index 0000000..4d52d15 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py @@ -0,0 +1,125 @@ +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 config.env import JwtConfig, RedisInitKeyConfig +from utils.response_util import * +from utils.log_util import * +from module_admin.annotation.log_annotation import log_decorator +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: Session = 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 + 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 + ) + 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.ACCESS_TOKEN_EXPIRE_MINUTES) + session_id = str(uuid.uuid4()) + access_token = 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 + ) + await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", access_token, + ex=timedelta(minutes=JwtConfig.REDIS_TOKEN_EXPIRE_MINUTES)) + # 此方法可实现同一账号同一时间只能登录一次 + # await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{result[0].user_id}", access_token, + # ex=timedelta(minutes=JwtConfig.REDIS_TOKEN_EXPIRE_MINUTES)) + 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} + ) + except Exception as e: + logger.exception(e) + return ResponseUtil.error(msg=str(e)) + + +@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)) + + +@loginController.get("/getRouters") +async def get_login_user_routers(request: Request, current_user: CurrentUserModel = Depends(LoginService.get_current_user), query_db: Session = 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.post("/getSmsCode", response_model=SmsCode) +async def get_sms_code(request: Request, user: ResetUserModel, query_db: Session = Depends(get_db)): + try: + sms_result = await get_sms_code_services(request, query_db, user) + if sms_result.is_success: + logger.info('获取成功') + return response_200(data=sms_result, message='获取成功') + else: + logger.warning(sms_result.message) + return response_400(data='', message=sms_result.message) + except Exception as e: + logger.exception(e) + return response_500(data="", message=str(e)) + + +@loginController.post("/forgetPwd", response_model=CrudResponseModel) +async def forget_user_pwd(request: Request, forget_user: ResetUserModel, query_db: Session = Depends(get_db)): + try: + forget_user_result = await forget_user_services(request, query_db, forget_user) + if forget_user_result.is_success: + logger.info(forget_user_result.message) + return response_200(data=forget_user_result, message=forget_user_result.message) + else: + logger.warning(forget_user_result.message) + return response_400(data="", message=forget_user_result.message) + except Exception as e: + logger.exception(e) + return response_500(data="", message=str(e)) + + +@loginController.post("/logout") +async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme)): + try: + payload = jwt.decode(token, JwtConfig.SECRET_KEY, algorithms=[JwtConfig.ALGORITHM]) + session_id: str = payload.get("session_id") + await logout_services(request, session_id) + logger.info('退出成功') + return ResponseUtil.success(msg="退出成功") + except Exception as e: + logger.exception(e) + return ResponseUtil.error(msg=str(e)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py new file mode 100644 index 0000000..79663fa --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py @@ -0,0 +1,109 @@ +from fastapi import APIRouter, Request +from fastapi import Depends +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.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import log_decorator + + +menuController = APIRouter(prefix='/system/menu', dependencies=[Depends(LoginService.get_current_user)]) + + +@menuController.get("/treeselect", dependencies=[Depends(CheckUserInterfaceAuth('common'))]) +async def get_system_menu_tree(request: Request, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + menu_query_result = 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}", dependencies=[Depends(CheckUserInterfaceAuth('common'))]) +async def get_system_role_menu_tree(request: Request, role_id: int, query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + role_menu_query_result = 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + menu_query_result = 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + add_menu.create_by = current_user.user.user_name + add_menu.update_by = current_user.user.user_name + add_menu_result = 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: Session = 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 = 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) +async def delete_system_menu(request: Request, menu_ids: str, query_db: Session = Depends(get_db)): + try: + delete_menu = DeleteMenuModel(menuIds=menu_ids) + delete_menu_result = 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'))]) +async def query_detail_system_menu(request: Request, menu_id: int, query_db: Session = Depends(get_db)): + try: + menu_detail_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py new file mode 100644 index 0000000..6c98361 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py @@ -0,0 +1,92 @@ +from fastapi import APIRouter, Request +from fastapi import Depends +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.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import log_decorator + + +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: Session = Depends(get_db)): + try: + notice_query = NoticeQueryModel(**notice_page_query.model_dump(by_alias=True)) + # 获取全量数据 + notice_query_result = NoticeService.get_notice_list_services(query_db, notice_query) + # 分页操作 + notice_page_query_result = get_page_obj(notice_query_result, notice_page_query.page_num, notice_page_query.page_size) + 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + add_notice.create_by = current_user.user.user_name + add_notice.update_by = current_user.user.user_name + add_notice_result = 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: Session = 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 = 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) +async def delete_system_notice(request: Request, notice_ids: str, query_db: Session = Depends(get_db)): + try: + delete_notice = DeleteNoticeModel(noticeIds=notice_ids) + delete_notice_result = 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'))]) +async def query_detail_system_post(request: Request, notice_id: int, query_db: Session = Depends(get_db)): + try: + notice_detail_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/online_controller.py b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py new file mode 100644 index 0000000..cecee5f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/online_controller.py @@ -0,0 +1,42 @@ +from fastapi import APIRouter +from fastapi import Depends +from config.get_db import get_db +from module_admin.service.login_service import LoginService, Session +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.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import log_decorator + + +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.delete("/{token_ids}", dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:forceLogout'))]) +@log_decorator(title='在线用户', business_type=7) +async def delete_monitor_online(request: Request, token_ids: str, query_db: Session = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/post_controler.py b/ruoyi-fastapi-backend/module_admin/controller/post_controler.py new file mode 100644 index 0000000..d07d2b1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/post_controler.py @@ -0,0 +1,109 @@ +from fastapi import APIRouter, Request +from fastapi import Depends +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.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import log_decorator + + +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: Session = Depends(get_db)): + try: + post_query = PostModel(**post_page_query.model_dump(by_alias=True)) + # 获取全量数据 + post_query_result = PostService.get_post_list_services(query_db, post_query) + # 分页操作 + post_page_query_result = get_page_obj(post_query_result, post_page_query.page_num, post_page_query.page_size) + 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + add_post.create_by = current_user.user.user_name + add_post.update_by = current_user.user.user_name + add_post_result = 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: Session = 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 = 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) +async def delete_system_post(request: Request, post_ids: str, query_db: Session = Depends(get_db)): + try: + delete_post = DeletePostModel(postIds=post_ids) + delete_post_result = 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'))]) +async def query_detail_system_post(request: Request, post_id: int, query_db: Session = Depends(get_db)): + try: + post_detail_result = 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: Session = Depends(get_db)): + try: + post_query = PostModel(**post_page_query.model_dump(by_alias=True)) + # 获取全量数据 + post_query_result = PostService.get_post_list_services(query_db, post_query) + post_export_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py new file mode 100644 index 0000000..4007ec5 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py @@ -0,0 +1,240 @@ +from fastapi import APIRouter, Request +from fastapi import Depends +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 * +from utils.common_util import bytes2file_response +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 + + +roleController = APIRouter(prefix='/system/role', dependencies=[Depends(LoginService.get_current_user)]) + + +@roleController.get("/deptTree/{role_id}", dependencies=[Depends(CheckUserInterfaceAuth('common'))]) +async def get_system_role_dept_tree(request: Request, role_id: int, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): + try: + dept_query_result = DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql) + role_dept_query_result = 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: Session = Depends(get_db)): + try: + role_query = RoleQueryModel(**role_page_query.model_dump(by_alias=True)) + role_query_result = RoleService.get_role_list_services(query_db, role_query) + # 分页操作 + role_page_query_result = get_page_obj(role_query_result, role_page_query.page_num, role_page_query.page_size) + 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + add_role.create_by = current_user.user.user_name + add_role.update_by = current_user.user.user_name + add_role_result = 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: Session = 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 = 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: Session = 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 = 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: Session = 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 = 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: Session = Depends(get_db)): + try: + role_detail_result = 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: Session = Depends(get_db)): + try: + role_query = RoleQueryModel(**role_page_query.model_dump(by_alias=True)) + # 获取全量数据 + role_query_result = RoleService.get_role_list_services(query_db, role_query) + role_export_result = 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: Session = 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 = 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('common'))]) +async def get_system_allocated_user_list(request: Request, user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), query_db: Session = Depends(get_db)): + try: + role_user_query = UserRoleQueryModel(**user_role.model_dump(by_alias=True)) + role_user_allocated_query_result = RoleService.get_role_user_allocated_list_services(query_db, role_user_query) + # 分页操作 + role_user_allocated_page_query_result = get_page_obj(role_user_allocated_query_result, user_role.page_num, user_role.page_size) + 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('common'))]) +async def get_system_unallocated_user_list(request: Request, user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query), query_db: Session = Depends(get_db)): + try: + role_user_query = UserRoleQueryModel(**user_role.model_dump(by_alias=True)) + role_user_unallocated_query_result = RoleService.get_role_user_unallocated_list_services(query_db, role_user_query) + # 分页操作 + role_user_unallocated_page_query_result = get_page_obj(role_user_unallocated_query_result, user_role.page_num, user_role.page_size) + 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: Session = Depends(get_db)): + try: + add_role_user_result = 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: Session = Depends(get_db)): + try: + cancel_user_role_result = 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: Session = Depends(get_db)): + try: + batch_cancel_user_role_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/server_controller.py b/ruoyi-fastapi-backend/module_admin/controller/server_controller.py new file mode 100644 index 0000000..b089912 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/server_controller.py @@ -0,0 +1,22 @@ +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 module_admin.aspect.interface_auth import CheckUserInterfaceAuth + + +serverController = APIRouter(prefix='/monitor/server', dependencies=[Depends(LoginService.get_current_user)]) + + +@serverController.get("", response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))]) +async def get_monitor_server_info(request: Request): + try: + # 获取全量数据 + server_info_query_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py new file mode 100644 index 0000000..c7760d6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -0,0 +1,307 @@ +from fastapi import APIRouter, Request +from fastapi import Depends, File, Query +from config.get_db import get_db +from module_admin.service.login_service import LoginService +from module_admin.service.user_service import * +from module_admin.service.dept_service import DeptService +from utils.page_util import * +from utils.response_util import * +from utils.log_util import * +from utils.common_util import bytes2file_response +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: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): + try: + dept_query_result = 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: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysUser'))): + try: + user_query = UserQueryModel(**user_page_query.model_dump(by_alias=True)) + # 获取全量数据 + user_query_result = UserService.get_user_list_services(query_db, user_query, data_scope_sql) + # 分页操作 + user_page_query_result = get_page_obj(user_query_result, user_page_query.page_num, user_page_query.page_size) + 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: Session = 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.update_by = current_user.user.user_name + add_user_result = 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: Session = 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 = 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: Session = 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() + ) + delete_user_result = 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: Session = 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 = 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: Session = 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 = 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + profile_user_result = 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + detail_user_result = 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + dir_path = os.path.join(CachePathConfig.PATH, 'profile', 'avatar') + try: + os.makedirs(dir_path) + except FileExistsError: + pass + avatar_name = f'blob_{datetime.now().strftime("%Y%m%d%H%M%S")}.jpeg' + avatar_path = os.path.join(dir_path, avatar_name) + with open(avatar_path, 'wb') as f: + f.write(avatarfile) + edit_user = EditUserModel( + userId=current_user.user.user_id, + avatar=f'/common/{CachePathConfig.PATHSTR}?taskPath=profile&taskId=avatar&filename={avatar_name}', + updateBy=current_user.user.user_name, + updateTime=datetime.now(), + type='avatar' + ) + edit_user_result = 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: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + edit_user = EditUserModel(**user_info.model_dump(by_alias=True, exclude={'role_ids', 'post_ids'}), roleIds=user_info.role_ids.split(','), postIds=user_info.post_ids.split(',')) + edit_user.user_id = current_user.user.user_id + edit_user.update_by = current_user.user.user_name + edit_user.update_time = datetime.now() + print(edit_user.model_dump()) + edit_user_result = 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, old_password: str = Query(alias='oldPassword'), new_password: str = Query(alias='newPassword'), query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + try: + reset_user = ResetUserModel( + userId=current_user.user.user_id, + oldPassword=old_password, + password=PwdUtil.get_password_hash(new_password), + updateBy=current_user.user.user_name, + updateTime=datetime.now() + ) + reset_user_result = 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: Session = 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'))]) +async def export_system_user_template(request: Request, query_db: Session = Depends(get_db)): + try: + user_import_template_result = 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: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysUser'))): + try: + user_query = UserQueryModel(**user_page_query.model_dump(by_alias=True)) + # 获取全量数据 + user_query_result = UserService.get_user_list_services(query_db, user_query, data_scope_sql) + user_export_result = 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'))]) +async def get_system_allocated_role_list(request: Request, user_id: int, query_db: Session = Depends(get_db)): + try: + user_role_query = UserRoleQueryModel(userId=user_id) + user_role_allocated_query_result = 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: Session = Depends(get_db)): + try: + add_user_role_result = 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)) diff --git a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py new file mode 100644 index 0000000..3efea39 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py @@ -0,0 +1,98 @@ +from sqlalchemy.orm import Session +from module_admin.entity.do.config_do import SysConfig +from module_admin.entity.vo.config_vo import * +from datetime import datetime, time + + +class ConfigDao: + """ + 参数配置管理模块数据库操作层 + """ + + @classmethod + def get_config_detail_by_id(cls, db: Session, config_id: int): + """ + 根据参数配置id获取参数配置详细信息 + :param db: orm对象 + :param config_id: 参数配置id + :return: 参数配置信息对象 + """ + config_info = db.query(SysConfig) \ + .filter(SysConfig.config_id == config_id) \ + .first() + + return config_info + + @classmethod + def get_config_detail_by_info(cls, db: Session, config: ConfigModel): + """ + 根据参数配置参数获取参数配置信息 + :param db: orm对象 + :param config: 参数配置参数对象 + :return: 参数配置信息对象 + """ + config_info = db.query(SysConfig) \ + .filter(SysConfig.config_key == config.config_key if config.config_key else True, + SysConfig.config_value == config.config_value if config.config_value else True) \ + .first() + + return config_info + + @classmethod + def get_config_list(cls, db: Session, query_object: ConfigQueryModel): + """ + 根据查询参数获取参数配置列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 参数配置列表信息对象 + """ + config_list = db.query(SysConfig) \ + .filter(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().all() + + return config_list + + @classmethod + def add_config_dao(cls, db: Session, config: ConfigModel): + """ + 新增参数配置数据库操作 + :param db: orm对象 + :param config: 参数配置对象 + :return: + """ + db_config = SysConfig(**config.model_dump()) + db.add(db_config) + db.flush() + + return db_config + + @classmethod + def edit_config_dao(cls, db: Session, config: dict): + """ + 编辑参数配置数据库操作 + :param db: orm对象 + :param config: 需要更新的参数配置字典 + :return: + """ + db.query(SysConfig) \ + .filter(SysConfig.config_id == config.get('config_id')) \ + .update(config) + + @classmethod + def delete_config_dao(cls, db: Session, config: ConfigModel): + """ + 删除参数配置数据库操作 + :param db: orm对象 + :param config: 参数配置对象 + :return: + """ + db.query(SysConfig) \ + .filter(SysConfig.config_id == config.config_id) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py new file mode 100644 index 0000000..ffe3b67 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py @@ -0,0 +1,194 @@ +from sqlalchemy.orm import Session +from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.vo.dept_vo import * +from utils.time_format_util import list_format_datetime + + +class DeptDao: + """ + 部门管理模块数据库操作层 + """ + + @classmethod + def get_dept_by_id(cls, db: Session, dept_id: int): + """ + 根据部门id获取在用部门信息 + :param db: orm对象 + :param dept_id: 部门id + :return: 在用部门信息对象 + """ + dept_info = db.query(SysDept) \ + .filter(SysDept.dept_id == dept_id, + SysDept.status == 0, + SysDept.del_flag == 0) \ + .first() + + return dept_info + + @classmethod + def get_dept_by_id_for_list(cls, db: Session, dept_id: int): + """ + 用于获取部门列表的工具方法 + :param db: orm对象 + :param dept_id: 部门id + :return: 部门id对应的信息对象 + """ + dept_info = db.query(SysDept) \ + .filter(SysDept.dept_id == dept_id, + SysDept.del_flag == 0) \ + .first() + + return dept_info + + @classmethod + def get_dept_detail_by_id(cls, db: Session, dept_id: int): + """ + 根据部门id获取部门详细信息 + :param db: orm对象 + :param dept_id: 部门id + :return: 部门信息对象 + """ + dept_info = db.query(SysDept) \ + .filter(SysDept.dept_id == dept_id, + SysDept.del_flag == 0) \ + .first() + + return dept_info + + @classmethod + def get_dept_detail_by_info(cls, db: Session, dept: DeptModel): + """ + 根据部门参数获取部门信息 + :param db: orm对象 + :param dept: 部门参数对象 + :return: 部门信息对象 + """ + dept_info = db.query(SysDept) \ + .filter(SysDept.parent_id == dept.parent_id if dept.parent_id else True, + SysDept.dept_name == dept.dept_name if dept.dept_name else True) \ + .first() + + return dept_info + + @classmethod + def get_dept_info_for_edit_option(cls, db: Session, dept_info: DeptModel, data_scope_sql: str): + """ + 获取部门编辑对应的在用部门列表信息 + :param db: orm对象 + :param dept_info: 部门对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门列表信息 + """ + dept_result = db.query(SysDept) \ + .filter(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().all() + + return list_format_datetime(dept_result) + + @classmethod + def get_children_dept(cls, db: Session, dept_id: int): + """ + 根据部门id查询当前部门的子部门列表信息 + :param db: orm对象 + :param dept_id: 部门id + :return: 子部门信息列表 + """ + dept_result = db.query(SysDept) \ + .filter(SysDept.parent_id == dept_id, + SysDept.del_flag == 0) \ + .all() + + return list_format_datetime(dept_result) + + @classmethod + def get_dept_all_ancestors(cls, db: Session): + """ + 获取所有部门的ancestors信息 + :param db: orm对象 + :return: ancestors信息列表 + """ + ancestors = db.query(SysDept.ancestors)\ + .filter(SysDept.del_flag == 0)\ + .all() + + return ancestors + + @classmethod + def get_dept_list_for_tree(cls, db: Session, dept_info: DeptModel, data_scope_sql: str): + """ + 获取所有在用部门列表信息 + :param db: orm对象 + :param dept_info: 部门对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 在用部门列表信息 + """ + dept_result = db.query(SysDept) \ + .filter(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().all() + + return dept_result + + @classmethod + def get_dept_list(cls, db: Session, page_object: DeptModel, data_scope_sql: str): + """ + 根据查询参数获取部门列表信息 + :param db: orm对象 + :param page_object: 不分页查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门列表信息对象 + """ + dept_result = db.query(SysDept) \ + .filter(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().all() + + return dept_result + + @classmethod + def add_dept_dao(cls, db: Session, dept: DeptModel): + """ + 新增部门数据库操作 + :param db: orm对象 + :param dept: 部门对象 + :return: 新增校验结果 + """ + db_dept = SysDept(**dept.model_dump()) + db.add(db_dept) + db.flush() + + return db_dept + + @classmethod + def edit_dept_dao(cls, db: Session, dept: dict): + """ + 编辑部门数据库操作 + :param db: orm对象 + :param dept: 需要更新的部门字典 + :return: 编辑校验结果 + """ + db.query(SysDept) \ + .filter(SysDept.dept_id == dept.get('dept_id')) \ + .update(dept) + + @classmethod + def delete_dept_dao(cls, db: Session, dept: DeptModel): + """ + 删除部门数据库操作 + :param db: orm对象 + :param dept: 部门对象 + :return: + """ + db.query(SysDept) \ + .filter(SysDept.dept_id == dept.dept_id) \ + .update({SysDept.del_flag: '2', SysDept.update_by: dept.update_by, SysDept.update_time: dept.update_time}) diff --git a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py new file mode 100644 index 0000000..d938c14 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py @@ -0,0 +1,219 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import Session +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 datetime import datetime, time + + +class DictTypeDao: + """ + 字典类型管理模块数据库操作层 + """ + + @classmethod + def get_dict_type_detail_by_id(cls, db: Session, dict_id: int): + """ + 根据字典类型id获取字典类型详细信息 + :param db: orm对象 + :param dict_id: 字典类型id + :return: 字典类型信息对象 + """ + dict_type_info = db.query(SysDictType) \ + .filter(SysDictType.dict_id == dict_id) \ + .first() + + return dict_type_info + + @classmethod + def get_dict_type_detail_by_info(cls, db: Session, dict_type: DictTypeModel): + """ + 根据字典类型参数获取字典类型信息 + :param db: orm对象 + :param dict_type: 字典类型参数对象 + :return: 字典类型信息对象 + """ + dict_type_info = db.query(SysDictType) \ + .filter(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) \ + .first() + + return dict_type_info + + @classmethod + def get_all_dict_type(cls, db: Session): + """ + 获取所有的字典类型信息 + :param db: orm对象 + :return: 字典类型信息列表对象 + """ + dict_type_info = db.query(SysDictType).all() + + return list_format_datetime(dict_type_info) + + @classmethod + def get_dict_type_list(cls, db: Session, query_object: DictTypeQueryModel): + """ + 根据查询参数获取字典类型列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 字典类型列表信息对象 + """ + dict_type_list = db.query(SysDictType) \ + .filter(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().all() + + return dict_type_list + + @classmethod + def add_dict_type_dao(cls, db: Session, dict_type: DictTypeModel): + """ + 新增字典类型数据库操作 + :param db: orm对象 + :param dict_type: 字典类型对象 + :return: + """ + db_dict_type = SysDictType(**dict_type.dict()) + db.add(db_dict_type) + db.flush() + + return db_dict_type + + @classmethod + def edit_dict_type_dao(cls, db: Session, dict_type: dict): + """ + 编辑字典类型数据库操作 + :param db: orm对象 + :param dict_type: 需要更新的字典类型字典 + :return: + """ + db.query(SysDictType) \ + .filter(SysDictType.dict_id == dict_type.get('dict_id')) \ + .update(dict_type) + + @classmethod + def delete_dict_type_dao(cls, db: Session, dict_type: DictTypeModel): + """ + 删除字典类型数据库操作 + :param db: orm对象 + :param dict_type: 字典类型对象 + :return: + """ + db.query(SysDictType) \ + .filter(SysDictType.dict_id == dict_type.dict_id) \ + .delete() + + +class DictDataDao: + """ + 字典数据管理模块数据库操作层 + """ + + @classmethod + def get_dict_data_detail_by_id(cls, db: Session, dict_code: int): + """ + 根据字典数据id获取字典数据详细信息 + :param db: orm对象 + :param dict_code: 字典数据id + :return: 字典数据信息对象 + """ + dict_data_info = db.query(SysDictData) \ + .filter(SysDictData.dict_code == dict_code) \ + .first() + + return dict_data_info + + @classmethod + def get_dict_data_detail_by_info(cls, db: Session, dict_data: DictDataModel): + """ + 根据字典数据参数获取字典数据信息 + :param db: orm对象 + :param dict_data: 字典数据参数对象 + :return: 字典数据信息对象 + """ + dict_data_info = db.query(SysDictData) \ + .filter(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) \ + .first() + + return dict_data_info + + @classmethod + def get_dict_data_list(cls, db: Session, query_object: DictDataModel): + """ + 根据查询参数获取字典数据列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 字典数据列表信息对象 + """ + dict_data_list = db.query(SysDictData) \ + .filter(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().all() + + return dict_data_list + + @classmethod + def query_dict_data_list(cls, db: Session, dict_type: str): + """ + 根据查询参数获取字典数据列表信息 + :param db: orm对象 + :param dict_type: 字典类型 + :return: 字典数据列表信息对象 + """ + dict_data_list = db.query(SysDictData).select_from(SysDictType) \ + .filter(SysDictType.dict_type == dict_type if dict_type else True, SysDictType.status == 0) \ + .outerjoin(SysDictData, and_(SysDictType.dict_type == SysDictData.dict_type, SysDictData.status == 0)) \ + .order_by(SysDictData.dict_sort) \ + .distinct().all() + + return dict_data_list + + @classmethod + def add_dict_data_dao(cls, db: Session, dict_data: DictDataModel): + """ + 新增字典数据数据库操作 + :param db: orm对象 + :param dict_data: 字典数据对象 + :return: + """ + db_data_type = SysDictData(**dict_data.dict()) + db.add(db_data_type) + db.flush() + + return db_data_type + + @classmethod + def edit_dict_data_dao(cls, db: Session, dict_data: dict): + """ + 编辑字典数据数据库操作 + :param db: orm对象 + :param dict_data: 需要更新的字典数据字典 + :return: + """ + db.query(SysDictData) \ + .filter(SysDictData.dict_code == dict_data.get('dict_code')) \ + .update(dict_data) + + @classmethod + def delete_dict_data_dao(cls, db: Session, dict_data: DictDataModel): + """ + 删除字典数据数据库操作 + :param db: orm对象 + :param dict_data: 字典数据对象 + :return: + """ + db.query(SysDictData) \ + .filter(SysDictData.dict_code == dict_data.dict_code) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py new file mode 100644 index 0000000..2456270 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py @@ -0,0 +1,108 @@ +from sqlalchemy.orm import Session +from module_admin.entity.do.job_do import SysJob +from module_admin.entity.vo.job_vo import * + + +class JobDao: + """ + 定时任务管理模块数据库操作层 + """ + + @classmethod + def get_job_detail_by_id(cls, db: Session, job_id: int): + """ + 根据定时任务id获取定时任务详细信息 + :param db: orm对象 + :param job_id: 定时任务id + :return: 定时任务信息对象 + """ + job_info = db.query(SysJob) \ + .filter(SysJob.job_id == job_id) \ + .first() + + return job_info + + @classmethod + def get_job_detail_by_info(cls, db: Session, job: JobModel): + """ + 根据定时任务参数获取定时任务信息 + :param db: orm对象 + :param job: 定时任务参数对象 + :return: 定时任务信息对象 + """ + job_info = db.query(SysJob) \ + .filter(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) \ + .first() + + return job_info + + @classmethod + def get_job_list(cls, db: Session, query_object: JobModel): + """ + 根据查询参数获取定时任务列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 定时任务列表信息对象 + """ + job_list = db.query(SysJob) \ + .filter(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().all() + + return job_list + + @classmethod + def get_job_list_for_scheduler(cls, db: Session): + """ + 获取定时任务列表信息 + :param db: orm对象 + :return: 定时任务列表信息对象 + """ + job_list = db.query(SysJob) \ + .filter(SysJob.status == 0) \ + .distinct().all() + + return job_list + + @classmethod + def add_job_dao(cls, db: Session, job: JobModel): + """ + 新增定时任务数据库操作 + :param db: orm对象 + :param job: 定时任务对象 + :return: + """ + db_job = SysJob(**job.model_dump()) + db.add(db_job) + db.flush() + + return db_job + + @classmethod + def edit_job_dao(cls, db: Session, job: dict): + """ + 编辑定时任务数据库操作 + :param db: orm对象 + :param job: 需要更新的定时任务字典 + :return: + """ + db.query(SysJob) \ + .filter(SysJob.job_id == job.get('job_id')) \ + .update(job) + + @classmethod + def delete_job_dao(cls, db: Session, job: JobModel): + """ + 删除定时任务数据库操作 + :param db: orm对象 + :param job: 定时任务对象 + :return: + """ + db.query(SysJob) \ + .filter(SysJob.job_id == job.job_id) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py new file mode 100644 index 0000000..6c090c7 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/job_log_dao.py @@ -0,0 +1,67 @@ +from sqlalchemy.orm import Session +from module_admin.entity.do.job_do import SysJobLog +from module_admin.entity.vo.job_vo import * +from datetime import datetime, time + + +class JobLogDao: + """ + 定时任务日志管理模块数据库操作层 + """ + + @classmethod + def get_job_log_list(cls, db: Session, query_object: JobLogQueryModel): + """ + 根据查询参数获取定时任务日志列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 定时任务日志列表信息对象 + """ + job_log_list = db.query(SysJobLog) \ + .filter(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().all() + + return job_log_list + + @classmethod + def add_job_log_dao(cls, db: Session, job_log: JobLogModel): + """ + 新增定时任务日志数据库操作 + :param db: orm对象 + :param job_log: 定时任务日志对象 + :return: + """ + db_job_log = SysJobLog(**job_log.model_dump()) + db.add(db_job_log) + db.flush() + + return db_job_log + + @classmethod + def delete_job_log_dao(cls, db: Session, job_log: JobLogModel): + """ + 删除定时任务日志数据库操作 + :param db: orm对象 + :param job_log: 定时任务日志对象 + :return: + """ + db.query(SysJobLog) \ + .filter(SysJobLog.job_log_id == job_log.job_log_id) \ + .delete() + + @classmethod + def clear_job_log_dao(cls, db: Session): + """ + 清除定时任务日志数据库操作 + :param db: orm对象 + :return: + """ + db.query(SysJobLog) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/log_dao.py b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py new file mode 100644 index 0000000..b6fdaac --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/log_dao.py @@ -0,0 +1,131 @@ +from sqlalchemy.orm import Session +from module_admin.entity.do.log_do import SysOperLog, SysLogininfor +from module_admin.entity.vo.log_vo import * +from utils.time_format_util import object_format_datetime, list_format_datetime +from datetime import datetime, time + + +class OperationLogDao: + """ + 操作日志管理模块数据库操作层 + """ + @classmethod + def get_operation_log_list(cls, db: Session, query_object: OperLogQueryModel): + """ + 根据查询参数获取操作日志列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 操作日志列表信息对象 + """ + operation_log_list = db.query(SysOperLog) \ + .filter(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().all() + + return operation_log_list + + @classmethod + def add_operation_log_dao(cls, db: Session, operation_log: OperLogModel): + """ + 新增操作日志数据库操作 + :param db: orm对象 + :param operation_log: 操作日志对象 + :return: 新增校验结果 + """ + db_operation_log = SysOperLog(**operation_log.model_dump()) + db.add(db_operation_log) + db.flush() + + return db_operation_log + + @classmethod + def delete_operation_log_dao(cls, db: Session, operation_log: OperLogModel): + """ + 删除操作日志数据库操作 + :param db: orm对象 + :param operation_log: 操作日志对象 + :return: + """ + db.query(SysOperLog) \ + .filter(SysOperLog.oper_id == operation_log.oper_id) \ + .delete() + + @classmethod + def clear_operation_log_dao(cls, db: Session): + """ + 清除操作日志数据库操作 + :param db: orm对象 + :return: + """ + db.query(SysOperLog) \ + .delete() + + +class LoginLogDao: + """ + 登录日志管理模块数据库操作层 + """ + + @classmethod + def get_login_log_list(cls, db: Session, query_object: LoginLogQueryModel): + """ + 根据查询参数获取登录日志列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 登录日志列表信息对象 + """ + login_log_list = db.query(SysLogininfor) \ + .filter(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().all() + + return login_log_list + + @classmethod + def add_login_log_dao(cls, db: Session, login_log: LogininforModel): + """ + 新增登录日志数据库操作 + :param db: orm对象 + :param login_log: 登录日志对象 + :return: 新增校验结果 + """ + db_login_log = SysLogininfor(**login_log.model_dump()) + db.add(db_login_log) + db.flush() + + return db_login_log + + @classmethod + def delete_login_log_dao(cls, db: Session, login_log: LogininforModel): + """ + 删除登录日志数据库操作 + :param db: orm对象 + :param login_log: 登录日志对象 + :return: + """ + db.query(SysLogininfor) \ + .filter(SysLogininfor.info_id == login_log.info_id) \ + .delete() + + @classmethod + def clear_login_log_dao(cls, db: Session): + """ + 清除登录日志数据库操作 + :param db: orm对象 + :return: + """ + db.query(SysLogininfor) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/login_dao.py b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py new file mode 100644 index 0000000..b932bc1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/login_dao.py @@ -0,0 +1,20 @@ +from sqlalchemy.orm import Session +from sqlalchemy import and_ +from module_admin.entity.do.user_do import SysUser +from module_admin.entity.do.dept_do import SysDept + + +def login_by_account(db: Session, user_name: str): + """ + 根据用户名查询用户信息 + :param db: orm对象 + :param user_name: 用户名 + :return: 用户对象 + """ + user = db.query(SysUser, SysDept)\ + .filter(SysUser.user_name == user_name, SysUser.del_flag == '0') \ + .outerjoin(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) \ + .distinct() \ + .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 new file mode 100644 index 0000000..272529b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py @@ -0,0 +1,142 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import Session +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 * + + +class MenuDao: + """ + 菜单管理模块数据库操作层 + """ + + @classmethod + def get_menu_detail_by_id(cls, db: Session, menu_id: int): + """ + 根据菜单id获取菜单详细信息 + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单信息对象 + """ + menu_info = db.query(SysMenu) \ + .filter(SysMenu.menu_id == menu_id) \ + .first() + + return menu_info + + @classmethod + def get_menu_detail_by_info(cls, db: Session, menu: MenuModel): + """ + 根据菜单参数获取菜单信息 + :param db: orm对象 + :param menu: 菜单参数对象 + :return: 菜单信息对象 + """ + menu_info = db.query(SysMenu) \ + .filter(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) \ + .first() + + return menu_info + + @classmethod + def get_menu_list_for_tree(cls, db: Session, user_id: int, role: list): + """ + 根据角色信息获取所有在用菜单列表信息 + :param db: orm对象 + :param user_id: 用户id + :param role: 用户角色列表信息 + :return: 菜单列表信息 + """ + role_id_list = [item.role_id for item in role] + if 1 in role_id_list: + menu_query_all = db.query(SysMenu) \ + .filter(SysMenu.status == 0) \ + .order_by(SysMenu.order_num) \ + .distinct().all() + else: + menu_query_all = db.query(SysMenu).select_from(SysUser) \ + .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ + .outerjoin(SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ + .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0,)) \ + .order_by(SysMenu.order_num) \ + .distinct().all() + + return menu_query_all + + @classmethod + def get_menu_list(cls, db: Session, page_object: MenuQueryModel, user_id: int, role: list): + """ + 根据查询参数获取菜单列表信息 + :param db: orm对象 + :param page_object: 不分页查询参数对象 + :param user_id: 用户id + :param role: 用户角色列表 + :return: 菜单列表信息对象 + """ + role_id_list = [item.role_id for item in role] + if 1 in role_id_list: + menu_query_all = db.query(SysMenu) \ + .filter(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().all() + else: + menu_query_all = db.query(SysMenu).select_from(SysUser) \ + .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ + .outerjoin(SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ + .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ + .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().all() + + return menu_query_all + + @classmethod + def add_menu_dao(cls, db: Session, menu: MenuModel): + """ + 新增菜单数据库操作 + :param db: orm对象 + :param menu: 菜单对象 + :return: + """ + db_menu = SysMenu(**menu.model_dump()) + db.add(db_menu) + db.flush() + + return db_menu + + @classmethod + def edit_menu_dao(cls, db: Session, menu: dict): + """ + 编辑菜单数据库操作 + :param db: orm对象 + :param menu: 需要更新的菜单字典 + :return: + """ + db.query(SysMenu) \ + .filter(SysMenu.menu_id == menu.get('menu_id')) \ + .update(menu) + + @classmethod + def delete_menu_dao(cls, db: Session, menu: MenuModel): + """ + 删除菜单数据库操作 + :param db: orm对象 + :param menu: 菜单对象 + :return: + """ + db.query(SysMenu) \ + .filter(SysMenu.menu_id == menu.menu_id) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py new file mode 100644 index 0000000..ff4b347 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py @@ -0,0 +1,99 @@ +from sqlalchemy.orm import Session +from module_admin.entity.do.notice_do import SysNotice +from module_admin.entity.vo.notice_vo import * +from datetime import datetime, time + + +class NoticeDao: + """ + 通知公告管理模块数据库操作层 + """ + + @classmethod + def get_notice_detail_by_id(cls, db: Session, notice_id: int): + """ + 根据通知公告id获取通知公告详细信息 + :param db: orm对象 + :param notice_id: 通知公告id + :return: 通知公告信息对象 + """ + notice_info = db.query(SysNotice) \ + .filter(SysNotice.notice_id == notice_id) \ + .first() + + return notice_info + + @classmethod + def get_notice_detail_by_info(cls, db: Session, notice: NoticeModel): + """ + 根据通知公告参数获取通知公告信息 + :param db: orm对象 + :param notice: 通知公告参数对象 + :return: 通知公告信息对象 + """ + notice_info = db.query(SysNotice) \ + .filter(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) \ + .first() + + return notice_info + + @classmethod + def get_notice_list(cls, db: Session, query_object: NoticeQueryModel): + """ + 根据查询参数获取通知公告列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 通知公告列表信息对象 + """ + notice_list = db.query(SysNotice) \ + .filter(SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True, + SysNotice.update_by.like(f'%{query_object.update_by}%') if query_object.update_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().all() + + return notice_list + + @classmethod + def add_notice_dao(cls, db: Session, notice: NoticeModel): + """ + 新增通知公告数据库操作 + :param db: orm对象 + :param notice: 通知公告对象 + :return: + """ + db_notice = SysNotice(**notice.model_dump()) + db.add(db_notice) + db.flush() + + return db_notice + + @classmethod + def edit_notice_dao(cls, db: Session, notice: dict): + """ + 编辑通知公告数据库操作 + :param db: orm对象 + :param notice: 需要更新的通知公告字典 + :return: + """ + db.query(SysNotice) \ + .filter(SysNotice.notice_id == notice.get('notice_id')) \ + .update(notice) + + @classmethod + def delete_notice_dao(cls, db: Session, notice: NoticeModel): + """ + 删除通知公告数据库操作 + :param db: orm对象 + :param notice: 通知公告对象 + :return: + """ + db.query(SysNotice) \ + .filter(SysNotice.notice_id == notice.notice_id) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py new file mode 100644 index 0000000..1c32dc3 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py @@ -0,0 +1,110 @@ +from sqlalchemy.orm import Session +from module_admin.entity.do.post_do import SysPost +from module_admin.entity.vo.post_vo import * + + +class PostDao: + """ + 岗位管理模块数据库操作层 + """ + + @classmethod + def get_post_by_id(cls, db: Session, post_id: int): + """ + 根据岗位id获取在用岗位详细信息 + :param db: orm对象 + :param post_id: 岗位id + :return: 在用岗位信息对象 + """ + post_info = db.query(SysPost) \ + .filter(SysPost.post_id == post_id, + SysPost.status == 0) \ + .first() + + return post_info + + @classmethod + def get_post_detail_by_id(cls, db: Session, post_id: int): + """ + 根据岗位id获取岗位详细信息 + :param db: orm对象 + :param post_id: 岗位id + :return: 岗位信息对象 + """ + post_info = db.query(SysPost) \ + .filter(SysPost.post_id == post_id) \ + .first() + + return post_info + + @classmethod + def get_post_detail_by_info(cls, db: Session, post: PostModel): + """ + 根据岗位参数获取岗位信息 + :param db: orm对象 + :param post: 岗位参数对象 + :return: 岗位信息对象 + """ + post_info = db.query(SysPost) \ + .filter(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) \ + .first() + + return post_info + + @classmethod + def get_post_list(cls, db: Session, query_object: PostModel): + """ + 根据查询参数获取岗位列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 岗位列表信息对象 + """ + post_list = db.query(SysPost) \ + .filter(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().all() + + return post_list + + @classmethod + def add_post_dao(cls, db: Session, post: PostModel): + """ + 新增岗位数据库操作 + :param db: orm对象 + :param post: 岗位对象 + :return: + """ + db_post = SysPost(**post.model_dump()) + db.add(db_post) + db.flush() + + return db_post + + @classmethod + def edit_post_dao(cls, db: Session, post: dict): + """ + 编辑岗位数据库操作 + :param db: orm对象 + :param post: 需要更新的岗位字典 + :return: + """ + db.query(SysPost) \ + .filter(SysPost.post_id == post.get('post_id')) \ + .update(post) + + @classmethod + def delete_post_dao(cls, db: Session, post: PostModel): + """ + 删除岗位数据库操作 + :param db: orm对象 + :param post: 岗位对象 + :return: + """ + db.query(SysPost) \ + .filter(SysPost.post_id == post.post_id) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py new file mode 100644 index 0000000..88bdac7 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py @@ -0,0 +1,222 @@ +from sqlalchemy import desc, func +from sqlalchemy.orm import Session +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 datetime import datetime, time + + +class RoleDao: + """ + 角色管理模块数据库操作层 + """ + + @classmethod + def get_role_by_name(cls, db: Session, role_name: str): + """ + 根据角色名获取在用角色信息 + :param db: orm对象 + :param role_name: 角色名 + :return: 当前角色名的角色信息对象 + """ + query_role_info = db.query(SysRole) \ + .filter(SysRole.status == 0, SysRole.del_flag == 0, SysRole.role_name == role_name) \ + .order_by(desc(SysRole.create_time)).distinct().first() + + return query_role_info + + @classmethod + def get_role_by_info(cls, db: Session, role: RoleModel): + """ + 根据角色参数获取角色信息 + :param db: orm对象 + :param role: 角色参数 + :return: 当前角色参数的角色信息对象 + """ + query_role_info = db.query(SysRole) \ + .filter(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().first() + + return query_role_info + + @classmethod + def get_role_by_id(cls, db: Session, role_id: int): + """ + 根据角色id获取在用角色信息 + :param db: orm对象 + :param role_id: 角色id + :return: 当前角色id的角色信息对象 + """ + role_info = db.query(SysRole) \ + .filter(SysRole.role_id == role_id, + SysRole.status == 0, + SysRole.del_flag == 0) \ + .first() + + return role_info + + @classmethod + def get_role_detail_by_id(cls, db: Session, role_id: int): + """ + 根据role_id获取角色详细信息 + :param db: orm对象 + :param role_id: 角色id + :return: 当前role_id的角色信息对象 + """ + query_role_info = db.query(SysRole) \ + .filter(SysRole.del_flag == 0, SysRole.role_id == role_id) \ + .distinct().first() + + return query_role_info + + @classmethod + def get_role_select_option_dao(cls, db: Session): + """ + 获取编辑页面对应的在用角色列表信息 + :param db: orm对象 + :return: 角色列表信息 + """ + role_info = db.query(SysRole) \ + .filter(SysRole.role_id != 1, SysRole.status == 0, SysRole.del_flag == 0) \ + .all() + + return role_info + + @classmethod + def get_role_list(cls, db: Session, query_object: RoleQueryModel): + """ + 根据查询参数获取角色列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :return: 角色列表信息对象 + """ + role_list = db.query(SysRole) \ + .filter(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) \ + .distinct().all() + + return role_list + + @classmethod + def add_role_dao(cls, db: Session, role: RoleModel): + """ + 新增角色数据库操作 + :param db: orm对象 + :param role: 角色对象 + :return: + """ + db_role = SysRole(**role.model_dump()) + db.add(db_role) + db.flush() + + return db_role + + @classmethod + def edit_role_dao(cls, db: Session, role: dict): + """ + 编辑角色数据库操作 + :param db: orm对象 + :param role: 需要更新的角色字典 + :return: + """ + db.query(SysRole) \ + .filter(SysRole.role_id == role.get('role_id')) \ + .update(role) + + @classmethod + def delete_role_dao(cls, db: Session, role: RoleModel): + """ + 删除角色数据库操作 + :param db: orm对象 + :param role: 角色对象 + :return: + """ + db.query(SysRole) \ + .filter(SysRole.role_id == role.role_id) \ + .update({SysRole.del_flag: '2', SysRole.update_by: role.update_by, SysRole.update_time: role.update_time}) + + @classmethod + def get_role_menu_dao(cls, db: Session, role_id: int): + """ + 根据角色id获取角色菜单关联列表信息 + :param db: orm对象 + :param role_id: 角色id + :return: 角色菜单关联列表信息 + """ + role_menu_query_all = db.query(SysRoleMenu) \ + .filter(SysRoleMenu.role_id == role_id) \ + .distinct().all() + + return role_menu_query_all + + @classmethod + def add_role_menu_dao(cls, db: Session, role_menu: RoleMenuModel): + """ + 新增角色菜单关联信息数据库操作 + :param db: orm对象 + :param role_menu: 用户角色菜单关联对象 + :return: + """ + db_role_menu = SysRoleMenu(**role_menu.model_dump()) + db.add(db_role_menu) + + @classmethod + def delete_role_menu_dao(cls, db: Session, role_menu: RoleMenuModel): + """ + 删除角色菜单关联信息数据库操作 + :param db: orm对象 + :param role_menu: 角色菜单关联对象 + :return: + """ + db.query(SysRoleMenu) \ + .filter(SysRoleMenu.role_id == role_menu.role_id) \ + .delete() + + @classmethod + def get_role_dept_dao(cls, db: Session, role_id: int): + """ + 根据角色id获取角色部门关联列表信息 + :param db: orm对象 + :param role_id: 角色id + :return: 角色部门关联列表信息 + """ + role_dept_query_all = db.query(SysRoleDept) \ + .filter(SysRoleDept.role_id == role_id, + ~db.query(SysDept).filter(func.find_in_set(SysRoleDept.dept_id, SysDept.ancestors)).exists() + ) \ + .distinct().all() + + return role_dept_query_all + + @classmethod + def add_role_dept_dao(cls, db: Session, role_dept: RoleDeptModel): + """ + 新增角色部门关联信息数据库操作 + :param db: orm对象 + :param role_dept: 用户角色部门关联对象 + :return: + """ + db_role_dept = SysRoleDept(**role_dept.dict()) + db.add(db_role_dept) + + @classmethod + def delete_role_dept_dao(cls, db: Session, role_dept: RoleDeptModel): + """ + 删除角色部门关联信息数据库操作 + :param db: orm对象 + :param role_dept: 角色部门关联对象 + :return: + """ + db.query(SysRoleDept) \ + .filter(SysRoleDept.role_id == role_dept.role_id) \ + .delete() diff --git a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py new file mode 100644 index 0000000..982ca81 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py @@ -0,0 +1,372 @@ +from sqlalchemy import and_, or_, desc, func +from sqlalchemy.orm import Session +from module_admin.entity.do.user_do import SysUser, SysUserRole, SysUserPost +from module_admin.entity.do.role_do import SysRole, 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 utils.time_format_util import list_format_datetime +from datetime import datetime, time + + +class UserDao: + """ + 用户管理模块数据库操作层 + """ + + @classmethod + def get_user_by_name(cls, db: Session, user_name: str): + """ + 根据用户名获取用户信息 + :param db: orm对象 + :param user_name: 用户名 + :return: 当前用户名的用户信息对象 + """ + query_user_info = db.query(SysUser) \ + .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_name == user_name) \ + .order_by(desc(SysUser.create_time)).distinct().first() + + return query_user_info + + @classmethod + def get_user_by_info(cls, db: Session, user: UserModel): + """ + 根据用户参数获取用户信息 + :param db: orm对象 + :param user: 用户参数 + :return: 当前用户参数的用户信息对象 + """ + query_user_info = db.query(SysUser) \ + .filter(SysUser.del_flag == 0, + SysUser.user_name == user.user_name) \ + .order_by(desc(SysUser.create_time)).distinct().first() + + return query_user_info + + @classmethod + def get_user_by_id(cls, db: Session, user_id: int): + """ + 根据user_id获取用户信息 + :param db: orm对象 + :param user_id: 用户id + :return: 当前user_id的用户信息对象 + """ + query_user_basic_info = db.query(SysUser) \ + .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .distinct().first() + query_user_dept_info = db.query(SysDept).select_from(SysUser) \ + .filter(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().first() + query_user_role_info = db.query(SysRole).select_from(SysUser) \ + .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ + .join(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ + .distinct().all() + query_user_post_info = db.query(SysPost).select_from(SysUser) \ + .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserPost, SysUser.user_id == SysUserPost.user_id) \ + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == 0)) \ + .distinct().all() + role_id_list = [item.role_id for item in query_user_role_info] + if 1 in role_id_list: + query_user_menu_info = db.query(SysMenu) \ + .filter(SysMenu.status == 0) \ + .distinct().all() + else: + query_user_menu_info = db.query(SysMenu).select_from(SysUser) \ + .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ + .outerjoin(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ + .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) \ + .order_by(SysMenu.order_num) \ + .distinct().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 + ) + + return results + + @classmethod + def get_user_detail_by_id(cls, db: Session, user_id: int): + """ + 根据user_id获取用户详细信息 + :param db: orm对象 + :param user_id: 用户id + :return: 当前user_id的用户信息对象 + """ + query_user_basic_info = db.query(SysUser) \ + .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .distinct().first() + query_user_dept_info = db.query(SysDept).select_from(SysUser) \ + .filter(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().first() + query_user_role_info = db.query(SysRole).select_from(SysUser) \ + .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ + .join(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ + .distinct().all() + query_user_post_info = db.query(SysPost).select_from(SysUser) \ + .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserPost, SysUser.user_id == SysUserPost.user_id) \ + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == 0)) \ + .distinct().all() + query_user_menu_info = db.query(SysMenu).select_from(SysUser) \ + .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ + .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ + .outerjoin(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ + .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) \ + .distinct().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 + ) + + return results + + @classmethod + def get_user_list(cls, db: Session, query_object: UserQueryModel, data_scope_sql: str): + """ + 根据查询参数获取用户列表信息 + :param db: orm对象 + :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 用户列表信息对象 + """ + user_list = db.query(SysUser, SysDept) \ + .filter(SysUser.del_flag == 0, + or_(SysUser.dept_id == query_object.dept_id, SysUser.dept_id.in_( + db.query(SysDept.dept_id).filter(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) + ) \ + .outerjoin(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) \ + .distinct().all() + + return user_list + + @classmethod + def add_user_dao(cls, db: Session, user: UserModel): + """ + 新增用户数据库操作 + :param db: orm对象 + :param user: 用户对象 + :return: 新增校验结果 + """ + db_user = SysUser(**user.model_dump()) + db.add(db_user) + db.flush() + + return db_user + + @classmethod + def edit_user_dao(cls, db: Session, user: dict): + """ + 编辑用户数据库操作 + :param db: orm对象 + :param user: 需要更新的用户字典 + :return: 编辑校验结果 + """ + db.query(SysUser) \ + .filter(SysUser.user_id == user.get('user_id')) \ + .update(user) + + @classmethod + def delete_user_dao(cls, db: Session, user: UserModel): + """ + 删除用户数据库操作 + :param db: orm对象 + :param user: 用户对象 + :return: + """ + db.query(SysUser) \ + .filter(SysUser.user_id == user.user_id) \ + .update({SysUser.del_flag: '2', SysUser.update_by: user.update_by, SysUser.update_time: user.update_time}) + + @classmethod + def get_user_role_allocated_list_by_user_id(cls, db: Session, query_object: UserRoleQueryModel): + """ + 根据用户id获取用户已分配的角色列表信息数据库操作 + :param db: orm对象 + :param query_object: 用户角色查询对象 + :return: 用户已分配的角色列表信息 + """ + allocated_role_list = db.query(SysRole) \ + .filter( + 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_( + db.query(SysUserRole.role_id).filter(SysUserRole.user_id == query_object.user_id) + ) + ).distinct().all() + + return allocated_role_list + + @classmethod + def get_user_role_unallocated_list_by_user_id(cls, db: Session, query_object: UserRoleQueryModel): + """ + 根据用户id获取用户未分配的角色列表信息数据库操作 + :param db: orm对象 + :param query_object: 用户角色查询对象 + :return: 用户未分配的角色列表信息 + """ + unallocated_role_list = db.query(SysRole) \ + .filter( + 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_( + db.query(SysUserRole.role_id).filter(SysUserRole.user_id == query_object.user_id) + ) + ).distinct().all() + + return list_format_datetime(unallocated_role_list) + + @classmethod + def get_user_role_allocated_list_by_role_id(cls, db: Session, query_object: UserRoleQueryModel): + """ + 根据角色id获取已分配的用户列表信息 + :param db: orm对象 + :param query_object: 用户角色查询对象 + :return: 角色已分配的用户列表信息 + """ + allocated_user_list = db.query(SysUser) \ + .filter( + 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_( + db.query(SysUserRole.user_id).filter(SysUserRole.role_id == query_object.role_id) + ) + ).distinct().all() + + return allocated_user_list + + @classmethod + def get_user_role_unallocated_list_by_role_id(cls, db: Session, query_object: UserRoleQueryModel): + """ + 根据角色id获取未分配的用户列表信息 + :param db: orm对象 + :param query_object: 用户角色查询对象 + :return: 角色未分配的用户列表信息 + """ + unallocated_user_list = db.query(SysUser) \ + .filter( + 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_( + db.query(SysUserRole.user_id).filter(SysUserRole.role_id == query_object.role_id) + ) + ).distinct().all() + + return unallocated_user_list + + @classmethod + def add_user_role_dao(cls, db: Session, user_role: UserRoleModel): + """ + 新增用户角色关联信息数据库操作 + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: + """ + db_user_role = SysUserRole(**user_role.model_dump()) + db.add(db_user_role) + + @classmethod + def delete_user_role_dao(cls, db: Session, user_role: UserRoleModel): + """ + 删除用户角色关联信息数据库操作 + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: + """ + db.query(SysUserRole) \ + .filter(SysUserRole.user_id == user_role.user_id) \ + .delete() + + @classmethod + def delete_user_role_by_user_and_role_dao(cls, db: Session, user_role: UserRoleModel): + """ + 根据用户id及角色id删除用户角色关联信息数据库操作 + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: + """ + db.query(SysUserRole) \ + .filter(SysUserRole.user_id == user_role.user_id, + SysUserRole.role_id == user_role.role_id if user_role.role_id else True) \ + .delete() + + @classmethod + def get_user_role_detail(cls, db: Session, user_role: UserRoleModel): + """ + 根据用户角色关联获取用户角色关联详细信息 + :param db: orm对象 + :param user_role: 用户角色关联对象 + :return: 用户角色关联信息 + """ + user_role_info = db.query(SysUserRole) \ + .filter(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id) \ + .distinct().first() + + return user_role_info + + @classmethod + def add_user_post_dao(cls, db: Session, user_post: UserPostModel): + """ + 新增用户岗位关联信息数据库操作 + :param db: orm对象 + :param user_post: 用户岗位关联对象 + :return: + """ + db_user_post = SysUserPost(**user_post.model_dump()) + db.add(db_user_post) + + @classmethod + def delete_user_post_dao(cls, db: Session, user_post: UserPostModel): + """ + 删除用户岗位关联信息数据库操作 + :param db: orm对象 + :param user_post: 用户岗位关联对象 + :return: + """ + db.query(SysUserPost) \ + .filter(SysUserPost.user_id == user_post.user_id) \ + .delete() + + @classmethod + def get_user_dept_info(cls, db: Session, dept_id: int): + dept_basic_info = db.query(SysDept) \ + .filter(SysDept.dept_id == dept_id, + SysDept.status == 0, + SysDept.del_flag == 0) \ + .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 new file mode 100644 index 0000000..5fb6476 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py @@ -0,0 +1,21 @@ +from sqlalchemy import Column, Integer, String, DateTime +from config.database import Base +from datetime import datetime + + +class SysConfig(Base): + """ + 参数配置表 + """ + __tablename__ = 'sys_config' + + config_id = Column(Integer, primary_key=True, autoincrement=True, comment='参数主键') + config_name = Column(String(100), nullable=True, default='', comment='参数名称') + config_key = Column(String(100), nullable=True, default='', comment='参数键名') + config_value = Column(String(500), nullable=True, default='', comment='参数键值') + config_type = Column(String(1), nullable=True, default='N', comment='系统内置(Y是 N否)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + 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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py new file mode 100644 index 0000000..0572927 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py @@ -0,0 +1,25 @@ +from sqlalchemy import Column, Integer, String, DateTime +from config.database import Base +from datetime import datetime + + +class SysDept(Base): + """ + 部门表 + """ + __tablename__ = 'sys_dept' + + dept_id = Column(Integer, primary_key=True, autoincrement=True, comment='部门id') + parent_id = Column(Integer, default=0, comment='父部门id') + ancestors = Column(String(50), nullable=True, default='', comment='祖级列表') + dept_name = Column(String(30), nullable=True, default='', comment='部门名称') + order_num = Column(Integer, default=0, comment='显示顺序') + leader = Column(String(20), nullable=True, default=None, comment='负责人') + phone = Column(String(11), nullable=True, default=None, comment='联系电话') + email = Column(String(50), nullable=True, default=None, comment='邮箱') + status = Column(String(1), nullable=True, default=0, comment='部门状态(0正常 1停用)') + del_flag = Column(String(1), nullable=True, default=0, comment='删除标志(0代表存在 2代表删除)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + 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='更新时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py new file mode 100644 index 0000000..a7d630e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py @@ -0,0 +1,46 @@ +from sqlalchemy import Column, Integer, String, DateTime, UniqueConstraint +from config.database import Base +from datetime import datetime + + +class SysDictType(Base): + """ + 字典类型表 + """ + __tablename__ = 'sys_dict_type' + + dict_id = Column(Integer, primary_key=True, autoincrement=True, comment='字典主键') + dict_name = Column(String(100), nullable=True, default='', comment='字典名称') + dict_type = Column(String(100), nullable=True, default='', comment='字典类型') + status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + 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='备注') + + __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='字典编码') + dict_sort = Column(Integer, nullable=True, default=0, comment='字典排序') + dict_label = Column(String(100), nullable=True, default='', comment='字典标签') + dict_value = Column(String(100), nullable=True, default='', comment='字典键值') + dict_type = Column(String(100), nullable=True, default='', comment='字典类型') + css_class = Column(String(100), nullable=True, default='', comment='样式属性(其他样式扩展)') + list_class = Column(String(100), nullable=True, default='', comment='表格回显样式') + is_default = Column(String(1), nullable=True, default='N', comment='是否默认(Y是 N否)') + status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + 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='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py new file mode 100644 index 0000000..aef168f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py @@ -0,0 +1,47 @@ +from sqlalchemy import Column, Integer, String, DateTime +from config.database import Base +from datetime import datetime + + +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='任务执行器') + 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禁止)') + 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='创建时间') + update_by = Column(String(64, collation='utf8_general_ci'), nullable=True, default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500, collation='utf8_general_ci'), nullable=True, default='', comment='备注信息') + + +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='任务执行器') + 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失败)') + 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 new file mode 100644 index 0000000..19d2b18 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py @@ -0,0 +1,52 @@ +from sqlalchemy import Column, Integer, String, DateTime, Text, BigInteger, Index +from config.database import Base +from datetime import datetime + + +class SysLogininfor(Base): + """ + 系统访问记录 + """ + __tablename__ = 'sys_logininfor' + + info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID') + user_name = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='用户账号') + ipaddr = Column(String(128, collation='utf8_general_ci'), nullable=True, default='', comment='登录IP地址') + 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失败)') + msg = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='提示消息') + login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间') + + idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status) + idx_sys_logininfor_lt = Index('idx_sys_logininfor_lt', login_time) + + +class SysOperLog(Base): + """ + 操作日志记录 + """ + __tablename__ = 'sys_oper_log' + + oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键') + title = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='模块标题') + business_type = Column(Integer, default=0, comment='业务类型(0其它 1新增 2修改 3删除)') + method = Column(String(100, collation='utf8_general_ci'), nullable=True, default='', comment='方法名称') + request_method = Column(String(10, collation='utf8_general_ci'), nullable=True, default='', comment='请求方式') + operator_type = Column(Integer, default=0, comment='操作类别(0其它 1后台用户 2手机端用户)') + oper_name = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='操作人员') + dept_name = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='部门名称') + oper_url = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='请求URL') + oper_ip = Column(String(128, collation='utf8_general_ci'), nullable=True, default='', comment='主机地址') + oper_location = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='操作地点') + oper_param = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='请求参数') + json_result = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='返回参数') + status = Column(Integer, default=0, comment='操作状态(0正常 1异常)') + error_msg = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='错误消息') + oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间') + cost_time = Column(BigInteger, default=0, comment='消耗时间') + + idx_sys_oper_log_bt = Index('idx_sys_oper_log_bt', business_type) + idx_sys_oper_log_s = Index('idx_sys_oper_log_s', status) + idx_sys_oper_log_ot = Index('idx_sys_oper_log_ot', oper_time) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py new file mode 100644 index 0000000..8f98be4 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py @@ -0,0 +1,30 @@ +from sqlalchemy import Column, Integer, String, DateTime +from config.database import Base +from datetime import datetime + + +class SysMenu(Base): + """ + 菜单权限表 + """ + __tablename__ = 'sys_menu' + + menu_id = Column(Integer, primary_key=True, autoincrement=True, comment='菜单ID') + menu_name = Column(String(50), nullable=False, default='', comment='菜单名称') + parent_id = Column(Integer, default=0, comment='父菜单ID') + order_num = Column(Integer, default=0, comment='显示顺序') + path = Column(String(200), nullable=True, default='', comment='路由地址') + component = Column(String(255), nullable=True, default=None, comment='组件路径') + query = Column(String(255), nullable=True, default=None, comment='路由参数') + is_frame = Column(Integer, default=1, comment='是否为外链(0是 1否)') + is_cache = Column(Integer, default=0, comment='是否缓存(0缓存 1不缓存)') + menu_type = Column(String(1), nullable=True, default='', comment='菜单类型(M目录 C菜单 F按钮)') + visible = Column(String(1), nullable=True, default='0', comment='菜单状态(0显示 1隐藏)') + status = Column(String(1), nullable=True, default='0', comment='菜单状态(0正常 1停用)') + perms = Column(String(100), nullable=True, default=None, comment='权限标识') + icon = Column(String(100), nullable=True, default='#', comment='菜单图标') + create_by = Column(String(64), nullable=True, default='', comment='创建者') + 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='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py new file mode 100644 index 0000000..3856658 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py @@ -0,0 +1,21 @@ +from sqlalchemy import Column, Integer, String, DateTime, LargeBinary +from config.database import Base +from datetime import datetime + + +class SysNotice(Base): + """ + 通知公告表 + """ + __tablename__ = 'sys_notice' + + notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID') + notice_title = Column(String(50, collation='utf8_general_ci'), nullable=False, comment='公告标题') + notice_type = Column(String(1, collation='utf8_general_ci'), nullable=False, comment='公告类型(1通知 2公告)') + notice_content = Column(LargeBinary, comment='公告内容') + status = Column(String(1, collation='utf8_general_ci'), default='0', comment='公告状态(0正常 1关闭)') + create_by = Column(String(64, collation='utf8_general_ci'), default='', comment='创建者') + create_time = Column(DateTime, comment='创建时间', default=datetime.now()) + update_by = Column(String(64, collation='utf8_general_ci'), default='', comment='更新者') + update_time = Column(DateTime, comment='更新时间', default=datetime.now()) + remark = Column(String(255, collation='utf8_general_ci'), comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py new file mode 100644 index 0000000..c6b189b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py @@ -0,0 +1,21 @@ +from sqlalchemy import Column, Integer, String, DateTime +from config.database import Base +from datetime import datetime + + +class SysPost(Base): + """ + 岗位信息表 + """ + __tablename__ = 'sys_post' + + post_id = Column(Integer, primary_key=True, autoincrement=True, comment='岗位ID') + post_code = Column(String(64), nullable=False, comment='岗位编码') + post_name = Column(String(50), nullable=False, comment='岗位名称') + post_sort = Column(Integer, nullable=False, comment='显示顺序') + status = Column(String(1), nullable=False, default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default='', comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py new file mode 100644 index 0000000..db29244 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py @@ -0,0 +1,45 @@ +from sqlalchemy import Column, Integer, String, DateTime +from config.database import Base +from datetime import datetime + + +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:本部门及以下数据权限)') + 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停用)') + del_flag = Column(String(1, collation='utf8_general_ci'), default='0', comment='删除标志(0代表存在 2代表删除)') + create_by = Column(String(64, collation='utf8_general_ci'), default='', comment='创建者') + create_time = Column(DateTime, default=datetime.now(), comment='创建时间') + update_by = Column(String(64, collation='utf8_general_ci'), default='', comment='更新者') + update_time = Column(DateTime, default=datetime.now(), comment='更新时间') + remark = Column(String(500, collation='utf8_general_ci'), comment='备注') + + +class SysRoleDept(Base): + """ + 角色和部门关联表 + """ + __tablename__ = 'sys_role_dept' + + role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') + dept_id = Column(Integer, primary_key=True, nullable=False, comment='部门ID') + + +class SysRoleMenu(Base): + """ + 角色和菜单关联表 + """ + __tablename__ = 'sys_role_menu' + + role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') + menu_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 new file mode 100644 index 0000000..9351547 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py @@ -0,0 +1,50 @@ +from sqlalchemy import Column, Integer, String, DateTime +from config.database import Base +from datetime import datetime + + +class SysUser(Base): + """ + 用户信息表 + """ + __tablename__ = 'sys_user' + + user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID') + dept_id = Column(Integer, comment='部门ID') + user_name = Column(String(30, collation='utf8_general_ci'), nullable=False, comment='用户账号') + nick_name = Column(String(30, collation='utf8_general_ci'), nullable=False, comment='用户昵称') + user_type = Column(String(2, collation='utf8_general_ci'), default='00', comment='用户类型(00系统用户)') + email = Column(String(50, collation='utf8_general_ci'), default='', comment='用户邮箱') + phonenumber = Column(String(11, collation='utf8_general_ci'), default='', comment='手机号码') + sex = Column(String(1, collation='utf8_general_ci'), default='0', comment='用户性别(0男 1女 2未知)') + avatar = Column(String(100, collation='utf8_general_ci'), default='', comment='头像地址') + password = Column(String(100, collation='utf8_general_ci'), default='', comment='密码') + status = Column(String(1, collation='utf8_general_ci'), default='0', comment='帐号状态(0正常 1停用)') + del_flag = Column(String(1, collation='utf8_general_ci'), default='0', comment='删除标志(0代表存在 2代表删除)') + login_ip = Column(String(128, collation='utf8_general_ci'), default='', comment='最后登录IP') + login_date = Column(DateTime, comment='最后登录时间') + create_by = Column(String(64, collation='utf8_general_ci'), default='', comment='创建者') + create_time = Column(DateTime, comment='创建时间', default=datetime.now()) + update_by = Column(String(64, collation='utf8_general_ci'), default='', comment='更新者') + update_time = Column(DateTime, comment='更新时间', default=datetime.now()) + remark = Column(String(500, collation='utf8_general_ci'), comment='备注') + + +class SysUserRole(Base): + """ + 用户和角色关联表 + """ + __tablename__ = 'sys_user_role' + + user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') + role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') + + +class SysUserPost(Base): + """ + 用户与岗位关联表 + """ + __tablename__ = 'sys_user_post' + + user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') + post_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 new file mode 100644 index 0000000..f2fc9ff --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/cache_vo.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel +from typing import Optional, List, Any + + +class CacheMonitorModel(BaseModel): + """ + 缓存监控信息对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + command_stats: Optional[List] = [] + db_size: Optional[int] = None + info: Optional[dict] = {} + + +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 diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py new file mode 100644 index 0000000..072269e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/common_vo.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel + + +class CrudResponseModel(BaseModel): + """ + 操作响应模型 + """ + is_success: bool + message: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py new file mode 100644 index 0000000..3d3cc39 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/config_vo.py @@ -0,0 +1,50 @@ +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 + + +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 + + +class ConfigQueryModel(ConfigModel): + """ + 参数配置管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class ConfigPageQueryModel(ConfigQueryModel): + """ + 参数配置管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeleteConfigModel(BaseModel): + """ + 删除参数配置模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + config_ids: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py new file mode 100644 index 0000000..9a8eb3e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dept_vo.py @@ -0,0 +1,47 @@ +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 + + +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 + + +@as_query +class DeptQueryModel(DeptModel): + """ + 部门管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +class DeleteDeptModel(BaseModel): + """ + 删除部门模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + dept_ids: str + update_by: Optional[str] = None + update_time: Optional[str] = None diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py new file mode 100644 index 0000000..f380802 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/dict_vo.py @@ -0,0 +1,98 @@ +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 + + +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 + + +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 + + +class DictTypeQueryModel(DictTypeModel): + """ + 字典类型管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class DictTypePageQueryModel(DictTypeQueryModel): + """ + 字典类型管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeleteDictTypeModel(BaseModel): + """ + 删除字典类型模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + dict_ids: str + + +class DictDataQueryModel(DictTypeModel): + """ + 字典数据管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class DictDataPageQueryModel(DictDataQueryModel): + """ + 字典数据管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeleteDictDataModel(BaseModel): + """ + 删除字典数据模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + dict_codes: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py new file mode 100644 index 0000000..fadcbea --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/job_vo.py @@ -0,0 +1,110 @@ +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 + + +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 + + +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 + + +class JobQueryModel(JobModel): + """ + 定时任务管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class JobPageQueryModel(JobQueryModel): + """ + 定时任务管理分页查询模型 + """ + page_num: int + page_size: int + + +class EditJobModel(JobModel): + """ + 编辑定时任务模型 + """ + type: Optional[str] = None + + +class DeleteJobModel(BaseModel): + """ + 删除定时任务模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + job_ids: str + + +class JobLogQueryModel(JobLogModel): + """ + 定时任务日志不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class JobLogPageQueryModel(JobLogQueryModel): + """ + 定时任务日志管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeleteJobLogModel(BaseModel): + """ + 删除定时任务日志模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + job_log_ids: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py new file mode 100644 index 0000000..e7a53bf --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/log_vo.py @@ -0,0 +1,111 @@ +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 + + +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 + + +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 + + +class OperLogQueryModel(OperLogModel): + """ + 操作日志管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class OperLogPageQueryModel(OperLogQueryModel): + """ + 操作日志管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeleteOperLogModel(BaseModel): + """ + 删除操作日志模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + oper_ids: str + + +class LoginLogQueryModel(LogininforModel): + """ + 登录日志管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + + +@as_query +@as_form +class LoginLogPageQueryModel(LoginLogQueryModel): + """ + 登录日志管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeleteLoginLogModel(BaseModel): + """ + 删除登录日志模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + info_ids: str + + +class UnlockUser(BaseModel): + """ + 解锁用户模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + user_name: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py new file mode 100644 index 0000000..6c1ba12 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/login_vo.py @@ -0,0 +1,34 @@ +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel +from typing import Optional + + +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 + + +class Token(BaseModel): + access_token: str + token_type: str + + +class CaptchaCode(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + captcha_enabled: bool + img: str + uuid: str + + +class SmsCode(BaseModel): + is_success: Optional[bool] = None + sms_code: str + session_id: str + message: Optional[str] = None \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py new file mode 100644 index 0000000..08f22e1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/menu_vo.py @@ -0,0 +1,50 @@ +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel +from datetime import datetime +from typing import Union, Optional, List +from module_admin.annotation.pydantic_annotation import as_query + + +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 + + +@as_query +class MenuQueryModel(MenuModel): + """ + 菜单管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +class DeleteMenuModel(BaseModel): + """ + 删除菜单模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + menu_ids: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py new file mode 100644 index 0000000..cf2eab0 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/notice_vo.py @@ -0,0 +1,50 @@ +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 + + +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 + + +class NoticeQueryModel(NoticeModel): + """ + 通知公告管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class NoticePageQueryModel(NoticeQueryModel): + """ + 通知公告管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeleteNoticeModel(BaseModel): + """ + 删除通知公告模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + notice_ids: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py new file mode 100644 index 0000000..616fd2a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/online_vo.py @@ -0,0 +1,39 @@ +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 + + +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 + + +@as_query +class OnlineQueryModel(OnlineModel): + """ + 岗位管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +class DeleteOnlineModel(BaseModel): + """ + 强退在线用户模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + token_ids: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py new file mode 100644 index 0000000..cd2136a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/post_vo.py @@ -0,0 +1,50 @@ +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 + + +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 + + +class PostQueryModel(PostModel): + """ + 岗位管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class PostPageQueryModel(PostQueryModel): + """ + 岗位管理分页查询模型 + """ + page_num: int + page_size: int + + +class DeletePostModel(BaseModel): + """ + 删除岗位模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + post_ids: str diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py new file mode 100644 index 0000000..3dfe305 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/role_vo.py @@ -0,0 +1,127 @@ +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 + + +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 + + @field_validator('menu_check_strictly', 'dept_check_strictly') + @classmethod + def check_filed_mapping(cls, v: Union[int, bool]) -> Union[int, bool]: + if v == 1: + v = True + elif v == 0: + v = False + elif v is True: + v = 1 + elif v is False: + v = 0 + return v + + @model_validator(mode='after') + def check_admin(self) -> 'RoleModel': + if self.role_id == 1: + self.admin = True + else: + self.admin = False + return self + + +class RoleMenuModel(BaseModel): + """ + 角色和菜单关联表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + role_id: Optional[int] = None + menu_id: Optional[int] = None + + +class RoleDeptModel(BaseModel): + """ + 角色和部门关联表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + role_id: Optional[int] = None + dept_id: Optional[int] = None + + +class RoleQueryModel(RoleModel): + """ + 角色管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class RolePageQueryModel(RoleQueryModel): + """ + 角色管理分页查询模型 + """ + page_num: int + page_size: int + + +class RoleMenuQueryModel(BaseModel): + """ + 角色菜单查询模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + menus: List = [] + checked_keys: List[int] = [] + + +class RoleDeptQueryModel(BaseModel): + """ + 角色部门查询模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + depts: List = [] + checked_keys: List[int] = [] + + +class AddRoleModel(RoleModel): + """ + 新增角色模型 + """ + dept_ids: List = [] + menu_ids: List = [] + type: Optional[str] = None + + +class DeleteRoleModel(BaseModel): + """ + 删除角色模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + role_ids: str + update_by: Optional[str] = None + update_time: Optional[datetime] = None diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py new file mode 100644 index 0000000..4323ddf --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/server_vo.py @@ -0,0 +1,66 @@ +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel +from typing import Optional, List + + +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 + + +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 + + +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 + + +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 + + +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 + + +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]] diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py new file mode 100644 index 0000000..9f7cc1b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -0,0 +1,210 @@ +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 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 + + +class TokenData(BaseModel): + """ + token解析结果 + """ + user_id: Union[int, None] = None + + +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 + + @model_validator(mode='after') + def check_admin(self) -> 'UserModel': + if self.user_id == 1: + self.admin = True + else: + self.admin = False + return self + + +class UserRoleModel(BaseModel): + """ + 用户和角色关联表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + user_id: Optional[int] = None + role_id: Optional[int] = None + + +class UserPostModel(BaseModel): + """ + 用户与岗位关联表对应pydantic模型 + """ + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + user_id: Optional[int] = None + post_id: Optional[int] = None + + +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]]] = [] + + +class CurrentUserModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + permissions: List + roles: List + user: Union[UserInfoModel, None] + + +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]] + + +class UserProfileModel(BaseModel): + """ + 获取个人信息响应模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + data: Union[UserInfoModel, None] + post_group: Union[str, None] + role_group: Union[str, None] + + +class UserQueryModel(UserModel): + """ + 用户管理不分页查询模型 + """ + begin_time: Optional[str] = None + end_time: Optional[str] = None + + +@as_query +@as_form +class UserPageQueryModel(UserQueryModel): + """ + 用户管理分页查询模型 + """ + page_num: int + page_size: int + + +class AddUserModel(UserModel): + """ + 新增用户模型 + """ + role_ids: Optional[List] = [] + post_ids: Optional[List] = [] + type: Optional[str] = None + + +class EditUserModel(AddUserModel): + """ + 编辑用户模型 + """ + role: Optional[List] = [] + + +class ResetUserModel(UserModel): + """ + 重置用户密码模型 + """ + old_password: Optional[str] = None + sms_code: Optional[str] = None + session_id: Optional[str] = None + + +class DeleteUserModel(BaseModel): + """ + 删除用户模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + user_ids: str + update_by: Optional[str] = None + update_time: Optional[datetime] = None + + +class UserRoleQueryModel(UserModel): + """ + 用户角色关联管理不分页查询模型 + """ + role_id: Optional[int] = None + + +@as_query +class UserRolePageQueryModel(UserRoleQueryModel): + """ + 用户角色关联管理分页查询模型 + """ + page_num: int + page_size: int + + +class SelectedRoleModel(RoleModel): + """ + 是否选择角色模型 + """ + flag: Optional[bool] = False + + +class UserRoleResponseModel(BaseModel): + """ + 用户角色关联管理列表返回模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + roles: List[Union[SelectedRoleModel, None]] = [] + user: UserInfoModel + + +@as_query +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 diff --git a/ruoyi-fastapi-backend/module_admin/service/cache_service.py b/ruoyi-fastapi-backend/module_admin/service/cache_service.py new file mode 100644 index 0000000..d021ef4 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/cache_service.py @@ -0,0 +1,123 @@ +from fastapi import Request +from module_admin.entity.vo.cache_vo import * +from config.env import RedisInitKeyConfig +from config.get_redis import RedisUtil +from module_admin.entity.vo.common_vo import CrudResponseModel + +class CacheService: + """ + 缓存监控模块服务层 + """ + + @classmethod + 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 + ) + + return result + + @classmethod + 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') + ) + ) + + return name_list + + @classmethod + 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}:")] + + return cache_key_list + + @classmethod + 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}") + + 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}*") + if cache_keys: + await request.app.state.redis.delete(*cache_keys) + result = dict(is_success=True, message=f"{cache_name}对应键值清除成功") + + return CrudResponseModel(**result) + + @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}") + if cache_keys: + await request.app.state.redis.delete(*cache_keys) + result = dict(is_success=True, message=f"{cache_key}清除成功") + + return CrudResponseModel(**result) + + @classmethod + async def clear_cache_monitor_all_services(cls, request: Request): + """ + 清除所有缓存service + :param request: Request对象 + :return: 操作缓存响应信息 + """ + cache_keys = await request.app.state.redis.keys() + 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) diff --git a/ruoyi-fastapi-backend/module_admin/service/captcha_service.py b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py new file mode 100644 index 0000000..b685f06 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/captcha_service.py @@ -0,0 +1,48 @@ +from PIL import Image, ImageDraw, ImageFont +import io +import os +import random +import base64 + + +class CaptchaService: + """ + 验证码模块服务层 + """ + + @classmethod + def create_captcha_image_service(cls): + # 创建空白图像 + image = Image.new('RGB', (160, 60), color='#EAEAEA') + + # 创建绘图对象 + draw = ImageDraw.Draw(image) + + # 设置字体 + font = ImageFont.truetype(os.path.join(os.path.abspath(os.getcwd()), 'assets', 'font', 'Arial.ttf'), size=30) + + # 生成两个0-9之间的随机整数 + num1 = random.randint(0, 9) + num2 = random.randint(0, 9) + # 从运算符列表中随机选择一个 + operational_character_list = ['+', '-', '*'] + operational_character = random.choice(operational_character_list) + # 根据选择的运算符进行计算 + if operational_character == '+': + result = num1 + num2 + elif operational_character == '-': + result = num1 - num2 + else: + result = num1 * num2 + # 绘制文本 + text = f"{num1} {operational_character} {num2} = ?" + draw.text((25, 15), text, fill='blue', font=font) + + # 将图像数据保存到内存中 + buffer = io.BytesIO() + image.save(buffer, format='PNG') + + # 将图像数据转换为base64字符串 + base64_string = base64.b64encode(buffer.getvalue()).decode() + + return [base64_string, result] diff --git a/ruoyi-fastapi-backend/module_admin/service/common_service.py b/ruoyi-fastapi-backend/module_admin/service/common_service.py new file mode 100644 index 0000000..447f28a --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/common_service.py @@ -0,0 +1,18 @@ +import os +from fastapi import UploadFile + + +class CommonService: + """ + 通用模块服务层 + """ + + @classmethod + def upload_service(cls, path: str, task_path: str, upload_id: str, file: UploadFile): + + filepath = os.path.join(path, task_path, upload_id, f'{file.filename}') + with open(filepath, 'wb') as f: + # 流式写出大型文件,这里的10代表10MB + for chunk in iter(lambda: file.file.read(1024 * 1024 * 10), b''): + f.write(chunk) + diff --git a/ruoyi-fastapi-backend/module_admin/service/config_service.py b/ruoyi-fastapi-backend/module_admin/service/config_service.py new file mode 100644 index 0000000..50e3d41 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/config_service.py @@ -0,0 +1,190 @@ +from fastapi import Request +from config.env import RedisInitKeyConfig +from module_admin.dao.config_dao import * +from module_admin.entity.vo.common_vo import CrudResponseModel +from utils.common_util import export_list2excel, CamelCaseUtil + + +class ConfigService: + """ + 参数配置管理模块服务层 + """ + + @classmethod + def get_config_list_services(cls, query_db: Session, query_object: ConfigQueryModel): + """ + 获取参数配置列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 参数配置列表信息对象 + """ + config_list_result = ConfigDao.get_config_list(query_db, query_object) + + return CamelCaseUtil.transform_result(config_list_result) + + @classmethod + async def init_cache_sys_config_services(cls, query_db: Session, redis): + """ + 应用初始化:获取所有参数配置对应的键值对信息并缓存service + :param query_db: orm对象 + :param redis: redis对象 + :return: + """ + # 获取以sys_config:开头的键列表 + keys = await redis.keys(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:*") + # 删除匹配的键 + if keys: + await redis.delete(*keys) + config_all = ConfigDao.get_config_list(query_db, ConfigQueryModel(**dict())) + for config_obj in config_all: + if config_obj.config_type == 'Y': + await redis.set(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:{config_obj.config_key}", config_obj.config_value) + + @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}") + + return result + + @classmethod + async def add_config_services(cls, request: Request, query_db: Session, page_object: ConfigModel): + """ + 新增参数配置信息service + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 新增参数配置对象 + :return: 新增参数配置校验结果 + """ + config = ConfigDao.get_config_detail_by_info(query_db, ConfigModel(configKey=page_object.config_key)) + if config: + result = dict(is_success=False, message='参数键名已存在') + else: + try: + ConfigDao.add_config_dao(query_db, page_object) + 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: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + async def edit_config_services(cls, request: Request, query_db: Session, 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 = 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 = ConfigDao.get_config_detail_by_info(query_db, page_object) + if config: + result = dict(is_success=False, message='参数配置已存在') + return CrudResponseModel(**result) + try: + ConfigDao.edit_config_dao(query_db, edit_config) + 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: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='参数配置不存在') + + return CrudResponseModel(**result) + + @classmethod + async def delete_config_services(cls, request: Request, query_db: Session, page_object: DeleteConfigModel): + """ + 删除参数配置信息service + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 删除参数配置对象 + :return: 删除参数配置校验结果 + """ + if page_object.config_ids.split(','): + config_id_list = page_object.config_ids.split(',') + try: + for config_id in config_id_list: + ConfigDao.delete_config_dao(query_db, ConfigModel(configId=config_id)) + 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: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入字典数据id为空') + return CrudResponseModel(**result) + + @classmethod + def config_detail_services(cls, query_db: Session, config_id: int): + """ + 获取参数配置详细信息service + :param query_db: orm对象 + :param config_id: 参数配置id + :return: 参数配置id对应的信息 + """ + config = ConfigDao.get_config_detail_by_id(query_db, config_id=config_id) + result = ConfigModel(**CamelCaseUtil.transform_result(config)) + + return result + + @staticmethod + 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": "备注", + } + + data = config_list + + for item in data: + if item.get('configType') == 'Y': + 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] + binary_data = export_list2excel(new_data) + + return binary_data + + @classmethod + async def refresh_sys_config_services(cls, request: Request, query_db: Session): + """ + 刷新字典缓存信息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) diff --git a/ruoyi-fastapi-backend/module_admin/service/dept_service.py b/ruoyi-fastapi-backend/module_admin/service/dept_service.py new file mode 100644 index 0000000..2c9db67 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/dept_service.py @@ -0,0 +1,213 @@ +from module_admin.dao.dept_dao import * +from module_admin.entity.vo.common_vo import CrudResponseModel +from utils.common_util import CamelCaseUtil + + +class DeptService: + """ + 部门管理模块服务层 + """ + + @classmethod + def get_dept_tree_services(cls, query_db: Session, page_object: DeptModel, data_scope_sql: str): + """ + 获取部门树信息service + :param query_db: orm对象 + :param page_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门树信息对象 + """ + dept_list_result = DeptDao.get_dept_list_for_tree(query_db, page_object, data_scope_sql) + dept_tree_result = cls.list_to_tree(dept_list_result) + + return dept_tree_result + + @classmethod + def get_dept_for_edit_option_services(cls, query_db: Session, page_object: DeptModel, data_scope_sql: str): + """ + 获取部门编辑部门树信息service + :param query_db: orm对象 + :param page_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门树信息对象 + """ + dept_list_result = DeptDao.get_dept_info_for_edit_option(query_db, page_object, data_scope_sql) + + return CamelCaseUtil.transform_result(dept_list_result) + + @classmethod + def get_dept_list_services(cls, query_db: Session, page_object: DeptModel, data_scope_sql: str): + """ + 获取部门列表信息service + :param query_db: orm对象 + :param page_object: 分页查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 部门列表信息对象 + """ + dept_list_result = DeptDao.get_dept_list(query_db, page_object, data_scope_sql) + + return CamelCaseUtil.transform_result(dept_list_result) + + @classmethod + def add_dept_services(cls, query_db: Session, page_object: DeptModel): + """ + 新增部门信息service + :param query_db: orm对象 + :param page_object: 新增部门对象 + :return: 新增部门校验结果 + """ + parent_info = 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 = 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: + DeptDao.add_dept_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + def edit_dept_services(cls, query_db: Session, page_object: DeptModel): + """ + 编辑部门信息service + :param query_db: orm对象 + :param page_object: 编辑部门对象 + :return: 编辑部门校验结果 + """ + parent_info = 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 = 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 = 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: + DeptDao.edit_dept_dao(query_db, edit_dept) + 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 + ) + ) + query_db.commit() + result = dict(is_success=True, message='更新成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='部门不存在') + + return CrudResponseModel(**result) + + @classmethod + def delete_dept_services(cls, query_db: Session, page_object: DeleteDeptModel): + """ + 删除部门信息service + :param query_db: orm对象 + :param page_object: 删除部门对象 + :return: 删除部门校验结果 + """ + if page_object.dept_ids.split(','): + dept_id_list = page_object.dept_ids.split(',') + ancestors = 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) + + DeptDao.delete_dept_dao(query_db, DeptModel(deptId=dept_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入部门id为空') + return CrudResponseModel(**result) + + @classmethod + def dept_detail_services(cls, query_db: Session, dept_id: int): + """ + 获取部门详细信息service + :param query_db: orm对象 + :param dept_id: 部门id + :return: 部门id对应的信息 + """ + dept = DeptDao.get_dept_detail_by_id(query_db, dept_id=dept_id) + result = DeptModel(**CamelCaseUtil.transform_result(dept)) + + return result + + @classmethod + 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] + # 转成id为key的字典 + mapping: dict = dict(zip([i['id'] for i in permission_list], permission_list)) + + # 树容器 + container: list = [] + + for d in permission_list: + # 如果找不到父级项,则是根节点 + parent: dict = mapping.get(d['parentId']) + if parent is None: + container.append(d) + else: + children: list = parent.get('children') + if not children: + children = [] + children.append(d) + parent.update({'children': children}) + + return container + + @classmethod + def update_children_info(cls, query_db, page_object): + """ + 工具方法:递归更新子部门信息 + :param query_db: orm对象 + :param page_object: 编辑部门对象 + :return: + """ + children_info = 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}' + 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 + ) + ) + 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 + )) diff --git a/ruoyi-fastapi-backend/module_admin/service/dict_service.py b/ruoyi-fastapi-backend/module_admin/service/dict_service.py new file mode 100644 index 0000000..d4bfe00 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/dict_service.py @@ -0,0 +1,363 @@ +from fastapi import Request +import json +from config.env import RedisInitKeyConfig +from module_admin.dao.dict_dao import * +from module_admin.entity.vo.common_vo import CrudResponseModel +from utils.common_util import export_list2excel, CamelCaseUtil + + +class DictTypeService: + """ + 字典类型管理模块服务层 + """ + + @classmethod + def get_dict_type_list_services(cls, query_db: Session, query_object: DictTypeQueryModel): + """ + 获取字典类型列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 字典类型列表信息对象 + """ + dict_type_list_result = DictTypeDao.get_dict_type_list(query_db, query_object) + + return CamelCaseUtil.transform_result(dict_type_list_result) + + @classmethod + async def add_dict_type_services(cls, request: Request, query_db: Session, page_object: DictTypeModel): + """ + 新增字典类型信息service + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 新增岗位对象 + :return: 新增字典类型校验结果 + """ + dict_type = DictTypeDao.get_dict_type_detail_by_info(query_db, DictTypeModel(dictType=page_object.dict_type)) + if dict_type: + result = dict(is_success=False, message='字典类型已存在') + else: + try: + DictTypeDao.add_dict_type_dao(query_db, page_object) + 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: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + async def edit_dict_type_services(cls, request: Request, query_db: Session, 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 = 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 = 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 = DictDataModel(dictType=dict_type_info.dict_type) + dict_data_list = 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) + DictDataDao.edit_dict_data_dao(query_db, edit_dict_data) + DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type) + 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: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='字典类型不存在') + + return CrudResponseModel(**result) + + @classmethod + async def delete_dict_type_services(cls, request: Request, query_db: Session, page_object: DeleteDictTypeModel): + """ + 删除字典类型信息service + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 删除字典类型对象 + :return: 删除字典类型校验结果 + """ + if page_object.dict_ids.split(','): + dict_id_list = page_object.dict_ids.split(',') + try: + for dict_id in dict_id_list: + DictTypeDao.delete_dict_type_dao(query_db, DictTypeModel(dictId=dict_id)) + 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: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入字典类型id为空') + return CrudResponseModel(**result) + + @classmethod + def dict_type_detail_services(cls, query_db: Session, dict_id: int): + """ + 获取字典类型详细信息service + :param query_db: orm对象 + :param dict_id: 字典类型id + :return: 字典类型id对应的信息 + """ + dict_type = DictTypeDao.get_dict_type_detail_by_id(query_db, dict_id=dict_id) + result = DictTypeModel(**CamelCaseUtil.transform_result(dict_type)) + + return result + + @staticmethod + 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": "备注", + } + + data = dict_type_list + + for item in data: + if item.get('status') == '0': + 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] + binary_data = export_list2excel(new_data) + + return binary_data + + @classmethod + async def refresh_sys_dict_services(cls, request: Request, query_db: Session): + """ + 刷新字典缓存信息service + :param request: Request对象 + :param query_db: orm对象 + :return: 刷新字典缓存校验结果 + """ + await DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis) + result = dict(is_success=True, message='刷新成功') + + return CrudResponseModel(**result) + + +class DictDataService: + """ + 字典数据管理模块服务层 + """ + + @classmethod + def get_dict_data_list_services(cls, query_db: Session, query_object: DictDataModel): + """ + 获取字典数据列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 字典数据列表信息对象 + """ + dict_data_list_result = DictDataDao.get_dict_data_list(query_db, query_object) + + return CamelCaseUtil.transform_result(dict_data_list_result) + + @classmethod + def query_dict_data_list_services(cls, query_db: Session, dict_type: str): + """ + 获取字典数据列表信息service + :param query_db: orm对象 + :param dict_type: 字典类型 + :return: 字典数据列表信息对象 + """ + dict_data_list_result = DictDataDao.query_dict_data_list(query_db, dict_type) + + return dict_data_list_result + + @classmethod + async def init_cache_sys_dict_services(cls, query_db: Session, redis): + """ + 应用初始化:获取所有字典类型对应的字典数据信息并缓存service + :param query_db: orm对象 + :param redis: redis对象 + :return: + """ + # 获取以sys_dict:开头的键列表 + keys = await redis.keys(f"{RedisInitKeyConfig.SYS_DICT.get('key')}:*") + # 删除匹配的键 + if keys: + await redis.delete(*keys) + dict_type_all = DictTypeDao.get_all_dict_type(query_db) + for dict_type_obj in [item for item in dict_type_all if item.status == '0']: + dict_type = dict_type_obj.dict_type + dict_data_list = 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)) + + @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}") + if dict_data_list_result: + result = json.loads(dict_data_list_result) + + return CamelCaseUtil.transform_result(result) + + @classmethod + async def add_dict_data_services(cls, request: Request, query_db: Session, page_object: DictDataModel): + """ + 新增字典数据信息service + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 新增岗位对象 + :return: 新增字典数据校验结果 + """ + dict_data = DictDataDao.get_dict_data_detail_by_info(query_db, page_object) + if dict_data: + result = dict(is_success=False, message='字典数据已存在') + else: + try: + DictDataDao.add_dict_data_dao(query_db, page_object) + 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: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + async def edit_dict_data_services(cls, request: Request, query_db: Session, 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 = 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 = DictDataDao.get_dict_data_detail_by_info(query_db, page_object) + if dict_data: + result = dict(is_success=False, message='字典数据已存在') + return CrudResponseModel(**result) + try: + DictDataDao.edit_dict_data_dao(query_db, edit_data_type) + 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: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='字典数据不存在') + + return CrudResponseModel(**result) + + @classmethod + async def delete_dict_data_services(cls, request: Request, query_db: Session, page_object: DeleteDictDataModel): + """ + 删除字典数据信息service + :param request: Request对象 + :param query_db: orm对象 + :param page_object: 删除字典数据对象 + :return: 删除字典数据校验结果 + """ + if page_object.dict_codes.split(','): + dict_code_list = page_object.dict_codes.split(',') + try: + for dict_code in dict_code_list: + DictDataDao.delete_dict_data_dao(query_db, DictDataModel(dictCode=dict_code)) + 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: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入字典数据id为空') + return CrudResponseModel(**result) + + @classmethod + def dict_data_detail_services(cls, query_db: Session, dict_code: int): + """ + 获取字典数据详细信息service + :param query_db: orm对象 + :param dict_code: 字典数据id + :return: 字典数据id对应的信息 + """ + dict_data = DictDataDao.get_dict_data_detail_by_id(query_db, dict_code=dict_code) + result = DictDataModel(**CamelCaseUtil.transform_result(dict_data)) + + return result + + @staticmethod + 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": "备注", + } + + data = dict_data_list + + for item in data: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '停用' + if item.get('isDefault') == 'Y': + 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] + 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 new file mode 100644 index 0000000..28bac95 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py @@ -0,0 +1,121 @@ +from module_admin.dao.job_log_dao import * +from module_admin.dao.dict_dao import DictDataDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from utils.common_util import export_list2excel, CamelCaseUtil + + +class JobLogService: + """ + 定时任务日志管理模块服务层 + """ + + @classmethod + def get_job_log_list_services(cls, query_db: Session, query_object: JobLogQueryModel): + """ + 获取定时任务日志列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 定时任务日志列表信息对象 + """ + job_log_list_result = JobLogDao.get_job_log_list(query_db, query_object) + + return CamelCaseUtil.transform_result(job_log_list_result) + + @classmethod + def add_job_log_services(cls, query_db: Session, page_object: JobLogModel): + """ + 新增定时任务日志信息service + :param query_db: orm对象 + :param page_object: 新增定时任务日志对象 + :return: 新增定时任务日志校验结果 + """ + try: + JobLogDao.add_job_log_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + result = dict(is_success=False, message=str(e)) + + return CrudResponseModel(**result) + + @classmethod + def delete_job_log_services(cls, query_db: Session, page_object: DeleteJobLogModel): + """ + 删除定时任务日志信息service + :param query_db: orm对象 + :param page_object: 删除定时任务日志对象 + :return: 删除定时任务日志校验结果 + """ + if page_object.job_log_ids.split(','): + job_log_id_list = page_object.job_log_ids.split(',') + try: + for job_log_id in job_log_id_list: + JobLogDao.delete_job_log_dao(query_db, JobLogModel(jobLogId=job_log_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入定时任务日志id为空') + return CrudResponseModel(**result) + + @classmethod + def clear_job_log_services(cls, query_db: Session): + """ + 清除定时任务日志信息service + :param query_db: orm对象 + :return: 清除定时任务日志校验结果 + """ + try: + JobLogDao.clear_job_log_dao(query_db) + query_db.commit() + result = dict(is_success=True, message='清除成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @staticmethod + def export_job_log_list_services(query_db, job_log_list: List): + """ + 导出定时任务日志信息service + :param query_db: orm对象 + :param job_log_list: 定时任务日志信息列表 + :return: 定时任务日志信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + "jobLogId": "任务日志编码", + "jobName": "任务名称", + "jobGroup": "任务组名", + "jobExecutor": "任务执行器", + "invokeTarget": "调用目标字符串", + "jobArgs": "位置参数", + "jobKwargs": "关键字参数", + "jobTrigger": "任务触发器", + "jobMessage": "日志信息", + "status": "执行状态", + "exceptionInfo": "异常信息", + "createTime": "创建时间", + } + + data = job_log_list + job_group_list = DictDataDao.query_dict_data_list(query_db, dict_type='sys_job_group') + job_group_option = [dict(label=item.dict_label, value=item.dict_value) for item in job_group_list] + job_group_option_dict = {item.get('value'): item for item in job_group_option} + + for item in data: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '暂停' + if str(item.get('job_group')) in job_group_option_dict.keys(): + item['job_group'] = job_group_option_dict.get(str(item.get('job_group'))).get('label') + 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 new file mode 100644 index 0000000..727ccd1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -0,0 +1,198 @@ +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 config.get_scheduler import SchedulerUtil + + +class JobService: + """ + 定时任务管理模块服务层 + """ + + @classmethod + def get_job_list_services(cls, query_db: Session, query_object: JobModel): + """ + 获取定时任务列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 定时任务列表信息对象 + """ + job_list_result = JobDao.get_job_list(query_db, query_object) + + return CamelCaseUtil.transform_result(job_list_result) + + @classmethod + def add_job_services(cls, query_db: Session, page_object: JobModel): + """ + 新增定时任务信息service + :param query_db: orm对象 + :param page_object: 新增定时任务对象 + :return: 新增定时任务校验结果 + """ + job = JobDao.get_job_detail_by_info(query_db, page_object) + if job: + result = dict(is_success=False, message='定时任务已存在') + else: + try: + JobDao.add_job_dao(query_db, page_object) + job_info = JobDao.get_job_detail_by_info(query_db, page_object) + if job_info.status == '0': + SchedulerUtil.add_scheduler_job(job_info=job_info) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + def edit_job_services(cls, query_db: Session, page_object: EditJobModel): + """ + 编辑定时任务信息service + :param query_db: orm对象 + :param page_object: 编辑定时任务对象 + :return: 编辑定时任务校验结果 + """ + edit_job = page_object.model_dump(exclude_unset=True) + if page_object.type == 'status': + del edit_job['type'] + job_info = cls.job_detail_services(query_db, edit_job.get('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 = JobDao.get_job_detail_by_info(query_db, page_object) + if job: + result = dict(is_success=False, message='定时任务已存在') + return CrudResponseModel(**result) + try: + JobDao.edit_job_dao(query_db, edit_job) + query_job = SchedulerUtil.get_scheduler_job(job_id=edit_job.get('job_id')) + if query_job: + SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id')) + if edit_job.get('status') == '0': + SchedulerUtil.add_scheduler_job(job_info=job_info) + query_db.commit() + result = dict(is_success=True, message='更新成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='定时任务不存在') + + return CrudResponseModel(**result) + + @classmethod + def execute_job_once_services(cls, query_db: Session, page_object: JobModel): + """ + 执行一次定时任务service + :param query_db: orm对象 + :param page_object: 定时任务对象 + :return: 执行一次定时任务结果 + """ + query_job = SchedulerUtil.get_scheduler_job(job_id=page_object.job_id) + if query_job: + SchedulerUtil.remove_scheduler_job(job_id=page_object.job_id) + job_info = 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='执行成功') + else: + result = dict(is_success=False, message='定时任务不存在') + + return CrudResponseModel(**result) + + @classmethod + def delete_job_services(cls, query_db: Session, page_object: DeleteJobModel): + """ + 删除定时任务信息service + :param query_db: orm对象 + :param page_object: 删除定时任务对象 + :return: 删除定时任务校验结果 + """ + if page_object.job_ids.split(','): + job_id_list = page_object.job_ids.split(',') + try: + for job_id in job_id_list: + JobDao.delete_job_dao(query_db, JobModel(jobId=job_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入定时任务id为空') + return CrudResponseModel(**result) + + @classmethod + def job_detail_services(cls, query_db: Session, job_id: int): + """ + 获取定时任务详细信息service + :param query_db: orm对象 + :param job_id: 定时任务id + :return: 定时任务id对应的信息 + """ + job = JobDao.get_job_detail_by_id(query_db, job_id=job_id) + result = JobModel(**CamelCaseUtil.transform_result(job)) + + return result + + @staticmethod + 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": "备注", + } + + 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_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_option_dict = {item.get('value'): item for item in job_executor_option} + + for item in data: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '暂停' + if str(item.get('jobGroup')) in job_group_option_dict.keys(): + 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') + if item.get('misfirePolicy') == '1': + item['misfirePolicy'] = '立即执行' + elif item.get('misfirePolicy') == '2': + item['misfirePolicy'] = '执行一次' + else: + item['misfirePolicy'] = '放弃执行' + if item.get('concurrent') == '0': + 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] + 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 new file mode 100644 index 0000000..a4546e5 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/log_service.py @@ -0,0 +1,242 @@ +from module_admin.dao.log_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 + + +class OperationLogService: + """ + 操作日志管理模块服务层 + """ + + @classmethod + def get_operation_log_list_services(cls, query_db: Session, query_object: OperLogQueryModel): + """ + 获取操作日志列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 操作日志列表信息对象 + """ + operation_log_list_result = OperationLogDao.get_operation_log_list(query_db, query_object) + + return CamelCaseUtil.transform_result(operation_log_list_result) + + @classmethod + def add_operation_log_services(cls, query_db: Session, page_object: OperLogModel): + """ + 新增操作日志service + :param query_db: orm对象 + :param page_object: 新增操作日志对象 + :return: 新增操作日志校验结果 + """ + try: + OperationLogDao.add_operation_log_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + result = dict(is_success=False, message=str(e)) + + return CrudResponseModel(**result) + + @classmethod + def delete_operation_log_services(cls, query_db: Session, page_object: DeleteOperLogModel): + """ + 删除操作日志信息service + :param query_db: orm对象 + :param page_object: 删除操作日志对象 + :return: 删除操作日志校验结果 + """ + if page_object.oper_ids.split(','): + oper_id_list = page_object.oper_ids.split(',') + try: + for oper_id in oper_id_list: + OperationLogDao.delete_operation_log_dao(query_db, OperLogModel(operId=oper_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入操作日志id为空') + return CrudResponseModel(**result) + + @classmethod + def clear_operation_log_services(cls, query_db: Session): + """ + 清除操作日志信息service + :param query_db: orm对象 + :return: 清除操作日志校验结果 + """ + try: + OperationLogDao.clear_operation_log_dao(query_db) + query_db.commit() + result = dict(is_success=True, message='清除成功') + except Exception as e: + 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": "消耗时间(毫秒)" + } + + 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_option_dict = {item.get('value'): item for item in operation_type_option} + + for item in data: + if item.get('status') == 0: + item['status'] = '成功' + else: + item['status'] = '失败' + 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] + binary_data = export_list2excel(new_data) + + return binary_data + + +class LoginLogService: + """ + 登录日志管理模块服务层 + """ + + @classmethod + def get_login_log_list_services(cls, query_db: Session, query_object: LoginLogQueryModel): + """ + 获取登录日志列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 登录日志列表信息对象 + """ + operation_log_list_result = LoginLogDao.get_login_log_list(query_db, query_object) + + return CamelCaseUtil.transform_result(operation_log_list_result) + + @classmethod + def add_login_log_services(cls, query_db: Session, page_object: LogininforModel): + """ + 新增登录日志service + :param query_db: orm对象 + :param page_object: 新增登录日志对象 + :return: 新增登录日志校验结果 + """ + try: + LoginLogDao.add_login_log_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + result = dict(is_success=False, message=str(e)) + + return CrudResponseModel(**result) + + @classmethod + def delete_login_log_services(cls, query_db: Session, page_object: DeleteLoginLogModel): + """ + 删除操作日志信息service + :param query_db: orm对象 + :param page_object: 删除操作日志对象 + :return: 删除操作日志校验结果 + """ + if page_object.info_ids.split(','): + info_id_list = page_object.info_ids.split(',') + try: + for info_id in info_id_list: + LoginLogDao.delete_login_log_dao(query_db, LogininforModel(infoId=info_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入登录日志id为空') + return CrudResponseModel(**result) + + @classmethod + def clear_login_log_services(cls, query_db: Session): + """ + 清除操作日志信息service + :param query_db: orm对象 + :return: 清除操作日志校验结果 + """ + try: + LoginLogDao.clear_login_log_dao(query_db) + query_db.commit() + result = dict(is_success=True, message='清除成功') + except Exception as e: + 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}") + if locked_user: + await request.app.state.redis.delete(f"account_lock:{unlock_user.user_name}") + result = dict(is_success=True, message='解锁成功') + else: + result = dict(is_success=False, message='该用户未锁定') + return CrudResponseModel(**result) + + @staticmethod + 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": "登录日期" + } + + data = login_log_list + + for item in data: + if item.get('status') == '0': + 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] + 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 new file mode 100644 index 0000000..ca1cee3 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/login_service.py @@ -0,0 +1,316 @@ +from fastapi import Request, Form +from fastapi import Depends +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from jose import JWTError, 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 config.env import JwtConfig, RedisInitKeyConfig +from utils.common_util import CamelCaseUtil +from utils.pwd_util import * +from utils.response_util import * +from utils.message_util import * +from config.get_db import get_db + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") + + +class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm): + """ + 自定义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) + ): + 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 + + +class LoginService: + """ + 登录模块服务层 + """ + @classmethod + async def authenticate_user(cls, request: Request, query_db: Session, login_user: UserLogin): + """ + 根据用户名密码校验用户登录 + :param request: Request对象 + :param query_db: orm对象 + :param login_user: 登录用户对象 + :return: 校验结果 + """ + account_lock = await request.app.state.redis.get( + f"{RedisInitKeyConfig.ACCOUNT_LOCK.get('key')}:{login_user.user_name}") + if login_user.user_name == account_lock: + logger.warning("账号已锁定,请稍后再试") + raise LoginException(data="", message="账号已锁定,请稍后再试") + # 判断是否开启验证码,开启则验证,否则不验证 + if login_user.captcha_enabled: + await cls.__check_login_captcha(request, login_user) + user = login_by_account(query_db, login_user.user_name) + if not user: + 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}") + 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)) + if password_error_count > 5: + await request.app.state.redis.delete( + f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('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="密码错误") + 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}") + return user + + @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}") + if not captcha_value: + logger.warning("验证码已失效") + raise LoginException(data="", message="验证码已失效") + if login_user.code != str(captcha_value): + logger.warning("验证码错误") + raise LoginException(data="", message="验证码错误") + return True + + @classmethod + 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 + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, JwtConfig.SECRET_KEY, algorithm=JwtConfig.ALGORITHM) + return encoded_jwt + + @classmethod + async def get_current_user(cls, request: Request = Request, token: str = Depends(oauth2_scheme), + query_db: Session = Depends(get_db)): + """ + 根据token获取当前用户信息 + :param request: Request对象 + :param token: 用户token + :param query_db: orm对象 + :return: 当前用户信息对象 + :raise: 令牌异常AuthException + """ + # if token[:6] != 'Bearer': + # logger.warning("用户token不合法") + # raise AuthException(data="", message="用户token不合法") + try: + if token.startswith('Bearer'): + token = token.split(' ')[1] + payload = jwt.decode(token, JwtConfig.SECRET_KEY, algorithms=[JwtConfig.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不合法") + token_data = TokenData(user_id=int(user_id)) + except JWTError: + logger.warning("用户token已失效,请重新登录") + raise AuthException(data="", message="用户token已失效,请重新登录") + query_user = 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不合法") + 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.get('key')}:{user.user_basic_info.user_id}") + if token == redis_token: + await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", redis_token, + ex=timedelta(minutes=JwtConfig.REDIS_TOKEN_EXPIRE_MINUTES)) + # await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{user.user_basic_info.user_id}", redis_token, + # ex=timedelta(minutes=JwtConfig.REDIS_TOKEN_EXPIRE_MINUTES)) + + role_id_list = [item.role_id for item in query_user.get('user_role_info')] + if 1 in role_id_list: + permissions = ['*:*:*'] + else: + permissions = [row.perms for row in query_user.get('user_menu_info')] + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + roles = [row.role_key for row in query_user.get('user_role_info')] + + current_user = CurrentUserModel( + permissions=permissions, + roles=roles, + user=UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + 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')) + ) + ) + return current_user + else: + logger.warning("用户token已失效,请重新登录") + raise AuthException(data="", message="用户token已失效,请重新登录") + + @classmethod + async def get_current_user_routers(cls, user_id: int, query_db: Session): + """ + 根据用户id获取当前用户路由信息 + :param user_id: 用户id + :param query_db: orm对象 + :return: 当前用户路由信息对象 + """ + query_user = UserDao.get_user_by_id(query_db, user_id=user_id) + user_router_menu = [row for row in query_user.get('user_menu_info') if row.menu_type in ['M', 'C']] + user_router = cls.__generate_user_router_menu(0, user_router_menu) + return user_router + + @classmethod + def __generate_user_router_menu(cls, pid: int, permission_list): + """ + 工具方法:根据菜单信息生成路由信息树形嵌套数据 + :param pid: 菜单id + :param permission_list: 菜单列表信息 + :return: 路由信息树形嵌套数据 + """ + router_list = [] + for permission in permission_list: + if permission.parent_id == pid: + children = cls.__generate_user_router_menu(permission.menu_id, permission_list) + router_list_data = {} + if permission.menu_type == 'M': + router_list_data['name'] = permission.path.capitalize() + router_list_data['path'] = f'/{permission.path}' + router_list_data['hidden'] = False if permission.visible == '0' else True + if permission.is_frame == 1: + router_list_data['redirect'] = 'noRedirect' + if permission.parent_id == 0: + router_list_data['component'] = 'Layout' + else: + router_list_data['component'] = 'ParentView' + if children: + router_list_data['alwaysShow'] = True + router_list_data['children'] = children + router_list_data['meta'] = { + 'title': permission.menu_name, + 'icon': permission.icon, + 'noCache': False if permission.is_cache == '0' else True, + 'link': permission.path if permission.is_frame == 0 else None + } + elif permission.menu_type == 'C': + router_list_data['name'] = permission.path.capitalize() + router_list_data['path'] = permission.path + router_list_data['hidden'] = False if permission.visible == '0' else True + router_list_data['component'] = permission.component + router_list_data['meta'] = { + 'title': permission.menu_name, + 'icon': permission.icon, + 'noCache': False if permission.is_cache == '0' else True, + 'link': permission.path if permission.is_frame == 0 else None + } + router_list.append(router_list_data) + + return router_list + + +async def get_sms_code_services(request: Request, query_db: Session, 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}") + if redis_sms_result: + return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='短信验证码仍在有效期内')) + is_user = 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)) + # 此处模拟调用短信服务 + message_service(sms_code) + + return SmsCode(**dict(is_success=True, sms_code=sms_code, session_id=session_id, message='获取成功')) + + return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='用户不存在')) + + +async def forget_user_services(request: Request, query_db: Session, 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}") + if forget_user.sms_code == redis_sms_result: + forget_user.password = PwdUtil.get_password_hash(forget_user.password) + forget_user.user_id = UserDao.get_user_by_name(query_db, forget_user.user_name).user_id + edit_result = UserService.reset_user_services(query_db, forget_user) + result = edit_result.dict() + 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}") + result = dict(is_success=False, message='短信验证码不正确') + + return CrudResponseModel(**result) + + +async def logout_services(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'{current_user.user.user_id}_access_token') + # await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id') + + return True diff --git a/ruoyi-fastapi-backend/module_admin/service/menu_service.py b/ruoyi-fastapi-backend/module_admin/service/menu_service.py new file mode 100644 index 0000000..10a295d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/menu_service.py @@ -0,0 +1,171 @@ +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 module_admin.dao.role_dao import RoleDao +from module_admin.dao.menu_dao import * +from utils.common_util import CamelCaseUtil + + +class MenuService: + """ + 菜单管理模块服务层 + """ + + @classmethod + def get_menu_tree_services(cls, query_db: Session, current_user: Optional[CurrentUserModel] = None): + """ + 获取菜单树信息service + :param query_db: orm对象 + :param current_user: 当前用户对象 + :return: 菜单树信息对象 + """ + menu_list_result = 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 + def get_role_menu_tree_services(cls, query_db: Session, 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 = 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 = RoleDao.get_role_menu_dao(query_db, role_id) + checked_keys = [row.menu_id for row in role_menu_list] + result = RoleMenuQueryModel( + menus=menu_tree_result, + checkedKeys=checked_keys + ) + + return result + + @classmethod + def get_menu_list_services(cls, query_db: Session, page_object: MenuQueryModel, current_user: Optional[CurrentUserModel] = None): + """ + 获取菜单列表信息service + :param query_db: orm对象 + :param page_object: 分页查询参数对象 + :param current_user: 当前用户对象 + :return: 菜单列表信息对象 + """ + menu_list_result = 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 + def add_menu_services(cls, query_db: Session, page_object: MenuModel): + """ + 新增菜单信息service + :param query_db: orm对象 + :param page_object: 新增菜单对象 + :return: 新增菜单校验结果 + """ + menu = 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='同一目录下不允许存在同名同类型的菜单') + else: + try: + MenuDao.add_menu_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + def edit_menu_services(cls, query_db: Session, page_object: MenuModel): + """ + 编辑菜单信息service + :param query_db: orm对象 + :param page_object: 编辑部门对象 + :return: 编辑菜单校验结果 + """ + edit_menu = page_object.model_dump(exclude_unset=True) + menu_info = 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 = 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: + MenuDao.edit_menu_dao(query_db, edit_menu) + query_db.commit() + result = dict(is_success=True, message='更新成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='菜单不存在') + + return CrudResponseModel(**result) + + @classmethod + def delete_menu_services(cls, query_db: Session, page_object: DeleteMenuModel): + """ + 删除菜单信息service + :param query_db: orm对象 + :param page_object: 删除菜单对象 + :return: 删除菜单校验结果 + """ + if page_object.menu_ids.split(','): + menu_id_list = page_object.menu_ids.split(',') + try: + for menu_id in menu_id_list: + MenuDao.delete_menu_dao(query_db, MenuModel(menuId=menu_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入菜单id为空') + return CrudResponseModel(**result) + + @classmethod + def menu_detail_services(cls, query_db: Session, menu_id: int): + """ + 获取菜单详细信息service + :param query_db: orm对象 + :param menu_id: 菜单id + :return: 菜单id对应的信息 + """ + menu = MenuDao.get_menu_detail_by_id(query_db, menu_id=menu_id) + result = MenuModel(**CamelCaseUtil.transform_result(menu)) + + return result + + @classmethod + 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] + # 转成id为key的字典 + mapping: dict = dict(zip([i['id'] for i in permission_list], permission_list)) + + # 树容器 + container: list = [] + + for d in permission_list: + # 如果找不到父级项,则是根节点 + parent: dict = mapping.get(d['parentId']) + if parent is None: + container.append(d) + else: + children: list = parent.get('children') + if not children: + children = [] + children.append(d) + parent.update({'children': children}) + + return container diff --git a/ruoyi-fastapi-backend/module_admin/service/notice_service.py b/ruoyi-fastapi-backend/module_admin/service/notice_service.py new file mode 100644 index 0000000..3b393be --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/notice_service.py @@ -0,0 +1,106 @@ +from module_admin.dao.notice_dao import * +from module_admin.entity.vo.common_vo import CrudResponseModel +from utils.common_util import export_list2excel, CamelCaseUtil + + +class NoticeService: + """ + 通知公告管理模块服务层 + """ + + @classmethod + def get_notice_list_services(cls, query_db: Session, query_object: NoticeQueryModel): + """ + 获取通知公告列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 通知公告列表信息对象 + """ + notice_list_result = NoticeDao.get_notice_list(query_db, query_object) + + return CamelCaseUtil.transform_result(notice_list_result) + + @classmethod + def add_notice_services(cls, query_db: Session, page_object: NoticeModel): + """ + 新增通知公告信息service + :param query_db: orm对象 + :param page_object: 新增通知公告对象 + :return: 新增通知公告校验结果 + """ + notice = NoticeDao.get_notice_detail_by_info(query_db, page_object) + if notice: + result = dict(is_success=False, message='通知公告已存在') + else: + try: + NoticeDao.add_notice_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + def edit_notice_services(cls, query_db: Session, page_object: NoticeModel): + """ + 编辑通知公告信息service + :param query_db: orm对象 + :param page_object: 编辑通知公告对象 + :return: 编辑通知公告校验结果 + """ + edit_notice = page_object.model_dump(exclude_unset=True) + notice_info = 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 = NoticeDao.get_notice_detail_by_info(query_db, page_object) + if notice: + result = dict(is_success=False, message='通知公告已存在') + return CrudResponseModel(**result) + try: + NoticeDao.edit_notice_dao(query_db, edit_notice) + query_db.commit() + result = dict(is_success=True, message='更新成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='通知公告不存在') + + return CrudResponseModel(**result) + + @classmethod + def delete_notice_services(cls, query_db: Session, page_object: DeleteNoticeModel): + """ + 删除通知公告信息service + :param query_db: orm对象 + :param page_object: 删除通知公告对象 + :return: 删除通知公告校验结果 + """ + if page_object.notice_ids.split(','): + notice_id_list = page_object.notice_ids.split(',') + try: + for notice_id in notice_id_list: + NoticeDao.delete_notice_dao(query_db, NoticeModel(noticeId=notice_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入通知公告id为空') + return CrudResponseModel(**result) + + @classmethod + def notice_detail_services(cls, query_db: Session, notice_id: int): + """ + 获取通知公告详细信息service + :param query_db: orm对象 + :param notice_id: 通知公告id + :return: 通知公告id对应的信息 + """ + notice = NoticeDao.get_notice_detail_by_id(query_db, notice_id=notice_id) + result = NoticeModel(**CamelCaseUtil.transform_result(notice)) + + return result diff --git a/ruoyi-fastapi-backend/module_admin/service/online_service.py b/ruoyi-fastapi-backend/module_admin/service/online_service.py new file mode 100644 index 0000000..3839c58 --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/online_service.py @@ -0,0 +1,71 @@ +from fastapi import Request +from jose import jwt +from config.env import JwtConfig, RedisInitKeyConfig +from module_admin.entity.vo.online_vo import * +from module_admin.entity.vo.common_vo import CrudResponseModel +from utils.common_util import CamelCaseUtil + + +class OnlineService: + """ + 在线用户管理模块服务层 + """ + + @classmethod + 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')}*") + 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] + online_info_list = [] + for item in access_token_values_list: + payload = jwt.decode(item, JwtConfig.SECRET_KEY, algorithms=[JwtConfig.ALGORITHM]) + online_dict = dict( + token_id=payload.get('session_id'), + user_name=payload.get('user_name'), + dept_name=payload.get('dept_name'), + ipaddr=payload.get('login_info').get('ipaddr'), + login_location=payload.get('login_info').get('loginLocation'), + browser=payload.get('login_info').get('browser'), + os=payload.get('login_info').get('os'), + login_time=payload.get('login_info').get('loginTime') + ) + if query_object.user_name and not query_object.ipaddr: + if query_object.user_name == payload.get('login_info').get('ipaddr'): + online_info_list = [online_dict] + break + elif not query_object.user_name and query_object.ipaddr: + if query_object.ipaddr == payload.get('ipaddr'): + 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'): + online_info_list = [online_dict] + break + else: + online_info_list.append(online_dict) + + return CamelCaseUtil.transform_result(online_info_list) + + @classmethod + 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(','): + 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='强退成功') + else: + result = dict(is_success=False, message='传入session_id为空') + return CrudResponseModel(**result) diff --git a/ruoyi-fastapi-backend/module_admin/service/post_service.py b/ruoyi-fastapi-backend/module_admin/service/post_service.py new file mode 100644 index 0000000..bc6f21f --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/post_service.py @@ -0,0 +1,138 @@ +from module_admin.dao.post_dao import * +from module_admin.entity.vo.common_vo import CrudResponseModel +from utils.common_util import export_list2excel, CamelCaseUtil + + +class PostService: + """ + 岗位管理模块服务层 + """ + @classmethod + def get_post_list_services(cls, query_db: Session, query_object: PostModel): + """ + 获取岗位列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 岗位列表信息对象 + """ + post_list_result = PostDao.get_post_list(query_db, query_object) + + return CamelCaseUtil.transform_result(post_list_result) + + @classmethod + def add_post_services(cls, query_db: Session, page_object: PostModel): + """ + 新增岗位信息service + :param query_db: orm对象 + :param page_object: 新增岗位对象 + :return: 新增岗位校验结果 + """ + post = PostDao.get_post_detail_by_info(query_db, PostModel(postName=page_object.post_name)) + if post: + result = dict(is_success=False, message='岗位名称已存在') + else: + try: + PostDao.add_post_dao(query_db, page_object) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + def edit_post_services(cls, query_db: Session, page_object: PostModel): + """ + 编辑岗位信息service + :param query_db: orm对象 + :param page_object: 编辑岗位对象 + :return: 编辑岗位校验结果 + """ + edit_post = page_object.model_dump(exclude_unset=True) + post_info = cls.post_detail_services(query_db, edit_post.get('post_id')) + if post_info: + if post_info.post_name != page_object.post_name: + post = 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: + PostDao.edit_post_dao(query_db, edit_post) + query_db.commit() + result = dict(is_success=True, message='更新成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='岗位不存在') + + return CrudResponseModel(**result) + + @classmethod + def delete_post_services(cls, query_db: Session, page_object: DeletePostModel): + """ + 删除岗位信息service + :param query_db: orm对象 + :param page_object: 删除岗位对象 + :return: 删除岗位校验结果 + """ + if page_object.post_ids.split(','): + post_id_list = page_object.post_ids.split(',') + try: + for post_id in post_id_list: + PostDao.delete_post_dao(query_db, PostModel(postId=post_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入岗位id为空') + return CrudResponseModel(**result) + + @classmethod + def post_detail_services(cls, query_db: Session, post_id: int): + """ + 获取岗位详细信息service + :param query_db: orm对象 + :param post_id: 岗位id + :return: 岗位id对应的信息 + """ + post = PostDao.get_post_detail_by_id(query_db, post_id=post_id) + result = PostModel(**CamelCaseUtil.transform_result(post)) + + return result + + @staticmethod + 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": "备注", + } + + data = post_list + + for item in data: + if item.get('status') == '0': + 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] + 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 new file mode 100644 index 0000000..cf95b6d --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/role_service.py @@ -0,0 +1,256 @@ +from module_admin.entity.vo.user_vo import UserInfoModel, UserRoleQueryModel +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.user_dao import UserDao +from module_admin.dao.role_dao import * +from utils.common_util import export_list2excel, CamelCaseUtil + + +class RoleService: + """ + 角色管理模块服务层 + """ + + @classmethod + def get_role_select_option_services(cls, query_db: Session): + """ + 获取角色列表不分页信息service + :param query_db: orm对象 + :return: 角色列表不分页信息对象 + """ + role_list_result = RoleDao.get_role_select_option_dao(query_db) + + return CamelCaseUtil.transform_result(role_list_result) + + @classmethod + def get_role_dept_tree_services(cls, query_db: Session, role_id: int): + """ + 根据角色id获取部门树信息service + :param query_db: orm对象 + :param role_id: 角色id + :return: 当前角色id的部门树信息对象 + """ + role_dept_list = RoleDao.get_role_dept_dao(query_db, role_id) + checked_keys = [row.dept_id for row in role_dept_list] + result = RoleDeptQueryModel( + checkedKeys=checked_keys + ) + + return result + + @classmethod + def get_role_list_services(cls, query_db: Session, query_object: RoleQueryModel): + """ + 获取角色列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :return: 角色列表信息对象 + """ + role_list_result = RoleDao.get_role_list(query_db, query_object) + + return CamelCaseUtil.transform_result(role_list_result) + + @classmethod + def add_role_services(cls, query_db: Session, page_object: AddRoleModel): + """ + 新增角色信息service + :param query_db: orm对象 + :param page_object: 新增角色对象 + :return: 新增角色校验结果 + """ + add_role = RoleModel(**page_object.model_dump(by_alias=True)) + role_name = RoleDao.get_role_by_info(query_db, RoleModel(roleName=page_object.role_name)) + role_key = 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='权限字符已存在') + else: + try: + add_result = RoleDao.add_role_dao(query_db, add_role) + role_id = add_result.role_id + if page_object.menu_ids: + for menu in page_object.menu_ids: + RoleDao.add_role_menu_dao(query_db, RoleMenuModel(roleId=role_id, menuId=menu)) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + def edit_role_services(cls, query_db: Session, page_object: AddRoleModel): + """ + 编辑角色信息service + :param query_db: orm对象 + :param page_object: 编辑角色对象 + :return: 编辑角色校验结果 + """ + edit_role = page_object.model_dump(exclude_unset=True) + if page_object.type != 'status': + del edit_role['menu_ids'] + if page_object.type == 'status': + del edit_role['type'] + role_info = 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 = 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 = 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) + try: + RoleDao.edit_role_dao(query_db, edit_role) + if page_object.type != 'status': + 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: + RoleDao.add_role_menu_dao(query_db, RoleMenuModel(roleId=page_object.role_id, menuId=menu)) + query_db.commit() + result = dict(is_success=True, message='更新成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='角色不存在') + + return CrudResponseModel(**result) + + @classmethod + def role_datascope_services(cls, query_db: Session, page_object: AddRoleModel): + """ + 分配角色数据权限service + :param query_db: orm对象 + :param page_object: 角色数据权限对象 + :return: 分配角色数据权限结果 + """ + edit_role = page_object.model_dump(exclude_unset=True) + del edit_role['dept_ids'] + role_info = 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 = 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 = 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) + try: + RoleDao.edit_role_dao(query_db, edit_role) + 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: + RoleDao.add_role_dept_dao(query_db, RoleDeptModel(roleId=page_object.role_id, deptId=dept)) + query_db.commit() + result = dict(is_success=True, message='分配成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='角色不存在') + + return CrudResponseModel(**result) + + @classmethod + def delete_role_services(cls, query_db: Session, page_object: DeleteRoleModel): + """ + 删除角色信息service + :param query_db: orm对象 + :param page_object: 删除角色对象 + :return: 删除角色校验结果 + """ + if page_object.role_ids.split(','): + 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) + RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(**role_id_dict)) + RoleDao.delete_role_dao(query_db, RoleModel(**role_id_dict)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入角色id为空') + return CrudResponseModel(**result) + + @classmethod + def role_detail_services(cls, query_db: Session, role_id: int): + """ + 获取角色详细信息service + :param query_db: orm对象 + :param role_id: 角色id + :return: 角色id对应的信息 + """ + role = RoleDao.get_role_detail_by_id(query_db, role_id=role_id) + result = RoleModel(**CamelCaseUtil.transform_result(role)) + + return result + + @staticmethod + 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": "备注", + } + + data = role_list + + for item in data: + if item.get('status') == '0': + 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] + binary_data = export_list2excel(new_data) + + return binary_data + + @classmethod + def get_role_user_allocated_list_services(cls, query_db: Session, page_object: UserRoleQueryModel): + """ + 根据角色id获取已分配用户列表 + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :return: 已分配用户列表 + """ + query_user_list = UserDao.get_user_role_allocated_list_by_role_id(query_db, page_object) + allocated_list = [UserInfoModel(**CamelCaseUtil.transform_result(row)) for row in query_user_list] + + return allocated_list + + @classmethod + def get_role_user_unallocated_list_services(cls, query_db: Session, page_object: UserRoleQueryModel): + """ + 根据角色id获取未分配用户列表 + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :return: 未分配用户列表 + """ + query_user_list = UserDao.get_user_role_unallocated_list_by_role_id(query_db, page_object) + unallocated_list = [UserInfoModel(**CamelCaseUtil.transform_result(row)) for row in query_user_list] + + return unallocated_list diff --git a/ruoyi-fastapi-backend/module_admin/service/server_service.py b/ruoyi-fastapi-backend/module_admin/service/server_service.py new file mode 100644 index 0000000..760823b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/server_service.py @@ -0,0 +1,94 @@ +import psutil +from utils.common_util import bytes2human +import platform +import socket +import os +import time +from module_admin.entity.vo.server_vo import * + + +class ServerService: + """ + 服务监控模块服务层 + """ + + @staticmethod + def get_server_monitor_info(): + # CPU信息 + # 获取CPU总核心数 + cpu_num = psutil.cpu_count(logical=True) + cpu_usage_percent = psutil.cpu_times_percent() + cpu_used = cpu_usage_percent.user + cpu_sys = cpu_usage_percent.system + cpu_free = cpu_usage_percent.idle + cpu = CpuInfo(cpuNum=cpu_num, used=cpu_used, sys=cpu_sys, free=cpu_free) + + # 内存信息 + memory_info = psutil.virtual_memory() + memory_total = bytes2human(memory_info.total) + memory_used = bytes2human(memory_info.used) + memory_free = bytes2human(memory_info.free) + memory_usage = memory_info.percent + mem = MemoryInfo(total=memory_total, used=memory_used, free=memory_free, usage=memory_usage) + + # 主机信息 + # 获取主机名 + hostname = socket.gethostname() + # 获取IP + computer_ip = socket.gethostbyname(hostname) + os_name = platform.platform() + 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) + + # python解释器信息 + current_pid = os.getpid() + current_process = psutil.Process(current_pid) + python_name = current_process.name() + 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)) + 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}分钟" + # 获取当前Python程序的pid + pid = os.getpid() + # 获取该进程的内存信息 + current_process_memory_info = psutil.Process(pid).memory_info() + py = PyInfo( + name=python_name, + version=python_version, + startTime=start_time, + runTime=run_time, + home=python_home, + 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) + ) + + # 磁盘信息 + io = psutil.disk_partitions() + sys_files = [] + for i in io: + o = psutil.disk_usage(i.device) + disk_data = SysFiles( + dirName=i.device, + sysTypeName=i.fstype, + typeName="本地固定磁盘(" + i.mountpoint.replace('\\', '') + ")", + total=bytes2human(o.total), + used=bytes2human(o.used), + free=bytes2human(o.free), + usage=f'{psutil.disk_usage(i.device).percent}%' + ) + sys_files.append(disk_data) + + result = ServerMonitorModel(cpu=cpu, mem=mem, sys=sys, py=py, sysFiles=sys_files) + + return result diff --git a/ruoyi-fastapi-backend/module_admin/service/user_service.py b/ruoyi-fastapi-backend/module_admin/service/user_service.py new file mode 100644 index 0000000..65c5c2b --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/user_service.py @@ -0,0 +1,475 @@ +from fastapi import UploadFile +from module_admin.service.role_service import RoleService +from module_admin.service.post_service import PostService +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.dao.user_dao import * +from utils.pwd_util import * +from utils.common_util import * + + +class UserService: + """ + 用户管理模块服务层 + """ + + @classmethod + def get_user_list_services(cls, query_db: Session, query_object: UserQueryModel, data_scope_sql: str): + """ + 获取用户列表信息service + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 用户列表信息对象 + """ + query_result = UserDao.get_user_list(query_db, query_object, data_scope_sql) + user_list_result = [] + if query_result: + user_list_result = [{**CamelCaseUtil.transform_result(row[0]), 'dept': CamelCaseUtil.transform_result(row[1])} for row in query_result] + + return user_list_result + + @classmethod + def add_user_services(cls, query_db: Session, page_object: AddUserModel): + """ + 新增用户信息service + :param query_db: orm对象 + :param page_object: 新增用户对象 + :return: 新增用户校验结果 + """ + add_user = UserModel(**page_object.model_dump(by_alias=True)) + user = UserDao.get_user_by_info(query_db, UserModel(userName=page_object.user_name)) + if user: + result = dict(is_success=False, message='用户名已存在') + else: + try: + add_result = UserDao.add_user_dao(query_db, add_user) + user_id = add_result.user_id + if page_object.role_ids: + for role in page_object.role_ids: + UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=role)) + if page_object.post_ids: + for post in page_object.post_ids: + UserDao.add_user_post_dao(query_db, UserPostModel(userId=user_id, postId=post)) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + def edit_user_services(cls, query_db: Session, page_object: EditUserModel): + """ + 编辑用户信息service + :param query_db: orm对象 + :param page_object: 编辑用户对象 + :return: 编辑用户校验结果 + """ + edit_user = page_object.model_dump(exclude_unset=True) + if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + del edit_user['role_ids'] + del edit_user['post_ids'] + del edit_user['role'] + if page_object.type == 'status' or page_object.type == 'avatar' or page_object.type == 'pwd': + del edit_user['type'] + user_info = 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 = UserDao.get_user_by_info(query_db, UserModel(userName=page_object.user_name)) + if user: + result = dict(is_success=False, message='用户名已存在') + return CrudResponseModel(**result) + try: + UserDao.edit_user_dao(query_db, edit_user) + if page_object.type != 'status' and page_object.type != 'avatar': + UserDao.delete_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) + 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: + 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: + UserDao.add_user_post_dao(query_db, UserPostModel(userId=page_object.user_id, postId=post)) + query_db.commit() + result = dict(is_success=True, message='更新成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='用户不存在') + + return CrudResponseModel(**result) + + @classmethod + def delete_user_services(cls, query_db: Session, page_object: DeleteUserModel): + """ + 删除用户信息service + :param query_db: orm对象 + :param page_object: 删除用户对象 + :return: 删除用户校验结果 + """ + if page_object.user_ids.split(','): + 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) + UserDao.delete_user_role_dao(query_db, UserRoleModel(**user_id_dict)) + UserDao.delete_user_post_dao(query_db, UserPostModel(**user_id_dict)) + UserDao.delete_user_dao(query_db, UserModel(**user_id_dict)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='传入用户id为空') + return CrudResponseModel(**result) + + @classmethod + def user_detail_services(cls, query_db: Session, user_id: Union[int, str]): + """ + 获取用户详细信息service + :param query_db: orm对象 + :param user_id: 用户id + :return: 用户id对应的信息 + """ + posts = PostService.get_post_list_services(query_db, PostModel(**{})) + roles = RoleService.get_role_select_option_services(query_db) + if user_id != '': + query_user = UserDao.get_user_detail_by_id(query_db, user_id=user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + post_ids_list = [row.post_id for row in query_user.get('user_post_info')] + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + role_ids_list = [row.role_id for row in query_user.get('user_role_info')] + + return UserDetailModel( + data=UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + 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')) + ), + postIds=post_ids_list, + posts=posts, + roleIds=role_ids_list, + roles=roles + ) + + return UserDetailModel( + posts=posts, + roles=roles + ) + + @classmethod + def user_profile_services(cls, query_db: Session, user_id: int): + """ + 获取用户详细信息service + :param query_db: orm对象 + :param user_id: 用户id + :return: 用户id对应的信息 + """ + query_user = UserDao.get_user_detail_by_id(query_db, user_id=user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + post_group = ','.join([row.post_name for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + role_group = ','.join([row.role_name for row in query_user.get('user_role_info')]) + + return UserProfileModel( + data=UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + 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')) + ), + postGroup=post_group, + roleGroup=role_group + ) + + @classmethod + def reset_user_services(cls, query_db: Session, page_object: ResetUserModel): + """ + 重置用户密码service + :param query_db: orm对象 + :param page_object: 重置用户对象 + :return: 重置用户校验结果 + """ + reset_user = page_object.model_dump(exclude_unset=True) + if page_object.old_password: + user = 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) + 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: + UserDao.edit_user_dao(query_db, reset_user) + query_db.commit() + result = dict(is_success=True, message='重置成功') + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @classmethod + async def batch_import_user_services(cls, query_db: Session, file: UploadFile, update_support: bool, current_user: CurrentUserModel): + """ + 批量导入用户service + :param query_db: orm对象 + :param file: 用户导入文件对象 + :param update_support: 用户存在时是否更新 + :param current_user: 当前用户对象 + :return: 批量导入用户结果 + """ + header_dict = { + "部门编号": "dept_id", + "登录名称": "user_name", + "用户名称": "nick_name", + "用户邮箱": "email", + "手机号码": "phonenumber", + "用户性别": "sex", + "帐号状态": "status" + } + contents = await file.read() + df = pd.read_excel(io.BytesIO(contents)) + await file.close() + df.rename(columns=header_dict, inplace=True) + add_error_result = [] + count = 0 + try: + for index, row in df.iterrows(): + count = count + 1 + if row['sex'] == '男': + row['sex'] = '0' + if row['sex'] == '女': + row['sex'] = '1' + if row['sex'] == '未知': + row['sex'] = '2' + if row['status'] == '正常': + row['status'] = '0' + if row['status'] == '停用': + row['status'] = '1' + add_user = UserModel( + deptId=row['dept_id'], + userName=row['user_name'], + password=PwdUtil.get_password_hash('123456'), + nickName=row['nick_name'], + email=row['email'], + phonenumber=str(row['phonenumber']), + sex=row['sex'], + status=row['status'], + createBy=current_user.user.user_name, + updateBy=current_user.user.user_name + ) + user_info = UserDao.get_user_by_info(query_db, UserModel(userName=row['user_name'])) + if user_info: + if update_support: + edit_user = UserModel( + userId=user_info.user_id, + deptId=row['dept_id'], + userName=row['user_name'], + nickName=row['nick_name'], + email=row['email'], + phonenumber=str(row['phonenumber']), + sex=row['sex'], + status=row['status'], + updateBy=current_user.user.user_name + ).model_dump(exclude_unset=True) + UserDao.edit_user_dao(query_db, edit_user) + else: + add_error_result.append(f"{count}.用户账号{row['user_name']}已存在") + else: + UserDao.add_user_dao(query_db, add_user) + query_db.commit() + result = dict(is_success=True, message='\n'.join(add_error_result)) + except Exception as e: + query_db.rollback() + raise e + + return CrudResponseModel(**result) + + @staticmethod + 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) + + return binary_data + + @staticmethod + 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": "备注", + } + + data = user_list + + for item in data: + if item.get('status') == '0': + item['status'] = '正常' + else: + item['status'] = '停用' + if item.get('sex') == '0': + item['sex'] = '男' + elif item.get('sex') == '1': + 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] + binary_data = export_list2excel(new_data) + + return binary_data + + @classmethod + def get_user_role_allocated_list_services(cls, query_db: Session, page_object: UserRoleQueryModel): + """ + 根据用户id获取已分配角色列表 + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :return: 已分配角色列表 + """ + query_user = UserDao.get_user_detail_by_id(query_db, page_object.user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + user = UserInfoModel( + **CamelCaseUtil.transform_result(query_user.get('user_basic_info')), + 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')) + ) + query_role_list = [SelectedRoleModel(**row) for row in 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 + ) + + return result + + @classmethod + def add_user_role_services(cls, query_db: Session, page_object: CrudUserRoleModel): + """ + 新增用户关联角色信息service + :param query_db: orm对象 + :param page_object: 新增用户关联角色对象 + :return: 新增用户关联角色校验结果 + """ + if page_object.user_id and page_object.role_ids: + role_id_list = page_object.role_ids.split(',') + try: + for role_id in role_id_list: + user_role = cls.detail_user_role_services(query_db, UserRoleModel(userId=page_object.user_id, roleId=role_id)) + if user_role: + continue + else: + UserDao.add_user_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=role_id)) + query_db.commit() + result = dict(is_success=True, message='分配成功') + except Exception as e: + query_db.rollback() + raise e + elif page_object.user_id and not page_object.role_ids: + try: + UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id)) + query_db.commit() + result = dict(is_success=True, message='分配成功') + except Exception as e: + query_db.rollback() + raise e + elif page_object.user_ids and page_object.role_id: + user_id_list = page_object.user_ids.split(',') + try: + for user_id in user_id_list: + user_role = cls.detail_user_role_services(query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)) + if user_role: + continue + else: + UserDao.add_user_role_dao(query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)) + query_db.commit() + result = dict(is_success=True, message='新增成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='不满足新增条件') + + return CrudResponseModel(**result) + + @classmethod + def delete_user_role_services(cls, query_db: Session, page_object: CrudUserRoleModel): + """ + 删除用户关联角色信息service + :param query_db: orm对象 + :param page_object: 删除用户关联角色对象 + :return: 删除用户关联角色校验结果 + """ + 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: + UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=page_object.user_id, roleId=page_object.role_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + elif page_object.user_ids and page_object.role_id: + user_id_list = page_object.user_ids.split(',') + try: + for user_id in user_id_list: + UserDao.delete_user_role_by_user_and_role_dao(query_db, UserRoleModel(userId=user_id, roleId=page_object.role_id)) + query_db.commit() + result = dict(is_success=True, message='删除成功') + except Exception as e: + query_db.rollback() + raise e + else: + result = dict(is_success=False, message='不满足删除条件') + else: + result = dict(is_success=False, message='传入用户角色关联信息为空') + + return CrudResponseModel(**result) + + @classmethod + def detail_user_role_services(cls, query_db: Session, page_object: UserRoleModel): + """ + 获取用户关联角色详细信息service + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :return: 用户关联角色详细信息 + """ + user_role = UserDao.get_user_role_detail(query_db, page_object) + + return user_role diff --git a/ruoyi-fastapi-backend/module_task/__init__.py b/ruoyi-fastapi-backend/module_task/__init__.py new file mode 100644 index 0000000..36fefe9 --- /dev/null +++ b/ruoyi-fastapi-backend/module_task/__init__.py @@ -0,0 +1 @@ +from . import scheduler_test diff --git a/ruoyi-fastapi-backend/module_task/scheduler_test.py b/ruoyi-fastapi-backend/module_task/scheduler_test.py new file mode 100644 index 0000000..b22dc6f --- /dev/null +++ b/ruoyi-fastapi-backend/module_task/scheduler_test.py @@ -0,0 +1,7 @@ +from datetime import datetime + + +def job(*args, **kwargs): + print(args) + print(kwargs) + print(f"{datetime.now()}执行了") diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql new file mode 100644 index 0000000..6380c98 --- /dev/null +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql @@ -0,0 +1,711 @@ +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept; +create table sys_dept ( + dept_id bigint(20) not null auto_increment comment '部门id', + parent_id bigint(20) default 0 comment '父部门id', + ancestors varchar(50) default '' comment '祖级列表', + dept_name varchar(30) default '' comment '部门名称', + order_num int(4) default 0 comment '显示顺序', + leader varchar(20) default null comment '负责人', + phone varchar(11) default null comment '联系电话', + email varchar(50) default null comment '邮箱', + status char(1) default '0' comment '部门状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (dept_id) +) engine=innodb auto_increment=200 comment = '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept values(100, 0, '0', '集团总公司', 0, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(101, 100, '0,100', '深圳分公司', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); + + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user; +create table sys_user ( + user_id bigint(20) not null auto_increment comment '用户ID', + dept_id bigint(20) default null comment '部门ID', + user_name varchar(30) not null comment '用户账号', + nick_name varchar(30) not null comment '用户昵称', + user_type varchar(2) default '00' comment '用户类型(00系统用户)', + email varchar(50) default '' comment '用户邮箱', + phonenumber varchar(11) default '' comment '手机号码', + sex char(1) default '0' comment '用户性别(0男 1女 2未知)', + avatar varchar(100) default '' comment '头像地址', + password varchar(100) default '' comment '密码', + status char(1) default '0' comment '帐号状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + login_ip varchar(128) default '' comment '最后登录IP', + login_date datetime comment '最后登录时间', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (user_id) +) engine=innodb auto_increment=100 comment = '用户信息表'; + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员'); +insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员'); + + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post; +create table sys_post +( + post_id bigint(20) not null auto_increment comment '岗位ID', + post_code varchar(64) not null comment '岗位编码', + post_name varchar(50) not null comment '岗位名称', + post_sort int(4) not null comment '显示顺序', + status char(1) not null comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (post_id) +) engine=innodb comment = '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role; +create table sys_role ( + role_id bigint(20) not null auto_increment comment '角色ID', + role_name varchar(30) not null comment '角色名称', + role_key varchar(100) not null comment '角色权限字符串', + role_sort int(4) not null comment '显示顺序', + data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示', + dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示', + status char(1) not null comment '角色状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (role_id) +) engine=innodb auto_increment=100 comment = '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role values('1', '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); +insert into sys_role values('2', '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色'); + + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu; +create table sys_menu ( + menu_id bigint(20) not null auto_increment comment '菜单ID', + menu_name varchar(50) not null comment '菜单名称', + parent_id bigint(20) default 0 comment '父菜单ID', + order_num int(4) default 0 comment '显示顺序', + path varchar(200) default '' comment '路由地址', + component varchar(255) default null comment '组件路径', + query varchar(255) default null 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按钮)', + visible char(1) default 0 comment '菜单状态(0显示 1隐藏)', + status char(1) default 0 comment '菜单状态(0正常 1停用)', + perms varchar(100) default null comment '权限标识', + icon varchar(100) default '#' comment '菜单图标', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注', + primary key (menu_id) +) engine=innodb auto_increment=2000 comment = '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +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('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('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('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('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('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('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('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, ''); + + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role; +create table sys_user_role ( + user_id bigint(20) not null comment '用户ID', + role_id bigint(20) not null comment '角色ID', + primary key(user_id, role_id) +) engine=innodb comment = '用户和角色关联表'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role values ('1', '1'); +insert into sys_user_role values ('2', '2'); + + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu; +create table sys_role_menu ( + role_id bigint(20) not null comment '角色ID', + menu_id bigint(20) not null comment '菜单ID', + primary key(role_id, menu_id) +) engine=innodb comment = '角色和菜单关联表'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu values ('2', '1'); +insert into sys_role_menu values ('2', '2'); +insert into sys_role_menu values ('2', '3'); +insert into sys_role_menu values ('2', '4'); +insert into sys_role_menu values ('2', '100'); +insert into sys_role_menu values ('2', '101'); +insert into sys_role_menu values ('2', '102'); +insert into sys_role_menu values ('2', '103'); +insert into sys_role_menu values ('2', '104'); +insert into sys_role_menu values ('2', '105'); +insert into sys_role_menu values ('2', '106'); +insert into sys_role_menu values ('2', '107'); +insert into sys_role_menu values ('2', '108'); +insert into sys_role_menu values ('2', '109'); +insert into sys_role_menu values ('2', '110'); +insert into sys_role_menu values ('2', '111'); +insert into sys_role_menu values ('2', '112'); +insert into sys_role_menu values ('2', '113'); +insert into sys_role_menu values ('2', '114'); +insert into sys_role_menu values ('2', '115'); +insert into sys_role_menu values ('2', '116'); +insert into sys_role_menu values ('2', '117'); +insert into sys_role_menu values ('2', '500'); +insert into sys_role_menu values ('2', '501'); +insert into sys_role_menu values ('2', '1000'); +insert into sys_role_menu values ('2', '1001'); +insert into sys_role_menu values ('2', '1002'); +insert into sys_role_menu values ('2', '1003'); +insert into sys_role_menu values ('2', '1004'); +insert into sys_role_menu values ('2', '1005'); +insert into sys_role_menu values ('2', '1006'); +insert into sys_role_menu values ('2', '1007'); +insert into sys_role_menu values ('2', '1008'); +insert into sys_role_menu values ('2', '1009'); +insert into sys_role_menu values ('2', '1010'); +insert into sys_role_menu values ('2', '1011'); +insert into sys_role_menu values ('2', '1012'); +insert into sys_role_menu values ('2', '1013'); +insert into sys_role_menu values ('2', '1014'); +insert into sys_role_menu values ('2', '1015'); +insert into sys_role_menu values ('2', '1016'); +insert into sys_role_menu values ('2', '1017'); +insert into sys_role_menu values ('2', '1018'); +insert into sys_role_menu values ('2', '1019'); +insert into sys_role_menu values ('2', '1020'); +insert into sys_role_menu values ('2', '1021'); +insert into sys_role_menu values ('2', '1022'); +insert into sys_role_menu values ('2', '1023'); +insert into sys_role_menu values ('2', '1024'); +insert into sys_role_menu values ('2', '1025'); +insert into sys_role_menu values ('2', '1026'); +insert into sys_role_menu values ('2', '1027'); +insert into sys_role_menu values ('2', '1028'); +insert into sys_role_menu values ('2', '1029'); +insert into sys_role_menu values ('2', '1030'); +insert into sys_role_menu values ('2', '1031'); +insert into sys_role_menu values ('2', '1032'); +insert into sys_role_menu values ('2', '1033'); +insert into sys_role_menu values ('2', '1034'); +insert into sys_role_menu values ('2', '1035'); +insert into sys_role_menu values ('2', '1036'); +insert into sys_role_menu values ('2', '1037'); +insert into sys_role_menu values ('2', '1038'); +insert into sys_role_menu values ('2', '1039'); +insert into sys_role_menu values ('2', '1040'); +insert into sys_role_menu values ('2', '1041'); +insert into sys_role_menu values ('2', '1042'); +insert into sys_role_menu values ('2', '1043'); +insert into sys_role_menu values ('2', '1044'); +insert into sys_role_menu values ('2', '1045'); +insert into sys_role_menu values ('2', '1046'); +insert into sys_role_menu values ('2', '1047'); +insert into sys_role_menu values ('2', '1048'); +insert into sys_role_menu values ('2', '1049'); +insert into sys_role_menu values ('2', '1050'); +insert into sys_role_menu values ('2', '1051'); +insert into sys_role_menu values ('2', '1052'); +insert into sys_role_menu values ('2', '1053'); +insert into sys_role_menu values ('2', '1054'); +insert into sys_role_menu values ('2', '1055'); +insert into sys_role_menu values ('2', '1056'); +insert into sys_role_menu values ('2', '1057'); +insert into sys_role_menu values ('2', '1058'); +insert into sys_role_menu values ('2', '1059'); +insert into sys_role_menu values ('2', '1060'); + +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept; +create table sys_role_dept ( + role_id bigint(20) not null comment '角色ID', + dept_id bigint(20) not null comment '部门ID', + primary key(role_id, dept_id) +) engine=innodb comment = '角色和部门关联表'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept values ('2', '100'); +insert into sys_role_dept values ('2', '101'); +insert into sys_role_dept values ('2', '105'); + + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post; +create table sys_user_post +( + user_id bigint(20) not null comment '用户ID', + post_id bigint(20) not null comment '岗位ID', + primary key (user_id, post_id) +) engine=innodb comment = '用户与岗位关联表'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post values ('1', '1'); +insert into sys_user_post values ('2', '2'); + + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log; +create table sys_oper_log ( + oper_id bigint(20) not null auto_increment comment '日志主键', + title varchar(50) default '' comment '模块标题', + business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)', + method varchar(100) default '' comment '方法名称', + request_method varchar(10) default '' comment '请求方式', + operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)', + oper_name varchar(50) default '' comment '操作人员', + dept_name varchar(50) default '' comment '部门名称', + oper_url varchar(255) default '' comment '请求URL', + oper_ip varchar(128) default '' comment '主机地址', + oper_location varchar(255) default '' comment '操作地点', + oper_param varchar(2000) default '' comment '请求参数', + json_result varchar(2000) default '' comment '返回参数', + status int(1) default 0 comment '操作状态(0正常 1异常)', + error_msg varchar(2000) default '' comment '错误消息', + oper_time datetime comment '操作时间', + cost_time bigint(20) default 0 comment '消耗时间', + primary key (oper_id), + key idx_sys_oper_log_bt (business_type), + key idx_sys_oper_log_s (status), + key idx_sys_oper_log_ot (oper_time) +) engine=innodb auto_increment=100 comment = '操作日志记录'; + + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type; +create table sys_dict_type +( + dict_id bigint(20) not null auto_increment comment '字典主键', + dict_name varchar(100) default '' comment '字典名称', + dict_type varchar(100) default '' comment '字典类型', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_id), + unique (dict_type) +) engine=innodb auto_increment=100 comment = '字典类型表'; + +insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表'); +insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表'); +insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表'); +insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表'); +insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表'); +insert into sys_dict_type values(6, '任务执行器', 'sys_job_executor', '0', 'admin', sysdate(), '', null, '任务执行器列表'); +insert into sys_dict_type values(7, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表'); +insert into sys_dict_type values(8, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表'); +insert into sys_dict_type values(9, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表'); +insert into sys_dict_type values(10, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表'); +insert into sys_dict_type values(11, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); + + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data; +create table sys_dict_data +( + dict_code bigint(20) not null auto_increment comment '字典编码', + dict_sort int(4) default 0 comment '字典排序', + dict_label varchar(100) default '' comment '字典标签', + dict_value varchar(100) default '' comment '字典键值', + dict_type varchar(100) default '' comment '字典类型', + css_class varchar(100) default null comment '样式属性(其他样式扩展)', + list_class varchar(100) default null comment '表格回显样式', + is_default char(1) default 'N' comment '是否默认(Y是 N否)', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_code) +) engine=innodb auto_increment=100 comment = '字典数据表'; + +insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男'); +insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女'); +insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知'); +insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单'); +insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单'); +insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(10, 1, '默认', 'default', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组'); +insert into sys_dict_data values(11, 2, '数据库', 'sqlalchemy', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '数据库分组'); +insert into sys_dict_data values(12, 3, 'redis', 'redis', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, 'reids分组'); +insert into sys_dict_data values(13, 1, '默认', 'default', 'sys_job_executor', '', '', 'N', '0', 'admin', sysdate(), '', null, '线程池'); +insert into sys_dict_data values(14, 2, '进程池', 'processpool', 'sys_job_executor', '', '', 'N', '0', 'admin', sysdate(), '', null, '进程池'); +insert into sys_dict_data values(15, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是'); +insert into sys_dict_data values(16, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否'); +insert into sys_dict_data values(17, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知'); +insert into sys_dict_data values(18, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告'); +insert into sys_dict_data values(19, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(20, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态'); +insert into sys_dict_data values(21, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作'); +insert into sys_dict_data values(22, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作'); +insert into sys_dict_data values(23, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作'); +insert into sys_dict_data values(24, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作'); +insert into sys_dict_data values(25, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作'); +insert into sys_dict_data values(26, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作'); +insert into sys_dict_data values(27, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作'); +insert into sys_dict_data values(28, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作'); +insert into sys_dict_data values(29, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作'); +insert into sys_dict_data values(30, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作'); +insert into sys_dict_data values(31, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(32, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); + + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config; +create table sys_config ( + config_id int(5) not null auto_increment comment '参数主键', + config_name varchar(100) default '' comment '参数名称', + config_key varchar(100) default '' comment '参数键名', + config_value varchar(500) default '' comment '参数键值', + config_type char(1) default 'N' comment '系统内置(Y是 N否)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (config_id) +) engine=innodb auto_increment=100 comment = '参数配置表'; + +insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' ); +insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456' ); +insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' ); +insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)'); +insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); + + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor; +create table sys_logininfor ( + info_id bigint(20) not null auto_increment comment '访问ID', + user_name varchar(50) default '' comment '用户账号', + ipaddr varchar(128) default '' comment '登录IP地址', + login_location varchar(255) default '' comment '登录地点', + browser varchar(50) default '' comment '浏览器类型', + os varchar(50) default '' comment '操作系统', + status char(1) default '0' comment '登录状态(0成功 1失败)', + msg varchar(255) default '' comment '提示消息', + login_time datetime comment '访问时间', + primary key (info_id), + key idx_sys_logininfor_s (status), + key idx_sys_logininfor_lt (login_time) +) engine=innodb auto_increment=100 comment = '系统访问记录'; + + +-- ---------------------------- +-- 15、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job; +create table sys_job ( + job_id bigint(20) not null auto_increment comment '任务ID', + job_name varchar(64) default '' comment '任务名称', + job_group varchar(64) default 'default' comment '任务组名', + job_executor varchar(64) default 'default' comment '任务执行器', + invoke_target varchar(500) not null comment '调用目标字符串', + job_args varchar(255) default '' comment '位置参数', + job_kwargs varchar(255) default '' comment '关键字参数', + cron_expression varchar(255) default '' comment 'cron执行表达式', + misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)', + status char(1) default '0' comment '状态(0正常 1暂停)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注信息', + primary key (job_id, job_name, job_group) +) engine=innodb auto_increment=100 comment = '定时任务调度表'; + +insert into sys_job values(1, '系统默认(无参)', 'default', 'default', 'module_task.scheduler_test.job', NULL, NULL, '0/10 * * * * * *', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(2, '系统默认(有参)', 'default', 'default', 'module_task.scheduler_test.job', 'test', NULL, '0/15 * * * * * *', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(3, '系统默认(多参)', 'default', 'default', 'module_task.scheduler_test.job', 'new', '{\"test\": 111}', '0/20 * * * * * *', '3', '1', '1', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 16、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log; +create table sys_job_log ( + job_log_id bigint(20) not null auto_increment comment '任务日志ID', + job_name varchar(64) not null comment '任务名称', + job_group varchar(64) not null comment '任务组名', + job_executor varchar(64) not null comment '任务执行器', + invoke_target varchar(500) not null comment '调用目标字符串', + job_args varchar(255) default '' comment '位置参数', + job_kwargs varchar(255) default '' comment '关键字参数', + job_trigger varchar(255) default '' comment '任务触发器', + job_message varchar(500) comment '日志信息', + status char(1) default '0' comment '执行状态(0正常 1失败)', + exception_info varchar(2000) default '' comment '异常信息', + create_time datetime comment '创建时间', + primary key (job_log_id) +) engine=innodb comment = '定时任务调度日志表'; + + +-- ---------------------------- +-- 17、通知公告表 +-- ---------------------------- +drop table if exists sys_notice; +create table sys_notice ( + notice_id int(4) not null auto_increment comment '公告ID', + notice_title varchar(50) not null comment '公告标题', + notice_type char(1) not null comment '公告类型(1通知 2公告)', + notice_content longblob default null comment '公告内容', + status char(1) default '0' comment '公告状态(0正常 1关闭)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(255) default null comment '备注', + primary key (notice_id) +) engine=innodb auto_increment=10 comment = '通知公告表'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice values('1', '温馨提醒:2018-07-01 vfadmin新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('2', '维护通知:2018-07-01 vfadmin系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); + + +-- ---------------------------- +-- 18、代码生成业务表 +-- ---------------------------- +drop table if exists gen_table; +create table gen_table ( + table_id bigint(20) not null auto_increment comment '编号', + table_name varchar(200) default '' comment '表名称', + table_comment varchar(500) default '' comment '表描述', + sub_table_name varchar(64) default null comment '关联子表的表名', + sub_table_fk_name varchar(64) default null comment '子表关联的外键名', + class_name varchar(100) default '' comment '实体类名称', + tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作)', + tpl_web_type varchar(30) default '' comment '前端模板类型(element-ui模版 element-plus模版)', + package_name varchar(100) comment '生成包路径', + module_name varchar(30) comment '生成模块名', + business_name varchar(30) comment '生成业务名', + function_name varchar(50) comment '生成功能名', + function_author varchar(50) comment '生成功能作者', + gen_type char(1) default '0' comment '生成代码方式(0zip压缩包 1自定义路径)', + gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)', + options varchar(1000) comment '其它生成选项', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (table_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表'; + + +-- ---------------------------- +-- 19、代码生成业务表字段 +-- ---------------------------- +drop table if exists gen_table_column; +create table gen_table_column ( + column_id bigint(20) not null auto_increment comment '编号', + table_id bigint(20) comment '归属表编号', + column_name varchar(200) comment '列名称', + column_comment varchar(500) comment '列描述', + column_type varchar(100) comment '列类型', + java_type varchar(500) comment 'JAVA类型', + java_field varchar(200) comment 'JAVA字段名', + is_pk char(1) comment '是否主键(1是)', + is_increment char(1) comment '是否自增(1是)', + is_required char(1) comment '是否必填(1是)', + is_insert char(1) comment '是否为插入字段(1是)', + is_edit char(1) comment '是否编辑字段(1是)', + is_list char(1) comment '是否列表字段(1是)', + is_query char(1) comment '是否查询字段(1是)', + query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)', + html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + dict_type varchar(200) default '' comment '字典类型', + sort int comment '排序', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (column_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表字段'; \ No newline at end of file diff --git a/ruoyi-fastapi-backend/utils/common_util.py b/ruoyi-fastapi-backend/utils/common_util.py new file mode 100644 index 0000000..a45d266 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/common_util.py @@ -0,0 +1,184 @@ +import pandas as pd +import io +import os +from openpyxl import Workbook +from openpyxl.styles import Alignment, PatternFill +from openpyxl.utils import get_column_letter +from openpyxl.worksheet.datavalidation import DataValidation +from typing import List +from config.env import CachePathConfig + + +def worship(): + print(""" +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// + """) + + +class CamelCaseUtil: + """ + 下划线形式(snake_case)转换为小驼峰形式(camelCase)工具方法 + """ + @classmethod + def __to_camel_case(cls, snake_str): + """ + 下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase) + :param snake_str: 下划线形式字符串 + :return: 小驼峰形式字符串 + """ + # 分割字符串 + words = snake_str.split('_') + # 小驼峰命名,第一个词首字母小写,其余词首字母大写 + return words[0] + ''.join(word.capitalize() for word in words[1:]) + + @classmethod + def transform_result(cls, result): + """ + 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + :param result: 输入数据 + :return: 小驼峰形式结果 + """ + if result is None: + return result + # 如果是字典,直接转换键 + elif isinstance(result, dict): + return {cls.__to_camel_case(k): v for k, v in result.items()} + # 如果是一组字典或其他类型的列表,遍历列表进行转换 + elif isinstance(result, list): + 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}) 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"): + """Used by various scripts. See: + http://goo.gl/zeJZl + + >>> bytes2human(10000) + '9.8K' + >>> bytes2human(100001221) + '95.4M' + """ + symbols = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB') + prefix = {} + for i, s in enumerate(symbols[1:]): + prefix[s] = 1 << (i + 1) * 10 + for symbol in reversed(symbols[1:]): + if n >= prefix[symbol]: + value = float(n) / prefix[symbol] + return format_str % locals() + return format_str % dict(symbol=symbols[0], value=n) + + +def bytes2file_response(bytes_info): + yield bytes_info + + +def export_list2excel(list_data: List): + """ + 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + :param list_data: 数据列表 + :return: 字典信息对应excel的二进制数据 + """ + df = pd.DataFrame(list_data) + binary_data = io.BytesIO() + df.to_excel(binary_data, index=False, engine='openpyxl') + binary_data = binary_data.getvalue() + + return binary_data + + +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: 选择器格式的表头预设的选项列表 + :return: 模板excel的二进制数据 + """ + # 创建Excel工作簿 + wb = Workbook() + # 选择默认的活动工作表 + ws = wb.active + + # 设置表头文字 + headers = header_list + + # 设置表头背景样式为灰色,前景色为白色 + header_fill = PatternFill(start_color="ababab", end_color="ababab", fill_type="solid") + + # 将表头写入第一行 + for col_num, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col_num) + cell.value = header + cell.fill = header_fill + # 设置列宽度为16 + ws.column_dimensions[chr(64 + col_num)].width = 12 + # 设置水平居中对齐 + cell.alignment = Alignment(horizontal='center') + + # 设置选择器的预设选项 + options = option_list + + # 获取selector_header的字母索引 + for selector_header in selector_header_list: + column_selector_header_index = headers.index(selector_header) + 1 + + # 创建数据有效性规则 + header_option = [] + 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.add( + f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576') + # 添加数据有效性规则到工作表 + ws.add_data_validation(dv) + + # 保存Excel文件为字节类型的数据 + file = io.BytesIO() + wb.save(file) + file.seek(0) + + # 读取字节数据 + excel_data = file.getvalue() + + return excel_data + + +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] + filepath = os.path.join(CachePathConfig.PATH, task_path, task_id, file_name) + + return filepath diff --git a/ruoyi-fastapi-backend/utils/log_util.py b/ruoyi-fastapi-backend/utils/log_util.py new file mode 100644 index 0000000..e904653 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/log_util.py @@ -0,0 +1,11 @@ +import os +import time +from loguru import logger + +log_path = os.path.join(os.getcwd(), 'logs') +if not os.path.exists(log_path): + os.mkdir(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") diff --git a/ruoyi-fastapi-backend/utils/message_util.py b/ruoyi-fastapi-backend/utils/message_util.py new file mode 100644 index 0000000..6a21f88 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/message_util.py @@ -0,0 +1,5 @@ +from utils.log_util import logger + + +def message_service(sms_code: str): + logger.info(f"短信验证码为{sms_code}") diff --git a/ruoyi-fastapi-backend/utils/page_util.py b/ruoyi-fastapi-backend/utils/page_util.py new file mode 100644 index 0000000..341f80f --- /dev/null +++ b/ruoyi-fastapi-backend/utils/page_util.py @@ -0,0 +1,98 @@ +import math +from typing import Optional, List + +from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel + + +class PageModel(BaseModel): + """ + 分页模型 + """ + offset: int + page_num: int + page_size: int + total: int + has_next: bool + + +class PageObjectResponse(BaseModel): + """ + 用户管理列表分页查询返回模型 + """ + rows: List = [] + page_num: int + page_size: int + total: int + has_next: bool + + +class PageResponseModel(BaseModel): + """ + 列表分页查询返回模型 + """ + model_config = ConfigDict(alias_generator=to_camel) + + rows: List = [] + page_num: Optional[int] = None + page_size: Optional[int] = None + total: int + has_next: Optional[bool] = None + + +def get_page_info(offset: int, page_num: int, page_size: int, count: int): + """ + 根据分页参数获取分页信息 + :param offset: 起始数据位置 + :param page_num: 当前页码 + :param page_size: 当前页面数据量 + :param count: 数据总数 + :return: 分页信息对象 + """ + has_next = False + if offset >= count: + res_offset_1 = (page_num - 2) * page_size + if res_offset_1 < 0: + res_offset = 0 + res_page_num = 1 + else: + res_offset = res_offset_1 + res_page_num = page_num - 1 + else: + res_offset = offset + if (res_offset + page_size) < count: + has_next = True + res_page_num = page_num + + result = dict(offset=res_offset, page_num=res_page_num, page_size=page_size, total=count, has_next=has_next) + + return PageModel(**result) + + +def get_page_obj(data_list: List, page_num: int, page_size: int): + """ + 输入数据列表data_list和分页信息,返回分页数据列表结果 + :param data_list: 原始数据列表 + :param page_num: 当前页码 + :param page_size: 当前页面数据量 + :return: 分页数据对象 + """ + # 计算起始索引和结束索引 + start = (page_num - 1) * page_size + end = page_num * page_size + + # 根据计算得到的起始索引和结束索引对数据列表进行切片 + paginated_data = data_list[start:end] + 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 + ) + + return result + + diff --git a/ruoyi-fastapi-backend/utils/pwd_util.py b/ruoyi-fastapi-backend/utils/pwd_util.py new file mode 100644 index 0000000..2e9a2f9 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/pwd_util.py @@ -0,0 +1,28 @@ +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +class PwdUtil: + """ + 密码工具类 + """ + + @classmethod + def verify_password(cls, plain_password, hashed_password): + """ + 工具方法:校验当前输入的密码与数据库存储的密码是否一致 + :param plain_password: 当前输入的密码 + :param hashed_password: 数据库存储的密码 + :return: 校验结果 + """ + return pwd_context.verify(plain_password, hashed_password) + + @classmethod + def get_password_hash(cls, input_password): + """ + 工具方法:对当前输入的密码进行加密 + :param input_password: 输入的密码 + :return: 加密成功的密码 + """ + return pwd_context.hash(input_password) diff --git a/ruoyi-fastapi-backend/utils/response_util.py b/ruoyi-fastapi-backend/utils/response_util.py new file mode 100644 index 0000000..4dab253 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/response_util.py @@ -0,0 +1,301 @@ +from fastapi import status +from fastapi.responses import JSONResponse, Response, StreamingResponse +from fastapi.encoders import jsonable_encoder +from typing import Any, Dict, Optional +from pydantic import BaseModel +from datetime import datetime + + +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: + """ + 成功响应方法 + :param msg: 可选,自定义成功响应信息 + :param data: 可选,成功响应结果中属性为data的值 + :param rows: 可选,成功响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,成功响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,成功响应结果中自定义属性的值 + :return: 成功响应结果 + """ + result = { + 'code': 200, + 'msg': msg + } + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': True, 'time': datetime.now()}) + + 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: + """ + 失败响应方法 + :param msg: 可选,自定义失败响应信息 + :param data: 可选,失败响应结果中属性为data的值 + :param rows: 可选,失败响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,失败响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,失败响应结果中自定义属性的值 + :return: 失败响应结果 + """ + result = { + 'code': 601, + 'msg': msg + } + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + 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: + """ + 未认证响应方法 + :param msg: 可选,自定义未认证响应信息 + :param data: 可选,未认证响应结果中属性为data的值 + :param rows: 可选,未认证响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,未认证响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,未认证响应结果中自定义属性的值 + :return: 未认证响应结果 + """ + result = { + 'code': 401, + 'msg': msg + } + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + 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: + """ + 未认证响应方法 + :param msg: 可选,自定义未认证响应信息 + :param data: 可选,未认证响应结果中属性为data的值 + :param rows: 可选,未认证响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,未认证响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,未认证响应结果中自定义属性的值 + :return: 未认证响应结果 + """ + result = { + 'code': 403, + 'msg': msg + } + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + 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: + """ + 错误响应方法 + :param msg: 可选,自定义错误响应信息 + :param data: 可选,错误响应结果中属性为data的值 + :param rows: 可选,错误响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,错误响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,错误响应结果中自定义属性的值 + :return: 错误响应结果 + """ + result = { + 'code': 500, + 'msg': msg + } + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump(by_alias=True)) + + result.update({'success': False, 'time': datetime.now()}) + + 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 + ) + + +def response_200(*, data: Any = None, message="获取成功") -> Response: + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder( + { + 'code': 200, + 'message': message, + 'data': data, + 'success': 'true', + 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + ) + ) + + +def response_400(*, data: Any = None, message: str = "获取失败") -> Response: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=jsonable_encoder( + { + 'code': 400, + 'message': message, + 'data': data, + 'success': 'false', + 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + ) + ) + + +def response_401(*, data: Any = None, message: str = "获取失败") -> Response: + return JSONResponse( + status_code=status.HTTP_401_UNAUTHORIZED, + content=jsonable_encoder( + { + 'code': 401, + 'message': message, + 'data': data, + 'success': 'false', + 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + ) + ) + + +def response_403(*, data: Any = None, message: str = "获取失败") -> Response: + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=jsonable_encoder( + { + 'code': 403, + 'message': message, + 'data': data, + 'success': 'false', + 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + ) + ) + + +def response_500(*, data: Any = None, message: str = "接口异常") -> Response: + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content=jsonable_encoder( + { + 'code': 500, + 'message': message, + 'data': data, + 'success': 'false', + 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + ) + ) + + +def streaming_response_200(*, data: Any = None): + return StreamingResponse( + status_code=status.HTTP_200_OK, + content=data, + ) + + +class AuthException(Exception): + """ + 自定义令牌异常AuthException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class PermissionException(Exception): + """ + 自定义权限异常PermissionException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class LoginException(Exception): + """ + 自定义登录异常LoginException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message diff --git a/ruoyi-fastapi-backend/utils/time_format_util.py b/ruoyi-fastapi-backend/utils/time_format_util.py new file mode 100644 index 0000000..ba19489 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/time_format_util.py @@ -0,0 +1,51 @@ +import datetime + + +def object_format_datetime(obj): + """ + :param obj: 输入一个对象 + :return:对目标对象所有datetime类型的属性格式化 + """ + for attr in dir(obj): + value = getattr(obj, attr) + if isinstance(value, datetime.datetime): + setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S')) + return obj + + +def list_format_datetime(lst): + """ + :param lst: 输入一个嵌套对象的列表 + :return: 对目标列表中所有对象的datetime类型的属性格式化 + """ + for obj in lst: + for attr in dir(obj): + value = getattr(obj, attr) + if isinstance(value, datetime.datetime): + setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S')) + return lst + + +def format_datetime_dict_list(dicts): + """ + 递归遍历嵌套字典,并将 datetime 值转换为字符串格式 + :param dicts: 输入一个嵌套字典的列表 + :return: 对目标列表中所有字典的datetime类型的属性格式化 + """ + result = [] + + for item in dicts: + new_item = {} + for k, v in item.items(): + if isinstance(v, dict): + # 递归遍历子字典 + new_item[k] = format_datetime_dict_list([v])[0] + elif isinstance(v, datetime.datetime): + # 如果值是 datetime 类型,则格式化为字符串 + new_item[k] = v.strftime('%Y-%m-%d %H:%M:%S') + else: + # 否则保留原始值 + new_item[k] = v + result.append(new_item) + + return result diff --git a/ruoyi-fastapi-frontend/.env.development b/ruoyi-fastapi-frontend/.env.development new file mode 100644 index 0000000..af9ba00 --- /dev/null +++ b/ruoyi-fastapi-frontend/.env.development @@ -0,0 +1,8 @@ +# 页面标题 +VITE_APP_TITLE = 若依管理系统 + +# 开发环境配置 +VITE_APP_ENV = 'development' + +# 若依管理系统/开发环境 +VITE_APP_BASE_API = '/dev-api' diff --git a/ruoyi-fastapi-frontend/.env.production b/ruoyi-fastapi-frontend/.env.production new file mode 100644 index 0000000..cbabf89 --- /dev/null +++ b/ruoyi-fastapi-frontend/.env.production @@ -0,0 +1,11 @@ +# 页面标题 +VITE_APP_TITLE = 若依管理系统 + +# 生产环境配置 +VITE_APP_ENV = 'production' + +# 若依管理系统/生产环境 +VITE_APP_BASE_API = '/prod-api' + +# 是否在打包时开启压缩,支持 gzip 和 brotli +VITE_BUILD_COMPRESS = gzip \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/.env.staging b/ruoyi-fastapi-frontend/.env.staging new file mode 100644 index 0000000..b11336d --- /dev/null +++ b/ruoyi-fastapi-frontend/.env.staging @@ -0,0 +1,11 @@ +# 页面标题 +VITE_APP_TITLE = 若依管理系统 + +# 生产环境配置 +VITE_APP_ENV = 'staging' + +# 若依管理系统/生产环境 +VITE_APP_BASE_API = '/stage-api' + +# 是否在打包时开启压缩,支持 gzip 和 brotli +VITE_BUILD_COMPRESS = gzip \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/.gitignore b/ruoyi-fastapi-frontend/.gitignore new file mode 100644 index 0000000..78a752d --- /dev/null +++ b/ruoyi-fastapi-frontend/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +**/*.log + +tests/**/coverage/ +tests/e2e/reports +selenium-debug.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock diff --git a/ruoyi-fastapi-frontend/LICENSE b/ruoyi-fastapi-frontend/LICENSE new file mode 100644 index 0000000..8564f29 --- /dev/null +++ b/ruoyi-fastapi-frontend/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 RuoYi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/README.md b/ruoyi-fastapi-frontend/README.md new file mode 100644 index 0000000..d420cc3 --- /dev/null +++ b/ruoyi-fastapi-frontend/README.md @@ -0,0 +1,109 @@ +

+ logo +

+

RuoYi v3.8.7

+

基于SpringBoot+Vue3前后端分离的Java快速开发框架

+

+ + + +

+ +## 平台简介 + +* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。 +* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast) 版本。 +* 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。 +* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   +* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   + +## 前端运行 + +```bash +# 克隆项目 +git clone https://github.com/yangzongzhuan/RuoYi-Vue3.git + +# 进入项目目录 +cd RuoYi-Vue3 + +# 安装依赖 +yarn --registry=https://registry.npmmirror.com + +# 启动服务 +yarn dev + +# 构建测试环境 yarn build:stage +# 构建生产环境 yarn build:prod +# 前端访问地址 http://localhost:80 +``` + +## 内置功能 + +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 通知公告:系统通知公告信息发布维护。 +9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10. 登录日志:系统登录日志记录查询包含登录异常。 +11. 在线用户:当前系统中活跃用户状态监控。 +12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 +14. 系统接口:根据业务代码自动生成相关的api接口文档。 +15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +16. 缓存监控:对系统的缓存信息查询,命令统计等。 +17. 在线构建器:拖动表单元素生成相应的HTML代码。 +18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 + +## 在线体验 + +- admin/admin123 +- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 + +演示地址:http://vue.ruoyi.vip +文档地址:http://doc.ruoyi.vip + +## 演示图 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +## 若依前后端分离交流群 + +QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) 点击按钮入群。 \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/bin/build.bat b/ruoyi-fastapi-frontend/bin/build.bat new file mode 100644 index 0000000..ecbb454 --- /dev/null +++ b/ruoyi-fastapi-frontend/bin/build.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅdistļ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn build:prod + +pause \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/bin/package.bat b/ruoyi-fastapi-frontend/bin/package.bat new file mode 100644 index 0000000..f5b24e0 --- /dev/null +++ b/ruoyi-fastapi-frontend/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] װWeḅnode_modulesļ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn --registry=https://registry.npmmirror.com + +pause \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/bin/run-web.bat b/ruoyi-fastapi-frontend/bin/run-web.bat new file mode 100644 index 0000000..d2fe397 --- /dev/null +++ b/ruoyi-fastapi-frontend/bin/run-web.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] ʹ Vite Web ̡ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn dev + +pause \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/html/ie.html b/ruoyi-fastapi-frontend/html/ie.html new file mode 100644 index 0000000..052ffcd --- /dev/null +++ b/ruoyi-fastapi-frontend/html/ie.html @@ -0,0 +1,46 @@ + + + + + + 请升级您的浏览器 + + + + + + +

请升级您的浏览器,以便我们更好的为您提供服务!

+

您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。

+
+

请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束

+

自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明

+
+

您可以选择更先进的浏览器

+

推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。

+ +
+ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/index.html b/ruoyi-fastapi-frontend/index.html new file mode 100644 index 0000000..d20d02e --- /dev/null +++ b/ruoyi-fastapi-frontend/index.html @@ -0,0 +1,215 @@ + + + + + + + + + + 若依管理系统 + + + + + +
+
+
+
+
+
正在加载系统资源,请耐心等待
+
+
+ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json new file mode 100644 index 0000000..a111f9c --- /dev/null +++ b/ruoyi-fastapi-frontend/package.json @@ -0,0 +1,45 @@ +{ + "name": "ruoyi", + "version": "3.8.7", + "description": "若依管理系统", + "author": "若依", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "vite", + "build:prod": "vite build", + "build:stage": "vite build --mode staging", + "preview": "vite preview" + }, + "repository": { + "type": "git", + "url": "https://gitee.com/y_project/RuoYi-Vue.git" + }, + "dependencies": { + "@element-plus/icons-vue": "2.3.1", + "@vueup/vue-quill": "1.2.0", + "@vueuse/core": "10.6.1", + "axios": "0.27.2", + "echarts": "5.4.3", + "element-plus": "2.4.3", + "file-saver": "2.0.5", + "fuse.js": "6.6.2", + "js-cookie": "3.0.5", + "jsencrypt": "3.3.2", + "nprogress": "0.2.0", + "pinia": "2.1.7", + "vue": "3.3.9", + "vue-cropper": "1.1.1", + "vue-router": "4.2.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "4.5.0", + "@vue/compiler-sfc": "3.3.9", + "sass": "1.69.5", + "unplugin-auto-import": "0.17.1", + "vite": "5.0.4", + "vite-plugin-compression": "0.5.1", + "vite-plugin-svg-icons": "2.0.1", + "unplugin-vue-setup-extend-plus": "1.0.0" + } +} diff --git a/ruoyi-fastapi-frontend/public/favicon.ico b/ruoyi-fastapi-frontend/public/favicon.ico new file mode 100644 index 0000000..e263760 Binary files /dev/null and b/ruoyi-fastapi-frontend/public/favicon.ico differ diff --git a/ruoyi-fastapi-frontend/src/App.vue b/ruoyi-fastapi-frontend/src/App.vue new file mode 100644 index 0000000..31839f2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/App.vue @@ -0,0 +1,15 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/api/login.js b/ruoyi-fastapi-frontend/src/api/login.js new file mode 100644 index 0000000..7b7388f --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/login.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 登录方法 +export function login(username, password, code, uuid) { + const data = { + username, + password, + code, + uuid + } + return request({ + url: '/login', + headers: { + isToken: false, + repeatSubmit: false + }, + method: 'post', + data: data + }) +} + +// 注册方法 +export function register(data) { + return request({ + url: '/register', + headers: { + isToken: false + }, + method: 'post', + data: data + }) +} + +// 获取用户详细信息 +export function getInfo() { + return request({ + url: '/getInfo', + method: 'get' + }) +} + +// 退出方法 +export function logout() { + return request({ + url: '/logout', + method: 'post' + }) +} + +// 获取验证码 +export function getCodeImg() { + return request({ + url: '/captchaImage', + headers: { + isToken: false + }, + method: 'get', + timeout: 20000 + }) +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/menu.js b/ruoyi-fastapi-frontend/src/api/menu.js new file mode 100644 index 0000000..faef101 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/menu.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取路由 +export const getRouters = () => { + return request({ + url: '/getRouters', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/monitor/cache.js b/ruoyi-fastapi-frontend/src/api/monitor/cache.js new file mode 100644 index 0000000..72c5f6a --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/monitor/cache.js @@ -0,0 +1,57 @@ +import request from '@/utils/request' + +// 查询缓存详细 +export function getCache() { + return request({ + url: '/monitor/cache', + method: 'get' + }) +} + +// 查询缓存名称列表 +export function listCacheName() { + return request({ + url: '/monitor/cache/getNames', + method: 'get' + }) +} + +// 查询缓存键名列表 +export function listCacheKey(cacheName) { + return request({ + url: '/monitor/cache/getKeys/' + cacheName, + method: 'get' + }) +} + +// 查询缓存内容 +export function getCacheValue(cacheName, cacheKey) { + return request({ + url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, + method: 'get' + }) +} + +// 清理指定名称缓存 +export function clearCacheName(cacheName) { + return request({ + url: '/monitor/cache/clearCacheName/' + cacheName, + method: 'delete' + }) +} + +// 清理指定键名缓存 +export function clearCacheKey(cacheKey) { + return request({ + url: '/monitor/cache/clearCacheKey/' + cacheKey, + method: 'delete' + }) +} + +// 清理全部缓存 +export function clearCacheAll() { + return request({ + url: '/monitor/cache/clearCacheAll', + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/monitor/job.js b/ruoyi-fastapi-frontend/src/api/monitor/job.js new file mode 100644 index 0000000..3815569 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/monitor/job.js @@ -0,0 +1,71 @@ +import request from '@/utils/request' + +// 查询定时任务调度列表 +export function listJob(query) { + return request({ + url: '/monitor/job/list', + method: 'get', + params: query + }) +} + +// 查询定时任务调度详细 +export function getJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'get' + }) +} + +// 新增定时任务调度 +export function addJob(data) { + return request({ + url: '/monitor/job', + method: 'post', + data: data + }) +} + +// 修改定时任务调度 +export function updateJob(data) { + return request({ + url: '/monitor/job', + method: 'put', + data: data + }) +} + +// 删除定时任务调度 +export function delJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'delete' + }) +} + +// 任务状态修改 +export function changeJobStatus(jobId, status) { + const data = { + jobId, + status + } + return request({ + url: '/monitor/job/changeStatus', + method: 'put', + data: data + }) +} + + +// 定时任务立即执行一次 +export function runJob(jobId, jobGroup) { + const data = { + jobId, + jobGroup + } + return request({ + url: '/monitor/job/run', + method: 'put', + data: data + }) +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/monitor/jobLog.js b/ruoyi-fastapi-frontend/src/api/monitor/jobLog.js new file mode 100644 index 0000000..6e0be61 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/monitor/jobLog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询调度日志列表 +export function listJobLog(query) { + return request({ + url: '/monitor/jobLog/list', + method: 'get', + params: query + }) +} + +// 删除调度日志 +export function delJobLog(jobLogId) { + return request({ + url: '/monitor/jobLog/' + jobLogId, + method: 'delete' + }) +} + +// 清空调度日志 +export function cleanJobLog() { + return request({ + url: '/monitor/jobLog/clean', + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/monitor/logininfor.js b/ruoyi-fastapi-frontend/src/api/monitor/logininfor.js new file mode 100644 index 0000000..4d112b7 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/monitor/logininfor.js @@ -0,0 +1,34 @@ +import request from '@/utils/request' + +// 查询登录日志列表 +export function list(query) { + return request({ + url: '/monitor/logininfor/list', + method: 'get', + params: query + }) +} + +// 删除登录日志 +export function delLogininfor(infoId) { + return request({ + url: '/monitor/logininfor/' + infoId, + method: 'delete' + }) +} + +// 解锁用户登录状态 +export function unlockLogininfor(userName) { + return request({ + url: '/monitor/logininfor/unlock/' + userName, + method: 'get' + }) +} + +// 清空登录日志 +export function cleanLogininfor() { + return request({ + url: '/monitor/logininfor/clean', + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/monitor/online.js b/ruoyi-fastapi-frontend/src/api/monitor/online.js new file mode 100644 index 0000000..bd22137 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/monitor/online.js @@ -0,0 +1,18 @@ +import request from '@/utils/request' + +// 查询在线用户列表 +export function list(query) { + return request({ + url: '/monitor/online/list', + method: 'get', + params: query + }) +} + +// 强退用户 +export function forceLogout(tokenId) { + return request({ + url: '/monitor/online/' + tokenId, + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/monitor/operlog.js b/ruoyi-fastapi-frontend/src/api/monitor/operlog.js new file mode 100644 index 0000000..a04bca8 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/monitor/operlog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询操作日志列表 +export function list(query) { + return request({ + url: '/monitor/operlog/list', + method: 'get', + params: query + }) +} + +// 删除操作日志 +export function delOperlog(operId) { + return request({ + url: '/monitor/operlog/' + operId, + method: 'delete' + }) +} + +// 清空操作日志 +export function cleanOperlog() { + return request({ + url: '/monitor/operlog/clean', + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/monitor/server.js b/ruoyi-fastapi-frontend/src/api/monitor/server.js new file mode 100644 index 0000000..e1f9ca2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/monitor/server.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取服务信息 +export function getServer() { + return request({ + url: '/monitor/server', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/system/config.js b/ruoyi-fastapi-frontend/src/api/system/config.js new file mode 100644 index 0000000..a404d82 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/config.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询参数列表 +export function listConfig(query) { + return request({ + url: '/system/config/list', + method: 'get', + params: query + }) +} + +// 查询参数详细 +export function getConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'get' + }) +} + +// 根据参数键名查询参数值 +export function getConfigKey(configKey) { + return request({ + url: '/system/config/configKey/' + configKey, + method: 'get' + }) +} + +// 新增参数配置 +export function addConfig(data) { + return request({ + url: '/system/config', + method: 'post', + data: data + }) +} + +// 修改参数配置 +export function updateConfig(data) { + return request({ + url: '/system/config', + method: 'put', + data: data + }) +} + +// 删除参数配置 +export function delConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'delete' + }) +} + +// 刷新参数缓存 +export function refreshCache() { + return request({ + url: '/system/config/refreshCache', + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/system/dept.js b/ruoyi-fastapi-frontend/src/api/system/dept.js new file mode 100644 index 0000000..fc943cd --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/dept.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询部门列表 +export function listDept(query) { + return request({ + url: '/system/dept/list', + method: 'get', + params: query + }) +} + +// 查询部门列表(排除节点) +export function listDeptExcludeChild(deptId) { + return request({ + url: '/system/dept/list/exclude/' + deptId, + method: 'get' + }) +} + +// 查询部门详细 +export function getDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'get' + }) +} + +// 新增部门 +export function addDept(data) { + return request({ + url: '/system/dept', + method: 'post', + data: data + }) +} + +// 修改部门 +export function updateDept(data) { + return request({ + url: '/system/dept', + method: 'put', + data: data + }) +} + +// 删除部门 +export function delDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/system/dict/data.js b/ruoyi-fastapi-frontend/src/api/system/dict/data.js new file mode 100644 index 0000000..6c9eb79 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/dict/data.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询字典数据列表 +export function listData(query) { + return request({ + url: '/system/dict/data/list', + method: 'get', + params: query + }) +} + +// 查询字典数据详细 +export function getData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'get' + }) +} + +// 根据字典类型查询字典数据信息 +export function getDicts(dictType) { + return request({ + url: '/system/dict/data/type/' + dictType, + method: 'get' + }) +} + +// 新增字典数据 +export function addData(data) { + return request({ + url: '/system/dict/data', + method: 'post', + data: data + }) +} + +// 修改字典数据 +export function updateData(data) { + return request({ + url: '/system/dict/data', + method: 'put', + data: data + }) +} + +// 删除字典数据 +export function delData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/system/dict/type.js b/ruoyi-fastapi-frontend/src/api/system/dict/type.js new file mode 100644 index 0000000..a0254ba --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/dict/type.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询字典类型列表 +export function listType(query) { + return request({ + url: '/system/dict/type/list', + method: 'get', + params: query + }) +} + +// 查询字典类型详细 +export function getType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'get' + }) +} + +// 新增字典类型 +export function addType(data) { + return request({ + url: '/system/dict/type', + method: 'post', + data: data + }) +} + +// 修改字典类型 +export function updateType(data) { + return request({ + url: '/system/dict/type', + method: 'put', + data: data + }) +} + +// 删除字典类型 +export function delType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'delete' + }) +} + +// 刷新字典缓存 +export function refreshCache() { + return request({ + url: '/system/dict/type/refreshCache', + method: 'delete' + }) +} + +// 获取字典选择框列表 +export function optionselect() { + return request({ + url: '/system/dict/type/optionselect', + method: 'get' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/system/menu.js b/ruoyi-fastapi-frontend/src/api/system/menu.js new file mode 100644 index 0000000..f6415c6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/menu.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询菜单列表 +export function listMenu(query) { + return request({ + url: '/system/menu/list', + method: 'get', + params: query + }) +} + +// 查询菜单详细 +export function getMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'get' + }) +} + +// 查询菜单下拉树结构 +export function treeselect() { + return request({ + url: '/system/menu/treeselect', + method: 'get' + }) +} + +// 根据角色ID查询菜单下拉树结构 +export function roleMenuTreeselect(roleId) { + return request({ + url: '/system/menu/roleMenuTreeselect/' + roleId, + method: 'get' + }) +} + +// 新增菜单 +export function addMenu(data) { + return request({ + url: '/system/menu', + method: 'post', + data: data + }) +} + +// 修改菜单 +export function updateMenu(data) { + return request({ + url: '/system/menu', + method: 'put', + data: data + }) +} + +// 删除菜单 +export function delMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/system/notice.js b/ruoyi-fastapi-frontend/src/api/system/notice.js new file mode 100644 index 0000000..c274ea5 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/notice.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询公告列表 +export function listNotice(query) { + return request({ + url: '/system/notice/list', + method: 'get', + params: query + }) +} + +// 查询公告详细 +export function getNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'get' + }) +} + +// 新增公告 +export function addNotice(data) { + return request({ + url: '/system/notice', + method: 'post', + data: data + }) +} + +// 修改公告 +export function updateNotice(data) { + return request({ + url: '/system/notice', + method: 'put', + data: data + }) +} + +// 删除公告 +export function delNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/system/post.js b/ruoyi-fastapi-frontend/src/api/system/post.js new file mode 100644 index 0000000..1a8e9ca --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/post.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询岗位列表 +export function listPost(query) { + return request({ + url: '/system/post/list', + method: 'get', + params: query + }) +} + +// 查询岗位详细 +export function getPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'get' + }) +} + +// 新增岗位 +export function addPost(data) { + return request({ + url: '/system/post', + method: 'post', + data: data + }) +} + +// 修改岗位 +export function updatePost(data) { + return request({ + url: '/system/post', + method: 'put', + data: data + }) +} + +// 删除岗位 +export function delPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/system/role.js b/ruoyi-fastapi-frontend/src/api/system/role.js new file mode 100644 index 0000000..f13e6f4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/role.js @@ -0,0 +1,119 @@ +import request from '@/utils/request' + +// 查询角色列表 +export function listRole(query) { + return request({ + url: '/system/role/list', + method: 'get', + params: query + }) +} + +// 查询角色详细 +export function getRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'get' + }) +} + +// 新增角色 +export function addRole(data) { + return request({ + url: '/system/role', + method: 'post', + data: data + }) +} + +// 修改角色 +export function updateRole(data) { + return request({ + url: '/system/role', + method: 'put', + data: data + }) +} + +// 角色数据权限 +export function dataScope(data) { + return request({ + url: '/system/role/dataScope', + method: 'put', + data: data + }) +} + +// 角色状态修改 +export function changeRoleStatus(roleId, status) { + const data = { + roleId, + status + } + return request({ + url: '/system/role/changeStatus', + method: 'put', + data: data + }) +} + +// 删除角色 +export function delRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'delete' + }) +} + +// 查询角色已授权用户列表 +export function allocatedUserList(query) { + return request({ + url: '/system/role/authUser/allocatedList', + method: 'get', + params: query + }) +} + +// 查询角色未授权用户列表 +export function unallocatedUserList(query) { + return request({ + url: '/system/role/authUser/unallocatedList', + method: 'get', + params: query + }) +} + +// 取消用户授权角色 +export function authUserCancel(data) { + return request({ + url: '/system/role/authUser/cancel', + method: 'put', + data: data + }) +} + +// 批量取消用户授权角色 +export function authUserCancelAll(data) { + return request({ + url: '/system/role/authUser/cancelAll', + method: 'put', + params: data + }) +} + +// 授权用户选择 +export function authUserSelectAll(data) { + return request({ + url: '/system/role/authUser/selectAll', + method: 'put', + params: data + }) +} + +// 根据角色ID查询部门树结构 +export function deptTreeSelect(roleId) { + return request({ + url: '/system/role/deptTree/' + roleId, + method: 'get' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/system/user.js b/ruoyi-fastapi-frontend/src/api/system/user.js new file mode 100644 index 0000000..f2f76ef --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/system/user.js @@ -0,0 +1,135 @@ +import request from '@/utils/request' +import { parseStrEmpty } from "@/utils/ruoyi"; + +// 查询用户列表 +export function listUser(query) { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }) +} + +// 查询用户详细 +export function getUser(userId) { + return request({ + url: '/system/user/' + parseStrEmpty(userId), + method: 'get' + }) +} + +// 新增用户 +export function addUser(data) { + return request({ + url: '/system/user', + method: 'post', + data: data + }) +} + +// 修改用户 +export function updateUser(data) { + return request({ + url: '/system/user', + method: 'put', + data: data + }) +} + +// 删除用户 +export function delUser(userId) { + return request({ + url: '/system/user/' + userId, + method: 'delete' + }) +} + +// 用户密码重置 +export function resetUserPwd(userId, password) { + const data = { + userId, + password + } + return request({ + url: '/system/user/resetPwd', + method: 'put', + data: data + }) +} + +// 用户状态修改 +export function changeUserStatus(userId, status) { + const data = { + userId, + status + } + return request({ + url: '/system/user/changeStatus', + method: 'put', + data: data + }) +} + +// 查询用户个人信息 +export function getUserProfile() { + return request({ + url: '/system/user/profile', + method: 'get' + }) +} + +// 修改用户个人信息 +export function updateUserProfile(data) { + return request({ + url: '/system/user/profile', + method: 'put', + data: data + }) +} + +// 用户密码重置 +export function updateUserPwd(oldPassword, newPassword) { + const data = { + oldPassword, + newPassword + } + return request({ + url: '/system/user/profile/updatePwd', + method: 'put', + params: data + }) +} + +// 用户头像上传 +export function uploadAvatar(data) { + return request({ + url: '/system/user/profile/avatar', + method: 'post', + data: data + }) +} + +// 查询授权角色 +export function getAuthRole(userId) { + return request({ + url: '/system/user/authRole/' + userId, + method: 'get' + }) +} + +// 保存授权角色 +export function updateAuthRole(data) { + return request({ + url: '/system/user/authRole', + method: 'put', + params: data + }) +} + +// 查询部门下拉树结构 +export function deptTreeSelect() { + return request({ + url: '/system/user/deptTree', + method: 'get' + }) +} diff --git a/ruoyi-fastapi-frontend/src/api/tool/gen.js b/ruoyi-fastapi-frontend/src/api/tool/gen.js new file mode 100644 index 0000000..4506927 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/api/tool/gen.js @@ -0,0 +1,76 @@ +import request from '@/utils/request' + +// 查询生成表数据 +export function listTable(query) { + return request({ + url: '/tool/gen/list', + method: 'get', + params: query + }) +} +// 查询db数据库列表 +export function listDbTable(query) { + return request({ + url: '/tool/gen/db/list', + method: 'get', + params: query + }) +} + +// 查询表详细信息 +export function getGenTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'get' + }) +} + +// 修改代码生成信息 +export function updateGenTable(data) { + return request({ + url: '/tool/gen', + method: 'put', + data: data + }) +} + +// 导入表 +export function importTable(data) { + return request({ + url: '/tool/gen/importTable', + method: 'post', + params: data + }) +} + +// 预览生成代码 +export function previewTable(tableId) { + return request({ + url: '/tool/gen/preview/' + tableId, + method: 'get' + }) +} + +// 删除表数据 +export function delTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'delete' + }) +} + +// 生成代码(自定义路径) +export function genCode(tableName) { + return request({ + url: '/tool/gen/genCode/' + tableName, + method: 'get' + }) +} + +// 同步数据库 +export function synchDb(tableName) { + return request({ + url: '/tool/gen/synchDb/' + tableName, + method: 'get' + }) +} diff --git a/ruoyi-fastapi-frontend/src/assets/401_images/401.gif b/ruoyi-fastapi-frontend/src/assets/401_images/401.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/ruoyi-fastapi-frontend/src/assets/401_images/401.gif differ diff --git a/ruoyi-fastapi-frontend/src/assets/404_images/404.png b/ruoyi-fastapi-frontend/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/ruoyi-fastapi-frontend/src/assets/404_images/404.png differ diff --git a/ruoyi-fastapi-frontend/src/assets/404_images/404_cloud.png b/ruoyi-fastapi-frontend/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/ruoyi-fastapi-frontend/src/assets/404_images/404_cloud.png differ diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/404.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/404.svg new file mode 100644 index 0000000..6df5019 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/bug.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/build.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/build.svg new file mode 100644 index 0000000..97c4688 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/build.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/button.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/button.svg new file mode 100644 index 0000000..904fddc --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/button.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/cascader.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/cascader.svg new file mode 100644 index 0000000..e256024 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/cascader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/chart.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/checkbox.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/checkbox.svg new file mode 100644 index 0000000..013fd3a --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/checkbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/clipboard.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/clipboard.svg new file mode 100644 index 0000000..90923ff --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/code.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/code.svg new file mode 100644 index 0000000..5f9c5ab --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/color.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/color.svg new file mode 100644 index 0000000..44a81aa --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/component.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/component.svg new file mode 100644 index 0000000..29c3458 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/component.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/dashboard.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/date-range.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/date-range.svg new file mode 100644 index 0000000..fda571e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/date-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/date.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/date.svg new file mode 100644 index 0000000..52dc73e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/date.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/dict.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/dict.svg new file mode 100644 index 0000000..4849377 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/dict.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/documentation.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/documentation.svg new file mode 100644 index 0000000..7043122 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/documentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/download.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/download.svg new file mode 100644 index 0000000..c896951 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/drag.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/druid.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/druid.svg new file mode 100644 index 0000000..a2b4b4e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/druid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/edit.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/education.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/education.svg new file mode 100644 index 0000000..7bfb01d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/education.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/email.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/email.svg new file mode 100644 index 0000000..74d25e2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/example.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/example.svg new file mode 100644 index 0000000..46f42b5 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/excel.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/excel.svg new file mode 100644 index 0000000..74d97b8 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/exit-fullscreen.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/eye-open.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/eye.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/form.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/form.svg new file mode 100644 index 0000000..dcbaa18 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/form.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/fullscreen.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/github.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/github.svg new file mode 100644 index 0000000..db0a0d4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/guide.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/icon.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/icon.svg new file mode 100644 index 0000000..82be8ee --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/input.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/input.svg new file mode 100644 index 0000000..ab91381 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/input.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/international.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/international.svg new file mode 100644 index 0000000..e9b56ee --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/international.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/job.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/job.svg new file mode 100644 index 0000000..2a93a25 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/job.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/language.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/link.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/link.svg new file mode 100644 index 0000000..48197ba --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/list.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/list.svg new file mode 100644 index 0000000..20259ed --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/lock.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/lock.svg new file mode 100644 index 0000000..74fee54 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/log.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/log.svg new file mode 100644 index 0000000..d879d33 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/logininfor.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/logininfor.svg new file mode 100644 index 0000000..267f844 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/logininfor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/message.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/money.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/monitor.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/monitor.svg new file mode 100644 index 0000000..bc308cb --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/monitor.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/nested.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/number.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/online.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/online.svg new file mode 100644 index 0000000..330a202 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/online.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/password.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/password.svg new file mode 100644 index 0000000..6c64def --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/pdf.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/pdf.svg new file mode 100644 index 0000000..957aa0c --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/people.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/people.svg new file mode 100644 index 0000000..2bd54ae --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/peoples.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/phone.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/phone.svg new file mode 100644 index 0000000..ab8e8c4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/post.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/post.svg new file mode 100644 index 0000000..2922c61 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/post.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/qq.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/qq.svg new file mode 100644 index 0000000..ee13d4e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/qq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/question.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/question.svg new file mode 100644 index 0000000..cf75bd4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/radio.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/radio.svg new file mode 100644 index 0000000..0cde345 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/rate.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/rate.svg new file mode 100644 index 0000000..aa3b14d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/redis-list.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/redis-list.svg new file mode 100644 index 0000000..98a15b2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/redis-list.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/redis.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/redis.svg new file mode 100644 index 0000000..2f1d62d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/row.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/row.svg new file mode 100644 index 0000000..0780992 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/row.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/search.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/search.svg new file mode 100644 index 0000000..84233dd --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/select.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/select.svg new file mode 100644 index 0000000..d628382 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/select.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/server.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/server.svg new file mode 100644 index 0000000..eb287e3 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/shopping.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/shopping.svg new file mode 100644 index 0000000..87513e7 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/size.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/skill.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/slider.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/slider.svg new file mode 100644 index 0000000..fbe4f39 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/slider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/star.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/star.svg new file mode 100644 index 0000000..6cf86e6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/swagger.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/swagger.svg new file mode 100644 index 0000000..05d4e7b --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/swagger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/switch.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/switch.svg new file mode 100644 index 0000000..0ba61e3 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/switch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/system.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/system.svg new file mode 100644 index 0000000..5992593 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/system.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/tab.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/tab.svg new file mode 100644 index 0000000..b4b48e4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/table.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/table.svg new file mode 100644 index 0000000..0e3dc9d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/textarea.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/textarea.svg new file mode 100644 index 0000000..2709f29 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/textarea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/theme.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/time-range.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/time-range.svg new file mode 100644 index 0000000..13c1202 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/time-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/time.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/time.svg new file mode 100644 index 0000000..b376e32 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/tool.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/tool.svg new file mode 100644 index 0000000..48e0e35 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/tree-table.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/tree-table.svg new file mode 100644 index 0000000..8aafdb8 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/tree-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/tree.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/tree.svg new file mode 100644 index 0000000..dd4b7dd --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/upload.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/upload.svg new file mode 100644 index 0000000..bae49c0 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/user.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/user.svg new file mode 100644 index 0000000..0ba0716 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/validCode.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/validCode.svg new file mode 100644 index 0000000..cfb1021 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/validCode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/wechat.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/wechat.svg new file mode 100644 index 0000000..c586e55 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/wechat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/zip.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/zip.svg new file mode 100644 index 0000000..f806fc4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/images/dark.svg b/ruoyi-fastapi-frontend/src/assets/images/dark.svg new file mode 100644 index 0000000..f646bd7 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/images/dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/images/light.svg b/ruoyi-fastapi-frontend/src/assets/images/light.svg new file mode 100644 index 0000000..ab7cc08 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/images/light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/images/login-background.jpg b/ruoyi-fastapi-frontend/src/assets/images/login-background.jpg new file mode 100644 index 0000000..8a89eb8 Binary files /dev/null and b/ruoyi-fastapi-frontend/src/assets/images/login-background.jpg differ diff --git a/ruoyi-fastapi-frontend/src/assets/images/pay.png b/ruoyi-fastapi-frontend/src/assets/images/pay.png new file mode 100644 index 0000000..bb8b967 Binary files /dev/null and b/ruoyi-fastapi-frontend/src/assets/images/pay.png differ diff --git a/ruoyi-fastapi-frontend/src/assets/images/profile.jpg b/ruoyi-fastapi-frontend/src/assets/images/profile.jpg new file mode 100644 index 0000000..b3a940b Binary files /dev/null and b/ruoyi-fastapi-frontend/src/assets/images/profile.jpg differ diff --git a/ruoyi-fastapi-frontend/src/assets/logo/logo.png b/ruoyi-fastapi-frontend/src/assets/logo/logo.png new file mode 100644 index 0000000..e263760 Binary files /dev/null and b/ruoyi-fastapi-frontend/src/assets/logo/logo.png differ diff --git a/ruoyi-fastapi-frontend/src/assets/styles/btn.scss b/ruoyi-fastapi-frontend/src/assets/styles/btn.scss new file mode 100644 index 0000000..3590d8d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/btn.scss @@ -0,0 +1,99 @@ +@import './variables.module.scss'; + +@mixin colorBtn($color) { + background: $color; + + &:hover { + color: $color; + + &:before, + &:after { + background: $color; + } + } +} + +.blue-btn { + @include colorBtn($blue) +} + +.light-blue-btn { + @include colorBtn($light-blue) +} + +.red-btn { + @include colorBtn($red) +} + +.pink-btn { + @include colorBtn($pink) +} + +.green-btn { + @include colorBtn($green) +} + +.tiffany-btn { + @include colorBtn($tiffany) +} + +.yellow-btn { + @include colorBtn($yellow) +} + +.pan-btn { + font-size: 14px; + color: #fff; + padding: 14px 36px; + border-radius: 8px; + border: none; + outline: none; + transition: 600ms ease all; + position: relative; + display: inline-block; + + &:hover { + background: #fff; + + &:before, + &:after { + width: 100%; + transition: 600ms ease all; + } + } + + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + height: 2px; + width: 0; + transition: 400ms ease all; + } + + &::after { + right: inherit; + top: inherit; + left: 0; + bottom: 0; + } +} + +.custom-button { + display: inline-block; + line-height: 1; + white-space: nowrap; + cursor: pointer; + background: #fff; + color: #fff; + -webkit-appearance: none; + text-align: center; + box-sizing: border-box; + outline: 0; + margin: 0; + padding: 10px 15px; + font-size: 14px; + border-radius: 4px; +} diff --git a/ruoyi-fastapi-frontend/src/assets/styles/element-ui.scss b/ruoyi-fastapi-frontend/src/assets/styles/element-ui.scss new file mode 100644 index 0000000..0f175f2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/element-ui.scss @@ -0,0 +1,96 @@ +// cover some element-ui styles + +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type="file"] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +.cell { + .el-tag { + margin-right: 0px; + } +} + +.small-padding { + .cell { + padding-left: 5px; + padding-right: 5px; + } +} + +.fixed-width { + .el-button--mini { + padding: 7px 10px; + width: 60px; + } +} + +.status-col { + .cell { + padding: 0 10px; + text-align: center; + + .el-tag { + margin-right: 0px; + } + } +} + +// to fixed https://github.com/ElemeFE/element/issues/2461 +.el-dialog { + transform: none; + left: 0; + position: relative; + margin: 0 auto; +} + +// refine element ui upload +.upload-container { + .el-upload { + width: 100%; + + .el-upload-dragger { + width: 100%; + height: 200px; + } + } +} + +// dropdown +.el-dropdown-menu { + a { + display: block + } +} + +// fix date-picker ui bug in filter-item +.el-range-editor.el-input__inner { + display: inline-flex !important; +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +.el-menu--collapse + > div + > .el-submenu + > .el-submenu__title + .el-submenu__icon-arrow { + display: none; +} + +.el-dropdown .el-dropdown-link{ + color: var(--el-color-primary) !important; +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/styles/index.scss b/ruoyi-fastapi-frontend/src/assets/styles/index.scss new file mode 100644 index 0000000..2b8dca5 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/index.scss @@ -0,0 +1,184 @@ +@import './variables.module.scss'; +@import './mixin.scss'; +@import './transition.scss'; +@import './element-ui.scss'; +@import './sidebar.scss'; +@import './btn.scss'; +@import './ruoyi.scss'; + +body { + height: 100%; + margin: 0; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +.no-padding { + padding: 0px !important; +} + +.padding-content { + padding: 4px 0; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.pr-5 { + padding-right: 5px; +} + +.pl-5 { + padding-left: 5px; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.inlineBlock { + display: block; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +} + +aside { + background: #eef1f6; + padding: 8px 24px; + margin-bottom: 20px; + border-radius: 2px; + display: block; + line-height: 32px; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + color: #2c3e50; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + a { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } + } +} + +//main-container全局样式 +.app-container { + padding: 20px; +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +.pagination-container { + margin-top: 30px; +} + +.text-center { + text-align: center +} + +.sub-navbar { + height: 50px; + line-height: 50px; + position: relative; + width: 100%; + text-align: right; + padding-right: 20px; + transition: 600ms ease position; + background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); + + .subtitle { + font-size: 20px; + color: #fff; + } + + &.draft { + background: #d0d0d0; + } + + &.deleted { + background: #d0d0d0; + } +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } +} + +.filter-container { + padding-bottom: 10px; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: 10px; + } +} diff --git a/ruoyi-fastapi-frontend/src/assets/styles/mixin.scss b/ruoyi-fastapi-frontend/src/assets/styles/mixin.scss new file mode 100644 index 0000000..06fa061 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/mixin.scss @@ -0,0 +1,66 @@ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} + +@mixin pct($pct) { + width: #{$pct}; + position: relative; + margin: 0 auto; +} + +@mixin triangle($width, $height, $color, $direction) { + $width: $width/2; + $color-border-style: $height solid $color; + $transparent-border-style: $width solid transparent; + height: 0; + width: 0; + + @if $direction==up { + border-bottom: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==right { + border-left: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } + + @else if $direction==down { + border-top: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==left { + border-right: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } +} diff --git a/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss new file mode 100644 index 0000000..155fdb7 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss @@ -0,0 +1,281 @@ + /** + * 通用css样式布局处理 + * Copyright (c) 2019 ruoyi + */ + + /** 基础通用 **/ +.pt5 { + padding-top: 5px; +} +.pr5 { + padding-right: 5px; +} +.pb5 { + padding-bottom: 5px; +} +.mt5 { + margin-top: 5px; +} +.mr5 { + margin-right: 5px; +} +.mb5 { + margin-bottom: 5px; +} +.mb8 { + margin-bottom: 8px; +} +.ml5 { + margin-left: 5px; +} +.mt10 { + margin-top: 10px; +} +.mr10 { + margin-right: 10px; +} +.mb10 { + margin-bottom: 10px; +} +.ml10 { + margin-left: 10px; +} +.mt20 { + margin-top: 20px; +} +.mr20 { + margin-right: 20px; +} +.mb20 { + margin-bottom: 20px; +} +.ml20 { + margin-left: 20px; +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.el-form .el-form-item__label { + font-weight: 700; +} +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog.scrollbar .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + .el-table__header-wrapper, .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #f8f8f9 !important; + color: #515a6e; + height: 40px !important; + font-size: 13px; + } + } + .el-table__body-wrapper { + .el-button [class*="el-icon-"] + span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size:15px; + color:#6379bb; + border-bottom:1px solid #ddd; + margin:8px 10px 25px 10px; + padding-bottom:5px +} + +/** 表格布局 **/ +.pagination-container { + position: relative; + height: 25px; + margin-bottom: 10px; + margin-top: 15px; + padding: 10px 20px !important; +} + +.el-dialog .pagination-container { + position: static !important; +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid #e5e6e7; + background: #FFFFFF none; + border-radius:4px; + width: 100%; +} + +.pagination-container .el-pagination { + right: 0; + position: absolute; +} + +@media ( max-width : 768px) { + .pagination-container .el-pagination > .el-pagination__jump { + display: none !important; + } + .pagination-container .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-table .fixed-width .el-button--small { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link { + cursor: pointer; + color: #409EFF; + margin-left: 10px; +} + +.el-table .el-dropdown, .el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content > .el-checkbox { + margin-right: 8px; +} + +.list-group-striped > .list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 7px !important; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px !important; +} + +.card-box { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48D1CC; + border-color: #48D1CC; + color: #FFFFFF; +} + +.el-button--cyan { + background-color: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: absolute; + top: 50%; + transform: translate(50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost{ + opacity: .8; + color: #fff!important; + background: #42b983!important; +} + +/* 表格右侧工具栏样式 */ +.top-right-btn { + margin-left: auto; +} diff --git a/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss b/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss new file mode 100644 index 0000000..8b3c472 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss @@ -0,0 +1,238 @@ +#app { + + .main-container { + height: 100%; + transition: margin-left .28s; + margin-left: $base-sidebar-width; + position: relative; + } + + .sidebarHide { + margin-left: 0!important; + } + + .sidebar-container { + -webkit-transition: width .28s; + transition: width 0.28s; + width: $base-sidebar-width !important; + background-color: $base-menu-background; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); + box-shadow: 2px 0 6px rgba(0,21,41,.35); + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + .el-menu-item, .menu-title { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } + + .el-menu-item .el-menu-tooltip__trigger { + display: inline-block !important; + } + + // menu hover + .sub-menu-title-noDropdown, + .el-sub-menu__title { + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .is-active > .el-sub-menu__title { + color: $base-menu-color-active !important; + } + + & .nest-menu .el-sub-menu>.el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: $base-sidebar-width !important; + + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title, + & .theme-dark .el-sub-menu .el-menu-item { + background-color: $base-sub-menu-background !important; + + &:hover { + background-color: $base-sub-menu-hover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .sub-menu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-sub-menu { + overflow: hidden; + + &>.el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + } + } + + .el-menu--collapse { + .el-sub-menu { + &>.el-sub-menu__title { + &>span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + &>i { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: $base-sidebar-width !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform .28s; + width: $base-sidebar-width !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$base-sidebar-width, 0, 0); + } + } + } + + .withoutAnimation { + + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + &>.el-menu { + .svg-icon { + margin-right: 16px; + } + } + + .nest-menu .el-sub-menu>.el-sub-menu__title, + .el-menu-item { + &:hover { + // you can use $sub-menuHover + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + // the scroll bar appears when the sub-menu is too long + >.el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/ruoyi-fastapi-frontend/src/assets/styles/transition.scss b/ruoyi-fastapi-frontend/src/assets/styles/transition.scss new file mode 100644 index 0000000..073f8c6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/transition.scss @@ -0,0 +1,49 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform--move, +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all .5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all .5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all .5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/ruoyi-fastapi-frontend/src/assets/styles/variables.module.scss b/ruoyi-fastapi-frontend/src/assets/styles/variables.module.scss new file mode 100644 index 0000000..3dbfaa7 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/styles/variables.module.scss @@ -0,0 +1,65 @@ +// base color +$blue: #324157; +$light-blue: #3A71A8; +$red: #C03639; +$pink: #E65D6E; +$green: #30B08F; +$tiffany: #4AB7BD; +$yellow: #FEC171; +$panGreen: #30B08F; + +// 默认菜单主题风格 +$base-menu-color: #bfcbd9; +$base-menu-color-active: #f4f4f5; +$base-menu-background: #304156; +$base-logo-title-color: #ffffff; + +$base-menu-light-color: rgba(0, 0, 0, 0.7); +$base-menu-light-background: #ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background: #1f2d3d; +$base-sub-menu-hover: #001528; + +// 自定义暗色菜单风格 +/** +$base-menu-color:hsla(0,0%,100%,.65); +$base-menu-color-active:#fff; +$base-menu-background:#001529; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#000c17; +$base-sub-menu-hover:#001528; +*/ + +$--color-primary: #409EFF; +$--color-success: #67C23A; +$--color-warning: #E6A23C; +$--color-danger: #F56C6C; +$--color-info: #909399; + +$base-sidebar-width: 200px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuColor: $base-menu-color; + menuLightColor: $base-menu-light-color; + menuColorActive: $base-menu-color-active; + menuBackground: $base-menu-background; + menuLightBackground: $base-menu-light-background; + subMenuBackground: $base-sub-menu-background; + subMenuHover: $base-sub-menu-hover; + sideBarWidth: $base-sidebar-width; + logoTitleColor: $base-logo-title-color; + logoLightTitleColor: $base-logo-light-title-color; + primaryColor: $--color-primary; + successColor: $--color-success; + dangerColor: $--color-danger; + infoColor: $--color-info; + warningColor: $--color-warning; +} diff --git a/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue b/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..489cba1 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue @@ -0,0 +1,66 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/day.vue b/ruoyi-fastapi-frontend/src/components/Crontab/day.vue new file mode 100644 index 0000000..25c4f79 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/day.vue @@ -0,0 +1,174 @@ + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/hour.vue b/ruoyi-fastapi-frontend/src/components/Crontab/hour.vue new file mode 100644 index 0000000..9f052ad --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/hour.vue @@ -0,0 +1,127 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/index.vue b/ruoyi-fastapi-frontend/src/components/Crontab/index.vue new file mode 100644 index 0000000..910c9b3 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/index.vue @@ -0,0 +1,310 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/min.vue b/ruoyi-fastapi-frontend/src/components/Crontab/min.vue new file mode 100644 index 0000000..5d80cd2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/min.vue @@ -0,0 +1,126 @@ + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/month.vue b/ruoyi-fastapi-frontend/src/components/Crontab/month.vue new file mode 100644 index 0000000..657d3f2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/month.vue @@ -0,0 +1,141 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/result.vue b/ruoyi-fastapi-frontend/src/components/Crontab/result.vue new file mode 100644 index 0000000..5a812ee --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/result.vue @@ -0,0 +1,540 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/second.vue b/ruoyi-fastapi-frontend/src/components/Crontab/second.vue new file mode 100644 index 0000000..a7e5798 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/second.vue @@ -0,0 +1,128 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/week.vue b/ruoyi-fastapi-frontend/src/components/Crontab/week.vue new file mode 100644 index 0000000..105a3be --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/week.vue @@ -0,0 +1,197 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/year.vue b/ruoyi-fastapi-frontend/src/components/Crontab/year.vue new file mode 100644 index 0000000..b26bdae --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Crontab/year.vue @@ -0,0 +1,149 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/DictTag/index.vue b/ruoyi-fastapi-frontend/src/components/DictTag/index.vue new file mode 100644 index 0000000..7d0888c --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/DictTag/index.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/Editor/index.vue b/ruoyi-fastapi-frontend/src/components/Editor/index.vue new file mode 100644 index 0000000..0a696f2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Editor/index.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/FileUpload/index.vue b/ruoyi-fastapi-frontend/src/components/FileUpload/index.vue new file mode 100644 index 0000000..2af9672 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/FileUpload/index.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/Hamburger/index.vue b/ruoyi-fastapi-frontend/src/components/Hamburger/index.vue new file mode 100644 index 0000000..18c201e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Hamburger/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/HeaderSearch/index.vue b/ruoyi-fastapi-frontend/src/components/HeaderSearch/index.vue new file mode 100644 index 0000000..6ef6d2c --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/HeaderSearch/index.vue @@ -0,0 +1,187 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/IconSelect/index.vue b/ruoyi-fastapi-frontend/src/components/IconSelect/index.vue new file mode 100644 index 0000000..517a4af --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/IconSelect/index.vue @@ -0,0 +1,111 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/IconSelect/requireIcons.js b/ruoyi-fastapi-frontend/src/components/IconSelect/requireIcons.js new file mode 100644 index 0000000..ac22fd7 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/IconSelect/requireIcons.js @@ -0,0 +1,8 @@ +let icons = [] +const modules = import.meta.glob('./../../assets/icons/svg/*.svg'); +for (const path in modules) { + const p = path.split('assets/icons/svg/')[1].split('.svg')[0]; + icons.push(p); +} + +export default icons \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/ImagePreview/index.vue b/ruoyi-fastapi-frontend/src/components/ImagePreview/index.vue new file mode 100644 index 0000000..7e3d2b6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/ImagePreview/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue b/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue new file mode 100644 index 0000000..55dafb8 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue @@ -0,0 +1,213 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/Pagination/index.vue b/ruoyi-fastapi-frontend/src/components/Pagination/index.vue new file mode 100644 index 0000000..38de953 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Pagination/index.vue @@ -0,0 +1,105 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/ParentView/index.vue b/ruoyi-fastapi-frontend/src/components/ParentView/index.vue new file mode 100644 index 0000000..7bf6148 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/ParentView/index.vue @@ -0,0 +1,3 @@ + diff --git a/ruoyi-fastapi-frontend/src/components/RightToolbar/index.vue b/ruoyi-fastapi-frontend/src/components/RightToolbar/index.vue new file mode 100644 index 0000000..5a53dd4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/RightToolbar/index.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/RuoYi/Doc/index.vue b/ruoyi-fastapi-frontend/src/components/RuoYi/Doc/index.vue new file mode 100644 index 0000000..51f13cb --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/RuoYi/Doc/index.vue @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/RuoYi/Git/index.vue b/ruoyi-fastapi-frontend/src/components/RuoYi/Git/index.vue new file mode 100644 index 0000000..b4459f3 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/RuoYi/Git/index.vue @@ -0,0 +1,13 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/components/Screenfull/index.vue b/ruoyi-fastapi-frontend/src/components/Screenfull/index.vue new file mode 100644 index 0000000..7ad28ea --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/Screenfull/index.vue @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/SizeSelect/index.vue b/ruoyi-fastapi-frontend/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..4c2e7e9 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/SizeSelect/index.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/SvgIcon/index.vue b/ruoyi-fastapi-frontend/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..8c101f6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/SvgIcon/index.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/SvgIcon/svgicon.js b/ruoyi-fastapi-frontend/src/components/SvgIcon/svgicon.js new file mode 100644 index 0000000..4431719 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/SvgIcon/svgicon.js @@ -0,0 +1,10 @@ +import * as components from '@element-plus/icons-vue' + +export default { + install: (app) => { + for (const key in components) { + const componentConfig = components[key]; + app.component(componentConfig.name, componentConfig); + } + }, +}; diff --git a/ruoyi-fastapi-frontend/src/components/TopNav/index.vue b/ruoyi-fastapi-frontend/src/components/TopNav/index.vue new file mode 100644 index 0000000..52b40ea --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/TopNav/index.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/components/TreeSelect/index.vue b/ruoyi-fastapi-frontend/src/components/TreeSelect/index.vue new file mode 100644 index 0000000..4ff0e76 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/TreeSelect/index.vue @@ -0,0 +1,156 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/components/iFrame/index.vue b/ruoyi-fastapi-frontend/src/components/iFrame/index.vue new file mode 100644 index 0000000..091b1a2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/components/iFrame/index.vue @@ -0,0 +1,31 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/layout/components/Navbar.vue b/ruoyi-fastapi-frontend/src/layout/components/Navbar.vue new file mode 100644 index 0000000..05e15af --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/Navbar.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/layout/components/Settings/index.vue b/ruoyi-fastapi-frontend/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..d0a0e88 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/Settings/index.vue @@ -0,0 +1,205 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/layout/components/Sidebar/Link.vue b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..8011431 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,40 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/layout/components/Sidebar/Logo.vue b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..67582d0 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,81 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/layout/components/Sidebar/SidebarItem.vue b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..3a85e7e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,102 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/layout/components/Sidebar/index.vue b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..9b14dfc --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/index.vue @@ -0,0 +1,54 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/layout/components/TagsView/ScrollPane.vue b/ruoyi-fastapi-frontend/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..516b5d2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,105 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/layout/components/TagsView/index.vue b/ruoyi-fastapi-frontend/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..c1b7e3f --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/TagsView/index.vue @@ -0,0 +1,338 @@ + + + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/layout/components/index.js b/ruoyi-fastapi-frontend/src/layout/components/index.js new file mode 100644 index 0000000..fd57731 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/components/index.js @@ -0,0 +1,4 @@ +export { default as AppMain } from './AppMain' +export { default as Navbar } from './Navbar' +export { default as Settings } from './Settings' +export { default as TagsView } from './TagsView/index.vue' diff --git a/ruoyi-fastapi-frontend/src/layout/index.vue b/ruoyi-fastapi-frontend/src/layout/index.vue new file mode 100644 index 0000000..3ddb165 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/layout/index.vue @@ -0,0 +1,111 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/main.js b/ruoyi-fastapi-frontend/src/main.js new file mode 100644 index 0000000..04b6801 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/main.js @@ -0,0 +1,84 @@ +import { createApp } from 'vue' + +import Cookies from 'js-cookie' + +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import locale from 'element-plus/es/locale/lang/zh-cn' + +import '@/assets/styles/index.scss' // global css + +import App from './App' +import store from './store' +import router from './router' +import directive from './directive' // directive + +// 注册指令 +import plugins from './plugins' // plugins +import { download } from '@/utils/request' + +// svg图标 +import 'virtual:svg-icons-register' +import SvgIcon from '@/components/SvgIcon' +import elementIcons from '@/components/SvgIcon/svgicon' + +import './permission' // permission control + +import { useDict } from '@/utils/dict' +import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' + +// 分页组件 +import Pagination from '@/components/Pagination' +// 自定义表格工具组件 +import RightToolbar from '@/components/RightToolbar' +// 富文本组件 +import Editor from "@/components/Editor" +// 文件上传组件 +import FileUpload from "@/components/FileUpload" +// 图片上传组件 +import ImageUpload from "@/components/ImageUpload" +// 图片预览组件 +import ImagePreview from "@/components/ImagePreview" +// 自定义树选择组件 +import TreeSelect from '@/components/TreeSelect' +// 字典标签组件 +import DictTag from '@/components/DictTag' + +const app = createApp(App) + +// 全局方法挂载 +app.config.globalProperties.useDict = useDict +app.config.globalProperties.download = download +app.config.globalProperties.parseTime = parseTime +app.config.globalProperties.resetForm = resetForm +app.config.globalProperties.handleTree = handleTree +app.config.globalProperties.addDateRange = addDateRange +app.config.globalProperties.selectDictLabel = selectDictLabel +app.config.globalProperties.selectDictLabels = selectDictLabels + +// 全局组件挂载 +app.component('DictTag', DictTag) +app.component('Pagination', Pagination) +app.component('TreeSelect', TreeSelect) +app.component('FileUpload', FileUpload) +app.component('ImageUpload', ImageUpload) +app.component('ImagePreview', ImagePreview) +app.component('RightToolbar', RightToolbar) +app.component('Editor', Editor) + +app.use(router) +app.use(store) +app.use(plugins) +app.use(elementIcons) +app.component('svg-icon', SvgIcon) + +directive(app) + +// 使用element-plus 并且设置全局的大小 +app.use(ElementPlus, { + locale: locale, + // 支持 large、default、small + size: Cookies.get('size') || 'default' +}) + +app.mount('#app') diff --git a/ruoyi-fastapi-frontend/src/permission.js b/ruoyi-fastapi-frontend/src/permission.js new file mode 100644 index 0000000..4d495ac --- /dev/null +++ b/ruoyi-fastapi-frontend/src/permission.js @@ -0,0 +1,65 @@ +import router from './router' +import { ElMessage } from 'element-plus' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import { getToken } from '@/utils/auth' +import { isHttp } from '@/utils/validate' +import { isRelogin } from '@/utils/request' +import useUserStore from '@/store/modules/user' +import useSettingsStore from '@/store/modules/settings' +import usePermissionStore from '@/store/modules/permission' + +NProgress.configure({ showSpinner: false }); + +const whiteList = ['/login', '/register']; + +router.beforeEach((to, from, next) => { + NProgress.start() + if (getToken()) { + to.meta.title && useSettingsStore().setTitle(to.meta.title) + /* has token*/ + if (to.path === '/login') { + next({ path: '/' }) + NProgress.done() + } else if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + if (useUserStore().roles.length === 0) { + isRelogin.show = true + // 判断当前用户是否已拉取完user_info信息 + useUserStore().getInfo().then(() => { + isRelogin.show = false + usePermissionStore().generateRoutes().then(accessRoutes => { + // 根据roles权限生成可访问的路由表 + accessRoutes.forEach(route => { + if (!isHttp(route.path)) { + router.addRoute(route) // 动态添加可访问路由表 + } + }) + next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + }) + }).catch(err => { + useUserStore().logOut().then(() => { + ElMessage.error(err) + next({ path: '/' }) + }) + }) + } else { + next() + } + } + } else { + // 没有token + if (whiteList.indexOf(to.path) !== -1) { + // 在免登录白名单,直接进入 + next() + } else { + next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 + NProgress.done() + } + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/ruoyi-fastapi-frontend/src/plugins/auth.js b/ruoyi-fastapi-frontend/src/plugins/auth.js new file mode 100644 index 0000000..5e8c28d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/plugins/auth.js @@ -0,0 +1,60 @@ +import useUserStore from '@/store/modules/user' + +function authPermission(permission) { + const all_permission = "*:*:*"; + const permissions = useUserStore().permissions + if (permission && permission.length > 0) { + return permissions.some(v => { + return all_permission === v || v === permission + }) + } else { + return false + } +} + +function authRole(role) { + const super_admin = "admin"; + const roles = useUserStore().roles + if (role && role.length > 0) { + return roles.some(v => { + return super_admin === v || v === role + }) + } else { + return false + } +} + +export default { + // 验证用户是否具备某权限 + hasPermi(permission) { + return authPermission(permission); + }, + // 验证用户是否含有指定权限,只需包含其中一个 + hasPermiOr(permissions) { + return permissions.some(item => { + return authPermission(item) + }) + }, + // 验证用户是否含有指定权限,必须全部拥有 + hasPermiAnd(permissions) { + return permissions.every(item => { + return authPermission(item) + }) + }, + // 验证用户是否具备某角色 + hasRole(role) { + return authRole(role); + }, + // 验证用户是否含有指定角色,只需包含其中一个 + hasRoleOr(roles) { + return roles.some(item => { + return authRole(item) + }) + }, + // 验证用户是否含有指定角色,必须全部拥有 + hasRoleAnd(roles) { + return roles.every(item => { + return authRole(item) + }) + } +} diff --git a/ruoyi-fastapi-frontend/src/plugins/cache.js b/ruoyi-fastapi-frontend/src/plugins/cache.js new file mode 100644 index 0000000..6b5c00b --- /dev/null +++ b/ruoyi-fastapi-frontend/src/plugins/cache.js @@ -0,0 +1,77 @@ +const sessionCache = { + set (key, value) { + if (!sessionStorage) { + return + } + if (key != null && value != null) { + sessionStorage.setItem(key, value) + } + }, + get (key) { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + return sessionStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + sessionStorage.removeItem(key); + } +} +const localCache = { + set (key, value) { + if (!localStorage) { + return + } + if (key != null && value != null) { + localStorage.setItem(key, value) + } + }, + get (key) { + if (!localStorage) { + return null + } + if (key == null) { + return null + } + return localStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + localStorage.removeItem(key); + } +} + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, + /** + * 本地缓存 + */ + local: localCache +} diff --git a/ruoyi-fastapi-frontend/src/plugins/download.js b/ruoyi-fastapi-frontend/src/plugins/download.js new file mode 100644 index 0000000..1a89efd --- /dev/null +++ b/ruoyi-fastapi-frontend/src/plugins/download.js @@ -0,0 +1,79 @@ +import axios from 'axios' +import { ElLoading, ElMessage } from 'element-plus' +import { saveAs } from 'file-saver' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate } from '@/utils/ruoyi' + +const baseURL = import.meta.env.VITE_APP_BASE_API +let downloadLoadingInstance; + +export default { + name(name, isDelete = true) { + var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + resource(resource) { + var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource); + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + zip(url, name) { + var url = baseURL + url + downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", }) + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/zip' }) + this.saveAs(blob, name) + } else { + this.printErrMsg(res.data); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + ElMessage.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) + }, + saveAs(text, name, opts) { + saveAs(text, name, opts); + }, + async printErrMsg(data) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + ElMessage.error(errMsg); + } +} + diff --git a/ruoyi-fastapi-frontend/src/plugins/index.js b/ruoyi-fastapi-frontend/src/plugins/index.js new file mode 100644 index 0000000..47d1b41 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/plugins/index.js @@ -0,0 +1,18 @@ +import tab from './tab' +import auth from './auth' +import cache from './cache' +import modal from './modal' +import download from './download' + +export default function installPlugins(app){ + // 页签操作 + app.config.globalProperties.$tab = tab + // 认证对象 + app.config.globalProperties.$auth = auth + // 缓存对象 + app.config.globalProperties.$cache = cache + // 模态框对象 + app.config.globalProperties.$modal = modal + // 下载文件 + app.config.globalProperties.$download = download +} diff --git a/ruoyi-fastapi-frontend/src/plugins/modal.js b/ruoyi-fastapi-frontend/src/plugins/modal.js new file mode 100644 index 0000000..b59e14d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/plugins/modal.js @@ -0,0 +1,82 @@ +import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus' + +let loadingInstance; + +export default { + // 消息提示 + msg(content) { + ElMessage.info(content) + }, + // 错误消息 + msgError(content) { + ElMessage.error(content) + }, + // 成功消息 + msgSuccess(content) { + ElMessage.success(content) + }, + // 警告消息 + msgWarning(content) { + ElMessage.warning(content) + }, + // 弹出提示 + alert(content) { + ElMessageBox.alert(content, "系统提示") + }, + // 错误提示 + alertError(content) { + ElMessageBox.alert(content, "系统提示", { type: 'error' }) + }, + // 成功提示 + alertSuccess(content) { + ElMessageBox.alert(content, "系统提示", { type: 'success' }) + }, + // 警告提示 + alertWarning(content) { + ElMessageBox.alert(content, "系统提示", { type: 'warning' }) + }, + // 通知提示 + notify(content) { + ElNotification.info(content) + }, + // 错误通知 + notifyError(content) { + ElNotification.error(content); + }, + // 成功通知 + notifySuccess(content) { + ElNotification.success(content) + }, + // 警告通知 + notifyWarning(content) { + ElNotification.warning(content) + }, + // 确认窗体 + confirm(content) { + return ElMessageBox.confirm(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 提交内容 + prompt(content) { + return ElMessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 打开遮罩层 + loading(content) { + loadingInstance = ElLoading.service({ + lock: true, + text: content, + background: "rgba(0, 0, 0, 0.7)", + }) + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close(); + } +} diff --git a/ruoyi-fastapi-frontend/src/plugins/tab.js b/ruoyi-fastapi-frontend/src/plugins/tab.js new file mode 100644 index 0000000..7b51cf5 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/plugins/tab.js @@ -0,0 +1,69 @@ +import useTagsViewStore from '@/store/modules/tagsView' +import router from '@/router' + +export default { + // 刷新当前tab页签 + refreshPage(obj) { + const { path, query, matched } = router.currentRoute.value; + if (obj === undefined) { + matched.forEach((m) => { + if (m.components && m.components.default && m.components.default.name) { + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { + obj = { name: m.components.default.name, path: path, query: query }; + } + } + }); + } + return useTagsViewStore().delCachedView(obj).then(() => { + const { path, query } = obj + router.replace({ + path: '/redirect' + path, + query: query + }) + }) + }, + // 关闭当前tab页签,打开新页签 + closeOpenPage(obj) { + useTagsViewStore().delView(router.currentRoute.value); + if (obj !== undefined) { + return router.push(obj); + } + }, + // 关闭指定tab页签 + closePage(obj) { + if (obj === undefined) { + return useTagsViewStore().delView(router.currentRoute.value).then(({ visitedViews }) => { + const latestView = visitedViews.slice(-1)[0] + if (latestView) { + return router.push(latestView.fullPath) + } + return router.push('/'); + }); + } + return useTagsViewStore().delView(obj); + }, + // 关闭所有tab页签 + closeAllPage() { + return useTagsViewStore().delAllViews(); + }, + // 关闭左侧tab页签 + closeLeftPage(obj) { + return useTagsViewStore().delLeftTags(obj || router.currentRoute.value); + }, + // 关闭右侧tab页签 + closeRightPage(obj) { + return useTagsViewStore().delRightTags(obj || router.currentRoute.value); + }, + // 关闭其他tab页签 + closeOtherPage(obj) { + return useTagsViewStore().delOthersViews(obj || router.currentRoute.value); + }, + // 打开tab页签 + openPage(url) { + return router.push(url); + }, + // 修改tab页签 + updatePage(obj) { + return useTagsViewStore().updateVisitedView(obj); + } +} diff --git a/ruoyi-fastapi-frontend/src/router/index.js b/ruoyi-fastapi-frontend/src/router/index.js new file mode 100644 index 0000000..c4a9d64 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/router/index.js @@ -0,0 +1,175 @@ +import { createWebHistory, createRouter } from 'vue-router' +/* Layout */ +import Layout from '@/layout' + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect/index.vue') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login'), + hidden: true + }, + { + path: '/register', + component: () => import('@/views/register'), + hidden: true + }, + { + path: "/:pathMatch(.*)*", + component: () => import('@/views/error/404'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/error/401'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: '/index', + children: [ + { + path: '/index', + component: () => import('@/views/index'), + name: 'Index', + meta: { title: '首页', icon: 'dashboard', affix: true } + } + ] + }, + { + path: '/user', + component: Layout, + hidden: true, + redirect: 'noredirect', + children: [ + { + path: 'profile', + component: () => import('@/views/system/user/profile/index'), + name: 'Profile', + meta: { title: '个人中心', icon: 'user' } + } + ] + } +] + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole'), + name: 'AuthRole', + meta: { title: '分配角色', activeMenu: '/system/user' } + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser'), + name: 'AuthUser', + meta: { title: '分配用户', activeMenu: '/system/role' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data'), + name: 'Data', + meta: { title: '字典数据', activeMenu: '/system/dict' } + } + ] + }, + { + path: '/monitor/job-log', + component: Layout, + hidden: true, + permissions: ['monitor:job:list'], + children: [ + { + path: 'index/:jobId(\\d+)', + component: () => import('@/views/monitor/job/log'), + name: 'JobLog', + meta: { title: '调度日志', activeMenu: '/monitor/job' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + path: 'index/:tableId(\\d+)', + component: () => import('@/views/tool/gen/editTable'), + name: 'GenEdit', + meta: { title: '修改生成配置', activeMenu: '/tool/gen' } + } + ] + } +] + +const router = createRouter({ + history: createWebHistory(), + routes: constantRoutes, + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition + } else { + return { top: 0 } + } + }, +}); + +export default router; diff --git a/ruoyi-fastapi-frontend/src/settings.js b/ruoyi-fastapi-frontend/src/settings.js new file mode 100644 index 0000000..08a0108 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/settings.js @@ -0,0 +1,47 @@ +export default { + /** + * 网页标题 + */ + title: import.meta.env.VITE_APP_TITLE, + /** + * 侧边栏主题 深色主题theme-dark,浅色主题theme-light + */ + sideTheme: 'theme-dark', + /** + * 是否系统布局配置 + */ + showSettings: true, + + /** + * 是否显示顶部导航 + */ + topNav: false, + + /** + * 是否显示 tagsView + */ + tagsView: true, + + /** + * 是否固定头部 + */ + fixedHeader: false, + + /** + * 是否显示logo + */ + sidebarLogo: true, + + /** + * 是否显示动态标题 + */ + dynamicTitle: false, + + /** + * @type {string | array} 'production' | ['production', 'development'] + * @description Need show err logs component. + * The default is only used in the production env + * If you want to also use it in dev, you can pass ['production', 'development'] + */ + errorLog: 'production' +} diff --git a/ruoyi-fastapi-frontend/src/store/index.js b/ruoyi-fastapi-frontend/src/store/index.js new file mode 100644 index 0000000..f10f389 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/store/index.js @@ -0,0 +1,3 @@ +const store = createPinia() + +export default store \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/store/modules/app.js b/ruoyi-fastapi-frontend/src/store/modules/app.js new file mode 100644 index 0000000..0b57159 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/store/modules/app.js @@ -0,0 +1,46 @@ +import Cookies from 'js-cookie' + +const useAppStore = defineStore( + 'app', + { + state: () => ({ + sidebar: { + opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, + withoutAnimation: false, + hide: false + }, + device: 'desktop', + size: Cookies.get('size') || 'default' + }), + actions: { + toggleSideBar(withoutAnimation) { + if (this.sidebar.hide) { + return false; + } + this.sidebar.opened = !this.sidebar.opened + this.sidebar.withoutAnimation = withoutAnimation + if (this.sidebar.opened) { + Cookies.set('sidebarStatus', 1) + } else { + Cookies.set('sidebarStatus', 0) + } + }, + closeSideBar({ withoutAnimation }) { + Cookies.set('sidebarStatus', 0) + this.sidebar.opened = false + this.sidebar.withoutAnimation = withoutAnimation + }, + toggleDevice(device) { + this.device = device + }, + setSize(size) { + this.size = size; + Cookies.set('size', size) + }, + toggleSideBarHide(status) { + this.sidebar.hide = status + } + } + }) + +export default useAppStore diff --git a/ruoyi-fastapi-frontend/src/store/modules/dict.js b/ruoyi-fastapi-frontend/src/store/modules/dict.js new file mode 100644 index 0000000..27fc308 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/store/modules/dict.js @@ -0,0 +1,57 @@ +const useDictStore = defineStore( + 'dict', + { + state: () => ({ + dict: new Array() + }), + actions: { + // 获取字典 + getDict(_key) { + if (_key == null && _key == "") { + return null; + } + try { + for (let i = 0; i < this.dict.length; i++) { + if (this.dict[i].key == _key) { + return this.dict[i].value; + } + } + } catch (e) { + return null; + } + }, + // 设置字典 + setDict(_key, value) { + if (_key !== null && _key !== "") { + this.dict.push({ + key: _key, + value: value + }); + } + }, + // 删除字典 + removeDict(_key) { + var bln = false; + try { + for (let i = 0; i < this.dict.length; i++) { + if (this.dict[i].key == _key) { + this.dict.splice(i, 1); + return true; + } + } + } catch (e) { + bln = false; + } + return bln; + }, + // 清空字典 + cleanDict() { + this.dict = new Array(); + }, + // 初始字典 + initDict() { + } + } + }) + +export default useDictStore diff --git a/ruoyi-fastapi-frontend/src/store/modules/permission.js b/ruoyi-fastapi-frontend/src/store/modules/permission.js new file mode 100644 index 0000000..958fe63 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/store/modules/permission.js @@ -0,0 +1,142 @@ +import auth from '@/plugins/auth' +import router, { constantRoutes, dynamicRoutes } from '@/router' +import { getRouters } from '@/api/menu' +import Layout from '@/layout/index' +import ParentView from '@/components/ParentView' +import InnerLink from '@/layout/components/InnerLink' + +// 匹配views里面所有的.vue文件 +const modules = import.meta.glob('./../../views/**/*.vue') + +const usePermissionStore = defineStore( + 'permission', + { + state: () => ({ + routes: [], + addRoutes: [], + defaultRoutes: [], + topbarRouters: [], + sidebarRouters: [] + }), + actions: { + setRoutes(routes) { + this.addRoutes = routes + this.routes = constantRoutes.concat(routes) + }, + setDefaultRoutes(routes) { + this.defaultRoutes = constantRoutes.concat(routes) + }, + setTopbarRoutes(routes) { + this.topbarRouters = routes + }, + setSidebarRouters(routes) { + this.sidebarRouters = routes + }, + generateRoutes(roles) { + return new Promise(resolve => { + // 向后端请求路由数据 + getRouters().then(res => { + const sdata = JSON.parse(JSON.stringify(res.data)) + const rdata = JSON.parse(JSON.stringify(res.data)) + const defaultData = JSON.parse(JSON.stringify(res.data)) + const sidebarRoutes = filterAsyncRouter(sdata) + const rewriteRoutes = filterAsyncRouter(rdata, false, true) + const defaultRoutes = filterAsyncRouter(defaultData) + const asyncRoutes = filterDynamicRoutes(dynamicRoutes) + asyncRoutes.forEach(route => { router.addRoute(route) }) + this.setRoutes(rewriteRoutes) + this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)) + this.setDefaultRoutes(sidebarRoutes) + this.setTopbarRoutes(defaultRoutes) + resolve(rewriteRoutes) + }) + }) + } + } + }) + +// 遍历后台传来的路由字符串,转换为组件对象 +function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { + return asyncRouterMap.filter(route => { + if (type && route.children) { + route.children = filterChildren(route.children) + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === 'Layout') { + route.component = Layout + } else if (route.component === 'ParentView') { + route.component = ParentView + } else if (route.component === 'InnerLink') { + route.component = InnerLink + } else { + route.component = loadView(route.component) + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type) + } else { + delete route['children'] + delete route['redirect'] + } + return true + }) +} + +function filterChildren(childrenMap, lastRouter = false) { + var children = [] + childrenMap.forEach((el, index) => { + if (el.children && el.children.length) { + if (el.component === 'ParentView' && !lastRouter) { + el.children.forEach(c => { + c.path = el.path + '/' + c.path + if (c.children && c.children.length) { + children = children.concat(filterChildren(c.children, c)) + return + } + children.push(c) + }) + return + } + } + if (lastRouter) { + el.path = lastRouter.path + '/' + el.path + if (el.children && el.children.length) { + children = children.concat(filterChildren(el.children, el)) + return + } + } + children = children.concat(el) + }) + return children +} + +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes) { + const res = [] + routes.forEach(route => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route) + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route) + } + } + }) + return res +} + +export const loadView = (view) => { + let res; + for (const path in modules) { + const dir = path.split('views/')[1].split('.vue')[0]; + if (dir === view) { + res = () => modules[path](); + } + } + return res; +} + +export default usePermissionStore diff --git a/ruoyi-fastapi-frontend/src/store/modules/settings.js b/ruoyi-fastapi-frontend/src/store/modules/settings.js new file mode 100644 index 0000000..22b7336 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/store/modules/settings.js @@ -0,0 +1,38 @@ +import defaultSettings from '@/settings' +import { useDynamicTitle } from '@/utils/dynamicTitle' + +const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings + +const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' + +const useSettingsStore = defineStore( + 'settings', + { + state: () => ({ + title: '', + theme: storageSetting.theme || '#409EFF', + sideTheme: storageSetting.sideTheme || sideTheme, + showSettings: showSettings, + topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, + tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, + fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, + sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, + dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle + }), + actions: { + // 修改布局设置 + changeSetting(data) { + const { key, value } = data + if (this.hasOwnProperty(key)) { + this[key] = value + } + }, + // 设置网页标题 + setTitle(title) { + this.title = title + useDynamicTitle(); + } + } + }) + +export default useSettingsStore diff --git a/ruoyi-fastapi-frontend/src/store/modules/tagsView.js b/ruoyi-fastapi-frontend/src/store/modules/tagsView.js new file mode 100644 index 0000000..9d07f33 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/store/modules/tagsView.js @@ -0,0 +1,182 @@ +const useTagsViewStore = defineStore( + 'tags-view', + { + state: () => ({ + visitedViews: [], + cachedViews: [], + iframeViews: [] + }), + actions: { + addView(view) { + this.addVisitedView(view) + this.addCachedView(view) + }, + addIframeView(view) { + if (this.iframeViews.some(v => v.path === view.path)) return + this.iframeViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + addVisitedView(view) { + if (this.visitedViews.some(v => v.path === view.path)) return + this.visitedViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + addCachedView(view) { + if (this.cachedViews.includes(view.name)) return + if (!view.meta.noCache) { + this.cachedViews.push(view.name) + } + }, + delView(view) { + return new Promise(resolve => { + this.delVisitedView(view) + this.delCachedView(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews] + }) + }) + }, + delVisitedView(view) { + return new Promise(resolve => { + for (const [i, v] of this.visitedViews.entries()) { + if (v.path === view.path) { + this.visitedViews.splice(i, 1) + break + } + } + this.iframeViews = this.iframeViews.filter(item => item.path !== view.path) + resolve([...this.visitedViews]) + }) + }, + delIframeView(view) { + return new Promise(resolve => { + this.iframeViews = this.iframeViews.filter(item => item.path !== view.path) + resolve([...this.iframeViews]) + }) + }, + delCachedView(view) { + return new Promise(resolve => { + const index = this.cachedViews.indexOf(view.name) + index > -1 && this.cachedViews.splice(index, 1) + resolve([...this.cachedViews]) + }) + }, + delOthersViews(view) { + return new Promise(resolve => { + this.delOthersVisitedViews(view) + this.delOthersCachedViews(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews] + }) + }) + }, + delOthersVisitedViews(view) { + return new Promise(resolve => { + this.visitedViews = this.visitedViews.filter(v => { + return v.meta.affix || v.path === view.path + }) + this.iframeViews = this.iframeViews.filter(item => item.path === view.path) + resolve([...this.visitedViews]) + }) + }, + delOthersCachedViews(view) { + return new Promise(resolve => { + const index = this.cachedViews.indexOf(view.name) + if (index > -1) { + this.cachedViews = this.cachedViews.slice(index, index + 1) + } else { + this.cachedViews = [] + } + resolve([...this.cachedViews]) + }) + }, + delAllViews(view) { + return new Promise(resolve => { + this.delAllVisitedViews(view) + this.delAllCachedViews(view) + resolve({ + visitedViews: [...this.visitedViews], + cachedViews: [...this.cachedViews] + }) + }) + }, + delAllVisitedViews(view) { + return new Promise(resolve => { + const affixTags = this.visitedViews.filter(tag => tag.meta.affix) + this.visitedViews = affixTags + this.iframeViews = [] + resolve([...this.visitedViews]) + }) + }, + delAllCachedViews(view) { + return new Promise(resolve => { + this.cachedViews = [] + resolve([...this.cachedViews]) + }) + }, + updateVisitedView(view) { + for (let v of this.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + }, + delRightTags(view) { + return new Promise(resolve => { + const index = this.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + this.visitedViews = this.visitedViews.filter((item, idx) => { + if (idx <= index || (item.meta && item.meta.affix)) { + return true + } + const i = this.cachedViews.indexOf(item.name) + if (i > -1) { + this.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = this.iframeViews.findIndex(v => v.path === item.path) + this.iframeViews.splice(fi, 1) + } + return false + }) + resolve([...this.visitedViews]) + }) + }, + delLeftTags(view) { + return new Promise(resolve => { + const index = this.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + this.visitedViews = this.visitedViews.filter((item, idx) => { + if (idx >= index || (item.meta && item.meta.affix)) { + return true + } + const i = this.cachedViews.indexOf(item.name) + if (i > -1) { + this.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = this.iframeViews.findIndex(v => v.path === item.path) + this.iframeViews.splice(fi, 1) + } + return false + }) + resolve([...this.visitedViews]) + }) + } + } + }) + +export default useTagsViewStore diff --git a/ruoyi-fastapi-frontend/src/store/modules/user.js b/ruoyi-fastapi-frontend/src/store/modules/user.js new file mode 100644 index 0000000..d439c44 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/store/modules/user.js @@ -0,0 +1,72 @@ +import { login, logout, getInfo } from '@/api/login' +import { getToken, setToken, removeToken } from '@/utils/auth' +import defAva from '@/assets/images/profile.jpg' + +const useUserStore = defineStore( + 'user', + { + state: () => ({ + token: getToken(), + id: '', + name: '', + avatar: '', + roles: [], + permissions: [] + }), + actions: { + // 登录 + login(userInfo) { + const username = userInfo.username.trim() + const password = userInfo.password + const code = userInfo.code + const uuid = userInfo.uuid + return new Promise((resolve, reject) => { + login(username, password, code, uuid).then(res => { + setToken(res.token) + this.token = res.token + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + // 获取用户信息 + getInfo() { + return new Promise((resolve, reject) => { + getInfo().then(res => { + const user = res.user + const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar; + + if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 + this.roles = res.roles + this.permissions = res.permissions + } else { + this.roles = ['ROLE_DEFAULT'] + } + this.id = user.userId + this.name = user.userName + this.avatar = avatar + resolve(res) + }).catch(error => { + reject(error) + }) + }) + }, + // 退出系统 + logOut() { + return new Promise((resolve, reject) => { + logout(this.token).then(() => { + this.token = '' + this.roles = [] + this.permissions = [] + removeToken() + resolve() + }).catch(error => { + reject(error) + }) + }) + } + } + }) + +export default useUserStore diff --git a/ruoyi-fastapi-frontend/src/utils/auth.js b/ruoyi-fastapi-frontend/src/utils/auth.js new file mode 100644 index 0000000..08a43d6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/auth.js @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'Admin-Token' + +export function getToken() { + return Cookies.get(TokenKey) +} + +export function setToken(token) { + return Cookies.set(TokenKey, token) +} + +export function removeToken() { + return Cookies.remove(TokenKey) +} diff --git a/ruoyi-fastapi-frontend/src/utils/dict.js b/ruoyi-fastapi-frontend/src/utils/dict.js new file mode 100644 index 0000000..9648f14 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/dict.js @@ -0,0 +1,24 @@ +import useDictStore from '@/store/modules/dict' +import { getDicts } from '@/api/system/dict/data' + +/** + * 获取字典数据 + */ +export function useDict(...args) { + const res = ref({}); + return (() => { + args.forEach((dictType, index) => { + res.value[dictType] = []; + const dicts = useDictStore().getDict(dictType); + if (dicts) { + res.value[dictType] = dicts; + } else { + getDicts(dictType).then(resp => { + res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })) + useDictStore().setDict(dictType, res.value[dictType]); + }) + } + }) + return toRefs(res.value); + })() +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/utils/dynamicTitle.js b/ruoyi-fastapi-frontend/src/utils/dynamicTitle.js new file mode 100644 index 0000000..64404b2 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/dynamicTitle.js @@ -0,0 +1,15 @@ +import store from '@/store' +import defaultSettings from '@/settings' +import useSettingsStore from '@/store/modules/settings' + +/** + * 动态修改标题 + */ +export function useDynamicTitle() { + const settingsStore = useSettingsStore(); + if (settingsStore.dynamicTitle) { + document.title = settingsStore.title + ' - ' + defaultSettings.title; + } else { + document.title = defaultSettings.title; + } +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/utils/errorCode.js b/ruoyi-fastapi-frontend/src/utils/errorCode.js new file mode 100644 index 0000000..d2111ee --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/errorCode.js @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} diff --git a/ruoyi-fastapi-frontend/src/utils/index.js b/ruoyi-fastapi-frontend/src/utils/index.js new file mode 100644 index 0000000..4e65504 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/index.js @@ -0,0 +1,390 @@ +import { parseTime } from './ruoyi' + +/** + * 表格时间格式化 + */ +export function formatDate(cellValue) { + if (cellValue == null || cellValue == "") return ""; + var date = new Date(cellValue) + var year = date.getFullYear() + var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 + var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() + var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds +} + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export function formatTime(time, option) { + if (('' + time).length === 10) { + time = parseInt(time) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param {string} url + * @returns {Object} + */ +export function getQueryObject(url) { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param {string} input value + * @returns {number} output value + */ +export function byteLength(str) { + // returns the byte length of an utf8 string + let s = str.length + for (var i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param {Array} actual + * @returns {Array} + */ +export function cleanArray(actual) { + const newArray = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param {Object} json + * @returns {Array} + */ +export function param(json) { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj(url) { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') + if (!search) { + return {} + } + const obj = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj +} + +/** + * @param {string} val + * @returns {string} + */ +export function html2Text(val) { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText +} + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export function objectMerge(target, source) { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export function toggleClass(element, className) { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += '' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param {string} type + * @returns {Date} + */ +export function getTime(type) { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()) + } +} + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +/** + * @param {Array} arr + * @returns {Array} + */ +export function uniqueArr(arr) { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString() { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536) + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function makeMap(str, expectsLowerCase) { + const map = Object.create(null) + const list = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? val => map[val.toLowerCase()] + : val => map[val] +} + +export const exportDefault = 'export default ' + +export const beautifierConf = { + html: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'separate', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: false, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + }, + js: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'normal', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: true, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + } +} + +// 首字母大小 +export function titleCase(str) { + return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) +} + +// 下划转驼峰 +export function camelCase(str) { + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) +} + +export function isNumberStr(str) { + return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) +} + diff --git a/ruoyi-fastapi-frontend/src/utils/jsencrypt.js b/ruoyi-fastapi-frontend/src/utils/jsencrypt.js new file mode 100644 index 0000000..78d9523 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/jsencrypt.js @@ -0,0 +1,30 @@ +import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' + +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' + +const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + + 'UP8iWi1Qw0Y=' + +// 加密 +export function encrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对数据进行加密 +} + +// 解密 +export function decrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) // 设置私钥 + return encryptor.decrypt(txt) // 对数据进行解密 +} + diff --git a/ruoyi-fastapi-frontend/src/utils/permission.js b/ruoyi-fastapi-frontend/src/utils/permission.js new file mode 100644 index 0000000..93fee87 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/permission.js @@ -0,0 +1,51 @@ +import useUserStore from '@/store/modules/user' + +/** + * 字符权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkPermi(value) { + if (value && value instanceof Array && value.length > 0) { + const permissions = useUserStore().permissions + const permissionDatas = value + const all_permission = "*:*:*"; + + const hasPermission = permissions.some(permission => { + return all_permission === permission || permissionDatas.includes(permission) + }) + + if (!hasPermission) { + return false + } + return true + } else { + console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) + return false + } +} + +/** + * 角色权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkRole(value) { + if (value && value instanceof Array && value.length > 0) { + const roles = useUserStore().roles + const permissionRoles = value + const super_admin = "admin"; + + const hasRole = roles.some(role => { + return super_admin === role || permissionRoles.includes(role) + }) + + if (!hasRole) { + return false + } + return true + } else { + console.error(`need roles! Like checkRole="['admin','editor']"`) + return false + } +} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/utils/request.js b/ruoyi-fastapi-frontend/src/utils/request.js new file mode 100644 index 0000000..00b910f --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/request.js @@ -0,0 +1,152 @@ +import axios from 'axios' +import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { tansParams, blobValidate } from '@/utils/ruoyi' +import cache from '@/plugins/cache' +import { saveAs } from 'file-saver' +import useUserStore from '@/store/modules/user' + +let downloadLoadingInstance; +// 是否显示重新登录 +export let isRelogin = { show: false }; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: import.meta.env.VITE_APP_BASE_API, + // 超时 + timeout: 10000 +}) + +// request拦截器 +service.interceptors.request.use(config => { + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 + const limitSize = 5 * 1024 * 1024; // 限制存放数据5M + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') + return config; + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } + } + } + return config +}, error => { + console.log(error) + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use(res => { + // 未设置状态码则默认成功状态 + const code = res.data.code || 200; + // 获取错误信息 + const msg = errorCode[code] || res.data.msg || errorCode['default'] + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + if (code === 401) { + if (!isRelogin.show) { + isRelogin.show = true; + ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + isRelogin.show = false; + useUserStore().logOut().then(() => { + location.href = '/index'; + }) + }).catch(() => { + isRelogin.show = false; + }); + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + } else if (code === 500) { + ElMessage({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code === 601) { + ElMessage({ message: msg, type: 'warning' }) + return Promise.reject(new Error(msg)) + } else if (code !== 200) { + ElNotification.error({ title: msg }) + return Promise.reject('error') + } else { + return Promise.resolve(res.data) + } + }, + error => { + console.log('err' + error) + let { message } = error; + if (message == "Network Error") { + message = "后端接口连接异常"; + } else if (message.includes("timeout")) { + message = "系统接口请求超时"; + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.substr(message.length - 3) + "异常"; + } + ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) + return Promise.reject(error) + } +) + +// 通用下载方法 +export function download(url, params, filename, config) { + downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", }) + return service.post(url, params, { + transformRequest: [(params) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob', + ...config + }).then(async (data) => { + const isBlob = blobValidate(data); + if (isBlob) { + const blob = new Blob([data]) + saveAs(blob, filename) + } else { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + ElMessage.error(errMsg); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + ElMessage.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) +} + +export default service diff --git a/ruoyi-fastapi-frontend/src/utils/ruoyi.js b/ruoyi-fastapi-frontend/src/utils/ruoyi.js new file mode 100644 index 0000000..4efca08 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/ruoyi.js @@ -0,0 +1,246 @@ + + +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +// 表单重置 +export function resetForm(refName) { + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } +} + +// 添加日期范围 +export function addDateRange(params, dateRange, propName) { + let search = params; + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; +} + +// 回显数据字典 +export function selectDictLabel(datas, value) { + if (value === undefined) { + return ""; + } + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label); + return true; + } + }) + if (actions.length === 0) { + actions.push(value); + } + return actions.join(''); +} + +// 回显数据字典(字符串数组) +export function selectDictLabels(datas, value, separator) { + if (value === undefined || value.length ===0) { + return ""; + } + if (Array.isArray(value)) { + value = value.join(","); + } + var actions = []; + var currentSeparator = undefined === separator ? "," : separator; + var temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + var match = false; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator); + match = true; + } + }) + if (!match) { + actions.push(temp[val] + currentSeparator); + } + }) + return actions.join('').substring(0, actions.join('').length - 1); +} + +// 字符串格式化(%s ) +export function sprintf(str) { + var args = arguments, flag = true, i = 1; + str = str.replace(/%s/g, function () { + var arg = args[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; +} + +// 转换字符串,undefined,null等转化为"" +export function parseStrEmpty(str) { + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; +} + +// 数据合并 +export function mergeRecursive(source, target) { + for (var p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; + } + } + return source; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export function handleTree(data, id, parentId, children) { + let config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; + + var childrenListMap = {}; + var nodeIds = {}; + var tree = []; + + for (let d of data) { + let parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (let d of data) { + let parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + + for (let t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (let c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + return tree; +} + +/** +* 参数处理 +* @param {*} params 参数 +*/ +export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result +} + + +// 返回项目路径 +export function getNormalPath(p) { + if (p.length === 0 || !p || p == 'undefined') { + return p + }; + let res = p.replace('//', '/') + if (res[res.length - 1] === '/') { + return res.slice(0, res.length - 1) + } + return res; +} + +// 验证是否为blob格式 +export function blobValidate(data) { + return data.type !== 'application/json' +} diff --git a/ruoyi-fastapi-frontend/src/utils/scroll-to.js b/ruoyi-fastapi-frontend/src/utils/scroll-to.js new file mode 100644 index 0000000..c5d8e04 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/scroll-to.js @@ -0,0 +1,58 @@ +Math.easeInOutQuad = function(t, b, c, d) { + t /= d / 2 + if (t < 1) { + return c / 2 * t * t + b + } + t-- + return -c / 2 * (t * (t - 2) - 1) + b +} + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +var requestAnimFrame = (function() { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } +})() + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +function move(amount) { + document.documentElement.scrollTop = amount + document.body.parentNode.scrollTop = amount + document.body.scrollTop = amount +} + +function position() { + return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop +} + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export function scrollTo(to, duration, callback) { + const start = position() + const change = to - start + const increment = 20 + let currentTime = 0 + duration = (typeof (duration) === 'undefined') ? 500 : duration + var animateScroll = function() { + // increment the time + currentTime += increment + // find the value with the quadratic in-out easing function + var val = Math.easeInOutQuad(currentTime, start, change, duration) + // move the document.body + move(val) + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll) + } else { + if (callback && typeof (callback) === 'function') { + // the animation is done so lets callback + callback() + } + } + } + animateScroll() +} diff --git a/ruoyi-fastapi-frontend/src/utils/theme.js b/ruoyi-fastapi-frontend/src/utils/theme.js new file mode 100644 index 0000000..f4badc6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/theme.js @@ -0,0 +1,49 @@ +// 处理主题样式 +export function handleThemeStyle(theme) { + document.documentElement.style.setProperty('--el-color-primary', theme) + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`) + } + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`) + } +} + +// hex颜色转rgb颜色 +export function hexToRgb(str) { + str = str.replace('#', '') + let hexs = str.match(/../g) + for (let i = 0; i < 3; i++) { + hexs[i] = parseInt(hexs[i], 16) + } + return hexs +} + +// rgb颜色转Hex颜色 +export function rgbToHex(r, g, b) { + let hexs = [r.toString(16), g.toString(16), b.toString(16)] + for (let i = 0; i < 3; i++) { + if (hexs[i].length == 1) { + hexs[i] = `0${hexs[i]}` + } + } + return `#${hexs.join('')}` +} + +// 变浅颜色值 +export function getLightColor(color, level) { + let rgb = hexToRgb(color) + for (let i = 0; i < 3; i++) { + rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]) + } + return rgbToHex(rgb[0], rgb[1], rgb[2]) +} + +// 变深颜色值 +export function getDarkColor(color, level) { + let rgb = hexToRgb(color) + for (let i = 0; i < 3; i++) { + rgb[i] = Math.floor(rgb[i] * (1 - level)) + } + return rgbToHex(rgb[0], rgb[1], rgb[2]) +} diff --git a/ruoyi-fastapi-frontend/src/utils/validate.js b/ruoyi-fastapi-frontend/src/utils/validate.js new file mode 100644 index 0000000..702add4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/utils/validate.js @@ -0,0 +1,93 @@ +/** + * 判断url是否是http或https + * @param {string} path + * @returns {Boolean} + */ + export function isHttp(url) { + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 +} + +/** + * 判断path是否为外链 + * @param {string} path + * @returns {Boolean} + */ + export function isExternal(path) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUsername(str) { + const valid_map = ['admin', 'editor'] + return valid_map.indexOf(str.trim()) >= 0 +} + +/** + * @param {string} url + * @returns {Boolean} + */ +export function validURL(url) { + const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ + return reg.test(url) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validLowerCase(str) { + const reg = /^[a-z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUpperCase(str) { + const reg = /^[A-Z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validAlphabets(str) { + const reg = /^[A-Za-z]+$/ + return reg.test(str) +} + +/** + * @param {string} email + * @returns {Boolean} + */ +export function validEmail(email) { + const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return reg.test(email) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function isString(str) { + if (typeof str === 'string' || str instanceof String) { + return true + } + return false +} + +/** + * @param {Array} arg + * @returns {Boolean} + */ +export function isArray(arg) { + if (typeof Array.isArray === 'undefined') { + return Object.prototype.toString.call(arg) === '[object Array]' + } + return Array.isArray(arg) +} diff --git a/ruoyi-fastapi-frontend/src/views/error/401.vue b/ruoyi-fastapi-frontend/src/views/error/401.vue new file mode 100644 index 0000000..1ba3792 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/error/401.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/views/error/404.vue b/ruoyi-fastapi-frontend/src/views/error/404.vue new file mode 100644 index 0000000..f205303 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/error/404.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/views/index.vue b/ruoyi-fastapi-frontend/src/views/index.vue new file mode 100644 index 0000000..55367c5 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/index.vue @@ -0,0 +1,1061 @@ + + + + + + diff --git a/ruoyi-fastapi-frontend/src/views/login.vue b/ruoyi-fastapi-frontend/src/views/login.vue new file mode 100644 index 0000000..c205797 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/login.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/cache/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/cache/index.vue new file mode 100644 index 0000000..f2a53a9 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/cache/index.vue @@ -0,0 +1,132 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/cache/list.vue b/ruoyi-fastapi-frontend/src/views/monitor/cache/list.vue new file mode 100644 index 0000000..1696f3a --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/cache/list.vue @@ -0,0 +1,246 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/druid/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/druid/index.vue new file mode 100644 index 0000000..13736ec --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/druid/index.vue @@ -0,0 +1,13 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/job/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/job/index.vue new file mode 100644 index 0000000..071626a --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/job/index.vue @@ -0,0 +1,483 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/job/log.vue b/ruoyi-fastapi-frontend/src/views/monitor/job/log.vue new file mode 100644 index 0000000..48f3626 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/job/log.vue @@ -0,0 +1,277 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/logininfor/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/logininfor/index.vue new file mode 100644 index 0000000..bd58a5d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/logininfor/index.vue @@ -0,0 +1,225 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/online/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/online/index.vue new file mode 100644 index 0000000..eb17ebc --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/online/index.vue @@ -0,0 +1,106 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/operlog/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/operlog/index.vue new file mode 100644 index 0000000..cb59a47 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/operlog/index.vue @@ -0,0 +1,301 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/monitor/server/index.vue b/ruoyi-fastapi-frontend/src/views/monitor/server/index.vue new file mode 100644 index 0000000..a1178b1 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/monitor/server/index.vue @@ -0,0 +1,187 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/redirect/index.vue b/ruoyi-fastapi-frontend/src/views/redirect/index.vue new file mode 100644 index 0000000..a469960 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/redirect/index.vue @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/views/register.vue b/ruoyi-fastapi-frontend/src/views/register.vue new file mode 100644 index 0000000..64aea63 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/register.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/config/index.vue b/ruoyi-fastapi-frontend/src/views/system/config/index.vue new file mode 100644 index 0000000..34fd53d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/config/index.vue @@ -0,0 +1,305 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/dept/index.vue b/ruoyi-fastapi-frontend/src/views/system/dept/index.vue new file mode 100644 index 0000000..d073a26 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/dept/index.vue @@ -0,0 +1,274 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/dict/data.vue b/ruoyi-fastapi-frontend/src/views/system/dict/data.vue new file mode 100644 index 0000000..f817fd0 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/dict/data.vue @@ -0,0 +1,350 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/dict/index.vue b/ruoyi-fastapi-frontend/src/views/system/dict/index.vue new file mode 100644 index 0000000..297ac84 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/dict/index.vue @@ -0,0 +1,312 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/menu/index.vue b/ruoyi-fastapi-frontend/src/views/system/menu/index.vue new file mode 100644 index 0000000..bb486e9 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/menu/index.vue @@ -0,0 +1,427 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/notice/index.vue b/ruoyi-fastapi-frontend/src/views/system/notice/index.vue new file mode 100644 index 0000000..c26b8c4 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/notice/index.vue @@ -0,0 +1,283 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/post/index.vue b/ruoyi-fastapi-frontend/src/views/system/post/index.vue new file mode 100644 index 0000000..6fd1b64 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/post/index.vue @@ -0,0 +1,277 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/role/authUser.vue b/ruoyi-fastapi-frontend/src/views/system/role/authUser.vue new file mode 100644 index 0000000..66b5f5e --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/role/authUser.vue @@ -0,0 +1,172 @@ + + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/role/index.vue b/ruoyi-fastapi-frontend/src/views/system/role/index.vue new file mode 100644 index 0000000..65c8216 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/role/index.vue @@ -0,0 +1,559 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/role/selectUser.vue b/ruoyi-fastapi-frontend/src/views/system/role/selectUser.vue new file mode 100644 index 0000000..9be1ec9 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/role/selectUser.vue @@ -0,0 +1,140 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/user/authRole.vue b/ruoyi-fastapi-frontend/src/views/system/user/authRole.vue new file mode 100644 index 0000000..0b5c14b --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/user/authRole.vue @@ -0,0 +1,112 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/user/index.vue b/ruoyi-fastapi-frontend/src/views/system/user/index.vue new file mode 100644 index 0000000..d6cfdd6 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/user/index.vue @@ -0,0 +1,607 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/user/profile/index.vue b/ruoyi-fastapi-frontend/src/views/system/user/profile/index.vue new file mode 100644 index 0000000..5851f05 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/user/profile/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue b/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue new file mode 100644 index 0000000..dec2d79 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/user/profile/resetPwd.vue @@ -0,0 +1,57 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/system/user/profile/userAvatar.vue b/ruoyi-fastapi-frontend/src/views/system/user/profile/userAvatar.vue new file mode 100644 index 0000000..3b39636 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/user/profile/userAvatar.vue @@ -0,0 +1,171 @@ + + + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/views/system/user/profile/userInfo.vue b/ruoyi-fastapi-frontend/src/views/system/user/profile/userInfo.vue new file mode 100644 index 0000000..a5e1c77 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/system/user/profile/userInfo.vue @@ -0,0 +1,67 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/tool/gen/basicInfoForm.vue b/ruoyi-fastapi-frontend/src/views/tool/gen/basicInfoForm.vue new file mode 100644 index 0000000..39c8515 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/tool/gen/basicInfoForm.vue @@ -0,0 +1,48 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/tool/gen/editTable.vue b/ruoyi-fastapi-frontend/src/views/tool/gen/editTable.vue new file mode 100644 index 0000000..ddcbfd5 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/tool/gen/editTable.vue @@ -0,0 +1,198 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/tool/gen/genInfoForm.vue b/ruoyi-fastapi-frontend/src/views/tool/gen/genInfoForm.vue new file mode 100644 index 0000000..1856eae --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/tool/gen/genInfoForm.vue @@ -0,0 +1,297 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/tool/gen/importTable.vue b/ruoyi-fastapi-frontend/src/views/tool/gen/importTable.vue new file mode 100644 index 0000000..33b5633 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/tool/gen/importTable.vue @@ -0,0 +1,118 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/tool/gen/index.vue b/ruoyi-fastapi-frontend/src/views/tool/gen/index.vue new file mode 100644 index 0000000..fb5bb23 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/tool/gen/index.vue @@ -0,0 +1,283 @@ + + + diff --git a/ruoyi-fastapi-frontend/src/views/tool/swagger/index.vue b/ruoyi-fastapi-frontend/src/views/tool/swagger/index.vue new file mode 100644 index 0000000..a44fa71 --- /dev/null +++ b/ruoyi-fastapi-frontend/src/views/tool/swagger/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/ruoyi-fastapi-frontend/vite.config.js b/ruoyi-fastapi-frontend/vite.config.js new file mode 100644 index 0000000..2b6f847 --- /dev/null +++ b/ruoyi-fastapi-frontend/vite.config.js @@ -0,0 +1,58 @@ +import { defineConfig, loadEnv } from 'vite' +import path from 'path' +import createVitePlugins from './vite/plugins' + +// https://vitejs.dev/config/ +export default defineConfig(({ mode, command }) => { + const env = loadEnv(mode, process.cwd()) + const { VITE_APP_ENV } = env + return { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + base: VITE_APP_ENV === 'production' ? '/' : '/', + plugins: createVitePlugins(env, command === 'build'), + resolve: { + // https://cn.vitejs.dev/config/#resolve-alias + alias: { + // 设置路径 + '~': path.resolve(__dirname, './'), + // 设置别名 + '@': path.resolve(__dirname, './src') + }, + // https://cn.vitejs.dev/config/#resolve-extensions + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] + }, + // vite 相关配置 + server: { + port: 80, + host: true, + open: true, + proxy: { + // https://cn.vitejs.dev/config/#server-proxy + '/dev-api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (p) => p.replace(/^\/dev-api/, '') + } + } + }, + //fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file + css: { + postcss: { + plugins: [ + { + postcssPlugin: 'internal:charset-removal', + AtRule: { + charset: (atRule) => { + if (atRule.name === 'charset') { + atRule.remove(); + } + } + } + } + ] + } + } + } +}) diff --git a/ruoyi-fastapi-frontend/vite/plugins/auto-import.js b/ruoyi-fastapi-frontend/vite/plugins/auto-import.js new file mode 100644 index 0000000..a5d3576 --- /dev/null +++ b/ruoyi-fastapi-frontend/vite/plugins/auto-import.js @@ -0,0 +1,12 @@ +import autoImport from 'unplugin-auto-import/vite' + +export default function createAutoImport() { + return autoImport({ + imports: [ + 'vue', + 'vue-router', + 'pinia' + ], + dts: false + }) +} diff --git a/ruoyi-fastapi-frontend/vite/plugins/compression.js b/ruoyi-fastapi-frontend/vite/plugins/compression.js new file mode 100644 index 0000000..e90aaec --- /dev/null +++ b/ruoyi-fastapi-frontend/vite/plugins/compression.js @@ -0,0 +1,28 @@ +import compression from 'vite-plugin-compression' + +export default function createCompression(env) { + const { VITE_BUILD_COMPRESS } = env + const plugin = [] + if (VITE_BUILD_COMPRESS) { + const compressList = VITE_BUILD_COMPRESS.split(',') + if (compressList.includes('gzip')) { + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + plugin.push( + compression({ + ext: '.gz', + deleteOriginFile: false + }) + ) + } + if (compressList.includes('brotli')) { + plugin.push( + compression({ + ext: '.br', + algorithm: 'brotliCompress', + deleteOriginFile: false + }) + ) + } + } + return plugin +} diff --git a/ruoyi-fastapi-frontend/vite/plugins/index.js b/ruoyi-fastapi-frontend/vite/plugins/index.js new file mode 100644 index 0000000..10e17c3 --- /dev/null +++ b/ruoyi-fastapi-frontend/vite/plugins/index.js @@ -0,0 +1,15 @@ +import vue from '@vitejs/plugin-vue' + +import createAutoImport from './auto-import' +import createSvgIcon from './svg-icon' +import createCompression from './compression' +import createSetupExtend from './setup-extend' + +export default function createVitePlugins(viteEnv, isBuild = false) { + const vitePlugins = [vue()] + vitePlugins.push(createAutoImport()) + vitePlugins.push(createSetupExtend()) + vitePlugins.push(createSvgIcon(isBuild)) + isBuild && vitePlugins.push(...createCompression(viteEnv)) + return vitePlugins +} diff --git a/ruoyi-fastapi-frontend/vite/plugins/setup-extend.js b/ruoyi-fastapi-frontend/vite/plugins/setup-extend.js new file mode 100644 index 0000000..ed8342e --- /dev/null +++ b/ruoyi-fastapi-frontend/vite/plugins/setup-extend.js @@ -0,0 +1,5 @@ +import setupExtend from 'unplugin-vue-setup-extend-plus/vite' + +export default function createSetupExtend() { + return setupExtend({}) +} diff --git a/ruoyi-fastapi-frontend/vite/plugins/svg-icon.js b/ruoyi-fastapi-frontend/vite/plugins/svg-icon.js new file mode 100644 index 0000000..30a4140 --- /dev/null +++ b/ruoyi-fastapi-frontend/vite/plugins/svg-icon.js @@ -0,0 +1,10 @@ +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' +import path from 'path' + +export default function createSvgIcon(isBuild) { + return createSvgIconsPlugin({ + iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: isBuild + }) +}