Bootstrap

【代码小抄】如何使用WebClient开发响应式接口

在全民AI的大势下,很多类Chat GPT的聊天对话吐字形式的产品层出不穷,服务端与前端都需要对生成式Stream的技术方案进行了解,本篇内容简单Java工程师如何借助Spring WebClient开发响应式接口

首先引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
  <version>2.7.16</version> # 修改为自己的版本
</dependency>

创建WebClient配置类

@Configuration
public class WebClientConfig {

    @Bean(name = "webClient") 
    WebClient webClient() { 
     ExchangeStrategies strategies = ExchangeStrategies.builder() // 创建一个ExchangeStrategies构建器,用于配置HTTP消息的编码和解码策略
             .codecs(configurer -> // 使用codecs方法来配置编解码器
                     configurer.defaultCodecs() // 获取默认的编解码器配置
                             .jackson2JsonDecoder(new Jackson2JsonDecoder()) // 使用Jackson 2 JSON解码器,并传入一个新的实例作为参数
             )
             .build(); // 构建ExchangeStrategies对象
     return WebClient.builder() // 创建一个WebClient构建器
             .exchangeStrategies(strategies) // 设置之前构建的ExchangeStrategies,用于处理消息编码和解码
             .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE) // 为所有请求设置默认的HTTP头,这里设置Content-Type为application/json,并指定UTF-8编码
             .build(); // 构建WebClient实例
    }

}

GPT相关Entity

@Data
public class CompletionRequest {
    // 对话的消息列表
    private List<Message> messages;
    // 聊天完成中可以生成的最大令牌数
    @SerializedName("max_tokens")
    public int maxTokens;
    // temperature范围 0 - 2,值越高,响应结果越随机(What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.)
    private Double temperature;

    private String model;
}

@Data
@AllArgsConstructor
public class Message {
    private String role;
    private String content;
}

@Data
public class CompletionResponse {
    private Long created;
    private String model;
    private List<Choice> choices;
}

@Data
public class Choice {
    private Delta delta;
    private int index;
    private String finish_reason;
}

@Data
public class Delta {
    private String role;
    private String content;
}

创建Controller,这里与平时接口不同的是媒体类型不再是 application/json ,而是text/event-stream

@ApiOperation(value = "发送补全对话", notes = "wheelmouse", httpMethod = "POST",
            produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @PostMapping(value = "/completion", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<CompletionResponse>  completion(@RequestBody CompletionRequest request) {
        return gptService.completion(request);
    }

编写Service方法


@Autowired
WebClient webClient;

// 发起GET请求
Mono<String> response = webClient.get() // 指定GET请求
       .uri("/api/data") // 指定请求的路径
       .retrieve() // 获取响应
       .bodyToMono(String.class); // 将响应体转换为字符串

// 订阅并消费响应
response.subscribe(
       result -> System.out.println("GET请求结果:" + result), // 处理成功的结果
       error -> System.out.println("GET请求错误:" + error.getMessage()) // 处理错误
);


// 发起POST请求
public Flux<CompletionResponse> completion(CompletionRequest request) {
   return webClient
    .post() // 创建一个POST请求
    .uri("localhost:8080/stream/api") // 设置请求的URL为第三方接口的URL
    .body(BodyInserters.fromObject(JacksonUtils.objToJson(chatBotCompletion))) // 将请求体设置为chatBotCompletion对象转换为JSON字符串的结果
    .retrieve() // 触发实际的HTTP请求并获取响应
    .bodyToFlux(String.class) // 将响应体转换为字符串类型的Flux流,这意味着响应可以是多个字符串消息
    .onErrorResume(WebClientResponseException.class, ex -> { // 当发生WebClientResponseException异常时,执行以下操作
        HttpStatus status = ex.getStatusCode(); // 获取异常中的HTTP状态码
        String res = ex.getResponseBodyAsString(); // 获取异常中的响应体字符串
        logger.error("OpenAI API error: {} {}", status, res); // 使用日志记录器记录错误信息
        return Mono.just(""); // 返回一个空的Mono以继续处理流程
    })
    .map(eventStream -> { // 对流中的每个字符串元素应用map操作,将其转换为其他对象
        return processEventStream(eventStream); // 调用processEventStream方法处理事件流
    })
    .doOnNext(responseList::add) // 当流中有新的元素时,将其添加到responseList集合中
    .doOnComplete(() -> handleResponse(conversationId, responseList)) // 当流完成时,调用handleResponse方法处理最终的响应
    // 补充响应条数
    .concatWith(Flux.just(new CompletionResponse())); // 将这个新对象作为流的一部分,追加到现有的流中
}

private CompletionResponse processEventStream(String eventStream) {
   CompletionResponse response = JacksonUtils.jsonToPojo(eventStream,
           CompletionResponse.class);
   if (response == null) {
       response = new CompletionResponse();
   }
   return response;
}

private void handleResponse(Long conversationId, List<CompletionResponse> responseList) {
   // do anything
}

效果展示:
使用postman测试接口,可以看到是吐字的方式返回响应
在这里插入图片描述

总结

通过以上介绍,我们可以看到Spring中的WebClient是一个非常强大和灵活的工具,可以方便地与React接口进行交互。它支持各种HTTP方法,可以处理不同类型的请求和响应,同时还提供了错误处理的机制。希望这篇文章能够帮助您更好地理解如何使用Spring中的WebClient来调用React接口。

公众号推荐

;