From 6aa62a5499d540f40856481af58a3602dec8d46f Mon Sep 17 00:00:00 2001 From: jade Date: Wed, 12 Aug 2026 17:29:10 +0900 Subject: [PATCH] fix: preserve Korean text in scaffold output --- .../dap/lib/util/PodScaffolder.java | 31 +-- .../dap/lib/util/ToolScaffolder.java | 215 +++++++++--------- .../dap/lib/util/ToolSourceUpdater.java | 7 +- .../dap/lib/util/ToolScaffolderTest.java | 41 ++++ .../InsuranceClaimProcessorUseCase.java | 8 +- .../src/main/resources/application-dev.yml | 2 +- .../cmm/usecase/MemoListRetrieverUseCase.java | 8 +- .../src/main/resources/application-dev.yml | 2 +- 8 files changed, 183 insertions(+), 131 deletions(-) diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java index 09bf3a8e..64125ddd 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; import java.nio.file.StandardOpenOption; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -72,7 +73,7 @@ public class PodScaffolder { implementation project(':dap-was-lib') } """; - Files.writeString(modulePath.resolve("build.gradle"), buildGradle); + writeUtf8(modulePath.resolve("build.gradle"), buildGradle); log.append("[3/6] Dockerfile 생성 중...\n"); String dockerfile = """ @@ -84,7 +85,7 @@ public class PodScaffolder { EXPOSE %s ENTRYPOINT ["java", "-jar", "app.jar"] """.formatted(moduleName, portStr); - Files.writeString(modulePath.resolve("Dockerfile"), dockerfile); + writeUtf8(modulePath.resolve("Dockerfile"), dockerfile); log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n"); Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/dap/mcc/" + shortName); @@ -121,7 +122,7 @@ public class PodScaffolder { } } """.formatted(shortName, shortName, capitalize(shortName), author, createDate, createDate, author, capitalize(shortName), capitalize(shortName)); - Files.writeString(srcPath.resolve("DapWas" + capitalize(shortName) + "Application.java"), appClass); + writeUtf8(srcPath.resolve("DapWas" + capitalize(shortName) + "Application.java"), appClass); Path resPath = modulePath.resolve("src/main/resources"); Files.createDirectories(resPath); @@ -145,7 +146,7 @@ public class PodScaffolder { tenant-domains: TESTER-DEV: ALL """.formatted(portStr, moduleName, moduleName.replace("dap-", "")); - Files.writeString(resPath.resolve("application.yml"), applicationYml); + writeUtf8(resPath.resolve("application.yml"), applicationYml); String applicationLocalYml = """ # Local 환경 전용 설정 (H2 메모리 DB 등) @@ -194,7 +195,7 @@ public class PodScaffolder { tool: url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} """; - Files.writeString(resPath.resolve("application-local.yml"), applicationLocalYml); + writeUtf8(resPath.resolve("application-local.yml"), applicationLocalYml); String applicationDevYml = """ # OCI 클라우드 환경 전용 설정 @@ -244,7 +245,7 @@ public class PodScaffolder { externalMci: url: http://10.176.32.176 """.formatted(portStr, portStr); - Files.writeString(resPath.resolve("application-dev.yml"), applicationDevYml); + writeUtf8(resPath.resolve("application-dev.yml"), applicationDevYml); String applicationTestYml = """ server: @@ -264,7 +265,7 @@ public class PodScaffolder { tool: url: ${AXHUB_TOOL_URL} """.formatted(portStr); - Files.writeString(resPath.resolve("application-test.yml"), applicationTestYml); + writeUtf8(resPath.resolve("application-test.yml"), applicationTestYml); String applicationProdYml = """ server: @@ -284,7 +285,7 @@ public class PodScaffolder { tool: url: ${AXHUB_TOOL_URL} """.formatted(portStr); - Files.writeString(resPath.resolve("application-prod.yml"), applicationProdYml); + writeUtf8(resPath.resolve("application-prod.yml"), applicationProdYml); String logbackXml = """ @@ -312,21 +313,21 @@ public class PodScaffolder { """.formatted(moduleName, moduleName); - Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml); + writeUtf8(resPath.resolve("logback-spring.xml"), logbackXml); log.append("[5/6] settings.gradle 에 모듈 등록 중...\n"); Path settingsPath = rootDir.resolve(Paths.get("settings.gradle")); if (Files.exists(settingsPath)) { - String settings = Files.readString(settingsPath); + String settings = Files.readString(settingsPath, StandardCharsets.UTF_8); if (!settings.contains("include '" + moduleName + "'")) { - Files.writeString(settingsPath, System.lineSeparator() + "include '" + moduleName + "'" + System.lineSeparator(), StandardOpenOption.APPEND); + Files.writeString(settingsPath, System.lineSeparator() + "include '" + moduleName + "'" + System.lineSeparator(), StandardCharsets.UTF_8, StandardOpenOption.APPEND); } } log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n"); Path dockerComposePath = rootDir.resolve(Paths.get("docker-compose.yml")); if (Files.exists(dockerComposePath)) { - String compose = Files.readString(dockerComposePath); + String compose = Files.readString(dockerComposePath, StandardCharsets.UTF_8); String serviceName = moduleName.replace("dap-", ""); // e.g. tool-payment if (!compose.contains(" " + serviceName + ":")) { String newService = """ @@ -352,7 +353,7 @@ public class PodScaffolder { - GLOW_COMMUNICATION_EAI_HOST=http://mci-mock - GLOW_COMMUNICATION_EAI_PORT=8080 """.formatted(serviceName, moduleName, portStr, portStr, serviceName, portStr); - Files.writeString(dockerComposePath, System.lineSeparator() + newService, StandardOpenOption.APPEND); + Files.writeString(dockerComposePath, System.lineSeparator() + newService, StandardCharsets.UTF_8, StandardOpenOption.APPEND); } } @@ -365,6 +366,10 @@ public class PodScaffolder { return log.toString(); } + private static void writeUtf8(Path path, String content) throws IOException { + Files.writeString(path, content, StandardCharsets.UTF_8); + } + private static String capitalize(String str) { if (str == null || str.isEmpty()) return str; return str.substring(0, 1).toUpperCase() + str.substring(1); diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java index 7125d841..d6da6dd5 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.LinkedHashSet; @@ -14,28 +15,28 @@ import java.util.Set; import java.util.Scanner; /** - * MCP Tool 肄붾뱶瑜??먮룞 ?앹꽦(Scaffolding)?섎뒗 ?좏떥由ы떚 ?대옒?? + * MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스입니다. * - * [?ㅽ뻾 諛⑸쾿] - * 諛⑸쾿 1. IDE(IntelliJ ???먯꽌 吏곸젒 ?ㅽ뻾 (?€?뷀삎 紐⑤뱶 異붿쿇 狩? - * - ???대옒??ToolScaffolder.java)瑜??닿퀬 main 硫붿꽌?쒕? 吏곸젒 ?ㅽ뻾(Run)?⑸땲?? - * - 肄섏넄 李쎌뿉 ?⑤뒗 吏덈Ц??李⑤??€濡?媛믪쓣 ?낅젰?섍린留??섎㈃ ?뚯씪???앹꽦?⑸땲?? + * [실행 방법] + * 방법 1. IDE(IntelliJ 등)에서 직접 실행 + * - ToolScaffolder.java의 main 메서드를 실행합니다. + * - 콘솔 질문에 차례대로 값을 입력하면 파일이 생성됩니다. * - * 諛⑸쾿 2. 而ㅻ㎤?쒕씪???곕????먯꽌 ?ㅽ뻾 (紐낅졊??湲곕컲) - * - 而댄뙆?? javac -encoding UTF-8 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/util/ToolScaffolder.java - * - ?ㅽ뻾: java -cp dap-was-lib/src/main/java io.shinhanlife.dap.lib.util.ToolScaffolder [?대쫫] [ID] "[?ㅻ챸]" "[洹몃9]" "[?듭떊諛⑹떇]" "[紐⑤뱢紐?" + * 방법 2. 명령줄에서 실행 + * - 컴파일: javac -encoding UTF-8 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java + * - 실행: java -cp dap-was-lib/src/main/java io.shinhanlife.dap.lib.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]" */ /** * @package io.shinhanlife.dap.lib.util * @className ToolScaffolder - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author 0986406 * @create 2026.09.01 *
- * ---------- 媛쒖젙?대젰 ----------
- * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+ * ---------- 개정이력 ----------
+ * 수정일       수정자     수정내용
  * ---------- -------- ---------------------------
- * 2026.09.01  0986406    理쒖큹?앹꽦
+ * 2026.09.01  0986406    최초생성
  * 
  * 
*/ @@ -65,8 +66,8 @@ public class ToolScaffolder { System.out.println(" MCP Tool Scaffolder (Java CLI) "); System.out.println("=========================================\n"); - String baseName = getOrAsk(args, 0, scanner, "1. ?앹꽦??Tool??湲곕낯 ?대쫫 (?? ExchangeRate) [?곷Ц PascalCase]: "); - String interfaceId = getOrAsk(args, 1, scanner, "2. ?덇굅??API ?명꽣?섏씠??ID (?? EXCH_001): "); + String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: "); + String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): "); String title = getOrAsk(args, 2, scanner, "3. Tool title: "); String description = getOrAsk(args, 3, scanner, "4. Tool description for LLM: "); String group = getOrAsk(args, 4, scanner, "5. Tool category: "); @@ -83,12 +84,12 @@ public class ToolScaffolder { String defaultAuthor = System.getProperty("user.name"); String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); - String author = getOrAsk(args, 7, scanner, "7. ?묒꽦??(?뷀꽣 ?낅젰 ??'" + defaultAuthor + "'): "); + String author = getOrAsk(args, 7, scanner, "8. 작성자(Enter 입력 시 '" + defaultAuthor + "'): "); if (author.trim().isEmpty()) author = defaultAuthor; - String createDate = getOrAsk(args, 8, scanner, "8. ?묒꽦??(?뷀꽣 ?낅젰 ??'" + defaultDate + "'): "); + String createDate = getOrAsk(args, 8, scanner, "9. 작성일(Enter 입력 시 '" + defaultDate + "'): "); if (createDate.trim().isEmpty()) createDate = defaultDate; - String useSchemaResourceStr = getOrAsk(args, 9, scanner, "9. input/output JSON Schema ?뚯씪 ?먮룞 ?앹꽦 ?щ? (y/N): "); + String useSchemaResourceStr = getOrAsk(args, 9, scanner, "10. input/output JSON Schema 파일 자동 생성 여부 (y/N): "); boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim()); String schemaResourceDirectory = "classpath:tool-schemas/" + group.toLowerCase() + "/"; @@ -161,7 +162,7 @@ public class ToolScaffolder { Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy")); Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter")); - // schema resource ?뚯씪 寃쎈줈 (useSchemaResource=true ???뚮쭔 ?앹꽦) + // Schema Resource 파일 경로(useSchemaResource=true일 때만 생성) boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty()); String schemaBaseName = toKebabCase(baseName); String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json"; @@ -222,25 +223,25 @@ public class ToolScaffolder { /** * @package %s.dto * @className %sRequest - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-             * ---------- 媛쒖젙?대젰 ----------
-             * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+             * ---------- 개정이력 ----------
+             * 수정일       수정자     수정내용
              * ---------- -------- ---------------------------
