本文目录导读:

我来详细解释PHP实现断点上传的几种方案:
基础原理
断点上传的核心是:
- 前端将文件切片
- 后端接收并保存每个分片
- 所有分片上传完成后合并
简单分片上传实现
前端代码(HTML + JavaScript)
<!DOCTYPE html>
<html>
<head>断点上传</title>
</head>
<body>
<input type="file" id="fileInput">
<button onclick="uploadFile()">开始上传</button>
<div id="progress"></div>
<script>
const chunkSize = 1 * 1024 * 1024; // 1MB 每片
async function uploadFile() {
const file = document.getElementById('fileInput').files[0];
if (!file) return;
const totalChunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
// 检查已上传的分片
const checkResponse = await fetch('check.php', {
method: 'POST',
body: JSON.stringify({
fileName: file.name,
totalChunks: totalChunks
})
});
const uploadedChunks = await checkResponse.json();
for (let i = 0; i < totalChunks; i++) {
if (uploadedChunks.includes(i)) continue;
const start = i * 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('fileName', file.name);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
try {
await fetch('upload.php', {
method: 'POST',
body: formData
});
const progress = Math.round(((i + 1) / totalChunks) * 100);
document.getElementById('progress').textContent =
`上传进度: ${progress}%`;
} catch (error) {
console.error(`分片 ${i} 上传失败`);
break;
}
}
// 通知服务器合并文件
await fetch('merge.php', {
method: 'POST',
body: JSON.stringify({
fileName: file.name,
totalChunks: totalChunks
})
});
}
</script>
</body>
</html>
后端PHP代码
upload.php(接收分片)
<?php
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$fileName = $_POST['fileName'];
$chunkIndex = (int)$_POST['chunkIndex'];
$uploadDir = 'uploads/chunks/';
// 创建上传目录
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
if (isset($_FILES['file'])) {
$chunkName = $fileName . '_chunk_' . $chunkIndex;
$chunkPath = $uploadDir . md5($chunkName);
// 保存分片
if (move_uploaded_file($_FILES['file']['tmp_name'], $chunkPath)) {
echo json_encode([
'success' => true,
'message' => '分片上传成功',
'chunkIndex' => $chunkIndex
]);
} else {
echo json_encode([
'success' => false,
'message' => '分片上传失败'
]);
}
}
}
check.php(检查已上传分片)
<?php
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$fileName = $input['fileName'];
$totalChunks = $input['totalChunks'];
$uploadDir = 'uploads/chunks/';
$uploadedChunks = [];
// 遍历检查已上传的分片
for ($i = 0; $i < $totalChunks; $i++) {
$chunkName = $fileName . '_chunk_' . $i;
$chunkPath = $uploadDir . md5($chunkName);
if (file_exists($chunkPath)) {
$uploadedChunks[] = $i;
}
}
echo json_encode($uploadedChunks);
merge.php(合并文件)
<?php
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$fileName = $input['fileName'];
$totalChunks = $input['totalChunks'];
$uploadDir = 'uploads/chunks/';
$finalDir = 'uploads/files/';
if (!file_exists($finalDir)) {
mkdir($finalDir, 0777, true);
}
$finalPath = $finalDir . $fileName;
// 创建并写入最终文件
$finalFile = fopen($finalPath, 'wb');
for ($i = 0; $i < $totalChunks; $i++) {
$chunkName = $fileName . '_chunk_' . $i;
$chunkPath = $uploadDir . md5($chunkName);
if (file_exists($chunkPath)) {
$chunkContent = file_get_contents($chunkPath);
fwrite($finalFile, $chunkContent);
// 删除已合并的分片
unlink($chunkPath);
} else {
fclose($finalFile);
echo json_encode([
'success' => false,
'message' => "分片 {$i} 缺失,合并失败"
]);
return;
}
}
fclose($finalFile);
echo json_encode([
'success' => true,
'message' => '文件合并成功',
'filePath' => $finalPath
]);
改进版本(带MD5校验)
<?php
// upload.php - 带MD5校验版本
function getFileMd5($filePath) {
if (file_exists($filePath)) {
return md5_file($filePath);
}
return null;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$fileName = $_POST['fileName'];
$chunkIndex = (int)$_POST['chunkIndex'];
$chunkMd5 = isset($_POST['md5']) ? $_POST['md5'] : '';
$uploadDir = 'uploads/chunks/';
$chunkName = $fileName . '_chunk_' . $chunkIndex;
$chunkPath = $uploadDir . md5($chunkName);
if (isset($_FILES['file'])) {
$tmpName = $_FILES['file']['tmp_name'];
// 计算上传分片的MD5
$uploadedMd5 = md5_file($tmpName);
// 校验MD5
if ($uploadedMd5 === $chunkMd5) {
if (move_uploaded_file($tmpName, $chunkPath)) {
echo json_encode([
'success' => true,
'message' => '分片上传成功,校验通过'
]);
}
} else {
echo json_encode([
'success' => false,
'message' => 'MD5校验失败,请重新上传'
]);
}
}
}
使用腾讯云/阿里云OSS断点续传
<?php
// 使用阿里云OSS SDK示例
require_once 'aliyun-oss-php-sdk/autoload.php';
use OSS\OssClient;
use OSS\Core\OssException;
$accessKeyId = "your-access-key-id";
$accessKeySecret = "your-access-key-secret";
$endpoint = "oss-cn-hangzhou.aliyuncs.com";
$bucket = "your-bucket";
try {
$ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
// 分片上传
$object = "uploads/example.zip";
$file = __DIR__ . "/example.zip";
// 初始化分片上传
$uploadId = $ossClient->initiateMultipartUpload($bucket, $object);
// 上传分片
$partSize = 10 * 1024 * 1024; // 10MB
$uploadFileSize = filesize($file);
$partCount = ceil($uploadFileSize / $partSize);
$parts = array();
for ($i = 1; $i <= $partCount; $i++) {
$start = ($i - 1) * $partSize;
$length = min($partSize, $uploadFileSize - $start);
$content = file_get_contents($file, false, null, $start, $length);
$result = $ossClient->uploadPart($bucket, $object, $uploadId, array(
'PartNumber' => $i,
'Content' => $content
));
$parts[] = array(
'PartNumber' => $i,
'ETag' => $result['ETag']
);
}
// 完成分片上传
$result = $ossClient->completeMultipartUpload($bucket, $object, $uploadId, $parts);
echo "文件上传成功: " . $result['Location'];
} catch (OssException $e) {
echo "上传失败: " . $e->getMessage();
}
最佳实践建议
前端优化
- 使用Web Worker处理分片,避免阻塞UI
- 添加重试机制
- 显示实时进度条
- 支持拖拽上传
后端优化
- 使用Redis记录已上传分片状态
- 定期清理过期分片
- 使用消息队列异步处理合并
- 限制文件大小和并发数
// 使用Redis记录分片状态
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = "upload:{$fileName}";
$redis->sAdd($key, $chunkIndex);
$redis->expire($key, 3600); // 1小时后过期
// 检查分片是否存在
if ($redis->sIsMember($key, $chunkIndex)) {
echo "该分片已上传";
}
这套方案可以满足大多数断点上传需求,如果需要处理超大文件,建议使用云存储服务商的SDK,它们提供了更完善的断点续传机制。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播