diff --git a/src/main/java/com/wecom/robot/config/RestTemplateConfig.java b/src/main/java/com/wecom/robot/config/RestTemplateConfig.java new file mode 100644 index 0000000..a49fb63 --- /dev/null +++ b/src/main/java/com/wecom/robot/config/RestTemplateConfig.java @@ -0,0 +1,18 @@ +package com.wecom.robot.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(5000); + return new RestTemplate(factory); + } +} diff --git a/src/main/java/com/wecom/robot/service/AiServiceClient.java b/src/main/java/com/wecom/robot/service/AiServiceClient.java new file mode 100644 index 0000000..d1221f3 --- /dev/null +++ b/src/main/java/com/wecom/robot/service/AiServiceClient.java @@ -0,0 +1,13 @@ +package com.wecom.robot.service; + +import com.wecom.robot.dto.ai.ChatRequest; +import com.wecom.robot.dto.ai.ChatResponse; + +import java.util.concurrent.CompletableFuture; + +public interface AiServiceClient { + + CompletableFuture generateReply(ChatRequest request); + + boolean healthCheck(); +} diff --git a/src/main/java/com/wecom/robot/service/impl/AiServiceClientImpl.java b/src/main/java/com/wecom/robot/service/impl/AiServiceClientImpl.java new file mode 100644 index 0000000..8193d82 --- /dev/null +++ b/src/main/java/com/wecom/robot/service/impl/AiServiceClientImpl.java @@ -0,0 +1,75 @@ +package com.wecom.robot.service.impl; + +import com.wecom.robot.config.AiServiceConfig; +import com.wecom.robot.dto.ai.ChatRequest; +import com.wecom.robot.dto.ai.ChatResponse; +import com.wecom.robot.service.AiServiceClient; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; +import io.github.resilience4j.timelimiter.annotation.TimeLimiter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AiServiceClientImpl implements AiServiceClient { + + private static final String CHAT_ENDPOINT = "/ai/chat"; + private static final String HEALTH_ENDPOINT = "/ai/health"; + + private final AiServiceConfig aiServiceConfig; + private final RestTemplate restTemplate; + + @Override + @CircuitBreaker(name = "aiService", fallbackMethod = "generateReplyFallback") + @TimeLimiter(name = "aiService") + public CompletableFuture generateReply(ChatRequest request) { + log.info("[AC-MCA-04] 调用 AI 服务: sessionId={}", request.getSessionId()); + + String url = aiServiceConfig.getUrl() + CHAT_ENDPOINT; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity( + url, entity, ChatResponse.class); + + log.info("[AC-MCA-05] AI 服务响应: sessionId={}, shouldTransfer={}", + request.getSessionId(), + response.getBody() != null ? response.getBody().getShouldTransfer() : null); + + return CompletableFuture.completedFuture(response.getBody()); + } + + public CompletableFuture generateReplyFallback(ChatRequest request, Throwable cause) { + log.warn("[AC-MCA-06][AC-MCA-07] AI 服务降级: sessionId={}, cause={}", + request.getSessionId(), cause.getMessage()); + + ChatResponse fallbackResponse = ChatResponse.fallbackWithTransfer( + "抱歉,我暂时无法回答您的问题,正在为您转接人工客服...", + cause.getMessage() + ); + + return CompletableFuture.completedFuture(fallbackResponse); + } + + @Override + public boolean healthCheck() { + try { + String url = aiServiceConfig.getUrl() + HEALTH_ENDPOINT; + ResponseEntity response = restTemplate.getForEntity(url, String.class); + return response.getStatusCode().is2xxSuccessful(); + } catch (Exception e) { + log.error("[AC-MCA-04] AI 服务健康检查失败: {}", e.getMessage()); + return false; + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0eb8dbb..0d01b43 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -42,3 +42,17 @@ channel: enabled: false jd: enabled: false + +resilience4j: + circuitbreaker: + instances: + aiService: + failure-rate-threshold: 50 + sliding-window-size: 10 + sliding-window-type: COUNT_BASED + wait-duration-in-open-state: 30s + permitted-number-of-calls-in-half-open-state: 3 + timelimiter: + instances: + aiService: + timeout-duration: 5s