PHP项目如何实现进度条效果?

wen PHP项目 2

本文目录导读:

PHP项目如何实现进度条效果?

  1. 基于Session + 轮询(最常用)
  2. 使用文件存储进度(适用于多用户)
  3. 使用数据库存储进度
  4. 使用WebSocket(更实时)
  5. 使用现代前端技术(Fetch + AbortController)
  6. 最佳实践建议

在PHP项目中实现进度条效果,通常需要结合前端轮询WebSocket技术,因为PHP本身是同步阻塞的,以下是几种常见实现方式:

基于Session + 轮询(最常用)

后端代码 (process.php)

<?php
session_start();
// 启动长时间任务
if (!isset($_GET['action'])) {
    // 模拟任务
    $_SESSION['progress'] = 0;
    $total = 100;
    for ($i = 1; $i <= $total; $i++) {
        // 模拟耗时操作
        usleep(50000); // 50ms
        $_SESSION['progress'] = round(($i / $total) * 100);
        // 如果需要停止
        if (isset($_SESSION['stop']) && $_SESSION['stop']) {
            $_SESSION['progress'] = -1;
            break;
        }
    }
    echo "完成";
}
// 获取进度
if (isset($_GET['action']) && $_GET['action'] == 'progress') {
    header('Content-Type: application/json');
    echo json_encode([
        'progress' => $_SESSION['progress'] ?? 0
    ]);
}
?>

前端代码 (HTML + JavaScript)

<!DOCTYPE html>
<html>
<head>PHP进度条示例</title>
    <style>
        .progress-bar {
            width: 100%;
            height: 30px;
            background-color: #f0f0f0;
            border-radius: 5px;
            margin: 20px 0;
        }
        .progress-fill {
            height: 100%;
            background-color: #4CAF50;
            border-radius: 5px;
            transition: width 0.3s ease;
            width: 0%;
            text-align: center;
            line-height: 30px;
            color: white;
        }
    </style>
</head>
<body>
    <button onclick="startTask()">开始任务</button>
    <div class="progress-bar">
        <div class="progress-fill" id="progressFill">0%</div>
    </div>
    <p id="status">等待开始...</p>
    <script>
        let progressInterval;
        function startTask() {
            document.getElementById('status').textContent = '任务进行中...';
            // 获取进度
            progressInterval = setInterval(function() {
                fetch('process.php?action=progress')
                    .then(response => response.json())
                    .then(data => {
                        const progress = data.progress;
                        if (progress >= 0 && progress <= 100) {
                            updateProgress(progress);
                        }
                        // 任务完成
                        if (progress >= 100) {
                            clearInterval(progressInterval);
                            document.getElementById('status').textContent = '任务完成!';
                        }
                        // 任务被取消
                        if (progress == -1) {
                            clearInterval(progressInterval);
                            document.getElementById('status').textContent = '任务已取消';
                        }
                    });
            }, 500); // 每500ms查询一次
            // 启动后台任务
            fetch('process.php');
        }
        function updateProgress(value) {
            const fill = document.getElementById('progressFill');
            fill.style.width = value + '%';
            fill.textContent = value + '%';
        }
    </script>
</body>
</html>

使用文件存储进度(适用于多用户)

后端代码

<?php
function getProgressFile($taskId) {
    return "/tmp/progress_" . md5($taskId) . ".txt";
}
// 创建任务
if (!isset($_GET['action'])) {
    $taskId = $_GET['task_id'] ?? uniqid();
    $progressFile = getProgressFile($taskId);
    // 模拟文件处理
    file_put_contents($progressFile, '0');
    $total = 100;
    for ($i = 1; $i <= $total; $i++) {
        usleep(50000);
        $progress = round(($i / $total) * 100);
        file_put_contents($progressFile, $progress);
    }
    file_put_contents($progressFile, '100');
    echo json_encode(['task_id' => $taskId]);
}
// 获取进度
if (isset($_GET['action']) && $_GET['action'] == 'progress') {
    $taskId = $_GET['task_id'] ?? '';
    $progressFile = getProgressFile($taskId);
    if (file_exists($progressFile)) {
        header('Content-Type: application/json');
        echo json_encode(['progress' => file_get_contents($progressFile)]);
    }
}
?>

使用数据库存储进度

<?php
// 数据库表结构
// CREATE TABLE task_progress (
//     id INT AUTO_INCREMENT PRIMARY KEY,
//     task_id VARCHAR(64) UNIQUE,
//     progress INT DEFAULT 0,
//     status VARCHAR(20) DEFAULT 'running',
//     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
// );
// 更新进度
function updateProgress($taskId, $progress, $status = 'running') {
    $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
    $stmt = $db->prepare("UPDATE task_progress SET progress = ?, status = ? WHERE task_id = ?");
    $stmt->execute([$progress, $status, $taskId]);
}
// 获取进度
function getProgress($taskId) {
    $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
    $stmt = $db->prepare("SELECT progress, status FROM task_progress WHERE task_id = ?");
    $stmt->execute([$taskId]);
    return $stmt->fetch(PDO::FETCH_ASSOC);
}
?>

使用WebSocket(更实时)

使用 RatchetSwoole 实现:

// 需要安装 Ratchet
// composer require cboden/ratchet
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ProgressWebSocket implements MessageComponentInterface {
    protected $clients;
    protected $progress = 0;
    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }
    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        echo "New connection! ({$conn->resourceId})\n";
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        // 客户端发送 "start" 开始任务
        if ($msg === 'start') {
            for ($i = 1; $i <= 100; $i++) {
                usleep(50000);
                $this->progress = $i;
                // 广播进度
                foreach ($this->clients as $client) {
                    $client->send($this->progress);
                }
            }
        }
    }
    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
    }
    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

使用现代前端技术(Fetch + AbortController)

// 支持取消任务的版本
async function startTaskWithCancel() {
    const controller = new AbortController();
    try {
        const response = await fetch('process.php', {
            signal: controller.signal
        });
        // 轮询进度
        const progressInterval = setInterval(async () => {
            const progressResponse = await fetch('progress.php');
            const data = await progressResponse.json();
            if (data.progress >= 100) {
                clearInterval(progressInterval);
            }
            updateProgress(data.progress);
        }, 200);
    } catch (err) {
        if (err.name === 'AbortError') {
            console.log('任务已取消');
        }
    }
}
// 取消任务
function cancelTask() {
    controller.abort();
    fetch('cancel.php'); // 通知后端取消
}

最佳实践建议

  1. 选择合适的方案

    • 简单项目:Session + 轮询
    • 高并发:文件或数据库存储
    • 实时性要求高:WebSocket
  2. 性能优化

    • 设置合理的轮询间隔(200-1000ms)
    • 使用缓存减少数据库查询
    • 考虑使用Redis存储进度
  3. 用户体验

    • 显示预计剩余时间
    • 支持取消操作
    • 添加动画过渡效果
    • 显示任务详细信息
  4. 安全性

    • 验证任务所有权
    • 设置超时机制
    • 防止重复请求

选择哪种实现方式取决于你的具体需求、服务器环境以及项目复杂度,对于大多数简单的PHP项目,Session + 轮询 是最简单直接的方案。

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