ForcePilot/backend/package/yuxi/channel/domain/service/pipeline.py
Kris 9a8a27bf36 feat(channel): 新增渠道网关模块完整实现
本次提交新增了完整的多渠道消息网关系统,包括:
1. 支持飞书、钉钉、Web、Hook 四种渠道的适配器与配置
2. 领域模型层:消息、会话、绑定、出箱等核心实体
3. 应用服务层:管道、中间件、DTO 与业务逻辑
4. 基础设施层:持久化、过滤器、队列等端口实现
5. 接口层:REST API、SSE、WebSocket 通信端点
6. 前端页面与路由配置,添加渠道管理菜单
7. 新增相关依赖包与 docker-compose 部署配置
2026-05-30 21:53:09 +08:00

67 lines
2.4 KiB
Python

from __future__ import annotations
import time
from typing import TYPE_CHECKING
from yuxi.channel.domain.service.message_context import MessageContext, Span
from yuxi.channel.domain.port.metrics_port import MetricsPort
if TYPE_CHECKING:
from yuxi.channel.domain.middleware.middleware import Middleware
class Pipeline:
def __init__(self, middlewares: list[Middleware], *, metrics: MetricsPort | None = None) -> None:
self._middlewares = middlewares
self._metrics = metrics
async def execute(self, ctx: MessageContext) -> MessageContext:
start = time.monotonic()
channel_type = ctx.channel_type or "unknown"
async def _chain(index: int, current_ctx: MessageContext) -> MessageContext:
if index >= len(self._middlewares):
return current_ctx
if current_ctx.is_aborted or current_ctx.is_skipped:
return current_ctx
middleware = self._middlewares[index]
span = Span(middleware_name=middleware.name)
span.started_at = time.monotonic()
try:
result_ctx = await middleware.process(current_ctx, lambda c: _chain(index + 1, c))
except Exception:
span.aborted = True
span.finished_at = time.monotonic()
span.duration_ms = (span.finished_at - span.started_at) * 1000
ctx.spans.append(span)
raise
span.finished_at = time.monotonic()
span.duration_ms = (span.finished_at - span.started_at) * 1000
span.aborted = result_ctx.is_aborted
span.skipped = result_ctx.is_skipped
ctx.spans.append(span)
if self._metrics:
await self._metrics.record_middleware_duration(middleware.name, channel_type, span.duration_ms / 1000)
return result_ctx
try:
result = await _chain(0, ctx)
finally:
if self._metrics:
elapsed = time.monotonic() - start
await self._metrics.record_pipeline_duration(channel_type, elapsed)
await self._metrics.record_pipeline_total(channel_type)
if ctx.is_aborted:
await self._metrics.record_pipeline_aborted(channel_type, ctx.abort_code or "unknown")
return result
@property
def middlewares(self) -> list[Middleware]:
return list(self._middlewares)