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:
fawney19
2026-02-01 17:28:00 +08:00
parent c246ccfc91
commit 7b66505634
219 changed files with 4732 additions and 2545 deletions

View File

@@ -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

View File

@@ -14,7 +14,6 @@ from src.core.logger import logger
from src.models.database import Announcement, AnnouncementRead, User, UserRole
class AnnouncementService:
"""公告系统服务"""

View File

@@ -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

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)

View File

@@ -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"],

View File

@@ -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}")