Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9658698
perf: 优化代码
insistence Jul 29, 2026
dbe4dac
perf: 优化字典类型属性提醒说明
insistence Jul 29, 2026
554a27b
perf: 优化RightToolbar搜索栏切换动画
insistence Jul 29, 2026
625801d
perf: 优化操作日志详细页面
insistence Jul 29, 2026
0ace444
feat: 新增锁定屏幕功能
insistence Jul 29, 2026
97475ef
feat: 字典类型列表新增抽屉效果详细信息
insistence Jul 29, 2026
e025770
perf: 优化页签功能&支持全屏按钮操作
insistence Jul 29, 2026
3a9b454
perf: 页签左右滚动效果兼容所有环境不依赖behavior
insistence Jul 29, 2026
138c279
perf: 优化tag全屏为页签内区域
insistence Jul 29, 2026
2dc0b3e
perf: 菜单搜索支持文本高亮&数量提示
insistence Jul 29, 2026
c5e25a9
feat: 添加持久化标签页开关功能
insistence Jul 29, 2026
4d4a388
perf: 优化页签显示
insistence Jul 29, 2026
7787379
perf: 优化菜单主题风格
insistence Jul 29, 2026
a39309e
perf: 优化快速点击页签刷新出现404问题
insistence Jul 29, 2026
a48995c
perf: 优化页签全屏显示
insistence Jul 29, 2026
c755a4a
feat: 代码生成支持表单布局选项
insistence Jul 29, 2026
15aeb88
feat: 新增树分割组件TreePanel
insistence Jul 29, 2026
6729bab
feat: 新增Excel导入组件ExcelImportDialog
insistence Jul 29, 2026
27a9d59
perf: 优化样式
insistence Jul 29, 2026
cb24a61
feat: 用户列表新增抽屉效果详细信息
insistence Jul 29, 2026
6f74936
perf: 代码生成修改拖拽时显示手指样式
insistence Jul 29, 2026
3933d15
feat: 新增标签页样式chrome风格
insistence Jul 29, 2026
7502e7e
feat: 新增菜单路由地址和名称的校验规则
insistence Jul 30, 2026
ab9c915
feat: 首页新增通知公告消息提醒
insistence Jul 30, 2026
49b1112
perf&feat: 优化定时任务详情页展示&补充执行时间字段
insistence Jul 30, 2026
db5eece
feat: 部门管理支持批量保存排序
insistence Jul 30, 2026
e9a1c4f
feat: 菜单管理支持批量保存排序
insistence Jul 30, 2026
afb4913
perf: 优化点击任务名称查看详细
insistence Jul 30, 2026
fd50198
feat: 菜单管理列表新增类型显示
insistence Jul 30, 2026
c478810
feat: 新增代码生成详情页功能
insistence Jul 30, 2026
2d63298
feat: 通知公告新增详细显示
insistence Jul 30, 2026
d8f11f0
perf: 优化白名单支持对通配符路径匹配
insistence Jul 30, 2026
451a035
feat: 通知公告新增阅读用户列表
insistence Jul 30, 2026
474e2fe
perf: 优化页签滚动条层级
insistence Jul 30, 2026
fe65c0c
feat: 用户密码支持自定义配置规则
insistence Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ruoyi-fastapi-backend/cli/groups/gen/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def build_gen_detail_text(self, payload: dict[str, object]) -> str:
f'business_name: {info.get("businessName", "-")}',
f'function_name: {info.get("functionName", "-")}',
f'function_author: {info.get("functionAuthor", "-")}',
f'form_col_num: {info.get("formColNum", "-")}',
f'gen_type: {info.get("genType", "-")}',
f'gen_path: {SHELL_TEXT_FORMATTER.truncate_text(info.get("genPath", ""), 120) or "-"}',
f'remark: {SHELL_TEXT_FORMATTER.truncate_text(info.get("remark", ""), 160) or "-"}',
Expand Down
6 changes: 6 additions & 0 deletions ruoyi-fastapi-backend/common/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class ApiNamespace:
等接口注解使用,用同一套“模块:功能”命名规则收敛缓存和限流场景。

