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
        )

 

二,测试效果:

image

posted @ 2026-06-24 20:04  刘宏缔的架构森林  阅读(2)  评论(0)    收藏  举报