```
feat: 添加公司AI管理平台基础架构 添加了完整的FastAPI后端项目结构,包括: - 环境配置文件(.env.example)和项目说明文档(README.md、AGENTS.md) - Dockerfile用于容器化部署 - 核心基础设施:配置管理、数据库连接、调度器、安全认证 - 模块化设计:AI代理、审批流程、审计日志、业务台账等功能模块 - 支持多数据库连接(主库和遗留系统只读库) - AI适配器支持OpenClaw、Hermes、OpenAI兼容接口 - 飞书集成、报表生成、风险监控等企业级功能 - 完整的依赖管理和测试指南 ```
This commit is contained in:
91
app/modules/feishu/client.py
Normal file
91
app/modules/feishu/client.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import json
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
|
||||
class FeishuClient:
|
||||
"""Small Feishu Open Platform client for tenant token and message APIs."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.settings = get_settings()
|
||||
self._tenant_access_token: str | None = None
|
||||
self._token_expires_at: float = 0
|
||||
|
||||
def _is_configured(self) -> bool:
|
||||
return bool(self.settings.feishu_app_id and self.settings.feishu_app_secret)
|
||||
|
||||
def _get_tenant_access_token(self) -> str:
|
||||
if not self._is_configured():
|
||||
raise HTTPException(status_code=503, detail="Feishu app credentials are not configured")
|
||||
if self._tenant_access_token and time.time() < self._token_expires_at:
|
||||
return self._tenant_access_token
|
||||
|
||||
url = f"{self.settings.feishu_base_url}/auth/v3/tenant_access_token/internal"
|
||||
payload = {
|
||||
"app_id": self.settings.feishu_app_id,
|
||||
"app_secret": self.settings.feishu_app_secret,
|
||||
}
|
||||
with httpx.Client(timeout=20) as client:
|
||||
response = client.post(url, json=payload)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if data.get("code") != 0:
|
||||
raise HTTPException(status_code=502, detail={"feishu_error": data})
|
||||
self._tenant_access_token = data["tenant_access_token"]
|
||||
self._token_expires_at = time.time() + int(data.get("expire", 7200)) - 300
|
||||
return self._tenant_access_token
|
||||
|
||||
def send_message(
|
||||
self,
|
||||
receive_id: str,
|
||||
receive_id_type: str,
|
||||
msg_type: str,
|
||||
content: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
token = self._get_tenant_access_token()
|
||||
url = f"{self.settings.feishu_base_url}/im/v1/messages"
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
params = {"receive_id_type": receive_id_type}
|
||||
payload = {
|
||||
"receive_id": receive_id,
|
||||
"msg_type": msg_type,
|
||||
"content": json.dumps(content, ensure_ascii=False),
|
||||
}
|
||||
with httpx.Client(timeout=20) as client:
|
||||
response = client.post(url, headers=headers, params=params, json=payload)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return data
|
||||
|
||||
def send_text(
|
||||
self,
|
||||
text: str,
|
||||
receive_id: str | None = None,
|
||||
receive_id_type: str = "chat_id",
|
||||
) -> dict:
|
||||
chat_id = receive_id or self.settings.feishu_default_chat_id
|
||||
if not chat_id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="receive_id or FEISHU_DEFAULT_CHAT_ID is required",
|
||||
)
|
||||
return self.send_message(chat_id, receive_id_type, "text", {"text": text})
|
||||
|
||||
def send_card(
|
||||
self,
|
||||
card: dict[str, Any],
|
||||
receive_id: str | None = None,
|
||||
receive_id_type: str = "chat_id",
|
||||
) -> dict:
|
||||
chat_id = receive_id or self.settings.feishu_default_chat_id
|
||||
if not chat_id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="receive_id or FEISHU_DEFAULT_CHAT_ID is required",
|
||||
)
|
||||
return self.send_message(chat_id, receive_id_type, "interactive", card)
|
||||
Reference in New Issue
Block a user