SpringBoot、SpringCloud使用webFlux的WebClient上传文件、下载文件的实现以及下载中报错Exceeded limit on max bytes to buffe解决方法

项目场景:

最近在做的一个Java后端的项目中需要先从其他服务器下载某个附件,在上传到本项目MinIO服务器,获的附件信息保存,然后实现方式我用的是WebClient实现的。

一、基本介绍

1、什么是 WebClient

从 Spring 5 开始,Spring 中全面引入了 Reactive 响应式编程。而 WebClient 则是 Spring WebFlux 模块提供的一个非阻塞的基于响应式编程的进行 Http 请求的客户端工具。
由于 WebClient 的请求模式属于异步非阻塞,能够以少量固定的线程处理高并发的 HTTP 请求。因此,从 Spring 5 开始,HTTP 服务之间的通信我们就可以考虑使用 WebClient 来取代之前的 RestTemplate。

2、WebClient 的优势
(1)与 RestTemplate 相比,WebClient 有如下优势:

非阻塞,Reactive 的,并支持更高的并发性和更少的硬件资源。
提供利用 Java 8 lambdas 的函数 API。
支持同步和异步方案。
支持从服务器向上或向下流式传输。
(2)RestTemplate 不适合在非阻塞应用程序中使用,因此 Spring WebFlux 应用程序应始终使用 WebClient。在大多数高并发场景中,WebClient 也应该是 Spring MVC 中的首选,并且用于编写一系列远程,相互依赖的调用。

编辑 pom.xml 文件,添加 Spring WebFlux 依赖,从而可以使用 WebClient。

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor.netty</groupId>
            <artifactId>reactor-netty-http</artifactId>
        </dependency>

二、创建 WebClient 实例

利用 builder 创建(推荐)

下面使用 builder() 返回一个 WebClient.Builder,然后再调用 build 就可以返回 WebClient 对象。并利用该对象请求一个网络接口,最后将结果以字符串的形式打印出来。

注意:由于返回的不是 WebClient 类型而是 WebClient.Builder,我们可以通过返回的 WebClient.Builder 设置一些配置参数(例如:baseUrl、header、cookie 等),然后再调用 build 就可以返回 WebClient 对象了

WebClient webClient = WebClient.builder()
        .baseUrl("http://better.blog.csdn.net")
        .defaultHeader(HttpHeaders.USER_AGENT,"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)")
        .defaultCookie("ACCESS_TOKEN", "test_token")
        .build();
 
Mono<String> mono = webClient
        .get() // GET 请求
        .uri("/posts/1")  // 请求路径
        .retrieve() // 获取响应体
        .bodyToMono(String.class); //响应数据类型转换
         
System.out.println(mono.block());

 下载文件和上传文件的详细例子

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;

/**
 * 文件 工具类
 * 工具类编写规范
 * 工具类定义为public final class 工具类名称;
 * 工具类名称的命名应为Utils结尾,例如 DateUtils;
 * 工具类的构造方法最好为private,及这个类无法实例化,无法new出一个工具类;
 * 工具类的方法应为static修饰的,表示静态方法,可直接通过类名.方法名的格式去访问;
 * 工具类应使用单例模式,不要使用new,重复创建对象,浪费系统资源
 *
 * @author QC 班长
 * @version V1.0
 * @since 2023/7/5 14:37
 */
public final class FileUtil {
    private static FileUtil fileUtil = null;

    static {
        // 类加载创建时只会创建一个对象
        if (fileUtil == null) {
            fileUtil = new FileUtil();
        }
    }

    private FileUtil() {
    }

    private static final int TEN = 10;
    private static final int HUNDRED = 100;
    private static final int ONE_ZERO_TWO_FOUR = 1024;


    /**
     * 调用接口的-认证秘钥
     */
    private static final String AUTHORIZATION_VALUE = "xxx";

    /**
     * 下载文件
     * @param dowUrl 完整下载地址
     * @return 本地文件路径
     * @throws IOException 异常
     */
    public static String downloadFile(String dowUrl) throws IOException {
        // 记录下开始下载时的时间
        Instant now = Instant.now();
        // 创建 WebClient 对象
        WebClient webClient = WebClient.builder()
                .codecs(obj -> obj.defaultCodecs().maxInMemorySize(HUNDRED * ONE_ZERO_TWO_FOUR * ONE_ZERO_TWO_FOUR))
                .build();
        //发送请求
        ClientResponse response = webClient
                .get() // GET 请求
                .uri(dowUrl)  // 请求路径
                .accept(MediaType.APPLICATION_OCTET_STREAM)
                .exchange()
                .block(); // 获取响应体
        String fileName = "";

        // 从header中获取原始文件名
        if (response != null) {
            HttpHeaders httpHeaders = response.headers().asHttpHeaders();
            String content = httpHeaders.getFirst(HttpHeaders.CONTENT_DISPOSITION);
            if (content != null) {
                fileName = content.substring(content.lastIndexOf("filename=") + TEN, content.length() - 1);
            }
            //解码已编码的url字符串,将十六进制转换成中文。
            fileName = System.getProperty("user.dir") + File.separator + URLDecoder.decode(fileName, "UTF-8");
            File out = new File(fileName);
            // 将文件保存(文件名不变)
            Resource resource = response.bodyToMono(Resource.class).block();
            if (resource != null) {
                FileUtils.copyInputStreamToFile(resource.getInputStream(), out);
            }
        }
        System.out.println("文件下载完成,耗时:" + ChronoUnit.SECONDS.between(now, Instant.now()) + " 秒");
        return fileName;
    }

