关于SpringBoot大文件RestTemplate下载解决方案

近期基于项目上使用到的RestTemplate下载文件流,遇到1G以上的大文件,下载需要3-4分钟,因为调用API接口没有做分片与多线程, 文件流全部采用同步方式加载,性能很慢。最近结合网上案例及自己总结,写了一个分片下载tuling/fileServer项目: 1.包含同步下载文件流在浏览器加载输出相关代码; 2.包含分片多线程下载分片文件及合并文件相关代码;

另外在DownloadThread项目中使用代码完成了一个远程RestUrl请求去获取一个远端资源大文件进行多线程分片下载 到本地的一个案例,可以下载一些诸如.mp4/.avi等视频类大文件。相关代码也一并打包上传。

同步下载,支持分片下载Range主要代码:

@Controller
public class DownLoadController {
    private static final String UTF8 = "UTF-8";
    @RequestMapping("/download")
    public void downLoadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
        File file = new File("D:\\DevTools\\ideaIU-2021.1.3.exe");
        response.setCharacterEncoding(UTF8);
        InputStream is = null;
        OutputStream os = null;
        try {
            // 分片下载 Range表示方式 bytes=100-1000  100-
            long fSize = file.length();
            response.setContentType("application/x-download");
            String fileName = URLEncoder.encode(file.getName(), UTF8);
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            // 支持分片下载
            response.setHeader("Accept-Range", "bytes");
            response.setHeader("fSize", String.valueOf(fSize));
            response.setHeader("fName", fileName);

            long pos = 0, last = fSize - 1, sum = 0;
            if (null != request.getHeader("Range")) {
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                String numberRange = request.getHeader("Range").replaceAll("bytes=", "");
                String[] strRange = numberRange.split("-");
                if (strRange.length == 2) {
                    pos = Long.parseLong(strRange[0].trim());
                    last = Long.parseLong(strRange[1].trim());
                    if (last > fSize-1) {
                        last = fSize - 1;
                    }
                } else {
                    pos = Long.parseLong(numberRange.replaceAll("-", "").trim());
                }
            }
            long rangeLength = last - pos + 1;
            String contentRange = new StringBuffer("bytes").append(pos).append("-").append(last).append("/").append(fSize).toString();
            response.setHeader("Content-Range", contentRange);
            response.setHeader("Content-Length", String.valueOf(rangeLength));

            os = new BufferedOutputStream(response.getOutputStream());
            is = new BufferedInputStream(new FileInputStream(file));
            is.skip(pos);
            byte[] buffer = new byte[1024];
            int length = 0;
            while (sum < rangeLength) {
                int readLength = (int) (rangeLength - sum);
                length = is.read(buffer, 0, (rangeLength - sum) <= buffer.length ? readLength : buffer.length);
                sum += length;
                os.write(buffer,0, length);
            }
            System.out.println("下载完成");
        }finally {
            if (is != null){
                is.close();
            }
            if (os != null){
                os.close();
            }
        }
    }
}

多线程分片下载分片文件,下载完成之后合并分片主要代码:

@RestController
public class DownloadClient {
    private static final Logger LOGGER = LoggerFactory.getLogger(DownloadClient.class);
    private final static long PER_PAGE = 1024L * 1024L * 50L;
    private final static String DOWN_PATH = "F:\\fileItem";
    ExecutorService taskExecutor = Executors.newFixedThreadPool(10);

