SpringBoot+vue文件上传下载预览大文件分片上传文件上传进度

文章目录

    • 学习链接
    • 上传文件
      • 前端
      • 后端代码
    • 下载文件
      • a标签下载
        • 前端代码
        • 后台代码
      • 动态a标签下载
        • 前端代码
      • axios + 动态a标签
        • 前端代码
      • 浏览器直接输入
    • 预览文件
      • 前端代码
      • 后端代码
    • 分片上传
      • 前后端分别md5加密
        • spark-md5
        • commons-codec
      • 分片上传实现1
        • 前端代码
        • 后端代码
      • 分片上传实现2
        • 前端代码

学习链接

Blob & File
spark-md5根据文件内容生成hash
大文件分片上传(批量并发,手动上传)vue组件封装-form组件
vue上传大文件/视频前后端(java)代码
springboot+vue自定义上传图片及视频
【视频流上传播放功能】前后端分离用springboot-vue简单实现视频流上传和播放功能【详细注释版本,包含前后端代码】
vue+SpringBoot实现大文件分块上传、断点续传和秒传
SpringBoot+Vue.js前后端分离实现大文件分块上传github地址
Spring Boot+VUE分片上传大文件到OSS服务器解决方案
fastloader gitee地址
细说分片上传与极速秒传(SpringBoot+Vue实现)
【java】java实现大文件的分片上传与下载(springboot+vue3) 这个不错,后面可以详细看下,代码地址:https://gitee.com/zzhua195/big-file-upload

上传文件

前台

  • 整个过程,就是在使用FormData 添加 上File(这个Blob),并且key要和后台的名字对应上
  • 在点击上传按钮开始上传之前,使用了URL.createObjectURL(File)创建blobUrl,给了img标签作图片预览
  • 上传完毕后,将input file的value置为空。若将input file置为空,则此时不能再从input file中获取file了,得等下次再选择图片才能获得file,将它置为空的目的是为了下次选择同样的图片,也能触发input file的change事件

后台

  • 后台仅仅就是用MultipartFile声明接收即可,可以使用@RequestParam注解 或 @RequestPart注解
  • 调用MultipartFile#transferTo保存文件
  • 可以从MultipartFile#getInputStream中获取流,比如上传到OSS。

在这里插入图片描述
前端控制台
在这里插入图片描述
后端控制台
在这里插入图片描述

前端

<template>
    <div>
        选择文件: <input type="file" ref="fileInputRef" @change="selectFile" multiple> <!-- 使用multiple属性,可选择多个文件 -->
        <br/>
        <img v-if="imgUrl" :src="imgUrl" alt="" style="width:54px;height:54px;">
        <el-button v-if="imgUrl" type="primary" @click="uploadFile">上传</el-button>

        <hr/>
    </div>
</template>

<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
            imgUrl:''
        }
    },
    methods: {
        selectFile() {

            let file = this.$refs['fileInputRef'].files[0]
            console.log(file)

            // 上传前, 可以预览该图片
            let blobUrl = URL.createObjectURL(file)
            this.imgUrl = blobUrl

        },
        uploadFile() {

            // 因为可能选择多个文件, 所以这里是个数组
            let file = this.$refs['fileInputRef'].files[0]

            let formData = new FormData()

            formData.append('mfile', file) // 必须和后端的参数名相同。(我们看到了, 其实就是把blob文件给了formData的一个key)
            formData.append("type", 'avatar')

            // 可以有下面2种方式, 来上传文件
            /* axiosInstance
                .post('http://127.0.0.1:8083/file/uploadFile',formData, {headers: {'a':'b'}})
                .then(res => {
                    console.log('响应回来: ',res);
                }) */
            axiosInstance({ // 这种传参方式, 在axios的index.d.ts中可以看到
                url:'http://127.0.0.1:8083/file/uploadFile',
                method:'post',
                data: formData, // 直接将FormData作为data传输
                headers: {
                    'a':'b' // 可携带自定义响应头
                }
            }).then(res => {
                console.log('响应回来: ',res);
            })

            console.log(this.$refs['fileInputRef'].value); // C:\fakepath\cfa86972-07a1-4527-8b8a-1991715ebbfe.png
            // 上传完文件后, 将value置为空, 以避免下次选择同样的图片而不会触发input file的change事件。
            // (注意清空value后,将不能再从input file中获取file,而原先的file仍然能够使用)
            this.$refs['fileInputRef'].value = ''
        }
    }
}
</script>

