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.hqwc.cn/news/318498.html

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

相关文章

载波相位测量--基本概念、基本原理、观测方程

伪距单点定位精度较低&#xff0c;但是我们平时导航定位时好像精度没有那么差&#xff0c;难道还有其它的卫星定位技术吗&#xff1f; 1.载波相位测量的基本概念 载波相位测量 把载波当做测距信号进行卫星定位的技术相位观测值 载波相位测量的观测值具体定义&#xff1a;接收…

linux 内核链表操作

操作系统内核, 如同其他程序, 常常需要维护数据结构的列表. 有时, Linux 内核已经同 时有几个列表实现. 为减少复制代码的数量, 内核开发者已经创建了一个标准环形的, 双 链表; 鼓励需要操作列表的人使用这个设施. 当使用链表接口时, 你应当一直记住列表函数不做加锁. 如果你的…

Vue 中的 ref 与 reactive:让你的应用更具响应性(中)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

B01、运行时数据区概述-03

1、什么是内存 内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。 2、线程共享和…

深入了解SoapUI - 从入门到精通的指南

SoapUI 是一个免费的开源测试工具&#xff0c;它能够通过 soap/http 协议来检查、调用和实现 Web Service 的功能、负载和符合性测试。 除了能够独立地使用作为一个测试软件外&#xff0c;SoapUI 还可以通过插件集成到 Eclipse、maven2.X、Netbeans 和 intellij 等开发环境中。…

QProgressDialog用法及结合QThread用法,四种线程使用

1 QProgressDialog概述 QProgressDialog类提供耗时操作的进度条。 进度对话框用于向用户指示操作将花费多长时间&#xff0c;并演示应用程序没有冻结。此外&#xff0c;QPorgressDialog还可以给用户一个中止操作的机会。 进度对话框的一个常见问题是很难知道何时使用它们;操作…

如何制作可预约的上门维修服务小程序?

上门维修服务已经成为人们日常生活中不可或缺的一部分。为了满足这一需求&#xff0c;我们学习如何无经验自己制作上门维修服务小程序。 首先&#xff0c;打开乔拓云-门店系统的后台&#xff0c;可以看到有很多各行各业的模版。这些模版涵盖了各种行业&#xff0c;包括家电维修…

宏晶微 MS9125 USB 投屏控制芯片 VGAHDM输出 全新原装

1.基本介绍 MS9125 是一款 USB 单芯片投屏器,内部集成了 USB2.0 控制器和数据收发模块、视频 DAC、HDMI 接口和音视频处理模块&#xff0c;MS9125 可以通过 USB 接口显示或者扩展 PC、智能手机、平板电脑的显示信息到更大尺寸的显示设备上&#xff0c;支持 VGA 和 HDMI 视频接…

ConcurrentHashMap源码学习

实现接口 ConcurrentMap&#xff08;Map的基础方法&#xff09;、Serializable(序列化) 基础属性 最大容量&#xff1a;2^30 默认容量&#xff1a;16 常用方法 PUT 调用PutVal方法进行插入。 判断key或value是否为空&#xff1a; 是&#xff1a;抛出空指针一场 否&#xff…

CSS 顶部位置翻转动画

<template><div class"container" mouseenter"startAnimation" mouseleave"stopAnimation"><!-- 旋方块 --><div class"box" :class"{ rotate-hor-top: isAnimating }"><!-- 元素内容 --><…

安装tensorrt环境在linux上

在linux上输入命令 bash cat /etc/os-release 命令查看系统版本 nvidia-smi命令后有内容弹出而没有报错,表明系统中安装了NVIDIA显卡驱动&#xff0c;并且该命令成功地显示了有关NVIDIA GPU的信息。 输入nvcc -V并且看到输出时,这表明您的系统中已经安装了NVIDIA的CUDA工具…

38个城市平均薪酬超1万元;丁俊晖6-5绝杀世界第一奥沙利文夺冠;原钉钉副总裁创业杀入AI Agent赛道

投融资 • 原钉钉副总裁创业杀入AI Agent赛道&#xff0c;AI 公司“斑头雁智能科技”获亿元融资• 荣盛石化与沙特阿美拟再相互收购下属公司股权• 斯年智驾完成B轮融资• 欧冶半导体完成A3及A4轮融资• 耀途天使轮项目旗芯微完成数亿元新一轮融资• 野牛王获近千万元天使轮融…