FastAPI查询参数与字符串校验:规范请求,规避异常

2 阅读11分钟

在FastAPI接口开发中,查询参数是接收客户端非路径动态数据的常用方式,常用于筛选、搜索、分页等场景。与路径参数不同,查询参数无需嵌入URL路径,可灵活传递可选或必填信息,但如果缺乏有效的校验,很容易导致非法参数传入,引发接口异常。FastAPI提供了强大的查询参数校验功能,结合Annotated和Query工具,可轻松实现参数的长度、格式、取值范围等约束,同时自动生成清晰的错误提示和API文档。本文将聚焦查询参数的基础用法、字符串校验细节、高级配置及常见问题排查,结合实际代码示例,详细讲解如何规范使用查询参数,确保接口请求的合法性和稳定性。

一、前置准备:基础环境与核心依赖

本文所有示例均基于Python 3.10+版本和FastAPI 0.95.1及以上版本(需支持Annotated),建议结合虚拟环境隔离依赖,避免版本冲突。基础环境搭建步骤如下:

# 激活虚拟环境(若未激活)
# Windows PowerShell
.venv\Scripts\Activate.ps1
# Linux、macOS及Windows Bash
# source .venv/bin/activate

# 安装依赖(确保FastAPI版本达标)
pip install "fastapi[standard]>=0.95.1"

核心依赖说明:Annotated用于为参数添加元数据和校验规则,Query用于声明查询参数的校验逻辑和额外信息,两者结合是FastAPI推荐的查询参数校验方式,简洁且符合Python开发直觉。

二、基础用法:查询参数的基本声明

查询参数的声明无需额外装饰器,只需在路径操作函数中定义参数,FastAPI会自动识别为查询参数。根据需求,可声明可选查询参数、必填查询参数,以及带有默认值的查询参数。

1. 可选查询参数(默认值为None)

当查询参数非必填时,可将其类型标注为「类型|None」,并设置默认值为None,FastAPI会自动识别为可选参数,客户端可选择是否传递该参数。

from fastapi import FastAPI

app = FastAPI()

# 可选查询参数q,类型为str或None,默认值为None
@app.get("/products/")
async def get_products(q: str | None = None):
    # 若无查询参数q,返回所有商品;若有,返回包含查询条件的结果
    result = {"products": [{"id": 1, "name": "笔记本电脑"}, {"id": 2, "name": "智能手机"}]}
    if q:
        result["query"] = q
        result["filtered_products"] = [p for p in result["products"] if q in p["name"]]
    return result

访问示例:访问http://127.0.0.1:8000/products/(无查询参数),返回所有商品;访问http://127.0.0.1:8000/products/?q=电脑,返回包含“电脑”的商品。

2. 带有默认值的查询参数

若希望查询参数有默认值,无需客户端传递即可使用,可直接设置默认值(非None),此时参数仍为可选,客户端传递参数时会覆盖默认值。

from fastapi import FastAPI

app = FastAPI()

# 查询参数page,默认值为1,用于分页
@app.get("/products/")
async def get_products(page: int = 1, page_size: int = 10):
    # 模拟分页逻辑:从第(page-1)*page_size条开始,取page_size条数据
    total = 50
    start = (page - 1) * page_size
    end = start + page_size
    products = [{"id": i, "name": f"商品{i}"} for i in range(start + 1, end + 1) if i <= total]
    return {"page": page, "page_size": page_size, "total": total, "products": products}

访问示例:访问http://127.0.0.1:8000/products/,默认返回第1页、每页10条商品;访问http://127.0.0.1:8000/products/?page=2&page_size=5,返回第2页、每页5条商品。

3. 必填查询参数

若查询参数为必填项,只需不设置默认值,同时标注明确的类型(无需包含None),FastAPI会自动校验客户端是否传递该参数,若未传递则返回清晰的错误提示。

from fastapi import FastAPI

app = FastAPI()

#  必填查询参数category,无默认值,必须传递
@app.get("/products/")
async def get_products(category: str):
    # 根据分类筛选商品
    products = [
        {"id": 1, "name": "笔记本电脑", "category": "电子"},
        {"id": 2, "name": "衬衫", "category": "服装"},
        {"id": 3, "name": "面包", "category": "食品"}
    ]
    filtered = [p for p in products if p["category"] == category]
    return {"category": category, "products": filtered}

