本文目录导读:

在PHP中处理大文件上传,核心思路是突破默认限制 + 分片上传 + 进度反馈 + 断点续传。
下面是针对不同场景和需求的完整解决方案,从基础配置到高级架构。
修改 PHP 配置(基础但必要)
这是处理大文件的第一步,修改 php.ini 或 .htaccess / nginx.conf。
; php.ini 配置 upload_max_filesize = 2G ; 允许上传的最大文件大小 post_max_size = 2.5G ; POST 数据最大值,必须 > upload_max_filesize max_execution_time = 3600 ; 脚本最大执行时间(秒),大文件上传需要更长时间 max_input_time = 3600 ; 解析输入数据的时间限制 memory_limit = 512M ; 内存限制,如果使用分片上传可以设小一些
注意:如果使用 Nginx,还需要调整 client_max_body_size:
# nginx.conf client_max_body_size 2048m;
前端分片上传(推荐方案)
对于超大文件(> 500MB),直接上传不现实,分片上传是标准做法。
前端 JavaScript + 后端 PHP 示例
前端(HTML + JavaScript):
<!DOCTYPE html>
<html>
<body>
<input type="file" id="fileInput" />
<button onclick="upload()">上传</button>
<progress id="progress" value="0" max="100"></progress>
<div id="status"></div>
<script>
function upload() {
const file = document.getElementById('fileInput').files[0];
if (!file) return alert('请选择文件');
const chunkSize = 5 * 1024 * 1024; // 5MB 一个分片
const totalChunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const uploadId = Date.now() + '_' + file.name; // 唯一标识
function uploadChunk() {
if (currentChunk >= totalChunks) {
// 所有分片上传完成,通知服务器合并
mergeChunks(uploadId, file.name, totalChunks);
return;
}
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk);
formData.append('uploadId', uploadId);
formData.append('chunkIndex', currentChunk);
formData.append('totalChunks', totalChunks);
formData.append('fileName', file.name);
fetch('/upload_chunk.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
currentChunk++;
const progress = (currentChunk / totalChunks) * 100;
document.getElementById('progress').value = progress;
document.getElementById('status').innerText =
`上传中:${Math.round(progress)}%`;
uploadChunk(); // 继续上传下一个分片
} else {
alert('分片上传失败:' + data.message);
}
})
.catch(error => {
console.error('上传出错:', error);
// 可以在这里实现重试逻辑
});
}
function mergeChunks(uploadId, fileName, totalChunks) {
fetch('/merge_chunks.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uploadId: uploadId,
fileName: fileName,
totalChunks: totalChunks
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('status').innerText = '上传完成!';
} else {
alert('合并失败:' + data.message);
}
});
}
// 开始上传第一个分片
uploadChunk();
}
</script>
</body>
</html>
后端 PHP(处理分片上传)
upload_chunk.php - 接收单个分片:
<?php
header('Content-Type: application/json');
$uploadDir = '/tmp/chunks/'; // 缓存分片的目录
$uploadId = $_POST['uploadId'];
$chunkIndex = $_POST['chunkIndex'];
$totalChunks = $_POST['totalChunks'];
$fileName = $_POST['fileName'];
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$chunkFile = $uploadDir . $uploadId . '_part_' . $chunkIndex;
// 保存分片
if (move_uploaded_file($_FILES['file']['tmp_name'], $chunkFile)) {
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'message' => '分片保存失败']);
}
merge_chunks.php - 合并所有分片:
<?php
header('Content-Type: application/json');
$data = json_decode(file_get_contents('php://input'), true);
$uploadId = $data['uploadId'];
$fileName = $data['fileName'];
$totalChunks = $data['totalChunks'];
$chunksDir = '/tmp/chunks/';
$finalDir = '/var/www/uploads/';
$finalFile = $finalDir . $fileName;
// 防止文件名冲突
if (file_exists($finalFile)) {
$finalFile = $finalDir . time() . '_' . $fileName;
}
$fp = fopen($finalFile, 'wb');
if (!$fp) {
echo json_encode(['success' => false, 'message' => '无法创建最终文件']);
exit;
}
for ($i = 0; $i < $totalChunks; $i++) {
$chunkFile = $chunksDir . $uploadId . '_part_' . $i;
if (file_exists($chunkFile)) {
$chunkData = file_get_contents($chunkFile);
fwrite($fp, $chunkData);
unlink($chunkFile); // 合并后删除分片
} else {
echo json_encode(['success' => false, 'message' => "分片 $i 不存在"]);
fclose($fp);
unlink($finalFile);
exit;
}
}
fclose($fp);
echo json_encode(['success' => true, 'file' => $finalFile]);
第三方库集成(生产环境推荐)
自己实现分片上传容易出错,建议使用成熟方案:
Resumable.js + PHP 后端
- 前端:使用 Resumable.js(基于 HTML5 File API)
- 后端:配合 resumable-php 处理分片和合并
// 前端使用 Resumable.js
var r = new Resumable({
target: '/upload.php',
chunkSize: 1 * 1024 * 1024, // 1MB
simultaneousUploads: 3,
testChunks: false
});
r.assignBrowse(document.getElementById('fileInput'));
r.on('fileProgress', function(file) {
// 更新进度条
});
r.on('fileSuccess', function(file) {
alert('上传成功!');
});
Web Uploader(百度出品)
提供一个完整的前端上传组件,支持分片、断点续传、图片预览等,PHP 后端有配套示例。
断点续传实现
断点续传需要记录已上传的分片信息:
- 前端:上传前检查服务端已收到的分片列表
- 服务端:为每个上传任务生成唯一 ID,记录已完成的分片索引
// 检查已上传的分片
function checkUploadedChunks($uploadId) {
$chunksDir = '/tmp/chunks/';
$uploaded = [];
if (is_dir($chunksDir)) {
$files = glob($chunksDir . $uploadId . '_part_*');
foreach ($files as $file) {
preg_match('/_part_(\d+)$/', $file, $matches);
if (isset($matches[1])) {
$uploaded[] = intval($matches[1]);
}
}
}
return $uploaded;
}
前端在上传前先调用 GET /check_upload.php?uploadId=xxx 获取已上传的分片,然后跳过这些分片。
安全性考虑
大文件上传容易成为攻击目标,必须做安全防护:
-
限制文件类型
$allowedTypes = ['application/pdf', 'image/jpeg', 'application/zip']; if (!in_array($_FILES['file']['type'], $allowedTypes)) { die('不允许的文件类型'); } -
扫描
// 使用 ClamAV 扫描 exec('clamscan ' . escapeshellarg($tempFile), $output, $returnCode); if ($returnCode !== 0) { unlink($tempFile); die('文件包含病毒'); } -
检查用户身份和配额
- 每个用户每日/每月上传限制
- 文件数量限制
-
防重复提交
- 使用令牌(token)机制
- 合并时加锁(flock)
替代方案
如果不想自己实现,可以考虑:
-
直接用云存储的 SDK(推荐)
- 阿里云 OSS:支持断点续传、分片上传
- 腾讯云 COS:支持 Web 直传、分片
- AWS S3:支持 Multipart Upload
- 前端直传到云存储,PHP 只处理业务逻辑
-
使用 WebSocket
对于实时性要求高的场景(如直播文件上传)
-
HTTP/2 Server Push
批量小文件上传场景
总结建议
| 文件大小 | 推荐方案 | 复杂度 |
|---|---|---|
| < 100MB | 直接上传 + 修改 PHP 配置 | 低 |
| 100MB - 2GB | 分片上传 | 中 |
| > 2GB | 云存储分片上传(如阿里云 OSS) | 低(集成 SDK) |
最佳实践:
- 前端分片 + 后端 PHP 合并是性价比最高的方案
- 生产环境优先考虑云存储服务
- 务必实现断点续传和进度反馈,提升用户体验
- 做好安全防护,限制文件类型和扫描病毒