<style>

</style>

后端代码

@PostMapping("uploadFile")
public Object uploadFile(@RequestPart("mfile")MultipartFile multipartFile,@RequestPart("type") String type) throws IOException {

    System.out.println(multipartFile.getClass());
    System.out.println(type);

    // 源文件名
    String originalFilename = multipartFile.getOriginalFilename();
    // 内容类型
    String contentType = multipartFile.getContentType();
    // 文件是否为空(无内容)
    boolean empty = multipartFile.isEmpty();
    // 文件大小
    long size = multipartFile.getSize();
    // 文件的字节数据
    byte[] bytes = multipartFile.getBytes();
    // 获取文件的字节输入流
    InputStream inputStream = multipartFile.getInputStream();
    // 将文件保存到指定路径下
    multipartFile.transferTo(new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + originalFilename));

    System.out.println(originalFilename);
    System.out.println(contentType);
    System.out.println(empty);
    System.out.println(size);
    System.out.println(bytes.length);

    HashMap<String, Object> data = new HashMap<>();
    data.put("data", "ok");
    return data;
}

下载文件

a标签下载

在这里插入图片描述

前端代码

<template>
    <div>
       <a href="http://127.0.0.1:8083/file/downloadFile?filename=头像a.png">avatar3.png</a>
    </div>
</template>

<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        
    }
}
</script>

<style>

</style>

后台代码

@GetMapping("downloadFile")
public void downloadFile(@RequestParam("filename") String filename) throws Exception {

    // 告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
    // 意思是未知的应用程序文件,浏览器一般不会自动执行或询问执行。浏览器会像对待,
    // 设置了HTTP头Content-Disposition值为attachment的文件一样来对待这类文件,即浏览器会触发下载行为
    response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
    // ,该响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者网页的一部分),还是以附件的形式下载并保存到本地。
    response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;fileName="+ URLEncoder.encode(filename, "UTF-8"));
    File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + filename);

    ServletOutputStream ros = response.getOutputStream();

    FileInputStream fis = new FileInputStream(file);
    byte[] bytes = new byte[2 * 1024];
    int len = 0;
    while ((len = fis.read(bytes)) != -1) {
        ros.write(bytes, 0, len);
    }

    ros.flush();
    ros.close();
    fis.close()

}

动态a标签下载

  • 后台代码仍然用上面a标签下载的代码即可

在这里插入图片描述

前端代码

  • 只需要动态创建a标签,添加到body,然后手动调用js触发a标签的click事件,触发下载
  • 下载完成之后,将a标签移除
  • 整个过程a标签的样式都是display:none
<template>
    <div>
        <el-button type="success" @click="downloadFile">下载文件</el-button>
    </div>
</template>

<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        downloadFile() {
            let a = document.createElement('a')
            a.href = 'http://127.0.0.1:8083/file/downloadFile?filename=头像a.png'
            document.body.appendChild(a)
            a.style.display = 'none'
            a.click()
            document.body.removeChild(a)
        }
    }
}
</script>

<style>

</style>

axios + 动态a标签

  • 后台代码仍然用上面a标签下载的代码即可
    • 这里再思考下,能不能前端做成分片下载?

在这里插入图片描述

前端代码

  • 收到后端的响应流 变成前端的 blob对象,然后使用浏览器的api,URL将blob对象创建为blobUrl,然后动态创建a标签,触发a标签点击完成下载
  • 使用/file/previewFile接口也是一样的效果,其实,只要后端往response里写数据,这里来就能用blob拿到
  • 下面的axios不要用封装的,因为要指定responseType:‘blob’,直接去拿数据
  • 这里其实还可以点击下载文件之后,弹个框什么的,然后再下载
