Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
飞书同步 API - 组织架构与用户同步管理接口
"""
import asyncio
import json
import logging
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession
from app.base_schema import ResponseModel
from app.database import get_db
from core.feishu_sync.schema import (
DeptTreeRequest,
FeishuSyncConfigUpdate,
TestConnectionRequest,
)
from core.feishu_sync.service import FeishuSyncService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/feishu-sync", tags=["飞书组织同步"])
@router.post("/test-connection", response_model=ResponseModel, summary="连接测试")
async def test_connection(data: TestConnectionRequest):
try:
result = await FeishuSyncService.test_connection(
app_id=data.app_id,
app_secret=data.app_secret,
)
return ResponseModel(data=result, message="连接成功")
except Exception as e:
raise HTTPException(status_code=400, detail=f"连接失败: {str(e)}")
@router.get("/config", summary="获取同步配置")
async def get_config():
from core.system_config.service import SystemConfigService
return await SystemConfigService.get_group_config("sync_feishu", mask_secrets=True)
@router.put("/config", response_model=ResponseModel, summary="保存同步配置")
async def update_config(
data: FeishuSyncConfigUpdate,
db: AsyncSession = Depends(get_db),
):
from core.system_config.service import SystemConfigService
configs = {k: v for k, v in data.model_dump().items() if v is not None}
updated = await SystemConfigService.update_group_config(db, "sync_feishu", configs)
return ResponseModel(data=updated, message="保存成功")
@router.post("/sync/dept", response_model=ResponseModel, summary="同步组织架构")
async def sync_departments(db: AsyncSession = Depends(get_db)):
try:
result = await FeishuSyncService.sync_departments(db)
return ResponseModel(data=result, message="组织架构同步完成")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"部门同步失败: {e}")
raise HTTPException(status_code=500, detail=f"同步失败: {str(e)}")
@router.post("/sync/user", response_model=ResponseModel, summary="同步用户")
async def sync_users(db: AsyncSession = Depends(get_db)):
try:
result = await FeishuSyncService.sync_users(db)
return ResponseModel(data=result, message="用户同步完成")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"用户同步失败: {e}")
raise HTTPException(status_code=500, detail=f"同步失败: {str(e)}")
@router.get("/stats", summary="获取同步统计")
async def get_sync_stats(db: AsyncSession = Depends(get_db)):
stats = await FeishuSyncService.get_sync_stats(db)
return stats
@router.get("/logs", summary="获取同步日志")
async def get_sync_logs(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
):
return await FeishuSyncService.get_sync_logs(db, page=page, page_size=page_size)
@router.post("/dept-tree", summary="获取飞书部门树")
async def get_dept_tree(data: DeptTreeRequest):
try:
tree = await FeishuSyncService.get_feishu_dept_tree(
app_id=data.app_id,
app_secret=data.app_secret,
)
return tree
except Exception as e:
raise HTTPException(status_code=400, detail=f"获取部门树失败: {str(e)}")
# ==================== 回调相关接口 ====================
@router.post("/callback", summary="飞书事件回调")
async def feishu_callback(request: Request):
"""
飞书事件订阅回调端点(无需认证)
处理两种场景:
1. URL 验证: {"type":"url_verification","challenge":"xxx","token":"xxx"}
2. 事件推送: {"encrypt":"xxx"} 或明文 v2.0 事件
签名通过 Header: X-Lark-Signature, X-Lark-Request-Timestamp, X-Lark-Request-Nonce
"""
from core.feishu_sync.callback_handler import FeishuCallbackHandler
try:
crypto = await FeishuCallbackHandler.get_crypto()
except ValueError as e:
logger.error(f"回调配置不完整: {e}")
raise HTTPException(status_code=500, detail="回调配置不完整")
raw_body = await request.body()
body_str = raw_body.decode("utf-8")
try:
body = json.loads(body_str)
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="无效的 JSON 请求体")
# URL 验证阶段飞书可能不带签名头,先尝试处理
if body.get("type") == "url_verification":
event_body = crypto.decrypt_event(body)
return crypto.handle_url_verification(event_body)
# 事件推送必须验证签名
signature = request.headers.get("X-Lark-Signature", "")
timestamp = request.headers.get("X-Lark-Request-Timestamp", "")
nonce = request.headers.get("X-Lark-Request-Nonce", "")
if not all([signature, timestamp, nonce]):
raise HTTPException(status_code=403, detail="缺少签名验证头")
if not crypto.verify_signature(timestamp, nonce, body_str, signature):
raise HTTPException(status_code=403, detail="签名验证失败")
event_body = crypto.decrypt_event(body)
event_type = event_body.get("header", {}).get("event_type", "")
logger.info(f"收到飞书回调事件: {event_type}")
asyncio.create_task(_safe_handle_event(event_body))
return {"code": 0, "msg": "ok"}
async def _safe_handle_event(event_body: dict) -> None:
"""安全地处理回调事件"""
from core.feishu_sync.callback_handler import FeishuCallbackHandler
try:
await FeishuCallbackHandler.handle_event(event_body)
except Exception as e:
logger.error(f"处理回调事件失败: {e}", exc_info=True)
@router.get("/callback/status", response_model=ResponseModel, summary="查询回调状态")
async def get_callback_status():
try:
result = await FeishuSyncService.get_callback_status()
return ResponseModel(data=result, message="查询成功")
except Exception as e:
return ResponseModel(data={"registered": False}, message="未注册或查询失败")