赞
踩
先上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哈哈~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。