151 lines
4.6 KiB
Python
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,
|
|
}
|