Java实现批量下载多文件(夹)压缩包(zip)续
2022/7/8 14:22:58
本文主要是介绍Java实现批量下载多文件(夹)压缩包(zip)续,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
问题现状
在Java实现批量下载多文件(夹)压缩包(zip)篇幅中通过在服务器上创建临时文件,借助hutool
的ZipUtil
将文件(夹)压缩写入至response
的OutputStream
,实现了多文件(夹)的压缩包下载。其大致流程图可大致描述为:
经过分析和验证上述方式实现的批量下载存在着下列问题
- 1.文件非常大的情形下,步骤1.2. 4将文件先下载到服务器带来了额外的耗时操作,对于用户来说下载文件只需要将文件从文件系统直接写入响应即可。
- 2.由于请求类型为
POST
,所以浏览器不能自动下载文件,步骤5即使将流已写入响应,但是浏览器并不能打开下载页面,需要前端接收到所有Blob
才能打开下载,用户体验极差,易给用户造成批量下载没反应的错觉。
是否存在一种方案,可以将批量下载接口转为GET请求,且可以将文件(夹)直接写入到response的OutputStream?
解决思路
1.首先由于批量下载接口batchDownloadFile
的参数类型为List<DownloadFileParam>
为复杂参数,故无法直接将POST
请求修改为GET;这时候该怎么办呢?
架构思维中,比较常用的一种思路便是分层架构!我门可以将批量下载接口拆为两个接口
通过POST
方式保存下载参数List<DownloadFileParam>
到Redis
,并返回Redis
中该下载参数对应唯一标示key
的接口getBatchDownloadKey
如下
@PostMapping(value = "/getBatchDownloadKey") public String getBatchDownloadKey(@RequestBody List<DownloadFileParam> params)...
根据返回下载参数唯一标示Key
进行批量下载的GET
接口batchDownloadFile
接口,定义如下
@GetMapping(value = "/batchDownloadFile", produces = "application/octet-stream) public void batchDownloadFile(@RequestParam("downLoadKey") String downLoadKey)
2.Java
提供了类ZipArchiveOutputStream
允许我们可以直接将带有目录结构的文件压缩到OutputStream
,其使用的伪代码如下
ZipArchiveOutputStream zous = new ZipArchiveOutputStream(response.getOutputStream()); //file为带有目录结构的文件比如:/文件夹/子文件夹/文件.txt ArchiveEntry entry = new ZipArchiveEntry(file); InputStream inputStream = file.getInputStream(); zous.putArchiveEntry(entry); try { int len; byte[] bytes = new byte[1024]; //inputStream为文件流 while ((len = inputStream.read(bytes)) != -1) { zous.write(bytes, 0, len); } zous.closeArchiveEntry(); zous.flush(); } catch (Exception e) { e.printStackTrace(); } finally { IoUtil.close(inputStream); }
这样我们就可以避免将文件下载到服务器带来的性能消耗。
3.整个过程的流程图如下
代码实现
保存下载参数请求getBatchDownloadKey
@PostMapping(value = "/getBatchDownloadKey") public String getBatchDownloadKey(@RequestBody List<DownloadFileParam> params) throws Exception { try { String key = IdGenerator.newShortId(); redisTemplate.opsForValue().set(key, JSONObject.toJSONString(params), 60, TimeUnit.SECONDS); return key; } catch (Exception e) { logger.error("getBatchDownloadKey error params={}", params, e); throw e; } }
根据Key
下载文件的接口定义batchDownloadFile
@GetMapping(value = "/pass/batchDownloadFile", produces = "application/octet-stream;charset=UTF-8") public void batchDownloadFile(@RequestParam("downLoadKey") String downLoadKey,@RequestParam("token") String token) throws Exception { try { fileService.batchDownloadFile(downLoadKey, getRequest(), getResponse(),token); } catch (Exception e) { logger.error("batchDownloadFile error params={}", downLoadKey, e); throw e; } }
fileService.batchDownloadFile
@Override public void batchDownloadFile(String key, HttpServletRequest request, HttpServletResponse response,String token) throws Exception { if (redisUtil.get(token) != null) { UserSession userSession = JSONObject.parseObject(redisUtil.get(token).toString(), UserSession.class); //如果存在session或者token是存在于project_token配置的值,通过认证 if (userSession != null) { Object result = redisTemplate.opsForValue().get(key); if (result == null) { throw new ParamInvalidException("无效的批量下载参数key"); } List<DownloadFileParam> params = JSONArray.parseArray(result.toString(), DownloadFileParam.class); //创建虚拟文件夹 String mockFileName = IdGenerator.newShortId(); String tmpDir = ""; FileUtil.mkdir(tmpDir); ZipArchiveOutputStream zous = null; try { //设置响应 response.reset(); response.setContentType("application/octet-stream"); response.setHeader("Accept-Ranges", "bytes"); String fileName = URLEncoder.encode(DateFormatUtil.formatDate(DateFormatUtil.yyyyMMdd, <span class="hljs-keyword">new</span> Date()) + <span class="hljs-string">".zip"</span>, <span class="hljs-string">"UTF-8"</span>).replaceAll(<span class="hljs-string">"\\+"</span>, <span class="hljs-string">"%20"</span>); response.setHeader(<span class="hljs-string">"Content-disposition"</span>, <span class="hljs-string">"attachment;filename*=utf-8''"</span> + fileName); response.setHeader(<span class="hljs-string">"Access-Control-Expose-Headers"</span>, <span class="hljs-string">"Content-Disposition"</span>); <span class="hljs-comment">//参数组装</span> zous = <span class="hljs-keyword">new</span> ZipArchiveOutputStream(response.getOutputStream()); zous.setUseZip64(Zip64Mode.AsNeeded); DownloadFileParam downloadFileParam = <span class="hljs-keyword">new</span> DownloadFileParam(); downloadFileParam.setFileName(mockFileName); downloadFileParam.setIsFolder(<span class="hljs-number">1</span>); downloadFileParam.setChilds(params); <span class="hljs-comment">//递归文件流添加zip</span> downloadFileToServer(tmpDir, downloadFileParam, zous); zous.closeArchiveEntry(); } <span class="hljs-keyword">finally</span> { zous.close(); } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ResultException(<span class="hljs-string">"服务内部错误"</span>); } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ResultException(<span class="hljs-string">"用户已下线,请重新登录"</span>); } }
downloadFileToServer
private void downloadFileToServer(String tmpDir, DownloadFileParam downloadFileParam, ZipArchiveOutputStream zous) throws Exception { List<DownloadFileParam> childs = downloadFileParam.getChilds(); if (EmptyUtils.isNotEmpty(childs)) { final String finalPath = tmpDir; childs.stream().forEach(dwp -> dwp.setFile(EmptyUtils.isNotEmpty(finalPath) ? finalPath + File.separator + dwp.getFileName() : dwp.getFileName())); for (int i = 0; i < childs.size(); i++) { DownloadFileParam param = childs.get(i); if (param.getIsFolder() == 0) { FileInfo fileInfo = fileInfoDao.findById(param.getFileId()).orElseThrow(() -> new DataNotFoundException("文件不存在或已被删除!")); List<GridFsResource> gridFSFileList = fileChunkDao.findAll(fileInfo.getFileMd5()); ArchiveEntry entry = new ZipArchiveEntry(param.getFile()); zous.putArchiveEntry(entry); if (gridFSFileList != null && gridFSFileList.size() > 0) { try { for (GridFsResource gridFSFile : gridFSFileList) { InputStream inputStream = gridFSFile.getInputStream(); try { int len; byte[] bytes = new byte[1024]; while ((len = inputStream.read(bytes)) != -1) { zous.write(bytes, 0, len); } } finally { IoUtil.close(inputStream); } } zous.closeArchiveEntry(); zous.flush(); } catch (Exception e) { e.printStackTrace(); } } } //递归下载文件到压缩流 downloadFileToServer(tmpDir, param, zous); } } }
方案总结
一般情况下下载接口最好用GET方式,浏览器会自动开始下载,除此之外,接口参数与下载接口参数间通过添加中间层解藕帮我们解决了POST下载转化为GET下载方式的问题,分层的架构思想是软件架构最常用的一种方式,再解决工作实际问题的过程中,我们要善于变通采用该方式。
来源:https://juejin.cn/post/7020398932630962207这篇关于Java实现批量下载多文件(夹)压缩包(zip)续的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南