文件分片上传

news2025/3/26 8:04:16

概要

在日常开发中上传文件是常见的功能,像使用 SpringBoot 作为服务端接收上传的文件是很方便的,但是默认情况下 SpringBoot 为我限定了单次上传文件的大小,默认是1MB,当我们单次上传的大小超过1MB的时候就会报错,这时候我们可以通过修改上传大小限制来解决这个问题,主要是这两个属性:

  • spring.servlet.multipart.max-file-size
  • spring.servlet.multipart.max-request-size

但是这对于小型项目或者能确定每次上传的文件大小大概范围的可以,对于大型项目和不确定文件大小的项目这样的操作显然不适用,试想一下,如果你设置为了100MB,那假如有100个人同时上传文件,项目的内存不就GG了吗?因此通用稳妥的解决方法是分片上传,例如上传一个100MB的文件我们可以将这个文件先分割为100分1MB的文件,然后分别上传这100份就可以了,当然这里分片的大小要根据你的服务器配置自行调节,并不是越小越好,这样既解决了文件限制大小,而且可以利用多线程更快的进行上传。

实现过程

在实现前我们首先要考虑好,如何让服务端知道多次请求上传的文件是同一个文件的分片?你可能会想让前端在发送时生成一个uid,然后每个分片上传时都携带这个uid,然后服务端在进行文件合并的时候通过这个来判断,这个思路是正确的,那这个uid怎么生成呢?这里我们可以使用 spark-md5.js 来为文件生成一个唯一的hash值,这样既可以区分分片文件也可以在后续进行文件下载时进行文件的校验,因为一个文件的hash值是固定的,只要文件被修改那么hash值就会发生改变。

但是计算文件的hash值对于小文件我们可以直接计算,但是对于大文件,我们就要分片计算然后合并,这个过程也不用担心,spark-md5.js 里有现成的方法。因此,现在大概的流程就是:选择文件,然后将文件进行分片,在分片的同时计算每一个分片的hash值,然后分片完成后合并hash获取到完整文件的hash,最后遍历分片构造上传FormData进行上传请求。

文件的分片可以使用 JavaScript 的 slice(file, start, end) 方法实现,这里再说一下 start 和 end 参数的确定,我们先定义几个变量:

  • chunks:表示分片的数量,这个是文件大小除以分片大小向下取整,可以通过Math.ceil()方法得到。
  • currentChunk:当前chunk索引,初始值设为0
  • chunkSize:分片的大小,本文中设置为了1MB

start 的值是 currentChunk*chunkSize 确定 end 参数时有两种情况:

  1. start+chunkSize >= file.size 时 end 为 file.size
  2. start+chunkSize < file.size 时 end 为 start + chunkSize

前端完整代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>文件分片上传/下载测试</title>
  <script src="./js/jquery.min.js"></script>
  <script src="./js/spark-md5.min.js"></script>
  <style>
    html,
    body {
      width: 100%;
      height: 100vh;
      margin: 0;
    }

    header {
      height: 30px;
      display: flex;
      align-items: center;
      margin: 0;
      padding: 10px;
      background-color: #333;
      color: white;
    }

    header>h1 {
      font-size: 18px;
    }

    main {
      height: calc(100% - 100px);
    }

    .uploadBox {
      height: 100%;
      display: flex;
      padding: 10px;
      font-size: 14px;
    }
  </style>
</head>