-             * %s  %s    理쒖큹?앹꽦
+             * %s  %s    최초생성
              * 
              * 
*/ @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class %sRequest { - @McpToolParam(description = "?섏떊???꾪솕踰덊샇", required = true) + @McpToolParam(description = "수신자 전화번호", required = true) -(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"}) private String phoneNumber; - @McpToolParam(description = "?꾩넚??硫붿떆吏€ ?댁슜", required = true) + @McpToolParam(description = "전송할 메시지 내용", required = true) private String message; } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); @@ -250,9 +251,9 @@ public class ToolScaffolder { .replaceAll("(?m)^\\s*@McpToolParam\\([^\\r\\n]*\\)\\R", "") .replaceAll("(?m)^\\s*-\\(\\?:[^\\r\\n]*\\R", "") .replace("private String phoneNumber;", "@Schema(example = \"01012345678\")\n private String phoneNumber;") - .replace("private String message;", "@Schema(example = \"?뚯뒪??硫붿떆吏€?낅땲??\")\n private String message;"); + .replace("private String message;", "@Schema(example = \"테스트 메시지입니다.\")\n private String message;"); reqContent = dtoContent(bizPackage + ".dto", baseName + "Request", inputFields, author, createDate, true); - Files.writeString(dtoDir.resolve(baseName + "Request.java"), reqContent); + writeUtf8(dtoDir.resolve(baseName + "Request.java"), reqContent); // Generate Response DTO String resContent = """ @@ -264,14 +265,14 @@ public class ToolScaffolder { /** * @package %s.dto * @className %sResponse - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-             * ---------- 媛쒖젙?대젰 ----------
-             * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+             * ---------- 개정이력 ----------
+             * 수정일       수정자     수정내용
              * ---------- -------- ---------------------------
-             * %s  %s    理쒖큹?앹꽦
+             * %s  %s    최초생성
              * 
              * 
*/ @@ -286,7 +287,7 @@ public class ToolScaffolder { } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); resContent = dtoContent(bizPackage + ".dto", baseName + "Response", outputFields, author, createDate, false); - Files.writeString(dtoDir.resolve(baseName + "Response.java"), resContent); + writeUtf8(dtoDir.resolve(baseName + "Response.java"), resContent); String toolName = toToolName(moduleName, group, baseName); @@ -310,14 +311,14 @@ public class ToolScaffolder { /** * @package %s.usecase * @className %sUseCase - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-             * ---------- 媛쒖젙?대젰 ----------
-             * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+             * ---------- 개정이력 ----------
+             * 수정일       수정자     수정내용
              * ---------- -------- ---------------------------
-             * %s  %s    理쒖큹?앹꽦
+             * %s  %s    최초생성
              *
              * 
*/ @@ -338,7 +339,7 @@ public class ToolScaffolder { baseName, baseName ); - Files.writeString(usecaseDir.resolve(baseName + "UseCase.java"), serviceInterfaceContent); + writeUtf8(usecaseDir.resolve(baseName + "UseCase.java"), serviceInterfaceContent); String serviceImplContent; @@ -361,14 +362,14 @@ public class ToolScaffolder { /** * @package %s.usecase.impl * @className %sUseCaseImpl - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @@ -382,9 +383,9 @@ public class ToolScaffolder { @Override public %sResponse execute(%sRequest req) { - log.info("[MCI Tool] {} ?붿껌 ?섏떊.", "%s"); + log.info("[MCI Tool] {} 요청 수신.", "%s"); try { - // MapStruct瑜??댁슜???먮룞 留ㅽ븨 (AI DTO -> MCI DTO) + // MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO) %s_I mciReq = converter.toLegacyRequest(req); Transfer resTransfer = mci.callTo( @@ -400,7 +401,7 @@ public class ToolScaffolder { : "MCI call completed without a response body."); return response; } catch (Exception e) { - log.error("[MCI Tool] ?곕룞 以??ㅻ쪟 諛쒖깮: {}", e.getMessage(), e); + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); %sResponse response = new %sResponse(); response.setResultCode("ERROR"); response.setResultMessage(e.getMessage() != null ? e.getMessage() : "Unknown error"); @@ -460,14 +461,14 @@ public class ToolScaffolder { /** * @package %s.usecase.impl * @className %sUseCaseImpl - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @@ -513,7 +514,7 @@ public class ToolScaffolder { ); } - Files.writeString(usecaseImplDir.resolve(baseName + "UseCaseImpl.java"), serviceImplContent); + writeUtf8(usecaseImplDir.resolve(baseName + "UseCaseImpl.java"), serviceImplContent); if (isMci) { String mciReqContent = """ @@ -524,32 +525,32 @@ public class ToolScaffolder { /** * @package %s.%s.io * @className %s_I - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @Data public class %s_I { /** - * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 ?섏떊??踰덊샇 ?뚮씪誘명꽣紐? + * EAI 시스템이 요구하는 수신자 번호 파라미터명 */ private String phone; /** - * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 硫붿떆吏€ ?댁슜 ?뚮씪誘명꽣紐? + * EAI 시스템이 요구하는 메시지 내용 파라미터명 */ private String content; } """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); mciReqContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_I", inputFields, author, createDate); - Files.writeString(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent); + writeUtf8(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent); String mciResContent = """ package %s.%s.io; @@ -559,14 +560,14 @@ public class ToolScaffolder { /** * @package %s.%s.io * @className %s_O - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @@ -576,7 +577,7 @@ public class ToolScaffolder { } """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); mciResContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_O", outputFields, author, createDate); - Files.writeString(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent); + writeUtf8(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent); String converterContent = """ package %s.converter; @@ -592,14 +593,14 @@ public class ToolScaffolder { /** * @package %s.converter * @className %sConverter - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @@ -628,7 +629,7 @@ public class ToolScaffolder { baseName, interfaceId ); converterContent = mciConverterContent(bizPackage, baseName, mciGroupPath.replace("/", "."), interfaceId); - Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent); + writeUtf8(converterDir.resolve(baseName + "Converter.java"), converterContent); log.append("\n=========================================\n"); log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n"); @@ -653,14 +654,14 @@ public class ToolScaffolder { /** * @package %s.%s * @className Mci%sClient - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                     * ---------- 媛쒖젙?대젰 ----------
-                     * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                     * ---------- 개정이력 ----------
+                     * 수정일       수정자     수정내용
                      * ---------- -------- ---------------------------
-                     * %s  %s    理쒖큹?앹꽦
+                     * %s  %s    최초생성
                      * 
                      * 
*/ @@ -677,7 +678,7 @@ public class ToolScaffolder { BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), clientPrefixCap, author, createDate, createDate, author, clientPrefixCap ); - Files.writeString(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java"), mciClientContent); + writeUtf8(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java"), mciClientContent); log.append("[MCI Client] ").append(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java")).append("\n"); } @@ -687,13 +688,13 @@ public class ToolScaffolder { String httpResponseClass = baseName + "HttpResponse"; String httpClientClass = httpApiClass + "Client"; - Files.writeString(httpIoDir.resolve(httpRequestClass + ".java"), + writeUtf8(httpIoDir.resolve(httpRequestClass + ".java"), dtoContent(httpPackage + ".io", httpRequestClass, inputFields, author, createDate, true)); - Files.writeString(httpIoDir.resolve(httpResponseClass + ".java"), + writeUtf8(httpIoDir.resolve(httpResponseClass + ".java"), dtoContent(httpPackage + ".io", httpResponseClass, outputFields, author, createDate, false)); - Files.writeString(httpClientDir.resolve(httpClientClass + ".java"), + writeUtf8(httpClientDir.resolve(httpClientClass + ".java"), httpClientContent(httpPackage, httpClientClass, httpApiName)); - Files.writeString(converterDir.resolve(baseName + "Converter.java"), + writeUtf8(converterDir.resolve(baseName + "Converter.java"), httpConverterContent(bizPackage, baseName, httpPackage)); log.append("\n=========================================\n"); @@ -716,32 +717,32 @@ public class ToolScaffolder { /** * @package %s.legacy * @className %sLegacyRequest - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @Data public class %sLegacyRequest { /** - * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 ?섏떊??踰덊샇 ?뚮씪誘명꽣紐? + * EAI 시스템이 요구하는 수신자 번호 파라미터명 */ private String phone; /** - * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 硫붿떆吏€ ?댁슜 ?뚮씪誘명꽣紐? + * EAI 시스템이 요구하는 메시지 내용 파라미터명 */ private String content; } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); legacyReqContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyRequest", inputFields, author, createDate, true); - Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent); + writeUtf8(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent); String legacyResContent = """ package %s.legacy; @@ -751,14 +752,14 @@ public class ToolScaffolder { /** * @package %s.legacy * @className %sLegacyResponse - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @@ -768,7 +769,7 @@ public class ToolScaffolder { } """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); legacyResContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyResponse", outputFields, author, createDate, true); - Files.writeString(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent); + writeUtf8(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent); String converterContent = """ package %s.converter; @@ -784,14 +785,14 @@ public class ToolScaffolder { /** * @package %s.converter * @className %sConverter - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author %s * @create %s *
-                 * ---------- 媛쒖젙?대젰 ----------
-                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
                  * ---------- -------- ---------------------------
-                 * %s  %s    理쒖큹?앹꽦
+                 * %s  %s    최초생성
                  * 
                  * 
*/ @@ -818,7 +819,7 @@ public class ToolScaffolder { baseName, baseName, baseName, baseName, baseName, baseName, baseName ); converterContent = legacyConverterContent(bizPackage, baseName); - Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent); + writeUtf8(converterDir.resolve(baseName + "Converter.java"), converterContent); log.append("\n=========================================\n"); log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n"); @@ -831,7 +832,7 @@ public class ToolScaffolder { log.append("[Legacy Response DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyResponse.java")).append("\n"); log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); } - // schema resource ?뚯씪 ?앹꽦 (useSchemaResource=true ???? + // Schema Resource 파일 생성(useSchemaResource=true일 때) if (useSchemaResource) { Files.createDirectories(schemaDir); String inputSchema = """ @@ -841,7 +842,7 @@ public class ToolScaffolder { "properties": { "TODO_FIELD": { "type": "string", - "description": "TODO: ?뚮씪誘명꽣 ?ㅻ챸???낅젰?섏꽭??" + "description": "TODO: 파라미터 설명을 입력하세요." } }, "required": [] @@ -854,19 +855,19 @@ public class ToolScaffolder { "properties": { "status": { "type": "string", - "description": "泥섎━ 寃곌낵 ?곹깭 (SUCCESS / FAILURE)", + "description": "처리 결과 상태 (SUCCESS / FAILURE)", "enum": ["SUCCESS", "FAILURE"] }, "message": { "type": "string", - "description": "泥섎━ 寃곌낵 硫붿떆吏€" + "description": "처리 결과 메시지" } }, "required": ["status"] } """; - Files.writeString(schemaDir.resolve(inputSchemaFileName), inputSchema); - Files.writeString(schemaDir.resolve(outputSchemaFileName), outputSchema); + writeUtf8(schemaDir.resolve(inputSchemaFileName), inputSchema); + writeUtf8(schemaDir.resolve(outputSchemaFileName), outputSchema); log.append("[Input Schema] ").append(schemaDir.resolve(inputSchemaFileName)).append("\n"); log.append("[Output Schema] ").append(schemaDir.resolve(outputSchemaFileName)).append("\n"); } @@ -881,9 +882,9 @@ public class ToolScaffolder { Files.createDirectories(wireMockBodyPath.getParent()); Files.createDirectories(wireMockMappingPath.getParent()); Files.createDirectories(podMockResponsePath.getParent()); - Files.writeString(wireMockBodyPath, mockResponse); - Files.writeString(wireMockMappingPath, wireMockMappingContent(interfaceId, wireMockBodyPath.getFileName().toString())); - Files.writeString(podMockResponsePath, mockResponse); + writeUtf8(wireMockBodyPath, mockResponse); + writeUtf8(wireMockMappingPath, wireMockMappingContent(interfaceId, wireMockBodyPath.getFileName().toString())); + writeUtf8(podMockResponsePath, mockResponse); ensureLocalHttpApiConfiguration(projectRoot, httpApiName, toolName); log.append("[WireMock Response] ").append(wireMockBodyPath).append("\n"); log.append("[WireMock Mapping] ").append(wireMockMappingPath).append("\n"); @@ -891,19 +892,19 @@ public class ToolScaffolder { } else { Path mockResponsePath = rootDir.resolve(Paths.get(moduleName, "src/main/resources/mock-responses", toolName + ".json")); Files.createDirectories(mockResponsePath.getParent()); - Files.writeString(mockResponsePath, mockResponse); + writeUtf8(mockResponsePath, mockResponse); log.append("[Mock Response] ").append(mockResponsePath).append("\n"); } Path generatedTestDir = rootDir.resolve(Paths.get(moduleName, "src/test/java/io/shinhanlife/dap/mcc/biz", group.toLowerCase(), "usecase")); Files.createDirectories(generatedTestDir); Path generatedTestPath = generatedTestDir.resolve(baseName + "UseCaseTest.java"); - Files.writeString(generatedTestPath, useCaseTestContent(bizPackage, baseName)); + writeUtf8(generatedTestPath, useCaseTestContent(bizPackage, baseName)); log.append("[Unit Test] ").append(generatedTestPath).append("\\n"); log.append("[Test Command] .\\gradlew.bat :").append(moduleName.substring(moduleName.lastIndexOf(java.io.File.separator) + 1)).append(":test --tests \"*").append(baseName).append("UseCaseTest\"\\n"); Files.createDirectories(definitionDir); Path definitionPath = definitionDir.resolve(toolName + ".yml"); - Files.writeString(definitionPath, toolDefinitionContentV17(toolName, title, description, group, + writeUtf8(definitionPath, toolDefinitionContentV17(toolName, title, description, group, interfaceId, inputFields, isMutationTool(baseName), definitionOptions)); log.append("[V17 Tool Definition] ").append(definitionPath).append("\n"); log.append("\n Tip: HTTP Tool은 WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요.\n"); @@ -1089,7 +1090,7 @@ public class ToolScaffolder { private static void ensureLocalHttpApiConfiguration(Path projectRoot, String httpApiName, String toolName) throws IOException { Path localConfigPath = projectRoot.resolve("dap-was-lib/src/main/resources/glow/application-glow-local.yml"); Files.createDirectories(localConfigPath.getParent()); - String existing = Files.exists(localConfigPath) ? Files.readString(localConfigPath) : ""; + String existing = Files.exists(localConfigPath) ? Files.readString(localConfigPath, StandardCharsets.UTF_8) : ""; if (java.util.regex.Pattern.compile("(?m)^\\s*-\\s+name:\\s*" + java.util.regex.Pattern.quote(httpApiName) + "\\s*$").matcher(existing).find()) { return; @@ -1133,7 +1134,11 @@ public class ToolScaffolder { enabled: true """; } - Files.writeString(localConfigPath, existing); + writeUtf8(localConfigPath, existing); + } + + private static void writeUtf8(Path path, String content) throws IOException { + Files.writeString(path, content, StandardCharsets.UTF_8); } private static String dtoContent(String packageName, String className, List fields, diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java index f0384524..cbf45303 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; import java.util.Comparator; /** Updates MCP SDK and project-owned metadata in a generated tool source file. */ @@ -27,10 +28,10 @@ public final class ToolSourceUpdater { throw new IllegalArgumentException("Tool source not found: " + toolName); } - String content = Files.readString(targetFile); + String content = Files.readString(targetFile, StandardCharsets.UTF_8); content = updateMcpTool(content, toolName, description); content = updateToolHint(content, categoryKey, register, requiresApproval); - Files.writeString(targetFile, content); + Files.writeString(targetFile, content, StandardCharsets.UTF_8); } private static Path findToolSource(Path rootDirectory, String toolName) throws IOException { @@ -47,7 +48,7 @@ public final class ToolSourceUpdater { private static boolean containsMcpTool(Path path, String toolName) { try { - return annotationArguments(Files.readString(path), "McpTool", toolName) != null; + return annotationArguments(Files.readString(path, StandardCharsets.UTF_8), "McpTool", toolName) != null; } catch (IOException exception) { throw new IllegalStateException("Failed to read tool source: " + path, exception); } diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java index 8dd8971c..6a48c94f 100644 --- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java @@ -10,12 +10,53 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.charset.StandardCharsets; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; class ToolScaffolderTest { + @Test + void generatesEveryToolSourceAsUtf8WithoutBrokenKoreanOrBom() throws Exception { + String moduleName = root.resolve("dap-was-korean").toString(); + + ToolScaffolder.scaffold("analysis data query", "CLYMCI00001", "분석 데이터 조회", + "분석 데이터를 조회하기 위한 도구로 다양한 분석 결과를 제공합니다.", + "cmm", "MCI", moduleName, "테스터", "2026.08.12", + false, null, null, null, + List.of(new ToolScaffolder.FieldDefinition("query", "String", "조회 조건", "계약 분석", true)), + List.of(new ToolScaffolder.FieldDefinition("analysisResult", "String", "분석 결과", "정상", false))); + + Path sourceRoot = root.resolve("dap-was-korean/src/main/java/io/shinhanlife/dap/mcc"); + Path useCase = sourceRoot.resolve("biz/cmm/usecase/AnalysisDataQueryUseCase.java"); + Path implementation = sourceRoot.resolve("biz/cmm/usecase/impl/AnalysisDataQueryUseCaseImpl.java"); + + String useCaseSource = Files.readString(useCase, StandardCharsets.UTF_8); + String implementationSource = Files.readString(implementation, StandardCharsets.UTF_8); + assertTrue(useCaseSource.contains("title = \"분석 데이터 조회\""), useCaseSource); + assertTrue(useCaseSource.contains("description = \"분석 데이터를 조회하기 위한 도구로 다양한 분석 결과를 제공합니다.\""), useCaseSource); + assertTrue(useCaseSource.contains("AX HUB 시스템 처리 클래스"), useCaseSource); + assertTrue(useCaseSource.contains("개정이력"), useCaseSource); + assertTrue(useCaseSource.contains("최초생성"), useCaseSource); + assertTrue(implementationSource.contains("요청 수신"), implementationSource); + assertTrue(implementationSource.contains("MapStruct를 이용한 자동 매핑"), implementationSource); + assertTrue(implementationSource.contains("연동 중 오류 발생"), implementationSource); + + try (var files = Files.walk(root.resolve("dap-was-korean"))) { + for (Path file : files.filter(Files::isRegularFile).toList()) { + byte[] bytes = Files.readAllBytes(file); + assertFalse(bytes.length >= 3 + && (bytes[0] & 0xff) == 0xef + && (bytes[1] & 0xff) == 0xbb + && (bytes[2] & 0xff) == 0xbf, + "UTF-8 BOM must not be generated: " + file); + String content = Files.readString(file, StandardCharsets.UTF_8); + assertFalse(content.contains("�"), "Invalid replacement character: " + file); + } + } + } + @Test void generatesV17ToolDefinitionTogetherWithToolSources() throws Exception { String moduleName = root.resolve("dap-was-v17-definition").toString(); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java index ec3faaa9..e061235d 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java @@ -8,14 +8,14 @@ import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse; /** * @package io.shinhanlife.dap.mcc.biz.ins.usecase * @className InsuranceClaimProcessorUseCase - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author Admin * @create 2026.08.11 *
- * ---------- 媛쒖젙?대젰 ----------
- * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+ * ---------- 개정이력 ----------
+ * 수정일       수정자     수정내용
  * ---------- -------- ---------------------------
- * 2026.08.11  Admin    理쒖큹?앹꽦
+ * 2026.08.11  Admin    최초생성
  *
  * 
*/ diff --git a/dap-was-oth/src/main/resources/application-dev.yml b/dap-was-oth/src/main/resources/application-dev.yml index 658693ab..b3e64636 100644 --- a/dap-was-oth/src/main/resources/application-dev.yml +++ b/dap-was-oth/src/main/resources/application-dev.yml @@ -1,4 +1,4 @@ -# OCI ?대씪?곕뱶 ?섍꼍 ?꾩슜 ?ㅼ젙 +# OCI 클라우드 개발 환경 전용 설정 server: port: ${PORT:8084} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java index 13cb3645..47a74a93 100644 --- a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java @@ -8,14 +8,14 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; /** * @package io.shinhanlife.dap.mcc.biz.cmm.usecase * @className MemoListRetrieverUseCase - * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @description AX HUB 시스템 처리 클래스 * @author Admin * @create 2026.08.11 *
- * ---------- 媛쒖젙?대젰 ----------
- * ?섏젙??     ?섏젙??   ?섏젙?댁슜
+ * ---------- 개정이력 ----------
+ * 수정일       수정자     수정내용
  * ---------- -------- ---------------------------
- * 2026.08.11  Admin    理쒖큹?앹꽦
+ * 2026.08.11  Admin    최초생성
  *
  * 
*/ diff --git a/dap-was-sms/src/main/resources/application-dev.yml b/dap-was-sms/src/main/resources/application-dev.yml index dfee070e..6aed956e 100644 --- a/dap-was-sms/src/main/resources/application-dev.yml +++ b/dap-was-sms/src/main/resources/application-dev.yml @@ -1,4 +1,4 @@ -# OCI ?대씪?곕뱶 ?섍꼍 ?꾩슜 ?ㅼ젙 +# OCI 클라우드 개발 환경 전용 설정 server: port: ${PORT:8082}