在長時間運行的 PHP 任務中(如批量數據導入、文件處理、報表生成),實時顯示執行進度百分比能大幅提升用戶體驗。本文介紹基於 Session 的簡單進度追蹤實現。
<?php
session_start();
// 設置任務總數(實際項目中可從數據庫或隊列獲取)
$zs = 21;
// 初始化 session 進度計數器
if (!isset($_SESSION['num'])) {
$_SESSION['num'] = $zs;
}
// 獲取並遞減進度值
$jd = $_SESSION['num']--;
// 輸出當前進度數值
echo $jd;
echo "<br />";
// 計算並顯示完成百分比
$percentage = 100 - round($jd / $zs * 100);
echo $percentage . "%";
?>
session_start() 必須在任何輸出前調用$zs 代表任務總步數$_SESSION['num'] 存儲剩餘步數,每次請求遞減(總數 - 剩餘) / 總數 * 100,使用 round() 四捨五入<?php
// task_processor.php
session_start();
class TaskProgress {
private $taskId;
private $totalSteps;
private $currentStep = 0;
public function __construct(string $taskId, int $totalSteps) {
$this->taskId = $taskId;
$this->totalSteps = $totalSteps;
$_SESSION[$this->taskId] = [
'total' => $totalSteps,
'current' => 0,
'status' => 'running',
'start_time' => time()
];
}
public function increment(int $step = 1): void {
$this->currentStep += $step;
$_SESSION[$this->taskId]['current'] = $this->currentStep;
if ($this->currentStep >= $this->totalSteps) {
$_SESSION[$this->taskId]['status'] = 'completed';
$_SESSION[$this->taskId]['end_time'] = time();
}
}
public function getProgress(): array {
return $_SESSION[$this->taskId] ?? [];
}
public function isCompleted(): bool {
return ($_SESSION[$this->taskId]['status'] ?? '') === 'completed';
}
}
// 模擬長任務
$task = new TaskProgress('import_users', 1000);
for ($i = 0; $i < 1000; $i++) {
// 執行實際業務邏輯...
usleep(10000); // 模擬耗時操作
$task->increment();
}
?>
<!DOCTYPE html>
<html>
<head>
<title>任務進度監控</title>
<style>
.progress-bar { width: 100%; height: 30px; background: #f0f0f0; border-radius: 15px; overflow: hidden; }
.progress-fill { height: 100%; background: linear-gradient(90deg, #4caf50, #8bc34a); transition: width 0.3s; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; }
</style>
</head>
<body>
<div>任務進度:</div>
<div class="progress-bar">
<div id="progress-fill" class="progress-fill" style="width: 0%">0%</div>
</div>
<div id="status">準備中...</div>
<script>
function pollProgress() {
fetch('get_progress.php?task=import_users')
.then(res => res.json())
.then(data => {
const pct = data.total > 0 ? Math.round(data.current / data.total * 100) : 0;
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('progress-fill').textContent = pct + '%';
document.getElementById('status').textContent = `${data.current} / ${data.total} (${data.status})`;
if (data.status !== 'completed') {
setTimeout(pollProgress, 500);
} else {
document.getElementById('status').textContent = '任務完成!耗時 ' + (data.end_time - data.start_time) + ' 秒';
}
});
}
pollProgress();
</script>
</body>
</html>
<?php
// get_progress.php
session_start();
header('Content-Type: application/json');
$taskId = $_GET['task'] ?? '';
if (empty($taskId) || !isset($_SESSION[$taskId])) {
echo json_encode(['error' => '任務不存在']);
exit;
}
echo json_encode($_SESSION[$taskId]);
?>
<?php
// Redis 版本進度存儲
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
function setProgress($taskId, $current, $total, $status = 'running') {
global $redis;
$key = "task:progress:$taskId";
$redis->hMSet($key, [
'current' => $current,
'total' => $total,
'status' => $status,
'updated' => time()
]);
$redis->expire($key, 3600); // 1小時過期
}
function getProgress($taskId) {
global $redis;
$key = "task:progress:$taskId";
return $redis->hGetAll($key);
}
?>
替代輪詢,服務端主動推送進度更新,減少 HTTP 開銷:
<?php
// sse_progress.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$taskId = $_GET['task'] ?? '';
$lastCurrent = 0;
while (true) {
$progress = getProgress($taskId); // 使用 Redis 或 Session
if ($progress && $progress['current'] != $lastCurrent) {
$pct = round($progress['current'] / $progress['total'] * 100);
echo "data: {$pct}%\n\n";
@ob_flush(); @flush();
$lastCurrent = $progress['current'];
}
if (($progress['status'] ?? '') === 'completed') break;
sleep(1);
}
?>
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 進度條卡在 0% | Session 文件鎖阻塞 | 使用 session_write_close() 或改用 Redis |
| 多標籤頁干擾 | 共用同一 Session | 生成唯一 Task ID,URL 參數傳遞 |
| 內存洩漏 | 舊任務數據未清理 | 設置 TTL 過期、定時清理任務 |
| 進度倒退 | 並發請求競態 | 使用原子操作(Redis INCR)或鎖機制 |
基於 Session 的進度顯示適合簡單單機場景,生產環境建議採用 Redis + SSE/WebSocket 方案,配合唯一任務 ID 實現高併發、分佈式、實時的進度監控體驗。