java实现大文件分片上传

背景:

        公司后台管理系统有个需求,需要上传体积比较大的文件:500M-1024M;此时普通的文件上传显然有些吃力了,加上我司服务器配置本就不高,带宽也不大,所以必须考虑多线程异步上传来提速;所以这里就要用到文件分片上传技术了。

技术选型:

        直接问GPT实现大文件分片上传比较好的解决方案,它给的答案是webUploader(链接是官方文档);这是由 Baidu FEX 团队开发的一款以 HTML5 为主,FLASH 为辅的现代文件上传组件。在现代的浏览器里面能充分发挥 HTML5 的优势,同时又不摒弃主流IE浏览器,沿用原来的 FLASH 运行时,兼容 IE6+,iOS 6+, android 4+。采用大文件分片并发上传,极大的提高了文件上传效率;功能强大且齐全,支持对文件内容的Hash计算分片上传,可实现上传进度条等功能。

实现原理:

        文件分片上传比较简单,就不画图了,前端(webUploader)将用户选择的文件根据开发者配置的分片参数进行分片计算,将文件分成N个小文件多次调用后端提供的分片文件上传接口(webUploader插件有默认的一套参数规范,文件ID及分片相关字段,后端将对保存分片临时文件),后端记录并判断当前文件所有分片是否上传完毕,若已上传完则将所有分片合并成完整的文件,完成后建议删除分片临时文件(若考虑做分片下载可以保留)。

前端引入webUploader:

这里推荐去CDN下载静态资源:

记得要先引入JQuery,webUploader依赖JQuery;前端页面引入CSS和JS文件即可,Uploader.swf文件在创建webUploader对象时指定,貌似用来做兼容的。

前端(笔者前端用的layui)核心代码:

//百度文件上传插件 WebUploaderlet uploader = WebUploader.create({// 选完文件后,是否自动上传。auto: true,// swf文件路径swf: contextPath + '/static/plugin/webuploader/Uploader.swf',pick: {id: '#webUploader',multiple: false},// 文件接收服务端。server: contextPath + '/common/file/shard/upload',// 文件分片上传相关配置chunked: true,chunkSize: 5 * 1024 * 1024, // 分片大小为 5MBchunkRetry: 3, // 上传失败最大重试次数threads: 5, // 同时最大上传线程数});//文件上传临时对象let fileUpload = {idPrefix: '' //文件id前缀, genIdPrefix: function () {this.idPrefix = new Date().getTime() + '_';}, mergeLoading: null //合并文件加载层, lastUploadResponse: null // 最后一次上传返回值, chunks: 0 // 文件分片数, uploadedChunks: 0 // 已上传文件分片数, sumUploadChunk: function () {if (this.chunks > 0) {this.uploadedChunks++;}}, checkResult: function () {if (this.uploadedChunks < this.chunks) {layer.open({title: '系统提示', content: '文件上传失败,请重新上传!', btn: ['我知道了']});}}};// 某个文件开始上传前触发,一个文件只会触发一次uploader.on('uploadStart', function (file) {$('#uploadProgressBar').show();// 生成文件id前缀fileUpload.genIdPrefix();});// 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次uploader.on('uploadBeforeSend', function (object, data, header) {// 重写文件id生成规则data.id = fileUpload.idPrefix + data.name;fileUpload.chunks = data.chunks != null ? data.chunks : 0;});uploader.on('uploadProgress', function (file, percentage) {// 更新进度条let value = Math.round(percentage * 100);element.progress('progressBar', value + '%');if (value == 100) {fileUpload.mergeLoading = layer.load();}});// 获取最后上传成功的文件信息,每个分片文件上传都会回调uploader.on('uploadAccept', function (file, response) {if (response == null || response.code !== '0000') {return;}fileUpload.sumUploadChunk();if (response.data != null && response.data.fileAccessPath != null) {fileUpload.lastUploadResponse = response.data;}});// 文件上传成功时触发uploader.on('uploadSuccess', function (file, response) {console.log('File ' + file.name + ' uploaded successfully.');layer.msg('文件上传成功!');$('#fileName').val(fileUpload.lastUploadResponse.fileOriginalName);$('#fileRelativePath').val(fileUpload.lastUploadResponse.fileRelativePath);})uploader.on('uploadComplete', function (file) {console.log('File' + file.name + 'uploaded complete.');console.log('总分片:' + fileUpload.chunks + ' 已上传:' + fileUpload.uploadedChunks);fileUpload.checkResult();$('#uploadProgressBar').hide();layer.close(fileUpload.mergeLoading);});

其中几个关键的节点的事件回调都提供了,使用起来很方便;其中“uploadProgress”事件实现了上传的实时进度条展示。

