Skip to main content

第2节:构建后端核心API

这一节我们要让 FastAPI 变得“有用”起来。我们将不再返回简单的 "Hello World",而是返回真正的电影数据

你需要做三件事:

  1. 创建电影数据:我们可以先编写假数据,后面再学习如何从数据库中获取真实数据。

  2. 随机电影接口:用于从电影数据中,随机筛选一个电影信息。

  3. 电影详情接口:用于显示这个电影的详细信息。

小知识:什么是接口

在开发中,“接口”(API)就像是一扇对外开放的窗口,它允许别人通过特定的地址(URL)来向你的服务提出请求,并获得相应的数据或结果。接口并不关心界面长什么样、视频怎么播放,它只负责“提供数据”和“执行操作”。

一句话理解: **接口就是前端、手机App、甚至其他程序与服务器沟通的桥梁。**它制定了规则:“你访问这个地址,我就给你返回什么。” 所以,当你能提供一个接口时,你的后端就真正开始“对外服务”了。

接下来,让我们一步一步来实现这些功能。

1.创建电影数据

main.py文件中,我们新建一个data_movie列表,包含几个示例的电影信息。代码如下:

from fastapi import FastAPI


app = FastAPI()

# --- 模拟数据库 ---
movies_data = [
{
"id": 1,
"title": "星际穿越 (Interstellar)",
"year": "2014",
"tags": "科幻 / 冒险 / 悬疑",
"score": 9.4,
"desc": "在地球环境日益恶化的未来,一组探险家利用新发现的虫洞,超越了人类太空旅行的极限,试图在广袤的宇宙中寻找人类的新家园。",
"bg": "https://images.unsplash.com/photo-1536440136628-849c177e76a1?q=80&w=2525&auto=format&fit=crop",
"director": "Christopher Nolan",
"actors": "Matthew McConaughey,Anne Hathaway,Jessica Chastain", # 注意:数据库存列表比较麻烦,这里先简化为字符串,中间用逗号隔开
"video_url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
},
{
"id": 2,
"title": "赛博朋克:边缘行者",
"year": "2022",
"tags": "科幻 / 动画 / 动作",
"score": 9.1,
"desc": "在一个沉迷于肉体改造的未来城市中,一名流浪街头的少年为了生存,选择成为一名雇佣兵——也就是众所周知的“赛博朋克”。",
"bg": "https://images.unsplash.com/photo-1626814026160-2237a95fc5a0?q=80&w=2070&auto=format&fit=crop",
"director": "Hiroyuki Imaishi",
"actors": "KENN,Aoi Yuki,Hiroki Touchi",
"video_url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"
},
{
"id": 3,
"title": "爱乐之城 (La La Land)",
"year": "2016",
"tags": "爱情 / 歌舞 / 剧情",
"score": 8.5,
"desc": "米娅渴望成为一名演员,但至今她仍旧只是片场咖啡厅里的一名平凡巴师。塞巴斯汀醉心于爵士乐,但在现实面前只能在餐厅里弹奏解闷的乐曲。",
"bg": "https://images.unsplash.com/photo-1518609878373-06d740f60d8b?q=80&w=2070&auto=format&fit=crop",
"director": "Damien Chazelle",
"actors": "Ryan Gosling,Emma Stone,John Legend",
"video_url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
},
{
"id": 4,
"title": "沙丘 (Dune)",
"year": "2021",
"tags": "科幻 / 冒险 / 史诗",
"score": 8.8,
"desc": "天赋异禀的少年保罗·厄崔迪被命运指引,为了保卫自己的家族和人民,决心前往浩瀚宇宙间最危险的星球,开启一场惊心动魄的冒险。",
"bg": "https://images.unsplash.com/photo-1541963463532-d68292c34b19?q=80&w=2000&auto=format&fit=crop",
"director": "Denis Villeneuve",
"actors": "Timothée Chalamet,Rebecca Ferguson,Zendaya",
"video_url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"
}
]

