在前几篇文章中,我用 DeepSeek 为大家讲解并实战演示了 AI 的用法以及 AI 编程最基本的 概念。
前面几篇文章,我们使用的 AI工具 是 DeepSeek、智谱、Kimi、豆包。
这几个AI工具都是“对话式AI”。
对话式AI 的本质是:你问一句,它答一句。就像是一个知识储备丰富的实习生,但是仅限于帮你解答,不能帮你完成任务。
你和 对话式AI 的关系:你是“提问者”,AI是“回答者”,AI负责“思考”与“回答”。
今天我们要往前走一步,聊聊 AI Agent (AI 智能体)。
AI Agent (AI 智能体) 不再局限于跟我们对话,而是能 思考任务、制定计划、调用工具、完成计划。
AI Agent = 对话式AI + 规划能力 + 调用工具 。
AI Agent 的本质式是:它不仅具备对话式 AI 的能力,还能自主理解、规划并执行复杂任务——不只会“说”,更会“做”。
你和 AI Agent 的关系:你是“老板”,AI是“数字员工”;
你下达任务目标,AI Agent 会自己想办法、动手干,最后把成果交给你。
AI Agent 的选择
现在的 AI Agent 远超于两年前,不论是能力、数量。
从两年前我经常使用的 Cursor ,到后面经常使用的Claude code。
去年腾讯还开发了一款 AI Agent:CodeBuddy。

(这份列表由AI总结)
对于 AI Agent,我们不是每一款都需要去使用,要坚持一个原则:适合自己的,才是最好的。
我从一开始使用的 cursor 转到 Claude code ,是因为它的终端命令行使用方式很简洁,用完就关掉,完全不会拖泥带水,一个项目中一次性开多个命令行来跑多个 Agent 也毫无压力。

AI Agent 跟 对话式AI 相比,便捷在哪呢?
还是用《你不用会写代码,用AI就能实现自己的编程想法》举例:
我们原来使用 对话式AI 的获取技术栈:

(使用 对话式AI 生成的技术栈有限,没法顾及到细节)
我使用的 AI Agent 是 Claude code。
Claude code 生成 的技术栈:


