feat: extract MciPagingUtils and update ToolScaffolder
All checks were successful
Deploy Tools / deploy (push) Successful in 1m30s

This commit is contained in:
jade
2026-09-17 10:22:25 +09:00
parent ae8ab85016
commit 5f58c22efe
2 changed files with 142 additions and 160 deletions

View File

@@ -0,0 +1,69 @@
package io.shinhanlife.dat.lib.paging;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* @package io.shinhanlife.dat.lib.paging
* @className MciPagingUtils
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
public final class MciPagingUtils {
private MciPagingUtils() {
}
/**
* 페이징 응답 데이터(List 필드)를 대상(target) 객체에 순차적으로 병합(append)합니다.
* 클래스 상속 계층 구조를 탐색하여 모든 List 타입 필드를 병합합니다.
*
* @param target 누적할 최종 응답 DTO
* @param src 각 페이지별 응답 DTO
*/
public static void mergeResponseData(Object target, Object src) {
if (target == null || src == null) {
return;
}
Class<?> current = target.getClass();
while (current != null && current != Object.class) {
for (Field field : current.getDeclaredFields()) {
if (List.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
try {
List<?> srcList = (List<?>) field.get(src);
if (srcList != null && !srcList.isEmpty()) {
@SuppressWarnings("unchecked")
List<Object> targetList = (List<Object>) field.get(target);
if (targetList == null) {
targetList = new ArrayList<>(srcList);
field.set(target, targetList);
} else {
if (!(targetList instanceof ArrayList)) {
targetList = new ArrayList<>(targetList);
field.set(target, targetList);
}
targetList.addAll(srcList);
}
}
} catch (Exception e) {
log.warn("[MCI Tool] 리스트 병합 중 예외 발생 (field: {}): {}", field.getName(), e.getMessage());
}
}
}
current = current.getSuperclass();
}
}
}

View File

