← 返回题目列表

FastAPI 中路径参数、查询参数和请求体参数如何区分?

高频 简单 第 2 / 27 题 更新于 2026/07/27
路由路径参数查询参数请求体

简化版

FastAPI 会根据参数声明自动判断来源:出现在路径模板里的参数是路径参数;基础类型且不在路径里的参数通常是查询参数;Pydantic 模型参数通常来自请求体。需要更明确控制时,可以使用 PathQueryBody 等工具。

详细版

FastAPI 的参数来源规则很清晰:

  • /users/{user_id} 中的 user_id 是路径参数;
  • 函数中未出现在路径里的基础类型参数,如 page: int = 1,通常是查询参数;
  • Pydantic BaseModel 类型通常表示请求体;
  • 多个 body 参数或特殊嵌套结构,可以用 Body 显式声明;
  • 请求头和 Cookie 分别用 HeaderCookie 声明。

示例:

@app.post("/users/{user_id}")
def update_user(
    user_id: int,
    verbose: bool = False,
    user: UserUpdate = Body(),
):
    return {"user_id": user_id, "verbose": verbose}

面试中要说明:FastAPI 的这种推断依赖类型注解和路径模板,但复杂场景最好显式声明,提升可读性。

完整版教学

一、为什么参数来源规则很重要

API 接口的输入可能来自很多地方:URL 路径、查询字符串、请求体、请求头、Cookie、文件表单。参数来源不清楚时,代码很容易变得混乱,接口文档也会含糊。

FastAPI 的优势是它能从函数签名推断大部分参数来源。例如:

@app.get("/users/{user_id}")
def get_user(user_id: int, detail: bool = False):
    return {"user_id": user_id, "detail": detail}

访问 /users/1?detail=true 时,user_id 来自路径,detail 来自查询字符串。开发者不用手动从 request 对象里取。

二、路径参数如何识别

路径参数由路由模板决定。凡是在路径中用 {} 声明的变量,都必须在函数参数中出现:

@app.get("/orders/{order_id}")
def get_order(order_id: int):
    return {"order_id": order_id}

order_id 会先从 URL 中取出,再根据类型注解转换成整数。如果转换失败,FastAPI 返回校验错误。

路径参数适合表达资源定位,例如用户 ID、订单 ID、文章 slug。它通常是必填的,因为没有路径参数就无法定位资源。

三、查询参数如何识别

函数参数没有出现在路径模板里,并且是基础类型,例如 strintboolfloat,通常会被当作查询参数:

@app.get("/products")
def list_products(page: int = 1, size: int = 20, keyword: str | None = None):
    return {"page": page, "size": size, "keyword": keyword}

访问方式是 /products?page=2&size=10&keyword=phone

查询参数适合表达筛选、排序、分页、搜索关键词。它们不应该用于传递复杂业务对象,也不适合承载敏感信息,因为 URL 可能被日志、浏览器历史或代理记录。

四、请求体参数如何识别

如果参数是 Pydantic 模型,FastAPI 通常会把它视为请求体:

class ProductCreate(BaseModel):
    name: str
    price: float

@app.post("/products")
def create_product(product: ProductCreate):
    return product

客户端需要发送 JSON:

{
  "name": "Keyboard",
  "price": 199.0
}

请求体适合创建或修改资源时提交结构化数据。POST、PUT、PATCH 常见请求体;GET 一般不建议依赖请求体。

五、什么时候显式使用 Path、Query、Body

虽然 FastAPI 能自动推断,但显式声明可以让接口意图更清楚,也能加约束:

from fastapi import Path, Query, Body

@app.put("/products/{product_id}")
def update_product(
    product_id: int = Path(gt=0, description="商品 ID"),
    keyword: str | None = Query(default=None, max_length=50),
    product: ProductUpdate = Body(),
):
    return {"product_id": product_id}

Path(gt=0) 表示路径 ID 必须大于 0。Query(max_length=50) 表示查询关键字最长 50。Body() 则明确说明该参数来自请求体。

在团队项目中,我更倾向对重要参数显式声明,因为这样文档更完整,维护者也更容易理解。

六、请求头、Cookie 和文件上传

FastAPI 还支持其他参数来源:

from fastapi import Header, Cookie

@app.get("/profile")
def profile(
    user_agent: str | None = Header(default=None),
    session_id: str | None = Cookie(default=None),
):
    return {"user_agent": user_agent, "session_id": session_id}

文件上传使用 FileUploadFile

from fastapi import File, UploadFile

@app.post("/upload")
async def upload(file: UploadFile = File()):
    return {"filename": file.filename}

这些工具让参数来源显式化,避免把所有东西都塞进 Request 对象里手动解析。

七、常见误区与追问

参数来源典型用途例子
路径参数定位唯一资源/users/42 中的 42
查询参数过滤、分页、排序?page=2&size=20
请求体创建或修改结构化对象JSON 用户资料
Header/Cookie元信息、认证凭证Authorizationsession_id
GET /products/100?detail=true
路径参数:product_id = 100
查询参数:detail = true

POST /products
请求体:{"name": "Keyboard", "price": 199.0}

记忆钩子:路径负责“找谁”,查询负责“怎么找”,请求体负责“提交什么”。

第一个误区是把所有参数都放到请求体。分页、筛选、排序更适合查询参数,因为它们描述的是“如何读取资源”,不是“提交一个资源对象”。

第二个误区是把敏感信息放到查询参数。token、密码、密钥不应该出现在 URL 里。

第三个误区是路径参数设计过深。比如 /a/{a_id}/b/{b_id}/c/{c_id}/d/{d_id} 可读性和维护性都不好,说明资源建模可能需要重新整理。

  • 误区:把所有参数都放到请求体。 分页、排序、筛选更适合查询参数,因为它们描述读取资源的方式,不是提交资源本身。
  • 误区:把密码、token、密钥放到查询参数。 URL 容易进入浏览器历史、网关日志、监控系统和 Referer,敏感凭证应放在 Header 或更合适的安全机制中。
  • 误区:路径设计层级越深越 RESTful。 /a/{a_id}/b/{b_id}/c/{c_id}/d/{d_id} 可读性差,通常说明资源边界需要重新建模。
  • 追问:多个 Pydantic 模型参数会怎样? FastAPI 会把它们都视为 body 的不同字段;如果想控制嵌套结构,可以用 Body(embed=True) 或显式请求体模型。
  • 追问:为什么重要参数建议显式用 PathQueryBody 显式声明能补充范围、长度、描述和示例,让校验、文档和团队可读性都更稳定。
  • 追问:GET 请求能不能带 body? HTTP 规范没有完全禁止,但生态支持和缓存代理语义都不稳定,面试和工程实践中通常不建议依赖 GET body。

八、加强记忆

记 FastAPI 参数来源规则:路径模板里出现的是路径参数,基础类型且不在路径里的是查询参数,Pydantic 模型通常是请求体。复杂或重要参数用 PathQueryBody 显式声明,既能校验,也能生成更清楚的文档。