springboot项目实现断点续传

这篇具有很好参考价值的文章主要介绍了springboot项目实现断点续传。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • java代码
package com.ruoyi.web.upload.controller;
import com.ruoyi.web.upload.dto.FileChunkDTO;
import com.ruoyi.web.upload.dto.FileChunkResultDTO;
import com.ruoyi.web.upload.result.Result;
import com.ruoyi.web.upload.service.IUploadService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
 * @ProjectName UploaderController
 * @author Administrator
 * @version 1.0.0
 * @Description 附件分片上传
 * @createTime 2022/4/13 0013 15:58
 */
@RestController
@RequestMapping("upload")
public class UploaderController {
   @Resource
   private IUploadService uploadService;
   /**
    * 检查分片是否存在
    *
    * @return
    */
   @GetMapping("chunk")
   public Result checkChunkExist(FileChunkDTO chunkDTO) {
      FileChunkResultDTO fileChunkCheckDTO;
      try {
         fileChunkCheckDTO = uploadService.checkChunkExist(chunkDTO);
         return Result.ok(fileChunkCheckDTO);
      } catch (Exception e) {
         return Result.fail(e.getMessage());
      }
   }
   /**
    * 上传文件分片
    *
    * @param chunkDTO
    * @return
    */
   @PostMapping("chunk")
   public Result uploadChunk(FileChunkDTO chunkDTO) {
      try {
         uploadService.uploadChunk(chunkDTO);
         return Result.ok(chunkDTO.getIdentifier());
      } catch (Exception e) {
         return Result.fail(e.getMessage());
      }
   }
   /**
    * 请求合并文件分片
    *
    * @param chunkDTO
    * @return
    */
   @PostMapping("merge")
   public Result mergeChunks(@RequestBody FileChunkDTO chunkDTO) {
      try {
         boolean success = uploadService.mergeChunk(chunkDTO.getIdentifier(), chunkDTO.getFilename(), chunkDTO.getTotalChunks());
         return Result.ok(success);
      } catch (Exception e) {
         return Result.fail(e.getMessage());
      }
   }
}
package com.ruoyi.web.upload.dto;
import org.springframework.web.multipart.MultipartFile;
/**
 * @ProjectName FileChunkDTO
 * @author Administrator
 * @version 1.0.0
 * @Description 附件分片上传
 * @createTime 2022/4/13 0013 15:59
 */
public class FileChunkDTO {
   /**
    * 文件 md5
    */
   private String identifier;
   /**
    * 分块文件
    */
   MultipartFile file;
   /**
    * 当前分块序号
    */
   private Integer chunkNumber;
   /**
    * 分块大小
    */
   private Long chunkSize;
   /**
    * 当前分块大小
    */
   private Long currentChunkSize;
   /**
    * 文件总大小
    */
   private Long totalSize;
   /**
    * 分块总数
    */
   private Integer totalChunks;
   /**
    * 文件名
    */
   private String filename;
   public String getIdentifier() {
      return identifier;
   }
   public void setIdentifier(String identifier) {
      this.identifier = identifier;
   }
   public MultipartFile getFile() {
      return file;
   }
   public void setFile(MultipartFile file) {
      this.file = file;
   }
   public Integer getChunkNumber() {
      return chunkNumber;
   }
   public void setChunkNumber(Integer chunkNumber) {
      this.chunkNumber = chunkNumber;
   }
   public Long getChunkSize() {
      return chunkSize;
   }
   public void setChunkSize(Long chunkSize) {
      this.chunkSize = chunkSize;
   }
   public Long getCurrentChunkSize() {
      return currentChunkSize;
   }
   public void setCurrentChunkSize(Long currentChunkSize) {
      this.currentChunkSize = currentChunkSize;
   }
   public Long getTotalSize() {
      return totalSize;
   }
   public void setTotalSize(Long totalSize) {
      this.totalSize = totalSize;
   }
   public Integer getTotalChunks() {
      return totalChunks;
   }
   public void setTotalChunks(Integer totalChunks) {
      this.totalChunks = totalChunks;
   }
   public String getFilename() {
      return filename;
   }
   public void setFilename(String filename) {
      this.filename = filename;
   }
   @Override
   public String toString() {
      return "FileChunkDTO{" +
              "identifier='" + identifier + '\'' +
              ", file=" + file +
              ", chunkNumber=" + chunkNumber +
              ", chunkSize=" + chunkSize +
              ", currentChunkSize=" + currentChunkSize +
              ", totalSize=" + totalSize +
              ", totalChunks=" + totalChunks +
              ", filename='" + filename + '\'' +
              '}';
   }
}
package com.ruoyi.web.upload.dto;
import java.util.Set;
/**
 * @ProjectName FileChunkResultDTO
 * @author Administrator
 * @version 1.0.0
 * @Description 附件分片上传
 * @createTime 2022/4/13 0013 15:59
 */
