diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/global/config/ApplicationEventConfig.java b/jtoon-core/core-api/src/main/java/shop/jtoon/global/config/ApplicationEventConfig.java new file mode 100644 index 0000000..54d0ba3 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/global/config/ApplicationEventConfig.java @@ -0,0 +1,31 @@ +package shop.jtoon.global.config; + +import java.util.concurrent.Executor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +public class ApplicationEventConfig { + + @Bean + public ApplicationEventMulticaster applicationEventMulticaster() { + SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); + eventMulticaster.setTaskExecutor(asyncExecutor()); + return eventMulticaster; + } + + private Executor asyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(10000); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(10); + executor.initialize(); + return executor; + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/global/util/AsyncEventListener.java b/jtoon-core/core-api/src/main/java/shop/jtoon/global/util/AsyncEventListener.java new file mode 100644 index 0000000..c20dcc9 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/global/util/AsyncEventListener.java @@ -0,0 +1,44 @@ +package shop.jtoon.global.util; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; + +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@EventListener +public @interface AsyncEventListener { + Phase phase() default Phase.AFTER_COMMIT; + + boolean fallbackExecution() default false; + + @AliasFor( + annotation = EventListener.class, + attribute = "classes" + ) + Class[] value() default {}; + + @AliasFor( + annotation = EventListener.class, + attribute = "classes" + ) + Class[] classes() default {}; + + @AliasFor( + annotation = EventListener.class, + attribute = "condition" + ) + String condition() default ""; + + @AliasFor( + annotation = EventListener.class, + attribute = "id" + ) + String id() default ""; +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/global/util/Phase.java b/jtoon-core/core-api/src/main/java/shop/jtoon/global/util/Phase.java new file mode 100644 index 0000000..f410c06 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/global/util/Phase.java @@ -0,0 +1,12 @@ +package shop.jtoon.global.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public enum Phase { + BEFORE_COMMIT, + AFTER_COMMIT, + AFTER_ROLLBACK, + AFTER_COMPLETION; +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java index 908c908..e23ae02 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java @@ -5,16 +5,20 @@ import java.util.List; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; +import shop.jtoon.dto.ImageUploadEvent; +import shop.jtoon.dto.MultiImageEvent; import shop.jtoon.exception.InvalidRequestException; import shop.jtoon.webtoon.domain.EpisodeMainInfo; import shop.jtoon.webtoon.domain.EpisodeSchema; import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.presentation.WebtoonImageUploadEventListener; import shop.jtoon.webtoon.request.CreateEpisodeReq; import shop.jtoon.webtoon.request.GetEpisodesReq; import shop.jtoon.webtoon.response.EpisodeInfoRes; @@ -27,30 +31,37 @@ public class EpisodeService { private final WebtoonClientService webtoonClientService; private final EpisodeDomainService episodeDomainService; + private final ApplicationEventPublisher publisher; public void createEpisode( Long memberId, Long webtoonId, - MultipartFile mainImage, + List mainImages, MultipartFile thumbnailImage, CreateEpisodeReq request ) { Webtoon webtoon = episodeDomainService.readWebtoon(webtoonId, memberId, request.no()); - String mainUrl = webtoonClientService.upload(request.toUploadImageDto(EPISODE_MAIN, webtoon.getTitle(), mainImage)); - String thumbnailUrl = webtoonClientService.upload(request.toUploadImageDto( + + MultiImageEvent mainUploadEvents = MultiImageEvent.builder() + .imageUploadEvents(mainImages.stream() + .map(mainImage -> request.toUploadImageDto(EPISODE_MAIN, webtoon.getTitle(), mainImage).toImageUploadEvent()) + .toList()) + .build(); + List mainUrls = mainUploadEvents.imageUploadEvents().stream() + .map(mainUploadEvent -> webtoonClientService.uploadUrl(mainUploadEvent)) + .toList(); + ImageUploadEvent thumbnailUploadEvent = request.toUploadImageDto( EPISODE_THUMBNAIL, webtoon.getTitle(), thumbnailImage - )); - - try { - EpisodeSchema episode = request.toEpisodeSchema(); - episodeDomainService.createEpisode(episode, webtoon, mainUrl, thumbnailUrl); - } catch (RuntimeException e) { - webtoonClientService.deleteImage(mainUrl); - webtoonClientService.deleteImage(thumbnailUrl); - throw new InvalidRequestException(EPISODE_CREATE_FAIL); - } + ).toImageUploadEvent(); + String thumbnailUrl = webtoonClientService.upload(thumbnailUploadEvent); + + EpisodeSchema episode = request.toEpisodeSchema(); + episodeDomainService.createEpisode(episode, webtoon, mainUrls, thumbnailUrl); + + publisher.publishEvent(mainUploadEvents); + publisher.publishEvent(thumbnailUploadEvent); } public List getEpisodes(Long webtoonId, GetEpisodesReq request) { diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonClientService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonClientService.java index 77a7420..44f8b64 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonClientService.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonClientService.java @@ -5,11 +5,13 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; import shop.jtoon.common.ImageType; +import shop.jtoon.dto.ImageUploadEvent; import shop.jtoon.dto.UploadImageDto; import shop.jtoon.service.S3Service; import shop.jtoon.webtoon.request.CreateEpisodeReq; @@ -27,8 +29,12 @@ public String upload(ImageType imageType, CreateWebtoonReq request, MultipartFil return s3Service.uploadImage(uploadImageDto); } - public String upload(UploadImageDto uploadImageDto) { - return s3Service.uploadImage(uploadImageDto); + public String uploadUrl(ImageUploadEvent imageUpload) { + return s3Service.uploadUrl(imageUpload.key()); + } + + public String upload(ImageUploadEvent imageUpload) { + return s3Service.uploadImage(imageUpload); } public void deleteImage(String thumbnailUrl) { diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java index 362090a..a6ef58c 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java @@ -6,10 +6,12 @@ import java.util.List; import java.util.Map; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; +import shop.jtoon.dto.ImageUploadEvent; import shop.jtoon.dto.UploadImageDto; import shop.jtoon.exception.InvalidRequestException; @@ -26,23 +28,21 @@ @RequiredArgsConstructor public class WebtoonService { - private final WebtoonClientService webtoonClientService; private final WebtoonDomainService webtoonDomainService; + private final ApplicationEventPublisher publisher; public void createWebtoon(Long memberId, MultipartFile thumbnailImage, CreateWebtoonReq request) { + ImageUploadEvent imageUploadEvent = request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage) + .toImageUploadEvent(); + String thumbnailUrl = webtoonClientService.uploadUrl(imageUploadEvent); webtoonDomainService.validateDuplicateTitle(request.title()); - String thumbnailUrl = webtoonClientService.upload(request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage)); - - try { - webtoonDomainService.createWebtoon(memberId, - request.toWebtoonInfo(thumbnailUrl), - request.toWebtoonGenres(), - request.toWebtoonDayOfWeeks()); - } catch (RuntimeException e) { - webtoonClientService.deleteImage(thumbnailUrl); - throw new InvalidRequestException(WEBTOON_CREATE_FAIL); - } + webtoonDomainService.createWebtoon(memberId, + request.toWebtoonInfo(thumbnailUrl), + request.toWebtoonGenres(), + request.toWebtoonDayOfWeeks()); + + publisher.publishEvent(imageUploadEvent); } public Map> getWebtoons(GetWebtoonsReq request) { diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java index d5e1028..8921725 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java @@ -52,11 +52,11 @@ public void createWebtoon( public void createEpisode( @CurrentUser MemberDto member, @PathVariable Long webtoonId, - @RequestPart MultipartFile mainImage, + @RequestPart List mainImages, @RequestPart MultipartFile thumbnailImage, @RequestPart @Valid CreateEpisodeReq request ) { - episodeService.createEpisode(member.id(), webtoonId, mainImage, thumbnailImage, request); + episodeService.createEpisode(member.id(), webtoonId, mainImages, thumbnailImage, request); } @GetMapping diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonImageUploadEventListener.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonImageUploadEventListener.java new file mode 100644 index 0000000..120d0ce --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonImageUploadEventListener.java @@ -0,0 +1,29 @@ +package shop.jtoon.webtoon.presentation; + +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.dto.ImageUploadEvent; +import shop.jtoon.dto.MultiImageEvent; +import shop.jtoon.global.util.AsyncEventListener; +import shop.jtoon.webtoon.application.WebtoonClientService; + +@Component +@RequiredArgsConstructor +public class WebtoonImageUploadEventListener { + + private final WebtoonClientService webtoonClientService; + + @AsyncEventListener + public void uploadImage(ImageUploadEvent imageUploadEvent) { + webtoonClientService.upload(imageUploadEvent); + } + + @AsyncEventListener + public void uploadMultiImages(MultiImageEvent multiImageEvent) { + multiImageEvent.imageUploadEvents().stream() + .parallel() + .forEach(webtoonClientService::upload); + } + +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java index 4cd487f..ede1d59 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java @@ -11,6 +11,7 @@ import lombok.Builder; import shop.jtoon.common.FileName; import shop.jtoon.common.ImageType; +import shop.jtoon.dto.ImageUploadEvent; import shop.jtoon.dto.UploadImageDto; import shop.jtoon.webtoon.domain.EpisodeSchema; import shop.jtoon.webtoon.entity.Episode; diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeInfoRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeInfoRes.java index fad7d6a..0b0e49f 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeInfoRes.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeInfoRes.java @@ -1,17 +1,19 @@ package shop.jtoon.webtoon.response; +import java.util.List; + import lombok.Builder; import shop.jtoon.webtoon.domain.EpisodeMainInfo; import shop.jtoon.webtoon.entity.Episode; @Builder public record EpisodeInfoRes( - String mainUrl + List mainUrls ) { public static EpisodeInfoRes from(EpisodeMainInfo episode) { return EpisodeInfoRes.builder() - .mainUrl(episode.mainUrl()) + .mainUrls(episode.mainUrls()) .build(); } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/util/WebtoonUitls.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/util/WebtoonUitls.java new file mode 100644 index 0000000..a02bc47 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/util/WebtoonUitls.java @@ -0,0 +1,10 @@ +package shop.jtoon.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class WebtoonUitls { + + public static final String URL_SPLITER = ","; +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeMainInfo.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeMainInfo.java index c21db2b..df0edfb 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeMainInfo.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeMainInfo.java @@ -1,16 +1,20 @@ package shop.jtoon.webtoon.domain; +import static shop.jtoon.util.WebtoonUitls.*; + +import java.util.List; + import lombok.Builder; import shop.jtoon.webtoon.entity.Episode; @Builder public record EpisodeMainInfo( - String mainUrl + List mainUrls ) { public static EpisodeMainInfo of(Episode episode) { return EpisodeMainInfo.builder() - .mainUrl(episode.getMainUrl()) + .mainUrls(List.of(episode.getMainUrl().split(URL_SPLITER))) .build(); } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeSchema.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeSchema.java index b2d2faf..03d8e2b 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeSchema.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeSchema.java @@ -1,6 +1,9 @@ package shop.jtoon.webtoon.domain; +import static shop.jtoon.util.WebtoonUitls.*; + import java.time.LocalDateTime; +import java.util.List; import org.antlr.v4.runtime.misc.NotNull; @@ -15,13 +18,13 @@ public record EpisodeSchema( boolean hasComment, LocalDateTime openedAt ) { - public Episode toEpisode(Webtoon webtoon, String mainUrl, String thumbnailUrl) { + public Episode toEpisode(Webtoon webtoon, List mainUrls, String thumbnailUrl) { return Episode.builder() .no(no) .title(title) .hasComment(hasComment) .openedAt(openedAt) - .mainUrl(mainUrl) + .mainUrl(String.join(URL_SPLITER, mainUrls)) .thumbnailUrl(thumbnailUrl) .webtoon(webtoon) .build(); diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/EpisodeDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/EpisodeDomainService.java index 2f99597..1c727d3 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/EpisodeDomainService.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/EpisodeDomainService.java @@ -39,9 +39,8 @@ public Webtoon readWebtoon(Long webtoonId, Long memberId, int no) { return webtoon; } - public void createEpisode(EpisodeSchema episodeSchema,Webtoon webtoon, String mainUrl, String thumbnailUrl) { - Episode episode = episodeSchema.toEpisode(webtoon,mainUrl,thumbnailUrl); - episodeWriter.write(episode); + public void createEpisode(EpisodeSchema episodeSchema,Webtoon webtoon, List mainUrl, String thumbnailUrl) { + webtoonManger.uploadEpisode(episodeSchema, webtoon,mainUrl,thumbnailUrl); } public EpisodeMainInfo readEpisode(Long episodeId) { diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonManger.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonManger.java index acecf6a..82b7877 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonManger.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonManger.java @@ -2,17 +2,19 @@ import static shop.jtoon.type.ErrorStatus.*; +import java.util.List; + import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import shop.jtoon.exception.DuplicatedException; import shop.jtoon.member.entity.Member; -import shop.jtoon.payment.repository.CookieReader; -import shop.jtoon.payment.service.CookieManager; +import shop.jtoon.webtoon.domain.EpisodeSchema; import shop.jtoon.webtoon.entity.Episode; import shop.jtoon.webtoon.entity.PurchasedEpisode; import shop.jtoon.webtoon.entity.Webtoon; import shop.jtoon.webtoon.repository.EpisodeReader; +import shop.jtoon.webtoon.repository.EpisodeWriter; import shop.jtoon.webtoon.repository.WebtoonReader; @Service @@ -21,7 +23,7 @@ public class WebtoonManger { private final WebtoonReader webtoonReader; private final EpisodeReader episodeReader; - + private final EpisodeWriter episodeWriter; public void validationTitle(String title) { if (webtoonReader.exsistsTitie(title)) { @@ -44,4 +46,9 @@ public void purchase(Episode episode, Member member) { PurchasedEpisode purchasedEpisode = PurchasedEpisode.create(member, episode); episodeReader.save(purchasedEpisode); } + + public void uploadEpisode(EpisodeSchema episodeSchema, Webtoon webtoon, List mainUrls, String thumbnailUrl) { + Episode episode = episodeSchema.toEpisode(webtoon,mainUrls,thumbnailUrl); + episodeWriter.write(episode); + } } diff --git a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/ImageUploadEvent.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/ImageUploadEvent.java new file mode 100644 index 0000000..67429a7 --- /dev/null +++ b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/ImageUploadEvent.java @@ -0,0 +1,13 @@ +package shop.jtoon.dto; + +import org.springframework.web.multipart.MultipartFile; + +import lombok.Builder; + +@Builder +public record ImageUploadEvent( + String key, + MultipartFile multipartFile +) { + +} diff --git a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/MultiImageEvent.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/MultiImageEvent.java new file mode 100644 index 0000000..cf5af5c --- /dev/null +++ b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/MultiImageEvent.java @@ -0,0 +1,12 @@ +package shop.jtoon.dto; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record MultiImageEvent( + List imageUploadEvents +) { + +} diff --git a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java index fe457f9..ace65a4 100644 --- a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java +++ b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java @@ -17,4 +17,11 @@ public record UploadImageDto( public String toKey() { return imageType.getPath(webtoonTitle, fileName.getValue()); } + + public ImageUploadEvent toImageUploadEvent() { + return ImageUploadEvent.builder() + .key(toKey()) + .multipartFile(image) + .build(); + } } diff --git a/jtoon-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java index b5cff1b..c2b10e8 100644 --- a/jtoon-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java +++ b/jtoon-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; +import shop.jtoon.dto.ImageUploadEvent; import shop.jtoon.dto.UploadImageDto; import shop.jtoon.util.S3Manager; @@ -12,9 +13,16 @@ public class S3Service { private final S3Manager s3Manager; + public String uploadUrl(String key) { + return s3Manager.uploadUrl(key); + } + public String uploadImage(UploadImageDto dto) { - String key = dto.toKey(); - return s3Manager.uploadImage(key, dto.image()); + return s3Manager.uploadImage(dto.toKey(), dto.image()); + } + + public String uploadImage(ImageUploadEvent imageUpload) { + return s3Manager.uploadImage(imageUpload.key(), imageUpload.multipartFile()); } public void deleteImage(String imageUrl) { diff --git a/jtoon-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java index e9708fd..fbb86c2 100644 --- a/jtoon-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java +++ b/jtoon-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java @@ -27,6 +27,10 @@ public class S3Manager { @Value("${spring.cloud.aws.cloud-front.url}") private String CLOUD_FRONT_URL; + public String uploadUrl(String key) { + return CLOUD_FRONT_URL + key; + } + public String uploadImage(String key, MultipartFile file) { try { s3Template.upload(