<template>
    <div>
        <el-button type="success" @click="downloadFile">下载文件</el-button>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        downloadFile() {
            axios({ // 使用原来的axios实例, 不能用封装的, 因为下面要直接拿响应的blob数据
                url:'http://127.0.0.1:8083/file/downloadFile?filename=头像a.png',
                method:'get',
                headers: {
                    'a':'b'
                },
                responseType: 'blob' // 这个可以在axios的index.d.ts中可以找到
            }).then(response=>{
                return response.data
            }).then(blob=>{
                console.log(blob);
                let ablob = new Blob([blob])
                let blobUrl = window.URL.createObjectURL(ablob)
                let tmpLink = document.createElement('a')
                tmpLink.style.display = 'none'
                tmpLink.href = blobUrl
                tmpLink.setAttribute('download','头像b.png')
                document.body.appendChild(tmpLink)
                tmpLink.click()
                document.body.removeChild(tmpLink)
                window.URL.revokeObjectURL(blobUrl)
            })
        }
    }
}
</script>

<style>

</style>

浏览器直接输入

直接在浏览器的地址栏输入,即可下载,同样用上面的地址即可:http://127.0.0.1:8083/file/downloadFile?filename=头像a.png

在这里插入图片描述

预览文件

  • 前端直接一个a标签即可,后端改个响应头即可。
  • 如果需要在页面中预览,则可使用上面提到的方法,获取到流之后,然后创建为blobUrl或dataUrl放入img标签的src属性即可。

在这里插入图片描述

前端代码

<template>
    <div>
        <a href="http://127.0.0.1:8083/file/previewFile?filename=头像a.png">头像a.png</a>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        
    }
}
</script>

<style>

</style>

后端代码

设置好响应头即可

@GetMapping("previewFile")
    public void previewFile(@RequestParam("filename") String filename) throws Exception {

    // 可使用ServletContext 通过文件名获取 媒体资源类型
    response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE);
    File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + filename);

    ServletOutputStream ros = response.getOutputStream();

    // 可参考: StreamUtils
    FileInputStream fis = new FileInputStream(file);
    byte[] bytes = new byte[4 * 1024];
    int len = 0;
    while ((len = fis.read(bytes)) != -1) {
        ros.write(bytes, 0, len);
    }

    ros.flush();
    ros.close();
	fis.close()
}

分片上传

前后端分别md5加密

在开始分片之前,先了解下md5加密,因为后面秒传需要用到,或者是其它场景需要标识到这个文件名文件二进制内容

  • md5的全称是message-digest algorithm 5(信息-摘要算法),它的作用是让大容量信息在用数字签名软件签署私人密匙前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的大整数)。
  • MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在UNIX下有很多软件在下载的时候都有一个文件名相同,文件扩展名为.md5的文件,在这个文件中通常只有一行文本,大致结构如:MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461,这就是tanajiya.tar.gz文件的数字签名。MD5将整个文件当作一个大文本信息,通过其不可逆的字符串变换算法,产生了这个唯一的MD5信息摘要

