from __future__ import annotations import asyncio import logging import os import time import uuid from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from app.api.feishu_events import router as feishu_events_router from app.api.feishu_external import router as feishu_external_router from app.api.logs import router as logs_router from app.api.projects import router as projects_router from app.clients.feishu_ws_client import FeishuWsClient from app.config.logging import setup_logging from app.services.approval_sync_service import ApprovalSyncService LOG_DIR = "logs" app = FastAPI() logger = logging.getLogger(__name__) access_logger = logging.getLogger("access") @app.on_event("startup") async def on_startup() -> None: os.makedirs(LOG_DIR, exist_ok=True) setup_logging(LOG_DIR) approval_service = ApprovalSyncService() await approval_service.ensure_unsubscribe_once() await approval_service.ensure_approval_subscription() async def handler(payload: dict) -> None: logger.info("开始处理飞书事件") try: event_payload = payload.get("event", payload) await approval_service.handle_event(event_payload) logger.info("完成处理飞书事件") except Exception as exc: logger.exception("处理飞书事件异常: %s", exc) ws_client = FeishuWsClient(handler, asyncio.get_running_loop()) ws_client.start() app.state.ws_client = ws_client @app.on_event("shutdown") async def on_shutdown() -> None: ws_client = getattr(app.state, "ws_client", None) if ws_client: await ws_client.stop() @app.middleware("http") async def request_logging(request: Request, call_next): request_id = str(uuid.uuid4()) start = time.time() try: raw_body = await request.body() body_text = raw_body.decode("utf-8") if raw_body else "" except Exception: body_text = "" logger.info( "request_id=%s client=%s method=%s path=%s query=%s body=%s", request_id, request.client.host if request.client else "-", request.method, request.url.path, request.url.query, body_text, ) try: response = await call_next(request) except Exception as exc: logger.exception("请求异常: %s", exc) response = JSONResponse(status_code=500, content={"detail": "internal error"}) duration = int((time.time() - start) * 1000) access_logger.info( "request_id=%s method=%s path=%s status=%s cost_ms=%s", request_id, request.method, request.url.path, response.status_code, duration, ) return response @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): logger.exception("未处理异常: %s", exc) return JSONResponse(status_code=500, content={"detail": "internal error"}) app.include_router(feishu_external_router) app.include_router(feishu_events_router) app.include_router(logs_router) app.include_router(projects_router) app.mount("/logs", StaticFiles(directory="app/static/logs", html=True), name="logs")