Laravel 12 的队列中间件与 HTTP 中间件完全独立,必须实现 Illuminate\Queue\Middleware 接口或 handle($job, $next) 方法,仅作用于队列任务执行过程,不参与请求生命周期,且不可复用 Http/Middleware 下的类。

直接说结论:Laravel 12 的队列任务中间件(Queue Middleware)和 HTTP 中间件不是一回事,不能复用 app/Http/Middleware 下的类;必须用 Illuminate\Queue\Middleware 接口或实现 handle() 方法的独立类,且只对队列任务生效,不参与请求生命周期。
队列中间件和 HTTP 中间件是两套系统
很多人一看到“中间件”就下意识去 app/Http/Middleware 目录写逻辑,结果发现加了 ->through(YourMiddleware::class) 没反应——因为队列任务根本不会经过 HTTP 中间件栈。Laravel 的队列中间件运行在 php artisan queue:work 进程内,作用域仅限于单个任务的执行前后。
- HTTP 中间件处理的是
Request → Response流程,依赖Pipeline和Closure $next - 队列中间件处理的是
Job → handle()执行过程,签名是public function handle($job, $next),其中$job是任务实例,$next是继续执行任务的闭包 - 队列中间件类**必须放在
app/Jobs/Middleware或任意可自动加载的命名空间下**,不能混在 Http 目录里
怎么写一个限制并发执行次数的队列中间件
比如你有个发送通知的任务,不想让它每秒跑 10 次压垮第三方 API,就得靠队列中间件 + Redis 做节流。关键点不是“拦截”,而是“让任务跳过执行并释放 worker”。
示例:防止同一用户 ID 的通知任务在 60 秒内重复执行超过 3 次
namespace App\Jobs\Middleware;
use Illuminate\Support\Facades\Redis;
use Illuminate\Queue\Middleware\ShouldQueueAfterResponse;
class ThrottleUserNotifications
{
public function handle($job, $next)
{
$userId = $job->userId ?? null;
if (! $userId) {
return $next($job);
}
$key = "throttle:notify:{$userId}";
$count = (int) Redis::incr($key);
if ($count === 1) {
Redis::expire($key, 60); // TTL 60 秒
}
if ($count <= 3) {
return $next($job);
}
// 超限:不执行任务,但也不失败,只是 release 回队列稍后重试
$job->release(60); // 60 秒后再试
}
}
- 必须用
Redis::incr()而不是 PHP 变量计数——队列 worker 是多进程的,内存不共享 -
$job->release(60)是安全退出方式;别用return空值或抛异常,否则会进 failed_jobs 表 - 这个中间件不返回响应,也不影响 HTTP 流程,只控制当前 job 是否往下走
如何绑定到具体队列任务上
队列中间件不是注册到 Kernel.php,而是在 Job 类里通过 $middleware 属性声明:
namespace App\Jobs;
use App\Jobs\Middleware\ThrottleUserNotifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $middleware = [
ThrottleUserNotifications::class,
];
public $userId;
public function __construct($userId)
{
$this->userId = $userId;
}
public function handle()
{
// 实际发送逻辑
}
}
- 中间件数组只对当前任务生效,不影响其他 Job
- 如果任务没实现
ShouldQueue,中间件完全不触发 - 多个中间件按数组顺序执行,前一个不调用
$next($job),后续全部跳过
容易被忽略的三个硬限制
队列中间件能力有限,有些事它做不了,硬要搞反而埋坑:
- 不能访问
$request或$response—— 队列任务没有 HTTP 上下文 - 不能用
redirect()、view()、auth()->user()这类依赖请求生命周期的辅助函数 - 不支持
terminate()方法——队列中间件没有响应后钩子,想记日志得用Log::info()同步写,别指望异步收尾


















