基于SpringBoot實現(xiàn)文件秒傳功能
前言
在開發(fā)Web應(yīng)用時,文件上傳是一個常見需求。然而,當(dāng)用戶需要上傳大文件或相同文件多次時,會造成帶寬浪費和服務(wù)器存儲冗余。此時可以使用文件秒傳技術(shù)通過識別重復(fù)文件,實現(xiàn)瞬間完成上傳的效果,大大提升了用戶體驗和系統(tǒng)效率。
文件秒傳原理
文件秒傳的核心原理是:
- 計算文件唯一標識(通常是MD5或SHA256值)
- 上傳前先檢查服務(wù)器是否已存在相同標識的文件
- 若存在,則直接引用已有文件,無需再次上傳
- 若不存在,則執(zhí)行常規(guī)上傳流程
這種方式能顯著減少網(wǎng)絡(luò)傳輸和避免存儲冗余。
代碼實現(xiàn)
1. 創(chuàng)建項目基礎(chǔ)結(jié)構(gòu)
首先創(chuàng)建Spring Boot項目,添加必要依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.18</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.1</version> </dependency> </dependencies>
2. 創(chuàng)建上傳存儲代碼
此處使用一個簡單的集合來存儲文件信息,實際使用需要替換為數(shù)據(jù)庫或其他持久化中間件。
import cn.hutool.crypto.digest.DigestUtil; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Service public class FileService { // 使用Map存儲文件信息,key為MD5,value為文件信息(實際使用時可替換為數(shù)據(jù)庫存儲) private final Map<String, FileInfo> fileStore = new ConcurrentHashMap<>(); /** * 檢查文件是否已存在 */ public FileInfo findByMd5(String md5) { return fileStore.get(md5); } /** * 保存文件信息 */ public FileInfo saveFile(String fileName, String fileMd5, Long fileSize, String filePath) { FileInfo fileInfo = new FileInfo(fileName, fileMd5, fileSize, filePath); fileStore.put(fileMd5, fileInfo); // 實際使用時插入數(shù)據(jù)庫 return fileInfo; } /** * 計算文件MD5 */ public String calculateMD5(MultipartFile file) throws IOException { return DigestUtil.md5Hex(file.getInputStream()); } }
定義一個簡單的文件信息實體類:
import cn.hutool.core.util.IdUtil; public class FileInfo { private String id = IdUtil.fastUUID(); private String fileName; private String fileMd5; private Long fileSize; private String filePath; public FileInfo(String fileName, String fileMd5, Long fileSize, String filePath) { this.fileName = fileName; this.fileMd5 = fileMd5; this.fileSize = fileSize; this.filePath = filePath; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getFileMd5() { return fileMd5; } public void setFileMd5(String fileMd5) { this.fileMd5 = fileMd5; } public Long getFileSize() { return fileSize; } public void setFileSize(Long fileSize) { this.fileSize = fileSize; } public String getFilePath() { return filePath; } public void setFilePath(String filePath) { this.filePath = filePath; } }
3. 創(chuàng)建Result類
為了統(tǒng)一返回結(jié)果格式,可以創(chuàng)建一個簡單的Result類。
public class Result { private boolean success; private Object data; private String message; public Result(boolean success, Object data, String message) { this.success = success; this.data = data; this.message = message; } public static Result success(Object data) { return new Result(true, data,"success"); } public static Result success(Object data,String message) { return new Result(true, data,message); } public static Result error(String message) { return new Result(false, null, message); } // Getters public boolean isSuccess() { return success; } public Object getData() { return data; } public String getMessage() { return message; } }
4. 創(chuàng)建Controller控制器
import cn.hutool.core.io.FileUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.File; @RestController @RequestMapping("/api/file") public class FileController { private static Logger logger = LoggerFactory.getLogger(FileController.class); @Autowired private FileService fileService; /** * 檢查文件是否已存在 */ @PostMapping("/check") public Result checkFile(@RequestParam("md5") String md5) { FileInfo fileInfo = fileService.findByMd5(md5); if (fileInfo != null) { return Result.success(fileInfo); } return Result.success(null); } /** * 上傳文件 */ @PostMapping("/upload") public Result uploadFile(@RequestParam("file") MultipartFile file) { try { // 計算文件MD5值 String md5 = fileService.calculateMD5(file); // 檢查文件是否已存在 FileInfo existFile = fileService.findByMd5(md5); if (existFile != null) { // todo 進行自定義的邏輯處理 return Result.success(existFile,"文件秒傳成功"); } // 文件不存在,執(zhí)行上傳 String originalFilename = file.getOriginalFilename(); String filePath = FileUtil.getTmpDir() + File.separator + originalFilename; // 保存到臨時目錄 // 存儲文件 file.transferTo(new File(filePath)); // 保存文件信息到內(nèi)存(實際使用時應(yīng)替換為數(shù)據(jù)庫) FileInfo fileInfo = fileService.saveFile(originalFilename, md5, file.getSize(), filePath); return Result.success(fileInfo,"文件上傳成功"); } catch (Exception e) { logger.error(e.getMessage(),e); return Result.error("文件上傳失?。? + e.getMessage()); } } }
4. 創(chuàng)建純HTML前端頁面
創(chuàng)建一個簡單的HTML上傳頁面:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>文件秒傳示例</title> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/spark-md5@3.0.2/spark-md5.min.js"></script> </head> <body> <h2>文件上傳(支持秒傳)</h2> <input type="file" id="fileInput" /> <button onclick="uploadFile()">上傳文件</button> <div id="progressBar" style="display:none;"> <div>上傳進度:<span id="progress">0%</span></div> </div> <div id="result"></div> <script> function uploadFile() { const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; if (!file) { alert('請選擇文件'); return; } document.getElementById('progressBar').style.display = 'block'; document.getElementById('result').innerText = '計算文件MD5中...'; // 計算文件MD5 calculateMD5(file).then(md5 => { document.getElementById('result').innerText = '正在檢查文件是否已存在...'; // 檢查文件是否已存在 return axios.post('/api/file/check', { md5: md5 }).then(response => { if (response.data.data && response.data.data.id) { // 文件已存在,執(zhí)行秒傳 document.getElementById('result').innerText = '文件秒傳成功!'; document.getElementById('progress').innerText = '100%'; return Promise.resolve(); } else { // 文件不存在,執(zhí)行上傳 const formData = new FormData(); formData.append('file', file); return axios.post('/api/file/upload', formData, { onUploadProgress: progressEvent => { const percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); document.getElementById('progress').innerText = percentCompleted + '%'; } }).then(response => { document.getElementById('result').innerText = '文件上傳成功!'; }); } }); }).catch(error => { document.getElementById('result').innerText = '錯誤:' + error.message; }); } // 計算文件MD5 function calculateMD5(file) { return new Promise((resolve, reject) => { const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const chunkSize = 2097152; // 2MB const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const spark = new SparkMD5.ArrayBuffer(); const fileReader = new FileReader(); fileReader.onload = function(e) { spark.append(e.target.result); currentChunk++; if (currentChunk < chunks) { loadNext(); } else { resolve(spark.end()); } }; fileReader.onerror = function() { reject('文件讀取錯誤'); }; function loadNext() { const start = currentChunk * chunkSize; const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } loadNext(); }); } </script> </body> </html>
5. 配置文件
在application.yml
中添加必要配置
server: port: 8080 spring: servlet: multipart: max-file-size: 100MB max-request-size: 100MB
效果
第一次上傳
第二次上傳
到此這篇關(guān)于基于SpringBoot實現(xiàn)文件秒傳功能的文章就介紹到這了,更多相關(guān)SpringBoot文件秒傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Map 通過 key 或者 value 過濾的實例代碼
這篇文章主要介紹了Java Map 通過 key 或者 value 過濾的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06Java實現(xiàn)迷你圖書管理系統(tǒng)案例全程
這篇文章主要為大家詳細介紹了如何利用java語言實現(xiàn)簡單的圖書管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-12-12Java編程中利用InetAddress類確定特殊IP地址的方法
這篇文章主要介紹了Java編程中利用InetAddress類確定特殊IP地址的方法,InetAddress類是Java網(wǎng)絡(luò)編程中一個相當(dāng)實用的類,需要的朋友可以參考下2015-11-11Spring事務(wù)失效的一種原因關(guān)于this調(diào)用的問題
這篇文章主要介紹了Spring事務(wù)失效的一種原因關(guān)于this調(diào)用的問題,本文給大家分享問題原因及解決辦法,通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2021-10-10