前言
在开发web应用时,文件上传是一个常见需求。然而,当用户需要上传大文件或相同文件多次时,会造成带宽浪费和服务器存储冗余。此时可以使用文件秒传技术通过识别重复文件,实现瞬间完成上传的效果,大大提升了用户体验和系统效率。
文件秒传原理
文件秒传的核心原理是:
- 计算文件唯一标识(通常是md5或sha256值)
- 上传前先检查服务器是否已存在相同标识的文件
- 若存在,则直接引用已有文件,无需再次上传
- 若不存在,则执行常规上传流程
这种方式能显著减少网络传输和避免存储冗余。
代码实现
1. 创建项目基础结构
首先创建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. 创建上传存储代码
此处使用一个简单的集合来存储文件信息,实际使用需要替换为数据库或其他持久化中间件。
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()); } }
定义一个简单的文件信息实体类:
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类。
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控制器
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上传页面:
<!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
中添加必要配置
server: port: 8080 spring: servlet: multipart: max-file-size: 100mb max-request-size: 100mb
效果
第一次上传
第二次上传
到此这篇关于基于springboot实现文件秒传功能的文章就介绍到这了,更多相关springboot文件秒传内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论