Files
ozon-seller-kit/server/services/suite_service.py
T
R524809 a77ec26a02 refactor(server): 移除已放弃的 Ozon API 直传模块,新增素材删除接口
- 删除 shops/publish/categories/ozon 相关路由、模型、schema 与客户端服务(V2.1 决策放弃 API 直传,改人工上传)
- materials 新增 DELETE /assets/{id}:删除素材记录与本地文件,并同步修正 asset_counts
- 试算页支持单张素材删除,生图弹窗交互微调
- 新增 docs/v2.1/HANDOFF.md 工作交接说明,README 补充索引
2026-08-28 17:47:57 +08:00

117 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""套图业务逻辑(从 api/suite.py 下沉):文本素材转换 / 模型校验 / 生成图回写商品素材。
api 层只留参数校验与调用;本模块可独立测试。
"""
from __future__ import annotations
import uuid
from schemas.suite import TextMaterial
from services.tasks import TaskImage
def texts_to_raw(texts: list[TextMaterial]) -> dict:
"""前端组装的文本素材 → prompt 上下文用的 raw dict(后写的覆盖先写的)。"""
raw: dict = {}
for t in texts:
if t.kind == "title" and t.content:
raw["title"] = t.content
elif t.kind == "price" and t.content:
raw["price"] = t.content
elif t.kind == "brand" and t.content:
raw["brand"] = t.content
elif t.kind == "params" and t.pairs:
merged = {p["key"]: p["value"] for p in (raw.get("params") or [])}
for p in t.pairs:
merged.setdefault(p["key"], p["value"])
raw["params"] = [{"key": k, "value": v} for k, v in merged.items()]
elif t.kind == "selling_point" and t.content:
raw["sellingPoints"] = t.content
elif t.kind == "desc" and t.content:
raw["desc"] = t.content
elif t.kind == "sales" and t.content:
raw["sales"] = t.content
elif t.kind == "shop" and t.content:
raw["shop"] = t.content
return raw
def validate_model(provider_name: str, model: str | None) -> None:
"""按 provider 校验模型名白名单,不合法抛 ValueError。"""
from schemas.suite import RIGHTAPI_MODELS, TONGYI_MODELS
if provider_name == "tongyi" and model and model not in TONGYI_MODELS:
raise ValueError(f"不支持的模型: {model}tongyi 支持: {TONGYI_MODELS}")
if provider_name == "rightapi" and model and model not in RIGHTAPI_MODELS:
raise ValueError(f"不支持的模型: {model}rightapi 支持: {RIGHTAPI_MODELS}")
async def append_generated_asset(
product_id: str,
image: TaskImage,
after_asset_id: str | None = None,
) -> str | None:
"""把一张生成完成的图追加为 product_assets(generated),并累加 asset_counts。返回 asset_id。
after_asset_id:插入锚点——新素材排在锚点之后(其余素材顺次后移),方便与原图对比;
缺省/锚点无效时追加到组尾。作为 run_suite 的逐张回调使用时签名须为 (image),
调用方用 partial 绑定 product_id。
"""
from sqlalchemy import func, select
from db import get_session_factory
from models import Product, ProductAsset
pid = uuid.UUID(product_id)
async with get_session_factory()() as db:
sort_order: int | None = None
if after_asset_id:
try:
anchor = await db.get(ProductAsset, UUID(after_asset_id))
except ValueError:
anchor = None
if anchor is not None and anchor.product_id == pid and anchor.group_key == "generated":
# 锚点之后的素材顺次后移,腾出插入位
followers = (
await db.scalars(
select(ProductAsset).where(
ProductAsset.product_id == pid,
ProductAsset.group_key == "generated",
ProductAsset.sort_order > anchor.sort_order,
)
)
).scalars().all()
for follower in followers:
follower.sort_order += 1
sort_order = anchor.sort_order + 1
if sort_order is None:
sort_order = await db.scalar(
select(func.coalesce(func.max(ProductAsset.sort_order), -1)).where(
ProductAsset.product_id == pid,
ProductAsset.group_key == "generated",
)
)
sort_order = (sort_order or 0) + 1
asset = ProductAsset(
product_id=pid,
group_key="generated",
variant_name=None,
sort_order=sort_order,
type="img",
source_url="",
stored_url=image.url,
status="uploaded",
)
db.add(asset)
await db.flush()
product = await db.get(Product, pid)
if product is not None:
counts = dict(product.asset_counts or {})
counts["generated"] = int(counts.get("generated") or 0) + 1
product.asset_counts = counts
await db.commit()
return str(asset.id)