如何在网页中展示Artisan命令执行进度并自动刷新?
在网页中实现Artisan长任务的进度条展示
核心思路
Artisan长任务不能直接在Web请求中同步执行(会导致请求超时),所以核心方案是用队列异步处理任务,通过Redis或数据库存储任务进度,前端通过轮询实时获取进度更新,任务完成后自动刷新页面。
步骤1:编写带进度追踪的Artisan命令
以Redis存储进度为例,实现一个能实时更新进度的长任务命令:
<?php namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\Redis; class LongRunningTask extends Command { protected $signature = 'task:long-run {taskId}'; protected $description = '执行带进度追踪的长时任务'; public function handle() { $taskId = $this->argument('taskId'); $totalSteps = 100; // 根据实际业务调整总步骤数 // 初始化任务状态 Redis::set("task:{$taskId}:progress", 0); Redis::set("task:{$taskId}:status", 'running'); try { for ($step = 1; $step <= $totalSteps; $step++) { // 替换为你的实际业务逻辑 sleep(0.1); // 更新进度 $progress = round($step / $totalSteps * 100, 2); Redis::set("task:{$taskId}:progress", $progress); } // 标记任务完成 Redis::set("task:{$taskId}:progress", 100); Redis::set("task:{$taskId}:status", 'completed'); } catch (\Exception $e) { Redis::set("task:{$taskId}:status", 'failed'); throw $e; } finally { // 设置缓存过期,避免内存占用 Redis::expire("task:{$taskId}:progress", 3600); Redis::expire("task:{$taskId}:status", 3600); } } }
步骤2:创建API接口
需要两个接口:启动任务、查询进度。
启动任务接口
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Queue; use Ramsey\Uuid\Uuid; class TaskController extends Controller { public function start() { $taskId = Uuid::uuid4()->toString(); // 将Artisan命令加入队列 Queue::push(function () use ($taskId) { $this->call('task:long-run', ['taskId' => $taskId]); }); return response()->json([ 'task_id' => $taskId, 'message' => '任务已启动' ]); } }
查询进度接口
public function progress(Request $request) { $taskId = $request->query('task_id'); $progress = Redis::get("task:{$taskId}:progress") ?? 0; $status = Redis::get("task:{$taskId}:status") ?? 'pending'; return response()->json([ 'progress' => (float)$progress, 'status' => $status ]); }
在routes/api.php注册路由:
Route::post('/task/start', [TaskController::class, 'start']); Route::get('/task/progress', [TaskController::class, 'progress']);
步骤3:前端实现进度条交互
用原生JS搭配Bootstrap进度条示例,实现点击按钮启动任务、实时更新进度、完成后刷新页面:
<!DOCTYPE html> <html> <head> <title>长任务进度展示</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <meta name="csrf-token" content="{{ csrf_token() }}"> </head> <body> <div class="container mt-5"> <button id="startTaskBtn" class="btn btn-primary mb-3">启动长任务</button> <div class="progress" style="height: 30px;"> <div id="progressBar" class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div> </div> </div> <script> const startBtn = document.getElementById('startTaskBtn'); const progressBar = document.getElementById('progressBar'); let pollTimer = null; startBtn.addEventListener('click', async () => { startBtn.disabled = true; startBtn.textContent = '任务执行中...'; // 发起启动任务请求 const startRes = await fetch('/api/task/start', { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, 'Content-Type': 'application/json' } }); const { task_id } = await startRes.json(); // 轮询进度 pollTimer = setInterval(async () => { const progressRes = await fetch(`/api/task/progress?task_id=${task_id}`); const { progress, status } = await progressRes.json(); // 更新进度条 const percent = Math.round(progress); progressBar.style.width = `${percent}%`; progressBar.textContent = `${percent}%`; progressBar.setAttribute('aria-valuenow', percent); // 处理任务状态 if (status === 'completed' || percent >= 100) { clearInterval(pollTimer); window.location.reload(); // 任务完成自动刷新 } else if (status === 'failed') { clearInterval(pollTimer); alert('任务执行失败!'); startBtn.disabled = false; startBtn.textContent = '启动长任务'; } }, 500); // 每500ms查询一次进度,可按需调整 }); </script> </body> </html>
关键注意事项
- 队列配置:确保Laravel队列驱动已配置(推荐Redis),并启动队列监听:
php artisan queue:work - 进度存储替代:如果不用Redis,可创建数据库表存储任务ID、进度、状态,适合小流量场景
- 轮询频率:避免过于频繁请求,500ms-1s的间隔较为合理
- 异常处理:前端需处理请求超时、任务失败等情况,避免无限等待
内容的提问来源于stack exchange,提问作者wilton
相关产品推荐
相关产品推荐

