日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

.vue文件_Spring Boot 2.x(十六):玩转vue文件上传

發布時間:2023/12/2 vue 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .vue文件_Spring Boot 2.x(十六):玩转vue文件上传 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

為什么使用Vue-Simple-Uploader

最近用到了Vue + Spring Boot來完成文件上傳的操作,踩了一些坑,對比了一些Vue的組件,發現了一個很好用的組件——Vue-Simple-Uploader,先附上gayhub的

,再說說為什么選用這個組件,對比vue-ant-design和element-ui的上傳組件,它能做到更多的事情,比如:

  • 可暫停、繼續上傳

  • 上傳隊列管理,支持最大并發上傳

  • 分塊上傳

  • 支持進度、預估剩余時間、出錯自動重試、重傳等操作

  • 支持“快傳”,通過文件判斷服務端是否已存在從而實現“快傳”

由于需求中需要用到斷點續傳,所以選用了這個組件,下面我會從最基礎的上傳開始說起:

單文件上傳、多文件上傳、文件夾上傳

Vue代碼:

?????????:options="uploadOptions1"
????????:autoStart="true"
????????class="uploader-app"
??????>
????????
????????選擇文件選擇文件夾
????????

該組件默認支持多文件上傳,這里我們從官方demo中粘貼過來這段代碼,然后在uploadOption1中配置上傳的路徑即可,其中uploader-btn 中設置directory屬性即可選擇文件夾進行上傳。

uploadOption1:

?uploadOptions1:?{
????????target:?"//localhost:18080/api/upload/single",//上傳的接口
????????testChunks:?false,?//是否開啟服務器分片校驗
????????fileParameterName:?"file",//默認的文件參數名
????????headers:?{},
????????query()?{},
????????categaryMap:?{?//用于限制上傳的類型
??????????image:?["gif",?"jpg",?"jpeg",?"png",?"bmp"]
????????}
}

在后臺的接口的編寫,我們為了方便,定義了一個chunk類用于接收組件默認傳輸的一些后面方便分塊斷點續傳的參數:

Chunk類

@Data
public?class?Chunk?implements?Serializable?{

????private?static?final?long?serialVersionUID?=?7073871700302406420L;

????private?Long?id;
????/**
?????*?當前文件塊,從1開始
?????*/
????private?Integer?chunkNumber;
????/**
?????*?分塊大小
?????*/
????private?Long?chunkSize;
????/**
?????*?當前分塊大小
?????*/
????private?Long?currentChunkSize;
????/**
?????*?總大小
?????*/
????private?Long?totalSize;
????/**
?????*?文件標識
?????*/
????private?String?identifier;
????/**
?????*?文件名
?????*/
????private?String?filename;
????/**
?????*?相對路徑
?????*/
????private?String?relativePath;
????/**
?????*?總塊數
?????*/
????private?Integer?totalChunks;
????/**
?????*?文件類型
?????*/
????private?String?type;

????/**
?????*?要上傳的文件
?????*/
????private?MultipartFile?file;
}

在編寫接口的時候,我們直接使用這個類作為參數去接收vue-simple-uploader傳來的參數即可,注意這里要使用POST來接收喲~

接口方法:

