MInIO 文件分片上传
项目传送门
1、前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="text/javascript" src="/js/jquery.js" th:src="@{/js/jquery.js}"></script>
<script type="text/javascript" src="/js/spark-md5.min.js" th:src="@{/js/spark-md5.min.js}"></script>
<input type="file" name="file" id="file">
<script>
const baseUrl = "http://localhost:18002";
function calculateFileMd5(file, chunkSize) {
return new Promise((resolve, reject) => {
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
let chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
let spark = new SparkMD5.ArrayBuffer();
let fileReader = new FileReader();
fileReader.onload = function (e) {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
let md5 = spark.end();
resolve(md5);
}
};
fileReader.onerror = function (e) {
reject(e);
};
function loadNext() {
let start = currentChunk * chunkSize;
let end = start + chunkSize;
if (end > file.size) {
end = file.size;
}
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
}
function calculateFileMd5ByDefaultChunkSize(file) {
return calculateFileMd5(file, 2097152);
}
function getFileType(fileName) {
return fileName.substr(fileName.lastIndexOf(".") + 1).toLowerCase();
}
document.getElementById("file").addEventListener("change", function () {
let file = this.files[0];
calculateFileMd5ByDefaultChunkSize(file).then(e => {
let md5 = e;
checkMd5(md5, file)
}).catch(e => {
console.error(e);
});
});
function checkMd5(md5, file) {
$.ajax({
url: baseUrl + "/file/check",
type: "GET",
data: {
md5: md5
},
async: true,
dataType: "json",
success: function (msg) {
console.log(msg);
if (msg.status === 20000) {
console.log("文件已经存在了,无需上传")
} else if (msg.status === 40004) {
console.log("文件不存在需要上传")
PostFile(file, 0, md5);
} else {
console.log('未知错误');
}
}
})
}
function PostFile(file, i, md5) {
let name = file.name,
size = file.size,
shardSize = 5 * 1024 * 1024,
shardCount = Math.ceil(size / shardSize);
if (i >= shardCount) {
return;
}
let start = i * shardSize;
let end = start + shardSize;
let packet = file.slice(start, end);
let form = new FormData();
form.append("md5", md5);
form.append("data", packet);
form.append("name", name);
form.append("totalSize", size);
form.append("total", shardCount);
form.append("index", i + 1);
$.ajax({
url: baseUrl + "/file/upload",
type: "POST",
data: form,
async: true,
dataType: "json",
processData: false,
contentType: false,
success: function (msg) {
console.log(msg);
if (msg.status === 20001) {
form = '';
i++;
PostFile(file, i, md5);
} else if (msg.status === 50000) {
form = '';
setInterval(function () {
PostFile(file, i, md5)
}, 2000);
} else if (msg.status === 20002) {
merge(shardCount, name, md5, getFileType(file.name), file.size)
console.log("上传成功");
} else {
console.log('未知错误');
}
}
})
}
function merge(shardCount, fileName, md5, fileType, fileSize) {
$.ajax({
url: baseUrl + "/file/merge",
type: "GET",
data: {
shardCount: shardCount,
fileName: fileName,
md5: md5,
fileType: fileType,
fileSize: fileSize
},
async: true,
dataType: "json",
success: function (msg) {
console.log(msg);
}
})
}
</script>
</body>
</html>
2、文件md5校验
@GetMapping(value = "/check")
public Map<String, Object> checkFileExists(String md5) {
Map<String, Object> resultMap = new HashMap<>();
if (ObjectUtils.isEmpty(md5)) {
resultMap.put("status", StatusCode.PARAM_ERROR.getCode());
return resultMap;
}
String url = (String) jsonRedisTemplate.boundHashOps(MD5_KEY).get(md5);
if (ObjectUtils.isEmpty(url)) {
resultMap.put("status", StatusCode.NOT_FOUND.getCode());
return resultMap;
}
resultMap.put("status", StatusCode.SUCCESS.getCode());
resultMap.put("url", url);
return resultMap;
}
3、文件分片上传
@PostMapping(value = "/upload")
public Map<String, Object> upload(HttpServletRequest req) {
Map<String, Object> map = new HashMap<>();
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;
MultipartFile file = multipartRequest.getFile("data");
if (file == null) {
map.put("status", StatusCode.FAILURE.getCode());
return map;
}
int index = Integer.parseInt(multipartRequest.getParameter("index"));
int total = Integer.parseInt(multipartRequest.getParameter("total"));
String fileName = multipartRequest.getParameter("name");
String md5 = multipartRequest.getParameter("md5");
minioTemplate.makeBucket(md5);
String objectName = String.valueOf(index);
log.info("index: {}, total:{}, fileName:{}, md5:{}, objectName:{}", index, total, fileName, md5, objectName);
if (index < total) {
try {
OssFile ossFile = minioTemplate.putChunkObject(file.getInputStream(), md5, objectName);
log.info("{} upload success {}", objectName, ossFile);
map.put("status", StatusCode.ALONE_CHUNK_UPLOAD_SUCCESS.getCode());
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("status", StatusCode.FAILURE.getCode());
return map;
}
} else {
try {
minioTemplate.putChunkObject(file.getInputStream(), md5, objectName);
map.put("status", StatusCode.ALL_CHUNK_UPLOAD_SUCCESS.getCode());
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("status", StatusCode.FAILURE.getCode());
return map;
}
}
}
4、文件合并
@GetMapping(value = "/merge")
public Map<String, Object> merge(Integer shardCount, String fileName, String md5, String fileType,
Long fileSize) {
Map<String, Object> retMap = new HashMap<>();
try {
List<String> objectNameList = minioTemplate.listObjectNames(md5);
if (shardCount != objectNameList.size()) {
retMap.put("status", StatusCode.FAILURE.getCode());
} else {
String targetBucketName =