前后端实现分段上传简易教程
1.大概思路
废话不多说,简单思路如下:
- 前端将将file文件分片成多个blob文件,每个blob可以按默认最大上传大小设置。
- 将每个blob编写简单的顺序id,然后通过异步的方式传给后端。
- 每个blob都需要计算md5值,同时后端也需要计算这是保证分段数据不损坏的关键。
- 最后后端按id顺序合成新的二进制数组,写入到文件中。
2.前后端环境
前端环境如下:
- vue这个不重要,不用vue也可以实现
- spark-md5这个是计算blob的md5利器
- axios进行异步http请求
后端主要环境如下:
- hutool包,这个主要也是用来计算MD5值和写入文件
3.前端案例代码
//blob切片
sliceFile(file) {
const maxsize = 1024 * 1024 //1M
var length = file.size
var segmentList = []
let to = maxsize
for (let start = 0; start < length; start = to, to += maxsize) {
segmentList.push(file.slice(start, to))
}
return segmentList
},
//发送切片
sendBlob(blob, allcode, index, allindex) {
var reader = new FileReader()
var formdata = new FormData()
formdata.append("file", blob)
reader.onload = (e) => {
var md5 = SparkMD5.hashBinary(e.target.result)
formdata.append("segcode", md5)
formdata.append("allcode", allcode)
formdata.append("index", index)
formdata.append("allindex", allindex)
formdata.append("services", this.selectedServiceList)
axios.post("http://localhost/audio/filesegment/upload", formdata).then(e => {
if (e.data.code === 200) {
console.log("文件上传成功");
this.message("上传文件成功", "可以播放返回音频")
} else if (e.data.code === 202) {
console.log("分段数据上传成功");
} else if (e.data.code === 500) {
console.log("文件破损,nothing");
// this.uploadFile() 如果这么调用的话会可能找不到递归出口
} else if (e.data.code === 502) {
console.log("分段破损,分段重传");
this.sendBlob(blob, allcode, index, allindex)
}
})
}
reader.readAsBinaryString(blob)
},
uploadFile() {
var file = this.$refs.file.files[0]
var segments = this.sliceFile(file)
console.log(file);
console.log(segments)
var fileReader = new FileReader()
fileReader.onload = (e) => {
var allcode = SparkMD5.hashBinary(e.target.result)
for (let index = 0; index < segments.length; index++) {
//该函数 内部post请求是异步的
this.sendBlob(segments[index], allcode, index, segments.length)
}
}
fileReader.readAsBinaryString(file)
},
html核心代码如下:
<input ref="file" type="file" />
<button @click="uploadFile" value="点击上传"></button>
4.后端案例代码
一开始我没意识到这个小小的案例尽然设计到了多线程的问题,于是将HashMap改成HashTable来存储分段数据
@PostMapping("/audio/filesegment/upload")
public Result reciveFileSeg(MultipartFile file,
String segcode,
String allcode,
Integer index,
Integer allindex) throws IOException {
System.out.println(Arrays.toString(services));
if(!container.containsKey(allcode)){
container.put(allcode,new Hashtable<>()); //hashmap具有多线程问题
}
int len = file.getBytes().length;
byte [] source = new byte[len];
ArrayUtil.copy(file.getBytes(),source,len);
// source[0] += RandomUtil.randomInt(0,2); 检验重传
if (!segcode.equals(DigestUtil.md5Hex(source))){
return new Result(502,"分段数据损坏",null);
}
//分段数据完整
Map<String,byte []> map = container.get(allcode);
map.put(String.valueOf(index),source); //不需要再写入container中了
if (map.keySet().size() == allindex){
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
//排序为了分段顺序正确
Object [] keys = map.keySet().stream().sorted(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.valueOf(o1)-Integer.valueOf(o2);
}
}).toArray();
for (int i = 0; i < keys.length; i++) {
byte [] seg = map.get(keys[i]);
buffer.write(seg);
}
byte [] fileByte = buffer.toByteArray();
String fileMd5 = DigestUtil.md5Hex(fileByte);
if (allcode.equals(fileMd5)){
container.remove(allcode); //删除放在map中的数据
System.out.println("完全上传成功");
FileUtil.writeBytes(fileByte,FileUtil.newFile(PATH+allcode));
//todo 作某些服务 可以传递serviceId代表具体的音频增强的功能,同时也可以处理心音和声学事件
return new Result(200,"文件完整上传",null);
}else{
System.out.println("完整文件校验失败");
return new Result(500,"完整文件校验失败",null);
}
}
// set(index, Arrays.asList(source)); //将数组转换为List
return new Result(202,"分段数据上传成功",null);
}
总结
如果有更好的思路或者疑问,欢迎留言。