Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,19 @@

## 模块 2: Application Development

> 理解软件是怎么运作的。基于 Chicago Booth BUSN 36110 课程核心思想,用螺旋式学习法(Spiral Learning)从零构建对 Web 应用的完整心智模型。
> 理解软件是怎么运作的。基于 Chicago Booth BUSN 36110 课程核心思想,用螺旋式学习法构建对 Web 应用的完整心智模型。
>
> → [**模块首页**](modules/app-dev-fundamentals/README.md)(课程思想、7 个目标、螺旋式学习法)

| 阶段 | 内容 |
|------|------|
| Web 基础 | HTTP、API、HTML / CSS |
| 编程逻辑 | 控制流、面向对象 |
| 动态应用 | 路由、表单、数据处理 |
| 数据层 | 关系型数据库、数据关联 |
| 安全与认证 | Cookie、认证、权限 |
| 综合实践 | "First of Many" 最终项目 |
> → [**模块首页**](modules/app-dev-fundamentals/README.md)(课程思想、教学理念、与 Agentic Coding 的关系)

| 章节 | 主题 | 一句话简介 |
|------|------|-----------|
| [01](modules/app-dev-fundamentals/01-how-the-web-works/README.md) | 互联网是怎么工作的 | HTTP 请求与响应,URL 的工作原理 |
| [02](modules/app-dev-fundamentals/02-data-modeling/README.md) | 数据建模 | 最高杠杆的技能——名词变成表 |
| [03](modules/app-dev-fundamentals/03-request-lifecycle/README.md) | 请求的生命周期 | RCAV: URL 如何变成你看到的页面 |
| [04](modules/app-dev-fundamentals/04-databases-and-crud/README.md) | 数据库与 CRUD | 增删改查——所有应用的骨架 |
| [05](modules/app-dev-fundamentals/05-apis/README.md) | API | "一切都是 HTTP 请求" |
| [06](modules/app-dev-fundamentals/06-auth-and-security/README.md) | 认证与安全 | 谁是你,你能做什么 |
| [方法论](modules/app-dev-fundamentals/methodology/README.md) | 教学理念 | 螺旋学习、砍范围、AI 是乘数 |

---

Expand Down
136 changes: 136 additions & 0 deletions modules/app-dev-fundamentals/01-how-the-web-works/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
> [← 模块首页](../README.md) · [下一章: 数据建模 →](../02-data-modeling/README.md)

# 01: 互联网是怎么工作的(HTTP 请求与响应)

> 一句话:你在浏览器输入一个网址,按下回车,发生了什么?

## 为什么 Coding Agent 用户需要懂这个

当你让 Claude Code 或 Cursor"加一个页面"时,你其实在要求它做一件非常具体的事:创建一个 URL,让服务器在收到这个 URL 的请求时返回一段 HTML 响应。

如果你不理解"页面 = URL = 请求 + 响应"这个等式,你就没法准确描述你想要什么。你可能会说"加一个关于我们的页面",但你真正需要说的是"加一个 `/about` 路由,返回公司介绍页面"。

后一种说法,AI 能更准确地执行。

## 核心心智模型

### 餐厅点餐类比

整个互联网的运作逻辑,和去餐厅吃饭差不多:

| 餐厅 | 互联网 |
|------|--------|
| 你(顾客) | 浏览器(Browser) |
| 你想吃的菜名 | 网址 / URL |
| 你的点菜单 | 请求(Request) |
| 厨房 | 服务器(Server) |
| 端上来的菜 | 响应(Response) |

流程是这样的:

1. 你(浏览器)走进餐厅,告诉服务员你要什么(输入 URL)
2. 服务员把你的点菜单递给厨房(发送 HTTP 请求)
3. 厨房做菜(服务器处理请求、查数据库、生成页面)
4. 菜端上来(服务器返回 HTTP 响应)
5. 你看到菜了(浏览器渲染页面)

