核心解决方案是采用分片上传结合断点续传技术,1. 客户端利用file api将大文件切片并生成唯一标识(如md5);2. 每个分片携带文件标识、索引等信息上传至服务端;3. 服务端php接收分片并存储于以文件哈希命名的临时目录中;4. 使用数据库或redis持久化记录各分片上传状态;5. 上传前客户端查询已上传分片列表实现断点续传;6. 所有分片上传完成后服务端按序合并文件并清理临时数据;7. 合并时采用流式写入避免内存溢出,最终返回完整文件路径,整个过程有效规避了php上传限制并提升了稳定性和用户体验。
处理PHP大文件上传,尤其是面对动辄几百兆甚至几个G的文件时,传统的上传方式确实会遇到瓶颈。核心的解决方案,或者说我个人认为最靠谱的路径,就是采用分片上传(Chunked Upload)结合断点续传(Resumable Upload)技术。这不仅仅是技术上的选择,更是对用户体验和系统稳定性的一个根本性提升。它将一个庞大的文件拆解成若干个小块,逐一上传,并记录上传进度,即使网络中断或浏览器关闭,也能在下次连接时从上次中断的地方继续。
要实现PHP的大文件分片上传与断点续传,这通常是一个客户端(浏览器/JS)与服务端(PHP)协作的工程。
客户端的职责:
立即学习“PHP免费学习笔记(深入)”;
File
slice()
服务端的职责(PHP):
upload_max_filesize
post_max_size
传统的PHP文件上传,说白了,就是浏览器一次性把整个文件通过HTTP POST请求发给服务器,然后PHP的
move_uploaded_file()
首先,最直观的就是PHP配置的限制。
php.ini
upload_max_filesize
post_max_size
max_execution_time
2M
8M
2G
memory_limit
max_execution_time
其次,网络环境的不稳定性是另一个大敌。我们的网络连接不是百分之百可靠的,尤其是在移动设备上或者跨国传输时。一个几百兆的文件,在上传过程中如果遇到网络波动、连接中断,那么整个上传过程就得从头再来。这对于用户来说,无疑是极度糟糕的体验,耗时耗力,而且挫败感十足。用户可能已经等了半小时,结果功亏一篑,谁能接受呢?
再者,用户体验的缺失。传统上传方式通常没有内置的进度条。用户上传一个大文件时,界面可能长时间没有响应,他们不知道上传进行到哪一步了,有没有卡住,甚至是否还在上传。这种“黑箱”操作很容易让用户感到焦虑和不确定。
所以,与其说是PHP本身处理不了大文件,不如说是在HTTP协议和PHP传统处理模式下,单次传输的“全有或全无”策略,在大文件面前显得力不从心。
分片上传的核心思想,就像把一头大象装进冰箱,得先切片,再分批装进去。
在客户端,这通常依赖于JavaScript和HTML5的
File
File.prototype.slice(start, end)
FormData
XMLHttpRequest
fetch
chunkIndex
totalChunks
在服务器端(PHP),这块的逻辑相对复杂一些,但思路清晰:
$_FILES['your_chunk_field']['tmp_name']
/tmp/uploads/md5_of_file/
move_uploaded_file()
0.chunk
1.chunk
file_put_contents($finalFilePath, file_get_contents($chunkFilePath), FILE_APPEND)
fopen()
'ab'
这里需要注意的是,合并时如果文件非常大,直接
file_get_contents
fread
fwrite
// 简化版PHP分片接收和记录逻辑 // 实际应用中需要更严谨的错误处理、安全校验和并发控制 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['chunk'])) { $fileHash = $_POST['file_hash'] ?? ''; $chunkIndex = (int)($_POST['chunk_index'] ?? 0); $totalChunks = (int)($_POST['total_chunks'] ?? 1); $fileName = $_POST['file_name'] ?? 'uploaded_file'; // 最终文件名 if (empty($fileHash) || !isset($_FILES['chunk'])) { http_response_code(400); echo json_encode(['code' => 400, 'message' => '参数缺失或文件未上传']); exit; } $uploadDir = 'uploads/temp/'; // 临时上传根目录 $tempFileDir = $uploadDir . $fileHash . '/'; // 特定文件的临时目录 if (!is_dir($tempFileDir)) { if (!mkdir($tempFileDir, 0777, true)) { http_response_code(500); echo json_encode(['code' => 500, 'message' => '无法创建临时目录']); exit; } } $chunkFilePath = $tempFileDir . $chunkIndex . '.chunk'; // 移动上传的分片到临时目录 if (move_uploaded_file($_FILES['chunk']['tmp_name'], $chunkFilePath)) { // 记录分片状态到数据库或Redis // 假设这里有一个函数来更新分片状态 // updateChunkStatus($fileHash, $chunkIndex, $totalChunks, $fileName); // 检查是否所有分片都已上传 $uploadedCount = count(glob($tempFileDir . '*.chunk')); if ($uploadedCount == $totalChunks) { // 所有分片已上传,开始合并 $finalDestination = 'uploads/final/' . $fileName; // 最终文件存储路径 $outputFile = fopen($finalDestination, 'ab'); // 追加模式打开或创建 if ($outputFile === false) { http_response_code(500); echo json_encode(['code' => 500, 'message' => '无法创建最终文件']); exit; } for ($i = 0; $i < $totalChunks; $i++) { $currentChunkPath = $tempFileDir . $i . '.chunk'; if (file_exists($currentChunkPath)) { $chunkContent = file_get_contents($currentChunkPath); fwrite($outputFile, $chunkContent); unlink($currentChunkPath); // 合并后删除分片 } else { // 理论上不应该发生,除非有分片丢失 fclose($outputFile); http_response_code(500); echo json_encode(['code' => 500, 'message' => '分片缺失,合并失败']); exit; } } fclose($outputFile); rmdir($tempFileDir); // 删除临时文件目录 // 更新数据库中文件状态为已完成 // markFileAsComplete($fileHash, $finalDestination); echo json_encode(['code' => 200, 'message' => '文件上传并合并成功', 'file_path' => $finalDestination]); } else { echo json_encode(['code' => 200, 'message' => '分片上传成功', 'uploaded_count' => $uploadedCount]); } } else { http_response_code(500); echo json_encode(['code' => 500, 'message' => '分片移动失败']); } } else { // 处理客户端查询已上传分片的请求 if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['file_hash'])) { $fileHash = $_GET['file_hash']; $tempFileDir = 'uploads/temp/' . $fileHash . '/'; $uploadedChunks = []; if (is_dir($tempFileDir)) { $files = glob($tempFileDir . '*.chunk'); foreach ($files as $file) { $chunkIndex = (int)basename($file, '.chunk'); $uploadedChunks[] = $chunkIndex; } } echo json_encode(['code' => 200, 'uploaded_chunks' => $uploadedChunks]); exit; } http_response_code(405); echo json_encode(['code' => 405, 'message' => '不支持的请求方法或缺少参数']); }
断点续传,顾名思义,就是从上次中断的地方继续。它的实现,是建立在分片上传的基础之上的,关键在于状态的持久化和查询。
设想一下,你正在上传一个大文件,突然网络断了。当你重新连接或者刷新页面时,你肯定不希望从头再来。
实现断点续传的步骤是这样的:
客户端查询已上传状态:
服务器响应已上传状态:
客户端根据状态调整上传策略:
服务器持续更新状态:
关键点在于:
通过这种机制,无论上传过程被中断多少次,只要文件标识不变,客户端都能向服务器查询当前进度,并从上次中断的地方无缝续传,极大地提升了用户体验和上传的成功率。
以上就是PHP如何处理大文件上传 PHP分片上传与断点续传技术的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号