【WEB系列】WebClient之文件上传

文章目录
  1. I. 项目环境
    1. 1. 依赖
    2. 2. 文件上传接口
    3. 3. 待上传文件
  2. II. 文件上传
    1. 1. 单个文件上传
    2. 2. 流上传
    3. 3. 字节数组上传
    4. 4. 多文件上传
    5. 5. BodyInserters方式
    6. 6. 测试输出
  3. II. 其他
    1. 0. 项目
    2. 1. 一灰灰Blog

在上一篇WebClient基本使用姿势中,介绍了如何借助WebClient来实现异步的GET/POST访问,接下来这篇文章则主要介绍文件上传的使用姿势

I. 项目环境

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

1. 依赖

使用WebClient,最主要的引入依赖如下(省略掉了SpringBoot的相关依赖,如对于如何创建SpringBoot项目不太清楚的小伙伴,可以关注一下我之前的博文)

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

2. 文件上传接口

借助WebFlux,写一个简单的文件上传的REST接口(关于WebFlux的使用姿势不属于本文重点,下面的代码如有不懂的地方,可以直接忽略掉或者关注一下接下来的WebFlux系列博文)

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
/**
* 文件上传
*
* @param filePart
* @return
*/
@PostMapping(path = "upload", produces = MediaType.MULTIPART_MIXED_VALUE)
public Mono<String> upload(@RequestPart(name = "data") FilePart filePart, ServerWebExchange exchange)
throws IOException {
Mono<MultiValueMap<String, Part>> ans = exchange.getMultipartData();

StringBuffer result = new StringBuffer("【basic uploads: ");
ans.subscribe(s -> {
for (Map.Entry<String, List<Part>> entry : s.entrySet()) {
for (Part part : entry.getValue()) {
result.append(entry.getKey()).append(":");
dataBuffer2str(part.content(), result);
}
}
});

result.append("】");
return Mono.just(result.toString());
}

private void dataBuffer2str(Flux<DataBuffer> data, StringBuffer buffer) {
data.subscribe(s -> {
byte[] bytes = new byte[s.readableByteCount()];
s.read(bytes);
buffer.append(new String(bytes)).append(";");
});
}

3. 待上传文件

在项目的资源目录resources下,新建一个文本文件,用于测试上传时使用

test.txt

1
hello 一灰灰😝ddd

II. 文件上传

在前面介绍RestTemplate的系列博文中,同样有一篇关于RestTemplate文件上传的博文,建议两篇对照阅读,可以获取双倍的收获哦

1. 单个文件上传

请注意,文件上传依然是POST请求,一般来讲,请求头的Content-Typemultipart/form-data

前面介绍WebClient的POST传参时,参数是封装在MultiValueMap中的,在文件上传中,依然如此,不同的是这个参数的构建方式

1
2
3
4
5
6
7
8
9
10
11
12
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("data",
new FileSystemResource(this.getClass().getClassLoader().getResource("test.txt").getFile()));

// 表单参数
builder.part("name", "一灰灰");

MultiValueMap<String, HttpEntity<?>> parts = builder.build();

WebClient webClient = WebClient.create("http://127.0.0.1:8080");
Mono<String> ans = webClient.post().uri("/upload").bodyValue(parts).retrieve().bodyToMono(String.class);
ans.subscribe(s -> System.out.println("upload file return : " + s));

重点关注一下借助MultipartBodyBuilder创建请求参数的过程

剩下的发起请求的姿势,与之前介绍的POST方式,没有什么区别

2. 流上传

当然需要上传时,多半也不会是上传文件,比如一个常见的case可能是下载远程资源,并上传给内部服务;所以我们会使用InputStreamResource来替换FileSystemResource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 以流的方式上传资源
builder = new MultipartBodyBuilder();
final InputStream stream = this.getClass().getClassLoader().getResourceAsStream("test.txt");
builder.part("data", new InputStreamResource(stream) {
@Override
public long contentLength() throws IOException {
// 这个方法需要重写,否则无法正确上传文件;原因在于父类是通过读取流数据来计算大小
return stream.available();
}

@Override
public String getFilename() {
return "test.txt";
}
});
parts = builder.build();
ans = webClient.post().uri("/upload").bodyValue(parts).retrieve().bodyToMono(String.class);
ans.subscribe(s -> System.out.println("upload stream return: " + s));

请注意:当不重写InpustStreamResourcecontentLengthgetFilename方法时,没法实现我们上传的目的哦

3. 字节数组上传

有流的方式,当然就不会缺少字节数组的方式,基本姿势与上面并无二样

1
2
3
4
5
6
7
8
9
10
11
// 以字节数组的方式上传资源
builder = new MultipartBodyBuilder();
builder.part("data", new ByteArrayResource("hello 一灰灰😝!!!".getBytes()) {
@Override
public String getFilename() {
return "test.txt";
}
});
parts = builder.build();
ans = webClient.post().uri("/upload").bodyValue(parts).retrieve().bodyToMono(String.class);
ans.subscribe(s -> System.out.println("upload bytes return: " + s));

4. 多文件上传

除了一个一个文件上传之外,某些case下也可能出现一次上传多个文件的情况,对于WebClient而言,无非就是构建上传参数的时候,多一个add而言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 多文件上传,key都是data,存value的是一个列表哦,所以没调用一次,表示新塞入一个资源
builder.part("data", new ByteArrayResource("hello 一灰灰😝!!!".getBytes()) {
@Override
public String getFilename() {
return "test.txt";
}
});
builder.part("data", new ByteArrayResource("welcome 二灰灰😭!!!".getBytes()) {
@Override
public String getFilename() {
return "test2.txt";
}
});
parts = builder.build();
ans = webClient.post().uri("/upload").bodyValue(parts).retrieve().bodyToMono(String.class);
ans.subscribe(s -> System.out.println("batch upload bytes return: " + s));

5. BodyInserters方式

除了上面的MultipartBodyBuilder创建传参之外,还可以借助BodyInserters来处理,前面在接收Post传参的两种姿势中也介绍过;

不过不同于之前的BodyInserters#fromFormData,我们这里使用的是BodyInserters#fromMultipartData (从调用的方法签名上,也知道两者的各自应用场景)

1
2
3
4
ans = webClient.post().uri("/upload").body(BodyInserters.fromMultipartData("data",
new FileSystemResource(this.getClass().getClassLoader().getResource("test.txt").getFile()))
.with("name", "form参数")).retrieve().bodyToMono(String.class);
ans.subscribe(s -> System.out.println("upload file build by BodyInserters return: " + s));

请注意,我们传参是通过body方法,而不是前面的bodyValue方法;如果使用错了,将无法达到预期的目的,而且极有可能调试半天也不知道啥原因…

6. 测试输出

所有上面的代码可以在文末的工程源码连接中获取,下面是执行的输出结果

II. 其他

0. 项目

系列博文

源码

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到