本文作者:DurkBlue

浅谈以下如何实现超大文件断点续传功能推荐

DurkBlue 昨天 144
浅谈以下如何实现超大文件断点续传功能摘要: 断点续传 就是下载文件时,不必重头开始下载,而是从指定的位置继续下载,这样的功能就叫做断点续传。  断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是...

断点续传 就是下载文件时,不必重头开始下载,而是从指定的位置继续下载,这样的功能就叫做断点续传。  断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。续传就是当一个未完成的下载任务再次开始时,会从上次的断点继续传送。

以前文件无法分割,但随着html5新特性的引入,类似普通字符串、数组的分割,我们可以可以使用slice方法来分割文件。所以断点续传的最基本实现也就是:前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。

而我们需要对FileList对象进行修改再提交,在之前的文章中知晓了这种提交的一些注意点,因为FileList对象不能直接更改,所以不能直接通过表单的.submit()方法上传提交,需要结合FormData对象生成一个新的数据,通过Ajax进行上传操作。


设计思路

1. 分片上传(Chunk Upload)

核心思想:将一个大文件(比如1GB)切割成多个固定大小的小分片(比如10MB/片),分别上传,服务端接收所有分片后再合并成完整文件。

解决问题:避免单次上传大文件时网络中断导致全部重传、请求超时、服务器内存溢出等问题。

2. 断点 续传(Resumable Upload)

核心思想:上传前先校验文件的唯一标识(如MD5/文件大小+修改时间),服务端查询该文件已上传的分片,只上传未完成的分片。

关键依赖:需要一个“分片记录器”(数据库/缓存),记录每个文件的分片上传状态(已上传分片索引、总分片数、文件唯一标识等)。

3. 多文件上传

本质是对单文件上传逻辑的复用,前端传递文件列表,后端循环处理每个文件的分片/断点续传逻辑即可。


整体实现流程

整个流程分为前端预处理和后端核心逻辑两部分,以下是标准化的实现步骤:


步骤1:文件唯一标识生成(前端+后端)

为了区分不同文件、校验分片归属,首先生成文件的唯一标识(推荐MD5):

前端:读取文件内容,计算文件的MD5(可使用SparkMD5等库),同时指定分片大小(如10MB),计算总分片数。

后端:接收MD5后,可作为文件的唯一ID,用于查询分片上传状态。


步骤2:断点续传校验(前端请求,后端响应)

前端上传前先请求后端:“文件MD5为某某的文件,哪些分片已经上传完成”

后端:查询分片记录(数据库/缓存),返回已上传的分片索引列表。

前端:根据返回结果,只上传未完成的分片(实现断点续传)。



步骤3:分片上传(核心)

前端将文件切割成分片,逐个/并发上传分片,每个分片携带:

1.文件MD5(文件唯一标识)

2.分片索引(第几个分片,从0开始)

3.总分片数

4.分片内容

后端接收分片后,保存到临时目录,并更新分片上传状态。



步骤4:合并分片(所有分片上传完成后)

前端确认所有分片上传完成后,请求后端合并分片:

后端:按分片索引顺序读取所有临时分片文件,写入最终文件路径,删除临时分片,更新文件上传状态为“完成”。


基于这个思路,以下博主分享代码

html部分



<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="http://localhost/jquery-4.0.0.min.js"></script>
<!-- 使用SparkMD5库来获取文件的MD5哈希值 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
</head>
<body>
<h1>html5大文件断点切割上传</h1>
<input type="file" id="fileInput" />
<button onclick="calculateMD5()">上传文件</button>
<div id="progress">0%</div>
<script>
function calculateMD5() {
   const fileInput = document.getElementById('fileInput');
    const file = fileInput.files[0];
    if (!file) {
        alert('请选择一个文件');
        return;
    }
    const reader = new FileReader();
    const spark = new SparkMD5.ArrayBuffer();
    let fileLoaded = false;
      reader.onload = function(e) {
        // 文件加载成功
        fileLoaded = true;
       spark.append(e.target.result); // 读取文件完成之后放进内存缓冲区
        const hash = spark.end(); // 获取hash文件哈希值
        console.log(hash,"曲中")
        uploadFile(file, hash);
                
    }
    reader.onerror = function() {
        console.log('Spark读取文件中出现意外错误');
    };
    reader.readAsArrayBuffer(file);
 }
