springboot2分片上傳與極速妙傳

分片上傳本質(zhì)就是在前端把一個完整的文件拆分成若干份文件上傳,上傳完成后,服務(wù)器再把上傳的若干份文件合并成一個完整的文件,再刪除若干份分片文件。

首先導(dǎo)包

<dependencies>
  <!-- 基本依賴 -->
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- mysql -->
  <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <!-- mapper -->
     <dependency>
     <groupId>tk.mybatis</groupId>
     <artifactId>mapper-spring-boot-starter</artifactId>
     <version>2.1.5</version>
  </dependency>
  <!-- fastjson -->
  <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>1.2.79</version>
  </dependency>
   <!-- lombok -->
   <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
   </dependency>
</dependencies>

application.yml配置:

server:
  port: 8080
  servlet:
    context-path: /test
  tomcat:
    max-http-form-post-size: -1
spring:
  datasource:
    name: DS #如果存在多個數(shù)據(jù)源萌京,監(jiān)控的時候可以通過名字來區(qū)分開來。如果沒有配置宏浩,將會生成一個名字知残,格式是:"DataSource-" + System.identityHashCode(this)
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://127.0.0.1:3306/upload?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT
    #hikari相關(guān)配置
    hikari:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: 123456
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

# mybatis配置
mybatis:
  type-aliases-package: com.upload.pojo
#  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true #使全局的映射器啟用或禁用緩存。
    lazy-loading-enabled: true #全局啟用或禁用延遲加載比庄。當(dāng)禁用時求妹,所有關(guān)聯(lián)對象都會即時加載。
    aggressive-lazy-loading: true #當(dāng)啟用時佳窑,有延遲加載屬性的對象在被調(diào)用時將會完全加載任意屬性屹逛。否則腰涧,每種屬性將會按需要加載盼理。
    jdbc-type-for-null: null #設(shè)置但JDBC類型為空時,某些驅(qū)動程序 要指定值,default:OTHER竿报,插入空值時不需要指定類型

logging:
  level:
   com.upload.dao: debug

再數(shù)據(jù)庫建一張表 file