public class FileChunkResultDTO {
   /**
    * 是否跳过上传
    */
   private Boolean skipUpload;
   /**
    * 已上传分片的集合
    */
   private Set<Integer> uploaded;
   public Boolean getSkipUpload() {
      return skipUpload;
   }
   public void setSkipUpload(Boolean skipUpload) {
      this.skipUpload = skipUpload;
   }
   public Set<Integer> getUploaded() {
      return uploaded;
   }
   public void setUploaded(Set<Integer> uploaded) {
      this.uploaded = uploaded;
   }
   public FileChunkResultDTO(Boolean skipUpload, Set<Integer> uploaded) {
      this.skipUpload = skipUpload;
      this.uploaded = uploaded;
   }
   public FileChunkResultDTO(Boolean skipUpload) {
      this.skipUpload = skipUpload;
   }
}
package com.ruoyi.web.upload.dto;
import lombok.Getter;
/**
   * @Author
   * @Date Created in  2023/2/23 17:25
   * @DESCRIPTION:  统一返回结果状态信息类
   * @Version V1.0
   */
@Getter
@SuppressWarnings("all")
public enum ResultCodeEnum {
    SUCCESS(200,"成功"),
    FAIL(201, "失败"),
    PARAM_ERROR( 202, "参数不正确"),
    SERVICE_ERROR(203, "服务异常"),
    DATA_ERROR(204, "数据异常"),
    DATA_UPDATE_ERROR(205, "数据版本异常"),

    LOGIN_AUTH(208, "未登陆"),
    PERMISSION(209, "没有权限"),

    CODE_ERROR(210, "验证码错误"),
    LOGIN_MOBLE_ERROR(211, "账号不正确"),
    LOGIN_DISABLED_ERROR(212, "改用户已被禁用"),
    REGISTER_MOBLE_ERROR(213, "手机号码格式不正确"),
    REGISTER_MOBLE_ERROR_NULL(214, "手机号码为空"),

    LOGIN_AURH(214, "需要登录"),
    LOGIN_ACL(215, "没有权限"),

    URL_ENCODE_ERROR( 216, "URL编码失败"),
    ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回调请求"),
    FETCH_ACCESSTOKEN_FAILD( 218, "获取accessToken失败"),
    FETCH_USERINFO_ERROR( 219, "获取用户信息失败");
    private Integer code;
    private String message;
    private ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}
package com.ruoyi.web.upload.service;
import com.ruoyi.web.upload.dto.FileChunkDTO;
import com.ruoyi.web.upload.dto.FileChunkResultDTO;
import java.io.IOException;
/**
 * @ProjectName IUploadService
 * @author Administrator
 * @version 1.0.0
 * @Description 附件分片上传
 * @createTime 2022/4/13 0013 15:59
 */
public interface IUploadService {
   /**
    * 检查文件是否存在,如果存在则跳过该文件的上传,如果不存在,返回需要上传的分片集合
    * @param chunkDTO
    * @return
    */
   FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO);
   /**
    * 上传文件分片
    * @param chunkDTO
    */
   void uploadChunk(FileChunkDTO chunkDTO) throws IOException;
   /**
    * 合并文件分片
    * @param identifier
    * @param fileName
    * @param totalChunks
    * @return
    * @throws IOException
    */
   boolean mergeChunk(String identifier,String fileName,Integer totalChunks)throws IOException;
}
package com.ruoyi.web.upload.service.impl;
import com.ruoyi.web.upload.dto.FileChunkDTO;
import com.ruoyi.web.upload.dto.FileChunkResultDTO;
import com.ruoyi.web.upload.service.IUploadService;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.*;
/**
 * @ProjectName UploadServiceImpl
 * @author Administrator
 * @version 1.0.0
 * @Description 附件分片上传
 * @createTime 2022/4/13 0013 15:59
 */
