"""配额管理路由""" from typing import Optional, Dict, Any from datetime import date from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from sqlalchemy.orm import Session from sqlalchemy import desc from ..database import get_db from ..models.tenant import Subscription from ..services.quota import QuotaService from .auth import get_current_user, require_operator from ..models.user import User router = APIRouter(prefix="/quota", tags=["配额管理"]) # ============= Schemas ============= class QuotaConfigUpdate(BaseModel): daily_calls: int = 0 daily_tokens: int = 0 monthly_calls: int = 0 monthly_tokens: int = 0 monthly_cost: float = 0 concurrent_calls: int = 0 class SubscriptionCreate(BaseModel): tenant_id: str app_code: str start_date: Optional[str] = None end_date: Optional[str] = None quota: QuotaConfigUpdate class SubscriptionUpdate(BaseModel): start_date: Optional[str] = None end_date: Optional[str] = None quota: Optional[QuotaConfigUpdate] = None status: Optional[str] = None # ============= Quota Check API ============= @router.get("/check") async def check_quota( tenant_id: str = Query(..., alias="tid"), app_code: str = Query(..., alias="aid"), estimated_tokens: int = Query(0), db: Session = Depends(get_db) ): """检查配额是否足够 用于调用前检查,返回是否允许继续调用 """ service = QuotaService(db) result = service.check_quota(tenant_id, app_code, estimated_tokens) return { "allowed": result.allowed, "reason": result.reason, "quota_type": result.quota_type, "limit": result.limit, "used": result.used, "remaining": result.remaining } @router.get("/summary") async def get_quota_summary( tenant_id: str = Query(...), app_code: str = Query(...), user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取配额使用汇总""" service = QuotaService(db) return service.get_quota_summary(tenant_id, app_code) @router.get("/usage") async def get_quota_usage( tenant_id: str = Query(...), app_code: str = Query(...), user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取配额使用情况""" service = QuotaService(db) usage = service.get_usage(tenant_id, app_code) return { "daily_calls": usage.daily_calls, "daily_tokens": usage.daily_tokens, "monthly_calls": usage.monthly_calls, "monthly_tokens": usage.monthly_tokens, "monthly_cost": round(usage.monthly_cost, 2) } # ============= Subscription API ============= @router.get("/subscriptions") async def list_subscriptions( page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100), tenant_id: Optional[str] = None, app_code: Optional[str] = None, status: Optional[str] = None, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取订阅列表""" query = db.query(Subscription) if tenant_id: query = query.filter(Subscription.tenant_id == tenant_id) if app_code: query = query.filter(Subscription.app_code == app_code) if status: query = query.filter(Subscription.status == status) total = query.count() items = query.order_by(desc(Subscription.created_at)).offset((page - 1) * size).limit(size).all() return { "total": total, "page": page, "size": size, "items": [format_subscription(s) for s in items] } @router.get("/subscriptions/{subscription_id}") async def get_subscription( subscription_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取订阅详情""" subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first() if not subscription: raise HTTPException(status_code=404, detail="订阅不存在") return format_subscription(subscription) @router.post("/subscriptions") async def create_subscription( data: SubscriptionCreate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """创建订阅""" # 检查是否已存在 existing = db.query(Subscription).filter( Subscription.tenant_id == data.tenant_id, Subscription.app_code == data.app_code, Subscription.status == 'active' ).first() if existing: raise HTTPException(status_code=400, detail="该租户应用已有活跃订阅") subscription = Subscription( tenant_id=data.tenant_id, app_code=data.app_code, start_date=data.start_date or date.today(), end_date=data.end_date, quota=data.quota.model_dump() if data.quota else {}, status='active' ) db.add(subscription) db.commit() db.refresh(subscription) return {"success": True, "id": subscription.id} @router.put("/subscriptions/{subscription_id}") async def update_subscription( subscription_id: int, data: SubscriptionUpdate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """更新订阅""" subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first() if not subscription: raise HTTPException(status_code=404, detail="订阅不存在") if data.start_date: subscription.start_date = data.start_date if data.end_date: subscription.end_date = data.end_date if data.quota: subscription.quota = data.quota.model_dump() if data.status: subscription.status = data.status db.commit() # 清除缓存 service = QuotaService(db) cache_key = f"quota:config:{subscription.tenant_id}:{subscription.app_code}" service._cache.delete(cache_key) return {"success": True} @router.delete("/subscriptions/{subscription_id}") async def delete_subscription( subscription_id: int, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """删除订阅""" subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first() if not subscription: raise HTTPException(status_code=404, detail="订阅不存在") db.delete(subscription) db.commit() return {"success": True} @router.put("/subscriptions/{subscription_id}/quota") async def update_quota( subscription_id: int, data: QuotaConfigUpdate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """更新订阅配额""" subscription = db.query(Subscription).filter(Subscription.id == subscription_id).first() if not subscription: raise HTTPException(status_code=404, detail="订阅不存在") subscription.quota = data.model_dump() db.commit() # 清除缓存 service = QuotaService(db) cache_key = f"quota:config:{subscription.tenant_id}:{subscription.app_code}" service._cache.delete(cache_key) return {"success": True} # ============= Helper Functions ============= def format_subscription(subscription: Subscription) -> dict: return { "id": subscription.id, "tenant_id": subscription.tenant_id, "app_code": subscription.app_code, "start_date": str(subscription.start_date) if subscription.start_date else None, "end_date": str(subscription.end_date) if subscription.end_date else None, "quota": subscription.quota or {}, "status": subscription.status, "created_at": subscription.created_at, "updated_at": subscription.updated_at }