Web 后端技术实战:Flask + AJAX/REST API + FastAPI 从零到上线

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
复制代码
┌──────────────────────────────────────────────┐
│           会话管理流程                         │
│                                              │
│  用户 → 登录 → 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