每一次你在网上点击一个链接、提交一个表单、刷新一个页面,都是在重复这个过程。没有例外。

### HTTP 方法:你在对服务器说什么

不是所有请求都一样。最常用的两种:

- **GET** — "给我看一下这个东西"。你打开一个页面、查看一篇文章,都是 GET 请求。就像在餐厅看菜单,你只是在看,没有改变任何东西。
- **POST** — "我要提交一些信息"。你注册一个账号、提交一个表单、发布一条评论,都是 POST 请求。就像正式点菜——你在告诉厨房"请做这道菜"。

还有 PUT/PATCH(修改)和 DELETE(删除),但 GET 和 POST 是最基础的两个。理解了这两个,其他的都是变体。

### URL 的结构

一个 URL 不是一串随机字符,它有明确的结构:

```
https://www.example.com/users/123?tab=posts
│ │ │ │
协议 域名 路径 查询参数
```

- **协议**(Protocol):`https://` — 通信规则,https 表示加密的
- **域名**(Domain):`www.example.com` — 服务器的地址,就像餐厅的名字和位置
- **路径**(Path):`/users/123` — 你想要的具体资源,就像菜单上的菜名
- **查询参数**(Query String):`?tab=posts` — 额外的要求,就像"少辣"、"多加醋"

当你让 AI"加一个用户详情页面"时,你其实在定义一个新的路径(比如 `/users/:id`),这个路径在收到 GET 请求时返回对应用户的信息。

### "动态"意味着什么

这是一个关键概念:**同一个 URL 模式,可以返回不同的内容**。

`/users/1` 和 `/users/2` 的 URL 结构一样,但返回的是不同用户的信息。这就是"动态"的意思——页面不是提前写好的文件,而是服务器根据请求中的参数,实时从数据库里查出数据,拼装成页面返回给你。

这和静态网页(一个 HTML 文件对应一个页面)完全不同。理解这一点,你才能理解为什么现代应用都需要数据库。

### 状态码:服务器在告诉你发生了什么

每个响应都会带一个三位数的状态码,告诉浏览器"结果怎么样":

| 状态码 | 含义 | 餐厅类比 |
|--------|------|----------|
| **200** | 成功(OK) | 菜上了,一切正常 |
| **301/302** | 重定向(Redirect) | "这道菜改名了,帮你换一个" |
| **404** | 找不到(Not Found) | "菜单上没有这道菜" |
| **403** | 没有权限(Forbidden) | "这是 VIP 专属,你不能点" |
| **500** | 服务器内部错误(Internal Server Error) | "厨房着火了" |

当你的应用报错时,第一步就是看状态码。404 说明 URL 路径写错了或者路由没配置;500 说明服务器端的代码有 bug。这是最基本的排错信息。

## 常见误区

### 误区 1:"网页是一个文件"

很多人以为每个网页都对应服务器上的一个 HTML 文件。在静态网站时代确实如此,但现代 Web 应用完全不同——每个页面都是服务器收到请求后**动态生成**的响应。同一个 URL,登录用户和未登录用户看到的内容可能完全不同。

### 误区 2:"前端和后端是两个独立的东西"

前端和后端不是两个独立的世界,它们通过 HTTP 请求紧密连接。前端(浏览器)发送请求,后端(服务器)处理请求并返回响应。理解它们之间的这条"管道"比分别理解两端更重要。

## 这个概念在 Agentic Coding 中的应用

理解 HTTP 请求-响应模型后,你在 Agentic Coding 模块中会更顺畅:

- **Week 2(全栈开发)**:你会用 AI 构建一个完整的 Web 应用。每一个页面、每一个表单提交,都是一次 HTTP 请求-响应。理解这个模型后,你能更准确地描述你要的功能。
- **Week 3(MCP 服务器)**:MCP 协议本质上也是基于请求-响应模式的通信。理解了 HTTP,你对 MCP 的理解会快很多。
- **调试**:当应用报错时,你的第一反应应该是"这是哪一步出了问题?是请求没发出去?是服务器处理出错?还是响应格式不对?"

