SpringBoot+Vue实现大文件上传(断点续传-前端控制)

news2024/11/15 17:44:50

SpringBoot+Vue实现大文件上传(断点续传)

1 环境 SpringBoot 3.2.1,Vue 2,ElementUI
2 问题 在前一篇文章,我们写了分片上传来实现大文件上传,存在一个问题就是,中间失败的话需要重新上传,那样的话效率低,我们可以基于分片上传来用断点续传,当中间失败了我们可以从某个文件块开始上传而不是从头开始。这个其实可以有两个方案,一个是在前端控制,后端返回上传失败,我们就记住上传失败的文件块的下标,下次从这个下标开始上传即可;第二种从后端控制,把每个文件块的状态记录在表里,下次上传时只保存没上传过的文件块即可。

效果图
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

前端代码

<template>
  <div class="container">
    <el-upload
        class="upload-demo"
        drag
        action="/xml/fileUpload"
        multiple
        :on-change="handleChange"
        :auto-upload="false">
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div class="el-upload__tip">
        <el-progress :style="{ width: percentage + '%' }"  :text-inside="true"
                     :stroke-width="24"
                     :percentage="percentage" :status="uploadStatus"></el-progress>
      </div>
    </el-upload>
    <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: 'App',
  data() {
    return {
      file: '',
      fileList: [],
      CHUNK_SIZE: 1024 * 1024 * 100,//100MB
      percentage: 0,
      chunkNo: 0,
      uploadStatus:''
    }
  },
  watch: {},
  created() {
  },
  methods: {
    async submitUpload() {
      //获取上传的文件信息
      const file = this.fileList[0].raw
      //分片
      const totalChunks = Math.ceil(file.size / this.CHUNK_SIZE);
      const index = this.chunkNo
      this.uploadStatus = 'success'
      for (let i = index; i < totalChunks; i++) {
        const start = i * this.CHUNK_SIZE;
        const end = Math.min(start + this.CHUNK_SIZE, file.size);
        //将文件切片
        const chunk = file.slice(start, end);
        //组装参数
        const formData = new FormData();
        formData.append('file', chunk);
        formData.append('fileName', file.name);
        formData.append('index', i);
        formData.append('status', 1);
        try {
          const res = await axios.post('/xml/bigFileUpload', formData)
          if (res.data.code === 200) {
            this.percentage = Math.ceil((i + 1) / totalChunks * 100)
            this.chunkNo = i + 1
          } else {
            this.$message({
              message: '上传失败',
              type: 'error'
            });
            this.uploadStatus = 'exception'
            return
          }
        }catch (err){
          console.log(err);
          this.$message.error('上传失败');
          this.uploadStatus = 'exception'
          return
        }
      }
      //调用合并分片请求
      await fetch('/xml/merge', {
        method: 'POST',
        body: JSON.stringify({fileName: file.name}),
        headers: {'Content-Type': 'application/json'}
      });
    },
    handleChange(file, fileList) {
      this.fileList = fileList
    },
  }
}
</script>

<style>
.container {
  display: flex;
}
.progress-number {
  position: absolute;
  right: 5px;
  top: 0;
  color: white;
  transition: opacity 0.5s ease; /* 文字的平滑过渡效果 */
}
</style>

后端代码

package org.wjg.onlinexml.controller;

import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.wjg.onlinexml.po.Result;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.util.Map;

@RestController
public class BigFileControll {
    // 获取资源文件夹的路径,路径为 项目所在路径/upload/
    private static final String UPLOAD_DIR = System.getProperty("user.dir") + "/upload/";

    /**
     * 保存分片
     * @param file
     * @param fileName
     * @param index
     * @return
     */
    @RequestMapping("/bigFileUpload")
    private Result bigFileUpload(@RequestParam("file") MultipartFile file, @RequestParam("fileName") String fileName, @RequestParam("index") int index,@RequestParam("status") int status) {
        if (file.isEmpty()) {
            return Result.builder().code(500).msg("上传失败!").build();
        }
        File uploadDir = new File(UPLOAD_DIR);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        File uploadFile = new File(UPLOAD_DIR + fileName + "_" + index);
        try {
            //模拟上传中断-----------------------
            if(status == 1){
                if(index == 2){
                    return Result.builder().code(500).msg("上传失败").build();
                }
            }
            //-------------------结束------------------
            file.transferTo(uploadFile);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.builder().code(500).msg("上传失败").build();
        }
        return Result.builder().code(200).msg("上传成功").build();
    }

