这个问题与服务器或者后端的关系更大吧,不过现在是自己做小项目,没法找后端对线。(我自己就是...)
问题描述与相关代码
主要有参考这里 https://www.shuzhiduo.com/A/KE5QmG605L/
文件上限2GB,文件切片每片为1MB,那么最多切片为2000片。前几次测试了小文件没遇到问题,上传更大些的文件后就发现,当上传完48片后,后续的所有请求都成500了。有换过不同的文件进行测试过,故目前只能上传48MB的文件。
后续又尝试了修改切片大小,将切片修改为了10MB每块依旧会产生问题,只能上传4块了,就是40MB。那么现在的状况就是不能上传48MB以上的文件。
修改apache和php配置有看过其他的回答尝试过,既然依旧有问题也许没改到点上吧。
请教各位是否有办法与建议,如果网络上有这方面的讲解或者这个问题的其他解法,请麻烦告诉我也可以节省时间重复回答。辛苦了!
JS
function videoFileUpload(){
var xhr = new XMLHttpRequest();
var form_data = new FormData();
//每片切片1MB
const LENGTH = 1024 * 1024 * 1;
var start = 0;
var end = start + LENGTH;
var blob;
var blob_num = 1;
var is_stop = 0;
//开始上传
this.start = function(){
//开始上传
var file = upload_video_file;
blob = cutFile(file);
sendFile(blob,file);
blob_num += 1;
}
//停止上传
this.stop = function () {
//后续在这里发送一个请求提示后台删除缓存切片文件
is_stop = 1;
}
//切割文件
function cutFile(file){
var file_blob = file.slice(start,end);
start = end;
end = start + LENGTH;
return file_blob;
};
//发送文件
function sendFile(blob,file){
//只有处于不停止状态才执行
if(is_stop == 0){
var total_blob_num = Math.ceil(file.size / LENGTH);
form_data.append('file',blob);
form_data.append('blob_num',blob_num);
form_data.append('total_blob_num',total_blob_num);
form_data.append('file_name',file.name);
//原本是异步的,但是这会导致请求取消,只能取消异步
xhr.open('POST','https://www.xxx.com/api/Video_file_upload.php',false);
xhr.onreadystatechange = function () {
//进行下一次发送
var t = setTimeout(function(){
//不处于停止状态时才执行
if(start < file.size){
blob = cutFile(file);
sendFile(blob,file);
blob_num += 1;
}
else{
setTimeout(t);
}
},1000);
}
xhr.send(form_data);
}
}
}
php
<?php
//实例化并获取系统变量传参
$upload = new Upload($_FILES['file']['tmp_name'],$_POST['blob_num'],$_POST['total_blob_num'],$_POST['file_name']);
//调用方法,返回结果
$upload->apiReturn();
class Upload{
private $filepath = '../source/video'; //上传目录
private $tmpPath; //PHP文件临时目录
private $blobNum; //第几个文件块
private $totalBlobNum; //文件块总数
private $fileName; //文件名
public function __construct($tmpPath, $blobNum, $totalBlobNum, $fileName){
$this->tmpPath = $tmpPath;
$this->blobNum = $blobNum;
$this->totalBlobNum = $totalBlobNum;
$this->fileName = $fileName;
$this->moveFile();
$this->fileMerge();
}
//判断是否是最后一块,如果是则进行文件合成并且删除文件块
private function fileMerge(){
if($this->blobNum == $this->totalBlobNum){
$blob = '';
for($i=1; $i<= $this->totalBlobNum; $i++){
$blob .= file_get_contents($this->filepath.'/'. $this->fileName.'__'.$i);
}
file_put_contents($this->filepath.'/'. $this->fileName,$blob);
$this->deleteFileBlob();
}
}
//删除文件块
private function deleteFileBlob(){
for($i=1; $i<= $this->totalBlobNum; $i++){
@unlink($this->filepath.'/'. $this->fileName.'__'.$i);
}
}
//移动文件
private function moveFile(){
$this->touchDir();
$filename = $this->filepath.'/'. $this->fileName.'__'.$this->blobNum;
move_uploaded_file($this->tmpPath,$filename);
}
//API返回数据
public function apiReturn(){
if($this->blobNum == $this->totalBlobNum){
if(file_exists($this->filepath.'/'. $this->fileName)){
$data['code'] = 2;
$data['msg'] = 'success';
$data['file_path'] = 'http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['DOCUMENT_URI']).str_replace('.','',$this->filepath).'/'. $this->fileName;
}
}
else{
if(file_exists($this->filepath.'/'. $this->fileName.'__'.$this->blobNum)){
$data['code'] = 1;
$data['msg'] = 'waiting for all';
$data['file_path'] = '';
}
}
header('Content-type: application/json');
echo json_encode($data);
}
//建立上传文件夹
private function touchDir(){
if(!file_exists($this->filepath)){
return mkdir($this->filepath);
}
}
}
相关图片
网络请求,网络状态有关的图片
成功被上传到服务器的文件,从图可见只能上传到第48片,之前上传成功的文件容量都小于48MB。
日志
access.log
ssl_request.log
error.log
php的日志没有报错(在5月27日有做过几次测试,但日志没有发生更新)
之前没有 Apache 的环境,看你上面一说,还真以为是 Apache 的问题,就没去复现,后面看到你补充说跟 FcgidMaxRequestLen,那肯定就不是 Apache 的问题了,抽时间专成搭了个 Apache 的环境,跑起来,然后发现是你前端代码的问题。
你前端代码里面,把 FormData 构造函数里面,FormData 只会在对象初始化的时候进行一次实例化,后续的 sendFile 里面就始终使用的是最开始的那一个 FormData 对象,而 FormData 的 append 方法又比较特殊,即使键名重复了,浏览器仍然会放进去,就导致你的每一个切片,都累计了前面切片的。
最终就导致了发送的大小远超了 Apache 的 FcgidMaxRequestLen 设定值,最后 Apache 响应了 500
切片上传的目的,就是为了在服务端设置较小的允许大小的情况下上传大体积的文件,而你这里直接去改 Apache 的配置,就跟切片上传没有关系了都
要修复这个 BUG,只需要把
new FormData
的操作放到sendFile
内部去,或者将 append 改为 set ,另外 xhr 对象最好也是放到里面去重新初始化。另外,你的 setTimeout 也存在 bug,只不过刚好有 if 条件兜住,else 里面应该是
clearTimeout(t)
才对。修复后的前端代码:
另外你的后端代码里面,在合并文件的时候,打开了文件读取后再进行的合并,这样会导致内存不足导致大文件在合并时失败,你应该找一些合适的方式去处理。
最后,我还想说,你参照的那个站,是一个内容农场,建议屏蔽。