D6 学习笔记:第一个 FastAPI——从「看不见后端」到「能跑的服务」

系列:海口三港 AI 全栈实战 · 从参赛大屏到 AI 平台
仓库:https://github.com/2003Tim/haikou-ai-port


前言:两个里程碑

D6 我做了两件让我兴奋的事:

  1. 用 50 行代码实现了一个 MiniFastAPI,把 FastAPI 内部机制看透了
  2. 真正跑起来一个 uvicorn 服务,浏览器打开 /docs 自动看到 5 个 API 文档

之前参赛时,我对"后端"是完全黑盒的——后端同学说"调这个接口就行",我照做。D6 我亲手让一个 HTTP 请求从浏览器走到 Python 函数,又走回浏览器,这感觉完全不同。


一、uvicorn 是什么?(一句话)

uvicorn 是 ASGI 服务器——它的工作是接收 HTTP 请求 → 转给 FastAPI 应用 → 把结果返回

餐厅类比

浏览器 ──HTTP 请求──▶  uvicorn(前台) ───▶  FastAPI(厨房)
浏览器 ◀──HTTP 响应──  uvicorn(上菜) ◀────  FastAPI(做菜)

uvicorn 本身不懂业务,只懂 HTTP 协议。真正的逻辑在 FastAPI 里。

为什么是 uvicorn?

服务器 适合 速度
app.run()(Flask 自带) Flask(同步) 🐢
gunicorn Flask、Django(WSGI) 🐇
uvicorn FastAPI(ASGI) 🚀

FastAPI 是异步框架,必须用 ASGI 服务器,uvicorn 是事实标准,底层用 uvloop(C 写的异步循环)极快。


二、一个 HTTP 请求的完整旅程

浏览器输入 http://127.0.0.1:8000/ports,发生了什么?

浏览器 ──TCP 握手──▶ uvicorn
       发送:GET /ports HTTP/1.1
       Host: 127.0.0.1:8000
                │
                ▼
         uvicorn 解析 HTTP 请求
                │
                ▼
         uvicorn 调用 FastAPI 应用
                │
                ▼
         FastAPI 查路由表:
           GET /ports → list_ports()
                │
                ▼
         调用 list_ports() 返回 [{"id":1,...}]
                │
                ▼
         Pydantic + JSON 序列化:
           [{"id":1,"name":"秀英港"},...]
                │
                ▼
         uvicorn 写回 socket
                │
                ▼
浏览器 ──收到 HTTP/1.1 200 OK──▶ 渲染 JSON

8 个步骤,但 FastAPI 帮你做了大部分。


三、为什么访问 URL 就自动调用函数?

答案: 装饰器 + 路由表

@app.get("/ports")
def list_ports():
    return [...]

等价于:

list_ports = app.get("/ports")(list_ports)

app.get("/ports") 返回装饰器,装饰器把 (method, path, func) 存到路由表:

self.routes = [
    ("GET", "/", read_root),
    ("GET", "/ports", list_ports),
    ("GET", "/hello/{name}", say_hello),
    ...
]

请求到来时,FastAPI 在路由表里查找匹配项,找到就调用对应函数。

路径参数提取:

  • /hello/Tim → 匹配 /hello/{name},提取 name="Tim"
  • /ports/2 → 匹配 /ports/{port_id},提取 port_id="2"

四、我自己写了一个 MiniFastAPI 看内部

50 行代码,核心就这 4 个步骤:

class MiniFastAPI:
    def get(self, path):
        def decorator(func):
            self.routes.append(("GET", path, func))
            return func
        return decorator

    def handle_request(self, method, path):
        for route_method, route_path, handler in self.routes:
            if route_method == method and path == route_path:
                result = handler()
                return json.dumps(result, ensure_ascii=False)
        return json.dumps({"error": "Not Found"})

跑完演示,我真的看到了:

  • 路由表在 @app.get(...) 执行时就建立
  • 请求到来时遍历路由表查找
  • 找到后调用函数 + JSON 序列化
  • 找不到返回 404

这就是 FastAPI 的核心。真实的 FastAPI 多了 Pydantic 校验、Swagger UI、异步、依赖注入等,但基础就是这个 50 行版本。


五、装饰器的3 个常见坑(D4 学的,D6 实战)

坑1:wrapper 不调用 func

def bad_decorator(func):
    def wrapper(*args, **kwargs):
        print("before")
        # ❌ 没调用 func!
        print("after")
    return wrapper

@bad_decorator
def slow_function():
    time.sleep(1)
    return "done"

slow_function()  # 原函数完全没执行!

坑2:wrapper 不 return result

def timer(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f"耗时")
        # ❌ 没 return result
    return wrapper

@timer
def get_name():
    return "Tim"

print(get_name())  # 输出 None!

坑3:wrapper 名字可以随便改

def timer(func):
    def inner(*args, **kwargs):  # ← 改名为 inner
        ...
    return inner

改名字不影响功能,但约定用 wrapper,代码更易读。

装饰器正确模板(必须 3 件事)

def my_decorator(func):
    def wrapper(*args, **kwargs):  # ① 定义新函数
        # 前置逻辑
        result = func(*args, **kwargs)  # ② 调用原函数
        # 后置逻辑
        return result  # ③ return 原函数的返回值
    return wrapper  # ④ return wrapper

六、JSON 序列化是干什么的?

Python 对象(dict / list)内存里的,浏览器看不懂。HTTP body 是字符串,所以要序列化:

import json
port = {"id": 1, "name": "秀英港"}
port_str = json.dumps(port, ensure_ascii=False)
# '{"id": 1, "name": "秀英港"}'  ← 浏览器能解析这个

为什么是 JSON 而不是别的?

  • 几乎所有语言都支持
  • 人类可读
  • 体积小(对比 XML)

七、Swagger UI 是 FastAPI 的杀手锏

uvicorn app.main:app --reload,浏览器打开 /docs:

  • 自动列出所有 API
  • 每个 API 可以直接"Try it out"测试
  • 路径参数、查询参数自动生成输入框
  • 响应示例自动展示

对比 Postman:Postman 需要手动建请求,Swagger UI 框架帮你生成。这是 FastAPI 比 Flask 体验好太多的地方


九、D6 跑通后的 5 个 API

GET /              → 欢迎信息
GET /hello/{name}  → 个性化问候(路径参数)
GET /ports         → 港口列表
GET /ports/{id}    → 单个港口(路径参数 + 查询参数 include_stats)
GET /health        → 健康检查

到 D6 结束时,我有了:

  • 一个能 uvicorn 起来的 Python Web 服务
  • 一个真正能用的 API(虽然数据是硬编码)
  • 自动生成的 Swagger UI

这是 v1.0 的起点。


下一步:D7 学什么?

D7 我会用 Pydantic 做数据校验,把硬编码的港口数据结构化——这是接入数据库前的最后一步。

到 D7 结束时,我会:

  • 港口数据有 Port Pydantic 模型(替代 dict)
  • API 自动校验请求参数(类型、必填、范围)
  • 错误返回标准化(422 而不是 500)

参考资料

posted @ 2026-08-22 15:16  吴逸清  阅读(16)  评论(0)    收藏  举报