springboot 文件高效上传

文件上传功能可以说对于后端服务是必须的,不同场景对文件上传的要求也各不相同,有的追求速度,有的注重稳定性,还有的需要考虑文件大小和安全性。所以便有了秒传、断点续传和分片上传等解决方案。

1、总述

秒传

秒传,顾名思义,就是几乎瞬间完成文件上传的过程。其实现原理是通过计算文件的哈希值(如 MD5 或 SHA-1),然后将这个唯一的标识符发送给服务器。如果服务器上已经存在相同的文件,则直接返回成功信息,避免了重复上传。这种方式不仅节省了带宽,也大大提高了用户体验。

断点续传

断点续传是指在网络不稳定或者用户主动中断上传后,能够从上次中断的地方继续上传,而不需要重新开始整个过程。这对于大文件上传尤为重要,因为它可以有效防止因网络问题导致的上传失败,同时也能节约用户的流量和时间。

分片上传

分片上传则是将一个大文件分割成多个小块分别上传,最后再由服务器合并成完整的文件。这种做法的好处是可以并行处理多个小文件,提高上传效率;同时,如果某一部分上传失败,只需要重传这一部分,不影响其他部分。

2、秒传

后端

在 SpringBoot 项目中,我们可以使用 MessageDigest 类来计算文件的 MD5 值,然后检查数据库中是否存在该文件。

