当前位置: 首页 > news >正文

大文件分片上传、断点续传、秒传

小文件上传

后端:SpringBoot+JDK17
前端:JavaScript+spark+md5.min.js

一、依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.2</version><relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>uploadDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>uploadDemo</name>
<description>uploadDemo</description>
<properties><java.version>17</java.version>
</properties>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>

二、业务代码

@RestController
public class UploadController{//上传路径public static final String UPLOAD_PATH = "D:\\upload";@RequestMapping("/upload")public ResponseEntity<Map<String,String>> upload(@RequestParam MultipartFile file) throws IOException {File dstFile = new File(UPLOAD_PATH,String.format("%s.%s", UUID.randomUUID(), StringUtils.getFilename(file.getOriginalFilename())));file.transferTo(dstFile);return ResponseEntity.ok(Map.of("path",dstFile.getAbsolutePath()));}
}

三、前端显示

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>upload</title>
</head>
<body>
upload<form enctype="multipart/form-data"><input type="file" name="fileInput" id="fileInput"><input type="button" value="上传" onclick="uploadFile()">
</form>上传结果
<span id="uploadResult"></span><script>var  uploadResult=document.getElementById("uploadResult")function uploadFile() {var fileInput = document.getElementById('fileInput');var file = fileInput.files[0];if (!file) return; // 没有选择文件var xhr = new XMLHttpRequest();// 处理上传进度xhr.upload.onprogress = function(event) {var percent = 100 * event.loaded / event.total;uploadResult.innerHTML='上传进度:' + percent + '%';};// 当上传完成时调用xhr.onload = function() {if (xhr.status === 200) {uploadResult.innerHTML='上传成功'+ xhr.responseText;}}xhr.onerror = function() {uploadResult.innerHTML='上传失败';}// 发送请求xhr.open('POST', '/upload', true);var formData = new FormData();formData.append('file', file);xhr.send(formData);}
</script></body>
</html>

在这里插入图片描述

【注意事项】

在上传过程会报文件大小限制错误,主要有三个参数需要设置:

org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (46302921) exceeds the configured maximum (10485760)

需在springboot的application.properties 或者application.yml中添加:

max-file-size
max-request-size

默认大小分别是1M和10M,因此需要重新设定

spring.servlet.multipart.max-file-size=1024MB  
spring.servlet.multipart.max-request-size=1024MB

如果使用nginx报 413状态码413 Request Entity Too Large,Nginx默认最大上传1MB文件,需要在nginx.conf配置文件中的 http{ }添加配置项:client_max_body_size 1024m
在这里插入图片描述

大文件上传

在这里插入图片描述
在这里插入图片描述

一、前端分片

计算文件MD5值用了spark-md5这个库
因为文件在传输写入过程中可能会出现错误,导致最终合成的文件可能和原文件不一样,所以要对比一下前端计算的MD5和后端计算的MD5是不是一样,保证上传数据的一致性
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>分片上传</title><script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
</head>
<body>
分片上传<form enctype="multipart/form-data"><input type="file" name="fileInput" id="fileInput"><input type="button" value="计算文件MD5" onclick="calculateFileMD5()"><input type="button" value="上传" onclick="uploadFile()"><input type="button" value="检测文件完整性" onclick="checkFile()">
</form><p>文件MD5:<span id="fileMd5"></span>
</p>
<p>上传结果:<span id="uploadResult"></span>
</p>
<p>检测文件完整性:<span id="checkFileRes"></span>
</p><script>//每片的大小var chunkSize = 1 * 1024 * 1024;var uploadResult = document.getElementById("uploadResult")var fileMd5Span = document.getElementById("fileMd5")var checkFileRes = document.getElementById("checkFileRes")var  fileMd5;function  calculateFileMD5(){var fileInput = document.getElementById('fileInput');var file = fileInput.files[0];getFileMd5(file).then((md5) => {console.info(md5)fileMd5=md5;fileMd5Span.innerHTML=md5;})}function uploadFile() {var fileInput = document.getElementById('fileInput');var file = fileInput.files[0];if (!file) return;if (!fileMd5) return;//获取到文件let fileArr = this.sliceFile(file);//保存文件名称let fileName = file.name;fileArr.forEach((e, i) => {//创建formdata对象let data = new FormData();data.append("totalNumber", fileArr.length)data.append("chunkSize", chunkSize)data.append("chunkNumber", i)data.append("md5", fileMd5)data.append("file", new File([e],fileName));upload(data);})}/*** 计算文件md5值*/function getFileMd5(file) {return new Promise((resolve, reject) => {let fileReader = new FileReader()fileReader.onload = function (event) {let fileMd5 = SparkMD5.ArrayBuffer.hash(event.target.result)resolve(fileMd5)}fileReader.readAsArrayBuffer(file)})}function upload(data) {var xhr = new XMLHttpRequest();// 当上传完成时调用xhr.onload = function () {if (xhr.status === 200) {uploadResult.append( '上传成功分片:' +data.get("chunkNumber")+'\t' ) ;}}xhr.onerror = function () {uploadResult.innerHTML = '上传失败';}// 发送请求xhr.open('POST', '/uploadBig', true);xhr.send(data);}function checkFile() {var xhr = new XMLHttpRequest();// 当上传完成时调用xhr.onload = function () {if (xhr.status === 200) {checkFileRes.innerHTML = '检测文件完整性成功:' + xhr.responseText;}}xhr.onerror = function () {checkFileRes.innerHTML = '检测文件完整性失败';}// 发送请求xhr.open('POST', '/checkFile', true);let data = new FormData();data.append("md5", fileMd5)xhr.send(data);}function sliceFile(file) {const chunks = [];let start = 0;let end;while (start < file.size) {end = Math.min(start + chunkSize, file.size);chunks.push(file.slice(start, end));start = end;}return chunks;}</script></body>
</html>

二、后端

两个接口/uploadBig用于每一片文件的上传和/checkFile检测文件的MD5
在这里插入图片描述

FileChannel fileChannel = randomAccessFile.getChannel();  
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, chunkNumber * chunkSize, fileData.length);  
mappedByteBuffer.put(fileData);

