首先安装vue-simple-uploader和spark MD5
npm install vue-simple-uploader --save
npm i spark MD5
然后在main.js里面添加
import uploader from 'vue-simple-uploader'
Vue.use(uploader)
新建一个FileUpload.vue组件
<template>
<uploader :options="options" :autoStart="false"
@file-success="onFileSuccess"
@file-added="filesAdded">
<uploader-unsupport></uploader-unsupport>
<uploader-drop>
<uploader-btn>选择文件</uploader-btn>
</uploader-drop>
<uploader-list></uploader-list>
</uploader>
</template>
<script>
import SparkMD5 from 'spark-md5'
export default {
name: "FileUpload",
data(){
return {
options: {
target: '/erp/file_upload/chunk',//SpringBoot后台接收文件夹数据的接口
testChunks: true,//是否分片-分片
chunkSize: 30 * 1024 * 1024,//分块大小
fileParameterName: 'file',
maxChunkRetries: 3, //最大自动失败重试上传次数
checkChunkUploadedByResponse: function (chunk, message) {
let objMessage = JSON.parse(message);
// 获取当前的上传块的集合
let chunkNumbers = objMessage.result.chunkNumbers;
// 判断当前的块是否被该集合包含,从而判定是否需要跳过
return (chunkNumbers || []).indexOf(chunk.offset + 1) >= 0;
}
}
}
},methods:{
onFileSuccess: function (rootFile, file, response, chunk) {
let res = JSON.parse(response);
if(res.code == "error"){
_this.$message({
message: res.message,
type: 'error'
});
}
//文件以存在,直接调用回调函数
if (res.result.code == 200) {
//执行回调函数
this.$emit('doneBlock',res);
}
// 需要合并
if (res.result.code == 205) {
const formData = new FormData();
formData.append("identifier", file.uniqueIdentifier);
formData.append("filename", file.name);
formData.append("totalSize", file.size);
this.$axios.post("/erp/file_upload/merge", formData).then((res) => {
//执行回调函数
this.$emit('doneBlock',res.data);
})
}
},filesAdded(file, event){
this.computeMD5(file)
},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 = 30 * 1024 * 1024;
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();
// 实时展示MD5的计算进度
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();//开始上传
}
}
}
</script>
在需要调用的页面加入代码:
HTML代码如下
<el-button type="primary" icon="el-icon-upload" @click="uploadFileVisible = true">上传</el-button>
<el-dialog title="上传资料" :visible.sync="uploadFileVisible" width="600px">
<file-upload style="height: 300px" @doneBlock="uploadSuccess"></file-upload>
</el-dialog>
JS代码如下
//导入我们刚刚新建的组件
import FileUpload from './group/FileUpload.vue';
export default {
name: "Test",
components: {FileUpload},
data() {
return {
uploadFileVisible:false
}
},methods: {
uploadFile(){
this.uploadFileVisible = true;
},uploadSuccess(obj){
//这里是上传成功后回调的函数,可以根据自己的业务需求来进行操作
//我这边上传成功后往数据库插入一条数据
var proFile ={};
proFile.fileName = obj.result.fileName;
proFile.recordDate = "";
proFile.recorder = localStorage.getItem('ms_username');
proFile.fileUrl = obj.result.fileUrl;
proFile.fileSize = obj.result.fileSize;
//保存的业务接口
this.$axios.post("/erp/test/save", proFile).then((res) => {
if (res.data.code == "error") {
this.$message({
message: res.data.msg,
type: 'error'
});
} else {
this.$message({
message: res.data.msg,
type: 'success'
});
this.getData();
}
})
}
}
}
接下来是后台代码
新建两个实体类,ErpFiles,ErpChunk
ErpFiles.java
package com.example.entity;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "erp_files")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class ErpFiles implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(generator = "jpa-uuid")
private String id;
private String fileName;
private String fileType;
private String fileUrl;
private Date creationTime;
private String userName;
private Long fileSize;
private String fileMd5;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public String getFileUrl() {
return fileUrl;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
}
public Date getCreationTime() {
return creationTime;
}
public void setCreationTime(Date creationTime) {
this.creationTime = creationTime;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public String getFileMd5() {
return fileMd5;
}
public void setFileMd5(String fileMd5) {
this.fileMd5 = fileMd5;
}
}
ErpChunk.java
package com.example.entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.web.multipart.MultipartFile;
@Entity
@Table(name = "erp_chunk")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class ErpChunk implements Serializable {
private static final long serialVersionUID = 7073871700302406420L;
@Id
@GeneratedValue(generator = "jpa-uuid")
private String id;
/**
* 当前文件块,从1开始
*/
private Integer chunkNumber;
/**
* 分块大小
*/
private Long chunkSize;
/**
* 当前分块大小
*/
private Long currentChunkSize;
/**
* 总大小
*/
private Long totalSize;
/**
* 文件标识
*/
private String identifier;
/**
* 文件名
*/
private String filename;
/**
* 相对路径
*/
private String relativePath;
/**
* 总块数
*/
private Integer totalChunks;
/**
* 文件类型
*/
private String type;
/**
* 要上传的文件
*/
@Transient
private MultipartFile file;
@Transient
private String parentId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getChunkNumber() {
return chunkNumber;
}
public void setChunkNumber(Integer chunkNumber) {
this.chunkNumber = chunkNumber;
}
public Long getChunkSize() {
return chunkSize;
}
public void setChunkSize(Long chunkSize) {
this.chunkSize = chunkSize;
}
public Long getCurrentChunkSize() {
return currentChunkSize;
}
public void setCurrentChunkSize(Long currentChunkSize) {
this.currentChunkSize = currentChunkSize;
}
public Long getTotalSize() {
return totalSize;
}
public void setTotalSize(Long totalSize) {
this.totalSize = totalSize;
}
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getRelativePath() {
return relativePath;
}
public void setRelativePath(String relativePath) {
this.relativePath = relativePath;
}
public Integer getTotalChunks() {
return totalChunks;
}
public void setTotalChunks(Integer totalChunks) {
this.totalChunks = totalChunks;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
public String getParentId() {
return parentId;
}
}
然后新建一个FileUploadController.java的文件
package com.example.controller;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.example.entity.ErpChunk;
import com.example.entity.ErpFiles;
import com.example.service.ErpChunkService;
import com.example.service.ErpFilesService;
import com.example.utils.GooidServerSay;
@Controller
@RequestMapping("/file_upload")
public class FileUploadController {
@Value("${file.uploadFolder1}")
private String uploadFolder;
@Value("${file.staticAccessPath1}")
private String staticAccessPath;
@Autowired
private ErpChunkService chunkService;
@Autowired
private ErpFilesService filesService;
/**
* 验证当前文件块是否上传
*
* @param chunk
* @return
*/
@RequestMapping(value = "/chunk", method = RequestMethod.GET)
@ResponseBody
public GooidServerSay checkChunks(ErpChunk chunk) {
GooidServerSay ret = new GooidServerSay();
try {
String identifier = chunk.getIdentifier();
Map<String, Object> params = new HashMap<String, Object>();
params.put("EQ_identifier", identifier);
List<ErpChunk> erpChunks = chunkService.find(params);
Set<Integer> chunkNumbers = new HashSet<Integer>();
for (ErpChunk erpChunk : erpChunks) {
chunkNumbers.add(erpChunk.getChunkNumber());
}
Map<String, Object> res = new HashMap<String, Object>();
res.put("chunkNumbers", chunkNumbers);
params = new HashMap<String, Object>();
params.put("EQ_fileMd5", identifier);
List<ErpFiles> files = filesService.find(params);
if (files.size() > 0) {
res.put("code", 200);
res.put("fileName", files.get(0).getFileName());
res.put("fileUrl", files.get(0).getFileUrl());
res.put("fileSize", files.get(0).getFileSize());
}else if (erpChunks.size() > 0 && erpChunks.size() == erpChunks.get(0).getTotalChunks()) {
res.put("message", "上传成功!");
res.put("code", 205);
}
ret.setResult(res);
} catch (Exception e) {
ret.setCode("error");
ret.setMsg("操作失败\n\n详细信息:" + e.getMessage());
e.printStackTrace();
}
return ret;
}
/**
* 分块上传
*
* @param chunk
* @return
*/
/**
* @param chunk
* @return
*/
@RequestMapping(value = "/chunk", method = RequestMethod.POST)
@ResponseBody
public GooidServerSay saveChunk(ErpChunk chunk) {
GooidServerSay ret = new GooidServerSay();
try {
// 这里的操作和保存单段落的基本是一致的~
MultipartFile file = chunk.getFile();
String identifier = chunk.getIdentifier();
byte[] bytes;
try {
bytes = file.getBytes();
// 这里的不同之处在于这里进行了一个保存分块时将文件名的按照-chunkNumber的进行保存
Path path = Paths
.get(generatePath(uploadFolder + LoginController.getUser().getUsername() + "/", chunk));
Files.write(path, bytes);
} catch (IOException e) {
e.printStackTrace();
}
if (chunk.getFilename().lastIndexOf(".") > 0) {
chunk.setType(chunk.getFilename().substring(chunk.getFilename().lastIndexOf(".")));
}
chunkService.save(chunk);
// 这里进行的是保存到redis,并返回集合的大小的操作
Map<String, Object> params = new HashMap<String, Object>();
params.put("EQ_identifier", identifier);
int chunks = chunkService.find(params).size();
Map<String, Object> result = new HashMap<>();
// 如果集合的大小和totalChunks相等,判定分块已经上传完毕,进行merge操作
if (chunks == chunk.getTotalChunks().intValue()) {
result.put("message", "上传成功!");
result.put("code", 205);
}
ret.setResult(result);
} catch (Exception e) {
e.printStackTrace();
ret.setCode("error");
ret.setMsg("操作失败\n\n详细信息:" + e.getMessage());
}
return ret;
}
/**
* 生成分块的文件路径
*/
private static String generatePath(String uploadFolder, ErpChunk chunk) {
StringBuilder sb = new StringBuilder();
// 拼接上传的路径
sb.append(uploadFolder).append(File.separator).append(chunk.getIdentifier());
// 判断uploadFolder/identifier 路径是否存在,不存在则创建
if (!Files.isWritable(Paths.get(sb.toString()))) {
try {
Files.createDirectories(Paths.get(sb.toString()));
} catch (IOException e) {
e.printStackTrace();
}
}
// 返回以 - 隔离的分块文件,后面跟的chunkNumber方便后面进行排序进行merge
return sb.append(File.separator).append(chunk.getFilename()).append("-").append(chunk.getChunkNumber())
.toString();
}
@RequestMapping(value = "/merge", method = RequestMethod.POST)
@ResponseBody
public GooidServerSay mergeChunks(ErpChunk chunk) {
GooidServerSay ret = new GooidServerSay();
try {
// 如果合并后的路径不存在,则新建
if (!Files.isWritable(Paths.get(uploadFolder + LoginController.getUser().getUsername() + "/"))) {
Files.createDirectories(Paths.get(uploadFolder + LoginController.getUser().getUsername() + "/"));
}
// 合并的文件名
String target = uploadFolder + LoginController.getUser().getUsername() + "/" + File.separator
+ chunk.getIdentifier()
+ (chunk.getFilename().lastIndexOf(".") > 0
? chunk.getFilename().substring(chunk.getFilename().lastIndexOf("."))
: "");
File file = new File(target);
if (!file.exists()) {
// 创建文件
Files.createFile(Paths.get(target));
// 遍历分块的文件夹,并进行过滤和排序后以追加的方式写入到合并后的文件
Files.list(Paths.get(uploadFolder + LoginController.getUser().getUsername() + "/" + File.separator
+ chunk.getIdentifier()))
// 过滤带有"-"的文件
.filter(path -> path.getFileName().toString().contains("-"))
// 按照从小到大进行排序
.sorted((o1, o2) -> {
String p1 = o1.getFileName().toString();
String p2 = o2.getFileName().toString();
int i1 = p1.lastIndexOf("-");
int i2 = p2.lastIndexOf("-");
return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
}).forEach(path -> {
try {
// 以追加的形式写入文件
Files.write(Paths.get(target), Files.readAllBytes(path), StandardOpenOption.APPEND);
// 合并后删除该块
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
});
File file1 = new File(uploadFolder + LoginController.getUser().getUsername() + "/" + File.separator
+ chunk.getIdentifier());
if (!file1.exists()) {
file1.delete();
}
}
ErpFiles files = new ErpFiles();
files.setFileName(chunk.getFilename());
files.setCreationTime(new Date());
files.setFileMd5(chunk.getIdentifier());
files.setFileSize(chunk.getTotalSize());
if (chunk.getFilename().lastIndexOf(".") > 0) {
files.setFileType(chunk.getFilename().substring(chunk.getFilename().lastIndexOf(".")));
}
files.setFileUrl("/erp/swagger/uploadFiles1/" + LoginController.getUser().getUsername() + "/"
+ chunk.getIdentifier() + chunk.getType());
files.setUserName(LoginController.getUser().getUsername());
filesService.save(files);
Map<String, String> result = new HashMap<String, String>();
result.put("fileName", chunk.getFilename());
result.put("fileUrl", files.getFileUrl());
result.put("fileSize", chunk.getTotalSize() + "");
ret.setResult(result);
} catch (IOException e) {
e.printStackTrace();
ret.setCode("error");
ret.setMsg("操作失败\n\n详细信息:" + e.getMessage());
}
return ret;
}
}