作为一名经历过多次文件上传系统开发的老手,我深知大文件上传的核心痛点。当文件体积超过1GB时,传统表单上传方式会面临内存溢出、网络超时、上传中断等问题。基于WebUploader前端方案,我们需要构建一个可靠的后端支持系统。
典型的文件分片上传系统包含以下核心组件:
PHP后端需要实现的接口清单:
php复制// 文件初始化接口
/api/init
// 分片上传接口
/api/chunk
// 合并分片接口
/api/merge
// 秒传验证接口
/api/verify
// 进度查询接口
/api/progress
合理设置分片大小对上传性能影响显著。根据经验值:
分片算法实现要点:
php复制function calculateChunks($fileSize, $chunkSize = 1048576) {
return ceil($fileSize / $chunkSize);
}
提示:分片大小需要与前端WebUploader配置保持一致,否则会导致合并失败
秒传技术的本质是文件指纹比对,流程如下:
数据库设计示例:
sql复制CREATE TABLE `file_index` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`file_md5` varchar(32) NOT NULL,
`file_path` varchar(255) NOT NULL,
`file_size` bigint(20) NOT NULL,
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_md5` (`file_md5`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
PHP验证逻辑:
php复制function checkFileExists($md5) {
$stmt = $pdo->prepare("SELECT file_path FROM file_index WHERE file_md5 = ?");
$stmt->execute([$md5]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
断点续传需要解决三个关键问题:
php复制// Redis存储结构
$redis->hSet("upload:{$fileMd5}", "chunk_{$chunkIndex}", 1);
php复制function getUploadProgress($fileMd5, $totalChunks) {
$uploaded = $redis->hLen("upload:{$fileMd5}");
return [
'uploaded' => $uploaded,
'total' => $totalChunks,
'percentage' => round(($uploaded / $totalChunks) * 100, 2)
];
}
分片接收接口核心逻辑:
php复制// /api/chunk 接口实现
$uploadDir = '/tmp/uploads/';
$chunkFile = $uploadDir . "{$fileMd5}_{$chunkIndex}.part";
if (!move_uploaded_file($_FILES['chunk']['tmp_name'], $chunkFile)) {
throw new Exception('分片保存失败');
}
// 记录分片上传完成
$redis->hSet("upload:{$fileMd5}", "chunk_{$chunkIndex}", 1);
注意事项:临时目录需要定期清理,建议设置cron任务删除超过7天的分片文件
合并分片的正确姿势:
php复制function mergeChunks($fileMd5, $fileName, $totalChunks) {
$outputFile = "/data/uploads/{$fileName}";
$chunkDir = '/tmp/uploads/';
// 验证所有分片是否完整
for ($i = 0; $i < $totalChunks; $i++) {
if (!file_exists("{$chunkDir}{$fileMd5}_{$i}.part")) {
throw new Exception("分片{$i}缺失");
}
}
// 按顺序合并文件
$fp = fopen($outputFile, 'wb');
for ($i = 0; $i < $totalChunks; $i++) {
$chunkFile = "{$chunkDir}{$fileMd5}_{$i}.part";
$chunkData = file_get_contents($chunkFile);
fwrite($fp, $chunkData);
unlink($chunkFile); // 删除已合并分片
}
fclose($fp);
// 验证文件完整性
if (md5_file($outputFile) !== $fileMd5) {
unlink($outputFile);
throw new Exception("文件合并校验失败");
}
return $outputFile;
}
针对大文件存储的优化建议:
php复制// 按日期分目录存储
$storagePath = '/data/uploads/' . date('Y/m/d') . '/';
实际开发中遇到的典型问题:
php复制// 确保分片按数字顺序排序
ksort($chunkFiles, SORT_NUMERIC);
php复制// 使用流式处理替代file_get_contents
$src = fopen($chunkFile, 'rb');
stream_copy_to_stream($src, $dest);
fclose($src);
php复制// 使用文件锁防止并发合并
$lock = fopen("lock_{$fileMd5}", 'w+');
if (flock($lock, LOCK_EX)) {
// 执行合并操作
flock($lock, LOCK_UN);
}
fclose($lock);
经过多次压力测试总结的经验:
php复制// 禁用文件系统atime更新
mount -o remount,noatime /data
ini复制; php.ini优化项
upload_max_filesize = 1024M
post_max_size = 1024M
max_execution_time = 0
memory_limit = 512M
javascript复制// WebUploader配置建议
{
threads: 3, // 并发上传数
chunkSize: 5 * 1024 * 1024, // 5MB分片
chunkRetry: 3 // 分片重试次数
}
必须实现的防护措施:
php复制$allowedTypes = [
'image/jpeg' => '.jpg',
'application/pdf' => '.pdf'
];
if (!isset($allowedTypes[$_FILES['file']['type']])) {
throw new Exception('文件类型不允许');
}
php复制function scanVirus($filePath) {
$clamscan = '/usr/bin/clamscan';
exec("$clamscan --no-summary $filePath", $output, $return);
return $return === 0;
}
php复制// 上传目录权限设置
chmod($uploadDir, 0750);
chown($uploadDir, 'www-data');
建议记录的监控指标:
日志记录示例:
php复制$logger->info('文件上传完成', [
'file_md5' => $fileMd5,
'file_size' => $fileSize,
'upload_time' => $uploadTime,
'client_ip' => $_SERVER['REMOTE_ADDR']
]);
针对不同规模的建议配置:
| 并发量 | CPU | 内存 | 存储 | PHP进程数 |
|---|---|---|---|---|
| <50 | 4核 | 8GB | 本地SAS | 20 |
| 50-200 | 8核 | 16GB | SSD RAID10 | 50 |
| >200 | 16核+ | 32GB+ | 分布式存储 | 100+ |
关键组件的冗余设计:
Nginx配置优化片段:
nginx复制client_max_body_size 1024m;
client_body_buffer_size 1m;
proxy_read_timeout 300;
经过多次项目实践验证,这套PHP后端方案可以稳定支持10GB级文件上传,配合WebUploader前端可实现以下指标:
对于需要兼容老旧浏览器的场景,建议在前端做好降级方案的同时,后端也需要保持接口的兼容性。比如对不支持FormData的浏览器,可以采用base64编码分片传输。