# Dify 工作流配置文档:`laser_card_variants_v1` > 文件路径: `docs/specs/2026-06-02-dify-laser-card-workflow.md` > > 本文档是 Dify 平台上 `laser_card_variants_v1` 工作流的完整配置指南。 ## 工作流概述 - **类型:** Workflow(API 触发) - **名称:** `laser_card_variants_v1` - **用途:** 接收用户图片 → 抠图 → MiniMax AI 生成背景+装饰 → laser-compositor 合成 → 返回 5 张镭射卡 URL - **预计耗时:** 30-60 秒 ## 开始节点 — 输入变量 > **Dify 平台限制:** Dify 的开始节点不支持 `array` 类型。`preset_codes` 和 `render_configs` 使用 **文本 (string)** 类型,传入 JSON 字符串,在代码节点中 `json.loads` 解析。 | 变量 | Dify 类型 | 必填 | 默认值 | 说明 | |---|---|---|---|---| | `source_image_url` | 文本 (string) | 是 | — | 用户原图 OSS signed URL | | `use_cutout` | 复选框 (boolean) | 是 | — | 是否人像抠图 | | `preset_codes` | 文本 (string) | 否 | `["dream","classic","holoFull","ice","sunset"]` | JSON 数组字符串,例如 `"[\"dream\",\"classic\"]"` | | `render_configs` | 文本 (string) | 是 | — | JSON 数组字符串,包含每个 variant 的 grating_config、bg_prompt、overlay_prompt | **Dify 平台操作步骤:** 1. 添加 `preset_codes`:类型 **文本**,变量名 `preset_codes`,**不**勾选必填,默认值填 `["dream","classic","holoFull","ice","sunset"]` 2. 添加 `render_configs`:类型 **文本**,变量名 `render_configs`,**勾选**必填,默认值留空 3. 确认 `source_image_url`(文本,必填)和 `use_cutout`(复选框,必填)正确 4. **保存**开始节点 ## 节点链路 ```mermaid flowchart TB START[开始节点] --> NORM[代码节点1: 参数展开] NORM --> BRANCH{条件: use_cutout?} BRANCH -->|true| SEG[HTTP节点: Gateway /segment 抠图] BRANCH -->|false| LOOP SEG -->|成功| LOOP[循环节点: preset_codes x5] SEG -->|失败| WARN1[添加 warning] WARN1 --> LOOP subgraph variant_loop[循环内: 每个 variant] BG[HTTP节点: MiniMax 生成背景图] DECO[HTTP节点: MiniMax 生成装饰图] COMP[HTTP节点: laser-compositor /compose] BG --> DECO --> COMP end LOOP --> variant_loop COMP --> AGG[代码节点2: 聚合输出] AGG --> END_NODE[结束节点] ``` --- ## 各节点详细配置 ### 节点 1:代码节点 — 参数展开 **输入:** `preset_codes`, `render_configs`, `use_cutout` **代码(Python):** ```python import json def main(preset_codes: str, render_configs: str, use_cutout: bool) -> dict: """ 解析 JSON 字符串并展开 variants 列表 因为 Dify 开始节点不支持 array 类型,preset_codes 和 render_configs 都以 string 传入 """ # 解析 preset_codes(字符串 → 列表) if preset_codes and preset_codes.strip(): try: preset_list = json.loads(preset_codes) except: preset_list = [] else: preset_list = [] if not preset_list: preset_list = ["dream", "classic", "holoFull", "ice", "sunset"] # 解析 render_configs(字符串 → 列表) if render_configs and render_configs.strip(): try: configs = json.loads(render_configs) except: configs = [] else: configs = [] # 构建 preset_id → config 映射 config_map = {rc.get("preset_id"): rc for rc in configs if "preset_id" in rc} variants = [] for pc in preset_list: if pc in config_map: cfg = config_map[pc] variants.append({ "preset_id": pc, "grating_config": cfg.get("grating_config", {}), "bg_prompt": cfg.get("bg_prompt", ""), "overlay_prompt": cfg.get("overlay_prompt", ""), }) return { "variants": json.dumps(variants), "variant_count": len(variants), "use_cutout": use_cutout, } ``` --- ### 节点 2:条件分支 — 抠图判断 **条件:** `{{#代码节点1.use_cutout#}}` == true - **true 分支 →** HTTP 节点调用抠图 API - **false 分支 →** 直接进入循环 --- ### 节点 3:HTTP 节点 — 抠图(Segment) ``` 方法: POST URL: http://{GATEWAY_HOST}/api/v1/segment Headers: Content-Type: multipart/form-data Body (form-data): image: {{文件上传引用}} scene: portrait Authorization: Bearer {{#env.GATEWAY_JWT_TOKEN#}} ``` > **注意:** Dify HTTP 节点的 multipart 能力有限。如果 Dify 不支持 multipart form-data,可以用代码节点构造 HTTP 请求,或改为让 Gateway 提供 JSON 接口(传入 image_url)。 **替代方案(推荐):Gateway 扩展 `/api/v1/segment` 支持 JSON body** ``` POST http://{GATEWAY_HOST}/api/v1/segment/json Body (JSON): { "image_url": "{{#开始节点.source_image_url#}}", "scene": "portrait" } ``` 返回: ```json { "success": true, "cutout_oss_key": "laser-card/.../xxx_cutout.png", "cutout_url_signed": "https://oss.example.com/..." } ``` 如果失败:设置 `cutout_url = ""`,记 warning。 --- ### 节点 4:循环节点 — variant 遍历 **循环对象:** `{{#代码节点1.variants#}}`(JSON array) **循环变量名:** `variant_item` 循环内包含 3 个子节点(串行执行): --- #### 子节点 4a:MiniMax 生成背景图 ``` 方法: POST URL: https://api.minimaxi.com/v1/image_generation Headers: Authorization: Bearer {{#env.MINIMAX_API_KEY#}} Content-Type: application/json Body (JSON): { "model": "image-01", "prompt": "{{#variant_item.bg_prompt#}}", "aspect_ratio": "3:4", "n": 1, "prompt_optimizer": true, "response_format": "url" } ``` **超时:** 30 秒 **输出提取:** - `bg_url` = `{{#输出.body.data[0].url#}}` 如果失败,设置 `bg_url = ""`,记 warning。 --- #### 子节点 4b:MiniMax 生成装饰图 ``` 方法: POST URL: https://api.minimaxi.com/v1/image_generation Headers: Authorization: Bearer {{#env.MINIMAX_API_KEY#}} Content-Type: application/json Body (JSON): { "model": "image-01", "prompt": "{{#variant_item.overlay_prompt#}}", "aspect_ratio": "3:4", "n": 1, "prompt_optimizer": true, "response_format": "url" } ``` **超时:** 30 秒 **输出提取:** - `overlay_url` = `{{#输出.body.data[0].url#}}` --- #### 子节点 4c:laser-compositor 合成 ``` 方法: POST URL: http://{LASER_COMPOSITOR_HOST}/compose Headers: Content-Type: application/json Body (JSON): { "background_url": "{{#子节点4a.bg_url#}}", "cutout_url": "{{#代码节点3.cutout_url_signed#}}", "overlay_url": "{{#子节点4b.overlay_url#}}", "grating_config": {{#variant_item.grating_config#}}, "export_width": 450, "export_height": 600, "variant_index": {{#循环索引#}}, "output_oss_key": "laser-card/{date}/{uuid}_variant_{{循环索引}}" } ``` **超时:** 30 秒 **返回:** ```json { "status": "succeeded", "variant_index": 0, "width": 450, "height": 600, "oss_key": "laser-card/.../variant_0.png", "signed_url": "https://oss.example.com/..." } ``` --- ### 节点 5:代码节点 — 聚合输出 **目的:** 将循环中 5 个 variant 的结果聚合为统一 JSON ```python import json def main(variant_results: list, cutout_url: str, warnings: list) -> dict: """ 聚合循环结果 variant_results: 从循环节点收集的结果数组 """ variants = [] output_warnings = warnings.copy() if warnings else [] for i, vr in enumerate(variant_results): if vr and vr.get("status") == "succeeded": variants.append({ "preset_id": vr.get("preset_id", f"variant_{i}"), "oss_key": vr.get("oss_key", ""), "signed_url": vr.get("signed_url", ""), "width": vr.get("width", 450), "height": vr.get("height", 600), }) else: output_warnings.append(f"variant_{i} failed") return { "status": "succeeded" if len(variants) > 0 else "failed", "variants": variants, "cutout_oss_key": cutout_url or "", "warnings": output_warnings, } ``` --- ### 结束节点 — 输出格式 ```json { "status": "succeeded", "variants": [ { "preset_id": "dream", "oss_key": "laser-card/xxxx/variant_0.png", "signed_url": "https://oss.example.com/...", "width": 450, "height": 600 } ], "cutout_oss_key": "laser-card/xxxx/cutout.png", "warnings": [] } ``` --- ## 环境变量(在 Dify 工作流中配置) | 变量 | 说明 | |---|---| | `GATEWAY_HOST` | Gateway 地址,如 `http://192.168.1.100:8080` | | `MINIMAX_API_KEY` | MiniMax API 密钥 | | `LASER_COMPOSITOR_HOST` | laser-compositor 地址,如 `http://127.0.0.1:7000` | --- ## 鉴权说明 Dify 工作流调用 Gateway `/api/v1/segment`(需要 JWT)时,需要在 Dify 的 HTTP 节点中传入 Bearer Token。有两种方式: 1. **在 Dify 环境变量中配置一个长期有效的 JWT token**(`GATEWAY_JWT_TOKEN`)— 简单但需要定期维护 2. **Gateway 扩展 `/api/v1/segment/json`** — 不强制 JWT 鉴权,仅限内网调用(Dify→Gateway 走内网) 推荐方案 2,减少 Token 维护成本。 --- ## 装饰图 prompt 模板 每个 preset 的 `bg_prompt` 和 `overlay_prompt` 由前端 `laserPresets.js` 的 `buildRenderConfigs()` 动态生成并传入。Dify 不需要维护 prompt 数据。 5 个 preset 的参考 prompt(维护在前端 `laserPresets.js`): | Preset | bg_prompt | overlay_prompt | |---|---|---| | dream | 梦幻柔光渐变背景,淡紫色调和粉色,柔和泡泡和星光光斑,虚化景深效果,3:4竖版比例 | 精致银色星点散布,细边框装饰线,透明背景,SVG风格手绘装饰元素 | | classic | 经典复古胶片风格背景,暖色调,浅香槟色渐变,艺术纹理和柔和光影,3:4竖版比例 | 经典复古边框花纹装饰,暖金色调和银色细线,透明背景,精致几何图案 | | holoFull | 全息科技感背景,银色金属质感,未来主义光影和几何线条,珍珠光泽渐变,3:4竖版比例 | 全息科技感装饰边框,霓虹色细线光晕,透明背景,电路板几何图案和星形闪光 | | ice | 冰雪冷色调背景,浅蓝灰渐变,冰晶纹理和雪花光斑,清爽透明感,3:4竖版比例 | 冰晶装饰边框,浅蓝色和银色星点,透明背景,雪花图案和细线装饰 | | sunset | 日落暖色调背景,玫瑰金渐变,暖橙色和粉色光影,温馨氛围,3:4竖版比例 | 日落暖色调装饰边框,玫瑰金和琥珀色星点,透明背景,柔和花卉和光线元素 | --- ## 测试方法 1. 在 Dify 平台创建 Workflow,按本文档配置各节点(**特别注意**:`preset_codes` 和 `render_configs` 选**文本**类型) 2. 使用 Dify 的"运行"功能测试,传入示例 input: > **关键:** `preset_codes` 和 `render_configs` 的值必须是 **JSON 字符串**(带转义引号),不是 JSON 对象。 ```json { "source_image_url": "https://oss.example.com/test/user_photo.jpg", "use_cutout": true, "preset_codes": "[\"dream\"]", "render_configs": "[{\"preset_id\":\"dream\",\"grating_config\":{\"sheen_band_angle\":135,\"sheen_intensity\":0.40,\"sheen_speed\":0.35,\"foil_coverage\":0.75,\"backdrop_tone\":\"#A8ACB2\"},\"bg_prompt\":\"梦幻柔光渐变背景,淡紫色调\",\"overlay_prompt\":\"精致银色星点装饰,透明背景\"}]" } ``` **外部 API 调用示例(通过 Gateway 触发):** ```json { "source_image_url": "https://oss.example.com/photo.jpg", "use_cutout": true, "preset_codes": "[\"dream\",\"classic\"]", "render_configs": "[{\"preset_id\":\"dream\",\"grating_config\":{\"sheen_band_angle\":135,\"sheen_intensity\":0.4},\"bg_prompt\":\"...\",\"overlay_prompt\":\"...\"}]" } ``` 3. 单个 preset 测试成功后,改为 5 个 preset 全量测试