Java实现浏览器大文件上传的示例详解

 更新时间:2024年07月02日 09:24:17   作者:BLACK595  
文件上传是许多项目都有的功能,用户上传小文件速度一般都很快,但如果是大文件几个g,几十个g的时候,上传了半天,马上就要完成的时候,网络波动一下,文件又要重新上传,所以本文给大家介绍了Java实现浏览器大文件上传的示例,需要的朋友可以参考下

前言

文件上传是许多项目都有的功能,用户上传小文件速度一般都很快,但如果是大文件几个g,几十个g的时候,上传了半天,马上就要完成的时候,网络波动一下,文件又要重新上传,抓狂。那有什么办法解决解决这个问题,答案就是把文件分片,一段一段把文件拆开上传。

核心讲解

原理

分片上传:把一个完整的文件,前端把文件分成多个小块的chunk,一块一块的传递给后端,后端接收到后再把全部的块拼接起来,这样就算在某个时间点发生网络波动,那么丢失的也只有一块。

秒传:前端在把文件分片前,先计算出文件的md5值,后端拿到这个md5先去检查下是否已经有这个文件了,如果有直接给前端上传成功。这就是我们在网盘上有时候出现的文件秒传,说明已经有人跟你上传过同一份文件了。

断点续传:当网络出现异常上传中断后我们继续上传时,先去后端请求接口,拿到已经上传过的分片下标,再继续上传没有上传的分片。

整体流程

  • 用户选择文件进行上传
  • 前端获取文件唯一标识md5
  • 判断文件md5是否已经保存,是则秒传
  • 判断文件分片是否已经上传部分,是则断点续传
  • 上传分片文件
  • 后端合并分片
  • 分片上传完成

功能分析

前端

前端实现的功能难点在于文件分片,和获取文件的md5。

文件分片

因为js的File对象继承自Blob,所以他也有slice方法,slice方法需要的参数有两个,一个是startByte文件起始读取的字节位置,另一个是endByte结束读取的字节位置。

let fileChunkList = []; //存放文件切片
let cur = 0;
// 分片
while(cur < file.size){
  fileChunkList.push(file.slice(cur,cur + chunkSize));
  cur += chunkSize;
}

获取文件md5

获取文件的md5,推荐使用SparkMD5的文件增量方式获取,如果直接计算文件的hash,文件过大时对浏览器负担会较大。

上传文件

通过check接口上传前先判断是否秒传和获取已经上传的分片下标。

function handleBeforeUpload(file) {
  const chunkSize = 1024 * 1024 * 10; // 10MB
  // 计算md5
  md5(file, chunkSize).then(md5 => {
    //检查是否秒传
    request({
      url: "/upload/check/" + md5,
      method: "get",
    }).then(result => {
      const isOk = result.isOk;
      const haveList2 = result.haveList; //已经上传的分片下标
      if(isOk) {
        console.log("秒传成功");
        return;
      }
      haveList.value = haveList2;
      let chunkIndex = 0;
      //上传第一个分片
      upload(fileChunkList.value, chunkIndex, md5, file);
    })
  });
  return false;
}

已经上传的这些分片下标要跳过上传

后端

分片来后端后,使用RandomAccessFile就可以在一个文件上进行操作,而不用使用创建多个临时文件最后合并的方式,通过分片下标和分片大小计算出偏移量,使用RandomAccessFile将跳到偏移开始位置存放数据。RandomAccessFile的第二个参数的model有如下;

➢ "r":以只读方式打开指定文件。 ➢ "rw":以读、写方式打开指定文件。 ➢ "rws":以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 ➢ "rwd":以读、写方式打开指定文件。相对于"rw"模式,还要求对文件内容的每个更新都同步写入到底层存储设备。

/**
 * 分片文件上传
 * @param file 文件
 * @param chunkIndex 分片下标
 * @param md5 md5
 * @param totalFileSize 文件总大小
 * @param fileName 文件名
 */