spark-md5

  1. 安装spark-md5

    npm install spark-md5 --save
    
  2. 对字符串操作

    • 常规用法

      // 16进制哈希
      var hexHash = SparkMD5.hash('Hi there');        // d9385462d3deff78c352ebb3f941ce12
      // 再次执行, 仍然是同样的值
      var hexHash = SparkMD5.hash('Hi there');        // d9385462d3deff78c352ebb3f941ce12
      
      // 感觉这个没事撒用(应该就是原始的二进制数据,然后这个二进制数据转成了字符串形式)
      var rawHash = SparkMD5.hash('Hi there', true);  // Ù8TbÓÞÿxÃRë³ùAÎ\x12
      // 可以如下模拟以下上面这个过程,
      var fr = new FileReader()
      fr.read(new Blob([SparkMD5.hash('Hi there',true)]))
      // 看如下,获取了跟上面一样的结果
      console.log(fr.result) // Ù8TbÓÞÿxÃRë³ùAÎ\x12
      
    • 进阶用法

      var spark = new SparkMD5();
      
      spark.append('Hi');
      spark.append(' there');
      
      // d9385462d3deff78c352ebb3f941ce12,这个跟上面一样
      var hexHash = spark.end();       
      
      // Ԍُ  不知道是个什么玩意,跟上面直接调用SparkMD5.hash('Hi there', true);的结果不一样
      var rawHash = spark.end(true);     
      
  • 对文件操作
    对一个D:\documents\尚硅谷谷粒学院项目视频教程\项目资料.zip的1.18G的文件进行md5,获取的是:0efda58eb4bbb4ea4b69f9ac0d566075

    下面的方法摘自:npmjs仓库的spark-md5,可以体会一下这个递归在js里的用法:给FileReader绑定load事件,根据分片信息获取分片数据,并使用FileReader去read这个数据,从而绑定的load事件的函数就会执行,当处理完这个分片数据后,然后去触发下一个分片,直到所有的分片都read了(那么上传分片的时候,也可以使用下面的递归这么玩)。

    <template>
    	<input type="file" ref="fileInputRef" @change="getMd5($event.target.files[0])" />
    </template>
    
    export default {
    	methods: {
    		getMd5(file) {
                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                    chunkSize = 10 * 1024 * 1024,
                    chunks = Math.ceil(file.size / chunkSize),
                    currentChunk = 0,
                    spark = new SparkMD5.ArrayBuffer(),
                    fileReader = new FileReader();
    
                fileReader.onload = function (e) {
                    console.log('read chunk nr', currentChunk + 1, 'of', chunks);
                    spark.append(e.target.result);
                    currentChunk++;
    
                    if (currentChunk < chunks) {
                        loadNext();
                    } else {
                        console.log('finished loading');
                        console.info('computed hash', spark.end());  // Compute hash
                    }
                };
    
                fileReader.onerror = function () {
                    console.warn('oops, something went wrong.');
                };
    
                function loadNext() {
                    var start = currentChunk * chunkSize,
                        end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
    
                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                }
    
                loadNext();
            },
    	}
    
    }
    
    

commons-codec

  • 需要先导入依赖

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.12</version>
    </dependency>
    
  • 对字符串操作

    import org.apache.commons.codec.digest.DigestUtils;
    
    public static void main(String[] args) {
    
        String md5 = DigestUtils.md5Hex("Hi there");
        
        // d9385462d3deff78c352ebb3f941ce12, 与前端的md5结果一致
        System.out.println(md5);
        System.out.println(md5.length());
    }
    
  • 对文件二进制数据内容操作

    public static void main(String[] args) throws IOException {
    
        String s = DigestUtils.md5Hex(new FileInputStream(new File("D:\\documents\\尚硅谷谷粒学院项目视频教程\\项目资料.zip")));
       
        // 与前端计算结果一致
        // 0efda58eb4bbb4ea4b69f9ac0d566075
        System.out.println(s); 
    }
    

分片上传实现1

这里只是实现分片上传的功能。会存在传参可能不合理,应该让要根据文件内容来标识到这个文件。后面需要根据具体的设计来改代码。比如设计表记录文件的每一个上传分片的记录,这样就能直到当前文件上传到第几个分片了,加入上传过程中分片失败了,下次上传前,先查询下这个文件上传到第几个分片了,然后就从那个分片后面开始上传。当根据文件内容计算的md5值能够在后台查到的话,那就直接算作秒传。

在这里插入图片描述

前端代码

