diff --git a/README.md b/README.md index 123bfe379c669aaae6407f8002650260b718725d..9fec6a0fbf1a83898de4bedd9b00d87f379ecfe7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@

logo

-

RuoYi-Vue3-FastAPI v1.6.2

+

RuoYi-Vue3-FastAPI v1.7.0

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

- + +

diff --git a/ruoyi-fastapi-backend/.env.dev b/ruoyi-fastapi-backend/.env.dev index 9ddf8f30d6fe9024e42ea67a741f4d54a59428dd..397c44abd3a8dcde0be693419e51aa7988c02a44 100644 --- a/ruoyi-fastapi-backend/.env.dev +++ b/ruoyi-fastapi-backend/.env.dev @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.6.2' +APP_VERSION= '1.7.0' # 应用是否开启热重载 APP_RELOAD = true # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/.env.prod b/ruoyi-fastapi-backend/.env.prod index c7bf1f14f59de9d9fd65d344c790c6df91ae76e4..11b8303b3c2d0da818d5fffe1c19927060b4fd76 100644 --- a/ruoyi-fastapi-backend/.env.prod +++ b/ruoyi-fastapi-backend/.env.prod @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.6.2' +APP_VERSION= '1.7.0' # 应用是否开启热重载 APP_RELOAD = false # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/alembic.ini b/ruoyi-fastapi-backend/alembic.ini new file mode 100644 index 0000000000000000000000000000000000000000..d9a7ee9581604b7fc3caea2422ec1489139c0fcd --- /dev/null +++ b/ruoyi-fastapi-backend/alembic.ini @@ -0,0 +1,153 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S + +[settings] +# This section is used to set environment variables for the alembic. +# The env option is used to specify the environment for the alembic. +# It can be set to dev or prod. +env = dev diff --git a/ruoyi-fastapi-backend/alembic/README b/ruoyi-fastapi-backend/alembic/README new file mode 100644 index 0000000000000000000000000000000000000000..e0d0858f266ec27b243e8b92301fc7002e1f2745 --- /dev/null +++ b/ruoyi-fastapi-backend/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration with an async dbapi. \ No newline at end of file diff --git a/ruoyi-fastapi-backend/alembic/env.py b/ruoyi-fastapi-backend/alembic/env.py new file mode 100644 index 0000000000000000000000000000000000000000..98828e63be62774254d792119f838947dd6bb62f --- /dev/null +++ b/ruoyi-fastapi-backend/alembic/env.py @@ -0,0 +1,116 @@ +import asyncio +import os +from alembic import context +from logging.config import fileConfig +from sqlalchemy import pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config +from config.database import Base, ASYNC_SQLALCHEMY_DATABASE_URL +from utils.import_util import ImportUtil + + +# 判断vesrions目录是否存在,如果不存在则创建 +alembic_veresions_path = 'alembic/versions' +if not os.path.exists(alembic_veresions_path): + os.makedirs(alembic_veresions_path) + + +# 自动查找所有模型 +found_models = ImportUtil.find_models(Base) + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +alembic_config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if alembic_config.config_file_name is not None: + fileConfig(alembic_config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +target_metadata = Base.metadata +# ASYNC_SQLALCHEMY_DATABASE_URL = 'mysql+asyncmy://root:mysqlroot@127.0.0.1:3306/ruoyi-fastapi' +# other values from the config, defined by the needs of env.py, +alembic_config.set_main_option('sqlalchemy.url', ASYNC_SQLALCHEMY_DATABASE_URL) + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = alembic_config.get_main_option('sqlalchemy.url') + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={'paramstyle': 'named'}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + def process_revision_directives(context, revision, directives): + script = directives[0] + + # 检查所有操作集是否为空 + all_empty = all(ops.is_empty() for ops in script.upgrade_ops_list) + + if all_empty: + # 如果没有实际变更,不生成迁移文件 + directives[:] = [] + print('❎️ 未检测到模型变更,不生成迁移文件') + else: + print('✅️ 检测到模型变更,生成迁移文件') + + context.configure( + connection=connection, + target_metadata=target_metadata, + compare_type=True, + compare_server_default=True, + transaction_per_migration=True, + process_revision_directives=process_revision_directives, + ) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_async_migrations() -> None: + """In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + connectable = async_engine_from_config( + alembic_config.get_section(alembic_config.config_ini_section, {}), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + + asyncio.run(run_async_migrations()) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/ruoyi-fastapi-backend/alembic/script.py.mako b/ruoyi-fastapi-backend/alembic/script.py.mako new file mode 100644 index 0000000000000000000000000000000000000000..11016301e749297acb67822efc7974ee53c905c6 --- /dev/null +++ b/ruoyi-fastapi-backend/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/ruoyi-fastapi-backend/config/env.py b/ruoyi-fastapi-backend/config/env.py index 192a2afa75baf0431f299dd2ac209ab6efb016e5..3a2b3af19ef8b3c0146d2ea0a14c1c5cc40e8b87 100644 --- a/ruoyi-fastapi-backend/config/env.py +++ b/ruoyi-fastapi-backend/config/env.py @@ -1,4 +1,5 @@ import argparse +import configparser import os import sys from dotenv import load_dotenv @@ -206,7 +207,15 @@ class GetConfig: """ 解析命令行参数 """ - if 'uvicorn' in sys.argv[0]: + # 检查是否在alembic环境中运行,如果是则跳过参数解析 + if 'alembic' in sys.argv[0] or any('alembic' in arg for arg in sys.argv): + ini_config = configparser.ConfigParser() + ini_config.read('alembic.ini', encoding='utf-8') + if 'settings' in ini_config: + # 获取env选项 + env_value = ini_config['settings'].get('env') + os.environ['APP_ENV'] = env_value if env_value else 'dev' + elif 'uvicorn' in sys.argv[0]: # 使用uvicorn启动时,命令行参数需要按照uvicorn的文档进行配置,无法自定义参数 pass else: diff --git a/ruoyi-fastapi-backend/config/get_db.py b/ruoyi-fastapi-backend/config/get_db.py index 20986aeebe8dedcfa9327dd569ca535ad0ef3ee2..e5930c7d11843e3362867dd4cf235714164260f4 100644 --- a/ruoyi-fastapi-backend/config/get_db.py +++ b/ruoyi-fastapi-backend/config/get_db.py @@ -18,7 +18,7 @@ async def init_create_table(): :return: """ - logger.info('初始化数据库连接...') + logger.info('🔎 初始化数据库连接...') async with async_engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) - logger.info('数据库连接成功') + logger.info('✅️ 数据库连接成功') diff --git a/ruoyi-fastapi-backend/config/get_redis.py b/ruoyi-fastapi-backend/config/get_redis.py index 9d78cad0c935cf9fc91a6f3b67aecff132174178..ee4b6bd37f3541169a9e28e04e3eccfbd08c02ce 100644 --- a/ruoyi-fastapi-backend/config/get_redis.py +++ b/ruoyi-fastapi-backend/config/get_redis.py @@ -19,7 +19,7 @@ class RedisUtil: :return: Redis连接对象 """ - logger.info('开始连接redis...') + logger.info('🔎 开始连接redis...') redis = await aioredis.from_url( url=f'redis://{RedisConfig.redis_host}', port=RedisConfig.redis_port, @@ -32,15 +32,15 @@ class RedisUtil: try: connection = await redis.ping() if connection: - logger.info('redis连接成功') + logger.info('✅️ redis连接成功') else: - logger.error('redis连接失败') + logger.error('❌️ redis连接失败') except AuthenticationError as e: - logger.error(f'redis用户名或密码错误,详细错误信息:{e}') + logger.error(f'❌️ redis用户名或密码错误,详细错误信息:{e}') except TimeoutError as e: - logger.error(f'redis连接超时,详细错误信息:{e}') + logger.error(f'❌️ redis连接超时,详细错误信息:{e}') except RedisError as e: - logger.error(f'redis连接错误,详细错误信息:{e}') + logger.error(f'❌️ redis连接错误,详细错误信息:{e}') return redis @classmethod @@ -52,7 +52,7 @@ class RedisUtil: :return: """ await app.state.redis.close() - logger.info('关闭redis连接成功') + logger.info('✅️ 关闭redis连接成功') @classmethod async def init_sys_dict(cls, redis): diff --git a/ruoyi-fastapi-backend/config/get_scheduler.py b/ruoyi-fastapi-backend/config/get_scheduler.py index 4b0c29834476783ee5501d031af350f6fe0050fa..fadcf02e849803dbeeee30535e7a4d1521040c9f 100644 --- a/ruoyi-fastapi-backend/config/get_scheduler.py +++ b/ruoyi-fastapi-backend/config/get_scheduler.py @@ -131,7 +131,7 @@ class SchedulerUtil: :return: """ - logger.info('开始启动定时任务...') + logger.info('🔎 开始启动定时任务...') scheduler.start() async with AsyncSessionLocal() as session: job_list = await JobDao.get_job_list_for_scheduler(session) @@ -139,7 +139,7 @@ class SchedulerUtil: cls.remove_scheduler_job(job_id=str(item.job_id)) cls.add_scheduler_job(item) scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL) - logger.info('系统初始定时任务加载成功') + logger.info('✅️ 系统初始定时任务加载成功') @classmethod async def close_system_scheduler(cls): @@ -149,7 +149,7 @@ class SchedulerUtil: :return: """ scheduler.shutdown() - logger.info('关闭定时任务成功') + logger.info('✅️ 关闭定时任务成功') @classmethod def get_scheduler_job(cls, job_id: Union[str, int]): diff --git a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py index 1d01c1c1ffc422a8a49b3f30fe70ec8ed361104a..f7e938cf8fe54de1ef1314359ee2737ed01f7caa 100644 --- a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py +++ b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py @@ -1,12 +1,13 @@ +import httpx import inspect import json import os -import requests import time +from async_lru import alru_cache from datetime import datetime from fastapi import Request from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse -from functools import lru_cache, wraps +from functools import wraps from sqlalchemy.ext.asyncio import AsyncSession from typing import Any, Callable, Literal, Optional from user_agents import parse @@ -46,7 +47,7 @@ class Log: def __call__(self, func): @wraps(func) async def wrapper(*args, **kwargs): - start_time = time.time() + start_time = time.perf_counter() # 获取被装饰函数的文件路径 file_path = inspect.getfile(func) # 获取项目根路径 @@ -74,7 +75,7 @@ class Log: oper_ip = request.headers.get('X-Forwarded-For') oper_location = '内网IP' if AppConfig.app_ip_location_query: - oper_location = get_ip_location(oper_ip) + oper_location = await get_ip_location(oper_ip) # 根据不同的请求类型使用不同的方法获取请求参数 content_type = request.headers.get('Content-Type') if content_type and ( @@ -129,7 +130,7 @@ class Log: logger.exception(e) result = ResponseUtil.error(msg=str(e)) # 获取请求耗时 - cost_time = float(time.time() - start_time) * 100 + cost_time = float(time.perf_counter() - start_time) * 1000 # 判断请求是否来自api文档 request_from_swagger = ( request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False @@ -203,8 +204,8 @@ class Log: return wrapper -@lru_cache() -def get_ip_location(oper_ip: str): +@alru_cache() +async def get_ip_location(oper_ip: str): """ 查询ip归属区域 @@ -215,12 +216,13 @@ def get_ip_location(oper_ip: str): try: if oper_ip != '127.0.0.1' and oper_ip != 'localhost': oper_location = '未知' - ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}') - if ip_result.status_code == 200: - prov = ip_result.json().get('data').get('prov') - city = ip_result.json().get('data').get('city') - if prov or city: - oper_location = f'{prov}-{city}' + async with httpx.AsyncClient() as client: + ip_result = await client.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}') + if ip_result.status_code == 200: + prov = ip_result.json().get('data', {}).get('prov') + city = ip_result.json().get('data', {}).get('city') + if prov or city: + oper_location = f'{prov}-{city}' except Exception as e: oper_location = '未知' print(e) diff --git a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py index 6d13fcc82133850d7ab9330c40538c8f4e505483..1a3c198aeecffcef95a9db0b0f3b47ff58e199c6 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/login_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/login_controller.py @@ -141,8 +141,11 @@ async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme) payload = jwt.decode( token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False} ) - session_id: str = payload.get('session_id') - await LoginService.logout_services(request, session_id) + if AppConfig.app_same_time_login: + token_id: str = payload.get('session_id') + else: + token_id: str = payload.get('user_id') + await LoginService.logout_services(request, token_id) logger.info('退出成功') return ResponseUtil.success(msg='退出成功') diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py index ed7cf7af4a5b2c15e06d0c982c5ae2feec85ae22..91d661130b078d37c5764cc2c4114b0df7aea09b 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -165,6 +165,7 @@ async def reset_system_user_pwd( edit_user = EditUserModel( userId=reset_user.user_id, password=PwdUtil.get_password_hash(reset_user.password), + pwdUpdateDate=datetime.now(), updateBy=current_user.user.user_name, updateTime=datetime.now(), type='pwd', @@ -304,6 +305,7 @@ async def reset_system_user_password( userId=current_user.user.user_id, oldPassword=reset_password.old_password, password=reset_password.new_password, + pwdUpdateDate=datetime.now(), updateBy=current_user.user.user_name, updateTime=datetime.now(), ) diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py index 805d4602b2c01b1e0552004fa1504592efbd3192..fc30048c501e800431414131dc8e0d1e640c0e72 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py @@ -104,15 +104,24 @@ class JobDao: return db_job @classmethod - async def edit_job_dao(cls, db: AsyncSession, job: dict): + async def edit_job_dao(cls, db: AsyncSession, job: dict, old_job: JobModel): """ 编辑定时任务数据库操作 :param db: orm对象 :param job: 需要更新的定时任务字典 + :param old_job: 原定时任务对象 :return: """ - await db.execute(update(SysJob), [job]) + await db.execute( + update(SysJob) + .where( + SysJob.job_id == old_job.job_id, + SysJob.job_name == old_job.job_name, + SysJob.job_group == old_job.job_group, + ) + .values(**job) + ) @classmethod async def delete_job_dao(cls, db: AsyncSession, job: JobModel): diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py index 012d2be2df2a391ad55069044dc268332cf474aa..8a3235dba9be60c24bc9199c3c9e2af9e9de8f4e 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy import CHAR, Column, DateTime, Integer, String from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysConfig(Base): @@ -9,14 +11,20 @@ class SysConfig(Base): """ __tablename__ = 'sys_config' + __table_args__ = {'comment': '参数配置表'} - config_id = Column(Integer, primary_key=True, autoincrement=True, comment='参数主键') - config_name = Column(String(100), nullable=True, default='', comment='参数名称') - config_key = Column(String(100), nullable=True, default='', comment='参数键名') - config_value = Column(String(500), nullable=True, default='', comment='参数键值') - config_type = Column(String(1), nullable=True, default='N', comment='系统内置(Y是 N否)') - create_by = Column(String(64), nullable=True, default='', comment='创建者') + config_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='参数主键') + config_name = Column(String(100), nullable=True, server_default="''", comment='参数名称') + config_key = Column(String(100), nullable=True, server_default="''", comment='参数键名') + config_value = Column(String(500), nullable=True, server_default="''", comment='参数键值') + config_type = Column(CHAR(1), nullable=True, server_default='N', comment='系统内置(Y是 N否)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default=None, comment='备注') + remark = Column( + String(500), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py index 44e2f02f3a6c6929f8d66dc6c2845320754ab8cd..48baf8d6695252bdd76058f005ab69624b5194f7 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysDept(Base): @@ -9,18 +11,34 @@ class SysDept(Base): """ __tablename__ = 'sys_dept' + __table_args__ = {'comment': '部门表'} - dept_id = Column(Integer, primary_key=True, autoincrement=True, comment='部门id') - parent_id = Column(Integer, default=0, comment='父部门id') - ancestors = Column(String(50), nullable=True, default='', comment='祖级列表') - dept_name = Column(String(30), nullable=True, default='', comment='部门名称') - order_num = Column(Integer, default=0, comment='显示顺序') - leader = Column(String(20), nullable=True, default=None, comment='负责人') - phone = Column(String(11), nullable=True, default=None, comment='联系电话') - email = Column(String(50), nullable=True, default=None, comment='邮箱') - status = Column(String(1), nullable=True, default='0', comment='部门状态(0正常 1停用)') - del_flag = Column(String(1), nullable=True, default='0', comment='删除标志(0代表存在 2代表删除)') - create_by = Column(String(64), nullable=True, default='', comment='创建者') + dept_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='部门id') + parent_id = Column(BigInteger, server_default='0', comment='父部门id') + ancestors = Column(String(50), nullable=True, server_default="''", comment='祖级列表') + dept_name = Column(String(30), nullable=True, server_default="''", comment='部门名称') + order_num = Column(Integer, server_default='0', comment='显示顺序') + leader = Column( + String(20), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='负责人', + ) + phone = Column( + String(11), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='联系电话', + ) + email = Column( + String(50), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='邮箱', + ) + status = Column(CHAR(1), nullable=True, server_default='0', comment='部门状态(0正常 1停用)') + del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py index 7a155ea5289c5f8eb339466d62eb1e89e5812167..de6a9c4cfb1c9503e6aa0725d1d8621bc5c0ca7b 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint +from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysDictType(Base): @@ -9,18 +11,22 @@ class SysDictType(Base): """ __tablename__ = 'sys_dict_type' + __table_args__ = {'comment': '字典类型表'} - dict_id = Column(Integer, primary_key=True, autoincrement=True, comment='字典主键') - dict_name = Column(String(100), nullable=True, default='', comment='字典名称') - dict_type = Column(String(100), nullable=True, default='', comment='字典类型') - status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)') - create_by = Column(String(64), nullable=True, default='', comment='创建者') + dict_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典主键') + dict_name = Column(String(100), nullable=True, server_default="''", comment='字典名称') + dict_type = Column(String(100), unique=True, nullable=True, server_default="''", comment='字典类型') + status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default=None, comment='备注') - - __table_args__ = (UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'),) + remark = Column( + String(500), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) class SysDictData(Base): @@ -29,18 +35,34 @@ class SysDictData(Base): """ __tablename__ = 'sys_dict_data' + __table_args__ = {'comment': '字典数据表'} - dict_code = Column(Integer, primary_key=True, autoincrement=True, comment='字典编码') - dict_sort = Column(Integer, nullable=True, default=0, comment='字典排序') - dict_label = Column(String(100), nullable=True, default='', comment='字典标签') - dict_value = Column(String(100), nullable=True, default='', comment='字典键值') - dict_type = Column(String(100), nullable=True, default='', comment='字典类型') - css_class = Column(String(100), nullable=True, default=None, comment='样式属性(其他样式扩展)') - list_class = Column(String(100), nullable=True, default=None, comment='表格回显样式') - is_default = Column(String(1), nullable=True, default='N', comment='是否默认(Y是 N否)') - status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)') - create_by = Column(String(64), nullable=True, default='', comment='创建者') + dict_code = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典编码') + dict_sort = Column(Integer, nullable=True, server_default='0', comment='字典排序') + dict_label = Column(String(100), nullable=True, server_default="''", comment='字典标签') + dict_value = Column(String(100), nullable=True, server_default="''", comment='字典键值') + dict_type = Column(String(100), nullable=True, server_default="''", comment='字典类型') + css_class = Column( + String(100), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='样式属性(其他样式扩展)', + ) + list_class = Column( + String(100), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='表格回显样式', + ) + is_default = Column(CHAR(1), nullable=True, server_default='N', comment='是否默认(Y是 N否)') + status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default=None, comment='备注') + remark = Column( + String(500), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py index b2cbfb8dbce4cb969a8b1de9b56ad4022d145815..b966066340d14bfe9f98d7f5d19a52671335ea68 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, DOUBLE, Index, LargeBinary, String from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysJob(Base): @@ -9,28 +11,29 @@ class SysJob(Base): """ __tablename__ = 'sys_job' + __table_args__ = {'comment': '定时任务调度表'} - job_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务ID') - job_name = Column(String(64), nullable=True, default='', comment='任务名称') - job_group = Column(String(64), nullable=True, default='default', comment='任务组名') - job_executor = Column(String(64), nullable=True, default='default', comment='任务执行器') + job_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务ID') + job_name = Column(String(64), primary_key=True, nullable=False, server_default="''", comment='任务名称') + job_group = Column(String(64), primary_key=True, nullable=False, server_default='default', comment='任务组名') + job_executor = Column(String(64), nullable=True, server_default='default', comment='任务执行器') invoke_target = Column(String(500), nullable=False, comment='调用目标字符串') - job_args = Column(String(255), nullable=True, default='', comment='位置参数') - job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数') - cron_expression = Column(String(255), nullable=True, default='', comment='cron执行表达式') + job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数') + job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数') + cron_expression = Column(String(255), nullable=True, server_default="''", comment='cron执行表达式') misfire_policy = Column( String(20), nullable=True, - default='3', + server_default='3', comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)', ) - concurrent = Column(String(1), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)') - status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1暂停)') - create_by = Column(String(64), nullable=True, default='', comment='创建者') + concurrent = Column(CHAR(1), nullable=True, server_default='1', comment='是否并发执行(0允许 1禁止)') + status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1暂停)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default='', comment='备注信息') + remark = Column(String(500), nullable=True, server_default="''", comment='备注信息') class SysJobLog(Base): @@ -39,16 +42,33 @@ class SysJobLog(Base): """ __tablename__ = 'sys_job_log' + __table_args__ = {'comment': '定时任务调度日志表'} - job_log_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务日志ID') + job_log_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务日志ID') job_name = Column(String(64), nullable=False, comment='任务名称') job_group = Column(String(64), nullable=False, comment='任务组名') job_executor = Column(String(64), nullable=False, comment='任务执行器') invoke_target = Column(String(500), nullable=False, comment='调用目标字符串') - job_args = Column(String(255), nullable=True, default='', comment='位置参数') - job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数') - job_trigger = Column(String(255), nullable=True, default='', comment='任务触发器') - job_message = Column(String(500), nullable=True, default='', comment='日志信息') - status = Column(String(1), nullable=True, default='0', comment='执行状态(0正常 1失败)') - exception_info = Column(String(2000), nullable=True, default='', comment='异常信息') + job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数') + job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数') + job_trigger = Column(String(255), nullable=True, server_default="''", comment='任务触发器') + job_message = Column(String(500), nullable=True, comment='日志信息') + status = Column(CHAR(1), nullable=True, server_default='0', comment='执行状态(0正常 1失败)') + exception_info = Column(String(2000), nullable=True, server_default="''", comment='异常信息') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + + +class ApschedulerJobs(Base): + """ + 定时任务调度任务表 + """ + + __tablename__ = 'apscheduler_jobs' + + id = Column(String(191), primary_key=True, nullable=False) + next_run_time = Column( + DOUBLE, nullable=True, server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False) + ) + job_state = Column(LargeBinary, nullable=False) + + idx_sys_logininfor_s = Index('ix_apscheduler_jobs_next_run_time', next_run_time) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py index f9e14ab49da43512cd36f089785b6892ed5f6fdd..981b02652d0ee7348f7ae81d51f9da851c205ffb 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py @@ -1,5 +1,5 @@ from datetime import datetime -from sqlalchemy import BigInteger, Column, DateTime, Index, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, Index, Integer, String from config.database import Base @@ -9,15 +9,16 @@ class SysLogininfor(Base): """ __tablename__ = 'sys_logininfor' - - info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID') - user_name = Column(String(50), nullable=True, default='', comment='用户账号') - ipaddr = Column(String(128), nullable=True, default='', comment='登录IP地址') - login_location = Column(String(255), nullable=True, default='', comment='登录地点') - browser = Column(String(50), nullable=True, default='', comment='浏览器类型') - os = Column(String(50), nullable=True, default='', comment='操作系统') - status = Column(String(1), nullable=True, default='0', comment='登录状态(0成功 1失败)') - msg = Column(String(255), nullable=True, default='', comment='提示消息') + __table_args__ = {'comment': '系统访问记录'} + + info_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='访问ID') + user_name = Column(String(50), nullable=True, server_default="''", comment='用户账号') + ipaddr = Column(String(128), nullable=True, server_default="''", comment='登录IP地址') + login_location = Column(String(255), nullable=True, server_default="''", comment='登录地点') + browser = Column(String(50), nullable=True, server_default="''", comment='浏览器类型') + os = Column(String(50), nullable=True, server_default="''", comment='操作系统') + status = Column(CHAR(1), nullable=True, server_default='0', comment='登录状态(0成功 1失败)') + msg = Column(String(255), nullable=True, server_default="''", comment='提示消息') login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间') idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status) @@ -30,24 +31,25 @@ class SysOperLog(Base): """ __tablename__ = 'sys_oper_log' - - oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键') - title = Column(String(50), nullable=True, default='', comment='模块标题') - business_type = Column(Integer, default=0, comment='业务类型(0其它 1新增 2修改 3删除)') - method = Column(String(100), nullable=True, default='', comment='方法名称') - request_method = Column(String(10), nullable=True, default='', comment='请求方式') - operator_type = Column(Integer, default=0, comment='操作类别(0其它 1后台用户 2手机端用户)') - oper_name = Column(String(50), nullable=True, default='', comment='操作人员') - dept_name = Column(String(50), nullable=True, default='', comment='部门名称') - oper_url = Column(String(255), nullable=True, default='', comment='请求URL') - oper_ip = Column(String(128), nullable=True, default='', comment='主机地址') - oper_location = Column(String(255), nullable=True, default='', comment='操作地点') - oper_param = Column(String(2000), nullable=True, default='', comment='请求参数') - json_result = Column(String(2000), nullable=True, default='', comment='返回参数') - status = Column(Integer, default=0, comment='操作状态(0正常 1异常)') - error_msg = Column(String(2000), nullable=True, default='', comment='错误消息') + __table_args__ = {'comment': '操作日志记录'} + + oper_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='日志主键') + title = Column(String(50), nullable=True, server_default="''", comment='模块标题') + business_type = Column(Integer, nullable=True, server_default='0', comment='业务类型(0其它 1新增 2修改 3删除)') + method = Column(String(100), nullable=True, server_default="''", comment='方法名称') + request_method = Column(String(10), nullable=True, server_default="''", comment='请求方式') + operator_type = Column(Integer, nullable=True, server_default='0', comment='操作类别(0其它 1后台用户 2手机端用户)') + oper_name = Column(String(50), nullable=True, server_default="''", comment='操作人员') + dept_name = Column(String(50), nullable=True, server_default="''", comment='部门名称') + oper_url = Column(String(255), nullable=True, server_default="''", comment='请求URL') + oper_ip = Column(String(128), nullable=True, server_default="''", comment='主机地址') + oper_location = Column(String(255), nullable=True, server_default="''", comment='操作地点') + oper_param = Column(String(2000), nullable=True, server_default="''", comment='请求参数') + json_result = Column(String(2000), nullable=True, server_default="''", comment='返回参数') + status = Column(Integer, nullable=True, server_default='0', comment='操作状态(0正常 1异常)') + error_msg = Column(String(2000), nullable=True, server_default="''", comment='错误消息') oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间') - cost_time = Column(BigInteger, default=0, comment='消耗时间') + cost_time = Column(BigInteger, nullable=True, server_default='0', comment='消耗时间') idx_sys_oper_log_bt = Index('idx_sys_oper_log_bt', business_type) idx_sys_oper_log_s = Index('idx_sys_oper_log_s', status) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py index d3ccffa5537b8b31ae73c7977dd6d444f49c2844..0a8a836f20d5d3e1964e3460d4579619fe0e2af6 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysMenu(Base): @@ -9,24 +11,40 @@ class SysMenu(Base): """ __tablename__ = 'sys_menu' + __table_args__ = {'comment': '菜单权限表'} - menu_id = Column(Integer, primary_key=True, autoincrement=True, comment='菜单ID') - menu_name = Column(String(50), nullable=False, default='', comment='菜单名称') - parent_id = Column(Integer, default=0, comment='父菜单ID') - order_num = Column(Integer, default=0, comment='显示顺序') - path = Column(String(200), nullable=True, default='', comment='路由地址') - component = Column(String(255), nullable=True, default=None, comment='组件路径') - query = Column(String(255), nullable=True, default=None, comment='路由参数') - route_name = Column(String(50), nullable=True, default='', comment='路由名称') - is_frame = Column(Integer, default=1, comment='是否为外链(0是 1否)') - is_cache = Column(Integer, default=0, comment='是否缓存(0缓存 1不缓存)') - menu_type = Column(String(1), nullable=True, default='', comment='菜单类型(M目录 C菜单 F按钮)') - visible = Column(String(1), nullable=True, default='0', comment='菜单状态(0显示 1隐藏)') - status = Column(String(1), nullable=True, default='0', comment='菜单状态(0正常 1停用)') - perms = Column(String(100), nullable=True, default=None, comment='权限标识') - icon = Column(String(100), nullable=True, default='#', comment='菜单图标') - create_by = Column(String(64), nullable=True, default='', comment='创建者') + menu_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='菜单ID') + menu_name = Column(String(50), nullable=False, comment='菜单名称') + parent_id = Column(BigInteger, nullable=True, server_default='0', comment='父菜单ID') + order_num = Column(Integer, server_default='0', comment='显示顺序') + path = Column(String(200), nullable=True, server_default="''", comment='路由地址') + component = Column( + String(255), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='组件路径', + ) + query = Column( + String(255), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='路由参数', + ) + route_name = Column(String(50), nullable=True, server_default="''", comment='路由名称') + is_frame = Column(Integer, nullable=True, server_default='1', comment='是否为外链(0是 1否)') + is_cache = Column(Integer, nullable=True, server_default='0', comment='是否缓存(0缓存 1不缓存)') + menu_type = Column(CHAR(1), nullable=True, server_default="''", comment='菜单类型(M目录 C菜单 F按钮)') + visible = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0显示 1隐藏)') + status = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0正常 1停用)') + perms = Column( + String(100), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='权限标识', + ) + icon = Column(String(100), nullable=True, server_default='#', comment='菜单图标') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), nullable=True, default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default='', comment='备注') + remark = Column(String(500), nullable=True, server_default="''", comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py index 9d1eb982ecb8f94eccc136bc09cb83ce8d5e3c74..0d6085417f76abd1921e1f2293d7a2883d0478d5 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py @@ -1,6 +1,9 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, LargeBinary, String +from sqlalchemy import CHAR, Column, DateTime, Integer, LargeBinary, String +from sqlalchemy.dialects import mysql from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysNotice(Base): @@ -9,14 +12,25 @@ class SysNotice(Base): """ __tablename__ = 'sys_notice' + __table_args__ = {'comment': '通知公告表'} - notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID') + notice_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='公告ID') notice_title = Column(String(50), nullable=False, comment='公告标题') - notice_type = Column(String(1), nullable=False, comment='公告类型(1通知 2公告)') - notice_content = Column(LargeBinary, comment='公告内容') - status = Column(String(1), default='0', comment='公告状态(0正常 1关闭)') - create_by = Column(String(64), default='', comment='创建者') - create_time = Column(DateTime, comment='创建时间', default=datetime.now()) - update_by = Column(String(64), default='', comment='更新者') - update_time = Column(DateTime, comment='更新时间', default=datetime.now()) - remark = Column(String(255), comment='备注') + notice_type = Column(CHAR(1), nullable=False, comment='公告类型(1通知 2公告)') + notice_content = Column( + mysql.LONGBLOB if DataBaseConfig.db_type == 'mysql' else LargeBinary, + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False), + comment='公告内容', + ) + status = Column(CHAR(1), nullable=True, server_default='0', comment='公告状态(0正常 1关闭)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') + create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now()) + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') + update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now()) + remark = Column( + String(255), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py index f231f720febde67dbe3a03ce73805a516dfb0c31..442d0f3cbae3c7c7dec77f1e63ce6401173669fc 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysPost(Base): @@ -9,14 +11,20 @@ class SysPost(Base): """ __tablename__ = 'sys_post' + __table_args__ = {'comment': '岗位信息表'} - post_id = Column(Integer, primary_key=True, autoincrement=True, comment='岗位ID') + post_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='岗位ID') post_code = Column(String(64), nullable=False, comment='岗位编码') post_name = Column(String(50), nullable=False, comment='岗位名称') post_sort = Column(Integer, nullable=False, comment='显示顺序') - status = Column(String(1), nullable=False, default='0', comment='状态(0正常 1停用)') - create_by = Column(String(64), default='', comment='创建者') + status = Column(CHAR(1), nullable=False, comment='状态(0正常 1停用)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default=None, comment='备注') + remark = Column( + String(500), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py index 58d4de12b7b7083c9b9fc8f3089aba4a1cab18dd..fecf9d198b68aa9757bafd3eeb4878f510a91dfc 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py @@ -1,6 +1,9 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, SmallInteger, String +from sqlalchemy.dialects import mysql from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysRole(Base): @@ -9,25 +12,42 @@ class SysRole(Base): """ __tablename__ = 'sys_role' + __table_args__ = {'comment': '角色信息表'} - role_id = Column(Integer, primary_key=True, autoincrement=True, comment='角色ID') + role_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='角色ID') role_name = Column(String(30), nullable=False, comment='角色名称') role_key = Column(String(100), nullable=False, comment='角色权限字符串') role_sort = Column(Integer, nullable=False, comment='显示顺序') data_scope = Column( - String(1), - default='1', + CHAR(1), + nullable=True, + server_default='1', comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', ) - menu_check_strictly = Column(Integer, default=1, comment='菜单树选择项是否关联显示') - dept_check_strictly = Column(Integer, default=1, comment='部门树选择项是否关联显示') - status = Column(String(1), nullable=False, default='0', comment='角色状态(0正常 1停用)') - del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)') - create_by = Column(String(64), default='', comment='创建者') - create_time = Column(DateTime, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), default='', comment='更新者') - update_time = Column(DateTime, default=datetime.now(), comment='更新时间') - remark = Column(String(500), default=None, comment='备注') + menu_check_strictly = Column( + mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger, + nullable=True, + server_default='1', + comment='菜单树选择项是否关联显示', + ) + dept_check_strictly = Column( + mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger, + nullable=True, + server_default='1', + comment='部门树选择项是否关联显示', + ) + status = Column(CHAR(1), nullable=False, comment='角色状态(0正常 1停用)') + del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column( + String(500), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) class SysRoleDept(Base): @@ -36,9 +56,10 @@ class SysRoleDept(Base): """ __tablename__ = 'sys_role_dept' + __table_args__ = {'comment': '角色和部门关联表'} - role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') - dept_id = Column(Integer, primary_key=True, nullable=False, comment='部门ID') + role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID') + dept_id = Column(BigInteger, primary_key=True, nullable=False, comment='部门ID') class SysRoleMenu(Base): @@ -47,6 +68,7 @@ class SysRoleMenu(Base): """ __tablename__ = 'sys_role_menu' + __table_args__ = {'comment': '角色和菜单关联表'} - role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') - menu_id = Column(Integer, primary_key=True, nullable=False, comment='菜单ID') + role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID') + menu_id = Column(BigInteger, primary_key=True, nullable=False, comment='菜单ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py index 2dd0ba0a5e31c238134b80f0f69e13580e3656fd..c71b5b7a04c00e067a3188a987c7dd057f21838e 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py @@ -1,6 +1,8 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, String from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class SysUser(Base): @@ -9,26 +11,38 @@ class SysUser(Base): """ __tablename__ = 'sys_user' + __table_args__ = {'comment': '用户信息表'} - user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID') - dept_id = Column(Integer, default=None, comment='部门ID') + user_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='用户ID') + dept_id = Column( + BigInteger, + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False), + comment='部门ID', + ) user_name = Column(String(30), nullable=False, comment='用户账号') nick_name = Column(String(30), nullable=False, comment='用户昵称') - user_type = Column(String(2), default='00', comment='用户类型(00系统用户)') - email = Column(String(50), default='', comment='用户邮箱') - phonenumber = Column(String(11), default='', comment='手机号码') - sex = Column(String(1), default='0', comment='用户性别(0男 1女 2未知)') - avatar = Column(String(100), default='', comment='头像地址') - password = Column(String(100), default='', comment='密码') - status = Column(String(1), default='0', comment='帐号状态(0正常 1停用)') - del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)') - login_ip = Column(String(128), default='', comment='最后登录IP') - login_date = Column(DateTime, comment='最后登录时间') - create_by = Column(String(64), default='', comment='创建者') - create_time = Column(DateTime, comment='创建时间', default=datetime.now()) - update_by = Column(String(64), default='', comment='更新者') - update_time = Column(DateTime, comment='更新时间', default=datetime.now()) - remark = Column(String(500), default=None, comment='备注') + user_type = Column(String(2), nullable=True, server_default='00', comment='用户类型(00系统用户)') + email = Column(String(50), nullable=True, server_default="''", comment='用户邮箱') + phonenumber = Column(String(11), nullable=True, server_default="''", comment='手机号码') + sex = Column(CHAR(1), nullable=True, server_default='0', comment='用户性别(0男 1女 2未知)') + avatar = Column(String(100), nullable=True, server_default="''", comment='头像地址') + password = Column(String(100), nullable=True, server_default="''", comment='密码') + status = Column(CHAR(1), nullable=True, server_default='0', comment='帐号状态(0正常 1停用)') + del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)') + login_ip = Column(String(128), nullable=True, server_default="''", comment='最后登录IP') + login_date = Column(DateTime, nullable=True, comment='最后登录时间') + pwd_update_date = Column(DateTime, nullable=True, comment='密码最后更新时间') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') + create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now()) + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') + update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now()) + remark = Column( + String(500), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) class SysUserRole(Base): @@ -37,9 +51,10 @@ class SysUserRole(Base): """ __tablename__ = 'sys_user_role' + __table_args__ = {'comment': '用户和角色关联表'} - user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') - role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') + user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID') + role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID') class SysUserPost(Base): @@ -48,6 +63,7 @@ class SysUserPost(Base): """ __tablename__ = 'sys_user_post' + __table_args__ = {'comment': '用户与岗位关联表'} - user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') - post_id = Column(Integer, primary_key=True, nullable=False, comment='岗位ID') + user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID') + post_id = Column(BigInteger, primary_key=True, nullable=False, comment='岗位ID') diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py index 4eac9535f0d8f242815f1017e2e51550d1472362..0230882ba29713b8a3fb3f242a1b67098e6dd35c 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -40,6 +40,7 @@ class UserModel(BaseModel): del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') login_ip: Optional[str] = Field(default=None, description='最后登录IP') login_date: Optional[datetime] = Field(default=None, description='最后登录时间') + pwd_update_date: Optional[datetime] = Field(default=None, description='密码最后更新时间') create_by: Optional[str] = Field(default=None, description='创建者') create_time: Optional[datetime] = Field(default=None, description='创建时间') update_by: Optional[str] = Field(default=None, description='更新者') @@ -125,6 +126,8 @@ class CurrentUserModel(BaseModel): permissions: List = Field(description='权限信息') roles: List = Field(description='角色信息') user: Union[UserInfoModel, None] = Field(description='用户信息') + is_default_modify_pwd: bool = Field(default=False, description='是否初始密码修改提醒') + is_password_expired: bool = Field(default=False, description='密码是否过期提醒') class UserDetailModel(BaseModel): diff --git a/ruoyi-fastapi-backend/module_admin/service/job_service.py b/ruoyi-fastapi-backend/module_admin/service/job_service.py index 55263c1cbed4d443e7b87012a3bec09e2fe557df..2b783f0a0be34277384af1683ee87967ec285961 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -129,7 +129,7 @@ class JobService: elif not await cls.check_job_unique_services(query_db, page_object): raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在') try: - await JobDao.edit_job_dao(query_db, edit_job) + await JobDao.edit_job_dao(query_db, edit_job, job_info) SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id')) if edit_job.get('status') == '0': job_info = await cls.job_detail_services(query_db, edit_job.get('job_id')) diff --git a/ruoyi-fastapi-backend/module_admin/service/login_service.py b/ruoyi-fastapi-backend/module_admin/service/login_service.py index 5579f0da08a55c6b7152d912a5f5f2ea207bcc90..81c894a74640a65b3cc892bfb1be9296ad229168 100644 --- a/ruoyi-fastapi-backend/module_admin/service/login_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/login_service.py @@ -218,7 +218,7 @@ class LoginService: else: # 此方法可实现同一账号同一时间只能登录一次 redis_token = await request.app.state.redis.get( - f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}" + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get("user_basic_info").user_id}' ) if token == redis_token: if AppConfig.app_same_time_login: @@ -229,7 +229,7 @@ class LoginService: ) else: await request.app.state.redis.set( - f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}", + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get("user_basic_info").user_id}', redis_token, ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), ) @@ -242,6 +242,12 @@ class LoginService: 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')] + is_default_modify_pwd = await cls.__init_password_is_modify( + request, query_user.get('user_basic_info').pwd_update_date + ) + is_password_expired = await cls.__password_is_expired( + request, query_user.get('user_basic_info').pwd_update_date + ) current_user = CurrentUserModel( permissions=permissions, @@ -253,12 +259,48 @@ class LoginService: dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')), role=CamelCaseUtil.transform_result(query_user.get('user_role_info')), ), + isDefaultModifyPwd=is_default_modify_pwd, + isPasswordExpired=is_password_expired, ) return current_user else: logger.warning('用户token已失效,请重新登录') raise AuthException(data='', message='用户token已失效,请重新登录') + @classmethod + async def __init_password_is_modify(cls, request: Request, pwd_update_date: datetime): + """ + 判断当前用户是否初始密码登录 + + :param request: Request对象 + :param pwd_update_date: 密码最后更新时间 + :return: 是否初始密码登录 + """ + init_password_is_modify = await request.app.state.redis.get( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.initPasswordModify' + ) + return init_password_is_modify == '1' and pwd_update_date is None + + @classmethod + async def __password_is_expired(cls, request: Request, pwd_update_date: datetime): + """ + 判断当前用户密码是否过期 + + :param request: Request对象 + :param pwd_update_date: 密码最后更新时间 + :return: 密码是否过期 + """ + password_validate_days = await request.app.state.redis.get( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.passwordValidateDays' + ) + if password_validate_days and int(password_validate_days) > 0: + if pwd_update_date is None: + return True + expire_date = pwd_update_date + timedelta(days=int(password_validate_days)) + if datetime.now() > expire_date: + return True + return False + @classmethod async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession): """ @@ -404,6 +446,7 @@ class LoginService: userName=user_register.username, nickName=user_register.username, password=PwdUtil.get_password_hash(user_register.password), + pwdUpdateDate=datetime.now(), ) result = await UserService.add_user_services(query_db, add_user) return result @@ -466,15 +509,15 @@ class LoginService: return CrudResponseModel(**result) @classmethod - async def logout_services(cls, request: Request, session_id: str): + async def logout_services(cls, request: Request, token_id: str): """ 退出登录services :param request: Request对象 - :param session_id: 会话编号 + :param token_id: 令牌编号 :return: 退出登录结果 """ - await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}') + await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_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') diff --git a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py index e64d0bfdf934f033526e9bd5fd3fa114e6738457..edd62f67d2594ff1ce194777ba94883b296de61c 100644 --- a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py +++ b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py @@ -1,7 +1,9 @@ from datetime import datetime -from sqlalchemy import Column, DateTime, ForeignKey, Integer, String +from sqlalchemy import BigInteger, CHAR, Column, DateTime, ForeignKey, Integer, String from sqlalchemy.orm import relationship from config.database import Base +from config.env import DataBaseConfig +from utils.common_util import SqlalchemyUtil class GenTable(Base): @@ -10,30 +12,48 @@ class GenTable(Base): """ __tablename__ = 'gen_table' + __table_args__ = {'comment': '代码生成业务表'} - table_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号') - table_name = Column(String(200), nullable=True, default='', comment='表名称') - table_comment = Column(String(500), nullable=True, default='', comment='表描述') - sub_table_name = Column(String(64), nullable=True, comment='关联子表的表名') - sub_table_fk_name = Column(String(64), nullable=True, comment='子表关联的外键名') - class_name = Column(String(100), nullable=True, default='', comment='实体类名称') - tpl_category = Column(String(200), nullable=True, default='crud', comment='使用的模板(crud单表操作 tree树表操作)') + table_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='编号') + table_name = Column(String(200), nullable=True, server_default="''", comment='表名称') + table_comment = Column(String(500), nullable=True, server_default="''", comment='表描述') + sub_table_name = Column( + String(64), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='关联子表的表名', + ) + sub_table_fk_name = Column( + String(64), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='子表关联的外键名', + ) + class_name = Column(String(100), nullable=True, server_default="''", comment='实体类名称') + tpl_category = Column( + String(200), nullable=True, server_default='crud', comment='使用的模板(crud单表操作 tree树表操作)' + ) tpl_web_type = Column( - String(30), nullable=True, default='', comment='前端模板类型(element-ui模版 element-plus模版)' + String(30), nullable=True, server_default="''", comment='前端模板类型(element-ui模版 element-plus模版)' ) package_name = Column(String(100), nullable=True, comment='生成包路径') module_name = Column(String(30), nullable=True, comment='生成模块名') business_name = Column(String(30), nullable=True, comment='生成业务名') - function_name = Column(String(100), nullable=True, comment='生成功能名') - function_author = Column(String(100), nullable=True, comment='生成功能作者') - gen_type = Column(String(1), nullable=True, default='0', comment='生成代码方式(0zip压缩包 1自定义路径)') - gen_path = Column(String(200), nullable=True, default='/', comment='生成路径(不填默认项目路径)') + function_name = Column(String(50), nullable=True, comment='生成功能名') + function_author = Column(String(50), nullable=True, comment='生成功能作者') + gen_type = Column(CHAR(1), nullable=True, server_default='0', comment='生成代码方式(0zip压缩包 1自定义路径)') + gen_path = Column(String(200), nullable=True, server_default='/', comment='生成路径(不填默认项目路径)') options = Column(String(1000), nullable=True, comment='其它生成选项') - create_by = Column(String(64), default='', comment='创建者') + create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), default='', comment='更新者') + update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default=None, comment='备注') + remark = Column( + String(500), + nullable=True, + server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), + comment='备注', + ) columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables') @@ -44,31 +64,34 @@ class GenTableColumn(Base): """ __tablename__ = 'gen_table_column' + __table_args__ = {'comment': '代码生成业务表字段'} - column_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号') - table_id = Column(Integer, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号') + column_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='编号') + table_id = Column(BigInteger, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号') column_name = Column(String(200), nullable=True, comment='列名称') column_comment = Column(String(500), nullable=True, comment='列描述') column_type = Column(String(100), nullable=True, comment='列类型') python_type = Column(String(500), nullable=True, comment='PYTHON类型') python_field = Column(String(200), nullable=True, comment='PYTHON字段名') - is_pk = Column(String(1), nullable=True, comment='是否主键(1是)') - is_increment = Column(String(1), nullable=True, comment='是否自增(1是)') - is_required = Column(String(1), nullable=True, comment='是否必填(1是)') - is_unique = Column(String(1), nullable=True, comment='是否唯一(1是)') - is_insert = Column(String(1), nullable=True, comment='是否为插入字段(1是)') - is_edit = Column(String(1), nullable=True, comment='是否编辑字段(1是)') - is_list = Column(String(1), nullable=True, comment='是否列表字段(1是)') - is_query = Column(String(1), nullable=True, comment='是否查询字段(1是)') - query_type = Column(String(200), nullable=True, default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)') + is_pk = Column(CHAR(1), nullable=True, comment='是否主键(1是)') + is_increment = Column(CHAR(1), nullable=True, comment='是否自增(1是)') + is_required = Column(CHAR(1), nullable=True, comment='是否必填(1是)') + is_unique = Column(CHAR(1), nullable=True, comment='是否唯一(1是)') + is_insert = Column(CHAR(1), nullable=True, comment='是否为插入字段(1是)') + is_edit = Column(CHAR(1), nullable=True, comment='是否编辑字段(1是)') + is_list = Column(CHAR(1), nullable=True, comment='是否列表字段(1是)') + is_query = Column(CHAR(1), nullable=True, comment='是否查询字段(1是)') + query_type = Column( + String(200), nullable=True, server_default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)' + ) html_type = Column( String(200), nullable=True, comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)' ) - dict_type = Column(String(200), nullable=True, default='', comment='字典类型') + dict_type = Column(String(200), nullable=True, server_default="''", comment='字典类型') sort = Column(Integer, nullable=True, comment='排序') - create_by = Column(String(64), default='', comment='创建者') + create_by = Column(String(64), server_default="''", comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') - update_by = Column(String(64), default='', comment='更新者') + update_by = Column(String(64), server_default="''", comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') tables = relationship('GenTable', back_populates='columns') diff --git a/ruoyi-fastapi-backend/requirements-pg.txt b/ruoyi-fastapi-backend/requirements-pg.txt index 095197fd1b036f26b405a66a16a47dce04f50f32..c9d1c249ccb26a3d5f0f5207cbe4f4acee7eec2c 100644 --- a/ruoyi-fastapi-backend/requirements-pg.txt +++ b/ruoyi-fastapi-backend/requirements-pg.txt @@ -1,18 +1,19 @@ +alembic==1.16.4 APScheduler==3.11.0 +async-lru==2.0.5 asyncpg==0.30.0 DateTime==5.5 -fastapi[all]==0.115.8 +fastapi[all]==0.116.1 loguru==0.7.3 openpyxl==3.1.5 -pandas==2.2.3 +pandas==2.3.2 passlib[bcrypt]==1.7.4 -Pillow==11.1.0 +Pillow==11.3.0 psutil==7.0.0 pydantic-validation-decorator==0.1.4 PyJWT[crypto]==2.10.1 psycopg2==2.9.10 -redis==5.2.1 -requests==2.32.3 -SQLAlchemy[asyncio]==2.0.38 -sqlglot[rs]==26.6.0 +redis==6.4.0 +SQLAlchemy[asyncio]==2.0.43 +sqlglot[rs]==27.8.0 user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/requirements.txt b/ruoyi-fastapi-backend/requirements.txt index 45ff472a1501b1fb70d6f98c5d819cf43eb9f19c..9040b49e56f1df021c3958e0d265ff2ea4eb62f9 100644 --- a/ruoyi-fastapi-backend/requirements.txt +++ b/ruoyi-fastapi-backend/requirements.txt @@ -1,18 +1,19 @@ +alembic==1.16.4 APScheduler==3.11.0 +async-lru==2.0.5 asyncmy==0.2.10 DateTime==5.5 -fastapi[all]==0.115.8 +fastapi[all]==0.116.1 loguru==0.7.3 openpyxl==3.1.5 -pandas==2.2.3 +pandas==2.3.2 passlib[bcrypt]==1.7.4 -Pillow==11.1.0 +Pillow==11.3.0 psutil==7.0.0 pydantic-validation-decorator==0.1.4 PyJWT[crypto]==2.10.1 PyMySQL==1.1.1 -redis==5.2.1 -requests==2.32.3 -SQLAlchemy[asyncio]==2.0.38 -sqlglot[rs]==26.6.0 +redis==6.4.0 +SQLAlchemy[asyncio]==2.0.43 +sqlglot[rs]==27.8.0 user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/server.py b/ruoyi-fastapi-backend/server.py index 5c8ad9cc023d6b17f9cf137b117b609a05d60c50..7a46e775f950099dba644ef7478d523548bfa461 100644 --- a/ruoyi-fastapi-backend/server.py +++ b/ruoyi-fastapi-backend/server.py @@ -31,14 +31,14 @@ from utils.log_util import logger # 生命周期事件 @asynccontextmanager async def lifespan(app: FastAPI): - logger.info(f'{AppConfig.app_name}开始启动') + logger.info(f'⏰️ {AppConfig.app_name}开始启动') worship() await init_create_table() app.state.redis = await RedisUtil.create_redis_pool() await RedisUtil.init_sys_dict(app.state.redis) await RedisUtil.init_sys_config(app.state.redis) await SchedulerUtil.init_system_scheduler() - logger.info(f'{AppConfig.app_name}启动成功') + logger.info(f'🚀 {AppConfig.app_name}启动成功') yield await RedisUtil.close_redis_pool(app) await SchedulerUtil.close_system_scheduler() diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql index b3731aa3f42630736fe5619415a2b4203b6f1373..18446b0b658a572c6ed2360832b070e57f77f340 100644 --- a/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql @@ -69,6 +69,7 @@ create table sys_user ( del_flag char(1) default '0', login_ip varchar(128) default '', login_date timestamp(0), + pwd_update_date timestamp(0), create_by varchar(64) default '', create_time timestamp(0), update_by varchar(64) default '', @@ -91,6 +92,7 @@ comment on column sys_user.status is '帐号状态(0正常 1停用)'; comment on column sys_user.del_flag is '删除标志(0代表存在 2代表删除)'; comment on column sys_user.login_ip is '最后登录IP'; comment on column sys_user.login_date is '最后登录时间'; +comment on column sys_user.pwd_update_date is '密码最后更新时间'; comment on column sys_user.create_by is '创建者'; comment on column sys_user.create_time is '创建时间'; comment on column sys_user.update_by is '更新者'; @@ -98,6 +100,12 @@ comment on column sys_user.update_time is '更新时间'; comment on column sys_user.remark is '备注'; comment on table sys_user is '用户信息表'; +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +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', current_timestamp, current_timestamp, 'admin', current_timestamp, '', 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', current_timestamp, current_timestamp, 'admin', current_timestamp, '', null, '测试员'); + -- ---------------------------- -- 3、岗位信息表 -- ---------------------------- @@ -136,12 +144,6 @@ insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', current insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', current_timestamp, '', null, ''); insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', current_timestamp, '', null, ''); --- ---------------------------- --- 初始化-用户信息表数据 --- ---------------------------- -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', current_timestamp, 'admin', current_timestamp, '', 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', current_timestamp, 'admin', current_timestamp, '', null, '测试员'); - -- ---------------------------- -- 4、角色信息表 -- ---------------------------- @@ -522,7 +524,7 @@ create table sys_oper_log ( status int4 default 0, error_msg varchar(2000) default '', oper_time timestamp(0), - cost_time int8 default 0, + cost_time bigint default 0, primary key (oper_id) ); alter sequence sys_oper_log_oper_id_seq restart 100; @@ -545,6 +547,7 @@ comment on column sys_oper_log.json_result is '返回参数'; comment on column sys_oper_log.status is '操作状态(0正常 1异常)'; comment on column sys_oper_log.error_msg is '错误消息'; comment on column sys_oper_log.oper_time is '操作时间'; +comment on column sys_oper_log.cost_time is '消耗时间'; comment on table sys_oper_log is '操作日志记录'; -- ---------------------------- @@ -703,6 +706,8 @@ insert into sys_config values(3, '主框架页-侧边栏主题', 'sys. insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', current_timestamp, '', null, '是否开启验证码功能(true开启,false关闭)'); insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', current_timestamp, '', null, '是否开启注册用户功能(true开启,false关闭)'); insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', current_timestamp, '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); +insert into sys_config values(7, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', current_timestamp, '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框'); +insert into sys_config values(8, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', current_timestamp, '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框'); -- ---------------------------- -- 14、系统访问记录 @@ -931,7 +936,8 @@ create table gen_table_column ( create_time timestamp(0), update_by varchar(64) default '', update_time timestamp(0), - primary key (column_id) + primary key (column_id), + constraint fk_gen_table_column_table_id foreign key (table_id) references gen_table(table_id) ); comment on column gen_table_column.column_id is '编号'; comment on column gen_table_column.table_id is '归属表编号'; diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql index d0ce11a55b1a50d9a9feeb84f8f0d4c46bc02333..e48900788a8efb1db20c87b37a55062f7682cfbd 100644 --- a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql @@ -54,6 +54,7 @@ create table sys_user ( del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', login_ip varchar(128) default '' comment '最后登录IP', login_date datetime comment '最后登录时间', + pwd_update_date datetime comment '密码最后更新时间', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', @@ -65,8 +66,8 @@ create table sys_user ( -- ---------------------------- -- 初始化-用户信息表数据 -- ---------------------------- -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, '测试员'); +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(), 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(), sysdate(), 'admin', sysdate(), '', null, '测试员'); -- ---------------------------- @@ -554,6 +555,8 @@ insert into sys_config values(3, '主框架页-侧边栏主题', 'sys. 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黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); +insert into sys_config values(7, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', sysdate(), '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框'); +insert into sys_config values(8, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框'); -- ---------------------------- @@ -709,5 +712,6 @@ create table gen_table_column ( create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', - primary key (column_id) + primary key (column_id), + foreign key (table_id) references gen_table(table_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 index b56000e453dec419417fc4abfb02dbdcacaa20ba..0ccb546998516ff6f8a4d1baa1d1d646daee7dfa 100644 --- a/ruoyi-fastapi-backend/utils/common_util.py +++ b/ruoyi-fastapi-backend/utils/common_util.py @@ -8,6 +8,7 @@ from openpyxl.utils import get_column_letter from openpyxl.worksheet.datavalidation import DataValidation from sqlalchemy.engine.row import Row from sqlalchemy.orm.collections import InstrumentedList +from sqlalchemy.sql.expression import TextClause, null from typing import Any, Dict, List, Literal, Union from config.database import Base from config.env import CachePathConfig @@ -100,6 +101,19 @@ class SqlalchemyUtil: return result_dict return result + @classmethod + def get_server_default_null(cls, dialect_name: str, need_explicit_null: bool = True) -> Union[TextClause, None]: + """ + 根据数据库方言动态返回值为null的server_default + + :param dialect_name: 数据库方言名称 + :param need_explicit_null: 是否需要显式DEFAULT NULL + :return: 不同数据库方言对应的null_server_default + """ + if need_explicit_null and dialect_name == 'postgresql': + return null() + return None + class CamelCaseUtil: """ diff --git a/ruoyi-fastapi-backend/utils/import_util.py b/ruoyi-fastapi-backend/utils/import_util.py new file mode 100644 index 0000000000000000000000000000000000000000..4bb2a63d7fcb12f862993a6496451a6147217ced --- /dev/null +++ b/ruoyi-fastapi-backend/utils/import_util.py @@ -0,0 +1,121 @@ +import importlib +import inspect +import os +from pathlib import Path +import sys +from functools import lru_cache +from sqlalchemy import inspect as sa_inspect +from typing import Any, List +from config.database import Base + + +class ImportUtil: + @classmethod + def find_project_root(cls) -> Path: + """ + 查找项目根目录 + + :return: 项目根目录路径 + """ + current_dir = Path(__file__).resolve().parent + while current_dir != current_dir.parent: + if any(current_dir.joinpath(file).exists() for file in ['setup.py', 'pyproject.toml', 'requirements.txt']): + return current_dir + current_dir = current_dir.parent + return Path(__file__).resolve().parent + + @classmethod + def is_valid_model(cls, obj: Any, base_class: Base) -> bool: + """ + 验证是否为有效的SQLAlchemy模型类 + + :param obj: 待验证的对象 + :param base_class: SQLAlchemy的Base类 + :return: 验证结果 + """ + # 必须继承自Base且不是Base本身 + if not (inspect.isclass(obj) and issubclass(obj, base_class) and obj is not base_class): + return False + + # 必须有表名定义(排除抽象基类) + if not hasattr(obj, '__tablename__') or obj.__tablename__ is None: + return False + + # 必须有至少一个列定义 + try: + return len(sa_inspect(obj).columns) > 0 + except Exception: + return False + + @classmethod + @lru_cache(maxsize=256) + def find_models(cls, base_class: Base) -> List[Base]: + """ + 查找并过滤有效的模型类,避免重复和无效定义 + + :param base_class: SQLAlchemy的Base类,用于验证模型类 + :return: 有效模型类列表 + """ + models = [] + # 按类对象去重 + seen_models = set() + # 按表名去重(防止同表名冲突) + seen_tables = set() + project_root = cls.find_project_root() + + sys.path.append(str(project_root)) + print(f"⏰️ 开始在项目根目录 {project_root} 中查找模型...") + + # 排除目录扩展 + exclude_dirs = { + 'venv', + '.env', + '.git', + '__pycache__', + 'migrations', + 'alembic', + 'tests', + 'test', + 'docs', + 'examples', + 'scripts', + } + + for root, dirs, files in os.walk(project_root): + dirs[:] = [d for d in dirs if d not in exclude_dirs] + + for file in files: + if file.endswith('.py') and not file.startswith('__'): + relative_path = Path(root).relative_to(project_root) + module_parts = list(relative_path.parts) + [file[:-3]] + module_name = '.'.join(module_parts) + + try: + module = importlib.import_module(module_name) + + for name, obj in inspect.getmembers(module, inspect.isclass): + # 验证模型有效性 + if not cls.is_valid_model(obj, base_class): + continue + + # 检查类对象重复 + if obj in seen_models: + continue + + # 检查表名重复 + table_name = obj.__tablename__ + if table_name in seen_tables: + continue + + seen_models.add(obj) + seen_tables.add(table_name) + models.append(obj) + print(f'✅️ 找到有效模型: {obj.__module__}.{obj.__name__} (表: {table_name})') + + except ImportError as e: + if 'cannot import name' not in str(e): + print(f'❗️ 警告: 无法导入模块 {module_name}: {e}') + except Exception as e: + print(f'❌️ 处理模块 {module_name} 时出错: {e}') + + return models diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json index 92ef3559b7ac799cb69b7f8218b0c2bf3b194caa..9ba7998ef2d9f742c9c35cfba509587e75f51431 100644 --- a/ruoyi-fastapi-frontend/package.json +++ b/ruoyi-fastapi-frontend/package.json @@ -1,6 +1,6 @@ { "name": "vfadmin", - "version": "1.6.2", + "version": "1.7.0", "description": "vfadmin管理系统", "author": "insistence", "license": "MIT", @@ -20,34 +20,36 @@ "@antv/g2plot": "^2.4.31", "@element-plus/icons-vue": "2.3.1", "@vueup/vue-quill": "1.2.0", - "@vueuse/core": "10.11.0", + "@vueuse/core": "13.3.0", "ant-design-vue": "^4.1.1", - "axios": "0.28.1", + "axios": "1.9.0", "clipboard": "2.0.11", - "echarts": "5.5.1", - "element-plus": "2.7.6", + "echarts": "5.6.0", + "element-plus": "2.10.7", "file-saver": "2.0.5", "fuse.js": "6.6.2", "js-beautify": "1.15.1", "js-cookie": "3.0.5", "jsencrypt": "3.3.2", "nprogress": "0.2.0", - "pinia": "2.1.7", - "splitpanes": "3.1.5", - "vue": "3.4.15", + "pinia": "3.0.2", + "splitpanes": "4.0.4", + "vue": "3.5.16", "vue-cropper": "1.1.1", - "vue-router": "4.4.0", + "vue-router": "4.5.1", "vuedraggable": "4.1.0" }, "devDependencies": { - "@vitejs/plugin-vue": "5.0.5", - "@vue/compiler-sfc": "3.3.9", + "@vitejs/plugin-vue": "5.2.4", "less": "^4.2.0", - "sass": "1.77.5", - "unplugin-auto-import": "0.17.6", + "sass-embedded": "1.89.1", + "unplugin-auto-import": "0.18.6", "unplugin-vue-setup-extend-plus": "1.0.1", - "vite": "5.3.2", + "vite": "6.3.5", "vite-plugin-compression": "0.5.1", "vite-plugin-svg-icons": "2.0.1" + }, + "overrides": { + "quill": "2.0.2" } } diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/enter.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/enter.svg new file mode 100644 index 0000000000000000000000000000000000000000..f7cabf29eeee4aeb4cf73916f78f296b4016856d --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/enter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/icons/svg/more-up.svg b/ruoyi-fastapi-frontend/src/assets/icons/svg/more-up.svg new file mode 100644 index 0000000000000000000000000000000000000000..d30ac11c1b5a99dab4286668ccc27ef68ae2c7eb --- /dev/null +++ b/ruoyi-fastapi-frontend/src/assets/icons/svg/more-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/assets/styles/btn.scss b/ruoyi-fastapi-frontend/src/assets/styles/btn.scss index 3590d8d2d371a199e9a051b9912bae4ad1ad161c..fee3ee13fcc191d355bcc5a4dd5e3c6c37f7aeaa 100644 --- a/ruoyi-fastapi-frontend/src/assets/styles/btn.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/btn.scss @@ -1,4 +1,4 @@ -@import './variables.module.scss'; +@use './variables.module.scss' as *; @mixin colorBtn($color) { background: $color; diff --git a/ruoyi-fastapi-frontend/src/assets/styles/index.scss b/ruoyi-fastapi-frontend/src/assets/styles/index.scss index efc1ddd3dfa1147cca48005412494791e82a490b..87898e6273d8405880c982598a469979bd0409e3 100644 --- a/ruoyi-fastapi-frontend/src/assets/styles/index.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/index.scss @@ -1,10 +1,9 @@ -@import './variables.module.scss'; -@import './mixin.scss'; -@import './transition.scss'; -@import './element-ui.scss'; -@import './sidebar.scss'; -@import './btn.scss'; -@import './ruoyi.scss'; +@use './mixin.scss'; +@use './transition.scss'; +@use './element-ui.scss'; +@use './sidebar.scss'; +@use './btn.scss'; +@use './ruoyi.scss'; body { height: 100%; diff --git a/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss index db9b1f6faa726ac3d5d91e00b7a69ad79ccee563..8e3ce44885ccb9699330aaec21a97830c680672b 100755 --- a/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss @@ -198,8 +198,6 @@ } .card-box { - padding-right: 15px; - padding-left: 15px; margin-bottom: 10px; } diff --git a/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss b/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss index 89820d1f87433ae586cfff3c33aa163a99462b29..57642960742af2f82498e3e4e5b770b51cee1d86 100755 --- a/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss @@ -1,9 +1,11 @@ +@use './variables.module.scss' as vars; + #app { .main-container { min-height: 100%; transition: margin-left .28s; - margin-left: $base-sidebar-width; + margin-left: vars.$base-sidebar-width; position: relative; } @@ -13,7 +15,7 @@ .sidebar-container { transition: width 0.28s; - width: $base-sidebar-width !important; + width: vars.$base-sidebar-width !important; height: 100%; position: fixed; font-size: 0px; @@ -23,7 +25,7 @@ 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); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); // reset element-ui css .horizontal-collapse-transition { @@ -87,12 +89,12 @@ } & .theme-dark .is-active > .el-sub-menu__title { - color: $base-menu-color-active !important; + color: vars.$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; + min-width: vars.$base-sidebar-width !important; &:hover { background-color: rgba(0, 0, 0, 0.06) !important; @@ -101,10 +103,10 @@ & .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; + background-color: vars.$base-sub-menu-background; &:hover { - background-color: $base-sub-menu-hover !important; + background-color: vars.$base-sub-menu-hover !important; } } } @@ -167,7 +169,7 @@ } .el-menu--collapse .el-menu .el-sub-menu { - min-width: $base-sidebar-width !important; + min-width: vars.$base-sidebar-width !important; } // mobile responsive @@ -178,14 +180,14 @@ .sidebar-container { transition: transform .28s; - width: $base-sidebar-width !important; + width: vars.$base-sidebar-width !important; } &.hideSidebar { .sidebar-container { pointer-events: none; transition-duration: 0.3s; - transform: translate3d(-$base-sidebar-width, 0, 0); + transform: translate3d(-(vars.$base-sidebar-width), 0, 0); } } } diff --git a/ruoyi-fastapi-frontend/src/assets/styles/transition.scss b/ruoyi-fastapi-frontend/src/assets/styles/transition.scss index 073f8c6ce27c63240f6cf1e832ead8b8bc91d8c6..19fc9e6fc32bc8cef5783383013ecda66f1d5518 100644 --- a/ruoyi-fastapi-frontend/src/assets/styles/transition.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/transition.scss @@ -6,7 +6,7 @@ transition: opacity 0.28s; } -.fade-enter, +.fade-enter-from, .fade-leave-active { opacity: 0; } @@ -18,7 +18,7 @@ transition: all .5s; } -.fade-transform-enter { +.fade-transform-enter-from { opacity: 0; transform: translateX(-30px); } @@ -34,7 +34,7 @@ transition: all .5s; } -.breadcrumb-enter, +.breadcrumb-enter-from, .breadcrumb-leave-active { opacity: 0; transform: translateX(20px); diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/index.vue b/ruoyi-fastapi-frontend/src/components/Crontab/index.vue index 103cf4c6283c7532d110e9488a9c428455af2fee..cbb41abc299e18cedece718f78c66abab5054afe 100644 --- a/ruoyi-fastapi-frontend/src/components/Crontab/index.vue +++ b/ruoyi-fastapi-frontend/src/components/Crontab/index.vue @@ -70,42 +70,46 @@

