Web 后端技术实战:Flask + AJAX/REST API + FastAPI 从零到上线
实战环境 : 华为云 FlexusX ECS | Ubuntu 24.04.4 | Python 3.12.3 | PostgreSQL 16.14
服务器 : ecs-88e7-0001 (139.9.128.210) | 8vCPU 16GiB | 可用区7
依赖版本: Flask 3.1.3 | psycopg 3.3.4 | FastAPI 0.138.2 | Uvicorn
目录
- [一、Flask 基础篇 (20个实验)](#一、Flask 基础篇 (20个实验))
- [二、Flask 数据库篇 (18个实验)](#二、Flask 数据库篇 (18个实验))
- [三、AJAX 异步技术 + REST API (8个实验)](#三、AJAX 异步技术 + REST API (8个实验))
- [四、FastAPI 框架 (7个实验)](#四、FastAPI 框架 (7个实验))
- 五、踩坑记录
一、Flask 基础篇
1.1 最小应用
python
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "<h1>Hello, Flask!</h1>"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
关键参数:
-
host="0.0.0.0"--- 允许外网访问(默认 127.0.0.1 仅本机) -
port=5000--- Flask 默认端口 -
debug=True--- 热重载 + 详细错误页(生产环境务必关闭)GET / → 200 | 响应长度: 17
GET /api/status → 200 | {"status":"ok","framework":"Flask"}
1.2 调试模式与错误日志
python
import logging
handler = logging.FileHandler("/root/flask_error.log")
handler.setLevel(logging.ERROR)
app.logger.addHandler(handler)
@app.route("/error")
def trigger_error():
app.logger.error("捕获异常", exc_info=True)
return "Error logged", 500
GET /error → 500 | Error logged: division by zero
错误日志大小: 287 bytes
1.3 路由系统
动态路由(URL 变量)
python
@app.route("/user/<username>") # 字符串类型
def profile(username): ...
@app.route("/post/<int:post_id>") # 整数类型(自动转换)
def show_post(post_id): ...
@app.route("/path/<path:subpath>") # 路径类型(含 /)
def catch_all(subpath): ...
类型转换器:
| 转换器 | 示例 | 匹配 |
|---|---|---|
string(默认) |
/user/<username> |
不含 / 的任意文本 |
int |
/post/<int:id> |
正整数 |
float |
/price/<float:amount> |
浮点数 |
path |
/files/<path:p> |
含 / 的路径 |
uuid |
/item/<uuid:uid> |
UUID 字符串 |
GET /user/alice → 200 | 用户: alice
GET /post/42 → 200 | 文章 #42
GET /path/a/b/c → 200 | 捕获路径: a/b/c
1.4 蓝图 (Blueprint) --- 模块化
┌──────────────────────────────────────────┐
│ Flask 主应用 │
│ │
│ app.register_blueprint(auth_bp) │
│ app.register_blueprint(blog_bp) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ auth 蓝图 │ │ blog 蓝图 │ │
│ │ /auth/login │ │ /blog/ │ │
│ │ /auth/register│ │ /blog/<id> │ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────┘
python
# auth_blueprint.py
auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
@auth_bp.route("/login")
def login(): return "登录页面"
# app.py
app.register_blueprint(auth_bp)
GET /auth/login → 200 | 登录页面
GET /auth/register → 200 | 注册页面
GET /blog/ → 200 | 博客首页
GET /blog/99 → 200 | 博客文章 #99
1.5 模板渲染(Jinja2)
┌──────────────────────────────────────┐
│ Jinja2 模板系统 │
│ │
│ {{ variable }} 变量输出 │
│ {% if/for %} 控制流 │
│ {% block %} 块占位 │
│ {% extends %} 模板继承 │
│ {% include %} 模板包含 │
│ {{ var|filter }} 过滤器 │
└──────────────────────────────────────┘
python
@app.route("/")
def index():
return render_template("index.html",
title="Flask模板渲染",
name="Python爱好者",
items=["Jinja2模板引擎", "变量插值", "控制流", "模板继承"]
)
GET / → 200 | 响应长度: 286
✓ Jinja2模板渲染成功
✓ 变量插值正常
✓ for循环渲染正常
1.6 模板继承
html
<!-- base.html (父模板) -->
<html>
<head>{% block title %}默认标题{% endblock %}</head>
<body>
<nav>导航栏</nav>
{% block content %}{% endblock %}
<footer>页脚</footer>
</body>
</html>
<!-- home.html (子模板) -->
{% extends "base.html" %}
{% block title %}首页 - Flask教程{% endblock %}
{% block content %}
<h2>欢迎</h2>
{% endblock %}
1.7 前后端架构对比
┌────────────────────────────────────────────────┐
│ Web 应用架构层次 │
├────────────────────────────────────────────────┤
│ 1-Tier (单层): 浏览器 + 业务逻辑 + 数据库一体 │
│ 示例: 单机Flask + SQLite │
├────────────────────────────────────────────────┤
│ 2-Tier (双层): 浏览器(客户端) ←→ Flask(服务端) │
│ 示例: 浏览器 → Flask → PostgreSQL│
├────────────────────────────────────────────────┤
│ 3-Tier (三层): 表现层 → 业务逻辑层 → 数据层 │
│ 示例: Vue → Flask → PostgreSQL │
└────────────────────────────────────────────────┘
前后端计算对比:
| 维度 | 前端 (JavaScript) | 后端 (Python) |
|---|---|---|
| 执行位置 | 浏览器 | 服务器 |
| 用户可见代码 | 是 | 否 |
| 适用场景 | 交互/展示 | 数据/安全 |
后端计算: 1+2+...+100 = 5050
n=1000 → sum=500500 | 公式: n(n+1)/2 = 1000*1001/2
1.8 Request / Response 详解
python
@app.route("/inspect", methods=["GET", "POST", "PUT", "DELETE"])
def inspect():
info = {
"method": request.method, # 请求方法
"url": request.url, # 完整URL
"path": request.path, # 路径(不含参数)
"host": request.host, # 主机+端口
"remote_addr": request.remote_addr, # 客户端IP
"headers": dict(request.headers), # 请求头
"args": dict(request.args), # URL参数
"form": dict(request.form), # 表单数据
"json": request.get_json(), # JSON请求体
}
return jsonify(info)
Method: GET
Params: {'page': '1', 'q': 'test'}
Remote: 127.0.0.1
POST JSON: {'action': 'test', 'user': 'alice'}
1.9 表单与异常处理
GET vs POST 对比:
| 特性 | GET | POST |
|---|---|---|
| 数据位置 | URL参数 | 请求体 |
| 安全性 | 低(URL可见) | 高(不在URL) |
| 数据大小 | ~2KB 限制 | 无限制 |
| 幂等性 | 是 | 否 |
| 缓存 | 可缓存 | 不可缓存 |
异常处理:
python
@app.route("/calc", methods=["POST"])
def calculator():
try:
a = request.form.get("a", type=float)
b = request.form.get("b", type=float)
if op == "/" and b == 0:
raise ZeroDivisionError("除数不能为零")
except (TypeError, ValueError) as e:
return f"输入格式错误: {e}", 400
except ZeroDivisionError as e:
return str(e), 400
POST /calc 10/3 → 200 | 结果: 3.333...
POST /calc 10/0 → 200 | 异常: 除数不能为零
二、Flask 数据库篇
2.1 PostgreSQL 连接
┌──────────┐ psycopg ┌──────────────┐
│ Flask │ ◄──────────────► │ PostgreSQL 16 │
│ 应用 │ conninfo │ (python_db) │
└──────────┘ └──────────────┘
python
import psycopg
# 连接字符串
DB_URL = "host=localhost dbname=python_db user=python_user password=Python@123"
with psycopg.connect(DB_URL) as conn:
with conn.cursor() as cur:
cur.execute("SELECT version()")
version = cur.fetchone()[0]
✓ PostgreSQL连接成功
版本: PostgreSQL 16.14
数据库: python_db | 用户: python_user
2.2 CRUD 操作速查
| 操作 | SQL | Python (psycopg3) |
|---|---|---|
| 建表 | CREATE TABLE users (...) |
cur.execute("CREATE TABLE...") |
| 插入 | INSERT INTO users VALUES (...) |
cur.execute("INSERT...", (val1, val2)) |
| 查询 | SELECT * FROM users |
cur.execute("SELECT..."); cur.fetchall() |
| 更新 | UPDATE users SET ... |
cur.execute("UPDATE...", (val, id)) |
| 删除 | DELETE FROM users WHERE id=? |
cur.execute("DELETE...", (id,)) |
| 搜索 | SELECT ... WHERE name LIKE '%q%' |
同上,参数化 %s |
| 排序 | ORDER BY col ASC/DESC |
同上 |
| 分页 | LIMIT n OFFSET m |
同上 |
2.3 注册 + 登录
python
# 注册
cur.execute(
"INSERT INTO flask_users (username, email, password_hash) VALUES (%s, %s, %s)",
(username, email, hashed_password)
)
conn.commit()
# 登录验证
cur.execute(
"SELECT * FROM flask_users WHERE username = %s AND password_hash = %s",
(username, hashed_password)
)
user = cur.fetchone()
✓ 创建表 flask_users
✓ 插入 3 条用户记录
#1 alice alice@example.com
#2 bob bob@example.com
#3 charlie charlie@example.com
⚠ 重复注册检测: duplicate key value violates unique constraint
2.4 数据库连接池 (ConnectionPool)
python
from psycopg_pool import ConnectionPool
# 创建连接池:最少2个,最多10个连接
pool = ConnectionPool(DB_URL, min_size=2, max_size=10, open=True)
# 使用连接池(自动获取/归还)
with pool.connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT count(*) FROM flask_users")
count = cur.fetchone()[0]
✓ 连接池创建: min=2, max=10
通过连接池查询: 用户总数 = 3
并发查询结果: id=1 → alice | id=3 → charlie | id=2 → bob
2.5 Cookie / Session 会话管理
┌──────────────────────────────────────────────┐
│ 会话管理流程 │
│ │
│ 用户 → 登录 → Session["username"]="alice" │
│ ↓ │
│ 浏览器 ← Set-Cookie: session=xxx │
│ ↓ │
│ 后续请求自动携带 Cookie → 服务端识别用户 │
│ ↓ │
│ 登出 → Session.pop("username") │
│ → Delete-Cookie │
└──────────────────────────────────────────────┘
python
app.secret_key = secrets.token_hex(32)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30)
@app.route("/login")
def login():
session["username"] = "alice"
session.permanent = True
@app.route("/")
def index():
if "username" in session:
return jsonify({"status": "logged_in", "username": session["username"]})
GET / (未登录) → {"status": "not_logged_in"}
GET /login?user=alice → {"status": "login_ok"}
GET / (已登录) → {"status": "logged_in", "username": "alice"}
✓ Session保持登录状态成功
Session过期时间: 30分钟
GET /logout → {"status": "logged_out"}
2.6 批量操作
python
# executemany 批量插入
users = [(f"user_{i:02d}", f"u{i:02d}@example.com") for i in range(1, 6)]
cur.executemany(
"INSERT INTO flask_users (username, email, password_hash) VALUES (%s, %s, %s)",
[(u, e, f"hash_{u}") for u, e in users]
)
conn.commit()
✓ executemany 批量插入 5 条用户, 耗时: 0.0076s
逐条插入 5 条用户, 耗时: 0.0079s
性能对比: executemany 快约 1.0x (数据量小时差异不明显)
2.7 模糊搜索 + 排序
python
# LIKE 模糊搜索
cur.execute(
"SELECT * FROM flask_users WHERE username LIKE %s ORDER BY username",
(f"%{search_term}%",)
)
# ORDER BY 排序
cur.execute("SELECT username FROM flask_users ORDER BY username ASC")
cur.execute("SELECT username FROM flask_users ORDER BY username DESC")
LIKE '%li%' 搜索结果: 2 条
#1 alice #6 charlie
ASC排序: alice → bob → charlie → diana → eve → frank
DESC排序: frank → eve → diana → charlie → bob → alice
2.8 分页(Pagination)
python
# LIMIT / OFFSET 分页
page, size = 1, 5
offset = (page - 1) * size
# 总记录数
cur.execute("SELECT count(*) FROM users")
total = cur.fetchone()[0]
total_pages = (total + size - 1) // size
# 数据查询
cur.execute(
"SELECT * FROM users ORDER BY id LIMIT %s OFFSET %s",
(size, offset)
)
users = cur.fetchall()
总记录数: 16
页码 每页 偏移 记录范围 数据
1 5 0 #1-#5 alice, bob, charlie, diana, eve
2 5 5 #6-#10 frank, user_01, user_02, user_03, user_04
3 5 10 #11-#15 user_05, user_06, user_07, user_08, user_09
1 10 0 #1-#10 alice, bob, charlie, diana, eve, frank, ...
总页数(每页5条): 4
总页数(每页10条): 2
三、AJAX 异步技术 + REST API
3.1 REST API 设计规范
REST API vs CRUD 对照表:
操作 HTTP方法 URL
────────────────────────────────────
查询全部 GET /api/users
查询单个 GET /api/users/<id>
创建 POST /api/users
更新 PUT /api/users/<id>
删除 DELETE /api/users/<id>
3.2 完整 AJAX CRUD 应用
python
# 后端 --- REST API
@app.route("/api/users", methods=["GET"])
def list_users():
search = request.args.get("q", "")
page = request.args.get("page", 1, type=int)
size = request.args.get("size", 10, type=int)
# ... 查询数据库
return jsonify({
"data": users,
"total": total,
"page": page,
"pages": (total + size - 1) // size
})
@app.route("/api/users/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
cur.execute("DELETE FROM users WHERE id = %s RETURNING *", (user_id,))
return jsonify({"deleted": row})
前端 --- fetch() API:
javascript
async function createUser() {
const resp = await fetch("/api/users", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({username, email})
});
const data = await resp.json();
// 刷新列表
}
GET /api/users → 200 | total=16
POST /api/users → 201 | id=20 username=ajax_user_01
PUT /api/users/20 → 200 | email→updated@test.com
DELETE /api/users/20 → 200 | deleted
GET /api/users?q=alice → 1 results
JSON导出: 16 条记录
3.3 fetch() vs XMLHttpRequest
| 特性 | fetch() | XMLHttpRequest |
|---|---|---|
| 语法 | fetch(url).then() |
new XMLHttpRequest() |
| Promise 支持 | ✓ 原生 Promise | ✗ 需回调/封装 |
| async/await | ✓ 完美配合 | ✗ 需手动包装 |
| 请求取消 | AbortController | xhr.abort() |
3.4 AJAX 请求头
http
Content-Type: application/json
Accept: application/json
X-Requested-With: XMLHttpRequest (传统Ajax标识,fetch可选)
四、FastAPI 框架
4.1 最小应用
python
from fastapi import FastAPI
app = FastAPI(
title="FastAPI教程",
version="1.0.0"
)
@app.get("/")
async def root():
return {"message": "Hello, FastAPI!"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
GET / → 200 | {"message":"Hello, FastAPI!","framework":"FastAPI"}
GET /docs → 200 (Swagger UI 自动生成)
GET /openapi.json → 200 | schema size: 503 chars
4.2 Flask vs FastAPI 全面对比
| 特性 | Flask | FastAPI |
|---|---|---|
| 协议 | WSGI(同步) | ASGI(异步) |
| 类型提示 | 可选 | 强类型(Pydantic) |
| API 文档 | 需 Flask-RESTX | 自动 Swagger/ReDoc |
| 数据验证 | 手动/Flask-Marsh | Pydantic 自动 |
| 异步支持 | 有限(3.x+) | 原生 async/await |
| 性能 | 中等 | 高(媲美 Go/Node) |
| 学习曲线 | 低 | 中 |
| 生态成熟度 | 非常成熟 | 快速增长 |
| 适用场景 | 传统 Web/模板 | API 服务/微服务 |
4.3 Pydantic 数据模型
python
from pydantic import BaseModel, Field
class UserCreate(BaseModel):
username: str = Field(..., min_length=2, max_length=50)
email: Optional[str] = Field(None, pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
age: Optional[int] = Field(None, ge=0, le=150)
class UserResponse(BaseModel):
id: int
username: str
email: Optional[str] = None
created_at: datetime
4.4 完整 CRUD(带类型验证)
python
@app.get("/users", response_model=List[UserResponse])
async def list_users(
q: Optional[str] = Query(None, description="搜索关键词"),
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100),
order: str = Query("id")
): ...
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate): ...
@app.put("/users/{user_id}", response_model=UserResponse)
async def update_user(user_id: int, user: UserUpdate): ...
@app.delete("/users/{user_id}")
async def delete_user(user_id: int): ...
GET /users → 200 | 2 users
#1 alice age=25
#2 bob age=30
POST /users → 201 | charlie id=3
PUT /users/3 → 200 | email→charlie_new@test.com
DELETE /users/3 → 200 | Deleted
POST (验证失败) → 422:
username: String should have at least 2 characters
age: Input should be less than or equal to 150
4.5 路径参数 vs 查询参数
| 维度 | Path Parameter | Query Parameter |
|---|---|---|
| URL 位置 | /users/{user_id} |
/users?page=1&size=10 |
| 是否必须 | 是(默认) | 否(可设默认值) |
| 用于 | 资源标识 | 过滤/排序/分页 |
| FastAPI 语法 | Path(...) |
Query(...) |
| 类型 | int/str/uuid/path | int/str/float/bool |
| 示例 | /posts/42 |
/posts?tag=python |
4.6 Query 参数校验选项
| 参数 | 类型 | 说明 |
|---|---|---|
default |
Any | 默认值 |
... (Ellipsis) |
--- | 标记为必填 |
min_length |
int | 字符串最小长度 |
max_length |
int | 字符串最大长度 |
pattern/regex |
str | 正则表达式匹配 |
ge (≥) |
int/float | 数值最小值 |
le (≤) |
int/float | 数值最大值 |
gt (>) |
int/float | 大于 |
lt (<) |
int/float | 小于 |
title |
str | API 文档标题 |
description |
str | API 文档描述 |
deprecated |
bool | 标记为弃用 |
python
@app.get("/search")
async def search(
q: str = Query(..., min_length=1, max_length=100),
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=100),
sort: str = Query("relevance", pattern="^(relevance|date|popular)$"),
): ...
4.7 请求体 + 嵌套模型
python
class Address(BaseModel):
street: str
city: str
zipcode: Optional[str] = None
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
tags: List[str] = Field(default_factory=list)
address: Optional[Address] = None # 嵌套模型
@app.post("/users", status_code=201)
async def create_user(user: UserCreate):
return {"created": user.model_dump()}
POST /users (嵌套模型) → 201
username: john
address: {"street":"Main St 123","city":"Shenzhen"}
POST /users/bulk → 201 | 3 users created
4.8 Pydantic Field 校验
| 参数 | 说明 | 示例 |
|---|---|---|
... |
必填 | Field(...) |
default |
默认值 | Field("default") |
min_length |
最小长度 | Field(..., min_length=2) |
max_length |
最大长度 | Field(..., max_length=50) |
ge/le/gt/lt |
数值范围 | Field(..., ge=0, le=100) |
pattern/regex |
正则匹配 | Field(..., pattern=r"\d+") |
examples |
示例值 | Field(..., examples=["x"]) |
五、踩坑记录
| # | 问题 | 原因 | 解决方案 |
|---|---|---|---|
| 1 | blinker 卸载失败 | Debian 安装的 blinker 缺少 RECORD | pip install --ignore-installed blinker |
| 2 | render_template_string(''' 嵌套 |
外层 ''' 和内层 ''' 冲突 |
外层用 """,内层用 '''(或反之) |
| 3 | 密码含 @ 被 SQLAlchemy URL 误解析 |
URL 中 @ 同时是密码和分隔符 |
使用 %40 URL 编码 |
| 4 | executemany 占位符与参数数量不匹配 | WHERE IN (%s, %s) 但只传一个参数 | IN 中使用动态占位符 |
| 5 | psycopg_pool 单独安装 | psycopg_pool 是独立包 | pip install psycopg_pool |
| 6 | Session 过期未生效 | 默认 session 非持久 | 设置 app.config['PERMANENT_SESSION_LIFETIME'] |
附录:完整架构图
┌─────────────────────────────────────────────────────────┐
│ Web 后端技术栈 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 前端 (SPA) │ │ 前端 (SSR) │ │
│ │ Vue/React/Angular│ │ Jinja2 模板 │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ AJAX/fetch │ SSR │
│ ▼ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ Web 框架层 │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Flask │ │ FastAPI │ │ │
│ │ │ (WSGI) │ │ (ASGI) │ │ │
│ │ ├──────────┤ ├──────────────┤ │ │
│ │ │ Jinja2 │ │ Swagger/ReDoc│ │ │
│ │ │ Session │ │ Pydantic │ │ │
│ │ │ Blueprint│ │ async/await │ │ │
│ │ └──────────┘ └──────────────┘ │ │
│ └────────────────┬───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ 数据访问层 │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ psycopg3 │ │ SQLAlchemy │ │ │
│ │ │ Connection│ │ ORM │ │ │
│ │ │ Pool │ │ Core │ │ │
│ │ └──────────┘ └──────────────┘ │ │
│ └────────────────┬───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ PostgreSQL 16 │ │
│ │ (python_db) │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
系列已发布 7 篇 :python3从零开始(58KB) → 深入编解码(36KB) → 文本与NLP(37KB) → Office(24KB) → 数据库(20KB) → 网络爬虫(33KB) → Web后端技术(本文)
累计: 621 实验 / 实战服务器: ecs-88e7-0001 (139.9.128.210) | Ubuntu 24.04.4 | Python 3.12.3