Java大文件上传、分片上传、多文件上传、断点续传、上传文件minio、分片上传minio等解决方案

  • 上传说明

           文件上传花样百出,根据不同场景使用不同方案进行实现尤为必要。通常开发过程中,文件较小,直接将文件转化为字节流上传到服务器,但是文件较大时,用普通的方法上传,显然效果不是很好,当文件上传一半中断再次上传时,发现需要重新开始,这种体验不是很爽,下面介绍几种好一点儿的上传方式。

这里讲讲如何在Spring boot 编写上传代码,如有问题可以在下留言,我并在文章末尾附上Java上传源码供大家下载。

    • 分片上传

      分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分
隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再
由服务端对所有上传的文件进行汇总整合成原始的文件。

    • 断点续传

          断点续传是在下载/上传时,将下载/上传任务(一个文件或一个压缩
包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,
如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载
未完成的部分,而没有必要从头开始上传/下载。

  • Redis启动安装

Redis安装包分为 Windows 版和 Linux 版:
Windows版下载地址:https://github.com/microsoftarchive/redis/releases
Linux版下载地址: https://download.redis.io/releases/
我当前使用的Windows版本:

 

 

  • minio下载启动

windows版本可以参考我之前的文档:window10安装minio_minio windows安装-CSDN博客

启动会提示:

 

以上是密码设置问题需要修改如下:

set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678

启动成功后会输出相应地址

  • 上传后端Java代码

   后端采用Spring boot项目结构,主要代码如下:

  1   /**
  2      * 单文件上传
  3      * 直接将传入的文件通过io流形式直接写入(服务器)指定路径下
  4      *
  5      * @param file 上传的文件
  6      * @return
  7      */
  8     @Override
  9     public ResultEntity<Boolean> singleFileUpload(MultipartFile file) {
 10         //实际情况下,这些路径都应该是服务器上面存储文件的路径
 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文件输入字节流里面的数据
 28                 fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去
 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      * 多文件上传
 51      * 直接将传入的多个文件通过io流形式直接写入(服务器)指定路径下
 52      * 写入指定路径下是通过多线程进行文件写入的,文件写入线程执行功能就和上面单文件写入是一样的
 53      *
 54      * @param files 上传的所有文件
 55      * @return
 56      */
 57     @Override
 58     public ResultEntity<Boolean> multipleFileUpload(MultipartFile[] files) {
 59         //实际情况下,这些路径都应该是服务器上面存储文件的路径
 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             //使用多线程来完成对每个文件的写入
 72             for (MultipartFile file : files) {
 73                 futures.add(partMergeTask.submit(new MultipleFileTaskExecutor(filePath, file)));
 74             }
 75  
 76             //这里主要用于监听各个文件写入线程是否执行结束
 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      * 单文件分片上传
 98      * 直接将传入的文件分片通过io流形式写入(服务器)指定临时路径下
 99      * 然后判断是否分片都上传完成,如果所有分片都上传完成的话,就把临时路径下的分片文件通过流形式读入合并并从新写入到(服务器)指定文件路径下
100      * 最后删除临时文件和临时文件夹,临时文件夹是通过文件的uuid进行命名的
101      *
102      * @param filePart  分片文件
103      * @param partIndex 当前分片值
104      * @param partNum   所有分片数
105      * @param fileName  当前文件名称
106      * @param fileUid   当前文件uuid
107      * @return
108      */
109     @Override
110     public ResultEntity<Boolean> singleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {
111         //实际情况下,这些路径都应该是服务器上面存储文件的路径
112         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径
113         String tempPath = filePath + "temp\\" + fileUid;//临时文件存放路径
114         File dir = new File(tempPath);
115         if (!dir.exists()) dir.mkdirs();
116  
117         //生成一个临时文件名
118         String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";
119         try {
120             //将分片存储到临时文件夹中
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                 //需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成
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                 //这里如果分片很多的情况下,可以采用多线程来执行
137                 for (int i = 0; i < partNum; i++) {
138                     //读取分片数据,进行分片合并
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文件输入字节流里面的数据
143                         fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去
144                     }
145                     fileInputStream.close();
146                 }
147                 fileOutputStream.flush();
148                 fileOutputStream.close();
149  
150                 // 删除临时文件夹里面的分片文件 如果使用流操作且没有关闭输入流,可能导致删除失败
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                 //在删除对应的临时文件夹
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         //通过返回成功的分片值,来验证分片是否有丢失
167         return ResultEntity.success(true, partIndex.toString());
168     }
169  
170     /**
171      * 多文件分片上传
172      * 先将所有文件分片读入到(服务器)指定临时路径下,每个文件的分片文件的临时文件夹都是已文件的uuid进行命名的
173      * 然后判断对已经上传所有分片的文件进行合并,此处是通过多线程对每一个文件的分片文件进行合并的
174      * 最后对已经合并完成的分片临时文件和文件夹进行删除
175      *
176      * @param filePart  分片文件
177      * @param partIndex 当前分片值
178      * @param partNum   总分片数
179      * @param fileName  当前文件名称
180      * @param fileUid   当前文件uuid
181      * @return
182      */
183     @Override
184     public ResultEntity<String> multipleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {
185         //实际情况下,这些路径都应该是服务器上面存储文件的路径
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             //如果临时文件夹中分片数量和实际分片数量一致的时候,就需要进行分片合并
198             one:
199             if (partNum.equals(tempFiles.length)) {
200                 //需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成
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                 //使用多线程来完成对每个文件的合并
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         //通过返回成功的分片值,来验证分片是否有丢失
217         return ResultEntity.success(partIndex.toString(), fileUid);
218     }
219  
220     /**
221      * 多文件(分片)秒传
222      * 通过对比已有的文件分片md5值和需要上传文件分片的MD5值,
223      * 在文件分片合并的时候,对已有的文件进行地址索引,对没有的文件进行临时文件写入
224      * 最后合并的时候根据不同的文件分片进行文件读取写入
225      *
226      * @param filePart  上传没有的分片文件
227      * @param fileInfo  当前分片文件相关信息
228      * @param fileOther 已存在文件分片相关信息
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         //实际情况下,这些路径都应该是服务器上面存储文件的路径
236         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径
237         //正常情况下,这个临时文件也应该放入(服务器)非临时文件夹中,这样方便下次其他文件上传查找是否曾经上传过类似的
238         //当前demo是单独存放在临时文件夹中,文件合并完成之后直接删除的
239         String tempPath = filePath + "temp\\" + upFileInfo.getFileUid();//临时文件存放路径
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             //如果临时文件夹中分片数量和实际分片数量一致的时候,就需要进行分片合并
254             one:
255             if ((upFileInfo.getPartNum() - notUpFileInfoList.size()) == tempFiles.length) {
256                 //需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成
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                 //使用多线程来完成对每个文件的合并
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         //通过返回成功的分片值,来验证分片是否有丢失
274         return ResultEntity.success(upFileInfo.getPartIndex().toString(), upFileInfo.getFileUid());
275     }
276  
277     /**
278      * 根据传入需要上传的文件片段的md5值来对比服务器中的文件的md5值,将已有对应的md5值的文件过滤出来,
279      * 通知前端或者自行出来这些文件,即为不需要上传的文件分片,并将已有的文件分片地址索引返回给前端进行出来
280      *
281      * @param upLoadFileListMd5 原本需要上传文件的索引分片信息
282      * @return
283      */
284     @Override
285     public ResultEntity<List<DiskFileIndexVo>> checkDiskFile(List<DiskFileIndexVo> upLoadFileListMd5) {
286         List<DiskFileIndexVo> notUploadFile;
287         try {
288             //后端服务器已经存在的分片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());//不需要上传文件的地址索引
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生成的)来判断此文件在服务器中是否未上传完整,
309      * 如果没上传完整,则返回相关上传进度等信息
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      * 单文件(分片)断点上传
330      *
331      * @param filePart 需要上传的分片文件
332      * @param fileInfo 当前需要上传的分片文件信息,如uuid,文件名,文件总分片数量等
333      * @return
334      */
335     @Override
336     public ResultEntity<String> singleFilePartPointUpload(MultipartFile filePart, String fileInfo) {
337         PointFileIndexVo pointFileIndexVo = JSONObject.parseObject(fileInfo, PointFileIndexVo.class);
338         //实际情况下,这些路径都应该是服务器上面存储文件的路径
339         String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径
340         String tempPath = filePath + "temp\\" + pointFileIndexVo.getFileMd5();//临时文件存放路径
341         File dir = new File(tempPath);
342         if (!dir.exists()) dir.mkdirs();
343  
344         //生成一个临时文件名
345         String tempFileNamePath = tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + pointFileIndexVo.getPartIndex() + ".part";
346         try {
347             //将分片存储到临时文件夹中
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                 //需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成
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                 //读取分片数据,进行分片合并
369                 FileOutputStream fileOutputStream = new FileOutputStream(filePath + pointFileIndexVo.getFileName());
370                 //这里如果分片很多的情况下,可以采用多线程来执行
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) {//读取fis文件输入字节流里面的数据
376                         fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去
377                     }
378                     fileInputStream.close();
379                 }
380                 fileOutputStream.flush();
381                 fileOutputStream.close();
382  
383                 // 删除临时文件夹里面的分片文件 如果使用流操作且没有关闭输入流,可能导致删除失败
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                 //在删除对应的临时文件夹
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         //通过返回成功的分片值,来验证分片是否有丢失
401         return ResultEntity.success(pointFileIndexVo.getFileMd5(), pointFileIndexVo.getPartIndex().toString());
402     }
403  
404     /**
405      * 获取(服务器)指定文件存储路径下所有文件MD5值
406      * 实际情况下,每一个文件的md5值都是单独保存在数据库或者其他存储机制中的,
407      * 不需要每次都去读取文件然后获取md5值,这样多次io读取很耗性能
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,然后在循环里面对文件片段上传的时候在将当前分片值一起传给后端。
后端将前端每次上传的文件,放入到缓存目录;
前端将全部的文件内容都上传完毕后,发送一个合并请求;
后端合并分片的之后对文件进行命名保存;
后端保存临时分片的时候命名索引,方便合并的时候按照分片索引进行合并;

vue模板代码:

 1       <!-- 单文件分片上传 -->
 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       <!-- 多文件分片上传 -->
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       <!-- 多文件(分片)秒传 -->
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属性定义:

 上传部分代码:

 minio分片上传:

 上传样式:

 

  • 功能演示及源码

部分演示图: 这里就以上传minio为例,测试上传minio以分片方式上

 以8M进行分切 28M刚好分了四个区,我们使用redis客户工具查看

 最后成功上传到minio中

 

而且看到上传文件大小为:28M

文件上传代码其实功能也简单也很明确,先将一个大文件分成n个小文件,然后给后端检测这些分片是否曾经上传中断过,即对这些分片进行过滤出来,并将过滤出对应的分片定位值结果返回给前端处理出不需要上传的分片和需要上传的文件分片,这里主要还是区分到确定是这个文件的分区文件。

这里,为了方便大家直接能够使用Java源码,本文所有都采用Spring boot框架模式,另外使用了第三方插件,如果大家使用中没有使用到minio可以不需要引入并把相关代码移除即可,代码使用了redis作为分区数量缓存,相对于Java内存更稳定些。

demo源码下载gitee地址(代码包含Java后端工程和vue2前端工程):java-file-upload-demo: java 多文件上传、多文件分片上传、多文件秒传、minio分片上传等功能

 

 

 


 

posted @ 2024-06-24 11:42  Angelasp  阅读(1319)  评论(3编辑  收藏  举报