Files
pod_trend_agent/graph/nodes/oss_upload_node.py
3218485270 f493bde8a9 POD 趋势感知 Agent:缓存热点模式 + 三图合成 + 热点去重/风格去重 + review 兜底
- 缓存热点批量流程(有采集缓存不触发 Google)
- 简报不足直接从采集缓存生成(轻量补齐)
- 三图合成(模特/印花/底图)+ 底图压缩 <2MB
- 热点去重→风格去重自动切换 + 不适合类目 review 兜底
- 透明背景(background=transparent)+ 提示词清洗(敏感词/背景描述)
- 任务前 basemap 校验 + 模板国家校验 + 模特任务级分配
2026-08-22 14:14:01 +08:00

110 lines
5.1 KiB
Python
Raw Permalink 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.
"""节点 7/7:压缩 + 上传阿里云 OSSoss_upload)。
在 product 之后运行:把 product 生成的成品图(composite / printed / design / basemap
压缩为 3:4 / ≥1340×1785 / <2MB 的 JPEG,上传到 config.oss 图床。
上传 key(图床路径):{国家}/{任务时间戳}/{货号}_{4位随机}.jpg
- 任务时间戳:任务开始记录(YYYYMMDDHHMMSS),state["task_timestamp"],缺失时取当前时间
- 货号:用户自定义前缀(config.product.code_prefix,默认 DG+ 3 位计数(000 起,最多 999)
- 4 位随机:大小写英文 + 数字
压缩/上传均带兜底:单图失败不影响其它;未配置 oss 或 enabled=false 时静默跳过。
"""
import random
import string
import time
from pathlib import Path
from typing import Any, Dict, List
from graph.validate import with_fallback
# design 设计稿是过程稿(不进模板),不压缩不上传;只传最终商品图
KIND_ORDER = ["composite", "printed", "basemap"]
MAX_CODE = 999 # 货号计数上限(000~998 共 999 张)
def _gen_rand4() -> str:
return "".join(random.choices(string.ascii_letters + string.digits, k=4))
@with_fallback("oss_upload")
def oss_upload_node(state: Dict[str, Any]) -> Dict[str, Any]:
products: List[Dict[str, Any]] = state.get("product") or []
config = state["config"] or {}
oss_cfg = config.get("oss") or {}
country = state.get("country", "")
if not (oss_cfg.get("oss_bucket") and oss_cfg.get("oss_key_id")):
print("[oss] 未配置 ossconfig.oss),跳过压缩上传节点。")
return {"oss": [], "stats": state.get("stats") or {}}
if not bool(oss_cfg.get("enabled", True)):
print("[oss] config.oss.enabled=false,跳过上传。")
return {"oss": [], "stats": state.get("stats") or {}}
from graph.oss_upload import build_oss_key, compress_for_oss, upload_to_oss
# 任务时间戳:任务开始记录;缺失则当前时间
ts = str(state.get("task_timestamp") or time.strftime("%Y%m%d%H%M%S"))
# 货号前缀:config.product.code_prefix(默认 DG
prefix = str(((config.get("product") or {}).get("code_prefix")) or "DG").strip()
# 序号从 state 续接(一次任务内跨多次节点调用不重号)
seq = int(state.get("oss_seq") or 0)
uploaded: List[Dict[str, Any]] = []
stats = dict(state.get("stats") or {})
for r in products:
spu = r.get("spu_code", "")
sku = r.get("sku_code", "")
for kind in KIND_ORDER:
src = r.get(f"{kind}_path")
if not src or not Path(src).exists():
continue
if seq >= MAX_CODE:
print(f"[oss] 货号计数已达上限 999,停止上传后续图片({src}")
break
try:
code = f"{prefix}{seq:03d}" # 货号:前缀 + 3 位计数(000 起)
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
key = build_oss_key(country, ts, code, _gen_rand4())
url = upload_to_oss(oss_cfg, compressed, key)
if url:
r[f"{kind}_url"] = url
r["oss_code"] = code
uploaded.append({"spu_code": spu, "sku_code": sku, "kind": kind,
"code": code, "url": url})
seq += 1
except Exception as e: # noqa: BLE001
print(f"[oss] 处理失败 {src}: {e}")
# 多色:color_composites 用于模板按颜色路由——首色用主图 url/code,额外色单独上传(独立货号)
color_ups: List[Dict[str, Any]] = []
comps = r.get("color_composites") or []
if comps and r.get("composite_url"):
color_ups.append({**comps[0], "url": r["composite_url"], "code": r.get("oss_code", "")})
for cc in comps[1:]:
src = cc.get("composite_path")
if not src or not Path(src).exists():
continue
if seq >= MAX_CODE:
print("[oss] 货号计数已达上限 999,停止上传颜色图")
break
try:
code = f"{prefix}{seq:03d}"
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
key = build_oss_key(country, ts, code, _gen_rand4())
url = upload_to_oss(oss_cfg, compressed, key)
if url:
cc["url"] = url
cc["code"] = code
color_ups.append(cc)
uploaded.append({"spu_code": spu, "sku_code": cc.get("sku_code"),
"kind": "composite_color", "code": code, "url": url})
seq += 1
except Exception as e: # noqa: BLE001
print(f"[oss] 颜色图上传失败 {src}: {e}")
if color_ups:
r["color_composites"] = color_ups
stats["oss"] = {"uploaded": len(uploaded), "timestamp": ts, "prefix": prefix, "seq": seq}
return {"oss": uploaded, "product": products, "oss_seq": seq, "stats": stats}