访问示例:访问http://127.0.0.1:8000/products/(未传递category),会返回错误提示,提示“field required”;访问http://127.0.0.1:8000/products/?category=电子,返回电子类商品。

三、核心功能:字符串校验的常用方式

对于字符串类型的查询参数,FastAPI提供了丰富的校验规则,如长度限制、正则匹配等,通过Annotated结合Query工具即可实现,无需手动编写校验逻辑,同时会自动生成错误提示和API文档。

1. 长度校验(min_length、max_length)

当需要限制字符串查询参数的长度时,可通过Query的min_length(最小长度)和max_length(最大长度)参数设置约束,若参数长度超出范围,FastAPI会自动返回错误。

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

# 字符串查询参数search,长度3-20个字符,可选
@app.get("/search/")
async def search(
    search: Annotated[str | None, Query(min_length=3, max_length=20)] = None
):
    if not search:
        return {"message": "请输入搜索关键词(3-20个字符)"}
    return {"search": search, "result": ["匹配结果1", "匹配结果2"]}

说明:访问http://127.0.0.1:8000/search/?search=ab(长度2),会返回错误提示“ensure this value has at least 3 characters”;访问http://127.0.0.1:8000/search/?search=abcdefghijklmnopqrstu(长度21),会提示“ensure this value has at most 20 characters”。

2. 正则表达式校验(pattern)

若需要限制字符串查询参数的格式(如仅允许字母、数字组合),可通过Query的pattern参数设置正则表达式,实现精准格式校验。

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

# 字符串查询参数username,仅允许字母和数字组合,必填
@app.get("/user/")
async def get_user(
    username: Annotated[str, Query(pattern="^[a-zA-Z0-9]+$")]
):
    return {"username": username, "message": "用户查询成功"}

说明:正则表达式^[a-zA-Z0-9]+$表示参数只能由字母(大小写)和数字组成,且不能为空;访问http://127.0.0.1:8000/user/?username=test123(合法),返回正常响应;访问http://127.0.0.1:8000/user/?username=test_123(含下划线),会返回格式错误提示。

3. 组合校验(多规则同时生效)

实际开发中,可同时设置多个校验规则,如长度限制+正则匹配,FastAPI会依次校验所有规则,只有全部满足才会正常处理请求。

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

# 组合校验:长度4-10个字符,且仅允许字母和数字
@app.get("/login/")
async def login(
    code: Annotated[str, Query(min_length=4, max_length=10, pattern="^[a-zA-Z0-9]+$")]
):
    return {"code": code, "message": "验证码校验通过"}

四、高级配置:查询参数的额外设置

除了基础校验,FastAPI还支持为查询参数添加别名、描述、弃用标记等额外配置,提升接口的可读性和可维护性,同时适配特殊的URL参数命名需求。

1. 别名参数(alias)

当URL中的查询参数名称与Python变量名不一致(如URL中为item-query,Python中无法使用连字符变量名)时,可通过Query的alias参数设置别名,FastAPI会根据别名解析URL中的参数。

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

# URL中查询参数为item-query,Python变量名为item_query
@app.get("/items/")
async def get_items(
    item_query: Annotated[str | None, Query(alias="item-query")] = None
):
    return {"item_query": item_query, "message": "查询成功"}

访问示例:访问http://127.0.0.1:8000/items/?item-query=foobaritems,FastAPI会自动将item-query的值解析给item_query变量;若访问该URL时出现“URL拼写可能存在错误,请检查”,需确认服务器已正常运行,且端口为8000,同时检查URL拼写是否正确(如是否多写或漏写字符)。

2. 元数据添加(title、description)