function uploadFile(file, fileMD5) {
    
    
    const CHUNK_SIZE = 1024 * 1024 * 5; // 5MB 每个分片大小
    const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
    checkFile(fileMD5, totalChunks);
    /**
     * offset  在那个分片索引标识开始上传
     * fileMD5  文件唯一标识
     * currentChunk 当前已经上传的分片数量
     * uploadedBytes 当前已完成上传的文件大小
     * */
    function uploadChunk(fileMD5, currentChunk) {
        offset = currentChunk * CHUNK_SIZE;
        let uploadedBytes = offset;
        const blob = file.slice(offset, offset + CHUNK_SIZE);
        const formData = new FormData();
        formData.append('chunkFile', blob);
        formData.append('chunkIndex', currentChunk);
        formData.append('totalChunks', totalChunks);
        formData.append('fileName', file.name);
        formData.append('fileSize', file.size);
        formData.append('chunkSize', CHUNK_SIZE);
        formData.append('fileMd5', fileMD5);
        $.ajax({
         url: 'http://localhost:60000/upload/chunk', 
            type: 'POST',
            data: formData,
            processData: false, 
            contentType: false, 
            xhr: function() { // Custom XMLHttpRequest to handle progress updates
                var xhr = new XMLHttpRequest(); // XMLHttpRequest object to monitor progress events
                xhr.upload.addEventListener("progress", function(evt) {
                    if (evt.lengthComputable) {
                        var percentComplete = Math.round((evt.loaded / evt.total) * 100);
                        uploadedBytes += evt.loaded; // Update uploaded bytes counter
                        $('#progress').text(percentComplete + '%'); // Update progress bar or text display on client side if needed
                    }
                }, false);
                return xhr; // Return the XHR object for jQuery to use it for the request
            },
            success: function(data) { // On successful upload of this chunk, upload the next one (recursive call)
            if (data.code == 200) {
                currentChunk++;
                if (currentChunk < totalChunks) {
                    uploadChunk(fileMD5, currentChunk); // 继续上传下一个分片
                } else {
                        // 所有分片上传完毕,发起文件合并请求
                    mergeFile(fileMD5);
                }
            } else {
                alert(data.msg);
            }
            }
        });
        
    }
    // 在执行上传该文件时,向后端提交检查该文件有没有中断上传的记录,如果有,则从上传失败的分片索引处开始继续上传
    function checkFile(fileMD5, chunksTotalNum){
        const formData = new FormData();
        formData.append('fileMd5', fileMD5);
        $.ajax({
            url: 'http://localhost:60000/upload/check', 
            type: 'POST',
            data: formData,
            processData: false, 
            contentType: false, 
            success: function(data) {
                
                if (data.code == 200) {
                    let arr = data.data;
                    if(arr.finishChunk.length == 0) uploadChunk(fileMD5, 0);
                    else if(chunksTotalNum <= arr.finishChunk.length){
                        if(arr.isMerge == 0) mergeFile(fileMD5);
                        else return alert('文件早已上传')
                    }
                    else{
                        // 继续上传未尽的分片
                        uploadChunk(fileMD5, arr.finishChunk[arr.finishChunk.length - 1]); // 继续上传下一个分片
                    }
                } else {
                    alert(data.msg);
                }
            }
        });
    }
    // 在分片上传全部完成后,向后端提交合并请求
    function mergeFile(fileMD5){
        const formData = new FormData();
        formData.append('fileMd5', fileMD5);
        $.ajax({
            url: 'http://localhost:60000/upload/merge', 
            type: 'POST',
            data: formData,
            processData: false, 
            contentType: false, 
            success: function(data) { // On successful upload of this chunk, upload the next one (recursive call)
                if (data.code == 200) {
                    alert('文件上传完成');
                } else {
                    alert('文件上传失败');
                }
            }
        });
    }
}
</script>
</body>
</html>


