怎样在PHP项目中实现进度追踪?

wen java案例 2

本文目录导读:

怎样在PHP项目中实现进度追踪?

  1. 方案一:基于文件或数据库 + 前端轮询(最常用、易实现)
  2. 方案二:Server-Sent Events (SSE) - 单向推送
  3. 方案三:WebSocket(最佳实时方案)
  4. 方案四:异步任务队列(生产环境推荐)
  5. 总结与推荐

在PHP项目中实现进度追踪,核心思路是让执行耗时任务的服务端请求进度的客户端(通常是浏览器)能够进行异步通信。

由于PHP默认是同步阻塞的,执行一个长时间任务时,请求会被挂起,无法同时返回进度,需要采用特定的架构和机制。

以下是几种主流且实用的实现方案,从简单到复杂排列:

基于文件或数据库 + 前端轮询(最常用、易实现)

这是最经典、兼容性最好的方案,将进度信息存储在共享存储中,前端定时请求查询。

工作流程:

  1. 发起任务:前端发送一个请求,后端接收后,创建一个唯一的任务ID(如UUID),并将任务状态(如 pending、进度0%)存入文件或数据库。
  2. 异步执行:后端立即返回任务ID,然后使用 fastcgi_finish_request()pcntl_fork/exec 等方式在后台启动实际的耗时任务。
  3. 更新进度:后台任务执行过程中,不断更新文件或数据库中该任务ID对应的进度(如 progress: 45%)。
  4. 前端轮询:前端拿到任务ID后,通过 setIntervalsetTimeout 定时(如每1-2秒)调用一个专门的“查询进度”API。
  5. 返回结果:“查询进度”API 从存储中读取该任务ID的进度并返回,当进度达到100%后,轮询结束,前端展示最终结果。

代码示例(核心逻辑):

<?php
// 1. 启动任务接口 (start_job.php)
$taskId = uniqid('task_', true);
file_put_contents("/tmp/progress_{$taskId}.txt", json_encode(['progress' => 0, 'status' => 'processing']));
// 立即返回任务ID给前端
echo json_encode(['task_id' => $taskId]);
flush(); // 确保输出立即发送
// 2. 在后台执行实际任务(使用 pcntl_fork 或 shell_exec 后台运行)
$command = "php background_task.php {$taskId} > /dev/null 2>&1 &";
exec($command);
exit; // 当前请求结束
<?php
// background_task.php
$taskId = $argv[1];
$totalSteps = 100;
for ($i = 1; $i <= $totalSteps; $i++) {
    // 模拟耗时操作
    sleep(1);
    // 更新进度文件
    $progressData = json_encode([
        'progress' => $i,
        'status' => $i === $totalSteps ? 'completed' : 'processing'
    ]);
    file_put_contents("/tmp/progress_{$taskId}.txt", $progressData);
}
echo "Job {$taskId} completed.\n";
<?php
// 3. 查询进度接口 (get_progress.php?task_id=xxx)
$taskId = $_GET['task_id'];
$progressFile = "/tmp/progress_{$taskId}.txt";
if (!file_exists($progressFile)) {
    echo json_encode(['progress' => 0, 'status' => 'not_found']);
    exit;
}
$data = json_decode(file_get_contents($progressFile), true);
header('Content-Type: application/json');
echo json_encode($data);

优点: 实现简单,不依赖外部服务。 缺点: 轮询产生额外请求,实时性一般;处理大量任务时文件I/O可能成为瓶颈。


Server-Sent Events (SSE) - 单向推送

SSE 允许服务端向客户端主动推送数据,适合进度追踪这种服务端主动更新的场景。

工作流程:

  1. 前端创建一个 EventSource 对象,连接到后端的SSE端点。
  2. 后端收到请求后,不立即关闭连接,而是设置响应头为 text/event-stream
  3. while 循环中,执行耗时任务,每次循环后 echo "data: " . json_encode($progress) . "\n\n";flush()
  4. 前端通过 EventSource.onmessage 监听并更新进度。

代码示例:

<?php
// sse_progress.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$total = 100;
for ($i = 1; $i <= $total; $i++) {
    // 模拟耗时操作
    sleep(1);
    $data = json_encode(['progress' => $i, 'total' => $total, 'message' => "Processing step $i"]);
    echo "data: {$data}\n\n";
    ob_flush();
    flush();
    if (connection_aborted()) {
        break; // 客户端断开连接时停止
    }
}
echo "event: complete\ndata: {}\n\n";
flush();

前端JavaScript:

const eventSource = new EventSource('/sse_progress.php');
eventSource.onmessage = function(event) {
    const data = JSON.parse(event.data);
    console.log('Progress:', data.progress);
    document.getElementById('progress-bar').style.width = data.progress + '%';
};
eventSource.addEventListener('complete', function() {
    eventSource.close();
    alert('Job completed!');
});
eventSource.onerror = function() {
    eventSource.close();
};

优点: 实时性最好,无需轮询,连接开销小。 缺点: 需要保持长连接,消耗服务器资源;不兼容老旧浏览器;无法通过CDN。