@app.get("/")
def read_root():
return {"message": "Hello Movie", "status": "OK"}

main.py中新增了一个movies_data列表,列表中的每一个元素就是一个字典,因为Python字典和JSON格式转化更为方便,形式如下:

[
{},
{},
]

说明:

movies_data只是为了演示方便,后面我们会用数据库来替换。我们想帮助更多的零基础入门小伙伴快速上手,所以采用了循序渐进的教学方式。这也是为什么大家喜欢大熊课堂的这种教学方式。

2.随机电影接口

我们创建随机电影接口,接口信息如下:

  • URL: /api/movie/random
  • 功能: 每次请求,都会随机返回一部电影的 JSON 数据。
  • 原理: 使用 Python 的 random.choice() 函数。

💻 代码实现

接下来,我们开始在main.py中继续新增代码:

from fastapi import FastAPI
import random

app = FastAPI()

# --- 模拟数据库 ---
movies_data = []

@app.get("/")
def read_root():
return {"message": "Hello Movie", "status": "OK"}

# 新增随机电影接口
@app.get("/api/movie/random")
async def get_random_movie():
return random.choice(movies_data)

为什么要用async 你在代码里看到了 async def,而不是普通的 def

asyncAsynchronous(异步)的缩写。

🍔 举个例子:麦当劳点餐

想象你在经营一家快餐店(这就是你的服务器),有两种工作模式:

  • 传统模式 (同步 Sync):

    只有一个收银员。顾客A来了点个汉堡,收银员向厨房下单,然后站在柜台前傻等,直到厨房把汉堡做好,他递给顾客A,顾客A走了,他才接待顾客B。

    • 缺点:如果厨房做汉堡慢,后面的顾客B、C、D全都在排队骂人。效率极低。
  • FastAPI 模式 (异步 Async):

    还是一个收银员。顾客A来了点个汉堡,收银员向厨房下单,给顾客A一个号牌,说:“您稍等”。他立刻转头接待顾客B。

    • 优点:虽然厨房做汉堡的速度没变,但收银员(服务器)在等待的时间里可以处理其他人的请求。这就是 “高并发”
# 定义一个异步函数
async def get_random_movie():
return ...

当你加上 async,就是在告诉 Python:“嘿,这个函数在处理任务时,如果遇到等待(比如读数据库、读文件),不要傻等,先去干别的事!”。这让 FastAPI 比传统的 Python 框架快很多。

@ 符号是什么?(装饰器)

@app.get("/api/movie/random")
def function(): ...

这个 @ 叫做 装饰器 (Decorator)。

你可以把它想象成便利贴或者路牌。

  • def function():这只是一个普普通通的 Python 函数,它躺在那里,没人知道它是干嘛的。
  • @app.get(...):这是给 FastAPI 贴了个条子。条子上写着:“如果有用户通过浏览器(GET方式)访问 /api/movie/random 这个地址,请立马叫醒下面这个函数来干活!”

如果没有这个 @,你的函数就是一个“孤岛”,外界永远访问不到它。

✅ 测试接口

保存代码后,终端应该会自动重载。如果没有,请手动运行 uvicorn main:app --reload

打开浏览器,访问网址:http://127.0.0.1:8000/api/movie/random,运行效果如下图所示。

image-20251123121525610

说明:

因为是随机生成的,所以大家显示的结果可能与我不同。每次刷新页面,也会随机生成一个新的。

打开浏览器访问交互文档:http://127.0.0.1:8000/docs

  1. 找到 GET /api/movie/random 栏目,点开。
  2. 点击 Try it out -> Execute
  3. 观察 Response body
  4. 再次点击 Execute。观察返回的电影是不是变了?如果是,说明随机功能正常!

运行效果如下图所示。

image-20251123121947519

3.电影详情接口