可通过Query的title和description参数,为查询参数添加标题和描述信息,这些信息会显示在自动生成的API文档(http://127.0.0.1:8000/docs)中,方便团队协作和接口测试。

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/products/")
async def get_products(
    keyword: Annotated[
        str | None,
        Query(
            title="搜索关键词",
            description="用于搜索商品的关键词,支持模糊匹配,长度3-20个字符",
            min_length=3,
            max_length=20
        )
    ] = None
):
    result = {"products": [{"id": 1, "name": "笔记本电脑"}, {"id": 2, "name": "智能手机"}]}
    if keyword:
        result["filtered_products"] = [p for p in result["products"] if keyword in p["name"]]
    return result

3. 弃用参数(deprecated)

若某个查询参数即将被废弃,但仍需暂时保留以兼容旧客户端,可通过Query的deprecated=True参数标记为弃用,API文档会明确标注该参数已弃用,提醒使用者更换新参数。

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/products/")
async def get_products(
    old_keyword: Annotated[
        str | None,
        Query(
            deprecated=True,
            description="该参数已弃用,请使用keyword参数替代"
        )
    ] = None,
    keyword: Annotated[str | None, Query(min_length=3)] = None
):
    search_key = keyword if keyword else old_keyword
    result = {"products": [{"id": 1, "name": "笔记本电脑"}, {"id": 2, "name": "智能手机"}]}
    if search_key:
        result["filtered_products"] = [p for p in result["products"] if search_key in p["name"]]
    return result

4. 隐藏参数(include_in_schema)

若某个查询参数无需在API文档中展示(如内部调试参数),可通过Query的include_in_schema=False参数,将其从OpenAPI文档中排除,既不影响使用,也能避免暴露内部参数。

from typing import Annotated
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/products/")
async def get_products(
    keyword: Annotated[str | None, Query(min_length=3)] = None,
    debug: Annotated[bool | None, Query(include_in_schema=False)] = False
):
    result = {"products": [{"id": 1, "name": "笔记本电脑"}, {"id": 2, "name": "智能手机"}]}
    if keyword:
        result["filtered_products"] = [p for p in result["products"] if keyword in p["name"]]
    # 调试参数,仅内部使用,不展示在API文档中
    if debug:
        result["debug_info"] = {"total": 2, "source": "database"}
    return result

五、特殊场景:查询参数列表与自定义校验

1. 接收多个查询参数(列表类型)

当需要接收多个相同名称的查询参数(如多选筛选)时,可通过Annotated结合Query,将参数类型声明为list[str],FastAPI会自动将多个参数值整理为列表。

from typing import Annotated, list
from fastapi import FastAPI, Query

app = FastAPI()

# 接收多个category参数,如?category=电子&category=服装
@app.get("/products/")
async def get_products(
    category: Annotated[list[str] | None, Query()] = None
):
    products = [
        {"id": 1, "name": "笔记本电脑", "category": "电子"},
        {"id": 2, "name": "衬衫", "category": "服装"},
        {"id": 3, "name": "面包", "category": "食品"},
        {"id": 4, "name": "手机", "category": "电子"}
    ]
    if category:
        filtered = [p for p in products if p["category"] in category]
    else:
        filtered = products
    return {"category": category, "products": filtered}

访问示例:访问http://127.0.0.1:8000/products/?category=电子&category=服装,会返回电子和服装类的所有商品。

2. 自定义校验(AfterValidator)

当内置校验规则无法满足需求时,可通过Pydantic的AfterValidator,自定义校验函数,实现个性化的参数校验逻辑(如校验参数是否符合特定格式)。

from typing import Annotated
from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

# 自定义校验函数:校验手机号格式(以13、14、15、17、18开头,共11位)
def check_phone(phone: str):
    if not phone.isdigit() or len(phone) != 11 or not phone.startswith(("13", "14", "15", "17", "18")):
        raise ValueError("手机号格式错误,需为11位数字,且以13、14、15、17、18开头")
    return phone

# 应用自定义校验
@app.get("/user/phone/")
async def get_user_by_phone(
    phone: Annotated[str, AfterValidator(check_phone)]
):
    return {"phone": phone, "message": "手机号校验通过,用户存在"}

六、常见问题排查

总结

查询参数是FastAPI接口开发中不可或缺的组件,而有效的参数校验是保证接口稳定性和安全性的关键。FastAPI通过Annotated和Query工具,提供了简洁、强大的查询参数校验功能,支持长度限制、正则匹配、别名设置、自定义校验等多种场景,无需手动编写复杂的校验逻辑,同时自动生成错误提示和API文档,大幅提升开发效率。

本文通过实际代码示例,详细讲解了查询参数的基础用法、字符串校验规则、高级配置及特殊场景处理,全程围绕核心内容展开,覆盖了实际开发中常见的需求和问题。掌握这些用法后,可规范查询参数的传递,规避非法参数引发的接口异常,让接口更具可读性、可维护性和安全性。