fastapi: 自定义参数校验逻辑
一,代码:
1,自定义参数校验单码:单字段校验和多字段校验
# 1. 定义请求体数据模型
class UserRegister(BaseModel):
username: str = Field(..., min_length=3, max_length=20, description="用户名长度需要在3-20个字符之间")
age: int = Field(..., ge=18, le=100, description="年龄必须在18到100岁之间")
password: str = Field(..., min_length=6, description="密码长度最小为6")
confirm_password: str = Field(..., description="确认密码长度最小为6")
# 2. 单个字段的自定义校验(检查用户名是否包含空格)
@field_validator("username")
@classmethod
def username_alphanumeric(cls, v: str) -> str:
if " " in v:
raise ValueError("用户名不能包含空格")
return v
# 3. 跨字段的联合校验(验证两次密码是否一致)
# 在 Pydantic v2 中,联合校验可以使用 model_validator
@model_validator(mode="after")
def check_passwords(self) -> "UserRegister":
if self.password != self.confirm_password:
raise ValueError("两次输入的密码不一致")
return self
@router.post("/register")
async def register_user(user: Annotated[UserRegister, Form()],):
# 如果代码走到这里,说明参数已经全部校验通过
return {"message": "注册成功", "user": user.username}
2,在异常处理时判断error的类别:
# 2. 捕获 FastAPI 参数校验异常 (422 Unprocessable Entity)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
# 获取当前请求路由的匹配结果
route = request.scope.get("route")
custom_errors = []
str_errors = ''
# 获取异常自带的错误详情
errors = exc.errors()
for error in errors:
# loc 包含了出错字段的路径,例如 ("body", "username") 或 ("query", "age")
print("error:", error )
loc = error.get("loc", [])
print("loc:", loc)
field_name = loc[-1] if loc else None
location_type = loc[0] if len(loc) >= 1 else None
print("field_name:", field_name)
description = "未定义描述"
print("location_type:", location_type)
# 如果是请求体(Body)校验失败,尝试从路由函数签名中找 Pydantic Model 并提取 description
error_type = error['type']
print("error_type:", error_type)
if error_type == 'value_error':
print("is value_error")
if str_errors == '':
# field_name + ":" +
str_errors = error['msg']
# = one_str_error
else:
str_errors += "," + error['msg']
else:
print("not is value_error")
is_base = is_basemodel(route, field_name) # 判断字段是否是基于BaseModel的子类
print("is_base:", is_base)
if route and hasattr(route, "dependant") and field_name:
print("开始查找数据:", field_name)
# 遍历路由中所有的 Query 参数定义
all_flat_params = (
route.dependant.query_params +
route.dependant.path_params +
route.dependant.header_params +
route.dependant.cookie_params
# route.dependant.body_params
)
for param in all_flat_params:
print("dir_param:", dir(param))
# 匹配当前报错的字段名(比如 "q")
print("param.name:", param.name)
if param.name == field_name:
# 关键点:FastAPI 把 Query(description="...") 存放在了 param.field_info 中
if hasattr(param, "field_info") and getattr(param.field_info, "description", None):
description = param.field_info.description
print("description1:", description)
error_msg = f"{description}"
if str_errors == '':
str_errors = error_msg
else:
str_errors += ";" + error_msg
break
body_flat_params = (
route.dependant.body_params
)
for param in body_flat_params:
print("dir_param:", dir(param))
# 匹配当前报错的字段名(比如 "q")
print("param.name:", param.name)
# if param.name == field_name:
# 关键点:FastAPI 把 Query(description="...") 存放在了 param.field_info 中
if hasattr(param, "field_info") and getattr(param.field_info, "description", None):
description = param.field_info.description
print("description1:", description)
error_msg = f"{description}"
if str_errors == '':
str_errors = error_msg
else:
str_errors += ";" + error_msg
break
if (location_type == "body" or location_type == "query") and route and field_name:
endpoint = route.endpoint
# 遍历路由函数的参数,寻找继承自 BaseModel 的参数
import inspect
sig = inspect.signature(endpoint)
print(sig.parameters.items())
field_info = ''
for param_name, param in sig.parameters.items():
model_cls = param.annotation
print("model_cls:", model_cls)
print(dir(model_cls))
if hasattr(model_cls, 'model_fields'):
field_info = model_cls.model_fields.get(field_name)
print("field_info:", field_info)
if field_info and field_info.description:
error_msg = f"{field_info.description}"
print("error_msg:", error_msg)
if str_errors == '':
str_errors = error_msg
else:
str_errors += ";" + error_msg
break
else:
print("没有model_fields属性")
if str_errors == '':
# field_name + ":" +
one_str_error = error['msg']
str_errors = one_str_error
content = BaseResponse(code=422, msg=str_errors, data=custom_errors).model_dump()
return JSONResponse(
status_code=422,
content=content
)
二,测试效果:

浙公网安备 33010602011771号