From 867aa398a39180b6e7a8a1a79e19a72b42786475 Mon Sep 17 00:00:00 2001 From: chpark Date: Wed, 4 Feb 2026 18:37:26 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B0=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ERP_SYNC_README.md | 226 +++++++ src/com/pms/api/CustomerApiClient.java | 255 ++++++++ src/com/pms/api/DepartmentApiClient.java | 261 ++++++++ src/com/pms/api/EmployeeApiClient.java | 268 ++++++++ src/com/pms/controller/AdminController.java | 85 +++ src/com/pms/mapper/batch.xml | 178 ++++++ src/com/pms/mapper/mybatisConf.xml | 13 +- src/com/pms/service/BatchService.java | 653 ++++++++++++++++++++ 8 files changed, 1933 insertions(+), 6 deletions(-) create mode 100644 ERP_SYNC_README.md create mode 100644 src/com/pms/api/CustomerApiClient.java create mode 100644 src/com/pms/api/DepartmentApiClient.java create mode 100644 src/com/pms/api/EmployeeApiClient.java create mode 100644 src/com/pms/mapper/batch.xml diff --git a/ERP_SYNC_README.md b/ERP_SYNC_README.md new file mode 100644 index 0000000..c187498 --- /dev/null +++ b/ERP_SYNC_README.md @@ -0,0 +1,226 @@ +# ERP API 데이터 동기화 배치 시스템 + +## 개요 +이 시스템은 외부 ERP API로부터 거래처, 부서, 사원 정보를 자동으로 가져와 PLM 시스템의 데이터베이스에 동기화하는 배치 프로그램입니다. + +## 주요 기능 + +### 1. 거래처 정보 동기화 (CustomerApiClient) +- **API 엔드포인트**: `/apiproxy/api16S11` +- **대상 테이블**: `client_mng` +- **동기화 데이터**: + - 거래처코드, 거래처명, 거래처약칭 + - 사업자등록번호, 대표자명 + - 업태, 종목 + - 주소, 연락처 정보 + - 사용여부 + +### 2. 부서 정보 동기화 (DepartmentApiClient) +- **API 엔드포인트**: `/apiproxy/api16S10` +- **대상 테이블**: `dept_info` +- **동기화 데이터**: + - 부서코드, 부서명 + - 회사코드, 부서레벨 + - 상위부서 정보 + - 정렬순서, 등록일/종료일 + +### 3. 사원 정보 동기화 (EmployeeApiClient) +- **API 엔드포인트**: `/apiproxy/api16S05` +- **대상 테이블**: `user_info` +- **동기화 데이터**: + - 사원번호, 사원코드, 사원명 + - 부서 정보 + - 직급, 직책 + - 연락처 (이메일, 휴대폰) + - 재직구분, 입사일, 생년월일 + +## 배치 실행 스케줄 + +### 자동 실행 +- **실행 시간**: 매일 새벽 00시 00분 +- **Cron 표현식**: `0 0 0 * * ?` +- **실행 메서드**: `BatchService.syncErpData()` + +### 수동 실행 +필요시 `BatchService.syncErpData()` 메서드를 직접 호출하여 수동 실행 가능 + +## 설치 및 설정 + +### 1. 데이터베이스 설정 +```bash +# PostgreSQL에 접속하여 테이블 생성 스크립트 실행 +psql -U [사용자명] -d [데이터베이스명] -f database/create_erp_sync_tables.sql +``` + +### 2. 소스 파일 구조 +``` +wace_plm/ +├── src/com/pms/ +│ ├── api/ +│ │ ├── CustomerApiClient.java # 거래처 API 클라이언트 +│ │ ├── DepartmentApiClient.java # 부서 API 클라이언트 +│ │ └── EmployeeApiClient.java # 사원 API 클라이언트 +│ ├── service/ +│ │ └── BatchService.java # 배치 서비스 (동기화 로직) +│ └── mapper/ +│ └── batch.xml # MyBatis 매퍼 (SQL 쿼리) +└── database/ + └── create_erp_sync_tables.sql # 테이블 생성 스크립트 +``` + +### 3. MyBatis 설정 +`mybatisConf.xml`에 batch 매퍼가 등록되어 있는지 확인: +```xml + +``` + +### 4. API 인증 정보 +API 클라이언트에 다음 정보가 설정되어 있습니다: +- **Base URL**: `https://erp.rps-korea.com` +- **회사코드**: `1000` +- **Caller Name**: `API_gcmsAmaranth40578` +- **Access Token**: (소스 코드 참조) +- **Hash Key**: (소스 코드 참조) + +## 동작 방식 + +### 1. API 호출 +각 API 클라이언트가 ERP 시스템에 HTTPS POST 요청을 전송합니다. +- TLS 1.2 프로토콜 사용 +- HMacSHA256 기반 인증 +- JSON 형식 데이터 송수신 + +### 2. 데이터 파싱 +API 응답 JSON을 파싱하여 Map 객체로 변환합니다. +- JDK 1.7 호환 방식의 수동 JSON 파싱 +- 필요한 필드만 추출 + +### 3. 데이터베이스 저장 +- 기존 데이터 존재 여부 확인 (코드 기준) +- 신규 데이터: INSERT +- 기존 데이터: UPDATE +- 트랜잭션 처리로 데이터 무결성 보장 + +### 4. 로깅 +- 각 단계별 진행 상황 콘솔 출력 +- 성공/실패 건수 집계 +- 오류 발생 시 상세 로그 출력 + +## 에러 처리 + +### API 호출 실패 +- 연결 타임아웃: 30초 +- 리다이렉트 자동 처리 +- HTTP 에러 코드 확인 및 로깅 + +### 데이터 파싱 오류 +- JSON 파싱 실패 시 해당 레코드 스킵 +- 오류 로그 출력 후 다음 레코드 처리 계속 + +### 데이터베이스 오류 +- 트랜잭션 롤백 +- 전체 배치 실패 처리 +- 상세 오류 메시지 출력 + +## 모니터링 + +### 로그 확인 +배치 실행 시 다음 정보가 출력됩니다: +``` +==================================== +ERP 데이터 동기화 배치 시작 +==================================== +거래처 정보 동기화 시작... +거래처 정보 동기화 완료 - 신규: X건, 수정: Y건 +부서 정보 동기화 시작... +부서 정보 동기화 완료 - 신규: X건, 수정: Y건 +사원 정보 동기화 시작... +사원 정보 동기화 완료 - 신규: X건, 수정: Y건 +ERP 데이터 동기화 배치 완료 +``` + +### 데이터 확인 +```sql +-- 거래처 정보 확인 +SELECT COUNT(*) FROM client_mng WHERE reg_user = 'batch_system'; + +-- 부서 정보 확인 +SELECT COUNT(*) FROM dept_info WHERE reg_user = 'batch_system'; + +-- 사원 정보 확인 +SELECT COUNT(*) FROM user_info WHERE reg_user = 'batch_system'; + +-- 최근 동기화 데이터 확인 +SELECT * FROM client_mng WHERE reg_user = 'batch_system' +ORDER BY reg_date DESC LIMIT 10; +``` + +## 주의사항 + +1. **데이터 중복 방지** + - 거래처: `tr_cd` (거래처코드) 기준 + - 부서: `dept_cd` (부서코드) 기준 + - 사원: `emp_cd` (사원코드) 기준 + +2. **네트워크 환경** + - ERP API 서버 접근 가능 여부 확인 + - 방화벽 설정 확인 + - SSL 인증서 검증 우회 (개발 환경) + +3. **성능 고려사항** + - 대량 데이터 처리 시 시간 소요 가능 + - 배치 실행 시간대 조정 가능 (현재: 새벽 00시) + - 필요시 페이징 처리 추가 고려 + +4. **데이터 정합성** + - 배치 실행 전 데이터 백업 권장 + - 테스트 환경에서 충분한 검증 후 운영 적용 + +## 트러블슈팅 + +### API 호출 실패 +``` +원인: 네트워크 연결 문제, 인증 실패 +해결: +- 네트워크 연결 확인 +- API 인증 정보 확인 +- ERP 서버 상태 확인 +``` + +### JSON 파싱 오류 +``` +원인: API 응답 형식 변경 +해결: +- API 응답 로그 확인 +- 파싱 로직 수정 +``` + +### 데이터베이스 오류 +``` +원인: 테이블 구조 불일치, 권한 문제 +해결: +- 테이블 생성 스크립트 재실행 +- 데이터베이스 권한 확인 +``` + +## 향후 개선 사항 + +1. **성능 최적화** + - 배치 INSERT/UPDATE 처리 + - 페이징 처리 추가 + +2. **모니터링 강화** + - 배치 실행 이력 테이블 추가 + - 알림 기능 추가 (이메일, 슬랙 등) + +3. **에러 복구** + - 실패 레코드 재처리 로직 + - 부분 실패 시 이어서 처리 + +4. **설정 외부화** + - API URL, 인증 정보를 설정 파일로 분리 + - 환경별 설정 관리 + +## 문의 + +기술 지원이 필요한 경우 개발팀에 문의하시기 바랍니다. diff --git a/src/com/pms/api/CustomerApiClient.java b/src/com/pms/api/CustomerApiClient.java new file mode 100644 index 0000000..8624990 --- /dev/null +++ b/src/com/pms/api/CustomerApiClient.java @@ -0,0 +1,255 @@ +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 CustomerApiClient { + + private static final String API_URL = "~/apiproxy/api16S11"; + 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자리, 필수) + * @return API 응답 결과 (JSON 문자열) + * @throws Exception API 호출 중 발생하는 예외 + */ + public String getCustomerList(String baseUrl, String coCd) 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("~", ""); + 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"); + + // 인증 헤더 설정 + connection.setRequestProperty("callerName", CALLER_NAME); + connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN); + + String transactionId = generateTransactionId(); + connection.setRequestProperty("transaction-id", transactionId); + + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + connection.setRequestProperty("timestamp", timestamp); + + connection.setRequestProperty("groupSeq", GROUP_SEQ); + + String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath); + connection.setRequestProperty("wehago-sign", wehagoSign); + + // 요청 본문 작성 + String requestBody = buildRequestBody(coCd); + + // 요청 전송 설정 + 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(); + + // 리다이렉트 처리 + if (responseCode == 301 || responseCode == 302 || responseCode == 303 || + responseCode == 307 || responseCode == 308) { + String location = connection.getHeaderField("Location"); + connection.disconnect(); + + if (location != null) { + 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 { + 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을 생성합니다. + */ + private String buildRequestBody(String coCd) { + StringBuilder json = new StringBuilder(); + json.append("{"); + json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\""); + json.append("}"); + return json.toString(); + } + + /** + * JSON 문자열 이스케이프 처리 + */ + 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 생성 + */ + 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 생성 + */ + private String generateWehagoSign(String accessToken, String transactionId, + String timestamp, String urlPath) throws Exception { + try { + String value = accessToken + transactionId + timestamp + urlPath; + + 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)); + + String base64Binary = Base64.encodeBase64String(encrypted); + return base64Binary; + + } catch (Exception e) { + System.err.println("Wehago-sign 생성 오류: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + } +} diff --git a/src/com/pms/api/DepartmentApiClient.java b/src/com/pms/api/DepartmentApiClient.java new file mode 100644 index 0000000..d5e96e5 --- /dev/null +++ b/src/com/pms/api/DepartmentApiClient.java @@ -0,0 +1,261 @@ +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 DepartmentApiClient { + + private static final String API_URL = "~/apiproxy/api16S10"; + 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 searchText 검색명 (부서코드 또는 부서명, 선택사항, 미입력 시 전체 조회) + * @return API 응답 결과 (JSON 문자열) + * @throws Exception API 호출 중 발생하는 예외 + */ + public String getDepartmentList(String baseUrl, String coCd, String searchText) 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("~", ""); + 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"); + + // 인증 헤더 설정 + connection.setRequestProperty("callerName", CALLER_NAME); + connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN); + + String transactionId = generateTransactionId(); + connection.setRequestProperty("transaction-id", transactionId); + + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + connection.setRequestProperty("timestamp", timestamp); + + connection.setRequestProperty("groupSeq", GROUP_SEQ); + + String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath); + connection.setRequestProperty("wehago-sign", wehagoSign); + + // 요청 본문 작성 + String requestBody = buildRequestBody(coCd, searchText); + + // 요청 전송 설정 + 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(); + + // 리다이렉트 처리 + if (responseCode == 301 || responseCode == 302 || responseCode == 303 || + responseCode == 307 || responseCode == 308) { + String location = connection.getHeaderField("Location"); + connection.disconnect(); + + if (location != null) { + 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 { + 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을 생성합니다. + */ + private String buildRequestBody(String coCd, String searchText) { + StringBuilder json = new StringBuilder(); + json.append("{"); + json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\""); + + if (searchText != null && !searchText.trim().isEmpty()) { + json.append(",\"searchText\":\"").append(escapeJson(searchText)).append("\""); + } + + json.append("}"); + return json.toString(); + } + + /** + * JSON 문자열 이스케이프 처리 + */ + 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 생성 + */ + 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 생성 + */ + private String generateWehagoSign(String accessToken, String transactionId, + String timestamp, String urlPath) throws Exception { + try { + String value = accessToken + transactionId + timestamp + urlPath; + + 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)); + + String base64Binary = Base64.encodeBase64String(encrypted); + return base64Binary; + + } catch (Exception e) { + System.err.println("Wehago-sign 생성 오류: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + } +} diff --git a/src/com/pms/api/EmployeeApiClient.java b/src/com/pms/api/EmployeeApiClient.java new file mode 100644 index 0000000..cceaaaf --- /dev/null +++ b/src/com/pms/api/EmployeeApiClient.java @@ -0,0 +1,268 @@ +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 EmployeeApiClient { + + private static final String API_URL = "~/apiproxy/api16S05"; + 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자리, 필수) + * @return API 응답 결과 (JSON 문자열) + * @throws Exception API 호출 중 발생하는 예외 + */ + public String getEmployeeList(String baseUrl, String coCd) 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("~", ""); + 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"); + + // 인증 헤더 설정 + connection.setRequestProperty("callerName", CALLER_NAME); + connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN); + + String transactionId = generateTransactionId(); + connection.setRequestProperty("transaction-id", transactionId); + + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + connection.setRequestProperty("timestamp", timestamp); + + connection.setRequestProperty("groupSeq", GROUP_SEQ); + + String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath); + connection.setRequestProperty("wehago-sign", wehagoSign); + + // 요청 본문 작성 + String requestBody = buildRequestBody(coCd); + + // 요청 전송 설정 + 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(); + + // 리다이렉트 처리 + if (responseCode == 301 || responseCode == 302 || responseCode == 303 || + responseCode == 307 || responseCode == 308) { + String location = connection.getHeaderField("Location"); + connection.disconnect(); + + if (location != null) { + 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 { + 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을 생성합니다. + */ + private String buildRequestBody(String coCd) { + StringBuilder json = new StringBuilder(); + json.append("{"); + + // header 섹션 + json.append("\"header\":{"); + json.append("\"groupSeq\":\"").append(escapeJson(GROUP_SEQ)).append("\""); + json.append(",\"empSeq\":\"\""); + json.append(",\"tId\":\"\""); + json.append(",\"pId\":\"\""); + json.append("}"); + + // body 섹션 + json.append(",\"body\":{"); + json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\""); + json.append("}"); + + json.append("}"); + return json.toString(); + } + + /** + * JSON 문자열 이스케이프 처리 + */ + 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 생성 + */ + 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 생성 + */ + private String generateWehagoSign(String accessToken, String transactionId, + String timestamp, String urlPath) throws Exception { + try { + String value = accessToken + transactionId + timestamp + urlPath; + + 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)); + + String base64Binary = Base64.encodeBase64String(encrypted); + return base64Binary; + + } catch (Exception e) { + System.err.println("Wehago-sign 생성 오류: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + } +} diff --git a/src/com/pms/controller/AdminController.java b/src/com/pms/controller/AdminController.java index 7356066..b0f933c 100644 --- a/src/com/pms/controller/AdminController.java +++ b/src/com/pms/controller/AdminController.java @@ -27,6 +27,7 @@ import com.pms.common.utils.Constants; import com.pms.common.SqlMapConfig; import com.pms.service.AdminService; import com.pms.service.CommonService; +import com.pms.service.BatchService; import org.apache.ibatis.session.SqlSession; @@ -39,6 +40,9 @@ public class AdminController extends BaseService { @Autowired CommonService commonService; + @Autowired + BatchService batchService; + @RequestMapping("/admin/adminMainFS.do") public String adminMainFS(HttpServletRequest request, @RequestParam Map paramMap){ //관리자 메뉴 호출 전 관리자 권한 여부 확인 @@ -5196,4 +5200,85 @@ public String clientImportFileProc(HttpServletRequest request, HttpSession sessi return resultMap; } + + /** + * ERP 사원 정보 동기화 수동 실행 + * @param request + * @param paramMap + * @return + */ + @RequestMapping("/admin/syncEmployeeDataManual.do") + @ResponseBody + public Map syncEmployeeDataManual(HttpServletRequest request, @RequestParam Map paramMap) { + Map resultMap = new HashMap(); + + try { + System.out.println("===================================="); + System.out.println("관리자 수동 ERP 사원 동기화 실행 요청"); + System.out.println("===================================="); + + resultMap = batchService.syncEmployeeDataManual(); + + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("success", false); + resultMap.put("message", "사원 동기화 실행 중 오류가 발생했습니다: " + e.getMessage()); + } + + return resultMap; + } + + /** + * ERP 부서 정보 동기화 수동 실행 + * @param request + * @param paramMap + * @return + */ + @RequestMapping("/admin/syncDepartmentDataManual.do") + @ResponseBody + public Map syncDepartmentDataManual(HttpServletRequest request, @RequestParam Map paramMap) { + Map resultMap = new HashMap(); + + try { + System.out.println("===================================="); + System.out.println("관리자 수동 ERP 부서 동기화 실행 요청"); + System.out.println("===================================="); + + resultMap = batchService.syncDepartmentDataManual(); + + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("success", false); + resultMap.put("message", "부서 동기화 실행 중 오류가 발생했습니다: " + e.getMessage()); + } + + return resultMap; + } + + /** + * ERP 거래처 정보 동기화 수동 실행 + * @param request + * @param paramMap + * @return + */ + @RequestMapping("/admin/syncCustomerDataManual.do") + @ResponseBody + public Map syncCustomerDataManual(HttpServletRequest request, @RequestParam Map paramMap) { + Map resultMap = new HashMap(); + + try { + System.out.println("===================================="); + System.out.println("관리자 수동 ERP 거래처 동기화 실행 요청"); + System.out.println("===================================="); + + resultMap = batchService.syncCustomerDataManual(); + + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("success", false); + resultMap.put("message", "거래처 동기화 실행 중 오류가 발생했습니다: " + e.getMessage()); + } + + return resultMap; + } } diff --git a/src/com/pms/mapper/batch.xml b/src/com/pms/mapper/batch.xml new file mode 100644 index 0000000..d99f6ee --- /dev/null +++ b/src/com/pms/mapper/batch.xml @@ -0,0 +1,178 @@ + + + + + + + + INSERT INTO client_mng ( + objid, client_cd, client_nm, tr_nmk, client_nmk, attr_nmk, client_type, + bus_reg_no, resident_no, ceo_nm, ceo_nmk, bus_type, bus_item, + post_no, addr1, addr2, tel_no, fax_no, homepage, email, + liq_rs, tr_fg, country_nm, class_cd, class_nm, grade_cd, grade_nm, + collect_client_cd, collect_client_nm, region_cd, region_nm, + trade_start_dt, trade_end_dt, use_yn, contract_start_dt, contract_end_dt, + trade_type, discount_rate, contract_amt, monthly_fee, payment_term, rcp_tp, + credit_limit, limit_return_day, + pur_bank_cd, pur_bank_nm, pur_branch_nm, pur_account_no, pur_account_holder, + pur_pay_plan, pur_slip_type, pur_tax_type, + sale_bank_cd, sale_bank_nm, sale_branch_nm, sale_account_no, + sale_collect_plan, sale_slip_type, sale_tax_type, + vendor_dept_nm, vendor_position, vendor_duty, vendor_manager_nm, + vendor_tel, vendor_ext, vendor_mobile, vendor_email, + mgr_dept_cd, mgr_dept_nm, mgr_position, mgr_duty, mgr_emp_cd, mgr_emp_nm, + mgr_tel, mgr_ext, mgr_mobile, mgr_email, mgr_remark, + rec_remark, rec_post_no, rec_addr1, rec_addr2, rec_tel, rec_fax, + project_cd, project_nm, pjt_nmk, ext_data_cd, e_tax_yn, + unit_report_client, sub_bus_no, procurement_yn, use_fg, for_yn, + plan_day_type, plan_day, purpose_type, + insert_id, insert_dt, modify_id, modify_dt + ) VALUES ( + #{objid}::numeric, #{client_cd}, #{client_nm}, #{tr_nmk}, #{client_nmk}, #{attr_nmk}, #{client_type}, + #{bus_reg_no}, #{resident_no}, #{ceo_nm}, #{ceo_nmk}, #{bus_type}, #{bus_item}, + #{post_no}, #{addr1}, #{addr2}, #{tel_no}, #{fax_no}, #{homepage}, #{email}, + #{liq_rs}, #{tr_fg}, #{country_nm}, #{class_cd}, #{class_nm}, #{grade_cd}, #{grade_nm}, + #{collect_client_cd}, #{collect_client_nm}, #{region_cd}, #{region_nm}, + #{trade_start_dt}, #{trade_end_dt}, #{use_yn}, #{contract_start_dt}, #{contract_end_dt}, + #{trade_type}, #{discount_rate}, #{contract_amt}, #{monthly_fee}, #{payment_term}, #{rcp_tp}, + #{credit_limit}, #{limit_return_day}, + #{pur_bank_cd}, #{pur_bank_nm}, #{pur_branch_nm}, #{pur_account_no}, #{pur_account_holder}, + #{pur_pay_plan}, #{pur_slip_type}, #{pur_tax_type}, + #{sale_bank_cd}, #{sale_bank_nm}, #{sale_branch_nm}, #{sale_account_no}, + #{sale_collect_plan}, #{sale_slip_type}, #{sale_tax_type}, + #{vendor_dept_nm}, #{vendor_position}, #{vendor_duty}, #{vendor_manager_nm}, + #{vendor_tel}, #{vendor_ext}, #{vendor_mobile}, #{vendor_email}, + #{mgr_dept_cd}, #{mgr_dept_nm}, #{mgr_position}, #{mgr_duty}, #{mgr_emp_cd}, #{mgr_emp_nm}, + #{mgr_tel}, #{mgr_ext}, #{mgr_mobile}, #{mgr_email}, #{mgr_remark}, + #{rec_remark}, #{rec_post_no}, #{rec_addr1}, #{rec_addr2}, #{rec_tel}, #{rec_fax}, + #{project_cd}, #{project_nm}, #{pjt_nmk}, #{ext_data_cd}, #{e_tax_yn}, + #{unit_report_client}, #{sub_bus_no}, #{procurement_yn}, #{use_fg}, #{for_yn}, + #{plan_day_type}, #{plan_day}, #{purpose_type}, + #{insert_id}, now() + ) ON CONFLICT (client_cd) DO + UPDATE SET + client_nm = #{client_nm}, tr_nmk = #{tr_nmk}, client_nmk = #{client_nmk}, attr_nmk = #{attr_nmk}, + client_type = #{client_type}, bus_reg_no = #{bus_reg_no}, resident_no = #{resident_no}, + ceo_nm = #{ceo_nm}, ceo_nmk = #{ceo_nmk}, bus_type = #{bus_type}, bus_item = #{bus_item}, + post_no = #{post_no}, addr1 = #{addr1}, addr2 = #{addr2}, tel_no = #{tel_no}, fax_no = #{fax_no}, + homepage = #{homepage}, email = #{email}, liq_rs = #{liq_rs}, tr_fg = #{tr_fg}, + country_nm = #{country_nm}, class_cd = #{class_cd}, class_nm = #{class_nm}, + grade_cd = #{grade_cd}, grade_nm = #{grade_nm}, collect_client_cd = #{collect_client_cd}, + collect_client_nm = #{collect_client_nm}, region_cd = #{region_cd}, region_nm = #{region_nm}, + trade_start_dt = #{trade_start_dt}, trade_end_dt = #{trade_end_dt}, use_yn = #{use_yn}, + contract_start_dt = #{contract_start_dt}, contract_end_dt = #{contract_end_dt}, + trade_type = #{trade_type}, discount_rate = #{discount_rate}, contract_amt = #{contract_amt}, + monthly_fee = #{monthly_fee}, payment_term = #{payment_term}, rcp_tp = #{rcp_tp}, + credit_limit = #{credit_limit}, limit_return_day = #{limit_return_day}, + pur_bank_cd = #{pur_bank_cd}, pur_bank_nm = #{pur_bank_nm}, pur_branch_nm = #{pur_branch_nm}, + pur_account_no = #{pur_account_no}, pur_account_holder = #{pur_account_holder}, + pur_pay_plan = #{pur_pay_plan}, pur_slip_type = #{pur_slip_type}, pur_tax_type = #{pur_tax_type}, + sale_bank_cd = #{sale_bank_cd}, sale_bank_nm = #{sale_bank_nm}, sale_branch_nm = #{sale_branch_nm}, + sale_account_no = #{sale_account_no}, sale_collect_plan = #{sale_collect_plan}, + sale_slip_type = #{sale_slip_type}, sale_tax_type = #{sale_tax_type}, + vendor_dept_nm = #{vendor_dept_nm}, vendor_position = #{vendor_position}, vendor_duty = #{vendor_duty}, + vendor_manager_nm = #{vendor_manager_nm}, vendor_tel = #{vendor_tel}, vendor_ext = #{vendor_ext}, + vendor_mobile = #{vendor_mobile}, vendor_email = #{vendor_email}, + mgr_dept_cd = #{mgr_dept_cd}, mgr_dept_nm = #{mgr_dept_nm}, mgr_position = #{mgr_position}, + mgr_duty = #{mgr_duty}, mgr_emp_cd = #{mgr_emp_cd}, mgr_emp_nm = #{mgr_emp_nm}, + mgr_tel = #{mgr_tel}, mgr_ext = #{mgr_ext}, mgr_mobile = #{mgr_mobile}, + mgr_email = #{mgr_email}, mgr_remark = #{mgr_remark}, + rec_remark = #{rec_remark}, rec_post_no = #{rec_post_no}, rec_addr1 = #{rec_addr1}, + rec_addr2 = #{rec_addr2}, rec_tel = #{rec_tel}, rec_fax = #{rec_fax}, + project_cd = #{project_cd}, project_nm = #{project_nm}, pjt_nmk = #{pjt_nmk}, + ext_data_cd = #{ext_data_cd}, e_tax_yn = #{e_tax_yn}, unit_report_client = #{unit_report_client}, + sub_bus_no = #{sub_bus_no}, procurement_yn = #{procurement_yn}, use_fg = #{use_fg}, + for_yn = #{for_yn}, plan_day_type = #{plan_day_type}, plan_day = #{plan_day}, + purpose_type = #{purpose_type}, modify_id = #{modify_id}, modify_dt = now() + + + + + INSERT INTO dept_info ( + dept_code, + parent_dept_code, + dept_name, + status, + data_type, + regdate + ) VALUES ( + #{dept_code}, + #{parent_dept_code}, + #{dept_name}, + #{status}, + #{data_type}, + NOW() + ) ON CONFLICT (dept_code) DO + UPDATE SET + parent_dept_code = #{parent_dept_code}, + dept_name = #{dept_name}, + status = #{status}, + data_type = #{data_type} + + + + + INSERT INTO user_info ( + sabun, + user_id, + user_password, + user_name, + user_name_eng, + dept_code, + dept_name, + position_code, + position_name, + rank, + email, + cell_phone, + user_type, + user_type_name, + status, + end_date, + data_type, + regdate + ) VALUES ( + #{sabun}, + #{user_id}, + #{user_password}, + #{user_name}, + #{user_name_eng}, + #{dept_code}, + #{dept_name}, + #{position_code}, + #{position_name}, + #{rank}, + #{email}, + #{cell_phone}, + #{user_type}, + #{user_type_name}, + #{status}, + CASE + WHEN #{end_date} IS NULL OR #{end_date} = '' THEN NULL + ELSE TO_TIMESTAMP(#{end_date}, 'YYYYMMDD') + END, + #{data_type}, + NOW() + ) ON CONFLICT (user_id) DO + UPDATE SET + sabun = #{sabun}, + user_name = #{user_name}, + user_name_eng = #{user_name_eng}, + dept_code = #{dept_code}, + dept_name = #{dept_name}, + position_code = #{position_code}, + position_name = #{position_name}, + rank = #{rank}, + email = #{email}, + cell_phone = #{cell_phone}, + user_type = #{user_type}, + user_type_name = #{user_type_name}, + status = #{status}, + end_date = CASE + WHEN #{end_date} IS NULL OR #{end_date} = '' THEN NULL + ELSE TO_TIMESTAMP(#{end_date}, 'YYYYMMDD') + END, + data_type = #{data_type} + + + diff --git a/src/com/pms/mapper/mybatisConf.xml b/src/com/pms/mapper/mybatisConf.xml index 66dfcab..d717bfb 100644 --- a/src/com/pms/mapper/mybatisConf.xml +++ b/src/com/pms/mapper/mybatisConf.xml @@ -89,10 +89,11 @@ - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/com/pms/service/BatchService.java b/src/com/pms/service/BatchService.java index 01f8a92..df71139 100644 --- a/src/com/pms/service/BatchService.java +++ b/src/com/pms/service/BatchService.java @@ -17,6 +17,9 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestParam; +import com.pms.api.CustomerApiClient; +import com.pms.api.DepartmentApiClient; +import com.pms.api.EmployeeApiClient; import com.pms.common.Message; import com.pms.common.SqlMapConfig; import com.pms.common.bean.PersonBean; @@ -35,6 +38,8 @@ public class BatchService extends BaseService { * @throws Exception */ + // 파트 도면 파일 자동 연결 배치 - 주석 처리 + /* static List targetFileList = new ArrayList(); @Scheduled(cron="0 59 23 * * ?") @@ -160,9 +165,657 @@ public class BatchService extends BaseService { } return targetFileList; } + */ + /** + * ERP API 데이터 동기화 배치 (매일 새벽 00시 실행) + * 거래처, 부서, 사원 정보를 ERP API로부터 가져와 DB에 저장 + */ + @Scheduled(cron="0 0 0 * * ?") + public void syncErpData() { + System.out.println("===================================="); + System.out.println("ERP 데이터 동기화 배치 시작 (자동)"); + System.out.println("===================================="); + + executeSyncErpData(); + } + /** + * 사원 정보만 동기화 (수동 실행) + * @return 성공 여부 Map + */ + public Map syncEmployeeDataManual() { + Map result = new HashMap(); + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + System.out.println("===================================="); + System.out.println("ERP 사원 정보 동기화 시작 (수동)"); + System.out.println("===================================="); + + try { + String baseUrl = "https://erp.rps-korea.com"; + String coCd = "1000"; + + syncEmployeeData(sqlSession, baseUrl, coCd); + + sqlSession.commit(); + result.put("success", true); + result.put("message", "사원 정보 동기화가 완료되었습니다."); + System.out.println("ERP 사원 정보 동기화 완료"); + } catch (Exception e) { + sqlSession.rollback(); + result.put("success", false); + result.put("message", "사원 정보 동기화 중 오류가 발생했습니다: " + e.getMessage()); + System.err.println("사원 정보 동기화 오류: " + e.getMessage()); + e.printStackTrace(); + } finally { + sqlSession.close(); + } + + return result; + } + /** + * 부서 정보만 동기화 (수동 실행) + * @return 성공 여부 Map + */ + public Map syncDepartmentDataManual() { + Map result = new HashMap(); + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + System.out.println("===================================="); + System.out.println("ERP 부서 정보 동기화 시작 (수동)"); + System.out.println("===================================="); + + try { + String baseUrl = "https://erp.rps-korea.com"; + String coCd = "1000"; + + syncDepartmentData(sqlSession, baseUrl, coCd); + + sqlSession.commit(); + result.put("success", true); + result.put("message", "부서 정보 동기화가 완료되었습니다."); + System.out.println("ERP 부서 정보 동기화 완료"); + } catch (Exception e) { + sqlSession.rollback(); + result.put("success", false); + result.put("message", "부서 정보 동기화 중 오류가 발생했습니다: " + e.getMessage()); + System.err.println("부서 정보 동기화 오류: " + e.getMessage()); + e.printStackTrace(); + } finally { + sqlSession.close(); + } + + return result; + } + + /** + * 거래처 정보만 동기화 (수동 실행) + * @return 성공 여부 Map + */ + public Map syncCustomerDataManual() { + Map result = new HashMap(); + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + System.out.println("===================================="); + System.out.println("ERP 거래처 정보 동기화 시작 (수동)"); + System.out.println("===================================="); + + try { + String baseUrl = "https://erp.rps-korea.com"; + String coCd = "1000"; + + syncCustomerData(sqlSession, baseUrl, coCd); + + sqlSession.commit(); + result.put("success", true); + result.put("message", "거래처 정보 동기화가 완료되었습니다."); + System.out.println("ERP 거래처 정보 동기화 완료"); + } catch (Exception e) { + sqlSession.rollback(); + result.put("success", false); + result.put("message", "거래처 정보 동기화 중 오류가 발생했습니다: " + e.getMessage()); + System.err.println("거래처 정보 동기화 오류: " + e.getMessage()); + e.printStackTrace(); + } finally { + sqlSession.close(); + } + + return result; + } + + /** + * ERP 데이터 동기화 실제 실행 로직 + */ + private void executeSyncErpData() { + SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false); + + try { + // API 기본 URL 및 회사코드 설정 + String baseUrl = "https://erp.rps-korea.com"; + String coCd = "1000"; + + // 1. 거래처 정보 동기화 + syncCustomerData(sqlSession, baseUrl, coCd); + + // 2. 부서 정보 동기화 + syncDepartmentData(sqlSession, baseUrl, coCd); + + // 3. 사원 정보 동기화 + syncEmployeeData(sqlSession, baseUrl, coCd); + + sqlSession.commit(); + System.out.println("ERP 데이터 동기화 배치 완료"); + + } catch (Exception e) { + sqlSession.rollback(); + System.err.println("ERP 데이터 동기화 중 오류 발생: " + e.getMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + sqlSession.close(); + } + } + + /** + * 거래처 정보 동기화 + */ + private void syncCustomerData(SqlSession sqlSession, String baseUrl, String coCd) { + try { + System.out.println("거래처 정보 동기화 시작..."); + + CustomerApiClient customerClient = new CustomerApiClient(); + String jsonResponse = customerClient.getCustomerList(baseUrl, coCd); + + // JSON 파싱 및 DB 저장 + List> customerList = parseCustomerJson(jsonResponse); + + int processCount = 0; + + // 처음 10건 로그 출력 + System.out.println("===================================="); + System.out.println("거래처 데이터 샘플 (최대 10건)"); + System.out.println("===================================="); + + for (Map customer : customerList) { + // 처음 10건만 로그 출력 + if (processCount < 10) { + System.out.println("[거래처 " + (processCount + 1) + "]"); + System.out.println(" - CLIENT_CD: " + customer.get("client_cd")); + System.out.println(" - CLIENT_NM: " + customer.get("client_nm")); + System.out.println(" - CEO_NM: " + customer.get("ceo_nm")); + System.out.println(" - TEL_NO: " + customer.get("tel_no")); + System.out.println(" - EMAIL: " + customer.get("email")); + System.out.println("---"); + } + + // UPSERT 실행 + sqlSession.insert("batch.upsertCustomer", customer); + processCount++; + } + + System.out.println("===================================="); + System.out.println("거래처 정보 동기화 완료 - 처리: " + processCount + "건"); + + } catch (Exception e) { + System.err.println("거래처 정보 동기화 실패: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + /** + * 부서 정보 동기화 + */ + private void syncDepartmentData(SqlSession sqlSession, String baseUrl, String coCd) { + try { + System.out.println("부서 정보 동기화 시작..."); + + DepartmentApiClient deptClient = new DepartmentApiClient(); + String jsonResponse = deptClient.getDepartmentList(baseUrl, coCd, null); + + // JSON 파싱 및 DB 저장 + List> deptList = parseDepartmentJson(jsonResponse); + + int processCount = 0; + + // 처음 10건 로그 출력 + System.out.println("===================================="); + System.out.println("부서 데이터 샘플 (최대 10건)"); + System.out.println("===================================="); + + for (Map dept : deptList) { + // 처음 10건만 로그 출력 + if (processCount < 10) { + System.out.println("[부서 " + (processCount + 1) + "]"); + System.out.println(" - DEPT_CODE: " + dept.get("dept_code")); + System.out.println(" - DEPT_NAME: " + dept.get("dept_name")); + System.out.println(" - PARENT_DEPT_CODE: " + dept.get("parent_dept_code")); + System.out.println(" - STATUS: " + dept.get("status")); + System.out.println("---"); + } + + // UPSERT 실행 + sqlSession.insert("batch.upsertDepartment", dept); + processCount++; + } + + System.out.println("===================================="); + System.out.println("부서 정보 동기화 완료 - 처리: " + processCount + "건"); + + } catch (Exception e) { + System.err.println("부서 정보 동기화 실패: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + /** + * 사원 정보 동기화 + */ + private void syncEmployeeData(SqlSession sqlSession, String baseUrl, String coCd) { + try { + System.out.println("사원 정보 동기화 시작..."); + + EmployeeApiClient empClient = new EmployeeApiClient(); + String jsonResponse = empClient.getEmployeeList(baseUrl, coCd); + + // JSON 파싱 및 DB 저장 + List> empList = parseEmployeeJson(jsonResponse); + + int processCount = 0; + + // 처음 10건 로그 출력 + System.out.println("===================================="); + System.out.println("사원 데이터 샘플 (최대 10건)"); + System.out.println("===================================="); + + for (Map emp : empList) { + // 처음 10건만 로그 출력 + if (processCount < 10) { + System.out.println("[사원 " + (processCount + 1) + "]"); + System.out.println(" - USER_ID: " + emp.get("user_id")); + System.out.println(" - USER_NAME: " + emp.get("user_name")); + System.out.println(" - USER_NAME_ENG: " + emp.get("user_name_eng")); + System.out.println(" - DEPT_CODE: " + emp.get("dept_code")); + System.out.println(" - DEPT_NAME: " + emp.get("dept_name")); + System.out.println(" - EMAIL: " + emp.get("email")); + System.out.println(" - CELL_PHONE: " + emp.get("cell_phone")); + System.out.println(" - STATUS: " + emp.get("status")); + System.out.println("---"); + } + + // UPSERT 실행 + sqlSession.insert("batch.upsertEmployee", emp); + processCount++; + } + + System.out.println("===================================="); + System.out.println("사원 정보 동기화 완료 - 처리: " + processCount + "건"); + + } catch (Exception e) { + System.err.println("사원 정보 동기화 실패: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + /** + * 거래처 JSON 파싱 + */ + private List> parseCustomerJson(String jsonResponse) { + List> customerList = new ArrayList>(); + + try { + // resultData 배열 찾기 + int dataStart = jsonResponse.indexOf("\"resultData\":["); + if (dataStart == -1) { + return customerList; + } + + String dataSection = jsonResponse.substring(dataStart + 14); + int bracketCount = 0; + int startIdx = -1; + + 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 customerJson = dataSection.substring(startIdx, i + 1); + Map customer = parseCustomerObject(customerJson); + if (customer != null) { + customerList.add(customer); + } + startIdx = -1; + } + } + } + } catch (Exception e) { + System.err.println("거래처 JSON 파싱 오류: " + e.getMessage()); + } + + return customerList; + } + + /** + * 거래처 객체 파싱 (실제 테이블 컬럼에 맞춤) + */ + private Map parseCustomerObject(String json) { + Map customer = new HashMap(); + + // objid는 자동생성 + customer.put("objid", CommonUtils.createObjId()); + + // 기본 정보 + customer.put("client_cd", extractJsonValue(json, "trCd")); // 거래처코드 + customer.put("client_nm", extractJsonValue(json, "trNm")); // 거래처명 + customer.put("tr_nmk", extractJsonValue(json, "trNm")); // 거래처명(한글) + customer.put("client_nmk", extractJsonValue(json, "trNm")); // 거래처명(한글) + customer.put("attr_nmk", extractJsonValue(json, "attrNm")); // 거래처약칭 + customer.put("client_type", extractJsonValue(json, "trFg")); // 거래처분류 + customer.put("bus_reg_no", extractJsonValue(json, "regNb")); // 사업자등록번호 + customer.put("resident_no", extractJsonValue(json, "pplNb")); // 주민번호 + customer.put("ceo_nm", extractJsonValue(json, "ceoNm")); // 대표자명 + customer.put("ceo_nmk", extractJsonValue(json, "ceoNm")); // 대표자명(한글) + customer.put("bus_type", extractJsonValue(json, "business")); // 업태 + customer.put("bus_item", extractJsonValue(json, "jongmok")); // 종목 + customer.put("post_no", extractJsonValue(json, "zip")); // 우편번호 + customer.put("addr1", extractJsonValue(json, "divAddr1")); // 주소1 + customer.put("addr2", extractJsonValue(json, "addr2")); // 주소2 + customer.put("tel_no", extractJsonValue(json, "tel")); // 전화번호 + customer.put("fax_no", extractJsonValue(json, "fax")); // 팩스번호 + customer.put("homepage", extractJsonValue(json, "homepage")); // 홈페이지 + customer.put("email", extractJsonValue(json, "email")); // 이메일 + customer.put("liq_rs", extractJsonValue(json, "liqRs")); // 주류코드 + customer.put("tr_fg", extractJsonValue(json, "trFg")); // 거래처분류 + customer.put("country_nm", extractJsonValue(json, "nationNm")); // 국가명 + customer.put("class_cd", extractJsonValue(json, "trgrpCd")); // 거래처분류코드 + customer.put("class_nm", extractJsonValue(json, "trgrpNm")); // 거래처분류명 + customer.put("grade_cd", extractJsonValue(json, "trratFg")); // 거래처등급코드 + customer.put("grade_nm", extractJsonValue(json, "trratNm")); // 거래처등급명 + customer.put("collect_client_cd", extractJsonValue(json, "clttrCd")); // 수금거래처코드 + customer.put("collect_client_nm", extractJsonValue(json, "clttrNm")); // 수금거래처명 + customer.put("region_cd", extractJsonValue(json, "localCd")); // 지역코드 + customer.put("region_nm", extractJsonValue(json, "localNm")); // 지역명 + customer.put("trade_start_dt", extractJsonValue(json, "frDt")); // 거래시작일 + customer.put("trade_end_dt", extractJsonValue(json, "toDt")); // 거래종료일 + customer.put("use_yn", extractJsonValue(json, "useYn")); // 사용여부 + customer.put("contract_start_dt", extractJsonValue(json, "interDt")); // 계약시작일 + customer.put("contract_end_dt", extractJsonValue(json, "dueDt")); // 계약종료일 + customer.put("trade_type", extractJsonValue(json, "trsoFg")); // 거래형태 + customer.put("discount_rate", extractJsonValue(json, "interRt")); // 할인율 + customer.put("contract_amt", extractJsonValue(json, "limitAm")); // 계약금액 + customer.put("monthly_fee", extractJsonValue(json, "interAm")); // 월용역비 + customer.put("payment_term", extractJsonValue(json, "payconDc")); // 결제조건 + customer.put("rcp_tp", extractJsonValue(json, "doudate1Fg")); // 예정일구분 + customer.put("credit_limit", extractJsonValue(json, "creditAm")); // 여신한도액 + customer.put("limit_return_day", extractJsonValue(json, "returnDt")); // 한도회귀일 + + // 매입 금융기관 정보 + customer.put("pur_bank_cd", extractJsonValue(json, "jiroCd")); // 매입은행코드 + customer.put("pur_bank_nm", extractJsonValue(json, "jiroNm")); // 매입은행명 + customer.put("pur_branch_nm", extractJsonValue(json, "placeNm")); // 매입지점명 + customer.put("pur_account_no", extractJsonValue(json, "baNb")); // 매입계좌번호 + customer.put("pur_account_holder", extractJsonValue(json, "depositor")); // 매입예금주 + customer.put("pur_pay_plan", extractJsonValue(json, "stOutSettlePay")); // 매입지급예정일 + customer.put("pur_slip_type", extractJsonValue(json, "stOutAcct1Fg")); // 매입전표유형 + customer.put("pur_tax_type", extractJsonValue(json, "stOutTax1Fg")); // 매입세무구분 + + // 매출 금융기관 정보 + customer.put("sale_bank_cd", extractJsonValue(json, "btrCd")); // 매출은행코드 + customer.put("sale_bank_nm", extractJsonValue(json, "btrCd")); // 매출은행명 + customer.put("sale_branch_nm", extractJsonValue(json, "stInDummy2")); // 매출지점명 + customer.put("sale_account_no", extractJsonValue(json, "stInBaNb")); // 매출계좌번호 + customer.put("sale_collect_plan", extractJsonValue(json, "stInSettlePay")); // 매출수금예정일 + customer.put("sale_slip_type", extractJsonValue(json, "stInAcct1Fg")); // 매출전표유형 + customer.put("sale_tax_type", extractJsonValue(json, "stInTax1Fg")); // 매출세무구분 + + // 거래처 담당자 정보 + customer.put("vendor_dept_nm", extractJsonValue(json, "stempgrpTrchargeDept")); // 거래처부서명 + customer.put("vendor_position", extractJsonValue(json, "stempgrpTrchargeTitle")); // 거래처직급 + customer.put("vendor_duty", extractJsonValue(json, "stempgrpTrchargeJop")); // 거래처담당업무 + customer.put("vendor_manager_nm", extractJsonValue(json, "stempgrpTrchargeEmp")); // 거래처담당자명 + customer.put("vendor_tel", extractJsonValue(json, "stempgrpTrchargeTel")); // 거래처전화번호 + customer.put("vendor_ext", extractJsonValue(json, "stempgrpTrchargeExt")); // 거래처내선 + customer.put("vendor_mobile", extractJsonValue(json, "stempgrpTrchargeHp")); // 거래처휴대폰 + customer.put("vendor_email", extractJsonValue(json, "stempgrpTrchargeEmail")); // 거래처이메일 + + // 관리담당자 정보 + customer.put("mgr_dept_cd", extractJsonValue(json, "stempDeptCd")); // 관리부서코드 + customer.put("mgr_dept_nm", extractJsonValue(json, "stempDeptNm")); // 관리부서명 + customer.put("mgr_position", extractJsonValue(json, "stempTitleDc")); // 관리직급 + customer.put("mgr_duty", extractJsonValue(json, "stempJobDc")); // 관리담당업무 + customer.put("mgr_emp_cd", extractJsonValue(json, "stempEmpCd")); // 관리사원코드 + customer.put("mgr_emp_nm", extractJsonValue(json, "stempEmpNm")); // 관리사원명 + customer.put("mgr_tel", extractJsonValue(json, "stempTel")); // 관리전화번호 + customer.put("mgr_ext", extractJsonValue(json, "stempTelDc")); // 관리내선 + customer.put("mgr_mobile", extractJsonValue(json, "stempHp")); // 관리휴대폰 + customer.put("mgr_email", extractJsonValue(json, "stempEmail")); // 관리이메일 + customer.put("mgr_remark", extractJsonValue(json, "stempRmkDc")); // 관리비고 + + // 수신처 정보 + customer.put("rec_remark", extractJsonValue(json, "streceiveRmkDc")); // 수신처비고 + customer.put("rec_post_no", extractJsonValue(json, "streceiveZip")); // 수신처우편번호 + customer.put("rec_addr1", extractJsonValue(json, "streceiveAddr1")); // 수신처주소1 + customer.put("rec_addr2", extractJsonValue(json, "streceiveAddr2")); // 수신처주소2 + customer.put("rec_tel", extractJsonValue(json, "streceiveTel")); // 수신처전화번호 + customer.put("rec_fax", extractJsonValue(json, "streceiveFax")); // 수신처팩스 + + // 프로젝트 정보 + customer.put("project_cd", extractJsonValue(json, "pjtCd")); // 프로젝트코드 + customer.put("project_nm", extractJsonValue(json, "pjtNm")); // 프로젝트명 + customer.put("pjt_nmk", extractJsonValue(json, "pjtNm")); // 프로젝트명(한글) + + // 기타 정보 + customer.put("ext_data_cd", extractJsonValue(json, "linkCd")); // 외부데이터코드 + customer.put("e_tax_yn", extractJsonValue(json, "jeonjaYn")); // 전자세금계산서여부 + customer.put("unit_report_client", extractJsonValue(json, "reptrCd")); // 단위신고거래처 + customer.put("sub_bus_no", extractJsonValue(json, "apprNb")); // 종사업장번호 + customer.put("procurement_yn", extractJsonValue(json, "ppsFg")); // 조달청다수공급자 + customer.put("use_fg", extractJsonValue(json, "liqFg")); // 용도구분 + customer.put("for_yn", extractJsonValue(json, "forYn")); // 내외국인여부 + customer.put("plan_day_type", extractJsonValue(json, "doudate1Fg")); // 예정일구분 + customer.put("plan_day", extractJsonValue(json, "doudate1Dd")); // 예정일 + customer.put("purpose_type", extractJsonValue(json, "liqFg")); // 용도구분 + + // 등록/수정 정보 + customer.put("insert_dt", extractJsonValue(json, "insertDt")); // 삽입일자 + customer.put("modify_dt", extractJsonValue(json, "modifyDt")); // 수정일 + customer.put("insert_id", "batch_system"); // 등록자 + customer.put("modify_id", "batch_system"); // 수정자 + + return customer; + } + + /** + * 부서 JSON 파싱 + */ + private List> parseDepartmentJson(String jsonResponse) { + List> deptList = new ArrayList>(); + + try { + int dataStart = jsonResponse.indexOf("\"resultData\":["); + if (dataStart == -1) { + return deptList; + } + + String dataSection = jsonResponse.substring(dataStart + 14); + int bracketCount = 0; + int startIdx = -1; + + 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 deptJson = dataSection.substring(startIdx, i + 1); + Map dept = parseDepartmentObject(deptJson); + if (dept != null) { + deptList.add(dept); + } + startIdx = -1; + } + } + } + } catch (Exception e) { + System.err.println("부서 JSON 파싱 오류: " + e.getMessage()); + } + + return deptList; + } + + /** + * 부서 객체 파싱 (실제 JSON 응답 기준으로 dept_info 테이블 매핑) + */ + private Map parseDepartmentObject(String json) { + Map dept = new HashMap(); + + // JSON 필드 → dept_info 테이블 컬럼 매핑 + dept.put("dept_code", extractJsonValue(json, "deptCd")); // 부서코드 (deptCd) + dept.put("parent_dept_code", extractJsonValue(json, "parentDeptCd")); // 상위부서코드 (parentDeptCd) + dept.put("dept_name", extractJsonValue(json, "deptNm")); // 부서명 (deptNm) + dept.put("status", "active"); // 상태 (기본값 active) + dept.put("data_type", "ERP"); // 데이터유형 + + return dept; + } + + /** + * 사원 JSON 파싱 + */ + private List> parseEmployeeJson(String jsonResponse) { + List> empList = new ArrayList>(); + + try { + int dataStart = jsonResponse.indexOf("\"resultData\":["); + if (dataStart == -1) { + return empList; + } + + String dataSection = jsonResponse.substring(dataStart + 14); + int bracketCount = 0; + int startIdx = -1; + + 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 empJson = dataSection.substring(startIdx, i + 1); + Map emp = parseEmployeeObject(empJson); + if (emp != null) { + empList.add(emp); + } + startIdx = -1; + } + } + } + } catch (Exception e) { + System.err.println("사원 JSON 파싱 오류: " + e.getMessage()); + } + + return empList; + } + + /** + * 사원 객체 파싱 (실제 JSON 응답 기준으로 user_info 테이블 매핑) + */ + private Map parseEmployeeObject(String json) { + Map emp = new HashMap(); + + // JSON 필드 → user_info 테이블 컬럼 매핑 + emp.put("sabun", extractJsonValue(json, "empCd")); // 사번 (empCd) + emp.put("user_id", extractJsonValue(json, "empCd")); // 사용자ID (empCd) + emp.put("user_password", "5715e7d798fd421c7100c435e7547e82"); // 기본 비밀번호 (MD5 해시) + emp.put("user_name", extractJsonValue(json, "korNm")); // 사용자명 (korNm) + emp.put("user_name_eng", extractJsonValue(json, "enlsNm")); // 영문명 (enlsNm) + emp.put("dept_code", extractJsonValue(json, "deptCd")); // 부서코드 (deptCd) + emp.put("dept_name", extractJsonValue(json, "deptNm")); // 부서명 (deptNm) + emp.put("position_code", extractJsonValue(json, "hclsCd")); // 직급코드 (hclsCd) + emp.put("position_name", extractJsonValue(json, "hclsNm")); // 직급명 (hclsNm) + emp.put("rank", extractJsonValue(json, "hrspNm")); // 직책명 (hrspNm - 사원, 대리 등) + emp.put("email", extractJsonValue(json, "emalAdd")); // 이메일 (emalAdd) + emp.put("cell_phone", extractJsonValue(json, "emgcTel")); // 휴대폰 (emgcTel) + emp.put("user_type", extractJsonValue(json, "enrlFg")); // 사용자유형 (enrlFg - J01:재직) + emp.put("(주)RPS", extractJsonValue(json, "enrlNm")); // 사용자유형명 (enrlNm) + + // 재직구분에 따른 상태 설정 (J01:재직=active, 그 외=inactive) + String enrlFg = extractJsonValue(json, "enrlFg"); + emp.put("status", "J01".equals(enrlFg) ? "active" : "inactive"); // 상태 + + // 퇴직일자 (rtrDt) + emp.put("end_date", extractJsonValue(json, "rtrDt")); // 종료일 (퇴직일) + + emp.put("data_type", "ERP"); // 데이터유형 + + return emp; + } + + /** + * JSON에서 특정 필드 값 추출 + */ + private 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' || + json.charAt(endIdx) == 'u' || + json.charAt(endIdx) == 'l')) { + endIdx++; + } + String value = json.substring(startIdx, endIdx).trim(); + if (value.startsWith("null")) { + return ""; + } + return value; + } + } }