feat: add CommonCodeResolver, codes.csv and RecruitmentFeeDisclosureData companyCode mapping
All checks were successful
Deploy Tools / deploy (push) Successful in 1m24s

This commit is contained in:
jade
2026-09-17 08:53:30 +09:00
parent d2a82db6a4
commit ae8ab85016
7 changed files with 1941 additions and 284 deletions

View File

@@ -0,0 +1,180 @@
package io.shinhanlife.dat.lib.util;
import jakarta.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @package io.shinhanlife.dat.lib.util
* @className CommonCodeResolver
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Component
public class CommonCodeResolver {
private static final String DEFAULT_RESOURCE_PATH = "codes.csv";
// Group -> (Code -> Name)
private static final Map<String, Map<String, String>> CODE_TO_NAME_MAP = new ConcurrentHashMap<>();
// Group -> (Normalized Name or Code -> Code)
private static final Map<String, Map<String, String>> NAME_TO_CODE_MAP = new ConcurrentHashMap<>();
static {
loadCodes(DEFAULT_RESOURCE_PATH);
}
@PostConstruct
public void init() {
loadCodes(DEFAULT_RESOURCE_PATH);
}
public static synchronized void reload() {
loadCodes(DEFAULT_RESOURCE_PATH);
}
public static synchronized void loadCodes(String resourcePath) {
ClassLoader classLoader = CommonCodeResolver.class.getClassLoader();
try (InputStream is = classLoader.getResourceAsStream(resourcePath)) {
if (is == null) {
log.warn("[CommonCodeResolver] 리소스 파일을 찾을 수 없습니다: {}", resourcePath);
return;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
CODE_TO_NAME_MAP.clear();
NAME_TO_CODE_MAP.clear();
String line;
boolean firstLine = true;
while ((line = reader.readLine()) != null) {
if (firstLine) {
firstLine = false;
if (line.startsWith("\uFEFF")) {
line = line.substring(1);
}
if (line.contains("그룹") || line.contains("group")) {
continue;
}
}
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
String[] parts = line.split(",", -1);
if (parts.length < 3) {
continue;
}
String group = parts[0].trim().toUpperCase(Locale.ROOT);
String code = parts[1].trim();
String name = parts[2].trim();
if (group.isEmpty() || code.isEmpty()) {
continue;
}
Map<String, String> codeToName = CODE_TO_NAME_MAP.computeIfAbsent(group, k -> new ConcurrentHashMap<>());
Map<String, String> nameToCode = NAME_TO_CODE_MAP.computeIfAbsent(group, k -> new ConcurrentHashMap<>());
codeToName.put(code, name);
nameToCode.put(code, code);
nameToCode.put(name, code);
nameToCode.put(name.toLowerCase(Locale.ROOT), code);
nameToCode.put(name.replace(" ", ""), code);
}
log.info("[CommonCodeResolver] 공통코드 로드 완료: {}개 그룹", CODE_TO_NAME_MAP.size());
}
} catch (Exception e) {
log.error("[CommonCodeResolver] 공통코드 로드 중 오류 발생: {}", e.getMessage(), e);
}
}
/**
* 입력값(명칭 또는 코드)을 처리계 표준 코드값으로 변환합니다.
*
* @param group 코드 그룹명 (예: COMPANY, STATUS)
* @param input 사용자가 입력한 명칭("삼성전자") 또는 코드("005930")
* @return 매핑된 코드값(예: "005930"). 매핑되지 않으면 원본 입력값 반환.
*/
public static String resolve(String group, String input) {
return resolve(group, input, input);
}
/**
* 입력값(명칭 또는 코드)을 처리계 표준 코드값으로 변환합니다.
*
* @param group 코드 그룹명
* @param input 입력값
* @param defaultValue 매핑 실패 시 반환할 기본값
* @return 매핑된 코드값 또는 defaultValue
*/
public static String resolve(String group, String input, String defaultValue) {
if (input == null || input.isBlank()) {
return defaultValue;
}
if (group == null) {
return input.trim();
}
String normalizedGroup = group.trim().toUpperCase(Locale.ROOT);
Map<String, String> nameToCode = NAME_TO_CODE_MAP.get(normalizedGroup);
if (nameToCode == null) {
return defaultValue;
}
String trimmed = input.trim();
String matched = nameToCode.get(trimmed);
if (matched != null) {
return matched;
}
matched = nameToCode.get(trimmed.toLowerCase(Locale.ROOT));
if (matched != null) {
return matched;
}
matched = nameToCode.get(trimmed.replace(" ", ""));
if (matched != null) {
return matched;
}
return defaultValue;
}
/**
* 코드값에 해당하는 표준 명칭을 반환합니다. (응답 변환 시 사용)
*/
public static String getName(String group, String code) {
if (group == null || code == null) {
return code;
}
String normalizedGroup = group.trim().toUpperCase(Locale.ROOT);
Map<String, String> codeToName = CODE_TO_NAME_MAP.get(normalizedGroup);
if (codeToName != null) {
return codeToName.getOrDefault(code.trim(), code);
}
return code;
}
/**
* 특정 그룹의 전체 코드-명칭 맵을 반환합니다.
*/
public static Map<String, String> getCodeMap(String group) {
if (group == null) {
return Collections.emptyMap();
}
Map<String, String> map = CODE_TO_NAME_MAP.get(group.trim().toUpperCase(Locale.ROOT));
return map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap();
}
}

