完整教程

使用 AI 进行软件开发编程的实战经验分享:以“掼蛋升级 / 转蛋计分”Web 软件为例

这篇文章不是一篇“AI 很强、以后程序员都不用写代码了”的空泛感想,也不是把几个提示词复制出来就算教程。我更想把一次真实的小型软件开发过程摊开来说:从一个具体需求出发,怎么让 AI 参与需求拆解、代码生成、排错、重构、部署和迭代;哪些地方 AI 确实省时间,哪些地方它会自信地犯错;开发者应该怎么掌舵,才能把 AI 从“会聊天的代码补全器”变成一个可协作的工程助手。

案例是一个“掼蛋升级 / 转蛋计分”的 Web 网络版计分软件。它不是演示项目,而是围绕真实使用场景做出来的工具:有人要新建比赛,有人要记录每一局分数,有人要看排名,有人要清算积分,有人要把赛局分享给别人,有人希望在手机上像 App 一样打开,有人还要把数据导出、导入、部署到服务器。项目里有前端页面、业务规则、服务端 API、SQLite 数据库、登录会话、分享链接、Windows 和 Linux 部署脚本,也有一堆很普通但很真实的问题:登录过期提示死循环、导入后列表不刷新、弹窗打开后底层页面还能滚动、网络卡顿时没有反馈、PWA 图标不对、数据库数据量变大后要不要建索引。

正因为它普通,才适合拿来讲 AI 编程。真正的软件开发,大多数时间不是写一个惊艳的算法,而是在规则、界面、状态、数据、部署、用户反馈之间来回对齐。AI 在这种工作里特别有用,但前提是你不能把它当成自动驾驶。更好的方式是把 AI 当作一个很勤快、反应很快、但需要你不断校准方向的搭档。

一、先说结论:AI 编程真正提升的是“工程推进速度”

我这次最大的体会是:AI 不会自动替你成为产品负责人、架构师、测试人员和运维人员,但它能把每个角色的许多重复劳动压缩掉。

它能帮你把一句“导入数据后列表还是旧的”拆成状态缓存、路由返回、服务端刷新、前端渲染四个问题;也能帮你根据现有代码风格补出几十个细节,比如按钮状态、错误提示、数据库索引、安装脚本里的端口检查。它尤其擅长在已有上下文里做“连续小步迭代”:先读代码,再定位函数,再改一小块,再跑起来验证。

但它不擅长替你判断“这个功能是不是用户真正需要的”。比如“比赛重复导入”的判断规则,AI 可以给出三五种方案:按比赛 ID、按名称和创建时间、按模式和玩家名单、按轮次摘要生成指纹。可最后到底用哪一种,必须结合使用场景决定。如果用户经常复制一场比赛模板再改名,那么只按玩家名单去重就会误杀;如果用户经常从旧版本导入同一份数据,那么只按 ID 去重又可能漏掉重复。AI 可以帮你穷举,不能替你承担产品判断。

所以我的经验可以压缩成一句话:AI 编程不是把人从工程里拿掉,而是把人从大量低价值敲字、查找和机械改动里解放出来,让人把注意力放在规则、边界、取舍和验收上。

二、项目背景:为什么一个计分工具也值得认真做

这个软件服务的是一个很具体的场景:掼蛋升级和转蛋比赛计分。

掼蛋升级的核心不是简单加减分,而是“升级”。比赛过程里,每一局会有胜方、升级步数和当前等级。项目中定义了两个升级规则:ONE_TWO_THREETWO_FOUR_SIX,也就是常见的“一二三”和“两四六”规则。等级从 2 一直走到 A1A2A3,前端代码里用一个 LEVELS 数组维护。每局记录之后,系统要能根据历史记录推导当前等级、胜负状态、最终赢家,还要支持回退上一局。

转蛋计分则更像多人积分账本。每局有分值、倍数、最终分,有赢家和输家,所有人的总分要实时累计。比赛结束后还要做积分清算,也就是把负分的人和正分的人配对,算出“谁给谁多少”。这个清算看似简单,其实用户体验很关键:你不能只给一堆数字,最好能让玩家一眼看懂付款方向。项目里就做了付款人、收款人和箭头连线的清算页面。

这两种玩法放在一个软件里,会带来几个工程问题。

第一,业务模式不同,但入口和基础结构相同。新建比赛、列表展示、详情页、分享、导入导出、删除、重命名,这些功能对掼蛋和转蛋都存在;但详情页内容、记录方式、结束条件又完全不同。项目里用 ZHUANDANGUANDAN_UPGRADE 两个模式常量区分,很多渲染函数会先判断模式再走不同逻辑。

第二,前端状态复杂。这个项目的前端主要集中在 app.js,包括路由、页面渲染、弹窗、toast、比赛状态、用户设置、导入导出、分享页、横竖屏控制等。它不是现代框架项目,而是原生 JavaScript 组织的单页应用。这种代码对 AI 很有挑战,因为没有 React/Vue 那种组件边界,AI 必须先理解现有函数之间的调用关系,不能一上来就凭习惯引入框架。