@Service
@SuppressWarnings("all")
public class UploadServiceImpl implements IUploadService {
   private Logger logger = LoggerFactory.getLogger(UploadServiceImpl.class);
   @Autowired
   private RedisTemplate redisTemplate;
   @Value("${ruoyi.profile}")
   private String uploadFolder;
   /**
    * 检查文件是否存在,如果存在则跳过该文件的上传,如果不存在,返回需要上传的分片集合
    *  检查分片是否存在
          ○ 检查目录下的文件是否存在。
          ○ 检查redis存储的分片是否存在。
          ○ 判断分片数量和总分片数量是否一致。
               如果文件存在并且分片上传完毕,标识已经完成附件的上传,可以进行秒传操作。
               如果文件不存在或者分片为上传完毕,则返回false并返回已经上传的分片信息。
    * @param chunkDTO
    * @return
    */
   @Override
   public FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO) {
      //1.检查文件是否已上传过
      //1.1)检查在磁盘中是否存在
      String fileFolderPath = getFileFolderPath(chunkDTO.getIdentifier());
      logger.info("fileFolderPath-->{}", fileFolderPath);
      String filePath = getFilePath(chunkDTO.getIdentifier(), chunkDTO.getFilename());
      File file = new File(filePath);
      boolean exists = file.exists();
      //1.2)检查Redis中是否存在,并且所有分片已经上传完成。
      Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
      if (uploaded != null && uploaded.size() == chunkDTO.getTotalChunks() && exists) {
         return new FileChunkResultDTO(true);
      }
      File fileFolder = new File(fileFolderPath);
      if (!fileFolder.exists()) {
         boolean mkdirs = fileFolder.mkdirs();
         logger.info("准备工作,创建文件夹,fileFolderPath:{},mkdirs:{}", fileFolderPath, mkdirs);
      }
      // 断点续传,返回已上传的分片
      return new FileChunkResultDTO(false, uploaded);
   }
   /**
    * 上传分片
    *  上传附件分片
           ○ 判断目录是否存在,如果不存在则创建目录。
           ○ 进行切片的拷贝,将切片拷贝到指定的目录。
           ○ 将该分片写入redis
    * @param chunkDTO
    */
   @Override
   public void uploadChunk(FileChunkDTO chunkDTO) {
      //分块的目录
      String chunkFileFolderPath = getChunkFileFolderPath(chunkDTO.getIdentifier());
      logger.info("分块的目录 -> {}", chunkFileFolderPath);
      File chunkFileFolder = new File(chunkFileFolderPath);
      if (!chunkFileFolder.exists()) {
         boolean mkdirs = chunkFileFolder.mkdirs();
         logger.info("创建分片文件夹:{}", mkdirs);
      }
      //写入分片
      try (
              InputStream inputStream = chunkDTO.getFile().getInputStream();
              FileOutputStream outputStream = new FileOutputStream(new File(chunkFileFolderPath + chunkDTO.getChunkNumber()))
      ) {
         IOUtils.copy(inputStream, outputStream);
         logger.info("文件标识:{},chunkNumber:{}", chunkDTO.getIdentifier(), chunkDTO.getChunkNumber());
         //将该分片写入redis
         long size = saveToRedis(chunkDTO);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   @Override
   public boolean mergeChunk(String identifier, String fileName, Integer totalChunks) throws IOException {
      return mergeChunks(identifier, fileName, totalChunks);
   }
   /**
    * 合并分片
    *
    * @param identifier
    * @param filename
    */
   private boolean mergeChunks(String identifier, String filename, Integer totalChunks) {
      String chunkFileFolderPath = getChunkFileFolderPath(identifier);
      String filePath = getFilePath(identifier, filename);
      // 检查分片是否都存在
      if (checkChunks(chunkFileFolderPath, totalChunks)) {
         File chunkFileFolder = new File(chunkFileFolderPath);
         File mergeFile = new File(filePath);
         File[] chunks = chunkFileFolder.listFiles();
         // 切片排序1、2/3、---
         List fileList = Arrays.asList(chunks);
         Collections.sort(fileList, (Comparator<File>) (o1, o2) -> {
            return Integer.parseInt(o1.getName()) - (Integer.parseInt(o2.getName()));
         });
         try {
            RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw");
            byte[] bytes = new byte[1024];
            for (File chunk : chunks) {
               RandomAccessFile randomAccessFileReader = new RandomAccessFile(chunk, "r");
               int len;
               while ((len = randomAccessFileReader.read(bytes)) != -1) {
                  randomAccessFileWriter.write(bytes, 0, len);
               }
               randomAccessFileReader.close();
            }
            randomAccessFileWriter.close();
         } catch (Exception e) {
            return false;
         }
         return true;
      }
      return false;
   }
   /**
    * 检查分片是否都存在
    * @param chunkFileFolderPath
    * @param totalChunks
    * @return
    */
   private boolean checkChunks(String chunkFileFolderPath, Integer totalChunks) {
      try {
         for (int i = 1; i <= totalChunks + 1; i++) {
            File file = new File(chunkFileFolderPath + File.separator + i);
            if (file.exists()) {
               continue;
            } else {
               return false;
            }
         }
      } catch (Exception e) {
         return false;
      }
      return true;
   }
   /**
    * 分片写入Redis
    * 判断切片是否已存在,如果未存在,则创建基础信息,并保存。
    * @param chunkDTO
    */
   private synchronized long saveToRedis(FileChunkDTO chunkDTO) {
      Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
      if (uploaded == null) {
         uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNumber()));
         HashMap<String, Object> objectObjectHashMap = new HashMap<>();
         objectObjectHashMap.put("uploaded", uploaded);
         objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks());
         objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize());
