mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
refactor: 全局适配 ApiFamily/EndpointKind 结构化标识体系
将新的 (ApiFamily, EndpointKind) / `family:kind` 签名体系应用到整个代码库: - API Handlers: 所有 adapter/handler 使用新的签名格式 - Services: provider, model, usage, cache, auth 等服务层适配 - Database: ProviderEndpoint 新增 api_family/endpoint_kind 字段 - Frontend: Provider 管理、Usage 表格等组件适配 - Tests: 更新所有相关测试用例
This commit is contained in:
@@ -6,12 +6,12 @@
|
||||
|
||||
from src.services.system.announcement import AnnouncementService
|
||||
from src.services.system.audit import AuditService
|
||||
from src.services.system.config import SystemConfigService
|
||||
from src.services.system.maintenance_scheduler import CleanupScheduler # 兼容旧名称
|
||||
from src.services.system.maintenance_scheduler import (
|
||||
CleanupScheduler, # 兼容旧名称
|
||||
MaintenanceScheduler,
|
||||
get_maintenance_scheduler,
|
||||
)
|
||||
from src.services.system.config import SystemConfigService
|
||||
from src.services.system.scheduler import APP_TIMEZONE, TaskScheduler, get_scheduler
|
||||
from src.services.system.sync_stats import SyncStatsService
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from src.core.logger import logger
|
||||
from src.models.database import Announcement, AnnouncementRead, User, UserRole
|
||||
|
||||
|
||||
|
||||
class AnnouncementService:
|
||||
"""公告系统服务"""
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ from src.core.logger import logger
|
||||
from src.database import get_db
|
||||
from src.models.database import AuditEventType, AuditLog
|
||||
|
||||
|
||||
|
||||
# 审计模型已移至 src/models/database.py
|
||||
|
||||
|
||||
|
||||
@@ -61,7 +61,9 @@ class CacheWarmupService:
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
if error_count > 0:
|
||||
logger.warning(f"缓存预热完成: {success_count}/3 成功, {error_count} 失败, 耗时 {elapsed:.2f}s")
|
||||
logger.warning(
|
||||
f"缓存预热完成: {success_count}/3 成功, {error_count} 失败, 耗时 {elapsed:.2f}s"
|
||||
)
|
||||
else:
|
||||
logger.info(f"缓存预热完成: {success_count}/3 成功, 耗时 {elapsed:.2f}s")
|
||||
|
||||
|
||||
@@ -227,7 +227,9 @@ class SystemConfigService:
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def set_config(db: Session, key: str, value: Any, description: str | None = None) -> SystemConfig:
|
||||
def set_config(
|
||||
db: Session, key: str, value: Any, description: str | None = None
|
||||
) -> SystemConfig:
|
||||
"""设置系统配置值"""
|
||||
config = db.query(SystemConfig).filter(SystemConfig.key == key).first()
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import delete
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -196,10 +196,12 @@ class MaintenanceScheduler:
|
||||
|
||||
logger.info("开始执行统计数据聚合...")
|
||||
|
||||
from src.models.database import StatsDaily, User as DBUser
|
||||
from src.services.system.scheduler import APP_TIMEZONE
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from src.models.database import StatsDaily
|
||||
from src.models.database import User as DBUser
|
||||
from src.services.system.scheduler import APP_TIMEZONE
|
||||
|
||||
# 使用业务时区计算日期,确保与定时任务触发时间一致
|
||||
# 定时任务在 Asia/Shanghai 凌晨 1 点触发,此时应聚合 Asia/Shanghai 的"昨天"
|
||||
app_tz = ZoneInfo(APP_TIMEZONE)
|
||||
@@ -227,9 +229,9 @@ class MaintenanceScheduler:
|
||||
from src.models.database import StatsDailyModel, StatsDailyProvider
|
||||
|
||||
yesterday_business_date = today_local.date() - timedelta(days=1)
|
||||
max_backfill_days: int = SystemConfigService.get_config(
|
||||
db, "max_stats_backfill_days", 30
|
||||
) or 30
|
||||
max_backfill_days: int = (
|
||||
SystemConfigService.get_config(db, "max_stats_backfill_days", 30) or 30
|
||||
)
|
||||
|
||||
# 计算回填检查的起始日期
|
||||
check_start_date = yesterday_business_date - timedelta(
|
||||
@@ -287,7 +289,9 @@ class MaintenanceScheduler:
|
||||
# 需要回填 StatsDailyProvider 的日期
|
||||
missing_provider_dates = all_dates - existing_provider_dates
|
||||
# 合并所有需要处理的日期
|
||||
dates_to_process = missing_daily_dates | missing_model_dates | missing_provider_dates
|
||||
dates_to_process = (
|
||||
missing_daily_dates | missing_model_dates | missing_provider_dates
|
||||
)
|
||||
|
||||
if dates_to_process:
|
||||
sorted_dates = sorted(dates_to_process)
|
||||
@@ -298,9 +302,7 @@ class MaintenanceScheduler:
|
||||
f"StatsDailyProvider 缺失 {len(missing_provider_dates)} 天)"
|
||||
)
|
||||
|
||||
users = (
|
||||
db.query(DBUser.id).filter(DBUser.is_active.is_(True)).all()
|
||||
)
|
||||
users = db.query(DBUser.id).filter(DBUser.is_active.is_(True)).all()
|
||||
|
||||
failed_dates = 0
|
||||
failed_users = 0
|
||||
@@ -496,15 +498,9 @@ class MaintenanceScheduler:
|
||||
|
||||
# 获取所有已配置 provider_ops 的活跃 Provider(只查询需要的字段)
|
||||
providers = (
|
||||
db.query(Provider.id, Provider.config)
|
||||
.filter(Provider.is_active.is_(True))
|
||||
.all()
|
||||
db.query(Provider.id, Provider.config).filter(Provider.is_active.is_(True)).all()
|
||||
)
|
||||
provider_ids = [
|
||||
p.id
|
||||
for p in providers
|
||||
if p.config and p.config.get("provider_ops")
|
||||
]
|
||||
provider_ids = [p.id for p in providers if p.config and p.config.get("provider_ops")]
|
||||
|
||||
if not provider_ids:
|
||||
logger.info("无已配置的 Provider,跳过签到任务")
|
||||
@@ -548,9 +544,7 @@ class MaintenanceScheduler:
|
||||
|
||||
# 统计结果
|
||||
success_count = sum(1 for _, success, _ in results if success)
|
||||
logger.info(
|
||||
f"Provider 签到完成: {success_count}/{len(provider_ids)} 成功"
|
||||
)
|
||||
logger.info(f"Provider 签到完成: {success_count}/{len(provider_ids)} 成功")
|
||||
|
||||
# 记录详细结果
|
||||
for provider_id, success, message in results:
|
||||
@@ -694,12 +688,12 @@ class MaintenanceScheduler:
|
||||
.values(
|
||||
request_body=null(),
|
||||
response_body=null(),
|
||||
request_body_compressed=compress_json(req_body)
|
||||
if req_body
|
||||
else None,
|
||||
response_body_compressed=compress_json(resp_body)
|
||||
if resp_body
|
||||
else None,
|
||||
request_body_compressed=(
|
||||
compress_json(req_body) if req_body else None
|
||||
),
|
||||
response_body_compressed=(
|
||||
compress_json(resp_body) if resp_body else None
|
||||
),
|
||||
)
|
||||
)
|
||||
if result.rowcount > 0:
|
||||
@@ -858,9 +852,7 @@ class MaintenanceScheduler:
|
||||
break
|
||||
|
||||
total_cleaned += rows_updated
|
||||
logger.debug(
|
||||
f"已清理 {rows_updated} 条记录的 header 字段,累计 {total_cleaned} 条"
|
||||
)
|
||||
logger.debug(f"已清理 {rows_updated} 条记录的 header 字段,累计 {total_cleaned} 条")
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
@@ -43,9 +43,7 @@ def _get_business_day_range(date: datetime) -> tuple[datetime, datetime]:
|
||||
app_tz = ZoneInfo(APP_TIMEZONE)
|
||||
|
||||
# 取日期部分,构造业务时区的当天 00:00:00
|
||||
day_start_local = datetime(
|
||||
date.year, date.month, date.day, 0, 0, 0, tzinfo=app_tz
|
||||
)
|
||||
day_start_local = datetime(date.year, date.month, date.day, 0, 0, 0, tzinfo=app_tz)
|
||||
day_end_local = day_start_local + timedelta(days=1)
|
||||
|
||||
# 转换为 UTC
|
||||
@@ -91,11 +89,9 @@ class StatsAggregatorService:
|
||||
"unique_providers": 0,
|
||||
}
|
||||
|
||||
error_requests = (
|
||||
base_query.filter(
|
||||
(Usage.status_code >= 400) | (Usage.error_message.isnot(None))
|
||||
).count()
|
||||
)
|
||||
error_requests = base_query.filter(
|
||||
(Usage.status_code >= 400) | (Usage.error_message.isnot(None))
|
||||
).count()
|
||||
|
||||
aggregated = (
|
||||
db.query(
|
||||
@@ -159,13 +155,17 @@ class StatsAggregatorService:
|
||||
"error_requests": error_requests,
|
||||
"input_tokens": int(aggregated.input_tokens or 0) if aggregated else 0,
|
||||
"output_tokens": int(aggregated.output_tokens or 0) if aggregated else 0,
|
||||
"cache_creation_tokens": int(aggregated.cache_creation_tokens or 0) if aggregated else 0,
|
||||
"cache_creation_tokens": (
|
||||
int(aggregated.cache_creation_tokens or 0) if aggregated else 0
|
||||
),
|
||||
"cache_read_tokens": int(aggregated.cache_read_tokens or 0) if aggregated else 0,
|
||||
"total_cost": float(aggregated.total_cost or 0) if aggregated else 0.0,
|
||||
"actual_total_cost": float(aggregated.actual_total_cost or 0) if aggregated else 0.0,
|
||||
"input_cost": float(aggregated.input_cost or 0) if aggregated else 0.0,
|
||||
"output_cost": float(aggregated.output_cost or 0) if aggregated else 0.0,
|
||||
"cache_creation_cost": float(aggregated.cache_creation_cost or 0) if aggregated else 0.0,
|
||||
"cache_creation_cost": (
|
||||
float(aggregated.cache_creation_cost or 0) if aggregated else 0.0
|
||||
),
|
||||
"cache_read_cost": float(aggregated.cache_read_cost or 0) if aggregated else 0.0,
|
||||
"avg_response_time_ms": float(aggregated.avg_response_time or 0) if aggregated else 0.0,
|
||||
"fallback_count": fallback_count,
|
||||
@@ -219,7 +219,9 @@ class StatsAggregatorService:
|
||||
db.commit()
|
||||
|
||||
# 日志使用业务日期(输入参数),而不是 UTC 日期
|
||||
logger.info(f"[StatsAggregator] 聚合日期 {date.date()} 完成: {computed['total_requests']} 请求")
|
||||
logger.info(
|
||||
f"[StatsAggregator] 聚合日期 {date.date()} 完成: {computed['total_requests']} 请求"
|
||||
)
|
||||
return stats
|
||||
|
||||
@staticmethod
|
||||
@@ -259,16 +261,16 @@ class StatsAggregatorService:
|
||||
|
||||
existing = (
|
||||
db.query(StatsDailyModel)
|
||||
.filter(and_(StatsDailyModel.date == day_start, StatsDailyModel.model == stat.model))
|
||||
.filter(
|
||||
and_(StatsDailyModel.date == day_start, StatsDailyModel.model == stat.model)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if existing:
|
||||
record = existing
|
||||
else:
|
||||
record = StatsDailyModel(
|
||||
id=str(uuid.uuid4()), date=day_start, model=stat.model
|
||||
)
|
||||
record = StatsDailyModel(id=str(uuid.uuid4()), date=day_start, model=stat.model)
|
||||
|
||||
record.total_requests = stat.total_requests or 0
|
||||
record.input_tokens = int(stat.input_tokens or 0)
|
||||
@@ -283,9 +285,7 @@ class StatsAggregatorService:
|
||||
results.append(record)
|
||||
|
||||
db.commit()
|
||||
logger.info(
|
||||
f"[StatsAggregator] 聚合日期 {date.date()} 模型统计完成: {len(results)} 个模型"
|
||||
)
|
||||
logger.info(f"[StatsAggregator] 聚合日期 {date.date()} 模型统计完成: {len(results)} 个模型")
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
@@ -322,7 +322,12 @@ class StatsAggregatorService:
|
||||
for stat in provider_stats:
|
||||
existing = (
|
||||
db.query(StatsDailyProvider)
|
||||
.filter(and_(StatsDailyProvider.date == day_start, StatsDailyProvider.provider_name == stat.provider_name))
|
||||
.filter(
|
||||
and_(
|
||||
StatsDailyProvider.date == day_start,
|
||||
StatsDailyProvider.provider_name == stat.provider_name,
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -384,24 +389,28 @@ class StatsAggregatorService:
|
||||
date_utc = stat.date.astimezone(timezone.utc)
|
||||
date_str = date_utc.astimezone(app_tz).date().isoformat()
|
||||
|
||||
result.append({
|
||||
"date": date_str,
|
||||
"model": stat.model,
|
||||
"requests": stat.total_requests,
|
||||
"tokens": (
|
||||
stat.input_tokens + stat.output_tokens +
|
||||
stat.cache_creation_tokens + stat.cache_read_tokens
|
||||
),
|
||||
"cost": stat.total_cost,
|
||||
"avg_response_time": stat.avg_response_time_ms / 1000.0 if stat.avg_response_time_ms else 0,
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"date": date_str,
|
||||
"model": stat.model,
|
||||
"requests": stat.total_requests,
|
||||
"tokens": (
|
||||
stat.input_tokens
|
||||
+ stat.output_tokens
|
||||
+ stat.cache_creation_tokens
|
||||
+ stat.cache_read_tokens
|
||||
),
|
||||
"cost": stat.total_cost,
|
||||
"avg_response_time": (
|
||||
stat.avg_response_time_ms / 1000.0 if stat.avg_response_time_ms else 0
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def aggregate_user_daily_stats(
|
||||
db: Session, user_id: str, date: datetime
|
||||
) -> StatsUserDaily:
|
||||
def aggregate_user_daily_stats(db: Session, user_id: str, date: datetime) -> StatsUserDaily:
|
||||
"""聚合指定用户指定日期的统计数据"""
|
||||
# 将业务日期转换为 UTC 时间范围
|
||||
day_start, day_end = _get_business_day_range(date)
|
||||
@@ -443,11 +452,9 @@ class StatsAggregatorService:
|
||||
db.commit()
|
||||
return stats
|
||||
|
||||
error_requests = (
|
||||
base_query.filter(
|
||||
(Usage.status_code >= 400) | (Usage.error_message.isnot(None))
|
||||
).count()
|
||||
)
|
||||
error_requests = base_query.filter(
|
||||
(Usage.status_code >= 400) | (Usage.error_message.isnot(None))
|
||||
).count()
|
||||
|
||||
aggregated = (
|
||||
db.query(
|
||||
@@ -581,11 +588,9 @@ class StatsAggregatorService:
|
||||
"actual_total_cost": 0.0,
|
||||
}
|
||||
|
||||
error_requests = (
|
||||
base_query.filter(
|
||||
(Usage.status_code >= 400) | (Usage.error_message.isnot(None))
|
||||
).count()
|
||||
)
|
||||
error_requests = base_query.filter(
|
||||
(Usage.status_code >= 400) | (Usage.error_message.isnot(None))
|
||||
).count()
|
||||
|
||||
aggregated = (
|
||||
db.query(
|
||||
@@ -624,8 +629,7 @@ class StatsAggregatorService:
|
||||
|
||||
return {
|
||||
"total_requests": summary.all_time_requests + today_stats["total_requests"],
|
||||
"success_requests": summary.all_time_success_requests
|
||||
+ today_stats["success_requests"],
|
||||
"success_requests": summary.all_time_success_requests + today_stats["success_requests"],
|
||||
"error_requests": summary.all_time_error_requests + today_stats["error_requests"],
|
||||
"input_tokens": summary.all_time_input_tokens + today_stats["input_tokens"],
|
||||
"output_tokens": summary.all_time_output_tokens + today_stats["output_tokens"],
|
||||
|
||||
@@ -3,7 +3,6 @@ API密钥统计同步服务
|
||||
定期同步API密钥的统计数据,确保与实际使用记录一致
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import func
|
||||
@@ -13,7 +12,6 @@ from src.core.logger import logger
|
||||
from src.models.database import ApiKey, Usage
|
||||
|
||||
|
||||
|
||||
class SyncStatsService:
|
||||
"""API密钥统计同步服务"""
|
||||
|
||||
@@ -75,12 +73,16 @@ class SyncStatsService:
|
||||
# 检查是否需要更新
|
||||
needs_update = False
|
||||
if api_key.total_requests != actual_requests:
|
||||
logger.info(f"API密钥 {api_key.id} 请求数不一致: {api_key.total_requests} -> {actual_requests}")
|
||||
logger.info(
|
||||
f"API密钥 {api_key.id} 请求数不一致: {api_key.total_requests} -> {actual_requests}"
|
||||
)
|
||||
api_key.total_requests = actual_requests
|
||||
needs_update = True
|
||||
|
||||
if abs(api_key.total_cost_usd - actual_cost) > 0.0001:
|
||||
logger.info(f"API密钥 {api_key.id} 费用不一致: {api_key.total_cost_usd} -> {actual_cost}")
|
||||
logger.info(
|
||||
f"API密钥 {api_key.id} 费用不一致: {api_key.total_cost_usd} -> {actual_cost}"
|
||||
)
|
||||
api_key.total_cost_usd = actual_cost
|
||||
needs_update = True
|
||||
|
||||
@@ -104,7 +106,9 @@ class SyncStatsService:
|
||||
|
||||
# 提交所有更改
|
||||
db.commit()
|
||||
logger.info(f"同步完成: 处理 {result['synced']} 个密钥, 更新 {result['updated']} 个, 错误 {result['errors']} 个")
|
||||
logger.info(
|
||||
f"同步完成: 处理 {result['synced']} 个密钥, 更新 {result['updated']} 个, 错误 {result['errors']} 个"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"同步统计数据时出错: {e}")
|
||||
|
||||
Reference in New Issue
Block a user