从 0 到 1:用 Cursor 搭一个开源证件照排版服务 zipress
古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。更完整的更新写在微信公众号「字与码」:工作经历、对新技术的想法,以及这些年走弯路的记录,会不定期发在那里。若觉得博客对你有用,欢迎顺手关注。
想在家里或文印店打印一版证件照,常见需求是:手里只有一张电子版,却要铺满整张相纸(5 寸、6 寸或 A4),还要按 1 寸、2 寸混排。网上工具不少,要么广告多,要么不能自托管。于是干脆从零做了一个开源小项目 zipress:上传一张照片,选纸张和排版模式,导出 300 DPI 的整版图,带裁切辅助线。
这篇文章的重点不是「又做了一个小工具」,而是:在 Cursor 里怎么用多轮对话把需求收敛成可交付的仓库——包含真实 prompt、每步进展、踩坑与修复。文内流程图用 PNG 图片(站点对 flowchart LR 一类 Mermaid 支持不稳定),头图则是 左侧 zipress 品牌区 + 右侧抽象排版意象 的合成图;若你把本机首页截图放到指定路径,可一键重生成头图(见文末「资源与截图」)。
用 Cursor 做完这件事:真实对话与进展
下面摘录的是会话里实际出现过的需求与方向(措辞略有压缩,技术要点未改)。进展用一句话标在每次提问之后,方便你对照自己的开发节奏。
我:要开发一个服务,实现证件照的排版,上传一张证件照(1 寸或 2 寸),可以选择输出排版格式……最后能保存为一张图片。参考类似的服务,可以补充功能。
Cursor:给出方案对比(纯 Pillow / 基于 HivisionIDPhotos / 纯前端 Canvas),推荐 FastAPI + Pillow 做引擎,Next 或简单前端;列出标准 mm/px、API 草图与分阶段路线图。
我:1、需要有新的代码仓库,在 ax2 账号下创建一个开源的项目;2、需要有账号体系;3、本项目的所有开发流程都需要完整记录,用于在 www.zicode.com 创建文章;4、界面需要现代化、工业化。
Cursor:确认邮箱登录、better-auth、Next + shadcn、项目名 zipress;更新方案为 monorepo、开发日志写 zicode 系列文。
我:开始执行。
Cursor:创建 GitHub 仓库 ax2/zipress,初始化 engine(布局引擎、裁切线、FastAPI)、web(Next、认证、暗色 UI)、测试与 Docker;推送初始 commit。
我:混排的时候生成报错。
Cursor:定位到混排必须传 `large_size` / `small_size`,前端选了「混排」却没传参;在 store 与 `/app` 增加两个尺寸选择器,并在请求里附带字段。
我:上传照片后,已上传照片的位置没有显示照片。
Cursor:引擎返回 `/uploads/...` 相对引擎主机,浏览器却向 Next 要静态文件;增加 `getEngineAssetUrl`,走 `/api/engine/uploads/...` 代理。
我:将本次会话从 0 开始总结成一篇博客……(略)继续。
Cursor:在 zicode 写一篇长文,API 实测小狗素材出图;构建通过。后续你补充 Windows 截图路径与头图合成需求。
如果你也在用 Cursor:把约束一次说清(仓库、认证、文章、视觉)比零散的「再改改」省轮次;报错贴现象 + 复现路径,Agent 更容易对准根因而不是糊一层 try/except。
当前已实现的特性(截至仓库 main)
| 模块 | 内容 |
|---|---|
| 引擎 | 证件照 1 寸 / 小 2 寸 / 2 寸 / 大 2 寸;相纸 5 寸 / 6 寸 / A4;300 DPI 网格排版;统一排版与混排(上半大、下半小);裁切角标虚线 |
| API | POST /upload、POST /layout(query photo_id)、预览与下载 JPEG |
| 前端 | 落地页;邮箱注册登录(better-auth + Drizzle + SQLite);/app 三栏编辑器(上传、预览、参数);/api/engine 反向代理 |
| 工程 | uv + pytest;pnpm + ESLint;Docker Compose 开发/生产说明 |
混排、缩略图代理属于「联调必现」类问题,已在上面 Cursor 摘录里标出。
目标与边界(回顾)
- 要做:标准尺寸、网格与混排、裁切线、打印级 DPI、账号、可 Docker。
- 暂不做:AI 抠图、美颜、模板市场——留给下文 TODO。
技术选型:图片 Python + Pillow;产品与账号 Next.js App Router + shadcn/ui;同源访问 用 Route Handler 把 /api/engine/* 指到 FastAPI,避免浏览器直连 8000 端口与静态资源跨域。
整体架构(流程图图片)
站点内嵌 Mermaid 的 flowchart LR 在部分构建环境下渲染不稳定,这里改为 Kroki 导出的 PNG,与正文一并进仓库。

用户主路径(流程图图片)

仓库与目录
代码开源在 MIT:github.com/ax2/zipress。
web/:Next.js(登录注册、落地页、排版编辑器)。engine/:FastAPI + 排版与导出。docker-compose.yml/docker-compose.dev.yml:编排前后端。
头图说明(左 60% 落地页 + 右 40% 意象)
头图文件:/heroes/zipress-from-zero-hero.png。合成规则:左侧约 60%(480px)为 zipress 落地页首屏截图(public/blog/zipress-from-zero/zipress-hero-left-source.png);右侧约 40%(320px)为与品牌色一致的抽象配图(public/heroes/zipress-from-zero-hero-right-ai.png,可由 AI 生成后替换)。整体画布 800×420,便于博客与公众号封面。
重新生成合成头图(需已安装 Pillow,例如用带依赖的 Python 环境):
cd astro-site
/home/alex/.local/python.venv/bin/python scripts/build-zipress-blog-assets.py
若未放置 zipress-hero-left-source.png,脚本会回退到 homepage-screenshot-raw.png,再回退到内置矢量占位左栏。右侧若缺少 AI 图,会回退到仓库内的 zipress-from-zero-hero-abstract-only.png。说明全文见 public/blog/zipress-from-zero/README-screenshots.md。
本地跑起来(开发)
引擎(默认 8000):
cd engine
uv sync
uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
前端(默认 3000,.env.local 配置 ENGINE_URL、DATABASE_URL、BETTER_AUTH_SECRET 等):
cd web
pnpm install
pnpm dev
注册后进入 /app 操作。引擎未启动时,页面会提示无法连接排版服务。
提示: 本地开发时请将浏览器访问地址与 NEXT_PUBLIC_APP_URL / BETTER_AUTH_URL 保持一致(例如统一用 http://localhost:3000)。若用 127.0.0.1 而环境变量仍是 localhost,better-auth 的会话域名不一致,会出现无法注册/登录的现象。
界面一览(截图)
登录与注册为同一套暗色卡片风格(shadcn/ui + Tailwind):


登录后进入 /app 三栏排版编辑器:左侧上传区、中间预览、右侧参数(证件照尺寸、相纸、模式等):

上传照片并切换到 混排 后,会出现「大尺寸(上半区)」「小尺寸(下半区)」等额外选项,与统一排版形成对比:

批量生成上述截图可使用仓库内脚本(需本机已启动 zipress 的 web 与引擎,且 Playwright 已安装 Chromium):
cd astro-site
/home/alex/.local/python.venv/bin/python scripts/capture_zipress_screenshots.py
真实导出:统一排版 vs 混排
同一张测试素材(生活照作「源图」),分别调用引擎 统一(1 寸 + 6 寸)与 混排(2 寸 + 1 寸 + 6 寸)导出如下——这是 API 级真实出图,可直接对照像素与裁切线。
统一排版(仅一种证件照尺寸铺满网格):

混排(上半区 2 寸、下半区 1 寸):

测试原图(素材):

Web 编辑器内预览(混排生成结果): 在 /app 选好相纸与「混排」尺寸后点击「生成排版」,中央画布即显示与引擎一致的整版预览(含裁切线),可与上文 API 导出的 JPG 对照。

登录、上传与参数面板等界面级截图见上文「界面一览」;若需自行替换配图,可将 PNG 覆盖 public/blog/zipress-from-zero/ 下对应文件,或参阅 README-screenshots.md。
部署到服务器(Docker)
生产可用根目录 docker-compose.yml:ENGINE_URL=http://engine:8000,BETTER_AUTH_SECRET 用强随机串,数据库文件挂卷。web 的 next.config.ts 需 output: "standalone" 与 Dockerfile 一致。开发可用 docker-compose.dev.yml(引擎热重载、前端 pnpm dev 监听 0.0.0.0)。
上线前替换密钥与公网 BETTER_AUTH_URL / NEXT_PUBLIC_APP_URL。
未来 TODO(规划)
| 优先级 | 项 |
|---|---|
| P1 | 背景去除 / 换底色(如 rembg 或轻量分割),与现有「仅排版」解耦为可选步骤 |
| P1 | 人脸检测居中裁剪,减少手动画布 |
| P2 | PDF 多页、A4 多页拼版 |
| P2 | 排版模板预设(「6 寸 8 张 1 寸」等一键参数) |
| P2 | 国际化(中/英 UI) |
| P3 | 历史记录(登录用户保存最近一次参数) |
| P3 | 简单图像调节(亮度/对比度/锐度) |
实现顺序上会先保证引擎 单测与 golden 图 再叠 AI,避免「效果不可复现」。
小结
zipress 这类工具的价值在 尺寸与 DPI 对齐;Cursor 的价值在 把约束、排错、文档绑在同一条会话里。如果这篇对你有用,欢迎给 github.com/ax2/zipress 点 Star,也欢迎在博客或「字与码」点赞、收藏、转发——方便后来者判断项目是否还在迭代。