feat: 添加飞书集成和审计API密钥认证

- 在数据库配置中添加飞书模型导入
- 添加审计API密钥配置项和认证中间件
- 实现飞书事件重复处理防止机制
- 为审批路由添加API密钥认证
- 优化AI适配器错误处理并添加JSON解析异常捕获
- 更新测试用例以包含新的认证和事件处理逻辑
```
This commit is contained in:
2026-07-06 11:34:15 +08:00
parent 0dbe5c1c2d
commit 514b14d390
16 changed files with 353 additions and 16 deletions

View File

@@ -1,14 +1,26 @@
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 AuditSource
from app.modules.audit.schemas import AuditLogCreate
from app.modules.feishu.commands import FeishuCommandService
from app.modules.feishu.constants import FeishuCommandKey
from app.modules.feishu.constants import (
FEISHU_LONG_CONNECTION_EVENT_ACTION,
FEISHU_WEBHOOK_EVENT_ACTION,
FeishuCommandKey,
FeishuPayloadKey,
)
from app.modules.feishu.models import FeishuEventReceipt
from app.modules.feishu.service import FeishuService
FEISHU_EVENT_ACTIONS = {
"webhook": FEISHU_WEBHOOK_EVENT_ACTION,
"long_connection": FEISHU_LONG_CONNECTION_EVENT_ACTION,
}
class FeishuEventService:
"""Handle Feishu message events from webhook or long connection."""
@@ -25,11 +37,16 @@ class FeishuEventService:
auto_reply: bool = True,
) -> dict[str, Any]:
self.feishu.verify_event(payload)
event_identity = _event_identity(payload, source)
if event_identity and not self._register_event(event_identity):
return {"ok": True, "handled": False, "duplicate": True}
self.feishu.audit.log(
AuditLogCreate(
actor=ActorValue.FEISHU,
source=AuditSource.FEISHU,
action=f"{source}_event",
action=FEISHU_EVENT_ACTIONS.get(source, FEISHU_WEBHOOK_EVENT_ACTION),
target_type=source,
target_id=event_identity.get("event_key") if event_identity else None,
request_payload=payload,
response_payload={"accepted": True},
)
@@ -44,3 +61,40 @@ class FeishuEventService:
auto_reply=auto_reply,
)
return {"ok": True, "handled": True, "result": result}
def _register_event(self, event_identity: dict[str, str | None]) -> bool:
receipt = FeishuEventReceipt(
event_key=str(event_identity["event_key"]),
source=str(event_identity["source"]),
event_id=event_identity.get("event_id"),
message_id=event_identity.get("message_id"),
)
self.db.add(receipt)
try:
self.db.flush()
except IntegrityError:
self.db.rollback()
return False
return True
def _event_identity(payload: dict[str, Any], source: str) -> dict[str, str | None] | None:
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, event_type or FeishuPayloadKey.EVENT, stable_id)
)
return {
"event_key": event_key,
"source": source,
"event_id": str(event_id) if event_id else None,
"message_id": str(message_id) if message_id else None,
}