diff --git a/src/main/java/com/goi/erp/client/SamsaraStatOdometerClient.java b/src/main/java/com/goi/erp/client/SamsaraStatOdometerClient.java index b3f1630..e575148 100644 --- a/src/main/java/com/goi/erp/client/SamsaraStatOdometerClient.java +++ b/src/main/java/com/goi/erp/client/SamsaraStatOdometerClient.java @@ -2,57 +2,117 @@ package com.goi.erp.client; import com.goi.erp.dto.ScheduleWorkerRequestDto; import com.goi.erp.dto.ext.ExtSamsaraOdometerFetchCommand; - +import com.goi.erp.dto.ext.ExtSamsaraOdometerRecordDto; import lombok.extern.slf4j.Slf4j; - import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + @Slf4j @Service public class SamsaraStatOdometerClient { - private final WebClient webClient; + /** WebClient in-memory 버퍼 한계 (기본 256KB → 16MB로 상향) */ + private static final int MAX_IN_MEMORY_SIZE = 16 * 1024 * 1024; // 16MB + + private final WebClient webClient; + + /** 한 번 호출 시 보낼 externalId 최대 개수 (기본 100) */ + private final int chunkSize; public SamsaraStatOdometerClient( WebClient.Builder webClientBuilder, - @Value("${integration.api.base-url}") String integrationBaseUrl + @Value("${integration.api.base-url}") String integrationBaseUrl, + @Value("${integration.api.odometer.chunk-size:100}") int chunkSize ) { this.webClient = webClientBuilder .baseUrl(integrationBaseUrl) + // DataBufferLimitException(262144) 방지 + .codecs(configurer -> + configurer.defaultCodecs().maxInMemorySize(MAX_IN_MEMORY_SIZE)) .build(); + this.chunkSize = chunkSize; } /** * integration-service 호출 - * POST /vehicle/odometer/fetch + * POST /vehicle/stat/odometer/fetch + * + * externalIds 가 chunkSize 를 넘으면 나눠서 호출하고 records 를 병합한다. */ public ExtSamsaraOdometerFetchCommand fetchOdometer( ScheduleWorkerRequestDto request ) { + List externalIds = request.getVehicleExternalIds(); - try { - return webClient.post() + // externalIds 가 없거나 chunkSize 이하이면 단건 호출 + if (externalIds == null || externalIds.size() <= chunkSize) { + return callOnce(request); + } + + // chunk 단위로 나눠서 호출 → records 병합 + final List originalIds = externalIds; + List mergedRecords = new ArrayList<>(); + String source = null; + LocalDateTime fetchedAt = null; + + try { + for (int i = 0; i < originalIds.size(); i += chunkSize) { + List chunk = originalIds.subList( + i, Math.min(i + chunkSize, originalIds.size())); + + // 호출자 객체에 청크 세팅 (마지막에 원복) + request.setVehicleExternalIds(chunk); + + ExtSamsaraOdometerFetchCommand part = callOnce(request); + if (part == null || part.getRecords() == null) { + continue; + } + + // source / fetchedAt 은 첫 응답 기준으로 유지 + if (source == null) { + source = part.getSource(); + fetchedAt = part.getFetchedAt(); + } + mergedRecords.addAll(part.getRecords()); + } + } finally { + // 호출자 request 객체 원복 (부수효과 방지) + request.setVehicleExternalIds(originalIds); + } + + log.info("[ODOMETER_RAW][CHUNK] totalExternalIds={} chunkSize={} mergedRecords={}", + originalIds.size(), chunkSize, mergedRecords.size()); + + return ExtSamsaraOdometerFetchCommand.builder() + .source(source) + .fetchedAt(fetchedAt) + .records(mergedRecords) + .build(); + } + + /** + * 실제 단건 호출. 현재 request 에 세팅된 externalIds 로 그대로 호출한다. + */ + private ExtSamsaraOdometerFetchCommand callOnce( + ScheduleWorkerRequestDto request + ) { + return webClient.post() .uri("/vehicle/stat/odometer/fetch") .bodyValue(request) .retrieve() .onStatus( - status -> status.is4xxClientError() || status.is5xxServerError(), - resp -> resp.bodyToMono(String.class) - .map(body -> - new RuntimeException( - "INTEGRATION_ERROR: " + body - ) - ) + status -> status.is4xxClientError() || status.is5xxServerError(), + resp -> resp.bodyToMono(String.class) + .map(body -> + new RuntimeException("INTEGRATION_ERROR: " + body) + ) ) .bodyToMono(ExtSamsaraOdometerFetchCommand.class) .block(); - - } catch (Exception e) { - // 이 메시지가 OPR 서비스 레벨까지 올라감 - throw e; - } } -} - +} \ No newline at end of file