<template>
    <div>
        <el-progress :text-inside="true" :stroke-width="26" :percentage="percentage" style="width: 350px;border-radius: 13px;border: 1px solid red;"></el-progress>
        <input type="file" ref="fileInputRef" />
        <el-button @click="uploadFile">上传文件</el-button>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
            // 进度条
            percentage: 0
        }
    },
    
    methods: {
        async uploadFile() {
            const { files } = this.$refs['fileInputRef']
            let file = files[0]
            console.log(file.name);

            let size = file.size
            console.log(size);

            // 3 -  (0  1  2)

            let chunkSize = 10 * 1024 * 1024 // 1个分片 10M
            let start = 0 // 上传的开始位置  
            let index = 0 // 分片索引, 从0开始(0,1,2...)

            let totalFragmentCount = Math.ceil(size / chunkSize) // 总的分片数量

            while (true) {

                let end; // 当前分片的结束位置(不包括,开区间)

                if (start + chunkSize > size) { // 如果加上了一个分片大小,超出了文件的大小, 那么结束位置就是文件大小
                    end = size
                } else {
                    end = start + chunkSize // 如果加上了一个分片大小,没超出了文件的大小, 那么结束位置就是start加上分片大小
                }

                // 对file分片,分片完后, 给分片一个名字, 这个名字可以在后台获取为分片文件的真实名字
                let sfile = new File([file.slice(start, end)],`${file.name}-${index}`) 

                // 上传完这个分片后, 再走下面的代码
                await this.uploadFragmentFile(sfile, index, file.name, totalFragmentCount)

                index++
                if (end == size) { // 检查是否传完了, 传完了的话, 就跳出循环
                    break
                }

                // 开始位置
                start = end
            }

            console.log('发送合并文件请求');
            this.mergeFragmentFile(file.name)

        },

        // 上传分片文件(将切分的分片文件上传)
        uploadFragmentFile(sfile, index, realFilename, totalFragmentCount) {
            return new Promise((resolve, reject) => {
                let formData = new FormData()
                formData.append('sFile', sfile)
                formData.append('index', index)
                formData.append('realFilename', realFilename)
                console.log('sfile', sfile, index);
                axios({
                    url: 'http://localhost:8083/file/uploadSliceFile',
                    method: 'post',
                    data: formData,
                    headers: {
                        'a': 'b'
                    }
                }).then(res => {
                    console.log(`上传第${index}个分片成功`);
                    this.percentage = parseFloat(((index + 1) / totalFragmentCount * 100).toFixed(1))
                    resolve()
                })
            })

        },

        // 合并分片文件(当所有分片上传成功之后, 发送合并分片的请求)
        mergeFragmentFile(realFilename) {
            axios({
                url: 'http://localhost:8083/file/mergeFragmentFile',
                method: 'post',
                params: { realFilename },
                headers: {
                    'a': 'b'
                }
            }).then(res => {
                console.log('合并成功');
            })
        }
    }
}
</script>

<style></style>

后端代码

@PostMapping("uploadSliceFile")
public Object uploadSliceFile(@RequestParam("sFile")MultipartFile sFile,@RequestParam("realFilename") String realFilename, @RequestParam("index") Integer index) throws IOException {

    String md5 = DigestUtils.md5Hex(realFilename);
    System.out.println(realFilename);
    System.out.println(md5);
    System.out.println("分片名: " + sFile.getOriginalFilename());

    File dir = new File("d:/Projects/practice/test-springboot/src/main/resources/file/fragment/" + md5);
    if (!dir.exists()) {
        dir.mkdirs();
    }
    File sFileWithIndex = new File("d:/Projects/practice/test-springboot/src/main/resources/file/fragment/" + md5 + "/" + index);
    sFile.transferTo(sFileWithIndex);

    HashMap<String, Object> data = new HashMap<>();
    data.put("data", "ok");
    return data;
}

