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 基础篇

1.1 最小应用

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 调试模式与错误日志

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 变量)
@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>   │     │
│  └──────────────┘  └──────────────┘     │
└──────────────────────────────────────────┘
# 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 }}   过滤器            │
└──────────────────────────────────────┘
@app.route("/")
def index():
    return render_template("index.html",
        title="Flask模板渲染",
        name="Python爱好者",
        items=["Jinja2模板引擎", "变量插值", "控制流", "模板继承"]
    )
GET / → 200 | 响应长度: 286
✓ Jinja2模板渲染成功
✓ 变量插值正常
✓ for循环渲染正常

1.6 模板继承

<!-- 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 详解

@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 对比

特性GETPOST
数据位置URL参数请求体
安全性低(URL可见)高(不在URL)
数据大小~2KB 限制无限制
幂等性
缓存可缓存不可缓存

异常处理

@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)  │
└──────────┘                  └──────────────┘
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 操作速查

操作SQLPython (psycopg3)
建表CREATE TABLE users (...)cur.execute("CREATE TABLE...")
插入INSERT INTO users VALUES (...)cur.execute("INSERT...", (val1, val2))
查询SELECT * FROM userscur.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 注册 + 登录

# 注册
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)

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                       │
└──────────────────────────────────────────────┘
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 批量操作

# 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 模糊搜索 + 排序

# 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)

# 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 应用

# 后端 — 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

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✓ 完美配合✗ 需手动包装
请求取消AbortControllerxhr.abort()

3.4 AJAX 请求头

Content-Type: application/json
Accept: application/json
X-Requested-With: XMLHttpRequest  (传统Ajax标识,fetch可选)

四、FastAPI 框架

4.1 最小应用

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 全面对比

特性FlaskFastAPI
协议WSGI(同步)ASGI(异步)
类型提示可选强类型(Pydantic)
API 文档需 Flask-RESTX自动 Swagger/ReDoc
数据验证手动/Flask-MarshPydantic 自动
异步支持有限(3.x+)原生 async/await
性能中等高(媲美 Go/Node)
学习曲线
生态成熟度非常成熟快速增长
适用场景传统 Web/模板API 服务/微服务

4.3 Pydantic 数据模型

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(带类型验证)

@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 ParameterQuery Parameter
URL 位置/users/{user_id}/users?page=1&size=10
是否必须是(默认)否(可设默认值)
用于资源标识过滤/排序/分页
FastAPI 语法Path(...)Query(...)
类型int/str/uuid/pathint/str/float/bool
示例/posts/42/posts?tag=python

4.6 Query 参数校验选项

参数类型说明
defaultAny默认值
... (Ellipsis)标记为必填
min_lengthint字符串最小长度
max_lengthint字符串最大长度
pattern/regexstr正则表达式匹配
ge (≥)int/float数值最小值
le (≤)int/float数值最大值
gt (>)int/float大于
lt (<)int/float小于
titlestrAPI 文档标题
descriptionstrAPI 文档描述
deprecatedbool标记为弃用
@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 请求体 + 嵌套模型

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"])

五、踩坑记录

#问题原因解决方案
1blinker 卸载失败Debian 安装的 blinker 缺少 RECORDpip install --ignore-installed blinker
2render_template_string(''' 嵌套外层 ''' 和内层 ''' 冲突外层用 """,内层用 '''(或反之)
3密码含 @ 被 SQLAlchemy URL 误解析URL 中 @ 同时是密码和分隔符使用 %40 URL 编码
4executemany 占位符与参数数量不匹配WHERE IN (%s, %s) 但只传一个参数IN 中使用动态占位符
5psycopg_pool 单独安装psycopg_pool 是独立包pip install psycopg_pool
6Session 过期未生效默认 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值