mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
refactor: 重构异步任务系统和计费服务架构
- 重构任务系统:新增 lifecycle (TaskStatus/BillingStatus)、context、application 模块 - 将 video tasks 泛化为 async tasks,支持更通用的异步任务管理 - 新增 Gemini Files 管理模块和管理界面 - 重构 billing 服务:拆分 schema.py 和 service.py - 新增 candidate 服务模块用于请求候选管理 - 数据库迁移:添加 billing_status、request_id、gemini_file_mappings 表和索引 - 移除废弃的 video_telemetry、task orchestrator 等模块
This commit is contained in:
@@ -272,6 +272,14 @@ class Usage(Base):
|
||||
"""使用记录模型"""
|
||||
|
||||
__tablename__ = "usage"
|
||||
__table_args__ = (
|
||||
# Composite indexes for common query patterns (analytics / list pages)
|
||||
Index("idx_usage_user_created", "user_id", "created_at"),
|
||||
Index("idx_usage_apikey_created", "api_key_id", "created_at"),
|
||||
Index("idx_usage_provider_model_created", "provider_name", "model", "created_at"),
|
||||
Index("idx_usage_provider_created", "provider_name", "created_at"),
|
||||
Index("idx_usage_model_created", "model", "created_at"),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), index=True)
|
||||
user_id = Column(String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
||||
@@ -347,6 +355,13 @@ class Usage(Base):
|
||||
# cancelled: 客户端主动断开连接
|
||||
status = Column(String(20), default="completed", nullable=False, index=True)
|
||||
|
||||
# 结算状态(与 status 解耦)
|
||||
# - pending: 等待结算(任务未完成 / 流式未结束)
|
||||
# - settled: 已结算(cost 已写入,可能 > 0 或 = 0)
|
||||
# - void: 作废(不收费,如任务未开始就取消)
|
||||
billing_status = Column(String(20), default="settled", nullable=False, index=True)
|
||||
finalized_at = Column(DateTime(timezone=True), nullable=True) # 结算完成时间(可选)
|
||||
|
||||
# 完整请求和响应记录
|
||||
request_headers = Column(JSON, nullable=True) # 客户端请求头
|
||||
request_body = Column(JSON, nullable=True) # 请求体(7天内未压缩)
|
||||
@@ -654,6 +669,14 @@ class Provider(Base):
|
||||
# 注意:如果全局配置 KEEP_PRIORITY_ON_CONVERSION=true,此字段被忽略(所有提供商都保持优先级)
|
||||
keep_priority_on_conversion = Column(Boolean, default=False, nullable=False)
|
||||
|
||||
# 是否允许格式转换(默认 True)
|
||||
# - True: 该提供商可以作为格式转换的目标(如 OpenAI 客户端请求可以路由到此 Gemini 提供商)
|
||||
# - False: 该提供商不接受需要格式转换的请求
|
||||
# 优先级逻辑:
|
||||
# - 全局开关 ON → 强制允许所有提供商的格式转换(忽略此字段)
|
||||
# - 全局开关 OFF → 由此字段决定是否允许该提供商的格式转换
|
||||
enable_format_conversion = Column(Boolean, default=False, nullable=False)
|
||||
|
||||
# 状态
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
|
||||
@@ -1350,12 +1373,26 @@ class ProviderAPIKey(Base):
|
||||
provider = relationship("Provider", back_populates="api_keys")
|
||||
|
||||
|
||||
def _generate_short_id(length: int = 12) -> str:
|
||||
"""生成 Gemini 风格的短 ID(小写字母+数字)"""
|
||||
import secrets
|
||||
import string
|
||||
|
||||
alphabet = string.ascii_lowercase + string.digits
|
||||
return "".join(secrets.choice(alphabet) for _ in range(length))
|
||||
|
||||
|
||||
class VideoTask(Base):
|
||||
"""视频生成任务"""
|
||||
|
||||
__tablename__ = "video_tasks"
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
# Gemini 风格的短 ID,用于对外暴露(如 operations/xxx)
|
||||
short_id = Column(String(16), unique=True, index=True, default=_generate_short_id)
|
||||
request_id = Column(
|
||||
String(100), unique=True, index=True, nullable=False
|
||||
) # 关联 Usage/RequestCandidate
|
||||
external_task_id = Column(String(200))
|
||||
|
||||
# 关联
|
||||
@@ -1865,6 +1902,7 @@ class RequestCandidate(Base):
|
||||
Index("idx_request_candidates_request_id", "request_id"),
|
||||
Index("idx_request_candidates_status", "status"),
|
||||
Index("idx_request_candidates_provider_id", "provider_id"),
|
||||
Index("idx_request_candidates_created_at", "created_at"),
|
||||
)
|
||||
|
||||
# 关系
|
||||
@@ -2109,5 +2147,60 @@ class StatsUserDaily(Base):
|
||||
user = relationship("User")
|
||||
|
||||
|
||||
class GeminiFileMapping(Base):
|
||||
"""
|
||||
Gemini Files API 文件与 Provider Key 的映射关系
|
||||
|
||||
用于持久化存储 file_id → key_id 的绑定关系,
|
||||
确保后续 generateContent 请求使用上传时的同一 Key。
|
||||
|
||||
Gemini 文件有 48 小时有效期,此表中的记录也会在过期后被清理。
|
||||
"""
|
||||
|
||||
__tablename__ = "gemini_file_mappings"
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), index=True)
|
||||
|
||||
# 文件名(如 files/abc123xyz)
|
||||
file_name = Column(String(255), nullable=False, unique=True, index=True)
|
||||
|
||||
# Provider Key ID(关联到 provider_api_keys 表)
|
||||
key_id = Column(
|
||||
String(36),
|
||||
ForeignKey("provider_api_keys.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# 用户 ID(用于权限验证,可选)
|
||||
user_id = Column(
|
||||
String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=True, index=True
|
||||
)
|
||||
|
||||
# 文件元数据(可选,用于调试)
|
||||
display_name = Column(String(255), nullable=True)
|
||||
mime_type = Column(String(100), nullable=True)
|
||||
|
||||
# 源文件哈希(用于关联相同源文件的不同上传,可选)
|
||||
# 当同一源文件上传到多个 Key 时,可通过此字段找到所有等效文件
|
||||
source_hash = Column(String(64), nullable=True, index=True)
|
||||
|
||||
# 时间戳
|
||||
created_at = Column(
|
||||
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
|
||||
)
|
||||
# 过期时间(Gemini 文件 48 小时后过期)
|
||||
expires_at = Column(DateTime(timezone=True), nullable=False, index=True)
|
||||
|
||||
# 关系
|
||||
key = relationship("ProviderAPIKey")
|
||||
user = relationship("User")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_gemini_file_mappings_expires", "expires_at"),
|
||||
Index("idx_gemini_file_mappings_source_hash", "source_hash"),
|
||||
)
|
||||
|
||||
|
||||
# 导入扩展的数据库模型
|
||||
from .database_extensions import ApiKeyProviderMapping, ProviderUsageTracking
|
||||
|
||||
@@ -635,6 +635,10 @@ class ProviderUpdateRequest(BaseModel):
|
||||
None,
|
||||
description="格式转换时是否保持优先级(True=保持原优先级,False=需要转换时降级)",
|
||||
)
|
||||
enable_format_conversion: bool | None = Field(
|
||||
None,
|
||||
description="是否允许格式转换(提供商级别开关)",
|
||||
)
|
||||
is_active: bool | None = None
|
||||
billing_type: str | None = Field(
|
||||
None, description="计费类型:monthly_quota/pay_as_you_go/free_tier"
|
||||
@@ -667,6 +671,10 @@ class ProviderWithEndpointsSummary(BaseModel):
|
||||
default=False,
|
||||
description="格式转换时是否保持优先级(True=保持原优先级,False=需要转换时降级)",
|
||||
)
|
||||
enable_format_conversion: bool = Field(
|
||||
default=True,
|
||||
description="是否允许格式转换(提供商级别开关)",
|
||||
)
|
||||
is_active: bool
|
||||
|
||||
# 计费相关字段
|
||||
|
||||
Reference in New Issue
Block a user