本文目录导读:

在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(更实时)
使用 Ratchet 或 Swoole 实现:
// 需要安装 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'); // 通知后端取消
}
最佳实践建议
-
选择合适的方案:
- 简单项目:Session + 轮询
- 高并发:文件或数据库存储
- 实时性要求高:WebSocket
-
性能优化:
- 设置合理的轮询间隔(200-1000ms)
- 使用缓存减少数据库查询
- 考虑使用Redis存储进度
-
用户体验:
- 显示预计剩余时间
- 支持取消操作
- 添加动画过渡效果
- 显示任务详细信息
-
安全性:
- 验证任务所有权
- 设置超时机制
- 防止重复请求
选择哪种实现方式取决于你的具体需求、服务器环境以及项目复杂度,对于大多数简单的PHP项目,Session + 轮询 是最简单直接的方案。