不管怎樣簡單的需求,在量級達到一定層次時,都會變得異常復雜
文件上傳簡單,文件變大就復雜
上傳大文件時,以下幾個變量會影響我們的用戶體驗
上傳時間會變長,高頻次文件上傳失敗,失敗后又需要重新上傳等等
為了解決上述問題,我們需要對大文件上傳單獨處理
這里涉及到分片上傳及斷點續傳兩個概念
分片上傳,就是將所要上傳的文件,按照一定的大小,將整個文件分隔成多個數據塊(Part)來進行分片上傳
如下圖
上傳完之后再由服務端對所有上傳的文件進行匯總整合成原始的文件
大致流程如下:
斷點續傳指的是在下載或上傳時,將下載或上傳任務人為的劃分為幾個部分
每一個部分采用一個線程進行上傳或下載,如果碰到網絡故障,可以從已經上傳或下載的部分開始繼續上傳下載未完成的部分,而沒有必要從頭開始上傳下載。用戶可以節省時間,提高速度
一般實現的方式有兩種:
上傳過程中將文件在服務器上寫為臨時文件,等全部寫完了(文件上傳完),將此臨時文件重命名為正式文件即可
如果中途上傳中斷過,下次上傳的時候根據當前臨時文件大小,作為在客戶端讀取文件的偏移量,從此位置繼續讀取文件數據塊,上傳到服務器從此偏移量繼續寫入文件即可
個上傳組件,需要具備的功能:
前后端分工:
前端:
后端:
整體思路比較簡單,拿到文件,保存文件唯一性標識,切割文件,分段上傳,每次上傳一段,根據唯一性標識判斷文件上傳進度,直到文件的全部片段上傳完畢
下面的內容都是偽代碼
讀取文件內容:
const input=document.querySelector('input');
input.addEventListener('change', function() {
var file=this.files[0];
});
可以使用md5實現文件的唯一性
const md5code=md5(file);
然后開始對文件進行分割
var reader=new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener("load", function(e) {
//每10M切割一段,這里只做一個切割演示,實際切割需要循環切割,
var slice=e.target.result.slice(0, 10*1024*1024);
});
h5上傳一個(一片)
const formdata=new FormData();
formdata.append('0', slice);
//這里是有一個坑的,部分設備無法獲取文件名稱,和文件類型,這個在最后給出解決方案
formdata.append('filename', file.filename);
var xhr=new XMLHttpRequest();
xhr.addEventListener('load', function() {
//xhr.responseText
});
xhr.open('POST', '');
xhr.send(formdata);
xhr.addEventListener('progress', updateProgress);
xhr.upload.addEventListener('progress', updateProgress);
function updateProgress(event) {
if (event.lengthComputable) {
//進度條
}
}
這里給出常見的圖片和視頻的文件類型判斷
function checkFileType(type, file, back) {
/**
* type png jpg mp4 ...
* file input.change=> this.files[0]
* back callback(boolean)
*/
var args=arguments;
if (args.length !=3) {
back(0);
}
var type=args[0]; // type='(png|jpg)' , 'png'
var file=args[1];
var back=typeof args[2]=='function' ? args[2] : function() {};
if (file.type=='') {
// 如果系統無法獲取文件類型,則讀取二進制流,對二進制進行解析文件類型
var imgType=[
'ff d8 ff', //jpg
'89 50 4e', //png
'0 0 0 14 66 74 79 70 69 73 6F 6D', //mp4
'0 0 0 18 66 74 79 70 33 67 70 35', //mp4
'0 0 0 0 66 74 79 70 33 67 70 35', //mp4
'0 0 0 0 66 74 79 70 4D 53 4E 56', //mp4
'0 0 0 0 66 74 79 70 69 73 6F 6D', //mp4
'0 0 0 18 66 74 79 70 6D 70 34 32', //m4v
'0 0 0 0 66 74 79 70 6D 70 34 32', //m4v
'0 0 0 14 66 74 79 70 71 74 20 20', //mov
'0 0 0 0 66 74 79 70 71 74 20 20', //mov
'0 0 0 0 6D 6F 6F 76', //mov
'4F 67 67 53 0 02', //ogg
'1A 45 DF A3', //ogg
'52 49 46 46 x x x x 41 56 49 20', //avi (RIFF fileSize fileType LIST)(52 49 46 46,DC 6C 57 09,41 56 49 20,4C 49 53 54)
];
var typeName=[
'jpg',
'png',
'mp4',
'mp4',
'mp4',
'mp4',
'mp4',
'm4v',
'm4v',
'mov',
'mov',
'mov',
'ogg',
'ogg',
'avi',
];
var sliceSize=/png|jpg|jpeg/.test(type) ? 3 : 12;
var reader=new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener("load", function(e) {
var slice=e.target.result.slice(0, sliceSize);
reader=null;
if (slice && slice.byteLength==sliceSize) {
var view=new Uint8Array(slice);
var arr=[];
view.forEach(function(v) {
arr.push(v.toString(16));
});
view=null;
var idx=arr.join(' ').indexOf(imgType);
if (idx > -1) {
back(typeName[idx]);
} else {
arr=arr.map(function(v) {
if (i > 3 && i < 8) {
return 'x';
}
return v;
});
var idx=arr.join(' ').indexOf(imgType);
if (idx > -1) {
back(typeName[idx]);
} else {
back(false);
}
}
} else {
back(false);
}
});
} else {
var type=file.name.match(/\.(\w+)$/)[1];
back(type);
}
}
調用方法如下
checkFileType('(mov|mp4|avi)',file,function(fileType){
// fileType=mp4,
// 如果file的類型不在枚舉之列,則返回false
});
上面上傳文件的一步,可以改成:
formdata.append('filename', md5code+'.'+fileType);
有了切割上傳后,也就有了文件唯一標識信息,斷點續傳變成了后臺的一個小小的邏輯判斷
后端主要做的內容為:根據前端傳給后臺的md5值,到服務器磁盤查找是否有之前未完成的文件合并信息(也就是未完成的半成品文件切片),取到之后根據上傳切片的數量,返回數據告訴前端開始從第幾節上傳
如果想要暫停切片的上傳,可以使用XMLHttpRequest的 abort方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>文件上傳</title>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script>
<style>
/* 自定義進度條樣式 */
.precent input[type=range] {
-webkit-appearance: none;
/*清除系統默認樣式*/
width: 7.8rem;
/* background: -webkit-linear-gradient(#ddd, #ddd) no-repeat, #ddd; */
/*設置左邊顏色為#61bd12,右邊顏色為#ddd*/
background-size: 75% 100%;
/*設置左右寬度比例*/
height: 0.6rem;
/*橫條的高度*/
border-radius: 0.4rem;
border: 1px solid #ddd;
box-shadow: 0 0 10px rgba(0,0,0,.125) inset ;
}
/*拖動塊的樣式*/
.precent input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
/*清除系統默認樣式*/
height: .9rem;
/*拖動塊高度*/
width: .9rem;
/*拖動塊寬度*/
background: #fff;
/*拖動塊背景*/
border-radius: 50%;
/*外觀設置為圓形*/
border: solid 1px #ddd;
/*設置邊框*/
}
</style>
</head>
<body>
<h1>大文件分片上傳測試</h1>
<div>
<input id="file" type="file" name="avatar" />
<div style="padding: 10px 0;">
<input id="submitBtn" type="button" value="提交" />
<input id="pauseBtn" type="button" value="暫停" />
</div>
<div class="precent">
<input type="range" value="0" /><span id="precentVal">0%</span>
</div>
</div>
<script type="text/javascript" src="./js/index.js"></script>
</body>
</html>
大文件分片上傳處理
$(document).ready(()=> {
const submitBtn=$('#submitBtn'); //提交按鈕
const precentDom=$(".precent input")[0]; // 進度條
const precentVal=$("#precentVal"); // 進度條值對應dom
const pauseBtn=$('#pauseBtn'); // 暫停按鈕
// 每個chunk的大小,設置為1兆
const chunkSize=1 * 1024 * 1024;
// 獲取slice方法,做兼容處理
const blobSlice=File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
// 對文件進行MD5加密(文件內容+文件標題形式)
const hashFile=(file)=> {
return new Promise((resolve, reject)=> {
const chunks=Math.ceil(file.size / chunkSize);
let currentChunk=0;
const spark=new SparkMD5.ArrayBuffer();
const fileReader=new FileReader();
function loadNext() {
const start=currentChunk * chunkSize;
const end=start + chunkSize >=file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
fileReader.onload=e=> {
spark.append(e.target.result); // Append array buffer
currentChunk +=1;
if (currentChunk < chunks) {
loadNext();
} else {
console.log('finished loading');
const result=spark.end();
// 通過內容和文件名稱進行md5加密
const sparkMd5=new SparkMD5();
sparkMd5.append(result);
sparkMd5.append(file.name);
const hexHash=sparkMd5.end();
resolve(hexHash);
}
};
fileReader.onerror=()=> {
console.warn('文件讀取失敗!');
};
loadNext();
}).catch(err=> {
console.log(err);
});
}
// 提交
submitBtn.on('click', async ()=> {
var pauseStatus=false;
var nowUploadNums=0
// 1.讀取文件
const fileDom=$('#file')[0];
const files=fileDom.files;
const file=files[0];
if (!file) {
alert('沒有獲取文件');
return;
}
// 2.設置分片參數屬性、獲取文件MD5值
const hash=await hashFile(file); //文件 hash
const blockCount=Math.ceil(file.size / chunkSize); // 分片總數
const axiosPromiseArray=[]; // axiosPromise數組
// 文件上傳
const uploadFile=()=> {
const start=nowUploadNums * chunkSize;
const end=Math.min(file.size, start + chunkSize);
// 構建表單
const form=new FormData();
// blobSlice.call(file, start, end)方法是用于進行文件分片
form.append('file', blobSlice.call(file, start, end));
form.append('index', nowUploadNums);
form.append('hash', hash);
// ajax提交 分片,此時 content-type 為 multipart/form-data
const axiosOptions={
onUploadProgress: e=> {
nowUploadNums++;
// 判斷分片是否上傳完成
if (nowUploadNums < blockCount) {
setPrecent(nowUploadNums, blockCount);
uploadFile(nowUploadNums)
} else {
// 4.所有分片上傳后,請求合并分片文件
axios.all(axiosPromiseArray).then(()=> {
setPrecent(blockCount, blockCount); // 全部上傳完成
axios.post('/file/merge_chunks', {
name: file.name,
total: blockCount,
hash
}).then(res=> {
console.log(res.data, file);
pauseStatus=false;
alert('上傳成功');
}).catch(err=> {
console.log(err);
});
});
}
},
};
// 加入到 Promise 數組中
if (!pauseStatus) {
axiosPromiseArray.push(axios.post('/file/upload', form, axiosOptions));
}
}
// 設置進度條
function setPrecent(now, total) {
var prencentValue=((now / total) * 100).toFixed(2)
precentDom.value=prencentValue
precentVal.text(prencentValue + '%')
precentDom.style.cssText=`background:-webkit-linear-gradient(top, #059CFA, #059CFA) 0% 0% / ${prencentValue}% 100% no-repeat`
}
// 暫停
pauseBtn.on('click', (e)=> {
pauseStatus=!pauseStatus;
e.currentTarget.value=pauseStatus ? '開始' : '暫停'
if (!pauseStatus) {
uploadFile(nowUploadNums)
}
})
uploadFile();
});
})
文件上傳和合并分片文件接口(node)
const Router=require('koa-router');
const multer=require('koa-multer');
const fs=require('fs-extra');
const path=require('path');
const router=new Router();
const { mkdirsSync }=require('../utils/dir');
const uploadPath=path.join(__dirname, 'upload');
const chunkUploadPath=path.join(uploadPath, 'temp');
const upload=multer({ dest: chunkUploadPath });
// 文件上傳接口
router.post('/file/upload', upload.single('file'), async (ctx, next)=> {
const { index, hash }=ctx.req.body;
const chunksPath=path.join(chunkUploadPath, hash, '/');
if(!fs.existsSync(chunksPath)) mkdirsSync(chunksPath);
fs.renameSync(ctx.req.file.path, chunksPath + hash + '-' + index);
ctx.status=200;
ctx.res.end('Success');
})
// 合并分片文件接口
router.post('/file/merge_chunks', async (ctx, next)=> {
const { name, total, hash }=ctx.request.body;
const chunksPath=path.join(chunkUploadPath, hash, '/');
const filePath=path.join(uploadPath, name);
// 讀取所有的chunks
const chunks=fs.readdirSync(chunksPath);
// 創建存儲文件
fs.writeFileSync(filePath, '');
if(chunks.length !==total || chunks.length===0) {
ctx.status=200;
ctx.res.end('切片文件數量不符合');
return;
}
for (let i=0; i < total; i++) {
// 追加寫入到文件中
fs.appendFileSync(filePath, fs.readFileSync(chunksPath + hash + '-' +i));
// 刪除本次使用的chunk
fs.unlinkSync(chunksPath + hash + '-' +i);
}
fs.rmdirSync(chunksPath);
// 文件合并成功,可以把文件信息進行入庫。
ctx.status=200;
ctx.res.end('Success');
})
當前的偽代碼,只是提供一個簡單的思路,想要把事情做到極致,我們還需要考慮到更多場景,比如
人生又何嘗不是如此,極致的人生體驗有無限可能,越是后面才發現越是精彩 ~_~
給大家分享我收集整理的各種學習資料,前端小白交學習流程,入門教程等回答-下面是學習資料參考。
前端學習交流、自學、學習資料等推薦 - 知乎
html實現本地文件的上傳,html實現文件上傳,html實現文件上傳解決方案,html實現文件上傳思路,html實現文件上傳實例,html實現文件上傳源碼,html實現文件分塊上傳,html實現文件分片上傳,html實現文件夾上傳,html實現文件加密上傳,
要求操作便利,一次選擇多個文件和文件夾進行上傳;
支持PC端全平臺操作系統,Windows,Linux,Mac
支持文件和文件夾的批量下載,斷點續傳。刷新頁面后繼續傳輸。關閉瀏覽器后保留進度信息。
支持文件夾批量上傳下載,服務器端保留文件夾層級結構,服務器端文件夾層級結構與本地相同。
支持大文件批量上傳(20G)和下載,同時需要保證上傳期間用戶電腦不出現卡死等體驗;
支持文件夾上傳,文件夾中的文件數量達到1萬個以上,且包含層級結構。
支持斷點續傳,關閉瀏覽器或刷新瀏覽器后仍然能夠保留進度。
支持文件夾結構管理,支持新建文件夾,支持文件夾目錄導航
交互友好,能夠及時反饋上傳的進度;
服務端的安全性,不因上傳文件功能導致JVM內存溢出影響其他功能使用;
最大限度利用網絡上行帶寬,提高上傳速度;
對于大文件的處理,無論是用戶端還是服務端,如果一次性進行讀取發送、接收都是不可取,很容易導致內存問題。所以對于大文件上傳,采用切塊分段上傳
從上傳的效率來看,利用多線程并發上傳能夠達到最大效率。
文件上傳頁面的前端可以選擇使用一些比較好用的上傳組件,例如百度的開源組件WebUploader,這些組件基本能滿足文件上傳的一些日常所需功能,如異步上傳文件,文件夾,拖拽式上傳,黏貼上傳,上傳進度監控,文件縮略圖,甚至是大文件斷點續傳,大文件秒傳。
在web項目中上傳文件夾現在已經成為了一個主流的需求。在OA,或者企業ERP系統中都有類似的需求。上傳文件夾并且保留層級結構能夠對用戶行成很好的引導,用戶使用起來也更方便。能夠提供更高級的應用支撐。
1.下載示例
https://gitee.com/xproer/up6-vue-cli
將up6組件復制到項目中
示例中已經包含此目錄
1.引入up6組件
2.配置接口地址
接口地址分別對應:文件初始化,文件數據上傳,文件進度,文件上傳完畢,文件刪除,文件夾初始化,文件夾刪除,文件列表
參考:http://www.ncmem.com/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de
3.處理事件
啟動測試
啟動成功
效果
數據庫
源碼工程文檔:https://drive.weixin.qq.com/s?k=ACoAYgezAAw1dWofra
源碼報價單:https://drive.weixin.qq.com/s?k=ACoAYgezAAwoiul8gl
OEM版報價單:https://drive.weixin.qq.com/s?k=ACoAYgezAAwuzp4W0a
產品源代碼:https://drive.weixin.qq.com/s?k=ACoAYgezAAwbdKCskc
授權碼生成器:https://drive.weixin.qq.com/s?k=ACoAYgezAAwTIcFph1
PRINGBOOT實現大文件分片上傳的方法,SPRINGBOOT大文件上傳、分片上傳、斷點續傳、秒傳的實現,SPRINGBOOT 整合 GRIDFS 、WEBUPLOADER實現大文件分塊上傳、斷點續傳、秒傳,SPRINGBOOT實現大文件上傳,斷點續傳,秒傳功能,SPRINGBOOT 中大文件(分片上傳)斷點續傳與極速秒傳功能的實現,SPRINGBOOT實現文件的上傳和下載,SPRINGMVC WEB項目大文件上傳下載解決方案,
SPRINGBOOT+VUE實現多文件上傳,SPRINGBOOT實現大文件上傳/下載(分片、斷點續傳),SPRINGBOOT+webuploader實現多文件上傳,SPRINGBOOT+js實現多文件上傳,SPRINGBOOT+VUE實現多文件上傳,網頁實現文件夾上傳斷點續傳,前端實現文件夾上傳斷點續傳,js實現文件夾上傳斷點續傳,JavaScript實現文件夾上傳斷點續傳,vue實現文件夾上傳斷點續傳,百度webuploader實現文件夾上傳斷點續傳,webuploader實現文件夾上傳斷點續傳,html5實現文件夾上傳斷點續傳
jsp實現文件夾上傳下載斷點續傳,jsp實現文件夾上傳斷點續傳解決方案,jsp實現文件夾上傳斷點續傳,JAVA 實現文件夾上傳(SPRINGBOOT 框架),SpringBoot實現HTTP大文件斷點續傳分片下載,JAVA以HTTP方式實現大文件分片,分段,分塊,分割下載。
SpringBoot主要是負責后端的業務邏輯和功能的實現。
客戶那邊是廣州的一家公司,也是做IT項目的。實際上對具體的技術實際沒有太大要求。
網上也考查和調研了一些組件,基本上都是調的HTML5的API,對HTML進行了一個基本的封閉,完全沒有自己的核心技術,研發的同事說不考慮這些免費的方案,一方面是沒有人維護,沒有技術支持,遇到問題基本上沒辦法解決,另一方面就是可擴展性差,用戶如果提了新需求也沒辦法來做擴展,沒法滿足。研發的同事說百度webuploader是免費坑人項目。連個人都找不到,領導說可以付費尋求技術支持,結果到他們官網找了半天才找到一個郵箱,發了郵件一年沒人回,我也是醉了。這種服務態度和服務質量,誰還敢在政府項目中用啊。這不是自己給自己找不痛快嗎?
需要支持斷點續傳,下載一半關閉電腦后,明天能夠繼續下載?;蛘哧P閉瀏覽器,或關閉網頁,或刷新網頁。
最好下載能夠支持加密下載,在下載過程中數據是加密的,下載完后自動解密,主要是有安全需求。
速度這塊的話,內網是希望跑滿的,百兆網絡的話12MB/S左右,千兆的話50MB/S左右。
需要支持文件夾下載,斷點續傳,下載保留層級結構。
網上搜到的SpringBoot的代碼不多,完整的不多,能用的也不多,基本上大部分的文章只是提供了少量的代碼,講一下思路,或者實現方案。
之前一般的做法都是使用HTML5來做的,大部都是傳文件的,傳文件夾的不多。網上能夠搜到的能用的不多。下來下的話,基本上都不能滿足用戶的 需求?;蛘哂脩粼谟玫臅r候總是會遇到這樣或那樣的問題,維護的話也很麻煩,用戶滿意度比較低。
公司有自已的產品,也是在我們自己的產品中集成這個功能,給客戶用,客戶還是比較多的,我們是做的行業軟件客戶每年都有一千多個。操作系統比較多,終端系統不統一,研發部門的同事用Windows,macOS多一些,后端運維同事用Linux多一些,主要就是centos和ubuntu,客戶那邊政府部門用信創國產化的多一些,有中標麒麟,銀河麒麟,統信UOS,龍芯,華為鯤鵬
我們主要是做政府項目,客戶也都是政府單位的,對用戶體驗要求比較高,要讓他們感覺用的方便,對穩定性要求比較高,基本上一年365天都不希望你出問題,對安全性要求也比較高,涉密了,信創國產化,不能連外網的,都是內網。兼容性要求比較高,有用WIN7+IE8的,也要兼容。
領導要求必須要提供技術支持,長期技術支持服務,長期的產品更新和維護服務,因為我們是做產品為主,給到客戶那邊也是長期維護,不是一錘子買賣,一般合作過都是5~10年的長期合作,客戶和領導都非常重視這一塊。
合作過程中也可能出現新的需求,或者二次開發,或者定制開發需求。需要滿足客戶需求,客戶那邊是什么環境我們必須要支持。
實際上核心也就是要求穩定,兼容性,可擴展性強,我們做的行業軟件,不是互聯網項目,不會經常變化,客戶那邊每天工作都是要用我們的產品。所以對產品穩定性要求很高。版本:6.5.40
代碼:https://gitee.com/xproer/up6-jsp-springboot/tree/6.5.40/
nosql示例
nosql示例不需要進行任何配置,可以直接訪問測試。
SQL示例
1.創建數據庫
2.配置數據庫連接
3.自動下載maven依賴
4.啟動項目
啟動成功
6.訪問及測試
默認頁面接口定義:
在瀏覽器中訪問:
數據表中的數據
相關問題:
1.javax.servlet.http.HttpServlet錯誤
2.項目無法發布到tomcat
3.md5計算完畢后卡住
4.服務器找不到config.json文件
5.Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile
相關參考:
文件保存位置
源碼工程文檔:https://drive.weixin.qq.com/s?k=ACoAYgezAAw1dWofra
源碼報價單:https://drive.weixin.qq.com/s?k=ACoAYgezAAwoiul8gl
OEM版報價單:https://drive.weixin.qq.com/s?k=ACoAYgezAAwuzp4W0a
控件源碼下載:https://drive.weixin.qq.com/s?k=ACoAYgezAAwbdKCskc
*請認真填寫需求信息,我們會在24小時內與您取得聯系。