<body>
  <header>
    <h1>文件分片上传测试页面</h1>
  </header>
  <main>
    <div class="uploadBox" id="upload-box">
      <div class="chooseFile">
        <label for="file">选择文件:</label>
        <button id="selectFile">选择文件</button>
        <div class="fileInfo">
          <p id="fileName">文件名称:</p>
          <p id="fileSize">文件大小:</p>
          <label for="chunkSize">分片大小:</label><input value="1024" id="chunkSize" type="number" placeholder="分片大小"><span>
            kb</span>
        </div>
        <input hidden type="file" id="file">
        <button style="width: 100%; margin-top:10px;">开始上传</button>
        <p id="uploadInfo"></p>
      </div>
    </div>
  </main>
  <script>
    $(document).ready(function () {
      $('#selectFile').click(() => {
        $('#file').click();
      });
      $('#file').change(async (e) => {
        var chunkSize = $('#chunkSize').val() * 1024;
        if (chunkSize == 0) {
          alert('分片大小不可以为0')
          return;
        }
        var file = e.target.files[0]
        $('#fileSize').append(Math.ceil(file.size / 1024) + ' kb')
        $('#fileName').append(file.name)
        //进行文件分割并上传
        sliceFileAndUpload(e.target.files, chunkSize)
      })
    });

    function sliceFileAndUpload(files, chunkSize) {
      var filePartList = [];
      var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
        file = files[0],
        chunks = Math.ceil(file.size / chunkSize),
        currentChunk = 0,
        spark = new SparkMD5.ArrayBuffer(),
        fileReader = new FileReader();
      fileReader.onload = e => {
        spark.append(e.target.result);
        currentChunk++;
        if (currentChunk < chunks) {
          loadNext();
        } else {
          var uid = spark.end();
          filePartList.forEach(item => {
            let formData = new FormData();
            formData.append('file', item.filePart);
            formData.append('uid', uid);
            formData.append('currentIndex', item.currentIndex);
            formData.append('fileName', file.name);
            $.ajax({
              url: '/file/uploadBySlice',
              method: 'POST',
              data: formData,
              processData: false,
              contentType: false,
              success: function (e) {
                console.log(e)
                $('#uploadInfo').append(e + "<br/>")
              }
            })
          })
          alert('上传完成,请到测试文件夹验证文件是否正确')
        }
      }
      fileReader.onerror = () => {
        console.log('文件读取处理发生错误')
      }
      function loadNext() {
        var temp = {}
        var start = currentChunk * chunkSize,
          end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
        let filePart = blobSlice.call(file, start, end);
        temp.currentIndex = start;
        temp.filePart = filePart;
        filePartList.push(temp);
        fileReader.readAsArrayBuffer(filePart);
      }
      loadNext();
    }
  </script>
</body>

</html>

由于是测试项目,前端的样式没有过多的美化,见谅。

服务端的代码就比较简单了,由于要进行文件的拼接,因此需要使用 RandomAccessFile 来进行文件的操作,因为它可以在指定的位置插入内容,它的 seek 就是这个作用,你可以把它看为一个指针,每次在插入文内容前先将这个指针拨到我们想要的位置,然后进行插入。

服务端代码

package vip.huhailong.slicefile.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

@RestController
@RequestMapping("/file")
public class FileController {

  @Value("${upload.dir}")
  private String uploadDir;

  @PostMapping("/uploadBySlice")
  public String uploadBySlice(MultipartFile file, String fileName, String uid, long currentIndex) throws IOException {
    mkdirUploadDir();
    String filePath = uploadDir + uid+"-"+fileName;
    try (RandomAccessFile accessFile = new RandomAccessFile(filePath, "rw")) {
      accessFile.seek(currentIndex);
      System.out.println("Thread name:"+Thread.currentThread().getName()+";currentIndex:" + currentIndex + "; fileSize:" + file.getBytes().length);
      accessFile.write(file.getBytes());
      return "Thread name:"+Thread.currentThread().getName()+";currentIndex:" + currentIndex + "; fileSize:" + file.getBytes().length;
    }
  }

  private void mkdirUploadDir() {
    File uploadDirFile = new File(uploadDir);
    if (!uploadDirFile.exists()) {
      boolean mkdirs = uploadDirFile.mkdirs();
      if (!mkdirs) {
        throw new RuntimeException("创建上传目录失败");
      }
    }
  }
}

测试截图

测试截图
测试截图

以上就是分片上传文件的简单实现,可以在此基础上增加更多的附加功能,本文中的代码对应的代码仓库地址:

