前言
承上篇上传文件之后,本文就主要介绍下SpringBoot下下载文件的方式,大致有两种Outputstream与ResponseEntity,并大概看一下速度对比
文件来源
这里还是以GridFS为例,主要演示的还是从mongo下载下来的文件,如果是本地服务器上的文件,前端传以文件路径直接获取流即可,如下:
1InputStream in = new FileInputStream(System.getProperty("user.dir") + filePath);
接下来就演示下使用GridFsTemplate下载文件,mongo的配置其实上篇已经贴过了,这里就直接贴代码了,具体的就不做解释了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96@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形式
1
2
3
4@RequestMapping(value = "/download2", method = RequestMethod.GET) public void downLoad2(HttpServletResponse response, String id) { userService.download2(response, id); }
controller层如上,只是测试所以很简略,因为是流的形式所以并不需要指定输出格式,下面看下service层实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43/** * 以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形式
1
2
3
4@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层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/** * 以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工具类,依赖如下
1
2
3
4
5<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency>
两种方式下载速度比较
经过测试,当文件小于1m内两种方式速度差不多,然后我测了5m的文件,结果如下:
可以看到OutputStream略慢一点点,当文件再大时这边也并没有作测试,总之本人推荐使用ResponseEntity形式下载文件~
后话
如果只是想显示某个路径下的图片而并不需要下载,那么采用如下形式:
1
2
3
4
5
6
7
8
9
10
11
12@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下载文件的实现及速度对比的全部内容,更多相关SpringBoot下载文件内容请搜索靠谱客的其他文章。
发表评论 取消回复