feat: 添加公司AI管理平台基础架构 添加了完整的FastAPI后端项目结构,包括: - 环境配置文件(.env.example)和项目说明文档(README.md、AGENTS.md) - Dockerfile用于容器化部署 - 核心基础设施:配置管理、数据库连接、调度器、安全认证 - 模块化设计:AI代理、审批流程、审计日志、业务台账等功能模块 - 支持多数据库连接(主库和遗留系统只读库) - AI适配器支持OpenClaw、Hermes、OpenAI兼容接口 - 飞书集成、报表生成、风险监控等企业级功能 - 完整的依赖管理和测试指南 ```
92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
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)
|