本文目录导读:

在PHP项目中实现断点续传恢复任务,主要涉及文件上传断点续传和任务(如大文件处理/导入)断点续传两种情况,以下是针对这两种场景的详细实现方案:
文件上传断点续传
前端处理
使用File.slice()分片上传,记录已上传分片:
// 前端分片上传示例
const CHUNK_SIZE = 1024 * 1024; // 1MB
function uploadFile(file) {
let start = 0;
const fileId = generateFileId(file); // 唯一标识
// 检查已上传进度
fetch(`/check-progress?file_id=${fileId}`)
.then(res => res.json())
.then(data => {
start = data.uploadedSize || 0;
uploadChunks(file, start, fileId);
});
}
function uploadChunks(file, start, fileId) {
const chunk = file.slice(start, start + CHUNK_SIZE);
const formData = new FormData();
formData.append('file', chunk);
formData.append('file_id', fileId);
formData.append('chunk_index', Math.floor(start / CHUNK_SIZE));
fetch('/upload-chunk', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.complete) {
mergeChunks(fileId);
} else {
uploadChunks(file, start + CHUNK_SIZE, fileId);
}
});
}
function mergeChunks(fileId) {
fetch('/merge-chunks', {
method: 'POST',
body: JSON.stringify({ file_id: fileId })
});
}
后端PHP实现
检查进度接口
// /check-progress
public function checkProgress() {
$fileId = $_GET['file_id'];
$uploadDir = '/tmp/chunks/' . $fileId;
$uploadedSize = 0;
if (is_dir($uploadDir)) {
$files = scandir($uploadDir);
foreach ($files as $file) {
if ($file !== '.' && $file !== '..') {
$uploadedSize += filesize($uploadDir . '/' . $file);
}
}
}
return json_encode([
'uploadedSize' => $uploadedSize,
'fileId' => $fileId
]);
}
上传分片接口
// /upload-chunk
public function uploadChunk() {
$fileId = $_POST['file_id'];
$chunkIndex = $_POST['chunk_index'];
$uploadDir = '/tmp/chunks/' . $fileId;
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$chunkFile = $uploadDir . '/' . $chunkIndex;
move_uploaded_file($_FILES['file']['tmp_name'], $chunkFile);
return json_encode([
'success' => true,
'complete' => $this->isUploadComplete($fileId)
]);
}
private function isUploadComplete($fileId) {
// 根据业务逻辑判断是否所有分片已上传
// 例如检查分片数量和总大小
}
合并分片接口
// /merge-chunks
public function mergeChunks() {
$data = json_decode(file_get_contents('php://input'), true);
$fileId = $data['file_id'];
$chunkDir = '/tmp/chunks/' . $fileId;
$finalPath = '/uploads/' . $fileId . '_final.zip';
$chunks = scandir($chunkDir);
sort($chunks); // 按顺序合并
$finalFile = fopen($finalPath, 'wb');
foreach ($chunks as $chunk) {
if ($chunk === '.' || $chunk === '..') continue;
$chunkContent = file_get_contents($chunkDir . '/' . $chunk);
fwrite($finalFile, $chunkContent);
}
fclose($finalFile);
// 清理临时文件
array_map('unlink', glob($chunkDir . '/*'));
rmdir($chunkDir);
return json_encode(['success' => true]);
}
任务断点续传(如大文件处理/数据导入)
任务状态记录方案
使用数据库或文件记录任务进度:
CREATE TABLE task_progress (
task_id VARCHAR(64) PRIMARY KEY,
file_id VARCHAR(64),
total_rows INT DEFAULT 0,
processed_rows INT DEFAULT 0,
current_offset BIGINT DEFAULT 0,
status ENUM('pending','processing','paused','completed','failed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
任务执行引擎
class ResumeableTask {
private $taskId;
private $filePath;
private $chunkSize = 1000; // 每次处理1000行
public function __construct($taskId, $filePath) {
$this->taskId = $taskId;
$this->filePath = $filePath;
}
public function process() {
// 获取当前进度
$progress = $this->getProgress();
// 打开文件并跳过已处理部分
$handle = fopen($this->filePath, 'r');
fseek($handle, $progress['current_offset']);
$rowCount = 0;
while (($line = fgets($handle)) !== false) {
// 处理每一行
$this->processLine($line);
$rowCount++;
$progress['processed_rows']++;
// 每处理chunkSize行更新一次进度
if ($rowCount % $this->chunkSize === 0) {
$progress['current_offset'] = ftell($handle);
$this->updateProgress($progress);
// 检查是否暂停
if ($this->shouldPause()) {
break;
}
}
}
fclose($handle);
// 任务完成
$this->markCompleted($progress);
}
// 断点恢复
public function resume() {
$progress = $this->getProgress();
if ($progress['status'] === 'paused' || $progress['status'] === 'failed') {
$this->process();
return true;
}
return false;
}
private function getProgress() {
// 从数据库或文件读取进度
return DB::table('task_progress')
->where('task_id', $this->taskId)
->first();
}
private function updateProgress($progress) {
DB::table('task_progress')
->where('task_id', $this->taskId)
->update($progress);
}
private function shouldPause() {
// 检查外部信号(如文件锁、Redis标志等)
return file_exists("/tmp/pause_{$this->taskId}.flag");
}
}
暂停/恢复API
// 暂停任务
public function pauseTask($taskId) {
// 创建暂停标志
file_put_contents("/tmp/pause_{$taskId}.flag", 'pause');
return json_encode(['success' => true]);
}
// 恢复任务
public function resumeTask($taskId) {
// 删除暂停标志
@unlink("/tmp/pause_{$taskId}.flag");
// 启动恢复处理(可通过后台进程或队列)
$task = new ResumeableTask($taskId, '/path/to/file.csv');
$task->resume();
return json_encode(['success' => true]);
}
// 前端轮询进度
public function getTaskProgress($taskId) {
$progress = DB::table('task_progress')
->where('task_id', $taskId)
->first();
return json_encode([
'total' => $progress->total_rows,
'processed' => $progress->processed_rows,
'status' => $progress->status
]);
}
核心优化策略
原子性操作
使用数据库事务或文件锁确保进度更新安全:
DB::transaction(function() use ($progress) {
DB::table('task_progress')
->where('task_id', $taskId)
->update($progress);
});
超时与重试机制
// 设置超时
set_time_limit(0);
// 重试逻辑
$retryTimes = 3;
while ($retryTimes > 0) {
try {
// 处理逻辑
break;
} catch (Exception $e) {
$retryTimes--;
if ($retryTimes === 0) throw $e;
sleep(1); // 等待后重试
}
}
数据校验
在合并分片或恢复任务前,执行完整性校验:
public function verifyChunks($fileId) {
// 检查所有分片是否存在
$expectedChunks = ceil($this->totalSize / CHUNK_SIZE);
// 使用MD5校验每个分片
}
并发控制
对于多线程/多进程场景,使用Redis锁:
$lockKey = "task_lock:{$taskId}";
if ($redis->setnx($lockKey, 1)) {
$redis->expire($lockKey, 60);
// 执行任务
$redis->del($lockKey);
}
最佳实践总结
| 场景 | 关键点 | 实现方式 |
|---|---|---|
| 文件上传断点续传 | 分片、进度追踪、合并 | 前端File.slice() + 后端按序合并 |
| 任务处理断点续传 | 状态持久化、精确偏移量 | 数据库记录 + fseek() 跳转 |
| 大规模数据处理 | 批处理、周期性提交 | 每处理N条记录更新一次进度 |
| 可靠性保证 | 原子操作、校验和、重试 | 事务、MD5哈希、指数退避重试 |
通过以上方案,可以在PHP项目中实现稳定可靠的断点续传恢复机制,支持从各种中断(网络故障、服务器重启、用户暂停等)中无缝恢复任务。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播