抖云科技

Good Luck To You!

Spring Boot 实现文件秒传功能

前言

在开发Web应用时,文件上传是一个常见需求。然而,当用户需要上传大文件或相同文件多次时,会造成带宽浪费和服务器存储冗余。此时可以使用文件秒传技术通过识别重复文件,实现瞬间完成上传的效果,大大提升了用户体验和系统效率。

文件秒传原理

文件秒传的核心原理是:

  1. 计算文件唯一标识(通常是MD5或SHA256值)

  2. 上传前先检查服务器是否已存在相同标识的文件

  3. 若存在,则直接引用已有文件,无需再次上传

  4. 若不存在,则执行常规上传流程

这种方式能显著减少网络传输避免存储冗余

代码实现

1. 创建项目基础结构

首先创建Spring Boot项目,添加必要依赖:

xml代码解读复制代码<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. 创建上传存储代码

此处使用一个简单的集合来存储文件信息,实际使用需要替换为数据库或其他持久化中间件。

arduino代码解读复制代码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为文件信息(实际使用时可替换为数据库存储)     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); // 实际使用时插入数据库         return fileInfo;     }       /**      * 计算文件MD5      */     public String calculateMD5(MultipartFile file) throws IOException {         return DigestUtil.md5Hex(file.getInputStream());     } }

定义一个简单的文件信息实体类:

typescript代码解读复制代码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. 创建Result类

为了统一返回结果格式,可以创建一个简单的Result类。

typescript代码解读复制代码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. 创建Controller控制器

kotlin代码解读复制代码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,"文件秒传成功");             }               // 文件不存在,执行上传             String originalFilename = file.getOriginalFilename();             String filePath = FileUtil.getTmpDir() + File.separator + originalFilename; // 保存到临时目录               // 存储文件             file.transferTo(new File(filePath));               // 保存文件信息到内存(实际使用时应替换为数据库)             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. 创建纯HTML前端页面

创建一个简单的HTML上传页面:

xml代码解读复制代码<!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) {                         // 文件已存在,执行秒传                         document.getElementById('result').innerText = '文件秒传成功!';                         document.getElementById('progress').innerText = '100%';                         return Promise.resolve();                     } else {                         // 文件不存在,执行上传                         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中添加必要配置

yaml代码解读复制代码server:   port: 8080  spring:   servlet:     multipart:       max-file-size: 100MB       max-request-size: 100MB


作者:风象南
链接:https://juejin.cn/post/7487859784231141417
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言

    Powered By Z-BlogPHP 1.7.3

    Copyright Your WebSite.Some Rights Reserved.