This commit is contained in:
root
2025-11-20 09:13:49 +05:00
parent 5c527079d0
commit d6ab1f5681
3 changed files with 124 additions and 16 deletions
@@ -19,6 +19,9 @@ import java.util.Map;
import reactor.netty.http.client.HttpClient;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import jakarta.annotation.PostConstruct;
@Service
@@ -34,6 +37,18 @@ public class OpenAIAnalyticsService {
@Value("${openai.timeoutMs:90000}")
private long timeoutMs;
@Value("${openai.retry.maxAttempts:5}")
private int maxRetryAttempts;
@Value("${openai.retry.initialDelayMs:1000}")
private long initialRetryDelayMs;
@Value("${openai.retry.maxDelayMs:60000}")
private long maxRetryDelayMs;
@Value("${openai.retry.multiplier:2.0}")
private double retryMultiplier;
@Value("${openai.api.key}")
private String apiKey;
@@ -156,16 +171,20 @@ public class OpenAIAnalyticsService {
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
})
.retryWhen(createRetrySpec("generate"))
.onErrorResume(err -> {
logger.error("OpenAI request failed: {} - {}", err.getMessage(),
logger.error("OpenAI request failed after retries: {} - {}", err.getMessage(),
err.getClass().getSimpleName());
if (err.getMessage().contains("401")) {
logger.error(
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
} else if (err.getMessage().contains("429")) {
logger.error("OpenAI API rate limit exceeded. Please try again later.");
} else if (err.getMessage().contains("500")) {
logger.error("OpenAI API server error. Please try again later.");
if (err instanceof WebClientResponseException) {
WebClientResponseException wcre = (WebClientResponseException) err;
if (wcre.getStatusCode().value() == 401) {
logger.error(
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
} else if (wcre.getStatusCode().value() == 429) {
logger.error("OpenAI API rate limit exceeded after all retry attempts.");
} else if (wcre.getStatusCode().value() >= 500) {
logger.error("OpenAI API server error after all retry attempts.");
}
}
return Mono.empty();
})
@@ -217,16 +236,20 @@ public class OpenAIAnalyticsService {
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
})
.retryWhen(createRetrySpec("generateWithInstruction"))
.onErrorResume(err -> {
logger.error("OpenAI request failed: {} - {}", err.getMessage(),
logger.error("OpenAI request failed after retries: {} - {}", err.getMessage(),
err.getClass().getSimpleName());
if (err.getMessage().contains("401")) {
logger.error(
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
} else if (err.getMessage().contains("429")) {
logger.error("OpenAI API rate limit exceeded. Please try again later.");
} else if (err.getMessage().contains("500")) {
logger.error("OpenAI API server error. Please try again later.");
if (err instanceof WebClientResponseException) {
WebClientResponseException wcre = (WebClientResponseException) err;
if (wcre.getStatusCode().value() == 401) {
logger.error(
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
} else if (wcre.getStatusCode().value() == 429) {
logger.error("OpenAI API rate limit exceeded after all retry attempts.");
} else if (wcre.getStatusCode().value() >= 500) {
logger.error("OpenAI API server error after all retry attempts.");
}
}
return Mono.empty();
})
@@ -254,4 +277,37 @@ public class OpenAIAnalyticsService {
return null;
}
}
private RetryBackoffSpec createRetrySpec(String operation) {
return Retry.backoff(maxRetryAttempts, Duration.ofMillis(initialRetryDelayMs))
.maxBackoff(Duration.ofMillis(maxRetryDelayMs))
.multiplier(retryMultiplier)
.filter(throwable -> {
if (throwable instanceof WebClientResponseException) {
WebClientResponseException wcre = (WebClientResponseException) throwable;
int statusCode = wcre.getStatusCode().value();
// Retry on 429 (rate limit) and 5xx (server errors)
boolean shouldRetry = statusCode == 429 || statusCode >= 500;
if (shouldRetry) {
logger.warn("OpenAI API returned {} for operation '{}'. Will retry...", statusCode,
operation);
}
return shouldRetry;
}
// Retry on network errors (timeouts, connection issues)
return throwable instanceof java.util.concurrent.TimeoutException
|| throwable instanceof java.net.ConnectException
|| throwable instanceof java.io.IOException;
})
.doBeforeRetry(retrySignal -> {
long attempt = retrySignal.totalRetries() + 1;
logger.info("Retrying OpenAI request for operation '{}' (attempt {}/{})",
operation, attempt, maxRetryAttempts);
})
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
logger.error("OpenAI request for operation '{}' exhausted all {} retry attempts",
operation, maxRetryAttempts);
return retrySignal.failure();
});
}
}
@@ -17,6 +17,8 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
@Service
public class OpenAiChartService {
@@ -31,6 +33,18 @@ public class OpenAiChartService {
@Value("${openai.timeoutMs:90000}")
private long timeoutMs;
@Value("${openai.retry.maxAttempts:5}")
private int maxRetryAttempts;
@Value("${openai.retry.initialDelayMs:1000}")
private long initialRetryDelayMs;
@Value("${openai.retry.maxDelayMs:60000}")
private long maxRetryDelayMs;
@Value("${openai.retry.multiplier:2.0}")
private double retryMultiplier;
public OpenAiChartService(
@Value("${openai.api.url:https://api.openai.com/v1/chat/completions}") String apiUrl,
@Value("${openai.api.key:}") String apiKey) {
@@ -93,6 +107,7 @@ public class OpenAiChartService {
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
})
.timeout(Duration.ofMillis(timeoutMs))
.retryWhen(createRetrySpec(taskName))
.doOnSuccess(response -> {
// 4. Логируем успешный, но еще не обработанный ответ
logger.info("Raw OpenAI response for task '{}': {}", taskName, response);
@@ -242,4 +257,37 @@ public class OpenAiChartService {
return buildSvgPrompt(json, "ru");
}
private RetryBackoffSpec createRetrySpec(String operation) {
return Retry.backoff(maxRetryAttempts, Duration.ofMillis(initialRetryDelayMs))
.maxBackoff(Duration.ofMillis(maxRetryDelayMs))
.multiplier(retryMultiplier)
.filter(throwable -> {
if (throwable instanceof WebClientResponseException) {
WebClientResponseException wcre = (WebClientResponseException) throwable;
int statusCode = wcre.getStatusCode().value();
// Retry on 429 (rate limit) and 5xx (server errors)
boolean shouldRetry = statusCode == 429 || statusCode >= 500;
if (shouldRetry) {
logger.warn("OpenAI API returned {} for operation '{}'. Will retry...", statusCode,
operation);
}
return shouldRetry;
}
// Retry on network errors (timeouts, connection issues)
return throwable instanceof java.util.concurrent.TimeoutException
|| throwable instanceof java.net.ConnectException
|| throwable instanceof java.io.IOException;
})
.doBeforeRetry(retrySignal -> {
long attempt = retrySignal.totalRetries() + 1;
logger.info("Retrying OpenAI request for operation '{}' (attempt {}/{})",
operation, attempt, maxRetryAttempts);
})
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
logger.error("OpenAI request for operation '{}' exhausted all {} retry attempts",
operation, maxRetryAttempts);
return retrySignal.failure();
});
}
}
@@ -58,6 +58,10 @@ openai.api.key=sk-proj-zEsggv3MCgvZ2aQbRhpCfyZPKn-isyylNhO16dekXGpM3yzi9H4g0zSjp
openai.api.url=https://api.openai.com/v1/chat/completions
openai.model.name=gpt-4o-mini
openai.timeoutMs=90000
openai.retry.maxAttempts=5
openai.retry.initialDelayMs=1000
openai.retry.maxDelayMs=60000
openai.retry.multiplier=2.0
# Email Configuration
spring.mail.host=smtp.gmail.com