SpringBoot下载文件的实现及速度对比

目录
  • 前言
  • 文件来源
  • 文件下载
    • 1、OutputStream形式
    • 2、ResponseEntity形式
  • 两种方式下载速度比较
  • 后话

前言

承上篇上传文件之后,本文就主要介绍下SpringBoot下下载文件的方式,大致有两种Outputstream与ResponseEntity,并大概看一下速度对比

文件来源

这里还是以GridFS为例,主要演示的还是从mongo下载下来的文件,如果是本地服务器上的文件,前端传以文件路径直接获取流即可,如下:

InputStream in = new FileInputStream(System.getProperty("user.dir") + filePath);

接下来就演示下使用GridFsTemplate下载文件,mongo的配置其实上篇已经贴过了,这里就直接贴代码了,具体的就不做解释了

@Service
@Slf4j
public class MongoConfig extends AbstractMongoConfiguration {

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private GridFSBucket gridFSBucket;

    @Override
    public MongoClient mongoClient() {
        MongoClient mongoClient = getMongoClient();
        return mongoClient;
    }

    public MongoClient getMongoClient() {
        // MongoDB地址列表
        List<ServerAddress> serverAddresses = new ArrayList<>();
        serverAddresses.add(new ServerAddress("10.1.61.101:27017"));
        // 连接认证
        MongoCredential credential = MongoCredential.createCredential("root", "admin", "Root_123".toCharArray());
        MongoClientOptions.Builder builder = MongoClientOptions.builder();

        //最大连接数
        builder.connectionsPerHost(10);
        //最小连接数
        builder.minConnectionsPerHost(0);
        //超时时间
        builder.connectTimeout(1000*3);
        // 一个线程成功获取到一个可用数据库之前的最大等待时间
        builder.maxWaitTime(5000);
        //此参数跟connectionsPerHost的乘机为一个线程变为可用的最大阻塞数,超过此乘机数之后的所有线程将及时获取一个异常.eg.connectionsPerHost=10 and threadsAllowedToBlockForConnectionMultiplier=5,最多50个线程等级一个链接,推荐配置为5
        builder.threadsAllowedToBlockForConnectionMultiplier(5);
        //最大空闲时间
        builder.maxConnectionIdleTime(1000*10);
        //设置池连接的最大生命时间。
        builder.maxConnectionLifeTime(1000*10);
        //连接超时时间
        builder.socketTimeout(1000*10);

        MongoClientOptions myOptions = builder.build();
        MongoClient mongoClient = new MongoClient(serverAddresses, credential, myOptions);
        return mongoClient;
    }

    @Override
    protected String getDatabaseName() {
        return "notifyTest";
    }

    /**
     * 获取另一个数据库
     * @return
     */
    public String getFilesDataBaseName() {
        return "notifyFiles";
    }

    /**
     * 用于切换不同的数据库
     * @return
     */
    public MongoDbFactory getDbFactory(String dataBaseName) {
        MongoDbFactory dbFactory = null;
        try {
            dbFactory = new SimpleMongoDbFactory(getMongoClient(), dataBaseName);
        } catch (Exception e) {
            log.error("Get mongo client have an error, please check reason...", e.getMessage());
        }
        return dbFactory;
    }

    /**
     * 获取文件存储模块
     * @return
     */
    public GridFsTemplate getGridFS() {
        return new GridFsTemplate(getDbFactory(getFilesDataBaseName()), mongoTemplate.getConverter());
    }

    @Bean
    public GridFSBucket getGridFSBuckets() {
        MongoDatabase db = getDbFactory(getFilesDataBaseName()).getDb();
        return GridFSBuckets.create(db);
    }

    /**
     * 为了解决springBoot2.0之后findOne方法返回类更改所新增 将GridFSFile 转为 GridFsResource
     * @param gridFsFile
     * @return
     */
    public GridFsResource convertGridFSFile2Resource(GridFSFile gridFsFile) {
        GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFsFile.getObjectId());
        return new GridFsResource(gridFsFile, gridFSDownloadStream);
    }
}

对比上篇配置,新增加的两个方法主要为了应对SpringBoot2.x之后,GridFsTemplate的findOne()方法返回从GridFSDBFile改为GridFSFile,导致文件下载时不能使用以前的GridFSDBFile 操作流了,所以加了转换操作

文件下载

分别把两种方式的下载实现贴出来

1、OutputStream形式

    @RequestMapping(value = "/download2", method = RequestMethod.GET)
    public void downLoad2(HttpServletResponse response, String id) {
        userService.download2(response, id);
    }