@RestController
@RequestMapping("/file")
public class FileController {@AutowiredFileService fileService;@PostMapping("/upload1")public ResponseEntity<String> 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 值,然后发送给后端进行校验。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>秒传</title><script src="spark-md5.js"></script>
</head>
<body>
<input type="file" id="fileInput" />
<button onclick="startUpload()">开始上传</button>
<hr>
<script>async function startUpload() {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}const md5 = await calculateMd5(file);const formData = new FormData();formData.append('md5', md5);const response = await fetch('/file/upload1', {method: 'POST',body: formData});const result = await response.text();if (response.ok) {if (result != "文件已存在") {// 开始上传文件}} else {console.error("上传失败: " + result);}}function calculateMd5(file) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onloadend = () => {const spark = new SparkMD5.ArrayBuffer();spark.append(reader.result);resolve(spark.end());};reader.onerror = () => reject(reader.error);reader.readAsArrayBuffer(file);});}
</script>
</body>
</html>

前端分为两个步骤:

  1. 计算文件的 MD5 值,计算之后发送给服务端确定文件是否存在。
  2. 如果文件已经存在,则不需要继续上传文件;如果文件不存在,则开始上传文件,上传文件和 MD5 校验请求类似,上面的案例代码中我就没有重复演示了,松哥在书里和之前的课程里都多次讲过文件上传,这里不再啰嗦。

3、分片上传

分片上传关键是在前端对文件切片,比如一个 100MB 的文件切为 50 份,每份 2MB。每次上传的时候,需要多一个参数记录当前上传的文件切片的起始位置。

比如:一个 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<String> 上传结果*/
@PostMapping("/upload2")
public ResponseEntity<String> 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("上传失败");}
}

后端每次处理的时候,需要先设置文件的起始位置。

前端

前端需要将文件切分成多个小块,然后依次上传。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>分片示例</title>
</head>
<body><input type="file" id="fileInput" /><button onclick="startUpload()">开始上传</button><script>async function startUpload() {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}const filename = file.name;let start = 0;uploadFile(file, start);}async function uploadFile(file, start) {const chunkSize = 1024 * 1024; // 每个分片1MBconst total = Math.ceil(file.size / chunkSize);for (let i = 0; i < total; i++) {const chunkStart = start + i * chunkSize;const chunkEnd = Math.min(chunkStart + chunkSize, file.size);const chunk = file.slice(chunkStart, chunkEnd);const formData = new FormData();formData.append('file', chunk);formData.append('start', chunkStart);formData.append('fileName', file.name);const response = await fetch('/file/upload2', {method: 'POST',body: formData});const result = await response.text();if (response.ok) {console.log(`分片 ${i + 1}/${total} 上传成功`);} else {console.error(`分片 ${i + 1}/${total} 上传失败: ${result}`);break;}}}</script>
</body>
</html>

4、断点续传

断点续传的技术原理类似于 分片上传。

当文件已经上传了一部分之后,断了需要重新开始上传。

大概思路是这样的:

  1. 前端先发送一个请求,检查要上传的文件在服务端是否已经存在,如果存在,目前大小是多少。
  2. 前端根据已经存在的大小,继续上传文件即可。

后端

后端检查的接口,如下:

@GetMapping("/check")
public ResponseEntity<Long> 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,表示前端从头开始上传该文件。

前端

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>断点续传示例</title>
</head>
<body>
<input type="file" id="fileInput"/>
<button onclick="startUpload()">开始上传</button><script>async function startUpload() {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}const filename = file.name;let start = await checkFile(filename);uploadFile(file, start);}async function checkFile(filename) {const response = await fetch(`/file/check?filename=${filename}`);const start = await response.json();return start;}async function uploadFile(file, start) {const chunkSize = 1024 * 1024; // 每个分片1MBconst total = Math.ceil((file.size - start) / chunkSize);for (let i = 0; i < total; i++) {const chunkStart = start + i * chunkSize;const chunkEnd = Math.min(chunkStart + chunkSize, file.size);const chunk = file.slice(chunkStart, chunkEnd);const formData = new FormData();formData.append('file', chunk);formData.append('start', chunkStart);formData.append('fileName', file.name);const response = await fetch('/file/upload2', {method: 'POST',body: formData});const result = await response.text();if (response.ok) {console.log(`分片 ${i + 1}/${total} 上传成功`);} else {console.error(`分片 ${i + 1}/${total} 上传失败: ${result}`);break;}}}
</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/472270.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Settings 单元测试 | 如何运行单元测试?

背景 在Android Settings 单元测试 | Telephony Network 模块 APN 案例中粗略介绍了单元测试逻辑内容&#xff0c;但是在独立APK里面如何将单元测试跑起来还是有疑问&#xff0c;因为APP不能直接install&#xff0c;无法借助Android Studio直接Run&#xff0c;在安装的一步会报…

flutter字体大小切换案例 小字体,标准字体,大字体,超大字体案例

flutter字体大小切换案例 小字体&#xff0c;标准字体&#xff0c;大字体&#xff0c;超大字体案例 Android iOS设备带有选择记录 我的flutter项目版本 environment: sdk: ‘>3.4.4 <4.0.0’ 图片案例 pubspec.yaml 添加依赖 # 屏幕尺寸适配 https://github.com/OpenF…

编译原理(手绘)

大家好&#xff0c;今天给大家分享一下我自己对c语言编译链接的一点见解&#xff08;本人是学生&#xff0c;有记笔记的习惯&#xff09;&#xff0c;那么今天就给大家分享我的笔记。 以上只是我本人的一些见解&#xff0c;并非绝对&#xff0c;欢迎大家一起交流。 那么今天分…

跳房子(弱化版)

题目描述 跳房子&#xff0c;也叫跳飞机&#xff0c;是一种世界性的儿童游戏&#xff0c;也是中国民间传统的体育游戏之一。 跳房子的游戏规则如下&#xff1a; 在地面上确定一个起点&#xff0c;然后在起点右侧画 n 个格子&#xff0c;这些格子都在同一条直线上。每个格子内…

初识Linux · 共享内存

目录 理解共享内存 Shared memmory code 理解共享内存 前文介绍的管道方式的通信&#xff0c;本文介绍的是进程通信的另外一种方式&#xff0c;即共享内存。但是这种通信方式的特点是只能本地通信&#xff0c;并且不像管道那样有保护机制&#xff0c;这里是没有的。 我们通…

机器学习day5-随机森林和线性代数1

十 集成学习方法之随机森林 集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。大致可以分为&#xff1a;Bagging&#xff0c;Boosting 和 Stacking 三大类型。 &#xff08;1&#xff09;每次有放回地从训练集中取出 n 个训练样本&…

Essential Cell Biology--Fifth Edition--Chapter one (6)

1.1.4.4 Internal Membranes Create Intracellular Compartments with Different Functions [细胞膜形成具有不同功能的细胞内隔室] 细胞核、线粒体和叶绿体并不是真核细胞中唯一的膜包围细胞器。细胞质中含有大量的[ a profusion of]其他细胞器&#xff0c;这些细胞器被单层膜…

基于VUE实现语音通话:边录边转发送语言消息、 播放pcm 音频

文章目录 引言I 音频协议音频格式:音频协议:II 实现协议创建ws对象初始化边录边转发送语言消息 setupPCM按下通话按钮时开始讲话,松开后停止讲话播放pcm 音频III 第三库recorderplayer调试引言 需求:电台通讯网(电台远程遥控软件-超短波)该系统通过网络、超短波终端等无线…

政务数据治理专栏开搞!

写在前面 忙忙碌碌干了一年政务数据治理的工作&#xff0c;从法人数据到自然人&#xff0c;从交通到地理信息等等&#xff0c;突发想法开一个专栏讲一讲政务数据遇到的问题&#xff0c;以及治理的成效&#xff0c;或许有朋友爱看。 政务数据&#xff0c;又称之为政务数据资源&a…

CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法

已经执行了conda init&#xff0c;但是还是会报错CondaError: Run ‘conda init’ before ‘conda activate’ 原因&#xff1a;权限不够 解决办法&#xff1a;以管理员身份运行cmd&#xff0c;然后进入要操作的文件夹下&#xff0c;重新执行 conda init 和 conda activate 就可…

【全面系统性介绍】虚拟机VM中CentOS 7 安装和网络配置指南

一、CentOS 7下载源 华为源&#xff1a;https://mirrors.huaweicloud.com/centos/7/isos/x86_64/ 阿里云源&#xff1a;centos-vault-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里云 百度网盘源&#xff1a;https://pan.baidu.com/s/1MjFPWS2P2pIRMLA2ioDlVg?pwdfudi &…

软考教材重点内容 信息安全工程师 第 4 章 网络安全体系与网络安全模型

4,1 网络安全体系的主要特征: (1)整体性。网络安全体系从全局、长远的角度实现安全保障&#xff0c;网络安全单元按照一定的规则&#xff0c;相互依赖、相互约束、相互作用而形成人机物一体化的网络安全保护方式。 (2)协同性。网络安全体系依赖于多种安全机制&#xff0c;通过各…

让空间计算触手可及,VR手套何以点石成金?

引言 如何让一位母亲与她去世的小女儿“重逢”&#xff1f;韩国MBC电视台《I Met You》节目实现了一个“不可能”心愿。 在空旷的绿幕中&#xff0c;母亲Jang Ji-sung透过VR头显&#xff0c;看到了三年前因白血病去世的女儿Nayeon。当她伸出双手&#xff0c;居然能摸到女儿的…

[Admin] Dashboard Filter for Mix Report Types

Background RevOps team has built a dashboard for sales team to track team members’ performance, but they’re blocked by how to provide a manager view based on sales’ hierarchy. Therefore, they seek for dev team’s help to clear their blocker. From foll…

WPF中如何使用区域导航

1.创建一个Prism框架的项目并设计好数据源 User如下&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace WPF练习17区域导航.Models {public class User{public int UserId { get; …

AR眼镜方案_AR智能眼镜阵列/衍射光波导显示方案

在当今AR智能眼镜的发展中&#xff0c;显示和光学组件成为了技术攻坚的主要领域。由于这些组件的高制造难度和成本&#xff0c;其光学显示模块在整个设备的成本中约占40%。 采用光波导技术的AR眼镜显示方案&#xff0c;核心结构通常由光机、波导和耦合器组成。光机内的微型显示…

一文学会docker中搭建kali

一文学会docker中搭建kali 本文环境&#xff1a;部署好docker的ubuntu系统主机一台 直接pull对应的镜像&#xff1a; docker pull kalilinux/kali-rolling 然后通过端口映射&#xff0c;将本地100端口映射到容器的22端口&#xff0c;就可以ssh了 docker run -it -p 100:22…

git上传文件到远程仓库

git上传项目到远程仓库 1. 生成SSH公钥(ssh-keygen),一直回车即可 2. 将公钥复制下来,粘贴至码云仓库 公钥默认地址: C:\Users\Administrator\.ssh3. 克隆项目到本地(复制SSH地址) 4. 上传文件到刚创建的项目(这里取名为test.py) 5. 上传需要做的几个步骤 (1) git add . 添…

STM32 串口输出调试信息

软硬件信息 CubeMX version 6.12.1Keil uVision V5.41.0.0 注意 串口有多种&#xff1a; TTL232485 串口的相关知识&#xff1a; 01-【HAL库】STM32实现串口打印&#xff08;printf方式) &#xff0c; 内含 TTL 和 232 区别。 我把 232 串口连进 STM32 串口助手收到的信息…

【计算机网络】TCP协议特点3

心跳机制 什么是心跳机制 心跳机制是在计算机系统、网络通信和许多其他技术领域广泛应用的一种机制&#xff0c;用于检测两个实体之间的连接是否仍然活跃&#xff0c;或者设备是否还在正常运行。就是每隔一段时间发送一个固定的消息给服务端&#xff0c;服务端回复一个固定…