"""API路由 - 对应Go的api/router.go""" from fastapi import APIRouter, Depends, HTTPException, Header, Query from typing import Optional from sqlalchemy.orm import Session from app.repositories import get_db from app.services import StockService, FuturesService, AdminService from app.models import ( Response, ErrorResponse, HealthResponse, KLineQueryRequest, SymbolListRequest, BatchKLineRequest, TradingDatesRequest, FuturesContractsRequest, SourceSwitchRequest, BackfillRequest ) from app.core.config import get_config router = APIRouter() # 获取配置 config = get_config() # 认证依赖 def verify_api_key(x_api_key: Optional[str] = Header(None)): """验证API Key""" if not x_api_key: raise HTTPException(status_code=401, detail="Missing API Key") # TODO: 验证API Key有效性 return x_api_key # ============================================ # 股票接口 # ============================================ @router.get("/stock/klines/{symbol}", response_model=Response) def query_stock_klines( symbol: str, start: str = Query(..., description="开始日期 YYYYMMDD", min_length=8, max_length=8), end: str = Query(..., description="结束日期 YYYYMMDD", min_length=8, max_length=8), freq: str = Query(default="1d", description="周期"), adjust: str = Query(default="", description="复权类型"), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """查询股票K线""" try: service = StockService(db) req = KLineQueryRequest( symbol=symbol, start=start, end=end, freq=freq, adjust=adjust ) data = service.query_klines(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/stock/symbols", response_model=Response) def list_stock_symbols( exchange: Optional[str] = Query(None, description="交易所筛选"), keyword: Optional[str] = Query(None, description="关键词搜索"), page: int = Query(default=1, ge=1, description="页码"), size: int = Query(default=20, ge=1, le=100, description="每页数量"), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """查询股票标的列表""" try: service = StockService(db) req = SymbolListRequest( exchange=exchange, keyword=keyword, page=page, size=size ) data = service.list_symbols(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/stock/klines/batch", response_model=Response) def batch_query_stock_klines( req: BatchKLineRequest, db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """批量查询股票K线""" try: service = StockService(db) data = service.batch_query_klines(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/stock/trading-dates", response_model=Response) def get_stock_trading_dates( start: str = Query(..., description="开始日期 YYYYMMDD", min_length=8, max_length=8), end: str = Query(..., description="结束日期 YYYYMMDD", min_length=8, max_length=8), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """获取股票交易日历""" try: service = StockService(db) req = TradingDatesRequest(start=start, end=end) data = service.get_trading_dates(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # ============================================ # 期货接口 # ============================================ @router.get("/futures/klines/{symbol}", response_model=Response) def query_futures_klines( symbol: str, start: str = Query(..., description="开始日期 YYYYMMDD", min_length=8, max_length=8), end: str = Query(..., description="结束日期 YYYYMMDD", min_length=8, max_length=8), freq: str = Query(default="1d", description="周期"), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """查询期货K线""" try: service = FuturesService(db) req = KLineQueryRequest( symbol=symbol, start=start, end=end, freq=freq ) data = service.query_klines(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/futures/symbols", response_model=Response) def list_futures_symbols( exchange: Optional[str] = Query(None, description="交易所筛选"), underlying: Optional[str] = Query(None, description="品种筛选"), keyword: Optional[str] = Query(None, description="关键词搜索"), page: int = Query(default=1, ge=1, description="页码"), size: int = Query(default=20, ge=1, le=100, description="每页数量"), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """查询期货标的列表""" try: service = FuturesService(db) req = SymbolListRequest( exchange=exchange, underlying=underlying, keyword=keyword, page=page, size=size ) data = service.list_symbols(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/futures/klines/batch", response_model=Response) def batch_query_futures_klines( req: BatchKLineRequest, db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """批量查询期货K线""" try: service = FuturesService(db) data = service.batch_query_klines(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/futures/continuous/{underlying}", response_model=Response) def query_continuous_klines( underlying: str, start: str = Query(..., description="开始日期 YYYYMMDD", min_length=8, max_length=8), end: str = Query(..., description="结束日期 YYYYMMDD", min_length=8, max_length=8), freq: str = Query(default="1d", description="周期"), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """查询主力连续合约K线(预留)""" # TODO: 实现主力连续合约查询 from app.models import KLineData data = KLineData( symbol=f"{underlying}.MAIN", name=f"{underlying}主力连续", freq=freq, count=0, items=[] ) return Response(code=0, message="success", data=data) @router.get("/futures/trading-dates", response_model=Response) def get_futures_trading_dates( start: str = Query(..., description="开始日期 YYYYMMDD", min_length=8, max_length=8), end: str = Query(..., description="结束日期 YYYYMMDD", min_length=8, max_length=8), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """获取期货交易日历""" try: service = FuturesService(db) req = TradingDatesRequest(start=start, end=end) data = service.get_trading_dates(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/futures/contracts", response_model=Response) def get_futures_contracts( underlying: str = Query(..., description="品种代码"), exchange: Optional[str] = Query(None, description="交易所筛选"), db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """获取品种合约列表""" try: service = FuturesService(db) req = FuturesContractsRequest(underlying=underlying, exchange=exchange) data = service.get_contracts_by_underlying(req) return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # ============================================ # 管理接口 # ============================================ @router.get("/admin/source/status", response_model=Response) def get_data_source_status( db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """获取数据源状态""" try: service = AdminService(db) data = service.get_data_source_status() return Response(code=0, message="success", data=data) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/admin/source/switch", response_model=Response) def switch_data_source( req: SourceSwitchRequest, db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """切换数据源""" try: service = AdminService(db) service.switch_data_source(req) return Response(code=0, message="数据源切换成功") except Exception as e: raise HTTPException(status_code=422, detail=str(e)) @router.post("/admin/backfill", response_model=Response) def backfill_data( req: BackfillRequest, db: Session = Depends(get_db), api_key: str = Depends(verify_api_key) ): """历史数据补录""" try: service = AdminService(db) task_id = service.backfill_data(req) return Response( code=0, message="补录任务已启动", data={"task_id": task_id} ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/admin/health", response_model=HealthResponse) def health_check( db: Session = Depends(get_db) ): """健康检查(无需认证)""" try: service = AdminService(db) return service.health_check() except Exception as e: raise HTTPException(status_code=503, detail=str(e))