新开神途手游发布网站网络营销的四个特点
前言
在开发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);}// Getterspublic 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);@Autowiredprivate 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中...';// 计算文件MD5calculateMD5(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;});}// 计算文件MD5function calculateMD5(file) {return new Promise((resolve, reject) => {const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;const chunkSize = 2097152; // 2MBconst 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: 8080spring:servlet:multipart:max-file-size: 100MBmax-request-size: 100MB