CREATE TABLE `file` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `path` varchar(255) NOT NULL DEFAULT '' COMMENT '文件路徑',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件名稱',
  `size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '文件大小',
  `suffix` varchar(10) NOT NULL DEFAULT '' COMMENT '后綴',
  `type` varchar(10) NOT NULL DEFAULT '' COMMENT '文件類型',
  `share_total` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '文件分片總數(shù)',
  `share_index` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '已上傳分片索引,默認(rèn)0',
  `key` varchar(32) NOT NULL DEFAULT '' COMMENT '文件唯一Key',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `key` (`key`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='文件分片上傳表';

創(chuàng)建對應(yīng)實體類 FilePojo

package com.upload.pojo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;

/**
 * @author xiaochi
 * @date 2022/3/14 22:52
 * @desc FilePojo
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "file")
public class FilePojo implements Serializable {
    private static final long serialVersionUID = -6334172193008858856L;

    @Id
    private Integer id;
    private String path;
    private String name;
    private Long size;
    private String suffix;
    @Column(name = "`type`")
    private String type;
    private Integer shareTotal;
    private Integer shareIndex;
    @Column(name = "`key`")
    private String key;
    private Date createTime;
    private Date updateTime;
}

接著再創(chuàng)建一個接收前端參數(shù)的Vo文件 FileVo

package com.upload.vo;

import com.upload.pojo.FilePojo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author xiaochi
 * @date 2022/3/15 8:38
 * @desc FileVo
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class FileVo extends FilePojo {

    private static final long serialVersionUID = -4528742454491886780L;

    private MultipartFile file;
}

FileVo擁有 FilePojo 的所有屬性。接著創(chuàng)建一個 FileDao文件

/**
 * @author xiaochi
 * @date 2022/3/14 22:56
 * @desc FileDao
 */
public interface FileDao extends Mapper<FilePojo>, MySqlMapper<FilePojo> {
}

接著創(chuàng)建控制器 UploadController

package com.upload.controller;

import com.upload.common.R;
import com.upload.dao.FileDao;
import com.upload.pojo.FilePojo;
import com.upload.vo.FileVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.entity.Example;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author xiaochi
 * @date 2022/3/14 22:08
 * @desc UploadController
 */
@Slf4j
@CrossOrigin("*")
@RestController
@RequestMapping("/file")
public class UploadController {

    private static final String FILE_PATH = "d:/upload/";

    @Autowired
    private FileDao fileDao;

    /**
     * 根據(jù)文件唯一key判斷是否有上傳
     * @param key
     * @return
     */
    @GetMapping("/check/{key}")
    public R<FilePojo> check(@PathVariable String key){
        return R.ok(findByKey(key));
    }

    /**
     * 分片上傳(表單接收)
     * @param fileVo
     * @return
     * @throws Exception
     */
    @PostMapping(value = "/upload")
//    public R<String> upload(@RequestBody @RequestParam("file") MultipartFile file) throws IOException {
    public R<String> upload(FileVo fileVo) throws Exception {// 表單接收
        MultipartFile file = fileVo.getFile();
        String filename = file.getOriginalFilename();
        String date  = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
        String localPath = new StringBuilder()
                .append(date)
                .append(File.separator)
                .append(fileVo.getName())
                .append(".")
                .append(fileVo.getShareIndex())
                .toString();// 分片文件路徑與后綴處理 2022/03/15\13-提交Git倉庫.mp4.0 、2022/03/15\13-提交Git倉庫.mp4.1鹃唯、2022/03/15\13-提交Git倉庫.mp4.2 .....
        fileVo.setPath(localPath);
        File dest = new File(FILE_PATH + localPath);
        if (!dest.getParentFile().exists()){
            dest.getParentFile().setWritable(true);
            dest.getParentFile().mkdirs();// 不加 getParentFile() 創(chuàng)建的是文件夾爱榕,不是文件
        }
        file.transferTo(dest);
        FilePojo filePojo = new FilePojo();
        BeanUtils.copyProperties(fileVo,filePojo);
        String path = new StringBuilder()
                .append(date)
                .append(File.separator)
                .append(fileVo.getName())
                .toString();// 數(shù)據(jù)庫保存的最后完整文件的路徑與名稱, 2022/03/15\13-提交Git倉庫.mp4
        filePojo.setPath(path);

        // 查詢之前是否有過上傳
        Example example = new Example(FilePojo.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("key",filePojo.getKey());
        FilePojo filePojoDb = fileDao.selectOneByExample(example);
        if (filePojoDb == null){
            fileDao.insertSelective(filePojo);
        }else {
            fileDao.updateByExampleSelective(filePojo,example);
        }

        // 判斷是否上傳玩最后一個分片文件俯渤,然后進(jìn)行合并完整文件并刪除所有分片文件
        if (fileVo.getShareIndex().equals(fileVo.getShareTotal()-1)){
            this.merge(filePojo);
        }
        return R.ok(path);
    }

    /**
     * 合并所有分片文件成功后并刪除所有分片文件
     * @param filePojo
     * @throws Exception
     */
    private void merge(FilePojo filePojo) throws Exception {
        String path = FILE_PATH + filePojo.getPath();
        //FileOutputStream outputStream = new FileOutputStream(new File(path), true);// true表示可追加
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(path), true));

        //FileInputStream inputStream = null;
        BufferedInputStream bis = null;
        byte[] byt = new byte[10* 1024 * 1024];
        int len;

        try {
            for (int i = 0,leng = filePojo.getShareTotal(); i < leng; i++) {
                // 從第i個分片讀取
                /*inputStream = new FileInputStream(new File(path + "." + i));//
                while ((len = inputStream.read(byt)) != -1){
                    outputStream.write(byt,0,len);
                }*/
                bis = new BufferedInputStream(new FileInputStream(new File(path + "." + i)));
                while ((len = bis.read(byt))!= -1){
                    bos.write(byt,0,len);
                }
            }
        }catch (IOException e){
            log.error("分片合并異常", e);
        }finally {
            /*try {
                if (inputStream != null){
                    inputStream.close();
                }
                outputStream.close();
            }catch (Exception e){
                log.error("IO流關(guān)閉異常", e);
            }*/
            bos.flush();
            bos.close();
            if (bis != null){
                bis.close();
            }
        }

        // 刪除分片文件
        System.gc();
        Thread.sleep(100);

        for (int i = 0,leng = filePojo.getShareTotal(); i < leng; i++) {
            String localPath = path + "." + i;
            File file = new File(localPath);
            if (file.exists()){
                boolean result = file.delete();
                log.info("刪除分片文件{}呆细,{}", localPath, result ? "成功" : "失敗");
            }
        }
    }

    /**
     * 根據(jù)文件唯一key查詢
     * @param key
     * @return
     */
    private FilePojo findByKey(String key){
        Example example = new Example(FilePojo.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("key",key);
        return fileDao.selectOneByExample(example);
    }
}

然后前端對應(yīng)的請求代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    上傳:
    <input type="file" id="file">
</div>
<button id="btn">點擊上傳</button>
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
    $(function () {
        $("#btn").click(function () {
            var file = $("#file").prop("files")[0];
            if (file){
                var size = file.size;
                var shareSize = 10 * 1024 * 1024;// 默認(rèn)分片大小 20M
                var shareIndex = 0;// 默認(rèn)從第0片開始
                var shareTotal = Math.ceil(size / shareSize);// 計算總分片

                var key = md5(file.name+file.type+file.size);

                var params = {
                    filename:file.name,
                    size:size,
                    suffix:file.name.substring(file.name.lastIndexOf(".")+1),
                    type:file.type,
                    shareTotal:shareTotal,
                    shareIndex:shareIndex,
                    key:key,
                };

                // 先查詢是否有過上傳,或 上傳到哪一個的索引
                $.ajax({
                    type: "GET", // 數(shù)據(jù)提交類型
                    url: "http://localhost:8080/test/file/check/"+key, // 發(fā)送地址
                    dataType:"json",
                    success:function (res) {
                        if (res && res.data){
                            // 存在說明之前上傳過八匠,接著判斷是否之前上傳完成
                            if (res.data.shareIndex === (res.data.shareTotal - 1)){
                                // 相等說之前已經(jīng)上傳完整
                                alert("極速秒傳成功");
                            }else{
                                // 不相等說明之前上傳中斷了,接著再上傳
                                upload(params,file,res.data.shareIndex,shareSize);
                            }
                        }else{
                            // 沒有穿過就從第0個分片開始上傳
                            upload(params,file,shareIndex,shareSize);
                        }
                    }
                });
            }
        })
    });

    /**
     * 上傳
     * @param params
     * @param file
     * @param shareIndex
     * @param shareSize
     */
    function upload(params,file,shareIndex,shareSize) {
        var start = shareIndex * shareSize;// 分片起始位置
        var end = Math.min(params.size,start+shareSize);
        var fileShare = file.slice(start,end);// 截取file文件分片趴酣,進(jìn)行上傳

        var formData = new FormData();
        formData.append("file",fileShare);
        formData.append("name",params.filename);
        formData.append("size",params.size);
        formData.append("suffix",params.suffix);
        formData.append("type",params.type);
        formData.append("shareTotal",params.shareTotal);
        formData.append("shareIndex",shareIndex);
        formData.append("key",params.key);

        $.ajax({
            type: "POST", // 數(shù)據(jù)提交類型
            url: "http://localhost:8080/test/file/upload", // 發(fā)送地址
            dataType:"json",
            data: formData, //發(fā)送數(shù)據(jù)
            // async: true, // 是否異步
            processData: false,
            contentType: false, // 注意:此處并不是json傳輸數(shù)據(jù)梨树,而是表單
            success:function (res) {
                if (shareIndex === (params.shareTotal - 1)){
                    // 分片文件已上傳完
                    return;
                }else{
                    // 遞歸上傳分片文件
                    upload(params,file,shareIndex+1,shareSize);
                }
            }
        })
    }
</script>
</body>
</html>

到此完成,但是這里后臺接口沒有做文件完整性驗證岖寞,如要進(jìn)行完整性驗證抡四,請看下面

文件完整性驗證上傳

要進(jìn)行文件完整性驗證就不能直接 MultipartFile接收了,要在前端吧文件轉(zhuǎn)成 Base64 進(jìn)行上傳了仗谆,然后后臺接收后解析成 MultipartFile

首先指巡,FileVo文件要修改下

package com.upload.vo;

import com.upload.pojo.FilePojo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author xiaochi
 * @date 2022/3/15 8:38
 * @desc FileVo
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class FileVo extends FilePojo {

    private static final long serialVersionUID = -4528742454491886780L;

    private String file;// base64文件字符串
    private String encryFile;// 前端進(jìn)行md5加密后的符
}

接著新建一個文件 Base64DecodeMultipartFile 用來將接收到的Base64字符串轉(zhuǎn)成 MultipartFile

package com.upload.util;

import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/** base64轉(zhuǎn)為multipartFile
 * @author xiaochi
 * @date 2022/4/11 15:30
 * @desc Base64DecodeMultipartFile
 */
public class Base64DecodeMultipartFile implements MultipartFile {
    private final byte[] imgContent;

    private final String header;

    public Base64DecodeMultipartFile(byte[] imgContent, String header) {
        this.imgContent = imgContent;
        this.header = header.split(";")[0];
    }

    @Override
    public String getName() {
        return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
    }

    @Override
    public String getOriginalFilename() {
        return System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1];
    }

    @Override
    public String getContentType() {
        return header.split(":")[1];
    }

    @Override
    public boolean isEmpty() {
        return imgContent == null || imgContent.length == 0;
    }

    @Override
    public long getSize() {
        return imgContent.length;
    }

    @Override
    public byte[] getBytes() throws IOException {
        return imgContent;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(imgContent);
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        new FileOutputStream(dest).write(imgContent);
    }

    /**
     *  * base64轉(zhuǎn)multipartFile
     *  * @param base64
     *  * @return
     */
    public static MultipartFile base64Convert(String base64) {
        String[] baseStrs = base64.split(",");
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] b = new byte[0];
        try {
            b = decoder.decodeBuffer(baseStrs[1]);
        } catch (IOException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < b.length; ++i) {
            if (b[i] < 0) {
                b[i] += 256;
            }
        }
        return new Base64DecodeMultipartFile(b, baseStrs[0]);
    }
}

接著修改上傳文件的接口控制器方法 UploadController的上傳方法 upload

/**
 * 分片上傳(表單接收),且進(jìn)行文件完整性驗證
 * @param fileVo
 * @return
 * @throws Exception
 */
@PostMapping("/upload")
public R<String> upload(FileVo fileVo) throws Exception {
    MultipartFile file = Base64DecodeMultipartFile.base64Convert(fileVo.getFile());// 將 Base64 字符串解析成 MultipartFile
    // 驗證文件完整性
    if (!Objects.equals(fileVo.getEncryFile(),DigestUtils.md5DigestAsHex(fileVo.getFile().getBytes()))){
        return R.error("上傳文件已被損壞");
    }
    String date  = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
    String localPath = new StringBuilder()
            .append(date)
            .append(File.separator)
            .append(fileVo.getName())
            .append(".")
            .append(fileVo.getShareIndex())
            .toString();// 分片文件路徑與后綴處理 2022/03/15\13-提交Git倉庫.mp4.0 隶垮、2022/03/15\13-提交Git倉庫.mp4.1藻雪、2022/03/15\13-提交Git倉庫.mp4.2 .....
    fileVo.setPath(localPath);
    File dest = new File(FILE_PATH + localPath);
    if (!dest.getParentFile().exists()){
        dest.getParentFile().setWritable(true);
        dest.getParentFile().mkdirs();// 不加 getParentFile() 創(chuàng)建的是文件夾,不是文件
    }
    file.transferTo(dest);
    FilePojo filePojo = new FilePojo();
    BeanUtils.copyProperties(fileVo,filePojo);
    String path = new StringBuilder()
            .append(date)
            .append(File.separator)
            .append(fileVo.getName())
            .toString();// 數(shù)據(jù)庫保存的最后完整文件的路徑與名稱狸吞, 2022/03/15\13-提交Git倉庫.mp4
    filePojo.setPath(path);

    // 查詢之前是否有過上傳
    Example example = new Example(FilePojo.class);
    Example.Criteria criteria = example.createCriteria();
    criteria.andEqualTo("key",filePojo.getKey());
    FilePojo filePojoDb = fileDao.selectOneByExample(example);
    if (filePojoDb == null){
        fileDao.insertSelective(filePojo);
    }else {
        fileDao.updateByExampleSelective(filePojo,example);
    }

    // 判斷是否上傳玩最后一個分片文件勉耀,然后進(jìn)行合并完整文件并刪除所有分片文件
    if (fileVo.getShareIndex().equals(fileVo.getShareTotal()-1)){
        this.merge(filePojo);
    }
    return R.ok(path);
}

接著修改對應(yīng)的前端代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    上傳:
    <input type="file" id="file">
</div>
<button id="btn">點擊上傳</button>
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
    $(function () {
        $("#btn").click(function () {
            var file = $("#file").prop("files")[0];
            if (file){
                var size = file.size;
                var shareSize = 10 * 1024 * 1024;// 默認(rèn)分片大小 20M
                var shareIndex = 0;// 默認(rèn)從第0片開始
                var shareTotal = Math.ceil(size / shareSize);// 計算總分片

                var key = md5(file.name+file.type+file.size);

                var params = {
                    filename:file.name,
                    size:size,
                    suffix:file.name.substring(file.name.lastIndexOf(".")+1),
                    type:file.type,
                    shareTotal:shareTotal,
                    shareIndex:shareIndex,
                    key:key,
                };

                // 先查詢是否有過上傳,或 上傳到哪一個的索引
                $.ajax({
                    type: "GET", // 數(shù)據(jù)提交類型
                    url: "http://localhost:8080/test/file/check/"+key, // 發(fā)送地址
                    dataType:"json",
                    success:function (res) {
                        if (res && res.data){
                            // 存在說明之前上傳過蹋偏,接著判斷是否之前上傳完成
                            if (res.data.shareIndex === (res.data.shareTotal - 1)){
                                // 相等說之前已經(jīng)上傳完整
                                alert("極速秒傳成功");
                            }else{
                                // 不相等說明之前上傳中斷了便斥,接著再上傳
                                upload(params,file,res.data.shareIndex,shareSize);
                            }
                        }else{
                            // 沒有穿過就從第0個分片開始上傳
                            upload(params,file,shareIndex,shareSize);
                        }
                    }
                });
            }
        })
    });

    /**
     * 上傳
     * @param params
     * @param file
     * @param shareIndex
     * @param shareSize
     */
    function upload(params,file,shareIndex,shareSize) {
        var start = shareIndex * shareSize;// 分片起始位置
        var end = Math.min(params.size,start+shareSize);
        var fileShare = file.slice(start,end);// 截取file文件分片,進(jìn)行上傳
        // base64 上傳且進(jìn)行文件完整性驗證
        fileToBase64(fileShare, function(base64){
            var formData = new FormData();
            formData.append("file",base64);
            formData.append("encryFile",md5(base64));// 用于驗證文件完整性
            formData.append("name",params.filename);
            formData.append("size",params.size);
            formData.append("suffix",params.suffix);
            formData.append("type",params.type);
            formData.append("shareTotal",params.shareTotal);
            formData.append("shareIndex",shareIndex);
            formData.append("key",params.key);

            $.ajax({
                type: "POST", // 數(shù)據(jù)提交類型
                url: "http://localhost:8080/test/file/upload", // 發(fā)送地址
                dataType:"json",
                data: formData, //發(fā)送數(shù)據(jù)
                // async: true, // 是否異步
                processData: false,
                contentType: false, // 注意:此處并不是json傳輸數(shù)據(jù)威始,而是表單
                success:function (res) {
                    // res.code = 1枢纠,表示上傳失敗
                    if (res.code == 1 || shareIndex === (params.shareTotal - 1)){
                        // 分片文件已上傳完
                        return;
                    }else{
                        // 遞歸上傳分片文件
                        upload(params,file,shareIndex+1,shareSize);
                    }
                }
            })
        })
    }
    
    /**
     * File 轉(zhuǎn) Base64 圖片
     */
    function fileToBase64(file, callback){
        const reader = new FileReader()
        reader.onload = function(evt){
           if(typeof callback === 'function') {
                callback(evt.target.result)
            } else {
                console.log("我是base64:", evt.target.result);
            }
        }
        /* readAsDataURL 方法會讀取指定的 Blob 或 File 對象
        ** 讀取操作完成的時候,會觸發(fā) onload 事件
        *  result 屬性將包含一個data:URL格式的字符串(base64編碼)以表示所讀取文件的內(nèi)容黎棠。
        */ 
        reader.readAsDataURL(file);
    }
</script>
</body>
</html>

然后運行上傳完整晋渺,ok,到此結(jié)束葫掉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末些举,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俭厚,更是在濱河造成了極大的恐慌户魏,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叼丑,居然都是意外死亡关翎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門鸠信,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纵寝,“玉大人,你說我怎么就攤上這事星立∷睿” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵绰垂,是天一觀的道長室奏。 經(jīng)常有香客問我,道長劲装,這世上最難降的妖魔是什么胧沫? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮占业,結(jié)果婚禮上绒怨,老公的妹妹穿的比我還像新娘。我一直安慰自己谦疾,他們只是感情好南蹂,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著餐蔬,像睡著了一般碎紊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上樊诺,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天仗考,我揣著相機(jī)與錄音,去河邊找鬼词爬。 笑死秃嗜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顿膨。 我是一名探鬼主播锅锨,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恋沃!你這毒婦竟也來了必搞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤囊咏,失蹤者是張志新(化名)和其女友劉穎恕洲,沒想到半個月后塔橡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡霜第,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年葛家,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泌类。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡癞谒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刃榨,到底是詐尸還是另有隱情弹砚,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布枢希,位于F島的核電站迅栅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏晴玖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一为流、第九天 我趴在偏房一處隱蔽的房頂上張望呕屎。 院中可真熱鬧,春花似錦敬察、人聲如沸秀睛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹂安。三九已至,卻和暖如春锐帜,著一層夾襖步出監(jiān)牢的瞬間田盈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工缴阎, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留允瞧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓蛮拔,卻偏偏與公主長得像述暂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子建炫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容