一般文件比较大时,可以采用第三方图片存储方案(如OSS),或者分块传输,本文将讲解使用php进行分块上传。
分块上传指的是将要上传的文件切成一片片小的区块进行上传,然后服务端接收这些前端上传过来的小区块,存储到
服务器存储文件的目录下,待上传完毕时将这些小区块合并成一个文件,并将已上传的这些区块删除。至此,上传过
程结束。
这里有几个细节需要注意,在上传时前端需要传给服务端一个“标志”,以表明这是一个分块上传的类型而不是文件
直接整个上传,用以区分这2种文件的上传类型。对于小文件可以直接上传,不需要分片。
(可选)其次前端需要再发送原文件的相关信息如文件名,便于服务端的存储。也可以不传,具体的存储逻辑开发者
可以自己实现。
技术栈:thinkphp、前端
以下是前端需要执行的一些代码逻辑
由于是个demo,因此开发者对于其它方面的需求可以自己实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分块上传</title>
<script src="/static/js/jquery.min.js"></script>
<!-- md5相关的库文件 -->
<script src="/static/js/core.js"></script>
<script src="/static/js/md5.js"></script>
</head>
<body>
<div>
<!-- 文件上传的元素 -->
<input type="file" id="file">
</div>
<script>
$(document).ready(function () {
// 监听文件上传
$('#file').change(function (e) {
// 定义每个区块的大小,以字节为单位
var chunk = 18*1024;
var uploadFile = $(this)[0].files[0]
var fileReader = new FileReader()
// 初始化记录的进度
var loaded = 0
console.log(uploadFile,e)
readPartFile(loaded,chunk)
var fileId = CryptoJS.MD5(uploadFile).toString()
// 监听加载完毕的区块
fileReader.onload = function (event) {
// 更新进度
loaded += chunk
var targetResult = event.target.result
// 转换成Blob对象
var targetBlob = new Blob([targetResult])
console.log('event',event,'mime',targetBlob)
var isEnd = loaded >= uploadFile.size ? 1 : 0
//每3秒上传一次,防止请求频率过快,时间可自定义
setTimeout(() => {
pieceUpload(targetBlob,isEnd,fileId,uploadFile.name,function (e) {
console.log(e)
if (isEnd) {
return alert('上传完成!')
}
//当前区块上传完毕后再上传下一个区块
readPartFile(loaded,chunk)
})
},3000)
}
// 将文件分成指定大小的区块,start是起始位置
function readPartFile(start) {
var piece = uploadFile.slice(start,start + chunk)
fileReader.readAsArrayBuffer(piece)
}
// 分块上传 file:上传的区块 isEnd:是否已整体上传结束 fileId:源文件的唯一标识 filename:文件名 callback:上传之后的回调
function pieceUpload(file,isEnd,fileId,filename,callback) {
try {
var formData = new FormData()
formData.append('file',file,'file')
formData.append('is_end',isEnd)
formData.append('file_id',fileId)
formData.append('filename',filename)
//向服务端表明该请求是一个分块上传
formData.append('type','chunk')
console.log(formData)
$.ajax({
//分块上传的接口url
url: '/index/index/chunkUpload',
type: 'POST',
data: formData,
processData: false,
// dataType: 'json',
// contentType: 'application/json',
contentType: false,
success: callback,
error: function (err) {
console.log(err)
}
})
} catch (e) {
console.log(e)
}
}
})
})
</script>
</body>
</html>
后端的代码逻辑
//执行分块上传的控制器方法
public function chunkUpload() {
if ($this->request->isPost()) {
//执行分块上传流程
$data = $this->request->post();
//判断是否是分块上传
if ($data['type'] === 'chunk') {
$file = request()->file('file');
//获取对应的上传配置
$fs = Filesystem::disk('local');
$ext = $file->extension();
$chunkPath = $data['file_id'].'/'.$file->md5().($ext ? '.'.$ext : '');
//存储分片文件到指定路径
$savename = $fs->putFileAs( 'chunk', $file,$chunkPath);
if (!$savename) {
return json([
'code' => 1,
'msg' => '上传失败',
'data' => [],
]);
}
if (!$data['is_end']) {
$extra['url'] = '';
} else {
//合并块文件
$fileUrl = '';
$chunkSaveDir = Filesystem::getDiskConfig('local');
$smallChunkDir = $chunkSaveDir['root'].'/chunk/'.$data['file_id'];
//获取已存储的属于源文件的所有分块文件 进行合并
if ($handle = opendir($smallChunkDir)) {
$chunkList = [];
$modifyTime = [];
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
$temp['path'] = $smallChunkDir.'/'.$file;
$temp['modify'] = filemtime($smallChunkDir.'/'.$file);
$chunkList[] = $temp;
$modifyTime[] = $temp['modify'];
}
}
//对分块文件进行排序
array_multisort($modifyTime,SORT_ASC,$chunkList);
$saveDir = Filesystem::getDiskConfig('public');
// $newPath = $saveDir['root'].'/'.date('Ymd').'/'.$data['file_id'].($newExt ? '.'.$newExt : '');
$newPath = $saveDir['root'].'/'.date('Ymd').'/'.$data['filename'];
if (!file_exists($saveDir['root'].'/'.date('Ymd'))) {
mkdir($saveDir['root'].'/'.date('Ymd'),0777,true);
}
$newFileHandle = fopen($newPath,'a+b');
foreach ($chunkList as $item) {
fwrite($newFileHandle,file_get_contents($item['path']));
unlink($item['path']);
}
rmdir($smallChunkDir);
//将合并后的文件存储到指定路径
$fileUrl = $saveDir['url'].'/'.date('Ymd').'/'.$data['filename'];
fclose($newFileHandle);
closedir($handle);
} else {
return json([
'code' => 1,
'msg' => '目录:'.$chunkSaveDir['root'].'/chunk/'.$data['file_id'].'不存在',
'data' => [],
]);
}
$extra['url'] = $fileUrl;
}
//合并流程结束
return json([
'code' => 0,
'msg' => '上传成功',
'data' => $extra,
]);
}
}
return view();
}
引入的js依赖:
<script src="/static/js/core.js"></script>
<script src="/static/js/md5.js"></script>
<script src="/static/js/jquery.min.js"></script>
授权许可:本文相关代码遵循MIT协议,用户可在遵循MIT协议所允许和禁止的范围下使用。若转载,请署名作者及来源。
额外说明:若您在使用代码的过程中遇到了问题,欢迎在评论下方留言。另外本人平时兼职
开发,若您有合适的开发需求,欢迎联系我。