LOGIN: 登录接口命名空间
LOGIN_UNLOCK_SCREEN: 锁屏解锁接口命名空间
REGISTER: 注册接口命名空间
LOGIN_USER_INFO: 登录用户信息接口命名空间
LOGIN_USER_ROUTERS: 登录用户路由接口命名空间
Expand Down Expand Up @@ -264,6 +265,7 @@ class ApiNamespace:
"""

LOGIN = 'login'
LOGIN_UNLOCK_SCREEN = 'login:unlock-screen'
REGISTER = 'register'
LOGIN_USER_INFO = 'login:user:info'
LOGIN_USER_ROUTERS = 'login:user:routers'
Expand Down Expand Up @@ -540,6 +542,7 @@ class MenuConstant:
"""
菜单常量

ROOT_ID: 根菜单ID
TYPE_DIR: 菜单类型(目录)
TYPE_MENU: 菜单类型(菜单)
TYPE_BUTTON: 菜单类型(按钮)
Expand All @@ -550,6 +553,7 @@ class MenuConstant:
INNER_LINK: InnerLink组件标识
"""

ROOT_ID = 0
TYPE_DIR = 'M'
TYPE_MENU = 'C'
TYPE_BUTTON = 'F'
Expand All @@ -572,6 +576,7 @@ class GenConstant:
TREE_NAME: 树名称字段
PARENT_MENU_ID: 上级菜单ID字段
PARENT_MENU_NAME: 上级菜单名称字段
GEN_VIEW: 生成详情页开关
COLUMNTYPE_STR: 数据库字符串类型
COLUMNTYPE_TEXT: 数据库文本类型
COLUMNTYPE_TIME: 数据库时间类型
Expand Down Expand Up @@ -608,6 +613,7 @@ class GenConstant:
TREE_NAME = 'treeName'
PARENT_MENU_ID = 'parentMenuId'
PARENT_MENU_NAME = 'parentMenuName'
GEN_VIEW = 'genView'
COLUMNTYPE_STR = (
['character varying', 'varchar', 'character', 'char']
if DataBaseConfig.db_type == 'postgresql'
Expand Down
34 changes: 34 additions & 0 deletions ruoyi-fastapi-backend/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,40 @@ class BusinessType(Enum):
CLEAN = 9


class PasswordCharacterType(Enum):
"""
密码字符范围类型
"""

def __init__(self, code: str, pattern: str, message: str) -> None:
self.code = code
self.pattern = pattern
self.message = message

@classmethod
def from_code(cls, code: str | bytes | None) -> 'PasswordCharacterType':
"""
根据配置值获取密码字符范围类型

:param code: 密码字符范围配置值
:return: 密码字符范围类型
"""
if isinstance(code, bytes):
code = code.decode()

return next((item for item in cls if item.code == code), cls.DEFAULT)

DEFAULT = ('0', r"""^[^<>"'|\\]+$""", '密码不能包含非法字符:< > " \' \\ |')
NUMBER = ('1', r'^[0-9]+$', '密码只能为数字(0-9)')
LETTER = ('2', r'^[a-zA-Z]+$', '密码只能为英文字母(a-z、A-Z)')
LETTER_NUMBER = ('3', r'^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$', '密码必须同时包含字母和数字')
LETTER_NUMBER_SPECIAL = (
'4',
r'^(?=.*[A-Za-z])(?=.*\d)(?=.*[~!@#$%^&*()\-=_+])[A-Za-z\d~!@#$%^&*()\-=_+]+$',
'密码必须同时包含字母、数字和特殊字符(~!@#$%^&*()-=_+)',
)


