Files
qihuaiflow/backend/python/app/api/token_usage.py
T

248 lines
7.1 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import and_, desc, asc, func, extract
from pydantic import BaseModel, Field
from typing import Optional
import uuid
from datetime import datetime, date, timedelta
from ..database import get_db
from ..models import (
User, TokenUsageLog, TokenBalance, AIModel,
gen_uuid
)
from ..auth import get_current_user
from ..middleware.log_service import LogService
router = APIRouter()
class RechargeRequest(BaseModel):
amount: int = Field(ge=1)
channel: str = Field(default="simulation")
@router.get("/overview")
def get_overview(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# 总消耗
total_used = db.query(func.coalesce(func.sum(TokenUsageLog.total_tokens), 0)).filter(
TokenUsageLog.user_id == current_user.id
).scalar()
# 今日使用中应用数
today_start = datetime.combine(date.today(), datetime.min.time())
today_apps = db.query(func.count(func.distinct(TokenUsageLog.model_id))).filter(
and_(
TokenUsageLog.user_id == current_user.id,
TokenUsageLog.created_at >= today_start,
)
).scalar() or 0
# 余额
balance = db.query(TokenBalance).filter(
TokenBalance.user_id == current_user.id
).first()
from datetime import timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
server_time = datetime.now(shanghai_tz).isoformat()
return {
"code": 0,
"data": {
"total_used": total_used or 0,
"balance": balance.balance if balance else 0,
"today_apps": today_apps,
"server_time": server_time,
},
"message": "ok",
}
@router.get("/rankings")
def get_rankings(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# Token消耗最多的模型
model_rankings = (
db.query(
TokenUsageLog.model_id,
func.sum(TokenUsageLog.total_tokens).label("total"),
func.count(TokenUsageLog.id).label("count"),
)
.filter(TokenUsageLog.user_id == current_user.id)
.group_by(TokenUsageLog.model_id)
.order_by(desc("total"))
.all()
)
models_list = []
for r in model_rankings:
model = db.query(AIModel).filter(AIModel.id == r.model_id).first()
models_list.append({
"model_id": r.model_id,
"model_name": model.name if model else "unknown",
"total_tokens": int(r.total),
"usage_count": int(r.count),
})
# TOP3应用
app_rankings = (
db.query(
TokenUsageLog.conversation_id,
func.sum(TokenUsageLog.total_tokens).label("total"),
)
.filter(TokenUsageLog.user_id == current_user.id)
.group_by(TokenUsageLog.conversation_id)
.order_by(desc("total"))
.limit(3)
.all()
)
apps_list = []
for r in app_rankings:
apps_list.append({
"conversation_id": r.conversation_id,
"total_tokens": int(r.total),
})
return {
"code": 0,
"data": {
"model_rankings": models_list,
"top_apps": apps_list,
},
"message": "ok",
}
@router.get("/logs")
def get_token_logs(
start_date: Optional[str] = Query(None, description="开始日期 YYYY-MM-DD"),
end_date: Optional[str] = Query(None, description="结束日期 YYYY-MM-DD"),
model_id: Optional[str] = Query(None),
app_type: Optional[str] = Query(None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
query = db.query(TokenUsageLog).filter(TokenUsageLog.user_id == current_user.id)
if start_date:
try:
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
query = query.filter(TokenUsageLog.created_at >= start_dt)
except ValueError:
raise HTTPException(status_code=400, detail="开始日期格式错误,请使用YYYY-MM-DD")
if end_date:
try:
end_dt = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
query = query.filter(TokenUsageLog.created_at < end_dt)
except ValueError:
raise HTTPException(status_code=400, detail="结束日期格式错误,请使用YYYY-MM-DD")
if model_id:
query = query.filter(TokenUsageLog.model_id == model_id)
total = query.count()
logs = (
query.order_by(desc(TokenUsageLog.created_at))
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
result = []
for log_item in logs:
model = db.query(AIModel).filter(AIModel.id == log_item.model_id).first()
result.append({
"id": log_item.id,
"model_id": log_item.model_id,
"model_name": model.name if model else "unknown",
"prompt_tokens": log_item.prompt_tokens or 0,
"completion_tokens": log_item.completion_tokens or 0,
"total_tokens": log_item.total_tokens or 0,
"conversation_id": log_item.conversation_id,
"created_at": log_item.created_at.isoformat() if log_item.created_at else None,
})
return {
"code": 0,
"data": {
"total": total,
"page": page,
"page_size": page_size,
"items": result,
},
"message": "ok",
}
@router.get("/balance")
def get_balance(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
balance = db.query(TokenBalance).filter(
TokenBalance.user_id == current_user.id
).first()
return {
"code": 0,
"data": {
"balance": balance.balance if balance else 0,
"updated_at": str(balance.updated_at) if balance and balance.updated_at else None,
},
"message": "ok",
}
@router.post("/recharge")
def recharge(
req: RechargeRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# 获取或创建余额记录
balance = db.query(TokenBalance).filter(
TokenBalance.user_id == current_user.id
).first()
if not balance:
balance = TokenBalance(
id=gen_uuid(),
user_id=current_user.id,
balance=0,
updated_at=datetime.now(),
)
db.add(balance)
db.flush()
old_balance = balance.balance or 0
balance.balance = old_balance + req.amount
balance.updated_at = datetime.now()
# 记录操作日志
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="recharge",
target="token_balance", target_id=balance.id,
detail=f"充值 {req.amount} tokens,余额从 {old_balance} 变为 {balance.balance}",
)
db.commit()
return {
"code": 0,
"data": {
"balance": balance.balance,
"recharged": req.amount,
},
"message": "充值成功",
}