""" API 路由定义 提供汇率查询和换算接口 """ from fastapi import APIRouter, HTTPException, Request from typing import Optional from app.models import ( RatesResponse, CurrencyRateResponse, ConvertRequest, ConvertResponse, BatchConvertRequest, BatchConvertResponse, CurrenciesResponse, CurrencyInfo, ServiceStatusResponse ) from app.utils.currency_names import CURRENCY_INFO, get_currency_info from app.utils.logger import logger router = APIRouter(prefix="/api", tags=["汇率接口"]) def get_exchange_service(request: Request): """从应用状态获取 ExchangeRateService 实例""" return request.app.state.exchange_service # ==================== 获取所有汇率 ==================== @router.get("/rates", response_model=RatesResponse) async def get_all_rates(request: Request): """ 获取所有货币汇率 返回缓存中的所有汇率数据,包括基准货币、汇率列表、更新时间等。 """ service = get_exchange_service(request) cached = service.get_cached_rates() if not cached: logger.warning("API /rates: 没有可用的缓存数据") raise HTTPException( status_code=503, detail="汇率数据不可用,请稍后重试。" ) return RatesResponse( success=True, base_currency=cached.base_code, rates=cached.rates, last_update=cached.last_update_utc, cached_at=cached.cached_at.isoformat(), currencies_count=len(cached.rates) ) # ==================== 获取特定货币汇率 ==================== @router.get("/rates/{currency}", response_model=CurrencyRateResponse) async def get_currency_rate(currency: str, request: Request): """ 获取特定货币对基准货币的汇率 - **currency**: 货币代码(如 USD, EUR, JPY) """ service = get_exchange_service(request) cached = service.get_cached_rates() if not cached: raise HTTPException( status_code=503, detail="汇率数据不可用" ) currency = currency.upper() if currency not in cached.rates: raise HTTPException( status_code=404, detail=f"未找到货币 '{currency}'" ) return CurrencyRateResponse( success=True, base_currency=cached.base_code, currency=currency, rate=cached.rates[currency] ) # ==================== 两种货币换算 ==================== @router.post("/convert", response_model=ConvertResponse) async def convert_currency(request: Request, body: ConvertRequest): """ 两种货币之间的汇率换算 - **from_currency**: 源货币代码 - **to_currency**: 目标货币代码 - **amount**: 换算金额 返回换算结果、汇率和反向汇率。 """ service = get_exchange_service(request) result = service.convert( body.from_currency, body.to_currency, body.amount ) if not result: raise HTTPException( status_code=400, detail="换算失败,请检查货币代码是否有效。" ) return result # ==================== GET 方式的货币换算 ==================== @router.get("/convert/{from_currency}/{to_currency}") async def convert_currency_get( from_currency: str, to_currency: str, amount: float, request: Request ): """ 两种货币之间的汇率换算(GET 方式) - **from_currency**: 源货币代码 - **to_currency**: 目标货币代码 - **amount**: 换算金额(查询参数) 示例: GET /api/convert/USD/CNY?amount=100 """ service = get_exchange_service(request) result = service.convert(from_currency, to_currency, amount) if not result: raise HTTPException( status_code=400, detail="换算失败,请检查货币代码是否有效。" ) return result # ==================== 批量换算 ==================== @router.post("/batch-convert", response_model=BatchConvertResponse) async def batch_convert(request: Request, body: BatchConvertRequest): """ 批量换算:计算某金额对应所有货币的值 - **base_currency**: 基准货币代码 - **amount**: 换算金额 返回所有货币的换算结果。 """ service = get_exchange_service(request) result = service.batch_convert(body.base_currency, body.amount) if not result: raise HTTPException( status_code=400, detail="批量换算失败,请检查基准货币是否有效。" ) return result # ==================== GET 方式的批量换算 ==================== @router.get("/batch-convert/{base_currency}") async def batch_convert_get(base_currency: str, amount: float, request: Request): """ 批量换算(GET 方式) - **base_currency**: 基准货币代码 - **amount**: 换算金额(查询参数) 示例: GET /api/batch-convert/USD?amount=100 """ service = get_exchange_service(request) result = service.batch_convert(base_currency, amount) if not result: raise HTTPException( status_code=400, detail="批量换算失败,请检查基准货币是否有效。" ) return result # ==================== 获取货币列表 ==================== @router.get("/currencies", response_model=CurrenciesResponse) async def get_currencies(request: Request): """ 获取支持的货币列表(含中英文名称) 返回所有可用货币的代码、名称和符号,按优先级排序。 """ service = get_exchange_service(request) settings = service.settings cached = service.get_cached_rates() if not cached: raise HTTPException( status_code=503, detail="汇率数据不可用" ) currencies = [] for code in cached.rates.keys(): info = get_currency_info(code) currencies.append(CurrencyInfo( code=code, name=info.get("name", code), name_cn=info.get("name_cn", code), symbol=info.get("symbol", "") )) # 按优先级排序 priority = settings.PRIORITY_CURRENCIES currencies.sort(key=lambda x: ( priority.index(x.code) if x.code in priority else 999, x.code )) return CurrenciesResponse( success=True, currencies=currencies ) # ==================== 服务状态 ==================== @router.get("/status", response_model=ServiceStatusResponse) async def get_status(request: Request): """ 获取服务状态 返回缓存状态、上次更新时间、货币数量等信息。 """ service = get_exchange_service(request) cached = service.get_cached_rates() return ServiceStatusResponse( status="healthy" if cached else "degraded", cache_valid=service.is_cache_valid(), last_update=cached.cached_at.isoformat() if cached else None, currencies_count=len(cached.rates) if cached else 0, api_keys_count=len(service.api_keys), update_interval_seconds=service.settings.CACHE_UPDATE_INTERVAL ) # ==================== 强制刷新缓存 ==================== @router.post("/refresh") async def refresh_cache(request: Request): """ 强制刷新汇率缓存 立即从 API 获取最新汇率数据,不等待定时任务。 注意:频繁调用可能导致 API 限流。 """ service = get_exchange_service(request) logger.info("收到手动刷新缓存请求") success = await service.update_cache() if success: cached = service.get_cached_rates() return { "success": True, "message": "缓存刷新成功", "currencies_count": len(cached.rates) if cached else 0, "cached_at": cached.cached_at.isoformat() if cached else None } else: raise HTTPException( status_code=503, detail="刷新缓存失败,请稍后重试。" )