后端部分(采用了springBoot+Mysql)


数据表


CREATE TABLE `file_upload_record` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `file_md5` varchar(64) NOT NULL COMMENT '文件唯一标识(MD5)',
  `file_name` varchar(255) NOT NULL COMMENT '文件名',
  `file_size` bigint(20) NOT NULL COMMENT '文件总大小(字节)',
  `chunk_size` bigint(20) NOT NULL COMMENT '分片大小(字节)',
  `total_chunks` int(11) NOT NULL COMMENT '总分片数',
  `uploaded_chunks` varchar(1000) DEFAULT '' COMMENT '已上传分片索引(逗号分隔,如0,1,2)',
  `file_path` varchar(500) DEFAULT NULL COMMENT '最终文件保存路径',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-未完成 1-已完成',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_file_md5` (`file_md5`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='文件上传记录表';


// maven依赖


<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
     <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
     </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <exclusions>
     <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
     </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

<!-- mysql驱动包 -->
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>
<!-- mybatis的相关依赖 -->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.3.1</version>
</dependency>
<!-- 日志输出依赖包 -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.4</version>
</dependency>

<!-- myBatis-plus -->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.3.1</version>
</dependency>
<!-- 文件操作工具 -->
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.15.0</version>
</dependency>
<!-- 校验MD5 -->
<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.15</version>
</dependency>


// 实体类


package com.durkblue.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.Date;

@TableName("file_upload_record")
public class FileUploadRecord {
   @TableId(type = IdType.AUTO)
   private Long id;
   // 文件MD5
   @TableField("file_md5")
   private String fileMd5;
   // 文件名
   
@TableField("file_name")
   private String fileName;
   // 文件总大小(字节)
   
@TableField("file_size")
   private Long fileSize;
   // 分片大小(字节)
   
@TableField("chunk_size")
   private Long chunkSize;
   // 总分片数
   
@TableField("total_chunks")
   private Integer totalChunks;
   // 已上传分片索引(逗号分隔)
   
@TableField("uploaded_chunks")
   private String uploadedChunks;
   // 最终文件路径
   
@TableField("file_path")
   private String filePath;
   // 上传状态:0-未完成 1-已完成
   
@TableField("status")
   private Integer status;
   @TableField("create_time")
   // 记录时间
   
private Date createTime;
   @TableField("update_time")
   // 上传时间
   
private Date updateTime;

   public Long getId() {
       return id;
   }

   public void setId(Long id) {
       this.id = id;
   }

   public String getFileMd5() {
       return fileMd5;
   }

   public void setFileMd5(String fileMd5) {
       this.fileMd5 = fileMd5;
   }

   public String getFileName() {
       return fileName;
   }

   public void setFileName(String fileName) {
       this.fileName = fileName;
   }

   public Long getFileSize() {
       return fileSize;
   }

   public void setFileSize(Long fileSize) {
       this.fileSize = fileSize;
   }

   public Long getChunkSize() {
       return chunkSize;
   }

   public void setChunkSize(Long chunkSize) {
       this.chunkSize = chunkSize;
   }

   public Integer getTotalChunks() {
       return totalChunks;
   }

   public void setTotalChunks(Integer totalChunks) {
       this.totalChunks = totalChunks;
   }

   public String getUploadedChunks() {
       return uploadedChunks;
   }

   public void setUploadedChunks(String uploadedChunks) {
       this.uploadedChunks = uploadedChunks;
   }

   public String getFilePath() {
       return filePath;
   }

   public void setFilePath(String filePath) {
       this.filePath = filePath;
   }

   public Integer getStatus() {
       return status;
   }

   public void setStatus(Integer status) {
       this.status = status;
   }

   public Date getCreateTime() {
       return createTime;
   }

   public void setCreateTime(Date createTime) {
       this.createTime = createTime;
   }

   public Date getUpdateTime() {
       return updateTime;
   }

