2 Commits

Author SHA1 Message Date
Boram
17c570f740 Merge remote-tracking branch 'origin/main'
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m9s
2026-08-14 11:31:04 +09:00
Boram
45e2c148ab 인터페이스 다운로드 변경사항 반영 2026-08-14 11:30:55 +09:00
2 changed files with 54 additions and 28 deletions

View File

@@ -11,7 +11,9 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
@@ -45,6 +47,7 @@ public class DtoExcelDownloadController {
private static final String MCI_BASE_PACKAGE = "io.shinhanlife.dap.mcc.infra.itrf.mci"; private static final String MCI_BASE_PACKAGE = "io.shinhanlife.dap.mcc.infra.itrf.mci";
private static final String INVALID_DTO_MESSAGE = private static final String INVALID_DTO_MESSAGE =
"dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요"; "dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요";
private static final Pattern VARIANT_SUFFIX = Pattern.compile("_[IO]$");
private final Map<String, String> dtoClasses; private final Map<String, String> dtoClasses;
@@ -54,8 +57,12 @@ public class DtoExcelDownloadController {
@GetMapping("/dto-download/options") @GetMapping("/dto-download/options")
public List<String> options() { public List<String> options() {
// 클래스패스에서 자동 검색된 DTO 목록을 화면에 제공한다. // DTO는 항상 _I/_O 한 쌍으로 존재하므로 접미사를 제거한 이름 단위로 묶어 화면에 제공한다.
return List.copyOf(dtoClasses.keySet()); Set<String> baseNames = new TreeSet<>();
for (String simpleName : dtoClasses.keySet()) {
baseNames.add(VARIANT_SUFFIX.matcher(simpleName).replaceFirst(""));
}
return List.copyOf(baseNames);
} }
@GetMapping("/dto-download/{dtoName}") @GetMapping("/dto-download/{dtoName}")
@@ -328,7 +335,9 @@ public class DtoExcelDownloadController {
private Annotation telegramMetadata(Field field) { private Annotation telegramMetadata(Field field) {
for (Annotation annotation : field.getDeclaredAnnotations()) { for (Annotation annotation : field.getDeclaredAnnotations()) {
if (annotation.annotationType().getSimpleName().equals("GlowTrgmField")) { String annotationName = annotation.annotationType().getSimpleName();
if (annotationName.equals("GlowTrgmField")
|| annotationName.equals("GlowMciFieldInfo")) {
return annotation; return annotation;
} }
} }

View File

@@ -94,7 +94,7 @@
const select = $('toolSelect'), args = $('arguments'), result = $('result'); const select = $('toolSelect'), args = $('arguments'), result = $('result');
async function loadDtoOptions() { async function loadDtoOptions() {
// 서버가 itrf/mci/**/io 패키지에서 찾은 DTO 목록으로 셀렉트박스를 구성한다. // 서버가 itrf/mci/**/io 패키지에서 찾은 DTO를 _I/_O 접미사 없이 이름 단위로 묶어 셀렉트박스를 구성한다.
const dtoSelect = $('dtoSelect'); const dtoSelect = $('dtoSelect');
try { try {
const response = await fetch('/dto-download/options', { headers: { 'Cache-Control': 'no-cache' } }); const response = await fetch('/dto-download/options', { headers: { 'Cache-Control': 'no-cache' } });
@@ -104,7 +104,7 @@
for (const dtoName of dtoNames) { for (const dtoName of dtoNames) {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = dtoName; option.value = dtoName;
option.textContent = `${dtoName}.java`; option.textContent = dtoName;
dtoSelect.appendChild(option); dtoSelect.appendChild(option);
} }
if (!dtoNames.length) dtoSelect.innerHTML = '<option value="">DTO 파일이 없습니다.</option>'; if (!dtoNames.length) dtoSelect.innerHTML = '<option value="">DTO 파일이 없습니다.</option>';
@@ -115,21 +115,28 @@
function openDtoModal() { $('dtoDownloadModal').classList.remove('hidden'); loadDtoOptions(); $('dtoSelect').focus(); } function openDtoModal() { $('dtoDownloadModal').classList.remove('hidden'); loadDtoOptions(); $('dtoSelect').focus(); }
function closeDtoModal() { $('dtoDownloadModal').classList.add('hidden'); $('dtoDownloadButton').focus(); } function closeDtoModal() { $('dtoDownloadModal').classList.add('hidden'); $('dtoDownloadButton').focus(); }
async function downloadSelectedDto() { async function downloadSelectedDto() {
// 선택한 DTO의 엑셀 생성 API를 호출하고 응답 Blob을 브라우저 다운로드로 저장한다. // 선택한 DTO 이름의 _I/_O 엑셀 생성 API를 순차 호출해 존재하는 파일을 각각 브라우저 다운로드로 저장한다.
const dtoName = $('dtoSelect').value; const baseName = $('dtoSelect').value;
if (!dtoName) return alert('다운로드할 DTO를 선택해주세요.'); if (!baseName) return alert('다운로드할 DTO를 선택해주세요.');
const confirmButton = $('dtoConfirmButton'); const confirmButton = $('dtoConfirmButton');
confirmButton.disabled = true; confirmButton.disabled = true;
const errors = [];
let successCount = 0;
try { try {
const response = await fetch(`/dto-download/${encodeURIComponent(dtoName)}`); for (const variant of ['I', 'O']) {
const dtoName = `${baseName}_${variant}`;
try {
const response = await fetch(`/dto-download/${encodeURIComponent(dtoName)}`, {
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' }
});
if (response.status === 404) continue; // _I/_O 중 한쪽만 존재하는 DTO는 정상 케이스이므로 건너뛴다.
if (!response.ok) { if (!response.ok) {
// 422는 GlowTrgmField 메타데이터가 없어 설계서 양식으로 변환할 수 없는 경우다. // 422는 GlowTrgmField 메타데이터가 없어 설계서 양식으로 변환할 수 없는 경우다.
const message = (await response.text()).trim(); const message = (await response.text()).trim();
throw new Error(response.status === 422 throw new Error(response.status === 422
? 'dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요' ? `${dtoName}: dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요`
: message || (response.status === 404 : message || `${dtoName} 다운로드에 실패했습니다. (HTTP ${response.status})`);
? `${dtoName}.xlsx 파일을 찾을 수 없습니다.`
: `다운로드에 실패했습니다. (HTTP ${response.status})`));
} }
const blob = await response.blob(); const blob = await response.blob();
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@@ -140,9 +147,19 @@
link.click(); link.click();
link.remove(); link.remove();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
closeDtoModal(); successCount += 1;
// 브라우저가 연속 다운로드를 한 번에 차단하지 않도록 다음 요청 전에 짧은 간격을 둔다.
await new Promise(resolve => setTimeout(resolve, 300));
} catch (error) { } catch (error) {
alert(error.message); errors.push(error.message);
}
}
if (successCount === 0) {
alert(errors.length ? errors.join('\n') : `${baseName} 파일을 찾을 수 없습니다.`);
return;
}
if (errors.length) alert(errors.join('\n'));
closeDtoModal();
} finally { } finally {
confirmButton.disabled = false; confirmButton.disabled = false;
} }