controller层如上,只是测试所以很简略,因为是流的形式所以并不需要指定输出格式,下面看下service层实现

    /**
     * 以OutputStream形式下载文件
     * @param response
     * @param id
     */
    @Override
    public void download2(HttpServletResponse response, String id) {
        GridFsTemplate gridFsTemplate = new GridFsTemplate(mongoConfig.getDbFactory(mongoConfig.getFilesDataBaseName()), mongoTemplate.getConverter());
        // 由于springBoot升级到2.x 之后 findOne方法返回由 GridFSDBFile 变为 GridFSFile 了,导致下载变得稍微有点繁琐
        GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));
        String fileName = gridFSFile.getFilename();
        GridFsResource gridFsResource = mongoConfig.convertGridFSFile2Resource(gridFSFile);
        // 从此处开始计时
        long startTime = System.currentTimeMillis();
        InputStream in = null;
        OutputStream out = null;
        try {
            // 这里需对中文进行转码处理
            fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
            // 告诉浏览器弹出下载对话框
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            byte[] buffer = new byte[1024];
            int len;
            // 获得输出流
            out = response.getOutputStream();
            in = gridFsResource.getInputStream();
            while ((len = in.read(buffer)) > 0) {
               out.write(buffer, 0 ,len);
            }
        } catch (IOException e) {
            log.error("transfer in error .");
        } finally {
            try {
                if (null != in)
                    in.close();
                if (null != out)
                    out.close();
                log.info("download file with stream total time : {}", System.currentTimeMillis() - startTime);
            } catch (IOException e){
                log.error("close IO error .");
            }
        }
    }

可以看到篇幅较长,注释也已经都在代码里了

2、ResponseEntity形式

    @RequestMapping(value = "/download", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public Object downLoad(String id) {
        return userService.download(id);
    }

controller需要指定输出格式application/octet-stream,标明是以流的形式下载文件,下面看下service层

    /**
     * 以ResponseEntity形式下载文件
     * @param id
     * @return
     */
    @Override
    public ResponseEntity<byte[]> download(String id) {
        GridFsTemplate gridFsTemplate = new GridFsTemplate(mongoConfig.getDbFactory(mongoConfig.getFilesDataBaseName()), mongoTemplate.getConverter());
        // 由于springBoot升级到2.x 之后 findOne方法返回由 GridFSDBFile 变为 GridFSFile 了,导致下载变得稍微有点繁琐
        GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));
        String fileName = gridFSFile.getFilename();
        GridFsResource gridFsResource = mongoConfig.convertGridFSFile2Resource(gridFSFile);
        // 从此处开始计时
        long startTime = System.currentTimeMillis();
        try {
            InputStream in = gridFsResource.getInputStream();
            // 请求体
            byte[] body = IOUtils.toByteArray(in);
            // 请求头
            HttpHeaders httpHeaders = new HttpHeaders();
            // 这里需对中文进行转码处理
            fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
            // 告诉浏览器弹出下载对话框
            httpHeaders.add("Content-Disposition", "attachment;filename=" + fileName);
            ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(body, httpHeaders, HttpStatus.OK);
            log.info("download file total with ResponseEntity time : {}", System.currentTimeMillis() - startTime);
            return responseEntity;
        } catch (IOException e) {
            log.error("transfer in error .");
        }
        return null;
    }

上面用到了IOUtils工具类,依赖如下

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

两种方式下载速度比较

经过测试,当文件小于1m内两种方式速度差不多,然后我测了5m的文件,结果如下:

可以看到OutputStream略慢一点点,当文件再大时这边也并没有作测试,总之本人推荐使用ResponseEntity形式下载文件~

后话

如果只是想显示某个路径下的图片而并不需要下载,那么采用如下形式:

    @RequestMapping(value = "/application/file/show", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE)
    public Object downloadFile(@RequestParam("path") String filePath) {
        try {
            InputStream in = new FileInputStream(System.getProperty("user.dir") + filePath);
            byte[] bytes = new byte[in.available()];
            in.read(bytes);
            return bytes;
        } catch (IOException e) {
            log.error("transfer byte error");
            return buildMessage(ResultModel.FAIL, "show pic error");
        }
    }

需要注意上述的available()方法,该方法是返回输入流中所包含的字节数,方便在读写操作时就能得知数量,能否使用取决于实现了InputStream这个抽象类的具体子类中有没有实现available这个方法。

如果实现了那么就可以取得大小,如果没有实现那么就获取不到。

例如FileInputStream就实现了available方法,那么就可以用new byte[in.available()];这种方式。

