责任链模式适合API需动态组合多个解耦校验步骤的场景,如鉴权→参数格式→业务规则→频控;不适用于强依赖原子校验或纯同步中间件。

责任链模式在API校验中适合什么场景
当你的API需要依次执行多个互不耦合的校验逻辑(比如鉴权 → 参数格式 → 业务规则 → 频控),且不同接口启用的校验项不同,或者某些校验需动态跳过时,责任链比硬编码 if-elif 或装饰器堆叠更易维护。它不适用于单次强依赖的原子校验(如 JWT 解析失败必须终止),也不适合纯同步阻塞型中间件(如 Django 的 middleware 已足够)。
用 Handler 类实现可插拔校验节点
每个校验步骤封装为继承自基类的 Handler 实例,关键在于统一接口和链式传递控制权。不要把所有逻辑塞进一个 handle() 方法里,而是让每个子类只关心「自己是否处理」和「是否继续传递」:
class Handler:
def __init__(self, next_handler: 'Handler' = None):
self.next = next_handler
<pre class="brush:php;toolbar:false;">def handle(self, request: dict) -> bool:
raise NotImplementedErrorclass AuthHandler(Handler): def handle(self, request: dict) -> bool: token = request.get("headers", {}).get("Authorization") if not token: request["error"] = "Missing Authorization header" return False
验证通过,交给下一个
return self.next.handle(request) if self.next else True
class SchemaHandler(Handler): def handle(self, request: dict) -> bool: try: json.loads(request.get("body", "{}")) except json.JSONDecodeError: request["error"] = "Invalid JSON body" return False return self.next.handle(request) if self.next else True
- 每个
handle()返回bool:表示「当前环节是否放行」,False即刻中断链 - 不要在
handle()内抛异常来控制流程——这会让调用方难捕获具体哪一环失败 -
request是可变字典,用于透传上下文(如提取后的 user_id、解析后的参数),但避免写入无关字段
构建链时注意顺序与短路行为
链的组装顺序直接影响校验优先级和性能。高频失败项(如缺失 token)应前置,避免浪费资源做后续校验;而耗时操作(如查 DB)尽量后置或做成可选分支。
# 正确:先轻量校验,再重操作
validator = AuthHandler(
SchemaHandler(
BusinessRuleHandler(
RateLimitHandler()
)
)
)
<h1>错误:把 DB 查询放在最前,每次请求都触发</h1><p>validator = DBUserLookupHandler(SchemaHandler(...))
- 用构造函数传参方式组装链,比用
set_next()方法更清晰、更易单元测试 - 如果某个环节可能被开关控制(如灰度期跳过频控),不要删节点,而是让其
handle()直接返回self.next.handle(request)(即透明转发) - Python 中没有尾递归优化,深层链(>10 层)可能触发
RecursionError,生产环境建议限制最大深度或改用循环遍历
集成到 FastAPI/Flask 路由时怎么传参
别把整个 Request 对象丢进链——框架的 Request 是不可变或带异步状态的。应提前提取你需要的字段,构造成 plain dict:
立即学习“Python免费学习笔记(深入)”;
@app.post("/order")
async def create_order(request: Request):
# 提前解包,不传原始 Request 实例
payload = {
"method": request.method,
"headers": dict(request.headers),
"body": await request.body(),
"query_params": dict(request.query_params),
"user_id": None, # 后续 handler 可填充
}
<pre class="brush:php;toolbar:false;">if not validator.handle(payload):
raise HTTPException(status_code=400, detail=payload["error"])
return {"user_id": payload["user_id"], "status": "ok"}
- FastAPI 的
request.body()是bytes,不是字符串,SchemaHandler要用json.loads(body.decode()) - 不要在 handler 里调用
await—— 同步链无法等待协程。如需异步校验(如 Redis 频控),得把整个链改成 async/await 风格,或用线程池包装 - 错误信息统一挂载到
request字典的"error"键下,便于上层统一响应格式,而不是各 handler 自己 raise
链越长,调试越难定位是哪个 handler 挂掉的。上线前务必给每个 handler 加日志,至少记录「进入」「跳过」「中断」三种状态,别只靠 error 字段反推。


















