main.py 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. """FastAPI 应用入口。"""
  2. from __future__ import annotations
  3. import asyncio
  4. import logging
  5. from contextlib import asynccontextmanager
  6. from typing import AsyncIterator
  7. from fastapi import FastAPI
  8. from app.routers import search
  9. from app.schemas import CacheStatus
  10. from app.services.cache import faq_cache, scheduler_loop
  11. logging.basicConfig(
  12. level=logging.INFO,
  13. format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
  14. )
  15. logger = logging.getLogger(__name__)
  16. @asynccontextmanager
  17. async def lifespan(app: FastAPI) -> AsyncIterator[None]:
  18. """启动时初始化缓存 + 启动定时器;关闭时优雅取消。"""
  19. logger.info("启动:首次加载 FAQ 缓存…")
  20. await faq_cache.refresh()
  21. task = asyncio.create_task(scheduler_loop(), name="faq-scheduler")
  22. logger.info("启动:定时刷新任务已运行")
  23. try:
  24. yield
  25. finally:
  26. logger.info("关闭:取消定时任务…")
  27. task.cancel()
  28. try:
  29. await task
  30. except asyncio.CancelledError:
  31. pass
  32. app = FastAPI(
  33. title="Zendesk FAQ Keyword Search",
  34. description="封装 Zendesk Help Center 搜索;FAQ sec_ids 每日 0 点刷新缓存。",
  35. version="1.0.0",
  36. lifespan=lifespan,
  37. )
  38. # 路由
  39. app.include_router(search.router)
  40. @app.get("/health", tags=["meta"])
  41. async def health() -> dict[str, str]:
  42. return {"status": "ok"}
  43. @app.get("/health/cache", response_model=CacheStatus, tags=["meta"])
  44. async def cache_status() -> CacheStatus:
  45. snap = faq_cache.snapshot()
  46. return CacheStatus(
  47. sec_ids_count=len(snap["sec_ids"]),
  48. last_updated_at=snap["last_updated_at"],
  49. next_refresh_at=snap["next_refresh_at"],
  50. )
  51. @app.post("/admin/cache/refresh", tags=["meta"])
  52. async def refresh_cache_now() -> dict[str, object]:
  53. """手动触发刷新(运维 / 测试用途)。"""
  54. await faq_cache.refresh()
  55. snap = faq_cache.snapshot()
  56. return {
  57. "success": True,
  58. "sec_ids_count": len(snap["sec_ids"]),
  59. "last_updated_at": snap["last_updated_at"],
  60. }