1st_fix_login_issue

This commit is contained in:
beabigegg
2025-09-02 10:31:35 +08:00
commit a60d965317
103 changed files with 12402 additions and 0 deletions

222
app/models/stats.py Normal file
View File

@@ -0,0 +1,222 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
API使用統計資料模型
Author: PANJIT IT Team
Created: 2024-01-28
Modified: 2024-01-28
"""
from datetime import datetime, timedelta
from sqlalchemy.sql import func
from app import db
class APIUsageStats(db.Model):
"""API使用統計表 (dt_api_usage_stats)"""
__tablename__ = 'dt_api_usage_stats'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('dt_users.id'), nullable=False, comment='使用者ID')
job_id = db.Column(db.Integer, db.ForeignKey('dt_translation_jobs.id'), comment='任務ID')
api_endpoint = db.Column(db.String(200), nullable=False, comment='API端點')
prompt_tokens = db.Column(db.Integer, default=0, comment='Prompt token數')
completion_tokens = db.Column(db.Integer, default=0, comment='Completion token數')
total_tokens = db.Column(db.Integer, default=0, comment='總token數')
prompt_unit_price = db.Column(db.Numeric(10, 8), default=0.00000000, comment='單價')
prompt_price_unit = db.Column(db.String(20), default='USD', comment='價格單位')
cost = db.Column(db.Numeric(10, 4), default=0.0000, comment='成本')
response_time_ms = db.Column(db.Integer, default=0, comment='回應時間(毫秒)')
success = db.Column(db.Boolean, default=True, comment='是否成功')
error_message = db.Column(db.Text, comment='錯誤訊息')
created_at = db.Column(db.DateTime, default=func.now(), comment='建立時間')
def __repr__(self):
return f'<APIUsageStats {self.api_endpoint}>'
def to_dict(self):
"""轉換為字典格式"""
return {
'id': self.id,
'user_id': self.user_id,
'job_id': self.job_id,
'api_endpoint': self.api_endpoint,
'prompt_tokens': self.prompt_tokens,
'completion_tokens': self.completion_tokens,
'total_tokens': self.total_tokens,
'prompt_unit_price': float(self.prompt_unit_price) if self.prompt_unit_price else 0.0,
'prompt_price_unit': self.prompt_price_unit,
'cost': float(self.cost) if self.cost else 0.0,
'response_time_ms': self.response_time_ms,
'success': self.success,
'error_message': self.error_message,
'created_at': self.created_at.isoformat() if self.created_at else None
}
@classmethod
def record_api_call(cls, user_id, job_id, api_endpoint, metadata, response_time_ms, success=True, error_message=None):
"""記錄 API 呼叫統計"""
# 從 Dify API metadata 解析使用量資訊
prompt_tokens = metadata.get('usage', {}).get('prompt_tokens', 0)
completion_tokens = metadata.get('usage', {}).get('completion_tokens', 0)
total_tokens = metadata.get('usage', {}).get('total_tokens', prompt_tokens + completion_tokens)
# 計算成本
prompt_unit_price = metadata.get('usage', {}).get('prompt_unit_price', 0.0)
prompt_price_unit = metadata.get('usage', {}).get('prompt_price_unit', 'USD')
# 成本計算:通常是 prompt_tokens * prompt_unit_price
cost = prompt_tokens * float(prompt_unit_price) if prompt_unit_price else 0.0
stats = cls(
user_id=user_id,
job_id=job_id,
api_endpoint=api_endpoint,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=total_tokens,
prompt_unit_price=prompt_unit_price,
prompt_price_unit=prompt_price_unit,
cost=cost,
response_time_ms=response_time_ms,
success=success,
error_message=error_message
)
db.session.add(stats)
db.session.commit()
return stats
@classmethod
def get_user_statistics(cls, user_id, start_date=None, end_date=None):
"""取得使用者統計資料"""
query = cls.query.filter_by(user_id=user_id)
if start_date:
query = query.filter(cls.created_at >= start_date)
if end_date:
query = query.filter(cls.created_at <= end_date)
# 統計資料
total_calls = query.count()
successful_calls = query.filter_by(success=True).count()
total_tokens = query.with_entities(func.sum(cls.total_tokens)).scalar() or 0
total_cost = query.with_entities(func.sum(cls.cost)).scalar() or 0.0
avg_response_time = query.with_entities(func.avg(cls.response_time_ms)).scalar() or 0
return {
'total_calls': total_calls,
'successful_calls': successful_calls,
'failed_calls': total_calls - successful_calls,
'success_rate': (successful_calls / total_calls * 100) if total_calls > 0 else 0,
'total_tokens': total_tokens,
'total_cost': float(total_cost),
'avg_response_time': float(avg_response_time) if avg_response_time else 0
}
@classmethod
def get_daily_statistics(cls, days=30):
"""取得每日統計資料"""
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=days)
# 按日期分組統計
daily_stats = db.session.query(
func.date(cls.created_at).label('date'),
func.count(cls.id).label('total_calls'),
func.sum(cls.total_tokens).label('total_tokens'),
func.sum(cls.cost).label('total_cost'),
func.count().filter(cls.success == True).label('successful_calls')
).filter(
cls.created_at >= start_date,
cls.created_at <= end_date
).group_by(func.date(cls.created_at)).all()
return [
{
'date': stat.date.isoformat(),
'total_calls': stat.total_calls,
'successful_calls': stat.successful_calls,
'failed_calls': stat.total_calls - stat.successful_calls,
'total_tokens': stat.total_tokens or 0,
'total_cost': float(stat.total_cost or 0)
}
for stat in daily_stats
]
@classmethod
def get_top_users(cls, limit=10, start_date=None, end_date=None):
"""取得使用量排行榜"""
query = db.session.query(
cls.user_id,
func.count(cls.id).label('total_calls'),
func.sum(cls.total_tokens).label('total_tokens'),
func.sum(cls.cost).label('total_cost')
)
if start_date:
query = query.filter(cls.created_at >= start_date)
if end_date:
query = query.filter(cls.created_at <= end_date)
top_users = query.group_by(cls.user_id).order_by(
func.sum(cls.cost).desc()
).limit(limit).all()
return [
{
'user_id': user.user_id,
'total_calls': user.total_calls,
'total_tokens': user.total_tokens or 0,
'total_cost': float(user.total_cost or 0)
}
for user in top_users
]
@classmethod
def get_cost_trend(cls, days=30):
"""取得成本趨勢"""
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=days)
# 按日期統計成本
cost_trend = db.session.query(
func.date(cls.created_at).label('date'),
func.sum(cls.cost).label('daily_cost')
).filter(
cls.created_at >= start_date,
cls.created_at <= end_date
).group_by(func.date(cls.created_at)).order_by(
func.date(cls.created_at)
).all()
return [
{
'date': trend.date.isoformat(),
'cost': float(trend.daily_cost or 0)
}
for trend in cost_trend
]
@classmethod
def get_endpoint_statistics(cls):
"""取得 API 端點統計"""
endpoint_stats = db.session.query(
cls.api_endpoint,
func.count(cls.id).label('total_calls'),
func.sum(cls.cost).label('total_cost'),
func.avg(cls.response_time_ms).label('avg_response_time')
).group_by(cls.api_endpoint).order_by(
func.count(cls.id).desc()
).all()
return [
{
'endpoint': stat.api_endpoint,
'total_calls': stat.total_calls,
'total_cost': float(stat.total_cost or 0),
'avg_response_time': float(stat.avg_response_time or 0)
}
for stat in endpoint_stats
]