1. 本课定位是什么、为何重要上一课服务已经能跑/health会回 ok/docs也能点。但真实 API 不会只有探活——书签业务要按资源区分列表、详情、删除还要支持搜索和分页雏形。如果所有逻辑都堆在一个main.py里用硬编码路径文件很快膨胀到难维护。本课把「服务端如何定义 URL 形状」系统化路径参数标识资源查询参数负责过滤与限制状态码表达结果语义并用APIRouter按资源拆分。数据仍用内存字典进程重启即消失为第 42 课 Body、第 44 课落库留接口形状。学完你应能实现书签的读列表、读详情、删除并分清 404 与 422。概念一句话路径参数嵌在 URL 路径里的变量如/bookmarks/3的3查询参数?后面的键值如?qpythonlimit10APIRouter把一组路由拆到子模块再挂到主appHTTPException主动抛出业务错误如 404并返回 JSON detail为何重要参数放错位置、状态码乱用前端与联调会极度痛苦。对比已学已学本课客户端「拼 URL、读状态码」服务端「定义 URL、返回状态码」函数参数来自调用方参数可来自 Path / Query单文件main.py多文件APIRouter只有/health资源型/bookmarks2. 本质从 HTTP 请求里取值很多人以为「路由就是字符串匹配」。FastAPI 更进一步根据装饰器上的路径模板 函数参数注解决定如何从请求里取值类型转换失败时自动 422不必手写一堆 if。上一课你返回固定 JSON这一课 JSON 开始依赖「谁在访问、带了什么参数」。先建立「入参位置」地图Path、Query、BodyBody 下节再写代码才不会把过滤条件塞进路径里。本质路径模板 注解 → 自动解析与校验业务「找不到」要自己 404。入参位置例子适合Path/bookmarks/{id}资源标识几乎总是必填Query?qlimit过滤、排序、分页常可选BodyJSON第 42 课创建/更新的结构化数据GET /bookmarks/3?qpy | | 路径参数 查询参数 bookmark_id3 qpy3. 约束与常见坑自动校验很爽但也带来新坑abc变不成int时是 422不是你业务里的 404。若一律返回 200 再在 body 里写error前端分支会写崩。这一节把约束和坑表列全命名一致、REST 名词化路径、204 删除、Router 拆分。红线清楚后综合实践里的 curl 预期才读得懂。约束路径参数名与函数参数名一致。注解成int时abc→422校验失败不是业务 404。404表示「类型对了但资源不存在」——要自己HTTPException。REST 习惯路径用名词资源动作用HTTP 方法。列表过滤优先 Query保持资源标识路径稳定。常见坑坑现象正确直觉用 200 表示「没找到」前端难写分支应用 404把过滤条件塞进路径/bookmarks/search/python难扩展过滤优先 Query单文件上百路由难找、易冲突APIRouterprefix/tags删除仍返回大 JSON多余可用204无正文路径参数名与函数名不一致取值错乱/校验怪名字对齐422 当 404 处理联调互相甩锅先看是类型错还是真没有4. 路径参数 vs 查询参数对照表两种参数都是「给服务端传值」但语义不同路径说「是哪个资源」查询说「怎么筛选/限制这次查看」。混用会让 URL 地图混乱。这一节用总表 最小代码钉牢并介绍Query的ge/le/description。这些约束会进 OpenAPI文档页上的限制不是摆设。路径参数查询参数形态/items/5/items?limit5必填感强标识资源常可选类型转换注解驱动注解 Query约束失败转类型失败 422约束失败 422书签例子/bookmarks/1?qpythonlimit10fromfastapiimportQueryrouter.get(/bookmarks/{bookmark_id})defget_one(bookmark_id:int):...router.get(/bookmarks)deflist_all(q:str|NoneNone,limit:intQuery(10,ge1,le100),):...Query约束含义ge/le大于等于 / 小于等于default不传时的默认值description写入 OpenAPI 文档小步预期limit0或limit9999应 422若设置了 ge/le。5. 状态码按用途归组客户端阶段你「读」状态码服务端阶段你「写」状态码。写错语义比写错字段更难查因为很多客户端按码分支而不是读 detail 字符串。本课先掌握 200/204/404/422201 创建成功会在第 42 课 POST 时高频出现。HTTPException是主动表达业务错误的标准方式。组码典型场景成功有体200查询详情/列表创建成功201POST 新建第 42 课常用成功无体204DELETE 成功客户端错404资源不存在校验错422参数类型/范围不合法fromfastapiimportHTTPExceptionraiseHTTPException(status_code404,detailbookmark not found)修改前修改后return {error: no}且 200raise HTTPException(404, detail...)前端只能猜 body前端可先看 status 再分支6. 路由组织APIRouter单文件 demo 能跑但书签、用户、上传一多就会「翻文件翻到哭」。APIRouter让你按资源拆模块统一前缀、文档 tags、主 app 只负责挂载。这一节建立目录直觉即可main.py创建 app 并include_routerrouters/bookmarks.py写资源路由。后续课目录会再长但「按资源拆 router」的习惯从本课开始。main.py # 创建 app、include_router routers/bookmarks.py # prefix/bookmarks, tags[bookmarks] routers/__init__.py # 使 routers 成为包API作用APIRouter(prefix..., tags...)统一前缀与文档分组app.include_router(router)挂载到应用router.get()挂在 prefix 根如/bookmarksrouter.get(/{id})详情路径注意router.get()与router.get(/)在不同版本/配置下对尾斜杠敏感本课列表用配合 prefixcurl 时用/bookmarks无尾斜杠即可。7. REST 直觉书签资源REST 不是教条考试而是「调用方好猜」的地图名词做路径动词做方法。/getBookmarkById这种动词路径能跑但难扩展、难缓存、难和前端约定。用书签资源把本课三个接口钉在地图上。写操作 POST/PATCH 留给模型课本课聚焦读与删先把 Path/Query/状态码练熟。方法路径含义GET/bookmarks列表可带 q、limitGET/bookmarks/{id}详情DELETE/bookmarks/{id}删除POST/bookmarks创建第 42 课PATCH/bookmarks/{id}部分更新第 42 课坏路径习惯更好习惯/getBookmarksGET /bookmarks/bookmarks/delete/1DELETE /bookmarks/1/bookmarks/search/pythonGET /bookmarks?qpython8. 落地场景内存书签列表数据放内存字典实现快、零依赖但进程重启即消失——这是刻意选择不是缺陷。第 44 课再换 SQLite接口形状尽量保持稳定。场景表帮助你对照「接口行为」写代码而不是先纠结数据库选型。接口行为GET /bookmarks列表q模糊标题limit截断GET /bookmarks/{id}详情或 404DELETE /bookmarks/{id}删除或 404成功 204GET /health探活主 app内存存储直觉_DB { 1: {id: 1, title: ..., url: ...}, ... }9. 小步示例404 与 422 对照这是本课最重要的体感实验之一。同一条「看起来像详情」的 URL失败原因不同状态码不同。分不清就会在联调时浪费整天。先看表综合实践里用 curl 亲自打一遍99与abc。请求更可能状态码原因GET /bookmarks/1存在200正常GET /bookmarks/99不存在404业务找不到GET /bookmarks/abcid 注解 int422类型校验失败GET /bookmarks?limit0ge1422范围校验失败DELETE /bookmarks/1成功204成功无正文10. 环境准备延续 day40 虚拟环境即可本课多一个routers包。Windows 推荐 Cygwin/WSL 执行 heredoc。项目要求Python3.10包fastapi、uvicorn[standard]目录day41/main.py、day41/routers/bookmarks.pymkdir-p~/python-lab/src/day41/routerscd~/python-lab/src/day41# 激活 venv 后pipinstallfastapi0.110uvicorn[standard]0.2711. 综合实践完整可运行脚本一次写入 router、空__init__、main并启动验证。请完整跑通四类 curl过滤列表、详情、不存在、删除。422 实验单独再打一次abc。Windows 请用 Cygwin/WSL 执行PowerShell 可手建同名文件。Uvicorn 占前台时另开终端验证。mkdir-p~/python-lab/src/day41/routerscd~/python-lab/src/day41catrouters/bookmarks.pyEOF from fastapi import APIRouter, HTTPException, Query router APIRouter(prefix/bookmarks, tags[bookmarks]) _DB { 1: {id: 1, title: FastAPI 文档, url: https://fastapi.tiangolo.com}, 2: {id: 2, title: Python 官网, url: https://www.python.org}, 3: {id: 3, title: Real Python, url: https://realpython.com}, } router.get() def list_bookmarks( q: str | None None, limit: int Query(default10, ge1, le100), ): items list(_DB.values()) if q: ql q.lower() items [x for x in items if ql in x[title].lower()] return {items: items[:limit], total: len(items)} router.get(/{bookmark_id}) def get_bookmark(bookmark_id: int): item _DB.get(bookmark_id) if not item: raise HTTPException(status_code404, detailbookmark not found) return item router.delete(/{bookmark_id}, status_code204) def delete_bookmark(bookmark_id: int): if bookmark_id not in _DB: raise HTTPException(status_code404, detailbookmark not found) del _DB[bookmark_id] return None EOFcatrouters/__init__.pyEOF EOF cat main.py EOF from fastapi import FastAPI from routers import bookmarks app FastAPI(titleDay41 Bookmark API, version0.1.0) app.include_router(bookmarks.router) app.get(/health) def health(): return {status: ok} EOFuvicorn main:app--reload--host127.0.0.1--port8000验证命令另开终端curl-shttp://127.0.0.1:8000/bookmarks?qpythoncurl-shttp://127.0.0.1:8000/bookmarks/1curl-shttp://127.0.0.1:8000/bookmarks/99curl-s-o/dev/null-w%{http_code}\n-XDELETEhttp://127.0.0.1:8000/bookmarks/2curl-shttp://127.0.0.1:8000/bookmarks/abccurl-shttp://127.0.0.1:8000/bookmarks?limit0预期要点请求预期qpython标题含 python 的项大小写不敏感id1200 对象id99404 {detail:...}DELETE 2 成功204idabc422limit0422修改前单文件只有/health。修改后资源路由拆分列表可过滤详情/删除语义完整。12. 路由注册顺序直觉了解固定路径与动态路径混用时声明顺序偶尔影响匹配例如/bookmarks/special与/bookmarks/{id}。本课数据简单但要知道更具体的路径通常应先于宽泛的{id}。入门不强制踩坑演示项目变大时若「明明写了路由却 422/404」回来查顺序与前缀。建议原因先写静态子路径避免被{id}吃掉prefix 统一在 Router减少手写重复/bookmarkstags 按资源/docs分组清晰13. 常见问答集中处理total 是过滤前还是过滤后、204 有没有 body、内存删除刷新后为何又回来、Query 默认值会不会进 URL。Qtotal应该是过滤后长度还是全库长度A本课示例用过滤后的len(items)产品里要文档写清前后端对齐即可。Q204 响应可以带 JSON 吗A语义上成功无正文客户端不要依赖 204 的 body。Q删除后重启服务数据又在A内存初始_DB写在代码里重启会重建第 44 课落库后才持久。Q不传limit会怎样A使用默认 10本课 Query default。14. 自我检查清单勾完再进第 42 课。Path/Query/404/422/Router 五项是本课硬指标。能解释路径参数与查询参数分工会写Query(..., ge, le)会HTTPException(404)知道abc→422、不存在 id→404会APIRouterinclude_routerDELETE 成功返回 204跑通过滤列表 curl15. 与前后课衔接课关系40会起服务 → 本课定义资源 URL42本课读删 → 下课 POST/PATCH Body44内存_DB→ SQLite 表总结收束几句带走路径标识资源查询负责过滤分页404 与 422 分工明确Router 按资源拆分内存库只为练接口形状。后面加 Body 和数据库时尽量少改 URL 地图。路径参数标识资源查询参数做过滤/分页。类型/范围失败 → 422资源不存在 → 404。DELETE 成功常用 204列表过滤优先 Query。APIRouter(prefix, tags)include_router拆分维护。内存字典重启即失接口形状为后续落库铺路。REST名词路径 HTTP 方法少用动词路径。小练笔先做再看答案。可选实践建议真的 curl 一遍 422把状态码记在本子上。题 1/bookmarks/foo且bookmark_id: int更常见A. 200 B. 404 C. 422题 2Query(10, ge1, le100)中ge含义题 3列表过滤用路径还是查询参数更合适为什么题 4DELETE 成功为何常用 204题 5写出「获取 id3 的书签」的方法 路径示例。题 6判断业务上找不到 id99应返回 422。题 7APIRouter(prefix/bookmarks)后列表函数装饰router.get()完整路径是题 8limit1000在le100时更可能A. 截断到 100 B. 422 C. 500题 9可选实践把默认limit改成 2不传 q 时列表最多几条动手验证。题 10为什么不建议/doDeleteBookmark?id1这种路径小练笔参考答案先自测再对照。意思对即可。题 1C题 2greater or equal最小值 1。题 3查询参数过滤可选、可组合路径应保持资源标识稳定。题 4成功且无需返回正文时204 语义更贴切。题 5GET /bookmarks/3主机端口按本地环境。题 6错应 404题 7/bookmarks或等价无尾斜杠形式按你框架配置题 8B题 9最多 2 条以你修改后运行为准。题 10应用 HTTP 方法表达动作路径保持资源名词更易缓存与约定。合理即可