slicefile: 分片上传测试Demo 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/963078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vmware虚拟机操作系统和本地操作系统互Ping要求、解决方式讲解

Vmware虚拟机操作系统和本地操作系统互Ping讲解 在虚拟化环境中&#xff0c;如VMware&#xff0c;虚拟机&#xff08;Virtual Machine&#xff0c;简称VM&#xff09;和本地操作系统之间进行Ping测试是一项常见的任务。Ping测试可用于检查虚拟机是否能够与本地操作系统或其他网…

wap2app 隐藏系统状态栏

一、首先创建wap2App项目 1、文件》新建》项目 2、选择Wap2App项目&#xff1a;输入项目名称、网站首页地址&#xff08;如果是本地localhost的话改为你的IP地址即可&#xff09;&#xff0c;点击创建 二、创建完wap2App项目后 隐藏系统状态栏只要修改1、2选项即可 1、找到根…

智慧工地源码 智慧大屏、手机APP、SaaS模式

一、智慧工地可以通过安全八要素来提升安全保障&#xff0c;具体措施包括&#xff1a; 1.安全管理制度&#xff1a;建立科学完善的安全管理制度&#xff0c;包括安全标准规范、安全生产手册等&#xff0c;明确各项安全管理职责和要求。 2.安全培训教育&#xff1a;对工地人…

港科夜闻|香港科大商学院李莹莹教授获研究资助局(RGC)授予研资局高级研究员头衔...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大商学院李莹莹教授获研究资助局(RGC)授予“研资局高级研究员”头衔。李教授的研究项目“金融风险大数据”将利用金融大数据进行严谨、全面的统计研究&#xff0c;更准确地进行风险评估和预测&#xff0c;构建高质…

Linux内核开启BBR加速

升级内核 BBR 模块从 4.9 版本的内核中开始支持&#xff0c;CentOS7 的发布版标配的是 kernel-3.10&#xff0c;所以首先需要升级内核到大于等于 4.9 的版本&#xff0c;然后再更改设置开启 BBR。 查看内核版本 1) uname -r 3.10.0-1160.71.1.el7.x86_64YUM方式升级安装 1&…

跨境做独立站,如何低成本引流?

大家都知道&#xff0c;海外的消费习惯与国内不同&#xff0c;独立站一向是海外消费者的最喜欢的购物方式之一&#xff0c;这也吸引了许多跨境商家开设独立站。 独立站不同于其他的第三方平台&#xff0c;其他平台可以靠平台自身流量来获得转化&#xff0c;而独立站本身没有流…

django.core.exceptions.AppRegistryNotReady: Apps aren‘t loaded yet.

运行django测试用例报错django.core.exceptions.AppRegistryNotReady: Apps arent loaded yet. 解决&#xff1a;在测试文件上方加上 django.setup() django.setup()是Django框架中的一个函数。它用于在非Django环境下使用Django的各种功能、模型和设置。 在常规的Django应用…

63.C++ mutable关键字

mutable 是C中的一个关键字&#xff0c;它用于修饰类的成员变量。当一个成员变量被声明为 mutable 时&#xff0c;它将允许在常量成员函数中修改这个成员变量的值&#xff0c;即使这个成员函数被声明为 const。 常量成员函数是类的成员函数&#xff0c;它们承诺不会修改类的成…

构建现代应用:Java中的热门架构概览

文章目录 1. 三层架构2. Spring框架3. 微服务架构4. Java EE&#xff08;Enterprise Edition&#xff09;5. 响应式架构6. 大数据架构7. 领域驱动设计&#xff08;Domain-Driven Design&#xff0c;DDD&#xff09;8. 安卓开发架构结论 &#x1f389;欢迎来到Java学习路线专栏~…

【算法竞赛宝典】猴子吃桃

【算法竞赛宝典】猴子吃桃 题目描述代码展示答案 题目描述 代码展示 //猴子吃桃 #include <iostream>using namespace std;int main() {int day 9, x1, x2 1;while (day > 0) {x1 (x2 1) * 2;//第&#xff11;天的桃子数是第&#xff12;的天的桃子数加&#xff…

