文件分片上传

1. 意义

普通文件流上传,这是一篇介绍普通的ajax + SpringMVC文件上传的文章,这里直接把文件转化为字节流上传到服务器,可能在大多数情况下没有什么问题。但是在上传超大文件时一旦中断只能重新上传,这是很让人奔溃的。
分片上传的意义在于把一个文件分成多份,一片一片的上传。当某一片上传失败时可以记录下来,进行重传或者其他处理,分片的附带好处还能很方便的实现进度条。

2.前端做法

前后端需要统一下每个切片的大小,我设置的是1M(也可以动态设定分片尺寸,比如放在请求参数中或者请求头中)
把文件用slice拆分后上传(像上传普通的文件一样就行)
直接上代码:

<!DOCTYPE html>
<html xmlns="http://c7.gg/cd5XT" xmlns:th="http://c7.gg/cd9re"
      xmlns:sec="http://c7.gg/cd9rv">
<head>
    <meta charset="UTF-8" />
    <title>Insert title here</title>
</head>
<body>
<h1 th:inlines="text">文件上传</h1>

<h2>分片上传</h2>
<p>选择文件: <input type="file" id="file" name="file"/></p>
<p><input type="submit" value="提交" onclick="commit()"/></p>
</body>
</html>
<script>
    // 每个文件切片大小定为1M(1024*1024字节)(需要跟服务器协商好).
    var BYTES_PER_SLICE = 1<<20;
    // 已发送的数量
    var  hasSendNum = 0;
    // 总切片数
    var totalSlices;

    // 提交方法
    function commit() {
        // 拿出选中的第一个文件
        var file = document.getElementById("file").files[0];
        // 文件的总字节数
        var totalSize = file.size;
        // 当前片数
        var index = 0;
        // 分片的开始、结束(不含)
        var start,end;
        // 文件名
        var fileName = file.name;
        // 初始化已发送数量为0
        hasSendNum = 0;

        // 计算文件切片总数(向上取整)
        totalSlices = Math.ceil(file.size / BYTES_PER_SLICE);
        // 不断循环将切片上传
        while(index < totalSlices) {

            start = index*BYTES_PER_SLICE;
            end = start + BYTES_PER_SLICE;

            var slice =file.slice(start,end);//切割文件
            uploadFile(slice, index++,fileName);
        }
    }

    //上传文件
    function uploadFile(slice, index,fileName) {
        var retry = 1;
        var formDate = new FormData();
        formDate.append("slice", slice);
        formDate.append("fileName",fileName);
        formDate.append("index",index);

        var xhr = new XMLHttpRequest();
        xhr.open('POST', 'sliceUpload', true);//false指同步上传,因为我的服务器内存较小,选择同步,如果追求速度,可以选择 //ture,异步上传
        xhr.onreadystatechange = ()=>uploadCallBack(xhr,slice,index,fileName);
        xhr.send(formDate);

    }

    /**
     * @desc 上传回调
     * @param xhr
     */
    function uploadCallBack(xhr,slice,index,fileName) {
        if(xhr.readyState==4) {
            if(xhr.status==200) {
                if(xhr.responseText==1) {
                    hasSendNum++;
                    console.log("第"+index+"片,完成度"+parseInt(hasSendNum/totalSlices*100)+"%");
                    if(hasSendNum==totalSlices) {
                        console.log("上传完毕");
                    }
                }
            } else {
                console.log("上传失败,重试##################################");
                // 重试
                uploadFile(slice, index,fileName);
            }
        }
    }
</script>

3. 服务端做法

服务端需要用到一个RandomAccessFile,不了解的可以自行百度,总之他的作用就是可以随机访问一个文件的任何位置,利用这个功能把前端发送过来的文件切片拼接成一个完整的文件。

代码如下:
1
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;

/**
 * @Author:Lvxingqing
 * @Description:
 * @Date:Create in 17:40 2018/6/27
 * @Modified By:
 */
@RestController
public class SliceUpload {
    private final int BYTES_PER_SLICE = 1<<20;
    @RequestMapping(value="sliceUpload",method= RequestMethod.POST)
    public int upload(@RequestParam("slice")MultipartFile slice,String fileName,int index) {
        int result = 0;
        if(slice.isEmpty()){
            return 0;
        }
        int size = (int) slice.getSize();
        System.out.println(fileName + "-->" + size);

        String path = "d:/test" ;
        File dest = new File(path + "/" + fileName);
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(dest,"rw");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        if(!dest.getParentFile().exists()){ //判断文件父目录是否存在
            dest.getParentFile().mkdir();
        }
        try {
            byte[] bytes = slice.getBytes(); //保存文件
            randomAccessFile.seek(index*BYTES_PER_SLICE);
            randomAccessFile.write(bytes);
            result = 1;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            try {
                randomAccessFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}

猜你喜欢

转载自blog.csdn.net/ftaomanman/article/details/86690837