    /**
     * 合并分片
     * @param request
     * @return
     */
    @PostMapping("/merge")
    public Result mergeChunks(@RequestBody Map<String, String> request) {
        String filename = request.get("fileName");
        File mergedFile = new File(UPLOAD_DIR + filename);
        try (FileOutputStream fos = new FileOutputStream(mergedFile)) {
            //循环获取分片,直到分片不存在为止
            for (int i = 0; ; i++) {
                File chunkFile = new File(UPLOAD_DIR + filename + "_" + i);
                if (!chunkFile.exists()) {
                    break;
                }
                //将分片复制到一个文件中,这种方法会追加
                Files.copy(chunkFile.toPath(), fos);
                //删除分片
                chunkFile.delete();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.builder().code(200).msg("合并成功").build();
    }
}


总结:这个方式基于分片上传,其实改动相对比较小,就是前端记录下文件块的下标,部分代码为模拟上传中断,方便大家测试,实际应用时可删除。请注意,前后端代码写的逻辑性不强,只为展示基础的用法,在实际使用时,可基于实际需求加以优化,如重新上传新文件时重置 上传进度和记录的文件块下标等,如有不对的地方,欢迎大家指正。

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

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

相关文章

QT WIN11 FluentUI APP开发

代码 import QtQuick import QtQuick.Controls import FluentUIItem {property bool autoPlay: trueproperty int loopTime: 2000property var modelproperty Component delegateproperty bool showIndicator: trueproperty int indicatorGravity : Qt.AlignBottom | Qt.Align…

【MySQL】一文带你理清<行级锁>(行锁,间隙锁,临键锁)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

STM32(六):定时器——输出比较实验

PWM驱动呼吸灯 源码&#xff1a; #include "stm32f10x.h" // Device headervoid PWM_Init(void) {RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启时钟TIM_InternalClockConfig(TIM2);//选择时基单元的时钟TIM_TimeBaseInitTypeDef TI…

怎么管控终端电脑上的移动端口

管控终端电脑上的移动端口&#xff0c;尤其是USB等移动端口&#xff0c;是确保企业数据安全和提升网络管理效率的重要手段。 一、使用注册表编辑器禁用USB端口&#xff08;适用于Windows系统&#xff09; 打开注册表编辑器&#xff1a; 同时按下“WinR”组合键&#xff0c;打…

【C++从小白到大牛】C++智能指针的使用、原理和分类

目录 1、我们为什么需要智能指针&#xff1f; 2、内存泄露 2.1 什么是内存泄漏&#xff0c;内存泄漏的危害 2.2如何避免内存泄漏 总结一下: 3.智能指针的使用及原理 3.1 RAII 3.2关于深拷贝和浅拷贝更深层次的理解&#xff1a; 3.3 std::auto_ptr 3.4 std::unique_pt…

《黑神话:悟空》登顶全球:游戏行业投资新风向与投资洞察

目录 引言 一、原创IP的崛起&#xff1a;文化共鸣与市场潜力 1《黑神话:悟空》的原创IP魅力 2 原创IP在游戏行业中的重要性 3 原创IP成为新的投资热点 4 文化共鸣的关键作用 二、高质量内容为王&#xff1a;技术与创新的双重驱动 1 《黑神话:悟空》的高质量内容展示 2…

Java接口interface(内含练习)

为什么有接口&#xff1f; 接口就是一种规则&#xff0c;更侧向是一种行为 接口的定义和使用 接口用关键字interface来定义 public interface 接口名{} 接口不能实例化 接口和接口之间是实现关系&#xff0c;通过implements关键字表示 public class 类名 implements 接口…

浅谈线性表——链表

文章目录 一、ArrayList的缺陷二、什么是链表&#xff1f;三、自我实现一个单向不带头非循环结构的链表3.1、实现代码3.2、代码解析 四、自我实现一个双向不带头非循环结构的链表4.1、实现代码 一、ArrayList的缺陷 前面学习了顺序表&#xff0c;顺序表在知道下标时可以快速的…

python应用之random模块(居然还有那么多的随机算法函数)

random 是 Python 的一个常用的内置模块&#xff0c;模块提供了生成随机数的功能&#xff0c;包含了多种生成随机数的函数&#xff0c;比如生成随机整数、随机浮点数、从序列中随机选择元素等。 使用 random模块 要使用 random模块&#xff0c;直接导入它即可。 import rand…

spring揭秘09-aop03-aop织入器织入横切逻辑与自动织入

文章目录 【README】【1】spring aop的织入【1.1】使用ProxyFactory 作为织入器【1.2】基于接口的代理&#xff08;JDK动态代理&#xff0c;目标类实现接口&#xff09;【补充】 【1.2】基于类的代理&#xff08;CGLIB动态代理&#xff0c;目标类没有实现接口&#xff09;【1.2…

Nginx: 配置项之autoIndex模块与Nginx变量

autoIndex模块 autoindex模块它所实现的一个基本功能&#xff0c;是当用户请求以 / 结尾式的URL&#xff0c;它会列出对应的目录结构比如说, 在实际的生态环境中&#xff0c;内部系统可能经常需要为用户提供一些下载功能。可能需要列出来某一个磁盘上的一个文件&#xff0c; 比…

【D-DCVRP】求解DCVRP改进贪婪算法(三)

一、Held-Harp模型 海尔德和卡尔普在1970年提出景点模型,用于求解TSP问题的最优解下界 该模型同样可以用于DCVRP问题,既有定理1成立。 定理1:根据Held-Karp模型使用向量 π = ( 0 , π 1 , π 2 , ⋯   , π n ) \pi=(0,\pi_1,\pi_2,\cdots,\pi_n) π=(0,π1​,π2​,⋯…

Datawhale第五期夏令营-CV竞赛

CV竞赛 0.赛事报名租用4090 1.开始运行下载文件提交结果 2.内容解释赛题背景赛题目标社会价值评分规则baseline精读代码什么是YOLO 主要代码内容精读使用Ultraalytics运行代码 0.赛事报名 赛事官网:https://www.marsbigdata.com/competition/details?id3839107548872 租用40…

【Redis】RDB和AOF持久化

RDB和AOF持久化 一、什么是持久化&#xff1f;二、RDB三、AOF 一、什么是持久化&#xff1f; 数据一般写在内存上&#xff0c;但是当重新启动计算机内存数据是会丢失的&#xff0c;而硬盘中的数据是不会丢失的&#xff0c;所以&#xff0c;当我们把数据从内存放到硬盘中的话就…

解决Windows下载完anaconda之后,在pycharm中使用anaconda

怎么下载anaconda我就不详细讲了&#xff0c;就是官方下载基本嫩都是下一步下一步你就可以 一、首先配置环境变量如图 二、查看anaconda情况 三、打开pycharm,如下图操作 ## 注意这里的.bat文件需要在你下载到的anaconda中去找 完毕

6款ai智能文章改写软件,轻松实现文章自动改写

在内容创作领域&#xff0c;改写文章是一项费时费力的工作。为了让创作者从繁琐的改写任务中解脱出来&#xff0c;本文将为你详细介绍六款ai智能文章改写软件&#xff0c;助你轻松实现文章自动改写&#xff0c;提升创作效率。 一、创作者的痛点&#xff1a;文章改写的挑战 作为…

【C++ Primer Plus习题】5.5

问题: 解答: #include <iostream> using namespace std;#define MONTHSCOUNT 12int main() {string months[MONTHSCOUNT] { "January","February","March","April","May","June","July","…

高斯混合模型原理及Python实践

高斯混合模型&#xff08;Gaussian Mixture Model&#xff0c;简称GMM&#xff09;是一种统计学中的概率模型&#xff0c;用于表示由多个高斯分布&#xff08;正态分布&#xff09;混合组成的数据集合。其核心原理基于假设数据集中的每个数据点都是由多个潜在的高斯分布之一生成…

SAP商业地产管理(RE-FX)

SAP 提供了多个模块来支持租赁业务流程和会计处理&#xff0c;这些模块包括但不限于&#xff1a; SAP Leasing&#xff1a;这是一个为租赁公司提供的行业解决方案&#xff0c;支持从租赁起源到中期变更和租赁结束选项的所有阶段的业务流程。SAP Leasing 集成了 SAP CRM 和 SAP …

Java 中的 BIO, NIO, AIO 原理以及示例代码

本文参考&#xff1a; https://blog.csdn.net/yhl_jxy/article/details/79335692 https://www.cnblogs.com/cuzzz/p/17290070.html https://www.cnblogs.com/cuzzz/p/17473398.html https://pdai.tech/md/java/io/java-io-nio-select-epoll.html 最近准备看 Kafka 源码&#xf…