SpringBoot 采用RestTemplate实现文件下载

1,192 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

背景

最近项目中有个需求通过浏览器实现远程文件下载功能,关于浏览器下载实现方案有两种:

  • 1.下载远程文件到本地服务器,浏览器在从本地服务器下载。
  • 2.采用文件流,浏览器直接下载远程文件。

具体实现

方案1:下载远程文件到本地

核心类实现

   @GetMapping("downloadFileToLocal")
    public void downloadFileToLocal(String url, String targetDir,
            Map<String, String> params)
    {
        //当前时间
        Instant startTime = Instant.now();
        String completeUrl = addGetQueryParam(url, params);
        logger.info("download file to local");
        try
        {
            String path = createDownloadDir(url, targetDir);
            logger.info("donwload file dest path:{}",path);
            ResponseEntity<byte[]> rsp = restTemplate.getForEntity(completeUrl, byte[].class);
            Files.write(Paths.get(path), Objects.requireNonNull(rsp.getBody(),
                    "未获取到下载文件"));
        }
        catch (IOException e)
        {
            logger.error("下载文件失败:", e);
        }
        
        logger.info("下载文件完成总耗时:{}",
                ChronoUnit.MILLIS.between(startTime, Instant.now()));
    } 
  

相关工具类

// 构造参数
     private String addGetQueryParam(String url, Map<String, String> params)
    {
        UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
                .fromHttpUrl(url);
        if (!CollectionUtils.isEmpty(params))
        {
            for (Map.Entry<String, ?> varEntry : params.entrySet())
            {
                uriComponentsBuilder.queryParam(varEntry.getKey(),
                        varEntry.getValue());
            }
        }
        return uriComponentsBuilder.build().encode().toString();
    }

     //创建或获取下载文件夹的路径 
    public String createDownloadDir(String url, String targetDir)
            throws IOException
    {
        String filename = url.substring(url.lastIndexOf("/") + 1);
        int i = 0;
        if ((i = url.indexOf("?")) != -1)
        {
            filename = filename.substring(0, i);
        }
        
        if (!Files.exists(Paths.get(targetDir)))
        {
            Files.createDirectories(Paths.get(targetDir));
        }
        
        String destPath= targetDir.endsWith("/") ? targetDir + filename : targetDir + "/"
                + filename;
        File file =new File(destPath);
        if(file.exists())
        {
           logger.error("file path:{} is exist",file.getPath());
           file.delete();    
        }
        
        return destPath;
    }

说明:实现文件下载的方式有多种,本文采用的是RestTemplate的方式进行远程文件下载,调用接口返回文件字节流下载到本地,如果对于RestTemplate不熟悉的朋友,可以参考我之前的文章。

前端实现

前端实现比较简单,传入相关参数提交form表单即可。

 function downloadLogs(oid)
    {
            var frm = $("#logExportFrom");
            $("#oid_exp").val(oid);
            var url = xxx";
            frm.attr("action",  url);
            frm.submit();
    }

小结

  • 1.此方案需要先将文件下载到本地,然后浏览器在直接下载,经过多次请求,时间消耗长。
  • 2.文件存储在本地占用了本地硬盘空间,浪费你硬盘资源。
  • 3.如果文件比较大或者文件下载并发量比较大,容易造成内存的大量占用,从而降低应用的运行效率,此方案适合于下载文件大小比较小的场景。

方案2:浏览器直接下载

核心类实现

 @GetMapping("downloadBigFile")
 public void downloadBigFile(HttpServletResponse response)
  {
        logger.info("Down load file start");
        try
        {
            //当前时间
            Instant now = Instant.now();
            String completeUrl = "http://xxx.com/image/test.png";
            try
            {                
                // 定义请求头的接收类型
                RequestCallback requestCallback = request -> request.getHeaders()
                        .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM,
                                        MediaType.ALL));
                
                // 针对大文件,采用的是 ResponseExtractor方式
                ResponseExtractor<ResponseEntity<Void>> responseExtractor = new ResponseExtractor<ResponseEntity<Void>>() 
                {  
                    @Override  
                    public ResponseEntity<Void> extractData(ClientHttpResponse clientHttpResponse) throws IOException {  
                        String fileName=getFileName(completeUrl);
                        response.setContentType("multipart/form-data");
                        response.setHeader("Content-Disposition", "attachment; filename=\""
                                + fileName + "\"");
                        InputStream ins = clientHttpResponse.getBody();
                        //拷贝文件流
                        StreamUtils.copy(ins, response.getOutputStream());  
                        return null;
                    }  
                };  
                
                restTemplate.execute(completeUrl, HttpMethod.GET,
                        requestCallback, responseExtractor);
            }
            catch (Exception e)
            {
                logger.error("[下载文件] 写入失败:", e);
            }
            
            logger.info("[下载文件] 完成,耗时:{}",
                    ChronoUnit.MILLIS.between(now, Instant.now()));
        }
        catch (Exception e)
        {
            logger.error("Down load file Error", e);
        }
    }

特别说明:

  • 1.设置了请求头APPLICATION_OCTET_STREAM,表示以流的形式进行数据加载
  • 2.采用RequestCallback和文件加流拷贝的方式,不是将全部文件的内容加载到磁盘文件中,而是每次只加载一部分内容,提升文件下载的效率,此方案适合大文件下载。

总结

本文讲解了SpringBoot 采用RestTemplate实现文件下载,实现的方案有几种,我们需要结合具体的业务场景进行分析,选择合适的方案。