Files
company-ai-platform/app/modules/feishu/routes.py
JiuContinent eb8267ed18 ```
feat(feishu): 添加飞书入站事件inbox和混合数据库协调功能

- 实现飞书入站事件持久化inbox机制,支持状态管理、租约锁定和重试退避
- 添加混合数据库基线协调工具,确保平台PostgreSQL结构安全对齐
- 增加运行组件心跳检测和readiness就绪检查机制
- 实现app_ticket事件的安全轮换和验证处理
- 添加生产环境运行编排和fail-closed安全机制
- 支持webhook快速确认和长连接独立进程处理
- 完善个人数据擦除时的待处理事件清理功能
```
2026-07-27 17:14:37 +08:00

113 lines
3.5 KiB
Python

from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from app.core.background.task_queue import enqueue_feishu_inbound_event
from app.core.config import get_settings
from app.core.constants import ActorValue
from app.core.database import get_db
from app.core.security import ApiPrincipal, require_api_key
from app.application.feishu import FeishuCommandService, FeishuEventService
from app.modules.feishu.constants import (
FeishuEventSource,
FeishuEventTransport,
FeishuPayloadKey,
FeishuResponseKey,
)
from app.modules.feishu.event_verification import FeishuWebhookVerifier
from app.modules.feishu.schemas import (
FeishuCardMessage,
FeishuCommandRequest,
FeishuCommandResult,
FeishuSendResult,
FeishuTextMessage,
)
from app.modules.feishu.service import FeishuService
router = APIRouter()
@router.post("/webhook")
async def feishu_webhook(
request: Request,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
) -> dict:
"""Handle Feishu webhook challenge and text command events."""
if get_settings().feishu_event_transport != FeishuEventTransport.WEBHOOK:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Feishu webhook transport is disabled",
)
payload = FeishuWebhookVerifier().verify(await request.body(), request.headers)
acceptance = FeishuEventService(db).accept_verified_event(
payload,
source=FeishuEventSource.WEBHOOK,
auto_reply=True,
)
if acceptance.event_key is not None and acceptance.should_dispatch:
background_tasks.add_task(
enqueue_feishu_inbound_event,
acceptance.event_key,
actor=ActorValue.FEISHU,
)
return acceptance.response
@router.post("/send-text", response_model=FeishuSendResult)
def send_text(
payload: FeishuTextMessage,
db: Session = Depends(get_db),
principal: ApiPrincipal = Depends(require_api_key),
) -> dict:
result = FeishuService(db).send_text(
payload.text,
receive_id=payload.receive_id,
receive_id_type=payload.receive_id_type,
actor=principal.actor,
tenant_key=payload.tenant_key,
)
return {
FeishuResponseKey.OK: result.get(FeishuPayloadKey.CODE) == 0,
FeishuResponseKey.PROVIDER_RESPONSE: result,
}
@router.post("/send-card", response_model=FeishuSendResult)
def send_card(
payload: FeishuCardMessage,
db: Session = Depends(get_db),
principal: ApiPrincipal = Depends(require_api_key),
) -> dict:
result = FeishuService(db).send_card(
payload.card,
receive_id=payload.receive_id,
receive_id_type=payload.receive_id_type,
actor=principal.actor,
tenant_key=payload.tenant_key,
)
return {
FeishuResponseKey.OK: result.get(FeishuPayloadKey.CODE) == 0,
FeishuResponseKey.PROVIDER_RESPONSE: result,
}
@router.post(
"/commands/preview",
response_model=FeishuCommandResult,
)
def preview_command(
payload: FeishuCommandRequest,
db: Session = Depends(get_db),
principal: ApiPrincipal = Depends(require_api_key),
) -> dict:
"""Preview local Feishu command routing without requiring webhook delivery."""
return FeishuCommandService(db).handle_text(
payload.text,
chat_id=payload.chat_id,
actor=principal.actor,
auto_reply=payload.auto_reply,
tenant_key=payload.tenant_key,
)