Files
laralog/app/Console/Commands/WorkermanServe.php
T

107 lines
3.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Console\Commands;
use App\Blog\Services\LlmClient;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
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 常驻服务:队列消费者(LLM 异步任务)+ WebSocket 进度推送';
public function handle(): int
{
Worker::$pidFile = storage_path('framework/workerman.pid');
$this->info('Workerman 启动中('.config('workerman.name').'...');
// 队列消费者:常驻进程内复用 Laravel 容器,避免每次任务重新启动框架
$queueWorker = new Worker();
$queueWorker->name = 'laralog-queue';
$queueWorker->count = (int) config('workerman.queue_workers', 2);
$queueWorker->onWorkerStart = function ($worker) {
$this->info("队列消费者 {$worker->id} 启动");
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;
}
private function consumeNextJob(): void
{
$queue = config('workerman.queue_connection', 'database');
if ($queue === 'database') {
$job = DB::table('jobs')
->whereNull('reserved_at')
->orderBy('id')
->lockForUpdate()
->first();
if (! $job) {
return;
}
DB::table('jobs')->where('id', $job->id)->update([
'reserved_at' => now()->getTimestamp(),
'attempts' => $job->attempts + 1,
]);
$payload = json_decode($job->payload, true);
try {
$this->runJob($payload);
DB::table('jobs')->where('id', $job->id)->delete();
} catch (\Throwable $e) {
Log::error('任务失败', ['job' => $job->id, 'error' => $e->getMessage()]);
$attempts = $job->attempts + 1;
if ($attempts >= 3) {
DB::table('jobs')->where('id', $job->id)->update(['reserved_at' => null, 'attempts' => $attempts]);
} else {
DB::table('jobs')->where('id', $job->id)->delete();
}
}
return;
}
// Redis 队列:降级到 artisan queue:work
Artisan::call('queue:work', ['--once' => true, '--stop-when-empty' => true]);
}
private function runJob(array $payload): void
{
$command = unserialize($payload['data']['command'] ?? '');
if ($command instanceof \App\Blog\Jobs\AiJob) {
$command->handle(app(LlmClient::class));
}
}
}