""" CoolBotDataSys API 服务 酷博特数据分析系统 RESTful API 版本: 1.0.0 """ import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from api.routes import collections_router, prices_router, health_router, yichens_router from api.middleware.response import ApiResponse # 配置日志 logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理""" logger.info("🚀 CoolBotDataSys API 启动...") yield logger.info("👋 CoolBotDataSys API 关闭...") # 创建 FastAPI 应用 app = FastAPI( title="CoolBotDataSys API", description=""" ## CoolBotDataSys - 酷博特数据分析系统 ### 功能模块 - **藏品管理** - 藏品的 CRUD 操作 - **价格追踪** - 历史价格查询和趋势分析 - **爬虫调度** - 数据采集任务管理 - **统计报表** - 数据统计和报表生成 ### 认证方式 当前版本暂不需要认证,后续添加 API Key 认证。 """, version="1.0.0", lifespan=lifespan, docs_url="/docs", redoc_url="/redoc" ) # CORS 配置 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 全局异常处理 @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): """HTTP 异常处理""" return JSONResponse( status_code=exc.status_code, content=ApiResponse.error( code=exc.status_code * 100, message=exc.detail ).model_dump() ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): """通用异常处理""" logger.error(f"未处理异常: {exc}", exc_info=True) return JSONResponse( status_code=500, content=ApiResponse.error( code=50000, message="Internal server error" ).model_dump() ) # 注册路由 app.include_router(health_router) app.include_router(yichens_router) app.include_router(collections_router) app.include_router(prices_router) # 统计接口 from api.middleware.response import ApiResponse from database import db @app.get("/api/v1/statistics", response_model=ApiResponse, tags=["statistics"]) async def get_statistics(): """获取系统统计信息""" with db.get_cursor() as cursor: # 藏品总数 cursor.execute("SELECT COUNT(*) as cnt FROM collections") total_collections = cursor.fetchone()["cnt"] # 价格记录总数 cursor.execute("SELECT COUNT(*) as cnt FROM price_history") total_price_records = cursor.fetchone()["cnt"] # 今日新增价格记录 cursor.execute(""" SELECT COUNT(*) as cnt FROM price_history WHERE DATE(crawled_at) = CURDATE() """) today_price_records = cursor.fetchone()["cnt"] # 最近爬虫运行时间 cursor.execute(""" SELECT MAX(finished_at) as last_run FROM crawl_logs WHERE status = 'success' """) last_crawl = cursor.fetchone()["last_run"] # 分类统计 cursor.execute(""" SELECT category, COUNT(*) as count FROM collections WHERE category IS NOT NULL GROUP BY category """) category_stats = {row["category"]: row["count"] for row in cursor.fetchall()} return ApiResponse.success({ "total_collections": total_collections, "total_price_records": total_price_records, "today_price_records": today_price_records, "last_crawl_time": last_crawl, "category_stats": category_stats }) # 爬虫调度接口 from crawlers.yichens_spider import YichensSpider @app.post("/api/v1/crawl-jobs/trigger", response_model=ApiResponse, tags=["crawl"]) async def trigger_crawl(source: str = "yichens"): """触发爬虫任务""" if source == "yichens": spider = YichensSpider() items = spider.run() return ApiResponse.success({ "source": source, "items_collected": len(items), "status": "completed" }, "Crawl job triggered successfully") else: raise HTTPException(status_code=400, detail=f"Unknown source: {source}") @app.get("/api/v1/crawl-jobs", response_model=ApiResponse, tags=["crawl"]) async def get_crawl_jobs(limit: int = 20): """获取爬虫运行历史""" with db.get_cursor() as cursor: cursor.execute(""" SELECT * FROM crawl_logs ORDER BY finished_at DESC LIMIT %s """, (limit,)) items = cursor.fetchall() return ApiResponse.success(items) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8080)