概述
约 1000 元/月的 AI 订阅服务,1 天完成了过去需要数天的全栈重构——不仅迁移了技术栈,还从零新增了管理后台,同时自动产出了 2700 行设计文档。12 个 OpenSpec 提案,2700 行设计文档,1400 行业务代码——一次关于 AI Coding 流程控制的真实记录。个人网站地址:http://dreamyouxi.com/
先说结果
核心收益
重构前后对比:
| 重构前(C++ LiteHttp) | 重构后(FastAPI) | |
|---|---|---|
| 更新一张卡片 | 手改 HTML 源码 → 重新编译 C++ → 部署 | 打开后台管理界面 → 填表提交,完成 |
| 管理后台 | 没有,所有操作靠改代码 | 完整的 Web 管理面板:增删改查、拖拽排序、图片裁剪上传、卡片置顶 |
| 首页加载 | 5.2 MB | 0.55 MB(降幅 89%) |
| 维护门槛 | 只有作者本人能维护(C++ 手写 HTTP 服务器) | Python + FastAPI,任何后端开发者都能接手 |
| 内容管理 | 硬编码在 HTML 中,改一个字要碰源码 | JSON 数据驱动 + 模板渲染,内容和代码完全分离 |
| 移动端 | 未适配 | 三断点响应式(手机/平板/桌面) |
其中管理后台是从 0 到 1 的新增——原来的 LiteHttp 没有任何管理界面,每次更新内容都要直接改 HTML 文件再重新部署。现在有了一个 598 行的完整管理面板(admin.html),支持卡片增删改查、SortableJS 拖拽排序、Cropper.js 图片裁剪、一键置顶、分类筛选。维护成本从"找到源码 → 编辑 → 编译 → 部署"变成了"打开浏览器 → 点几下"。
产出明细
| 指标 | 数据 |
|---|---|
| 总耗时 | 1 天(含 Claude Code 环境搭建 + 全部前后端重构) |
| AI 协作时间 | 约 5 小时高密度 Coding |
| 提案数 | 12 个完整 OpenSpec 提案 |
| 后端代码 | server.py 414 行(路由 + API + 中间件,一个文件搞定) |
| 前端模板 | 4 个 Jinja2 模板共 933 行(index 213 + admin 598 + about 66 + detail 56) |
| 样式 | index.css 94 行(替代原来 298KB 的合并 CSS) |
| 数据 | cards.json 800 行(73 张卡片 + 16 个详情页,从硬编码 HTML 迁移而来) |
| 测试 | 72 行 pytest 测试用例 |
| 设计文档 | 71 个 OpenSpec artifacts,共 2700 行(proposal + design + specs + tasks) |
| 月费 | Max 5 计划约 1000 元人民币,当天消耗约 60% 额度,未触发限流 |
一句话总结:1000 元/月的 AI 伙伴,1 天完成了过去需要数周的全栈重构——不仅迁移了技术栈,还从零新增了管理后台,同时自动产出了 2700 行设计文档。
一、背景:一个学生时代的技术追求,和它 11 年后的技术债
dreamyouxi.com 是我的个人作品展示网站,2015 年上线,至今运行 11 年。它的底层是一套完全自研的 C++ + C# + Lua 服务器——LiteHttp。这是我学生时代的作品,出于对底层技术的好奇和自我挑战,从 TCP socket 开始手写了整个 HTTP 服务器:协议解析、路由分发、静态文件服务、CGI 接口——每一层都是自己造的轮子。当时的目标很纯粹:想知道一个 Web 服务器到底是怎么工作的,所以从零写一个。这套东西确实扛住了时间的考验,运行了 11 年。但学生时代的代码终究是学生时代的代码,问题也积累了 11 年:
- 维护成本极高:改一个小功能要重新编译 C++ 再部署,toolchain 都快找不到了
- 技术栈完全孤立:C++ Web 服务器没有生态,所有中间件都是手写的,没有文档,只有我自己能维护
- 数据全部硬编码:73 张卡片、16 个详情页的数据直接写死在 HTML 里,加一张卡片要手改两个 HTML 文件
- 前端停留在 2015 年:298KB 的合并 CSS、17 个 JS 文件、Revolution Slider、fontello 527 个图标字体——大部分从未被使用
LiteHttp 完成了它的历史使命——它让我理解了 Web 的底层原理,也支撑了网站 11 年。但 2026 年,继续修补一个学生时代的 C++ 服务器已经不再合理。我决定彻底重构。
二、为什么选 Claude Code
市面上 AI Coding 工具不少,我选 Claude Code 的原因很具体:
1. 它是 CLI 工具,不是聊天框
Claude Code 运行在终端里,直接操作文件系统。它能读代码、写代码、跑命令、看输出,交互循环和人类开发者一样。不需要手动复制粘贴代码片段到聊天窗口,也不需要把 AI 的输出再手动粘回编辑器。
2. Opus 4.6 + Thinking 的推理能力
这次重构不是"写一个 TODO 应用"——它需要理解 11 年前的 C++ 代码结构、提取硬编码在 HTML 里的 73 张卡片数据、保持旧页面的路由兼容性、处理无扩展名文件的中间件逻辑。这些都要求模型有足够的上下文理解能力和推理链条。Opus 4.6 在这方面非常可靠。
3. CLAUDE.md 的项目感知
Claude Code 会读取项目根目录的 CLAUDE.md 文件作为持久化的项目指令。我在里面写清了目录结构、路由规则、安全约束、模板规范,之后每次对话 AI 都不需要重新了解项目背景。这一点在 12 个提案迭代过程中价值巨大——AI 始终知道"不要引入数据库""不要修改无扩展名文件""卡片 id 不得复用"。
三、OpenSpec:让 AI Coding 变得可控
问题:AI 写代码太快,人跟不上
直接让 AI "帮我重构网站"的结果通常是:它一口气改了 20 个文件,你看不过来,确认不了对错,出了问题也不知道从哪里回退。AI 的速度优势反而变成了风险。
解法:提案驱动的四阶段工作流
我采用 OpenSpec 工作流来约束 AI 的开发流程。它把每次变更拆成四个阶段:
Explore(分析) → Propose(提案) → [人工确认] → Apply(实施) → Archive(归档)
每个阶段都是一个 Skill 命令,AI 必须按顺序执行,不能跳过:
| 阶段 | 做什么 | 不能做什么 |
|---|---|---|
/opsx:explore | 读代码、分析问题、讨论方案 | 不能写业务代码 |
/openspec-propose | 生成提案 + 设计 + 规格 + 任务清单 | 不能写业务代码 |
/opsx:apply | 按任务清单逐项实施 | 不能跳过任务、不能擅改方案 |
/opsx:archive | 归档完成的提案 | — |
关键约束:Propose 完成后必须等人确认,未经确认不能进入 Apply。这不是形式主义。12 次提案里,至少有 3 次我在 review 阶段调整了方案——比如 AI 提议用 Nginx 做 Gzip 压缩,但我们的部署没有 Nginx 层,改成了 FastAPI 内置的 GZipMiddleware;又比如 AI 想给所有图片做 <picture> 标签兼容,但 WebP 兼容率已经 97%,我砍掉了这个额外复杂度。
一个提案长什么样
以「流量优化」提案为例,AI 生成的 artifacts 包括:
proposal.md — 概述目标、范围和影响:目标是启用 Gzip 压缩、设置 Cache-Control、清理重复文件,预期效果是传输量减少 60–80%。
design.md — 技术决策和权衡:
| 决策 | 方案 | 理由 | 否决的替代方案 |
|---|---|---|---|
| 压缩方式 | Starlette GZipMiddleware | FastAPI 内置,零依赖 | Nginx 反向代理(部署没有 Nginx) |
| 压缩阈值 | 500 字节 | 太小的文件压缩后反而更大 | 1KB(过于保守) |
| 缓存策略 | 静态资源 30 天,HTML no-cache | 资源文件名不含 hash,30 天合理 | 永久缓存+hash(改动太大) |
| API 缓存 | no-store | 管理接口不能缓存 | — |
specs/ — 每个子功能的技术规格(验收标准):
- Gzip:所有 text/html、text/css、application/javascript 响应体在 >500 字节时自动压缩
- Cache-Control:
/cdn/、/css/、/js/、/fonts/、/res/前缀的资源返回public, max-age=2592000 - 清理:jquery.min.js 在 3 个目录存在副本,保留 cdn/ 下的,删除 js/ 和 style/js/ 的
tasks.md — 实施任务清单:
- [x] 1.1 添加 GZipMiddleware(minimum_size=500)
- [x] 1.2 验证 gzip Content-Encoding 头
- [x] 1.3 验证小文件不被压缩
- [x] 2.1 添加 Cache-Control 中间件
- [x] 2.2 静态资源 → public, max-age=2592000
- [x] 2.3 HTML 页面 → no-cache
- [x] 2.4 Admin API → no-store
- [x] 3.1 识别重复文件
- [x] 3.2 grep 搜索所有引用
- [x] 3.3 更新引用路径
- [x] 3.4 删除重复文件(www/js/ 388KB)
- [x] 3.5 grep 验证零残留
这套 artifacts 就是 AI 和人之间的「合约」——人确认了提案,AI 就严格按任务清单实施,不多做也不少做。
四、12 个提案的完整重构历程
整个重构拆成 12 个 OpenSpec 提案,自然形成了四个阶段。以下按实际执行顺序展开。
阶段一:基础架构迁移(2 个提案)
提案 1:FastAPI 服务化 + 后台管理
这是整个重构的地基。AI 需要完成以下工作:
- 数据提取:从 index.html 和 index2.html 两个硬编码页面中提取 73 张卡片数据,从 16 个独立 HTML 详情页提取结构化内容,全部写入
cards.json - 路由设计:
/和/page/{n}走模板分页,/{slug}.html先查 details 数据再回退静态文件,blog/login/archive 等无扩展名文件通过中间件直接返回 - 后台系统:完整的管理面板(598 行 admin.html),支持卡片 CRUD、拖拽排序、图片裁剪上传
- 安全机制:token 认证、每日 5 次密码错误限制、禁用 FastAPI 文档路由
AI 在 design.md 中记录了关键的数据模型设计:
{
"page_size": 20,
"cards": [
{"id": 1, "title": "标题", "link": "URL", "image": "路径", "category": "web|graphic"}
],
"details": {
"slug": {
"title": "详情标题",
"descriptions": ["段落", "## 小标题", "段落"],
"links": [{"label": "按钮文字", "url": "URL"}]
}
}
}
三个顶层字段(page_size / cards / details),之后所有提案都严格向后兼容这个结构。最终 server.py 只有 415 行,涵盖了:页面路由、公共 API、管理 API(登录/CRUD/排序/置顶/上传)、中间件(无扩展名 HTML、Server 头、Cache-Control)。
提案 2:Admin 增强
第一个提案部署后暴露了几个实际问题:密码硬编码在源码里、卡片日期嵌在标题中("激斗火柴人-2018"这种格式需要前端运行时解析)、排序和图片处理需要外部工具。AI 在 Explore 阶段分析了这些问题,然后在 Propose 阶段一次性给出了 7 项增强:
- 密码外置 →
secret.txt文件(不用环境变量,因为 Windows 下文件更方便) - 日期字段独立 → 迁移脚本将 "标题-年份" 拆分为
title+date两个字段 - 新卡片置顶 → insert(0) 而非 append,默认分类改为 "web"(职业生涯)
- 卡片置顶 →
pinned布尔字段 + 红色边框视觉标记 - 拖拽排序 → SortableJS,只在"全部"视图下启用(筛选视图下禁用,避免部分排序混乱)
- 图片裁剪 → Cropper.js,支持自由/16:9/4:3/1:1 四种比例,客户端裁剪不加后端负担
- 守护进程部署 → pythonw 后台运行,
--debug参数控制热重载
这 7 项增强里有一个有意思的设计决策:为什么拖拽排序在筛选视图下要禁用?因为如果用户在"学生时代"分类下拖拽排序,只会看到部分卡片,排序结果应用到全局列表时会产生不可预期的位置变化。AI 在 design.md 里记录了这个决策及原因,这样半年后回来维护不会疑惑"为什么排序有时候不能用"。
阶段二:前端现代化(4 个提案)
提案 3:前端重写——750KB 到 10KB
这是整个重构中最"暴力"的一步。AI 在 Explore 阶段分析了现有前端:298KB 合并 CSS(index_all_style2.css)只用了其中约 44 个类;17 个外部 JS 文件中,fontello 加载了 527 个图标但实际用了 0 个,Revolution Slider 100+KB 只为了两张图片的轮播,fancybox/owl carousel/prettify 从未被调用。AI 提出的方案很直接:全部删掉,从零手写。design.md 中记录了每个决策:
| 组件 | 旧方案 | 新方案 | 理由 |
|---|---|---|---|
| 样式 | 298KB 合并 CSS | ~200 行手写 CSS | 只有 44 个类,150-200 行足够 |
| 轮播 | Revolution Slider (100+KB JS) | 纯 CSS @keyframes | 只有 2 张图片,CSS 动画完全够用 |
| 导航栏 | Bootstrap navbar | flexbox 5 个链接 | 没有下拉菜单,不需要框架 |
| 布局 | isotope.js | CSS Grid | 已不需要筛选动画 |
| 图标字体 | fontello (527 icons) | 删除 | 实际使用 0 个 |
最终创建了一个 www/cdn/index.css,用 flexbox + CSS Grid + @keyframes 三件套替代了整个前端依赖。但 AI 也在 design.md 里标注了"不删除旧文件"——因为 www/ 下还有其他独立维护的静态 HTML 页面引用了 index_all_style2.css,清理需要作为独立提案处理。这种边界感知是 AI 在 OpenSpec 约束下做得很好的地方。
提案 4-6:视觉迭代
前端重写后经历了三轮视觉迭代:
- 卡片换肤(提案 4):从暗色覆盖层改为白底 + 底部信息栏,标题常驻可见(不再 hover 才显示),加入分类标签徽章,分页从伪卡片改为独立分页栏
- 主页重设计(提案 5):CSS Grid 4 列布局,AJAX 分页(新增
GET /api/cards?page=N接口避免全页刷新),访问计数器(stats.json 持久化,初始值 111727 来自旧站统计),简化导航和页脚 - 移动端适配(提案 6):纯 CSS media query,三个断点:
/* 手机 <768px */ → 1 列,容器宽度 95%
/* 平板 768-1024px */ → 2 列,容器宽度 90%
/* 桌面 >1024px */ → 4 列,容器宽度 85%
这三个提案的特点是纯 CSS 变更,不动后端。AI 在每个提案的 Non-Goals 里都明确标注了不改 server.py、不改 cards.json 结构。这种自我约束让每个提案的影响范围非常可控——出问题只需要看 CSS。
阶段三:流量优化(4 个提案)
提案 7:CDN 本地化
网站原来所有静态资源都从外部 CDN(http://cdn.dreamyouxi.com/)加载。CDN 不再维护后需要迁移到本地。AI 的方案极简:纯字符串替换。在 cards.json 中将 "http://cdn.dreamyouxi.com/ 替换为 "/cdn/,在模板和 HTML 中做同样的替换,然后 grep 验证零残留。154 个文件已经缓存在 www/cdn/ 目录下,所以这个提案的风险极低——只是改引用路径,文件本身不需要动。AI 在 tasks.md 里逐文件列出了需要替换的文件和验证步骤。
提案 8:Gzip + 缓存头 + 文件去重
这是前面详细展示过的流量优化提案。值得补充的是 AI 在实施过程中发现的一个问题:AI 原计划删除 www/js/ 下所有重复的 JS 文件,但在 grep 搜索引用时发现 www/ 下的独立 HTML 页面(full_stack_engine.html)仍在引用 style/ 目录下的文件。于是 AI 暂停实施,回写 tasks.md,将 style/ 目录标记为"保留,有外部引用",只删除了确认无引用的 www/js/ 目录(388KB)。这就是 OpenSpec 的 artifact 回写规则在实际中的价值——实施中发现的问题不是留在对话里然后忘掉,而是写回到文档中持久化。
提案 9-10:WebP 图片优化
两个提案分别处理存量和增量:
存量转换(提案 9):
- 用 Pillow 批量转换
www/cdn/下 99 张图片为 WebP(quality=80) - 大于 760px 宽的图片自动缩放(卡片显示宽度 380px,760px = 2x 视网膜屏足够)
- favicon.ico 从 128×128 的 66KB ICO 换成 32×32 的 2KB PNG(favicon 的 WebP 兼容性不如 PNG)
- 保留 timer.png(1×10 像素占位图,1 字节,无需转换)
- 更新 cards.json 所有图片路径为
.webp,grep 验证零残留,删除原始文件
AI 在 design.md 里记录了 quality 参数的测试数据:78.jpg: 172KB → 56KB (quality=80),slider-bg2: 116KB → 37KB。quality=85 体积提升不值得,quality=75 有可感知的画质下降。
增量一致性(提案 10):
- 修改
api_upload接口:上传的 JPG/PNG 自动通过 Pillow 转换为 WebP - 添加
Pillow==12.2.0到 requirements.txt - 顺带清理了前端重写后遗留的废弃文件:
www/res/js/(20 个 JS)、www/style/(旧主题副本)、www/fonts/(fontawesome)、17 个 CDN JS 文件、8 个 CSS/sourcemap——共约 3.6MB
最终效果:
| 优化措施 | 传输大小 | 累计降幅 |
|---|---|---|
| 初始状态 | 5.2 MB | — |
| 压缩图片尺寸 | 3.3 MB | 37% |
| Gzip + 去重 | 2.7 MB | 48% |
| 精简 CSS/JS | 2.4 MB | 54% |
| WebP 格式 | 0.55 MB | 89% |
阶段四:收尾打磨(2 个提案)
提案 11-12:UI 统一 + 数据清理
最后两个提案处理视觉一致性和数据完整性:
- 卡片统一为 2px #d0d0d0 边框、hover 蓝色边框 + scale(1.12) 放大
- 固定 16:9 宽高比,无图片的卡片显示灰色背景 + JS 动态缩放标题字体
- 详情页从
<center>布局改为 max-width 800px 的白色卡片(左对齐) - 页脚统一为三栏结构(名称、日期标签橙色、分类标签)
- cards.json 数据清理:删除 3 张重复卡片(ID 5/6/12),ID 按新到旧重排,修正错误日期(PLANESHOOTER 2026→2016)
- 最终全部 26 个 pytest 测试通过
五、AI Coding 的实际协作模式
人做什么
回顾整个过程,人的工作集中在三件事:
1. 定方向
每个提案开始前,我会用一两句话描述需求("前端太重了优化一下""图片换成 WebP"),AI 进入 Explore 分析现状,然后 Propose 生成完整方案。我的工作是在方案中做判断:用不用 Nginx?要不要 <picture> 兼容?拖拽排序在筛选视图下怎么处理?
2. 审提案
这是最关键的环节。AI 生成的 design.md 里每个决策都有"方案 / 理由 / 否决的替代方案"三栏。我只需要逐条看"否决理由"是否合理。大部分时候 AI 的判断是准确的,但偶尔需要纠正——比如它倾向于做更多兼容性工作(WebP 兼容、旧浏览器适配),而我知道这个站的访客几乎全是开发者和游戏玩家,不需要兼容 IE。
3. 跑验收
每个提案完成后,AI 会运行 pytest 测试(最终 26 个测试用例),我手动在浏览器里检查页面渲染。两层验收,一个自动一个人工。
AI 做什么
AI 的工作量远超"写代码"这一步:
- 分析现状:读旧代码、统计文件大小、识别重复文件、计算覆盖率
- 写提案文档:proposal + design + specs + tasks 四层完整 artifacts
- 实施编码:server.py (415 行)、4 个 Jinja2 模板(index 213 行、admin 598 行、detail 56 行、about 67 行)、CSS、数据迁移脚本
- 数据迁移:从硬编码 HTML 提取 73 张卡片 + 16 个详情页到 JSON
- 批量处理:99 张图片格式转换、CDN 路径替换、废弃文件清理
- 写测试:26 个 pytest 用例覆盖模板渲染、API 认证、数据读写
- 自我验证:每次变更后 grep 验证零残留、确认无引用遗漏
一个典型的交互循环
以 WebP 图片优化为例,实际的交互大致是:
我:图片太大了,换成 WebP
↓
AI:[Explore] 分析 www/cdn/ 下 99 张图片,统计总大小 4.9MB,
列出 WebP 兼容率 97%,估算 quality=80 可减少 40-60%
↓
AI:[Propose] 生成提案:直接替换(不做 <picture> 兼容)、
quality=80、760px 缩放、favicon 换 PNG、保留 timer.png
↓
我:确认。不需要兼容,直接替换。
↓
AI:[Apply] 逐项执行 tasks.md:
→ 批量转换 99 张图片
→ 更新 cards.json 路径
→ 更新模板引用
→ 删除原始文件
→ grep 验证零残留
→ pytest 全部通过
↓
AI:[Archive] 归档提案到 changes/archive/
整个过程大约 15-20 分钟。我的实际输入只有两句话,但过程完全可追溯。
六、成本分析
| 项目 | 数据 |
|---|---|
| 模型 | Claude Opus 4.6 + Thinking |
| 工具 | Claude Code (CLI / VSCode 扩展) |
| 计划 | Max 5(约 $200/月,约 1000 元人民币) |
| 总耗时 | 约 5 小时高密度 Coding |
| 额度消耗 | 约 60% 每日上限 |
| 限流 | 未触发 |
| 提案数 | 12 个完整 OpenSpec 提案 |
| 代码产出 | server.py 415 行 + 4 模板 934 行 + CSS + 测试 |
| 文档产出 | 12 套 proposal + design + specs + tasks |
关键数字:1000 元/月,换来一个能读懂 11 年旧代码、会写提案、能做数据迁移、会批量处理图片、还会写测试的全栈协作伙伴。如果雇一个外包做同样的工作,报价不会低于 5 位数。而且 Max 5 计划的 Opus 4.6 额度比想象中充裕。5 小时高密度使用(不是偶尔问一句,是持续的 Explore → Propose → Apply 全流程)只消耗了 60% 额度,完全不需要省着用。
七、OpenSpec 的核心价值
用了 12 个提案之后,我总结 OpenSpec 工作流的价值在于四点:
1. 它是 AI 的「工作记忆」
Claude Code 的对话上下文是有限的。一个长会话里,AI 可能忘记前面做过什么决策。但 OpenSpec 的 artifacts 是写在文件里的——design.md 里的决策、tasks.md 里的进度、specs/ 里的验收标准。即使对话被截断或开启新会话,AI 读到这些文件就能恢复上下文。
2. 它是变更的「防火墙」
每个提案都有明确的 Goals 和 Non-Goals。AI 在"前端重写"提案里不会去动 server.py,在"流量优化"提案里不会去改卡片样式。这种边界约束让每次变更的爆炸半径可控——出问题只需要在当前提案的范围内排查。
3. 它是决策的「审计日志」
为什么用 GZipMiddleware 而不用 Nginx?为什么 WebP quality 是 80 不是 85?为什么拖拽排序在筛选视图下禁用?这些决策全部记录在 design.md 里,带着"方案 / 理由 / 否决的替代方案"。半年后回来维护,不用猜"当时为什么这么写"。
4. 它是人机协作的「协议层」
人看得懂 proposal.md(自然语言描述需求和影响),AI 看得懂 tasks.md(结构化的实施清单)。两边用同一套文档对齐理解,消除"我以为你要的是那个"的沟通偏差。
八、踩过的坑和建议
坑 1:AI 倾向于过度设计
AI 几乎在每个提案里都会提出一些"锦上添花"的方案——WebP 兼容降级、CSS 预处理器、数据库迁移。如果你不 review 就确认,最终会得到一个比需要的更复杂的系统。提案审核不能走过场。
坑 2:批量替换需要 grep 验证
CDN 路径替换、图片格式替换这种批量操作,AI 做完后必须 grep 验证零残留。我在 CLAUDE.md 里写入了"涉及批量替换的变更必须验证零残留",之后每个相关提案 AI 都会自动在 tasks.md 里加上 grep 验证步骤。