WebSocket(最佳实时方案)

适用于需要双向实时通信(如进度追踪 + 用户取消任务)的场景,可以使用 Ratchet、Swoole、Workerman 等库。

工作流程:

  1. 前端通过 WebSocket 连接到后端服务(通常独立运行,如端口8080)。
  2. 前端发送任务启动消息。
  3. 后端 WebSocket 服务端接收消息,创建任务并存储任务ID与连接ID的映射。
  4. 后台任务执行时,通过连接ID找到对应的 WebSocket 连接,推送进度消息。
  5. 前端接收消息并更新UI。

示例(使用 Workerman):

<?php
use Workerman\Worker;
use Workerman\Lib\Timer;
$worker = new Worker('websocket://0.0.0.0:8080');
$worker->count = 4;
// 存储任务ID到连接ID的映射
$taskMap = [];
$worker->onMessage = function ($connection, $data) {
    $data = json_decode($data, true);
    if ($data['action'] === 'start') {
        $taskId = uniqid();
        $taskMap[$taskId] = $connection->id;
        // 向客户端发送任务ID
        $connection->send(json_encode(['type' => 'task_id', 'task_id' => $taskId]));
        // 模拟后台任务(真实场景应使用异步任务队列)
        for ($i = 1; $i <= 100; $i++) {
            Timer::add($i, function () use ($connection, $taskId, $i) {
                $connection->send(json_encode(['type' => 'progress', 'task_id' => $taskId, 'progress' => $i]));
            });
        }
    }
};
Worker::runAll();

优点: 全双工,实时性最高,功能最强。 缺点: 实现复杂,需要额外服务和端口;运维成本较高。


异步任务队列(生产环境推荐)

将耗时任务交给专业的队列系统处理,这是最健壮、可扩展的方案,可以结合数据库或 Redis 存储进度。

工具选择:

  • 队列: Redis + php-resque / laravel-queue / beanstalkd
  • 进度存储: Redis(推荐,支持原子操作)、MySQL

工作流程:

  1. 任务入队:前端请求将任务推入队列,并立即获得任务ID和初始状态(如 pending)。
  2. Worker 异步处理:后台 Worker 进程(独立于Web服务器)从队列中取出任务并执行。
  3. 更新进度:Worker 在每处理一部分后,更新 Redis 中该任务ID的进度值(SET task:{id}:progress 45)。
  4. 前端轮询:前端通过另一个接口(如 /api/progress?task_id=xx)查询 Redis 中的进度。

示例(使用 Laravel 队列 + Redis):

// 1. 分发任务
$task = new ProcessDataJob($data);
$taskId = $task->job->getJobId(); // Laravel 自动生成Job ID
dispatch($task);
// 2. 在 Job 的 handle 方法中更新进度
public function handle()
{
    $redis = Redis::connection();
    $total = 1000;
    foreach ($items as $index => $item) {
        // 处理数据...
        $progress = ($index + 1) / $total * 100;
        $redis->set('task:' . $this->job->getJobId() . ':progress', $progress);
        $redis->expire('task:' . $this->job->getJobId() . ':progress', 3600);
    }
    $redis->set('task:' . $this->job->getJobId() . ':progress', 100);
    $redis->set('task:' . $this->job->getJobId() . ':result', json_encode($result));
}
// 3. 查询进度 API
public function getProgress($taskId)
{
    $progress = Redis::get('task:' . $taskId . ':progress');
    $result = Redis::get('task:' . $taskId . ':result');
    return response()->json([
        'progress' => $progress ?? 0,
        'result' => $result ? json_decode($result, true) : null,
        'status' => $progress == 100 ? 'completed' : 'processing'
    ]);
}

优点: 生产环境最稳定,可扩展性强,任务失败可重试。 缺点: 需要额外搭建队列服务(Redis、RabbitMQ等),架构相对复杂。


总结与推荐

方案 实时性 实现难度 服务器资源消耗 生产环境可靠度 推荐场景
文件/数据库 + 前端轮询 低(秒级) ★☆☆☆☆ 简单内部工具,演示项目
SSE 高(毫秒级) ★★☆☆☆ 中(长连接) 简单的实时进度展示,单项目
WebSocket 最高 ★★★★☆ 中(长连接) 需要双向通信,如聊天+进度
异步任务队列(推荐) 中(秒级) ★★★☆☆ 中(独立Worker) 最高 生产环境首选,企业级应用

对于大多数中型以上的PHP项目,最推荐的组合是:

  • Laravel 队列 / Symfony Messenger + Redis
  • 配合前端轮询(最简单)或 SSE(如果你追求更好的实时体验)。

最终建议:

  1. 如果是小项目或学习: 使用方案一(文件 + 轮询)
  2. 如果是正式生产项目: 使用方案四(异步队列 + Redis),这是最符合工程化、最容易维护和扩展的方式。
  3. 如果需要非常高的实时性(如实时监控大盘): 考虑方案二(SSE),实现成本低于WebSocket。

抱歉,评论功能暂时关闭!