141 lines
3.8 KiB
Python
141 lines
3.8 KiB
Python
"""
|
||
Avatar 形象克隆模型
|
||
==================
|
||
|
||
存储用户克隆形象的信息,作为本地 localStorage 的云端备份。
|
||
"""
|
||
|
||
from datetime import UTC, datetime
|
||
|
||
from sqlalchemy import BigInteger, DateTime, ForeignKey, String, Text
|
||
from sqlalchemy.dialects.postgresql import UUID
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from app.db.session import Base
|
||
from app.schemas.enums import AvatarCloneStatus
|
||
|
||
|
||
class Avatar(Base):
|
||
"""
|
||
形象克隆记录表
|
||
|
||
用于备份用户在本地创建的克隆形象,支持换机恢复和客服排查。
|
||
"""
|
||
|
||
__tablename__ = "avatars"
|
||
|
||
# 主键:本地生成的唯一标识(与 Kling element_id 无关)
|
||
id: Mapped[str] = mapped_column(
|
||
String(64),
|
||
primary_key=True,
|
||
comment="本地形象唯一标识(如 avt_xxx)",
|
||
)
|
||
|
||
# 关联用户(外键,对应 users.id)
|
||
user_id: Mapped[str] = mapped_column(
|
||
UUID(as_uuid=False),
|
||
ForeignKey("users.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="关联用户 ID",
|
||
)
|
||
|
||
# 形象展示名称
|
||
name: Mapped[str] = mapped_column(
|
||
String(64),
|
||
nullable=False,
|
||
comment="形象展示名称",
|
||
)
|
||
|
||
# 供应商标识
|
||
provider: Mapped[str] = mapped_column(
|
||
String(32),
|
||
nullable=False,
|
||
default="kling",
|
||
comment="供应商标识: kling",
|
||
)
|
||
|
||
# Kling 自定义音色 ID(创建成功后回填)
|
||
voice_id: Mapped[str | None] = mapped_column(
|
||
String(64),
|
||
nullable=True,
|
||
comment="Kling 自定义音色 ID",
|
||
)
|
||
|
||
# 供应商主体 ID(创建成功后回填,用于调用 omni-video API)
|
||
provider_element_id: Mapped[int | None] = mapped_column(
|
||
BigInteger,
|
||
nullable=True,
|
||
comment="供应商主体 ID(数字类型,调用 API 时使用)",
|
||
)
|
||
|
||
# 供应商任务 ID(用于客服追溯)
|
||
provider_voice_job_id: Mapped[str | None] = mapped_column(
|
||
String(128),
|
||
nullable=True,
|
||
index=True,
|
||
comment="供应商自定义音色任务 ID",
|
||
)
|
||
|
||
provider_element_job_id: Mapped[str | None] = mapped_column(
|
||
String(128),
|
||
nullable=True,
|
||
index=True,
|
||
comment="供应商主体创建任务 ID",
|
||
)
|
||
|
||
# 资源地址
|
||
video_url: Mapped[str] = mapped_column(
|
||
Text,
|
||
nullable=False,
|
||
comment="原始人物视频 URL",
|
||
)
|
||
|
||
trial_url: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="音色试听音频 URL",
|
||
)
|
||
|
||
# 状态机
|
||
status: Mapped[str] = mapped_column(
|
||
String(32),
|
||
nullable=False,
|
||
default=AvatarCloneStatus.PENDING.value,
|
||
comment="状态: pending/voice_processing/voice_failed/element_processing/element_failed/succeed/timeout",
|
||
)
|
||
|
||
# 失败原因(用户可读)
|
||
fail_reason: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="失败原因(中文可读)",
|
||
)
|
||
|
||
# 软删除标记
|
||
deleted_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True),
|
||
nullable=True,
|
||
comment="软删除时间,NULL 表示未删除",
|
||
)
|
||
|
||
# 时间戳
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True),
|
||
default=lambda: datetime.now(UTC),
|
||
nullable=False,
|
||
comment="记录创建时间",
|
||
)
|
||
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True),
|
||
default=lambda: datetime.now(UTC),
|
||
onupdate=lambda: datetime.now(UTC),
|
||
nullable=False,
|
||
comment="记录更新时间",
|
||
)
|
||
|
||
def to_dict(self) -> dict:
|
||
"""转换为字典(用于序列化)"""
|
||
return {column.name: getattr(self, column.name) for column in self.__table__.columns}
|