中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

Java大文件(jian)(jian)上傳(chuan)、分片上傳(chuan)、多文件(jian)(jian)上傳(chuan)、斷點續傳(chuan)、上傳(chuan)文件(jian)(jian)minio、分片上傳(chuan)minio等解決(jue)方案

  • 上傳說明

           文件上傳花樣百出,根據不同場景使用不同方案進行實現尤為必要。通常開發過程中,文件較小,直接將文件轉化為字節流上傳到服務器,但是文件較大時,用普通的方法上傳,顯然效果不是很好,當文件上傳一半中斷再次上傳時,發現需要重新開始,這種體驗不是很爽,下面介紹幾種好一點兒的上傳方式。

這(zhe)里(li)講講如(ru)何在Spring boot 編(bian)寫上傳(chuan)代碼(ma),如(ru)有問題可以在下留言,我并在文章末尾附上Java上傳(chuan)源碼(ma)供(gong)大家下載。

    • 分片上傳

      分片上傳,就是將所要上傳的文件,按照一定的大小,將整個文件分
隔成多個數據塊(我們稱之為Part)來進行分別上傳,上傳完之后再
由服(fu)務端對所有上傳的(de)文件進(jin)行匯總整合成原始的(de)文件。

    • 斷點續傳

          斷點續傳是在下載/上傳時,將下載/上傳任務(一個文件或一個壓縮
包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳/下載,
如果碰到網絡故障,可以從已經上傳/下載的部分開始繼續上傳/下載
未完成的(de)部分,而沒有必要從(cong)頭(tou)開始上傳/下載。

  • Redis啟動安裝

Redis安裝包分為 Windows 版和 Linux 版:
Windows版下載地址://github.com/microsoftarchive/redis/releases
Linux版下載地址: //download.redis.io/releases/
我當前使用(yong)的Windows版本:

 

 

  • minio下載啟動

windows版本可以參考我之前的文檔:window10安(an)(an)裝minio_minio windows安(an)(an)裝-CSDN博客

啟動會提示:

 

以上(shang)是密碼(ma)設置問題需要修改(gai)如下:

set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678

啟動成功后會輸出相應地址

  • 上傳后端Java代碼

   后(hou)端采用(yong)Spring boot項目結構,主要代碼如下:

  1   /**
  2      * 單(dan)文件上傳
  3      * 直接將傳(chuan)入的文件通過io流形式直接寫入(服務器(qi))指定路徑下(xia)
  4      *
  5      * @param file 上傳的文件
  6      * @return
  7      */
  8     @Override
  9     public ResultEntity<Boolean> singleFileUpload(MultipartFile file) {
 10         //實際情況下,這些(xie)路徑都應該(gai)是服務器上面存儲文件的(de)路徑
 11         String filePath = System.getProperty("user.dir") + "\\file\\";
 12         File dir = new File(filePath);
 13         if (!dir.exists()) dir.mkdir();
 14  
 15         if (file == null) {
 16             return ResultEntity.error(false, "上傳文件為空!");
 17         }
 18         InputStream fileInputStream = null;
 19         FileOutputStream fileOutputStream = null;
 20         try {
 21             String filename = file.getOriginalFilename();
 22             fileOutputStream = new FileOutputStream(filePath + filename);
 23             fileInputStream = file.getInputStream();
 24  
 25             byte[] buf = new byte[1024 * 8];
 26             int length;
 27             while ((length = fileInputStream.read(buf)) != -1) {//讀取fis文件輸入字節流里(li)面的數據
 28                 fileOutputStream.write(buf, 0, length);//通(tong)過fos文(wen)件輸出字(zi)節(jie)流寫出去
 29             }
 30             log.info("單文件上傳完成!文件路徑:{},文件名:{},文件大小:{}", filePath, filename, file.getSize());
 31             return ResultEntity.success(true, "單文件上傳完成!");
 32         } catch (IOException e) {
 33             return ResultEntity.error(true, "單文件上傳失敗!");
 34         } finally {
 35             try {
 36                 if (fileOutputStream != null) {
 37                     fileOutputStream.close();
 38                     fileOutputStream.flush();
 39                 }
 40                 if (fileInputStream != null) {
 41                     fileInputStream.close();
 42                 }
 43             } catch (Exception e) {
 44                 e.printStackTrace();
 45             }
 46         }
 47     }
 48  
 49     /**
 50      * 多文(wen)件上傳
 51      * 直接(jie)將傳入的多(duo)個(ge)文件(jian)通過io流形式直接(jie)寫入(服務器)指定路徑下(xia)
 52      * 寫(xie)入(ru)指定路(lu)徑(jing)下是(shi)通過多(duo)線程進(jin)行(xing)文件(jian)寫(xie)入(ru)的,文件(jian)寫(xie)入(ru)線程執行(xing)功能就和上(shang)面單文件(jian)寫(xie)入(ru)是(shi)一樣的
 53      *
 54      * @param files 上(shang)傳的所有文件
 55      * @return
 56      */
 57     @Override
 58     public ResultEntity<Boolean> multipleFileUpload(MultipartFile[] files) {
 59         //實際(ji)情況下,這些路徑都(dou)應該(gai)是服務器上面存(cun)儲文件的(de)路徑
 60         String filePath = System.getProperty("user.dir") + "\\file\\";
 61         File dir = new File(filePath);
 62         if (!dir.exists()) dir.mkdir();
 63  
 64         if (files.length == 0) {
 65             return ResultEntity.error(false, "上傳文件為空!");
 66         }
 67         ArrayList<String> uploadFiles = new ArrayList<>();
 68         try {
 69  
 70             ArrayList<Future<String>> futures = new ArrayList<>();
 71             //使用多線(xian)程來(lai)完成對每個文件的寫入
 72             for (MultipartFile file : files) {
 73                 futures.add(partMergeTask.submit(new MultipleFileTaskExecutor(filePath, file)));
 74             }
 75  
 76             //這里主要用(yong)于監(jian)聽各個文件寫入(ru)線程是否執行結(jie)束
 77             int count = 0;
 78             while (count != futures.size()) {
 79                 for (Future<String> future : futures) {
 80                     if (future.isDone()) {
 81                         uploadFiles.add(future.get());
 82                         count++;
 83                     }
 84                 }
 85                 Thread.sleep(1);
 86             }
 87             log.info("多文件上傳完成!文件路徑:{},文件信息:{}", filePath, uploadFiles);
 88             return ResultEntity.success(true, "多文件上傳完成!");
 89         } catch (Exception e) {
 90             log.error("多文件分片上傳失敗!", e);
 91             return ResultEntity.error(true, "多文件上傳失敗!");
 92         }
 93  
 94     }
 95  
 96     /**
 97      * 單文件分片上傳(chuan)
 98      * 直接將傳入(ru)的文件(jian)分(fen)片通過(guo)io流形式寫入(ru)(服務器(qi))指定臨時(shi)路徑下
 99      * 然后判斷(duan)是否分片都(dou)上傳完成(cheng),如果所有分片都(dou)上傳完成(cheng)的話,就把臨時路徑(jing)下的分片文件(jian)通過(guo)流形式讀入合并并從新寫入到(dao)(服務器(qi))指(zhi)定文件(jian)路徑(jing)下
100      * 最后刪除臨(lin)(lin)時文件(jian)(jian)和臨(lin)(lin)時文件(jian)(jian)夾,臨(lin)(lin)時文件(jian)(jian)夾是(shi)通過文件(jian)(jian)的uuid進行(xing)命(ming)名的
101      *
102      * @param filePart  分(fen)片文件
103      * @param partIndex 當前(qian)分片值
104      * @param partNum   所有分片數(shu)
105      * @param fileName  當前(qian)文件名稱
106      * @param fileUid   當(dang)前文件uuid
107      * @return
108      */
109     @Override
110     public ResultEntity<Boolean> singleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {
111         //實際情況下,這(zhe)些路徑(jing)都應(ying)該是服務(wu)器上面存儲文件的路徑(jing)
112         String filePath = System.getProperty("user.dir") + "\\file\\";//文(wen)件存(cun)放(fang)路(lu)徑
113         String tempPath = filePath + "temp\\" + fileUid;//臨時文件存放路徑
114         File dir = new File(tempPath);
115         if (!dir.exists()) dir.mkdirs();
116  
117         //生成一個臨時文(wen)件(jian)名(ming)
118         String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";
119         try {
120             //將分(fen)片存儲到臨時文(wen)件(jian)夾中
121             filePart.transferTo(new File(tempFileNamePath));
122  
123             File tempDir = new File(tempPath);
124             File[] tempFiles = tempDir.listFiles();
125  
126             one:
127             if (partNum.equals(Objects.requireNonNull(tempFiles).length)) {
128                 //需(xu)要校驗一下,表示已(yi)有異步程序正(zheng)在合并了;如(ru)果是(shi)分布式(shi)這個校驗可以加入redis的(de)分布式(shi)鎖來完成(cheng)
129                 if (isMergePart.get(fileUid) != null) {
130                     break one;
131                 }
132                 isMergePart.put(fileUid, tempFiles.length);
133                 System.out.println("所有分片上傳完成,預計總分片:" + partNum + "; 實際總分片:" + tempFiles.length);
134  
135                 FileOutputStream fileOutputStream = new FileOutputStream(filePath + fileName);
136                 //這里如果分片很(hen)多的情(qing)況(kuang)下,可以采用多線程來執(zhi)行(xing)
137                 for (int i = 0; i < partNum; i++) {
138                     //讀取(qu)分(fen)片數據,進行分(fen)片合(he)并
139                     FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + fileName + "_" + i + ".part");
140                     byte[] buf = new byte[1024 * 8];//8MB
141                     int length;
142                     while ((length = fileInputStream.read(buf)) != -1) {//讀取fis文件輸入字節流(liu)里面的數據
143                         fileOutputStream.write(buf, 0, length);//通過(guo)fos文(wen)件(jian)輸出字節流寫(xie)出去
144                     }
145                     fileInputStream.close();
146                 }
147                 fileOutputStream.flush();
148                 fileOutputStream.close();
149  
150                 // 刪(shan)除臨時(shi)文(wen)件夾里面的分片文(wen)件 如果使用流操作且沒有關(guan)閉輸入流,可能(neng)導致刪(shan)除失敗
151                 for (int i = 0; i < partNum; i++) {
152                     boolean delete = new File(tempPath + "\\" + fileName + "_" + i + ".part").delete();
153                     File file = new File(tempPath + "\\" + fileName + "_" + i + ".part");
154                 }
155                 //在刪除對(dui)應的臨時文件夾
156                 if (Objects.requireNonNull(tempDir.listFiles()).length == 0) {
157                     tempDir.delete();
158                 }
159                 isMergePart.remove(fileUid);
160             }
161  
162         } catch (Exception e) {
163             log.error("單文件分片上傳失敗!", e);
164             return ResultEntity.error(false, "單文件分片上傳失敗");
165         }
166         //通過返(fan)回成功(gong)的(de)分片值(zhi),來驗證(zheng)分片是否(fou)有丟失
167         return ResultEntity.success(true, partIndex.toString());
168     }
169  
170     /**
171      * 多文件分片(pian)上(shang)傳
172      * 先(xian)將所(suo)有文(wen)件(jian)分(fen)片讀入到(服務器)指定臨時路徑下(xia),每個文(wen)件(jian)的(de)(de)分(fen)片文(wen)件(jian)的(de)(de)臨時文(wen)件(jian)夾都是已(yi)文(wen)件(jian)的(de)(de)uuid進行命名(ming)的(de)(de)
173      * 然后判斷對(dui)已(yi)經上傳所有分片的文(wen)件進行(xing)(xing)合并(bing),此(ci)處是通過多線(xian)程對(dui)每一個文(wen)件的分片文(wen)件進行(xing)(xing)合并(bing)的
174      * 最后(hou)對已經(jing)合(he)并完成的(de)分片(pian)臨時文件和文件夾(jia)進行刪除
175      *
176      * @param filePart  分(fen)片(pian)文件
177      * @param partIndex 當前分片(pian)值
178      * @param partNum   總分片數
179      * @param fileName  當(dang)前文件(jian)名稱
180      * @param fileUid   當前文(wen)件uuid
181      * @return
182      */
183     @Override
184     public ResultEntity<String> multipleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {
185         //實際情況下(xia),這些路(lu)徑都應該(gai)是服(fu)務器上面存儲文件(jian)的路(lu)徑
186         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路徑
187         String tempPath = filePath + "temp\\" + fileUid;//臨時文件存放路徑
188         File dir = new File(tempPath);
189         if (!dir.exists()) dir.mkdirs();
190         //生成一個臨時文件名
191         String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";
192         try {
193             filePart.transferTo(new File(tempFileNamePath));
194  
195             File tempDir = new File(tempPath);
196             File[] tempFiles = tempDir.listFiles();
197             //如果臨時文件(jian)夾中分(fen)(fen)片數(shu)(shu)量和實際分(fen)(fen)片數(shu)(shu)量一致的時候,就需要進(jin)行(xing)分(fen)(fen)片合(he)并(bing)
198             one:
199             if (partNum.equals(tempFiles.length)) {
200                 //需(xu)要校(xiao)驗一下,表示已有異步程(cheng)序正在合(he)并了;如果是分(fen)布式這個校(xiao)驗可以(yi)加入(ru)redis的分(fen)布式鎖來完成
201                 if (isMergePart.get(fileUid) != null) {
202                     break one;
203                 }
204                 isMergePart.put(fileUid, tempFiles.length);
205                 System.out.println(fileName + ":所有分片上傳完成,預計總分片:" + partNum + "; 實際總分片:" + tempFiles.length);
206  
207                 //使用多線程來完成對(dui)每個文件的合并
208                 Future<Integer> submit = partMergeTask.submit(new PartMergeTaskExecutor(filePath, tempPath, fileName, partNum));
209                 System.out.println("上傳文件名:" + fileName + "; 總大小:" + submit.get());
210                 isMergePart.remove(fileUid);
211             }
212         } catch (Exception e) {
213             log.error("{}:多文件分片上傳失敗!", fileName, e);
214             return ResultEntity.error("", "多文件分片上傳失敗");
215         }
216         //通過返回成功的分片(pian)值(zhi),來驗證分片(pian)是(shi)否有丟失(shi)
217         return ResultEntity.success(partIndex.toString(), fileUid);
218     }
219  
220     /**
221      * 多文件(jian)(分片)秒(miao)傳(chuan)
222      * 通過對比已有的文(wen)件分片md5值和需要(yao)上傳(chuan)文(wen)件分片的MD5值,
223      * 在(zai)文(wen)件(jian)分片合并(bing)的(de)時候(hou),對已有的(de)文(wen)件(jian)進行地(di)址索引,對沒(mei)有的(de)文(wen)件(jian)進行臨時文(wen)件(jian)寫入
224      * 最后合并的時候根據不同(tong)的文件(jian)分片進行文件(jian)讀取寫入
225      *
226      * @param filePart  上(shang)傳沒有的分(fen)片文件
227      * @param fileInfo  當前分片文件相關信息
228      * @param fileOther 已(yi)存在文件分片相關信息
229      * @return
230      */
231     @Override
232     public ResultEntity<String> multipleFilePartFlashUpload(MultipartFile filePart, String fileInfo, String fileOther) {
233         DiskFileIndexVo upFileInfo = JSONObject.parseObject(fileInfo, DiskFileIndexVo.class);
234         List<DiskFileIndexVo> notUpFileInfoList = JSON.parseArray(fileOther, DiskFileIndexVo.class);
235         //實(shi)際情況下,這些路(lu)徑都應該是服務器上面存儲(chu)文件(jian)的(de)路(lu)徑
236         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存(cun)放(fang)路徑
237         //正常情況下(xia)(xia),這(zhe)(zhe)個臨時(shi)文(wen)(wen)件(jian)也應(ying)該放入(服(fu)務(wu)器)非臨時(shi)文(wen)(wen)件(jian)夾中,這(zhe)(zhe)樣方便(bian)下(xia)(xia)次(ci)其他文(wen)(wen)件(jian)上(shang)傳查(cha)找是否曾經(jing)上(shang)傳過類似的
238         //當前demo是單(dan)獨存放在(zai)臨時文件(jian)夾中(zhong),文件(jian)合并(bing)完成之后直接刪(shan)除的
239         String tempPath = filePath + "temp\\" + upFileInfo.getFileUid();//臨時(shi)文(wen)件存放路徑
240  
241         File dir = new File(tempPath);
242         if (!dir.exists()) dir.mkdirs();
243         //生成一個臨時文件名
244         String tempFileNamePath = tempPath + "\\" + upFileInfo.getFileName() + "_" + upFileInfo.getPartIndex() + ".part";
245  
246         try {
247             filePart.transferTo(new File(tempFileNamePath));
248  
249             File tempDir = new File(tempPath);
250             File[] tempFiles = tempDir.listFiles();
251             notUpFileInfoList = notUpFileInfoList.stream().filter(e ->
252                     upFileInfo.getFileUid().equals(e.getFileUid())).collect(Collectors.toList());
253             //如果臨時文(wen)件夾中(zhong)分(fen)片數量和(he)實際分(fen)片數量一致的時候,就需要(yao)進(jin)行分(fen)片合并
254             one:
255             if ((upFileInfo.getPartNum() - notUpFileInfoList.size()) == tempFiles.length) {
256                 //需(xu)要校驗一(yi)下(xia),表示已有異(yi)步程序正在合(he)并了;如果是分布式這個校驗可(ke)以加入redis的(de)分布式鎖來完(wan)成(cheng)
257                 if (isMergePart.get(upFileInfo.getFileUid()) != null) {
258                     break one;
259                 }
260                 isMergePart.put(upFileInfo.getFileUid(), tempFiles.length);
261                 System.out.println(upFileInfo.getFileName() + ":所有分片上傳完成,預計總分片:" + upFileInfo.getPartNum()
262                         + "; 實際總分片:" + tempFiles.length + "; 已存在分片數:" + notUpFileInfoList.size());
263  
264                 //使(shi)用多線程(cheng)來完成對每個(ge)文件的合并
265                 Future<Integer> submit = partMergeTask.submit(
266                         new PartMergeFlashTaskExecutor(filePath, upFileInfo, notUpFileInfoList));
267                 isMergePart.remove(upFileInfo.getFileUid());
268             }
269         } catch (Exception e) {
270             log.error("{}:多文件(分片)秒傳失敗!", upFileInfo.getFileName(), e);
271             return ResultEntity.error("", "多文件(分片)秒傳失敗!");
272         }
273         //通過返(fan)回成功的分(fen)片(pian)(pian)值,來驗證分(fen)片(pian)(pian)是否(fou)有丟失
274         return ResultEntity.success(upFileInfo.getPartIndex().toString(), upFileInfo.getFileUid());
275     }
276  
277     /**
278      * 根據傳入需(xu)要上傳的(de)(de)文件片段的(de)(de)md5值(zhi)(zhi)來對比服務器中的(de)(de)文件的(de)(de)md5值(zhi)(zhi),將已有對應的(de)(de)md5值(zhi)(zhi)的(de)(de)文件過濾出(chu)來,
279      * 通(tong)知前端或(huo)者自行出(chu)來(lai)這些文(wen)(wen)件(jian),即(ji)為不(bu)需要上(shang)傳的文(wen)(wen)件(jian)分片,并將已有的文(wen)(wen)件(jian)分片地址索引返回給前端進行出(chu)來(lai)
280      *
281      * @param upLoadFileListMd5 原本需要上(shang)傳文件的索(suo)引分片信息
282      * @return
283      */
284     @Override
285     public ResultEntity<List<DiskFileIndexVo>> checkDiskFile(List<DiskFileIndexVo> upLoadFileListMd5) {
286         List<DiskFileIndexVo> notUploadFile;
287         try {
288             //后端(duan)服(fu)務(wu)器已經存在的分片md5值集合
289             List<DiskFileIndexVo> diskFileMd5IndexList = diskFileIndexVos;
290  
291             notUploadFile = upLoadFileListMd5.stream().filter(uf -> diskFileMd5IndexList.stream().anyMatch(
292                     df -> {
293                         if (df.getFileMd5().equals(uf.getFileMd5())) {
294                             uf.setFileIndex(df.getFileName());//不需(xu)要上傳文件的地址索引
295                             return true;
296                         }
297                         return false;
298                     })).collect(Collectors.toList());
299             log.info("過濾出不需要上傳的文件分片:{}", notUploadFile);
300         } catch (Exception e) {
301             log.error("上傳文件檢測異常!", e);
302             return ResultEntity.error("上傳文件檢測異常!");
303         }
304         return ResultEntity.success(notUploadFile);
305     }
306  
307     /**
308      * 根據文件uuid(md5生成的(de))來判斷此文件在服務(wu)器中是否未(wei)上傳完(wan)整,
309      * 如(ru)果沒(mei)上傳完整,則(ze)返(fan)回(hui)相(xiang)關上傳進度等信息
310      *
311      * @param pointFileIndexVo
312      * @return
313      */
314     @Override
315     public ResultEntity<PointFileIndexVo> checkUploadFileIndex(PointFileIndexVo pointFileIndexVo) {
316         try {
317             List<String> list = uploadProgress.get(pointFileIndexVo.getFileMd5());
318             if (list == null) list = new ArrayList<>();
319             pointFileIndexVo.setParts(list);
320             System.out.println("已上傳部分:" + list);
321             return ResultEntity.success(pointFileIndexVo);
322         } catch (Exception e) {
323             log.error("上傳文件檢測異常!", e);
324             return ResultEntity.error("上傳文件檢測異常!");
325         }
326     }
327  
328     /**
329      * 單(dan)文件(分片(pian))斷點(dian)上傳
330      *
331      * @param filePart 需要上傳的分片文件
332      * @param fileInfo 當前需要上傳的分(fen)片文(wen)件(jian)信息,如uuid,文(wen)件(jian)名,文(wen)件(jian)總分(fen)片數量等
333      * @return
334      */
335     @Override
336     public ResultEntity<String> singleFilePartPointUpload(MultipartFile filePart, String fileInfo) {
337         PointFileIndexVo pointFileIndexVo = JSONObject.parseObject(fileInfo, PointFileIndexVo.class);
338         //實際情況(kuang)下,這些路徑都(dou)應該是服務器上面存(cun)儲(chu)文(wen)件的路徑
339         String filePath = System.getProperty("user.dir") + "\\file\\";//文件(jian)存放路徑
340         String tempPath = filePath + "temp\\" + pointFileIndexVo.getFileMd5();//臨時文件存放路徑
341         File dir = new File(tempPath);
342         if (!dir.exists()) dir.mkdirs();
343  
344         //生成一個臨時文(wen)件名
345         String tempFileNamePath = tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + pointFileIndexVo.getPartIndex() + ".part";
346         try {
347             //將分片存儲到(dao)臨時文件夾(jia)中
348             filePart.transferTo(new File(tempFileNamePath));
349  
350             List<String> partIndex = uploadProgress.get(pointFileIndexVo.getFileMd5());
351             if (Objects.isNull(partIndex)) {
352                 partIndex = new ArrayList<>();
353             }
354             partIndex.add(pointFileIndexVo.getPartIndex().toString());
355             uploadProgress.put(pointFileIndexVo.getFileMd5(), partIndex);
356  
357             File tempDir = new File(tempPath);
358             File[] tempFiles = tempDir.listFiles();
359  
360             one:
361             if (pointFileIndexVo.getPartNum().equals(Objects.requireNonNull(tempFiles).length)) {
362                 //需(xu)要校驗一下(xia),表(biao)示已有(you)異步程序正在合并了;如果是分布(bu)式(shi)這(zhe)個校驗可以加入(ru)redis的分布(bu)式(shi)鎖來完成
363                 if (isMergePart.get(pointFileIndexVo.getFileMd5()) != null) {
364                     break one;
365                 }
366                 isMergePart.put(pointFileIndexVo.getFileMd5(), tempFiles.length);
367                 System.out.println("所有分片上傳完成,預計總分片:" + pointFileIndexVo.getPartNum() + "; 實際總分片:" + tempFiles.length);
368                 //讀取(qu)分片數據(ju),進行分片合并
369                 FileOutputStream fileOutputStream = new FileOutputStream(filePath + pointFileIndexVo.getFileName());
370                 //這里如果分片很多(duo)的情況下,可以(yi)采用多(duo)線程來執行
371                 for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) {
372                     FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part");
373                     byte[] buf = new byte[1024 * 8];//8MB
374                     int length;
375                     while ((length = fileInputStream.read(buf)) != -1) {//讀(du)取fis文件(jian)輸入字節(jie)流里(li)面的數據
376                         fileOutputStream.write(buf, 0, length);//通(tong)過fos文件輸出字(zi)節流寫出去
377                     }
378                     fileInputStream.close();
379                 }
380                 fileOutputStream.flush();
381                 fileOutputStream.close();
382  
383                 // 刪除臨(lin)時文件(jian)夾(jia)里面的分片文件(jian) 如果使用(yong)流操作(zuo)且沒有關閉輸入流,可能(neng)導致刪除失敗
384                 for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) {
385                     boolean delete = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part").delete();
386                     File file = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part");
387                 }
388                 //在刪除(chu)對應的臨時文件夾
389                 if (Objects.requireNonNull(tempDir.listFiles()).length == 0) {
390                   tempDir.delete();
391                 }
392                 isMergePart.remove(pointFileIndexVo.getFileMd5());
393                 uploadProgress.remove(pointFileIndexVo.getFileMd5());
394             }
395  
396         } catch (Exception e) {
397             log.error("單文件分片上傳失敗!", e);
398             return ResultEntity.error(pointFileIndexVo.getFileMd5(), "單文件分片上傳失敗");
399         }
400         //通過(guo)返回成功的分片值,來(lai)驗證分片是否有丟失
401         return ResultEntity.success(pointFileIndexVo.getFileMd5(), pointFileIndexVo.getPartIndex().toString());
402     }
403  
404     /**
405      * 獲取(服務器)指定文件存(cun)儲(chu)路徑下所有(you)文件MD5值
406      * 實際情(qing)況下,每(mei)一(yi)個文(wen)件(jian)的(de)md5值都(dou)是單獨(du)保(bao)存在數據庫或者其他存儲機制(zhi)中的(de),
407      * 不需(xu)要每次都去讀(du)取(qu)文(wen)件然(ran)后獲取(qu)md5值(zhi),這樣多次io讀(du)取(qu)很耗性能
408      *
409      * @return
410      * @throws Exception
411      */
412     @Bean
413     private List<DiskFileIndexVo> getDiskFileMd5Index() throws Exception {
414         String filePath = System.getProperty("user.dir") + "\\file\\part\\";
415         File saveFileDir = new File(filePath);
416         if (!saveFileDir.exists()) saveFileDir.mkdirs();
417  
418         List<DiskFileIndexVo> diskFileIndexVoList = new ArrayList<>();
419         File[] listFiles = saveFileDir.listFiles();
420         if (listFiles == null) return diskFileIndexVoList;
421  
422         for (File listFile : listFiles) {
423             String fileName = listFile.getName();
424             FileInputStream fileInputStream = new FileInputStream(filePath + fileName);
425             String md5DigestAsHex = DigestUtils.md5DigestAsHex(fileInputStream);
426  
427             DiskFileIndexVo diskFileIndexVo = new DiskFileIndexVo();
428             diskFileIndexVo.setFileName(fileName);
429             diskFileIndexVo.setFileMd5(md5DigestAsHex);
430             diskFileIndexVoList.add(diskFileIndexVo);
431             fileInputStream.close();
432         }
433  
434         diskFileIndexVos = diskFileIndexVoList;
435         log.info("服務器磁盤所有文件 {}", diskFileIndexVoList);
436         return diskFileIndexVoList;
437     }

