当前位置:   article > 正文

SpringBoot+Minio实现文件断点续传_springboot minio断点续传

springboot minio断点续传

大家好,我是程序员阿药。今天和大家分享的是一个上传文件的方式:断点续传

一次性上传的弊端

较大的文件一次性上传会比较慢,中途退出或网络延迟、故障等,容易出现超时等问题。待问题修复后再次上传时仍需要从头开始上传。

断点续传的优势

断点续传允许在上传中断后继续上传,而无需从头开始。

断点续传的原理

断点续传需要先把上传文件按一定大小分块,然后将所有分块上传,最后通过上传的所有分块合并出原文件。如图:

断点续传流程

1. 执行文件分块(文件分块)、2. 检查分块是否已经上传、3. 上传分块、4. 合并分块

如图:

断点续传代码

  1. public void uploadFile() throws IOException {
  2. // 原文件路径
  3. String sourcePath = "D:\\Data\\videos\\test.mp4";
  4. // 本地分块文件存储路径
  5. String localChunkFilePath = "D:\\Data\\chunk\\";
  6. // 原文件md5
  7. String sourceMd5 = DigestUtils.md5DigestAsHex(new FileInputStream(sourcePath));
  8. // 1.执行文件分块,返回分块总数
  9. int chunkTotal = fileToChunk(sourcePath, localChunkFilePath);
  10. int i = 0;
  11. for (; i < chunkTotal; i++) {
  12. // 2.检查分块是否已经上传
  13. boolean isSuccess = uploadController.checkChunks(sourceMd5, i);
  14. if (isSuccess) {
  15. System.out.println(i + "号分块已经上传成功");
  16. } else {
  17. // 3.上传分块
  18. isSuccess = uploadController.uploadChunks(sourceMd5, i, localChunkFilePath + i);
  19. if (isSuccess) {
  20. System.out.println("执行上传" + i + "号分块成功");
  21. } else {
  22. System.out.println("执行上传" + i + "号分块失败");
  23. break;
  24. }
  25. }
  26. }
  27. // 4.合并分块
  28. if (i == chunkTotal) {
  29. System.out.println("所有分块均上传成功,开始合并分块");
  30. boolean isSuccess = uploadController.mergeChunks(sourceMd5, chunkTotal, sourcePath);
  31. if (isSuccess) {
  32. System.out.println("合并分块成功");
  33. } else {
  34. System.out.println("合并分块失败");
  35. }
  36. }
  37. }
  38. /**
  39. * 检查分块是否已经上传
  40. * @param fileMd5 文件md5
  41. * @param chunkIndex 分块序号
  42. */
  43. public boolean checkChunks(String fileMd5, int chunkIndex) {
  44. // 根据md5得到分块文件所在目录的路径
  45. String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  46. // 分块路径
  47. String chunkPath = chunkFileFolderPath + chunkIndex;
  48. // 判断分块是否已经存在
  49. StatObjectArgs statObjectArgs = StatObjectArgs.builder()
  50. .bucket(bucket)
  51. .object(chunkPath)
  52. .build();
  53. try {
  54. minioClient.statObject(statObjectArgs);
  55. } catch (Exception e) {
  56. // 分块不存在
  57. return false;
  58. }
  59. // 分块存在
  60. return true;
  61. }
  62. /**
  63. * 上传分块
  64. * @param fileMd5 文件md5
  65. * @param chunkIndex 分块序号
  66. * @param localChunkFilePath 分块文件本地路径
  67. */
  68. public boolean uploadChunks(String fileMd5, int chunkIndex, String localChunkFilePath) {
  69. // 分块路径
  70. String chunkPath = getChunkFileFolderPath(fileMd5) + chunkIndex;
  71. // 获取文件的mimeType
  72. String mimeType = getMimeType(".temp");
  73. // 将分块文件上传到minio
  74. try {
  75. UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
  76. .bucket(bucket) // 存储桶
  77. .filename(localChunkFilePath) // 分块文件本地路径
  78. .object(chunkPath) // 对象名(分块路径)
  79. .contentType(mimeType) // 设置媒体文件类型
  80. .build();
  81. // 将分块从本地 localChunkFilePath 位置上传到 bucket 桶中的 chunkPath 位置
  82. minioClient.uploadObject(uploadObjectArgs);
  83. } catch (Exception e) {
  84. // 上传失败
  85. return false;
  86. }
  87. // 上传成功
  88. return true;
  89. }
  90. /**
  91. * 合并分块
  92. * @param fileMd5 文件md5
  93. * @param chunkTotal 分块总数
  94. * @param sourceFileName 原文件名称
  95. */
  96. public boolean mergeChunks(String fileMd5, int chunkTotal, String sourceFileName) {
  97. // 分块文件所在目录
  98. String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  99. // 找到所有的分块文件
  100. List<ComposeSource> sources = Stream.iterate(0, i -> ++i)
  101. .limit(chunkTotal)
  102. .map(i -> ComposeSource.builder().bucket(bucket).object(chunkFileFolderPath + i).build())
  103. .collect(Collectors.toList());
  104. // 源文件扩展名
  105. String extension = sourceFileName.substring(sourceFileName.lastIndexOf("."));
  106. // 分块合并后文件的路径(对象名)
  107. String objectName = getFilePathByMd5(fileMd5, extension);
  108. // 指定合并后文件的objectName等信息
  109. ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
  110. .bucket(bucket)
  111. .object(objectName) // 合并后文件的路径(对象名)
  112. .sources(sources) // 所有分块
  113. .build();
  114. // 合并文件
  115. // 报错size 1048576 must be greater than 5242880,minio默认的分块文件大小为5M
  116. try {
  117. minioClient.composeObject(composeObjectArgs);
  118. } catch (Exception e) {
  119. return false;
  120. }
  121. // 校验合并后的和源文件是否一致,一致则上传成功
  122. // 先下载合并后的文件
  123. File file = downloadFileFromMinIO(objectName);
  124. try (FileInputStream fileInputStream = new FileInputStream(file)) {
  125. // 计算合并后文件的md5
  126. String mergeFileMd5 = DigestUtils.md5DigestAsHex(fileInputStream);
  127. // 比较原始md5和合并后文件的md5
  128. if (!fileMd5.equals(mergeFileMd5)) {
  129. // 合并失败
  130. return false;
  131. }
  132. } catch (Exception e) {
  133. // 合并失败
  134. return false;
  135. }
  136. // 合并成功
  137. return true;
  138. }

测试结果

注意:报错size 1048576 must be greater than 5242880(Minio默认的分块文件大小为5M

1. 上传过程未中断。文件分块后全部上传成功,且分块合并原文件成功。如图:

 

 

2. 制造上传分块过程中断场景,将已经上传的6号分块及以后分块全部删除,同时删除合并后的文件。如图:

 

 

再次运行代码,查看分块是否可以从断点处(6号分块)开始上传。测试结果如图: 

今天的分享到此结束啦,喜欢的小伙伴可以点赞关注支持一下。

有需要全部源码的可以关注我的微信公众号:”程序员阿药“,回复:”断点续传“ 即可获得全部源码。 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/528108
推荐阅读
相关标签
  

闽ICP备14008679号