From a98aaf571a3330c192cafcb05d39557de950d1f6 Mon Sep 17 00:00:00 2001 From: parksey Date: Sun, 21 Apr 2024 03:52:37 +0900 Subject: [PATCH 1/3] refactor: Multipartfile -> byte[] --- .../webtoon/application/EpisodeService.java | 7 +------ .../application/WebtoonClientService.java | 14 +------------- .../webtoon/application/WebtoonService.java | 2 +- .../java/shop/jtoon/dto/ImageUploadEvent.java | 4 +--- .../main/java/shop/jtoon/dto/UploadImageDto.java | 16 ++++++++++++---- .../main/java/shop/jtoon/service/S3Service.java | 7 +------ .../src/main/java/shop/jtoon/util/S3Manager.java | 13 +++---------- .../main/java/shop/jtoon/type/ErrorStatus.java | 2 +- 8 files changed, 21 insertions(+), 44 deletions(-) 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 6a846d8..985f758 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 @@ -1,7 +1,6 @@ package shop.jtoon.webtoon.application; import static shop.jtoon.common.ImageType.*; -import static shop.jtoon.type.ErrorStatus.*; import java.util.List; @@ -10,15 +9,11 @@ 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.request.MultiImagesReq; @@ -47,7 +42,7 @@ public void createEpisode( .imageUploadEvents(mainImages.toMultiImageEvent(request, webtoon.getTitle())) .build(); List mainUrls = mainUploadEvents.imageUploadEvents().stream() - .map(webtoonClientService::uploadUrl) + .map(webtoonClientService::parseUrl) .toList(); ImageUploadEvent thumbnailUploadEvent = request.toUploadImageDto( 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 a83180d..71f6189 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 @@ -1,16 +1,10 @@ package shop.jtoon.webtoon.application; - - 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.CreateWebtoonReq; @Service @RequiredArgsConstructor @@ -18,13 +12,7 @@ public class WebtoonClientService { private final S3Service s3Service; - public String upload(ImageType imageType, CreateWebtoonReq request, MultipartFile thumbnailImage) { - UploadImageDto uploadImageDto = request.toUploadImageDto(imageType, thumbnailImage); - - return s3Service.uploadImage(uploadImageDto); - } - - public String uploadUrl(ImageUploadEvent imageUpload) { + public String parseUrl(ImageUploadEvent imageUpload) { return s3Service.uploadUrl(imageUpload.key()); } 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 1bf9e53..afd89e7 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 @@ -30,7 +30,7 @@ public class WebtoonService { public void createWebtoon(Long memberId, MultipartFile thumbnailImage, CreateWebtoonReq request) { ImageUploadEvent imageUploadEvent = request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage) .toImageUploadEvent(); - String thumbnailUrl = webtoonClientService.uploadUrl(imageUploadEvent); + String thumbnailUrl = webtoonClientService.parseUrl(imageUploadEvent); webtoonDomainService.validateDuplicateTitle(request.title()); webtoonDomainService.createWebtoon(memberId, 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 index 67429a7..0e9974f 100644 --- 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 @@ -1,13 +1,11 @@ package shop.jtoon.dto; -import org.springframework.web.multipart.MultipartFile; - import lombok.Builder; @Builder public record ImageUploadEvent( String key, - MultipartFile multipartFile + byte[] data ) { } 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 ace65a4..06048b2 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 @@ -1,10 +1,14 @@ package shop.jtoon.dto; +import java.io.IOException; + import org.springframework.web.multipart.MultipartFile; import lombok.Builder; import shop.jtoon.common.FileName; import shop.jtoon.common.ImageType; +import shop.jtoon.exception.NotFoundException; +import shop.jtoon.type.ErrorStatus; @Builder public record UploadImageDto( @@ -19,9 +23,13 @@ public String toKey() { } public ImageUploadEvent toImageUploadEvent() { - return ImageUploadEvent.builder() - .key(toKey()) - .multipartFile(image) - .build(); + try { + return ImageUploadEvent.builder() + .key(toKey()) + .data(image.getBytes()) + .build(); + } catch (IOException exception) { + throw new NotFoundException(ErrorStatus.DATA_NOT_FOUND); + } } } 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 c2b10e8..6967d0b 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 @@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor; import shop.jtoon.dto.ImageUploadEvent; -import shop.jtoon.dto.UploadImageDto; import shop.jtoon.util.S3Manager; @Service @@ -17,12 +16,8 @@ public String uploadUrl(String key) { return s3Manager.uploadUrl(key); } - public String uploadImage(UploadImageDto dto) { - return s3Manager.uploadImage(dto.toKey(), dto.image()); - } - public String uploadImage(ImageUploadEvent imageUpload) { - return s3Manager.uploadImage(imageUpload.key(), imageUpload.multipartFile()); + return s3Manager.uploadImage(imageUpload.key(), imageUpload.data()); } 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 fbb86c2..13ceedb 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 @@ -1,16 +1,13 @@ package shop.jtoon.util; -import java.io.IOException; +import java.io.ByteArrayInputStream; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; import io.awspring.cloud.s3.ObjectMetadata; import io.awspring.cloud.s3.S3Template; import lombok.RequiredArgsConstructor; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.type.ErrorStatus; @Component @RequiredArgsConstructor @@ -31,19 +28,15 @@ public String uploadUrl(String key) { return CLOUD_FRONT_URL + key; } - public String uploadImage(String key, MultipartFile file) { - try { + public String uploadImage(String key, byte[] file) { s3Template.upload( BUCKET, key, - file.getInputStream(), + new ByteArrayInputStream(file), ObjectMetadata.builder().contentType("image/png").build() ); return CLOUD_FRONT_URL + key; - } catch (IOException e) { - throw new InvalidRequestException(ErrorStatus.S3_UPLOAD_FAIL); - } } public void delete(String objectUrl) { diff --git a/jtoon-system/src/main/java/shop/jtoon/type/ErrorStatus.java b/jtoon-system/src/main/java/shop/jtoon/type/ErrorStatus.java index 479053d..1940f07 100644 --- a/jtoon-system/src/main/java/shop/jtoon/type/ErrorStatus.java +++ b/jtoon-system/src/main/java/shop/jtoon/type/ErrorStatus.java @@ -70,7 +70,7 @@ public enum ErrorStatus { EPISODE_OPENED_AT_IS_NULL("회차 공개일자 값이 NULL 입니다."), S3_UPLOAD_FAIL("S3 이미지 업로드에 실패했습니다."), - ; + DATA_NOT_FOUND("데이터 찾기 실패"); private final String message; } From 6fb9b2822b1dc6bd063400d2d2f8040a979675aa Mon Sep 17 00:00:00 2001 From: parksey Date: Sun, 21 Apr 2024 06:43:50 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20transactional=20outbox=20patter?= =?UTF-8?q?n=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jtoon-core/core-api/build.gradle | 3 ++ .../java/shop/jtoon/event/EventService.java | 34 +++++++++++++ .../webtoon/application/EpisodeService.java | 16 +++---- .../application/WebtoonClientService.java | 6 +-- .../webtoon/application/WebtoonService.java | 15 +++--- .../WebtoonImageUploadEventListener.java | 13 +++-- .../webtoon/request/CreateEpisodeReq.java | 4 -- .../webtoon/request/CreateWebtoonReq.java | 5 -- .../jtoon/webtoon/request/ImageEvent.java | 33 +++++++++++++ .../webtoon/request}/MultiImageEvent.java | 4 +- .../jtoon/webtoon/request/MultiImagesReq.java | 5 +- .../webtoon/request}/UploadImageDto.java | 6 +-- jtoon-core/core-domain/build.gradle | 6 +++ .../shop/jtoon/event/domain/ImagePayload.java | 19 ++++++++ .../shop/jtoon/event/domain/ImagePublish.java | 48 +++++++++++++++++++ .../java/shop/jtoon/event/entity/Event.java | 41 ++++++++++++++++ .../shop/jtoon/event/entity/EventStatus.java | 6 +++ .../jtoon/event/repository/EventReader.java | 20 ++++++++ .../event/repository/EventRepository.java | 9 ++++ .../repository/EventSearchRepository.java | 29 +++++++++++ .../jtoon/event/repository/EventWriter.java | 17 +++++++ .../event/service/EventDomainService.java | 27 +++++++++++ .../webtoon/service/WebtoonDomainService.java | 10 ++-- ...ImageUploadEvent.java => ImageUpload.java} | 2 +- .../java/shop/jtoon/service/S3Service.java | 7 +-- .../main/java/shop/jtoon/util/S3Manager.java | 4 +- 26 files changed, 334 insertions(+), 55 deletions(-) create mode 100644 jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java create mode 100644 jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java rename {jtoon-internal/s3-client/src/main/java/shop/jtoon/dto => jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request}/MultiImageEvent.java (59%) rename {jtoon-internal/s3-client/src/main/java/shop/jtoon/dto => jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request}/UploadImageDto.java (85%) create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePublish.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/EventStatus.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventReader.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventRepository.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventSearchRepository.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java create mode 100644 jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java rename jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/{ImageUploadEvent.java => ImageUpload.java} (73%) diff --git a/jtoon-core/core-api/build.gradle b/jtoon-core/core-api/build.gradle index cfd400a..af066a2 100644 --- a/jtoon-core/core-api/build.gradle +++ b/jtoon-core/core-api/build.gradle @@ -38,6 +38,9 @@ dependencies { // OAuth2 implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + // Jakarta tx + implementation 'org.springframework:spring-tx' + // JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java new file mode 100644 index 0000000..36e177d --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java @@ -0,0 +1,34 @@ +package shop.jtoon.event; + +import java.time.LocalDateTime; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.event.domain.ImagePublish; +import shop.jtoon.event.service.EventDomainService; +import shop.jtoon.webtoon.application.WebtoonClientService; +import shop.jtoon.webtoon.request.ImageEvent; + +@Service +@RequiredArgsConstructor +public class EventService { + + private final EventDomainService eventDomainService; + private final WebtoonClientService webtoonClientService; + + @Scheduled(cron = "0/10 * * * * *") + @Transactional + public void publish() { + LocalDateTime now = LocalDateTime.now(); + + eventDomainService.readRecentEvent(now).stream() + .peek(imagePublish -> + webtoonClientService.upload(ImageEvent.toImageEvent(imagePublish.getImagePayload()).toImageUpload()) + ) + .peek(ImagePublish::updateStatus) + .forEach(eventDomainService::updateEvent); + } +} 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 985f758..1444e02 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 @@ -9,13 +9,13 @@ import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.ImageUploadEvent; -import shop.jtoon.dto.MultiImageEvent; +import shop.jtoon.webtoon.request.MultiImageEvent; import shop.jtoon.webtoon.domain.EpisodeMainInfo; import shop.jtoon.webtoon.domain.EpisodeSchema; import shop.jtoon.webtoon.entity.Webtoon; import shop.jtoon.webtoon.request.CreateEpisodeReq; import shop.jtoon.webtoon.request.GetEpisodesReq; +import shop.jtoon.webtoon.request.ImageEvent; import shop.jtoon.webtoon.request.MultiImagesReq; import shop.jtoon.webtoon.response.EpisodeInfoRes; import shop.jtoon.webtoon.response.EpisodeItemRes; @@ -39,18 +39,18 @@ public void createEpisode( Webtoon webtoon = episodeDomainService.readWebtoon(webtoonId, memberId, request.no()); MultiImageEvent mainUploadEvents = MultiImageEvent.builder() - .imageUploadEvents(mainImages.toMultiImageEvent(request, webtoon.getTitle())) + .imageEvents(mainImages.toMultiImageEvent(request, webtoon.getTitle())) .build(); - List mainUrls = mainUploadEvents.imageUploadEvents().stream() - .map(webtoonClientService::parseUrl) + List mainUrls = mainUploadEvents.imageEvents().stream() + .map(imageEvent -> webtoonClientService.parseUrl(imageEvent.toImageUpload())) .toList(); - ImageUploadEvent thumbnailUploadEvent = request.toUploadImageDto( + ImageEvent thumbnailUploadEvent = request.toUploadImageDto( EPISODE_THUMBNAIL, webtoon.getTitle(), thumbnailImage - ).toImageUploadEvent(); - String thumbnailUrl = webtoonClientService.upload(thumbnailUploadEvent); + ).toImageEvent(); + String thumbnailUrl = webtoonClientService.parseUrl(thumbnailUploadEvent.toImageUpload()); EpisodeSchema episode = request.toEpisodeSchema(); episodeDomainService.createEpisode(episode, webtoon, mainUrls, thumbnailUrl); 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 71f6189..23ff415 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 @@ -3,7 +3,7 @@ import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.ImageUploadEvent; +import shop.jtoon.dto.ImageUpload; import shop.jtoon.service.S3Service; @Service @@ -12,11 +12,11 @@ public class WebtoonClientService { private final S3Service s3Service; - public String parseUrl(ImageUploadEvent imageUpload) { + public String parseUrl(ImageUpload imageUpload) { return s3Service.uploadUrl(imageUpload.key()); } - public String upload(ImageUploadEvent imageUpload) { + public ImageUpload upload(ImageUpload imageUpload) { return s3Service.uploadImage(imageUpload); } 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 afd89e7..096060c 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 @@ -5,16 +5,15 @@ 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.webtoon.domain.WebtoonDetail; import shop.jtoon.webtoon.entity.enums.DayOfWeek; import shop.jtoon.webtoon.request.CreateWebtoonReq; import shop.jtoon.webtoon.request.GetWebtoonsReq; +import shop.jtoon.webtoon.request.ImageEvent; import shop.jtoon.webtoon.response.WebtoonInfoRes; import shop.jtoon.webtoon.response.WebtoonItemRes; import shop.jtoon.webtoon.service.WebtoonDomainService; @@ -25,20 +24,18 @@ 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.parseUrl(imageUploadEvent); + ImageEvent imageEvent = request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage) + .toImageEvent(); + String thumbnailUrl = webtoonClientService.parseUrl(imageEvent.toImageUpload()); webtoonDomainService.validateDuplicateTitle(request.title()); webtoonDomainService.createWebtoon(memberId, request.toWebtoonInfo(thumbnailUrl), request.toWebtoonGenres(), - request.toWebtoonDayOfWeeks()); - - publisher.publishEvent(imageUploadEvent); + request.toWebtoonDayOfWeeks(), + imageEvent.toImagePayload()); } public Map> getWebtoons(GetWebtoonsReq request) { 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 index 120d0ce..dc2bb36 100644 --- 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 @@ -3,10 +3,10 @@ import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.ImageUploadEvent; -import shop.jtoon.dto.MultiImageEvent; +import shop.jtoon.dto.ImageUpload; import shop.jtoon.global.util.AsyncEventListener; import shop.jtoon.webtoon.application.WebtoonClientService; +import shop.jtoon.webtoon.request.MultiImageEvent; @Component @RequiredArgsConstructor @@ -15,15 +15,14 @@ public class WebtoonImageUploadEventListener { private final WebtoonClientService webtoonClientService; @AsyncEventListener - public void uploadImage(ImageUploadEvent imageUploadEvent) { - webtoonClientService.upload(imageUploadEvent); + public void uploadImage(ImageUpload imageUpload) { + webtoonClientService.upload(imageUpload); } @AsyncEventListener public void uploadMultiImages(MultiImageEvent multiImageEvent) { - multiImageEvent.imageUploadEvents().stream() + multiImageEvent.imageEvents().stream() .parallel() - .forEach(webtoonClientService::upload); + .forEach(imageEvent -> webtoonClientService.upload(imageEvent.toImageUpload())); } - } 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 ede1d59..34469c4 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,11 +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; -import shop.jtoon.webtoon.entity.Webtoon; @Builder public record CreateEpisodeReq( diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java index e6f52b8..62917c3 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java @@ -1,6 +1,5 @@ package shop.jtoon.webtoon.request; -import java.util.List; import java.util.Set; import org.springframework.web.multipart.MultipartFile; @@ -11,13 +10,9 @@ import lombok.Builder; import shop.jtoon.common.FileName; import shop.jtoon.common.ImageType; -import shop.jtoon.dto.UploadImageDto; import shop.jtoon.webtoon.domain.WebtoonDayOfWeeks; import shop.jtoon.webtoon.domain.WebtoonGenres; import shop.jtoon.webtoon.domain.WebtoonInfo; -import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; -import shop.jtoon.webtoon.entity.GenreWebtoon; -import shop.jtoon.webtoon.entity.Webtoon; import shop.jtoon.webtoon.entity.enums.AgeLimit; import shop.jtoon.webtoon.entity.enums.DayOfWeek; import shop.jtoon.webtoon.entity.enums.Genre; diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java new file mode 100644 index 0000000..39eb58f --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java @@ -0,0 +1,33 @@ +package shop.jtoon.webtoon.request; + +import lombok.Builder; +import shop.jtoon.dto.ImageUpload; +import shop.jtoon.event.domain.ImagePayload; + +@Builder +public record ImageEvent( + String key, + byte[] data +) { + + public ImageUpload toImageUpload() { + return ImageUpload.builder() + .key(key) + .data(data) + .build(); + } + + public ImagePayload toImagePayload() { + return ImagePayload.builder() + .key(key) + .data(data) + .build(); + } + + public static ImageEvent toImageEvent(ImagePayload imagePayload) { + return ImageEvent.builder() + .key(imagePayload.key()) + .data(imagePayload.data()) + .build(); + } +} diff --git a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/MultiImageEvent.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/MultiImageEvent.java similarity index 59% rename from jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/MultiImageEvent.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/MultiImageEvent.java index cf5af5c..52b3316 100644 --- a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/MultiImageEvent.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/MultiImageEvent.java @@ -1,4 +1,4 @@ -package shop.jtoon.dto; +package shop.jtoon.webtoon.request; import java.util.List; @@ -6,7 +6,7 @@ @Builder public record MultiImageEvent( - List imageUploadEvents + List imageEvents ) { } diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/MultiImagesReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/MultiImagesReq.java index 4bd938e..568b9be 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/MultiImagesReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/MultiImagesReq.java @@ -7,16 +7,15 @@ import org.springframework.web.multipart.MultipartFile; import lombok.Builder; -import shop.jtoon.dto.ImageUploadEvent; @Builder public record MultiImagesReq( List mainImages ) { - public List toMultiImageEvent(CreateEpisodeReq request, String webtoonTitle) { + public List toMultiImageEvent(CreateEpisodeReq request, String webtoonTitle) { return mainImages.stream() - .map(mainImage -> request.toUploadImageDto(EPISODE_MAIN, webtoonTitle, mainImage).toImageUploadEvent()) + .map(mainImage -> request.toUploadImageDto(EPISODE_MAIN, webtoonTitle, mainImage).toImageEvent()) .toList(); } } diff --git a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/UploadImageDto.java similarity index 85% rename from jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/UploadImageDto.java index 06048b2..507b15f 100644 --- a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/UploadImageDto.java @@ -1,4 +1,4 @@ -package shop.jtoon.dto; +package shop.jtoon.webtoon.request; import java.io.IOException; @@ -22,9 +22,9 @@ public String toKey() { return imageType.getPath(webtoonTitle, fileName.getValue()); } - public ImageUploadEvent toImageUploadEvent() { + public ImageEvent toImageEvent() { try { - return ImageUploadEvent.builder() + return ImageEvent.builder() .key(toKey()) .data(image.getBytes()) .build(); diff --git a/jtoon-core/core-domain/build.gradle b/jtoon-core/core-domain/build.gradle index 1a777e9..6dda275 100644 --- a/jtoon-core/core-domain/build.gradle +++ b/jtoon-core/core-domain/build.gradle @@ -26,6 +26,12 @@ dependencies { // JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // Hibernate type parser + implementation 'io.hypersistence:hypersistence-utils-hibernate-60:3.3.1' + + // JSON + implementation 'com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:2.16.1' + // H2 implementation 'com.h2database:h2' diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java new file mode 100644 index 0000000..324f0a2 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java @@ -0,0 +1,19 @@ +package shop.jtoon.event.domain; + +import lombok.Builder; +import shop.jtoon.event.entity.Event; +import shop.jtoon.event.entity.EventStatus; + +@Builder +public record ImagePayload( + String key, + byte[] data +) { + + public Event toEvent() { + return Event.builder() + .status(EventStatus.READY) + .payload(this) + .build(); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePublish.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePublish.java new file mode 100644 index 0000000..85a56bf --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePublish.java @@ -0,0 +1,48 @@ +package shop.jtoon.event.domain; + +import java.time.LocalDateTime; +import java.util.List; + +import lombok.Builder; +import lombok.Getter; +import shop.jtoon.event.entity.Event; +import shop.jtoon.event.entity.EventStatus; + +@Getter +public class ImagePublish { + private Long id; + private LocalDateTime publishDate; + private ImagePayload imagePayload; + private EventStatus eventStatus; + + @Builder + public ImagePublish(ImagePayload imagePayload, EventStatus eventStatus, Long id, LocalDateTime publishDate) { + this.id = id; + this.imagePayload = imagePayload; + this.eventStatus = eventStatus; + this.publishDate = publishDate; + } + + public static Event toEvent(ImagePublish imagePublish) { + return Event.builder() + .id(imagePublish.id) + .status(imagePublish.eventStatus) + .payload(imagePublish.imagePayload) + .build(); + } + + public static List toImagePublishes(List events) { + return events.stream() + .map(event -> ImagePublish.builder() + .id(event.getId()) + .eventStatus(event.getStatus()) + .imagePayload(event.getPayload()) + .publishDate(event.getCreatedAt()) + .build()) + .toList(); + } + + public void updateStatus() { + this.eventStatus = EventStatus.OK; + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java new file mode 100644 index 0000000..8f7e824 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java @@ -0,0 +1,41 @@ +package shop.jtoon.event.entity; + +import org.hibernate.annotations.Type; + +import io.hypersistence.utils.hibernate.type.json.JsonType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import shop.jtoon.common.BaseTimeEntity; +import shop.jtoon.event.domain.ImagePayload; + +@Getter +@Entity +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class Event extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_id") + private Long id; + + @Enumerated(EnumType.STRING) + private EventStatus status; + + @Type(JsonType.class) + private ImagePayload payload; + + @Builder + public Event(Long id, EventStatus status, ImagePayload payload) { + this.status = status; + this.payload = payload; + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/EventStatus.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/EventStatus.java new file mode 100644 index 0000000..ba6df80 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/EventStatus.java @@ -0,0 +1,6 @@ +package shop.jtoon.event.entity; + +public enum EventStatus { + READY, + OK +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventReader.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventReader.java new file mode 100644 index 0000000..f7588e2 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventReader.java @@ -0,0 +1,20 @@ +package shop.jtoon.event.repository; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.event.entity.Event; + +@Repository +@RequiredArgsConstructor +public class EventReader { + + private final EventSearchRepository eventSearchRepository; + + public List readRecentEvent(LocalDateTime now) { + return eventSearchRepository.findByCreateAtBefore(now); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventRepository.java new file mode 100644 index 0000000..a7a4d87 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventRepository.java @@ -0,0 +1,9 @@ +package shop.jtoon.event.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import shop.jtoon.event.entity.Event; + +public interface EventRepository extends JpaRepository { + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventSearchRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventSearchRepository.java new file mode 100644 index 0000000..00d70e8 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventSearchRepository.java @@ -0,0 +1,29 @@ +package shop.jtoon.event.repository; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.event.entity.Event; +import shop.jtoon.event.entity.EventStatus; +import shop.jtoon.event.entity.QEvent; + +@Repository +@RequiredArgsConstructor +public class EventSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findByCreateAtBefore(LocalDateTime now) { + return jpaQueryFactory.selectFrom(QEvent.event) + .where( + QEvent.event.createdAt.before(now), + QEvent.event.status.eq(EventStatus.READY) + ) + .fetch(); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java new file mode 100644 index 0000000..56f1179 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java @@ -0,0 +1,17 @@ +package shop.jtoon.event.repository; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.event.entity.Event; + +@Repository +@RequiredArgsConstructor +public class EventWriter { + + private final EventRepository eventRepository; + + public void write(Event event) { + eventRepository.save(event); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java new file mode 100644 index 0000000..3a66876 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java @@ -0,0 +1,27 @@ +package shop.jtoon.event.service; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.event.domain.ImagePublish; +import shop.jtoon.event.repository.EventReader; +import shop.jtoon.event.repository.EventWriter; + +@Service +@RequiredArgsConstructor +public class EventDomainService { + + private final EventWriter eventWriter; + private final EventReader eventReader; + + public List readRecentEvent(LocalDateTime now) { + return ImagePublish.toImagePublishes(eventReader.readRecentEvent(now)); + } + + public void updateEvent(ImagePublish imagePublish) { + eventWriter.write(ImagePublish.toEvent(imagePublish)); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java index 6adf7a8..58367b8 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java @@ -1,7 +1,6 @@ package shop.jtoon.webtoon.service; import static java.util.stream.Collectors.*; -import static shop.jtoon.type.ErrorStatus.*; import java.util.List; import java.util.Map; @@ -10,15 +9,16 @@ import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; -import shop.jtoon.exception.NotFoundException; +import shop.jtoon.event.domain.ImagePayload; +import shop.jtoon.event.repository.EventWriter; import shop.jtoon.member.entity.Member; import shop.jtoon.member.repository.MemberReader; import shop.jtoon.webtoon.domain.SearchWebtoon; import shop.jtoon.webtoon.domain.WebtoonDayOfWeeks; import shop.jtoon.webtoon.domain.WebtoonDetail; -import shop.jtoon.webtoon.domain.WebtoonSchema; import shop.jtoon.webtoon.domain.WebtoonGenres; import shop.jtoon.webtoon.domain.WebtoonInfo; +import shop.jtoon.webtoon.domain.WebtoonSchema; import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; import shop.jtoon.webtoon.entity.GenreWebtoon; import shop.jtoon.webtoon.entity.Webtoon; @@ -36,13 +36,14 @@ public class WebtoonDomainService { private final MemberReader memberReader; private final WebtoonWriter webtoonWriter; private final WebtoonReader webtoonReader; + private final EventWriter eventWriter; public void validateDuplicateTitle(String title) { webtoonManger.validationTitle(title); } @Transactional - public void createWebtoon(Long memberId, WebtoonInfo info, WebtoonGenres genres, WebtoonDayOfWeeks dayOfWeeks) { + public void createWebtoon(Long memberId, WebtoonInfo info, WebtoonGenres genres, WebtoonDayOfWeeks dayOfWeeks, ImagePayload imagePayload) { Member member = memberReader.read(memberId); Webtoon webtoon = info.toWebtoonEntity(member); @@ -50,6 +51,7 @@ public void createWebtoon(Long memberId, WebtoonInfo info, WebtoonGenres genres, List genreWebtoons = genres.toGenreWebtoonEntity(webtoon); webtoonWriter.createWebtoon(webtoon, dayOfWeekWebtoons, genreWebtoons); + eventWriter.write(imagePayload.toEvent()); } public Map> readWebtoons(SearchWebtoon search) { 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/ImageUpload.java similarity index 73% rename from jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/ImageUploadEvent.java rename to jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/ImageUpload.java index 0e9974f..7966cb0 100644 --- a/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/ImageUploadEvent.java +++ b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/ImageUpload.java @@ -3,7 +3,7 @@ import lombok.Builder; @Builder -public record ImageUploadEvent( +public record ImageUpload( String key, byte[] data ) { 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 6967d0b..d5f8e92 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,7 +3,7 @@ import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.ImageUploadEvent; +import shop.jtoon.dto.ImageUpload; import shop.jtoon.util.S3Manager; @Service @@ -16,8 +16,9 @@ public String uploadUrl(String key) { return s3Manager.uploadUrl(key); } - public String uploadImage(ImageUploadEvent imageUpload) { - return s3Manager.uploadImage(imageUpload.key(), imageUpload.data()); + public ImageUpload uploadImage(ImageUpload imageUpload) { + s3Manager.uploadImage(imageUpload.key(), imageUpload.data()); + return imageUpload; } 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 13ceedb..beb3e45 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 @@ -28,15 +28,13 @@ public String uploadUrl(String key) { return CLOUD_FRONT_URL + key; } - public String uploadImage(String key, byte[] file) { + public void uploadImage(String key, byte[] file) { s3Template.upload( BUCKET, key, new ByteArrayInputStream(file), ObjectMetadata.builder().contentType("image/png").build() ); - - return CLOUD_FRONT_URL + key; } public void delete(String objectUrl) { From 7954ac1c5723dc9edd8998257b97624e2418427a Mon Sep 17 00:00:00 2001 From: parksey Date: Mon, 22 Apr 2024 13:37:14 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20Redis=EB=A5=BC=20message=20queu?= =?UTF-8?q?e=EB=A1=9C=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/shop/jtoon/event/EventService.java | 52 ++++++++-- .../webtoon/application/WebtoonService.java | 8 +- .../jtoon/webtoon/request/ImageEvent.java | 36 ++++++- .../shop/jtoon/event/domain/ImagePayload.java | 8 -- .../java/shop/jtoon/event/entity/Event.java | 1 + .../jtoon/event/repository/EventWriter.java | 9 ++ .../event/service/EventDomainService.java | 6 +- .../shop/jtoon/webtoon/entity/Webtoon.java | 8 ++ .../webtoon/repository/WebtoonReader.java | 3 +- .../webtoon/repository/WebtoonRepository.java | 16 ++++ .../repository/WebtoonSearchRepository.java | 2 + .../webtoon/repository/WebtoonWriter.java | 4 + .../webtoon/service/WebtoonDomainService.java | 15 ++- .../test/java/shop/jtoon/ApplicationTest.java | 8 ++ .../jtoon/webtoon/WebtoonServiceTest.java | 94 +++++++++++++++++++ .../src/test/resources/application-test.yml | 34 +++++++ .../java/shop/jtoon/dto/ImagePublishData.java | 12 +++ .../jtoon/repository/ListRedisRepository.java | 41 ++++++++ .../shop/jtoon/service/EventRedisService.java | 31 ++++++ 19 files changed, 359 insertions(+), 29 deletions(-) create mode 100644 jtoon-core/core-domain/src/test/java/shop/jtoon/ApplicationTest.java create mode 100644 jtoon-core/core-domain/src/test/java/shop/jtoon/webtoon/WebtoonServiceTest.java create mode 100644 jtoon-core/core-domain/src/test/resources/application-test.yml create mode 100644 jtoon-db/db-redis/src/main/java/shop/jtoon/dto/ImagePublishData.java create mode 100644 jtoon-db/db-redis/src/main/java/shop/jtoon/repository/ListRedisRepository.java create mode 100644 jtoon-db/db-redis/src/main/java/shop/jtoon/service/EventRedisService.java diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java index 36e177d..89db87b 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/event/EventService.java @@ -1,16 +1,22 @@ package shop.jtoon.event; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; +import shop.jtoon.dto.ImagePublishData; import shop.jtoon.event.domain.ImagePublish; import shop.jtoon.event.service.EventDomainService; +import shop.jtoon.service.EventRedisService; import shop.jtoon.webtoon.application.WebtoonClientService; import shop.jtoon.webtoon.request.ImageEvent; +import shop.jtoon.webtoon.service.WebtoonDomainService; @Service @RequiredArgsConstructor @@ -18,17 +24,51 @@ public class EventService { private final EventDomainService eventDomainService; private final WebtoonClientService webtoonClientService; + private final EventRedisService eventRedisService; + private final WebtoonDomainService webtoonDomainService; @Scheduled(cron = "0/10 * * * * *") @Transactional public void publish() { LocalDateTime now = LocalDateTime.now(); - eventDomainService.readRecentEvent(now).stream() - .peek(imagePublish -> - webtoonClientService.upload(ImageEvent.toImageEvent(imagePublish.getImagePayload()).toImageUpload()) - ) - .peek(ImagePublish::updateStatus) - .forEach(eventDomainService::updateEvent); + List publishes = eventDomainService.readRecentEvent(now).stream() + .map(imagePublish -> { + webtoonClientService.upload(ImageEvent.toImageEvent(imagePublish.getImagePayload()).toImageUpload()); + imagePublish.updateStatus(); + + return imagePublish; + }) + .toList(); + + eventDomainService.update(publishes); + } + + @Scheduled(cron = "0/10 * * * * *") + @Transactional + public void eventExecute() { + List webtoonIds = new ArrayList<>(); + List publishes = eventRedisService.consume().stream() + .parallel() + .map(imagePublishData -> updateEvent(imagePublishData, webtoonIds)) + .filter(Objects::nonNull) + .map(ImageEvent::toImagePublish) + .toList(); + + eventDomainService.update(publishes); + webtoonDomainService.updateWebtoonStatus(webtoonIds); + } + + private ImageEvent updateEvent(ImagePublishData imagePublishData, List wetoonIds) { + ImageEvent imageEvent = ImageEvent.toImageEvent(imagePublishData); + + try { + webtoonClientService.upload(imageEvent.toImageUpload()); + wetoonIds.add(imagePublishData.id()); + + return null; + } catch (Exception e) { + return imageEvent; + } } } 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 096060c..658cfef 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 @@ -9,6 +9,7 @@ import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; +import shop.jtoon.service.EventRedisService; import shop.jtoon.webtoon.domain.WebtoonDetail; import shop.jtoon.webtoon.entity.enums.DayOfWeek; import shop.jtoon.webtoon.request.CreateWebtoonReq; @@ -24,6 +25,7 @@ public class WebtoonService { private final WebtoonClientService webtoonClientService; private final WebtoonDomainService webtoonDomainService; + private final EventRedisService eventRedisService; public void createWebtoon(Long memberId, MultipartFile thumbnailImage, CreateWebtoonReq request) { ImageEvent imageEvent = request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage) @@ -31,11 +33,11 @@ public void createWebtoon(Long memberId, MultipartFile thumbnailImage, CreateWeb String thumbnailUrl = webtoonClientService.parseUrl(imageEvent.toImageUpload()); webtoonDomainService.validateDuplicateTitle(request.title()); - webtoonDomainService.createWebtoon(memberId, + Long webtoonId = webtoonDomainService.createWebtoon(memberId, request.toWebtoonInfo(thumbnailUrl), request.toWebtoonGenres(), - request.toWebtoonDayOfWeeks(), - imageEvent.toImagePayload()); + request.toWebtoonDayOfWeeks()); + eventRedisService.publish(imageEvent.imagePublishData(webtoonId)); } public Map> getWebtoons(GetWebtoonsReq request) { diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java index 39eb58f..b6c8d4d 100644 --- a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/ImageEvent.java @@ -1,8 +1,13 @@ package shop.jtoon.webtoon.request; +import java.time.LocalDateTime; + import lombok.Builder; +import shop.jtoon.dto.ImagePublishData; import shop.jtoon.dto.ImageUpload; import shop.jtoon.event.domain.ImagePayload; +import shop.jtoon.event.domain.ImagePublish; +import shop.jtoon.event.entity.EventStatus; @Builder public record ImageEvent( @@ -10,6 +15,20 @@ public record ImageEvent( byte[] data ) { + public static ImageEvent toImageEvent(ImagePayload imagePayload) { + return ImageEvent.builder() + .key(imagePayload.key()) + .data(imagePayload.data()) + .build(); + } + + public static ImageEvent toImageEvent(ImagePublishData imagePublishData) { + return ImageEvent.builder() + .key(imagePublishData.key()) + .data(imagePublishData.data()) + .build(); + } + public ImageUpload toImageUpload() { return ImageUpload.builder() .key(key) @@ -24,10 +43,19 @@ public ImagePayload toImagePayload() { .build(); } - public static ImageEvent toImageEvent(ImagePayload imagePayload) { - return ImageEvent.builder() - .key(imagePayload.key()) - .data(imagePayload.data()) + public ImagePublish toImagePublish() { + return ImagePublish.builder() + .eventStatus(EventStatus.READY) + .imagePayload(this.toImagePayload()) + .publishDate(LocalDateTime.now()) + .build(); + } + + public ImagePublishData imagePublishData(Long id) { + return ImagePublishData.builder() + .id(id) + .key(key) + .data(data) .build(); } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java index 324f0a2..d554605 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/domain/ImagePayload.java @@ -1,8 +1,6 @@ package shop.jtoon.event.domain; import lombok.Builder; -import shop.jtoon.event.entity.Event; -import shop.jtoon.event.entity.EventStatus; @Builder public record ImagePayload( @@ -10,10 +8,4 @@ public record ImagePayload( byte[] data ) { - public Event toEvent() { - return Event.builder() - .status(EventStatus.READY) - .payload(this) - .build(); - } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java index 8f7e824..97eb82d 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/entity/Event.java @@ -31,6 +31,7 @@ public class Event extends BaseTimeEntity { private EventStatus status; @Type(JsonType.class) + @Column(columnDefinition = "json") private ImagePayload payload; @Builder diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java index 56f1179..25fc522 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/repository/EventWriter.java @@ -1,8 +1,11 @@ package shop.jtoon.event.repository; +import java.util.List; + import org.springframework.stereotype.Repository; import lombok.RequiredArgsConstructor; +import shop.jtoon.event.domain.ImagePublish; import shop.jtoon.event.entity.Event; @Repository @@ -14,4 +17,10 @@ public class EventWriter { public void write(Event event) { eventRepository.save(event); } + + public void writeEvnets(List publishes) { + eventRepository.saveAll(publishes.stream() + .map(ImagePublish::toEvent) + .toList()); + } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java index 3a66876..6666b32 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/event/service/EventDomainService.java @@ -4,6 +4,7 @@ import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import shop.jtoon.event.domain.ImagePublish; @@ -21,7 +22,8 @@ public List readRecentEvent(LocalDateTime now) { return ImagePublish.toImagePublishes(eventReader.readRecentEvent(now)); } - public void updateEvent(ImagePublish imagePublish) { - eventWriter.write(ImagePublish.toEvent(imagePublish)); + @Transactional + public void update(List publishes) { + eventWriter.writeEvnets(publishes); } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Webtoon.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Webtoon.java index 61daa8d..0372e92 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Webtoon.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Webtoon.java @@ -5,6 +5,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -16,6 +18,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import shop.jtoon.event.entity.EventStatus; import shop.jtoon.exception.InvalidRequestException; import shop.jtoon.common.BaseTimeEntity; import shop.jtoon.member.entity.Member; @@ -54,6 +57,10 @@ public class Webtoon extends BaseTimeEntity { @JoinColumn(name = "author_id", nullable = false) private Member author; + @Enumerated(EnumType.STRING) + @Column(name = "status") + private EventStatus eventStatus; + @Builder private Webtoon( String title, @@ -69,6 +76,7 @@ private Webtoon( this.thumbnailUrl = requireNonNull(thumbnailUrl, WEBTOON_THUMBNAIL_URL_IS_NULL.getMessage()); this.cookieCount = validateCookieCount(cookieCount); this.author = requireNonNull(author, WEBTOON_AUTHOR_IS_NULL.getMessage()); + this.eventStatus = EventStatus.READY; } public void validateAuthor(Long memberId) { diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonReader.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonReader.java index c89d6b8..75c75c7 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonReader.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonReader.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository; import lombok.RequiredArgsConstructor; +import shop.jtoon.event.entity.EventStatus; import shop.jtoon.exception.NotFoundException; import shop.jtoon.webtoon.domain.SearchWebtoon; import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; @@ -28,7 +29,7 @@ public List search(SearchWebtoon search) { } public Webtoon read(Long webtoonId) { - return webtoonRepository.findById(webtoonId).orElseThrow(() -> new NotFoundException(WEBTOON_NOT_FOUND)); + return webtoonRepository.findByIdAndEventStatus(webtoonId, EventStatus.OK).orElseThrow(() -> new NotFoundException(WEBTOON_NOT_FOUND)); } public List readDayOfWebtoon(Webtoon webtoon) { diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonRepository.java index 4a480d7..abcd7c5 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonRepository.java @@ -1,10 +1,26 @@ package shop.jtoon.webtoon.repository; +import java.util.List; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import shop.jtoon.event.entity.EventStatus; import shop.jtoon.webtoon.entity.Webtoon; public interface WebtoonRepository extends JpaRepository { boolean existsByTitle(String title); + + Optional findByIdAndEventStatus(Long webtoonId, EventStatus eventStatus); + + @Modifying + @Query("update Webtoon set eventStatus = 'OK' where id = :webtoonId") + void updateStatus(Long webtoonId); + + @Modifying + @Query("update Webtoon set eventStatus = 'OK' where id in (:webtoonIds)") + void updateStatus(List webtoonIds); } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonSearchRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonSearchRepository.java index 12f3208..38b5141 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonSearchRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonSearchRepository.java @@ -8,6 +8,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; +import shop.jtoon.event.entity.EventStatus; import shop.jtoon.member.entity.QMember; import shop.jtoon.util.DynamicQuery; import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; @@ -26,6 +27,7 @@ public List findWebtoons(DayOfWeek dayOfWeek, String keyword) .join(QDayOfWeekWebtoon.dayOfWeekWebtoon.webtoon, QWebtoon.webtoon) .join(QWebtoon.webtoon.author, QMember.member) .where( + QWebtoon.webtoon.eventStatus.eq(EventStatus.OK), DynamicQuery.generateEq(dayOfWeek, QDayOfWeekWebtoon.dayOfWeekWebtoon.dayOfWeek::eq), DynamicQuery.generateEq(keyword, this::containsKeyword) ) diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonWriter.java index f5e31a7..8291a6f 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonWriter.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonWriter.java @@ -23,4 +23,8 @@ public void createWebtoon(Webtoon webtoon, List dayOfWeekWebto dayOfWeekWebtoonRepository.saveAll(dayOfWeekWebtoons); genreWebtoonRepository.saveAll(genreWebtoons); } + + public void update(List webtoonIds) { + webtoonRepository.updateStatus(webtoonIds); + } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java index 58367b8..5d8ace5 100644 --- a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java @@ -9,8 +9,7 @@ import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; -import shop.jtoon.event.domain.ImagePayload; -import shop.jtoon.event.repository.EventWriter; +import lombok.extern.slf4j.Slf4j; import shop.jtoon.member.entity.Member; import shop.jtoon.member.repository.MemberReader; import shop.jtoon.webtoon.domain.SearchWebtoon; @@ -27,6 +26,7 @@ import shop.jtoon.webtoon.repository.WebtoonReader; import shop.jtoon.webtoon.repository.WebtoonWriter; +@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -36,14 +36,13 @@ public class WebtoonDomainService { private final MemberReader memberReader; private final WebtoonWriter webtoonWriter; private final WebtoonReader webtoonReader; - private final EventWriter eventWriter; public void validateDuplicateTitle(String title) { webtoonManger.validationTitle(title); } @Transactional - public void createWebtoon(Long memberId, WebtoonInfo info, WebtoonGenres genres, WebtoonDayOfWeeks dayOfWeeks, ImagePayload imagePayload) { + public Long createWebtoon(Long memberId, WebtoonInfo info, WebtoonGenres genres, WebtoonDayOfWeeks dayOfWeeks) { Member member = memberReader.read(memberId); Webtoon webtoon = info.toWebtoonEntity(member); @@ -51,7 +50,8 @@ public void createWebtoon(Long memberId, WebtoonInfo info, WebtoonGenres genres, List genreWebtoons = genres.toGenreWebtoonEntity(webtoon); webtoonWriter.createWebtoon(webtoon, dayOfWeekWebtoons, genreWebtoons); - eventWriter.write(imagePayload.toEvent()); + + return webtoon.getId(); } public Map> readWebtoons(SearchWebtoon search) { @@ -67,4 +67,9 @@ public WebtoonDetail readWebtoonDetail(Long webtoonId) { return WebtoonDetail.of(webtoon, dayOfWeeks, genres); } + + @Transactional + public void updateWebtoonStatus(List webtoonIds) { + webtoonWriter.update(webtoonIds); + } } diff --git a/jtoon-core/core-domain/src/test/java/shop/jtoon/ApplicationTest.java b/jtoon-core/core-domain/src/test/java/shop/jtoon/ApplicationTest.java new file mode 100644 index 0000000..86bd9f5 --- /dev/null +++ b/jtoon-core/core-domain/src/test/java/shop/jtoon/ApplicationTest.java @@ -0,0 +1,8 @@ +package shop.jtoon; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ApplicationTest { + +} diff --git a/jtoon-core/core-domain/src/test/java/shop/jtoon/webtoon/WebtoonServiceTest.java b/jtoon-core/core-domain/src/test/java/shop/jtoon/webtoon/WebtoonServiceTest.java new file mode 100644 index 0000000..71e3a86 --- /dev/null +++ b/jtoon-core/core-domain/src/test/java/shop/jtoon/webtoon/WebtoonServiceTest.java @@ -0,0 +1,94 @@ +package shop.jtoon.webtoon; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +import shop.jtoon.member.entity.Gender; +import shop.jtoon.member.entity.LoginType; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.entity.Role; +import shop.jtoon.member.repository.MemberRepository; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.AgeLimit; +import shop.jtoon.webtoon.repository.DayOfWeekWebtoonRepository; +import shop.jtoon.webtoon.repository.GenreWebtoonRepository; +import shop.jtoon.webtoon.repository.WebtoonRepository; +import shop.jtoon.webtoon.repository.WebtoonWriter; +import shop.jtoon.webtoon.service.WebtoonDomainService; + +@EnableJpaAuditing +@DataJpaTest +class WebtoonServiceTest { + + @Autowired + WebtoonRepository webtoonRepository; + + @Autowired + MemberRepository memberRepository; + + @Autowired + DayOfWeekWebtoonRepository dayOfWeekWebtoonRepository; + + @Autowired + GenreWebtoonRepository genreWebtoonRepository; + + WebtoonDomainService webtoonDomainService; + + @BeforeEach + void init() { + WebtoonWriter webtoonWriter = new WebtoonWriter(webtoonRepository, dayOfWeekWebtoonRepository, + genreWebtoonRepository); + webtoonDomainService = new WebtoonDomainService(null, null, webtoonWriter, null); + } + + @Test + void update_bulk_query() { + + List members = new ArrayList<>(); + List webtoons = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + members.add(memberFixture()); + webtoons.add(webtoonFixture(members.get(i))); + } + + memberRepository.saveAll(members); + memberRepository.flush(); + webtoonRepository.saveAll(webtoons); + webtoonRepository.flush(); + + webtoonDomainService.updateWebtoonStatus(webtoons.stream().map(Webtoon::getId).toList()); + webtoonRepository.flush(); + + } + + Webtoon webtoonFixture(Member member) { + return Webtoon.builder() + .title(UUID.randomUUID().toString().substring(0, 10)) + .description("description") + .ageLimit(AgeLimit.AGE_12) + .thumbnailUrl("thumbnailUrl") + .cookieCount(4) + .author(member) + .build(); + } + + Member memberFixture() { + return Member.builder() + .name("n") + .role(Role.USER) + .phone(UUID.randomUUID().toString().substring(0, 10)) + .email(UUID.randomUUID().toString().substring(0, 10)) + .nickname(UUID.randomUUID().toString().substring(0, 10)) + .loginType(LoginType.KAKAO) + .password("1234") + .gender(Gender.MALE) + .build(); + } +} diff --git a/jtoon-core/core-domain/src/test/resources/application-test.yml b/jtoon-core/core-domain/src/test/resources/application-test.yml new file mode 100644 index 0000000..21c5766 --- /dev/null +++ b/jtoon-core/core-domain/src/test/resources/application-test.yml @@ -0,0 +1,34 @@ +spring: + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: +# format_sql: true +# show_sql: true + dialect: org.hibernate.dialect.MySQL5InnoDBDialect + use_sql_comments: true + jdbc: + batch_size: 50 + + datasource: + url: jdbc:h2:~/jtoon;MODE=MYSQL;DB_CLOSE_ON_EXIT=FALSE;maxQuerySizeToLog=999999; + username: sa + password: + driver-class-name: org.h2.Driver + hikari: + data-source-properties: + rewriteBatchedStatements: true + profileSQL: true + logger: Slf4JLogger + + +logging: + level: + org: + hibernate: + type: + descriptor: + sql: trace + +logging.level.org.springframework.transaction.interceptor: trace diff --git a/jtoon-db/db-redis/src/main/java/shop/jtoon/dto/ImagePublishData.java b/jtoon-db/db-redis/src/main/java/shop/jtoon/dto/ImagePublishData.java new file mode 100644 index 0000000..8072f03 --- /dev/null +++ b/jtoon-db/db-redis/src/main/java/shop/jtoon/dto/ImagePublishData.java @@ -0,0 +1,12 @@ +package shop.jtoon.dto; + +import lombok.Builder; + +@Builder +public record ImagePublishData( + Long id, + String key, + byte[] data +) { + +} diff --git a/jtoon-db/db-redis/src/main/java/shop/jtoon/repository/ListRedisRepository.java b/jtoon-db/db-redis/src/main/java/shop/jtoon/repository/ListRedisRepository.java new file mode 100644 index 0000000..656ddfe --- /dev/null +++ b/jtoon-db/db-redis/src/main/java/shop/jtoon/repository/ListRedisRepository.java @@ -0,0 +1,41 @@ +package shop.jtoon.repository; + +import java.time.Duration; +import java.util.Date; +import java.util.List; + +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.hash.Jackson2HashMapper; +import org.springframework.stereotype.Repository; + +@Repository +public class ListRedisRepository { + + private final RedisTemplate redisTemplate; + private final ListOperations listOperations; + private final Jackson2HashMapper hashMapper; + + public ListRedisRepository(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + listOperations = redisTemplate.opsForList(); + hashMapper = new Jackson2HashMapper(false); + } + + public void push(String key, Object value, Duration timeout) { + listOperations.leftPush(key, hashMapper.toHash(value)); + redisTemplate.expire(key, timeout); + } + + public void delete(String key) { + redisTemplate.expireAt(key, new Date()); + } + + public List pop(String key, Long count) { + return listOperations.rightPop(key, count); + } + + public Long size(String key) { + return listOperations.size(key); + } +} \ No newline at end of file diff --git a/jtoon-db/db-redis/src/main/java/shop/jtoon/service/EventRedisService.java b/jtoon-db/db-redis/src/main/java/shop/jtoon/service/EventRedisService.java new file mode 100644 index 0000000..8324235 --- /dev/null +++ b/jtoon-db/db-redis/src/main/java/shop/jtoon/service/EventRedisService.java @@ -0,0 +1,31 @@ +package shop.jtoon.service; + +import java.time.Duration; +import java.util.List; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.dto.ImagePublishData; +import shop.jtoon.repository.ListRedisRepository; + +@Service +@RequiredArgsConstructor +public class EventRedisService { + + private static final String EVENT_KEY = "EVENT"; + private static final int EXPIRE_MINUTES = 1; + + private final ListRedisRepository listRedisRepository; + + public void publish(ImagePublishData imagePublishData) { + listRedisRepository.push(EVENT_KEY, imagePublishData, Duration.ofMinutes(EXPIRE_MINUTES)); + } + + public List consume() { + return listRedisRepository.pop(EVENT_KEY, listRedisRepository.size(EVENT_KEY)) + .stream() + .map(ImagePublishData.class::cast) + .toList(); + } +}