代碼結構圖:

 

  • 前端代碼

整體的過程如下
前端將文件按照百分比進行計算,每次上傳文件的百分之一(文件分片),給文件分片做上序號及文件uuid,然后在循環里面對文件片段上傳的時候在將當前分片值一起傳給后端。
后端將前端每次上傳的文件,放入到緩存目錄;
前端將全部的文件內容都上傳完畢后,發送一個合并請求;
后端合并分片的之后對文件進行命名保存;
后端保存(cun)臨時(shi)分片的時(shi)候命(ming)名索引,方便合并(bing)的時(shi)候按照分片索引進(jin)行(xing)合并(bing);

vue模板代碼:

 1       <!-- 單文(wen)件分片上傳 -->
 2       <div class="fileUploadStyle">
 3         <h3>單文件分片上傳</h3>
 4         <el-upload ref="upload" name="files" action="#" :on-change="selectSinglePartFile"
 5           :on-remove="removeSingleFilePart" :file-list="singleFilePart.fileList" :auto-upload="false">
 6           <el-button slot="trigger" size="small" type="primary">選取文件</el-button>
 7           <el-button style="margin-left: 10px;" size="small" type="success"
 8             @click="singleFilePartUpload">點擊進行單文件分片上傳</el-button>
 9           <div slot="tip" class="el-upload__tip">主要用于測試單文件分片上傳</div>