@PostMapping("/shard")
public AjaxResult shardUpload(@RequestParam MultipartFile file, @RequestParam Integer chunkIndex,
                              @RequestParam String md5, @RequestParam Long totalFileSize,
                              @RequestParam String fileName) throws Exception{
    // 存放文件目录
    String dirPath = System.getProperty("user.dir") + "/file/"+md5+"/";
    File dirFile = new File(dirPath);
    if(!dirFile.exists()){
        dirFile.mkdir();
    }
    File tempFile = new File(dirPath + fileName);
    RandomAccessFile rw = new RandomAccessFile(tempFile, "rw");
    // 定位到分片的偏移量
    rw.seek(CHUNK_SIZE * chunkIndex);
    // 写入分片数据
    rw.write(file.getBytes());
    // 关闭流
    rw.close();
    // 读取已经分片集合
    List<Object> hasChunkList;
    String hasChunkKey = CHUNK_PREFIX + md5;
    if(redisCache.hasKey(hasChunkKey)){
        hasChunkList = redisCache.getCacheList(hasChunkKey);
    } else {
        hasChunkList = new ArrayList<>();
    }
    hasChunkList.add(chunkIndex);
    // 将最新的分片下标更新到Redis中
    redisCache.addCacheListOne(hasChunkKey,chunkIndex);
    // 判断是否上传完成
    int totalNeedChunks = (int) Math.ceil((double) totalFileSize / CHUNK_SIZE);
    // 总共需要的分片数 和 已经分片上传的数量相等 则上传完成
    boolean isOk = totalNeedChunks == hasChunkList.size();
    if(isOk){
        redisCache.setCacheObject(UPLOAD_ISOK_PREFIX + md5, true);
    }
    AjaxResult ajax = AjaxResult.success();
    ajax.put("hasChunkList",hasChunkList);
    ajax.put("isOk",isOk);
    return ajax;
}

最终演示

上传完成演示

秒传演示

断点演示

待优化

  • 提供查询进度接口,前端进度条展示,增加用户体验。
  • 多线程上传,不同分片用多线程,提高下载速度。

到此这篇关于Java实现浏览器大文件上传的示例详解的文章就介绍到这了,更多相关Java浏览器大文件上传内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot整合logback实现日志管理操作

    springboot整合logback实现日志管理操作

    本章节是记录logback在springboot项目中的简单使用,本文将会演示如何通过logback将日志记录到日志文件或输出到控制台等管理操作,感兴趣的朋友跟随小编一起看看吧
    2024-02-02
  • 关于Spring AOP使用时的一些问题汇总

    关于Spring AOP使用时的一些问题汇总

    这篇文章主要给大家汇总介绍了关于Spring AOP使用时的一些问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • SpringBoot多租户配置与实现示例

    SpringBoot多租户配置与实现示例

    本文详细介绍了在SpringBoot中实现多租户架构的方法和步骤,包括配置数据源、Hibernate拦截器、租户解析器等,以共享数据库、共享数据表的方式,确保数据隔离和安全性,感兴趣的可以了解一下
    2024-09-09
  • Feign调用服务时丢失Cookie和Header信息的解决方案

    Feign调用服务时丢失Cookie和Header信息的解决方案

    这篇文章主要介绍了Feign调用服务时丢失Cookie和Header信息的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • java网上商城开发之邮件发送功能(全)

    java网上商城开发之邮件发送功能(全)

    这篇文章主要介绍了java网上商城开发之邮件发送功能,第一部分介绍了环境配置,第二部分则介绍了具体实现代码,感兴趣的小伙伴们可以参考一下
    2016-03-03
  • Spring中ThreadLocal的解析

    Spring中ThreadLocal的解析

    这篇文章主要介绍了Spring中ThreadLocal的解析,Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度。这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突,下面一起进入文章学子详细内容吧
    2022-01-01
  • JAVA Stack详细介绍和示例学习

    JAVA Stack详细介绍和示例学习

    JAVA Stack是栈。它的特性是:先进后出(FILO, First In Last Out)。
    2013-11-11
  • MyBatis实现留言板的示例代码

    MyBatis实现留言板的示例代码

    本文主要介绍了MyBatis实现留言板的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • Redis之GEO存储地理位置信息的使用

    Redis之GEO存储地理位置信息的使用

    在外卖软件中的附近的美食店铺、外卖小哥的距离,打车软件附近的车辆,交友软件中附近的小姐姐。我们都可以利用redis的GEO地理位置计算得出。本文就来详细的介绍一下
    2021-10-10
  • Java十分钟精通进阶适配器模式

    Java十分钟精通进阶适配器模式

    适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能
    2022-04-04

最新评论