88 lines
3.4 KiB
PHP
88 lines
3.4 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use Illuminate\Console\Command;
|
||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||
use Illuminate\Queue\Worker as QueueWorker;
|
||
use Illuminate\Queue\WorkerOptions;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Workerman\Connection\TcpConnection;
|
||
use Workerman\Timer;
|
||
use Workerman\Worker;
|
||
|
||
class WorkermanServe extends Command
|
||
{
|
||
protected $signature = 'workerman:serve {action=start : start/stop/restart/reload}';
|
||
|
||
protected $description = '启动 Workerman 常驻服务:队列消费者(database/redis)+ WebSocket 进度推送';
|
||
|
||
public function handle(): int
|
||
{
|
||
Worker::$pidFile = storage_path('framework/workerman.pid');
|
||
|
||
$this->info('Workerman 启动中('.config('workerman.name').')...');
|
||
|
||
// 队列消费者:复用 Laravel 队列 Worker(database / redis 统一支持),
|
||
// 常驻进程内框架只启动一次,省去每个任务的启动开销
|
||
$queueWorker = new Worker();
|
||
$queueWorker->name = 'laralog-queue';
|
||
$queueWorker->count = (int) config('workerman.queue_workers', 2);
|
||
$queueWorker->onWorkerStart = function ($worker) {
|
||
$this->info("队列消费者 {$worker->id} 启动(连接:".config('workerman.queue_connection').',队列:'.config('workerman.queues').')');
|
||
Timer::add(1, function () {
|
||
try {
|
||
$this->consumeNextJob();
|
||
} catch (\Throwable $e) {
|
||
Log::error('Workerman 任务执行失败', ['error' => $e->getMessage()]);
|
||
}
|
||
});
|
||
};
|
||
|
||
// WebSocket:向后台推送任务进度
|
||
$ws = new Worker('websocket://'.config('workerman.websocket_host', '0.0.0.0').':'.config('workerman.websocket_port', 8787));
|
||
$ws->name = 'laralog-ws';
|
||
$ws->onConnect = function (TcpConnection $connection) {
|
||
\App\Blog\Support\WorkermanBroadcaster::add($connection);
|
||
$connection->send(json_encode(['event' => 'connected']));
|
||
};
|
||
$ws->onClose = fn (TcpConnection $connection) => \App\Blog\Support\WorkermanBroadcaster::remove($connection);
|
||
|
||
Worker::runAll();
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
/**
|
||
* 消费一个任务。使用 Laravel 队列 Worker:
|
||
* - 支持 database / redis 等所有驱动(redis 的 reserved/重试/超时语义由驱动处理)
|
||
* - sleep=0 避免阻塞 Workerman event loop(由 1s Timer 驱动轮询)
|
||
* - 任务 handle() 的依赖(如 LlmClient)由容器自动注入
|
||
*/
|
||
private function consumeNextJob(): void
|
||
{
|
||
$connection = (string) config('workerman.queue_connection', config('queue.default'));
|
||
// Worker 内部按逗号分隔解析多队列(如 "default,ai")
|
||
$queues = (string) config('workerman.queues', 'default');
|
||
$queues = $queues === '' ? 'default' : $queues;
|
||
|
||
$worker = new QueueWorker(
|
||
app('queue'),
|
||
app('events'),
|
||
app(ExceptionHandler::class),
|
||
fn () => app()->isDownForMaintenance(),
|
||
);
|
||
|
||
$options = new WorkerOptions(
|
||
name: 'laralog',
|
||
backoff: 0,
|
||
memory: 128,
|
||
timeout: (int) config('workerman.timeout', 60),
|
||
sleep: 0,
|
||
maxTries: (int) config('workerman.max_tries', 3),
|
||
);
|
||
|
||
$worker->runNextJob($connection, $queues, $options);
|
||
}
|
||
}
|