View File

@@ -0,0 +1,17 @@
그룹,코드,명칭
COMPANY,005930,삼성전자
COMPANY,035420,NAVER
COMPANY,035720,카카오
COMPANY,000660,SK하이닉스
COMPANY,055550,신한지주
COMPANY,086790,하나금융지주
COMPANY,105560,KB금융
COMPANY,316140,우리금융지주
COMPANY,000810,삼성화재
COMPANY,088350,한화생명
COMPANY,005830,DB손해보험
COMPANY,000030,우리은행
STATUS,01,정상
STATUS,02,비정상
STATUS,03,해지
STATUS,04,실효
1 그룹 코드 명칭
2 COMPANY 005930 삼성전자
3 COMPANY 035420 NAVER
4 COMPANY 035720 카카오
5 COMPANY 000660 SK하이닉스
6 COMPANY 055550 신한지주
7 COMPANY 086790 하나금융지주
8 COMPANY 105560 KB금융
9 COMPANY 316140 우리금융지주
10 COMPANY 000810 삼성화재
11 COMPANY 088350 한화생명
12 COMPANY 005830 DB손해보험
13 COMPANY 000030 우리은행
14 STATUS 01 정상
15 STATUS 02 비정상
16 STATUS 03 해지
17 STATUS 04 실효

View File

@@ -0,0 +1,62 @@
package io.shinhanlife.dat.lib.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* @package io.shinhanlife.dat.lib.util
* @className CommonCodeResolverTest
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
class CommonCodeResolverTest {
@Test
void resolvesCompanyCodeFromExactName() {
assertEquals("005930", CommonCodeResolver.resolve("COMPANY", "삼성전자"));
assertEquals("035420", CommonCodeResolver.resolve("COMPANY", "NAVER"));
assertEquals("035720", CommonCodeResolver.resolve("COMPANY", "카카오"));
assertEquals("055550", CommonCodeResolver.resolve("COMPANY", "신한지주"));
}
@Test
void resolvesCompanyCodeWhenAlreadyCode() {
assertEquals("005930", CommonCodeResolver.resolve("COMPANY", "005930"));
assertEquals("035420", CommonCodeResolver.resolve("COMPANY", "035420"));
}
@Test
void resolvesCaseInsensitiveAndSpaceIgnored() {
assertEquals("035420", CommonCodeResolver.resolve("COMPANY", "naver"));
assertEquals("005930", CommonCodeResolver.resolve("COMPANY", "삼성 전자"));
}
@Test
void resolvesStatusGroupCode() {
assertEquals("01", CommonCodeResolver.resolve("STATUS", "정상"));
assertEquals("02", CommonCodeResolver.resolve("STATUS", "비정상"));
assertEquals("03", CommonCodeResolver.resolve("STATUS", "해지"));
assertEquals("01", CommonCodeResolver.resolve("STATUS", "01"));
}
@Test
void getsNameFromCode() {
assertEquals("삼성전자", CommonCodeResolver.getName("COMPANY", "005930"));
assertEquals("정상", CommonCodeResolver.getName("STATUS", "01"));
}
@Test
void returnsOriginalWhenNotFound() {
assertEquals("알수없는회사", CommonCodeResolver.resolve("COMPANY", "알수없는회사"));
assertEquals("99", CommonCodeResolver.resolve("STATUS", "99"));
}
}