feat: 添加飞书用户模块和订阅功能支持

- 新增feishu_users模块用于处理飞书用户身份验证和权限管理
- 新增subscriptions模块用于处理订阅相关功能
- 新增personalization模块用于个性化服务
- 在alembic迁移配置中注册新的模型模块
- 在API路由器中添加feishu_users和subscriptions路由
- 实现事件调度服务的改进,包括错误处理和状态更新优化
- 添加飞书命令处理的权限检查机制
- 实现飞书应用票据事件处理
- 改进审计日志记录功能
```
This commit is contained in:
2026-07-27 08:02:17 +08:00
parent db751f03b4
commit d7db84571d
148 changed files with 17110 additions and 765 deletions

View File

@@ -1,3 +1,4 @@
from hashlib import sha256
from secrets import compare_digest
from typing import Any
@@ -22,10 +23,18 @@ from app.modules.feishu.constants import (
class FeishuService:
"""Send Feishu messages and record audit entries for outbound actions."""
def __init__(self, db: Session):
def __init__(self, db: Session, tenant_key: str | None = None):
self.db = db
self.audit = AuditService(db)
self.client = FeishuClient()
self.client = FeishuClient(db)
self.tenant_key = _optional_text(tenant_key) or _optional_text(
get_settings().feishu_default_tenant_key
)
def set_tenant_key(self, tenant_key: str | None) -> None:
"""Set the default tenant used by subsequent outbound operations."""
self.tenant_key = _optional_text(tenant_key)
def verify_event(self, payload: dict[str, Any]) -> None:
settings = get_settings()
@@ -49,21 +58,32 @@ class FeishuService:
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = ActorValue.SYSTEM,
uuid: str | None = None,
tenant_key: str | None = None,
record_audit: bool = True,
) -> dict[str, Any]:
result = self.client.send_text(text, receive_id, receive_id_type)
self.audit.log(
AuditLogCreate(
actor=actor,
source=AuditSource.FEISHU,
action=AuditAction.FEISHU_SEND_TEXT,
request_payload={
FeishuPayloadKey.RECEIVE_ID: receive_id,
FeishuPayloadKey.RECEIVE_ID_TYPE: receive_id_type,
FeishuPayloadKey.TEXT: text,
},
response_payload=result,
)
result = self.client.send_text(
text,
receive_id,
receive_id_type,
uuid,
tenant_key=self._resolve_tenant_key(tenant_key),
)
if record_audit:
self.audit.log(
AuditLogCreate(
actor=actor,
source=AuditSource.FEISHU,
action=AuditAction.FEISHU_SEND_TEXT,
request_payload={
"receive_target_hash": _target_fingerprint(receive_id),
FeishuPayloadKey.RECEIVE_ID_TYPE: receive_id_type,
"content_length": len(text),
FeishuPayloadKey.UUID: uuid,
},
response_payload=result,
)
)
return result
def send_card(
@@ -72,17 +92,26 @@ class FeishuService:
receive_id: str | None = None,
receive_id_type: str = FeishuReceiveIdType.CHAT_ID,
actor: str = ActorValue.SYSTEM,
uuid: str | None = None,
tenant_key: str | None = None,
) -> dict[str, Any]:
result = self.client.send_card(card, receive_id, receive_id_type)
result = self.client.send_card(
card,
receive_id,
receive_id_type,
uuid,
tenant_key=self._resolve_tenant_key(tenant_key),
)
self.audit.log(
AuditLogCreate(
actor=actor,
source=AuditSource.FEISHU,
action=AuditAction.FEISHU_SEND_CARD,
request_payload={
FeishuPayloadKey.RECEIVE_ID: receive_id,
"receive_target_hash": _target_fingerprint(receive_id),
FeishuPayloadKey.RECEIVE_ID_TYPE: receive_id_type,
FeishuPayloadKey.CARD: card,
"card_element_count": len(card.get(FeishuPayloadKey.ELEMENTS) or []),
FeishuPayloadKey.UUID: uuid,
},
response_payload=result,
)
@@ -93,8 +122,12 @@ class FeishuService:
self,
image: bytes,
actor: str = ActorValue.SYSTEM,
tenant_key: str | None = None,
) -> dict[str, Any]:
result = self.client.upload_image(image)
result = self.client.upload_image(
image,
tenant_key=self._resolve_tenant_key(tenant_key),
)
self.audit.log(
AuditLogCreate(
actor=actor,
@@ -106,6 +139,9 @@ class FeishuService:
)
return result
def _resolve_tenant_key(self, tenant_key: str | None) -> str | None:
return _optional_text(tenant_key) or self.tenant_key
@staticmethod
def build_basic_card(
title: str,
@@ -144,3 +180,14 @@ class FeishuService:
},
FeishuPayloadKey.ELEMENTS: elements,
}
def _target_fingerprint(receive_id: str | None) -> str | None:
if not receive_id:
return None
return sha256(receive_id.encode("utf-8")).hexdigest()[:16]
def _optional_text(value: str | None) -> str | None:
text = str(value or "").strip()
return text or None