You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
10 KiB

"""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 config.server.auth_enabled:
# 认证已禁用,跳过验证
return x_api_key or "anonymous"
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))