CS50 Week 9: Flask - 构建你的第一个 Web 应用
Week 9 概述
本周主题:使用 Flask 框架构建动态 Web 应用程序
在过去的几周里,我们学习了多种编程语言、技术和策略。事实上,这门课与其说是 C 语言课或 Python 课,不如说是一门编程思维课,让你能够继续追随未来的编程趋势。
今天我们将整合之前学过的所有技术:HTML、CSS、SQL、Python 和 JavaScript,创建属于自己的 Web 应用程序!
💡 提示:本周所学技能非常适合用来完成 CS50 的最终项目(Final Project)!
🌐 Web 开发基础知识(给初学者)
在深入 Flask 之前,让我们先理解一些 Web 开发的核心概念:
什么是 Web 服务器?
┌─────────────┐ 请求(Request) ┌─────────────┐
│ 浏览器 │ ─────────────────────────────> │ 服务器 │
│ (Client) │ <───────────────────────────── │ (Server) │
└─────────────┘ 响应(Response) └─────────────┘
- 客户端(Client):你的浏览器(Chrome、Safari、Firefox 等)
- 服务器(Server):远程计算机,存储网站文件并处理请求
- 请求(Request):浏览器向服务器发送的”我要看这个页面”的消息
- 响应(Response):服务器返回的 HTML、CSS、图片等内容
静态页面 vs 动态页面
| 类型 | 说明 | 例子 |
|---|---|---|
| 静态页面 | HTML 文件固定不变,所有用户看到的内容相同 | 个人简历页面 |
| 动态页面 | 服务器根据用户请求实时生成 HTML | 淘宝商品页面、微博时间线 |
本周重点:学习如何用 Python + Flask 创建动态页面!
URL 结构解析
https://www.example.com/route?key=value&key2=value2
│ │ │ │
│ │ │ └── 查询参数(Query Parameters)
│ │ └── 路由(Route)
│ └── 域名(Domain)
└── 协议(Protocol)
- 路由(Route):服务器上的一个”地址”,对应一个功能
- 查询参数:通过
?key=value的形式传递数据给服务器
http-server 回顾
迄今为止,我们见到的所有 HTML 都是预先编写且静态的。
过去,当你访问网页时,浏览器会下载 HTML 页面供你查看。这类页面被称为静态页面,因为 HTML 中的内容完全等同于用户所见,由客户端(浏览器)直接渲染。
动态页面则是由 Python 等语言即时生成 HTML 的能力。你可以创建基于用户输入或行为,由服务器端代码动态生成的网页。
之前我们使用 http-server 来提供网页服务。今天我们将采用新型服务器——Flask,它能解析网址并根据 URL 执行相应操作。
上周你看到过如下 URL 格式:
https://www.example.com/folder/file.html
请注意,file.html 是位于 example.com 域名下名为 folder 的文件夹中的 HTML 文件。
Flask 入门
什么是 Flask?
Flask 是一个用 Python 编写的 Web 框架(或称微框架)。
框架(Framework):一套预先写好的代码库,帮助开发者更快速地构建应用程序。你不需要从零开始写所有代码。
Flask 的核心优势
- ✅ 简单易学:几行代码就能创建一个 Web 应用
- ✅ 灵活:你可以按需添加功能
- ✅ Python 生态:可以使用所有 Python 库
创建第一个 Flask 应用
文件结构
在开始之前,我们需要了解 Flask 项目的基本结构:
my_flask_app/
├── app.py # 主程序文件(Flask 应用代码)
├── requirements.txt # 依赖库列表
├── static/ # 静态文件(CSS、JS、图片)
└── templates/ # HTML 模板文件
└── index.html
Step 1: 创建 requirements.txt
这个文件列出了 Flask 应用运行所需的库:
Flask
Step 2: 创建 app.py
# 最简单的 Flask 应用:返回文本
from flask import Flask
# 创建 Flask 应用实例
# __name__ 告诉 Flask 当前模块的名称
app = Flask(__name__)
# 定义路由:当用户访问 "/" 时,执行这个函数
@app.route("/")
def index():
return "hello, world"
代码详解
让我们逐行理解这段代码:
| 代码 | 说明 |
|---|---|
from flask import Flask |
从 flask 库导入 Flask 类 |
app = Flask(__name__) |
创建一个 Flask 应用实例 |
@app.route("/") |
装饰器:定义 URL 路由 “/” |
def index(): |
当用户访问 “/” 时,执行这个函数 |
return "hello, world" |
返回给浏览器的内容 |
🔑 什么是装饰器(Decorator)?
@app.route("/")是 Python 的装饰器语法。它的作用是给函数”附加”功能。在这里,它告诉 Flask:当用户访问
/这个 URL 时,请执行下面的函数。你可以把装饰器理解为一个”标签”,标记这个函数对应哪个 URL。
Step 3: 运行应用
在终端窗口中输入:
flask run
然后访问终端显示的 URL(通常是 http://127.0.0.1:5000/),你就能看到 “hello, world”!
控制台日志示例:
* Restarting with stat
127.0.0.1 - - [07/Jan/2026 14:16:53] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [07/Jan/2026 14:17:08] "GET /?name=holly HTTP/1.1" 200 -
日志解读:
127.0.0.1:访问者的 IP 地址(本地主机)GET /:HTTP 请求方法和路径HTTP/1.1:HTTP 协议版本200:HTTP 状态码(200 表示成功)
返回 HTML 内容
Flask 也可以返回 HTML 代码:
# 返回 HTML 字符串
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return '<!DOCTYPE html><html lang="en"><head><title>hello</title></head><body>hello, world</body></html>'
但是,把 HTML 写在 Python 代码里很不优雅!代码难以阅读和维护。
更好的方案:使用模板(Templates)。
使用模板(Templates)
Flask 支持使用 Jinja2 模板引擎,让 HTML 和 Python 代码分离。
创建 templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>hello</title>
</head>
<body>
hello, { { name } }
</body>
</html>
🔑 Jinja2 模板语法
{ { variable } }:输出变量的值{ % ... % }:执行语句(如 if、for){# ... #}:注释
修改 app.py
# 使用模板和 URL 参数
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
# 从 URL 参数获取 name,默认值为 "world"
name = request.args.get("name", "world")
# 渲染模板,并传递变量
return render_template("index.html", name=name)
代码详解
| 代码 | 说明 |
|---|---|
from flask import render_template, request |
导入模板渲染函数和请求对象 |
request.args.get("name", "world") |
获取 URL 中的 name 参数,如果没有则默认为 “world” |
render_template("index.html", name=name) |
渲染模板,并把 name 变量传递给模板 |
测试
确保文件结构如下:
.
├── app.py
├── requirements.txt
└── templates
└── index.html
运行 flask run,然后尝试访问:
http://127.0.0.1:5000/→ 显示 “hello, world”http://127.0.0.1:5000/?name=David→ 显示 “hello, David”
表单(Forms)
我们知道大多数用户不会在地址栏中输入参数。程序员依赖于用户在网页上填写表单。
创建表单页面
修改 templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>hello</title>
</head>
<body>
<form action="/greet" method="get">
<input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
<button type="submit">Greet</button>
</form>
</body>
</html>
HTML 表单详解
| 属性/元素 | 说明 |
|---|---|
<form> |
表单容器 |
action="/greet" |
表单提交到的 URL |
method="get" |
HTTP 方法(GET 或 POST) |
<input> |
输入框 |
name="name" |
输入框的名称,服务器通过这个名称获取值 |
placeholder="Name" |
输入框的提示文字 |
autocomplete="off" |
禁用自动补全 |
autofocus |
页面加载后自动聚焦到这个输入框 |
<button type="submit"> |
提交按钮 |
添加问候路由
修改 app.py:
# 添加表单和第二个路由
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/greet")
def greet():
return render_template("greet.html", name=request.args.get("name", "world"))
创建 templates/greet.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width">
<title>hello</title>
</head>
<body>
hello, { { name } }
</body>
</html>
模板继承(Template Inheritance)
index.html 和 greet.html 包含大量相同的代码。我们可以使用模板继承来减少重复。
创建基础模板 layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>hello</title>
</head>
<body>
{ % block body % }{ % endblock % }
</body>
</html>
🔑 模板继承语法
{ % block name % }{ % endblock % }:定义一个可被子模板覆盖的”块”- 子模板使用
{ % extends "layout.html" % }继承父模板- 子模板使用
{ % block name % }...{ % endblock % }填充内容
修改 index.html
{ % extends "layout.html" % }
{ % block body % }
<form action="/greet" method="get">
<input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
<button type="submit">Greet</button>
</form>
{ % endblock % }
修改 greet.html
{ % extends "layout.html" % }
{ % block body % }
hello, { { name } }
{ % endblock % }
这样,公共的 HTML 结构只需要写一次!
请求方法(Request Methods)
GET vs POST
| 方法 | 特点 | 适用场景 |
|---|---|---|
| GET | 数据在 URL 中可见 | 搜索、获取数据 |
| POST | 数据在请求体中,URL 不可见 | 登录、提交表单 |
安全问题:使用 GET 方法时,用户名和密码会出现在 URL 中!
http://example.com/login?username=david&password=12345
这是非常不安全的!
使用 POST 方法
修改 app.py:
# 使用 POST 方法
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/greet", methods=["POST"])
def greet():
# 注意:POST 方法使用 request.form 而不是 request.args
return render_template("greet.html", name=request.form.get("name", "world"))
修改 index.html 的表单:
{ % extends "layout.html" % }
{ % block body % }
<form action="/greet" method="post">
<input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
<button type="submit">Greet</button>
</form>
{ % endblock % }
GET vs POST 代码对比
| GET | POST | |
|---|---|---|
| HTML 表单 | method="get" |
method="post" |
| Python 获取数据 | request.args.get() |
request.form.get() |
| 数据位置 | URL 参数 | 请求体 |
单一路由处理多种方法
可以让一个路由同时处理 GET 和 POST 请求:
# 单一路由处理 GET 和 POST
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
return render_template("greet.html", name=request.form.get("name", "world"))
return render_template("index.html")
处理空输入
如果用户不输入名字,会显示 “hello,” 而没有名字。我们可以在模板中处理这个情况:
{ % extends "layout.html" % }
{ % block body % }
hello,
{ % if name -% }
{ { name } }
{ %- else -% }
world
{ %- endif % }
{ % endblock % }
Frosh IMs 项目:完整的注册系统
现在让我们创建一个更完整的 Web 应用:学生体育活动注册系统。
项目结构
froshims/
├── app.py
├── requirements.txt
├── static/
│ └── cat.jpg
└── templates/
├── index.html
├── layout.html
├── success.html
├── failure.html
├── error.html
└── registrants.html
创建项目
mkdir froshims
cd froshims
mkdir templates static
基础版本
requirements.txt
Flask
app.py
# 实现注册表单,使用下拉菜单,服务器端验证
from flask import Flask, render_template, request
app = Flask(__name__)
# 可选的运动项目列表
SPORTS = [
"Basketball",
"Soccer",
"Ultimate Frisbee"
]
@app.route("/")
def index():
# 将运动列表传递给模板
return render_template("index.html", sports=SPORTS)
@app.route("/register", methods=["POST"])
def register():
# 验证提交的数据
if not request.form.get("name") or request.form.get("sport") not in SPORTS:
return render_template("failure.html")
# 注册成功
return render_template("success.html")
templates/layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width">
<title>froshims</title>
</head>
<body>
{ % block body % }{ % endblock % }
</body>
</html>
templates/index.html
{ % extends "layout.html" % }
{ % block body % }
<h1>Register</h1>
<form action="/register" method="post">
<input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
<select name="sport">
<option selected value="">Sport</option>
{ % for sport in sports % }
<option value="{ { sport } }">{ { sport } }</option>
{ % endfor % }
</select>
<button type="submit">Register</button>
</form>
{ % endblock % }
templates/success.html
{ % extends "layout.html" % }
{ % block body % }
You are registered!
{ % endblock % }
templates/failure.html
{ % extends "layout.html" % }
{ % block body % }
You are not registered!
{ % endblock % }
运行 flask run,你就能看到注册页面了!

改进版本:存储注册者
上面的版本有个问题:注册信息没有保存!让我们改进它。
改进 app.py
# 使用字典存储注册者
from flask import Flask, redirect, render_template, request
app = Flask(__name__)
# 存储注册者的字典
REGISTRANTS = {}
SPORTS = [
"Basketball",
"Soccer",
"Ultimate Frisbee"
]
@app.route("/")
def index():
return render_template("index.html", sports=SPORTS)
@app.route("/register", methods=["POST"])
def register():
# 验证姓名
name = request.form.get("name")
if not name:
return render_template("error.html", message="Missing name")
# 验证运动项目
sport = request.form.get("sport")
if not sport:
return render_template("error.html", message="Missing sport")
if sport not in SPORTS:
return render_template("error.html", message="Invalid sport")
# 保存注册信息
REGISTRANTS[name] = sport
# 重定向到注册者列表页面
return redirect("/registrants")
@app.route("/registrants")
def registrants():
return render_template("registrants.html", registrants=REGISTRANTS)
templates/error.html
{ % extends "layout.html" % }
{ % block body % }
<h1>Error</h1>
<p>{ { message } }</p>
<img alt="Grumpy Cat" src="/static/cat.jpg">
{ % endblock % }
templates/registrants.html
{ % extends "layout.html" % }
{ % block body % }
<h1>Registrants</h1>
<ul>
{ % for name in registrants % }
<li>{ { name } } registered for { { registrants[name] } }</li>
{ % endfor % }
</ul>
{ % endblock % }

问题:数据存储在内存中,服务器重启后数据就丢失了!
Flask 与 SQL 数据库
为了让数据持久化(服务器重启后仍然存在),我们需要使用数据库!
配置数据库
下载数据库文件
从课程页面下载 froshims.db。
查看数据库结构
sqlite3 froshims.db
sqlite> .schema
CREATE TABLE registrants (id INTEGER, name TEXT NOT NULL, sport TEXT NOT NULL, PRIMARY KEY(id));
sqlite> SELECT * FROM registrants;
更新 requirements.txt
cs50
Flask
更新 app.py
# 使用 SQLite 数据库存储注册者
from cs50 import SQL
from flask import Flask, redirect, render_template, request
app = Flask(__name__)
# 连接数据库
db = SQL("sqlite:///froshims.db")
SPORTS = [
"Basketball",
"Soccer",
"Ultimate Frisbee"
]
@app.route("/")
def index():
return render_template("index.html", sports=SPORTS)
@app.route("/deregister", methods=["POST"])
def deregister():
# 获取要注销的用户 ID
id = request.form.get("id")
if id:
# 从数据库删除
db.execute("DELETE FROM registrants WHERE id = ?", id)
return redirect("/registrants")
@app.route("/register", methods=["POST"])
def register():
# 验证姓名
name = request.form.get("name")
if not name:
return render_template("error.html", message="Missing name")
# 验证运动项目(支持多选)
sports = request.form.getlist("sport")
if not sports:
return render_template("error.html", message="Missing sport")
for sport in sports:
if sport not in SPORTS:
return render_template("error.html", message="Invalid sport")
# 将数据插入数据库
for sport in sports:
db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport)
# 重定向到注册者列表
return redirect("/registrants")
@app.route("/registrants")
def registrants():
# 从数据库查询所有注册者
registrants = db.execute("SELECT * FROM registrants")
return render_template("registrants.html", registrants=registrants)
更新 templates/registrants.html
{ % extends "layout.html" % }
{ % block body % }
<h1>Registrants</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Sport</th>
<th></th>
</tr>
</thead>
<tbody>
{ % for registrant in registrants % }
<tr>
<td>{ { registrant.name } }</td>
<td>{ { registrant.sport } }</td>
<td>
<form action="/deregister" method="post">
<input name="id" type="hidden" value="{ { registrant.id } }">
<button type="submit">Deregister</button>
</form>
</td>
</tr>
{ % endfor % }
</tbody>
</table>
{ % endblock % }
测试数据库
运行应用并注册几个用户,然后查询数据库:
sqlite3 froshims.db
sqlite> SELECT * FROM registrants;
+----+-------+------------+
| id | name | sport |
+----+-------+------------+
| 1 | John | Basketball |
| 2 | Holly | Soccer |
+----+-------+------------+

现在数据会持久化保存了!🎉
更多关于 Flask 的用法,请查看 Flask 官方文档。
Cookies 和 Session(会话管理)
MVC 架构
在继续之前,让我们理解一个重要的软件架构模式——MVC:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Model │ <── │ Controller │ ──> │ View │
│ (模型) │ │ (控制器) │ │ (视图) │
│ 数据库/SQL │ │ app.py │ │ HTML模板 │
└─────────────┘ └─────────────┘ └─────────────┘
- Model(模型):数据的存储和处理(数据库)
- View(视图):用户看到的界面(HTML 模板)
- Controller(控制器):处理业务逻辑(app.py)
安全问题
之前的 froshims 应用存在安全隐患:
- 任何人都可以点击 “Deregister” 按钮删除其他人的注册信息!
- 没有用户认证,无法区分不同的用户
什么是 Cookie 和 Session?
Cookie
Cookie 是存储在浏览器中的小型数据片段,用于在客户端和服务器之间传递信息。
HTTP 请求头中的 Cookie:
GET / HTTP/2
Host: accounts.google.com
Cookie: session=abc123xyz
Session
Session(会话) 是基于 Cookie 的用户认证机制:
┌─────────────┐ ┌─────────────┐
│ 浏览器 │ Cookie: sid=123 │ 服务器 │
│ │ ───────────────> │ │
│ │ │ session: │
│ │ │ 123 -> 用户A │
└─────────────┘ └─────────────┘
服务器通过 Cookie 中的 Session ID 识别用户身份。
实现登录功能
项目结构
login/
├── app.py
├── requirements.txt
└── templates/
├── index.html
├── layout.html
└── login.html
requirements.txt
Flask
Flask-Session
templates/layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width">
<title>login</title>
</head>
<body>
{ % block body % }{ % endblock % }
</body>
</html>
templates/index.html
{ % extends "layout.html" % }
{ % block body % }
{ % if name -% }
You are logged in as { { name } }. <a href="/logout">Log out</a>.
{ %- else -% }
You are not logged in. <a href="/login">Log in</a>.
{ %- endif % }
{ % endblock % }
templates/login.html
{ % extends "layout.html" % }
{ % block body % }
<form action="/login" method="post">
<input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
<button type="submit">Log In</button>
</form>
{ % endblock % }
app.py
from flask import Flask, redirect, render_template, request, session
from flask_session import Session
# 配置应用
app = Flask(__name__)
# 配置 Session
app.config["SESSION_PERMANENT"] = False # 关闭浏览器后 session 失效
app.config["SESSION_TYPE"] = "filesystem" # 将 session 存储在文件系统中
Session(app)
@app.route("/")
def index():
# 从 session 获取用户名
return render_template("index.html", name=session.get("name"))
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
# 将用户名存入 session
session["name"] = request.form.get("name")
return redirect("/")
return render_template("login.html")
@app.route("/logout")
def logout():
# 清除 session
session.clear()
return redirect("/")
代码详解
| 代码 | 说明 |
|---|---|
from flask_session import Session |
导入 Flask-Session 扩展 |
app.config["SESSION_TYPE"] = "filesystem" |
配置 session 存储方式 |
Session(app) |
初始化 session 扩展 |
session["name"] = ... |
存储数据到 session |
session.get("name") |
从 session 获取数据 |
session.clear() |
清除所有 session 数据 |
Session 让你能够确保只有特定用户可以访问特定功能,没有人能冒充其他用户!
更多关于 Flask Session 的用法,参考 Flask-Session 文档。
购物车项目(Shopping Cart)
这是一个使用 Session 实现购物车功能的完整示例。
项目结构
store/
├── app.py
├── requirements.txt
├── store.db
└── templates/
├── books.html
├── cart.html
└── layout.html
requirements.txt
cs50
Flask
Flask-Session
app.py
from cs50 import SQL
from flask import Flask, redirect, render_template, request, session
from flask_session import Session
# 配置应用
app = Flask(__name__)
# 连接数据库
db = SQL("sqlite:///store.db")
# 配置 Session
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
@app.route("/")
def index():
# 查询所有书籍
books = db.execute("SELECT * FROM books")
return render_template("books.html", books=books)
@app.route("/cart", methods=["GET", "POST"])
def cart():
# 确保购物车存在
if "cart" not in session:
session["cart"] = []
# POST:添加商品到购物车
if request.method == "POST":
book_id = request.form.get("id")
if book_id:
session["cart"].append(book_id)
return redirect("/cart")
# GET:显示购物车内容
books = db.execute("SELECT * FROM books WHERE id IN (?)", session["cart"])
return render_template("cart.html", books=books)
templates/layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width">
<title>store</title>
</head>
<body>
{ % block body % }{ % endblock % }
</body>
</html>
templates/books.html
{ % extends "layout.html" % }
{ % block body % }
<h1>Books</h1>
{ % for book in books % }
<h2>{ { book["title"] } }</h2>
<form action="/cart" method="post">
<input name="id" type="hidden" value="{ { book['id'] } }">
<button type="submit">Add to Cart</button>
</form>
{ % endfor % }
{ % endblock % }
templates/cart.html
{ % extends "layout.html" % }
{ % block body % }
<h1>Cart</h1>
<ol>
{ % for book in books % }
<li>{ { book["title"] } }</li>
{ % endfor % }
</ol>
{ % endblock % }
API 和 JSON
什么是 API?
API(Application Programming Interface,应用程序接口) 是一系列规范,让不同的软件系统能够相互通信。
例如:
- 使用 IMDB 的 API 获取电影信息
- 使用天气 API 获取天气预报
- 使用地图 API 在网站上显示地图
AJAX:异步请求
AJAX(Asynchronous JavaScript and XML) 允许网页在不刷新整个页面的情况下与服务器交换数据。
使用 AJAX 实现实时搜索
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, width=device-width">
<title>shows</title>
</head>
<body>
<input autocomplete="off" autofocus placeholder="Query" type="search">
<ul></ul>
<script>
let input = document.querySelector('input');
// 监听输入事件
input.addEventListener('input', async function() {
// 发送请求到服务器
let response = await fetch('/search?q=' + input.value);
// 获取响应文本
let shows = await response.text();
// 更新页面内容
document.querySelector('ul').innerHTML = shows;
});
</script>
</body>
</html>
AJAX 代码详解
| 代码 | 说明 |
|---|---|
input.addEventListener('input', ...) |
监听输入框的输入事件 |
async function() |
异步函数,可以使用 await |
await fetch('/search?q=...') |
发送 HTTP 请求并等待响应 |
await response.text() |
获取响应的文本内容 |
document.querySelector('ul').innerHTML = shows |
更新页面 |
JSON:数据交换格式
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
JSON 示例
[
{"id": 1, "title": "The Office"},
{"id": 2, "title": "Friends"},
{"id": 3, "title": "Breaking Bad"}
]
返回 JSON 响应
from flask import Flask, jsonify, request
from cs50 import SQL
app = Flask(__name__)
db = SQL("sqlite:///shows.db")
@app.route("/search")
def search():
q = request.args.get("q")
if q:
shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%")
else:
shows = []
# 使用 jsonify 返回 JSON 格式的响应
return jsonify(shows)
在 JavaScript 中处理 JSON
<script>
let input = document.querySelector('input');
input.addEventListener('input', async function() {
let response = await fetch('/search?q=' + input.value);
// 将响应解析为 JSON
let shows = await response.json();
let html = '';
for (let i in shows) {
// 防止 XSS 攻击
let title = shows[i].title.replace('<', '<').replace('&', '&');
html += '<li>' + title + '</li>';
}
document.querySelector('ul').innerHTML = html;
});
</script>
更多关于 JSON 的使用,请阅读 JSON 官方文档。
总结
在本节课中,我们学习了如何使用 Python、SQL 和 Flask 创建 Web 应用程序。
核心知识点回顾
| 主题 | 关键概念 |
|---|---|
| Flask 基础 | 路由、装饰器、模板渲染 |
| 表单处理 | GET vs POST、表单验证 |
| 模板 | Jinja2 语法、模板继承 |
| 数据库 | 使用 SQL 持久化数据 |
| Session | 用户认证、Cookie |
| API/JSON | AJAX、数据交换 |
Flask 项目最佳实践
- ✅ 使用模板继承减少重复代码
- ✅ 验证所有用户输入(永远不要信任客户端!)
- ✅ 使用 POST 方法提交敏感数据
- ✅ 使用 Session 管理用户状态
- ✅ 使用数据库持久化数据
下一步
恭喜你完成了 Week 9 的学习!你现在具备了使用 Python、Flask、HTML 和 SQL 开发 Web 应用程序的能力。
建议:
- 🎯 用这些技能完成 CS50 的最终项目
- 📚 深入学习 Flask 官方文档
- 🔒 学习 Web 安全知识(SQL 注入、XSS 等)
参考资料:
下次见! 🚀