|
|
"""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))
|