| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- """FastAPI 应用入口。"""
- from __future__ import annotations
- import asyncio
- import logging
- from contextlib import asynccontextmanager
- from typing import AsyncIterator
- from fastapi import FastAPI
- from app.exception_handlers import register_exception_handlers
- from app.response import ApiResponse
- from app.routers import search
- from app.schemas import CacheRefreshResult, CacheStatus
- from app.services.cache import faq_cache, scheduler_loop
- logging.basicConfig(
- level=logging.INFO,
- format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
- )
- logger = logging.getLogger(__name__)
- @asynccontextmanager
- async def lifespan(app: FastAPI) -> AsyncIterator[None]:
- """启动时初始化缓存 + 启动定时器;关闭时优雅取消。"""
- logger.info("启动:首次加载 FAQ 缓存…")
- await faq_cache.refresh()
- task = asyncio.create_task(scheduler_loop(), name="faq-scheduler")
- logger.info("启动:定时刷新任务已运行")
- try:
- yield
- finally:
- logger.info("关闭:取消定时任务…")
- task.cancel()
- try:
- await task
- except asyncio.CancelledError:
- pass
- app = FastAPI(
- title="Zendesk FAQ Keyword Search",
- description="封装 Zendesk Help Center 搜索;FAQ sec_ids 每日 0 点刷新缓存。",
- version="1.0.0",
- lifespan=lifespan,
- )
- # 注册全局异常处理器(统一错误响应格式 {code, data, msg})
- register_exception_handlers(app)
- # 路由
- app.include_router(search.router)
- @app.get(
- "/health",
- response_model=ApiResponse[dict[str, str]],
- tags=["meta"],
- )
- async def health() -> ApiResponse[dict[str, str]]:
- """健康检查。"""
- return ApiResponse.ok(data={"status": "ok"})
- @app.get(
- "/health/cache",
- response_model=ApiResponse[CacheStatus],
- tags=["meta"],
- )
- async def cache_status() -> ApiResponse[CacheStatus]:
- """缓存状态查看。"""
- snap = faq_cache.snapshot()
- return ApiResponse.ok(
- data=CacheStatus(
- sec_ids_count=len(snap["sec_ids"]),
- last_updated_at=snap["last_updated_at"],
- next_refresh_at=snap["next_refresh_at"],
- )
- )
- @app.post(
- "/admin/cache/refresh",
- response_model=ApiResponse[CacheRefreshResult],
- tags=["meta"],
- )
- async def refresh_cache_now() -> ApiResponse[CacheRefreshResult]:
- """手动触发刷新(运维 / 测试用途)。"""
- await faq_cache.refresh()
- snap = faq_cache.snapshot()
- return ApiResponse.ok(
- data=CacheRefreshResult(
- sec_ids_count=len(snap["sec_ids"]),
- last_updated_at=snap["last_updated_at"],
- ),
- msg="缓存刷新成功",
- )
|