ARTICLE · 1325388
【设计文档 V1.0】ComfyUI 模型迁移系统搭建实录
01 起点:一个很具体的问题
我的 ComfyUI 模型分布在三个位置:ComfyUI 的共享模型目录、SD 的模型目录,以及一个专门用来归档的迁移目标目录。
真正麻烦的不是“复制文件”,而是工作流 JSON 里通常只写:
xxx.safetensors它不写目录,也不写完整路径。同一个文件名可能出现在 loras、vae或 diffusion_models下;目标目录里可能已经有一个同名但版本不同的文件;一个模型还可能被多个工作流同时引用。
手动迁移时,最怕三件事:
覆盖了目标端已有的另一个版本; 工作流还在引用,源文件却被删了; 以为迁移成功,其实只是“目标里本来就有”。
最初只有一份设计文档,功能列得很全:解析、共享分析、迁移、日志、配置。真正开始写代码时,我先把目标压缩成一个可以验证的闭环:
导入工作流 → 解析模型 → 看清共享关系 → 安全迁移 → 有日志可查。
02 先划边界,再写代码
这个工具从一开始就不是“平台”,而是本地单用户工具。边界定得越清楚,后面越不容易走偏。
只监听 127.0.0.1,不做账号、不做云同步;SQLite 只做索引,文件系统才是事实来源; 默认复制,剪贴必须显式开启并再次确认; 迁移只写入三个已配置根目录之一,路径穿越直接拒绝; 不追求“大而全”,优先把最危险的迁移路径做对。
技术栈也不复杂:FastAPI + SQLite 做后端,Astro 构建静态前端,由 FastAPI 同源托管。生产环境只有一个进程,不需要额外部署 Web 服务器。
这个选择牺牲了“分布式扩展”,换来的是一句话就能启动、数据完全在本机的确定性。
03 模型身份:整个系统的地基
系统里所有表、树、迁移动作都围绕一个 rel_path,但它不能直接等于工作流里的引用。
最终统一成:
models/<类别>/<文件名>导入工作流时,后端会扫描已配置的模型根目录,把只有文件名的引用解析成这个身份。找不到时保留:
models/(未找到)/<文件名>并且明确标记为“未找到”,而不是假装成功。
这里有两个容易踩的坑。
第一,迁移目标也要参与解析。如果只扫 ComfyUI 和 SD,已经迁移到目标目录的模型会被误报为“文件缺失”。后来引入了源 + 目标并集清单,工作流树里能区分“已迁移”“已在目标”“未迁移”“未找到”。
第二,路径要统一。Windows 用户会粘贴反斜杠,JSON、SQL、日志、HTML 属性各有各的转义规则。系统在入口统一转成正斜杠,UNC 前缀保留为 //server/share。这看起来是小事,但它决定了后面所有比较、分组和日志是否可靠。

主页面
04 迁移不是复制文件那么简单
一个安全的迁移,至少要通过四道关。
第一道:预检。先算需要多少字节、目标盘还剩多少;空间不够直接拒绝,不等到复制到一半才失败。
第二道:同名冲突。目标端已有同名文件时,不能默默覆盖。系统会比较大小,并计算“大小 + 首尾 1 MB”的快速指纹;大小不同或指纹不同,就在弹窗里列出风险,必须勾选「覆盖」才能继续。
这里没有用完整 SHA256,是一个有意的取舍:预检发生在用户选择目标位置的瞬间,GB 级文件全量哈希会让弹窗明显变慢。快速指纹不是密码学证明,但足以把“同名不同内容”的风险摆到用户面前。完整哈希留到后续版本。
第三道:原子写入。复制先写到目标目录的临时文件,完成后再用 os.replace替换。中途失败或取消,不会留下半截的正式文件;临时 .part会被清理。
第四道:删源门控。默认只复制。只有设置页开启「允许删除源文件」后,界面才出现「剪贴」入口;即使出现,也要用户明确勾选并点击「开始剪贴」。
还有一个容易被忽略的边界:当目标位置和源位置是同一个目录时,剪贴没有任何意义。早期版本会出现“点击开始剪贴,弹窗关闭,但什么都没发生”。后来预检直接返回 same_location,前端禁用确认按钮并提示原因。

覆盖风险确认
05 从同步请求到“看得见的进度”
最初迁移是一个同步请求:浏览器发起,后端复制,完成后返回。
小文件没问题,GB 级模型就会让页面长时间没有反馈。用户不知道是在复制、卡住,还是已经失败。
后来改成了后台任务:
POST /api/migrate/tasks → task_idGET /api/migrate/tasks/:id → 进度 / 速度 / ETA / 逐文件结果POST /api/migrate/tasks/:id/cancel → 取消复制循环按 4 MB 分块,前端每 300 ms 轮询一次。页面上能看到字节、百分比、速度和预计剩余时间;取消会停止循环并清理临时文件,已完成的文件保留。
为什么用轮询而不是 SSE?因为这是本地单用户工具,任务数量很少。轮询实现更简单,断线后重新打开页面也能恢复状态;SSE 会多一层连接生命周期,收益不大。
在 1 GB 演示文件上,进度从 0% 走到 100%,中间能看到约 38%、79% 的采样点。这个测试后来也成了打包后的回归用例。

