幸运飞艇网站建设,济南设计公司招聘信息,域名服务商排名,wordpress 获取当前位置前言在软件工程里#xff0c;在处理“大”的时候一直是一个难点和难点#xff0c;如并发大、数据量大、文件大#xff0c;对硬件进行升级可以解决一些问题#xff0c;但这并不最聪明的办法#xff0c;而对于老板来说#xff0c;这也不是成本最小的办法。作为开发人员来说…前言在软件工程里在处理“大”的时候一直是一个难点和难点如并发大、数据量大、文件大对硬件进行升级可以解决一些问题但这并不最聪明的办法而对于老板来说这也不是成本最小的办法。作为开发人员来说在面对类似极端的问题时只可智取不可硬刚最大化利用好现有的资源以更加优雅的办法来满足用户多样化的需求。今天的主题也是一个“大”的问题就是大文件如何上传和下载其实在解决这个问题之前有一个问题是绕不过去的什么才是大文件几兆几十兆几百兆这其实是一个极具争议的问题在不同的业务场景下对于大文件的“大”的理解和定义肯定是不同的。但这并不是本文的重点这篇文章的重点是想和大家分享一下大文件如何优雅实现大文件的上传和下载。文章示例环境配置信息jdk版本:1.8开发工具Intellij iDEA 2020.1springboot:2.3.9.RELEASE实现思路对于各种“大”的问题一般都是采用是分而治之的理念进行设计而对于大文件上传来说从这一理念出发具体的方法就是大文件分片和分片的合并。什么是大文件分片呢什么又是合并分片呢如果分片上传过程中个别分片上传失败了需要重新分片上传吗文件上传本身是一个很简单的需求当量级达到一定程度时就变得异常复杂了需要考虑的问题点很多。大文件分片将要上传的大文件按照一定的规则大小将整个文件进行切片即把一个大的数据文件切分成小的数据块然后再按照一定的策略串行或并发进行上传合并分片大文件分片合并是大文件分片的后续当一个大文件被切分成小的数据块上传到服务器时需要把这些小的数据块按照原来的顺序再进行合并还原成原来的大文件分片和合并其实很好理解类似于现实生活中如果想把一架大飞机藏在洞口比较小的山洞里要怎么做呢肯定是先把飞机拆成小的零件然后运输到山洞里面再根据飞机的装配图纸把所有的零件再装好。原理就这么简单。断点续传大文件分片上传的时候各个分片的上传有可能会碰到网络故障而导致一些分片文件上传失败如果网络恢复后可以从上传失败的分片开始上传而直接跳过上传成功的分片文件部分可以节约时间提高上传效率这就是断点上传如下图一个大文件被切分成了n个分片分片上传过程中分片2和分片3因为某种原因上传失败了再次上传的时候会直接跳过所有上传成功的分片直接从失败的分片2和分片3开始上传实现原理1、前端使用百度的WebUploader组件选中待上传的大文件然后计算出文件的md5值2、WebUploader组件开启分片上传后选中的待上传文件会会按照配置好的分片规则进行分片分片文件上传前会计算出分片文件的md5值3、webuploader组件api计算出分片的md5值后会携带文件md5值和分片文件的md5值调用后台接口检验当前分片是否已经上传过若上传过则直接跳过若未上传过则开始分片上传3、前端往后端传输分片文件的过程可以是并发执行这里一定注意传递到后台的分片并不是按照分片的顺序来的后端收到分片文件后会保存分片文件到硬盘、网盘等存储介质上同是持久化分片文件md5值、文件md5值4、待所有的分片上传成功后会触发WebUploader的uploadSuccess事件然后再发起合并分片请求5、后端再次检查所有的分片是否上传完整若上传完整则开始合并所有分片md5消息摘要算法属Hash算法一类主要特点是不可逆相同数据的md5值肯定一样不同数据的md5值不一样对于数据文件不管文件名字是否相同如果数据文件内容相同则文件的md值是相同的webuploader组件提供了文件的md5值的计算方法其计算过程是异步的代码实现大文件分片上传的实现原理实际上比较简单和清晰的那么落到代码实现上还有几个问题需要解决1、webuploader分片上传怎么开启2、文件的md5值如何计算3、文件如合分片4、检验分片是否上传的业务逻辑是什么5、分片上传的的请求在哪里触发6、合并文件分片的请求在哪里触发1、webuploader分片上传怎么开启webuploader的分片上传开启实际上很简单在创建webuploader对象时设置chunked为true即表示开启分片上传chunkSize可以设置分片大小即以多大的体积进行分片chunkRetry可以设置重传次数有的时候由于网络原因分片上传的会失败这里即是失败允许重的次数threads可以设置允许最大由几个进程发起上传请求uploader WebUploader.create({// swf文件路径swf: http://localhost:8080/lib/Uploader.swf,// 分片文件上传接口server: http://localhost:8080/file/upload,// 选择文件的按钮。可选。pick: #picker,fileVal: multipartFile,//后端用来接收上传文件的参数名称chunked: true,//开启分片上传chunkSize: 1024 * 1024 * 10,//设置分片大小chunkRetry: 2,//设置重传次数有的时候由于网络原因分片上传的会失败这里即是失败允许重的次数threads: 3//允许同时最大上传进程数
});2、文件的md5值如何计算文件的md5计算可以引用spark-md5.js据传言是javascript里md5加密计算速度最快的当然在webuploader.js里也有具体的api可以使用引入webuploader.js后调用 WebUploader.Uploader.md5File(...)即可计算文件的md5值这里需要注意的是md5File(...)有三个参数分别是file数据起始索引位置、数据结束索引位置返回的是一个promise对象要想拿到具体的值还要再调用then(function(val){})具体逻辑如下1、当添加完文件后webuploader的fileQueued事件被触发2、fileQueued事件触发的业务逻辑主要是计算出文件的的md5值这里注意是计算出整个文件的md5值而不是一部分关键是md5File(...)方法的后两个参数数据起始索引位置、数据结束索引位置md5的计算过程是异步操作并且文件越大计算用时越长3、deferred的作用就是监控异步计算文件md5值这个异步操作的执行状态4、文件md5值计算完成后更新状态为已完成这时 deferred.done()会触发5、文件md5值计算完成更新md5计算标志位为true这时再点击开始上传按钮时就会再有弹窗提示md5计算中...请稍侯webuploader内部有很多种command其中有一个叫before-send-file,也可以在文件上传前会触发此时还没有开始分片可以用来做文件整体的md5计算但是不建议用这个因为before-send-file的触发时机是要晚于fileQueued事件的before-send-file是在点击开始上传按钮执行uploader.upload()后才会触发而fileQueued事件是在选择文件后立刻触发不用等到点击开始上传按钮/*** 当有文件被添加进队列后触发* 主要逻辑1、文件被添加到队列后开始计算文件的md5值* 2、md5的计算过程是异步操作并且文件越大计算用时越长* 3、变量md5FlagMap是文件md5值计算的标志位计算完成后设置当前文件的md5Flag为true*///md5FlagMap用于存储文件md5计算完成的标志位多个文件时分别设置标志位key是文件名value是true或false
var md5FlagMap new Map();
uploader.on(fileQueued, function (file) {md5FlagMap.set(file.name, false);//文件md5值计算的标志位默认为falsevar deferred WebUploader.Deferred();//deferred用于监控异步计算文件md5值这个异步操作的执行状态uploader.md5File(file, 0, file.size - 1).then(function (fileMd5) {file.wholeMd5 fileMd5;file_md5 fileMd5;deferred.resolve(file.name);//文件md5值计算完成后更新状态为已完成这时 deferred.done()会触发})//文件越大文件的md5值计算用时越长因此md5的计算搞成异步执行是合理的如果异步执行比较慢的话会顺序执行到这里$(#thelist).append(div id file.id classitem h4 classinfo file.name /h4 p classstate开始计算大文件的md5......br//p /div)//文件的md5计算完成会触发这里的回调函数deferred.done(function (name) {md5FlagMap.set(name, true);//更新md5计算标志位为true$(# file.id).find(p.state).append(大文件的md5计算完成br/);})return deferred.promise();
})3、文件如何分片webuploader对象中配置好相关的开启分片设置参数后当有文件被选中添加后webuploader会帮你对文件按配置参数进行分片在分片文件发送到后台之前webuploader内部另一个commandbefore-send会触发before-send的触发时机上分片上传之前可以用作在分片发送到之前计算出分片文件的md5调用后台接口做分片是否已经上传的验证如果分片已经上传成功了直接跳过不会再调用分片的上传接口如果分片未上传则会把分片的md5值赋给分片block上用于下次上传时分片是否已经上传的校验WebUploader.Uploader.register({add-file: addFile,before-send-file: beforeSendFile,before-send: beforeSend,after-send-file: afterSendFile
}, {addFile: function (file) {console.log(1, file)},beforeSendFile: function (file) {console.log(2, file)},beforeSend: function (block) {console.log(3)var file block.file;var deferred WebUploader.Base.Deferred();(new WebUploader.Uploader()).md5File(file, block.start, block.end).then(function (value) {$.ajax({url: http://localhost:8080/file/check,//检查当前分片是否已经上传method: post,data: {chunkMd5: value, fileMd5: file_md5,chunk:block.chunk},success: function (res) {if (res) {deferred.reject();} else {deferred.resolve(value);}}});})deferred.done(function (value) {console.log(分片md5:, value)block.chunkMd5 value;})return deferred;},afterSendFile: function (file) {console.log(4, file)}
})4、检验分片是否上传的业务逻辑是什么在webuploader内部的一个commandbefore-send已经完成了分片文件的md5计算以及请求后台接口来校验当前分片文件是否已经上传如果已上传那么会直接跳过当前分片上传接口的调用uploadBeforeSend事件也不会再触发当某个分片文件在发送前触发主要用来询问是否要添加附带参数大文件在开起分片上传的前提下此事件可能会触发多次如果未上传则uploadBeforeSend事件会触发携带一些分片的参数信息发起分片上传请求// 分片模式下当文件的分块在发送前触发
uploader.on(uploadBeforeSend, function (block, data) {var file block.file;console.log(uploadBeforeSend:, block.chunkMd5)var file block.file;data.originalFilename file.originalFilename;data.md5Value file.wholeMd5;data.start block.start;data.end block.end;data.chunk block.chunk;data.chunks block.chunks;data.chunkMd5 block.chunkMd5;
});在后台检验分片是否上传的逻辑很简单1、在分片文件上传接口中这里使用redis缓存了分片md5和文件md5以及其他相关的一些分片信息用到hash数据结构key为文件的md5hashkey是“chunk_md5_”分片索引value就是分片文件的md5值这里也可以使用数据库来存储2、接口被调用的时候根据前端传过来的当前分片的索引位置取出分片的md5与前端传过来的分片文件md5进行比较如果相同则说明当前分片已经上传成功如果不相同则说明未上传过PostMapping(/check)
public boolean check(String fileMd5,String chunk,String chunkMd5) {Object o redisTemplate.opsForHash().get(fileMd5, chunk_md5_chunk);if (chunkMd5.equals(o)) {return true;}return false;
}
/*** 分片上传接口* param request* param multipartFile* return* throws IOException*/
PostMapping(/upload)
public String upload(HttpServletRequest request, MultipartFile multipartFile) throws IOException {log.info(分片上传....);MapString, String requestParam this.doRequestParam(request);String md5Value requestParam.get(md5Value);//整体文件的md5值String chunkIndex requestParam.get(chunk);//分片在所有分片文件中索引位置Object chunkLocation redisTemplate.opsForHash().get(md5Value, chunk_md5_ chunkIndex);if (chunkLocation ! null) {log.info(分片已上传md5Value_chunkIndex);return success;}String end requestParam.get(end);//当前分片在整个数据文件中的结束位置String chunks requestParam.get(chunks);//文件总共被分了多少片String fileSize requestParam.get(size);//文件大小String chunkMd5 requestParam.get(chunkMd5);//分片文件的md5值String userDir System.getProperty(user.dir);String chunkFilePath userDir File.separator chunkMd5 _ chunkIndex;File file new File(chunkFilePath);multipartFile.transferTo(file);MapString,String mapnew HashMap();map.put(chunk_location_chunkIndex,chunkFilePath);//分片存储路径map.put( chunk_end_ chunkIndex, end);map.put(file_size,fileSize);map.put(file_chunks,chunks);map.put(chunk_md5_chunkIndex,chunkMd5);redisTemplate.opsForHash().putAll(md5Value,map);return success;
}5、分片上传的的请求在哪里触发当选择文件后就开始计算整体文件的md5值了未计算完成前点击开始上传按钮会直接弹出“md5计算中...请稍侯”考虑到多文件上传的情况这里使用了map对象md5FlagMap来存储每个文件的标志key是文件名称value是true或false表示分片文件md5文件是否计算完成如果不想用按钮来触发上传WebUploader有一个参数是auto默认是false可以改为true选中文件后自动开始上传
//开始上传按钮被点击时触发
$(#ctlBtn).click(function () {console.log(ctlBtn)//md5FlagMap存储有文件md5计算的标志位// 同时上传多个文件时上传前要判断一下文件的md5是否计算完成// 如果有未计算完成的则继续等待计算结果//文件上传标志位如果多个文件有一个没有完成md5计算则不能开始上传这里在实际业务中可以更换成其他交互样式酌情优化为哪个文件的md5计算完成则开始哪个文件的上传var uploadFloag true;md5FlagMap.forEach(function (value, key) {if (!value) {uploadFloag false;alert(md5计算中...请稍侯)//文件md5计算未完成会弹出弹窗提示}})if (uploadFloag) {uploader.upload();//文件md5计算完成后开始分片上传}
})