[虚幻引擎插件介绍] DTGlobalEvent 蓝图全局事件, Actor, UMG 相互回调,自由回调通知事件函数,支持自定义参数。

本插件可以在虚幻的蓝图 Actor&#xff0c; Obiect&#xff0c;UMG 里面指定绑定和执行消息&#xff0c;可带自定义参数。 参数支持 Bool&#xff0c;Byte&#xff0c;Int&#xff0c;Int64&#xff0c;Float&#xff0c;Name&#xff0c;String&#xff0c;Text&#xff0c;Ve…

自定义spring-boot-start的jar包被引用时,提示找不到bean

类似这个报错&#xff1a; 重点要看一下我们自定义的start包下的config配置 BeanConditionalOnProperty(prefix "file", value "iSenable", havingValue "true")public FileServiceTemplate fileServiceTemplate(){return new FileServiceTe…

开开心心带你学习MySQL数据库篇之二

如何增大自己进入大厂的概率? 作业写得扎实,博客写得扎实,github/gitee搞的扎实 > 进入大厂的概率就会超过其他同学. 对于最近java行业有点饱和的看法? 大家不要只关注编程语言,这就把路走窄了!!! ~~ 投简历的时候,各种相关的岗位都是可以尝试的 ~~ 除了投后端开发,还…

Lliux管理员一些小技巧

1、查看bash日志 history命令显示日期和时间 2、打印时候对行列转换 xargs命令是改变已存在的文件的输出格式。“cat 文件名”是根据文件的行分隔符输出显示在屏幕上。如想改变一下&#xff0c;想把所有行合并为一行&#xff0c;就可以使用管道及xargs命令。 cat 文件名 |…

Bevformer:通过时空变换从多摄像机图像学习鸟瞰图表示

论文地址&#xff1a;BEVFormer: Learning Bird’s-Eye-View Representation from Multi-Camera Images via Spatiotemporal Transformers 代码地址&#xff1a;https://github.com/zhiqi-li/BEVFormer 论文背景 三维视觉感知任务&#xff0c;包括基于多摄像机图像的三维检测…

外贸soho不要随便给客户推荐供应商

我们常常以为大公司做事会更稳妥&#xff0c;更有保障&#xff0c;很多时候愿意选择大公司合作&#xff0c;为的就是图个心安&#xff0c;最近碰到的事实恰好相反&#xff01; 事情经过是这样的&#xff0c;有个客户除了找家具之外&#xff0c;还找很多其他的产品&#xff0c;…

芯片开发之难如何破解?龙智诚邀您前往DR IP-SoC China 2023 Day

2023年9月6日&#xff08;周三&#xff09;&#xff0c;龙智即将亮相D&R IP-SoC China 2023 Day&#xff0c;呈现集成了Perforce与Atlassian产品的芯片开发解决方案&#xff0c;助力企业更好、更快地进行芯片开发。 龙智资深顾问、技术支持部门负责人李培将带来主题演讲—…

无网安装OpenLDAP

一、服务器操作系统 CentsOS8.2 二、软件获取 2.1、OpenLDAP 官网下载OpenLDAP 2.2、BDB berkeley-db-5.1.29 (OpenLDAP当前与6.x版本不兼容&#xff0c;READEME中明确写出兼容4.44.8或5.05.1)&#xff1a; BerkeleyDB下载 2.3、LDAP Administrtor LDAP Administrtor…

基于改进莱维飞行和混沌映射的粒子群优化BP神经网络分类研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

VScode SSH无法免密登录

配置方法 引用高赞贴&#xff1a;点击 debug方法 连不上需要找到问题原因&#xff0c;看ssh的 log Linux服务器&#xff1a;2222是我们指定的端口&#xff0c;可以是1234等 sudo /usr/sbin/sshd -d -p 2222windows这边&#xff1a;端口号要一致 ssh -vvv ubuntusername192…