From 2bd7e93af735d5f88c508a056f96ffd78275a1d8 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Fri, 28 Nov 2025 13:24:41 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E4=BC=9A=E8=AF=9D=E7=AE=A1=E7=90=86=EF=BC=8C=E7=A1=AE?= =?UTF-8?q?=E4=BF=9D=E4=BA=8B=E5=8A=A1=E5=9C=A8=E6=AD=A3=E5=B8=B8=E9=80=80?= =?UTF-8?q?=E5=87=BA=E6=97=B6=E8=87=AA=E5=8A=A8=E6=8F=90=E4=BA=A4=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=9C=A8=E5=BC=82=E5=B8=B8=E6=97=B6=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E5=9B=9E=E6=BB=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/message_receive/message_handler.py | 3 ++ src/common/database/core/session.py | 16 +++++++++- .../database/optimization/batch_scheduler.py | 31 ++++++++++--------- .../database/optimization/connection_pool.py | 25 ++++++++++++--- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/chat/message_receive/message_handler.py b/src/chat/message_receive/message_handler.py index a7238c2fd..635f337cf 100644 --- a/src/chat/message_receive/message_handler.py +++ b/src/chat/message_receive/message_handler.py @@ -135,6 +135,7 @@ class MessageHandler: handler=self._handle_adapter_response_route, name="adapter_response_handler", message_type="adapter_response", + priority=100 ) # 注册 notice 消息处理器(处理通知消息,如戳一戳、禁言等) @@ -153,6 +154,7 @@ class MessageHandler: handler=self._handle_notice_message, name="notice_message_handler", message_type="notice", + priority=90 ) # 注册默认消息处理器(处理所有其他消息) @@ -160,6 +162,7 @@ class MessageHandler: predicate=lambda _: True, # 匹配所有消息 handler=self._handle_normal_message, name="default_message_handler", + priority=50 ) logger.info("MessageHandler 已向 MessageRuntime 注册处理器和钩子") diff --git a/src/common/database/core/session.py b/src/common/database/core/session.py index b033088f9..9cd701fc2 100644 --- a/src/common/database/core/session.py +++ b/src/common/database/core/session.py @@ -126,6 +126,12 @@ async def get_db_session_direct() -> AsyncGenerator[AsyncSession, None]: 用于特殊场景,如需要完全独立的连接时。 一般情况下应使用 get_db_session()。 + 事务管理说明: + - 正常退出时自动提交事务 + - 发生异常时自动回滚事务 + - 如果用户代码已手动调用 commit/rollback,再次调用是安全的 + - 适用于所有数据库类型(SQLite, MySQL, PostgreSQL) + Yields: AsyncSession: SQLAlchemy异步会话对象 """ @@ -139,8 +145,16 @@ async def get_db_session_direct() -> AsyncGenerator[AsyncSession, None]: await _apply_session_settings(session, global_config.database.database_type) yield session + + # 正常退出时提交事务 + # 这对所有数据库都很重要,因为 SQLAlchemy 默认不是 autocommit 模式 + # 检查事务是否活动,避免在已回滚的事务上提交 + if session.is_active: + await session.commit() except Exception: - await session.rollback() + # 检查是否需要回滚(事务是否活动) + if session.is_active: + await session.rollback() raise finally: await session.close() diff --git a/src/common/database/optimization/batch_scheduler.py b/src/common/database/optimization/batch_scheduler.py index 821d0b1af..f2d2591fb 100644 --- a/src/common/database/optimization/batch_scheduler.py +++ b/src/common/database/optimization/batch_scheduler.py @@ -17,7 +17,7 @@ from typing import Any, TypeVar from sqlalchemy import delete, insert, select, update -from src.common.database.core.session import get_db_session +from src.common.database.core.session import get_db_session_direct from src.common.logger import get_logger from src.common.memory_utils import estimate_size_smart @@ -330,7 +330,7 @@ class AdaptiveBatchScheduler: operations: list[BatchOperation], ) -> None: """批量执行查询操作""" - async with get_db_session() as session: + async with get_db_session_direct() as session: for op in operations: try: # 构建查询 @@ -371,7 +371,7 @@ class AdaptiveBatchScheduler: operations: list[BatchOperation], ) -> None: """批量执行插入操作""" - async with get_db_session() as session: + async with get_db_session_direct() as session: try: # 收集数据,并过滤掉 id=None 的情况(让数据库自动生成) all_data = [] @@ -387,7 +387,7 @@ class AdaptiveBatchScheduler: # 批量插入 stmt = insert(operations[0].model_class).values(all_data) await session.execute(stmt) - await session.commit() + # 注意:commit 由 get_db_session_direct 上下文管理器自动处理 # 设置结果 for op in operations: @@ -402,20 +402,21 @@ class AdaptiveBatchScheduler: except Exception as e: logger.error(f"批量插入失败: {e}") - await session.rollback() + # 注意:rollback 由 get_db_session_direct 上下文管理器自动处理 for op in operations: if op.future and not op.future.done(): op.future.set_exception(e) + raise # 重新抛出异常以触发 rollback async def _execute_update_batch( self, operations: list[BatchOperation], ) -> None: """批量执行更新操作""" - async with get_db_session() as session: + async with get_db_session_direct() as session: results = [] try: - # 🔧 修复:收集所有操作后一次性commit,而不是循环中多次commit + # 🔧 收集所有操作后一次性commit,而不是循环中多次commit for op in operations: # 构建更新语句 stmt = update(op.model_class) @@ -430,8 +431,7 @@ class AdaptiveBatchScheduler: result = await session.execute(stmt) results.append((op, result.rowcount)) - # 所有操作成功后,一次性commit - await session.commit() + # 注意:commit 由 get_db_session_direct 上下文管理器自动处理 # 设置所有操作的结果 for op, rowcount in results: @@ -446,21 +446,22 @@ class AdaptiveBatchScheduler: except Exception as e: logger.error(f"批量更新失败: {e}") - await session.rollback() + # 注意:rollback 由 get_db_session_direct 上下文管理器自动处理 # 所有操作都失败 for op in operations: if op.future and not op.future.done(): op.future.set_exception(e) + raise # 重新抛出异常以触发 rollback async def _execute_delete_batch( self, operations: list[BatchOperation], ) -> None: """批量执行删除操作""" - async with get_db_session() as session: + async with get_db_session_direct() as session: results = [] try: - # 🔧 修复:收集所有操作后一次性commit,而不是循环中多次commit + # 🔧 收集所有操作后一次性commit,而不是循环中多次commit for op in operations: # 构建删除语句 stmt = delete(op.model_class) @@ -472,8 +473,7 @@ class AdaptiveBatchScheduler: result = await session.execute(stmt) results.append((op, result.rowcount)) - # 所有操作成功后,一次性commit - await session.commit() + # 注意:commit 由 get_db_session_direct 上下文管理器自动处理 # 设置所有操作的结果 for op, rowcount in results: @@ -488,11 +488,12 @@ class AdaptiveBatchScheduler: except Exception as e: logger.error(f"批量删除失败: {e}") - await session.rollback() + # 注意:rollback 由 get_db_session_direct 上下文管理器自动处理 # 所有操作都失败 for op in operations: if op.future and not op.future.done(): op.future.set_exception(e) + raise # 重新抛出异常以触发 rollback async def _adjust_parameters(self) -> None: """根据性能自适应调整参数""" diff --git a/src/common/database/optimization/connection_pool.py b/src/common/database/optimization/connection_pool.py index 4030bc061..ed7d3e5ef 100644 --- a/src/common/database/optimization/connection_pool.py +++ b/src/common/database/optimization/connection_pool.py @@ -123,6 +123,12 @@ class ConnectionPoolManager: """ 获取数据库会话的透明包装器 如果有可用连接则复用,否则创建新连接 + + 事务管理说明: + - 正常退出时自动提交事务 + - 发生异常时自动回滚事务 + - 如果用户代码已手动调用 commit/rollback,再次调用是安全的(空操作) + - 支持所有数据库类型:SQLite、MySQL、PostgreSQL """ connection_info = None @@ -151,21 +157,30 @@ class ConnectionPoolManager: yield connection_info.session - # 🔧 修复:正常退出时提交事务 - # 这对SQLite至关重要,因为SQLite没有autocommit + # 🔧 正常退出时提交事务 + # 这对所有数据库(SQLite、MySQL、PostgreSQL)都很重要 + # 因为 SQLAlchemy 默认使用事务模式,不会自动提交 + # 注意:如果用户代码已调用 commit(),这里的 commit() 是安全的空操作 if connection_info and connection_info.session: try: - await connection_info.session.commit() + # 检查事务是否处于活动状态,避免在已回滚的事务上提交 + if connection_info.session.is_active: + await connection_info.session.commit() except Exception as commit_error: logger.warning(f"提交事务时出错: {commit_error}") - await connection_info.session.rollback() + try: + await connection_info.session.rollback() + except Exception: + pass # 忽略回滚错误,因为事务可能已经结束 raise except Exception: # 发生错误时回滚连接 if connection_info and connection_info.session: try: - await connection_info.session.rollback() + # 检查是否需要回滚(事务是否活动) + if connection_info.session.is_active: + await connection_info.session.rollback() except Exception as rollback_error: logger.warning(f"回滚连接时出错: {rollback_error}") raise From 454b143db261d864b906be3096ceff884dc59c76 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Fri, 28 Nov 2025 13:40:35 +0800 Subject: [PATCH 2/5] =?UTF-8?q?refactor(message=5Fhandler):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E5=99=A8=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=86=97=E4=BD=99=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/message_receive/message_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/chat/message_receive/message_handler.py b/src/chat/message_receive/message_handler.py index 635f337cf..1688797f8 100644 --- a/src/chat/message_receive/message_handler.py +++ b/src/chat/message_receive/message_handler.py @@ -134,7 +134,6 @@ class MessageHandler: predicate=_is_adapter_response, handler=self._handle_adapter_response_route, name="adapter_response_handler", - message_type="adapter_response", priority=100 ) @@ -153,7 +152,6 @@ class MessageHandler: predicate=_is_notice_message, handler=self._handle_notice_message, name="notice_message_handler", - message_type="notice", priority=90 ) From 302d48ff8582121b6e30464dfa0c35b960ea277c Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Fri, 28 Nov 2025 13:43:28 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(emoji=5Fmanager):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=A1=A8=E6=83=85=E5=8C=85=E6=B3=A8=E5=86=8C=E6=97=B6=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=E7=94=9F=E6=88=90=E7=9A=84=E5=BC=82=E6=AD=A5=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/emoji_system/emoji_manager.py | 34 +++++++++++--------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/chat/emoji_system/emoji_manager.py b/src/chat/emoji_system/emoji_manager.py index b1a359d3c..6a9437757 100644 --- a/src/chat/emoji_system/emoji_manager.py +++ b/src/chat/emoji_system/emoji_manager.py @@ -1107,28 +1107,22 @@ class EmojiManager: if emoji_base64 is None: # 再次检查读取 logger.error(f"[注册失败] 无法读取图片以生成描述: {filename}") return False - task = asyncio.create_task(self.build_emoji_description(emoji_base64)) - def after_built_description(fut: asyncio.Future): - if fut.cancelled(): - logger.error(f"[注册失败] 描述生成任务被取消: {filename}") - elif fut.exception(): - logger.error(f"[注册失败] 描述生成任务出错 ({filename}): {fut.exception()}") - else: - description, emotions = fut.result() + # 等待描述生成完成 + description, emotions = await self.build_emoji_description(emoji_base64) - if not description: # 检查描述是否成功生成或审核通过 - logger.warning(f"[注册失败] 未能生成有效描述或审核未通过: {filename}") - # 删除未能生成描述的文件 - try: - os.remove(file_full_path) - logger.info(f"[清理] 删除描述生成失败的文件: {filename}") - except Exception as e: - logger.error(f"[错误] 删除描述生成失败文件时出错: {e!s}") - return False - new_emoji.description = description - new_emoji.emotion = emotions - task.add_done_callback(after_built_description) + if not description: # 检查描述是否成功生成或审核通过 + logger.warning(f"[注册失败] 未能生成有效描述或审核未通过: {filename}") + # 删除未能生成描述的文件 + try: + os.remove(file_full_path) + logger.info(f"[清理] 删除描述生成失败的文件: {filename}") + except Exception as e: + logger.error(f"[错误] 删除描述生成失败文件时出错: {e!s}") + return False + + new_emoji.description = description + new_emoji.emotion = emotions except Exception as build_desc_error: logger.error(f"[注册失败] 生成描述/情感时出错 ({filename}): {build_desc_error}") # 同样考虑删除文件 From 6c808d1052c76a9fae14ea0a60c91348d2c32b54 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Fri, 28 Nov 2025 13:54:57 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(event=5Fmanager):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20ON=5FNOTICE=5FRECEIVED=20=E4=BA=8B=E4=BB=B6=E5=88=B0?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=BA=8B=E4=BB=B6=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin_system/core/event_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugin_system/core/event_manager.py b/src/plugin_system/core/event_manager.py index f9e4de553..93f63642b 100644 --- a/src/plugin_system/core/event_manager.py +++ b/src/plugin_system/core/event_manager.py @@ -433,6 +433,7 @@ class EventManager: EventType.AFTER_LLM, EventType.POST_SEND, EventType.AFTER_SEND, + EventType.ON_NOTICE_RECEIVED ] for event_name in default_events: From bfc45cb4f0b0892afa99e69d3f75ee0bf1714caa Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Fri, 28 Nov 2025 14:12:35 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(message=5Fhandler):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20ON=5FNOTICE=5FRECEIVED=20=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E6=9D=83=E9=99=90=E7=BB=84=E4=B8=BA=20SYSTEM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/message_receive/message_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chat/message_receive/message_handler.py b/src/chat/message_receive/message_handler.py index 1688797f8..29d78e2b3 100644 --- a/src/chat/message_receive/message_handler.py +++ b/src/chat/message_receive/message_handler.py @@ -315,7 +315,7 @@ class MessageHandler: # 触发 notice 事件(可供插件监听) await event_manager.trigger_event( EventType.ON_NOTICE_RECEIVED, - permission_group="USER", + permission_group="SYSTEM", message=message, notice_type=notice_type, chat_stream=chat,