수신측 - JacksonUtil.escapeControlChars() 적용 범위 확대
JsonReader 에만 걸려 있어 TemplateCodeConvertAdapterErrorMsgHandler 등
직접 readTree() 하는 경로가 "Illegal unquoted character ((CTRL-CHAR, code 10))"
로 실패했다. JacksonUtil.readTree() 공용 진입점과 payload 를 파싱하는
핸들러/필터에 적용한다.
송신측 - MessageUtil.stripControlChars() 신규
TemplateAdapterErrorMsgHandler.render() 가 Matcher.quoteReplacement() 로만
치환하고 있었다. 이것은 정규식 치환용이라 JSON/XML 이스케이프가 아니어서,
값에 개행이 있으면 깨진 JSON 을 만들어 응답으로 내보냈다. 실제로 위 파싱
오류의 원문도 상대 전문이 아니라 이 렌더 결과였다.
템플릿이 JSON/XML 어느 쪽인지 알 수 없어 포맷별 이스케이프가 불가능하므로
제어문자를 걸러낸다. 탭/개행/CR 은 공백으로, 나머지 제어문자와 DEL 은 제거.
render() 의 스칼라 치환과 renderRow() 의 행 필드 치환에만 적용해
템플릿 서식(들여쓰기/개행)은 보존한다.
검증: gradlew compileJava BUILD SUCCESSFUL.
databind 2.12.7 런타임에서 0x00~0x1F 전수 + 로그 재현 케이스 통과.
상대 시스템이 개행을 이스케이프하지 않고 그대로 보내는 경우
"Illegal unquoted character ((CTRL-CHAR, code 10))" 로 파싱이 실패했다.
파서 옵션(ALLOW_UNESCAPED_CONTROL_CHARS)으로 푸는 대신, 입력을 표준 JSON 으로
정규화하는 JacksonUtil.escapeControlChars() 를 추가하고 파서는 strict 로 유지한다.
문자열 리터럴 "안" 의 제어문자만 바꾼다. JSON 은 토큰 사이의 개행/탭을 공백으로
허용하므로 구조적 공백까지 치환하면 pretty-print 된 JSON 이 오히려 깨진다.
- EAIServiceMonitor: timeOutCodes.indexOf 판정을 '> 0' -> '>= 0' 으로 수정
(목록 첫 번째 코드가 타임아웃이 아닌 시스템에러로 분류되던 문제)
- EAIServiceMonitor: getLogRspErrCd() null 대비 defaultString 적용, 리터럴 비교 순서 변경
- EAILogDAO, EAIFileLogger: 응답에러코드 substring(1,2) 를 널 안전한 StringUtils.substring 으로 교체
JacksonUtil.newNumberSafeMapper() 는 USE_BIG_DECIMAL_FOR_FLOATS +
withExactBigDecimals 로 입력(파싱)측만 막고 있었다. DecimalNode 직렬화는
BigDecimal.toString() 이라 scale 이 음수이거나 adjusted exponent < -6 일 때
지수 표기가 나온다. 즉 파싱을 제대로 해도 내보낼 때 작은 소수가 깨졌다.
0.00000012 -> 1.2E-7 , -0.0000005 -> -5E-7
WRITE_BIGDECIMAL_AS_PLAIN 을 추가해 출력측까지 보존한다.
(금액처럼 scale >= 0 인 큰 값은 원래 영향 없음. 이율/환율이 대상)
또한 JacksonUtil.setValue() 가 ArrayNode.set(int, String) 을 쓰고 있었다.
이 오버로드는 jackson-databind 2.13 부터이고, 실제 런타임은 2.12.7 이다
(2.12.7 은 set(int, JsonNode) 하나뿐). jackson-dataformat-xml:2.13.1 이
compileOnly 로 컴파일 클래스패스에만 2.13.1 을 올려 컴파일만 통과하던
상태였고, 배열 경로에 setText() 하면 운영에서 NoSuchMethodError 가 났다.
TextNode.valueOf() 로 감싸 set(int, JsonNode) 에 바인딩한다.
그 외
- TemplateCodeConvertAdapterErrorMsgHandler: 응답 JSON 왕복 지점 전환
- HttpAdapterServiceSupport, HttpClient5AdapterServiceRestAddFilter: 직렬화 지점 전환
- JsonReader: parse() 호출마다 ObjectMapper 를 새로 만들던 것을 static 싱글턴으로
바꾸고 중복 설정 코드를 JacksonUtil 에 위임
- MessageKeyExtractor: 선언만 하고 쓰지 않는 ObjectMapper 필드 제거
- build.gradle: 미사용 jackson-dataformat-xml 제거 (common 은 Ignite 경유로 jackson 확보)
검증: gradlew compileJava BUILD SUCCESSFUL, databind 2.12.7 런타임에서
숫자 왕복 4건 + setText 배열/객체/미존재 경로 5건 전부 통과.
base64/hex 변환, DAMO 및 EncryptionManager 암복호화, 배포 버전 조회 API를
기존 CryptoModuleManage/SessionCacheManage 컬렉션과 동일한 스타일로 샘플링.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/manage/crypto 하위에 등록된 암호화모듈 목록/단건 조회, DYNAMIC 키
전략(KeyDerivationStrategy) 로드 상태 확인, 동적 키 캐시 키 목록 조회,
Base64 기반 암복호화 라운드트립 테스트 엔드포인트를 제공한다.
원본 암호키(encKeyHex/decKeyHex/ivHex)는 응답에 포함하지 않는다.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>