feat: 优化图片资源删除管理,生图任务列表管理

This commit is contained in:
2026-08-30 14:50:33 +08:00
parent 4ee667142b
commit 957289f717
46 changed files with 4180 additions and 524 deletions
+3
View File
@@ -4,6 +4,7 @@
由 legacy.api 路由 import 链注册到同一 Base.metadata。
"""
from models.asset import ProductAsset
from models.job import BackgroundJob, BackgroundJobItem
from models.product import Product
from models.user import User
@@ -11,4 +12,6 @@ __all__ = [
"User",
"Product",
"ProductAsset",
"BackgroundJob",
"BackgroundJobItem",
]
+11 -3
View File
@@ -4,11 +4,12 @@ from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text, Uuid, func
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column
from db import Base
from models.enums import AssetStatus
from models.types import JSONType
class ProductAsset(Base):
@@ -22,8 +23,15 @@ class ProductAsset(Base):
variant_name: Mapped[str | None] = mapped_column(String(128), nullable=True) # SKU 规格名
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) # 本地路径或七牛公网 URL
source_url: Mapped[str] = mapped_column(Text, default="") # 采集源站地址;生成图旧数据可能存无水印 URL
stored_url: Mapped[str | None] = mapped_column(Text, nullable=True) # 当前展示版 URL(兼容旧接口)
# 图片生命周期:original 始终无水印;当前版用于页面预览、下载、导出。
# 未加水印时 original 与 current 指向同一对象;不保留历史水印版。
original_url: Mapped[str | None] = mapped_column(Text, nullable=True)
original_storage_key: Mapped[str | None] = mapped_column(String(1024), nullable=True)
current_storage_key: Mapped[str | None] = mapped_column(String(1024), nullable=True)
watermark_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
watermark_config: Mapped[dict | None] = mapped_column(JSONType, nullable=True)
status: Mapped[AssetStatus] = mapped_column(
Enum(AssetStatus, native_enum=False, length=16), default=AssetStatus.pending, index=True
)
+78
View File
@@ -0,0 +1,78 @@
"""持久化后台任务:AI 套图、单张编辑共用的父任务与逐图子任务。"""
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column
from db import Base
from models.types import JSONType
class BackgroundJob(Base):
"""一个用户可见的后台操作:套图、单张编辑或批量水印。"""
__tablename__ = "background_jobs"
id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
# 商品删除后保留任务诊断记录,但 Worker 不会再把结果回写到不存在的商品。
product_id: Mapped[uuid.UUID | None] = mapped_column(
Uuid(as_uuid=True), ForeignKey("products.id", ondelete="SET NULL"), nullable=True, index=True
)
kind: Mapped[str] = mapped_column(String(32), index=True) # image_suite | image_edit | watermark_batch
status: Mapped[str] = mapped_column(String(24), default="queued", index=True)
priority: Mapped[int] = mapped_column(Integer, default=0, index=True)
provider: Mapped[str] = mapped_column(String(32), default="")
model: Mapped[str | None] = mapped_column(String(128), nullable=True)
platform: Mapped[str] = mapped_column(String(16), default="ozon")
lang: Mapped[str] = mapped_column(String(16), default="ru")
ratio: Mapped[str] = mapped_column(String(16), default="3:4")
style_set: Mapped[int] = mapped_column(Integer, default=1)
# 原始请求的不可变快照:商品上下文、参考图池、风格、要求、水印等。
request_payload: Mapped[dict] = mapped_column(JSONType, default=dict)
total: Mapped[int] = mapped_column(Integer, default=0)
completed: Mapped[int] = mapped_column(Integer, default=0)
failed: Mapped[int] = mapped_column(Integer, default=0)
cancel_requested: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
error: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), index=True
)
class BackgroundJobItem(Base):
"""任务中的一个可调度工作单元:套图的一张图或单张编辑。"""
__tablename__ = "background_job_items"
id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
job_id: Mapped[uuid.UUID] = mapped_column(
Uuid(as_uuid=True), ForeignKey("background_jobs.id", ondelete="CASCADE"), index=True
)
position: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String(24), default="queued", index=True)
payload: Mapped[dict] = mapped_column(JSONType, default=dict)
# 供应商异步任务 ID 与类型(pcore 还需 edits/generations);重启后继续轮询,不重新提交。
provider_task_id: Mapped[str | None] = mapped_column(String(512), nullable=True, index=True)
provider_task_kind: Mapped[str | None] = mapped_column(String(64), nullable=True)
attempts: Mapped[int] = mapped_column(Integer, default=0)
max_attempts: Mapped[int] = mapped_column(Integer, default=3)
result_url: Mapped[str | None] = mapped_column(Text, nullable=True)
source_url: Mapped[str | None] = mapped_column(Text, nullable=True)
watermark_config: Mapped[dict | None] = mapped_column(JSONType, nullable=True)
generated_asset_id: Mapped[uuid.UUID | None] = mapped_column(Uuid(as_uuid=True), nullable=True)
error: Mapped[str | None] = mapped_column(Text, nullable=True)
next_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
lease_owner: Mapped[str | None] = mapped_column(String(128), nullable=True)
lease_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), index=True
)