文件上传功能几乎是每个 Web 应用不可或缺的一部分。无论是个人博客中的图片上传,还是企业级应用中的文档管理,文件上传都扮演着至关重要的角色。今天,松哥和大家来聊聊文件上传中的几个高级玩法——秒传、断点续传和分片上传。
一 文件上传的常见场景在日常开发中,文件上传的场景多种多样。比如,在线教育平台上的视频资源上传,社交平台上的图片分享,以及企业内部的知识文档管理等。这些场景对文件上传的要求也各不相同,有的追求速度,有的注重稳定性,还有的需要考虑文件大小和安全性。因此,针对不同需求,我们有了秒传、断点续传和分片上传等解决方案。
二 秒传、断点上传与分片上传秒传秒传,顾名思义,就是几乎瞬间完成文件上传的过程。其实现原理是通过计算文件的哈希值(如 MD5 或 SHA-1),然后将这个唯一的标识符发送给服务器。如果服务器上已经存在相同的文件,则直接返回成功信息,避免了重复上传。这种方式不仅节省了带宽,也大大提高了用户体验。
断点续传断点续传是指在网络不稳定或者用户主动中断上传后,能够从上次中断的地方继续上传,而不需要重新开始整个过程。这对于大文件上传尤为重要,因为它可以有效防止因网络问题导致的上传失败,同时也能节约用户的流量和时间。
分片上传分片上传则是将一个大文件分割成多个小块分别上传,最后再由服务器合并成完整的文件。这种做法的好处是可以并行处理多个小文件,提高上传效率;同时,如果某一部分上传失败,只需要重传这一部分,不影响其他部分。
三 秒传实战后端实现在 SpringBoot 项目中,我们可以使用 MessageDigest 类来计算文件的 MD5 值,然后检查数据库中是否存在该文件。
@RestController@RequestMapping("/file")public FileController { @Autowired FileService fileService; @PostMapping("/upload1") public ResponseEntity secondUpload(@RequestParam(value = "file",required = false) MultipartFile file,@RequestParam(required = false,value = "md5") String md5) { try { // 检查数据库中是否已存在该文件 if (fileService.existsByMd5(md5)) { return ResponseEntity.ok("文件已存在"); } // 保存文件到服务器 file.transferTo(new File("/path/to/save/" + file.getOriginalFilename())); // 保存文件信息到数据库 fileService.save(new FileInfo(file.getOriginalFilename(), DigestUtils.md5DigestAsHex(file.getInputStream()))); return ResponseEntity.ok("上传成功"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传失败"); } }}前端调用前端可以通过 JavaScript 的 FileReader API 读取文件内容,通过 spark-md5 计算 MD5 值,然后发送给后端进行校验。
秒传
前端分为两个步骤:
计算文件的 MD5 值,计算之后发送给服务端确定文件是否存在。如果文件已经存在,则不需要继续上传文件;如果文件不存在,则开始上传文件,上传文件和 MD5 校验请求类似,上面的案例代码中我就没有重复演示了,松哥在书里和之前的课程里都多次讲过文件上传,这里不再啰嗦。四 分片上传实战分片上传关键是在前端对文件切片,比如一个 10MB 的文件切为 10 份,每份 1MB。每次上传的时候,需要多一个参数记录当前上传的文件切片的起始位置。
比如一个 10MB 的文件,切为 10 份,每份 1MB,那么:
第 0 片,从 0 开始,一共是 1024*1024 个字节。第 1 片,从 1024*1024 开始,一共是 1024*1024 个字节。第 2 片...
把这个搞懂,后面的代码就好理解了。
后端实现private static final String UPLOAD_DIR = System.getProperty("user.home") + "/uploads/";/** * 上传文件到指定位置 * * @param file 上传的文件 * @param start 文件开始上传的位置 * @return ResponseEntity 上传结果 */@PostMapping("/upload2")public ResponseEntity resumeUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start,@RequestParam("fileName") String fileName) { try { File directory = new File(UPLOAD_DIR); if (!directory.exists()) { directory.mkdirs(); } File targetFile = new File(UPLOAD_DIR + fileName); RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw"); FileChannel channel = randomAccessFile.getChannel(); channel.position(start); channel.transferFrom(file.getResource().readableChannel(), start, file.getSize()); channel.close(); randomAccessFile.close(); return ResponseEntity.ok("上传成功"); } catch (Exception e) { System.out.println("上传失败: "+e.getMessage()); return ResponseEntity.status(500).body("上传失败"); }}
后端每次处理的时候,需要先设置文件的起始位置。
前端调用前端需要将文件切分成多个小块,然后依次上传。
分片示例 五 断点续传实战断点续传的技术原理类似于分片上传。
当文件已经上传了一部分之后,断了需要重新开始上传。
那么我们的思路是这样的:
前端先发送一个请求,检查要上传的文件在服务端是否已经存在,如果存在,目前大小是多少。前端根据已经存在的大小,继续上传文件即可。后端案例先来看后端检查的接口,如下:
@GetMapping("/check")public ResponseEntity checkFile(@RequestParam("filename") String filename) { File file = new File(UPLOAD_DIR + filename); if (file.exists()) { return ResponseEntity.ok(file.length()); } else { return ResponseEntity.ok(0L); }}
如果文件存在,则返回已经存在的文件大小。
如果文件不存在,则返回 0,表示前端从头开始上传该文件。
前端调用 断点续传示例
这个案例实际上是一个断点续传+分片上传的案例,相关知识点并不难,小伙伴们可以自行体会下。
六 总结好了,以上就是关于文件上传中秒传、断点续传和分片上传的实战分享。通过这些技术的应用,我们可以极大地提升文件上传的效率和稳定性,改善用户体验。希望各位小伙伴在自己的项目中也能灵活运用这些技巧,解决实际问题。
本文完整案例:https://github.com/lenve/springboot3-samples/tree/main/file_upload