   public void setUpdateTime(Date updateTime) {
       this.updateTime = updateTime;
   }
}


// mapper层

package com.durkblue.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.durkblue.domain.FileUploadRecord;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface FileUploadRecordMapper extends BaseMapper<FileUploadRecord> {
}


// service层接口


package com.durkblue.service;

import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

public interface IFileUploadService {
   /**
    *
断点续传校验,返回已上传的分片索引
   
*
    *
@param fileMd5 文件分片标识
   
* @return 分片索引
   
*/
   
Map<String, Object> checkUploadedChunks(String fileMd5);

   /**
    *
上传总文件当中单个分片
   
* @param fileMd5  总文件唯一标识
   
* @param fileName 总文件名称
   
* @param fileSize 总文件大小
   
* @param chunkSize 分片文件大小
   
* @param chunkIndex 分片索引
   
* @param totalChunks 总分片数
   
* @param chunkFile 分片文件
   
* @return 是否上传该分片成功
   
* @throws IOException 异常
   
*/
   
boolean uploadChunk(String fileMd5, String fileName, Long fileSize, Long chunkSize,
                       Integer chunkIndex, Integer totalChunks, MultipartFile chunkFile) throws IOException;

   /**
    *
合并所有文件分片
   
* @param fileMd5 总文件唯一标识
   
* @return 是否合并成功
   
* @throws IOException 异常
   
*/
   
boolean mergeChunks(String fileMd5) throws IOException;


}


// service层实现类