class RedisInitKeyConfig(Enum):
"""
系统内置Redis键名
Expand Down
54 changes: 49 additions & 5 deletions ruoyi-fastapi-backend/config/get_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ async def _execute_async_job_with_log(
"""
status = '0'
exception_info = ''
start_time = datetime.now()
job_executor = job_info.job_executor
if iscoroutinefunction(job_func):
job_executor = 'default'
Expand All @@ -790,26 +791,45 @@ async def _execute_async_job_with_log(
exception_info = str(e)
logger.error(f'❌ 异步执行任务 {job_info.job_name} 失败: {e}')
finally:
cls._record_job_execution_log(job_info, job_executor, status, exception_info)
cls._record_job_execution_log(
job_info,
job_executor,
status,
exception_info,
start_time,
datetime.now(),
)

@classmethod
def _record_job_execution_log(cls, job_info: JobModel, job_executor: str, status: str, exception_info: str) -> None:
def _record_job_execution_log(
cls,
job_info: JobModel,
job_executor: str,
status: str,
exception_info: str,
start_time: datetime,
end_time: datetime,
) -> None:
"""
记录任务执行日志(用于非 Leader Worker 直接执行任务时)

:param job_info: 任务对象信息
:param job_executor: 任务执行器
:param status: 执行状态 0-成功 1-失败
:param exception_info: 异常信息
:param start_time: 执行开始时间
:param end_time: 执行结束时间
:return: None
"""
try:
job_args = job_info.job_args if job_info.job_args else ''
job_kwargs = job_info.job_kwargs if job_info.job_kwargs else '{}'
job_trigger = str(MyCronTrigger.from_crontab(job_info.cron_expression)) if job_info.cron_expression else ''
run_ms = int((end_time - start_time).total_seconds() * 1000)
job_message = (
f'事件类型: DirectExecution(非Leader), 任务ID: {job_info.job_id}, '
f'任务名称: {job_info.job_name}, 执行于{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
f'任务名称: {job_info.job_name}, 执行于{end_time.strftime("%Y-%m-%d %H:%M:%S")}, '
f'总共耗时:{run_ms}毫秒'
)
job_log = JobLogModel(
jobName=job_info.job_name,
Expand All @@ -822,7 +842,9 @@ def _record_job_execution_log(cls, job_info: JobModel, job_executor: str, status
jobMessage=job_message,
status=status,
exceptionInfo=exception_info,
createTime=datetime.now(),
startTime=start_time,
endTime=end_time,
createTime=end_time,
)
session = cls._get_session_local()()
try:
Expand Down Expand Up @@ -990,6 +1012,7 @@ def execute_scheduler_job_once(cls, job_info: JobModel) -> None:
kwargs = json.loads(job_info.job_kwargs) if job_info.job_kwargs else {}
status = '0'
exception_info = ''
start_time = datetime.now()
try:
if iscoroutinefunction(job_func):
asyncio.create_task(cls._execute_async_job_with_log(job_func, job_info, args, kwargs)) # noqa: RUF006
Expand All @@ -1002,7 +1025,14 @@ def execute_scheduler_job_once(cls, job_info: JobModel) -> None:
finally:
# 同步任务记录日志(异步任务在 _execute_async_job_with_log 中记录)
if not iscoroutinefunction(job_func):
cls._record_job_execution_log(job_info, job_executor, status, exception_info)
cls._record_job_execution_log(
job_info,
job_executor,
status,
exception_info,
start_time,
datetime.now(),
)
return

# 应用锁 worker:通过 scheduler 执行
Expand Down Expand Up @@ -1078,8 +1108,20 @@ def scheduler_event_listener(cls, event: SchedulerEvent) -> None:
job_kwargs = json.dumps(kwargs) if kwargs else '{}'
# 获取任务触发器
job_trigger = str(query_job_info.get('trigger'))
start_time = None
end_time = None
run_ms = None
if event_type == 'JobExecutionEvent':
scheduled_run_time = getattr(event, 'scheduled_run_time', None)
if scheduled_run_time:
start_time = scheduled_run_time.astimezone().replace(tzinfo=None)
end_time = datetime.now()
if start_time:
run_ms = int((end_time - start_time).total_seconds() * 1000)
# 构造日志消息
job_message = f'事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
if run_ms is not None:
job_message += f', 总共耗时:{run_ms}毫秒'
job_log = JobLogModel(
jobName=job_name,
jobGroup=job_group,
Expand All @@ -1091,6 +1133,8 @@ def scheduler_event_listener(cls, event: SchedulerEvent) -> None:
jobMessage=job_message,
status=status,
exceptionInfo=exception_info,
startTime=start_time,
endTime=end_time,
createTime=datetime.now(),
)
session = cls._get_session_local()()
Expand Down
28 changes: 27 additions & 1 deletion ruoyi-fastapi-backend/module_admin/controller/dept_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from common.router import APIRouterPro
from common.vo import DataResponseModel, ResponseBaseModel
from module_admin.entity.do.dept_do import SysDept
from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptQueryModel
from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptQueryModel, DeptSortModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.dept_service import DeptService
from utils.log_util import logger
Expand Down Expand Up @@ -122,6 +122,32 @@ async def edit_system_dept(
return ResponseUtil.success(msg=edit_dept_result.message)


@dept_controller.put(
'/updateSort',
summary='保存部门排序接口',
description='用于批量保存部门显示顺序',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:dept:edit')],
)
@ApiCacheEvict(namespaces=ApiGroup.DATA_SCOPE_MUTATION)
@Log(title='保存部门排序', business_type=BusinessType.UPDATE)
async def update_system_dept_sort(
request: Request,
dept_sort: DeptSortModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
data_scope_sql: Annotated[ColumnElement, DataScopeDependency(SysDept)],
) -> Response:
update_sort_result = await DeptService.update_dept_sort_services(
query_db,
dept_sort,
None if current_user.user.admin else data_scope_sql,
)
logger.info(update_sort_result.message)

return ResponseUtil.success(msg=update_sort_result.message)


@dept_controller.delete(
'/{dept_ids}',
summary='删除部门接口',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
from common.router import APIRouterPro
from common.vo import CrudResponseModel, DataResponseModel, DynamicResponseModel, ResponseBaseModel
from config.env import AppConfig, JwtConfig
from module_admin.entity.vo.login_vo import LoginToken, RouterModel, Token, UserLogin, UserRegister
from module_admin.entity.vo.login_vo import (
LoginToken,
RouterModel,
Token,
UnlockScreenModel,
UserLogin,
UserRegister,
)
from module_admin.entity.vo.user_vo import CurrentUserModel, EditUserModel
from module_admin.service.login_service import CustomOAuth2PasswordRequestForm, LoginService, oauth2_scheme
from module_admin.service.user_service import UserService
Expand Down Expand Up @@ -122,6 +129,25 @@ async def get_login_user_routers(
return ResponseUtil.success(data=user_routers)


@login_controller.post(
'/unlockscreen',
summary='解锁屏幕接口',
description='用于校验当前登录用户密码并解锁屏幕',
response_model=ResponseBaseModel,
)
@ApiRateLimit(namespace=ApiNamespace.LOGIN_UNLOCK_SCREEN, preset=ApiRateLimitPreset.USER_SECURITY_MUTATION)
async def unlock_screen(
request: Request,
unlock_screen_data: UnlockScreenModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
current_user: Annotated[CurrentUserModel, CurrentUserDependency()],
) -> Response:
await LoginService.unlock_screen_services(query_db, current_user, unlock_screen_data.password)
logger.info('解锁成功')

return ResponseUtil.success(msg='解锁成功')


@login_controller.post(
'/register',
summary='注册接口',
Expand Down
22 changes: 21 additions & 1 deletion ruoyi-fastapi-backend/module_admin/controller/menu_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from common.enums import BusinessType
from common.router import APIRouterPro
from common.vo import DataResponseModel, DynamicResponseModel, ResponseBaseModel
from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel, MenuTreeModel
from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel, MenuSortModel, MenuTreeModel
from module_admin.entity.vo.role_vo import RoleMenuQueryModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.menu_service import MenuService
Expand Down Expand Up @@ -133,6 +133,26 @@ async def edit_system_menu(
return ResponseUtil.success(msg=edit_menu_result.message)


@menu_controller.put(
'/updateSort',
summary='保存菜单排序接口',
description='用于批量保存菜单显示顺序',
response_model=ResponseBaseModel,
dependencies=[UserInterfaceAuthDependency('system:menu:edit')],
)
@ApiCacheEvict(namespaces=ApiGroup.MENU_MUTATION)
@Log(title='保存菜单排序', business_type=BusinessType.UPDATE)
async def update_system_menu_sort(
request: Request,
menu_sort: MenuSortModel,
query_db: Annotated[AsyncSession, DBSessionDependency()],
) -> Response:
update_sort_result = await MenuService.update_menu_sort_services(query_db, menu_sort)
logger.info(update_sort_result.message)

return ResponseUtil.success(msg=update_sort_result.message)


@menu_controller.delete(
'/{menu_ids}',
summary='删除菜单接口',
Expand Down
Loading
Loading