//            objectObjectHashMap.put("path", getFileRelativelyPath(chunkDTO.getIdentifier(), chunkDTO.getFilename()));
         objectObjectHashMap.put("path", chunkDTO.getFilename());
         redisTemplate.opsForHash().putAll(chunkDTO.getIdentifier(), objectObjectHashMap);
      } else {
         uploaded.add(chunkDTO.getChunkNumber());
         redisTemplate.opsForHash().put(chunkDTO.getIdentifier(), "uploaded", uploaded);
      }
      return uploaded.size();
   }
   /**
    * 得到文件的绝对路径
    *
    * @param identifier
    * @param filename
    * @return
    */
   private String getFilePath(String identifier, String filename) {
      String ext = filename.substring(filename.lastIndexOf("."));
//        return getFileFolderPath(identifier) + identifier + ext;
      return uploadFolder + filename;
   }
   /**
    * 得到文件的相对路径
    *
    * @param identifier
    * @param filename
    * @return
    */
   private String getFileRelativelyPath(String identifier, String filename) {
      String ext = filename.substring(filename.lastIndexOf("."));
      return "/" + identifier.substring(0, 1) + "/" +
              identifier.substring(1, 2) + "/" +
              identifier + "/" + identifier
              + ext;
   }
   /**
    * 得到分块文件所属的目录
    *
    * @param identifier
    * @return
    */
   private String getChunkFileFolderPath(String identifier) {
      return getFileFolderPath(identifier) + "chunks" + File.separator;
   }
   /**
    * 得到文件所属的目录
    *
    * @param identifier
    * @return
    */
   private String getFileFolderPath(String identifier) {
      return uploadFolder + identifier.substring(0, 1) + File.separator +
              identifier.substring(1, 2) + File.separator +
              identifier + File.separator;
//        return uploadFolder;
   }
}
package com.ruoyi.web.upload.result;
import com.ruoyi.web.upload.dto.ResultCodeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @Author
 * @Date Created in  2023/2/23 17:25
 * @DESCRIPTION:  全局统一返回结果
 * @Version V1.0
 */
