package com.pms.api; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.security.cert.X509Certificate; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import java.util.Random; /** * 창고 정보 조회 API 클라이언트 * 회사의 창고정보를 조회하는 API를 호출합니다. */ public class WarehouseApiClient { private static final String API_URL = "~/apiproxy/api20A00S00801"; // 실제 키값 (기존 API 클라이언트와 동일) private static final String CALLER_NAME = "API_gcmsAmaranth40578"; private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc"; private static final String HASH_KEY = "22519103205540290721741689643674301018832465"; private static final String GROUP_SEQ = "gcmsAmaranth40578"; /** * 창고 정보를 조회합니다. * * @param baseUrl API 서버의 기본 URL (예: https://erp.rps-korea.com) * @param coCd 회사코드 (4자리, 필수) * @param baselocFg 기준위치구분 (0: 전체, 1: 기준위치만) * @return API 응답 결과 (JSON 문자열) * @throws Exception API 호출 중 발생하는 예외 */ public String getWarehouseList(String baseUrl, String coCd, String baselocFg) throws Exception { if (coCd == null || coCd.trim().isEmpty()) { throw new IllegalArgumentException("회사코드(coCd)는 필수입니다."); } // JDK 1.7에서 TLS 1.2 활성화 System.setProperty("https.protocols", "TLSv1.2"); // SSL 인증서 검증 우회 (개발 환경용) TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() { public boolean verify(String hostname, javax.net.ssl.SSLSession session) { return true; } }); // API URL 구성 String urlPath = API_URL.replace("~", ""); // baseUrl 끝에 슬래시가 있으면 제거 String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; String fullUrl = cleanBaseUrl + urlPath; URL url = new URL(fullUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // 연결 타임아웃 설정 (30초) connection.setConnectTimeout(30000); connection.setReadTimeout(30000); // 리다이렉트 자동 따라가기 비활성화 (수동 처리) connection.setInstanceFollowRedirects(false); try { // HTTP 메서드 설정 connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); connection.setRequestProperty("Accept", "application/json"); // 인증 프로토콜 가이드에 따른 헤더 설정 // 1. callerName: 호출 구분명 connection.setRequestProperty("callerName", CALLER_NAME); // 2. Authorization: Bearer + accessToken connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN); // 3. transaction-id: 32자리 랜덤 문자열 String transactionId = generateTransactionId(); connection.setRequestProperty("transaction-id", transactionId); // 4. timestamp: Unix timestamp (초 단위) String timestamp = String.valueOf(System.currentTimeMillis() / 1000); connection.setRequestProperty("timestamp", timestamp); // 5. groupSeq: Amaranth10 그룹시퀀스 connection.setRequestProperty("groupSeq", GROUP_SEQ); // 6. wehago-sign: HMacSHA256 생성 String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath); connection.setRequestProperty("wehago-sign", wehagoSign); // 요청 본문 작성 String requestBody = buildRequestBody(coCd, baselocFg); // 요청 전송 설정 connection.setDoOutput(true); connection.setDoInput(true); // 요청 본문 전송 OutputStreamWriter writer = new OutputStreamWriter( connection.getOutputStream(), StandardCharsets.UTF_8); writer.write(requestBody); writer.flush(); writer.close(); // 응답 코드 확인 int responseCode = connection.getResponseCode(); // 리다이렉트 처리 (301, 302, 303, 307, 308) if (responseCode == 301 || responseCode == 302 || responseCode == 303 || responseCode == 307 || responseCode == 308) { String location = connection.getHeaderField("Location"); connection.disconnect(); if (location != null) { // 리다이렉트 URL로 재시도 URL redirectUrl = new URL(location); connection = (HttpURLConnection) redirectUrl.openConnection(); connection.setConnectTimeout(30000); connection.setReadTimeout(30000); connection.setInstanceFollowRedirects(false); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); connection.setRequestProperty("Accept", "application/json"); // 리다이렉트 시에도 동일한 인증 헤더 재설정 connection.setRequestProperty("callerName", CALLER_NAME); connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN); connection.setRequestProperty("transaction-id", transactionId); connection.setRequestProperty("timestamp", timestamp); connection.setRequestProperty("groupSeq", GROUP_SEQ); connection.setRequestProperty("wehago-sign", wehagoSign); // 동일한 요청 본문 재사용 connection.setDoOutput(true); connection.setDoInput(true); OutputStreamWriter redirectWriter = new OutputStreamWriter( connection.getOutputStream(), StandardCharsets.UTF_8); redirectWriter.write(requestBody); redirectWriter.flush(); redirectWriter.close(); responseCode = connection.getResponseCode(); } } // 응답 읽기 BufferedReader reader = null; StringBuilder response = new StringBuilder(); try { if (responseCode >= 200 && responseCode < 300) { reader = new BufferedReader(new InputStreamReader( connection.getInputStream(), StandardCharsets.UTF_8)); } else { // 에러 스트림이 null일 수 있으므로 체크 java.io.InputStream errorStream = connection.getErrorStream(); if (errorStream != null) { reader = new BufferedReader(new InputStreamReader( errorStream, StandardCharsets.UTF_8)); } else { // 에러 스트림이 없으면 응답 코드만 반환 throw new Exception("API 호출 실패: HTTP " + responseCode + " (에러 응답 본문 없음)"); } } String line; while ((line = reader.readLine()) != null) { response.append(line); } } finally { if (reader != null) { reader.close(); } } if (responseCode >= 200 && responseCode < 300) { return response.toString(); } else { throw new Exception("API 호출 실패: HTTP " + responseCode + " - " + response.toString()); } } finally { connection.disconnect(); } } /** * 요청 본문 JSON을 생성합니다. * * @param coCd 회사코드 * @param baselocFg 기준위치구분 * @return JSON 문자열 */ private String buildRequestBody(String coCd, String baselocFg) { StringBuilder json = new StringBuilder(); json.append("{"); json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\""); if (baselocFg != null && !baselocFg.trim().isEmpty()) { json.append(",\"baselocFg\":\"").append(escapeJson(baselocFg)).append("\""); } json.append("}"); return json.toString(); } /** * JSON 문자열 이스케이프 처리 * * @param value 원본 문자열 * @return 이스케이프된 문자열 */ private String escapeJson(String value) { if (value == null) { return ""; } return value.replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") .replace("\t", "\\t"); } /** * 32자리 랜덤 transaction-id 생성 * * @return 32자리 랜덤 문자열 */ private String generateTransactionId() { String chars = "0123456789abcdef"; Random random = new Random(); StringBuilder sb = new StringBuilder(32); for (int i = 0; i < 32; i++) { sb.append(chars.charAt(random.nextInt(chars.length()))); } return sb.toString(); } /** * Wehago-sign 생성 * HMacSHA256(key=hashKey, value=accessToken+transactionId+timestamp+url) 후 Base64 인코딩 * * @param accessToken 접근 토큰 * @param transactionId 트랜잭션 ID * @param timestamp 타임스탬프 * @param urlPath URL 경로 * @return Base64 인코딩된 서명 * @throws Exception 서명 생성 중 발생하는 예외 */ private String generateWehagoSign(String accessToken, String transactionId, String timestamp, String urlPath) throws Exception { try { // value = accessToken + transactionId + timestamp + url String value = accessToken + transactionId + timestamp + urlPath; // HMacSHA256 생성 SecretKeySpec keySpec = new SecretKeySpec( HASH_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(keySpec); byte[] encrypted = mac.doFinal(value.getBytes(StandardCharsets.UTF_8)); // Base64 인코딩 String base64Binary = Base64.encodeBase64String(encrypted); return base64Binary; } catch (Exception e) { System.err.println("Wehago-sign 생성 오류: " + e.getMessage()); e.printStackTrace(); throw e; } } /** * JSON 응답을 읽기 쉬운 텍스트 형식으로 출력 * * @param jsonResponse JSON 응답 문자열 */ private static void printWarehouseList(String jsonResponse) { try { // 간단한 JSON 파싱 (JDK 1.7 호환) if (jsonResponse == null || jsonResponse.trim().isEmpty()) { System.out.println("응답 데이터가 없습니다."); return; } // resultData 배열 찾기 int dataStart = jsonResponse.indexOf("\"resultData\":["); if (dataStart == -1) { System.out.println("창고 데이터를 찾을 수 없습니다."); System.out.println("원본 응답: " + jsonResponse); return; } // resultCode 확인 int codeStart = jsonResponse.indexOf("\"resultCode\":"); if (codeStart != -1) { int codeEnd = jsonResponse.indexOf(",", codeStart); if (codeEnd == -1) codeEnd = jsonResponse.indexOf("}", codeStart); String resultCode = jsonResponse.substring(codeStart + 13, codeEnd).trim(); System.out.println("결과 코드: " + resultCode); } // resultMsg 확인 int msgStart = jsonResponse.indexOf("\"resultMsg\":\""); if (msgStart != -1) { int msgEnd = jsonResponse.indexOf("\"", msgStart + 13); String resultMsg = jsonResponse.substring(msgStart + 13, msgEnd); System.out.println("결과 메시지: " + resultMsg); System.out.println(); } // 창고 정보 파싱 및 출력 String dataSection = jsonResponse.substring(dataStart + 14); int bracketCount = 0; int startIdx = -1; int warehouseCount = 0; System.out.println("===================================="); System.out.println("창고 목록"); System.out.println("===================================="); System.out.println(); for (int i = 0; i < dataSection.length(); i++) { char c = dataSection.charAt(i); if (c == '{') { if (bracketCount == 0) { startIdx = i; } bracketCount++; } else if (c == '}') { bracketCount--; if (bracketCount == 0 && startIdx != -1) { // 창고 객체 추출 String warehouseJson = dataSection.substring(startIdx, i + 1); printWarehouseInfo(warehouseJson, ++warehouseCount); startIdx = -1; } } } System.out.println(); System.out.println("===================================="); System.out.println("총 " + warehouseCount + "개 창고"); System.out.println("===================================="); } catch (Exception e) { System.err.println("출력 중 오류 발생: " + e.getMessage()); System.out.println("원본 응답: " + jsonResponse); } } /** * 개별 창고 정보를 읽기 쉬운 형식으로 출력 * * @param warehouseJson 창고 JSON 문자열 * @param index 순번 */ private static void printWarehouseInfo(String warehouseJson, int index) { System.out.println("[" + index + "] 창고 정보"); System.out.println("------------------------------------"); // API 문서 기준 필드 추출 String coCd = extractJsonValue(warehouseJson, "coCd"); String baselocCd = extractJsonValue(warehouseJson, "baselocCd"); String baselocNm = extractJsonValue(warehouseJson, "baselocNm"); String baselocFg = extractJsonValue(warehouseJson, "baselocFg"); String divCd = extractJsonValue(warehouseJson, "divCd"); String inlocCd = extractJsonValue(warehouseJson, "inlocCd"); String inlocNm = extractJsonValue(warehouseJson, "inlocNm"); String outlocCd = extractJsonValue(warehouseJson, "outlocCd"); String outlocNm = extractJsonValue(warehouseJson, "outlocNm"); String baselocDc = extractJsonValue(warehouseJson, "baselocDc"); String useYn = extractJsonValue(warehouseJson, "useYn"); String useYnNm = extractJsonValue(warehouseJson, "useYnNm"); System.out.println(" 회사코드: " + (coCd.isEmpty() ? "-" : coCd)); System.out.println(" 창고코드: " + (baselocCd.isEmpty() ? "-" : baselocCd)); System.out.println(" 창고명: " + (baselocNm.isEmpty() ? "-" : baselocNm)); // 구분코드 해석 String baselocFgNm = "-"; if ("0".equals(baselocFg)) baselocFgNm = "창고"; else if ("1".equals(baselocFg)) baselocFgNm = "생산공정"; else if ("2".equals(baselocFg)) baselocFgNm = "외주공정"; System.out.println(" 구분: " + baselocFgNm + " (" + baselocFg + ")"); if (!divCd.isEmpty()) { System.out.println(" 사업장코드: " + divCd); } if (!inlocCd.isEmpty()) { System.out.println(" 입고기본장소: " + inlocCd + (inlocNm.isEmpty() ? "" : " (" + inlocNm + ")")); } if (!outlocCd.isEmpty()) { System.out.println(" 출고기본장소: " + outlocCd + (outlocNm.isEmpty() ? "" : " (" + outlocNm + ")")); } if (!baselocDc.isEmpty()) { System.out.println(" 비고: " + baselocDc); } if (!useYnNm.isEmpty()) { System.out.println(" 사용여부: " + useYnNm); } else if (!useYn.isEmpty()) { System.out.println(" 사용여부: " + (useYn.equals("1") ? "사용" : "미사용")); } System.out.println(); } /** * JSON 문자열에서 특정 필드의 값을 추출 * * @param json JSON 문자열 * @param fieldName 필드명 * @return 필드 값 (없으면 빈 문자열) */ private static String extractJsonValue(String json, String fieldName) { String searchKey = "\"" + fieldName + "\":"; int startIdx = json.indexOf(searchKey); if (startIdx == -1) { return ""; } startIdx += searchKey.length(); // 공백 제거 while (startIdx < json.length() && Character.isWhitespace(json.charAt(startIdx))) { startIdx++; } if (startIdx >= json.length()) { return ""; } char firstChar = json.charAt(startIdx); // 문자열 값인 경우 if (firstChar == '"') { int endIdx = json.indexOf('"', startIdx + 1); if (endIdx == -1) return ""; return json.substring(startIdx + 1, endIdx); } // 숫자나 null인 경우 else { int endIdx = startIdx; while (endIdx < json.length() && (Character.isDigit(json.charAt(endIdx)) || json.charAt(endIdx) == '.' || json.charAt(endIdx) == '-' || json.charAt(endIdx) == 'n' || // null json.charAt(endIdx) == 'u' || json.charAt(endIdx) == 'l')) { endIdx++; } String value = json.substring(startIdx, endIdx).trim(); // null 체크 if (value.startsWith("null")) { return ""; } return value; } } /** * 테스트 메인 메서드 */ public static void main(String[] args) { // JDK 1.7에서 TLS 1.2 활성화 System.setProperty("https.protocols", "TLSv1.2"); WarehouseApiClient client = new WarehouseApiClient(); try { System.out.println("===================================="); System.out.println("창고 조회 API 호출 테스트"); System.out.println("===================================="); System.out.println(); String baseUrl = "https://erp.rps-korea.com"; String apiEndpoint = API_URL.replace("~", ""); // ~ 제거 System.out.println("API 서버: " + baseUrl); System.out.println("API 엔드포인트: " + apiEndpoint); System.out.println(); // 회사코드 1000, baselocFg=0 으로 창고 조회 System.out.println("[1] 회사코드 1000, baselocFg=0 으로 창고 조회"); System.out.println("------------------------------------"); String response = client.getWarehouseList( baseUrl, "1000", // 회사코드 "0" // baselocFg: 0 (창고) ); // 원본 응답 출력 System.out.println("원본 응답: " + response); System.out.println(); // JSON 응답을 읽기 쉬운 텍스트 형식으로 출력 printWarehouseList(response); System.out.println(); } catch (Exception e) { System.err.println("===================================="); System.err.println("오류 발생: " + e.getMessage()); System.err.println("===================================="); e.printStackTrace(); } } }