先上GitHub地址:前端代码后台代码,具体代码下载回来就行,这里只讲核心部分。。。
核心思路: 前端对文件进行分片,并且发送文件的唯一标识(文件名、类型、大小或者其他属性进行md5摘要计算可得)、分片索引(第几个分片)、分片总数、文件名称(方便合并后的文件名称)记住这4个参数;后台判断分片索引等于分片总数就开始合并,通过流输出追加的方式合并文件。
先看前端的分片代码:

总分片数=文件大小/每片的大小,再向上取整。

 let shardTotal = Math.ceil(size / shardSize); //总片数

项目已经安装MD5组件
// 生成文件标识,标识多次上传的是不是同一个文件

let key = this.$md5(file.name + file.size + file.type);

在这里插入图片描述
文件分片截取核心代码:

 let fileShard = file.slice(start, end); //从文件中截取当前的分片数据

在这里插入图片描述
类似数据库分页原理,但是要注意切片的终点,当切片不足预定的切片大小,那就取文件的大小作为终点的边界。
点击按钮触发事件函数:

 async handUpLoad(req) {
                let _this = this;
                var file = req.file;
                /*  console.log('handUpLoad', req)
                  console.log(file);*/
                //let param = new FormData();
                //通过append向form对象添加数据

                //文件名称和格式,方便后台合并的时候知道要合成什么格式
                let fileName = file.name;
                let suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();
                //这里判断文件格式,有其他格式的自行判断
                if (suffix != 'mp4') {
                    this.$message.error('文件格式错了哦。。');
                    return;
                }

                // 文件分片
                // let shardSize = 10 * 1024 * 1024;    //以10MB为一个分片
                // let shardSize = 50 * 1024;    //以50KB为一个分片
                let shardSize = _this.shardSize;
                let shardIndex = 1;		//分片索引,1表示第1个分片
                let size = file.size;
                let shardTotal = Math.ceil(size / shardSize); //总片数
                // 生成文件标识,标识多次上传的是不是同一个文件
                let key = this.$md5(file.name + file.size + file.type);
                let param = {
                    key: key,
                    shardIndex: shardIndex,
                    shardSize: shardSize,
                    shardTotal: shardTotal,
                    size: size,
                    fileName: fileName,
                    suffix: suffix
                }
                /*param.append("uid", key);
                param.append("shardIndex", shardIndex);
                param.append("shardSize", shardSize);
                param.append("shardTotal", shardTotal);
                param.append("size", size);
                param.append("fileName", fileName);
                param.append("suffix", suffix);

*/

                let checkIndexData = await _this.check(key);//得到文件分片索引
                let checkIndex = checkIndexData.findex;

                //console.log(checkIndexData)
                if (checkIndex == -1) {
                    this.recursionUpload(param, file);
                } else if (checkIndex < shardTotal) {
                    param.shardIndex = param.shardIndex + 1;
                    this.recursionUpload(param, file);
                } else {
                    this.videoUrl = checkIndexData.fname;//把地址赋值给视频标签
                    this.$message({
                        message: '极速秒传成功。。。。。',
                        type: 'success'
                    });
                }


                //console.log('结果:', res)
            },

前端在发起分片传输的时候先向后台检查分片信息,根据分片索引情况调整分片索引。
在这里插入图片描述
前端采取递归的传输方式:

async recursionUpload(param, file) {
                //FormData私有类对象,访问不到,可以通过get判断值是否传进去
                let _this = this;
                let key = param.key;
                let shardIndex = param.shardIndex;
                let shardTotal = param.shardTotal;
                let shardSize = param.shardSize;
                let size = param.size;
                let fileName = param.fileName;
                let suffix = param.suffix;

                let fileShard = _this.getFileShard(shardIndex, shardSize, file);

                //param.append("file", fileShard);//文件切分后的分片
                //param.file = fileShard;
                let totalParam = new FormData();
                totalParam.append('file', fileShard);
                totalParam.append("key", key);
                totalParam.append("shardIndex", shardIndex);
                totalParam.append("shardSize", shardSize);
                totalParam.append("shardTotal", shardTotal);
                totalParam.append("size", size);
                totalParam.append("fileName", fileName);
                totalParam.append("suffix", suffix);
                let config = {
                    //添加请求头
                    headers: {"Content-Type": "multipart/form-data"}
                };
                console.log(param);
                var res = await this.$http.post('/upload', totalParam, config)

                var resData = res.data;
                if (resData.status) {
                    if (shardIndex < shardTotal) {
                        this.$notify({
                            title: '成功',
                            message: '分片' + shardIndex + '上传完成。。。。。。',
                            type: 'success'
                        });
                    } else {
                        this.videoUrl = resData.data;//把地址赋值给视频标签
                        this.$notify({
                            title: '全部成功',
                            message: '文件上传完成。。。。。。',
                            type: 'success'
                        });
                    }

                    if (shardIndex < shardTotal) {
                        console.log('下一份片开始。。。。。。');
                        // 上传下一个分片
                        param.shardIndex = param.shardIndex + 1;
                        _this.recursionUpload(param, file);
                    }
                }


            }

在这里插入图片描述

后台处理:
先接受前端的分片索引检查,实质查询数据记录的分片信息
在这里插入图片描述

和正常的接收文件操作一样,先记录每次文件分片的上传的4个信息没有记录则是新增,有记录则是通过唯一标识修改分片索引
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
当前索引分片数==总分片数则开始合并文件:
在这里插入图片描述

合并:

 public void merge(FilePojo filePojo) throws Exception {
        Long shardTotal = filePojo.getShardTotal();
        File newFile = new File(FileConstance.FILE_PATH + filePojo.getFileName());
        if (newFile.exists()) {
            newFile.delete();
        }
        FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加写入
        FileInputStream fileInputStream = null;//分片文件
        byte[] byt = new byte[10 * 1024 * 1024];
        int len;
        try {
            for (int i = 0; i < shardTotal; i++) {
                // 读取第i个分片
                fileInputStream = new FileInputStream(new File(FileConstance.FILE_PATH + filePojo.getKey() + "." + (i + 1))); //  course\6sfSqfOwzmik4A4icMYuUe.mp4.1
                while ((len = fileInputStream.read(byt)) != -1) {
                    outputStream.write(byt, 0, len);//一直追加到合并的新文件中
                }
            }
        } catch (IOException e) {
            log.error("分片合并异常", e);
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                outputStream.close();
                log.info("IO流关闭");
                System.gc();
            } catch (Exception e) {
                log.error("IO流关闭", e);
            }
        }
        log.info("合并分片结束");
    }

定义文件输出流:
后面的参数一定要true,设置可追加

 FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加写入

在这里插入图片描述
遍历所有分片,然后通过流的形式,边读边写;
测试效果:
全新分片上传:
在这里插入图片描述
本机文件情况:
在这里插入图片描述
再次上传-极速秒传:
在这里插入图片描述
断点续传:
这里模拟,先完整上传,我们删除合并的文件和其他分片,保留2个分片,然后数据库的索引也是改为2,
在这里插入图片描述
再次操作:就会从第二分片开始
在这里插入图片描述
文件合并后没有数据丢失:
在这里插入图片描述
撒花~~~~~~~~~~~完结O(∩_∩)O哈哈~

Logo

前往低代码交流专区

更多推荐