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

小文件上传

后端: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算法更为复杂,返回给用户文件路径也更为安全,要防止被别人算出文件路径了
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

1 Supervised Machine Learning Regression and Classification

文章目录 Week1OverViewSupervised LearningUnsupervised LearningLinear Regression ModelCost functionGradient Descent Week2Muliple FeatureVectorizationGradient Descent for Multiple RegressionFeature ScalingGradient DescentFeature EngineeringPolynomial Regress…

UE5 - UI Material Lab 学习笔记

1、学习资料收集 UI Material Lab : https://www.unrealengine.com/marketplace/zh-CN/product/ui-material-lab 视频1&#xff1a;https://www.bilibili.com/video/BV1Hm4y1t7Kn/?spm_id_from333.337.search-card.all.click&vd_source707ec8983cc32e6e065d5496a7f79ee6 视…

记录一次 添加脚本的记录+改错记录

1.Update 和 Delete 一定要记得where条件 update 表名称 set 字段1‘修改的值’ &#xff08;单引号&#xff09; where 字段‘’ and Aid‘’; update jxkh22 set JXKH2200001 ,JXKH2201002 where B003 and JXKH22034;delete from table_name where condition delete from …

Linux控制---进程程序替换

前言&#xff1a;前面我们学洗了Linux进程退出的相关知识&#xff0c;了解了什么是进程退出&#xff0c;已经进程等待的相关话题&#xff0c;今天&#xff0c;我们来学习Linux中的进程程序替换&#xff0c;进程程序替换在Linux中可以用于实现新程序的启动、程序升级、多进程程序…

11.10 知识总结(数据的增删改查、如何创建表关系、Django框架的请求生命周期流程图)

一、 数据的增删改查 1.1 用户列表的展示 把数据表中得用户数据都给查询出来展示在页面上 添加数据 id username password gender age action 修改 删除 1.2 修…

【数据库开发】DataX开发环境的安装部署(Python、Java)

文章目录 1、简介1.1 DataX简介1.2 DataX功能1.3 支持的数据通道 2、DataX安装配置2.1 DataX2.2 Java2.3 Python 3、DataX Web安装配置3.1 mysql3.2 DataX Web3.2.1 简介3.2.2 架构图3.2.3 依赖环境3.2.4 安装 4、入门使用4.1 DataX自带打印示例测试4.2 DataX生成任务模板文件4…

kubernetes集群编排——istio

官网&#xff1a;https://istio.io/latest/zh/about/service-mesh/ 部署 [rootk8s2 ~]# tar zxf istio-1.19.3-linux-amd64.tar.gz [rootk8s2 ~]# cd istio-1.19.3/[rootk8s2 istio-1.19.3]# export PATH$PWD/bin:$PATH demo专为测试准备的功能集合 [rootk8s2 istio-1.19.3]# i…

Linux文件系统之inode

文章目录 1. 磁盘1.1 认识磁盘1.2 磁盘物理构造1.3 磁盘逻辑结构 2. 文件系统3. 如何理解目录 1. 磁盘 1.1 认识磁盘 文件 内容 属性&#xff0c;而文件是存储在磁盘上&#xff0c;那么可以理解为磁盘上存储的文件 存储的文件内容 存储的文件属性。 文件的内容采用的是块式…

kube-bench-CIS基准的自动化扫描工具学习

仓库地址&#xff1a;GitHub - aquasecurity/kube-bench: Checks whether Kubernetes is deployed according to security best practices as defined in the CIS Kubernetes Benchmark kube-bench,检查 Kubernetes 是否根据 CIS Kubernetes 基准中定义的安全最佳实践部署,下载…

应用协议安全:Rsync-common 未授权访问.

应用协议安全&#xff1a;Rsync-common 未授权访问. Rsync 是 Linux 下一款数据备份工具&#xff0c;支持通过 rsync 协议、ssh 协议进行远程文件传输。其中 rsync 协议默认监听 873 端口&#xff0c;如果目标开启了 rsync 服务&#xff0c;并且没有配置 ACL 或访问密码&#…

