摘要
公司官网首页挂了一个”视频复刻”体验位,之前一直是前端占位:游客点”开始创作”只存草稿。这一轮把它接成了真家伙——免费档 Minimax Turbo 480P 直接调用本机 RTX 5090 上的视频工作台出片,实测 44~56 秒一条,公网端到端验收 10/10 全绿。本文完整记录:对着一份编译型 .pyd 后端怎么反推 API 契约、422 报错怎么逐字段试出正确形态、跨域死结怎么用“同源反代 + 内网隧道”解开、以及验收脚本里那个让我白等 9 分钟的低级 bug。所有内网地址、端口、路径均已脱敏。

一、目标形态:游客零登录,点一下就出片
产品诉求很朴素:官网首页的视频复刻面板里,模型档位选单中的 Minimax Turbo 480P 免费开放,游客不用注册登录,选一张模板首帧、点“开始创作”,等一分钟就能在线看片。其余 12 个会员档位维持原登录流不动。
听起来是“前端调个接口”,实际上横着三座山:
- 后端是个编译产物。本机视频工作台(ComfyUI 套壳 + 一个
api_models.cp312-win_amd64.pyd)对外只有 HTTP API,没有源码、没有可信文档,参数格式全靠报错反推。 - GPU 在我家里,官网在云上。出片引擎跑在本地工作站的 5090 上,公网入口是云上的一台 nginx,中间隔着两层内网。
- 不能碰别人的生产配置。云上那台 nginx 上还跑着同事维护的其他服务,改配置必须增量、可回滚、零波及。
二、契约侦察:对着一份闭源 .pyd 把 API 打穿
第一步不是写代码,是把真实契约挖出来。工作台自带的 OpenAPI 描述文件(/api/v1/openapi.json)是主要线索源,配合健康检查与只读接口交叉验证:
import requests
B = "http://127.0.0.1:8600/api/v1" # 本机回环,端口已虚构
requests.get(B + "/health").json()
# {'status': 'ready', 'comfyui': 'ready', 'gpu': 'RTX 5090 online'}
requests.get(B + "/workflows").json() # 支持的生成模式
requests.get(B + "/loras").json() # 已装 LoRA 清单
真正有价值的情报全部来自受控的失败——用最小 payload 去提交任务,读 422 的逐字段报错,一次修一个:
✗ loras:[{name:"turbo", enabled:true}] → 422 extra_forbidden: enabled
✗ loras:[{name:"turbo", type:"lora", weight:1.0}] → 422 ×3,三个字段全不被认识
✓ loras:[{name:"video_turbo_4step", strength:0.9}] → 200,任务受理
结论值得刻在桌上:loras 元素只认 {name, strength} 两键,多一个键就炸。同类暗坑还有:base_precision 收字符串不收浮点、scheduler 只认枚举值、steps 必须是整数。图片上传也不走寻常路——不是表单字段直塞任务体,而是先传资产再引用:
asset = requests.post(B + "/assets?asset_type=image",
files={"file": open("first.jpg", "rb")}).json()
# {'id': 'asset_ab12cd34ef56', 'url': '/api/v1/assets/...'}
提交与回收的完整形态(这条任务实测 56 秒出片):
body = {"type": "image_to_video",
"inputs": {"image": asset["id"], "prompt": prompt,
"duration": 6, "resolution": "832*480"},
"generation": {"max_new_tokens": 64,
"loras": [{"name": "video_turbo_4step",
"strength": 0.9}]}}
job = requests.post(B + "/jobs", json=body).json()
# 轮询 /jobs/{id}/status → completed 后取 /view?filename=...&type=output

