vue+springboot上傳大文件的實現(xiàn)示例
前言
眾所周知,上傳大文件是一件很麻煩的事情,假如一條路走到黑,直接一次性把文件上傳上去,對于小文件是可以這樣做,但是對于大文件可能會出現(xiàn)網(wǎng)絡問題,請求響應時長等等導致文件上傳失敗,那么這次來教大家如何用vue+srpingboot項目上傳大文件
邏輯
需要做大文件上傳應該考慮到如下邏輯:
- 大文件上傳一般需要將文件切片(chunk)上傳,然后再將所有切片合并為完整的文件??梢园匆韵逻壿嬤M行實現(xiàn):
- 前端在頁面中選擇要上傳的文件,并使用Blob.slice方法對文件進行切片,一般每個切片大小為固定值(比如5MB),并記錄總共有多少個切片。
- 將切片分別上傳到后端服務,可以使用XMLHttpRequest或Axios等庫發(fā)送Ajax請求。對于每個切片,需要包含三個參數(shù):當前切片索引(從0開始)、切片總數(shù)、切片文件數(shù)據(jù)。
- 后端服務接收到切片后,保存到指定路徑下的臨時文件中,并記錄已上傳的切片索引和上傳狀態(tài)。如果某個切片上傳失敗,則通知前端重傳該切片。
- 當所有切片都上傳成功后,后端服務讀取所有切片內容并將其合并為完整的文件??梢允褂胘ava.io.SequenceInputStream和BufferedOutputStream來實現(xiàn)文件合并。
- 最后返回文件上傳成功的響應結果給前端即可。
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Upload</title>
</head>
<body>
<input type="file" id="fileInput">
<button onclick="upload()">Upload</button>
<script>
function upload() {
let file = document.getElementById("fileInput").files[0];
let chunkSize = 5 * 1024 * 1024; // 切片大小為5MB
let totalChunks = Math.ceil(file.size / chunkSize); // 計算切片總數(shù)
let index = 0;
while (index < totalChunks) {
let chunk = file.slice(index * chunkSize, (index + 1) * chunkSize);
let formData = new FormData();
formData.append("file", chunk);
formData.append("index", index);
formData.append("totalChunks", totalChunks);
// 發(fā)送Ajax請求上傳切片
$.ajax({
url: "/uploadChunk",
type: "POST",
data: formData,
processData: false,
contentType: false,
success: function () {
if (++index >= totalChunks) {
// 所有切片上傳完成,通知服務端合并文件
$.post("/mergeFile", {fileName: file.name}, function () {
alert("Upload complete!");
})
}
}
});
}
}
</script>
</body>
</html>后端
controller層:
@RestController
public class FileController {
@Value("${file.upload-path}")
private String uploadPath;
@PostMapping("/uploadChunk")
public void uploadChunk(@RequestParam("file") MultipartFile file,
@RequestParam("index") int index,
@RequestParam("totalChunks") int totalChunks) throws IOException {
// 以文件名+切片索引號為文件名保存切片文件
String fileName = file.getOriginalFilename() + "." + index;
Path tempFile = Paths.get(uploadPath, fileName);
Files.write(tempFile, file.getBytes());
// 記錄上傳狀態(tài)
String uploadFlag = UUID.randomUUID().toString();
redisTemplate.opsForList().set("upload:" + fileName, index, uploadFlag);
// 如果所有切片已上傳,則通知合并文件
if (isAllChunksUploaded(fileName, totalChunks)) {
sendMergeRequest(fileName, totalChunks);
}
}
@PostMapping("/mergeFile")
public void mergeFile(String fileName) throws IOException {
// 所有切片均已成功上傳,進行文件合并
List<File> chunkFiles = new ArrayList<>();
for (int i = 0; i < getTotalChunks(fileName); i++) {
String chunkFileName = fileName + "." + i;
Path tempFile = Paths.get(uploadPath, chunkFileName);
chunkFiles.add(tempFile.toFile());
}
Path destFile = Paths.get(uploadPath, fileName);
try (OutputStream out = Files.newOutputStream(destFile);
SequenceInputStream seqIn = new SequenceInputStream(Collections.enumeration(chunkFiles));
BufferedInputStream bufIn = new BufferedInputStream(seqIn)) {
byte[] buffer = new byte[1024];
int len;
while ((len = bufIn.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
// 清理臨時文件和上傳狀態(tài)記錄
for (int i = 0; i < getTotalChunks(fileName); i++) {
String chunkFileName = fileName + "." + i;
Path tempFile = Paths.get(uploadPath, chunkFileName);
Files.deleteIfExists(tempFile);
redisTemplate.delete("upload:" + chunkFileName);
}
}
private int getTotalChunks(String fileName) {
// 根據(jù)文件名獲取總切片數(shù)
return Objects.requireNonNull(Paths.get(uploadPath, fileName).toFile().listFiles()).length;
}
private boolean isAllChunksUploaded(String fileName, int totalChunks) {
// 判斷所有切片是否已都上傳完成
List<String> uploadFlags = redisTemplate.opsForList().range("upload:" + fileName, 0, -1);
return uploadFlags != null && uploadFlags.size() == totalChunks;
}
private void sendMergeRequest(String fileName, int totalChunks) {
// 發(fā)送合并文件請求
new Thread(() -> {
try {
URL url = new URL("http://localhost:8080/mergeFile");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
OutputStream out = conn.getOutputStream();
String query = "fileName=" + fileName;
out.write(query.getBytes());
out.flush();
out.close();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
while (br.readLine() != null) ;
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
@Autowired
private RedisTemplate<String, Object> redisTemplate;
}
其中,file.upload-path為文件上傳的保存路徑,可以在application.properties或application.yml中進行配置。同時需要添加RedisTemplate的Bean以便記錄上傳狀態(tài)。
RedisTemplate配置
如果需要使用RedisTemplate,需要引入下方的包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
同時在yml配置redis的信息
spring.redis.host=localhost spring.redis.port=6379 spring.redis.database=0
然后在自己的類中這樣使用
@Component
public class myClass {
? ? @Autowired
? ? private RedisTemplate<String, Object> redisTemplate;
? ? public void set(String key, Object value) {
? ? ? ? redisTemplate.opsForValue().set(key, value);
? ? }
? ? public Object get(String key) {
? ? ? ? return redisTemplate.opsForValue().get(key);
? ? }
}注意事項
- 需要控制每次上傳的切片大小,以兼顧上傳速度和穩(wěn)定性,避免占用過多服務器資源或因網(wǎng)絡不穩(wěn)定而導致上傳失敗。
- 切片上傳存在先后順序,需要保證所有切片都上傳完成后再進行合并,否則可能會出現(xiàn)文件不完整或者文件合并錯誤等情況。
- 上傳完成后需要及時清理臨時文件,避免因為占用過多磁盤空間而導致服務器崩潰??梢栽O置一個定期任務來清理過期的臨時文件。
結語
到此這篇關于vue+springboot上傳大文件的實現(xiàn)示例的文章就介紹到這了,更多相關vue springboot上傳大文件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue刷新頁面后params參數(shù)丟失的原因和解決方法
這篇文章主要給大家介紹了vue刷新頁面后params參數(shù)丟失的原因和解決方法,文中通過代碼示例給大家介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2023-12-12
Vue瀏覽器鏈接與接口參數(shù)實現(xiàn)加密過程詳解
這篇文章主要介紹了Vue瀏覽器鏈接與接口參數(shù)實現(xiàn)加密過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-12-12
Vue使用Echarts實現(xiàn)大屏可視化布局示例詳細講解
這篇文章主要介紹了Vue使用Echarts實現(xiàn)大屏可視化布局示例,本文通過實例代碼圖文相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01
vue之a-table中實現(xiàn)清空選中的數(shù)據(jù)
今天小編就為大家分享一篇vue之a-table中實現(xiàn)清空選中的數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11
Element InputNumber 計數(shù)器的實現(xiàn)示例
這篇文章主要介紹了Element InputNumber 計數(shù)器的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08
Vue指令v-for遍歷輸出JavaScript數(shù)組及json對象的常見方式小結
這篇文章主要介紹了Vue指令v-for遍歷輸出JavaScript數(shù)組及json對象的常見方式,結合實例形式總結分析了vue.js使用v-for指令遍歷輸出js數(shù)組與json對象的常見操作技巧,需要的朋友可以參考下2019-02-02

