当前位置: 首页 > 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…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...