|
|
@@ -6,12 +6,10 @@ from collections.abc import Mapping
|
|
|
from dataclasses import dataclass
|
|
|
from typing import Any
|
|
|
|
|
|
-from fastapi import APIRouter, HTTPException, Request
|
|
|
-
|
|
|
from gateway.core.channels.backends.echo_executor import EchoExecutorBackend
|
|
|
from gateway.core.channels.backends.memory_trace import MemoryTraceBackend
|
|
|
-from gateway.core.channels.backends.user_id import DefaultUserIdentityResolver
|
|
|
from gateway.core.channels.feishu.connector import FeishuConnector, WebhookParseError
|
|
|
+from gateway.core.channels.feishu.identity import DefaultUserIdentityResolver
|
|
|
from gateway.core.channels.feishu.router import FeishuMessageRouter
|
|
|
from gateway.core.channels.manager import ChannelRegistry
|
|
|
from gateway.core.channels.types import RouteResult
|
|
|
@@ -35,9 +33,7 @@ class FeishuChannelConfig:
|
|
|
|
|
|
|
|
|
class FeishuChannelManager(ChannelRegistry):
|
|
|
- """
|
|
|
- 飞书渠道:配置、连接器、路由与入站 webhook;继承 ``ChannelRegistry`` 的注册/启停能力。
|
|
|
- """
|
|
|
+ """飞书渠道:组装连接器、Trace 后端、执行器与消息路由;继承 ``ChannelRegistry`` 的注册/启停能力。"""
|
|
|
|
|
|
def __init__(self, config: FeishuChannelConfig | None = None) -> None:
|
|
|
super().__init__()
|
|
|
@@ -78,8 +74,22 @@ class FeishuChannelManager(ChannelRegistry):
|
|
|
def message_router(self) -> FeishuMessageRouter:
|
|
|
return self._router
|
|
|
|
|
|
+ @classmethod
|
|
|
+ def from_env(cls) -> FeishuChannelManager:
|
|
|
+ """从环境变量构造实例(与 docker-compose / .env 配合)。"""
|
|
|
+ return cls(
|
|
|
+ FeishuChannelConfig(
|
|
|
+ feishu_http_base_url=os.getenv("FEISHU_HTTP_BASE_URL", "http://127.0.0.1:4380").strip(),
|
|
|
+ http_timeout=float(os.getenv("FEISHU_HTTP_TIMEOUT", "120")),
|
|
|
+ echo_replies=os.getenv("CHANNELS_ECHO_REPLIES", "true").lower() in ("1", "true", "yes"),
|
|
|
+ echo_prefix=os.getenv("CHANNELS_ECHO_PREFIX", "[Gateway] "),
|
|
|
+ dispatch_reactions=os.getenv("CHANNELS_DISPATCH_REACTIONS", "false").lower() in ("1", "true", "yes"),
|
|
|
+ dispatch_card_actions=os.getenv("CHANNELS_DISPATCH_CARD_ACTIONS", "false").lower() in ("1", "true", "yes"),
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
async def handle_feishu_inbound_webhook(self, body: Mapping[str, Any]) -> RouteResult:
|
|
|
- """POST /api/channels/feishu/inbound/webhook"""
|
|
|
+ """处理飞书适配层 POST 到 ``/api/channels/feishu/inbound/webhook`` 的规范化事件。"""
|
|
|
cid = self._config.channel_id
|
|
|
if not self._running.get(cid, False):
|
|
|
return RouteResult(ok=False, error="channel_stopped")
|
|
|
@@ -90,71 +100,3 @@ class FeishuChannelManager(ChannelRegistry):
|
|
|
return RouteResult(ok=False, error=str(e))
|
|
|
|
|
|
return await self._router.route_feishu_inbound_event(event)
|
|
|
-
|
|
|
-
|
|
|
-def build_channels_api_router(channel_manager: FeishuChannelManager) -> APIRouter:
|
|
|
- """
|
|
|
- 构建 ``/api/channels`` 下的 FastAPI 路由(飞书 webhook、``GET .../status``)。
|
|
|
-
|
|
|
- 放在本模块而非 ``router.py``,避免 ``manager`` ↔ ``FeishuMessageRouter`` 循环导入。
|
|
|
- """
|
|
|
- channels = APIRouter(prefix="/api/channels", tags=["channels"])
|
|
|
- feishu = APIRouter(prefix="/feishu", tags=["feishu"])
|
|
|
-
|
|
|
- @feishu.post("/inbound/webhook")
|
|
|
- async def feishu_inbound_webhook(request: Request) -> dict[str, Any]:
|
|
|
- """
|
|
|
- Feishu HTTP 适配服务经 ``GATEWAY_FEISHU_WEBHOOK_URL`` POST 规范化 JSON 到此端点。
|
|
|
- """
|
|
|
- try:
|
|
|
- body = await request.json()
|
|
|
- except Exception:
|
|
|
- raise HTTPException(status_code=400, detail="invalid_json")
|
|
|
-
|
|
|
- if not isinstance(body, dict):
|
|
|
- raise HTTPException(status_code=400, detail="body_must_be_object")
|
|
|
-
|
|
|
- result = await channel_manager.handle_feishu_inbound_webhook(body)
|
|
|
- if not result.ok and result.error:
|
|
|
- logger.warning("feishu inbound webhook error: %s", result.error)
|
|
|
-
|
|
|
- out: dict[str, Any] = {
|
|
|
- "ok": result.ok,
|
|
|
- "skipped": result.skipped,
|
|
|
- "reason": result.reason,
|
|
|
- "trace_id": result.trace_id,
|
|
|
- "task_id": result.task_id,
|
|
|
- "user_id": result.user_id,
|
|
|
- "workspace_id": result.workspace_id,
|
|
|
- }
|
|
|
- if result.error:
|
|
|
- out["error"] = result.error
|
|
|
- return out
|
|
|
-
|
|
|
- @channels.get("/{channel_id}/status")
|
|
|
- async def channel_status(channel_id: str) -> dict[str, Any]:
|
|
|
- return channel_manager.get_channel_status(channel_id)
|
|
|
-
|
|
|
- channels.include_router(feishu)
|
|
|
- return channels
|
|
|
-
|
|
|
-
|
|
|
-def channel_manager_from_env() -> FeishuChannelManager:
|
|
|
- """从环境变量构造(与 docker-compose / .env 配合)。"""
|
|
|
- base = os.getenv("FEISHU_HTTP_BASE_URL", "http://127.0.0.1:4380").strip()
|
|
|
- timeout = float(os.getenv("FEISHU_HTTP_TIMEOUT", "120"))
|
|
|
- echo = os.getenv("CHANNELS_ECHO_REPLIES", "true").lower() in ("1", "true", "yes")
|
|
|
- reactions = os.getenv("CHANNELS_DISPATCH_REACTIONS", "false").lower() in ("1", "true", "yes")
|
|
|
- cards = os.getenv("CHANNELS_DISPATCH_CARD_ACTIONS", "false").lower() in ("1", "true", "yes")
|
|
|
- prefix = os.getenv("CHANNELS_ECHO_PREFIX", "[Gateway] ")
|
|
|
-
|
|
|
- return FeishuChannelManager(
|
|
|
- FeishuChannelConfig(
|
|
|
- feishu_http_base_url=base,
|
|
|
- http_timeout=timeout,
|
|
|
- echo_replies=echo,
|
|
|
- echo_prefix=prefix,
|
|
|
- dispatch_reactions=reactions,
|
|
|
- dispatch_card_actions=cards,
|
|
|
- )
|
|
|
- )
|