Hyperf中间件权重数值越小越靠后执行,即数值越大越先执行;认证类中间件应设高权重(如10或15),日志类设低权重(如1~3),跨域等响应修饰设为0或-1;多处注册时按权重升序合并排序,非声明顺序;依赖必须通过容器注入,禁用new实例;调试用middleware:list命令验证实际注册顺序。

中间件执行顺序不按预期?权重数值越小越靠后
Hyperf 的 JSON-RPC 和 HTTP 中间件都走 PSR-15 洋葱模型,但权重(priority)不是“越大越优先执行”,而是数值越小,越靠近响应返回端——也就是越晚执行。比如 LogMiddleware 设为 1,它会在所有 5、10 的中间件之后才运行;而 AuthMiddleware 设为 10,才是最先拦截请求的那个。
常见错误现象:
- 日志里看不到认证失败记录 →
LogMiddleware权重比AuthMiddleware小,导致认证异常时日志还没来得及写就中断了 - 限流没生效 →
RateLimitMiddleware被配成2,结果排在权限校验之后,恶意请求已穿透到业务层 - 跨域头丢失 →
CorsMiddleware未显式设权,按默认0运行,成了最外层中间件,但被更早的中间件提前 return 响应,压根没走到它
实操建议:
- 认证类中间件统一用
10或更高(如15),确保第一个触达请求 - 权限/角色校验设为
8,紧随其后 - 日志、监控类中间件设为
3~1,放在业务处理之后、响应封装之前 - 跨域、压缩等通用响应修饰中间件保留
0或显式设为-1,让它在最外层兜底
全局配置 vs 注解配置:优先级叠加规则要厘清
当一个接口同时被路由中间件、注解中间件和全局中间件包围时,Hyperf 不是简单取最大值或加总,而是分层合并后**按数值升序重排**。最终顺序由三者共同注册的实例集合决定,不是谁“覆盖”谁。
例如:
- 全局配置中定义了
AuthMiddleware::class => 10 - 路由组里加了
PermissionMiddleware::class => 8 - 控制器方法上写了
#[Middleware(LogMiddleware::class, 2)]
那实际执行顺序就是:AuthMiddleware(10)→ PermissionMiddleware(8)→ LogMiddleware(2)。注意:这里不是按声明位置,而是按数字大小排序后执行——即数值大的先执行,数值小的后执行。
容易踩的坑:
- 以为注解中间件“更近”,就该优先执行 → 错,它只影响是否注册,不改变权重计算逻辑
- 在多个地方重复注册同一个中间件类(如两次
AuthMiddleware)→ 会注册两个实例,可能造成重复鉴权或 session 冲突 - 混用字符串类名和对象实例注册 → 类名会被容器解析一次,对象则直接使用,可能导致构造参数不一致
协程上下文穿透失败?中间件里别手动 new 实例
Hyperf 的中间件在协程内运行,很多依赖(如 RequestInterface、Container)需从当前协程上下文获取。如果在中间件 process() 方法里直接 new AuthMiddleware() 或调用静态方法绕过容器,会导致依赖无法注入、协程变量丢失、DB 连接池错乱。
典型表现:
- 中间件里能取到
$request->getHeader('token'),但后续调用UserService::getUserById()报Connection refused→ 因为 service 实例没走容器,没绑定当前协程的 DB 连接 - 日志中间件写入的 trace_id 是空的 →
TraceContext未从上下文提取,而是用了新实例的默认值
正确做法:
- 所有依赖必须通过
#[Inject]或构造函数注入,让容器管理生命周期 - 避免在
process()中 new 任何框架组件类,包括Db::connection()、Redis::get()等,改用注入的代理对象 - 若需动态创建中间件(如按路径加载不同鉴权策略),用
$this->container->get(YourMiddleware::class)替代new
调试中间件顺序:用 php bin/hyperf.php middleware:list
Hyperf 自带命令可实时查看当前已注册的中间件及其权重,比翻 config 更可靠,尤其适合上线前验证。
运行命令:
php bin/hyperf.php middleware:list --type=jsonrpc-http
输出示例(节选):
[
{"middleware": "App\Middleware\AuthMiddleware", "priority": 10},
{"middleware": "App\Middleware\PermissionMiddleware", "priority": 8},
{"middleware": "App\Middleware\LogMiddleware", "priority": 2}
]
关键提醒:
- 该命令只显示已启用且成功注册的中间件,如果某中间件类名拼错或命名空间不存在,它根本不会出现在列表里
-
--type参数必须与配置键完全一致,比如jsonrpc-http不能写成jsonrpc或http - 开发中建议每次改完中间件配置都跑一遍这个命令,确认顺序符合预期——权重数字本身不重要,重要的是它们组成的相对序列
真正容易被忽略的是:中间件权重一旦写死在注解或路由配置里,就很难被环境变量覆盖。如果需要灰度切换某中间件,不要改 priority 数值,而是用条件判断在 process() 内部跳过逻辑,否则多环境部署极易出错。


