????@PostMapping("single")
????public?void?singleUpload(Chunk?chunk)?{
???????????????????//?獲取傳來的文件
????????MultipartFile?file?=?chunk.getFile();
????????//?獲取文件名
????????String?filename?=?chunk.getFilename();
????????try?{
????????????//?獲取文件的內容
????????????byte[]?bytes?=?file.getBytes();
????????????//?SINGLE_UPLOADER是我定義的一個路徑常量,這里的意思是,如果不存在該目錄,則去創建
????????????if?(!Files.isWritable(Paths.get(SINGLE_FOLDER)))?{
????????????????Files.createDirectories(Paths.get(SINGLE_FOLDER));
????????????}
????????????//?獲取上傳文件的路徑
????????????Path?path?=?Paths.get(SINGLE_FOLDER,filename);
????????????//?將字節寫入該文件
????????????Files.write(path,?bytes);
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}

這里需要注意一點,如果文件過大的話,Spring Boot后臺會報錯

org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException:?The?field?file?exceeds?its?maximum?permitted?size?of?1048576?bytes.

這時需要在application.yml中配置servlet的最大接收文件大小(默認大小是1MB和10MB)

spring:
??servlet:
????multipart:
??????max-file-size:?10MB?
??????max-request-size:?100MB

下面我們啟動項目,選擇需要上傳的文件就可以看到效果了~ 是不是很方便~ 但是同樣的事情其余的組件基本上也可以做到,之所以選擇這個,更多的是因為它可以支持斷點分塊上傳,實現上傳過程中斷網,再次聯網的話可以從斷點位置開始繼續秒傳~下面我們來看看斷點續傳是怎么玩的。

斷點分塊續傳

先說一下分塊斷點續傳的大概原理,我們在組件可以配置分塊的大小,大于該值的文件會被分割成若干塊兒去上傳,同時將該分塊的chunkNumber保存到數據庫(Mysql ?or Redis,這里我選擇的是Redis)

組件上傳的時候會攜帶一個identifier的參數(這里我采用的是默認的值,你也可以通過生成md5的方式來重新賦值參數),將identifier作為Redis的key,設置hashKey為”chunkNumber“,value是由每次上傳的chunkNumber組成的一個Set集合。

在將uploadOption中的testChunk的值設置為true之后,該組件會先發一個get請求,獲取到已經上傳的chunkNumber集合,然后在checkChunkUploadedByResponse方法中判斷是否存在該片段來進行跳過,發送post請求上傳分塊的文件。

每次上傳片段的時候,service層返回當前的集合大小,并與參數中的totalChunks進行對比,如果發現相等,就返回一個狀態值,來控制前端發出merge請求,將剛剛上傳的分塊合為一個文件,至此文件的斷點分塊上傳就完成了。

未命名文件

下面是對應的代碼~

Vue代碼:

????????:options="uploadOptions2"
????????:autoStart="true"
????????:files="files"
????????@file-added="onFileAdded2"
????????@file-success="onFileSuccess2"
????????@file-progress="onFileProgress2"
????????@file-error="onFileError2"
??????>
????????
????????分塊上傳
????????

校驗是否上傳過的代碼

?uploadOptions2:?{
????????target:?"//localhost:18080/api/upload/chunk",
????????chunkSize:?1?*?1024?*?1024,
????????testChunks:?true,
????????checkChunkUploadedByResponse:?function(chunk,?message)?{
??????????let?objMessage?=?JSON.parse(message);
??????????????//?獲取當前的上傳塊的集合
??????????let?chunkNumbers?=?objMessage.chunkNumbers;
??????????//?判斷當前的塊是否被該集合包含,從而判定是否需要跳過
??????????return?(chunkNumbers?||?[]).indexOf(chunk.offset?+?1)?>=?0;
????????},
????????headers:?{},
????????query()?{},
????????categaryMap:?{
??????????image:?["gif",?"jpg",?"jpeg",?"png",?"bmp"],
??????????zip:?["zip"],
??????????document:?["csv"]
????????}
}

上傳后成功的處理,判斷狀態來進行merge操作

onFileSuccess2(rootFile,?file,?response,?chunk)?{
??????let?res?=?JSON.parse(response);
??????????//?后臺報錯
??????if?(res.code?==?1)?{
????????return;
??????}
??????//?需要合并
??????if?(res.code?==?205)?{
????????//?發送merge請求,參數為identifier和filename,這個要注意需要和后臺的Chunk類中的參數名對應,否則會接收不到~
????????const?formData?=?new?FormData();
????????formData.append("identifier",?file.uniqueIdentifier);
????????formData.append("filename",?file.name);
????????merge(formData).then(response?=>?{});
??????}?
????},

判定是否存在的代碼,注意這里的是GET請求!!!

????@GetMapping("chunk")
????public?Map?checkChunks(Chunk?chunk)?{
????????return?uploadService.checkChunkExits(chunk);
????}

????@Override
????public?Map?checkChunkExits(Chunk?chunk)?{
????????Map?res?=?new?HashMap<>();
????????String?identifier?=?chunk.getIdentifier();if?(redisDao.existsKey(identifier))?{
????????????Set?chunkNumbers?=?(Set)?redisDao.hmGet(identifier,?"chunkNumberList");
????????????res.put("chunkNumbers",chunkNumbers);
????????}return?res;
????}

保存分塊,并保存數據到Redis的代碼。這里的是POST請求!!!

????@PostMapping("chunk")????
????public?Map?saveChunk(Chunk?chunk)?{
????????//?這里的操作和保存單段落的基本是一致的~
????????MultipartFile?file?=?chunk.getFile();
????????Integer?chunkNumber?=?chunk.getChunkNumber();
????????String?identifier?=?chunk.getIdentifier();
????????byte[]?bytes;
????????try?{
????????????bytes?=?file.getBytes();
????????????//?這里的不同之處在于這里進行了一個保存分塊時將文件名的按照-chunkNumber的進行保存
????????????Path?path?=?Paths.get(generatePath(CHUNK_FOLDER,?chunk));
????????????Files.write(path,?bytes);
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????????????????????//?這里進行的是保存到redis,并返回集合的大小的操作
????????Integer?chunks?=?uploadService.saveChunk(chunkNumber,?identifier);
????????Map?result?=?new?HashMap<>();//?如果集合的大小和totalChunks相等,判定分塊已經上傳完畢,進行merge操作if?(chunks.equals(chunk.getTotalChunks()))?{
????????????result.put("message","上傳成功!");
????????????result.put("code",?205);
????????}return?result;
????}/**
??????*?生成分塊的文件路徑
??????*/private?static?String?generatePath(String?uploadFolder,?Chunk?chunk)?{
????????StringBuilder?sb?=?new?StringBuilder();//?拼接上傳的路徑
????????sb.append(uploadFolder).append(File.separator).append(chunk.getIdentifier());//判斷uploadFolder/identifier?路徑是否存在,不存在則創建if?(!Files.isWritable(Paths.get(sb.toString())))?{try?{
????????????????Files.createDirectories(Paths.get(sb.toString()));
????????????}?catch?(IOException?e)?{
????????????????log.error(e.getMessage(),?e);
????????????}
????????}//?返回以?-?隔離的分塊文件,后面跟的chunkNumber方便后面進行排序進行mergereturn?sb.append(File.separator)
????????????????.append(chunk.getFilename())
????????????????.append("-")
????????????????.append(chunk.getChunkNumber()).toString();
????}/**
?????*?保存信息到Redis
?????*/public?Integer?saveChunk(Integer?chunkNumber,?String?identifier)?{//?獲取目前的chunkList
????????Set?oldChunkNumber?=?(Set)?redisDao.hmGet(identifier,?"chunkNumberList");//?如果獲取為空,則新建Set集合,并將當前分塊的chunkNumber加入后存到Redisif?(Objects.isNull(oldChunkNumber))?{
????????????Set?newChunkNumber?=?new?HashSet<>();
????????????newChunkNumber.add(chunkNumber);
????????????redisDao.hmSet(identifier,?"chunkNumberList",?newChunkNumber);//?返回集合的大小return?newChunkNumber.size();
????????}?else?{//?如果不為空,將當前分塊的chunkNumber加到當前的chunkList中,并存入Redis
????????????oldChunkNumber.add(chunkNumber);
????????????redisDao.hmSet(identifier,?"chunkNumberList",?oldChunkNumber);//?返回集合的大小return?oldChunkNumber.size();
????????}
????}

