From 609910b646a46daac464239f90579d38120bb6c3 Mon Sep 17 00:00:00 2001 From: jiwonkim Date: Fri, 28 Feb 2025 11:30:02 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20CoupangPartnersService=20=EC=B0=A8?= =?UTF-8?q?=EB=8B=A8=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CoupangPartnersService.java | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java b/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java index 11d2fac..bed5f08 100644 --- a/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java +++ b/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java @@ -40,7 +40,7 @@ public CoupangPartnersService(ProductRepository productRepository) { } /** - * DB에서 모든 쿠팡 상품을 조회하고, 파트너스 링크로 업데이트 + * DB에서 모든 쿠팡 상품을 조회하고, 파트너스 링크로 업데이트 (배치+대기 적용) */ @Transactional public int updateAllCoupangProductLinks() { @@ -49,21 +49,45 @@ public int updateAllCoupangProductLinks() { List coupangProducts = productRepository.findByMallName("Coupang"); log.info("📦 총 {}개의 쿠팡 상품을 찾음", coupangProducts.size()); + // 한 번에 처리할 상품 수 (필요에 따라 조정) + final int BATCH_SIZE = 50; + // 각 배치 처리 후 대기 시간 (밀리초) (필요에 따라 조정) + final long SLEEP_MS = 60000L; + int updatedCount = 0; - for (Product product : coupangProducts) { - String originalUrl = product.getLink(); - log.info("🔗 상품 ID {}의 기존 URL: {}", product.getProductId(), originalUrl); - - String partnerLink = generatePartnerLink(originalUrl); - if (partnerLink != null) { - log.info("✅ 상품 ID {}의 변환된 파트너스 링크: {}", product.getProductId(), partnerLink); - product.setLink(partnerLink); - productRepository.save(product); - updatedCount++; - } else { - log.warn("⚠️ 파트너스 링크 생성 실패 (상품 ID: {})", product.getProductId()); + + // 배치(Chunk) 단위로 상품을 나눠 처리 + for (int i = 0; i < coupangProducts.size(); i += BATCH_SIZE) { + List batch = coupangProducts.subList(i, Math.min(i + BATCH_SIZE, coupangProducts.size())); + log.info("🔸 Batch 처리: index {} ~ {} (총 {}개)", i, i + batch.size() - 1, batch.size()); + + for (Product product : batch) { + String originalUrl = product.getLink(); + log.info("🔗 상품 ID {}의 기존 URL: {}", product.getProductId(), originalUrl); + + String partnerLink = generatePartnerLink(originalUrl); + if (partnerLink != null) { + log.info("✅ 상품 ID {}의 변환된 파트너스 링크: {}", product.getProductId(), partnerLink); + product.setLink(partnerLink); + productRepository.save(product); + updatedCount++; + } else { + log.warn("⚠️ 파트너스 링크 생성 실패 (상품 ID: {})", product.getProductId()); + } + } + + // 한 배치를 끝냈으므로 일정 시간 대기 (과도 호출 방지) + if (i + BATCH_SIZE < coupangProducts.size()) { + log.info("🔸 Batch 처리 완료: {}개 상품 업데이트, 다음 배치 전 {}ms 대기", batch.size(), SLEEP_MS); + try { + Thread.sleep(SLEEP_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("스레드 대기 중 인터럽트 발생: {}", e.getMessage()); + } } } + log.info("🎯 [END] 총 {}개의 쿠팡 상품이 업데이트됨", updatedCount); return updatedCount; } @@ -73,7 +97,6 @@ public int updateAllCoupangProductLinks() { */ private String generatePartnerLink(String originalUrl) { try { - // 엔드포인트 URI (baseUrl과 결합) String endpoint = "/v2/providers/affiliate_open_api/apis/openapi/v1/deeplink"; String apiUrl = baseUrl + endpoint; log.info("📡 쿠팡 파트너스 API 호출: {}", apiUrl); @@ -89,7 +112,6 @@ private String generatePartnerLink(String originalUrl) { headers.set("X-Request-Id", requestId); log.info("🆔 X-Request-Id: {}", requestId); - // 요청 바디 구성 (문서 예시에 맞게 coupangUrls와 subId 포함) Map requestBody = new HashMap<>(); requestBody.put("coupangUrls", Collections.singletonList(originalUrl)); requestBody.put("subId", partnerId); @@ -137,17 +159,14 @@ private String generatePartnerLink(String originalUrl) { * "CEA algorithm=HmacSHA256, access-key=ACCESS_KEY, signed-date=SIGNED_DATE, signature=SIGNATURE" */ private String generateAuthorizationHeader(String method, String uri) { - // GMT 기준 날짜/시간 생성 (형식: yyMMdd'T'HHmmss'Z') SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyMMdd'T'HHmmss'Z'"); dateFormatGmt.setTimeZone(TimeZone.getTimeZone("GMT")); String signedDate = dateFormatGmt.format(new Date()); - // uri에서 path와 query 분리 (query가 없는 경우 빈 문자열 사용) String[] parts = uri.split("\\?", 2); String path = parts[0]; String query = (parts.length == 2) ? parts[1] : ""; - // 메시지 생성 String message = signedDate + method + path + query; log.debug("🔐 서명할 메시지: {}", message); @@ -163,7 +182,6 @@ private String generateAuthorizationHeader(String method, String uri) { throw new RuntimeException("HMAC 서명 생성 오류: " + e.getMessage(), e); } - // 최종 Authorization 헤더 생성 return String.format("CEA algorithm=%s, access-key=%s, signed-date=%s, signature=%s", "HmacSHA256", accessKey, signedDate, signature); } From 5e166849207d6015cae0699686ef05f6f5619735 Mon Sep 17 00:00:00 2001 From: jiwonkim Date: Thu, 27 Feb 2025 12:00:00 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20CoupangPartnersService=20=EC=B0=A8?= =?UTF-8?q?=EB=8B=A8=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CoupangPartnersService.java | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java b/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java index bed5f08..2496b9d 100644 --- a/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java +++ b/src/main/java/com/team4/giftidea/service/CoupangPartnersService.java @@ -40,7 +40,7 @@ public CoupangPartnersService(ProductRepository productRepository) { } /** - * DB에서 모든 쿠팡 상품을 조회하고, 파트너스 링크로 업데이트 (배치+대기 적용) + * 배치 & 상품 단위로 대기 시간을 두어 과도 호출을 피하는 방식 */ @Transactional public int updateAllCoupangProductLinks() { @@ -49,19 +49,21 @@ public int updateAllCoupangProductLinks() { List coupangProducts = productRepository.findByMallName("Coupang"); log.info("📦 총 {}개의 쿠팡 상품을 찾음", coupangProducts.size()); - // 한 번에 처리할 상품 수 (필요에 따라 조정) + // 한 번에 처리할 상품 수 final int BATCH_SIZE = 50; - // 각 배치 처리 후 대기 시간 (밀리초) (필요에 따라 조정) - final long SLEEP_MS = 60000L; + // 각 상품 처리 후 대기 (ms) - 1초 + final long ITEM_SLEEP_MS = 1000L; + // 배치 완료 후 대기 (ms) - 10초 (상황에 따라 늘리거나 줄일 수 있음) + final long BATCH_SLEEP_MS = 10000L; int updatedCount = 0; - // 배치(Chunk) 단위로 상품을 나눠 처리 for (int i = 0; i < coupangProducts.size(); i += BATCH_SIZE) { List batch = coupangProducts.subList(i, Math.min(i + BATCH_SIZE, coupangProducts.size())); log.info("🔸 Batch 처리: index {} ~ {} (총 {}개)", i, i + batch.size() - 1, batch.size()); - for (Product product : batch) { + for (int j = 0; j < batch.size(); j++) { + Product product = batch.get(j); String originalUrl = product.getLink(); log.info("🔗 상품 ID {}의 기존 URL: {}", product.getProductId(), originalUrl); @@ -74,16 +76,26 @@ public int updateAllCoupangProductLinks() { } else { log.warn("⚠️ 파트너스 링크 생성 실패 (상품 ID: {})", product.getProductId()); } + + // [중요] 상품 1건 처리 후 1초 대기 -> 1분 최대 60건 + if (j < batch.size() - 1) { + try { + Thread.sleep(ITEM_SLEEP_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("상품 단위 대기 중 인터럽트 발생: {}", e.getMessage()); + } + } } - // 한 배치를 끝냈으므로 일정 시간 대기 (과도 호출 방지) + // 배치가 끝났다면 추가로 10초 대기 if (i + BATCH_SIZE < coupangProducts.size()) { - log.info("🔸 Batch 처리 완료: {}개 상품 업데이트, 다음 배치 전 {}ms 대기", batch.size(), SLEEP_MS); + log.info("🔸 Batch 처리 완료: {}개 상품 업데이트, 다음 배치 전 {}ms 대기", batch.size(), BATCH_SLEEP_MS); try { - Thread.sleep(SLEEP_MS); + Thread.sleep(BATCH_SLEEP_MS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - log.warn("스레드 대기 중 인터럽트 발생: {}", e.getMessage()); + log.warn("배치 단위 대기 중 인터럽트 발생: {}", e.getMessage()); } } } @@ -151,12 +163,6 @@ private String generatePartnerLink(String originalUrl) { /** * HMAC 서명 기반의 Authorization 헤더 생성 - * - * 메시지 형식: signedDate + method + path + query - * signedDate 포맷: "yyMMdd'T'HHmmss'Z'" (GMT 기준) - * - * 최종 형식: - * "CEA algorithm=HmacSHA256, access-key=ACCESS_KEY, signed-date=SIGNED_DATE, signature=SIGNATURE" */ private String generateAuthorizationHeader(String method, String uri) { SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyMMdd'T'HHmmss'Z'");