@PostMapping("mergeFragmentFile")
public Object mergeFragmentFile(@RequestParam String realFilename) throws IOException {

    System.out.println("-------开始合并文件");

    // 合并的文件
    RandomAccessFile raf = new RandomAccessFile("d:/Projects/practice/test-springboot/src/main/resources/file/" + realFilename, "rw");

    // 获取分片所在文件夹
    String md5 = DigestUtils.md5Hex(realFilename);
    System.out.println(realFilename);
    System.out.println(md5);
    File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/fragment/" + md5);
    File[] files = file.listFiles();
    int num = files.length;
    System.out.println(num);

    byte[] bytes = new byte[5 * 1024];

    // 合并分片
    for (int i = 0; i < num; i++) {
        File iFile = new File(file, String.valueOf(i));
        // 将每一个分片文件包装为缓冲流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(iFile));
        int len = 0;
        // 将分片文件包装的流写入RandomAccessFile
        while ((len = bis.read(bytes)) != -1) {
            raf.write(bytes, 0, len);
        }
        bis.close();
    }

    // 删除分片所在文件夹的分片文件
    for (File tmpFile : files) {
        tmpFile.delete();
    }
    // 删除分片所在文件夹
    file.delete();

    raf.close();

    HashMap<String, Object> data = new HashMap<>();
    data.put("data", "ok");
    return data;
}

分片上传实现2

上面vue实现的分片上传,有些问题

  • 只能把一个文件进行拆分了,然后一个个分片上传,这个上传过程不能中断,中断了的话,又得从头开始分片,这样前面上传完的分片就废了,也就是不能从指定的分片开始上传
  • 也不能在上传的过程中,不支持暂停上传,继续上传。在某次上传过程中,停止上传,然后后端告诉前端从哪个分片开始上传。
  • 将上面的while+asyn+await,改成递归上传
  • 后端代码不变,主要修改前端代码,后面需要记录每个文件的分片上传到哪个分片了,然后在上传这个文件前,查询一下到哪个分片了,然后从指定的分片开始上传

在这里插入图片描述

前端代码

<template>
    <div>
        <el-progress :text-inside="true" :stroke-width="26" :percentage="percentage"
            style="width: 350px;border-radius: 13px;border: 1px solid red;"></el-progress>
        <input type="file" ref="fileInputRef" />
        <el-button @click="uploadFile">开始上传文件</el-button>
        <el-button @click="stopUpload">暂停上传</el-button>
        <el-button @click="countinueUpload">继续上传</el-button>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
            // 进度条
            percentage: 0,
            // 已上传完成的分片索引
            index: -1,
            // 是否暂停上传
            isStop: false
        }
    },

    methods: {
        // 停止上传
        stopUpload() {
            this.isStop = true
        },
        // 继续上传
        countinueUpload() {
            this.isStop = false
            this.uploadFileFromIndex(++this.index)
        },
        // 上传
        uploadFile() {
            this.uploadFileFromIndex(0)
        },

        // 从第几个分片开始上传(index从0开始算,index=0算作第一个分片)
        uploadFileFromIndex(index) {

            let _this = this

            const { files } = this.$refs['fileInputRef']
            let file = files[0]

            let chunkSize = 5 * 1024 * 1024 // 分片大小 10M
            let chunkTotalCount = Math.ceil(file.size / chunkSize) // 分片总数
            // debugger

            uploadSliceFile(index)

            // 上传指定索引的分片文件
            function uploadSliceFile(idx) {

                if (idx >= chunkTotalCount) {
                    console.log('文件已上传完成...');
                    return
                }

                // 分片开始位置
                let start = idx * chunkSize
                // 分片结束位置
                let end = (start + chunkSize) > file.size ? file.size : start + chunkSize
                // 对文件分片
                let sFile = new File([file.slice(start, end)], `${file.name}.${idx}`)

                let formData = new FormData()
                formData.append('sFile', sFile)
                formData.append('realFilename', file.name)
                formData.append('index', idx)

                axios({
                    url: 'http://localhost:8083/file/uploadSliceFile',
                    method: 'post',
                    data: formData,
                    headers: {
                        'a': 'b'
                    }
                }).then(res => {
                    if (idx === chunkTotalCount - 1) {
                        // 已经上传完了最后一个分片
                        console.log('上传完成');
                        // 记录已完成的分片索引
                        _this.index = idx
                        _this.percentage = 100
                        // 发送合并文件请求
                        mergeFragmentFile(file.name)
                    } else {

                        // 上传完成指定索引的分片之后, 更新文件上传进度
                        _this.percentage = parseFloat(((idx + 1) / chunkTotalCount * 100).toFixed(1))

                        // 记录已完成的分片索引
                        _this.index = idx

                        if (!_this.isStop) {
                            // 如果没有点击暂停的话, 再上传下一个索引的分片
                            uploadSliceFile(++idx)
                        }
                    }

                })
            }

            // 发送合并分片文件请求
            function mergeFragmentFile(realFilename) {
                axios({
                    url: 'http://localhost:8083/file/mergeFragmentFile',
                    method: 'post',
                    params: { realFilename },
                    headers: {
                        'a': 'b'
                    }
                }).then(res => {
                    console.log('合并成功');
                })
            }

        }


    }
}
</script>

