前言:
之前所寫的文件上傳類通常進行考慮的是文件的類型、大小是否符合要求條件。當上傳大文件時就要考慮到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的代碼太多了不貼了,后面把代碼整出來,放倉庫里再來把地址貼出來。
結語:快過年了,總算迎來了好消息,算個新年禮物吧!
越努力!越幸運!