但是,网络编程的时候Socket中取到的InputStream,就没有实现这个方法,那么就不可以使用这种方式创建数组。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 在 Spring Boot 项目中实现文件下载功能

    (一)需求 在您的 springboot 项目中,可能会存在让用户下载文档的需求,比如让用户下载 readme 文档来更好地了解该项目的概况或使用方法. 所以,您需要为用户提供可以下载文件的 API ,将用户希望获取的文件作为下载资源返回给前端. (二)代码 maven 依赖 请您创建好一个 springboot 项目,一定要引入 web 依赖: <dependency> <groupId>org.springframework.boot</groupId> <a

  • 详解SpringBoot下文件上传与下载的实现

    SpringBoot后台如何实现文件上传下载? 最近做的一个项目涉及到文件上传与下载.前端上传采用百度webUploader插件.有关该插件的使用方法还在研究中,日后整理再记录.本文主要介绍SpringBoot后台对文件上传与下载的处理. 单文件上传 / 单文件上传 @RequestMapping(value = "/upload") @ResponseBody public String upload(@RequestParam("file") Multipart

  • springboot实现文件上传和下载功能

    spring boot 引入"约定大于配置"的概念,实现自动配置,节约了开发人员的开发成本,并且凭借其微服务架构的方式和较少的配置,一出来就占据大片开发人员的芳心.大部分的配置从开发人员可见变成了相对透明了,要想进一步熟悉还需要关注源码. 1.文件上传(前端页面): <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/lo

  • SpringBoot下载文件的实现及速度对比

    目录 前言 文件来源 文件下载 1.OutputStream形式 2.ResponseEntity形式 两种方式下载速度比较 后话 前言 承上篇上传文件之后,本文就主要介绍下SpringBoot下下载文件的方式,大致有两种Outputstream与ResponseEntity,并大概看一下速度对比 文件来源 这里还是以GridFS为例,主要演示的还是从mongo下载下来的文件,如果是本地服务器上的文件,前端传以文件路径直接获取流即可,如下: InputStream in = new FileIn

  • SpringBoot下载Excel文件时,报错文件损坏的解决方案

    SpringBoot下载Excel文件文件损坏 我把模板文件放在了resources目录下 maven插件打包项目的时候,默认会压缩resources目录下的文件. 服务器读取的文件流来自于压缩后的文件,而返回给浏览器时,浏览器把他当作正常的文件解析,自然不能得到正确的结果. 解决方案: 配置一下maven插件,打包的时候不要压缩模板文件,排除拓展名为xlsx的文件. <plugin> <groupId>org.apache.maven.plugins</groupId>

  • springboot各种下载文件的方式汇总

    目录 一.使用response输出流下载 二.使用ResponseEntity 三.注意 总结 一.使用response输出流下载 注意第一种方式返回值必须为void @GetMapping("/t1") public void down1(HttpServletResponse response) throws Exception { response.reset(); response.setContentType("application/octet-stream;ch

  • SpringBoot实现文件上传下载功能小结

    最近做的一个项目涉及到文件上传与下载.前端上传采用百度webUploader插件.有关该插件的使用方法还在研究中,日后整理再记录.本文主要介绍SpringBoot后台对文件上传与下载的处理. 单文件上传 // 单文件上传 @RequestMapping(value = "/upload") @ResponseBody public String upload(@RequestParam("file") MultipartFile file) { try { if (

  • springboot 中文件上传下载实例代码

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者. Spring Boot特点 1. 创建独立的Spring应用程序 2. 嵌入的Tomcat,无需部署WAR文件 3. 简化Maven配置 4. 自动配置Spr

  • springboot整合vue实现上传下载文件

    springboot整合vue实现上传下载文件,供大家参考,具体内容如下 环境 springboot 1.5.x 完整代码下载:springboot整合vue实现上传下载 1.上传下载文件api文件 设置上传路径,如例子: private final static String rootPath = System.getProperty("user.home")+File.separator+fileDir+File.separator; api接口: 下载url示例:http://l

  • springboot打jar包之后下载文件的路径问题

    错误信息 能看到下载的路径中包含了jar包,wlaqWeb-0.0.1-SNAPSHOT.jar! java.io.FileNotFoundException: file:\C:\Users\PENGXIN\Desktop\网络安全\jars\wlaqWeb-0.0.1-SNAPSHOT.jar!\BOOT-INF\classes!\static\securityParameter\template\网络安全台账(模板).xlsx (文件名.目录名或卷标语法不正确.) at java.io.Fi

  • springboot+vue实现页面下载文件

    本文实例为大家分享了springboot+vue页面下载文件的具体代码,供大家参考,具体内容如下 1.前端代码: <template v-slot:operate="{ row }"> <vxe-button style="color: #409eff; font-weight: bolder" class="el-icon-download" title="成果下载" circle @click="

  • SpringBoot上传和下载文件的原理解析

    技术概述 我们的项目是实现一个论坛.在论坛上发布博客时应该要可以上传文件,用户阅读博客是应该要可以下载文件.于是我去学习了SpringBoot的上传和下载文件,我感觉技术的难点在于使用图床并隐藏文件真实的存放地址. 技术详述 针对使用自己的服务器作为图床, 首先配置WebMvcConfigurer,SpringBoot2.0以后的版本可以使用WebMvcConfigurer实现类方式来进行配置 即创建一个实体类实现WebMvcConfigurer接口 public class WebConfig

随机推荐