合并的后臺代碼:

????@PostMapping("merge")
????public?void?mergeChunks(Chunk?chunk)?{
????????String?fileName?=?chunk.getFilename();
????????uploadService.mergeFile(fileName,CHUNK_FOLDER?+?File.separator?+?chunk.getIdentifier());
????}

????@Override
????public?void?mergeFile(String?fileName,?String?chunkFolder)?{
????????try?{
????????????//?如果合并后的路徑不存在,則新建
????????????if?(!Files.isWritable(Paths.get(mergeFolder)))?{
????????????????Files.createDirectories(Paths.get(mergeFolder));
????????????}
????????????//?合并的文件名
????????????String?target?=?mergeFolder?+?File.separator?+?fileName;
????????????//?創建文件
????????????Files.createFile(Paths.get(target));
????????????//?遍歷分塊的文件夾,并進行過濾和排序后以追加的方式寫入到合并后的文件
????????????Files.list(Paths.get(chunkFolder))
?????????????????????//過濾帶有"-"的文件
????????????????????.filter(path?->?path.getFileName().toString().contains("-"))
?????????????????????//按照從小到大進行排序
????????????????????.sorted((o1,?o2)?->?{
????????????????????????String?p1?=?o1.getFileName().toString();
????????????????????????String?p2?=?o2.getFileName().toString();
????????????????????????int?i1?=?p1.lastIndexOf("-");
????????????????????????int?i2?=?p2.lastIndexOf("-");
????????????????????????return?Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
????????????????????})
????????????????????.forEach(path?->?{
????????????????????????try?{
????????????????????????????//以追加的形式寫入文件
????????????????????????????Files.write(Paths.get(target),?Files.readAllBytes(path),?StandardOpenOption.APPEND);
????????????????????????????//合并后刪除該塊
????????????????????????????Files.delete(path);
????????????????????????}?catch?(IOException?e)?{
????????????????????????????e.printStackTrace();
????????????????????????}
????????????????????});
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}

至此,我們的斷點續傳就完美結束了,完整的代碼我已經上傳到gayhub~,歡迎 star fork pr~ (后面還會把博文也上傳到gayhub喲~)

前端:https://github.com/viyog/viboot-front

后臺:https://github.com/viyog/viboot

寫在后面

最近由于家庭+工作忙昏了頭,鴿了這么久很是抱歉,從這周開始恢復更新,同時本人在準備往大數據轉型,后續會出一系列的Java轉型大數據的學習筆記,包括Java基礎系列的深入解讀和重寫,同時Spring Boot系列還會一直保持連載,不過可能不會每周都更,我會把目前使用Spring Boot中遇到的問題和坑寫一寫,謝謝一直支持我的粉絲們~愛你們~

我從沒見過一個不孤獨的人會發出耀眼的光芒

純原創技術公號

作者:Vi.Young

掃碼獲取更多干貨

?博客園:Vi的技術博客

總結

以上是生活随笔為你收集整理的.vue文件_Spring Boot 2.x(十六):玩转vue文件上传的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。