@Data
@ApiModel(value = "全局统一返回结果")
@SuppressWarnings("all")
public class Result<T> {
    @ApiModelProperty(value = "返回码")
    private Integer code;
    @ApiModelProperty(value = "返回消息")
    private String message;
    @ApiModelProperty(value = "返回数据")
    private T data;
    private Long total;
    public Result(){}
    protected static <T> Result<T> build(T data) {
        Result<T> result = new Result<T>();
        if (data != null)
            result.setData(data);
        return result;
    }
    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }
    public static <T> Result<T> build(Integer code, String message) {
        Result<T> result = build(null);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
    public static<T> Result<T> ok(){
        return Result.ok(null);
    }
    /**
     * 操作成功
     * @param data
     * @param <T>
     * @return
     */
    public static<T> Result<T> ok(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }
    public static<T> Result<T> fail(){
        return Result.fail(null);
    }
    /**
     * 操作失败
     * @param data
     * @param <T>
     * @return
     */
    public static<T> Result<T> fail(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.FAIL);
    }
    public Result<T> message(String msg){
        this.setMessage(msg);
        return this;
    }
    public Result<T> code(Integer code){
        this.setCode(code);
        return this;
    }
    public boolean isOk() {
        if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
            return true;
        }
        return false;
    }
}
  • 前端代码
  • mainjs导入uploader
import uploader from 'vue-simple-uploader'
Vue.use(uploader)
  • 安装uploader和spark-md5的依赖
npm install --save vue-simple-uploader
npm install --save spark-md5
  • 创建uploader组件
<template>
  <div>
    <uploader
        :autoStart="false"
        :options="options"
        :file-status-text="statusText"
        class="uploader-example"
        @file-complete="fileComplete"
        @complete="complete"
        @file-success="fileSuccess"
        @files-added="filesAdded"
    >
      <uploader-unsupport></uploader-unsupport>
      <uploader-drop>
        <p>将文件拖放到此处以上传</p>
        <uploader-btn>选择文件</uploader-btn>
        <uploader-btn :attrs="attrs">选择图片</uploader-btn>
        <uploader-btn :directory="true">选择文件夹</uploader-btn>
      </uploader-drop>
      <!-- <uploader-list></uploader-list> -->
      <uploader-files> </uploader-files>
    </uploader>
    <br />
    <el-button @click="allStart()" :disabled="disabled">全部开始</el-button>
    <el-button @click="allStop()" style="margin-left: 4px">全部暂停</el-button>
    <el-button @click="allRemove()" style="margin-left: 4px">全部移除</el-button>
  </div>
</template>