@@ -119,17 +119,6 @@ public class ToolScaffolder {
}
}
public static String businessProposalGuide() {
return """
[업무 제안서 작성 가이드]
통합계약원부 조회
1. 조회 대상·식별 기준: 무엇을 어떤 값으로 찾는지
계약번호가 있으면 상세 원부를 조회
2. 조회 목적·사용처: 어떤 업무 상황에서 쓰는지
해지·대출·보험금 상세는 별도 Tool
""";
}
public static String scaffoldUseCase(String useCaseName, String moduleName, String author,
String createDate, List<ToolMethodDefinition> tools) throws IOException {
if (tools == null || tools.isEmpty()) {
@@ -340,7 +329,7 @@ public class ToolScaffolder {
? tool.clientSystemCode().toUpperCase(Locale.ROOT) + "Converter"
: baseName + "Converter";
writeUtf8(targetConverterDir.resolve(converterName + ".java"),
groupedMciConverterContent(converterPkg, bizPackage, baseName, ioPackage, ioPrefix, converterName));
groupedMciConverterContent(converterPkg, bizPackage, baseName, ioPackage, ioPrefix, converterName, pagingMode));
} else {
writeUtf8(ioDir.resolve(baseName + "HttpRequest.java"),
dtoContent(ioPackage + ".io", baseName + "HttpRequest", tool.inputFields(), "", "", true));
@@ -440,7 +429,8 @@ public class ToolScaffolder {
.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n")
.append("import ").append(bizPackage).append(".paging.").append(pagingInterface).append(";\n")
.append("import io.shinhanlife.dat.lib.paging.MciPage;\n")
.append("import io.shinhanlife.dat.lib.paging.").append(pagingInfoType).append(";\n");
.append("import io.shinhanlife.dat.lib.paging.").append(pagingInfoType).append(";\n")
.append("import io.shinhanlife.dat.lib.paging.MciPagingUtils;\n");
if (!fields.toString().contains(" " + pagingVar + ";")) {
fields.append(" private final ").append(pagingInterface).append(" ").append(pagingVar).append(";\n");
@@ -488,39 +478,7 @@ public class ToolScaffolder {
if (hasMciOrPaging) {
imports.append("import lombok.extern.slf4j.Slf4j;\n");
}
boolean hasPaging = tools.stream().anyMatch(t -> t.definitionOptions() != null && t.definitionOptions().pagingModeOrNone() != PagingMode.NONE);
if (hasPaging) {
methods.append("""
private void mergeResponseData(Object target, Object src) {
if (target == null || src == null) return;
for (java.lang.reflect.Field field : target.getClass().getDeclaredFields()) {
if (java.util.List.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
try {
java.util.List<?> srcList = (java.util.List<?>) field.get(src);
if (srcList != null && !srcList.isEmpty()) {
@SuppressWarnings("unchecked")
java.util.List<Object> targetList = (java.util.List<Object>) field.get(target);
if (targetList == null) {
targetList = new java.util.ArrayList<>(srcList);
field.set(target, targetList);
} else {
if (!(targetList instanceof java.util.ArrayList)) {
targetList = new java.util.ArrayList<>(targetList);
field.set(target, targetList);
}
targetList.addAll(srcList);
}
}
} catch (Exception e) {
log.warn("[MCI Tool] 리스트 병합 중 예외 발생 (field: {}): {}", field.getName(), e.getMessage());
}
}
}
}
""");
}
return "package " + bizPackage + ".usecase.impl;\n\n"
+ "import " + bizPackage + ".usecase." + useCaseBaseName + "UseCase;\n"
+ "import lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n" + imports
@@ -538,7 +496,7 @@ public class ToolScaffolder {
@Override
public %sResponse %s(%sRequest req) {
log.info("[MCI Tool] {} 요청 수신 (페이징 모드: %s).", "%s", "%s");
log.info("[MCI Tool] {} 요청 수신 (페이징 모드: {}).", "%s", "%s");
try {
%s pagingInfo = null;
%s pagingInfoLast = null;
@@ -554,7 +512,7 @@ public class ToolScaffolder {
if (finalResponse == null) {
finalResponse = page.data();
} else if (page.data() != null) {
mergeResponseData(finalResponse, page.data());
MciPagingUtils.mergeResponseData(finalResponse, page.data());
}
if (!page.hasNext()) {
hasMore = false;
@@ -566,8 +524,18 @@ public class ToolScaffolder {
if (finalResponse == null) {
finalResponse = new %sResponse();
finalResponse.setResultCode("FAIL");
finalResponse.setResultMessage("MCI 응답 데이터가 존재하지 않습니다.");
finalResponse.setHasMore(false);
finalResponse.setPageNo(1);
finalResponse.setTotalPageCount(0);
finalResponse.setTotalCount(0);
return finalResponse;
}
if (finalResponse.getResultCode() == null || finalResponse.getResultCode().isBlank()) {
finalResponse.setResultCode("SUCCESS");
}
finalResponse.setResultCode("SUCCESS");
finalResponse.setHasMore(hasMore);
if (pagingInfoLast != null) {
int lastPageNo = Math.max(1, pagingInfoLast.getPageNo() - 1);
@@ -598,7 +566,7 @@ public class ToolScaffolder {
}
""".formatted(
baseName, tool.methodName(), baseName,
pagingMode, snakeToolName, pagingMode,
snakeToolName, pagingMode,
pagingInfoType,
pagingInfoType,
baseName,
@@ -610,7 +578,7 @@ public class ToolScaffolder {
@Override
public %sResponse %s(%sRequest req) {
log.info("[MCI Tool] {} 요청 수신 (페이징 모드: %s).", "%s", "%s");
log.info("[MCI Tool] {} 요청 수신 (페이징 모드: {}).", "%s", "%s");
try {
%s pagingInfo = null;
%sResponse finalResponse = null;
@@ -624,7 +592,7 @@ public class ToolScaffolder {
if (finalResponse == null) {
finalResponse = page.data();
} else if (page.data() != null) {
mergeResponseData(finalResponse, page.data());
MciPagingUtils.mergeResponseData(finalResponse, page.data());
}
if (!page.hasNext()) {
hasMore = false;
@@ -636,8 +604,15 @@ public class ToolScaffolder {
if (finalResponse == null) {
finalResponse = new %sResponse();
finalResponse.setResultCode("FAIL");
finalResponse.setResultMessage("MCI 응답 데이터가 존재하지 않습니다.");
finalResponse.setHasMore(false);
return finalResponse;
}
if (finalResponse.getResultCode() == null || finalResponse.getResultCode().isBlank()) {
finalResponse.setResultCode("SUCCESS");
}
finalResponse.setResultCode("SUCCESS");
finalResponse.setHasMore(hasMore);
if (hasMore) {
finalResponse.setResultMessage("MCI 기본 최대 조회 건수(10회)까지 조회되었습니다. 추가 데이터가 더 존재합니다 (총 " + loopCount + "회 호출, hasMore=true).");
@@ -656,7 +631,7 @@ public class ToolScaffolder {
}
""".formatted(
baseName, tool.methodName(), baseName,
pagingMode, snakeToolName, pagingMode,
snakeToolName, pagingMode,
pagingInfoType,
baseName,
baseName, pagingInfoType, pagingVariable,
@@ -729,16 +704,26 @@ public class ToolScaffolder {
+ "public interface " + useCaseBaseName + "Converter {\n}\n";
}
private static String groupedMciConverterContent(String converterPackage, String bizPackage, String baseName, String ioPackage, String interfaceId, String converterName) {
private static String groupedMciConverterContent(String converterPackage, String bizPackage, String baseName, String ioPackage, String interfaceId, String converterName, PagingMode pagingMode) {
String pageIgnore = "";
if (pagingMode == PagingMode.PAGE_NUMBER) {
pageIgnore = " @Mapping(target = \"pageInfo\", ignore = true)\n";
} else if (pagingMode == PagingMode.SCROLL) {
pageIgnore = " @Mapping(target = \"scrPageInfo\", ignore = true)\n";
}
return "package " + converterPackage + ";\n\n"
+ "import " + bizPackage + ".dto." + baseName + "Request;\n"
+ "import " + bizPackage + ".dto." + baseName + "Response;\n"
+ "import " + ioPackage + ".io." + interfaceId + "_I;\n"
+ "import " + ioPackage + ".io." + interfaceId + "_O;\n"
+ "import org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n"
+ "import org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.ReportingPolicy;\n\n"
+ "@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\n"
+ "public interface " + converterName + " {\n"
+ " " + interfaceId + "_I toRequest(" + baseName + "Request request);\n"
+ pageIgnore
+ " " + interfaceId + "_I toRequest(" + baseName + "Request request);\n\n"
+ " default " + interfaceId + "_I toLegacyRequest(" + baseName + "Request request) {\n"
+ " return toRequest(request);\n"
+ " }\n\n"
+ " " + baseName + "Response toResponse(" + interfaceId + "_O response);\n}\n";
}
@@ -844,6 +829,7 @@ public class ToolScaffolder {
implementation = addImport(implementation, "import " + bizPackage + ".paging." + pagingInterface + ";");
implementation = addImport(implementation, "import io.shinhanlife.dat.lib.paging.MciPage;");
implementation = addImport(implementation, "import io.shinhanlife.dat.lib.paging." + pagingInfoType + ";");
implementation = addImport(implementation, "import io.shinhanlife.dat.lib.paging.MciPagingUtils;");
implementation = addImport(implementation, "import lombok.extern.slf4j.Slf4j;");
if (!implementation.contains("@Slf4j")) {
implementation = implementation.replaceFirst("public class ", "@Slf4j\npublic class ");
@@ -857,38 +843,6 @@ public class ToolScaffolder {
}
String method = groupedToolPagingMethodContent(tool, baseName, pagingVar, pagingMode, pagingInfoType);
implementation = insertBeforeLastBrace(implementation, method);
if (!implementation.contains("mergeResponseData(")) {
implementation = insertBeforeLastBrace(implementation, """
private void mergeResponseData(Object target, Object src) {
if (target == null || src == null) return;
for (java.lang.reflect.Field field : target.getClass().getDeclaredFields()) {
if (java.util.List.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
try {
java.util.List<?> srcList = (java.util.List<?>) field.get(src);
if (srcList != null && !srcList.isEmpty()) {
@SuppressWarnings("unchecked")
java.util.List<Object> targetList = (java.util.List<Object>) field.get(target);
if (targetList == null) {
targetList = new java.util.ArrayList<>(srcList);
field.set(target, targetList);
} else {
if (!(targetList instanceof java.util.ArrayList)) {
targetList = new java.util.ArrayList<>(targetList);
field.set(target, targetList);
}
targetList.addAll(srcList);
}
}
} catch (Exception e) {
log.warn("[MCI Tool] 리스트 병합 중 예외 발생 (field: {}): {}", field.getName(), e.getMessage());
}
}
}
}
""");
}
} else {
String clientClassName;
String clientVariable;
@@ -1381,6 +1335,7 @@ public class ToolScaffolder {
import %s.paging.%s;
import io.shinhanlife.dat.lib.paging.MciPage;
import io.shinhanlife.dat.lib.paging.PgNumPagingInfo;
import io.shinhanlife.dat.lib.paging.MciPagingUtils;
import io.shinhanlife.glow.BizException;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@@ -1425,7 +1380,7 @@ public class ToolScaffolder {
if (finalResponse == null) {
finalResponse = page.data();
} else if (page.data() != null) {
mergeResponseData(finalResponse, page.data());
MciPagingUtils.mergeResponseData(finalResponse, page.data());
}
if (!page.hasNext()) {
hasMore = false;
@@ -1477,34 +1432,6 @@ public class ToolScaffolder {
return response;
}
}
private void mergeResponseData(%sResponse target, %sResponse src) {
if (target == null || src == null) return;
for (java.lang.reflect.Field field : target.getClass().getDeclaredFields()) {
if (java.util.List.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
try {
java.util.List<?> srcList = (java.util.List<?>) field.get(src);
if (srcList != null && !srcList.isEmpty()) {
@SuppressWarnings("unchecked")
java.util.List<Object> targetList = (java.util.List<Object>) field.get(target);
if (targetList == null) {
targetList = new java.util.ArrayList<>(srcList);
field.set(target, targetList);
} else {
if (!(targetList instanceof java.util.ArrayList)) {
targetList = new java.util.ArrayList<>(targetList);
field.set(target, targetList);
}
targetList.addAll(srcList);
}
}
} catch (Exception e) {
log.warn("[MCI Tool] 리스트 병합 중 예외 발생 (field: {}): {}", field.getName(), e.getMessage());
}
}
}
}
}
""".formatted(
bizPackage,
@@ -1525,7 +1452,6 @@ public class ToolScaffolder {
baseName,
baseName,
baseName, baseName,
baseName, baseName,
baseName, baseName
);
} else {
@@ -1538,6 +1464,7 @@ public class ToolScaffolder {
import %s.paging.%s;
import io.shinhanlife.dat.lib.paging.MciPage;
import io.shinhanlife.dat.lib.paging.ScrollPagingInfo;
import io.shinhanlife.dat.lib.paging.MciPagingUtils;
import io.shinhanlife.glow.BizException;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@@ -1580,7 +1507,7 @@ public class ToolScaffolder {
if (finalResponse == null) {
finalResponse = page.data();
} else if (page.data() != null) {
mergeResponseData(finalResponse, page.data());
MciPagingUtils.mergeResponseData(finalResponse, page.data());
}
if (!page.hasNext()) {
hasMore = false;
@@ -1617,34 +1544,6 @@ public class ToolScaffolder {
return response;
}
}
private void mergeResponseData(%sResponse target, %sResponse src) {
if (target == null || src == null) return;
for (java.lang.reflect.Field field : target.getClass().getDeclaredFields()) {
if (java.util.List.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
try {
java.util.List<?> srcList = (java.util.List<?>) field.get(src);
if (srcList != null && !srcList.isEmpty()) {
@SuppressWarnings("unchecked")
java.util.List<Object> targetList = (java.util.List<Object>) field.get(target);
if (targetList == null) {
targetList = new java.util.ArrayList<>(srcList);
field.set(target, targetList);
} else {
if (!(targetList instanceof java.util.ArrayList)) {
targetList = new java.util.ArrayList<>(targetList);
field.set(target, targetList);
}
targetList.addAll(srcList);
}
}
} catch (Exception e) {
log.warn("[MCI Tool] 리스트 병합 중 예외 발생 (field: {}): {}", field.getName(), e.getMessage());
}
}
}
}
}
""".formatted(
bizPackage,
@@ -1665,7 +1564,6 @@ public class ToolScaffolder {
baseName,
baseName,
baseName, baseName,
baseName, baseName,
baseName, baseName
);
}
@@ -2644,6 +2542,8 @@ public class ToolScaffolder {
import io.shinhanlife.dat.lib.paging.ScrollPagingInfo;
import io.shinhanlife.glow.db.dto.ScrPageInfo;
import io.shinhanlife.glow.communication.dto.Transfer;
import java.lang.reflect.Method;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -2675,6 +2575,9 @@ public class ToolScaffolder {
if (request.getScrPageInfo() == null) {
request.setScrPageInfo(new ScrPageInfo());
}
if (request.getScrPageInfo().getPageDataCc() <= 0) {
request.getScrPageInfo().setPageDataCc(20);
}
if (pagingInfo != null) {
if (pagingInfo.getScrlmhdNm() != null) request.getScrPageInfo().setScrlmhdNm(pagingInfo.getScrlmhdNm());
if (pagingInfo.getScrlItva() != null) request.getScrPageInfo().setScrlItva(pagingInfo.getScrlItva());
@@ -2682,14 +2585,14 @@ public class ToolScaffolder {
if (pagingInfo.getPageDataCc() > 0) request.getScrPageInfo().setPageDataCc(pagingInfo.getPageDataCc());
}
%s_I mciReq = converter.toLegacyRequest(request);
%s_I mciReq = converter.toRequest(request);
if (mciReq != null && request.getScrPageInfo() != null) {
try {
java.lang.reflect.Method setMethod = mciReq.getClass().getMethod("setScrPageInfo", java.util.List.class);
setMethod.invoke(mciReq, java.util.List.of(request.getScrPageInfo()));
Method setMethod = mciReq.getClass().getMethod("setScrPageInfo", List.class);
setMethod.invoke(mciReq, List.of(request.getScrPageInfo()));
} catch (NoSuchMethodException e) {
try {
java.lang.reflect.Method setMethod = mciReq.getClass().getMethod("setScrPageInfo", ScrPageInfo.class);
Method setMethod = mciReq.getClass().getMethod("setScrPageInfo", ScrPageInfo.class);
setMethod.invoke(mciReq, request.getScrPageInfo());
} catch (Exception ignored) {}
} catch (Exception ignored) {}
@@ -2703,9 +2606,9 @@ public class ToolScaffolder {
ScrPageInfo resPageInfo = null;
if (mciRes != null) {
try {
java.lang.reflect.Method getMethod = mciRes.getClass().getMethod("getScrPageInfo");
Method getMethod = mciRes.getClass().getMethod("getScrPageInfo");
Object val = getMethod.invoke(mciRes);
if (val instanceof java.util.List<?> list && !list.isEmpty()) {
if (val instanceof List<?> list && !list.isEmpty()) {
if (list.get(0) instanceof ScrPageInfo pi) resPageInfo = pi;
} else if (val instanceof ScrPageInfo pi) {
resPageInfo = pi;
@@ -2765,6 +2668,8 @@ public class ToolScaffolder {
import io.shinhanlife.dat.lib.paging.PgNumPagingInfo;
import io.shinhanlife.glow.db.dto.PageInfo;
import io.shinhanlife.glow.communication.dto.Transfer;
import java.lang.reflect.Method;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -2796,19 +2701,25 @@ public class ToolScaffolder {
if (request.getPageInfo() == null) {
request.setPageInfo(new PageInfo());
}
if (request.getPageInfo().getPageNo() <= 0) {
request.getPageInfo().setPageNo(1);
}
if (request.getPageInfo().getPageDataCc() <= 0) {
request.getPageInfo().setPageDataCc(20);
}
if (pagingInfo != null) {
if (pagingInfo.getPageNo() > 0) request.getPageInfo().setPageNo(pagingInfo.getPageNo());
if (pagingInfo.getPageDataCc() > 0) request.getPageInfo().setPageDataCc(pagingInfo.getPageDataCc());
}
%s_I mciReq = converter.toLegacyRequest(request);
%s_I mciReq = converter.toRequest(request);
if (mciReq != null) {
try {
java.lang.reflect.Method setMethod = mciReq.getClass().getMethod("setPageInfo", java.util.List.class);
setMethod.invoke(mciReq, java.util.List.of(request.getPageInfo()));
Method setMethod = mciReq.getClass().getMethod("setPageInfo", List.class);
setMethod.invoke(mciReq, List.of(request.getPageInfo()));
} catch (NoSuchMethodException e) {
try {
java.lang.reflect.Method setMethod = mciReq.getClass().getMethod("setPageInfo", PageInfo.class);
Method setMethod = mciReq.getClass().getMethod("setPageInfo", PageInfo.class);
setMethod.invoke(mciReq, request.getPageInfo());
} catch (Exception ignored) {}
} catch (Exception ignored) {}
@@ -2822,9 +2733,9 @@ public class ToolScaffolder {
PageInfo resPageInfo = null;
if (mciRes != null) {
try {
java.lang.reflect.Method getMethod = mciRes.getClass().getMethod("getPageInfo");
Method getMethod = mciRes.getClass().getMethod("getPageInfo");
Object val = getMethod.invoke(mciRes);
if (val instanceof java.util.List<?> list && !list.isEmpty()) {
if (val instanceof List<?> list && !list.isEmpty()) {
if (list.get(0) instanceof PageInfo pi) resPageInfo = pi;
} else if (val instanceof PageInfo pi) {
resPageInfo = pi;
@@ -3238,6 +3149,8 @@ public class ToolScaffolder {
public interface %s {
// Field names differ? Add mappings like this before the method.
// @Mapping(source = "sourceField", target = "targetField")
@Mapping(target = "pageInfo", ignore = true)
@Mapping(target = "scrPageInfo", ignore = true)
%s_I toLegacyRequest(%sRequest request);
default %s_I toRequest(%sRequest request) {
return toLegacyRequest(request);