## 动手试一试

不需要安装任何东西,点开就能练:

### 练习 1:看一个真实的 HTTP 请求
1. 打开浏览器,按 `F12`(或右键 → 检查)打开开发者工具
2. 切换到 **Network**(网络)标签
3. 在地址栏输入任意网址(比如 `https://httpbin.org/get`)并回车
4. 观察 Network 面板里出现的请求:请求方法(GET)、状态码(200)、响应内容(JSON)
5. 这就是一个完整的 HTTP 请求-响应过程

### 练习 2:发送不同类型的请求
打开 [httpbin.org](https://httpbin.org/) — 这是一个专门用来测试 HTTP 请求的免费服务:
- 点击 `/get` → 看 GET 请求返回什么
- 点击 `/status/404` → 体验一个 404 状态码
- 点击 `/status/500` → 体验一个服务器错误

### 练习 3:理解 URL 结构
观察这个 URL,尝试拆解它的组成部分:
```
https://www.google.com/search?q=coding+agent&hl=zh
```
- 协议是什么?域名是什么?路径是什么?查询参数有哪些?
- 试着修改 `q=` 后面的内容,看搜索结果怎么变化

## 下一步

→ [02: 数据建模 — 最高杠杆的技能](../02-data-modeling/README.md)

---

[← 模块首页](../README.md) · [下一章: 数据建模 →](../02-data-modeling/README.md)
203 changes: 203 additions & 0 deletions modules/app-dev-fundamentals/02-data-modeling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
> [← 模块首页](../README.md) · [← 上一章: 互联网是怎么工作的](../01-how-the-web-works/README.md) · [下一章: 请求的生命周期 →](../03-request-lifecycle/README.md)

# 02: 数据建模 — 最高杠杆的技能

> 一句话:在写任何代码之前,先想清楚你要存什么数据、数据之间是什么关系。

## 为什么这是最重要的一课

> "If we can get this part right, the rest of the code practically writes itself."
>
> (如果我们能把数据建模做对,剩下的代码几乎是自动写出来的。)

这句话在传统开发中就已经是真理。在 Coding Agent 时代,它的分量增加了十倍。

为什么?因为 AI 特别擅长根据数据结构来生成代码。你给 AI 一个清晰的数据模型——有哪些表、每张表有哪些字段、表之间是什么关系——AI 就能自动生成增删改查的接口、表单页面、数据校验逻辑。但如果你的数据模型是错的,AI 生成的一切都会建立在一个有裂缝的地基上。

**一个正确的数据模型,是你能给 Coding Agent 的最高杠杆输入。**

## 核心心智模型

### "名词变成表"方法

数据建模听起来很抽象,但方法非常简单:

1. **列出你的应用里的核心名词** — 用户、文章、评论、订单、商品、标签……
2. **每个名词 = 一张数据表** — User 表、Article 表、Comment 表……
3. **每张表的列 = 这个名词的属性** — User 表有 name、email、password;Article 表有 title、body、published_at

就这样。数据建模的第一步不是画 ER 图、不是写 SQL,而是用日常语言列出你的应用里有哪些"东西"。

**试一试**:想想你日常用的任何一个应用——微信、淘宝、抖音。列出它的核心名词。你会发现,虽然界面天差地别,但底层的数据结构都是"名词 + 属性 + 关系"。

### "纸质表格测试"

这是检验数据模型是否正确的最朴素方法:

1. 在纸上画出你的每张表,像 Excel 那样,列头是字段名
2. 填上几行示例数据(真实的、具体的数据,不是"xxx")
3. 用手指模拟查询:"我想查张三发布的所有文章"——你能从这些表里找到吗?
4. 再试:"我想看某篇文章的所有评论"——找得到吗?
5. 如果找不到,说明你缺了某个字段或某个关系

如果你手动能查到想要的数据,说明数据模型是对的。如果查不到,问题一定出在表的设计上——要么缺了某张表,要么缺了某个关联字段。

这个测试看起来很"低技术",但它比任何建模工具都有效。因为它迫使你用真实数据去验证,而不是在抽象概念里打转。

### 关系类型

数据之间的关系只有三种。记住这三种,你就能建模 95% 的应用:

#### 一对多(One-to-Many)

一个用户有多篇文章,但每篇文章只属于一个用户。

怎么实现?在"多"的那一方加一个外键(Foreign Key)。文章表里加一个 `user_id` 列,指向用户表的 `id`。

```
用户表 (Users) 文章表 (Articles)
┌────┬──────┐ ┌────┬──────────┬─────────┐
│ id │ name │ │ id │ title │ user_id │
├────┼──────┤ ├────┼──────────┼─────────┤
│ 1 │ 张三 │ │ 1 │ 第一篇 │ 1 │
│ 2 │ 李四 │ │ 2 │ 第二篇 │ 1 │
└────┴──────┘ │ 3 │ 第三篇 │ 2 │
└────┴──────────┴─────────┘
```

张三(id=1)有两篇文章,李四(id=2)有一篇。通过 `user_id` 就能查到。

#### 多对多(Many-to-Many)

一个学生选多门课,一门课有多个学生。

这时候不能简单地在一方加外键。你需要一张**中间表**(Join Table),专门记录"谁选了哪门课":

```
学生表 选课表 (Enrollments) 课程表
┌────┬──────┐ ┌────┬────────────┬───────────┐ ┌────┬────────┐
│ id │ name │ │ id │ student_id │ course_id │ │ id │ name │
├────┼──────┤ ├────┼────────────┼───────────┤ ├────┼────────┤
│ 1 │ 张三 │ │ 1 │ 1 │ 1 │ │ 1 │ 数学 │
│ 2 │ 李四 │ │ 2 │ 1 │ 2 │ │ 2 │ 英语 │
└────┴──────┘ │ 3 │ 2 │ 1 │ └────┴────────┘
└────┴────────────┴───────────┘
```

张三选了数学和英语,李四选了数学。中间表就是那个"粘合剂"。

#### 一对一(One-to-One)

一个用户有一份个人档案。相对少见,但偶尔会用到。处理方式和一对多类似——在其中一方加外键,**但必须加上唯一性约束**(Unique Constraint),确保每个用户只能有一份档案。没有唯一性约束的话,它实际上就变成了一对多。

### 判断关系的黄金问题

面对任意两个名词,问自己两个问题:

> "一个 X 能有多个 Y 吗?一个 Y 能有多个 X 吗?"

| X → 多个 Y? | Y → 多个 X? | 关系类型 |
|:---:|:---:|:---:|
| 否 | 否 | 一对一 |
| 是 | 否 | 一对多(X 是"一",Y 是"多") |
| 否 | 是 | 一对多(Y 是"一",X 是"多") |
| 是 | 是 | 多对多 |

每次遇到新的名词关系,用这两个问题一测,答案立刻出来。

## 常见误区

### 误区 1:"先写代码再想数据结构"

这是最常见的错误。很多人上来就让 AI 写页面、写功能,结果做到一半发现数据存不对,只好推翻重来。**数据结构决定一切**。先用 5 分钟想清楚名词、属性、关系,后面能省几个小时的返工。

### 误区 2:"表越多越好"或"越少越好"

表的数量不是目标,**每个独立的名词一张表**才是原则。不要把不同的东西硬塞进一张表(比如把"用户"和"订单"放在一起),也不要把一个东西拆成多张表(比如把用户的基本信息和联系方式分成两张表,除非有特殊理由)。

### 误区 3:"AI 会帮我设计数据模型"

AI 可以建议数据模型,但只有你才理解你的业务领域。"一个老师能教多门课吗?"这种问题取决于你的业务规则,不取决于技术。如果你在教培机构,一个老师可能只教一门课;如果你在大学,一个老师通常教多门课。AI 不知道你的具体情况,你知道。

## 练习:用你自己的项目试一试

试着为一个**读书笔记应用**做数据建模:

**第一步:列出名词**
- 用户(User)
- 书(Book)
- 笔记(Note)
- 标签(Tag)

**第二步:列出属性**
- User:name、email、password
- Book:title、author、isbn
- Note:content、page_number、created_at
- Tag:name

**第三步:用黄金问题判断关系**
- 一个用户能有多本书吗?是。一本书能属于多个用户吗?是。→ **多对多**(需要中间表,比如 UserBook)
- 一本书能有多条笔记吗?是。一条笔记能属于多本书吗?否。→ **一对多**(Note 加 book_id)
- 一条笔记能有多个标签吗?是。一个标签能属于多条笔记吗?是。→ **多对多**(需要中间表 NoteTag)
- 一个用户能有多条笔记吗?是。一条笔记能属于多个用户吗?否。→ **一对多**(Note 加 user_id)

**第四步:纸质表格测试**

在纸上画出这些表,填上几行数据,试试能不能查到"张三在《思考,快与慢》这本书上写的所有笔记"。如果能查到,数据模型就是对的。

## 动手试一试

### 在线 SQL 练习:亲手建表查询
打开 [SQLiteOnline](https://sqliteonline.com/) — 浏览器里直接写 SQL,不需要安装任何东西:

1. **建一张用户表**(复制粘贴到左侧编辑器,点 Run):
```sql
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
);
INSERT INTO users VALUES (1, '张三', 'zhang@example.com');
INSERT INTO users VALUES (2, '李四', 'li@example.com');
SELECT * FROM users;
```

2. **建一张文章表,体验外键关联**:
```sql
CREATE TABLE articles (
id INTEGER PRIMARY KEY,
title TEXT,
user_id INTEGER
);
INSERT INTO articles VALUES (1, '第一篇文章', 1);
INSERT INTO articles VALUES (2, '第二篇文章', 1);
INSERT INTO articles VALUES (3, '第三篇文章', 2);
SELECT * FROM articles WHERE user_id = 1;
```
最后一行查询的意思是:"查找张三(id=1)的所有文章"。运行看看结果。

3. **试试连表查询**:
```sql
SELECT users.name, articles.title
FROM articles
JOIN users ON articles.user_id = users.id;
```
这就是把两张表"连"起来,同时看到作者名字和文章标题。

> 这些 SQL 你不需要背。重要的是亲手跑一遍,体会"表"和"关系"在真实数据库中是怎么工作的。

## 这个概念在 Agentic Coding 中的应用

数据建模是你与 Coding Agent 协作的起点。实际操作建议:

1. **先描述数据模型,再描述功能** — 给 AI 的 prompt 中,第一段就应该是数据模型:"这个应用有四张表:User、Book、Note、Tag,关系如下……"。然后再说功能需求。这是最高效的沟通方式。
2. **把数据模型写进 CLAUDE.md** — 如果你用 Claude Code,在项目的 CLAUDE.md 文件中明确写出数据模型。这样 AI 在整个开发过程中都有一致的参照。(参见 [Agentic Coding Week 4](../../agentic-coding/week4/README.md))
3. **数据模型变了,所有代码都要跟着变** — 如果你中途发现某个关系类型判断错了(比如把一对多误判为一对一),要立刻修正。越早修正代价越小。

## 下一步

→ [03: 请求的生命周期(RCAV 模式)](../03-request-lifecycle/README.md)

---

[← 上一章: 互联网是怎么工作的](../01-how-the-web-works/README.md) · [下一章: 请求的生命周期 →](../03-request-lifecycle/README.md)
Loading