<script>
import axios from "axios";
import SparkMD5 from "spark-md5";
import {upload} from "@/api/user";
// import storage from "store";
// import { ACCESS_TOKEN } from '@/store/mutation-types'
export default {
  name: "Home",
  data() {
    return {
      skip: false,
      options: {
        target: "//localhost:9999/upload/chunk",
        // 开启服务端分片校验功能
        testChunks: true,
        parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) {
          return parsedTimeRemaining
              .replace(/\syears?/, "年")
              .replace(/\days?/, "天")
              .replace(/\shours?/, "小时")
              .replace(/\sminutes?/, "分钟")
              .replace(/\sseconds?/, "秒");
        },
        // 服务器分片校验函数
        checkChunkUploadedByResponse: (chunk, message) => {
          const result = JSON.parse(message);
          if (result.data.skipUpload) {
            this.skip = true;
            return true;
          }
          return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0;
        },
        // headers: {
        //   // 在header中添加的验证,请根据实际业务来
        //   "Access-Token": storage.get(ACCESS_TOKEN),
        // },
      },
      attrs: {
        accept: "image/*",
      },
      statusText: {
        success: "上传成功",
        error: "上传出错了",
        uploading: "上传中...",
        paused: "暂停中...",
        waiting: "等待中...",
        cmd5: "计算文件MD5中...",
      },
      fileList: [],
      disabled: true,
    };
  },
  watch: {
    fileList(o, n) {
      this.disabled = false;
    },
  },
  methods: {
    // fileSuccess(rootFile, file, response, chunk) {
    //   // console.log(rootFile);
    //   // console.log(file);
    //   // console.log(message);
    //   // console.log(chunk);
    //   const result = JSON.parse(response);
    //   console.log(result.success, this.skip);
    //
    //   if (result.success && !this.skip) {
    //     axios
    //         .post(
    //             "http://127.0.0.1:9999/upload/merge",
    //             {
    //               identifier: file.uniqueIdentifier,
    //               filename: file.name,
    //               totalChunks: chunk.offset,
    //             },
    //             // {
    //             //   headers: { "Access-Token": storage.get(ACCESS_TOKEN) }
    //             // }
    //         )
    //         .then((res) => {
    //           if (res.data.success) {
    //             console.log("上传成功");
    //           } else {
    //             console.log(res);
    //           }
    //         })
    //         .catch(function (error) {
    //           console.log(error);
    //         });
    //   } else {
    //     console.log("上传成功,不需要合并");
    //   }
    //   if (this.skip) {
    //     this.skip = false;
    //   }
    // },
    fileSuccess(rootFile, file, response, chunk) {
      // console.log(rootFile);
      // console.log(file);
      // console.log(message);
      // console.log(chunk);
      const result = JSON.parse(response);
      console.log(result.success, this.skip);
      const user = {
        identifier: file.uniqueIdentifier,
        filename: file.name,
        totalChunks: chunk.offset,
      }
      if (result.success && !this.skip) {
        upload(user).then((res) => {
          if (res.code == 200) {
            console.log("上传成功");
          } else {
            console.log(res);
          }
        })
            .catch(function (error) {
              console.log(error);
            });
      } else {
        console.log("上传成功,不需要合并");
      }
      if (this.skip) {
        this.skip = false;
      }
    },
    fileComplete(rootFile) {
      // 一个根文件(文件夹)成功上传完成。
      // console.log("fileComplete", rootFile);
      // console.log("一个根文件(文件夹)成功上传完成。");
    },
    complete() {
      // 上传完毕。
      // console.log("complete");
    },
    filesAdded(file, fileList, event) {
      // console.log(file);
      file.forEach((e) => {
        this.fileList.push(e);
        this.computeMD5(e);
      });
    },
    computeMD5(file) {
      let fileReader = new FileReader();
      let time = new Date().getTime();
      let blobSlice =
          File.prototype.slice ||
          File.prototype.mozSlice ||
          File.prototype.webkitSlice;
      let currentChunk = 0;
      const chunkSize = 1024 * 1024;
      let chunks = Math.ceil(file.size / chunkSize);
      let spark = new SparkMD5.ArrayBuffer();
      // 文件状态设为"计算MD5"
      file.cmd5 = true; //文件状态为“计算md5...”
      file.pause();
      loadNext();
      fileReader.onload = (e) => {
        spark.append(e.target.result);
        if (currentChunk < chunks) {
          currentChunk++;
          loadNext();
          // 实时展示MD5的计算进度
          console.log(
              `${currentChunk}分片解析完成, 开始第${
                  currentChunk + 1
              } / ${chunks}分片解析`
          );
        } else {
          let md5 = spark.end();
          console.log(
              `MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
                  file.size
              } 用时:${new Date().getTime() - time} ms`
          );
          spark.destroy(); //释放缓存
          file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
          file.cmd5 = false; //取消计算md5状态
          file.resume(); //开始上传
        }
      };
      fileReader.onerror = function () {
        this.error(`文件${file.name}读取出错,请检查该文件`);
        file.cancel();
      };
      function loadNext() {
        let start = currentChunk * chunkSize;
        let end =
            start + chunkSize >= file.size ? file.size : start + chunkSize;
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
      }
    },
    allStart() {
      console.log(this.fileList);
      this.fileList.map((e) => {
        if (e.paused) {
          e.resume();
        }
      });
    },
    allStop() {
      console.log(this.fileList);
      this.fileList.map((e) => {
        if (!e.paused) {
          e.pause();
        }
      });
    },
    allRemove() {
      this.fileList.map((e) => {
        e.cancel();
      });
      this.fileList = [];
    },
  },
};
</script>

<style>
.uploader-example {
  width: 100%;
  padding: 15px;
  margin: 0px auto 0;
  font-size: 12px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
}
.uploader-example .uploader-btn {
  margin-right: 4px;
}
.uploader-example .uploader-list {
  max-height: 440px;
  overflow: auto;
  overflow-x: hidden;
  overflow-y: auto;
}
</style>

文章来源地址https://www.toymoban.com/news/detail-682331.html

