国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳

這篇具有很好參考價值的文章主要介紹了【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

功能介紹

文件上傳

  • 小文件(圖片、文檔、視頻)上傳可以直接使用很多ui框架封裝的上傳組件,或者自己寫一個input 上傳,利用FormData 對象提交文件數(shù)據(jù),后端使用spring提供的MultipartFile進行文件的接收,然后寫入即可。
  • 但是對于比較大的文件,比如上傳2G左右的文件(http上傳),就需要將文件分片上傳(file.slice()),否則中間http長時間連接可能會斷掉

分片上傳

分片上傳,就是將所要上傳的文件,按照一定的大小,將整個文件分隔成多個數(shù)據(jù)塊(我們稱之為Part)來進行分別上傳,上傳完之后再由服務端對所有上傳的文件進行匯總整合成原始的文件

秒傳

  • 通俗的說,你把要上傳的東西上傳,服務器會先做MD5校驗,如果服務器上有一樣的東西,它就直接給你個新地址,其實你下載的都是服務器上的同一個文件
  • 想要不秒傳,其實只要讓MD5改變,就是對文件本身做一下修改(改名字不行),例如一個文本文件,你多加幾個字,MD5就變了,就不會秒傳了

斷點續(xù)傳

  • 斷點續(xù)傳是在下載或上傳時,將下載或上傳任務(一個文件或一個壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳或下載
  • 如果碰到網(wǎng)絡故障,可以從已經(jīng)上傳或下載的部分開始繼續(xù)上傳或者下載未完成的部分,而沒有必要從頭開始上傳或者下載。本文的斷點續(xù)傳主要是針對斷點上傳場景。

相關概念

  • chunkNumber: 當前塊的次序,第一個塊是 1,注意不是從 0 開始的。
  • totalChunks: 文件被分成塊的總數(shù)。
  • chunkSize: 分塊大小,根據(jù) totalSize 和這個值你就可以計算出總共的塊數(shù)。注意最后一塊的大小可能會比這個要大。
  • currentChunkSize: 當前塊的大小,實際大小。
  • totalSize: 文件總大小。
  • identifier: 這個就是MD5值,每個文件的唯一標示。
  • filename: 文件名

相關方法

  • .upload() 開始或者繼續(xù)上傳。
  • .pause() 暫停上傳。
  • .resume() 繼續(xù)上傳。
  • .cancel() 取消所有上傳文件,文件會被移除掉。
  • .progress() 返回一個0-1的浮點數(shù),當前上傳進度。
  • .isUploading() 返回一個布爾值標示是否還有文件正在上傳中。
  • .addFile(file) 添加一個原生的文件對象到上傳列表中。
  • .removeFile(file) 從上傳列表中移除一個指定的 Uploader.File 實例對象。

大文件上傳流程

  1. 前端對文件進行MD5加密,并且將文件按一定的規(guī)則分片
  2. vue-simple-uploader先會發(fā)送get請求校驗分片數(shù)據(jù)在服務端是否完整,如果完整則進行秒傳,如果不完整或者無數(shù)據(jù),則進行分片上傳。
  3. 后臺校驗MD5值,根據(jù)上傳的序號和分片大小計算相應的開始位置并寫入該分片數(shù)據(jù)到文件中。
    【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳,SpringBoot,Java,spring boot,后端,java

前端切片處理邏輯

【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳,SpringBoot,Java,spring boot,后端,java

后端處理切片的邏輯

【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳,SpringBoot,Java,spring boot,后端,java

流程解析

  1. 在created時,初始化uploader組件,指定分片大小、上傳方式等配置。
  2. 在onFileAdded方法中,當選擇文件計算MD5后,調(diào)用file.resume()開始上傳。
  3. file.resume()內(nèi)部首先發(fā)送一個GET請求,詢問服務端該文件已上傳的分片。
  4. 服務端返回一個JSON,里面包含已上傳分片的列表。
  5. uploader組件調(diào)用checkChunkUploadedByResponse,校驗當前分片是否在已上傳的列表中。
  6. 對未上傳的分片,file.resume()會繼續(xù)觸發(fā)上傳該分片的POST請求。
  7. POST請求會包含一個分片的數(shù)據(jù)和偏移量等信息。
  8. 服務端接收分片數(shù)據(jù),寫入文件的指定位置并返回成功響應。
  9. uploader組件會記錄該分片已上傳完成。
  10. 依次上傳完所有分片后,服務器端合并所有分片成一個完整的文件。
  11. onFileSuccess被調(diào)用,通知上傳成功。
  12. 這樣通過GET請求詢問已上傳分片+POST上傳未完成分片+校驗的方式,實現(xiàn)了斷點續(xù)傳/分片上傳。
    【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳,SpringBoot,Java,spring boot,后端,java

后端代碼實現(xiàn)

SpringBoot2.7.16+MySQL+JPA+hutool

功能目標

  1. get請求接口校驗上傳文件MD5值和文件是否完整
  2. post請求接收上傳文件,并且計算分片,寫入合成文件
  3. 文件完整上傳完成時,往文件存儲表tool_local_storage中加一條該文件的信息
  4. get請求接口實現(xiàn)簡單的文件下載

1.建表SQL

DROP TABLE IF EXISTS `file_chunk`;
CREATE TABLE `file_chunk`  (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`file_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名',
`chunk_number` int(11) NULL DEFAULT NULL COMMENT '當前分片,從1開始',
`chunk_size` float NULL DEFAULT NULL COMMENT '分片大小',
`current_chunk_size` float NULL DEFAULT NULL COMMENT '當前分片大小',
`total_size` double(20, 0) NULL DEFAULT NULL COMMENT '文件總大小',
`total_chunk` int(11) NULL DEFAULT NULL COMMENT '總分片數(shù)',
`identifier` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件標識',
`relative_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'md5校驗碼',
`createtime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatetime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1529 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `tool_local_storage`;
CREATE TABLE `tool_local_storage`  (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`real_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件真實的名稱',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名',
`suffix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '后綴',
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '路徑',
`type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '類型',
`size` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '大小',
`identifier` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'md5校驗碼\r\n',
`create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '創(chuàng)建者',
`update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
`createtime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatetime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3360 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件存儲' ROW_FORMAT = Compact;

2.引入依賴

		<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.4</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>

3.實體類

package com.zjl.domin;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Data
@Entity
@Table(name = "file_chunk")
public class FileChunkParam implements Serializable {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "chunk_number")
    private Integer chunkNumber;

    @Column(name = "chunk_size")
    private Float chunkSize;

    @Column(name = "current_chunk_size")
    private Float currentChunkSize;

    @Column(name = "total_chunk")
    private Integer totalChunks;

    @Column(name = "total_size")
    private Double totalSize;

    @Column(name = "identifier")
    private String identifier;

    @Column(name = "file_name")
    private String filename;

    @Column(name = "relative_path")
    private String relativePath;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(name = "createtime")
    private Date createtime;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(name = "updatetime")
    private Date updatetime;

    @Transient
    private MultipartFile file;
}
package com.zjl.domin;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Data
@Entity
@Table(name = "tool_local_storage")
public class LocalStorage implements Serializable {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "real_name")
    private String realName;

    @Column(name = "name")
    private String name;

    @Column(name = "suffix")
    private String suffix;

    @Column(name = "path")
    private String path;

    @Column(name = "type")
    private String type;

    @Column(name = "size")
    private String size;

    @Column(name = "identifier")
    private String identifier;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(name = "createtime")
    private Date createtime;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(name = "updatetime")
    private Date updatetime;

    public LocalStorage() {
    }

    public LocalStorage(String realName, String name, String suffix, String path, String type, String size, String identifier) {
        this.realName = realName;
        this.name = name;
        this.suffix = suffix;
        this.path = path;
        this.type = type;
        this.size = size;
        this.identifier = identifier;
    }

    public LocalStorage(Long id, String realName, String name, String suffix, String path, String type, String size, String identifier) {
        this.id = id;
        this.realName = realName;
        this.name = name;
        this.suffix = suffix;
        this.path = path;
        this.type = type;
        this.size = size;
        this.identifier = identifier;
    }

    public void copy(LocalStorage source) {
        BeanUtil.copyProperties(source, this, CopyOptions.create().setIgnoreNullValue(true));
    }
}

4.響應模板

package com.zjl.domin;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class ResultVO<T> {
    /**
     * 錯誤碼.
     */
    private Integer code;

    /**
     * 提示信息.
     */
    private String msg;

    /**
     * 具體內(nèi)容.
     */
    private T data;

    public ResultVO(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public ResultVO(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResultVO() {
    }
}

5.枚舉類

package com.zjl.enums;

import lombok.Getter;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
public enum MessageEnum {
    /**
     * 消息枚舉
     */
    FAIL(-1, "操作失敗"),
    SUCCESS(200, "操作成功"),
    RECORD_NOT_EXISTED(1001, "記錄不存在"),
    PARAM_NOT_NULL(1002, "參數(shù)不能為空"),
    PARAM_INVALID(1003, "參數(shù)錯誤"),
    UPLOAD_FILE_NOT_NULL(1004, "上傳文件不能為空"),
    OVER_FILE_MAX_SIZE(1005, "超出文件大小");

    MessageEnum(int value, String text) {
        this.code = value;
        this.message = text;
    }

    @Getter
    private final int code;

    @Getter
    private final String message;

    public static MessageEnum valueOf(int value) {
        MessageEnum[] enums = values();
        for (MessageEnum enumItem : enums) {
            if (value == enumItem.getCode()) {
                return enumItem;
            }
        }
        return null;
    }
}

6.自定義異常

package com.zjl.exception;

import com.zjl.enums.MessageEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseErrorException extends RuntimeException {

    private static final long serialVersionUID = 6386720492655133851L;
    private int code;
    private String error;

    public BaseErrorException(MessageEnum messageEnum) {
        this.code = messageEnum.getCode();
        this.error = messageEnum.getMessage();
    }
}
package com.zjl.exception;

import com.zjl.enums.MessageEnum;
import lombok.Data;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Data
public class BusinessException extends BaseErrorException {

    private static final long serialVersionUID = 2369773524406947262L;

    public BusinessException(MessageEnum messageEnum) {
        super(messageEnum);
    }

    public BusinessException(String error) {
        super.setCode(-1);
        super.setError(error);
    }
}

7.工具類

package com.zjl.utils;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelUtil;
import com.zjl.enums.MessageEnum;
import com.zjl.exception.BusinessException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:File工具類,擴展 hutool 工具包
 */
public class FileUtil extends cn.hutool.core.io.FileUtil {
    private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
    /**
     * 系統(tǒng)臨時目錄
     * <br>
     * windows 包含路徑分割符,但Linux 不包含,
     * 在windows \\==\ 前提下,
     * 為安全起見 同意拼裝 路徑分割符,
     * <pre>
     *       java.io.tmpdir
     *       windows : C:\Users/xxx\AppData\Local\Temp\
     *       linux: /temp
     * </pre>
     */
    public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
    /**
     * 定義GB的計算常量
     */
    private static final int GB = 1024 * 1024 * 1024;
    /**
     * 定義MB的計算常量
     */
    private static final int MB = 1024 * 1024;
    /**
     * 定義KB的計算常量
     */
    private static final int KB = 1024;

    /**
     * 格式化小數(shù)
     */
    private static final DecimalFormat DF = new DecimalFormat("0.00");

    /**
     * MultipartFile轉File
     */
    public static File toFile(MultipartFile multipartFile) {
        // 獲取文件名
        String fileName = multipartFile.getOriginalFilename();
        // 獲取文件后綴
        String prefix = "." + getExtensionName(fileName);
        File file = null;
        try {
            // 用uuid作為文件名,防止生成的臨時文件重復
            file = File.createTempFile(IdUtil.simpleUUID(), prefix);
            // MultipartFile to File
            multipartFile.transferTo(file);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return file;
    }

    /**
     * 獲取文件擴展名,不帶 .
     */
    public static String getExtensionName(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length() - 1))) {
                return filename.substring(dot + 1);
            }
        }
        return filename;
    }

    /**
     * Java文件操作 獲取不帶擴展名的文件名
     */
    public static String getFileNameNoEx(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length()))) {
                return filename.substring(0, dot);
            }
        }
        return filename;
    }

    /**
     * 文件大小轉換
     */
    public static String getSize(long size) {
        String resultSize;
        if (size / GB >= 1) {
            //如果當前Byte的值大于等于1GB
            resultSize = DF.format(size / (float) GB) + "GB   ";
        } else if (size / MB >= 1) {
            //如果當前Byte的值大于等于1MB
            resultSize = DF.format(size / (float) MB) + "MB   ";
        } else if (size / KB >= 1) {
            //如果當前Byte的值大于等于1KB
            resultSize = DF.format(size / (float) KB) + "KB   ";
        } else {
            resultSize = size + "B   ";
        }
        return resultSize;
    }

    /**
     * inputStream 轉 File
     */
    static File inputStreamToFile(InputStream ins, String name) throws Exception {
        File file = new File(SYS_TEM_DIR + name);
        if (file.exists()) {
            return file;
        }
        OutputStream os = new FileOutputStream(file);
        int bytesRead;
        int len = 8192;
        byte[] buffer = new byte[len];
        while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        os.close();
        ins.close();
        return file;
    }

    /**
     * 將文件名解析成文件的上傳路徑
     */
    public static File upload(MultipartFile file, String filePath) {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS");
        String name = getFileNameNoEx(file.getOriginalFilename());
        String suffix = getExtensionName(file.getOriginalFilename());
        String nowStr = "-" + format.format(date);
        try {
            String fileName = name + nowStr + "." + suffix;
            String path = filePath + fileName;
            // getCanonicalFile 可解析正確各種路徑
            File dest = new File(path).getCanonicalFile();
            // 檢測是否存在目錄
            if (!dest.getParentFile().exists()) {
                if (!dest.getParentFile().mkdirs()) {
                    System.out.println("was not successful.");
                }
            }
            // 文件寫入
            file.transferTo(dest);
            return dest;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 導出excel
     */
    public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {
        String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
        File file = new File(tempPath);
        BigExcelWriter writer = ExcelUtil.getBigWriter(file);
        // 一次性寫出內(nèi)容,使用默認樣式,強制輸出標題
        writer.write(list, true);
        //response為HttpServletResponse對象
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        //test.xls是彈出下載對話框的文件名,不能為中文,中文請自行編碼
        response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
        ServletOutputStream out = response.getOutputStream();
        // 終止后刪除臨時文件
        file.deleteOnExit();
        writer.flush(out, true);
        //此處記得關閉輸出Servlet流
        IoUtil.close(out);
    }

    public static String getFileType(String type) {
        String documents = "txt pdf pps wps doc docx ppt pptx xls xlsx";
        String music = "mp3 wav wma mpa ram ra aac aif m4a";
        String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
        String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
        if (image.contains(type)) {
            return "圖片";
        } else if (documents.contains(type)) {
            return "文檔";
        } else if (music.contains(type)) {
            return "音樂";
        } else if (video.contains(type)) {
            return "視頻";
        } else {
            return "其他";
        }
    }

    public static String getTransferFileType(String type) {
        String documents = "txt pdf pps wps doc docx ppt pptx xls xlsx";
        String music = "mp3 wav wma mpa ram ra aac aif m4a";
        String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
        String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
        if (image.contains(type)) {
            return "image";
        } else if (documents.contains(type)) {
            return "documents";
        } else if (music.contains(type)) {
            return "music";
        } else if (video.contains(type)) {
            return "video";
        } else {
            return "other";
        }
    }

    public static void checkSize(long maxSize, long size) {
        // 1M
        int len = 1024 * 1024;
        if (size > (maxSize * len)) {
            throw new BusinessException(MessageEnum.OVER_FILE_MAX_SIZE);
        }
    }

    /**
     * 判斷兩個文件是否相同
     */
    public static boolean check(File file1, File file2) {
        String img1Md5 = getMd5(file1);
        String img2Md5 = getMd5(file2);
        return img1Md5.equals(img2Md5);
    }

    /**
     * 判斷兩個文件是否相同
     */
    public static boolean check(String file1Md5, String file2Md5) {
        return file1Md5.equals(file2Md5);
    }

    private static byte[] getByte(File file) {
        // 得到文件長度
        byte[] b = new byte[(int) file.length()];
        try {
            InputStream in = new FileInputStream(file);
            try {
                System.out.println(in.read(b));
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        } catch (FileNotFoundException e) {
            log.error(e.getMessage(), e);
            return null;
        }
        return b;
    }

    private static String getMd5(byte[] bytes) {
        // 16進制字符
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
            mdTemp.update(bytes);
            byte[] md = mdTemp.digest();
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            // 移位 輸出字符串
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 下載文件
     *
     * @param request  /
     * @param response /
     * @param file     /
     */
    public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) throws UnsupportedEncodingException {
        response.setCharacterEncoding(request.getCharacterEncoding());
        response.setContentType("application/octet-stream");
        FileInputStream fis = null;

        String filename = filenameEncoding(file.getName(), request);
        try {
            fis = new FileInputStream(file);
            response.setHeader("Content-Disposition", String.format("attachment;filename=%s", filename));
            IOUtils.copy(fis, response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                    if (deleteOnExit) {
                        file.deleteOnExit();
                    }
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    public static String getMd5(File file) {
        return getMd5(getByte(file));
    }

    public static String filenameEncoding(String filename, HttpServletRequest request) throws UnsupportedEncodingException {
        // 獲得請求頭中的User-Agent
        String agent = request.getHeader("User-Agent");
        // 根據(jù)不同的客戶端進行不同的編碼

        if (agent.contains("MSIE")) {
            // IE瀏覽器
            filename = URLEncoder.encode(filename, "utf-8");
        } else if (agent.contains("Firefox")) {
            // 火狐瀏覽器
            BASE64Encoder base64Encoder = new BASE64Encoder();
            filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
        } else {
            // 其它瀏覽器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }
}

8.Controller層

package com.zjl.controller;
import com.zjl.domin.FileChunkParam;
import com.zjl.domin.ResultVO;
import com.zjl.service.FileChunkService;
import com.zjl.service.FileService;
import com.zjl.service.LocalStorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@RestController
@Slf4j
@RequestMapping("/api")
public class FileUploadController {

    @Resource
    private FileService fileService;
    @Resource
    private FileChunkService fileChunkService;
    @Resource
    private LocalStorageService localStorageService;

    @GetMapping("/upload")
    public ResultVO<Map<String, Object>> checkUpload(FileChunkParam param) {
        log.info("文件MD5:" + param.getIdentifier());
        List<FileChunkParam> list = fileChunkService.findByMd5(param.getIdentifier());
        Map<String, Object> data = new HashMap<>(1);
        // 判斷文件存不存在
        if (list.size() == 0) {
            data.put("uploaded", false);
            return new ResultVO<>(200, "上傳成功", data);
        }
        // 處理單文件
        if (list.get(0).getTotalChunks() == 1) {
            data.put("uploaded", true);
            data.put("url", "");
            return new ResultVO<Map<String, Object>>(200, "上傳成功", data);
        }
        // 處理分片
        int[] uploadedFiles = new int[list.size()];
        int index = 0;
        for (FileChunkParam fileChunkItem : list) {
            uploadedFiles[index] = fileChunkItem.getChunkNumber();
            index++;
        }
        data.put("uploadedChunks", uploadedFiles);
        return new ResultVO<Map<String, Object>>(200, "上傳成功", data);
    }

    @PostMapping("/upload")
    public ResultVO chunkUpload(FileChunkParam param) {
        log.info("上傳文件:{}", param);
        boolean flag = fileService.uploadFile(param);
        if (!flag) {
            return new ResultVO(211, "上傳失敗");
        }
        return new ResultVO(200, "上傳成功");
    }


    @GetMapping(value = "/download/{md5}/{name}")
    public void downloadbyname(HttpServletRequest request, HttpServletResponse response, @PathVariable String name, @PathVariable String md5) throws IOException {
        localStorageService.downloadByName(name, md5, request, response);
    }
}

9.FileService

package com.zjl.service;


import com.zjl.domin.FileChunkParam;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
public interface FileService {
    /**
     * 上傳文件
     * @param param 參數(shù)
     * @return
     */
    boolean uploadFile(FileChunkParam param);
}
package com.zjl.service.impl;

import com.zjl.domin.FileChunkParam;
import com.zjl.enums.MessageEnum;
import com.zjl.exception.BusinessException;
import com.zjl.service.FileChunkService;
import com.zjl.service.FileService;
import com.zjl.service.LocalStorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import sun.misc.Cleaner;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Service("fileService")
@Slf4j
public class FileServiceImpl implements FileService {
    /**
     * 默認的分片大?。?0MB
     */
    public static final long DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024;

    @Value("${file.BASE_FILE_SAVE_PATH}")
    private String BASE_FILE_SAVE_PATH;

    @Resource
    private FileChunkService fileChunkService;

    @Resource
    private LocalStorageService localStorageService;

    @Override
    public boolean uploadFile(FileChunkParam param) {
        if (null == param.getFile()) {
            throw new BusinessException(MessageEnum.UPLOAD_FILE_NOT_NULL);
        }
        // 判斷目錄是否存在,不存在則創(chuàng)建目錄
        File savePath = new File(BASE_FILE_SAVE_PATH);
        if (!savePath.exists()) {
            boolean flag = savePath.mkdirs();
            if (!flag) {
                log.error("保存目錄創(chuàng)建失敗");
                return false;
            }
        }


        //  todo 處理文件夾上傳(上傳目錄下新建上傳的文件夾)
        /*String relativePath = param.getRelativePath();
        if (relativePath.contains("/") || relativePath.contains(File.separator)) {
            String div = relativePath.contains(File.separator) ? File.separator : "/";
            String tempPath = relativePath.substring(0, relativePath.lastIndexOf(div));
            savePath = new File(BASE_FILE_SAVE_PATH + File.separator + tempPath);
            if (!savePath.exists()) {
                boolean flag = savePath.mkdirs();
                if (!flag) {
                    log.error("保存目錄創(chuàng)建失敗");
                    return false;
                }
            }
        }*/


        // 這里可以使用 uuid 來指定文件名,上傳完成后再重命名,F(xiàn)ile.separator指文件目錄分割符,win上的"\",Linux上的"/"。
        String fullFileName = savePath + File.separator + param.getFilename();
        // 單文件上傳
        if (param.getTotalChunks() == 1) {
            return uploadSingleFile(fullFileName, param);
        }
        // 分片上傳,這里使用 uploadFileByRandomAccessFile 方法,也可以使用 uploadFileByMappedByteBuffer 方法上傳
        boolean flag = uploadFileByRandomAccessFile(fullFileName, param);
        if (!flag) {
            return false;
        }
        // 保存分片上傳信息
        fileChunkService.saveFileChunk(param);
        return true;
    }


    private boolean uploadFileByRandomAccessFile(String resultFileName, FileChunkParam param) {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(resultFileName, "rw")) {
            // 分片大小必須和前端匹配,否則上傳會導致文件損壞
            long chunkSize = param.getChunkSize() == 0L ? DEFAULT_CHUNK_SIZE : param.getChunkSize().longValue();
            // 偏移量
            long offset = chunkSize * (param.getChunkNumber() - 1);
            // 定位到該分片的偏移量
            randomAccessFile.seek(offset);
            // 寫入
            randomAccessFile.write(param.getFile().getBytes());
        } catch (IOException e) {
            log.error("文件上傳失?。? + e);
            return false;
        }
        return true;
    }

    private boolean uploadFileByMappedByteBuffer(String resultFileName, FileChunkParam param) {
        // 分片上傳
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(resultFileName, "rw");
             FileChannel fileChannel = randomAccessFile.getChannel()) {
            // 分片大小必須和前端匹配,否則上傳會導致文件損壞
            long chunkSize = param.getChunkSize() == 0L ? DEFAULT_CHUNK_SIZE : param.getChunkSize().longValue();
            // 寫入文件
            long offset = chunkSize * (param.getChunkNumber() - 1);
            byte[] fileBytes = param.getFile().getBytes();
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileBytes.length);
            mappedByteBuffer.put(fileBytes);
            // 釋放
            unmap(mappedByteBuffer);
        } catch (IOException e) {
            log.error("文件上傳失?。? + e);
            return false;
        }
        return true;
    }

    private boolean uploadSingleFile(String resultFileName, FileChunkParam param) {
        File saveFile = new File(resultFileName);
        try {
            // 寫入
            param.getFile().transferTo(saveFile);
            localStorageService.saveLocalStorage(param);
        } catch (IOException e) {
            log.error("文件上傳失?。? + e);
            return false;
        }
        return true;
    }

    /**
     * 釋放 MappedByteBuffer
     * 在 MappedByteBuffer 釋放后再對它進行讀操作的話就會引發(fā) jvm crash,在并發(fā)情況下很容易發(fā)生
     * 正在釋放時另一個線程正開始讀取,于是 crash 就發(fā)生了。所以為了系統(tǒng)穩(wěn)定性釋放前一般需要檢
     * 查是否還有線程在讀或寫
     * 來源:https://my.oschina.net/feichexia/blog/212318
     *
     * @param mappedByteBuffer mappedByteBuffer
     */
    public static void unmap(final MappedByteBuffer mappedByteBuffer) {
        try {
            if (mappedByteBuffer == null) {
                return;
            }
            mappedByteBuffer.force();
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                try {
                    Method getCleanerMethod = mappedByteBuffer.getClass()
                            .getMethod("cleaner");
                    getCleanerMethod.setAccessible(true);
                    Cleaner cleaner =
                            (Cleaner) getCleanerMethod
                                    .invoke(mappedByteBuffer, new Object[0]);
                    cleaner.clean();
                } catch (Exception e) {
                    log.error("MappedByteBuffer 釋放失?。? + e);
                }
                System.out.println("clean MappedByteBuffer completed");
                return null;
            });
        } catch (Exception e) {
            log.error("unmap error:" + e);
        }
    }
}

10.LocalStorageService

package com.zjl.service;


import com.zjl.domin.FileChunkParam;
import com.zjl.domin.LocalStorage;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
public interface LocalStorageService {
    /**
     * 根據(jù)文件 md5 查詢
     *
     * @param md5 md5
     * @return
     */
    LocalStorage findByMd5(String md5);

    /**
     * 保存記錄
     *
     * @param localStorage 記錄參數(shù)
     */
    void saveLocalStorage(LocalStorage localStorage);

    /**
     * 保存記錄
     *
     * @param param 記錄參數(shù)
     */
    void saveLocalStorage(FileChunkParam param);

    /**
     * 刪除記錄
     *
     * @param localStorage localStorage
     * @return
     */
    void delete(LocalStorage localStorage);

    /**
     * 根據(jù) id 刪除
     *
     * @param id id
     * @return
     */
    void deleteById(Long id);

    void downloadByName(String name, String md5, HttpServletRequest request, HttpServletResponse response);
}
package com.zjl.service.impl;

import com.zjl.domin.FileChunkParam;
import com.zjl.domin.LocalStorage;
import com.zjl.repository.LocalStorageRepository;
import com.zjl.service.LocalStorageService;
import com.zjl.utils.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.UnsupportedEncodingException;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Service
@Slf4j
public class LocalStorageServiceImpl implements LocalStorageService {
    @Resource
    private LocalStorageRepository localStorageRepository;

    @Value("${file.BASE_FILE_SAVE_PATH}")
    private String BASE_FILE_SAVE_PATH;

    @Override
    public LocalStorage findByMd5(String md5) {
        return localStorageRepository.findByIdentifier(md5);
    }

    @Override
    public void saveLocalStorage(LocalStorage localStorage) {
        localStorageRepository.save(localStorage);
    }

    @Override
    public void saveLocalStorage(FileChunkParam param) {
        Long id = null;
        LocalStorage byIdentifier = localStorageRepository.findByIdentifier(param.getIdentifier());
        if (!ObjectUtils.isEmpty(byIdentifier)) {
            id = byIdentifier.getId();
        }
        String name = param.getFilename();
        String suffix = FileUtil.getExtensionName(name);
        String type = FileUtil.getFileType(suffix);
        LocalStorage localStorage = new LocalStorage(
                id,
                name,
                FileUtil.getFileNameNoEx(name),
                suffix,
                param.getRelativePath(),
                type,
                FileUtil.getSize(param.getTotalSize().longValue()),
                param.getIdentifier()
        );
        localStorageRepository.save(localStorage);
    }

    @Override
    public void delete(LocalStorage localStorage) {
        localStorageRepository.delete(localStorage);
    }

    @Override
    public void deleteById(Long id) {
        localStorageRepository.deleteById(id);
    }

    @Override
    public void downloadByName(String name, String md5, HttpServletRequest request, HttpServletResponse response) {
        LocalStorage storage = localStorageRepository.findByRealNameAndIdentifier(name, md5);
        if (ObjectUtils.isEmpty(storage)) {
            return;
        }
        File tofile = new File(BASE_FILE_SAVE_PATH + File.separator + storage.getPath());
        try {
            FileUtil.downloadFile(request, response, tofile, false);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}

11.FileChunkService

package com.zjl.service;


import com.zjl.domin.FileChunkParam;

import java.util.List;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
public interface FileChunkService {
    /**
     * 根據(jù)文件 md5 查詢
     *
     * @param md5 md5
     * @return
     */
    List<FileChunkParam> findByMd5(String md5);

    /**
     * 保存記錄
     *
     * @param param 記錄參數(shù)
     */
    void saveFileChunk(FileChunkParam param);

    /**
     * 刪除記錄
     *
     * @param fileChunk fileChunk
     * @return
     */
    void delete(FileChunkParam fileChunk);

    /**
     * 根據(jù) id 刪除
     *
     * @param id id
     * @return
     */
    void deleteById(Long id);
}
package com.zjl.service.impl;

import com.zjl.domin.FileChunkParam;
import com.zjl.repository.FileChunkRepository;
import com.zjl.service.FileChunkService;
import com.zjl.service.LocalStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Service
public class FileChunkServiceImpl implements FileChunkService {
    @Resource
    private FileChunkRepository fileChunkRepository;

    @Resource
    private LocalStorageService localStorageService;

    @Override
    public List<FileChunkParam> findByMd5(String md5) {
        return fileChunkRepository.findByIdentifier(md5);
    }

    @Override
    public void saveFileChunk(FileChunkParam param) {
        fileChunkRepository.save(param);
        // 當文件分片完整上傳完成,存一份在LocalStorage表中
        if (param.getChunkNumber().equals(param.getTotalChunks())) {
            localStorageService.saveLocalStorage(param);
        }
    }

    @Override
    public void delete(FileChunkParam fileChunk) {
        fileChunkRepository.delete(fileChunk);
    }

    @Override
    public void deleteById(Long id) {
        fileChunkRepository.deleteById(id);
    }
}

12. Repository

package com.zjl.repository;

import com.zjl.domin.FileChunkParam;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import java.util.List;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
public interface FileChunkRepository extends JpaRepository<FileChunkParam, Long>, JpaSpecificationExecutor<FileChunkParam> {

    List<FileChunkParam> findByIdentifier(String identifier);
}
package com.zjl.repository;

import com.zjl.domin.LocalStorage;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
public interface LocalStorageRepository extends JpaRepository<LocalStorage, Long>, JpaSpecificationExecutor<LocalStorage> {
    LocalStorage findByIdentifier(String identifier);

    LocalStorage findByRealNameAndIdentifier(String name, String md5);
}

13.跨域配置

package com.zjl.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author: zjl
 * @datetime: 2024/4/9
 * @desc:
 */
@Configuration
public class GlobalCorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowCredentials(true)
                .allowedHeaders("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("Authorization", "Cache-Control", "Content-Type")
                .maxAge(3600);
    }
}

前端Vue

源碼下載地址:

鏈接:https://pan.baidu.com/s/1KFzWdq-kfOAxMKDaCPCDPQ?pwd=6666 提取碼:6666 文章來源地址http://www.zghlxwxcb.cn/news/detail-853571.html

關鍵代碼

安裝插件、指定分片大小

import SparkMD5 from "spark-md5";
const FILE_UPLOAD_ID_KEY = "file_upload_id";
// 分片大小,20MB
const CHUNK_SIZE = 20 * 1024 * 1024;

定義后端接口地址、判斷分片是否上傳

// 上傳地址
        target: "http://127.0.0.1:9999/api/upload",
        // 是否開啟服務器分片校驗。默認為 true
        testChunks: true,
        // 真正上傳的時候使用的 HTTP 方法,默認 POST
        uploadMethod: "post",
        // 分片大小
        chunkSize: CHUNK_SIZE,
        // 并發(fā)上傳數(shù),默認為 3
        simultaneousUploads: 3,
        /**
         * 判斷分片是否上傳,秒傳和斷點續(xù)傳基于此方法
         * 這里根據(jù)實際業(yè)務來 用來判斷哪些片已經(jīng)上傳過了 不用再重復上傳了 [這里可以用來寫斷點續(xù)傳!?。
         */
        checkChunkUploadedByResponse: (chunk, message) => {
          // message是后臺返回
          let messageObj = JSON.parse(message);
          let dataObj = messageObj.data;
          if (dataObj.uploaded !== undefined) {
            return dataObj.uploaded;
          }
          // 判斷文件或分片是否已上傳,已上傳返回 true
          // 這里的 uploadedChunks 是后臺返回]
          return (dataObj.uploadedChunks || []).indexOf(chunk.offset + 1) >= 0;
        },

計算MD5,并校驗是否已上傳

onFileAdded(file, event) {
      this.uploadFileList.push(file);
      console.log("file :>> ", file);
      // 有時 fileType為空,需截取字符
      console.log("文件類型:" + file.fileType);
      // 文件大小
      console.log("文件大?。? + file.size + "B");
      // 1. todo 判斷文件類型是否允許上傳
      // 2. 計算文件 MD5 并請求后臺判斷是否已上傳,是則取消上傳
      console.log("校驗MD5");
      this.getFileMD5(file, (md5) => {
        if (md5 != "") {
          // 修改文件唯一標識
          file.uniqueIdentifier = md5;
          // 請求后臺判斷是否上傳
          // 恢復上傳
          file.resume();
        }
      });
    },
// 計算文件的MD5值
    getFileMD5(file, callback) {
      let spark = new SparkMD5.ArrayBuffer();
      let fileReader = new FileReader();
      //獲取文件分片對象(注意它的兼容性,在不同瀏覽器的寫法不同)
      let blobSlice =
        File.prototype.slice ||
        File.prototype.mozSlice ||
        File.prototype.webkitSlice;
      // 當前分片下標
      let currentChunk = 0;
      // 分片總數(shù)(向下取整)
      let chunks = Math.ceil(file.size / CHUNK_SIZE);
      // MD5加密開始時間
      let startTime = new Date().getTime();
      // 暫停上傳
      file.pause();
      loadNext();
      // fileReader.readAsArrayBuffer操作會觸發(fā)onload事件
      fileReader.onload = function (e) {
        // console.log("currentChunk :>> ", currentChunk);
        spark.append(e.target.result);
        if (currentChunk < chunks) {
          currentChunk++;
          loadNext();
        } else {
          // 該文件的md5值
          let md5 = spark.end();
          console.log(
            `MD5計算完畢:${md5},耗時:${new Date().getTime() - startTime} ms.`
          );
          // 回調(diào)傳值md5
          callback(md5);
        }
      };
      fileReader.onerror = function () {
        this.$message.error("文件讀取錯誤");
        file.cancel();
      };
      // 加載下一個分片
      function loadNext() {
        const start = currentChunk * CHUNK_SIZE;
        const end =
          start + CHUNK_SIZE >= file.size ? file.size : start + CHUNK_SIZE;
        // 文件分片操作,讀取下一分片(fileReader.readAsArrayBuffer操作會觸發(fā)onload事件)
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
      }
    },
fileStatusText(status, response) {
      if (status === "md5") {
        return "校驗MD5";
      } else {
        return this.fileStatusTextObj[status];
      }
    },

計算上傳進度

onFileProgress(rootFile, file, chunk) {
      console.log(`當前進度:${Math.ceil(file._prevProgress * 100)}%`);
    },

到了這里,關于【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)

    springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)

    網(wǎng)上關于minio分片上傳的資料不太詳細,缺斤少兩,所以我基于他們的代碼做了一些修改,demo能夠正常運行起來,但是偶爾也會發(fā)生一些小bug,不過這些都無傷大雅,最終目的是理解代碼背后的邏輯和流程 流程: 前端獲取生成文件MD5,發(fā)送至后臺判斷是否有該文件緩存,有

    2024年02月02日
    瀏覽(27)
  • Minio大文件分片上傳、斷點續(xù)傳實現(xiàn)

    Minio大文件分片上傳、斷點續(xù)傳實現(xiàn)

    使用minio api實現(xiàn)分片上傳及斷點續(xù)傳功能。 前端準備:獲取大文件的MD5值,將文件分片,5M為一分片,排好順序,并按順序命名(1,2,3這種后面比較好合并) 在上傳分片階段,前端有上傳進度條 1、檢驗文件MD5值 1.1 redis中查看MD5是否存在 1.2 判斷臨時文件夾是否存在 boolean d

    2024年02月09日
    瀏覽(27)
  • minio&前后端分離上傳視頻/上傳大文件——前后端分離斷點續(xù)傳&minio分片上傳實現(xiàn)

    minio&前后端分離上傳視頻/上傳大文件——前后端分離斷點續(xù)傳&minio分片上傳實現(xiàn)

    ????????分布式文件系統(tǒng)-minio: 第一章:分布式文件系統(tǒng)介紹與minio介紹與使用(附minio java client 使用) 第二章:minio前后端分離上傳視頻/上傳大文件——前后端分離斷點續(xù)傳minio分片上傳實現(xiàn) 斷點續(xù)傳指的是在下載或上傳時,將下載或上傳任務(一個文件或一個壓縮包

    2024年02月03日
    瀏覽(45)
  • 前端文件上傳(文件上傳,分片上傳,斷點續(xù)傳)

    前端文件上傳(文件上傳,分片上傳,斷點續(xù)傳)

    普通文件上傳 思路: 首先獲取用戶選擇的文件對象,并將其添加到一個 FormData 對象中。然后,使用 axios 的 post 方法將 FormData 對象發(fā)送到服務器。在 then 和 catch 中,我們分別處理上傳成功和失敗的情況,并輸出相應的信息。 需要注意,在使用 axios 進行文件上傳時,必須將

    2024年02月22日
    瀏覽(30)
  • springboot 、html分片上傳,斷點續(xù)傳

    后端代碼 注意:合并分片代碼中: Files.write(mergedFilePath, Files.readAllBytes(chunkFilePath), StandardOpenOption.APPEND); 如果不設置該值 StandardOpenOption.APPEND ,無法打開合并后的文件 前端代碼

    2024年02月05日
    瀏覽(21)
  • spring boot 阿里云oss 文件分片上傳、斷點續(xù)傳

    spring boot 阿里云oss 文件分片上傳、斷點續(xù)傳

    文章目錄 前言 一、申請阿里云oss 二、上代碼 總結 ? ? ? 阿里云對象存儲OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存儲服務,可提供99.9999999999%(12個9)的數(shù)據(jù)持久性,99.995%的數(shù)據(jù)可用性。多種存儲類型供選擇,全面優(yōu)化存儲成本。 ? ? 您可以使用阿

    2024年02月07日
    瀏覽(27)
  • 大文件上傳阿里云oss,分片、斷點續(xù)傳進度條展示

    大文件上傳阿里云oss,分片、斷點續(xù)傳進度條展示

    前端頁面展示 大文件如果不采用分片上傳會導致卡死、內(nèi)存占用過高導致程序奔潰等一些列問題。 通常在文件大于100 MB的情況下,建議采用分片上傳的方法,通過斷點續(xù)傳和重試,提高上傳成功率。如果在文件小于100 MB的情況下使用分片上傳,且partSize設置不合理的情況下,

    2024年02月11日
    瀏覽(26)
  • 前端 + 后端 實現(xiàn)分片上傳(斷點續(xù)傳/極速秒傳)

    先記錄下,后面有時間再去實現(xiàn) (已實現(xiàn),可參考 SpringBoot+vue文件上傳下載預覽大文件分片上傳文件上傳進度 SpringBoot+vue 大文件分片下載) 可參考鏈接:vue上傳大文件/視頻前后端(java)代碼 前端slice分片上傳,后端用表記錄分片索引和分片大小和分片總數(shù),當接受完最后

    2023年04月17日
    瀏覽(25)
  • springboot實現(xiàn)minio文件分片上傳

    在Spring Boot中實現(xiàn)MinIO的文件分片上傳涉及到幾個關鍵步驟。MinIO是一個高性能的分布式對象存儲服務,它兼容Amazon S3的API。分片上傳主要用于上傳大文件,將大文件分成多個部分,分別上傳,最后再將這些部分合并成一個完整的文件。這樣做的好處是提高了上傳的可靠性,并

    2024年01月17日
    瀏覽(24)
  • 基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點續(xù)傳的全局上傳

    基于vue-simple-uploader封裝文件分片上傳、秒傳及斷點續(xù)傳的全局上傳

    1. 前言 文件上傳 小文件(圖片、文檔、視頻)上傳可以直接使用很多ui框架封裝的上傳組件,或者自己寫一個input 上傳,利用FormData 對象提交文件數(shù)據(jù),后端使用spring提供的MultipartFile進行文件的接收,然后寫入即可。但是對于比較大的文件,比如上傳2G左右的文件(http上傳

    2024年02月06日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包