从 0 到 1:用 Cursor 搭一个开源证件照排版服务 zipress
原创 · 约 20 分钟阅读 · 阅读 --
Last updated on

从 0 到 1:用 Cursor 搭一个开源证件照排版服务 zipress

作者: Alex Xiang


古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 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 网格排版;统一排版与混排(上半大、下半小);裁切角标虚线
APIPOST /uploadPOST /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,与正文一并进仓库。

zipress 整体架构:浏览器、Next.js、Python 引擎

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

从上传到下载的主路径

仓库与目录

代码开源在 MITgithub.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_URLDATABASE_URLBETTER_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):

zipress 登录页

zipress 注册页

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

zipress 排版编辑器 · 登录后主界面

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

zipress 排版编辑器 · 上传后选择混排

批量生成上述截图可使用仓库内脚本(需本机已启动 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 级真实出图,可直接对照像素与裁切线。

统一排版(仅一种证件照尺寸铺满网格):

统一排版导出(6 寸相纸 + 1 寸)

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

混排导出(6 寸相纸,2 寸 + 1 寸)

测试原图(素材):

测试素材原图

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

zipress 编辑器 · 混排生成结果(6 寸 / 上半 2 寸 + 下半 1 寸)

登录、上传与参数面板等界面级截图见上文「界面一览」;若需自行替换配图,可将 PNG 覆盖 public/blog/zipress-from-zero/ 下对应文件,或参阅 README-screenshots.md

部署到服务器(Docker)

生产可用根目录 docker-compose.ymlENGINE_URL=http://engine:8000BETTER_AUTH_SECRET 用强随机串,数据库文件挂卷。webnext.config.tsoutput: "standalone" 与 Dockerfile 一致。开发可用 docker-compose.dev.yml(引擎热重载、前端 pnpm dev 监听 0.0.0.0)。

上线前替换密钥与公网 BETTER_AUTH_URL / NEXT_PUBLIC_APP_URL

未来 TODO(规划)

优先级
P1背景去除 / 换底色(如 rembg 或轻量分割),与现有「仅排版」解耦为可选步骤
P1人脸检测居中裁剪,减少手动画布
P2PDF 多页A4 多页拼版
P2排版模板预设(「6 寸 8 张 1 寸」等一键参数)
P2国际化(中/英 UI)
P3历史记录(登录用户保存最近一次参数)
P3简单图像调节(亮度/对比度/锐度)

实现顺序上会先保证引擎 单测与 golden 图 再叠 AI,避免「效果不可复现」。

小结

zipress 这类工具的价值在 尺寸与 DPI 对齐;Cursor 的价值在 把约束、排错、文档绑在同一条会话里。如果这篇对你有用,欢迎给 github.com/ax2/zipress 点 Star,也欢迎在博客或「字与码」点赞、收藏、转发——方便后来者判断项目是否还在迭代。