Java 택배API 연동 가이드
이 가이드에서 다루는 내용
• Java 11+ HttpClient를 사용한 API 호출
• OkHttp 라이브러리 예제
• Spring Boot 웹훅 컨트롤러
• 에러 처리 및 재시도 로직
• Java 11+ HttpClient를 사용한 API 호출
• OkHttp 라이브러리 예제
• Spring Boot 웹훅 컨트롤러
• 에러 처리 및 재시도 로직
1. 의존성 추가
Maven (pom.xml)
<dependencies>
<!-- JSON 파싱 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- OkHttp (선택) -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
</dependencies> Gradle (build.gradle)
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
} 2. 환경 변수 설정
// application.properties (Spring Boot)
delivery.api.public-key=${DELIVERY_PUBLIC_KEY}
delivery.api.secret-key=${DELIVERY_SECRET_KEY}
delivery.api.base-url=https://api.deliveryapi.co.kr/v1 보안 주의
Secret Key를 코드에 직접 하드코딩하지 마세요. 환경 변수나 시크릿 매니저를 사용하세요.
Secret Key를 코드에 직접 하드코딩하지 마세요. 환경 변수나 시크릿 매니저를 사용하세요.
3. 배송 조회 (Java 11+ HttpClient)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
public class DeliveryApiClient {
private static final String BASE_URL = "https://api.deliveryapi.co.kr/v1";
private final String publicKey;
private final String secretKey;
private final HttpClient httpClient;
private final Gson gson;
public DeliveryApiClient(String publicKey, String secretKey) {
this.publicKey = publicKey;
this.secretKey = secretKey;
this.httpClient = HttpClient.newHttpClient();
this.gson = new Gson();
}
public JsonObject trackDelivery(String courierCode, String trackingNumber) throws Exception {
String url = BASE_URL + "/tracking/trace";
// JSON 페이로드 생성
String jsonBody = String.format(
"{\"items\":[{\"courierCode\":\"%s\",\"trackingNumber\":\"%s\"}]}",
courierCode, trackingNumber
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + publicKey + ":" + secretKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
if (response.statusCode() != 200) {
throw new RuntimeException("API Error: " + response.body());
}
JsonObject fullResponse = gson.fromJson(response.body(), JsonObject.class);
// results 배열의 첫 번째 결과 반환
return fullResponse.getAsJsonObject("data").getAsJsonArray("results").get(0).getAsJsonObject();
}
public static void main(String[] args) throws Exception {
String publicKey = System.getenv("DELIVERY_PUBLIC_KEY");
String secretKey = System.getenv("DELIVERY_SECRET_KEY");
DeliveryApiClient client = new DeliveryApiClient(publicKey, secretKey);
JsonObject result = client.trackDelivery("cj", "1234567890");
if (result.get("success").getAsBoolean()) {
JsonObject data = result.getAsJsonObject("data");
System.out.println("배송 상태: " + data.get("deliveryStatus").getAsString());
} else {
System.out.println("조회 실패: " + result.get("message").getAsString());
}
}
} 4. OkHttp 사용 예제
import okhttp3.*;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.IOException;
public class DeliveryApiOkHttp {
private static final String BASE_URL = "https://api.deliveryapi.co.kr/v1";
private final String publicKey;
private final String secretKey;
private final OkHttpClient client;
private final Gson gson;
public DeliveryApiOkHttp(String publicKey, String secretKey) {
this.publicKey = publicKey;
this.secretKey = secretKey;
this.client = new OkHttpClient();
this.gson = new Gson();
}
public JsonObject trackDelivery(String courierCode, String trackingNumber) throws IOException {
String jsonBody = String.format(
"{\"items\":[{\"courierCode\":\"%s\",\"trackingNumber\":\"%s\"}]}",
courierCode, trackingNumber
);
RequestBody body = RequestBody.create(
jsonBody,
MediaType.parse("application/json")
);
Request request = new Request.Builder()
.url(BASE_URL + "/tracking/trace")
.addHeader("Authorization", "Bearer " + publicKey + ":" + secretKey)
.addHeader("Content-Type", "application/json")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("API Error: " + response.body().string());
}
JsonObject fullResponse = gson.fromJson(response.body().string(), JsonObject.class);
return fullResponse.getAsJsonObject("data").getAsJsonArray("results").get(0).getAsJsonObject();
}
}
} 5. DTO 클래스
import java.util.List;
public class TrackingResult {
private boolean success;
private TrackingData data;
// Getters and Setters
public boolean isSuccess() { return success; }
public TrackingData getData() { return data; }
}
public class TrackingData {
private String courierCode;
private String courierName;
private String trackingNumber;
private String deliveryStatus;
private String deliveryStatusText;
private boolean isDelivered;
private List<Progress> progresses;
// Getters and Setters
}
public class Progress {
private String dateTime;
private String status;
private String statusCode;
private String location;
private String description;
// Getters and Setters
}
// 사용 예시
TrackingResult result = gson.fromJson(responseBody, TrackingResult.class);
System.out.println("상태: " + result.getData().getDeliveryStatusText()); 6. Spring Boot 웹훅 컨트롤러
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.HexFormat;
@RestController
@RequestMapping("/webhook")
public class DeliveryWebhookController {
@Value("${delivery.webhook.secret}")
private String webhookSecret;
@PostMapping("/delivery")
public ResponseEntity<?> handleWebhook(
@RequestBody String payload,
@RequestHeader("X-Webhook-Signature") String signature,
@RequestHeader("X-Webhook-Timestamp") String timestamp
) {
// 서명 검증
if (!verifySignature(payload, signature, timestamp)) {
return ResponseEntity.status(401)
.body(Map.of("error", "Invalid signature"));
}
// JSON 파싱
Gson gson = new Gson();
JsonObject json = gson.fromJson(payload, JsonObject.class);
String event = json.get("event").getAsString();
JsonArray items = json.getAsJsonArray("items");
for (JsonElement element : items) {
JsonObject item = element.getAsJsonObject();
switch (event) {
case "tracking.polled":
handleStatusChanged(item);
break;
case "tracking.completed":
handleDelivered(item);
break;
}
}
return ResponseEntity.ok(Map.of("received", true));
}
private boolean verifySignature(String payload, String signature, String timestamp) {
try {
// timestamp 유효성 검증 (5분 허용)
long now = System.currentTimeMillis() / 1000;
if (Math.abs(now - Long.parseLong(timestamp)) > 300) {
return false;
}
// 서명 대상: timestamp.payload
String signedPayload = timestamp + "." + payload;
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
webhookSecret.getBytes(),
"HmacSHA256"
);
mac.init(secretKey);
byte[] hash = mac.doFinal(signedPayload.getBytes());
String expected = HexFormat.of().formatHex(hash);
// 헤더값: sha256={hex} → prefix 제거 후 비교
String received = signature.replace("sha256=", "");
return expected.equals(received);
} catch (Exception e) {
return false;
}
}
private void handleStatusChanged(JsonObject data) {
String trackingNumber = data.get("trackingNumber").getAsString();
String status = data.get("currentStatus").getAsString();
System.out.println("배송 상태 변경: " + trackingNumber + " -> " + status);
// 비즈니스 로직 처리
}
private void handleDelivered(JsonObject data) {
String trackingNumber = data.get("trackingNumber").getAsString();
System.out.println("배송 완료: " + trackingNumber);
// 배송 완료 처리
}
} 7. 에러 처리 및 재시도
import java.util.concurrent.TimeUnit;
public class DeliveryApiWithRetry {
private final DeliveryApiClient client;
private final int maxRetries;
public DeliveryApiWithRetry(String apiKey, int maxRetries) {
this.client = new DeliveryApiClient(apiKey);
this.maxRetries = maxRetries;
}
public JsonObject trackWithRetry(String courier, String trackingNumber) throws Exception {
Exception lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return client.trackDelivery(courier, trackingNumber);
} catch (Exception e) {
lastException = e;
// 재시도 가능 여부 확인
if (isRetryable(e) && attempt < maxRetries) {
long delay = (long) Math.pow(2, attempt) * 1000; // 지수 백오프
System.out.printf("재시도 %d/%d, %dms 후...%n", attempt, maxRetries, delay);
TimeUnit.MILLISECONDS.sleep(delay);
continue;
}
throw e;
}
}
throw lastException;
}
private boolean isRetryable(Exception e) {
// 네트워크 에러 또는 5xx 서버 에러
String message = e.getMessage();
return message.contains("5") || message.contains("timeout") || message.contains("connect");
}
}