前言:
之前所写的文件上传类通常进行考虑的是文件的类型、大小是否符合要求条件。当上传大文件时就要考虑到php的配置和服务器的配置问题。之前简单的觉得只要将php.ini中的表单上传的 大小,单脚本执行的最大时间都配 大就行了。显然这是很小白吃的做法。这样改完之后页面及服务器还是会崩溃。差不多几百兆这样吧。所以查阅资料,采用将大文件分割上传的方式来解决。这里进行记录下。
内容:
- 首先记录下更改文件上传大小的一些配置信息
- 打开php.ini
- file_uploads = on //是否允许通过HTTP上传文件的开关,默认开启
- upload_tmp_dir //临时文件储存的路径
- upload_max_filesize 20M //允许上传的文件最大值
- post_max_size 22M //通过表单POST所能上传的大小
- max_execution_time 600 //单个PHP页面允许运行的最大时间
- max_input_time 600 //单个PHP页面接收数据所需的最大时间,默认60秒
- memory_limit 256M //单个PHP页面执行过程中可占用的最大内存,默认8M
通过更改上述的配置就可以调整允许文件上传的大小。(有的还需要调整服务器的一些配置)
补充:413错误 如果服务器是nginx的话,需要更改配置nginx_conf 中的client_max_body_size 24M,设置接收客户端发送过来包的最大值。记得放在http里,重启服务器,用restart,不要用reload。
贴下具体修改nginx配置的博文:http://blog.51cto.com/13673885/2299771。
- 接着开始实现文件的分割上传。
文件通过HTML的input标签的file来选择文件上传。通过H5新对象FileReader。就像字面上的意思一样FileRaeder对象就是一个读取本地文件的对象。FileReader对象可以将本地文件读取后以base64位的编码返回。(具体有关FileReader对象的使用,请自行百度,或者阅读以下博文,写的很具体。博文连接:https://www.cnblogs.com/hhhyaaon/p/5929492.html)
实际开发经历:
- 第一次尝试
- 通过input file标签来选择文件
- 采用FileReader对象对文件进行读取
- 通过ajax异步将文件的base64编码发送给服务端
- 服务端接收后对编码进行解码并保存到文件中。
- 测试结果失败,当文件过大时所编码的长度也越长,通过ajax异步提交参数的最大上线为8000个字节。
- 第二次尝试
- 在第一次的基础上对所获取到的base64编码进行分割上传
- 将所获取的base64编码字符串分成几份并进行编号,在循环调用ajax进行发送
- 服务端接收到后对数据进行解码,以编号进行命名
- 接收完所有小文件后,调用后台方法将小文件进行合并
- 测试结果失败,当上传文件超过1G时,浏览器就崩溃了。应该是在读取文件时,文件过大,一次性读出返回base64编码过大,导致页面崩溃。
- (这边实现上要注意一点,在对base64编码分割的时候,每一段数据长度要是4的倍数才行。不然会导致文件在服务端解码合并后出错。可以去理解下base64的编码原理就懂了。当然也可以在服务器代码上避免,就是将所接收到的所有base64的字符串先拼接起来再进行解码,就不会造成错误。不过建议不要这样子,以为当上传的文件太大,假设1G的文件,编码后产生1.3G编码在代码中去执行操作,会造成内存溢出!!!!)
- 第三次尝试
- 在第二次的基础上想着在读取文件获取编码的过程也进行分批读取来避免一次性读取过大的文件导致页面崩溃。
- 这边就要使用到H5的file.slice()来对文件进行分块,从而实现分批读取分批上传。
- 通过FlieReader对象来读取文件快
- 通过ajax将base64编码异步发送到服务端
- 服务端接收数据进行解码和文件保存
- 测试成功,测试上传了快4G的文件。
- (由于将文件进行了分段,所以在上传大文件时会发起大量的ajax请求,产生大量的并发,可能会导致页面再次崩溃。所以我才用错开请求的方式。减慢产生ajax请求的速度。)
具体实现代码
接下来贴点代码
前端框架:layui
后端框架:tp5
页面代码:
<div class="layui-form-item"> <label class="layui-form-label">视频上传</label> <div class="layui-input-block layui-upload-video-btn"> <ul> <li class="img-upload"> <label></label> <input type="file" class="video-upload-file layui-upload-video-file-btn" name="file"/> <video width="320" height="240" controls style="display: none"> <source src="" type="video/mp4"> <source src="" type="video/ogg"> 您的浏览器不支持Video标签。 </video> <span style="display: none">X</span> <input type="hidden" class="video-link-id" name="video_link_id" value=""> </li> <li>//视频上传会比较久(上传完会有提示)</li> </ul> </div> </div>
js代码:
$('.video-upload-file').on('change',function(){
layer.msg('正在提交视频......');
//隐藏按钮,显示进度条
$('.layui-upload-video').hide();
$('.layui-progress-ads').show();
var loads_video = layer.load(2,{shade: [0.2, '#3a3535']}); //产生加载圈,禁止用户其他操作
var thisFile = $(this);
var reader=new FileReader();
var file_size = this.files[0].size; //文件大小
var limit = 8388608; //每次读取文件的大小
// var limit = 1048000; //每次读取文件的大小
var up_count = Math.ceil(file_size/limit); //总上传次数
var type = this.files[0].type.substr(this.files[0].type.indexOf('/')+1); //文件类型
var success_num = 0; //用于存放上传成功的数据的id
var check = 1; //防止多次合并
console.log('文件大小:'+this.files[0].size);
console.log('文件类型:'+type);
console.log('分割上传次数:'+up_count);
//分段读取文件
readFile(this.files[0], 0, limit);
function readFile(file, num, limit){
// console.log('第'+num+'次:'+num*limit);
reader.readAsDataURL(file.slice(num*limit, (num+1)*limit));
reader.onload = function(e){
console.log(reader.result.length);
console.log(reader.result);
//异步base64的数据传输到服务器
ajax_way(reader.result, name, num+1, thisFile);
if((num+1)*limit <= file_size){
readFile(file, num+1, limit);
}
}
}
function ajax_way(data,name,num, thisFile){
//避免一次性生成太多的请求
if(num+1 > 60){
// console.log('等待两秒');
sleep(6000);
// console.log('等待结束');
}
$.ajax({
url: "<?= url('admin/video/up_mfile');?>",
type: "POST",
data: {video:data,name:name,num:num},
// async:false, //是否采用同步,串行发送请求
success: function (data) {
if(data.code == 1){
//上传成功,成功次数加一
success_num++;
console.log(num+'完成');
console.log('已完成:'+success_num+'/'+up_count);
//计算完成的百分比
var precentage = Math.ceil((success_num/up_count)*100);
//更改进度条显示
$('.layui-progress-ads-btn').attr('lay-percent', precentage+'%');
$('.layui-progress-ads-btn').css('width', precentage+'%');
$('.layui-progress-text').html(precentage+'%');
//如果分割文件都上传了则调用接口合并文件
if(success_num == up_count && check == 1){
check = 0;
success_num = 0;
merge_mfile(name, up_count, thisFile, type);
}
}
},
error:function(e){
console.log('出错了:'+num);
//传输出错则重新上传
ajax_way(data, name, num, thisFile);
}
});
}
//合并文件
function merge_mfile(name, count, thisFile, type){
$.ajax({
url:"<?= url('admin/video/merge_mfile');?>",
data:{name:name, count:count, type:type},
type:"POST",
success:function(data){
if (data.code==1){
layer.close(loads_video);
layer.msg('视频提交成功');
thisFile.siblings('.video-link-id').val(data.data);
}else{
layer.msg('视频提交异常请重新提交');
//显示按钮,隐藏进度条
$('.layui-upload-video').show();
$('.layui-progress-ads').hide();
//将进度条置零
$('.layui-progress-ads-btn').attr('lay-percent', '0%');
$('.layui-progress-ads-btn').css('width', '0%');
$('.layui-progress-text').html('0%');
//清空已选中的文件
var file = $(".layui-upload-video-file-btn");
file.after(file.clone().val(""));
file.remove();
}
}
})
}
function sleep(n) { //n表示的毫秒数
var start = new Date().getTime();
while (true) if (new Date().getTime() - start > n) break;
}
return false;
});
php的代码太多了不贴了,后面把代码整出来,放仓库里再来把地址贴出来。
结语:快过年了,总算迎来了好消息,算个新年礼物吧!
越努力!越幸运!