千家信息网

SpringBoot中下载文件的方式有哪些

发表于:2024-10-21 作者:千家信息网编辑
千家信息网最后更新 2024年10月21日,这篇文章将为大家详细讲解有关SpringBoot中下载文件的方式有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。文件来源这里还是以GridFS为例,主要演示的还
千家信息网最后更新 2024年10月21日SpringBoot中下载文件的方式有哪些

这篇文章将为大家详细讲解有关SpringBoot中下载文件的方式有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

文件来源

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

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

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

@Service@Slf4jpublic 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 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 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 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工具类,依赖如下

    commons-io    commons-io    2.4

两种方式下载速度比较

经过测试,当文件小于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,就没有实现这个方法,那么就不可以使用这种方式创建数组。

关于"SpringBoot中下载文件的方式有哪些"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

0