# AI辅助生成:deepseek-v3-2,2026年3月20日 import logging import os from pathlib import Path from fastapi.responses import FileResponse, HTMLResponse, JSONResponse import httpx from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException, Request, staticfiles from fastapi.middleware.cors import CORSMiddleware from dotenv import load_dotenv # 统一配置日志格式和级别,确保 delivery_service 等的 INFO 日志可见 logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) # 降低 APScheduler 运行心跳日志,避免每分钟刷屏 logging.getLogger("apscheduler").setLevel(logging.WARNING) from apscheduler.schedulers.asyncio import AsyncIOScheduler from app.services.fetcher_service import fetch_and_save_trending_data from app.services.summary_service import generate_unified_summaries from app.services.delivery_service import check_and_deliver from app.database import engine from app.models.models import Base from app.initialize import init # 路由总线 from app.api.router import api_router CRAWL_INTERVAL = int(os.getenv("CRAWL_INTERVAL_MINUTES", 10)) SUMMARY_INTERVAL = int(os.getenv("SUMMARY_INTERVAL_MINUTES", 30)) scheduler = AsyncIOScheduler() @asynccontextmanager async def lifespan(app: FastAPI): # 1. 数据库建表 logging.info("正在初始化数据库表...") Base.metadata.create_all(bind=engine) logging.info("数据库表初始化完成!") logging.info("初始化订阅源") init() logging.info("订阅源初始化完毕") # 爬取订阅源 scheduler.add_job( fetch_and_save_trending_data, 'interval', minutes=CRAWL_INTERVAL, id='trending_fetch_job', replace_existing=True ) # 平台摘要 scheduler.add_job( generate_unified_summaries, 'interval', minutes=SUMMARY_INTERVAL, id='ai_summary_job', replace_existing=True ) # 推送调度 scheduler.add_job( check_and_deliver, 'interval', minutes=1, id='delivery_check_job', replace_existing=True, ) scheduler.start() logging.info(f"定时抓取任务已启动,每 {CRAWL_INTERVAL} 分钟执行一次") logging.info(f"AI 摘要生成任务已启动,每 {SUMMARY_INTERVAL} 分钟执行一次") logging.info("邮件推送调度已启动,每分钟检查一次") yield scheduler.shutdown() logging.info("定时任务已安全关闭") app = FastAPI(title="AI 新闻聚合引擎 API", lifespan=lifespan) app.add_middleware( CORSMiddleware, # allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"], allow_origins=["*"], # allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(api_router, prefix="/api/v1") # AI辅助生成结束 @app.get("/api/{full_path:path}") async def api_not_found(full_path: str): return {"detail": "API Not Found"} staticPath = staticfiles.StaticFiles(directory="app/static", html=True) app.mount("/", staticPath, name="static") INDEX_HTML = Path("app/static/index.html").read_text(encoding="utf-8") @app.exception_handler(404) async def not_found_handler(request: Request, exc: HTTPException): if request.url.path.startswith("/api/"): return JSONResponse({"detail": "Not Found"}, status_code=404) return HTMLResponse(INDEX_HTML) @app.get("/", tags=["健康检查"]) async def root(): return {"message": "Welcome to AI News Aggregator API", "status": "ok"}