jQuery + ThinkPHP 实现大文件切割上传
为什么服务器不能直接上传大文件呢?跟php.ini的几个配置有关:
upload_max_filesize = 2M //PHP最大能接受的文件大小
post_max_size = 8M //PHP能收到的最大POST值'
memory_limit = 128M //内存上限
max_execution_time = 30 //最大执行时间
不过也不能简单粗暴的把上面这几个值调大,否则服务器的内存资源被吃光是早晚的问题。
所以,就得从技术方面下手解决了。
解决思路
JS思路
1、监听文件上传onchange事件;2、获取到上传的文件File对象;
3、把文件的File对象的文件二进制内容进行切割,并且附加到FormData对象中;
4、把FormData对象通过Ajax发送到服务器;
5、重复【步骤3】、【步骤4】,直到文件发送完。
PHP思路
1、建立上传文件夹;2、把接收到的文件从上传临时目录移动到上传文件夹;
3、所有的分片文件上传完成以后,对分片文件按照顺序进行合并;
4、删除文件夹和(或)分片文件;
5、返回合并后的文件路径。
DEMO代码
前端部分代码
<!DOCTYPE HTML>
<html>
<head>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.0/spark-md5.min.js"></script>
</head>
<body>
<form id="uploadForm" method="post">
<input type="file" name="file" id="file">
<input type="submit">
</form>
<script>
$("#uploadForm").on("submit", function (e) {
const LENGTH = 1024 * 1024; // 每次上传的文件大小,即把文件分割成指定大小的分块文件。单位为Bytes
var file = $("#file")[0].files[0]; // 获取要上传的文件
var start = 0; // 当前文件的分割起始位置
var end = start + LENGTH; // 当前文件的分割结束位置
var blob; // 当前的分割文件块。用来存储文件块的内容
var blob_num = 1; // 当前的分割文件对应的块数
var total_blob_num = Math.ceil(file.size / LENGTH); // 总的文件分割块数
/**
* 切割文件
* @param file
* @return blob
*/
function cutFile (file) {
// 按照指定的起始和结束位置,获取指定部分的文件内容
var file_blob = file.slice(
start,
end
);
// 分割完之后,结束位置更改为下一次分割的起始位置
start = end;
// 下一次分割的起始位置加上分割长度作为下一次分割的结束位置
end = start + LENGTH;
// 返回被分割出来的文件内容
return file_blob;
}
/**
* 发送文件
* @param blob
* @param file
*/
function sendFile (blob, file) {
var formData = new FormData();
formData.append(
'file',
blob
);
formData.append(
'blob_num',
blob_num
);
formData.append(
'total_blob_num',
total_blob_num
);
formData.append(
'file_name',
file.name
);
// 计算当前上传文件的MD5内容
calculate(file, function (md5) {
formData.append(
'md5',
md5
);
$.ajax({
url: "/index/upload.html",
type: "post",
cache: false,
processData: false,
contentType: false,
data: formData,
success: function (res) {
// 此处延时1000毫秒,防止程序执行混乱
setTimeout(function () {
// 如果当前的分割块数未超过总的分割块数,则继续分割并上传
if (blob_num <= total_blob_num) {
blob = cutFile(file);
sendFile(
blob,
file
);
blob_num += 1;
} else {
alert("上传完成!");
}
}, 1000);
},
});
});
}
/**
* @param file
* @param callback
*/
function calculate (file, callBack) {
function loadNext () {
var start = currentChunk * chunkSize,
end = start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsBinaryString(blobSlice.call(
file,
start,
end
));
};
var fileReader = new FileReader(),
blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice,
chunkSize = 2097152,
// read in chunks of 2MB
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark = new SparkMD5();
fileReader.onload = function (e) {
spark.appendBinary(e.target.result); // append binary string
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
callBack(spark.end());
}
};
loadNext();
}
blob = cutFile(file);
sendFile(
blob,
file
);
blob_num += 1;
return false;
});
</script>
</body>
</html>
PHP代码
<?php
declare (strict_types = 1);
namespace app\index\controller;
use think\facade\View;
use think\Request;
class Index
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index()
{
//
return View::fetch('index');
}
public function upload()
{
// 获取当前的分割的块数
$blobNum = (integer)request()->param(
'blob_num'
);
// 获取总的分割块数
$totalBlobNum = (integer)request()->param(
'total_blob_num'
);
// 获取上传的文件的真实文件名(不包含路径)
$fileName = request()->param(
'file_name'
);
// 获取文件内容的MD5格式,防止与其它上传文件冲突
$md5 = request()->param(
'md5'
);
// 获取当前上传的分片文件
$file = request()->file(
'file'
);
// 获取文件最终被存储的路径和文件名(路径仅相对于当前项目根目录位置)
$realFileName = 'storage/index/material/' . (new \think\File('/' . $fileName, false))->hashName();
// 获取分片文件的临时存储位置
$storagePath = root_path() . 'runtime/storage/' . $md5 . '/';
// 把每一个分片文件按照块数命名,并且存储到临时存储的位置
\think\facade\Filesystem::putFileAs(
$md5,
$file,
$blobNum . ''
);
// 如果已经上传了最后一个分片文件,则合并并存储最终文件,并删除临时文件
if ($blobNum >= $totalBlobNum) {
// 遍历每一个分片文件
for ($i = 1; $i <= $totalBlobNum; $i++) {
// 获取当前遍历到的分片文件
$tmpFile = $storagePath . $i . '';
// 判断当前的分片文件是否存在,不存在则跳过
if (!is_file($tmpFile)) {
continue;
}
// 读取当前的分片文件的内容
$tmpFileContent = file_get_contents(
$tmpFile
);
// 判断文件最终的存储路径是否存在,不存在则创建
if (!is_dir(pathinfo(public_path() . $realFileName)['dirname'])) {
mkdir(
pathinfo(public_path() . $realFileName)['dirname'],
0777,
true
);
}
// 把当前的分片文件追加到最终的文件里面
file_put_contents(
public_path() . $realFileName,
$tmpFileContent,
FILE_APPEND
);
// 当前的分片文件已经处理完,没有用了,删除掉
unlink(
$tmpFile
);
}
// 删除分片文件的临时存储位置(文件夹)
rmdir($storagePath);
} else { // 如果上传的分片文件还不是最后一个分片文件
// to do something...
}
return json([
'code' => 0,
'msg' => 'Success',
'data' => '/' . $realFileName,
]);
}
}
好了,上传完成
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。