10         </el-upload>
11         <el-progress :text-inside="true" class="progress" :stroke-width="26" :percentage="singlePartFileProgress" />
12       </div>
13       <!-- 多(duo)文(wen)件分(fen)片上傳 -->
14       <div class="fileUploadStyle">
15         <h3>多文件分片上傳</h3>
16         <el-upload ref="upload" name="files" action="#" :on-change="selectMultiplePartFile"
17           :on-remove="removeMultiplePartFile" :file-list="multipleFilePart.fileList" :auto-upload="false">
18           <el-button slot="trigger" size="small" type="primary">選取文件</el-button>
19           <el-button style="margin-left: 10px;" size="small" type="success"
20             @click="multipleFilePartUpload">點擊進行多文件分片上傳</el-button>
21           <div slot="tip" class="el-upload__tip">主要用于測試多文件分片上傳</div>
22         </el-upload>
23       </div>
24       <!-- 多文(wen)件(分片)秒傳 -->
25       <div class="fileUploadStyle">
26         <h3>多文件(分片MD5值)秒傳</h3>
27         <el-upload ref="upload" name="files" action="#" :on-change="selectMultiplePartFlashFile"
28           :on-remove="removeMultiplePartFlashFile" :file-list="multipleFilePartFlash.fileList" :auto-upload="false">
29           <el-button slot="trigger" size="small" type="primary">選取文件</el-button>
30           <el-button style="margin-left: 10px;" size="small" type="success"
31             @click="multipleFilePartFlashUpload">點擊進行文件秒傳</el-button>
32           <div slot="tip" class="el-upload__tip">主要用于測試多文件(分片MD5值)秒傳</div>
33         </el-upload>
34       </div>

