💥💥✈️✈️欢迎阅读本文章❤️❤️💥💥
🏆本篇文章阅读大约耗时五分钟。
⛳️motto:不积跬步、无以千里
📋📋📋本文目录如下:🎁🎁🎁
目录
前言
简述
实现
测试
章末
前言
小伙伴们大家好,上周闲暇之余在本地测试部署了几个开源项目,没什么大问题,开源项目是真的很开源,项目报告论文一并齐全!文章链接如下:
【项目部署】✈️普通 SpringBoot 项目拿到源码后怎么在本地部署启动
【项目部署】⭐️基于 SpringBoot 的医护人员排版系统的部署
这篇文章接着了解下开发中常见的需求,文件上传功能,允许系统的用户将本地文件通过网络传输到系统服务器上(当然,也可以不直接存储在服务器,现在有很多对象存储服务,比如 阿里的 oss, 华为的 obs 服务,都可以存储文件,使用也很方便),但是如果是特别大的文件并且不进行控制,可能会对服务器造成一系列影响,常见的影响有很多:增加服务器负担,导致服务器响应缓慢等;针对以上情况,现在有很多处理方式,比如分片上传方案
简述
何为分片?简单讲就是将大文件分成多个较小的文件,然后依次或者并行将这些上传到服务器的过程;等所有的分片文件上传完毕之后,服务器可以将其合并为原始文件
另外可以调研下分片过程中出现异常导致部分文件上传失败,需要怎么处理,感觉可以根据分片的序列号记录,找到未上传成功的那部分,再重试上传,这样也可以避免传统整个文件上传失败导致全部要重新上传(有时间再去调研下吧,先这样了)
实现
正如上面所讲,分片需要前端处理好文件的分割,后端提供相应的上传接口,所以本地就简单模拟下操作页面(不要在意页面的简陋哈)
1、前端页面(uploadBigFile.html)
只有三个按钮,选择文件可以自定义选择要上传的文件
上传文件这里会将文件按照指定大小分割文件并调用上传接口
合并文件会触发后端服务将对应上传完成的分片文件合并为原始文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
<style>
body {
font-family: Arial, sans-serif;
}
h2 {
color: #333;
}
input, button {
margin: 10px 0;
}
</style>
</head>
<body>
<div id="app">
<h2>文件上传</h2>
<!-- 文件选择 -->
<input type="file" id="fileInput" />
<br><br>
<!-- 上传按钮 -->
<button id="uploadBtn">上传文件</button>
<br><br>
<!-- 合并文件按钮 -->
<button id="mergeBtn">合并文件</button>
</div>
<script>
document.getElementById('uploadBtn').addEventListener('click', uploadFile);
document.getElementById('mergeBtn').addEventListener('click', mergeFile);
let fileName = ''; // 用于存储文件名
// 上传文件(分块上传)
function uploadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert('请选择一个文件');
return;
}
fileName = file.name;
uploadFileInChunks(file);
}
// 将文件分块上传
function uploadFileInChunks(file) {
const chunkSize = 1024 * 1024 * 2; // 每个块的大小是 2MB
let start = 0;
let chunkIndex = 0;
while (start < file.size) {
const chunk = file.slice(start, start + chunkSize);
console.log(`上传第 ${chunkIndex} 块`);
uploadChunk(chunk, chunkIndex);
start += chunkSize;
chunkIndex++;
}
}
// 上传每一块文件
function uploadChunk(chunk, chunkIndex) {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', chunkIndex);
formData.append('fileName', fileName);
fetch('http://localhost:8890/upload-chunk', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('上传失败:', error);
});
}
// 合并文件
function mergeFile() {
const formData = new FormData();
formData.append('fileName', fileName);
fetch('http://localhost:8890/merge-chunks', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log('文件合并成功', data);
})
.catch(error => {
console.error('合并失败:', error);
});
}
</script>
</body>
</html>
2、后端接口
后端接口是直接将文件存储在本地指定文件夹下的
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
/**
* @author HuangBenben
*/
@RestController
public class ChunkController {
// 临时文件存储目录
private static final String TEMP_DIR = "C:\\Data\\extFiles\\temp\\";
// 合并后文件存储目录
private static final String TARGET_DIR = "C:\\Data\\extFiles\\final\\";
/**
* 处理文件分块上传
*
* @param chunk 文件块
* @param chunkIndex 当前块的索引
* @param fileName 文件名
* @return 返回上传状态信息
* @throws IOException 如果发生IO异常
*/
@PostMapping("/upload-chunk")
public ResponseEntity<String> uploadChunk(
@RequestParam("chunk") MultipartFile chunk,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("fileName") String fileName) throws IOException {
// 创建目录结构,如果不存在则创建
File dir = new File(TEMP_DIR + fileName);
if (!dir.exists()) {
dir.mkdirs();
}
// 创建当前分块的文件
File chunkFile = new File(dir, "chunk_" + chunkIndex);
// 写入文件分块数据
try (OutputStream os = new FileOutputStream(chunkFile)) {
os.write(chunk.getBytes());
}
// 返回成功消息
return ResponseEntity.ok("Chunk " + chunkIndex + " uploaded successfully.");
}
/**
* 合并文件分块
*
* @param fileName 文件名
* @return 合并完成的响应
* @throws IOException 如果发生IO异常
*/
@PostMapping("/merge-chunks")
public ResponseEntity<String> mergeChunks(@RequestParam("fileName") String fileName) throws IOException {
// 获取存储文件块的临时目录
File dir = new File(TEMP_DIR + fileName);
// 创建合并后的目标文件
File mergedFile = new File(TARGET_DIR + fileName);
// 合并文件块
try (OutputStream os = new FileOutputStream(mergedFile)) {
// 遍历目录中的所有文件块并合并
for (int i = 0, len = dir.listFiles().length; i < len; i++) {
File chunkFile = new File(dir, "chunk_" + i);
// 将文件块写入合并文件中
Files.copy(chunkFile.toPath(), os);
// 删除已经处理过的文件块
chunkFile.delete();
}
}
// 删除临时目录(所有文件块处理完后)
dir.delete();
// 返回合并完成的响应
return ResponseEntity.ok("文件合并完成");
}
}
3、配置调整
按照 SpringBoot 的默认配置,单次请求的文件上限是 1MB,如下,不更改配置的应该会遇到这个异常
Resolved [org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field chunk exceeds its maximum permitted size of 1048576 bytes.]
可以在配置中调整下上限,如下:
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
测试
简单看下,启动项目后先访问到静态资源,然后选择一个要上传的文件(本地上传的是一个 5 MB 左右的文档)
上传文件
每片文件大小 2MB ,分了三片上传,文件分割没问题,接口相应成功,去对应的文件夹下看看是否存储成功,检查下对应的是否有三个文件
合并文件
检查下接口响应正常,看下是否有原始文件,并且内容是否和源文件是否一致
ok 测试没问题,并且在合并文件之后,对应文件的分片文件也已经删除了
章末
文章到这里就结束了~
往期推荐 > > >
【接口负载】✈️整合 Resilience4j 指定接口负载,避免过载
【SpringBoot】⭐️整合 Redis 实现百万级数据实时排序
【SpringBoot】✈️本地集成支付宝支付功能