Files
image-suite-studio/server/models.py
T
2026-08-16 17:32:43 +08:00

119 lines
5.8 KiB
Python

"""数据模型:Product(商品)/ ProductAsset(采集素材)/ Suite(套图任务)/ SuiteImage(生成图)。"""
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Float, ForeignKey, Integer, JSON, String, Text, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column
from db import Base
# 产品阶段
STAGE_COLLECTED = "collected"
STAGE_GENERATED = "generated"
# 素材/生成图状态
STATUS_PENDING = "pending"
STATUS_DOWNLOADING = "downloading"
STATUS_OK = "ok"
STATUS_FAILED = "failed"
# 套图任务状态
SUITE_PENDING = "pending"
SUITE_RUNNING = "running"
SUITE_DONE = "done"
SUITE_PARTIAL = "partial"
SUITE_FAILED = "failed"
class Product(Base):
__tablename__ = "products"
id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
stage: Mapped[str] = mapped_column(String(16), default=STAGE_COLLECTED, index=True)
# 采集溯源
source_platform: Mapped[str | None] = mapped_column(String(16), nullable=True) # ozon | 1688 | taobao
source_item_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
source_url: Mapped[str | None] = mapped_column(Text, nullable=True)
name: Mapped[str] = mapped_column(Text, default="")
description: Mapped[str] = mapped_column(Text, default="")
price: Mapped[float | None] = mapped_column(Float, nullable=True)
# 采集原文:{title, price, brand, params: [...], sellingPoints, desc, texts: [...]}
raw: Mapped[dict | None] = mapped_column(JSON, nullable=True)
asset_counts: Mapped[dict | None] = mapped_column(JSON, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), index=True
)
class ProductAsset(Base):
"""采集素材(源站图片,转存到本地 media)。"""
__tablename__ = "product_assets"
id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
product_id: Mapped[uuid.UUID] = mapped_column(
Uuid(as_uuid=True), ForeignKey("products.id", ondelete="CASCADE"), index=True
)
group_key: Mapped[str] = mapped_column(String(16), default="main") # main/sku/detail/video
variant_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
type: Mapped[str] = mapped_column(String(8), default="img") # img / video
source_url: Mapped[str] = mapped_column(Text, default="")
stored_url: Mapped[str | None] = mapped_column(Text, nullable=True) # 本地 media key 或公网 URL
status: Mapped[str] = mapped_column(String(16), default=STATUS_PENDING, index=True)
dedupe_key: Mapped[str | None] = mapped_column(String(512), nullable=True, index=True)
error: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class Suite(Base):
"""一次套图生成任务(无状态:直接携带采集数据,不依赖商品库)。"""
__tablename__ = "suites"
id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
# 兼容旧的商品挂载路径;工具化流程为空
product_id: Mapped[uuid.UUID | None] = mapped_column(
Uuid(as_uuid=True), ForeignKey("products.id", ondelete="CASCADE"), nullable=True, index=True
)
status: Mapped[str] = mapped_column(String(16), default=SUITE_PENDING, index=True)
style_set: Mapped[int] = mapped_column(Integer, default=1) # 风格模板 1-5
style_prompt: Mapped[str | None] = mapped_column(Text, nullable=True) # 用户改写的风格提示词(覆盖模板)
platform: Mapped[str] = mapped_column(String(8), default="cn") # 目标平台 ozon | wb | cn
lang: Mapped[str] = mapped_column(String(4), default="zh") # ru / zh(由平台推导)
ratio: Mapped[str] = mapped_column(String(8), default="1:1") # 图片比例(由平台推导)
types: Mapped[list | None] = mapped_column(JSON, nullable=True) # 图类型 id 列表(旧)
plan: Mapped[list | None] = mapped_column(JSON, nullable=True) # 出图方案(展开后的逐张任务)
provider: Mapped[str] = mapped_column(String(16), default="doubao")
model: Mapped[str | None] = mapped_column(String(64), nullable=True) # 生图模型名(覆盖 provider 默认)
# 工具化流程:请求自带的数据(生图上下文 + 参考图 URL 列表)
context: Mapped[dict | None] = mapped_column(JSON, nullable=True)
ref_images: Mapped[list | None] = mapped_column(JSON, nullable=True)
error: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class SuiteImage(Base):
"""任务里单张生成图。"""
__tablename__ = "suite_images"
id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
suite_id: Mapped[uuid.UUID] = mapped_column(
Uuid(as_uuid=True), ForeignKey("suites.id", ondelete="CASCADE"), index=True
)
type_id: Mapped[str] = mapped_column(String(32)) # white_bg / key_features / ...
name: Mapped[str] = mapped_column(String(64), default="") # 中文名(文件名)
stored_url: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(String(16), default=STATUS_PENDING)
error: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())