Files
company-ai-platform/app/modules/feishu/events.py
JiuContinent 0a153b264a ```
refactor: 移除审批和回写功能模块

移除了整个审批(approvals)和官方回写(writebacks)功能模块,
包括相关模型、路由、服务和配置项。更新了数据库迁移文件,
删除了相关的审批请求表和官方回写运行表。同时从API路由器中
移除了相应的路由,并调整了安全常量和字段验证器以匹配变更。
```
2026-07-08 17:08:59 +08:00

126 lines
4.5 KiB
Python

from typing import Any
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.core.constants import ActorValue
from app.modules.audit.constants import AuditAction, AuditSource
from app.modules.audit.schemas import AuditLogCreate
from app.modules.feishu.commands import FeishuCommandService
from app.modules.feishu.constants import (
FeishuCommandKey,
FeishuEventReceiptKey,
FeishuEventSource,
FeishuPayloadKey,
FeishuResponseKey,
)
from app.modules.feishu.models import FeishuEventReceipt
from app.modules.feishu.service import FeishuService
FEISHU_EVENT_ACTIONS = {
FeishuEventSource.WEBHOOK: AuditAction.FEISHU_WEBHOOK_EVENT,
FeishuEventSource.LONG_CONNECTION: AuditAction.FEISHU_LONG_CONNECTION_EVENT,
}
class FeishuEventService:
"""Handle Feishu message events from webhook or long connection."""
def __init__(self, db: Session):
self.db = db
self.feishu = FeishuService(db)
self.commands = FeishuCommandService(db)
def handle_event(
self,
payload: dict[str, Any],
source: str | FeishuEventSource,
auto_reply: bool = True,
) -> dict[str, Any]:
self.feishu.verify_event(payload)
challenge = payload.get(FeishuPayloadKey.CHALLENGE)
if challenge:
return {FeishuResponseKey.CHALLENGE: challenge}
source_value = _normalize_source(source)
event_identity = _event_identity(payload, source)
if event_identity and not self._register_event(event_identity):
return {
FeishuResponseKey.OK: True,
FeishuResponseKey.HANDLED: False,
FeishuResponseKey.DUPLICATE: True,
}
self.feishu.audit.log(
AuditLogCreate(
actor=ActorValue.FEISHU,
source=AuditSource.FEISHU,
action=FEISHU_EVENT_ACTIONS[source_value],
target_type=source_value,
target_id=(
event_identity.get(FeishuEventReceiptKey.EVENT_KEY)
if event_identity
else None
),
request_payload=payload,
response_payload={FeishuResponseKey.ACCEPTED: True},
)
)
command = self.commands.extract_event_command(payload)
if not command:
return {FeishuResponseKey.OK: True, FeishuResponseKey.HANDLED: False}
result = self.commands.handle_text(
command[FeishuCommandKey.TEXT],
chat_id=command[FeishuCommandKey.CHAT_ID],
actor=command[FeishuCommandKey.ACTOR],
auto_reply=auto_reply,
)
return {
FeishuResponseKey.OK: True,
FeishuResponseKey.HANDLED: True,
FeishuResponseKey.RESULT: result,
}
def _register_event(self, event_identity: dict[str, str | None]) -> bool:
receipt = FeishuEventReceipt(
event_key=str(event_identity[FeishuEventReceiptKey.EVENT_KEY]),
source=str(event_identity[FeishuEventReceiptKey.SOURCE]),
event_id=event_identity.get(FeishuEventReceiptKey.EVENT_ID),
message_id=event_identity.get(FeishuEventReceiptKey.MESSAGE_ID),
)
self.db.add(receipt)
try:
self.db.flush()
except IntegrityError:
self.db.rollback()
return False
return True
def _normalize_source(source: str | FeishuEventSource) -> FeishuEventSource:
return FeishuEventSource(source)
def _event_identity(
payload: dict[str, Any],
source: str | FeishuEventSource,
) -> dict[str, str | None] | None:
source_value = _normalize_source(source)
header = payload.get(FeishuPayloadKey.HEADER) or {}
event = payload.get(FeishuPayloadKey.EVENT) or {}
message = event.get(FeishuPayloadKey.MESSAGE) or {}
event_id = header.get(FeishuPayloadKey.EVENT_ID)
message_id = message.get(FeishuPayloadKey.MESSAGE_ID)
stable_id = event_id or message_id
if not stable_id:
return None
event_type = header.get(FeishuPayloadKey.EVENT_TYPE)
event_key = ":".join(
str(part)
for part in (source_value, event_type or FeishuPayloadKey.EVENT, stable_id)
)
return {
FeishuEventReceiptKey.EVENT_KEY: event_key,
FeishuEventReceiptKey.SOURCE: source_value,
FeishuEventReceiptKey.EVENT_ID: str(event_id) if event_id else None,
FeishuEventReceiptKey.MESSAGE_ID: str(message_id) if message_id else None,
}