<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>文件分片上传</title>
</head>
<body>
<input type="file" id="File">
<button id="Upload">上传</button>
<p id="Status"></p>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<script>
let BlockSize=2*1048576;
let Url="Upload.php";
let StatusText=$("#Status");
function QueueSendFile(Url,File,BlockSize,Index,MaxNumber){
if(Index<MaxNumber){
let Data=new FormData();
Data.append("File",File.slice(Index*BlockSize,(Index+1)*BlockSize),File.name);
StatusText.text(`文件大小${File.size}字节,分片大小${BlockSize}字节,总共${MaxNumber}分片,当前正在上传第${Index+1}块分片`);
$.ajax(Url,{
data:Data,
type:"post",
processData:false,
contentType:false,
success:function (Result) {
console.log(Result)
if(Result.Status===1){
StatusText.text(`文件大小${File.size}字节,分片大小${BlockSize}字节,总共${MaxNumber}分片,第${Index+1}块分片上传完成`);
Index++;
QueueSendFile(Url,File,BlockSize,Index,MaxNumber);
}
else if(Result.Status===0){
Index=MaxNumber;
StatusText.text("出现错误:"+Result.Message);
}
else{
Index=MaxNumber;
StatusText.text("服务器返回不正确的数据");
}
},
error:function (){
Index=MaxNumber;
StatusText.text("请求错误");
}
});
}
else{
StatusText.text("文件上传完成");
}
}
$("#Upload").click(function (){
let File=$("#File")[0].files;
if(File.length>0){
File=File[0];
QueueSendFile(Url,File,BlockSize,0,Math.ceil(File.size/BlockSize));
}
else{
StatusText.text("必须选择文件");
}
});
</script>
</body>
</html>
Upload.php
<?php
function return_data($Message,$IsSuccess=true){
header('content-type:application/json');
exit(json_encode(['Status'=>$IsSuccess?1:0,'Message'=>$Message]));
}
function error_return($Message){
return_data($Message,false);
}
function success_return($Message=''){
return_data($Message);
}
$UserId=1;
if(isset($_FILES['File'])){
$File=$_FILES['File'];
$BasePath=__DIR__.DIRECTORY_SEPARATOR.'Files'.DIRECTORY_SEPARATOR;
if(!file_exists($BasePath)){
mkdir($BasePath);
}
$UserPath=$BasePath.$UserId.DIRECTORY_SEPARATOR;
if(!file_exists($UserPath)){
mkdir($BasePath);
}
$SavePath=$UserPath.$File['name'];
if(!file_exists($SavePath)){
file_put_contents($SavePath,'');
}
file_put_contents($SavePath,file_get_contents($File['tmp_name']),FILE_APPEND);
success_return();
}
else{
error_return('参数错误');
}
其实写此文之前我也尝试过别的分片方案:
可以乱序同时上传的
由前端每次发送分片索引和总大小,php把上传的文件建立文件夹,分片文件按分片索引全放在里面,每次检测文件总大小和总大小是否相同,相同就执行合并分片文件的操作,但这个有个缺点,随着分片文件的增多每次计算大小会越来越慢,有两千多个分片时每个总处理时间都超过了100ms(有我电脑慢的原因)
可乱序但不支持同时上传的
大体与上面方案差不多,就是前段发送总大小改成了总数量,每次检测文件夹文件数量和总数量是否一致,仅仅获取文件数量会比上面快得多
这里再说下其它的几个问题:
合并分片操作费时如何处理?
可以在文件上传完成后单独请求执行分片合并操作,如果时间不是特别长可以同步完成,很长而且此类操作比较多就需要使用定时任务来执行合并分片的操作,更加及时些可以使用任务队列实现,前端可以简单地使用轮询来获取操作是否完成
如何保证上传文件的正确性?
在上传之前计算文件的哈希值,在进行合并分片或上传最后一个分片时将哈希值传给php,php来比对完整文件的哈希是否一致