迁移进度
06 页面是产品的另一半
后端正确,不代表工具好用。
最终前端有五个 Tab:主页面、路径配置、模型文件、工作流、日志记录。
模型文件按目录分组,默认折叠,支持一键展开/折叠、搜索、状态筛选、大小排序和引用数; 工作流页可以搜索工作流/模型/目录,默认折叠模型树,显示 JSON 文件的真实修改时间; 工作流可以「打开」所在目录,也可以删除记录,并可选同时删除本地 JSON; 日志页显示源/目标的绝对路径,点击文件名即可复制; 扫描预览会列出目录下全部 JSON,支持全选/反选,不再只显示前几行。
这些细节大多来自真实使用中的反馈,而不是最初的设计稿:
浏览器拦截原生 confirm(),导致删除按钮“点了没反应”——改成页面内确认弹窗;扫描列表被 max-height: 300px截断,用户以为只扫到几个文件——改成完整渲染并加全选;页面缓存了旧 JS,新功能不生效——入口页加 Cache-Control: no-store;日志删除按钮文字被挤成竖排——加宽操作列并禁止换行。
它们不是“核心算法”,但决定了用户是否敢把模型交给这个工具。

模型文件列表

扫描预览
07 64 个测试带来的确定性
迁移工具最不能接受的,是“大多数时候没问题”。
所以关键路径尽量做成可独立测试的 seam:
路径规范化; 工作流引用提取(UI 格式、API 格式、subgraph); 模型解析与多根去重; 空间预检与同名冲突; 原子复制、取消清理、路径穿越拒绝; 异步任务进度、完成、取消; 配置保存、扫描导入、工作流删除本地 JSON; HTTP 全流程。
最终 pytest是 64 项通过。测试不能证明没有 bug,但能保证每次改 UI、改迁移流程时,那些最危险的行为没有被悄悄改坏。
08 打包最后一公里:被 Smart App Control 拦下
功能完成后,第一版用 PyInstaller 打成了 Windows exe:图标、版本信息、内置 Python,理论上双击就能用。
但在这台测试机上,exe 被 Windows 直接拦截:
Code Integrity determined that a process attempted to load ...that did not meet the Enterprise signing level requirements(Event ID 3077)这不是代码错误,而是分发问题:未签名 exe 在启用了 Application Control / Smart App Control 的机器上会被拒绝。买代码签名证书可以解决,但对一个刚发布的本地工具来说成本太高。
于是换了一条路:
使用 Python 官方签名的 3.13 嵌入式运行时; 把 FastAPI、uvicorn、pydantic 等依赖装进 runtime/Lib/site-packages;用 启动.cmd调用runtime/python.exe launcher.py;浏览器自动打开,数据保存在同级 data/。
最终便携包约 14.7 MB,不需要用户安装 Python。已经在启用 Application Control 的机器上验证启动;也测试了带空格、带中文的目录路径。
发布包还带了 SHA256 校验和。它不是“绝对安全”的证明,但至少让下载后的完整性可以被检查。
09 V1.0 现在能做什么
解析 ComfyUI 工作流,识别模型引用和跨工作流共享; 按目录查看模型,支持折叠、搜索、引用数、批量迁移; 迁移前做空间预检和同名冲突检查; 原子复制,默认保留源文件;剪贴需要显式开启; 后台任务显示进度、速度、ETA,可取消; 覆盖同名不同版本前必须勾选确认; 工作流可直接打开 JSON 所在目录,删除时可选删除本地文件; 日志记录成功/失败,支持筛选、清空、复制文件名。
下载、解压、双击 启动.cmd,然后在「路径配置」里填好自己的三个模型目录和工作流目录,就可以开始用。
10 下一步
V1.0 还不是终点。接下来更值得做的,不是继续堆按钮,而是把“可信”做得更彻底:
完整 SHA256 校验,替换快速指纹; 任务状态落 SQLite,服务重启后不丢失; 生成 extra_model_paths.yaml参考片段;条件允许时做代码签名,减少安全软件提示。
回头看,这个项目最重要的变化,不是从 0 写到 1,而是把“迁移”从一个动作,拆成了一串可解释、可预检、可取消、可追溯的选择。
工具的价值,有时不是替用户做决定,而是让风险变得可见。

工作流列表
如果你也在多个模型目录之间搬文件,希望这套“先预检、再确认、可取消”的思路,能帮你少踩几个坑。















































































