Files
video-gen/video-gen-api/app/services/api_v3/usage_log_service.py
T
root 0c511f3451 1、增加调用 AI 视频生成能力和虚拟素材库管理的对外api
2、增加后台apikkey管理
3、增加apikey单独的模型定价
4、增加apikey调用情况
5、完善所有数据的注释增加
2026-08-06 13:13:28 +08:00

151 lines
4.6 KiB
Python

import logging
from datetime import datetime, timedelta, timezone
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.api.api_usage_log import ApiUsageLog
from app.utils.id_gen import generate_id
logger = logging.getLogger("videogen")
async def record_usage(
db: AsyncSession,
api_key_id: str,
request_type: str,
model_name: str,
gen_type: str,
status: str,
task_id: str | None = None,
credits_cost: float = 0.0,
tokens_used: int = 0,
request_duration_ms: int = 0,
error_message: str | None = None,
error_code: str | None = None,
request_payload_json: str | None = None,
price_action: str | None = None,
resolution: str | None = None,
duration: int | None = None,
refund_amount: float | None = None,
quota_before: float | None = None,
quota_after: float | None = None,
price_detail_json: str | None = None,
) -> ApiUsageLog:
"""记录一次 API 调用日志。"""
# 确定 price_action
if price_action:
action = price_action
elif status == "failed":
action = "refund"
else:
action = "deduct"
log = ApiUsageLog(
id=generate_id(),
api_key_id=api_key_id,
api_generation_task_id=task_id,
price_action=action,
request_type=request_type,
model_name=model_name,
gen_type=gen_type,
resolution=resolution,
duration=duration,
credits_cost=credits_cost,
refund_amount=refund_amount or 0.0,
quota_before=quota_before,
quota_after=quota_after,
tokens_used=tokens_used,
request_duration_ms=request_duration_ms,
price_detail_json=price_detail_json,
status=status,
error_message=error_message,
error_code=error_code,
request_payload_json=request_payload_json,
)
db.add(log)
await db.flush()
return log
async def list_usage_logs(
db: AsyncSession,
api_key_id: str | None = None,
skip: int = 0,
limit: int = 50,
start_date: datetime | None = None,
end_date: datetime | None = None,
) -> tuple[int, list[ApiUsageLog]]:
"""查询使用日志(分页+筛选)。"""
query = select(ApiUsageLog)
count_query = select(func.count(ApiUsageLog.id))
filters = []
if api_key_id:
filters.append(ApiUsageLog.api_key_id == api_key_id)
if start_date:
filters.append(ApiUsageLog.created_at >= start_date)
if end_date:
filters.append(ApiUsageLog.created_at <= end_date)
for f in filters:
query = query.where(f)
count_query = count_query.where(f)
total_result = await db.execute(count_query)
total = total_result.scalar_one()
query = query.order_by(ApiUsageLog.created_at.desc()).offset(skip).limit(limit)
result = await db.execute(query)
logs = list(result.scalars().all())
return total, logs
async def get_usage_summary(
db: AsyncSession,
api_key_id: str | None = None,
days: int = 30,
) -> dict:
"""获取使用汇总统计。"""
now = datetime.now(timezone.utc)
start = now - timedelta(days=days)
query = select(
func.count(ApiUsageLog.id).label("total_requests"),
func.coalesce(func.sum(ApiUsageLog.credits_cost), 0).label("total_credits"),
func.coalesce(func.sum(ApiUsageLog.tokens_used), 0).label("total_tokens"),
func.coalesce(func.avg(ApiUsageLog.request_duration_ms), 0).label("avg_duration"),
).where(ApiUsageLog.created_at >= start)
if api_key_id:
query = query.where(ApiUsageLog.api_key_id == api_key_id)
result = await db.execute(query)
row = result.one()
# 成功/失败统计
success_query = select(func.count(ApiUsageLog.id)).where(
ApiUsageLog.created_at >= start,
ApiUsageLog.status == "success",
)
failed_query = select(func.count(ApiUsageLog.id)).where(
ApiUsageLog.created_at >= start,
ApiUsageLog.status == "failed",
)
if api_key_id:
success_query = success_query.where(ApiUsageLog.api_key_id == api_key_id)
failed_query = failed_query.where(ApiUsageLog.api_key_id == api_key_id)
success_result = await db.execute(success_query)
failed_result = await db.execute(failed_query)
return {
"total_requests": row.total_requests or 0,
"total_credits_cost": float(row.total_credits or 0),
"total_tokens_used": int(row.total_tokens or 0),
"avg_duration_ms": int(row.avg_duration or 0),
"success_count": success_result.scalar_one() or 0,
"failed_count": failed_result.scalar_one() or 0,
}