第三,网络版要有服务端。早期本地计分工具只要 localStorage 就能保存数据,但网络版需要账号、会话、跨设备数据、分享链接和服务器部署。项目后端使用 Node.js 的 node:sqlite,数据存在 SQLite 里。服务端提供两个主要 API:/api/account 处理注册、登录、会话校验、修改昵称、退出登录;/api/match 处理比赛列表、获取详情、创建、保存、删除、分享和访问分享。

第四,用户主要在手机上操作。计分现场不是坐在办公室里慢慢点,而是饭桌、活动室、比赛间隙,手机屏幕小,网络可能不稳定,误触和等待都很影响体验。所以像“弹窗打开后底层页面不能滚动”“网络慢要提示”“登录过期不能疯狂弹 toast”这些问题,在普通后台系统里可能只是小瑕疵,在这个工具里就是实打实的体验问题。

这就是 AI 参与开发时必须先知道的背景。你不给 AI 这些上下文,它就只能写出“看起来对”的代码;你把这些上下文喂进去,它才有可能做出“用起来对”的改动。

三、第一步不是写代码,而是把需求变成可执行的任务

很多人用 AI 编程的第一个误区,是直接发一句:“帮我做一个计分软件。”这类提示词通常会得到一个漂亮但空洞的 Demo:四个输入框、一个添加按钮、一张表。对真实项目来说,这还远远不够。

更有效的方法是把需求拆成三个层级。

第一层是目标:这个软件给谁用,解决什么问题。比如:“给掼蛋升级和转蛋比赛使用,手机端优先,支持多人计分、比赛记录、积分清算、数据保存、分享赛局。”

第二层是规则:什么叫一局,什么叫结束,什么数据必须保存,什么操作可以撤回。比如转蛋每局需要记录分值、倍数、赢家、输家;掼蛋升级需要记录胜方和升级步数;比赛结束后不能继续计分;回退只能删除最后一局;清算只对转蛋积分有意义。

第三层是工程约束:用什么技术,部署在哪里,数据怎么存,兼容哪些环境。这个项目里,前端是原生 HTML/CSS/JavaScript,后端是 Node.js,数据库是 SQLite,配置在 config.json,默认端口 3000,可以通过 Windows 脚本本地启动,也可以打 Linux 包部署到宝塔服务器。

当你把需求整理成这三层后,AI 的输出质量会明显变好。因为它不再需要猜“你到底想做什么”,而是可以围绕明确边界做实现。

我在这个项目里经常使用的一种问法是:“先读现有代码,不要急着改。请告诉我相关功能分布在哪些文件、哪些函数、你准备改哪里。”这句话很重要。它能阻止 AI 直接生成一坨和项目风格不一致的新代码。

比如要解决“登录过期 toast 死循环”,AI 需要先找到:

net.js 里请求失败时如何识别 token 失效;

app.js 里启动时如何校验登录;

ensureOnlineMatches 里什么时候加载比赛列表;

render 是否会因为加载失败反复触发;

toast 是在哪里调用的。

只有把这些串起来,才能判断死循环是不是因为“渲染触发加载,加载失败 toast,状态没被标记为失败,下次渲染继续加载”。如果 AI 没读代码,直接说“加一个防抖”或者“把 toast 去掉”,就只是掩盖问题。

四、让 AI 先做“代码考古”,别一上来就做“代码创作”

在旧项目或迭代项目里,AI 最有价值的第一件事不是写代码,而是读代码。

这个计分软件的文件并不多,但 app.js 比较长,承担了大部分前端业务。如果人工一点点翻,很容易漏。AI 的优势是可以快速搜索关键词,建立地图。比如它会先找 ZHUANDANGUANDAN_UPGRADEroundsettleshareimporttoastmodalroute 这些关键词,然后把相关函数串起来。

通过这种方式,我们能得到一个项目地图:

index.html 是技术经验分享首页;app/index.html 是计分程序入口,加载 app/styles.cssapp/qrcode.min.jsapp/net.jsapp/app.js

app/app.js 是前端主逻辑,包含模式常量、状态管理、路由渲染、比赛列表、详情页、计分表单、清算、分享、登录页、导入导出、弹窗和 toast。

app/net.js 是 API 客户端,统一处理会话、请求超时、慢请求提醒、token 失效识别。

server/server.js 是 HTTP 服务,负责静态文件和 API 路由。

server/db.js 是业务数据层,处理账号、会话、比赛、分数、分享链接。

server/schema.sql 是 SQLite 表结构和索引。

start-local.ps1 是 Windows 本地启动脚本,会检查端口、启动服务,并自动打开浏览器。

deploy/linuxdeploy/windows 是发布、安装、服务化运行相关脚本和说明。

这张地图的价值很大。后面无论做功能还是排错,都不会在文件里迷路。

我建议每次让 AI 接手一个已有项目时,都先让它输出类似“代码地图”的内容。哪怕它要花几分钟读文件,也比直接改代码可靠得多。软件开发里最贵的不是敲代码,而是改错地方。

五、案例一:从本地版到网络版,AI 适合做“迁移脚手架”

计分工具从本地单机变成网络版,本质上是把数据从浏览器本地存储迁移到服务端。这个过程看起来只是“加一个后端”,实际会牵动很多地方。

本地版可以直接 localStorage.setItem,网络版不能这么做。网络版需要:

用户注册和登录;

会话 token 保存和校验;

服务端保存比赛和分数;

前端每次进入首页加载服务端比赛列表;

保存失败时给提示;

多设备同时编辑时避免覆盖;

分享链接可以让未登录用户查看赛局;

导入数据时要逐场上传。

AI 在这类迁移里很适合做“脚手架”。比如后端服务可以设计得很轻:server.js 只负责解析请求、限制请求体大小、分发 /api/account/api/match,静态资源直接从项目根目录返回。真正的业务放到 db.js。这样职责比较清晰,AI 修改时不容易把所有东西混在一起。

项目里服务端配置也保持简单:config.json 控制端口、数据库目录、数据库文件名、会话有效期和分享链接有效期。默认端口是 3000,数据库默认在 data/scorekeeper.sqlite。这类配置不要硬编码在代码里,因为一旦部署到服务器,端口、路径、反向代理都会变。

数据库设计也体现了“够用但不复杂”的原则。主要表包括:

users:保存用户手机号、昵称、密码盐和哈希。

user_sessions:保存 token、用户 ID、过期时间。

matches:保存比赛主体,包括名称、模式、玩家、规则、清算时间、版本号等。

match_score:保存每一局分数或升级记录。

match_sharelinks:保存分享 token、比赛 ID 和过期时间。

这里有一个值得分享的经验:不要让 AI 一开始就设计一个过度范式化的数据库。计分软件的数据规模有限,业务对象也比较明确。玩家列表、转蛋分层、每局 entries 等结构用 JSON 存在字段里,是一个务实选择。这样可以减少表连接和迁移成本,同时又把比赛主体和每局记录拆开,避免一个比赛所有历史都塞在单个大 JSON 里。

当然,JSON 字段也意味着某些查询不能靠数据库直接筛选,比如“查某个玩家参与过的所有比赛”就不方便。但这个项目当前的主要查询是按用户列比赛、按比赛读全部局数、按分享 token 读比赛,所以这个设计是匹配需求的。

AI 在这里的价值是快速生成基础 CRUD、参数校验、JSON 序列化、事务写入和错误响应。人的价值是决定边界:哪些字段结构化,哪些字段 JSON 化;哪些 API 需要登录,哪些 API 可以公开;数据冲突时是强制覆盖,还是用版本号保护。

这个项目里 matchesrev 字段,保存比赛时如果前端传来的 rev 和数据库不一致,服务端返回 409,提示“数据已被其他设备更新”。这是一个小但重要的设计。没有它,多设备同时计分时很容易后保存的人覆盖先保存的记录。AI 很容易写出“直接 update”的代码,但真实工具需要考虑这种冲突。

六、案例二:登录会话不是“能登录”就完事

登录功能是 AI 最容易写得像 Demo 的地方。手机号、密码、提交、成功跳转,这些都很简单。但真实软件里,麻烦在会话生命周期。

这个项目的会话策略是:token 默认有效 30 天,并且采用滑动续期。也就是说,只要用户 30 天内使用过一次,服务端就把过期时间顺延到“当前时间 + 30 天”;如果连续 30 天没用,才需要重新登录。

这个设计对用户友好,因为计分工具不是每天都打开,但一旦活动时打开,最好不要突然要求登录。服务端 userByToken 做了这件事:查到有效 token 后,更新 expire_at

但会话功能真正踩坑的是“失效时怎么处理”。项目待办里有一个问题:“网络版出现了打开页面后,一直在闪烁显示 toast ‘登录已过期,请重新登录’,死循环的样子。”

这类 bug 很典型。它不是某一行语法错,而是状态机错。可能的链路是:

页面启动,发现本地有 token;

前端调用服务端校验或加载比赛列表;

服务端返回 401;

前端清除 session,并 toast 提示;

渲染函数再次执行;

因为状态没有标记“已经失败”或“已经退出登录”,又触发加载;

再次 401,再次 toast。

解决它不能靠“把 toast 文案改短”,而要让加载逻辑变成可控状态。项目里后来用 onlineLoadedonlineLoadingonlineError 这类状态避免重复自动请求,并且让重试只通过用户点击触发。net.js 也会在识别 token 失效时给 error 标记 tokenInvalid,让上层决定清 session 和跳登录。

这里我对 AI 的用法是:先让它画出流程,再让它改。比如提示:

“请分析登录启动流程和比赛列表加载流程,找出为什么 401 会导致 toast 循环。不要先改代码,先列出触发链路和需要设置的状态。”

AI 如果能把链路说清楚,再改就比较稳。如果它说不清楚,说明还没读懂代码,直接改风险很大。

这个案例给我的经验是:登录不是一个页面,而是一组状态。AI 可以写登录表单,但你必须要求它考虑未登录、已登录、会话有效、会话过期、网络失败、服务端慢、退出登录这些状态。少一个状态,用户就会遇到奇怪体验。

七、案例三:网络慢时,用户需要“被告知”

真实用户最怕的不是失败,而是不知道发生了什么。比如点击保存后没反应,用户不知道是没点到、网络卡、服务端挂了,还是其实已经保存成功但页面没刷新。

