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分片上传等功能