From 867a51a1cd65fb38ae47f7b4190c867201a65a60 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:05:31 +0000 Subject: [PATCH] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquiring/codec/MessageCodec.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java new file mode 100644 index 0000000..1b9f1f4 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java @@ -0,0 +1,105 @@ +package com.klaro.acquirecore.acquiring.codec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * 전문 언팩/팩 커덱 (codec) + * 바이너리 전문 파싱 및 조립 담당 + */ +@Component +public class MessageCodec { + + private static final Logger log = LoggerFactory.getLogger(MessageCodec.class); + + // 전문 헤더 길이 + private static final int HEADER_LENGTH = 20; + // 전문 트레일러 길이 + private static final int TRAILER_LENGTH = 4; + + /** + * 원시 전문 언팩 + * @param rawMessage 원시 바이트 배열 + * @return 파싱된 전문 객체 + */ + public ParsedMessage unpack(byte[] rawMessage) { + log.debug("전문 언팩 시작 - 길이: {}", rawMessage.length); + + if (rawMessage == null || rawMessage.length < HEADER_LENGTH + TRAILER_LENGTH) { + throw new IllegalArgumentException("전문 길이 오류"); + } + + ParsedMessage parsed = new ParsedMessage(); + + // 헤더 파싱 + parsed.setMessageType(decodeString(rawMessage, 0, 4)); + parsed.setTxId(decodeString(rawMessage, 4, 12)); + parsed.setTxCode(decodeString(rawMessage, 16, 4)); + + // 바디 파싱 (가변 길이) + int bodyLength = rawMessage.length - HEADER_LENGTH - TRAILER_LENGTH; + if (bodyLength > 0) { + parsed.setMerchantId(decodeString(rawMessage, HEADER_LENGTH, 15)); + parsed.setCardNumber(decodeString(rawMessage, HEADER_LENGTH + 15, 20)); + parsed.setAmount(decodeString(rawMessage, HEADER_LENGTH + 35, 12)); + parsed.setApprovalCode(decodeString(rawMessage, HEADER_LENGTH + 47, 10)); + } + + // 트레일러 검증 + byte[] trailer = new byte[TRAILER_LENGTH]; + System.arraycopy(rawMessage, rawMessage.length - TRAILER_LENGTH, trailer, 0, TRAILER_LENGTH); + parsed.setTrailer(new String(trailer)); + + log.debug("전문 언팩 완료 - TX_ID: {}, TX_CODE: {}", parsed.getTxId(), parsed.getTxCode()); + return parsed; + } + + /** + * 전문 팩 (응답 전문 생성) + * @param parsed 파싱된 전문 + * @return 원시 바이트 배열 + */ + public byte[] pack(ParsedMessage parsed) { + log.debug("전문 팩 시작 - TX_ID: {}", parsed.getTxId()); + + StringBuilder sb = new StringBuilder(); + sb.append(padRight(parsed.getMessageType(), 4)); + sb.append(padRight(parsed.getTxId(), 12)); + sb.append(padRight(parsed.getTxCode(), 4)); + sb.append(padRight(parsed.getMerchantId(), 15)); + sb.append(padRight(parsed.getCardNumber(), 20)); + sb.append(padRight(parsed.getAmount(), 12)); + sb.append(padRight(parsed.getApprovalCode(), 10)); + sb.append("ETX"); + + return sb.toString().getBytes(); + } + + /** + * 바이트 배열에서 문자열 추출 + */ + private String decodeString(byte[] data, int offset, int length) { + if (offset + length > data.length) { + return ""; + } + byte[] sub = new byte[length]; + System.arraycopy(data, offset, sub, 0, length); + return new String(sub).trim(); + } + + /** + * 문자열을 지정 길이만큼 패딩 + */ + private String padRight(String value, int length) { + if (value == null) value = ""; + if (value.length() >= length) { + return value.substring(0, length); + } + StringBuilder sb = new StringBuilder(value); + while (sb.length() < length) { + sb.append(' '); + } + return sb.toString(); + } +}