电影详情接口信息如下:

  • URL: /api/movie/{movie_id}
  • 功能: 根据 URL 中传入的 ID,返回特定电影。
  • 类型检查: 注意代码中的 movie_id: int。如果你访问 /api/movie/abc,FastAPI 会自动报错,因为它知道 abc 不是整数。这就是 FastAPI 的强大之处!

💻 代码实现

在main.py函数中,创建get_movie_by_id()函数。代码如下:

from fastapi import FastAPI,HTTPException

# --- 3. 电影详情接口 (带参数) ---
# 定义路由:当网址是 /api/movie/1 时,1 会被赋值给 movie_id
@app.get("/api/movie/{movie_id}")
async def get_movie_by_id(movie_id: int):

# 1. 遍历:去 movies 列表里一个一个翻
for movie in movies_data:
# 2. 比对:如果这部电影的 id 等于用户传进来的 id
if movie["id"] == movie_id:
# 3. 找到啦!直接把这一整条数据返回去
return movie

# 4. 兜底:如果循环都跑完了(所有电影都看过了),还没 return,说明没找到。
# 这时候不能装作无事发生,必须“报错”。
# 404 是 HTTP 协议中代表“未找到”的标准代号。
raise HTTPException(status_code=404, detail="未找到该 ID 的电影")

为什么写 movie_id: int?(类型提示)

async def get_movie_by_id(movie_id: int):

这里的 : int 叫做 类型提示 (Type Hint)。

在以前的 Python 里,大家不怎么写这个。但在 FastAPI 里,它充当了 “安检员” 的角色。

  • 场景 A (没有安检):用户访问 /api/movie/abc。函数拿到了字符串 "abc",然后去数据库里找 ID 为 "abc" 的电影。程序可能会崩溃,或者报错报得很奇怪。
  • 场景 B (有 : int 安检):用户访问 /api/movie/abc。FastAPI 的安检员一看:“咦?这里的规则要求是整数 (int),你给我的是字母?”
    • FastAPI 会直接拦截,拒绝执行函数,并自动返回一个清晰的错误信息给用户:“value is not a valid integer”。

这帮你省去了大量的 if type(movie_id) != int: 这种繁琐的检查代码。

为什么要使用HTTPException

用户请求 999,但系统只有 1–4,那你必须告诉用户结果:

✔ 找不到 ✔ 并用正确的 HTTP 状态码返回 ✔ 而不是假装没事

这是一个合格 API 的责任

return 的字典去哪了?(JSON 序列化)

return {"id": 1, "title": "..."}

我们在 Python 里写的是 字典 (Dictionary),但在浏览器里看到的是 JSON

  • Python 字典:只有 Python 认识的数据格式。
  • JSON:互联网通用的“普通话”。前端(JS)、后端(Python/Java/Go)、手机App都能读懂。

FastAPI 默默地做了一个巨大的贡献:它自动把你的 Python 字典“翻译”成了 JSON 字符串,然后发给浏览器。你完全不需要手动去调用 json.dumps()

测试接口

保存代码后,终端应该会自动重载。如果没有,请手动运行 uvicorn main:app --reload

✅ 测试任务 :获取指定电影

打开浏览器,访问网址:http://127.0.0.1:8000/api/movie/1,运行效果如下图所示。

image-20251123122424627

✅ 测试任务 :测试错误的类型

打开浏览器,访问网址:http://127.0.0.1:8000/api/movie/aaa。因为`aaa`不是指定的Int类型,运行效果如下: image-20251123122513444

✅ 测试任务 :测试错误处理 (404)

如果访问一个不存在的id数字,例如,访问网址:http://127.0.0.1:8000/api/movie/999。运行效果如下:

image-20251123122641790

4.总结

到目前为止,你已经理解:

✔ 如何创建模拟数据库

✔ 如何返回结构化 JSON

✔ 如何构建 REST API

✔ 什么是 async

✔ 什么是 类型检查

✔ 如何做错误处理

✔ 如何测试 API

目前为止我们还没有看到网站的页面,别急,下一节我们就来引入HTML页面,让你能在项目中看到效果!