FastAPI 中文错误提示需通过自定义 field_validator 抛 ValueError 或重写 RequestValidationError 处理器实现;description 不影响运行时错误文案,ERROR_MESSAGES 映射 type 与中文模板并解析 loc 和 ctx 才能精准输出。

FastAPI 默认用 Pydantic 做参数校验,但错误提示是英文、结构扁平、字段路径不直观——直接改 Field 的 description 或注释没用,它不会进错误消息;想输出中文且带字段名,必须干预验证失败后的错误组装过程。
Pydantic v2 中文错误提示不能靠 description 实现
很多人试过在 Field 里写 description="用户名不能为空" 或加注释,但发现报错时还是显示 "Field required"。这是因为 description 只用于生成 OpenAPI 文档,不参与运行时错误构造。
-
Field的default、default_factory、validation_alias等参数影响行为,但description和title完全不参与RequestValidationError的msg生成 - Pydantic v2 的错误模板固化在内部(如
missing对应"Field required"),不提供全局替换接口 - 真正能控制错误文案的入口只有两个:自定义
field_validator抛出带中文的ValueError,或重写异常处理器对e.errors()做映射
用 field_validator + ValueError 实现单字段中文提示
适用于明确知道哪个字段要强约束、且错误文案固定的情况。Pydantic 会把 ValueError 的字符串直接塞进 msg 字段。
from pydantic import BaseModel, field_validator
<p>class UserLogin(BaseModel):
username: str
password: str</p><pre class="brush:php;toolbar:false;">@field_validator("username")
def username_must_not_be_empty(cls, v):
if not v or not v.strip():
raise ValueError("用户名不能为空")
return v.strip()
@field_validator("password")
def password_must_be_at_least_6_chars(cls, v):
if len(v) < 6:
raise ValueError("密码长度不能少于6位")
return v
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- 注意:不要在
@field_validator里访问其他字段(比如对比password和confirm_password),那是@model_validator(mode="after")的职责 - 抛出的
ValueError字符串会原样出现在响应的msg里,前端可直接展示 - 这种写法绕过了 Pydantic 默认的
missing/string_type错误类型,所以错误type会变成"value_error",需前端适配
用 exception_handler 统一转译 errors() 列表为中文
这是最通用、可控性最强的方式,适合中大型项目。核心是解析 exc.errors() 中每个错误项的 type 和 loc,再查表映射为中文文案。
from fastapi import Request, FastAPI
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
<p>ERROR_MESSAGES = {
"missing": "缺少必填字段",
"string_type": "字段必须是字符串",
"int_parsing": "字段必须是有效整数",
"greater_than": "数值不能小于{gt}",
"less_than": "数值不能大于{lt}",
"string_too_short": "字符串长度不能少于{min_length}位",
}</p><p>@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = []
for error in exc.errors():
err_type = error["type"]
loc = error["loc"]
msg = error["msg"]</p><h1>字段路径转成易读形式,如 ('body', 'user', 'name') → "user.name"</h1><pre class="brush:php;toolbar:false;"> field = ".".join(str(x) for x in loc[1:]) if len(loc) > 1 else loc[-1]
# 查表 + 格式化动态值
template = ERROR_MESSAGES.get(err_type, msg)
try:
formatted_msg = template.format(**error.get("ctx", {}))
except Exception:
formatted_msg = template
errors.append({"field": field, "message": formatted_msg})
return JSONResponse(
status_code=422,
content={"code": 40001, "message": "参数校验失败", "errors": errors},
)
-
exc.errors()返回的是 list of dict,每个 dict 含loc(tuple)、msg(原始英文)、type(错误码)、ctx(上下文变量,如gt,min_length) - 不要直接修改
msg,而应基于type查表——因为同一type在不同字段可能需要不同文案(比如email字段的string_type可提示“邮箱格式错误”,而name字段则提示“姓名必须是文本”) -
loc[1:]是为了跳过'body'或'query'这类顶层标识,只取业务字段路径
嵌套模型和 Query/Path 参数的校验差异
Pydantic 模型只接管请求体(Body),而 Query、Path、Form 参数走的是 FastAPI 自己的依赖注入校验链,错误结构一致但触发时机不同。
- 对
Query参数加约束,要用Query(min_length=2),不是Field;它的错误loc是('query', 'q'),不是('body', 'q') - 嵌套模型(如
user: UserCreate)出错时,loc会是('body', 'user', 'age'),解析时注意层级 -
Form数据默认不被 Pydantic 模型接收,必须显式用Form(...)声明,否则会被当普通字符串处理 - 所有这些场景下,
RequestValidationError都会被同一个@app.exception_handler捕获,无需分别注册
真正难的不是写中文提示,而是维护错误码与业务语义的映射关系——比如 greater_than 在年龄字段是“不能小于0”,在金额字段却是“不能小于0.01”。这类逻辑必须沉淀到配置表或策略函数里,硬编码在 handler 里很快就会失控。

