package com.durkblue.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.durkblue.domain.FileUploadRecord;
import com.durkblue.mapper.FileUploadRecordMapper;
import com.durkblue.service.IFileUploadService;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.ibatis.util.MapUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class FileUploadServiceImpl implements IFileUploadService {
   // 当分片上传全部完成后最终文件存储目录
   
private final String finalPath = "D:\\file\\upload\\";

   // 临时文件分片存储目录
   
private String tempPath = "D:\\file\\tmp\\";
   @Autowired
   private FileUploadRecordMapper fileUploadRecordMapper;

   @Override
   public Map<String, Object> checkUploadedChunks(String fileMd5) {
       Map<String, Object> dataMap = new HashMap<>();
       LambdaQueryWrapper<FileUploadRecord> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(FileUploadRecord::getFileMd5, fileMd5);
       FileUploadRecord record = fileUploadRecordMapper.selectOne(queryWrapper);

       // 无记录:返回空集合(所有分片都未上传)
       
if (Objects.isNull(record)) {
           dataMap.put("finishChunk", new HashSet<>());// 当前已经上传的分片索引
           
dataMap.put("isMerge", 0);// 当前文件是否需要文件合并操作0=1=
       
}else{
           // 已完成:返回所有分片索引(前端无需再上传)
           
if (record.getStatus() == 1) {
               Set<Integer> allChunks = new HashSet<>();
               for (int i = 0; i < record.getTotalChunks(); i++) {
                   allChunks.add(i);
               }
               dataMap.put("finishChunk", allChunks);
               dataMap.put("isMerge", record.getStatus());
           }
           else{
               // 未完成:解析已上传分片索引
               
String uploadedChunks = record.getUploadedChunks();
               if (uploadedChunks == null || uploadedChunks.isEmpty()) {
                   dataMap.put("finishChunk", new HashSet<>());
                   dataMap.put("isMerge", record.getStatus());
               }else{
                   Set<Integer> chunkSet = Arrays.stream(uploadedChunks.split(","))
                           .map(Integer::parseInt)
                           .collect(Collectors.toSet());
                   dataMap.put("finishChunk", chunkSet);
                   dataMap.put("isMerge", record.getStatus());
               }
           }

       }
       return dataMap;
   }

   @Override
   public boolean uploadChunk(String fileMd5, String fileName, Long fileSize, Long chunkSize, Integer chunkIndex, Integer totalChunks, MultipartFile chunkFile) throws IOException {
       // 1. 校验分片文件
       
if (chunkFile.isEmpty()) {
           throw new RuntimeException("分片文件为空");
       }

       // 2. 创建临时目录(按文件MD5分目录,避免分片冲突)
       
File tempDir = new File(tempPath + File.separator + fileMd5);
       if (!tempDir.exists()) {
           FileUtils.forceMkdir(tempDir);
       }

       // 3. 保存分片文件(命名规则:fileMd5 + "_" + chunkIndex
       
File chunkFileObj = new File(tempDir, fileMd5 + "_" + chunkIndex);
       FileUtils.copyInputStreamToFile(chunkFile.getInputStream(), chunkFileObj);

       // 4. 更新分片上传记录
       
LambdaQueryWrapper<FileUploadRecord> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(FileUploadRecord::getFileMd5, fileMd5);
       FileUploadRecord record = fileUploadRecordMapper.selectOne(queryWrapper);

       if (Objects.isNull(record)) {
           // 首次上传:新增记录
           
record = new FileUploadRecord();
           record.setFileMd5(fileMd5);
           record.setFileName(fileName);
           record.setFileSize(fileSize);
           record.setChunkSize(chunkSize);
           record.setTotalChunks(totalChunks);
           record.setUploadedChunks(chunkIndex.toString());
           record.setStatus(0);
           fileUploadRecordMapper.insert(record);
       } else {
           // 非首次上传:追加分片索引
           
Map<String, Object> uploadMap = checkUploadedChunks(fileMd5);
           Set<Integer> uploadedSet = (Set<Integer>) MapUtils.getObject(uploadMap, "finishChunk");
           uploadedSet.add(chunkIndex);
           String newUploadedChunks = uploadedSet.stream()
                   .sorted()
                   .map(String::valueOf)
                   .collect(Collectors.joining(","));
           record.setUploadedChunks(newUploadedChunks);

           fileUploadRecordMapper.updateById(record);
           // 判断是否最后一个分片上传
           
if(uploadedSet.size() == record.getTotalChunks()){
               // 最后一个分片上传,执行文件合并
               
mergeChunks(fileMd5);
           }
       }

       return true;
   }

   @Override
   public boolean mergeChunks(String fileMd5) throws IOException {
       // 1. 查询文件记录
       
LambdaQueryWrapper<FileUploadRecord> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(FileUploadRecord::getFileMd5, fileMd5);
       FileUploadRecord record = fileUploadRecordMapper.selectOne(queryWrapper);
       if (record == null || record.getStatus() == 1) {
           return true; // 无记录或已合并,直接返回成功
       
}

       // 2. 校验所有分片是否上传完成
       
Map<String, Object> uploadMap = checkUploadedChunks(fileMd5);
       Set<Integer> uploadedChunks = (Set<Integer>) MapUtils.getObject(uploadMap, "finishChunk");
       if (uploadedChunks.size() != record.getTotalChunks()) {
           throw new RuntimeException("分片未全部上传,无法合并");
       }

       // 3. 创建最终文件目录
       
File finalDir = new File(finalPath);
       if (!finalDir.exists()) {
           FileUtils.forceMkdir(finalDir);
       }

       // 4. 合并分片到最终文件
       
File finalFile = new File(finalDir, record.getFileName());
       try (FileOutputStream fos = new FileOutputStream(finalFile, true);
            BufferedOutputStream bos = new BufferedOutputStream(fos)) {
           // 按分片索引顺序读取并写入
           
for (int i = 0; i < record.getTotalChunks(); i++) {
               File chunkFile = new File(tempPath + File.separator + fileMd5, fileMd5 + "_" + i);
               if (!chunkFile.exists()) {
                   throw new RuntimeException("分片" + i + "不存在");
               }
               // 写入分片内容
               
FileUtils.copyFile(chunkFile, bos);
               // 删除临时分片文件
               
FileUtils.delete(chunkFile);
           }
       }

       // 5. 更新记录:标记为已完成,保存最终文件路径
       
record.setFilePath(finalFile.getAbsolutePath());
       record.setStatus(1);
       fileUploadRecordMapper.updateById(record);

       // 6. 删除临时目录
       
File tempDir = new File(tempPath + File.separator + fileMd5);
       FileUtils.deleteDirectory(tempDir);

       return true;
   }
}


 // 工具类