直接把生成的技术栈贴出来吧:
# 技术栈文档:时间线记录**版本**:v0.1**对应 PRD**:v0.1**日期**:2026-06-08---## 1. 技术选型总览| 层级 | 技术 | 说明 ||------|------|------|| 前端 | HTML + CSS + 原生 JavaScript | 不使用框架,手动组件化 || 后端 | PHP 7.4+ | 无框架,原生 PHP 文件 || 数据存储 | JSON 文件 | 无数据库 || 图片存储 | 本地文件系统 | 原图 + 缩略图分离 || 图片处理 | PHP GD 库 | 上传时生成缩略图 |---## 2. 整体架构```浏览器 (HTML/CSS/原生JS)|| HTTP / fetch (JSON API)vPHP 后端 (api/*.php)|+---> data/records.json (记录数据)+---> data/temp_images.json (上传暂存)+---> uploads/images/ (原始图片)+---> uploads/thumbs/ (缩略图)```前端通过 `fetch` 调用 PHP RESTful API,PHP 读写 JSON 文件作为持久化层。图片以文件形式存储在服务器磁盘上,JSON 中仅保存路径引用。---## 3. 目录结构```timeline-app/├── index.html # 单页应用入口├── .htaccess # Apache 配置(安全规则、路由)├── css/│ ├── base.css # 重置样式、CSS 变量、排版│ ├── layouts.css # 图片布局(大图/横排/网格/九宫格)│ └── components/│ ├── card.css # 记录卡片│ ├── form.css # 新建/编辑表单│ ├── calendar.css # 日历组件│ ├── search.css # 搜索页│ ├── modal.css # 弹窗│ └── navbar.css # 导航栏├── js/│ ├── app.js # 应用入口,路由注册│ ├── router.js # 哈希路由器│ ├── api.js # API 请求封装│ ├── store.js # 简单状态管理│ ├── pages/│ │ ├── timeline.js # 首页信息流│ │ ├── record-form.js # 新建/编辑记录页│ │ ├── search.js # 搜索结果页│ │ └── calendar.js # 日历月视图页│ ├── components/│ │ ├── record-card.js # 单条记录卡片渲染│ │ ├── image-grid.js # 图片布局渲染器│ │ ├── tag-list.js # 标签列表渲染│ │ ├── modal.js # 通用弹窗│ │ ├── toast.js # 轻提示│ │ └── image-uploader.js # 图片上传+预览组件│ └── utils/│ ├── date.js # 日期格式化│ ├── lazy-load.js # 图片懒加载│ └── infinite-scroll.js # 无限滚动封装├── api/│ ├── config.php # 配置文件(路径、常量)│ ├── init.php # 公共引导(JSON头、锁机制)│ ├── records.php # 记录 CRUD│ ├── upload.php # 图片上传│ ├── search.php # 关键词搜索│ └── calendar.php # 日历数据├── data/│ ├── records.json # 主数据文件│ └── temp_images.json # 上传暂存图片信息└── uploads/├── images/ # 原始图片(按 年/月 子目录)└── thumbs/ # 缩略图(按 年/月 子目录)```---## 4. JSON 数据结构设计### 4.1 records.json```json{"meta": {"version": "1.0","lastModified": "2026-06-08T12:00:00+08:00"},"records": [{"id": "rec_1717800000_a3f2","content": "第一次自己拿勺子吃饭,吃得满脸都是但超认真","tags": ["吃饭", "成长"],"images": [{"id": "img_001","original": "uploads/images/2026/06/1717800000_a3f2_001.jpg","thumbnail": "uploads/thumbs/2026/06/1717800000_a3f2_001_thumb.jpg","width": 1920,"height": 1080}],"createdAt": "2026-06-08T18:30:00+08:00","updatedAt": "2026-06-08T18:30:00+08:00","recordDate": "2026-06-08"}]}```### 4.2 字段设计说明| 字段 | 类型 | 说明 ||------|------|------|| `id` | string | 格式 `rec_{timestamp}_{4位随机hex}`。时间戳前缀保证天然有序,随机后缀避免冲突 || `content` | string | 文字内容,完整记录用户输入 || `tags` | string[] | 自由标签数组,不做去重和预设 || `images` | array | 图片对象数组,每个包含 id、原图路径、缩略图路径、宽高 || `createdAt` | string | 系统创建时间(ISO 8601),不可修改 || `updatedAt` | string | 最后修改时间(ISO 8601),每次编辑时更新 || `recordDate` | string | 记录日期(YYYY-MM-DD),用户可修改(FR-6)。排序和日历查询基于此字段 |**recordDate 独立于 createdAt 的原因**:PRD FR-6 明确要求用户可以修改记录日期。`createdAt` 是系统时间不可变,`recordDate` 是用户指定的业务日期,信息流排序和日历高亮都基于 `recordDate`。### 4.3 图片存储方案```uploads/├── images/│ └── 2026/│ └── 06/│ ├── 1717800000_a3f2_001.jpg│ └── 1717800000_a3f2_002.jpg└── thumbs/└── 2026/└── 06/├── 1717800000_a3f2_001_thumb.jpg└── 1717800000_a3f2_002_thumb.jpg```- 按 `年/月` 建子目录,避免单目录文件过多- 文件名格式:`{timestamp}_{recordId后4位}_{序号}`- 缩略图:上传时 PHP GD 库同步生成,固定宽度 400px 等比缩放,JPEG 质量 80%- 信息流中使用缩略图,点击查看时加载原图---## 5. PHP API 设计### 5.1 公共约定**请求格式**:- 普通 API:`Content-Type: application/json`- 文件上传:`multipart/form-data`**统一响应格式**:成功:```json{"success": true,"data": { ... },"error": null}```失败:```json{"success": false,"data": null,"error": {"code": "RECORD_NOT_FOUND","message": "记录不存在"}}```### 5.2 API 端点列表| 方法 | 端点 | 功能 | 对应 PRD ||------|------|------|----------|| GET | `api/records.php` | 获取记录列表(分页) | FR-8 || GET | `api/records.php?id=xxx` | 获取单条记录 | FR-6 || POST | `api/records.php` | 新建记录 | FR-1~5 || PUT | `api/records.php?id=xxx` | 更新记录 | FR-6 || DELETE | `api/records.php?id=xxx` | 删除记录 | FR-7 || POST | `api/upload.php` | 图片上传 | FR-3 || GET | `api/calendar.php?month=YYYY-MM` | 日历月数据 | FR-12 || GET | `api/search.php?q=keyword` | 关键词搜索 | FR-14 |### 5.3 各端点详细设计#### (1) GET api/records.php — 获取记录列表首页信息流分页加载,支持按日期筛选。**Query 参数**:- `page` (int, 默认1) — 页码- `limit` (int, 默认20) — 每页条数- `date` (string, 可选) — 指定日期,格式 `YYYY-MM-DD`**请求示例**:```GET api/records.php?page=1&limit=20GET api/records.php?date=2026-06-08```**响应**:```json{"success": true,"data": {"records": [ ... ],"pagination": {"page": 1,"limit": 20,"total": 156,"hasMore": true}}}```**PHP 逻辑**:1. 读取 `records.json`,解析为数组2. 按 `recordDate` 降序排列3. 若有 `date` 参数,过滤 `recordDate === date` 的记录4. 按 `page` 和 `limit` 做数组切片5. 返回分页结果#### (2) GET api/records.php?id=xxx — 获取单条记录**响应**:```json{"success": true,"data": {"id": "rec_1717800000_a3f2","content": "...","tags": ["吃饭"],"images": [ ... ],"createdAt": "...","updatedAt": "...","recordDate": "2026-06-08"}}```#### (3) POST api/records.php — 新建记录**请求体**:```json{"content": "第一次自己拿勺子吃饭","tags": ["吃饭", "成长"],"imageIds": ["tmp_1717800000_001", "tmp_1717800000_002"],"recordDate": "2026-06-08"}```> 图片先通过 `upload.php` 上传获得临时 ID,创建记录时再关联。上传和文字编辑解耦,用户可以先传图再写文字。**PHP 逻辑**:1. 生成 `id`:`rec_{time()}_{bin2hex(random_bytes(2))}`2. 将临时图片从暂存区移入正式目录3. 读取 JSON,追加记录,写回4. 返回完整记录#### (4) PUT api/records.php?id=xxx — 更新记录**请求体**:```json{"content": "修改后的文字","tags": ["吃饭", "成长", "新增标签"],"imageIds": ["img_001", "tmp_new_image"],"removedImageIds": ["img_002"],"recordDate": "2026-06-07"}```**PHP 逻辑**:1. 找到目标记录2. 删除 `removedImageIds` 对应的图片文件和缩略图3. 处理新增图片(从暂存区移入)4. 更新字段,设置 `updatedAt`5. 写回 JSON#### (5) DELETE api/records.php?id=xxx — 删除记录**PHP 逻辑**:1. 找到目标记录2. 遍历其 `images` 数组,逐一删除原图和缩略图文件3. 从 JSON 中移除该记录4. 写回 JSON#### (6) POST api/upload.php — 图片上传**请求**:`multipart/form-data`,字段名 `image`**PHP 逻辑**:1. 验证文件类型(仅接受 `image/jpeg`、`image/png`、`image/webp`)2. 验证文件大小(上限 10MB)3. 生成文件名,保存原图到 `uploads/images/{Y}/{m}/`4. 用 GD 库生成缩略图(宽度 400px,等比缩放),保存到 `uploads/thumbs/{Y}/{m}/`5. 将图片信息暂存到 `data/temp_images.json`(含临时 ID、路径、过期时间)6. 返回临时图片 ID 和预览 URL**响应**:```json{"success": true,"data": {"imageId": "tmp_1717800000_a1b2","previewUrl": "uploads/thumbs/2026/06/1717800000_a1b2_thumb.jpg","originalUrl": "uploads/images/2026/06/1717800000_a1b2.jpg","width": 1920,"height": 1080}}```#### (7) GET api/calendar.php — 日历数据**Query 参数**:- `month` (string) — 格式 `YYYY-MM`**响应**:```json{"success": true,"data": {"month": "2026-06","days": {"01": 2,"03": 1,"08": 5,"15": 1}}}````days` 对象的 key 是日期(两位),value 是该日记录条数。前端用此数据高亮有记录的日期。**PHP 逻辑**:遍历所有记录,按 `recordDate` 归属于指定月份的,统计每个日期的数量。#### (8) GET api/search.php — 关键词搜索**Query 参数**:- `q` (string) — 搜索关键词- `page` (int, 默认1)- `limit` (int, 默认20)**响应**:与记录列表相同的结构。**PHP 逻辑**:1. 读取所有记录2. 对每条记录用 `mb_stripos`(支持中文不区分大小写)匹配 `content` 和 `tags`3. 按 `recordDate` 降序返回匹配结果```phpfunction searchRecords(array $records, string $query): array {$results = [];$query = mb_strtolower($query, 'UTF-8');foreach ($records as $record) {$matched = false;// 搜索文字内容$content = mb_strtolower($record['content'], 'UTF-8');if (mb_strpos($content, $query) !== false) {$matched = true;}// 搜索标签if (!$matched) {foreach ($record['tags'] as $tag) {if (mb_strpos(mb_strtolower($tag, 'UTF-8'), $query) !== false) {$matched = true;break;}}}if ($matched) {$results[] = $record;}}// 按 recordDate 降序usort($results, fn($a, $b) => strcmp($b['recordDate'], $a['recordDate']));return $results;}```### 5.4 JSON 读写锁机制使用 `flock()` 文件排他锁,封装为统一的锁操作函数。所有 API 端点对 `records.json` 的读写必须通过此函数。```phpfunction withJsonLock(string $filePath, callable $operation): mixed {$lockFile = $filePath . '.lock';$fp = fopen($lockFile, 'w+');if (!flock($fp, LOCK_EX)) {fclose($fp);throw new Exception('无法获取文件锁');}try {$data = json_decode(file_get_contents($filePath), true);$result = $operation($data);file_put_contents($filePath,json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),LOCK_EX);return $result;} finally {flock($fp, LOCK_UN);fclose($fp);}}```关键点:- 使用独立的 `.lock` 文件,避免锁和读写竞争同一个文件句柄- `LOCK_EX` 排他锁保证同一时刻只有一个写操作- 读操作也加锁(`LOCK_SH` 共享锁),避免读到写了一半的脏数据- 写操作完成后更新 `meta.lastModified`### 5.5 .htaccess 安全配置```apache# 禁止直接访问 data/ 目录<Directory "data">Require all denied</Directory># 禁止直接访问 .lock 文件<Files ~ "\.lock$">Require all denied</Files>```---## 6. 前端架构### 6.1 路由方案使用 `location.hash` 实现单页应用路由,无需服务端配置。**路由注册**(在 `app.js` 中):```javascriptRouter.register('/', TimelinePage.show);Router.register('/new', RecordFormPage.show);Router.register('/edit/:id', RecordFormPage.show);Router.register('/search', SearchPage.show);Router.register('/calendar', CalendarPage.show);```**路由实现**(`router.js`):- 监听 `hashchange` 事件- 支持参数路由(如 `#/edit/rec_xxx` 中的 `:id`)- 路由切换时清空 `#app` 容器,渲染新页面### 6.2 页面与组件划分**页面模块**(对应路由):| 模块 | 路由 | 功能 ||------|------|------|| `pages/timeline.js` | `#/` | 首页信息流(FR-8~11) || `pages/record-form.js` | `#/new`、`#/edit/:id` | 新建/编辑记录(FR-1~7) || `pages/search.js` | `#/search` | 搜索结果列表(FR-14~15) || `pages/calendar.js` | `#/calendar` | 日历月视图(FR-12~13) |**组件模块**(可复用):| 模块 | 职责 ||------|------|| `components/record-card.js` | 渲染单条记录卡片(图文+标签) || `components/image-grid.js` | 根据图片数量选择布局(1大图/2-3横排/4网格/5-9九宫格) || `components/tag-list.js` | 渲染标签列表 || `components/modal.js` | 通用弹窗(删除确认等) || `components/toast.js` | 轻提示(保存成功等) || `components/image-uploader.js` | 图片上传、预览、删除、排序 |**组件约定**:每个组件导出一个对象,包含:- `render(data)` → 返回 HTML 字符串- `bind(containerEl, callbacks)` → DOM 插入后调用,绑定事件### 6.3 API 请求封装`js/api.js` 统一封装所有后端请求:```javascriptconst Api = {async request(method, url, data = null) { ... },getRecords(page, limit) { ... },getRecord(id) { ... },createRecord(data) { ... },updateRecord(id, data) { ... },deleteRecord(id) { ... },uploadImage(file) { ... }, // FormData 特殊处理search(query, page) { ... },getCalendar(month) { ... }};```### 6.4 图片上传预览**流程**:1. 用户点击添加图片 → 触发隐藏的 `<input type="file" multiple accept="image/*">`2. **即时预览**:`URL.createObjectURL(file)` 生成本地 blob URL,立即显示缩略图3. **后台上传**:调用 `Api.uploadImage(file)`,使用 `XMLHttpRequest` 获取上传进度4. 上传完成后标记为"已上传",保存服务器返回的 `imageId`5. 最终保存记录时,收集所有已上传成功的 `imageId` 数组传给后端**限制**:单次最多 9 张图片(PRD FR-10 的九宫格上限)。### 6.5 信息流无限滚动使用 `IntersectionObserver` 监听哨兵元素,提前 200px 触发加载下一页。```[记录卡片...][记录卡片...][记录卡片...][哨兵元素] ← 进入视口时触发加载[加载动画...]```配合图片懒加载:卡片中的图片使用 `data-src` 存放真实 URL,另一个 `IntersectionObserver` 监听图片进入视口时才设置 `src`。### 6.6 日历组件1. 调用 `Api.getCalendar('2026-06')` 获取当月数据2. 前端渲染月历网格(6 行 7 列表格)3. 有记录的日期高亮可点击,无记录的日期置灰不可点(FR-12)4. 顶部左右箭头切换月份5. 点击高亮日期 → 信息流跳转到该日第一条记录(FR-13)**HTML 结构**:```div.calendardiv.calendar-headerbutton.prev-monthspan.current-month(2026年6月)button.next-monthdiv.calendar-weekdaysspan 日 / span 一 / ... / span 六div.calendar-griddiv.calendar-day.disabled -- 无记录div.calendar-day.highlighted[data-date] -- 有记录,显示条数```### 6.7 图片布局规则(对应 FR-10)| 图片数 | 布局 | CSS 实现 ||--------|------|----------|| 1 张 | 单张大图 | `width: 100%` || 2-3 张 | 横向排列 | `display: flex; flex-wrap: wrap` || 4 张 | 2×2 网格 | `display: grid; grid-template-columns: 1fr 1fr` || 5-9 张 | 九宫格 | `display: grid; grid-template-columns: 1fr 1fr 1fr`,超出 9 张显示前 9 张并标注 `+N` |### 6.8 简单状态管理```javascriptconst Store = {state: {records: [], // 当前已加载的记录currentPage: 1,hasMore: true,selectedDate: null},listeners: [],setState(updates) {Object.assign(this.state, updates);this.listeners.forEach(fn => fn(this.state));},subscribe(fn) {this.listeners.push(fn);return () => this.listeners = this.listeners.filter(f => f !== fn);}};```V1 中主要用于首页信息流状态维护,各页面通过 API 直接获取数据。---## 7. 关键技术决策### 7.1 图片存储**决策:上传时 GD 库同步生成缩略图**理由:- GD 库是 PHP 内置扩展,几乎所有主机都支持- 不引入额外的图片处理服务,保持架构简单- 信息流中使用缩略图,点击查看时加载原图,平衡加载速度和画质- 缩略图尺寸:宽度 400px,等比缩放,JPEG 质量 80%### 7.2 并发安全**决策:`flock()` 文件排他锁 + 最后写入者胜出**- 所有 JSON 读写通过 `withJsonLock()` 统一封装- 写操作用 `LOCK_EX` 排他锁,读操作用 `LOCK_SH` 共享锁- 对于同一记录的并发编辑,采用"最后写入者胜出"策略- `flock()` 在 NFS 上不可靠,此应用面向单机部署,足够使用### 7.3 搜索**决策:PHP 端 `mb_strpos` 遍历 JSON**理由:- 轻量工具预计几百到几千条记录,JSON 在 1MB 以内- PHP 读取 1MB JSON 并遍历,响应时间在 100ms 内- 不引入全文搜索引擎,保持架构简单- 搜索范围覆盖 `content` 和 `tags`(对应 FR-14)**优化预留**:未来数据量增长后可迁移到 SQLite(改动量很小,只需替换 PHP 数据读写层)。### 7.4 移动端适配**决策:移动优先 CSS**- PRD 用户旅程明确是"用手机随手记录",移动端为第一优先级- 基础样式面向 375px 宽度设计- 使用 `@media (min-width: 768px)` 做桌面端适配- 图片上传使用 `capture="environment"`,手机浏览器会调起相机- 按钮最小 44px 点击区域,滚动性能优化(`will-change: transform`)---## 8. 开发顺序建议| 阶段 | 内容 | 涉及功能 ||------|------|----------|| **一、骨架** | 目录结构、`.htaccess`、`config.php`/`init.php`、`records.php` GET 列表、`router.js`、`index.html`、首页信息流基本展示 | FR-8, FR-9 || **二、记录** | `upload.php`(含缩略图)、图片上传组件、新建/编辑/删除记录 | FR-1~7, FR-10, FR-11 || **三、检索** | `calendar.php` + 日历组件、`search.php` + 搜索页、日期跳转 | FR-12~15 || **四、优化** | 图片懒加载、移动端适配、错误处理、loading 状态、临时文件清理 | — |
(整整500多行,详细到每一个细节,这就是 AI Agent 的强大)
编码阶段:
Claude code:



( Claude code 直接按照技术栈中的进行逐步开发,开发好的代码也是直接在项目文件夹)
部署后的情况:


(直接一气呵成,省去了调试的时间,也实现了点开查看完整图)
如果说使用好 对话式AI 进行AI编程能省去50%的时间,
那使用 AI Agent能省去80%的时间。
Claude code 的扩展
Claude code 作为一个 AI Agent ,它的能力不仅仅是实现编程,
熟练掌握CLAUDE.md、Skills、subagents、hooks、MCP 的扩展,
你能实现重复、复杂、跨系统的工作流的自动化。
关于Claude code的安装,可以参考官网的安装教程,如果有不熟悉或者有疑惑的,也可以在后台询问我,我如果有空可以给予帮助。
谢谢你看我的文章!
夜雨聆风