You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在网页中展示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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 04:06:20