diff --git a/README.md b/README.md index 97321ae2123b5c7ed02426d9d92f86e861a41fff..9e50d0eb60b43a482c0690a79a47870a64ada030 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@

logo

-

RuoYi-Vue-FastAPI v1.6.2

+

RuoYi-Vue-FastAPI v1.7.0

基于RuoYi-Vue+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/.eslintignore b/ruoyi-fastapi-frontend/.eslintignore deleted file mode 100644 index 89be6f6596352653bf50bfdf3e8794dc6d90e366..0000000000000000000000000000000000000000 --- a/ruoyi-fastapi-frontend/.eslintignore +++ /dev/null @@ -1,10 +0,0 @@ -# 忽略build目录下类型为js的文件的语法检查 -build/*.js -# 忽略src/assets目录下文件的语法检查 -src/assets -# 忽略public目录下文件的语法检查 -public -# 忽略当前目录下为js的文件的语法检查 -*.js -# 忽略当前目录下为vue的文件的语法检查 -*.vue \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/.eslintrc.js b/ruoyi-fastapi-frontend/.eslintrc.js deleted file mode 100644 index 82bbdeea60fec5062fad77c9cc110c6f831afc71..0000000000000000000000000000000000000000 --- a/ruoyi-fastapi-frontend/.eslintrc.js +++ /dev/null @@ -1,199 +0,0 @@ -// ESlint 检查配置 -module.exports = { - root: true, - parserOptions: { - parser: 'babel-eslint', - sourceType: 'module' - }, - env: { - browser: true, - node: true, - es6: true, - }, - extends: ['plugin:vue/recommended', 'eslint:recommended'], - - // add your custom rules here - //it is base on https://github.com/vuejs/eslint-config-vue - rules: { - "vue/max-attributes-per-line": [2, { - "singleline": 10, - "multiline": { - "max": 1, - "allowFirstLine": false - } - }], - "vue/singleline-html-element-content-newline": "off", - "vue/multiline-html-element-content-newline":"off", - "vue/name-property-casing": ["error", "PascalCase"], - "vue/no-v-html": "off", - 'accessor-pairs': 2, - 'arrow-spacing': [2, { - 'before': true, - 'after': true - }], - 'block-spacing': [2, 'always'], - 'brace-style': [2, '1tbs', { - 'allowSingleLine': true - }], - 'camelcase': [0, { - 'properties': 'always' - }], - 'comma-dangle': [2, 'never'], - 'comma-spacing': [2, { - 'before': false, - 'after': true - }], - 'comma-style': [2, 'last'], - 'constructor-super': 2, - 'curly': [2, 'multi-line'], - 'dot-location': [2, 'property'], - 'eol-last': 2, - 'eqeqeq': ["error", "always", {"null": "ignore"}], - 'generator-star-spacing': [2, { - 'before': true, - 'after': true - }], - 'handle-callback-err': [2, '^(err|error)$'], - 'indent': [2, 2, { - 'SwitchCase': 1 - }], - 'jsx-quotes': [2, 'prefer-single'], - 'key-spacing': [2, { - 'beforeColon': false, - 'afterColon': true - }], - 'keyword-spacing': [2, { - 'before': true, - 'after': true - }], - 'new-cap': [2, { - 'newIsCap': true, - 'capIsNew': false - }], - 'new-parens': 2, - 'no-array-constructor': 2, - 'no-caller': 2, - 'no-console': 'off', - 'no-class-assign': 2, - 'no-cond-assign': 2, - 'no-const-assign': 2, - 'no-control-regex': 0, - 'no-delete-var': 2, - 'no-dupe-args': 2, - 'no-dupe-class-members': 2, - 'no-dupe-keys': 2, - 'no-duplicate-case': 2, - 'no-empty-character-class': 2, - 'no-empty-pattern': 2, - 'no-eval': 2, - 'no-ex-assign': 2, - 'no-extend-native': 2, - 'no-extra-bind': 2, - 'no-extra-boolean-cast': 2, - 'no-extra-parens': [2, 'functions'], - 'no-fallthrough': 2, - 'no-floating-decimal': 2, - 'no-func-assign': 2, - 'no-implied-eval': 2, - 'no-inner-declarations': [2, 'functions'], - 'no-invalid-regexp': 2, - 'no-irregular-whitespace': 2, - 'no-iterator': 2, - 'no-label-var': 2, - 'no-labels': [2, { - 'allowLoop': false, - 'allowSwitch': false - }], - 'no-lone-blocks': 2, - 'no-mixed-spaces-and-tabs': 2, - 'no-multi-spaces': 2, - 'no-multi-str': 2, - 'no-multiple-empty-lines': [2, { - 'max': 1 - }], - 'no-native-reassign': 2, - 'no-negated-in-lhs': 2, - 'no-new-object': 2, - 'no-new-require': 2, - 'no-new-symbol': 2, - 'no-new-wrappers': 2, - 'no-obj-calls': 2, - 'no-octal': 2, - 'no-octal-escape': 2, - 'no-path-concat': 2, - 'no-proto': 2, - 'no-redeclare': 2, - 'no-regex-spaces': 2, - 'no-return-assign': [2, 'except-parens'], - 'no-self-assign': 2, - 'no-self-compare': 2, - 'no-sequences': 2, - 'no-shadow-restricted-names': 2, - 'no-spaced-func': 2, - 'no-sparse-arrays': 2, - 'no-this-before-super': 2, - 'no-throw-literal': 2, - 'no-trailing-spaces': 2, - 'no-undef': 2, - 'no-undef-init': 2, - 'no-unexpected-multiline': 2, - 'no-unmodified-loop-condition': 2, - 'no-unneeded-ternary': [2, { - 'defaultAssignment': false - }], - 'no-unreachable': 2, - 'no-unsafe-finally': 2, - 'no-unused-vars': [2, { - 'vars': 'all', - 'args': 'none' - }], - 'no-useless-call': 2, - 'no-useless-computed-key': 2, - 'no-useless-constructor': 2, - 'no-useless-escape': 0, - 'no-whitespace-before-property': 2, - 'no-with': 2, - 'one-var': [2, { - 'initialized': 'never' - }], - 'operator-linebreak': [2, 'after', { - 'overrides': { - '?': 'before', - ':': 'before' - } - }], - 'padded-blocks': [2, 'never'], - 'quotes': [2, 'single', { - 'avoidEscape': true, - 'allowTemplateLiterals': true - }], - 'semi': [2, 'never'], - 'semi-spacing': [2, { - 'before': false, - 'after': true - }], - 'space-before-blocks': [2, 'always'], - 'space-before-function-paren': [2, 'never'], - 'space-in-parens': [2, 'never'], - 'space-infix-ops': 2, - 'space-unary-ops': [2, { - 'words': true, - 'nonwords': false - }], - 'spaced-comment': [2, 'always', { - 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] - }], - 'template-curly-spacing': [2, 'never'], - 'use-isnan': 2, - 'valid-typeof': 2, - 'wrap-iife': [2, 'any'], - 'yield-star-spacing': [2, 'both'], - 'yoda': [2, 'never'], - 'prefer-const': 2, - 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, - 'object-curly-spacing': [2, 'always', { - objectsInObjects: false - }], - 'array-bracket-spacing': [2, 'never'] - } -} diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json index a599d822b000692d4120017fb4fbcffdd235c625..71fe5928312937cda8178baba5483b151fd9ba11 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", @@ -8,19 +8,7 @@ "dev": "vue-cli-service serve", "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", - "preview": "node build/index.js --preview", - "lint": "eslint --ext .js,.vue src" - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "src/**/*.{js,vue}": [ - "eslint --fix", - "git add" - ] + "preview": "node build/index.js --preview" }, "keywords": [ "vue", @@ -59,27 +47,20 @@ "vue": "2.6.12", "vue-count-to": "1.0.13", "vue-cropper": "0.5.5", - "vue-meta": "2.4.0", "vue-router": "3.4.9", "vuedraggable": "2.24.3", "vuex": "3.6.0" }, "devDependencies": { "@vue/cli-plugin-babel": "4.4.6", - "@vue/cli-plugin-eslint": "4.4.6", "@vue/cli-service": "4.4.6", - "babel-eslint": "10.1.0", "babel-plugin-dynamic-import-node": "2.3.3", "babel-plugin-import": "^1.13.8", "chalk": "4.1.0", "compression-webpack-plugin": "6.1.2", "connect": "3.6.6", - "eslint": "7.15.0", - "eslint-plugin-vue": "7.2.0", "less": "^3.13.1", "less-loader": "^5.0.0", - "lint-staged": "10.5.3", - "runjs": "4.4.2", "sass": "1.32.13", "sass-loader": "10.1.1", "script-ext-html-webpack-plugin": "2.1.5", diff --git a/ruoyi-fastapi-frontend/src/App.vue b/ruoyi-fastapi-frontend/src/App.vue index b92ea37928e578d8c81a11bdbaed98508b01e597..6f386478de1ee5a91f4c200d0075548ab48844eb 100644 --- a/ruoyi-fastapi-frontend/src/App.vue +++ b/ruoyi-fastapi-frontend/src/App.vue @@ -10,15 +10,7 @@ import ThemePicker from "@/components/ThemePicker"; export default { name: "App", - components: { ThemePicker }, - metaInfo() { - return { - title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title, - titleTemplate: title => { - return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE - } - } - } + components: { ThemePicker } }; + diff --git a/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue b/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue index bf7e3814b7208d7449343f2cc585a9a8688eb315..7e5b241ebf2abe507afe06dd8df05a655afa2d7e 100644 --- a/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue +++ b/ruoyi-fastapi-frontend/src/components/ImageUpload/index.vue @@ -2,10 +2,12 @@
-
+
请上传 @@ -45,29 +47,49 @@ - - diff --git a/ruoyi-fastapi-frontend/src/components/RightToolbar/index.vue b/ruoyi-fastapi-frontend/src/components/RightToolbar/index.vue index 67da293008e3a22f644ee40fad97e4fe7ff53c22..c71029a728b78d89aa81ad917dbe16b0469ecf31 100644 --- a/ruoyi-fastapi-frontend/src/components/RightToolbar/index.vue +++ b/ruoyi-fastapi-frontend/src/components/RightToolbar/index.vue @@ -7,14 +7,19 @@ - + - + + diff --git a/ruoyi-fastapi-frontend/src/components/TopNav/index.vue b/ruoyi-fastapi-frontend/src/components/TopNav/index.vue index eb94842fc05d7ffd4a30264721c9866308933f0d..cb3fd38d52843bf28aea36bff56b1d67c05cf5b7 100644 --- a/ruoyi-fastapi-frontend/src/components/TopNav/index.vue +++ b/ruoyi-fastapi-frontend/src/components/TopNav/index.vue @@ -57,7 +57,7 @@ export default { this.routers.map((menu) => { if (menu.hidden !== true) { // 兼容顶部栏一级菜单内部跳转 - if (menu.path === "/") { + if (menu.path === '/' && menu.children) { topMenus.push(menu.children[0]); } else { topMenus.push(menu); diff --git a/ruoyi-fastapi-frontend/src/directive/index.js b/ruoyi-fastapi-frontend/src/directive/index.js index b9b07da3cf36d65311affd82897769b7c71c677a..39fe5821f9b95fed7684987b75158c40f0461a2b 100644 --- a/ruoyi-fastapi-frontend/src/directive/index.js +++ b/ruoyi-fastapi-frontend/src/directive/index.js @@ -17,7 +17,7 @@ const install = function(Vue) { if (window.Vue) { window['hasRole'] = hasRole window['hasPermi'] = hasPermi - Vue.use(install); // eslint-disable-line + Vue.use(install); } export default install diff --git a/ruoyi-fastapi-frontend/src/layout/components/AppMain.vue b/ruoyi-fastapi-frontend/src/layout/components/AppMain.vue index 66b33bf89de6d015f236b7bb9c7600e0a50a7a38..9209165a69b74becb5e2331402a77722c02f8d33 100644 --- a/ruoyi-fastapi-frontend/src/layout/components/AppMain.vue +++ b/ruoyi-fastapi-frontend/src/layout/components/AppMain.vue @@ -6,15 +6,17 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/layout/components/Navbar.vue b/ruoyi-fastapi-frontend/src/layout/components/Navbar.vue index e2cbf7ff9ddd6d705e0b17c2c8c2ac6830cc295c..5a7aa7ab4723c2e535792b022ac046d15885c04d 100644 --- a/ruoyi-fastapi-frontend/src/layout/components/Navbar.vue +++ b/ruoyi-fastapi-frontend/src/layout/components/Navbar.vue @@ -2,8 +2,8 @@