파트너코드 조회

This commit is contained in:
Rinjae
2025-11-26 14:28:12 +09:00
parent f0cffdf9da
commit 818155242f
9 changed files with 518 additions and 38 deletions
@@ -0,0 +1,22 @@
package com.eactive.ext.kjb.common;
public class KjbObpException extends Exception {
private int statusCode;
public KjbObpException(String message) {
super(message);
}
public KjbObpException(String message, Throwable cause) {
super(message, cause);
}
public KjbObpException(int statusCode, String message) {
super(message);
this.statusCode = statusCode;
}
public int getStatusCode() {
return statusCode;
}
}
@@ -91,6 +91,18 @@ public class KjbProperty {
@NotEmpty
private String obpPartnerCodeValue = "100001-01";
@KjbPropertyValue(key = "kjb.obp.connection_timeout", defaultValue = "5")
@Min(1) @Max(10)
private int obpConnectionTimeout = 5;
@KjbPropertyValue(key = "kjb.obp.response_timeout", defaultValue = "5")
@Min(1) @Max(30)
private int obpResponseTimeout = 5;
@KjbPropertyValue(key = "kjb.obp.timeout", defaultValue = "10")
@Min(1) @Max(30)
private int obpTimeout = 10;
@KjbPropertyValue(key = "kjb.obp.url.commission", defaultValue = "/api/billing/billing/findBillingForCondition")
@NotEmpty
@@ -1,36 +1,84 @@
package com.eactive.ext.kjb.obp;
import com.google.gson.JsonObject;
import com.eactive.ext.kjb.common.KjbObpException;
import com.eactive.ext.kjb.common.KjbProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.stream.Collectors;
@Slf4j
public class KjbObpModule {
private static final String FAKE_PARTNER_CODE = "999999-99";
private static KjbObpModule _instance;
private final KjbProperty property;
private final ObpClient client;
public static synchronized KjbObpModule getInstance(KjbProperty kjbProperty) {
if (_instance == null) {
_instance = new KjbObpModule(kjbProperty);
}
return _instance;
}
private KjbObpModule(KjbProperty kjbProperty) {
if (kjbProperty == null) {
throw new IllegalArgumentException("KjbProperty cannot be null");
}
this.property = kjbProperty;
this.client = ObpClient.getInstance(kjbProperty);
}
/**
* Trim, 숫자이외 자동 치환
* @param bizNo
* @return
* 사업자등록번호로 partnerCode 조회
* @param bizNo 사업자등록번호 (숫자 외 문자 자동 제거)
* @return partnerCode
* @throws KjbObpException 조회 실패 시
*/
public static JsonObject queryOrganization(String bizNo) {
public String queryPartnerCode(String bizNo) throws KjbObpException {
bizNo = ObpUtil.onlyDigit(bizNo);
if (StringUtils.isEmpty(bizNo)) {
throw new IllegalArgumentException("BizNo is not empty.");
throw new IllegalArgumentException("BizNo is empty.");
}
if (bizNo.length() != 10) {
throw new IllegalArgumentException("BizNo is not valid.");
throw new IllegalArgumentException("BizNo must be 10 digits. (input: " + bizNo + ")");
}
return null;
// 가상모드: URL 미설정 시 가상 파트너코드 반환
if (!client.isRealMode()) {
log.info("[OBP] 가상모드 - 가상 partnerCode 반환: {}", FAKE_PARTNER_CODE);
return FAKE_PARTNER_CODE;
}
String path = String.format(property.getObpUrlOrganizationFmt(), bizNo);
log.debug("[OBP] queryPartnerCode - bizNo: {}, path: {}", bizNo, path);
ObpResponse response = client.get(path);
if (response.getStatus() != 200) {
log.warn("[OBP] queryPartnerCode failed - status: {}, body: {}", response.getStatus(), response.getBody());
throw new KjbObpException(response.getStatus(), "OBP 조회 실패: " + response.getBody());
}
ObpOrganization org = ObpOrganization.fromResponse(response);
if (org == null) {
throw new KjbObpException("OBP 응답 파싱 실패");
}
if (!org.hasPartnerCode()) {
throw new KjbObpException("partnerCode가 없습니다. bizNo: " + bizNo);
}
return org.getPartnerCode();
}
public static void main(String[] args) {
log.info("queryOrganization Test");
queryOrganization("123asd123asd123");
/**
* ObpClient 반환 (직접 API 호출용)
*/
public ObpClient getClient() {
return client;
}
}
@@ -0,0 +1,183 @@
package com.eactive.ext.kjb.obp;
import com.eactive.ext.kjb.common.KjbProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.EntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.Timeout;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.*;
@Slf4j
public class ObpClient {
private static ObpClient _instance;
private final KjbProperty property;
private final CloseableHttpClient httpClient;
private final RequestConfig requestConfig;
private final boolean isRealMode;
public synchronized static ObpClient getInstance(KjbProperty kjbProperty) {
if (_instance == null) {
_instance = new ObpClient(kjbProperty);
}
return _instance;
}
private ObpClient(KjbProperty kjbProperty) {
if (kjbProperty == null) {
throw new IllegalArgumentException("KjbProperty cannot be null");
}
this.property = kjbProperty;
this.isRealMode = StringUtils.isNotEmpty(kjbProperty.getObpUrl());
this.requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(kjbProperty.getObpConnectionTimeout()))
.setResponseTimeout(Timeout.ofSeconds(kjbProperty.getObpResponseTimeout()))
.build();
if (isRealMode) {
this.httpClient = HttpClients.createDefault();
} else {
this.httpClient = null;
log.warn("[OBP] URL이 설정되지 않음. 실제 OBP 연동 없이 동작함");
}
}
/**
* GET 요청
* @param path API 경로 (예: /api/customer/...)
* @return ObpResponse
*/
public ObpResponse get(String path) {
return get(path, null);
}
/**
* GET 요청 (추가 헤더 포함)
* @param path API 경로
* @param additionalHeaders 추가 헤더
* @return ObpResponse
*/
public ObpResponse get(String path, Map<String, String> additionalHeaders) {
String url = property.getObpUrl() + path;
HttpGet httpGet = new HttpGet(url);
applyDefaultHeaders(httpGet);
applyAdditionalHeaders(httpGet, additionalHeaders);
httpGet.setConfig(requestConfig);
return execute(httpGet);
}
/**
* POST 요청 (JSON body)
* @param path API 경로
* @param jsonBody JSON 문자열
* @return ObpResponse
*/
public ObpResponse post(String path, String jsonBody) {
return post(path, jsonBody, null);
}
/**
* POST 요청 (JSON body, 추가 헤더 포함)
* @param path API 경로
* @param jsonBody JSON 문자열
* @param additionalHeaders 추가 헤더
* @return ObpResponse
*/
public ObpResponse post(String path, String jsonBody, Map<String, String> additionalHeaders) {
String url = property.getObpUrl() + path;
HttpPost httpPost = new HttpPost(url);
applyDefaultHeaders(httpPost);
applyAdditionalHeaders(httpPost, additionalHeaders);
httpPost.setConfig(requestConfig);
if (StringUtils.isNotEmpty(jsonBody)) {
httpPost.setEntity(EntityBuilder.create()
.setText(jsonBody)
.setContentType(ContentType.APPLICATION_JSON)
.build());
}
return execute(httpPost);
}
private void applyDefaultHeaders(HttpUriRequestBase request) {
request.setHeader("Content-Type", "application/json; charset=utf-8");
request.setHeader(property.getObpTrustSystemKey(), property.getObpTrustSystemValue());
request.setHeader(property.getObpPartnerCodeKey(), property.getObpPartnerCodeValue());
}
private void applyAdditionalHeaders(HttpUriRequestBase request, Map<String, String> additionalHeaders) {
if (additionalHeaders != null) {
additionalHeaders.forEach(request::setHeader);
}
}
private ObpResponse execute(HttpUriRequestBase request) {
if (!isRealMode) {
log.warn("[OBP] URL 미설정으로 실제 호출하지 않음. 요청: {} {}", request.getMethod(), request.getRequestUri());
ObpResponse mockResponse = new ObpResponse();
mockResponse.setStatus(200);
mockResponse.setBody("{}");
return mockResponse;
}
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
log.debug("[OBP] 요청: {} {}", request.getMethod(), request.getRequestUri());
Future<ObpResponse> future = executor.submit(() ->
httpClient.execute(request, response -> {
ObpResponse obpResponse = new ObpResponse();
obpResponse.setStatus(response.getCode());
obpResponse.setBody(EntityUtils.toString(response.getEntity(), "UTF-8"));
Arrays.asList(response.getHeaders()).forEach(header ->
obpResponse.getHeaders().put(header.getName(), header.getValue()));
return obpResponse;
})
);
ObpResponse response = future.get(property.getObpTimeout(), TimeUnit.SECONDS);
if (log.isDebugEnabled()) {
log.debug("[OBP] 응답: status={}, body={}", response.getStatus(), response.getBody());
}
return response;
} catch (InterruptedException | ExecutionException | TimeoutException e) {
log.error("[OBP] 호출 실패: {} {}", request.getMethod(), request.getRequestUri(), e);
ObpResponse errorResponse = new ObpResponse();
errorResponse.setStatus(-1);
errorResponse.setBody(e.getMessage());
return errorResponse;
} finally {
executor.shutdown();
}
}
/**
* 실제 모드 여부
*/
public boolean isRealMode() {
return isRealMode;
}
}
@@ -0,0 +1,121 @@
package com.eactive.ext.kjb.obp;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
/**
* OBP Organization 조회 응답
* - 필수 필드만 명시적으로 선언하고, 나머지는 rawJson으로 보관하여 유연성 확보
*/
@Data
public class ObpOrganization {
private static final Gson gson = new Gson();
// 핵심 필드
@SerializedName("partnerCode")
private String partnerCode;
@SerializedName("id")
private String id;
@SerializedName("name")
private String name;
@SerializedName("state")
private String state;
@SerializedName("status")
private String status;
@SerializedName("businessManRegistrationNo")
private String businessManRegistrationNo;
@SerializedName("corporateRegistrationNo")
private String corporateRegistrationNo;
@SerializedName("representativeName")
private String representativeName;
// 원본 JSON (추가 필드 접근용
private transient JsonObject rawJson;
/**
* JSON 문자열에서 파싱
*/
public static ObpOrganization fromJson(String json) {
if (json == null || json.trim().isEmpty()) {
return null;
}
ObpOrganization org = gson.fromJson(json, ObpOrganization.class);
org.rawJson = new JsonParser().parse(json).getAsJsonObject();
return org;
}
/**
* ObpResponse에서 파싱
*/
public static ObpOrganization fromResponse(ObpResponse response) {
if (response == null || response.getStatus() != 200) {
return null;
}
return fromJson(response.getBody());
}
/**
* 원본 JSON에서 추가 필드 조회
* @param fieldName 필드명 (예: "englishName", "description")
* @return 필드 값 (없으면 null)
*/
public String getField(String fieldName) {
if (rawJson == null || !rawJson.has(fieldName) || rawJson.get(fieldName).isJsonNull()) {
return null;
}
return rawJson.get(fieldName).getAsString();
}
/**
* 원본 JSON에서 중첩 필드 조회
* @param path 경로 (예: "type.name", "type.parent.name")
* @return 필드 값 (없으면 null)
*/
public String getNestedField(String path) {
if (rawJson == null || path == null) {
return null;
}
String[] parts = path.split("\\.");
JsonObject current = rawJson;
for (int i = 0; i < parts.length - 1; i++) {
if (!current.has(parts[i]) || current.get(parts[i]).isJsonNull()) {
return null;
}
current = current.getAsJsonObject(parts[i]);
}
String lastField = parts[parts.length - 1];
if (!current.has(lastField) || current.get(lastField).isJsonNull()) {
return null;
}
return current.get(lastField).getAsString();
}
/**
* 원본 JSON 객체 반환
*/
public JsonObject getRawJson() {
return rawJson;
}
/**
* partnerCode 유효성 확인
*/
public boolean hasPartnerCode() {
return partnerCode != null && !partnerCode.trim().isEmpty();
}
}
@@ -0,0 +1,4 @@
package com.eactive.ext.kjb.obp;
public class ObpRequest {
}
@@ -1,4 +1,4 @@
package com.eactive.ext.kjb.obp.common;
package com.eactive.ext.kjb.obp;
import lombok.Data;
@@ -1,22 +0,0 @@
package com.eactive.ext.kjb.obp.common;
import com.eactive.ext.kjb.common.KjbProperty;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ObpClient {
private static ObpClient _instance;
public synchronized static ObpClient getInstance(KjbProperty kjbProperty) {
if (_instance == null) {
_instance = new ObpClient(kjbProperty);
}
return _instance;
}
private ObpClient(KjbProperty kjbProperty) {
}
}