利用SpringBoot和vue-simple-uploader进行文件的分片上传
利用SpringBoot和vue-simple-uploader进行文件的分片上传效果【上传Zip文件为例,可以自行扩展】引入vue-simple-uploader安装上传插件npm install vue-simple-uploader --savemain.js全局引入上传插件import uploader from 'vue-simple-uploade...
·
利用SpringBoot和vue-simple-uploader进行文件的分片上传
效果【上传Zip文件为例,可以自行扩展】
引入vue-simple-uploader
- 安装上传插件
npm install vue-simple-uploader --save
- main.js全局引入上传插件
import uploader from 'vue-simple-uploader' Vue.use(uploader)
- 安装md5校验插件(保证上传文件的完整性和一致性)
npm install spark-md5 --save
页面
<template>
<div>
<uploader :key="uploader_key" :options="options" class="uploader-example"
:autoStart="false"
@file-success="onFileSuccess"
@file-added="filesAdded">
<uploader-unsupport></uploader-unsupport>
<uploader-drop>
<uploader-btn :single="true" :attrs="attrs">选择Zip文件</uploader-btn>
</uploader-drop>
<uploader-list></uploader-list>
</uploader>
</div>
</template>
<script>
import SparkMD5 from 'spark-md5';
export default {
data() {
return {
uploader_key: new Date().getTime(),
options: {
target: '/chunk/chunkUpload',
testChunks: false,
},
attrs: {
accept: '.zip'
}
}
},
methods: {
onFileSuccess: function (rootFile, file, response, chunk) {
console.log(JSON.parse(response).model);
},
computeMD5(file) {
const loading = this.$loading({
lock: true,
text: '正在计算MD5',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
let fileReader = new FileReader();
let time = new Date().getTime();
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
let currentChunk = 0;
const chunkSize = 10 * 1024 * 1000;
let chunks = Math.ceil(file.size / chunkSize);
let spark = new SparkMD5.ArrayBuffer();
file.pause();
loadNext();
fileReader.onload = (e => {
spark.append(e.target.result);
if (currentChunk < chunks) {
currentChunk++;
loadNext();
this.$nextTick(() => {
console.log('校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%')
})
} else {
let md5 = spark.end();
loading.close();
this.computeMD5Success(md5, file);
console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
}
});
fileReader.onerror = function () {
this.error(`文件${file.name}读取出错,请检查该文件`);
loading.close();
file.cancel();
};
function loadNext() {
let start = currentChunk * chunkSize;
let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
}
},
computeMD5Success(md5, file) {
file.uniqueIdentifier = md5;//把md5值作为文件的识别码
file.resume();//开始上传
},
filesAdded(file, event) {
//大小判断
const isLt100M = file.size / 1024 / 1024 < 10;
if (!isLt100M) {
this.$message.error(this.$t("error.error_upload_file_max"));
} else {
this.computeMD5(file)
}
}
}
}
</script>
<style>
.uploader-example {
width: 90%;
padding: 15px;
margin: 40px auto 0;
font-size: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, .4);
}
.uploader-example .uploader-btn {
margin-right: 4px;
}
.uploader-example .uploader-list {
max-height: 440px;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
}
</style>
后台
-
引入工具
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency>
-
控制类
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; @RestController @RequestMapping("/chunk") public class ChunkController { @RequestMapping("/chunkUpload") public StdOut chunkUpload(MultipartFileParam param, HttpServletRequest request, HttpServletResponse response) { StdOut out = new StdOut(); File file = new File("C:\\chunk_test");//存储路径 ChunkService chunkService = new ChunkService(); String path = file.getAbsolutePath(); response.setContentType("text/html;charset=UTF-8"); try { //判断前端Form表单格式是否支持文件上传 boolean isMultipart = ServletFileUpload.isMultipartContent(request); if (!isMultipart) { out.setCode(StdOut.PARAMETER_NULL); out.setMessage("表单格式错误"); return out; } else { param.setTaskId(param.getIdentifier()); out.setModel(chunkService.chunkUploadByMappedByteBuffer(param, path)); return out; } } catch (NotSameFileExpection e) { out.setCode(StdOut.FAIL); out.setMessage("MD5校验失败"); return out; } catch (Exception e) { out.setCode(StdOut.FAIL); out.setMessage("上传失败"); return out; } } }
-
StdOut类(只是封装的返回类)
public class StdOut { public static final int SUCCESS = 200; public static final int FAIL = 400; public static final int PARAMETER_NULL = 500; public static final int NO_LOGIN = 600; private int code = 200; private Object model = null; private String message = null; public StdOut() { this.setCode(200); this.setModel((Object)null); } public StdOut(int code) { this.setCode(code); this.setModel((Object)null); } public StdOut(List<Map<String, Object>> model) { this.setCode(200); this.setModel(model); } public StdOut(int code, List<Map<String, Object>> model) { this.setCode(code); this.setModel(model); } public int getCode() { return this.code; } public void setCode(int code) { this.code = code; } public String toString() { return JSON.toJSONString(this); } public Object getModel() { return this.model; } public void setModel(Object model) { this.model = model; } public String getMessage() { return this.message; } public void setMessage(String message) { this.message = message; } }
-
MultipartFileParam类(文件信息类)
import org.springframework.web.multipart.MultipartFile; public class MultipartFileParam { private String taskId; private int chunkNumber; private long chunkSize; private int totalChunks; private String identifier; private MultipartFile file; public String getTaskId() { return taskId; } public void setTaskId(String taskId) { this.taskId = taskId; } public int getChunkNumber() { return chunkNumber; } public void setChunkNumber(int chunkNumber) { this.chunkNumber = chunkNumber; } public long getChunkSize() { return chunkSize; } public void setChunkSize(long chunkSize) { this.chunkSize = chunkSize; } public int getTotalChunks() { return totalChunks; } public void setTotalChunks(int totalChunks) { this.totalChunks = totalChunks; } public String getIdentifier() { return identifier; } public void setIdentifier(String identifier) { this.identifier = identifier; } public MultipartFile getFile() { return file; } public void setFile(MultipartFile file) { this.file = file; } }
-
ChunkService类
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.UUID; public class ChunkService { public String chunkUploadByMappedByteBuffer(MultipartFileParam param, String filePath) throws IOException, NotSameFileExpection { if (param.getTaskId() == null || "".equals(param.getTaskId())) { param.setTaskId(UUID.randomUUID().toString()); } String fileName = param.getFile().getOriginalFilename(); String tempFileName = param.getTaskId() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp"; File fileDir = new File(filePath); if (!fileDir.exists()) { fileDir.mkdirs(); } File tempFile = new File(filePath, tempFileName); //第一步 打开将要写入的文件 RandomAccessFile raf = new RandomAccessFile(tempFile, "rw"); //第二步 打开通道 FileChannel fileChannel = raf.getChannel(); //第三步 计算偏移量 long position = (param.getChunkNumber() - 1) * param.getChunkSize(); //第四步 获取分片数据 byte[] fileData = param.getFile().getBytes(); //第五步 写入数据 fileChannel.position(position); fileChannel.write(ByteBuffer.wrap(fileData)); fileChannel.force(true); fileChannel.close(); raf.close(); //判断是否完成文件的传输并进行校验与重命名 boolean isComplete = checkUploadStatus(param, fileName, filePath); if (isComplete) { FileInputStream fileInputStream = new FileInputStream(tempFile.getPath()); String md5 = DigestUtils.md5Hex(fileInputStream); fileInputStream.close(); if (StringUtils.isNotBlank(md5) && !md5.equals(param.getIdentifier())) { throw new NotSameFileExpection(); } renameFile(tempFile, fileName); return fileName; } return null; } public void renameFile(File toBeRenamed, String toFileNewName) { if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) { System.err.println("文件不存在"); return; } String p = toBeRenamed.getParent(); File newFile = new File(p + File.separatorChar + toFileNewName); toBeRenamed.renameTo(newFile); } public boolean checkUploadStatus(MultipartFileParam param, String fileName, String filePath) throws IOException { File confFile = new File(filePath, fileName + ".conf"); RandomAccessFile confAccessFile = new RandomAccessFile(confFile, "rw"); //设置文件长度 confAccessFile.setLength(param.getTotalChunks()); //设置起始偏移量 confAccessFile.seek(param.getChunkNumber() - 1); //将指定的一个字节写入文件中 127, confAccessFile.write(Byte.MAX_VALUE); byte[] completeStatusList = FileUtils.readFileToByteArray(confFile); confAccessFile.close();//不关闭会造成无法占用 //创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是127 for (int i = 0; i < completeStatusList.length; i++) { if (completeStatusList[i] != Byte.MAX_VALUE) { return false; } } confFile.delete(); return true; } }
6.NotSameFileExpection类
public class NotSameFileExpection extends Exception {
public NotSameFileExpection() {
super("File MD5 Different");
}
}
遇到问题
- 根据自己的实际情况进行取舍,灵活处理
更多推荐
已为社区贡献16条内容
所有评论(0)