<style></style>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spark RDD 持久化(CheckPoint 检查点)

RDD Cache 缓存 RDD 通过 Cache 或者 Persist 方法将前面的计算结果缓存&#xff0c;默认情况下会把数据以缓存 在 JVM 的堆内存中。但是并不是这两个方法被调用时立即缓存&#xff0c;而是触发后面的 action 算 子时&#xff0c;该 RDD 将会被缓存在计算节点的内存中 // cach…

常用排序算法汇总—Python版

一、选择排序 1. 原理&#xff1a; 选择排序&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法&#xff0c;它的基本思路是将数组按顺序分成已排序部分和未排序部分&#xff0c;然后每次从未排序部分中选择出最小的元素&#xff0c;将其添加到已排序部分的末尾…

【五一创作】【软考:软件设计师】 5 计算机组成与体系结构(三)认证技术 | 计算机可靠性

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于软考中级&#xff1a;软件设计师系列专栏,本专栏服务于软考中级的软件设计师考试,包括不限于知识点讲解与真题讲解两大部分,并且提供电子教材与电子版真题,关注私聊即可 …

三范式(详解+例子)

第一范式&#xff08;1NF&#xff09;&#xff1a;每一列都是不可分割的原子数据项&#xff08;什么意思&#xff0c;每一项都不可分割&#xff0c;像下面的表格就能分割&#xff0c;所以它连第一范式都算不上&#xff09; 分割后的样子 &#xff08;它就是第一范式了&#xff…

FPGA学习_01_基础知识(有点劝退,心灵弱小者勿入)

有些人喜欢直接拿开发板看教程开干&#xff0c;我认为了解点历史发展没什么坏处&#xff0c;一些FPGA的基础知识也是同样重要的。 1.1. FPGA的主要厂商 XILINX 占据FPGA绝大部分的市场份额 ALTERA 被 INTEL 167亿美元收购 改名为INTEL LATTICE 被神秘的中国公…

HMM理论学习笔记-隐马尔可夫模型的三个元素、假设和问题

文章目录 概率论基础条件概率全概公式边缘概率联合概率联合概率与边缘概率的关系贝叶斯公式&#xff08;条件联合概率&#xff09;马尔科夫链的概念 HMM简述HMM的三个元素符号定义1、状态转移概率矩阵A2、观测概率矩阵B3、初始状态概率向量π HMM的三个假设1、齐次马尔可夫假设…

Spring Boot使用(基础)

目录 1.Spring Boot是什么? 2.Spring Boot使用 2.1Spring目录介绍 2.2SpringBoot的使用 1.Spring Boot是什么? Spring Boot就是Spring脚手架,就是为了简化Spring开发而诞生的 Spring Boot的优点: 1.快速集成框架,提供了秒级继承各种框架,提供了启动添加依赖的功能 2.内…

简单搭建node后台(笔记用)

毕设过程 mongodb 配置 使用node写后台一些语法运用bug关于安装一款群控软件后&#xff0c;修改了环境变量导致后台崩溃![](https://img-blog.csdnimg.cn/7c684b2e318048b3ad1db78484e10e6a.jpeg) vue管理后台 mongodb 配置 https://blog.csdn.net/weixin_43405300/article/de…

