背景
在上传大型文件时,一般采用的都是分片、断点续传等技术,这样不会导致因文件过大而造成系统超时或者过压等情况。
接下来我们进入教学
如果有帮助到您,麻烦请点击个收藏、赞,谢谢~
一、实际效果图
整个前端网页的效果图:
二、后端代码
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 {@Autowiredprivate 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);}@Overridepublic 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,500public 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; // 1MBconst 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>
四、接下来我们演示下
五、留言区
有什么问题可以在底下评论区留言,看到会在本文后追加回复!