这个项目在 net.js 里做了两个时间阈值:

请求超过 3.5 秒,会派发一个 scorekeeper:network-slow 事件;

请求超过 12 秒,会用 AbortController 主动中断,并提示请求超时。

前端监听慢请求事件,弹出“网络较慢,正在等待服务端响应…”的提示。这样用户至少知道系统在处理,不会反复点按钮。

这个设计很小,但很实用。AI 往往会只写:

await fetch("/api/xxx")

但真实项目最好统一封装请求。统一封装的好处是:

所有 API 都有超时;

所有 API 都能识别服务端错误;

所有 API 都能识别会话失效;

所有 API 都能在慢请求时通知 UI;

以后要加 base URL、重试、日志,也只改一个地方。

所以我建议:用 AI 做网络版应用时,第一时间让它建立一个 API 客户端,而不是在每个按钮事件里散落 fetch。这类基础设施越早统一,后续迭代越轻松。

当然,慢请求提示也要防止骚扰。项目里做了简单节流:短时间内不要重复弹同样的 toast。否则网络一慢,每个请求都提示,也会很烦。

八、案例四:导入数据后列表不刷新,是典型的“状态来源”问题

项目待办里还有一个真实问题:“导入数据成功后,返回比赛列表页,发现列表还是导入前的老样子,必须手动刷新浏览器。”

这个问题特别适合拿来讲 AI 排错。因为它表面是“页面没刷新”,本质是“状态来源不一致”。

网络版数据的权威来源是服务端 SQLite。前端的 state.matches 只是当前页面缓存。导入数据时,如果只是把文件里的比赛逐个调用服务端创建,但没有把创建结果合并到 state.matches,或者没有强制重新拉取列表,那么首页当然还是旧数据。

解决方案有两种:

第一种,导入每创建成功一场,就把服务端返回的比赛插入前端状态。

第二种,导入完成后调用强制刷新函数,从服务端重新拉比赛列表。

我更倾向第二种,因为服务端可能会做规范化处理,比如重设 ID、更新时间、版本号、去重等。导入完成后重新拉取,能保证前端状态和服务端一致。

这个项目里有 forceReloadOnlineMatchesrefreshOnlineData 一类函数,就是为这种场景准备的。AI 改这个问题时,不能只在导入成功后 render()render() 只是用当前状态画页面,如果状态没更新,画一百次也是旧列表。

这件事可以总结成一个原则:当用户反馈“页面没更新”,先问“数据源有没有更新”,再问“渲染有没有触发”。很多前端 bug 都是这两个概念混在一起导致的。

我在和 AI 协作时,会明确要求它区分:

服务端数据是否已变化;

前端内存状态是否已变化;

当前路由是否回到列表页;

列表页是否重新读取状态;

是否存在分页缓存,比如首页只显示前 20 场。

AI 如果按照这几个问题检查,基本能定位到真实原因。

九、案例五:导入去重,先设计规则,再写代码

“避免重复比赛多次导入”听起来简单,但其实是产品规则问题。

最粗暴的办法是按比赛 ID 去重。如果导出文件里的 ID 和服务端已有 ID 一样,就跳过。但问题是,导入时有时会重新生成 ID,或者不同设备导出的同一场比赛 ID 不一致。

另一种办法是按比赛名称去重。这个也不可靠,因为很多人会用默认名称,比如“2026-06-27 转蛋比赛 1”。不同活动可能同名。

更稳妥的办法是生成一个“比赛指纹”。项目里可以用这些字段组合:

比赛模式;

比赛名称;

创建时间;

玩家名单;

规则配置,比如掼蛋升级规则、转蛋分层和最大倍数;

每局记录摘要,比如局数、每局创建时间、赢家输家、分数、升级步数。

但指纹越严格,越容易漏判;越宽松,越容易误判。所以更实用的方案是“两层判断”。

第一层是强匹配:如果比赛 ID 已存在,直接认为重复。

第二层是业务匹配:用模式、玩家名单、创建时间、局数、规则和关键得分记录生成 duplicateMatchKey,如果 key 相同,就认为大概率重复。

项目中已经有类似 duplicateMatchKey(match) 的函数,用于导入时构造已有比赛的 key 集合,然后逐场检查导入列表。这样导入结果可以告诉用户:成功导入多少场,跳过重复多少场,掼蛋多少场,转蛋多少场。

这个功能我不建议一上来让 AI 直接写。更好的流程是:

先让 AI 给出去重方案;

人工确认哪些字段算重复;

再让 AI 实现;

最后准备几组测试数据验证误判和漏判。

这正是 AI 编程里“人做判断,AI 做实现”的典型例子。因为一旦去重规则错了,可能导致用户以为数据导入成功,实际跳过了不该跳过的比赛;也可能导致同一场比赛重复出现好几次。两种都很糟。

十、案例六:弹窗穿透滚动,是移动端 Web 的常见坑

项目待办里还有一个问题:“在转蛋赛局详情页上弹出计分页面、清算页面等页面时,如果滑动屏幕会导致底部的赛局详情页滚动。”

这个问题在桌面浏览器里可能不明显,在手机上非常明显。用户打开一个底部弹窗或对话框,手指滑动弹窗内容时,底层页面跟着滚了。等关闭弹窗,原来的页面位置变了,体验很差。

