dto다운로드
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 22s

This commit is contained in:
Boram
2026-09-03 10:46:10 +09:00
parent 693ddea18d
commit 82c087e2da

View File

@@ -1,8 +1,11 @@
package io.shinhanlife.dat.mcg.presentation;
import io.shinhanlife.dat.mcg.config.GatewayFallbackProperties;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import java.util.Set;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
@@ -12,61 +15,104 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
/**
* Gateway 화면에서 들어온 DTO 엑셀 요청을 실제 DTO 클래스가 있는 Tool Pod로 전달한다.
* 브라우저가 동일 출처(8081)만 호출하도록 하여 CORS와 배포 주소 차이를 숨긴다.
*/
/** Aggregates DTO downloads from the CUS, SAL, PRO, and SYS Tool Pods. */
@RestController
public class DtoDownloadProxyController {
private static final MediaType XLSX_MEDIA_TYPE = MediaType.parseMediaType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
private static final List<String> DTO_ROUTE_KEYS = List.of("cus", "sal", "pro", "sys");
private final RestClient restClient;
private final String toolPodUrl;
private final List<String> toolPodUrls;
public DtoDownloadProxyController(
RestClient.Builder restClientBuilder,
@Value("${mcp.gateway.fallback.default-url:http://localhost:8084}") String toolPodUrl) {
GatewayFallbackProperties fallbackProperties) {
this.restClient = restClientBuilder.build();
this.toolPodUrl = toolPodUrl.replaceAll("/+$", "");
this.toolPodUrls = resolveToolPodUrls(fallbackProperties);
}
@GetMapping("/dto-download/options")
public List<String> options() {
// 셀렉트박스에 표시할 DTO 이름 목록을 Tool Pod에서 조회한다.
List<String> options = restClient.get()
.uri(toolPodUrl + "/dto-download/options")
.retrieve()
.body(new ParameterizedTypeReference<>() {});
return options == null ? List.of() : options;
Set<String> mergedOptions = new LinkedHashSet<>();
for (String toolPodUrl : toolPodUrls) {
try {
List<String> podOptions = restClient.get()
.uri(toolPodUrl + "/dto-download/options")
.retrieve()
.body(new ParameterizedTypeReference<>() {});
if (podOptions != null) {
mergedOptions.addAll(podOptions);
}
} catch (RestClientException ignored) {
// An unavailable Pod must not hide DTOs returned by the other running Pods.
}
}
return mergedOptions.stream().sorted().toList();
}
@GetMapping("/dto-download/{dtoName}")
public ResponseEntity<byte[]> download(@PathVariable("dtoName") String dtoName) {
byte[] workbook;
try {
// 생성된 엑셀 바이트를 그대로 브라우저에 전달한다.
workbook = restClient.get()
.uri(toolPodUrl + "/dto-download/{dtoName}", dtoName)
.retrieve()
.body(byte[].class);
} catch (RestClientResponseException error) {
// 형식 불일치 등의 상태 코드와 오류 메시지도 변경 없이 전달한다.
return ResponseEntity.status(error.getStatusCode())
.contentType(error.getResponseHeaders() != null
&& error.getResponseHeaders().getContentType() != null
? error.getResponseHeaders().getContentType() : MediaType.TEXT_PLAIN)
.body(error.getResponseBodyAsByteArray());
for (String toolPodUrl : toolPodUrls) {
try {
byte[] workbook = restClient.get()
.uri(toolPodUrl + "/dto-download/{dtoName}", dtoName)
.retrieve()
.body(byte[].class);
return workbookResponse(dtoName, workbook);
} catch (RestClientResponseException error) {
if (error.getStatusCode().value() == 404) {
continue;
}
return downstreamError(error);
} catch (RestClientException ignored) {
// Try the next configured Tool Pod when this Pod cannot be reached.
}
}
String fileName = dtoName + ".xlsx";
return ResponseEntity.status(404)
.contentType(new MediaType("text", "plain", StandardCharsets.UTF_8))
.body(("DTO not found in CUS/SAL/PRO/SYS: " + dtoName)
.getBytes(StandardCharsets.UTF_8));
}
private static List<String> resolveToolPodUrls(GatewayFallbackProperties properties) {
List<String> urls = new ArrayList<>();
for (String routeKey : DTO_ROUTE_KEYS) {
String url = properties.getRoutes().get(routeKey);
if (url != null && !url.isBlank()) {
String normalized = url.replaceAll("/+$", "");
if (!urls.contains(normalized)) {
urls.add(normalized);
}
}
}
if (urls.isEmpty() && properties.getDefaultUrl() != null
&& !properties.getDefaultUrl().isBlank()) {
urls.add(properties.getDefaultUrl().replaceAll("/+$", ""));
}
return List.copyOf(urls);
}
private static ResponseEntity<byte[]> workbookResponse(String dtoName, byte[] workbook) {
byte[] body = workbook == null ? new byte[0] : workbook;
return ResponseEntity.ok()
.contentType(XLSX_MEDIA_TYPE)
.contentLength(workbook == null ? 0 : workbook.length)
.contentLength(body.length)
.header(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.attachment().filename(fileName).build().toString())
.body(workbook == null ? new byte[0] : workbook);
ContentDisposition.attachment().filename(dtoName + ".xlsx").build().toString())
.body(body);
}
private static ResponseEntity<byte[]> downstreamError(RestClientResponseException error) {
return ResponseEntity.status(error.getStatusCode())
.contentType(error.getResponseHeaders() != null
&& error.getResponseHeaders().getContentType() != null
? error.getResponseHeaders().getContentType() : MediaType.TEXT_PLAIN)
.body(error.getResponseBodyAsByteArray());
}
}