CoolBotDataSys/api/main.py

169 lines
5.0 KiB
Python
Raw Normal View History

"""
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 YichensPostSpider
@app.post("/api/v1/crawl-jobs/trigger", response_model=ApiResponse, tags=["crawl"])
async def trigger_crawl(source: str = "yichens"):
"""触发爬虫任务"""
if source == "yichens":
spider = YichensPostSpider()
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)