共计 2296 个字符,预计需要花费 6 分钟才能阅读完成。
关于大文件上传
思路
- 使用 js 读取 form 表单中选择的 file,计算文件 md5 值,并上传 md5 值到服务端,检查文件是否已上传过(类似秒传功能)
- 若文件未上传过,按照其大小切成 1MB 大小的块,小于 1MB 的不用切
- 用 ajax 异步提交切好的块上传至服务端(一个块一个请求,不阻塞,多线程)
- 当上传完成所有切块,发起一个合并文件的请求,服务端进行前面上传的文件块的合并,合并完成即上传完成。
实现
js 计算文件 md5 使用 spark-md5.js, 据说这个库使用的是世界上最快的 md5 算法。
js 对文件切片并使用 ajax 上传切片:
let size = file.size; // 获取文件大小 | |
const shardSize = 1024 * 1024; // 块大小 1MB | |
let shardCount = Math.ceil(size/shardSize); // 可切成的块数 | |
for(let i = 0; i < shardCount; i++){ | |
let start = i * shardSize, | |
end = Math.min(size, start + shardSize); | |
let form = new FormData(); | |
form.append('file', file.slice(start, end)); // 用 slice 方法切片 | |
form.append('size', end - start); | |
form.append('name', name); | |
form.append('total', shardCount); | |
form.append('md5', file_md5); // 文件 md5 值 | |
form.append('index', i); // 第几块 | |
$.ajax({ | |
url: 'upload.php?type=shard', | |
type: "POST", | |
data: form, | |
// async: false, // 是否异步上传,默认 true | |
processData: false, // 很重要,告诉 jquery 不要对 form 进行处理 | |
contentType: false, // 很重要,指定为 false 才能形成正确的 Content-Type | |
success: function (res) {// 成功回调} | |
} | |
} |
php 端保存切片
$path = __DIR__ . '/uploads'; | |
$file = $_FILES['file']; | |
$total = $_POST['total']; | |
$index = $_POST['index']; | |
$size = $_POST['size']; | |
$dst_file = $path . '/' . $name . '-' . $total . ':' . $index; // 切片文件存储的文件名 | |
if ($file["error"] > 0) {echo json_encode(['code'=>400, 'msg'=>$file["error"]]);die; | |
} else {$res = move_uploaded_file($file['tmp_name'], $dst_file); | |
if ($res) {file_put_contents($dst_file . '.info', $size); // 切片上传成功,写一个保存其大小的文件,后续合并是校验文件用的到 | |
echo json_encode(['code'=>200, 'msg'=>'shard ok']);die; | |
} else {echo json_encode(['code'=>400, 'msg'=>'shard move_uploaded_file error']);die; | |
} | |
} |
php 端合并
function mergeFile($name, $total, &$msg) | |
{ | |
// 校验切片文件是否都上传完成,是否完整 | |
for ($i = 0; $i < $total; $i++) {if (!file_exists($name . '-' . $total . ':' . $i . '.info') || !file_exists($name . '-' . $total . ':' . $i)) { | |
$msg = "shard error $i"; | |
return false; | |
} else if (filesize($name . '-' . $total . ':' . $i) != file_get_contents($name . '-' . $total . ':' . $i . '.info')) { | |
$msg = "shard size error $i"; | |
return false; | |
} | |
} | |
@unlink($name); | |
if (file_exists($name . '.lock')) { // 加锁 防止有其他进程写文件,造成文件损坏 | |
$msg = 'on lock'; | |
return false; | |
} | |
touch($name . '.lock'); | |
$file = fopen($name, 'a+'); | |
for ($i = 0; $i < $total; $i++) { // 按切片顺序写入文件 | |
$shardFile = fopen($name . '-' . $total . ':' . $i, 'r'); | |
$shardData = fread($shardFile, filesize($name . '-' . $total . ':' . $i)); | |
fwrite($file, $shardData); | |
fclose($shardFile); | |
unlink($name . '-' . $total . ':' . $i); | |
unlink($name . '-' . $total . ':' . $i . '.info'); | |
} | |
fclose($file); | |
unlink($name . '.lock'); | |
return true; | |
} |
我也写好了一个 demo,传送门
下面是这个 demo 的效果图:
这个 demo 有些方面还不够完善,后续持续完善吧~
原文连接:
关于大文件上传
更多分享知识点, 请扫码关注微信公众号:
正文完
发表至: javascript
2019-05-24