后端Controller代码:

    /*** 文件分片上传* * * @param file* @param fileUploadInfoDTO* @return*/@PostMapping(value = "shard/upload")public Layui<FileUploadService.FileBean> uploadFileByShard(@RequestParam("file") MultipartFile file,FileUploadInfoDTO fileUploadInfoDTO) {if (null == fileUploadInfoDTO) {return Layui.error("文件信息为空");}if (null == file || file.getSize() <= 0) {return Layui.error("文件内容为空");}log.info("fileName=[{}]", file.getName());log.info("fileSize=[{}]", file.getSize());log.info("fileShardUpload=[{}]", JSONUtil.toJsonStr(fileUploadInfoDTO));FileUploadService.FileBean fileBean = fileShardUploadService.uploadFileByShard(fileUploadInfoDTO, file);return Layui.success(fileBean);}/*** @Author: XiangPeng* @Date: 2023/12/22 12:01*/@Getter
@Setter
public class FileUploadInfoDTO implements Serializable {private static final long serialVersionUID = -1L;/*** 文件 ID*/private String id;/*** 文件名*/private String name;/*** 文件类型*/private String type;/*** 文件最后修改日期*/private String lastModifiedDate;/*** 文件大小*/private Long size;/*** 分片总数*/private int chunks;/*** 当前分片序号*/private int chunk;
}@Getter
@Setter
public class FileUploadCacheDTO implements Serializable {private static final long serialVersionUID = 1L;/*** 文件 ID*/private String id;/*** 文件名*/private String name;/*** 分片总数*/private int chunks;/*** 当前已上传分片索引*/private List<Integer> uploadedChunkIndex;public FileUploadCacheDTO(FileUploadInfoDTO fileUploadInfoDTO) {this.id = fileUploadInfoDTO.getId();this.name = fileUploadInfoDTO.getName();this.chunks = fileUploadInfoDTO.getChunks();this.uploadedChunkIndex = Lists.newArrayList();}public FileUploadCacheDTO() {}
}

