背景
在上传大型文件时,一般采用的都是分片、断点续传等技术,这样不会导致因文件过大而造成系统超时或者过压等情况。
接下来开始教学。
一、实际效果图
整个前端网页的效果图:
二、后端代码
1.开发环境
开发工具:idea ,开发语言:java ,框架:springboot
2.创建文件上传工具类
创建工具类:FileUtils
import com.caozhen.common.constant.HttpStatusConstant;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
/**
* @author caozhen
* @ClassName FileUtils
* @description: 文件上传
* @date 2023年10月07日
* @version: 1.0
*/
@Component
public class FileUtils {
//文件路径,可以统一配置
private static final String UPLOAD_DIR = "d:\\uploads";
/***
* 断点续传(上传文件)
* @param file
* @return
*/
public ResultAjax resumeFile(MultipartFile file,
int chunkNumber,
int totalChunks,
String identifier) {
try {
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
String fileName = identifier + "-chunk-" + chunkNumber;
File chunkFile = new File(uploadDir, fileName);
try (InputStream inputStream = file.getInputStream();
FileOutputStream outputStream = new FileOutputStream(chunkFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
if (chunkNumber == totalChunks - 1) {
File finalFile = new File(UPLOAD_DIR + File.separator + identifier);
for (int i = 0; i < totalChunks; i++) {
File part = new File(uploadDir, identifier + "-chunk-" + i);
try (FileInputStream partStream = new FileInputStream(part);
FileOutputStream finalStream = new FileOutputStream(finalFile, true)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = partStream.read(buffer)) != -1) {
finalStream.write(buffer, 0, bytesRead);
}
}
part.delete();
}
uploadDir.delete();
}
return ResultAjax.ok("Chunk " + chunkNumber + " uploaded");
} catch (IOException e) {
e.printStackTrace();
return ResultAjax.error(HttpStatusConstant.HTTP_CODE_500, "上传失败:" + e.getMessage());
}
}
}
3.创建controller类
@RestController
@Slf4j
@Api(tags = "系统服务")
@RequestMapping("/sys/test/")
public class SystemController {
@Autowired
private FileUtils fileUtils;
@ApiOperation("测试文件断点续传-上传")
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResultAjax fileUploads(@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("identifier") String identifier) {
return fileUtils.resumeFile(file,chunkNumber,totalChunks,identifier);
}
}
4.自定义封装类ResultAjax
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author caozhen
* @ClassName ResultAjax
* @description: 全局封装统一返回实体类
* @date 2023年07月26日
* @version: 1.0
*/
public class ResultAjax extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 1L;
public ResultAjax() {
put("success", true);
put("code", HttpStatusConstant.HTTP_CODE_200);
put("msg", "操作成功!");
put("data", null);
}
public ResultAjax(Integer code) {
put("success", true);
put("code", code);
put("msg", "操作成功!");
put("data", null);
}
public ResultAjax(Integer code, String msg) {
put("success", true);
put("code", code);
put("msg", msg);
put("data", null);
}
public static ResultAjax error() {
return error(HttpStatusConstant.HTTP_CODE_500, "未知异常,请联系管理员");
}
public static ResultAjax errorDebug(String message) {
return error(HttpStatusConstant.HTTP_CODE_500, "未知异常 " + message + ",请联系管理员");
}
public static ResultAjax error(String msg) {
return error(HttpStatusConstant.HTTP_CODE_500, msg);
}
public static ResultAjax error(int code, String msg) {
ResultAjax r = new ResultAjax();
r.put("success", false);
r.put("code", code);
r.put("msg", msg);
return r;
}
public ResultAjax errorInfo(String msg) {
this.put("errorMsg", msg);
return this;
}
public static ResultAjax ok(Object data) {
ResultAjax r = new ResultAjax();
r.put("success", true);
r.put("code", HttpStatusConstant.HTTP_CODE_200);
r.put("msg", "操作成功!");
r.put("data", data);
return r;
}
public static ResultAjax ok(Map<String, Object> map) {
ResultAjax r = new ResultAjax();
r.putAll(map);
r.put("data", null);
return r;
}
public static ResultAjax ok(String msg, Integer code) {
ResultAjax r = new ResultAjax();
r.put("success", true);
r.put("code", code);
r.put("msg", msg);
r.put("data", null);
return r;
}
public static ResultAjax ok() {
return new ResultAjax().put("msg", "操作成功!").put("data", null);
}
public static ResultAjax ok(Integer size) {
return new ResultAjax().put("data", null);
}
@Override
public ResultAjax put(String key, Object value) {
super.put(key, value);
return this;
}
/**
* 添加返回结果数据
*
* @param key
* @param value
* @return
*/
public ResultAjax putData(String key, Object value) {
Map<String, Object> map = (HashMap<String, Object>) this.get("data");
map.put(key, value);
return this;
}
}
public class HttpStatusConstant {
//目前常用的,200,500
public final static Integer HTTP_CODE_200 = 200;
public final static Integer HTTP_CODE_500 = 500;
//拦截
public final static Integer HTTP_CODE_403 = 403;
public final static Integer HTTP_CODE_401 = 401;
}
三、前端代码
<!DOCTYPE html>
<html>
<head>
<title>文件分片上传</title>
</head>
<body>
<input type="file" id="fileInput">
<button id="startUpload">开始上传</button>
<button id="pauseResumeUpload">暂停上传</button>
<progress id="progress" max="100" value="0"></progress>
<span id="progressText">0%</span>
<script>
const fileInput = document.getElementById('fileInput');
const startUpload = document.getElementById('startUpload');
const pauseResumeUpload = document.getElementById('pauseResumeUpload');
const progress = document.getElementById('progress');
const progressText = document.getElementById('progressText');
let file;
let uploading = false;
let currentChunk = 0;
let uploadPaused = false;
let xhr;
fileInput.addEventListener('change', (event) => {
file = event.target.files[0];
});
async function uploadFile() {
const chunkSize = 1 * 1024 * 1024; // 1MB
const totalChunks = Math.ceil(file.size / chunkSize);
for (currentChunk; currentChunk < totalChunks; currentChunk++) {
if (uploadPaused) {
break;
}
const startByte = currentChunk * chunkSize;
const endByte = Math.min(startByte + chunkSize, file.size);
const chunk = file.slice(startByte, endByte);
const formData = new FormData();
formData.append('file', chunk);
formData.append('chunkNumber', currentChunk);
formData.append('totalChunks', totalChunks);
formData.append('identifier', file.name);
const response = await fetch('http://localhost:8000/system/sys/test/upload', {
method: 'POST',
body: formData,
});
if (response.status !== 200) {
console.error('Error uploading chunk: ', response.statusText);
break;
}
const percent = ((currentChunk + 1) / totalChunks) * 100;
progress.value = percent;
progressText.textContent = `${
Math.round(percent)}%`;
}
if (currentChunk >= totalChunks) {
alert('文件上传成功!');
uploading = false;
currentChunk = 0;
progress.value = 100;
progressText.textContent = '100%';
}
}
startUpload.addEventListener('click', () => {
if (file && !uploading) {
uploading = true;
uploadFile();
}
});
pauseResumeUpload.addEventListener('click', () => {
if (uploading) {
if (!uploadPaused) {
uploadPaused = true;
pauseResumeUpload.textContent = '继续上传';
} else {
uploadPaused = false;
pauseResumeUpload.textContent = '暂停上传';
uploadFile();
}
}
});
</script>
</body>
</html>