com.durkblue.utiljava.util.HashMapjava.util.ObjectsAjaxResult HashMap<StringObject> {
    = String = String = String = String = ()
    {
    }

    (codeString msg)
    {
        .put(code).put(msg)}

    (Object dataInteger total){
        .put(data).put(total).put().put()}
    (codeString msgObject data)
    {
        .put(code).put(msg)(!Objects.(data))
        {
            .put(data)}
    }

    AjaxResult ()
    {
        AjaxResult.()}

    AjaxResult (Object data)
    {
        AjaxResult.(data)}

    AjaxResult (String msg)
    {
        AjaxResult.(msg)}

    AjaxResult (String msgObject data)
    {
        AjaxResult(msgdata)}



    AjaxResult ()
    {
        AjaxResult.()}

    AjaxResult (String msg)
    {
        AjaxResult.(msg)}

    AjaxResult (String msgObject data)
    {
        AjaxResult(msgdata)}

    AjaxResult (codeString msg)
    {
        AjaxResult(codemsg)}



}


// controller层

package com.durkblue.controller;


import com.durkblue.service.IFileUploadService;
import com.durkblue.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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.IOException;
import java.util.Map;
import java.util.Set;

@RestController
@RequestMapping("/upload")
public class FileUploadController {
   @Autowired
   private IFileUploadService fileUploadService;

   /**
    *
断点续传校验接口
   
*/
   
@PostMapping("/check")
   public AjaxResult checkUpload(String fileMd5) {
       Map<String, Object> uploadedChunks = fileUploadService.checkUploadedChunks(fileMd5);
       return AjaxResult.success(uploadedChunks);
   }

   /**
    *
分片上传接口(单文件分片)
   
*/
   
@PostMapping("/chunk")
   public AjaxResult uploadChunk(String fileMd5, String fileName, Long fileSize, Long chunkSize, Integer chunkIndex,
                                 Integer totalChunks, MultipartFile chunkFile) {
       try {
           fileUploadService.uploadChunk(fileMd5, fileName, fileSize,
                   chunkSize, chunkIndex, totalChunks, chunkFile);
           return AjaxResult.success("分片上传成功");
       } catch (IOException e) {
           return AjaxResult.error("分片上传失败:" + e.getMessage());
       }
   }
   /**
    *
分片合并接口
   
*/
   
@PostMapping("/merge")
   public AjaxResult mergeChunks(String fileMd5) {
       try {
           fileUploadService.mergeChunks(fileMd5);
           return AjaxResult.success("文件合并成功");
       } catch (IOException e) {
           return AjaxResult.error("文件合并失败:" + e.getMessage());
       }
   }

}


// 解决跨域问题


package com.durkblue.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
   @Override
   public void addCorsMappings(CorsRegistry registry) {
       registry.addMapping("/upload/**") // 调整为你的映射路径
               
.allowedOrigins("http://localhost") // 替换为你的前端应用的 URL
               .allowedMethods("POST", "GET", "PUT", "DELETE") // 根据需要添加更多方法
               
.allowCredentials(true)
               .maxAge(3600);
   }

}


// springboot配置文件

:
  :
    : file-resume-from-breakpoint
  :
    : jdbc:mysql://localhost:3306/ruoyi-wvp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    : ....: root
    : root
    :
      : : : : : : :
    :
      : 50MB
      : 100MB
:
  : classpath:/mapping/*Mapper.xml
  : com.durkblue.domain
  :
    : :
  :


浅谈以下如何实现超大文件断点续传功能  第1张


此篇文章由DurkBlue发布,转载一下需要注明来处
文章投稿或转载声明

来源:DurkBlue版权归原作者所有,转载请保留出处。本站文章发布于 昨天
温馨提示:文章内容系作者个人观点,不代表DurkBlue博客对其观点赞同或支持。

赞(0)

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

支付宝扫一扫打赏

微信扫一扫打赏

阅读
分享