    /**
     * 上传文件
     * @param filePath 本地文件路径
     * @return 文件信息
     * @throws IOException 异常
     */
    public static String uploadFile(String filePath) throws IOException {
        // 记录下开始下载时的时间
        Instant now = Instant.now();
        FileSystemResource upload = new FileSystemResource(new File(filePath));
        // 封装请求参数
        MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
        param.add("file", upload);
        param.add("formCode", "form_5964");
        param.add("bucketName", "education");
        WebClient webClient = WebClient.builder().build();
        String response = webClient
                .post() // POST 请求
                .uri("http://10.1.1.204/gw/form-api/file/upload")  // 请求路径
                .header(HttpHeaders.AUTHORIZATION, AUTHORIZATION_VALUE)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromMultipartData(param))
                .retrieve() // 获取响应体
                .bodyToMono(String.class).block(); //响应数据类型转换
        System.out.println("文件下载完成,耗时:" + ChronoUnit.SECONDS.between(now, Instant.now()) + " 秒");
        // 将 JSON 响应转换为 Map
        Map result = JSON.parseObject(response, Map.class);
        if (result != null) {
            //获取文件信息并返回
            JSONObject jsonObject = (JSONObject) result.get("data");
            return jsonObject.getString("uid");
        } else {
            return "";
        }

    }

    public static void main(String[] args) throws IOException {
        String fileName = downloadFile("http://ibeastidea.com/file/downFile/a86727dfe5f449f29ae262d50aad98");
        System.out.println(fileName);
        String uid = uploadFile(fileName);
    }
}

问题描述

其中在下载时报错如下

org.springframework.core.io.buffer.DataBufferLimitException: 
Exceeded limit on max bytes to buffer : 262144
    at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:98)
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 


原因分析:

DataBufferLimitException: Exceeded limit on max bytes to buffer :262144,发现只能上传256KB以下的文件,但现在文件随便就是10M或者更大。因为接收响应数据的缓冲区不够大,限制为256k。(AbstractDataBufferDecoder类写死了限制)

只能上传256KB以下的文件,但现在文件随便就是10M或者更大。


解决方案:

可以通过WebClient.build时可以利用codecs方法修改maxInMemorySize值。

    private static final int HUNDRED = 100;
    private static final int ONE_ZERO_TWO_FOUR = 1024;

    // 创建 WebClient 对象
    WebClient webClient = WebClient.builder()
    //修改maxInMemorySize的缓存值,默认是256k。修改为100MB
                .codecs(obj -> obj.defaultCodecs().maxInMemorySize(HUNDRED * ONE_ZERO_TWO_FOUR * ONE_ZERO_TWO_FOUR))
                .build();

参考文献

 0、SpringBoot - 网络请求客户端WebClient使用详解_springboot webclient_weixin_35688430的博客-CSDN博客

1、SpringBoot - 网络请求客户端WebClient使用详解5(上传文件)

2、spring-boot - Spring Boot 通过 WebClient 将文件从一个 api 传递/上传到另一个 api - IT工具网 

3、spring - 精讲响应式WebClient第4篇-文件上传与下载 - 个人文章 - SegmentFault 思否 

4、【WEB系列】WebClient之文件上传 | 一灰灰Blog 

5、SpringBoot - 网络请求客户端WebClient(文件处理)_webclient 接收zip_weixin_35688430的博客-CSDN博客 

6、WebFlux_webfux_欺骗可乐的博客-CSDN博客 

7、webFlux框架webClient请求数据报错:Exceeded limit on max bytes to buffer : 262144_现实、太残忍的博客-CSDN博客 

8、spring boot 中出现DataBufferLimitException: Exceeded limit on max bytes to buffer :262144_spring_我真的不吃辣条-华为云开发者联盟 

猜你喜欢

转载自blog.csdn.net/qq_35624642/article/details/131551364