js屬性定義:

 上(shang)傳部分代碼:

 minio分片上傳:

 上傳樣式(shi):

 

  • 功能演示及源碼

部分演示圖: 這里就(jiu)以上(shang)(shang)傳minio為例,測試(shi)上(shang)(shang)傳minio以分片方(fang)式上(shang)(shang)

 以8M進行分切(qie) 28M剛好分了四個區,我們使(shi)用redis客戶工具查(cha)看

 最后成功上傳到minio中

 

而且(qie)看(kan)到上(shang)傳文件大小為(wei):28M

文件上(shang)傳(chuan)代(dai)碼其實(shi)功能也簡(jian)單也很明確,先將一個(ge)(ge)大(da)文件分(fen)(fen)成n個(ge)(ge)小文件,然(ran)后給后端檢(jian)測這(zhe)些(xie)分(fen)(fen)片(pian)(pian)(pian)是否曾(ceng)經(jing)上(shang)傳(chuan)中斷過,即(ji)對這(zhe)些(xie)分(fen)(fen)片(pian)(pian)(pian)進行過濾(lv)出來,并(bing)將過濾(lv)出對應的分(fen)(fen)片(pian)(pian)(pian)定位值結果返回(hui)給前端處理出不需要上(shang)傳(chuan)的分(fen)(fen)片(pian)(pian)(pian)和需要上(shang)傳(chuan)的文件分(fen)(fen)片(pian)(pian)(pian),這(zhe)里主要還(huan)是區分(fen)(fen)到確定是這(zhe)個(ge)(ge)文件的分(fen)(fen)區文件。

這(zhe)里,為了(le)方便大家直接能夠使(shi)用(yong)(yong)Java源(yuan)碼,本文所(suo)有都采用(yong)(yong)Spring boot框架模(mo)式,另外使(shi)用(yong)(yong)了(le)第三方插(cha)件(jian),如果大家使(shi)用(yong)(yong)中(zhong)沒有使(shi)用(yong)(yong)到minio可(ke)以不需要引入(ru)并把相(xiang)關(guan)代(dai)碼移除即可(ke),代(dai)碼使(shi)用(yong)(yong)了(le)redis作(zuo)為分(fen)區(qu)數(shu)量緩存(cun),相(xiang)對于Java內(nei)存(cun)更穩(wen)定些。

demo源碼下載gitee地址(代碼包含Java后端工程和vue2前端工程):

 

 

 


 

posted @ 2024-06-24 11:42  Angelasp  閱讀(3284)  評論(3)    收藏  舉報