【SPSS】相关分析和偏相关分析详细操作过程(附案例实战)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

java的spi机制使用场景讲解和具体使用

八股文背多了&#xff0c;相信大家都听说过一个词&#xff0c;SPI扩展。 有的面试官就很喜欢问这个问题&#xff0c;SpringBoot的自动装配是如何实现的&#xff1f; 基本上&#xff0c;你一说是基于spring的SPI扩展机制&#xff0c;再把spring.factories文件和EnableAutoConf…

C++(多态上)

目录: 1.多态的概念 2.多态的定义和实现 3.虚函数构成重写的特例 4.剖析一道非常经典的题 5.剖析多态的原理 ------------------------------------------------------------------------------------------------------------------------- 1.多态的概念 概念:通俗来说&#…

Word2vec原理+实战学习笔记(二)

来源&#xff1a;投稿 作者&#xff1a;阿克西 编辑&#xff1a;学姐 前篇&#xff1a;Word2vec原理实战学习笔记&#xff08;一&#xff09;​​​​​​​ 视频链接&#xff1a;https://ai.deepshare.net/detail/p_5ee62f90022ee_zFpnlHXA/6 5 对比模型&#xff08;论文Mod…

Python使用AI photo2cartoon制作属于你的漫画头像

Python使用AI photo2cartoon制作属于你的漫画头像 1. 效果图2. 原理3. 源码参考 git clone https://github.com/minivision-ai/photo2cartoon.git cd ./photo2cartoon python test.py --photo_path images/photo_test.jpg --save_path images/cartoon_result.png1. 效果图 官方…

(22)目标检测算法之 yolov8模型导出总结

yolov8模型导出总结 不断更新中… 几种部署情况: onnxxmlengine官网说明:https://github.com/ultralytics/ultralytics/blob/main/docs/modes/export.md导出参数: onnx 参数解析format: 导出的模型形式:onnx xml engine ... imgsz: 设置模型的输入尺寸大小,默认640*640 ke…

磁盘和固态磁盘

磁盘和固态磁盘 磁盘的物理结构 ​ 磁盘的表面由一些磁性的物质组成&#xff0c;可以用这些磁性物质来记录二进制数据。磁盘的盘面被划分成一个个磁道&#xff0c;这样一个“圈”就是一个磁道。同一磁盘上不同磁道上记录的信息量相同&#xff0c;因此内侧磁道上的数据密度较大…

STM32F429移植microPython笔记

目录 一、microPython下载。二、安装开发环境。三、编译开发板源码。四、下载验证。 一、microPython下载。 https://micropython.org/download/官网 下载后放在linux中。 解压命令&#xff1a; tar -xvf micropython-1.19.1.tar.xz 二、安装开发环境。 sudo apt-get inst…

【Java笔试强训 14】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;计算日期…

玩着3dmax把Python学了-01

3ds Max 2022以前的版本要借助Python的api来实现Python编程达到编辑绘图脚本的功能&#xff0c;但是好消息来了&#xff0c;3ds Max 2022 起&#xff0c;MaxPlus 不再作为 3ds Max 的 Python API 包含在内。而是3ds Max 将 Python 3.7 的标准版本包涵其中了&#xff0c;位于 [3…

Filter 过滤器

Filter过滤器介绍 这里我们讲解Filter的执行流程&#xff0c;从下图可以大致了解到&#xff0c;当客户端发送请求的时候&#xff0c;会经过过滤器&#xff0c;然后才能到我们的servlet&#xff0c;当我们的servlet处理完请求之后&#xff0c;我们的response还是先经过过滤器才…

基于SpringBoot的线上日志阅读器

软件特点 部署后能通过浏览器查看线上日志。支持Linux、Windows服务器。采用随机读取的方式&#xff0c;支持大文件的读取。支持实时打印新增的日志&#xff08;类终端&#xff09;。支持日志搜索。 使用手册 基本页面 配置路径 配置日志所在的目录&#xff0c;配置后按回车…