时间表达式

- - + + + + - - - - - - - - + + + + + + + + + +
{{item}}Cron 表达式
{{item}}Cron 表达式
- {{crontabValueObj.second}} - {{crontabValueObj.second}} - - {{crontabValueObj.min}} - {{crontabValueObj.min}} - - {{crontabValueObj.hour}} - {{crontabValueObj.hour}} - - {{crontabValueObj.day}} - {{crontabValueObj.day}} - - {{crontabValueObj.month}} - {{crontabValueObj.month}} - - {{crontabValueObj.week}} - {{crontabValueObj.week}} - - {{crontabValueObj.year}} - {{crontabValueObj.year}} - - {{crontabValueString}} - {{crontabValueString}} -
+ {{crontabValueObj.second}} + {{crontabValueObj.second}} + + {{crontabValueObj.min}} + {{crontabValueObj.min}} + + {{crontabValueObj.hour}} + {{crontabValueObj.hour}} + + {{crontabValueObj.day}} + {{crontabValueObj.day}} + + {{crontabValueObj.month}} + {{crontabValueObj.month}} + + {{crontabValueObj.week}} + {{crontabValueObj.week}} + + {{crontabValueObj.year}} + {{crontabValueObj.year}} + + {{crontabValueString}} + {{crontabValueString}} +
diff --git a/ruoyi-fastapi-frontend/src/components/Crontab/year.vue b/ruoyi-fastapi-frontend/src/components/Crontab/year.vue index fa921aef89d2d5ebd620b8960551b31488fb9664..14758bafc56788fa8cd458953789e7890175784c 100644 --- a/ruoyi-fastapi-frontend/src/components/Crontab/year.vue +++ b/ruoyi-fastapi-frontend/src/components/Crontab/year.vue @@ -61,22 +61,24 @@ const props = defineProps({ } } }) -const fullYear = ref(0) -const maxFullYear = ref(0) + +const fullYear = Number(new Date().getFullYear()) +const maxFullYear = fullYear + 10 const radioValue = ref(1) -const cycle01 = ref(0) -const cycle02 = ref(0) -const average01 = ref(0) +const cycle01 = ref(fullYear) +const cycle02 = ref(fullYear + 1) +const average01 = ref(fullYear) const average02 = ref(1) const checkboxList = ref([]) -const checkCopy = ref([]) +const checkCopy = ref([fullYear]) + const cycleTotal = computed(() => { - cycle01.value = props.check(cycle01.value, fullYear.value, maxFullYear.value - 1) - cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear.value) + cycle01.value = props.check(cycle01.value, fullYear, maxFullYear - 1) + cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear) return cycle01.value + '-' + cycle02.value }) const averageTotal = computed(() => { - average01.value = props.check(average01.value, fullYear.value, maxFullYear.value - 1) + average01.value = props.check(average01.value, fullYear, maxFullYear - 1) average02.value = props.check(average02.value, 1, 10) return average01.value + '/' + average02.value }) @@ -97,8 +99,8 @@ function changeRadioValue(value) { radioValue.value = 3 } else if (value.indexOf("/") > -1) { const indexArr = value.split('/') - average01.value = Number(indexArr[1]) - average02.value = Number(indexArr[0]) + average01.value = Number(indexArr[0]) + average02.value = Number(indexArr[1]) radioValue.value = 4 } else { checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))] @@ -129,14 +131,6 @@ function onRadioChange() { break } } -onMounted(() => { - fullYear.value = Number(new Date().getFullYear()) - maxFullYear.value = fullYear.value + 10 - cycle01.value = fullYear.value - cycle02.value = cycle01.value + 1 - average01.value = fullYear.value - checkCopy.value = [fullYear.value] -}) \ No newline at end of file + diff --git a/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue b/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue index 4d1969343be894fe49fb017ccbd0fbd26b315679..d984a09a1cf2583619d3f0d13c0ec10cdac51cda 100644 --- a/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue +++ b/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue @@ -2,10 +2,12 @@
-
+
请上传