我用 Cursor 三天从零到可上线:uni-app + Fastify 全栈小程序复盘
我用 Cursor 在 三天左右 的日历窗口里,把一个能往生产环境推 的全栈小程序「药品过期提醒」从 0 推到可联调的主干:录入效期、提醒、微信侧能力等,技术栈是 uni-app (Vue 3) + Fastify + TypeScript + PostgreSQL ,pnpm monorepo (apps/backend、apps/miniprogram、packages/shared-types)。同一套范围,如果完全靠我自己啃文档、手写样板,主链路至少要 一周量级的人日 ;把重复劳动丢给模型之后,我把纯写码 + Code Review 收在 大约两个工作日 ,剩下时间主要花在微信控制台、真机联调和安全收口 上。下文按技术模块 拆开写,个人经验,供参考。

导语:这篇你能带走什么
我自己趟下来,觉得最值得带走的是这三件事:
- Cursor 怎么嵌进真实交付 :下文正文里我刻意只留 3 段完整 Prompt (脚手架、同步、登录),避免打断阅读;其余我常用的片段收在文末 附录,需要时展开复制即可。
- 小程序 + 自建后端 里我踩过的鉴权、同步、部署坑位;
- Cursor Rules (
.cursor/rules):我把「只用uni.*、迁移策略」写进仓库后,新开的对话明显少跑偏。
一、成品与技术边界
我做的是 药品过期提醒 :管药品与效期、到期前提醒,并结合微信能力触达(订阅消息等);登录侧我留了多种扩展(微信 / 抖音 / 邮箱 OTP 等),数据要云端同步,另外单独跑 scheduler 扫库发提醒。
技术上我没有做「列表 CRUD 演示页」:uni-app (Vue 3) + Fastify + PostgreSQL ,pnpm 拆包;后端有 JWT + 登出后 token_version 作废 、前端有 乐观更新与冲突回滚 、另外还有 定时任务入口 / 守护进程思路 和 生产向 env 校验,都是能对着 checklist 往真环境推的那种复杂度。
立项时我让模型先帮我拆里程碑与风险,那段 Prompt 我放在 附录 A,避免正文过长。
技术架构一览
我画的协作关系是:小程序端 ↔ 自建 HTTPS API ↔ PostgreSQL ;packages/shared-types 只做编译期 共享 DTO(零运行时依赖)。请求进 Fastify 后:auth 插件校验 JWT,并将 tv(token)与 DB token_version 对齐;药品写入走集中 LWW + 逻辑删 的 service。到期提醒由 scheduler (独立进程 / cron / PM2)读库后调微信订阅消息 ;大文件我走 upload 路由代理到对象存储(按环境启用)。
二、Monorepo 与脚手架
我自己单干的时候,光是 workspace、tsconfig、ESLint、Fastify 插件顺序、pages.json 对齐,小半天到一天 就没了。这次我直接把约束写满丢给 Cursor:apps/backend(Fastify + TS + tsx watch)、apps/miniprogram(uni-app Vue 3)、packages/shared-types(纯 DTO) ,要 pnpm-workspace.yaml、各包 package.json、基础 tsconfig/ESLint,以及后端的 JWT 签发/校验和插件式路由骨架。
Prompt(可直接复制)
text
请搭建 pnpm monorepo,包含三个包:
1)apps/backend:Fastify 4 + TypeScript。开发用 tsx watch;生产构建输出 dist。配置 ESLint + 偏严格的 tsconfig。用 app.ts 集中注册插件与路由(按业务域拆成多个路由文件)。提供 JWT HS256 签发/校验工具;Fastify 鉴权插件校验 Bearer JWT,从 DB 挂载 request.user;预留 token_version(tv)用于登出作废 JWT 的设计(若一次做不完,用注释说明后续接线)。
2)apps/miniprogram:uni-app Vue 3 + Pinia + TypeScript。最小 pages.json:首页/我的两个 tab,login、add 等页面放在分包骨架中。业务代码只用 uni.*,不要直接写 wx / tt。
3)packages/shared-types:纯 TypeScript DTO/类型,给前后端共用,零运行时依赖。
另外生成:pnpm-workspace.yaml、根 package.json 脚本(如 dev:backend、dev:miniprogram、lint、typecheck:backend,名称可微调但要在 README 片段里写清)。
硬性要求:后端启动时必须先跑「环境变量校验模块」,再加载任何会读 JWT 密钥、DATABASE_URL 等机密的模块;在 server 入口用简短注释写清启动顺序。
依赖尽量少;只用 pnpm。除脚手架外不要臆造产品功能。
产出价值 :Fastify 路由/插件习惯、JWT 骨架、能直接 dev 的 uni 最小工程。
人必须盯 :对照官方文档核对、路径别名、env 必须先于会读密钥的模块加载(避免 JWT 先于环境变量初始化)。
体感:纯手写脚手架约 半天~一天 ;我这边 AI 出首版 + 自己对照文档过一遍,多在 40~90 分钟(模板洁癖越重越久)。
三、核心业务与多端同步
我遇到的需求听起来很土:增删改、多端可看、弱网可用、联网对账。真正麻烦的是三件事:并发写谁说了算、乐观更新失败怎么回滚、删除用物理删还是逻辑删。
Cursor 一上来就给我套了行业里最常见的一套:LWW(updatedAt)+ is_deleted + 乐观更新 ;服务端 batch 返回冲突、客户端 rollback、登录后 batch/snapshot 对账。我最后落成的形态大致是 services/medicine-write.ts 管写入,Pinia 里 pushUpdateToServer 一类方法管乐观更新与回滚。
一个小高光:列表「闪一下又回去」
联调时我遇到过最典型的坑:本地先改了 Pinia,列表 UI 立刻变新,请求失败后数据弹回旧值 ,用户会觉得「闪一下」。一开始我以为是接口偶发 500,后来才发现有时是 409/冲突分支没统一进 rollback ,有时是 batch 里部分成功部分失败 ,我只回滚了整条请求而没有按 id 合并服务端返回。我让 Cursor 对着 pushUpdateToServer 和 batch 响应结构把「失败快照」收窄到请求前深拷贝 ,并在冲突时走服务端时间戳覆盖------这类 bug 用 AI 改 diff 很快,但根因一定要自己用日志和断点钉死,否则模型会越补越乱。
Prompt(可直接复制)
text
在现有 Fastify + PostgreSQL 后端、以及 uni-app(Vue 3 + Pinia)客户端上,实现「药品列表」域的完整写入与同步。
后端要求:
- CRUD 使用逻辑删除:is_deleted 布尔字段;参与同步的数据不要物理硬删。
- LWW:更新时比较 updatedAt(或客户端上报的 updatedAt);若服务端更新,则在统一 JSON envelope 里返回该 id 的冲突信息。
- 提供 POST batch:接收一批创建/更新/删除操作,按 id 返回结果与冲突列表。
- 提供适合登录后对账的 snapshot 或 list 接口(在注释里写清推荐客户端调用顺序)。
写入路径集中到单一 service 模块(例如 medicine-write.ts),路由层只编排参数与 HTTP。
前端要求:
- Pinia 乐观更新:先改本地状态 + 持久化,再调 API;失败则回滚到请求前快照。
- 登录成功后:先把本地待同步变更用 batch 推上去,再拉 snapshot/list 合并。
类型与 packages/shared-types 对齐。仅在并发边界不直观处加简短英文注释。
人必须盯 :字段与产品是否一致、列表查询与索引、空态/归档/重复 ID 等用例。AI 铺主干,人收口与测试矩阵。
体感:纯手写 2~3 天 ;我这边 大半天~1 天 能出可测主干(需求不乱改的前提下)。
四、登录与身份
这条链路我自己走过一遍:code → session → 手机号或邮箱 OTP → 落库 → JWT 。微信侧还有 AES、各种错误码,最耗时的永远是对齐文档和真机,不是写那几行 TypeScript。
Prompt(可直接复制)
text
实现微信小程序「手机号登录」:uni-app 前端 + Fastify 后端。
前端(uni-app):
- 用 uni.login 取 code;按当前基础库版本接入官方「手机号」能力(若模板/组件 ID 因项目而异,用 TODO 标出)。
- POST 到后端,grantType 为 mini_phone,字段与后端约定一致。
后端(Fastify):
- 调微信 code2Session 换 session;所需 env(appId/appSecret 等)写在说明里,代码里禁止写死密钥。
- 按微信文档解密手机号载荷;upsert 用户;签发 JWT,claims 含 sub(用户 id)、tv(token_version 整数)。
- POST /v1/auth/login 的请求体按 grantType 做可扩展联合类型;为后续 email_otp 预留分支或桩实现。
- 错误用统一 JSON envelope + 稳定业务错误码;不要把密钥或微信原始错误体原样返回给客户端。
文件拆分参考:wechat-mini-login.ts(服务)、auth-routes.ts(HTTP)、pages/pkg/login/login.vue(页面)。未写自动化测试时,附手动联调检查清单。
分层示例(与仓库对齐时可自查路径):services/wechat-mini-login.ts、routes/auth-routes.ts、pages/pkg/login/login.vue。
体感:业内首次接某家小程序登录,1~2 天联调 很常见;我这次有 AI 铺骨架,主要卡在 控制台、真机、env ,首登跑通多在 小半天量级。
五、提醒、任务与部署
定时扫库 → 模板消息 → 平台 API → 可观测;多实例还要想 advisory lock / 幂等 / 单 worker 。我让 Cursor 先出 daemon + dry-run + 结构化日志 的骨架,跑几个实例、重试策略、模板 ID 合规仍是我拍板。
调度相关的完整 Prompt 我放在 附录 B,正文不再重复贴大段。
六、打磨与线上边角
我后期的节奏基本是:把 tsc / ESLint 输出整段贴进对话 → 让模型按文件小步改 → 我自己做 diff review 。401 统一进 uni.request 封装、栈深决定 navigateBack 还是 switchTab;SMTP 把连接 / 问候 / 读写超时拆开,日志里能分清是 CONN 挂了还是 MAIL 阶段挂了 ------这些「修修补补」用的 Prompt 我收在 附录 C,正文只保留思路。
七、效率对比
下表我用 「人日」量级 粗估(1 人日 ≈ 8 小时有效产出),方便和上文「约一周 vs 约两天写码」对齐;不是精确会计。
| 模块 | 纯手写粗估 | AI 辅助 + 我 | 相对节省(粗) |
|---|---|---|---|
| 脚手架与工程化 | ~0.5 人日 | ~0.15 人日 | 约 七成 |
| CRUD + 同步与冲突 | ~3 人日 | ~0.5 人日 | 约 八成 |
| 登录与联调 | ~1.5 人日 | ~0.5 人日 | 约 七成 |
| 定时任务与部署脚本 | ~1 人日 | ~0.25 人日 | 约 七成五 |
| 打磨与修问题 | ~1 人日 | ~0.25 人日 | 约 七成五 |
| 合计(量级) | ~7 人日 | ~1.6 人日 | 约省四分之三工时 |
八、高回报习惯:Cursor Rules
我吃过亏:只在对话里口头说「别用 wx」,过两周新页面照样直连平台 API,import 风格也全乱。.cursor/rules/*.mdc 写进仓库之后,新开 Agent 明显少跑偏------官方文档见 Rules for AI。
起草 Rules 的 Prompt 我放在 附录 D 。我自己习惯先花 二三十分钟 写一版能跑的规则,后面每踩一个坑就 追加一条,比从头写长篇 README 划算。
九、AI 到底省了什么
我自己复盘下来,省下来的主要是这三块:
- 少翻文档的上下文切换------我把官方片段、错误栈、当前文件一股脑丢进对话,让模型先做「对齐初稿」;
- 少写重复样板------路由、DTO、简单 CRUD、配置骨架,我不再从零敲;
- 少一些低级遗漏------错误分支、类型、明显边界,模型能帮我扫一轮。
但我仍然要自己扛:架构取舍、安全(鉴权/密钥/日志)、关键测试、对最终 diff 签字 。工具再强,方向盘还是在我手里。
十、后续写作方向
- Cursor Rules 实战:uni-app 下
#ifdef与跨端坑怎么写进规则 - 小程序 + Fastify:401、tabBar、分包与登录回栈
- Monorepo 共享 DTO:pnpm workspace + TS 引用踩坑
附录:其余 Prompt 全文
附录 A:立项与边界(点击展开)
text
我要做一款微信小程序「药品过期提醒」,并自建后端。
MVP 范围:维护药品与效期;到期前提醒用户(订阅消息或等价能力);登录后多端数据同步;至少支持一种登录方式(微信手机号),后续可扩展邮箱 OTP。
明确不做(MVP):不对接药房系统、不依赖 OCR、不做复杂运营后台(除非极简单占位页)。
技术约束:客户端 uni-app Vue 3;服务端 Fastify + PostgreSQL;pnpm monorepo,独立 shared-types/DTO 包;JWT 鉴权;逻辑删除 + LWW 同步;生产环境必须做 env 校验、缺关键变量时 fail-fast。
请输出:可落在「约三个日历日」内的主线里程碑(脚手架 → CRUD/同步 → 登录 → 提醒/任务 → 打磨上线),并列出需要提前注意的风险(审核、模板 ID、scheduler 幂等与多实例等)。
附录 B:提醒调度与部署(点击展开)
text
在现有 Node + PostgreSQL 栈上,增加「药品到期提醒」调度能力。
交付物:
- 独立入口脚本(或小型 daemon),可被 cron 或 PM2 拉起:查询 DB 中到期待提醒记录,组装微信订阅消息载荷,调用发送 HTTP(若模板 ID 未配置,用清晰 TODO 桩掉真实请求)。
- 每次运行打结构化日志:开始/结束、处理条数、失败原因码。
- 多实例幂等:在注释里对比 Postgres advisory lock 与「单 runner 标记」等方案;任选一种简单防护,避免多进程下重复触达,并写清取舍。
- 极简 PM2 ecosystem.config.cjs 或 Dockerfile 注释二选一(示例即可)。
环境变量:DATABASE_URL、微信相关变量、LOG_LEVEL;生产/类生产模式下缺关键变量则 fail-fast。
提供 dry-run 模式:只打日志描述将发送什么,不真实调用发送接口。
附录 C:Lint / 401 / SMTP 超时(点击展开)
Prompt C1 --- 修类型 / Lint
text
只修复下面贴出的 TypeScript / ESLint 报错对应问题。最小 diff,不要顺手重构无关代码。优先遵守仓库 README 与 Cursor Rules 里已有约定。
--- 从下一行起粘贴 tsc 或 eslint 完整输出 ---
Prompt C2 --- 401 统一处理
text
在 uni-app 工程里,把 HTTP 封装集中到现有 API 助手(路径以仓库为准,常见为 utils/api.ts)。
要求:
- 收到 HTTP 401:清除本地 token;只弹一次用户可见的 modal;若多个并发请求同时 401,要做去重,避免连弹多次。
- 用户点掉 modal 后:若页面栈深度 > 1 则 navigateBack(),否则 switchTab 回首页或 reLaunch 到登录页------选一种行为写死即可,并用简短英文注释说明原因(便于代码审阅)。
- 不要新增 npm 依赖。
Prompt C3 --- SMTP / 发信超时拆分(后端)
text
在邮件发送客户端(SMTP 或厂商 SDK)里,把超时拆细:连接超时、问候阶段超时、socket 读写超时;分别暴露为环境变量并给合理默认值。日志里要能区分失败发生在 CONN 还是 MAIL 阶段;禁止打印凭据或邮件正文。
附录 D:起草 Cursor Rules(点击展开)
text
为本仓库起草一份 .cursor/rules/*.mdc(Markdown),给 Cursor Agent 用:
- 强制:代码与注释、标识符、git commit 用英文;用户可见界面文案可中文。
- Vue SFC 文件名 kebab-case;业务代码只用 uni.*,禁止直接写 wx / tt。
- 定时器必须在 onUnload 清理。
- 数据库变更只用版本化迁移,禁止把删表重建当生产升级路径。
- 用短条目写清 monorepo:apps/backend、apps/miniprogram、packages/shared-types 各自职责;后端启动须先校验 env,再加载会读 JWT 密钥的模块。
- 可补充本项目的鉴权(JWT + token_version)与同步(LWW + 逻辑删)一句话,避免模型乱改核心语义。
输出:单个 .mdc 文件可直接保存的正文。
掘金若不支持 <details> 折叠,可将本节整段复制到编辑器「代码块」或移入语雀/飞书后再链回。
结尾
写完这篇我最大的感受是:AI 没有替我负责 ,它只是把「搜文档、起样板、改低级错」从时间轴上挪走了;真正费神的还是微信那条长链路、同步语义、以及上线前我敢不敢签字 。如果你也在做小程序 + 自建后端,欢迎 评论区 丢你的卡点,我能帮上忙会回。觉得有用可以 点个赞,也方便后面还想写续篇时有个动力。