diff --git a/app/db/oper/subscribe.py b/app/db/oper/subscribe.py index ce41c698a..2419da654 100644 --- a/app/db/oper/subscribe.py +++ b/app/db/oper/subscribe.py @@ -9,7 +9,6 @@ """ import time from collections.abc import Awaitable, Callable -from dataclasses import dataclass from typing import Any, Tuple, List, Optional from sqlalchemy import delete as sqlalchemy_delete, select @@ -28,13 +27,31 @@ AfterCommitEffect = Callable[[int], None] AsyncAfterCommitEffect = Callable[[int], Awaitable[None]] -@dataclass(frozen=True, slots=True) class SubscribeStageResult: """Oper 暂存结果,按 Application 端口需要暴露最小只读状态。""" - subscribe_id: int - message: str - created: bool + __slots__ = ("_subscribe_id", "_message", "_created") + + def __init__(self, subscribe_id: int, message: str, created: bool) -> None: + """保存写入后的订阅 ID、消息和是否创建标志。""" + self._subscribe_id = subscribe_id + self._message = message + self._created = created + + @property + def subscribe_id(self) -> int: + """返回已暂存或已存在的订阅 ID。""" + return self._subscribe_id + + @property + def message(self) -> str: + """返回暂存结果的人类可读消息。""" + return self._message + + @property + def created(self) -> bool: + """返回本次暂存是否创建了新记录。""" + return self._created def _normalize_integer_flags(payload: dict, fields: Tuple[str, ...] = INTEGER_FLAG_FIELDS) -> dict: diff --git a/app/runtime/event/contracts.py b/app/runtime/event/contracts.py index 418fd5243..521b3b512 100644 --- a/app/runtime/event/contracts.py +++ b/app/runtime/event/contracts.py @@ -130,17 +130,40 @@ EVENT_CONTRACTS = { } -def get_event_contract(event_type: EventType | ChainEventType) -> EventContract: - """返回 enum 事件的完整登记契约。""" - return EVENT_CONTRACTS[event_type] +def normalize_event_type( + event_type: EventType | ChainEventType | str, +) -> EventType | ChainEventType | str: + """把旧 SDK 传入的已知字符串恢复为 enum,未知扩展值保持原样。""" + if not isinstance(event_type, str): + return event_type + for enum_type in (EventType, ChainEventType): + try: + return enum_type(event_type) + except ValueError: + continue + return event_type + + +def get_event_contract( + event_type: EventType | ChainEventType | str, +) -> EventContract: + """返回已登记事件的完整契约,兼容传入 enum value 字符串。""" + normalized = normalize_event_type(event_type) + if isinstance(normalized, str): + raise KeyError(normalized) + return EVENT_CONTRACTS[normalized] def validate_event_payload( - event_type: EventType | ChainEventType, + event_type: EventType | ChainEventType | str, payload: Any, ) -> tuple[str, ...]: """在发送边界诊断首批 typed payload,保持原对象和插件 dict 形状不变。""" - model = get_event_contract(event_type).payload_model + try: + model = get_event_contract(event_type).payload_model + except KeyError: + # 动态插件在旧 ABI 下可能使用宿主枚举之外的字符串事件。 + return () if model is None or payload is None: return () if isinstance(payload, model): diff --git a/app/runtime/events.py b/app/runtime/events.py index a2deee143..908e3435e 100644 --- a/app/runtime/events.py +++ b/app/runtime/events.py @@ -20,7 +20,7 @@ from app.runtime.event.binding import ( from app.runtime.event.dispatch import EventDispatcher from app.runtime.event.errors import EventErrorNotifier, EventErrorPolicy from app.runtime.event.registry import EventRegistry -from app.runtime.event.contracts import validate_event_payload +from app.runtime.event.contracts import normalize_event_type, validate_event_payload from app.runtime.correlation import get_correlation_id from app.runtime.observability import record_metric @@ -35,7 +35,7 @@ class Event: 事件类,封装事件的基本信息 """ - def __init__(self, event_type: Union[EventType, ChainEventType], + def __init__(self, event_type: Union[EventType, ChainEventType, str], event_data: Optional[Union[Dict, ChainEventData]] = None, priority: Optional[int] = DEFAULT_EVENT_PRIORITY, correlation_id: Optional[str] = None): @@ -45,11 +45,12 @@ class Event: :param priority: 可选,事件的优先级,默认为 10 :param correlation_id: 生产事件时固化的请求关联 ID """ + event_type = normalize_event_type(event_type) payload_problems = validate_event_payload(event_type, event_data) if payload_problems: logger.warning( "事件 %s payload 与登记契约不一致:%s;当前保留旧 payload 继续投递", - event_type.value, + getattr(event_type, "value", event_type), "; ".join(payload_problems), ) self.event_id = str(uuid.uuid4()) # 事件ID @@ -63,7 +64,8 @@ class Event: 重写 __repr__ 方法,用于返回事件的详细信息,包括事件类型、事件ID和优先级 """ event_kind = Event.get_event_kind(self.event_type) - return f"<{event_kind}: {self.event_type.value}, ID: {self.event_id}, Priority: {self.priority}>" + event_name = getattr(self.event_type, "value", self.event_type) + return f"<{event_kind}: {event_name}, ID: {self.event_id}, Priority: {self.priority}>" def __lt__(self, other): """ @@ -73,7 +75,7 @@ class Event: return self.priority < other.priority @staticmethod - def get_event_kind(event_type: Union[EventType, ChainEventType]) -> str: + def get_event_kind(event_type: Union[EventType, ChainEventType, str]) -> str: """ 根据事件类型判断事件是广播事件还是链式事件 :param event_type: 事件类型,支持 EventType 或 ChainEventType