Redis05-集群方案

目录 Redis集群方案 主从复制 主从复制的基本原理 主从复制的工作流程 乐观复制 主从复制的优势 哨兵机制 哨兵的关键作用 服务状态监控 哨兵选举Master规则 分片集群 分片集群中的数据读写 数据写入 数据读取 一致性哈希和客户端分片 Redis集群方案 微服务时代…

TDengine 与煤科院五大系统实现兼容性互认,助力煤矿智能化安全体系搭建

近日&#xff0c;涛思数据与煤炭科学技术研究院&#xff08;以下简称煤科院&#xff09;已完成数个产品兼容互认证工作&#xff0c;经双方共同严格测试&#xff0c;涛思数据旗下物联网、工业大数据平台 TDengine V3.X 与煤炭科学技术研究院旗下煤矿复合灾害监测监控预警系统、煤…

Elasticsearch docker-compose 使用 Logstash 从 JSON 文件中预加载数据

在我们创建 Elasticsearch 进行开发时&#xff0c;最简单的办法就是在本地使用 docker-compose 来一键部署一个 Elasticsearch 集群。有时&#xff0c;特别是在准备测试环境时&#xff0c;开发人员希望从一开始就创建包含一些测试数据的数据库容器。我们可以使用 Logstash 来很…

什么是自动化测试框架?

无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架。之前学习自动化测试的过程中&#xff0c;一直对“框架”这个词知其然不知其所以然。 最近看了很多自动化相关的资料&#xff0c;加上自己的一些实践&#xff0c;算是对“框架”…

使用python电脑轻量级控制手机—adb命令和手机投屏

文章目录 一、通过无线连接手机和电脑二、使用adb命令轻量级控制手机二、使用scrcpy控制手机 通过电脑控制手机有多种方式如appnium等&#xff0c;本文介绍的是两种轻量级的方案&#xff0c;使用adb命令刚和手机投屏。 一、通过无线连接手机和电脑 1、手机设置 开发者选项—us…

使用 MATLAB HDL Coder 和 FPGA 快速实现自动白平衡(AWB)

使用 MATLAB HDL Coder 和 FPGA 快速实现自动白平衡&#xff08;AWB&#xff09; 在此项目中&#xff0c;我们将使用 MATLAB Simulink 和 HDL 编码器创建自定义 IP -- AWB。 MATLAB 设计 自动白平衡模块的设计是使用 HDL Coder 在 MATLAB 和 Simulink 中创建的。HDL Coder能够生…

11.15 知识总结(模板层、模型层)

一、 模板层 1.1 过滤器 1.什么是过滤器&#xff1f; 过滤器类似于python的内置函数&#xff0c;用来把变量值加以修饰后再显示。 2. 语法 1、 {{ 变量名|过滤器名 }} 2、链式调用&#xff1a;上一个过滤器的结果继续被下一个过滤器处理 {{ 变量名|过滤器1|过滤器2 }} 3、有的过…

idea中搭建Spring boot项目(借助Spring Initializer)

创建新项目 启动端口 在项目配置文件application.properties中写入 #启动端口server.port8088编写测试方法 创建控制类文件夹–>便于规范我们新建一个controller包–>建一个HelloWorld.class package com.example.hellospringboot.controller;import org.springframew…

Linux下MSSQL (SQL Server)数据库无法启动故障处理

有同事反馈一套CentOS7下的mssql server2017无法启动需要我帮忙看看&#xff0c;启动报错情况如下 检查日志并没有更新日志信息 乍一看mssql-server服务有问题&#xff0c;检查mssql也确实没有进程 既然服务有问题&#xff0c;那么我们用一种方式直接手工后台启动mssql引擎来…

arcgis--填充面域空洞

方法一&#xff1a;使用【编辑器】-【合并工具】进行填充。首选需要在相同图层中构造一个填充空洞的面域&#xff0c;然后利用【合并】工具进行最后填充。 打开一幅含有空洞的矢量数据&#xff0c;如下&#xff1a; 打开【开始编辑】-【构造工具】-【面】进行覆盖空洞的面域的…