diff --git a/.gitignore b/.gitignore index 39e0b93..b6fed4c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,9 @@ mongodb .DS_Store elasticsearch/ .cursorrules +application-stageing.yml application-stage.yml +application-production.yml application-prod.yml application-local.yml !/docker/elasticsearch diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/BasePublicDataClient.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/BasePublicDataClient.java index 64af69b..a736d23 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/BasePublicDataClient.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/BasePublicDataClient.java @@ -3,9 +3,9 @@ import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryRegistry; import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.request.BasePublicDataRequest; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.BasePublicDataItem; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.PublicDataResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.request.BasePublicDataRequest; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.BasePublicDataItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.PublicDataResponse; import org.atdev.artrip.external.culturalapi.properties.PublicDataProperties; import org.atdev.artrip.global.apipayload.exception.ExternalApiException; import org.springframework.core.ParameterizedTypeReference; @@ -79,7 +79,6 @@ protected URI buildUri(R request) { UriComponentsBuilder builder = UriComponentsBuilder .fromHttpUrl(properties.getBaseUrl()) .path(getApiPath()) -// .queryParam("serviceKey", properties.getServiceKey()) .queryParam("PageNo", request.getPageNo()) .queryParam("numOfrows", request.getNumOfRows()) .queryParam("sortStdr", request.getSortStdr()); diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/CultureInfoApiClient.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/CultureInfoApiClient.java index f6e5fe2..3c493d2 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/CultureInfoApiClient.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/CultureInfoApiClient.java @@ -2,11 +2,10 @@ import io.github.resilience4j.retry.RetryRegistry; import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.domain.exhibit.reponse.ExhibitDetailResponse; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.request.CultureInfoRequest; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailResponse; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoItem; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoListResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.request.CultureInfoRequest; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoDetailResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoListResponse; import org.atdev.artrip.external.culturalapi.properties.PublicDataProperties; import org.atdev.artrip.global.apipayload.exception.ExternalApiException; import org.springframework.core.ParameterizedTypeReference; @@ -20,16 +19,16 @@ @Slf4j @Component -public class CultureInfoApiClient extends BasePublicDataClient{ +public class CultureInfoApiClient extends BasePublicDataClient { - private static final String REALM_CODE_EXHIBITION = "D000"; + private static final String SERVICE_TP = "A"; private static final String PATH_REALM2 = "/realm2"; private static final String PATH_DETAILS2 = "/detail2"; public CultureInfoApiClient( WebClient publicDataWebClient, PublicDataProperties properties, - RetryRegistry retryRegistry){ + RetryRegistry retryRegistry) { super(publicDataWebClient, properties, retryRegistry); } @@ -45,7 +44,8 @@ protected String getApiPath() { @Override protected ParameterizedTypeReference getResponseTypeRef() { - return new ParameterizedTypeReference<>() {}; + return new ParameterizedTypeReference<>() { + }; } @Override @@ -55,7 +55,7 @@ protected Class getResponseClass() { @Override protected void addRequestParams(UriComponentsBuilder builder, CultureInfoRequest request) { - builder.queryParam("realmCode", REALM_CODE_EXHIBITION); + builder.queryParam("serviceTp", SERVICE_TP); if (request.getFrom() != null) { builder.queryParam("from", request.getFrom()); @@ -106,7 +106,7 @@ public CultureInfoListResponse fetchExhibits(int pageNo) { CultureInfoRequest request = CultureInfoRequest.builder() .serviceKey(properties.getServiceKey()) .pageNo(pageNo) - .numOfRows(pageNo) + .numOfRows(properties.getPageSize()) .build(); return fetch(request); } @@ -156,5 +156,4 @@ private void logDetailResponse(CultureInfoDetailResponse response) { log.warn("상세 조회 실패 - error: {}", response.getErrorMessage()); } } - -} +} \ No newline at end of file diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/mapper/CultureInfoMapper.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/mapper/CultureInfoMapper.java index 01eb5b3..49baf58 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/mapper/CultureInfoMapper.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/mapper/CultureInfoMapper.java @@ -1,12 +1,11 @@ package org.atdev.artrip.external.culturalapi.cultureinfo.mapper; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.atdev.artrip.domain.Enum.Status; import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailItem; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoDetailItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoItem; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/CultureInfoSyncService.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/CultureInfoSyncService.java index 94ee7d5..f057e37 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/CultureInfoSyncService.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/CultureInfoSyncService.java @@ -10,14 +10,14 @@ import org.atdev.artrip.domain.keyword.data.Keyword; import org.atdev.artrip.elastic.service.ExhibitIndexService; import org.atdev.artrip.external.culturalapi.cultureinfo.client.CultureInfoApiClient; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailItem; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailResponse; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoItem; -import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoListResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoDetailItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoDetailResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response.CultureInfoListResponse; import org.atdev.artrip.external.culturalapi.cultureinfo.mapper.CultureInfoMapper; import org.atdev.artrip.global.apipayload.exception.ExternalApiException; +import org.atdev.artrip.global.s3.service.S3Service; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,10 +25,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Function; @Slf4j @@ -42,6 +39,7 @@ public class CultureInfoSyncService { private final ExhibitHallRepository exhibitHallRepository; private final KeywordMatchingService keywordMatchingService; private final ExhibitIndexService exhibitIndexService; + private final S3Service s3Service; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final int MAX_PAGES = 200; @@ -173,6 +171,14 @@ private void processItem(CultureInfoItem item, Map hallCach } } + String posterUrl = exhibit.getPosterUrl(); + if (posterUrl != null && !s3Service.isInternalUrl(posterUrl)) { + String s3Url = s3Service.uploadPosterFromExternalUrl(posterUrl); + if (s3Url != null && !s3Url.equals(posterUrl)) { + exhibit.setPosterUrl(s3Url); + } + } + Optional existing = exhibitRepository .findByTitleAndStartDate(exhibit.getTitle(), exhibit.getStartDate()); diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/controller/CultureInfoSyncController.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/controller/CultureInfoSyncController.java index cf78098..7327855 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/controller/CultureInfoSyncController.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/controller/CultureInfoSyncController.java @@ -18,31 +18,13 @@ public class CultureInfoSyncController { private final CultureInfoSyncService CultureInfoSyncService; - - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); private final ExhibitIndexService exhibitIndexService; - @GetMapping("exhibits/latest") - public CommonResponse> syncLatest() { - String today = LocalDate.now().format(DATE_FORMATTER); - log.info("최신 전시 동기화 : {}", today); - - CultureInfoSyncService.SyncResult result = CultureInfoSyncService.syncLatest(); - - return CommonResponse.onSuccess(Map.of( - "from", today, - "inserted", result.getInserted(), - "updated", result.getUpdated(), - "skipped", result.getSkipped(), - "failed", result.getFailed() - )); - - } - @GetMapping("/exhibits") public CommonResponse> syncByPeriod( @RequestParam String from, @RequestParam(required = false) String to) { + log.info("기간별 전시 동기화 시작 - 기간: {} ~ {}", from, to != null ? to : "전체"); CultureInfoSyncService.SyncResult result; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/BasePublicDataRequest.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/request/BasePublicDataRequest.java similarity index 82% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/BasePublicDataRequest.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/request/BasePublicDataRequest.java index 8dc5709..cd598be 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/BasePublicDataRequest.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/request/BasePublicDataRequest.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.request; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.request; import lombok.*; import lombok.experimental.SuperBuilder; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/CultureInfoRequest.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/request/CultureInfoRequest.java similarity index 84% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/CultureInfoRequest.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/request/CultureInfoRequest.java index 7c9e9b0..288ae88 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/CultureInfoRequest.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/request/CultureInfoRequest.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.request; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.request; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BaseCultureInfoResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/BaseCultureInfoResponse.java similarity index 91% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BaseCultureInfoResponse.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/BaseCultureInfoResponse.java index 9e17d8c..de61b18 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BaseCultureInfoResponse.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/BaseCultureInfoResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BasePublicDataItem.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/BasePublicDataItem.java similarity index 69% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BasePublicDataItem.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/BasePublicDataItem.java index 67f842e..a9ae450 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BasePublicDataItem.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/BasePublicDataItem.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; public interface BasePublicDataItem { diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailItem.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoDetailItem.java similarity index 93% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailItem.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoDetailItem.java index 551e1b2..0c2ebf8 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailItem.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoDetailItem.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoDetailResponse.java similarity index 95% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailResponse.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoDetailResponse.java index 8b9e3c7..eb61c2a 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailResponse.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoDetailResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoItem.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoItem.java similarity index 93% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoItem.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoItem.java index 1d25ce7..ef14a01 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoItem.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoItem.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; @@ -60,7 +60,7 @@ public boolean isValid() { } public boolean isExhibition() { - return "전시".equals(realmName); + return "전시".equals(serviceName); } public String getFullAddress() { diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoListResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoListResponse.java similarity index 97% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoListResponse.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoListResponse.java index 1e730b7..f115360 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoListResponse.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/CultureInfoListResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataHeader.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/PublicDataHeader.java similarity index 88% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataHeader.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/PublicDataHeader.java index 82f8f57..d2a9793 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataHeader.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/PublicDataHeader.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/PublicDataResponse.java similarity index 84% rename from src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataResponse.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/PublicDataResponse.java index 50aab38..1677f9e 100644 --- a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataResponse.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/dto/response/PublicDataResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.web.dto.response; import java.util.List; diff --git a/src/main/java/org/atdev/artrip/global/s3/service/S3Service.java b/src/main/java/org/atdev/artrip/global/s3/service/S3Service.java index c0cca60..290d230 100644 --- a/src/main/java/org/atdev/artrip/global/s3/service/S3Service.java +++ b/src/main/java/org/atdev/artrip/global/s3/service/S3Service.java @@ -16,10 +16,7 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.net.URLDecoder; -import java.net.URLEncoder; +import java.net.*; import java.nio.charset.StandardCharsets; import java.util.*; @@ -252,7 +249,105 @@ private String uploadToFolder(MultipartFile file, String folder) { return uploadToS3(file, folder); } + public String uploadFromExternalUrl(String externalUrl, String folder){ + if (externalUrl == null || externalUrl.isBlank()) { + return null; + } + + if (isInternalUrl(externalUrl)) { + return externalUrl; + } + + + try { + URL url = new URI(externalUrl).toURL(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(10000); + connection.setRequestProperty("User-Agent", "Mozilla/5.0"); + connection.setInstanceFollowRedirects(true); + + int responseCode = connection.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP || + responseCode == HttpURLConnection.HTTP_MOVED_PERM || + responseCode == 307 || responseCode == 300 + ) { + String redirectUrl = connection.getHeaderField("Location"); + + if (redirectUrl != null && !redirectUrl.isBlank()) { + // 리다이렉트 URL로 재귀 호출 + return uploadFromExternalUrl(redirectUrl, folder); + } + } + + if (responseCode != 200) { + return externalUrl; + } + + String contentType = connection.getContentType(); + String extension = getExtensionFromContentType(contentType); + if (extension == null) { + extension = getExtensionFromUrl(externalUrl); + } + + String s3Key = String.format("%s/%s.%s", folder, UUID.randomUUID().toString().substring(0, 10), extension); + try (InputStream inputStream = connection.getInputStream()){ + byte[] imageBytes = inputStream.readAllBytes(); + + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .contentType(contentType != null ? contentType : "image/" + extension) + .contentLength((long) imageBytes.length) + .cacheControl("public, max-age=31536000") + .build(); + + s3Client.putObject(putObjectRequest, RequestBody.fromBytes(imageBytes)); + + return buildImageUrl(s3Key); + + } + } catch (Exception e) { + log.error("외부 이미지 업로드 실패 - URL: {}, 에러: {}", externalUrl, e.getMessage()); + return externalUrl; + } + } + private String getExtensionFromUrl(String url) { + try { + String path = new URI(url).getPath(); + int lastDot = path.lastIndexOf('.'); + if (lastDot > 0) { + return path.substring(lastDot + 1).toLowerCase(); + } + } catch (Exception ignored) {} + return "jpg"; + } + + private String getExtensionFromContentType(String contentType) { + if (contentType == null) return null; + return switch (contentType.toLowerCase()) { + case "image/jpeg", "image/jpg" -> "jpg"; + case "image/png" -> "png"; + case "image/webp" -> "webp"; + case "image/gif" -> "gif"; + default -> null; + }; + } + public boolean isInternalUrl(String url) { + if (url == null || url.isBlank()) { + return false; + } + return url.contains("s3.ap-northeast-2.amazonaws.com") || + url.contains("cloudfront.net") || + url.contains(bucketName); + } + + public String uploadPosterFromExternalUrl(String externalUrl) { + return uploadFromExternalUrl(externalUrl, FOLDER_POSTERS); + } } \ No newline at end of file