后端Service层代码:

    /*** 文件分片上传* * @param fileUploadInfoDTO* @param file* @return*/public FileBean uploadFileByShard(FileUploadInfoDTO fileUploadInfoDTO, MultipartFile file) {if (fileUploadInfoDTO == null || file == null) {throw new ServiceException("文件上传失败!");}// 无需分片的小文件直接上传if (fileUploadInfoDTO.getChunks() <= 0) {return super.commonUpload(file);}String fileId = fileUploadInfoDTO.getId();// 生成分片临时文件,文件名格式:文件id_分片序号FileBean fileBean = super.commonUpload(fileId + StrUtil.UNDERLINE + fileUploadInfoDTO.getChunk(), file);// redis缓存数据FileUploadCacheDTO fileUploadInfo = null;synchronized (this) {// 查询文件id是否存在,不存在则创建,存在则更新已上传分片数fileUploadInfo = (FileUploadCacheDTO) redisService.get(genRedisKey(fileId));// 第一个分片文件上传if (fileUploadInfo == null) {fileUploadInfo = new FileUploadCacheDTO(fileUploadInfoDTO);}fileUploadInfo.getUploadedChunkIndex().add(fileUploadInfoDTO.getChunk());redisService.set(genRedisKey(fileId), fileUploadInfo);// 判断所有分片文件是否上传完成if ((fileUploadInfo.getUploadedChunkIndex().size()) < fileUploadInfo.getChunks()) {return fileBean;}}// 合并文件return mergeChunks(fileUploadInfo);}/*** 分片文件全部上传完成则合并文件,清除缓存并返回文件地址* * @param fileUploadCache* @return*/private FileBean mergeChunks(FileUploadCacheDTO fileUploadCache) {String mergeFileRelativePath = super.getCommonPath().getFileRelativePath() + fileUploadCache.getId();String mergeFilePath = super.getCommonPath().getBasePath() + mergeFileRelativePath;RandomAccessFile mergedFile = null;File chunkTempFile = null;RandomAccessFile chunkFile = null;try {mergedFile = new RandomAccessFile(mergeFilePath, "rw");for (int i = 0; i < fileUploadCache.getChunks(); i++) {// 读取分片文件chunkTempFile = new File(super.getCommonPath().getFileFullPath() + fileUploadCache.getId() + StrUtil.UNDERLINE + i);byte[] buffer = new byte[1024 * 1024];int bytesRead;chunkFile = new RandomAccessFile(chunkTempFile, "r");// 合并分片文件while ((bytesRead = chunkFile.read(buffer)) != -1) {mergedFile.write(buffer, 0, bytesRead);}chunkFile.close();}} catch (IOException e) {log.error("merge file chunk error, fileId=[{}]", fileUploadCache.getId(), e);} finally {try {if (mergedFile != null) {mergedFile.close();}} catch (IOException e) {}redisService.remove(genRedisKey(fileUploadCache.getId()));// 删除分片文件removeChunkFiles(super.getCommonPath().getFileFullPath(), fileUploadCache);}return FileBean.builder().fileOriginalName(fileUploadCache.getName()).fileRelativePath(mergeFileRelativePath).fileAccessPath(super.getNginxPath() + mergeFileRelativePath).build();}private void removeChunkFiles(String fileFullPathPrefix, FileUploadCacheDTO fileUploadCache) {taskExecutor.execute(() -> {try {// 延迟1秒删除TimeUnit.SECONDS.sleep(1);String fileFullPath;for (int i = 0; i < fileUploadCache.getChunks(); i++) {try {fileFullPath = fileFullPathPrefix + fileUploadCache.getId() + StrUtil.UNDERLINE + i;FileUtil.del(fileFullPath);log.info("file[{}] delete success.", fileFullPath);} catch (Exception e) {log.error("delete temp file error.", e);}}} catch (Exception e) {log.error("delete temp chunk file error.", e);}});}private String genRedisKey(String id) {return FILE_SHARD_UPLOAD_KEY + id;}

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

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

相关文章

mfc100u.dll文件丢失,有五种不同解决方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到mfc100u.dll文件”。那么&#xff0c;mfc100u.dll文件到底是什么&#xff1f;为什么会出现丢失的情况&#xff1f;本文将详细介绍mfc100u.dll文件的作用以及丢失的原因&#xff0c;并…

Anaconda下载与超安装详解教程

1 Anaconda 1.1 简介 Anaconda 指的是一个开源的 Python 发行版本&#xff0c;其包含了 conda、Python等180多个科学包及其依赖项。 因为包含了大量的科学包&#xff0c;Anaconda 的下载文件比较大&#xff08;约 477MB&#xff09;&#xff0c;如果只需要某些包&#xff0c;…

安全与认证Week4

目录 本章需要理解的问题 历年题还出现 Web Security (TLS/SSL) 关于网络 使用网络会受到的威胁 各层安全协议 S/MIME、PGP&#xff08;后面和S/MIME一起出现&#xff09;、Kerberos、TLS/SSL 和 IP/IPSec 分别是&#xff1a; S/MIME (Secure/Multipurpose Internet Mail Exten…

DsPdf:GcPdf 7.0 for NET Crack

DsPdf:GcPdf 7.0 用于全面文档控制的功能丰富的 C# .NET PDF API 库 PDF 文档解决方案&#xff08;DsPdf&#xff0c;以前称为 GcPdf&#xff09;可让您快速、高效地生成文档&#xff0c;且无需依赖任何内存。 在 C# .NET 中生成、加载、编辑和保存 PDF 文档 支持多种语言的全…

安装Unity详细教程(如何获取免费个人版许可证)

文章目录 下载Unity Hub安装Unity Hub登录获取免费个人版许可证安装Unity编辑器卸载Unity编辑器 下载Unity Hub 首先&#xff0c;我们需要到Unity的官网下载Unity Hub&#xff1a;Unity CN 我们可以在Unity Hub上管理我们的编辑器版本和项目文件。 安装Unity Hub 然后安装Un…

科技智慧,产业链全覆盖:河南恩珅德农业的养殖业务优势

河南恩珅德农业以科技智慧和全产业链覆盖的优势&#xff0c;成功打造了一体化的养殖业务模式&#xff0c;为养殖者提供了全面的支持和优越的管理体验。以下是该企业养殖业务的核心优势&#xff1a; 1. 先进科技智慧 河南恩珅德农业充分利用先进的科技手段&#xff0c;引入智能…

PS 2024全新开挂神器Portraiture v4.1.2升级版,功能强大,一键安装永久使用

关于PS修图插件&#xff0c;相信大家都有安装过使用过&#xff0c;而且还不止安装了一款&#xff0c;比如最为经典的DR5.0人像精修插件&#xff0c;Retouch4me11合1插件&#xff0c;Portraiture磨皮插件&#xff0c;这些都是人像精修插件中的领跑者。 其中 Portraiture 刚刚升…

视频剪辑方法:掌握视频嵌套合并技术,释放无限创意

随着数字媒体的普及&#xff0c;视频剪辑已是创意表达的重要技巧。通过掌握视频嵌套合并技术&#xff0c;可以将多个视频片段融合在一起&#xff0c;创造出独特的视觉效果和故事叙述。现在一起看云炫AI智剪批量剪辑视频嵌套合并方法&#xff0c;释放无限创意。 准备视频素材&a…

【Unity】 HTFramework框架(四十七)编辑器日志中使用超链接的技巧

更新日期&#xff1a;2024年1月3日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 日志中使用超链接超链接-网络地址超链接-本地地址超链接-项目资源文件超链接-脚本对象 日志中使用超链接 在编辑器控制台Console中的日志是支持富文本的&…

前端开发个人简历范本(2024最新版-附模板)

前端开发工程师个人简历范本> 年龄 25岁 性别 男 毕业院校 XX大学 张三 学历 邮箱 leeywai-tools.cn 本科 专业 计算机科学与技术 个人梗概 拥有扎实的前端开发技能和丰富的实践经验 善于与团队合作&#xff0c;适应能力强&#xff0c;能够快速融入团队并贡献自…

工业相机如何实现实时和本地Raw格式图像和Bitmap格式图像的保存和相互转换(C#代码,UI界面版)

工业相机如何实现实时和本地Raw图像和Bitmap图像的保存和相互转换&#xff08;C#代码&#xff0c;UI界面版&#xff09; 工业相机图像格式工业相机实现Raw图像和Bitmap图像的保存和转换的技术背景在相机SDK中获取图像转换图像的代码分析工业相机回调函数里保存Bitmap图像数据工…

《动手学深度学习》学习笔记 第7章 现代卷积神经网络

本系列为《动手学深度学习》学习笔记 书籍链接&#xff1a;动手学深度学习 笔记是从第四章开始&#xff0c;前面三章为基础知识&#xff0c;有需要的可以自己去看看 关于本系列笔记&#xff1a; 书里为了让读者更好的理解&#xff0c;有大篇幅的描述性的文字&#xff0c;内容很…

mysql聚簇索引和非聚簇索引

目录 InnoDB引擎MylSAM引擎聚簇索引的优点和缺点参考 聚簇索引和非聚簇索引的区别&#xff1a;叶节点是否存放一整行记录。 聚簇索引:将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据。 非聚簇索引:将数据与索引分开存储&#xff0c;索引结构的叶子节点指向了数据对…

SpringBoot+ShardingSphereJDBC实战(读写分离,分库分表,垂直拆分、水平拆分)附源码

参考&#xff1a;https://www.51cto.com/article/747736.html https://blog.csdn.net/qq_41581588/article/details/126966665 源码地址&#xff1a;gitgitee.com:jackXUYY/springboot-example.git 读写分离测试 我们启用后缀名为dev的配置文件&#xff0c;如下&#xff0c;…

nginx 一、安装与conf浅析

文章目录 一、安装nginxdocker方式安装linux方式安装Ubuntu 或 Debian 系统&#xff1a;CentOS 或 RHEL 系统&#xff1a; macOS 系统&#xff08;使用 Homebrew&#xff09;&#xff1a;Windows 系统&#xff1a; 二、nginx.conf浅析 Nginx&#xff08;发音为“engine-x”&…

椭圆中点算法

原理 椭圆的扫描转换与圆的扫描转换有相似之处&#xff0c;但也有不同&#xff0c;主要区别是椭圆弧上存在改变主位移方向的临界点。瞬时针绘制四分椭圆弧的中点算法&#xff0c;根据对称性可以绘制完整的椭圆。 四分椭圆弧 中心在原点&#xff0c;长半轴为 a a a、短半轴为…

01-03

利用模板类完成顺序表

简易电子琴

#include<reg51.h> //包含51单片机寄存器定义的头文件 sbit P14P1^4; //将P14位定义为P1.4引脚 sbit P15P1^5; //将P15位定义为P1.5引脚 sbit P16P1^6; //将P16位定义为P1.6引脚 sbit P17P1^7; //将P17位定义为P1.7引脚 unsigned char keyval; …

高端大气的在线文档

背景 产品介绍&#xff0c;帮助手册&#xff0c;操作手册&#xff0c;开发说明&#xff0c;个人的简单网站等等&#xff0c;都需要一个在线的文档&#xff0c;特别是开源社区的在线文档也非常需要&#xff0c;开源社区也为此提供了大量的工具。如何找到一款高端大气的&#xf…

解决Golang WriteHeader设置后,Content-Type失效的问题

场景 最近笔者在研究web框架过程中&#xff0c;发现了一个响应类型的问题&#xff0c;困扰许久&#xff0c;原因就是设置了响应状态码后&#xff0c;然后设置响应类型为application/json。在实际请求后&#xff0c;响应类型变成了text/plain; charsetutf-8格式。 问题解决&…