到了这里,关于springboot项目实现断点续传的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • spring boot 阿里云oss 文件分片上传、断点续传

    文章目录 前言 一、申请阿里云oss 二、上代码 总结       阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,可提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。     您可以使用阿

    2024年02月07日
    浏览(48)
  • 前端 + 后端 实现分片上传(断点续传/极速秒传)

    先记录下,后面有时间再去实现 (已实现,可参考 SpringBoot+vue文件上传下载预览大文件分片上传文件上传进度 SpringBoot+vue 大文件分片下载) 可参考链接:vue上传大文件/视频前后端(java)代码 前端slice分片上传,后端用表记录分片索引和分片大小和分片总数,当接受完最后

    2023年04月17日
    浏览(47)
  • SpringBoot整合minio实现断点续传、分片上传(附源码)

    在Web开发中,大文件的上传是必不可少的功能之一。本文将介绍如何使用SpringBoot整合minio实现一个简单的大文件上传网站。 项目下载 gitee:https://gitee.com/wusupweilgy/springboot-vue.git 前端:vue2、element-ui组件、axios 后端:springboot、minio、mybatis-plus、redis 断点续传 分片上传 前端显示

    2024年02月04日
    浏览(48)
  • Java实现文件断点续传

    文件断点续传代码 测试代码

    2024年03月09日
    浏览(39)
  • 【SpringBoot整合系列】SpringBoot 实现大文件分片上传、断点续传及秒传

    小文件(图片、文档、视频)上传可以直接使用很多ui框架封装的上传组件,或者自己写一个input 上传,利用FormData 对象提交文件数据,后端使用spring提供的MultipartFile进行文件的接收,然后写入即可。 但是对于比较大的文件,比如上传2G左右的文件(http上传),就需要将文件

    2024年04月16日
    浏览(65)
  • aliyun-oss-sdk阿里云OSS视频上传(断点续传)前端实现

    最近上传视频的功能,突然炸了,两年没动的代码,突然不行辽,首次上传成功,后面继续上传就可以,但凡有一次上传失败,再上传文件就不行。 这里博主使用的是凭证上传方式哈。 官方文档:Web(JavaScript)上传SDK 查看了官方文档,确实是提到了,如使用1.5.3之前版本重

    2024年02月07日
    浏览(39)
  • Vue项目中大文件切片上传实现秒传、断点续传的详细实现教程

    在Vue项目中,大图片和多数据Excel等大文件的上传是一个非常常见的需求。然而,由于文件大小较大,上传速度很慢,传输中断等问题也难以避免。因此,为了提高上传效率和成功率,我们需要使用切片上传的方式,实现文件秒传、断点续传、错误重试、控制并发等功能,并

    2024年02月12日
    浏览(74)
  • 【前端面试】中大文件上传/下载:中等文件代理服务器放行+大文件切片传输+并发请求+localstorage实现断点续传

    目录 切片上传~spark-md5 原理:流式计算+分块处理 文件标识spark-md5:A-B A.切片哈希值合并 B.首尾切片+其他切片前中后各取2M 计算hash:A-B(参考React的Fiber架构) A.线程:web-worker B.空闲:requestIdleCallback 异步并发控制:A-B(参考http2的多路复用) A.promise.allSettled() B.并发数max=

    2024年02月12日
    浏览(55)
  • 【万字长文】Vue+SpringBoot实现大文件秒传、断点续传和分片上传完整教程(提供Gitee源码)

    前言:最近在实际项目中碰到一个需求,客户可能会上传比较大的文件,如果采用传统的文件上传方案可能会存在服务器压力大、资源浪费甚至内存溢出的一些安全风险,所以为了解决一系列问题,需要采用新的技术方案来实现大文件的上传;空闲的时候参考了网上的一些相

    2024年02月12日
    浏览(44)
  • 前端文件上传(文件上传,分片上传,断点续传)

    普通文件上传 思路: 首先获取用户选择的文件对象,并将其添加到一个 FormData 对象中。然后,使用 axios 的 post 方法将 FormData 对象发送到服务器。在 then 和 catch 中,我们分别处理上传成功和失败的情况,并输出相应的信息。 需要注意,在使用 axios 进行文件上传时,必须将

    2024年02月22日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包