在这里插入图片描述

@RestController
public class UploadController {public static final String UPLOAD_PATH = "D:\\upload\\";/*** @param chunkSize   每个分片大小* @param chunkNumber 当前分片* @param md5         文件总MD5* @param file        当前分片文件数据* @return* @throws IOException*/@RequestMapping("/uploadBig")public ResponseEntity<Map<String, String>> uploadBig(@RequestParam Long chunkSize, @RequestParam Integer totalNumber, @RequestParam Long chunkNumber, @RequestParam String md5, @RequestParam MultipartFile file) throws IOException {//文件存放位置String dstFile = String.format("%s\\%s\\%s.%s", UPLOAD_PATH, md5, md5, StringUtils.getFilenameExtension(file.getOriginalFilename()));//上传分片信息存放位置String confFile = String.format("%s\\%s\\%s.conf", UPLOAD_PATH, md5, md5);//第一次创建分片记录文件//创建目录File dir = new File(dstFile).getParentFile();if (!dir.exists()) {dir.mkdir();//所有分片状态设置为0byte[] bytes = new byte[totalNumber];Files.write(Path.of(confFile), bytes);}//随机分片写入文件try (RandomAccessFile randomAccessFile = new RandomAccessFile(dstFile, "rw");RandomAccessFile randomAccessConfFile = new RandomAccessFile(confFile, "rw");InputStream inputStream = file.getInputStream()) {//定位到该分片的偏移量(可以将光标移到文件指定位置开始写数据,每一个文件每将上传分片编号chunkNumber都是不一样的,所以各自写自己文件块,多线程写同一个文件不会出现线程安全问题)randomAccessFile.seek(chunkNumber * chunkSize);//写入该分片数据大文件写入时用RandomAccessFile可能比较慢,可以使用MappedByteBuffer内存映射来加速大文件写入,不过使用MappedByteBuffer如果要删除文件可能会存在删除不掉,因为删除了磁盘上的文件,内存的文件还是存在的randomAccessFile.write(inputStream.readAllBytes());//定位到当前分片状态位置randomAccessConfFile.seek(chunkNumber);//设置当前分片上传状态为1randomAccessConfFile.write(1);}return ResponseEntity.ok(Map.of("path", dstFile));}/*** 获取文件分片状态,检测文件MD5合法性** @param md5* @return* @throws Exception*/@RequestMapping("/checkFile")public ResponseEntity<Map<String, String>> uploadBig(@RequestParam String md5) throws Exception {String uploadPath = String.format("%s\\%s\\%s.conf", UPLOAD_PATH, md5, md5);Path path = Path.of(uploadPath);//MD5目录不存在文件从未上传过if (!Files.exists(path.getParent())) {return ResponseEntity.ok(Map.of("msg", "文件未上传"));}//判断文件是否上传成功StringBuilder stringBuilder = new StringBuilder();byte[] bytes = Files.readAllBytes(path);for (byte b : bytes) {stringBuilder.append(String.valueOf(b));}//所有分片上传完成计算文件MD5if (!stringBuilder.toString().contains("0")) {File file = new File(String.format("%s\\%s\\", UPLOAD_PATH, md5));File[] files = file.listFiles();String filePath = "";for (File f : files) {//计算文件MD5是否相等if (!f.getName().contains("conf")) {filePath = f.getAbsolutePath();try (InputStream inputStream = new FileInputStream(f)) {String md5pwd = DigestUtils.md5DigestAsHex(inputStream);if (!md5pwd.equalsIgnoreCase(md5)) {return ResponseEntity.ok(Map.of("msg", "文件上传失败"));}}}}return ResponseEntity.ok(Map.of("path", filePath));} else {//文件未上传完成,反回每个分片状态,前端将未上传的分片继续上传return ResponseEntity.ok(Map.of("chucks", stringBuilder.toString()));}}}

断点续传

在这里插入图片描述
在这里插入图片描述
用/checkFile接口,文件里如果有未完成上传的分片,接口返回chunks字段对就的位置值为0,前端将未上传的分片继续上传,完成后再调用/checkFile就完成了断点续传

秒传

只要修改前端代码流程就好了,比如张三上传了一个文件,然后李四又上传了同样内容的文件,同一文件的MD5值可以认为是一样的(虽然会存在不同文件的MD5一样,不过概率很小,可以认为MD5一样文件就是一样)李四调用/checkFile接口后,后端直接返回了李四上传的文件路径,李四就完成了秒传。大部分云盘秒传的思路应该也是这样,只不过计算文件HASH算法更为复杂,返回给用户文件路径也更为安全,要防止被别人算出文件路径了
在这里插入图片描述
在这里插入图片描述

相关文章:

大文件分片上传、断点续传、秒传

小文件上传 后端&#xff1a;SpringBootJDK17 前端&#xff1a;JavaScriptsparkmd5.min.js 一、依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.2</ve…...

DAY53 1143.最长公共子序列 + 1035.不相交的线 + 53. 最大子序和

1143.最长公共子序列 题目要求&#xff1a;给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长公共子序列的长度。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08;也可以不删…...

短剧App开发:个性化的内容推荐

随着移动互联网的普及和用户需求的多样化&#xff0c;短剧App作为一种新兴的内容消费模式&#xff0c;受到了越来越多用户的青睐。在短剧App开发中&#xff0c;个性化的内容推荐是一个重要的功能&#xff0c;它能够根据用户的兴趣偏好和行为数据&#xff0c;为他们提供更精准、…...

互斥量保护资源

一、概念 在多数情况下&#xff0c;互斥型信号量和二值型信号量非常相似&#xff0c;但是从功能上二值型信号量用于同步&#xff0c; 而互斥型信号量用于资源保护。 互斥型信号量和二值型信号量还有一个最大的区别&#xff0c;互斥型信号量可以有效解决优先级反转现 象。 …...

天机学堂-1、项目搭建,微服务架构设计

1.学习背景 各位同学大家好&#xff0c;经过前面的学习我们已经掌握了《微服务架构》的核心技术栈。相信大家也体会到了微服务架构相对于项目一的单体架构要复杂很多&#xff0c;你的脑袋里也会有很多的问号&#xff1a; 微服务架构该如何拆分&#xff1f; 到了公司中我需要自…...

windows 电脑删除不了.TTF的文件

出现这个问题&#xff0c;首先检查&#xff0c;你的.ttf文件是不是在哪个软件中打开了。 如果是&#xff0c;先关掉&#xff0c;然后在删一遍试试。 如果这个还是不行试着打开控制面板>外观和个性化> 字体 > 字体设置>还原默认字体设置勾选&#xff0c;然后重启一下…...

C#多线程的操作

文章目录 1 使用线程意义2 C#线程开启的四种方式2.1 异步委托开启线程2.2 通过Thread类开启线程2.3 通过线程池开启线程2.4 通过任务Task开启线程 3 前台线程和后台线程简述3.1 前台线程3.2 后台线程 4 简述Thread和Task开启线程的区别4.1 Thread效果展示4.2 Task效果展示4.3 区…...

MyBatis Plus—CRUD 接口

Service CRUD 接口 说明: 通用 Service CRUD 封装IService (opens new window)接口&#xff0c;进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆&#xff0c;泛型 T 为任意实体对象建议如果存在自定义通用 Servi…...

火焰图:链路追踪分析的可视化利器

什么是火焰图&#xff1f; 火焰图用于可视化分布式链路追踪&#xff0c;通过使用持续时间和不同颜色的水平条形来表示请求执行路径中的每个服务调用。分布式跟踪的火焰图包括错误、延迟数据等详情&#xff0c;帮助开发人员识别和解决应用程序中的瓶颈问题。 链路追踪与 Span …...

中睿天下Coremail | 2023年Q3企业邮箱安全态势观察报告

10月25日&#xff0c;北京中睿天下信息技术有限公司联合Coremail邮件安全发布《2023年第三季度企业邮箱安全性研究报告》。2023年第三季度企业邮箱安全呈现出何种态势&#xff1f;作为邮箱管理员&#xff0c;我们又该如何做好防护&#xff1f; 以下为精华版阅读&#xff0c;如需…...

HBuilderX vue项目打包上传到服务器

完成后有个’dist’目录,把真个目录通过FTP 上传到服务器,Mac电脑使用cyberduck 上传 服务器使用‘宝塔’进行一件部署,基本上就是傻瓜式的点击下一步...

2656. K 个元素的最大和 --力扣 --JAVA

题目 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你需要执行以下操作 恰好 k 次&#xff0c;最大化你的得分&#xff1a; 从 nums 中选择一个元素 m 。 将选中的元素 m 从数组中删除。 将新元素 m 1 添加到数组中。 你的得分增加 m 。 请你返回执行以上操作恰好 k …...

从HTTP到Tomcat:揭秘Web应用的底层协议与高性能容器

WEB服务器 1. HTTP协议1.1 HTTP-概述1.1.1 介绍1.2.2 特点 2.2 HTTP-请求协议2.3 HTTP-响应协议2.3.1 格式介绍2.3.2 响应状态码 2.4 HTTP-协议解析 2. WEB服务器-Tomcat2.1 简介2.1.1 服务器概述2.1.2 Web服务器2.1.3 Tomcat 2.2 基本使用2.2.1 下载2.2.2 安装与卸载2.2.3 启动…...

百度搜索智能化算力调控分配方法

作者 | 泰来 导读 随着近年深度学习技术的发展&#xff0c;搜索算法复杂度不断上升&#xff0c;算力供给需求出现了爆发式的增长。伴随着AI技术逐步走到深水区&#xff0c;算法红利在逐步消失&#xff0c;边际效应日益显著&#xff0c;算力效能的提升尤为重要&#xff0c;同时随…...

如何搭建接口自动化测试框架?

经过了一年多的接口测试工作&#xff0c;旧的框架也做了一些新的调整&#xff0c;删除了很多冗余的功能&#xff0c;只保留了最基本的接口结构验证、接口回归测试、线上定时巡检功能。 一、框架的演进 界面 UI 做了优化&#xff0c;整个框架的画风突然不一样了&#xff08;人靠…...

ubuntu 20.04+ORB_SLAM3 安装配库教程

目录 安装ros(如果只是运行ORB-SLAM3&#xff0c;可以跳过安装)0. ros 安装教程1. 安装opencv2. 安装Pangolin3. 安装Eigen34.安装Python & libssl-dev5.安装boost库6.安装ceres库&#xff08;不必须&#xff09;7.安装Sophus库&#xff08;不必须&#xff09;8. 安装g20库…...

Poly风格模型的创建与使用_unity基础开发教程

Poly风格模型的创建与使用 安装Poly相关组件Poly模型的创建Poly模型编辑 安装Poly相关组件 打开资源包管理器Package Manager 在弹出的窗口左上角Packages选择Unity Registry 搜索框搜索 Poly 搜索结果点击Polybrush 点击右下角 Install 同时也别忘了导入一下模型示例&#…...

终于有人把VMware虚拟机三种网络模式讲清楚了!

前段时间VMware更新了&#xff0c;你用上最新版了吗&#xff1f; 有几个网工在操作中遇到过各种各样的问题。 比如说由于公司服务器重启导致出现下面的问题&#xff1a;在Xshell里连接虚拟机映射时连接失败&#xff1b;能够连接上虚拟机的映射地址&#xff0c;但git pull时报…...

Flutter实践二:repository模式

1.repository 几乎所有的APP&#xff0c;从简单的到最复杂的&#xff0c;在它们的架构里几乎都包括状态管理和数据源这两部分。状态管理常见的有Bloc、Cubit、Provider、ViewModel等&#xff0c;数据源则是一些直接和数据库或者网络客户端进行交互&#xff0c;取得相应的数据&…...

交换机Vlan和端口配置(H3C)

交换机Vlan配置&#xff08;H3C&#xff09; 配置VLAN配置VLAN接口的IP地址开启ARP网关保护功能&#xff0c;配置被保护的网关IP地址 配置VLAN Vlan物理端口3GigabitEthernet 1/0/1 ~ GigabitEthernet 1/0/14 &#xff1b;GigabitEthernet 2/0/1 ~ GigabitEthernet 2/0/1450Gi…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

9-Oracle 23 ai Vector Search 特性 知识准备

很多小伙伴是不是参加了 免费认证课程&#xff08;限时至2025/5/15&#xff09; Oracle AI Vector Search 1Z0-184-25考试&#xff0c;都顺利拿到certified了没。 各行各业的AI 大模型的到来&#xff0c;传统的数据库中的SQL还能不能打&#xff0c;结构化和非结构的话数据如何和…...

rm视觉学习1-自瞄部分

首先先感谢中南大学的开源&#xff0c;提供了很全面的思路&#xff0c;减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接&#xff1a;https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架&#xff1a; 代码框架结构&#xff1a;readme有…...

【多线程初阶】单例模式 指令重排序问题

文章目录 1.单例模式1)饿汉模式2)懒汉模式①.单线程版本②.多线程版本 2.分析单例模式里的线程安全问题1)饿汉模式2)懒汉模式懒汉模式是如何出现线程安全问题的 3.解决问题进一步优化加锁导致的执行效率优化预防内存可见性问题 4.解决指令重排序问题 1.单例模式 单例模式确保某…...

构建Docker镜像的Dockerfile文件详解

文章目录 前言Dockerfile 案例docker build1. 基本构建2. 指定 Dockerfile 路径3. 设置构建时变量4. 不使用缓存5. 删除中间容器6. 拉取最新基础镜像7. 静默输出完整示例 docker runDockerFile 入门syntax指定构造器FROM基础镜像RUN命令注释COPY复制ENV设置环境变量EXPOSE暴露端…...