这类问题 AI 如果没有移动端经验,可能会只加一个半透明遮罩。但遮罩只能挡点击,挡不住所有滚动链路。更完整的处理通常包括:

弹窗打开时给 body 或根容器加锁定类;

记录当前滚动位置;

设置 overflow: hidden 或固定定位;

弹窗内部允许滚动,但滚动到顶部或底部时不要把滚动传给底层;

关闭弹窗时恢复滚动位置;

所有弹窗统一走同一个 modal()closeModal(),不要每个页面单独实现。

这个项目有 #overlay-root,弹窗都挂在里面,这是好事。AI 修复这类问题时,应该优先改统一弹窗机制,而不是在“转蛋计分弹窗”里单独打补丁。因为项目里还有清算页、添加玩家、调整顺序、比赛详情、分享二维码、修改昵称、确认删除等很多弹窗。如果只修一个,其他地方还会出问题。

这里有一个很重要的工程原则:凡是跨页面一致的交互问题,都应该在基础设施层解决。弹窗穿透、toast 节流、请求超时、会话失效、按钮 loading,这些都不应该散落在每个业务函数里。

AI 很擅长找所有调用 modal( 的地方,也擅长统一修改弹窗函数。你可以让它先列出所有弹窗入口,再判断是否都走同一个函数。如果是,就改一个地方;如果不是,就先收口。

十一、案例七:分享赛局,不只是生成一个链接

计分软件里的分享功能很实用:比赛创建者生成一个链接或二维码,其他人打开后可以查看当前赛局和记录。

这个功能看似简单,其实有几个细节。

第一,分享链接不能永久有效。项目里 config.json 设置分享有效期,默认 12 小时。服务端 match_sharelinks 表保存 expire_at,访问分享时如果过期就返回 410

第二,分享页不应该暴露用户账号信息。服务端 getShared 会构造一个安全对象,只返回比赛所需字段,比如 ID、名称、模式、规则、玩家、轮次、创建时间、更新时间、版本号等。

第三,分享页需要刷新。比赛还在进行时,别人打开分享页可能看到旧分数,所以前端提供刷新按钮。项目里分享状态有 loadingrefreshingexpirederror 等状态。

第四,二维码生成要有兜底。项目加载了 qrcode.min.js,生成二维码失败时要给提示,复制链接也要兼容剪贴板 API 不可用的情况。

AI 在这个功能里可以帮忙很大:生成 token、设计表结构、写 API、写二维码弹窗、写分享页状态。但人要提醒它:分享是“只读访问”,不能要求登录,不能返回敏感字段,不能永不过期。

很多 Demo 级 AI 代码会把分享做成“把当前页面 URL 复制一下”,这对本地状态应用没有意义。真正的网络分享一定要让数据在服务端可访问,并且有权限边界。

十二、案例八:SQLite 不是玩具,但要提前建对索引

项目待办里提到:“请评估当前 SQLite 数据库在数据量增加到很大之后,有没有要建索引的字段,请提前考虑并建好。”

这是一个很好的工程意识。很多小工具一开始数据少,怎么查都快;等用久了,比赛多、分数多、分享链接多,再补索引就容易出问题。

这个项目的查询模式很明确,所以索引也不复杂。

user_sessions 需要按 token 查会话,token 是主键;还需要按用户和过期时间清理,所以建了 user_idexpire_at 索引。

matches 的主键是 (owner_id, id),因为每个用户拥有自己的比赛。首页列表按 owner_idcreated_at DESC 查,所以需要 idx_matches_owner_created。如果按模式筛选首页,比如掼蛋和转蛋分开显示,就需要 idx_matches_owner_mode_created。如果以后按更新时间同步,也可以用 idx_matches_owner_updated

match_score(owner_id, match_id) 读某场比赛所有轮次,并按 round_indexcreated_at 排序,所以建了 idx_match_score_roundsidx_match_score_created

match_sharelinks 需要按分享 token 查,token 是主键;还需要按比赛查已有分享链接,以及按过期时间清理,所以建了 match 和 expire 索引。

这套索引没有追求花哨,而是贴合实际 SQL。AI 很容易建议“给所有字段都建索引”,这反而会拖慢写入、增加数据库体积。索引不是越多越好,而是要围绕查询路径。

我的经验是,让 AI 做数据库优化时,一定要先让它列出当前 SQL:

列表页怎么查;

详情页怎么查;

保存时怎么删旧分数和插新分数;

分享页怎么查;

会话怎么查;

清理过期数据怎么查。

只有 SQL 路径清楚,索引建议才靠谱。

十三、案例九:部署脚本是 AI 很适合发力的地方

很多个人或小团队项目,代码写完后最痛苦的是部署。尤其用户不熟悉 Linux,部署文档如果不够细,就会卡在 Node 版本、端口、防火墙、Nginx 反向代理、systemd 服务、文件权限这些地方。

这个项目除了源码,还准备了 Windows 和 Linux 部署材料。

Windows 本地有 start-local.ps1,会读取 config.json,检查 Node 和 npm,检查端口是否已有服务占用,进入 server 目录,必要时执行 npm install,启动服务,并在服务健康后打开浏览器。

Linux 目录里有宝塔部署手册、Nginx 配置示例、systemd 服务文件、安装脚本和备份脚本。部署流程包括生成 zip 包、上传到服务器、安装 Node.js 24、解压、执行安装脚本、检查 systemctl status、配置 Nginx 反向代理、检查 /health

这类工作 AI 特别适合做,因为它需要大量清晰步骤和脚本细节。人容易漏掉“Node 版本必须是 24,因为项目使用 node:sqlite”这种关键约束;AI 如果读到 server/package.json 和代码里的 require("node:sqlite"),就能在文档里反复强调。

但部署脚本也不能完全相信 AI。尤其是涉及删除、覆盖、停止进程、写系统服务文件的时候,要逐行检查。我的做法是让 AI 生成后,再让它解释每一步会影响什么文件、什么端口、什么服务。能解释清楚,再执行。

还有一个经验:部署文档要写给“最不熟悉环境的人”。不要只写“配置 Nginx”,而要写在哪个面板、点哪个菜单、把哪段 location / 放进去、如何 nginx -t、如何重载、如何看日志。AI 很适合把这些步骤补齐。

十四、AI 写 UI:不要只追求“好看”,要追求“现场好用”

计分工具的 UI 目标不是炫酷,而是比赛现场好用。

手机上要能快速新建比赛;

比赛详情页要一眼看到当前局数、状态、分数;

计分按钮要明显;

回退、删除、清算这类危险操作要确认;

转蛋积分正负要用颜色区分;

清算页面要让付款方向清楚;

设置页要能退出登录、修改昵称、导入导出;

分享页要适合别人只读查看。

AI 在 UI 上很容易犯两个错误。

第一个错误是营销化。它喜欢生成大标题、大渐变、大卡片、很多说明文字。但工具类应用应该直接进入操作界面,不需要像官网一样先讲价值主张。

第二个错误是“看起来有控件,实际流程不完整”。比如它可能给一个“导入”按钮,但没有进度、没有重复检查、没有错误结果页;给一个“保存”按钮,但点击后没有 loading,也没有失败恢复。

所以让 AI 做 UI 时,提示词里要强调目标用户和工作流。例如:

“这是手机端比赛现场使用的计分工具,用户要在多人等待时快速记录一局分数。界面要紧凑、可扫读、按钮要容易点,失败要提示,不要营销页。”

有了这个约束,AI 才会把注意力放在流程上,而不是装饰上。

这个项目的一个细节是模式配色:掼蛋升级和转蛋计分分别有不同主色,通过 modeStyle(mode) 注入 CSS 变量。这样用户在不同模式下有视觉区分,但整体结构保持一致。这种小设计很适合让 AI 帮你贯彻到页面里:所有顶栏、按钮、弹窗都用同一套模式变量,不要每处手写颜色。

十五、用 AI 排错时,最有效的提示词是“复现链路 + 约束”

我在这个项目里总结出一类很有效的提示词结构:

“现象是什么;在哪个版本/页面/操作后出现;期望是什么;请先分析相关代码路径,不要直接修改;列出你认为的原因和准备改的文件;确认后再改。”

比如弹窗穿透问题,可以这样问:

“在转蛋赛局详情页打开计分弹窗或清算弹窗后,手机上滑动弹窗会带动底层详情页滚动。请检查所有弹窗是否统一使用 modal,分析应该在通用弹窗层加锁还是单独修某个页面,先给出方案。”

导入刷新问题,可以这样问:

“网络版导入数据成功后回到比赛列表,列表仍是导入前的数据,手动刷新浏览器才显示。请分析导入流程、服务端创建、前端 state 更新和首页渲染之间的关系,不要只调用 render。”

登录过期问题,可以这样问:

“打开页面后一直闪烁 toast ‘登录已过期,请重新登录’,像死循环。请分析启动校验、ensureOnlineMatches、Net.validate、render 之间是否存在失败后自动重试。”

这些提示词有几个共同点:

它们描述了用户操作,而不是只说“有 bug”;

它们告诉 AI 先分析,不急着改;

它们明确了不要用表面修复;

它们要求 AI 找代码路径。

AI 的代码能力很强,但你要把它拉回“工程现场”。没有复现链路的 bug,AI 很容易根据经验猜;有复现链路,它就能像一个初级同事一样沿着调用栈查。

十六、让 AI 改代码,要控制改动半径

AI 很容易“顺手优化”。你让它修一个按钮图标,它可能重构整个图标系统;你让它修一个导入刷新,它可能顺手改路由;你让它加索引,它可能重写数据访问层。很多时候这不是恶意,而是它觉得“这样更完整”。

真实项目里,改动半径越大,回归风险越高。我的做法是每次都限制范围:

只改相关函数;

保持现有技术栈;

不要引入新依赖;

不要改无关样式;

不要改变已有数据格式,除非明确做迁移;

每次修改后说明影响的用户流程。

比如这个项目的前端是原生 JS。如果 AI 建议引入 Vue 或 React,那就不适合。不是框架不好,而是为一个现有工具引入框架会改变构建、部署、维护方式。除非项目准备大重构,否则应该沿用现有结构。

同样,服务端已经用 Node 原生 HTTP 和 SQLite。如果只是加一个 API,没有必要引入 Express。小项目的价值在于部署简单,依赖少。AI 写代码时喜欢使用熟悉框架,但你要提醒它“跟随项目现有风格”。

这个原则特别重要:AI 不是越多写越好,而是越准确越好。

十七、测试和验收:AI 可以帮你列清单,但不能替你感受体验

这个项目的很多问题都需要真实操作验证。比如:

登录后刷新页面,是否保持会话;

token 过期后是否只提示一次并回到登录;

网络断开时保存是否有明确提示;

导入重复数据是否跳过;

导入成功后首页是否立刻显示新比赛;

转蛋计分后排名和总分是否正确;

掼蛋升级到 A 后是否正确结束;

回退上一局后状态是否恢复;

打开清算弹窗后底层页面是否无法滚动;

分享链接过期后是否显示失效;

手机添加到桌面后图标是否正确;

宝塔部署后 /health 是否正常;

Nginx 反向代理后 API 是否能访问。

AI 可以帮你生成这份验收清单,也可以帮你写自动化测试或手动测试步骤。但有些体验必须人自己试。比如“按钮是否容易点”“toast 是否烦人”“清算图是否看得懂”“弹窗在小屏手机上有没有遮住按钮”,这些不是单元测试能完全覆盖的。

我建议每次用 AI 改完功能后,让它输出三样东西:

改了哪些文件;

影响哪些流程;

建议怎么验证。

这能逼 AI 从“代码完成”切换到“功能交付”。很多时候你会从验证清单里发现它漏了边界。

十八、关于“真实有料”的提示词:把 AI 当采访对象,也当编辑

如果你想用 AI 写技术文章,不要只让它“写一篇关于 AI 编程的文章”。那样大概率得到套话。

更好的做法是给它真实材料:

项目目录;

README;

数据库 schema;

核心源码片段;

待办问题;

部署文档;

你自己的开发经历;

哪些坑让你印象深。

然后让 AI 先提炼文章骨架,再写正文。你还可以让它扮演编辑,检查有没有空话、有没有具体案例、有没有工程细节、有没有读者能照着做的步骤。

这篇文章本身也是同样方法写出来的:先读取项目结构,再把功能和问题抽出来,最后围绕 AI 协作流程组织成经验分享。文章里讲到的会话续期、请求超时、分享链接、导入去重、SQLite 索引、宝塔部署,都来自项目实际文件,而不是凭空编的。

技术文章要像真人写,关键不是加口语词,而是有真实判断。比如“AI 很强”是空话;“导入成功后只 render 不够,因为前端 state 还是旧的,应该重新拉服务端列表”就是经验。前者谁都能说,后者是踩过坑才会说。

十九、我推荐的一套 AI 编程工作流

结合这个项目,我现在比较推荐下面这套流程。

第一步,明确目标和边界。

不要直接说“做个功能”,而是说清楚用户场景、成功标准、不能改变什么。比如“修复网络版导入后列表不刷新的问题,保持现有原生 JS 架构,不引入新依赖,导入完成后首页必须显示服务端最新数据。”

第二步,让 AI 读代码并输出地图。

让它找相关文件、函数、状态变量和调用链。它读出来的地图,你也能顺便检查它有没有理解错。

第三步,让 AI 给方案,不急着改。

尤其是涉及业务规则、数据结构、权限、导入去重、部署脚本时,先要方案。方案里要包含取舍和风险。

第四步,小步修改。

一次只解决一个问题。修登录循环就不要顺手改登录页 UI;修弹窗滚动就不要顺手改所有弹窗样式。

第五步,运行和验证。

能跑命令就跑命令,能打开页面就打开页面。不要只看 AI 说“应该可以”。如果环境跑不起来,也要让它说明未验证的风险。

第六步,总结变更。

改完后让 AI 写简短总结:改了什么、为什么、怎么验证、还有什么风险。这对后续维护很有帮助。

这套流程听起来比“直接生成代码”慢,但实际更快。因为返工少。

二十、几个我会反复提醒 AI 的工程原则

第一,先读现有代码。没有上下文的“最佳实践”常常是错的。

第二,优先修根因。登录 toast 循环不是 toast 问题,是加载状态问题。

第三,统一处理通用问题。网络超时、弹窗锁滚动、会话失效、错误提示,都应该收口到公共层。

第四,数据源要清楚。网络版权威数据在服务端,前端缓存只是缓存。

第五,业务规则先确认。导入去重、升级规则、清算规则都不能让 AI 自己拍脑袋。

第六,少引依赖。小工具最怕为了一个小功能引入一堆构建复杂度。

第七,部署也是产品的一部分。用户打不开,再好的功能也没用。

第八,移动端体验要真机思维。弹窗、滚动、按钮大小、网络慢,都要按现场使用考虑。

第九,索引跟着查询走。不要为了“性能”给所有字段建索引。

第十,AI 的输出要验收。看起来能运行,不等于用户流程完整。

二十一、这个项目里 AI 最有价值的地方

如果只选几个最明显的价值点,我会选这些。

第一,快速理解大文件。app.js 这种长业务文件,人工翻很费劲,AI 用搜索和总结能很快建立结构。

第二,补齐边界状态。比如网络慢、请求超时、会话过期、服务端错误、导入重复、分享过期。

第三,生成重复性代码。CRUD、表结构、API 分发、导入导出进度、部署脚本、文档步骤,这些 AI 很适合。

第四,做方案对比。比如导入去重按 ID、按名称、按指纹,各有什么问题;SQLite 哪些索引必要,哪些没必要。

第五,写面向用户的文档。宝塔 Linux 部署这种文档,AI 能写得很细,降低交付成本。

第六,保持迭代节奏。一个小问题从定位到修改到总结,AI 能把中间很多机械劳动接走,让开发者更专注判断。

二十二、这个项目里 AI 容易出错的地方

同样要诚实说,AI 也有明显风险。

第一,它可能误读业务规则。掼蛋升级和转蛋计分都有地方规则,AI 不一定懂。比如升级步数、A1/A2/A3、清算方式,必须由人确认。

第二,它可能只修表象。toast 循环、列表不刷新、弹窗滚动,表面都很简单,根因却在状态流。

第三,它可能引入不必要复杂度。比如为了一个小 API 引入框架,为了一个弹窗改整个 UI。

第四,它可能忽略编码和部署环境。这个项目路径和内容有中文,部署环境有 Windows、Linux、宝塔、Node 24,任何一个没注意都可能出问题。

第五,它可能没有真实体验感。按钮大小、弹窗遮挡、手机滚动、现场计分压力,这些需要人用过才知道。

所以我不会把 AI 当“自动程序员”,而是当一个高效率协作者。它负责快,我负责准。

二十三、给想用 AI 做软件开发的人的建议

如果你刚开始用 AI 写代码,我建议不要从大而全的项目开始。可以从一个真实小工具开始,像这个计分软件一样,需求具体、用户明确、功能闭环。这样你能学到完整流程:需求、实现、数据、错误、部署、反馈、迭代。

不要满足于让 AI 生成一个 Demo。Demo 最容易,难的是把它变成别人愿意用的工具。

每次遇到 bug,不要只让 AI “修复”。先让它解释。解释不清楚的改动,通常不可靠。

每次做功能,不要只看代码有没有写完。要看用户流程有没有闭环。比如导入功能,闭环不是“能选择文件”,而是选择文件、解析、展示摘要、确认、去重、上传、显示进度、返回结果、刷新列表、失败可理解。

每次做服务端,不要忘记权限、会话、错误码、数据冲突和日志。AI 很容易写出“快乐路径”,但真实用户总会走到边界。

每次做移动端 Web,不要只在桌面浏览器缩窄窗口看。真正的手机浏览器有软键盘、安全区、滚动惯性、添加到桌面、缓存和网络波动。

每次做部署,不要只说“运行 npm start”。要有端口、进程管理、开机自启、反向代理、健康检查、备份和升级方案。

二十四、一个可直接复用的 AI 协作模板

下面这个模板,是我认为比较适合实际项目的:

你先阅读当前项目代码,不要立即修改。

目标:
我要解决/实现:……

背景:
用户在什么场景下使用:……
当前现象:……
期望结果:……

约束:
保持现有技术栈,不引入新依赖。
不要改无关文件。
不要改变已有数据格式,除非你先说明迁移方案。

请先输出:
1. 相关文件和函数。
2. 当前流程链路。
3. 可能原因或实现方案。
4. 你准备修改的最小范围。
5. 验证步骤。

等我确认后再改代码。

如果是很明确的小 bug,也可以让 AI 直接改,但我仍然建议保留“先读代码”和“最小范围”这两个要求。

二十五、回看这个计分软件:它不大,但很完整

这个“掼蛋升级 / 转蛋计分”Web 网络版项目,从外面看只是一个计分牌。但作为 AI 编程案例,它包含了很多真实工程元素:

它有业务规则:掼蛋升级、转蛋积分、清算、回退。

它有前端状态:路由、模式、列表、详情、弹窗、toast、分享页。

它有服务端:账号、会话、比赛、分数、分享链接。

它有数据库:SQLite 表结构、事务写入、索引。

它有网络体验:慢请求提示、超时、错误处理。

它有数据迁移:导入导出、重复检查、强制刷新。

它有移动端体验:弹窗滚动、按钮、PWA 图标。

它有部署:Windows 本地启动、Linux 宝塔安装、Nginx 反向代理、systemd 服务。

这些东西串起来,才像一个真正的软件。AI 参与其中,不是替代开发者,而是让开发者能更快地把这些环节串稳。

我觉得这就是目前 AI 编程最现实、也最有价值的使用方式:它不是魔法,而是一个非常强的工程增幅器。你越能说清楚问题、越能控制边界、越能验证结果,它就越有用。你越想把所有判断都交给它,它就越容易带你绕路。

最后,用这个项目的一句话作为总结:一个计分软件真正难的,不是把分数加起来,而是让一群人在真实场景里放心地用它。AI 能帮我们更快写出代码,但“放心”这件事,仍然来自开发者对需求、边界和体验的认真把关。

查看编程实战示例 查看 Markdown 原文