    @RequestMapping("/downloadFile")
    public String downloadFile() {
        // 探测下载
        FileInfo fileInfo = download(0, 10, -1, null);
        if (fileInfo != null) {
            long pages =  fileInfo.fSize / PER_PAGE;
            for (long i = 0; i <= pages; i++) {
                Future<FileInfo> future = taskExecutor.submit(new DownloadThread(i * PER_PAGE, (i + 1) * PER_PAGE - 1, i, fileInfo.fName));
                if (!future.isCancelled()) {
                    try {
                        fileInfo = future.get();
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }
            return System.getProperty("user.home") + "\\Downloads\\" + fileInfo.fName;
        }
        return null;
    }

    class FileInfo {
        long fSize;
        String fName;

        public FileInfo(long fSize, String fName) {
            this.fSize = fSize;
            this.fName = fName;
        }
    }

    /**
     * 根据开始位置/结束位置
     * 分片下载文件,临时存储文件分片
     * 文件大小=结束位置-开始位置
     *
     * @return
     */
    private FileInfo download(long start, long end, long page, String fName) {
        File dir = new File(DOWN_PATH);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        // 断点下载
        File file = new File(DOWN_PATH, page + "-" + fName);
        if (file.exists() && page != -1 && file.length() == PER_PAGE) {
            return null;
        }
        try {
            HttpClient client = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/download");
            httpGet.setHeader("Range", "bytes=" + start + "-" + end);
            HttpResponse response = client.execute(httpGet);
            String fSize = response.getFirstHeader("fSize").getValue();
            fName = URLDecoder.decode(response.getFirstHeader("fName").getValue(), "UTF-8");
            HttpEntity entity = response.getEntity();
            InputStream is = entity.getContent();
            FileOutputStream fos = new FileOutputStream(file);
            byte[] buffer = new byte[1024];
            int ch;
            while ((ch = is.read(buffer)) != -1) {
                fos.write(buffer, 0, ch);
            }
            is.close();
            fos.flush();
            fos.close();
            // 最后一个分片
            if (end - Long.parseLong(fSize) > 0) {
                // 开始合并文件
                mergeFile(fName, page);
            }

            return new FileInfo(Long.parseLong(fSize), fName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void mergeFile(String fName, long page) {
        File file = new File(DOWN_PATH, fName);
        try {
            BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
            for (long i = 0; i <= page; i++) {
                File tempFile = new File(DOWN_PATH, i + "-" + fName);
                while (!file.exists() || (i != page && tempFile.length() < PER_PAGE)) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                byte[] bytes = FileUtils.readFileToByteArray(tempFile);
                os.write(bytes);
                os.flush();
                tempFile.delete();
            }
            File testFile = new File(DOWN_PATH, -1 + "-null");
            testFile.delete();
            os.flush();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取远程文件尺寸
     */
    private long getRemoteFileSize(String remoteFileUrl) throws IOException {
        long fileSize = 0;
        HttpURLConnection httpConnection = (HttpURLConnection) new URL(remoteFileUrl).openConnection();
        //使用HEAD方法
        httpConnection.setRequestMethod("HEAD");
        int responseCode = httpConnection.getResponseCode();
        if (responseCode >= 400) {
            LOGGER.debug("Web服务器响应错误!");
            return 0;
        }
        String sHeader;
        for (int i = 1;; i++) {
            sHeader = httpConnection.getHeaderFieldKey(i);
            if (sHeader != null && sHeader.equals("Content-Length")) {
                LOGGER.debug("文件大小ContentLength:" + httpConnection.getContentLength());
                fileSize = Long.parseLong(httpConnection.getHeaderField(sHeader));
                break;
            }
        }
        return fileSize;
    }

    class DownloadThread implements Callable<FileInfo> {
        long start;
        long end;
        long page;
        String fName;

        public DownloadThread(long start, long end, long page, String fName) {
            this.start = start;
            this.end = end;
            this.page = page;
            this.fName = fName;
        }

        @Override
        public FileInfo call() {
            return download(start, end, page, fName);
        }
    }
}

代码都在本地亲测(已修复Bug)可用,目前比较欠缺的是没有实现在分片下载时对应浏览器进行下载展示,需要暂存在本地磁盘目录。 目前将代码开源,希望能有更好解决方案的Coder Fork支持!也欢迎Star捧场。

博文参考了图灵学院相关的分片下载案例教程,并修改了部分代码实现:

WebUploader--基于SpringBoot搭建,Java文件上传下载高阶实战

本文代码已上传至GitHub:

BurstDownload

到此这篇关于SpringBoot大文件RestTemplate下载解决方案的文章就介绍到这了,更多相关SpringBoot RestTemplate下载内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解SpringBoot中RestTemplate的几种实现

    RestTemplate的多种实现 使用JDK默认的http library 使用Apache提供的httpclient 使用Okhttp3 @Configuration public class RestConfig { @Bean public RestTemplate restTemplate(){ RestTemplate restTemplate = new RestTemplate(); return restTemplate; } @Bean("urlConnection"

  • Springboot集成restTemplate过程详解

    一restTemplate简介 restTemplate底层是基于HttpURLConnection实现的restful风格的接口调用,类似于webservice,rpc远程调用,但其工作模式更加轻量级,方便于rest请求之间的调用,完成数据之间的交互,在springCloud之中也有一席之地.大致调用过程如下图 二restTemplate常用方法列表 forObeject跟forEntity有什么区别呢?主要的区别是forEntity的功能更加强大一些,其返回值是一个ResponseEntit

  • SpringBoot RestTemplate GET POST请求的实例讲解

    一)RestTemplate简介 RestTemplate是HTTP客户端库提供了一个更高水平的API.主要用于Rest服务调用. RestTemplate方法: 方法组 描述 getForObject 通过GET检索表示形式. getForEntity ResponseEntity通过使用GET 检索(即状态,标头和正文). headForHeaders 通过使用HEAD检索资源的所有标头. postForLocation 通过使用POST创建新资源,并Location从响应中返回标头. po

  • 详解SpringBoot通过restTemplate实现消费服务

    一.RestTemplate说明 RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率.前面的博客中http://www.jb51.net/article/132885.htm,已经使用Jersey客户端来实现了消费spring boot的Restful服务,接下来,我们使用RestTemplate来消费前面示例中的Restful服务,前面的示例: springboot整合H2内存

  • Springboot RestTemplate 简单使用解析

    前言 spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可. 相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式.该类主要用到的函数有:exchange.getForEntity.postForEntity等.我主要用的是后面两个函数,来执行发送get跟post请求. 首先是RestTemplat

  • 关于SpringBoot大文件RestTemplate下载解决方案

    近期基于项目上使用到的RestTemplate下载文件流,遇到1G以上的大文件,下载需要3-4分钟,因为调用API接口没有做分片与多线程, 文件流全部采用同步方式加载,性能很慢.最近结合网上案例及自己总结,写了一个分片下载tuling/fileServer项目: 1.包含同步下载文件流在浏览器加载输出相关代码: 2.包含分片多线程下载分片文件及合并文件相关代码: 另外在DownloadThread项目中使用代码完成了一个远程RestUrl请求去获取一个远端资源大文件进行多线程分片下载 到本地的一

  • asp.net大文件上传解决方案实例代码

    以ASP.NET Core WebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API ,包括文件的上传和下载. 准备文件上传的API #region 文件上传 可以带参数 [HttpPost("upload")] public JsonResult uploadProject(IFormFile file, string userId) { if (file != null) { var fileDir = "D:\\aaa"

  • JavaScript 中如何实现大文件并行下载

    目录 一.HTTP 范围请求 1.1 Range 语法 二.如何实现大文件下载 2.1 定义辅助函数 2.2 大文件下载使用示例 三.总结 相信有些小伙伴已经了解大文件上传的解决方案,在上传大文件时,为了提高上传的效率,我们一般会使用 Blob.slice 方法对大文件按照指定的大小进行切割,然后在开启多线程进行分块上传,等所有分块都成功上传后,再通知服务端进行分块合并. 那么对大文件下载来说,我们能否采用类似的思想呢?在服务端支持 Range 请求首部的条件下,我们也是可以实现多线程分块下载的

  • php实现大文件断点续传下载实例代码

    php实现大文件断点续传下载实例,看完你就知道超过100M以上的大文件如何断点传输了,这个功能还是比较经典实用的,毕竟大文件上传功能经常用得到. require_once('download.class.php'); date_default_timezone_set('Asia/Shanghai'); error_reporting(E_STRICT); function errorHandler($errno, $errstr, $errfile, $errline) { echo '<p>

  • python 多线程将大文件分开下载后在合并的实例

    废话不多说了,上代码吧: import threading import requests import time import os class Mythread(threading.Thread): def __init__(self,url,startpos,endpos,f): super(Mythread,self).__init__() self.url=url self.startpos=startpos self.endpos=endpos self.fd=f def downl

  • springboot大文件上传、分片上传、断点续传、秒传的实现

    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送.接收都是不可取,很容易导致内存问题.所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率. 本文是基于 springboot + vue 实现的文件上传,本文主要介绍服务端实现文件上传的步骤及代码实现,vue的实现步骤及实现请移步本人的另一篇文章 vue 大文件分片上传 - 断点续传.并发上传 上传分步: 本人分析上传总共分为: 检查文件是否已上传,如已上传可实现秒传 创建临时文件(._tmp

  • 前端大文件上传与下载(分片上传)的详细过程

    目录 一.问题 二.解决 1.第一步选择文件 2.校验文件是否符合规范 3.文件切片上传 4.分片上传注意点 5.大文件下载 总结 一.问题 日常业务中难免出现前端需要向后端传输大型文件的情况,这时单次的请求不能满足传输大文件的需求,就需要用到分片上传 业务需求为:用户可以上传小于20G的镜像文件,并进显示当前上传进度 前端:vue3.x+Element Plus组件+axios 二.解决 解决思路简单为前端选择文件后读取到文件的基本信息,包括:文件的大小.文件格式等信息,用于前端校验,校验完成

  • asp.net下大文件上传知识整理

    最近做在做ePartner项目,涉及到文件上传的问题. 以前也做过文件上传,但都是些小文件,不超过2M. 这次要求上传100M以上的东西. 没办法找来资料研究了一下.基于WEB的文件上传可以使用FTP和HTTP两种协议,用FTP的话虽然传输稳定,但安全性是个严重的问题,而且FTP服务器读用户库获取权限,这样对于用户使用来说还是不太方便. 剩下只有HTTP.在HTTP中有3种方式,PUT.WEBDAV.RFC1867,前2种方法不适合大文件上传,目前我们使用的web上传都是基于RFC1867标准的

  • java多文件压缩下载的解决方法

    Java多文件压缩下载解决方案,供大家参考,具体内容如下 需求: 会员运营平台经过改版后页面增加了许多全部下载链接,上周上线比较仓促,全部下载是一个直接下载ZIP压缩文件的链接,每个ZIP压缩文件都是由公司运营人员将页面需要下载的文件全部压缩成一个ZIP压缩文件,然后通过公司的交易运营平台上传至文件资料系统,会员运营平台则可以直接获取ZIP压缩文件地址进行下载 下面是一个页面示例: 需求分析: 通过上面需求和页面可以分析出,公司运营人员将页面全部需要下载的文件进行ZIP压缩后上传文件资料系统确实

  • SpringBoot 中大文件(分片上传)断点续传与极速秒传功能的实现

    1.创建SpringBoot项目 本项目采用springboot + mybatis-plus +jquery +thymeleaf组成 2.项目流程图 3.在pom中添加以下依赖 <!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optio

随机推荐