將大文件按固定塊大小(如 10MB)切分為多個臨時文件,上傳時逐塊傳輸,中斷後可從失敗塊繼續;合併時按序讀取臨時文件追加寫入目標文件。
<?php
class FileChunker {
private $chunkSize;
private $tempDir;
private $prefix;
public function __construct(int $chunkSize = 10 * 1024 * 1024, string $tempDir = '/tmp', string $prefix = 'chunk_') {
$this->chunkSize = $chunkSize;
$this->tempDir = rtrim($tempDir, '/');
$this->prefix = $prefix;
}
/**
* 分割文件
* @return array ['total' => 總塊數, 'files' => 臨時文件路徑數組]
*/
public function split(string $sourcePath): array {
if (!is_file($sourcePath)) throw new InvalidArgumentException('Source file not exists');
$handle = fopen($sourcePath, 'rb');
$files = [];
$index = 0;
while (!feof($handle)) {
$chunk = fread($handle, $this->chunkSize);
if ($chunk === '') break;
$index++;
$chunkFile = "{$this->tempDir}/{$this->prefix}{$index}.part";
file_put_contents($chunkFile, $chunk);
$files[] = $chunkFile;
}
fclose($handle);
return ['total' => $index, 'files' => $files];
}
/**
* 合併文件
* @param array $chunkFiles 按序排列的臨時文件路徑
*/
public function merge(array $chunkFiles, string $targetPath): bool {
$target = fopen($targetPath, 'wb');
foreach ($chunkFiles as $file) {
if (!is_file($file)) return false;
$src = fopen($file, 'rb');
stream_copy_to_stream($src, $target);
fclose($src);
}
fclose($target);
return true;
}
/**
* 清理臨時文件
*/
public function cleanup(array $chunkFiles): void {
foreach ($chunkFiles as $file) {
@unlink($file);
}
}
}
// 使用示例
$chunker = new FileChunker(10 * 1024 * 1024, sys_get_temp_dir(), 'upload_');
// 1. 分割(服務端預處理或客戶端分片上傳)
$result = $chunker->split('/path/to/large_file.zip');
echo "分割完成,共 {$result['total']} 片\n";
// 2. 模擬上傳後合併
$success = $chunker->merge($result['files'], '/path/to/restored.zip');
echo $success ? '合併成功' : '合併失敗';
// 3. 清理
$chunker->cleanup($result['files'])
blob.slice(start, end) 切片<?php
// 1. 接收分片
$chunkIndex = (int)($_POST['chunk'] ?? 0);
$totalChunks = (int)($_POST['chunks'] ?? 0);
$fileHash = $_POST['file_hash'] ?? ''; // 唯一標識
$tempDir = sys_get_temp_dir() . "/uploads/{$fileHash}";
@mkdir($tempDir, 0755, true);
$chunkFile = "{$tempDir}/{$chunkIndex}.part";
move_uploaded_file($_FILES['file']['tmp_name'], $chunkFile);
// 2. 檢查是否全部到齊
$uploaded = glob("{$tempDir}/*.part");
if (count($uploaded) === $totalChunks) {
// 3. 合併(按文件名排序保證順序)
sort($uploaded, SORT_NATURAL);
$targetPath = "/data/uploads/{$fileHash}.zip";
$fp = fopen($targetPath, 'wb');
foreach ($uploaded as $part) {
$fh = fopen($part, 'rb');
stream_copy_to_stream($fh, $fp);
fclose($fh);
}
fclose($fp);
// 4. 校驗完整性(可選)
if (hash_file('sha256', $targetPath) === $fileHash) {
// 5. 清理分片
array_map('unlink', $uploaded);
rmdir($tempDir);
echo json_encode(['status' => 'complete', 'path' => $targetPath]);
} else {
echo json_encode(['status' => 'checksum_failed']);
}
} else {
echo json_encode(['status' => 'chunk_received', 'received' => count($uploaded)]);
}
| 優化 | 說明 |
|---|---|
| 流式複製 | stream_copy_to_stream 避免將大塊內容載入內存 |
| 分片校驗 | 上傳前計算 SparkMD5/Hash,支持秒傳、斷點校驗 |
| 並發控制 | Nginx limit_req_zone + PHP 信號量防止磁盤 IO 暴衝 |
| 斷點恢復 | 前端記錄已上傳索引,續傳時跳過 |
| 臨時文件清理 | 定時任務清理 24h 以上未完成上傳目錄 |
| 大文件直傳雲存儲 | 阿里云 OSS/騰訊云 COS 分片上傳 API,減輕源站壓力 |