WebClient 同步、异步调用实现对比

2024-07-12 1548阅读

文章目录

  • 一、概述
  • 二、pom依赖
  • 三、代码结构
  • 四、源码传送
    • 1、异步代码
    • 2、同步代码
    • 3、完整代码

      一、概述

      WebClient是Spring WebFlux模块提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具,从Spring5.0开始WebClient作为RestTemplete的替代品,有更好的响应式能力,支持异步调用,可以在Spring项目中实现网络请求。

      二、pom依赖

      		
      			org.springframework
      			spring-webflux
      			5.2.3.RELEASE
      		
      		
      			io.projectreactor.netty
      			reactor-netty
      			0.9.4.RELEASE
      		
      		
      			org.apache.logging.log4j
      			log4j-slf4j-impl
      			2.12.1
      		
      		
      			com.fasterxml.jackson.core
      			jackson-databind
      			2.13.0
      		
      		
      			org.apache.commons
      			commons-lang3
      			3.10
      		
      		
      			commons-io
      			commons-io
      			2.5
      		
      		
      			org.projectlombok
      			lombok
      			1.18.12
      			provided
      		
      		
      			junit
      			junit
      			4.12
      		
      

      三、代码结构

      WebClient 同步、异步调用实现对比

      图片请手工放入 src\test\resources\123.jpg

      WebClient 同步、异步调用实现对比

      单元测试

      WebClient 同步、异步调用实现对比

      四、源码传送

      1、异步代码

      import java.awt.Desktop;
      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.nio.charset.StandardCharsets;
      import java.util.Collections;
      import java.util.HashMap;
      import java.util.Map;
      import java.util.concurrent.TimeUnit;
      import javax.imageio.ImageIO;
      import org.apache.commons.lang3.RandomUtils;
      import org.junit.BeforeClass;
      import org.junit.Test;
      import org.springframework.core.io.ClassPathResource;
      import org.springframework.core.io.Resource;
      import org.springframework.http.MediaType;
      import org.springframework.util.FileCopyUtils;
      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 org.springframework.web.util.UriComponentsBuilder;
      import com.fly.http.bean.ImageShowDialog;
      import com.fly.http.bean.JsonBeanUtils;
      import com.fly.http.bean.SearchReq;
      import lombok.extern.slf4j.Slf4j;
      import reactor.core.publisher.Mono;
      /**
       * http请求WebClient异步调用实现
       */
      @Slf4j
      public class WebClientAsyncTest
      {
          private WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build();
          
          private void openImage(Resource resource)
          {
              try
              {
                  new ImageShowDialog(ImageIO.read(resource.getInputStream()));
              }
              catch (IOException e)
              {
                  log.error(e.getMessage(), e);
              }
          }
          
          @BeforeClass
          public static void init()
          {
              new File("download").mkdirs();
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws IOException
           * @throws InterruptedException
           */
          @Test
          public void testDownFile()
              throws IOException, InterruptedException
          {
              Mono mono = webClient.get()
                  .uri("https://00fly.online/upload/urls.txt")
                  .accept(MediaType.IMAGE_JPEG)
                  .exchange()
                  .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers()))
                  .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode()))
                  .flatMap(clientResponse -> clientResponse.bodyToMono(Resource.class));
              
              // 保存到本地
              mono.subscribe(resource -> {
                  try
                  {
                      FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream("download/urls.txt"));
                  }
                  catch (IOException e)
                  {
                      log.error(e.getMessage(), e);
                  }
              });
              TimeUnit.SECONDS.sleep(2);
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws IOException
           * @throws InterruptedException
           */
          @Test
          public void testDownImg001()
              throws IOException, InterruptedException
          {
              Mono mono = webClient.get()
                  .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg")
                  .accept(MediaType.IMAGE_JPEG)
                  .retrieve() // 获取响应体
                  .bodyToMono(Resource.class);
              mono.subscribe(resource -> openImage(resource));
              TimeUnit.SECONDS.sleep(10);
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws IOException
           * @throws InterruptedException
           */
          @Test
          public void testDownImg002()
              throws IOException, InterruptedException
          {
              Mono mono = webClient.get()
                  .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg")
                  .accept(MediaType.IMAGE_JPEG)
                  .retrieve() // 获取响应体
                  .bodyToMono(Resource.class);
              
              // 保存到本地
              mono.subscribe(resource -> {
                  try
                  {
                      File dest = new File(String.format("download/img_%s.jpg", System.currentTimeMillis()));
                      FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream(dest));
                      if (Desktop.isDesktopSupported())
                      {
                          Desktop.getDesktop().open(dest.getParentFile());
                      }
                  }
                  catch (IOException e)
                  {
                      log.error(e.getMessage(), e);
                  }
              });
              TimeUnit.SECONDS.sleep(2);
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testExchange001()
              throws InterruptedException
          {
              // get
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              Mono monoGet = webClient.get()
                  .uri(uriBuilder -> uriBuilder.scheme("https")
                      .host("httpbin.org")
                      .path("/get")
                      .queryParams(params) // 等价 queryParam("q1", "java").queryParam("q2", "python")
                      .build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange();
              
              monoGet.subscribe(clientResponse -> {
                  log.info("----- headers: {}", clientResponse.headers());
                  log.info("----- statusCode: {}", clientResponse.statusCode());
                  clientResponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body));
              });
              TimeUnit.SECONDS.sleep(2);
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testExchange002()
              throws InterruptedException
          {
              // get
              Mono monoGet = webClient.get()
                  .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange();
              monoGet.subscribe(clientResponse -> {
                  log.info("----- headers: {}", clientResponse.headers());
                  log.info("----- statusCode: {}", clientResponse.statusCode());
                  clientResponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body));
              });
              
              // formData post
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              Mono monoPost = webClient.post()
                  .uri("https://httpbin.org/post")
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python")
                  .exchange();
              monoPost.subscribe(clientResponse -> {
                  log.info("----- headers: {}", clientResponse.headers());
                  log.info("----- statusCode: {}", clientResponse.statusCode());
                  clientResponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body));
              });
              TimeUnit.SECONDS.sleep(2);
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testFormDataPost()
              throws InterruptedException
          {
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              Mono mono = webClient.post()
                  .uri("https://httpbin.org/post")
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python")
                  .retrieve() // 获取响应体
                  .bodyToMono(String.class); // 响应数据类型转换
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testGet001()
              throws InterruptedException
          {
              Mono mono = webClient.get()
                  .uri("https://httpbin.org/{method}", "get") // {任意命名}
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .retrieve()
                  .bodyToMono(String.class);
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testGet002()
              throws InterruptedException
          {
              Mono mono = webClient.get().uri("https://httpbin.org/get").acceptCharset(StandardCharsets.UTF_8).accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML).exchange();
              mono.subscribe(reponse -> {
                  log.info("----- headers: {}", reponse.headers());
                  log.info("----- statusCode: {}", reponse.statusCode());
                  reponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body));
              });
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testGet003()
              throws InterruptedException
          {
              Mono mono = webClient.get()
                  .uri("https://httpbin.org/get")
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange()
                  .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers()))
                  .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode()))
                  .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用, https://httpbin.org/get?q=java
           * 
           * @throws InterruptedException
           */
          @Test
          public void testGet004()
              throws InterruptedException
          {
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q", "java");
              String uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParams(params).toUriString();
              
              // 注意比较
              // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q", "java", "python").toUriString();
              // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q1", "java").queryParam("q2", "python").toUriString();
              
              Mono mono = webClient.get()
                  .uri(uri)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange()
                  .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers()))
                  .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode()))
                  .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testGet005()
              throws InterruptedException
          {
              Mono mono = webClient.get()
                  .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .retrieve()
                  .bodyToMono(String.class);
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testJsonBody001()
              throws InterruptedException
          {
              Mono mono = webClient.post()
                  .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/post").build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .contentType(MediaType.APPLICATION_JSON)
                  .bodyValue(Collections.singletonMap("q", "java"))
                  .retrieve()
                  .bodyToMono(String.class);
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws IOException
           * @throws InterruptedException
           */
          @Test
          public void testJsonBody002()
              throws IOException, InterruptedException
          {
              Mono mono;
              int num = RandomUtils.nextInt(1, 4);
              switch (num)
              {
                  case 1: // 方式1,javaBean
                      SearchReq req = new SearchReq();
                      req.setPageNo(1);
                      req.setPageSize(10);
                      req.setKeyword("1");
                      mono = webClient.post()
                          .uri("https://httpbin.org/post")
                          .acceptCharset(StandardCharsets.UTF_8)
                          .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                          .bodyValue(req) // 设置JsonBody
                          .retrieve()
                          .bodyToMono(String.class);
                      mono.subscribe(body -> log.info("reponse: {}", body));
                      break;
                  
                  case 2: // 方式2,HashMap
                      Map params = new HashMap();
                      params.put("pageNo", "2");
                      params.put("pageSize", "20");
                      params.put("keyword", "2");
                      mono = webClient.post()
                          .uri("https://httpbin.org/post")
                          .acceptCharset(StandardCharsets.UTF_8)
                          .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                          .bodyValue(params) // 设置JsonBody
                          .retrieve()
                          .bodyToMono(String.class);
                      mono.subscribe(body -> log.info("reponse: {}", body));
                      break;
                  
                  case 3: // 方式3,json字符串
                      Map params2 = new HashMap();
                      params2.put("pageNo", "2");
                      params2.put("pageSize", "20");
                      params2.put("keyword", "2");
                      mono = webClient.post()
                          .uri("https://httpbin.org/post")
                          .acceptCharset(StandardCharsets.UTF_8)
                          .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                          .body(BodyInserters.fromValue(JsonBeanUtils.beanToJson(params2, false))) // 设置formData
                          .retrieve()
                          .bodyToMono(String.class);
                      mono.subscribe(body -> log.info("reponse: {}", body));
                      break;
              }
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testUpload001()
              throws InterruptedException
          {
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              params.add("file", new ClassPathResource("123.jpg"));
              Mono mono = webClient.post()
                  .uri("https://httpbin.org/post")
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromMultipartData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python")
                  .retrieve() // 获取响应体
                  .bodyToMono(String.class); // 响应数据类型转换
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
          
          /**
           * WebClient异步调用
           * 
           * @throws InterruptedException
           */
          @Test
          public void testUpload002()
              throws InterruptedException
          {
              Mono mono = webClient.post()
                  .uri("https://httpbin.org/post")
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromMultipartData("q1", "java").with("q2", "python").with("file", new ClassPathResource("123.jpg")))
                  .retrieve() // 获取响应体
                  .bodyToMono(String.class); // 响应数据类型转换
              mono.subscribe(body -> log.info("----- reponse: {}", body));
              TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成
          }
      }
      

      2、同步代码

      import java.awt.Desktop;
      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.nio.charset.StandardCharsets;
      import java.util.Collections;
      import java.util.HashMap;
      import java.util.Map;
      import java.util.concurrent.TimeUnit;
      import javax.imageio.ImageIO;
      import org.apache.commons.lang3.RandomUtils;
      import org.junit.BeforeClass;
      import org.junit.Test;
      import org.springframework.core.io.ClassPathResource;
      import org.springframework.core.io.Resource;
      import org.springframework.http.MediaType;
      import org.springframework.util.FileCopyUtils;
      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 org.springframework.web.util.UriComponentsBuilder;
      import com.fly.http.bean.ImageShowDialog;
      import com.fly.http.bean.JsonBeanUtils;
      import com.fly.http.bean.SearchReq;
      import lombok.extern.slf4j.Slf4j;
      import reactor.core.publisher.Mono;
      /**
       * http请求WebClient同步调用实现
       */
      @Slf4j
      public class WebClientSyncTest
      {
          private WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build();
          
          private void openImage(Resource resource)
          {
              try
              {
                  new ImageShowDialog(ImageIO.read(resource.getInputStream()));
              }
              catch (IOException e)
              {
                  log.error(e.getMessage(), e);
              }
          }
          
          @BeforeClass
          public static void init()
          {
              new File("download").mkdirs();
          }
          
          /**
           * WebClient同步调用
           * 
           * @throws IOException
           */
          @Test
          public void testDownFile()
              throws IOException
          {
              Mono mono = webClient.get().uri("https://00fly.online/upload/urls.txt").accept(MediaType.IMAGE_JPEG).exchange();
              ClientResponse response = mono.block();
              log.info("----- headers: {}", response.headers());
              log.info("----- statusCode: {}", response.statusCode());
              
              // 保存到本地
              Resource resource = response.bodyToMono(Resource.class).block();
              FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream("download/urls.txt"));
          }
          
          /**
           * WebClient同步调用
           * 
           * @throws IOException
           * @throws InterruptedException
           */
          @Test
          public void testDownImg001()
              throws IOException, InterruptedException
          {
              Mono mono = webClient.get()
                  .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg")
                  .accept(MediaType.IMAGE_JPEG)
                  .retrieve() // 获取响应体
                  .bodyToMono(Resource.class);
              openImage(mono.block());
              TimeUnit.SECONDS.sleep(10);
          }
          
          /**
           * WebClient同步调用
           * 
           * @throws IOException
           */
          @Test
          public void testDownImg002()
              throws IOException
          {
              Mono mono = webClient.get()
                  .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg")
                  .accept(MediaType.IMAGE_JPEG)
                  .retrieve() // 获取响应体
                  .bodyToMono(Resource.class);
              
              // 保存到本地
              Resource resource = mono.block();
              File dest = new File(String.format("download/img_%s.jpg", System.currentTimeMillis()));
              FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream(dest));
              if (Desktop.isDesktopSupported())
              {
                  Desktop.getDesktop().open(dest.getParentFile());
              }
          }
          
          /**
           * WebClient同步调用
           */
          @Test
          public void testExchange001()
          {
              // get
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              Mono monoGet = webClient.get()
                  .uri(uriBuilder -> uriBuilder.scheme("https")
                      .host("httpbin.org")
                      .path("/get")
                      .queryParams(params) // 等价 queryParam("q1", "java").queryParam("q2", "python")
                      .build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange();
              
              ClientResponse clientResponse = monoGet.block(); // 获取完整的响应对象
              log.info("----- headers: {}", clientResponse.headers());
              log.info("----- statusCode: {}", clientResponse.statusCode());
              log.info("----- reponse: {}", clientResponse.bodyToMono(String.class).block());
          }
          
          /**
           * WebClient同步调用
           */
          @Test
          public void testExchange002()
          {
              // get
              Mono monoGet = webClient.get()
                  .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange();
              ClientResponse clientResponse = monoGet.block(); // 获取完整的响应对象
              log.info("----- headers: {}", clientResponse.headers());
              log.info("----- statusCode: {}", clientResponse.statusCode());
              log.info("----- reponse: {}", clientResponse.bodyToMono(String.class).block());
              
              // formData post
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              Mono monoPost = webClient.post()
                  .uri("https://httpbin.org/post")
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python")
                  .exchange();
              ClientResponse clientResponse2 = monoPost.block(); // 获取完整的响应对象
              log.info("----- headers: {}", clientResponse2.headers());
              log.info("----- statusCode: {}", clientResponse2.statusCode());
              log.info("----- reponse: {}", clientResponse2.bodyToMono(String.class).block());
          }
          
          /**
           * WebClient同步调用
           */
          @Test
          public void testFormDataPost()
          {
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              Mono mono = webClient.post()
                  .uri("https://httpbin.org/post")
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python")
                  .retrieve() // 获取响应体
                  .bodyToMono(String.class); // 响应数据类型转换
              log.info("reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用
           */
          @Test
          public void testGet001()
          {
              Mono mono = webClient.get()
                  .uri("https://httpbin.org/{method}", "get") // {任意命名}
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .retrieve()
                  .bodyToMono(String.class);
              log.info("reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用
           */
          @Test
          public void testGet002()
          {
              Mono mono = webClient.get()
                  .uri("https://httpbin.org/get")
                  .acceptCharset(StandardCharsets.UTF_8)
                  .exchange()
                  .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers()))
                  .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode()))
                  .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));
              log.info("reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用
           * 
           */
          @Test
          public void testGet003()
          {
              Mono mono = webClient.get()
                  .uri("https://httpbin.org/get")
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange()
                  .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers()))
                  .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode()))
                  .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));
              log.info("reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用, https://httpbin.org/get?q=java
           * 
           */
          @Test
          public void testGet004()
          {
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q", "java");
              String uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParams(params).toUriString();
              
              // 注意比较
              // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q", "java", "python").toUriString();
              // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q1", "java").queryParam("q2", "python").toUriString();
              
              Mono mono = webClient.get()
                  .uri(uri)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .exchange()
                  .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers()))
                  .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode()))
                  .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));
              log.info("reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用
           * 
           */
          @Test
          public void testGet005()
          {
              Mono mono = webClient.get()
                  .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .retrieve()
                  .bodyToMono(String.class);
              log.info("reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用
           * 
           */
          @Test
          public void testJsonBody001()
          {
              Mono mono = webClient.post()
                  .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/post").build())
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .contentType(MediaType.APPLICATION_JSON)
                  .bodyValue(Collections.singletonMap("q", "java"))
                  .retrieve()
                  .bodyToMono(String.class);
              log.info("reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用
           * 
           * @throws IOException
           */
          @Test
          public void testJsonBody002()
              throws IOException
          {
              Mono mono;
              int num = RandomUtils.nextInt(1, 4);
              switch (num)
              {
                  case 1: // 方式1,javaBean
                      SearchReq req = new SearchReq();
                      req.setPageNo(1);
                      req.setPageSize(10);
                      req.setKeyword("1");
                      mono = webClient.post()
                          .uri("https://httpbin.org/post")
                          .acceptCharset(StandardCharsets.UTF_8)
                          .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                          .bodyValue(req) // 设置JsonBody
                          .retrieve()
                          .bodyToMono(String.class);
                      log.info("reponse: {}", mono.block());
                      break;
                  
                  case 2: // 方式2,HashMap
                      Map params = new HashMap();
                      params.put("pageNo", "2");
                      params.put("pageSize", "20");
                      params.put("keyword", "2");
                      mono = webClient.post()
                          .uri("https://httpbin.org/post")
                          .acceptCharset(StandardCharsets.UTF_8)
                          .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                          .bodyValue(params) // 设置JsonBody
                          .retrieve()
                          .bodyToMono(String.class);
                      log.info("reponse: {}", mono.block());
                      break;
                  
                  case 3: // 方式3,json字符串
                      Map params2 = new HashMap();
                      params2.put("pageNo", "2");
                      params2.put("pageSize", "20");
                      params2.put("keyword", "2");
                      mono = webClient.post()
                          .uri("https://httpbin.org/post")
                          .acceptCharset(StandardCharsets.UTF_8)
                          .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                          .body(BodyInserters.fromValue(JsonBeanUtils.beanToJson(params2, false))) // 设置formData
                          .retrieve()
                          .bodyToMono(String.class);
                      log.info("reponse: {}", mono.block());
                      break;
              }
          }
          
          /**
           * WebClient同步调用
           * 
           */
          @Test
          public void testUpload001()
          {
              MultiValueMap params = new LinkedMultiValueMap();
              params.add("q1", "java");
              params.add("q2", "python");
              params.add("file", new ClassPathResource("123.jpg"));
              Mono mono = webClient.post()
                  .uri("https://httpbin.org/post")
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromMultipartData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python")
                  .retrieve() // 获取响应体
                  .bodyToMono(String.class); // 响应数据类型转换
              log.info("----- reponse: {}", mono.block());
          }
          
          /**
           * WebClient同步调用
           * 
           */
          @Test
          public void testUpload002()
          {
              Mono mono = webClient.post()
                  .uri("https://httpbin.org/post")
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                  .acceptCharset(StandardCharsets.UTF_8)
                  .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML)
                  .body(BodyInserters.fromMultipartData("q1", "java").with("q2", "python").with("file", new ClassPathResource("123.jpg")))
                  .retrieve() // 获取响应体
                  .bodyToMono(String.class); // 响应数据类型转换
              log.info("----- reponse: {}", mono.block());
          }
      }
      

      3、完整代码

      如何使用下面的备份文件恢复成原始的项目代码,请移步查阅:神奇代码恢复工具

      //goto docker\docker-compose.yml
      version: '3.7'
      services:
        hello:
          image: registry.cn-shanghai.aliyuncs.com/00fly/web-client:0.0.1
          container_name: web-client
          deploy:
            resources:
              limits:
                cpus: '1'
                memory: 200M
              reservations:
                cpus: '0.05'
                memory: 100M
          environment:
            JAVA_OPTS: -server -Xms100m -Xmx100m -Djava.security.egd=file:/dev/./urandom
          restart: on-failure
          logging:
            driver: json-file
            options:
              max-size: 5m
              max-file: '1'
      //goto docker\restart.sh
      #!/bin/bash
      docker-compose down && docker system prune -f && docker-compose up -d && docker stats
      //goto docker\stop.sh
      #!/bin/bash
      docker-compose down
      //goto Dockerfile
      FROM openjdk:8-jre-alpine
      RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
      COPY target/web-client-*.jar  /app.jar
      ENTRYPOINT ["java","-jar","/app.jar"]
      //goto pom.xml
      
      
      	4.0.0
      	com.fly
      	web-client
      	0.0.1
      	web-client
      	jar
      	
      		registry.cn-shanghai.aliyuncs.com
      		UTF-8
      		1.8
      		true
      	
      	
      		
      			org.springframework
      			spring-webflux
      			5.2.3.RELEASE
      		
      		
      			io.projectreactor.netty
      			reactor-netty
      			0.9.4.RELEASE
      		
      		
      			org.apache.logging.log4j
      			log4j-slf4j-impl
      			2.12.1
      		
      		
      			com.fasterxml.jackson.core
      			jackson-databind
      			2.13.0
      		
      		
      			org.apache.commons
      			commons-lang3
      			3.10
      		
      		
      			commons-io
      			commons-io
      			2.5
      		
      		
      			org.projectlombok
      			lombok
      			1.18.12
      			provided
      		
      		
      			junit
      			junit
      			4.12
      		
      	
      	
      		${project.artifactId}-${project.version}
      		
      			
      				org.apache.maven.plugins
      				maven-compiler-plugin
      				3.10.1
      				
      					1.8
      					1.8
      					UTF-8
      				
      			
      			
      				org.apache.maven.plugins
      				maven-shade-plugin
      				3.4.0
      				
      					false
      				
      				
      					
      						package
      						
      							shade
      						
      						
      							false
      							
      								
      									*:*
      								
      							
      							
      								
      								
      									com.fly.http.RunMain
      								
      							
      						
      					
      				
      			
      			
      			
      				io.fabric8
      				docker-maven-plugin
      				0.40.0
      				
      					
      						package
      						
      							build
      							push
      							remove
      						
      					
      				
      				
      					
      					
      					
      					${docker.hub}
      					
      						
      							
      								${docker.hub}/00fly/${project.artifactId}:${project.version}
      							
      								${project.basedir}
      							
      						
      					
      				
      			
      		
      	
      
      //goto src\main\java\com\fly\http\FluxWebClient.java
      package com.fly.http;
      import java.nio.charset.StandardCharsets;
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.List;
      import java.util.concurrent.atomic.AtomicInteger;
      import org.apache.commons.lang3.RandomUtils;
      import org.apache.commons.lang3.StringUtils;
      import org.springframework.http.MediaType;
      import org.springframework.web.reactive.function.client.WebClient;
      import lombok.extern.slf4j.Slf4j;
      /**
       * WebClient是RestTemplete的替代品,有更好的响应式能力,支持异步调用
      * * https://blog.csdn.net/zzhongcy/article/details/105412842 * */ @Slf4j public class FluxWebClient { private List urls = new ArrayList(); // 缓冲区默认256k,设为-1以解决报错Exceeded limit on max bytes to buffer : 262144 private WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build(); public void visitAll() { // block转换为同步调用 if (urls.isEmpty()) { log.info("★★★★★★★★ urls isEmpty, now get urls from api ★★★★★★★★"); String resp = webClient.get().uri("https://00fly.online/upload/urls.txt").acceptCharset(StandardCharsets.UTF_8).accept(MediaType.TEXT_HTML).retrieve().bodyToMono(String.class).block(); urls = Arrays.asList(StringUtils.split(resp, "\r\n")); } // 异步访问 AtomicInteger count = new AtomicInteger(0); urls.stream() .filter(url -> RandomUtils.nextBoolean()) .forEach(url -> webClient.get() .uri(url) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.TEXT_HTML) .retrieve() .bodyToMono(String.class) .subscribe(r -> log.info("process complted: {}. {}", count.incrementAndGet(), url), e -> log.error(e.getMessage()))); log.info("total:{} ==> ############## 异步请求已提交 ##############", urls.size()); } } //goto src\main\java\com\fly\http\RunMain.java package com.fly.http; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j public class RunMain { private static FluxWebClient webClient = new FluxWebClient(); /** * 程序运行入口 * */ public static void main(String[] args) { scheduledThreadPoolExecutorStart(); } private static void scheduledThreadPoolExecutorStart() { new ScheduledThreadPoolExecutor(2).scheduleAtFixedRate(() -> { webClient.visitAll(); }, 0L, 30, TimeUnit.SECONDS); log.info("======== ScheduledThreadPoolExecutor started!"); } /** * Timer线程安全, 但单线程执行, 抛出异常时, task会终止 */ @Deprecated protected static void timeStart() { new Timer().scheduleAtFixedRate(new TimerTask() { @Override public void run() { webClient.visitAll(); } }, 0L, 30 * 1000L); log.info("======== Timer started!"); } } //goto src\main\resources\log4j2.xml //goto src\test\java\com\fly\http\ApiTest.java package com.fly.http; import java.awt.Desktop; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.util.ResourceUtils; import org.springframework.web.reactive.function.client.WebClient; import lombok.extern.slf4j.Slf4j; @Slf4j public class ApiTest { // 缓冲区默认256k,设为-1以解决报错Exceeded limit on max bytes to buffer : 262144 private WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build(); /** * 写入文本文件 * * @param urls * @see [类、类#方法、类#成员] */ private void process(List urls) { try { if (ResourceUtils.isFileURL(ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX))) { String path = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX).getPath() + "urls.txt"; File dest = new File(path); FileUtils.writeLines(dest, StandardCharsets.UTF_8.name(), urls); Desktop.getDesktop().open(dest); } } catch (IOException e) { log.error(e.getMessage(), e); } } @Test public void test001() throws IOException { String jsonBody = webClient.get() .uri("https://00fly.online/upload/data.json") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToMono(String.class) .block() .replace("{", "{\n") .replace("}", "}\n") .replace(",", ",\n"); try (InputStream is = new ByteArrayInputStream(jsonBody.getBytes(StandardCharsets.UTF_8))) { List urls = IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> StringUtils.contains(line, "\"url\":")).map(n -> StringUtils.substringBetween(n, ":\"", "\",")).collect(Collectors.toList()); log.info("★★★★★★★★ urls: {} ★★★★★★★★", urls.size()); process(urls); } } @Test public void test002() throws IOException { Resource resource = new ClassPathResource("data.json"); String jsonBody = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8).replace("{", "{\n").replace("}", "}\n").replace(",", ",\n"); try (InputStream is = new ByteArrayInputStream(jsonBody.getBytes(StandardCharsets.UTF_8))) { List urls = IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> StringUtils.contains(line, "\"url\":")).map(n -> StringUtils.substringBetween(n, ":\"", "\",")).collect(Collectors.toList()); log.info("★★★★★★★★ urls: {} ★★★★★★★★", urls.size()); process(urls); } } @Test public void test003() { String resp = webClient.get().uri("https://00fly.online/upload/urls.txt").acceptCharset(StandardCharsets.UTF_8).accept(MediaType.TEXT_HTML).retrieve().bodyToMono(String.class).block(); List urls = Arrays.asList(StringUtils.split(resp, "\r\n")); AtomicInteger count = new AtomicInteger(0); urls.stream().forEach(url -> log.info("{}. {}", count.incrementAndGet(), url)); } } //goto src\test\java\com\fly\http\bean\ImageShowDialog.java package com.fly.http.bean; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JDialog; import javax.swing.JLabel; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; /** * * 弹出窗口 * * @author 00fly * @version [版本号, 2023年3月3日] * @see [相关类/方法] * @since [产品/模块版本] */ public class ImageShowDialog extends JDialog { private static final long serialVersionUID = -7240357454480002551L; public static void main(String[] args) throws IOException { Resource resources = new ClassPathResource("123.jpg"); BufferedImage image = ImageIO.read(resources.getInputStream()); new ImageShowDialog(image); } public ImageShowDialog(BufferedImage image) { super(); setTitle("图片查看工具"); setSize(image.getWidth(), image.getHeight() + 30); Dimension screenSize = getToolkit().getScreenSize(); Dimension dialogSize = getSize(); dialogSize.height = Math.min(screenSize.height, dialogSize.height); dialogSize.width = Math.min(screenSize.width, dialogSize.width); setLocation((screenSize.width - dialogSize.width) / 2, (screenSize.height - dialogSize.height) / 2); add(new JLabel(new ImageIcon(image))); setVisible(true); setResizable(false); setAlwaysOnTop(true); setDefaultCloseOperation(DISPOSE_ON_CLOSE); } } //goto src\test\java\com\fly\http\bean\JsonBeanUtils.java package com.fly.http.bean; import java.io.IOException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; /** * JsonBean转换工具 * * @author 00fly * */ public class JsonBeanUtils { private static ObjectMapper objectMapper = new ObjectMapper(); /** * bean转json字符串 * * @param bean * @return * @throws IOException */ public static String beanToJson(Object bean) throws IOException { String jsonText = objectMapper.writeValueAsString(bean); return objectMapper.readTree(jsonText).toPrettyString(); } /** * bean转json字符串 * * @param bean * @param pretty 是否格式美化 * @return * @throws IOException */ public static String beanToJson(Object bean, boolean pretty) throws IOException { String jsonText = objectMapper.writeValueAsString(bean); if (pretty) { return objectMapper.readTree(jsonText).toPrettyString(); } return objectMapper.readTree(jsonText).toString(); } /** * json字符串转bean * * @param jsonText * @return * @throws IOException */ public static T jsonToBean(String jsonText, Class clazz) throws IOException { return objectMapper.readValue(jsonText, clazz); } /** * json字符串转bean * * @param jsonText * @return * @throws IOException */ public static T jsonToBean(String jsonText, JavaType javaType) throws IOException { return objectMapper.readValue(jsonText, javaType); } /** * json字符串转bean * * @param jsonText * @return * @throws IOException */ public static T jsonToBean(String jsonText, TypeReference typeRef) throws IOException { return objectMapper.readValue(jsonText, typeRef); } } //goto src\test\java\com\fly\http\bean\SearchReq.java package com.fly.http.bean; import lombok.Data; @Data public class SearchReq { Integer pageNo = 1; Integer pageSize = 10; String keyword; } //goto src\test\java\com\fly\http\WebClientAsyncTest.java package com.fly.http; import java.awt.Desktop; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; import org.apache.commons.lang3.RandomUtils; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.util.FileCopyUtils; 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 org.springframework.web.util.UriComponentsBuilder; import com.fly.http.bean.ImageShowDialog; import com.fly.http.bean.JsonBeanUtils; import com.fly.http.bean.SearchReq; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; /** * http请求WebClient异步调用实现 */ @Slf4j public class WebClientAsyncTest { private WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build(); private void openImage(Resource resource) { try { new ImageShowDialog(ImageIO.read(resource.getInputStream())); } catch (IOException e) { log.error(e.getMessage(), e); } } @BeforeClass public static void init() { new File("download").mkdirs(); } /** * WebClient异步调用 * * @throws IOException * @throws InterruptedException */ @Test public void testDownFile() throws IOException, InterruptedException { Mono mono = webClient.get() .uri("https://00fly.online/upload/urls.txt") .accept(MediaType.IMAGE_JPEG) .exchange() .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers())) .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode())) .flatMap(clientResponse -> clientResponse.bodyToMono(Resource.class)); // 保存到本地 mono.subscribe(resource -> { try { FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream("download/urls.txt")); } catch (IOException e) { log.error(e.getMessage(), e); } }); TimeUnit.SECONDS.sleep(2); } /** * WebClient异步调用 * * @throws IOException * @throws InterruptedException */ @Test public void testDownImg001() throws IOException, InterruptedException { Mono mono = webClient.get() .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg") .accept(MediaType.IMAGE_JPEG) .retrieve() // 获取响应体 .bodyToMono(Resource.class); mono.subscribe(resource -> openImage(resource)); TimeUnit.SECONDS.sleep(10); } /** * WebClient异步调用 * * @throws IOException * @throws InterruptedException */ @Test public void testDownImg002() throws IOException, InterruptedException { Mono mono = webClient.get() .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg") .accept(MediaType.IMAGE_JPEG) .retrieve() // 获取响应体 .bodyToMono(Resource.class); // 保存到本地 mono.subscribe(resource -> { try { File dest = new File(String.format("download/img_%s.jpg", System.currentTimeMillis())); FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream(dest)); if (Desktop.isDesktopSupported()) { Desktop.getDesktop().open(dest.getParentFile()); } } catch (IOException e) { log.error(e.getMessage(), e); } }); TimeUnit.SECONDS.sleep(2); } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testExchange001() throws InterruptedException { // get MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); Mono monoGet = webClient.get() .uri(uriBuilder -> uriBuilder.scheme("https") .host("httpbin.org") .path("/get") .queryParams(params) // 等价 queryParam("q1", "java").queryParam("q2", "python") .build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange(); monoGet.subscribe(clientResponse -> { log.info("----- headers: {}", clientResponse.headers()); log.info("----- statusCode: {}", clientResponse.statusCode()); clientResponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body)); }); TimeUnit.SECONDS.sleep(2); } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testExchange002() throws InterruptedException { // get Mono monoGet = webClient.get() .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange(); monoGet.subscribe(clientResponse -> { log.info("----- headers: {}", clientResponse.headers()); log.info("----- statusCode: {}", clientResponse.statusCode()); clientResponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body)); }); // formData post MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); Mono monoPost = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python") .exchange(); monoPost.subscribe(clientResponse -> { log.info("----- headers: {}", clientResponse.headers()); log.info("----- statusCode: {}", clientResponse.statusCode()); clientResponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body)); }); TimeUnit.SECONDS.sleep(2); } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testFormDataPost() throws InterruptedException { MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); Mono mono = webClient.post() .uri("https://httpbin.org/post") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python") .retrieve() // 获取响应体 .bodyToMono(String.class); // 响应数据类型转换 mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testGet001() throws InterruptedException { Mono mono = webClient.get() .uri("https://httpbin.org/{method}", "get") // {任意命名} .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .retrieve() .bodyToMono(String.class); mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testGet002() throws InterruptedException { Mono mono = webClient.get().uri("https://httpbin.org/get").acceptCharset(StandardCharsets.UTF_8).accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML).exchange(); mono.subscribe(reponse -> { log.info("----- headers: {}", reponse.headers()); log.info("----- statusCode: {}", reponse.statusCode()); reponse.bodyToMono(String.class).subscribe(body -> log.info("----- reponse: {}", body)); }); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testGet003() throws InterruptedException { Mono mono = webClient.get() .uri("https://httpbin.org/get") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange() .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers())) .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode())) .flatMap(clientResponse -> clientResponse.bodyToMono(String.class)); mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用, https://httpbin.org/get?q=java * * @throws InterruptedException */ @Test public void testGet004() throws InterruptedException { MultiValueMap params = new LinkedMultiValueMap(); params.add("q", "java"); String uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParams(params).toUriString(); // 注意比较 // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q", "java", "python").toUriString(); // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q1", "java").queryParam("q2", "python").toUriString(); Mono mono = webClient.get() .uri(uri) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange() .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers())) .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode())) .flatMap(clientResponse -> clientResponse.bodyToMono(String.class)); mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testGet005() throws InterruptedException { Mono mono = webClient.get() .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .retrieve() .bodyToMono(String.class); mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testJsonBody001() throws InterruptedException { Mono mono = webClient.post() .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/post").build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .contentType(MediaType.APPLICATION_JSON) .bodyValue(Collections.singletonMap("q", "java")) .retrieve() .bodyToMono(String.class); mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws IOException * @throws InterruptedException */ @Test public void testJsonBody002() throws IOException, InterruptedException { Mono mono; int num = RandomUtils.nextInt(1, 4); switch (num) { case 1: // 方式1,javaBean SearchReq req = new SearchReq(); req.setPageNo(1); req.setPageSize(10); req.setKeyword("1"); mono = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .bodyValue(req) // 设置JsonBody .retrieve() .bodyToMono(String.class); mono.subscribe(body -> log.info("reponse: {}", body)); break; case 2: // 方式2,HashMap Map params = new HashMap(); params.put("pageNo", "2"); params.put("pageSize", "20"); params.put("keyword", "2"); mono = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .bodyValue(params) // 设置JsonBody .retrieve() .bodyToMono(String.class); mono.subscribe(body -> log.info("reponse: {}", body)); break; case 3: // 方式3,json字符串 Map params2 = new HashMap(); params2.put("pageNo", "2"); params2.put("pageSize", "20"); params2.put("keyword", "2"); mono = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromValue(JsonBeanUtils.beanToJson(params2, false))) // 设置formData .retrieve() .bodyToMono(String.class); mono.subscribe(body -> log.info("reponse: {}", body)); break; } TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testUpload001() throws InterruptedException { MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); params.add("file", new ClassPathResource("123.jpg")); Mono mono = webClient.post() .uri("https://httpbin.org/post") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromMultipartData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python") .retrieve() // 获取响应体 .bodyToMono(String.class); // 响应数据类型转换 mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } /** * WebClient异步调用 * * @throws InterruptedException */ @Test public void testUpload002() throws InterruptedException { Mono mono = webClient.post() .uri("https://httpbin.org/post") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromMultipartData("q1", "java").with("q2", "python").with("file", new ClassPathResource("123.jpg"))) .retrieve() // 获取响应体 .bodyToMono(String.class); // 响应数据类型转换 mono.subscribe(body -> log.info("----- reponse: {}", body)); TimeUnit.SECONDS.sleep(2); // 重要,等待异步调用完成 } } //goto src\test\java\com\fly\http\WebClientSyncTest.java package com.fly.http; import java.awt.Desktop; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; import org.apache.commons.lang3.RandomUtils; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.util.FileCopyUtils; 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 org.springframework.web.util.UriComponentsBuilder; import com.fly.http.bean.ImageShowDialog; import com.fly.http.bean.JsonBeanUtils; import com.fly.http.bean.SearchReq; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; /** * http请求WebClient同步调用实现 */ @Slf4j public class WebClientSyncTest { private WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build(); private void openImage(Resource resource) { try { new ImageShowDialog(ImageIO.read(resource.getInputStream())); } catch (IOException e) { log.error(e.getMessage(), e); } } @BeforeClass public static void init() { new File("download").mkdirs(); } /** * WebClient同步调用 * * @throws IOException */ @Test public void testDownFile() throws IOException { Mono mono = webClient.get().uri("https://00fly.online/upload/urls.txt").accept(MediaType.IMAGE_JPEG).exchange(); ClientResponse response = mono.block(); log.info("----- headers: {}", response.headers()); log.info("----- statusCode: {}", response.statusCode()); // 保存到本地 Resource resource = response.bodyToMono(Resource.class).block(); FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream("download/urls.txt")); } /** * WebClient同步调用 * * @throws IOException * @throws InterruptedException */ @Test public void testDownImg001() throws IOException, InterruptedException { Mono mono = webClient.get() .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg") .accept(MediaType.IMAGE_JPEG) .retrieve() // 获取响应体 .bodyToMono(Resource.class); openImage(mono.block()); TimeUnit.SECONDS.sleep(10); } /** * WebClient同步调用 * * @throws IOException */ @Test public void testDownImg002() throws IOException { Mono mono = webClient.get() .uri("https://00fly.online/upload/2019/02/201902262129360274AKuFZcUfip.jpg") .accept(MediaType.IMAGE_JPEG) .retrieve() // 获取响应体 .bodyToMono(Resource.class); // 保存到本地 Resource resource = mono.block(); File dest = new File(String.format("download/img_%s.jpg", System.currentTimeMillis())); FileCopyUtils.copy(resource.getInputStream(), new FileOutputStream(dest)); if (Desktop.isDesktopSupported()) { Desktop.getDesktop().open(dest.getParentFile()); } } /** * WebClient同步调用 */ @Test public void testExchange001() { // get MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); Mono monoGet = webClient.get() .uri(uriBuilder -> uriBuilder.scheme("https") .host("httpbin.org") .path("/get") .queryParams(params) // 等价 queryParam("q1", "java").queryParam("q2", "python") .build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange(); ClientResponse clientResponse = monoGet.block(); // 获取完整的响应对象 log.info("----- headers: {}", clientResponse.headers()); log.info("----- statusCode: {}", clientResponse.statusCode()); log.info("----- reponse: {}", clientResponse.bodyToMono(String.class).block()); } /** * WebClient同步调用 */ @Test public void testExchange002() { // get Mono monoGet = webClient.get() .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange(); ClientResponse clientResponse = monoGet.block(); // 获取完整的响应对象 log.info("----- headers: {}", clientResponse.headers()); log.info("----- statusCode: {}", clientResponse.statusCode()); log.info("----- reponse: {}", clientResponse.bodyToMono(String.class).block()); // formData post MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); Mono monoPost = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python") .exchange(); ClientResponse clientResponse2 = monoPost.block(); // 获取完整的响应对象 log.info("----- headers: {}", clientResponse2.headers()); log.info("----- statusCode: {}", clientResponse2.statusCode()); log.info("----- reponse: {}", clientResponse2.bodyToMono(String.class).block()); } /** * WebClient同步调用 */ @Test public void testFormDataPost() { MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); Mono mono = webClient.post() .uri("https://httpbin.org/post") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromFormData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python") .retrieve() // 获取响应体 .bodyToMono(String.class); // 响应数据类型转换 log.info("reponse: {}", mono.block()); } /** * WebClient同步调用 */ @Test public void testGet001() { Mono mono = webClient.get() .uri("https://httpbin.org/{method}", "get") // {任意命名} .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .retrieve() .bodyToMono(String.class); log.info("reponse: {}", mono.block()); } /** * WebClient同步调用 */ @Test public void testGet002() { Mono mono = webClient.get() .uri("https://httpbin.org/get") .acceptCharset(StandardCharsets.UTF_8) .exchange() .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers())) .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode())) .flatMap(clientResponse -> clientResponse.bodyToMono(String.class)); log.info("reponse: {}", mono.block()); } /** * WebClient同步调用 * */ @Test public void testGet003() { Mono mono = webClient.get() .uri("https://httpbin.org/get") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange() .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers())) .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode())) .flatMap(clientResponse -> clientResponse.bodyToMono(String.class)); log.info("reponse: {}", mono.block()); } /** * WebClient同步调用, https://httpbin.org/get?q=java * */ @Test public void testGet004() { MultiValueMap params = new LinkedMultiValueMap(); params.add("q", "java"); String uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParams(params).toUriString(); // 注意比较 // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q", "java", "python").toUriString(); // uri = UriComponentsBuilder.fromUriString("https://httpbin.org/get").queryParam("q1", "java").queryParam("q2", "python").toUriString(); Mono mono = webClient.get() .uri(uri) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .exchange() .doOnSuccess(clientResponse -> log.info("----- headers: {}", clientResponse.headers())) .doOnSuccess(clientResponse -> log.info("----- statusCode: {}", clientResponse.statusCode())) .flatMap(clientResponse -> clientResponse.bodyToMono(String.class)); log.info("reponse: {}", mono.block()); } /** * WebClient同步调用 * */ @Test public void testGet005() { Mono mono = webClient.get() .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/get").queryParam("q1", "java").queryParam("q2", "python").build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .retrieve() .bodyToMono(String.class); log.info("reponse: {}", mono.block()); } /** * WebClient同步调用 * */ @Test public void testJsonBody001() { Mono mono = webClient.post() .uri(uriBuilder -> uriBuilder.scheme("https").host("httpbin.org").path("/post").build()) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .contentType(MediaType.APPLICATION_JSON) .bodyValue(Collections.singletonMap("q", "java")) .retrieve() .bodyToMono(String.class); log.info("reponse: {}", mono.block()); } /** * WebClient同步调用 * * @throws IOException */ @Test public void testJsonBody002() throws IOException { Mono mono; int num = RandomUtils.nextInt(1, 4); switch (num) { case 1: // 方式1,javaBean SearchReq req = new SearchReq(); req.setPageNo(1); req.setPageSize(10); req.setKeyword("1"); mono = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .bodyValue(req) // 设置JsonBody .retrieve() .bodyToMono(String.class); log.info("reponse: {}", mono.block()); break; case 2: // 方式2,HashMap Map params = new HashMap(); params.put("pageNo", "2"); params.put("pageSize", "20"); params.put("keyword", "2"); mono = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .bodyValue(params) // 设置JsonBody .retrieve() .bodyToMono(String.class); log.info("reponse: {}", mono.block()); break; case 3: // 方式3,json字符串 Map params2 = new HashMap(); params2.put("pageNo", "2"); params2.put("pageSize", "20"); params2.put("keyword", "2"); mono = webClient.post() .uri("https://httpbin.org/post") .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromValue(JsonBeanUtils.beanToJson(params2, false))) // 设置formData .retrieve() .bodyToMono(String.class); log.info("reponse: {}", mono.block()); break; } } /** * WebClient同步调用 * */ @Test public void testUpload001() { MultiValueMap params = new LinkedMultiValueMap(); params.add("q1", "java"); params.add("q2", "python"); params.add("file", new ClassPathResource("123.jpg")); Mono mono = webClient.post() .uri("https://httpbin.org/post") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromMultipartData(params)) // 设置formData,等价 BodyInserters.fromFormData("q1", "java").with("q2", "python") .retrieve() // 获取响应体 .bodyToMono(String.class); // 响应数据类型转换 log.info("----- reponse: {}", mono.block()); } /** * WebClient同步调用 * */ @Test public void testUpload002() { Mono mono = webClient.post() .uri("https://httpbin.org/post") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .acceptCharset(StandardCharsets.UTF_8) .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML) .body(BodyInserters.fromMultipartData("q1", "java").with("q2", "python").with("file", new ClassPathResource("123.jpg"))) .retrieve() // 获取响应体 .bodyToMono(String.class); // 响应数据类型转换 log.info("----- reponse: {}", mono.block()); } }

      有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

      -over-

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]