零依赖搭一个视频生成 Demo:Node 18 + 原生 ES Modules + 单文件代理
零依赖搭一个视频生成 Demo
我之前拿 MiniMax 视频生成 API 搓过一个 Demo,周末重新跑了一遍、读了一下源码、做了次安全审计,顺手把这篇复盘写下来。
不是为了吹”多牛”——它克制得刚好。1000 行 Node 代码、零 node_modules、零打包器,把”异步视频生成”这件事拆得清清楚楚。
但我自审的时候也发现了三处安全坑——README 第 296-301 行我自己标的”无鉴权 / 监听 0.0.0.0 / Key 存 localStorage”之外,还漏了三处。这篇文章两件事都讲。
这东西是什么
简单说:它是 MiniMax 视频生成 API 的前端 Demo。
- 文生视频、图生视频(支持 15 种运镜指令:
[推进]、[拉远]、[左移]等) - 历史记录存后端 JSON 文件(支持 1000 条上限、原子写入、并发写锁)
- 视频生成后自动缓存到本地(因为
download_url1 小时就过期,不缓存等于丢) - 历史可导入导出 JSON / CSV,跨设备同步
零运行时依赖。Node 18+ 自带 fetch,前端用原生 ES Modules,clone 下来 node server.mjs 就能跑。没有 node_modules 那个黑洞。
下面这张是我在本地把服务跑起来后截的实拍:API Key 输入框、模型/分辨率/时长三个下拉、prompt 输入、首帧图上传区、生成按钮,下面挂着两条已经成功生成并缓存到本地的历史(中式八球台球宝贝 + 森林小鹿)。
仓库:https://github.com/HChaoHui/MiniMax-Video
三个值得记的工程细节
1. 劫持原生 <select> 的 value setter
<select> 太丑,想替换成自定义的,又得支持键盘 ↑↓ Enter Esc Space。
常规做法:加一个状态层(把所有用 sel.value = 'x' 的地方改成 setState('x')),然后让自定义 UI 监听 state。
我当时选了一个更骚的解法——
Object.defineProperty(realSelect, 'value', {
set(v) {
realSelect.dataset.value = v;
// 同步自定义 UI
},
get() { return realSelect.dataset.value; }
});
劫持原生 <select> 的 value setter。所以 sel.value = 'x' 这类代码不用改,既走原生行为,又自动同步自定义 UI。整个项目里别的 JS 一行不用动。
我回头看这个写法时愣了一下——我自己写大概率会再搞一个状态层把这件事兜住。
2. 写锁
历史记录是 JSON 文件,浏览器每次操作都要写。我自己写的时候就被这个问题绊过——两个标签页同时开着、或者点得太快,就会出现半写入。
我最后用的写法:
let writeQueue = Promise.resolve();
function writeHistory(data) {
writeQueue = writeQueue.then(async () => {
const tmp = HISTORY_FILE + '.tmp';
await fs.writeFile(tmp, JSON.stringify(data, null, 2));
await fs.rename(tmp, HISTORY_FILE); // 原子替换
});
return writeQueue;
}
先写临时文件,再 rename 原子替换,Promise 串行化避免并发。
简单粗暴但够用。对 1000 行的 JSON 引入 native 依赖是过度设计——SQLite 那种原生模块编译一遍三分钟,值不值的自己掂量。
3. 错误码对照表
这个是写代码时没想到的。
我当时把 API 文档截图丢给大模型,它不光把对接代码写了,还顺手从文档里抠了一个错误码表出来塞进 README:
| 错误码 | 含义 |
|---|---|
1002 | 触发限流 |
1004 | API Key 错误 |
1008 | 余额不足 |
1026 | 内容敏感 |
2013 | 参数异常 |
1027 | 输出内容错误 |
这种”我自己没打算写、但模型主动加的细节”,在 M3 这种原生多模态的模型上特别常见——视觉理解 + 长文档解析 + 代码生成是它的原生场景,扫一眼文档就把这种”上下文里的隐性信息”挖出来。
但也有反向坑,我也踩了:模型会瞎猜文档里没写的东西。错误码那块它就”补全”了几个看起来合理但实际不存在的码。遇到不确定就标 TODO,不要瞎猜——这条提示要主动加。
顺手审计:三处我没标的安全坑
我自己 README 第 296-301 行只标了三条安全风险(没有用户认证、监听 0.0.0.0、API Key 存 localStorage)。但还漏了三处,我周末重跑一遍才看出来。
坑 1:/api/* 代理是裸的
后端有个 /api/* 代理,功能是把浏览器请求转发到 api.minimaxi.com 并附上用户的 API Key。
if (req.url.startsWith('/api/')) return proxyRequest(req, res, req.method);
但 proxyRequest 只校验 X-API-Key 头有没有,不校验是谁给的。配合 Access-Control-Allow-Origin: *,任何能访问 3000 端口的人都能拿自己的 API Key 走你的代理——如果你部署在公网,Key 会被盗刷。
修法:加一个 ADMIN_TOKEN 环境变量 + 中间件,所有 /api/* 必须带 X-Admin-Token 头。三行代码的事。
坑 2:DELETE 类接口无鉴权
// DELETE /api/history — 清空全部
// DELETE /api/orphan_videos — 全清
// POST /api/orphan_videos/clean — 选择性清理
这三个接口没有任何鉴权。任何能访问端口的人 curl -X DELETE 就能把你所有历史一键清空。
修法:同上,ADMIN_TOKEN 中间件覆盖所有 /api/*。
坑 3:readBody 无大小限制
function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on('data', c => chunks.push(c));
// ...
});
}
不限 Content-Length,不限累计字节数。攻击者发个 10GB 的 POST /api/history 就能把你 Node 进程内存打爆。简单限个 MAX_BODY = 10 * 1024 * 1024 就够。
这类项目适合谁
如果你:
- 是个人开发者,想快速出活
- 能接受”和 AI 一起写代码”——你定方向它落地,过程里你 review
- 项目规模在一千行到几千行
那这种”单文件后端 + 原生 ES Modules 前端 + 零依赖”的玩法挺合适的。
反过来,大型工程、严格 PR 流程、强合规项目,这种玩法现阶段还不合适。AI 在快速迭代,工程化背书也不够。
收个尾
代码差不多 1000 行,README 300 多行,全部开源。
clone 下来加个 API Key 就能跑。但先加鉴权再放到公网——别嫌烦,上面那三坑你躺中一个就够喝一壶的。
仓库:https://github.com/HChaoHui/MiniMax-Video
如果你也想搓一个 MiniMax 视频生成的应用,可以从这里开始。
如果你也在用大模型搓项目,欢迎留言讲讲你的玩法,交流一下。