ForcePilot/backend/package/yuxi/channels/adapters/sqlalchemy_transaction_adapter.py
Kris bb1934023e refactor: 完成会话持久化与事务机制重构,清理冗余代码
本次提交是一次大型架构重构,核心变更包括:
1. 调整持久化适配器为无状态实现,通过session_factory按需获取会话
2. 重构事务共享与透传机制,统一使用_session_scope管理会话生命周期
3. 移除OutboxEntry聚合根内的版本自增逻辑,由持久化层统一管理
4. 优化微信插件会话类型枚举与配置对齐
5. 简化出站管道阶段依赖注入与流程逻辑
6. 删除健康检查自动释放会话的冗余代码
7. 重构多个定时任务处理器,移除显式会话工厂创建逻辑
8. 修复会话延迟加载异常问题,新增时区转换工具函数
2026-07-10 04:10:33 +08:00

147 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""SQLAlchemy 事务适配器。
实现 ``TransactionPort`` 契约,基于 SQLAlchemy ``AsyncSession`` 提供事务
边界控制能力。事务边界由应用层(管道或用例编排器)显式调用,被驱动适配
器通过 ``TransactionContext`` 透传C-I1加入同一事务**不得** 自主提交。
事务共享机制:``create_driven_adapters(db, session_factory, ...)`` 将同一
请求级 ``AsyncSession`` 注入到 ``ConversationAdapter`` /
``SqlAlchemyTransactionAdapter``(请求级事务边界适配器,共享 ``db`` 保证
事务一致性);``ChannelPersistenceAdapter`` 为无状态适配器(注入
``session_factory``),通过 ``_session_scope(tx)`` 按需获取 session
``tx`` 非空时复用 ``tx.get_session()`` 返回的请求级 ``db``(加入应用层
事务),``tx`` 为 ``None`` 时通过 ``session_factory()`` 创建独立 session
并自主提交。
"""
from __future__ import annotations
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.channels.contract.ports.driven.transaction_port import (
TransactionPort,
)
__all__ = ["SqlAlchemyTransactionAdapter", "SqlAlchemyTransactionContext"]
class SqlAlchemyTransactionContext:
"""SQLAlchemy 事务上下文。
封装 ``AsyncSession`` 与其事务。被驱动适配器在构造时共享同一
``AsyncSession````begin()`` 在共享 session 上开启事务,所有适配器
的写操作自动加入。上下文管理器退出时自动提交(无异常)或回滚
(有异常)。
被驱动适配器 **不得** 调用 ``commit`` / ``rollback``,仅由应用层
通过 ``TransactionPort`` 控制。
"""
def __init__(self, session: AsyncSession) -> None:
"""初始化事务上下文。
Args:
session: SQLAlchemy 异步会话,事务边界由本上下文控制。
"""
self._session = session
self._txn: Any = None
def get_session(self) -> AsyncSession:
"""返回底层共享 ``AsyncSession``,供未在构造时共享 session 的适配器复用主事务。
C-I1``AgentRunAdapter`` 等适配器在构造时未与事务适配器共享 session
通过本方法获取主事务的共享 session复用同一事务避免独立提交产生
孤儿记录。
"""
return self._session
async def commit(self) -> None:
"""提交当前事务。
仅由应用层调用,被驱动适配器 **不得** 调用。提交后清空事务对象,
避免 ``__aexit__`` 重复提交。
"""
if self._txn is not None:
await self._txn.commit()
self._txn = None
async def rollback(self) -> None:
"""回滚当前事务。
仅由应用层调用,被驱动适配器 **不得** 调用。回滚后清空事务对象,
避免 ``__aexit__`` 重复回滚。
"""
if self._txn is not None:
await self._txn.rollback()
self._txn = None
async def __aenter__(self) -> SqlAlchemyTransactionContext:
"""进入事务上下文,开启 SQLAlchemy 事务。
SQLAlchemy 2.0 autobegin 语义下,前置读操作(如 ``getChannelSessionByPeer``
等 SELECT 查询)会在 session 上隐式开启只读事务。若不处理,
``begin()`` 会抛 ``InvalidRequestError: A transaction is already
begun on this Session.``。
此处检测并提交隐式事务后再开启显式事务。安全性保证:按适配器契约,
``tx=None`` 的写操作自主提交autobegin 事务仅含读操作,提交不会
产生副作用数据落库。应用层显式事务边界§10.1)由此方法独占控制。
"""
if self._session.in_transaction():
await self._session.commit()
self._txn = await self._session.begin()
return self
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
"""退出事务上下文。
若事务未被显式提交/回滚(``self._txn`` 非空且 ``is_active``),则按
异常状态自动提交(无异常)或回滚(有异常);若已被显式提交/回滚
``self._txn`` 为空)或已被 SQLAlchemy 内部关闭(``is_active=False``
如 flush 失败自动关闭事务),则跳过,避免 ``ResourceClosedError``。
事务对象退出后释放引用,避免泄漏。
"""
try:
if self._txn is not None and self._txn.is_active:
if exc is None:
await self._txn.commit()
else:
await self._txn.rollback()
finally:
self._txn = None
class SqlAlchemyTransactionAdapter(TransactionPort):
"""SQLAlchemy 事务适配器。
实现 ``TransactionPort`` 契约,基于共享的 ``AsyncSession`` 提供事务
边界控制。事务边界由应用层显式调用 ``begin()`` 开启,被驱动适配器
通过构造时共享的 session 加入同一事务。
关键约束:
- 事务边界 **必须** 由应用层控制。
- 被驱动适配器 **不得** 自主调用 ``commit`` / ``rollback``。
- 事务范围 **必须** 由应用层显式声明。
"""
def __init__(self, session: AsyncSession) -> None:
"""初始化事务适配器。
Args:
session: SQLAlchemy 异步会话,与被驱动适配器共享以保证事务
一致性。
"""
self._session = session
def begin(self) -> SqlAlchemyTransactionContext:
"""开启一个新事务,返回 SQLAlchemy 事务上下文。
Returns:
SQLAlchemy 事务上下文,被驱动适配器通过构造时共享的 session
加入同一事务。上下文管理器退出时自动提交(无异常)或回滚
(有异常)。
"""
return SqlAlchemyTransactionContext(self._session)