三、跨域死结:让官网域名和自己的 GPU 同源
浏览器在 https://www.example.com 里直接 fetch 一个内网地址 https://198.51.100.7:8600 是死路(混合内容 + CORS + 内网不可达)。备选方案里,最省事的是给工作台配一个与官网同源的公开路径:
浏览器 ──► https://www.example.com/qs/api/v1/...
│ 云上 nginx:location /qs/ 纯增量
▼
127.0.0.1:8600(隧道落点,ssh -R 反建的口)
▼
本机 TLS 反代 :7600
▼
127.0.0.1:8600 工作台 API(RTX 5090 出片)
nginx 侧的补丁保持最小面积,逐字复制自同机上已稳定运行三个月的另一条反代(只换路径前缀与 rewrite),连 WebSocket 升级头都原样保留——进度推送要用它:
location /qs/ {
rewrite ^/qs/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8600;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 300s;
}
四、改生产配置的纪律:备份、语法闸、自动回滚
云机上有别人的服务,所以这枚补丁写成了带安全规程的脚本,而不是手敲 sed:
- 先
cp -a出带时间戳的备份; - 用标记块做幂等注入(重复执行不产生第二个 location);
nginx -t不过就自动还原备份并重新加载——语法闸门永远在 reload 之前;- 验证双管齐下:
nginx -T里 grep 到自己的块 + 公网curl /qs/api/v1/health拿到 ready; - 对照测试:同事维护的另一条业务路径行为不变,确认零波及。
还有一颗排掉的雷:仓库里那份历史部署脚本带 rsync --delete,构建母本早已过期,跑一次就会静默删光线上多余文件。正确姿势永远是只上传目标小文件 + 双端 md5 核对,而不是整目录同步。
五、前端引擎:拦截一个按钮,长出一条真链路
官网面板是原生 JS,没有框架。用“接管 CTA 按钮”的方式接入,对存量逻辑零侵入:
const FREE = tierId === "minimax_turbo" && /480p/.test(spec);
document.querySelectorAll(".console-cta").forEach(b =>
b.addEventListener("click", (e) => {
if (!FREE || !state.userImage) return; // 非免费档 → 原登录流
e.stopImmediatePropagation(); // 抢在旧 handler 前
runTurbo(state.userImage, prompt, spec);
}));
要点清单:先传图、再建任务;进度浮标轮询 /jobs/{id}/status,刷新页面后可续查(job id 落 localStorage,下次开机自动恢复);出片 URL 是相对路径,统一加 /qs 前缀改写;任何一步失败 → toast 提示后自动降级回原“存草稿”行为,用户永远有退路。顺手修掉一个 UX 打架:免费档生成时旧 handler 的“草稿已存”toast 和引擎进度 toast 先后弹出——在旧 handler 开头对免费档静默放行即可。
六、验收翻车实录:id 当 class 查,白等九分钟
端到端脚本第一版跑出来永远超时:任务提交成功、后端 status 明明已 completed,前端断言却死活等不到成片浮层。逐段排查后看到这种令人窒息的操作:
// DOM 长这样:
// <div id="yl-replica-result">…</div>
// 脚本却写成:
document.querySelector(".yl-replica-result") // 永远 null
// 正确姿势:
document.getElementById("yl-replica-result")
一个选择器笔误,让“等 90 秒”的轮询窗口改成 300 秒也照样挂——因为断言永远不可能成立。修完之后本地 14 项断言全绿。教训:断言失败先证明断言本身可执行,再怀疑链路。
七、三重验收与最终数据
| 场景 | 结果 | 出片耗时 |
|---|---|---|
| 本机直连 API(侦察期) | 832×480,576KB | 56s |
| 本地同构镜像端到端(Playwright 真实点击) | 14/14 PASS | 48s |
| 公网官网真实首页端到端 | 10/10 PASS | 44s |
真实用户路径全程:选模板卡 → 首帧自动抽图入槽 → 点“开始创作”被引擎接管 → 进度浮标 0→100 → 成片浮层可播可下载 → 刷新后“Done ✓”回看入口 → 点击重开浮层。零 JS 异常,游客全程未登录。
八、可迁移的经验
- 对着闭源后端,422 是最诚实的文档——最小 payload + 逐字段试探,比读压缩包里的 bundle 快一个数量级。
- 同域反代 > CORS 白名单:给内网引擎开一条公网域名的子路径,前端零配置、无预检、无混合内容问题;反代块照抄已验证的存量配置最安全。
- 改生产配置的三板斧:时间戳备份、语法闸(
nginx -t不过不 reload)、自动回滚。再加一条:部署前先 diff 线上基线,证明队友没动过,再动手。 - 给同事留接管口:网关地址、模型名、档位参数全部集中在一个 JSON 契约文件里,未来换成带鉴权的收费网关,只改一处配置,前端零改动——这条要写进交接文档。
- 免费 + GPU 必须谈限流:每个游客点一次就真占 5090 五十秒,上线当天就要把 IP 级配额想好,别等账单教育。



