From 1fa86d461dfde1837e7db0b9a0c434cc0a5e1266 Mon Sep 17 00:00:00 2001 From: JIN-076 <57834671+JIN-076@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:59:19 +0900 Subject: [PATCH] =?UTF-8?q?[REFACT]=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=97=85=EC=A0=81=20=EB=8B=AC=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20(#338)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [REFACT] 업적 이력 및 업적 달성, 알림 이벤트에 대한 Event DTO 클래스 리팩토링 * [REFACT] '프로젝트 생성' 작업에 대한 이벤트 발행 처리 로직 리팩토링 * [REFACT] 유저의 이력 처리, 업적의 달성 체크, 업적 달성 알림에 대한 이벤트 구독 처리 핸들러 리팩토링 * [REFACT] 불필요한 클래스 제거 * [RENAME] 이벤트 발행을 위한 VO 클래스 패키지 이동 * [REFACT] 업적 이력, 달성 여부 체크, 달성 알림 이벤트에 대한 핸들러 리팩토링 * [REFACT] 연속 달성 업적을 위한 progressCount 조회 메서드 추가 구현 * [REFACT] 업적 이력 및 달성 처리를 위한 Helper 클래스 구현 * [REFACT] AOP 제거 및 새로운 트랜잭션의 업적 헬퍼 클래스 호출 * [REFACT] 한 번에 업적 달성 가능한 업적에 대한 Count 조회 시, Null 핸들링 로직 구현 * [FIX] 제거된 메서드를 다른 메서드로 대체 * [FIX] 제거된 메서드를 다른 메서드로 대체 --- .../common/aop/AchievementEventAspect.java | 182 ------------ .../event/AccumulateAchievementEvent.java | 19 -- .../AccumulateAchievementUpdateEvent.java | 11 - .../common/event/AchievementEvent.java | 13 - .../event/SequenceAchievementEvent.java | 16 - .../event/SequenceAchievementUpdateEvent.java | 11 - .../common/event/UserActivityEvent.java | 24 -- .../domain/vo/AchievementCompletionEvent.java | 17 ++ .../vo/AchievementNotificationEvent.java | 17 ++ .../mogakgo/common/event/domain/vo/Event.java | 15 + .../event/domain/vo/UserActivityEvent.java | 15 + .../handler/AchievementCheckListener.java | 152 ++++++++++ .../handler/AchievementEventHandler.java | 275 ----------------- .../event/handler/NotificationListener.java | 68 +++++ .../event/handler/UserActivityListener.java | 78 +++++ .../application/AchievementEventService.java | 281 ------------------ .../AchievementProgressService.java | 21 ++ .../application/MatchingEventHelper.java | 80 +++++ .../infrastructure/MatchingJpaRepository.java | 4 +- .../ProfileCardLikeEventHelper.java | 60 ++++ .../application/ProfileCardLikeService.java | 3 + .../application/ProjectEventHelper.java | 67 +++++ .../project/application/ProjectService.java | 5 + .../infrastructure/ProjectJpaRepository.java | 2 +- .../ProjectJoinRequestEventHelper.java | 58 ++++ .../ProjectJoinRequestService.java | 8 + .../review/application/ReviewEventHelper.java | 63 ++++ .../review/application/ReviewService.java | 20 +- 28 files changed, 747 insertions(+), 838 deletions(-) delete mode 100644 src/main/java/io/oeid/mogakgo/common/aop/AchievementEventAspect.java delete mode 100644 src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementEvent.java delete mode 100644 src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementUpdateEvent.java delete mode 100644 src/main/java/io/oeid/mogakgo/common/event/AchievementEvent.java delete mode 100644 src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementEvent.java delete mode 100644 src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementUpdateEvent.java delete mode 100644 src/main/java/io/oeid/mogakgo/common/event/UserActivityEvent.java create mode 100644 src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementCompletionEvent.java create mode 100644 src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementNotificationEvent.java create mode 100644 src/main/java/io/oeid/mogakgo/common/event/domain/vo/Event.java create mode 100644 src/main/java/io/oeid/mogakgo/common/event/domain/vo/UserActivityEvent.java create mode 100644 src/main/java/io/oeid/mogakgo/common/event/handler/AchievementCheckListener.java delete mode 100644 src/main/java/io/oeid/mogakgo/common/event/handler/AchievementEventHandler.java create mode 100644 src/main/java/io/oeid/mogakgo/common/event/handler/NotificationListener.java create mode 100644 src/main/java/io/oeid/mogakgo/common/event/handler/UserActivityListener.java delete mode 100644 src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementEventService.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/matching/application/MatchingEventHelper.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeEventHelper.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/project/application/ProjectEventHelper.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestEventHelper.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/review/application/ReviewEventHelper.java diff --git a/src/main/java/io/oeid/mogakgo/common/aop/AchievementEventAspect.java b/src/main/java/io/oeid/mogakgo/common/aop/AchievementEventAspect.java deleted file mode 100644 index 6a46e901..00000000 --- a/src/main/java/io/oeid/mogakgo/common/aop/AchievementEventAspect.java +++ /dev/null @@ -1,182 +0,0 @@ -package io.oeid.mogakgo.common.aop; - -import static io.oeid.mogakgo.exception.code.ErrorCode404.PROJECT_JOIN_REQUEST_NOT_FOUND; -import static io.oeid.mogakgo.exception.code.ErrorCode404.PROJECT_NOT_FOUND; - -import io.oeid.mogakgo.domain.achievement.application.AchievementEventService; -import io.oeid.mogakgo.domain.achievement.application.AchievementProgressService; -import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; -import io.oeid.mogakgo.domain.matching.application.MatchingService; -import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCreateAPIReq; -import io.oeid.mogakgo.domain.project.domain.entity.Project; -import io.oeid.mogakgo.domain.project.exception.ProjectException; -import io.oeid.mogakgo.domain.project.infrastructure.ProjectJpaRepository; -import io.oeid.mogakgo.domain.project.presentation.dto.req.ProjectCreateReq; -import io.oeid.mogakgo.domain.project_join_req.application.dto.req.ProjectJoinCreateReq; -import io.oeid.mogakgo.domain.project_join_req.exception.ProjectJoinRequestException; -import io.oeid.mogakgo.domain.project_join_req.infrastructure.ProjectJoinRequestJpaRepository; -import io.oeid.mogakgo.domain.review.application.dto.req.ReviewCreateReq; -import io.oeid.mogakgo.domain.user.application.UserCommonService; -import io.oeid.mogakgo.domain.user.domain.User; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Aspect -@Component -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class AchievementEventAspect { - - private static final int MAX_SERVICE_AREA = 26; - private static final int SAME_MATCHING_COUNT = 2; - - private final AchievementEventService achievementEventService; - private final UserCommonService userCommonService; - private final ProjectJpaRepository projectRepository; - private final ProjectJoinRequestJpaRepository projectJoinRequestRepository; - private final MatchingService matchingService; - private final AchievementProgressService achievementProgressService; - - @Pointcut("execution(public * io.oeid.mogakgo.domain.review.application.ReviewService.createNewReview(Long, ..))") - public void updateJandiRateExecution() {} - - @Pointcut("execution(public * io.oeid.mogakgo.domain.project.application.ProjectService.create(Long, ..))") - public void createProjectExecution() {} - - @Pointcut("execution(public * io.oeid.mogakgo.domain.project_join_req.application.ProjectJoinRequestService.create(Long, ..))") - public void createJoinRequestExecution() {} - - @Pointcut("execution(public * io.oeid.mogakgo.domain.project_join_req.application.ProjectJoinRequestService.accept(Long, ..))") - public void acceptJoinRequestExecution() {} - - @Pointcut("execution(public * io.oeid.mogakgo.domain.profile.application.ProfileCardLikeService.create(Long, ..))") - public void createLikeExecution() {} - - @Transactional(propagation = Propagation.REQUIRES_NEW) - @AfterReturning(pointcut = "updateJandiRateExecution() && args(userId, request)") - public void publishCompletedEvent(JoinPoint joinPoint, Long userId, ReviewCreateReq request) { - - // -- '리뷰' 잔디력이 업데이트되는 사용자에 대한 업적 이벤트 발행 - User receiver = userCommonService.getUserById(request.getReceiverId()); - achievementEventService.publishCompletedEventWithVerify(receiver.getId(), - ActivityType.FRESH_DEVELOPER, receiver.getJandiRate()); - } - - @Transactional(propagation = Propagation.REQUIRES_NEW) - @AfterReturning(pointcut = "createProjectExecution() && args(userId, request)") - public void publishSequenceEvent(JoinPoint joinPoint, Long userId, ProjectCreateReq request) { - - log.info("AOP 호출 on Thread={}", Thread.currentThread().getName()); - - // -- '생성자' 프로젝트를 생성한 사용자에 대한 업적 이벤트 발행 - achievementEventService.publishSequenceEventWithVerify(userId, - ActivityType.PLEASE_GIVE_ME_MOGAK); - - achievementEventService.publishCompletedEventWithVerify(userId, - ActivityType.BRAVE_EXPLORER, checkCreatedProjectCountByRegion(userId)); - - log.info("AOP 호출 완료 on Thread={}", Thread.currentThread().getName()); - } - - @Transactional(propagation = Propagation.REQUIRES_NEW) - @AfterReturning(pointcut = "createJoinRequestExecution() && args(userId, request)") - public void publishAccumulateEvent(JoinPoint joinPoint, Long userId, ProjectJoinCreateReq request) { - - // -- '생성자' 매칭 요청을 수신한 사용자에 대한 업적 이벤트 발행 - Project project = getProject(request.getProjectId()); - achievementEventService.publishAccumulateEventWithVerify( - userId, ActivityType.CATCH_ME_IF_YOU_CAN, - getAccumulatedProgressCount(project.getCreator().getId(), ActivityType.CATCH_ME_IF_YOU_CAN) - ); - } - - @Transactional(propagation = Propagation.REQUIRES_NEW) - @AfterReturning(pointcut = "acceptJoinRequestExecution() && args(userId, projectRequestId)") - public void publishEventAboutMatching(JoinPoint joinPoint, Long userId, Long projectRequestId) { - - // -- '생성자' 매칭 요청을 수락한 사용자에 대한 업적 이벤트 발행 - achievementEventService.publishAccumulateEventWithVerify(userId, - ActivityType.GOOD_PERSON_GOOD_MEETUP, - getAccumulatedProgressCount(userId, ActivityType.GOOD_PERSON_GOOD_MEETUP) - ); - - achievementEventService.publishSequenceEventWithVerify(userId, ActivityType.LIKE_E); - - achievementEventService.publishCompletedEventWithVerify(userId, - ActivityType.NOMAD_CODER, checkMatchedProjectCountByRegion(userId)); - - Long participantId = getParticipantIdFromJoinRequest(projectRequestId); - achievementEventService.publishCompletedEventWithVerify(userId, - ActivityType.MY_DESTINY, - checkMatchingCountWithSameUser(userId, participantId)); - - // -- '참여자' 매칭 요청을 생성한 사용자에 대한 업적 이벤트 발행 - achievementEventService.publishAccumulateEventWithVerify(participantId, - ActivityType.GOOD_PERSON_GOOD_MEETUP, - getAccumulatedProgressCount(participantId, ActivityType.GOOD_PERSON_GOOD_MEETUP) - ); - - achievementEventService.publishSequenceEventWithVerify(participantId, - ActivityType.LIKE_E); - - achievementEventService.publishCompletedEventWithVerify(participantId, - ActivityType.NOMAD_CODER, checkMatchedProjectCountByRegion(participantId)); - - achievementEventService.publishCompletedEventWithVerify(participantId, - ActivityType.MY_DESTINY, - checkMatchingCountWithSameUser(userId, participantId)); - } - - @Transactional(propagation = Propagation.REQUIRES_NEW) - @AfterReturning(pointcut = "createLikeExecution() && args(userId, request)") - public void publishEventAboutLike(JoinPoint joinPoint, Long userId, UserProfileLikeCreateAPIReq request) { - - // -- '찔러보기' 요청을 생성한 사용자에 대한 업적 이벤트 발행 - achievementEventService.publishCompletedEventWithoutVerify(userId, ActivityType.LEAVE_YOUR_MARK); - - // -- '찔러보기' 요청을 수신한 사용자에 대한 업적 이벤트 발행 - Long receiverId = request.getReceiverId(); - achievementEventService.publishAccumulateEventWithVerify( - receiverId, ActivityType.WHAT_A_POPULAR_PERSON, - getAccumulatedProgressCount(receiverId, ActivityType.WHAT_A_POPULAR_PERSON) - ); - } - - private Project getProject(Long projectId) { - return projectRepository.findById(projectId) - .orElseThrow(() -> new ProjectException(PROJECT_NOT_FOUND)); - } - - private Integer checkCreatedProjectCountByRegion(Long userId) { - Integer progressCount = projectRepository.getRegionCountByUserId(userId); - return progressCount.equals(MAX_SERVICE_AREA) ? 1 : 0; - } - - private Integer checkMatchedProjectCountByRegion(Long userId) { - Integer progressCount = matchingService.getRegionCountByMatching(userId); - return progressCount.equals(MAX_SERVICE_AREA) ? 1 : 0; - } - - private Integer checkMatchingCountWithSameUser(Long userId, Long participantId) { - Integer progressCount = matchingService.getDuplicateMatching(userId, participantId); - return progressCount.equals(SAME_MATCHING_COUNT) ? 1 : 0; - } - - private Integer getAccumulatedProgressCount(Long userId, ActivityType activityType) { - return achievementProgressService.getAccumulatedProgressCount(userId, activityType); - } - - private Long getParticipantIdFromJoinRequest(Long projectRequestId) { - return projectJoinRequestRepository.findByIdWithProject(projectRequestId) - .orElseThrow(() -> new ProjectJoinRequestException(PROJECT_JOIN_REQUEST_NOT_FOUND)) - .getSender().getId(); - } -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementEvent.java b/src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementEvent.java deleted file mode 100644 index 7860a634..00000000 --- a/src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.oeid.mogakgo.common.event; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class AccumulateAchievementEvent extends AchievementEvent { - - private final Integer progressCount; - private final Boolean completed; - - @Builder - public AccumulateAchievementEvent(Long userId, Long achievementId, - Integer progressCount, Boolean completed) { - super(userId, achievementId); - this.progressCount = progressCount; - this.completed = completed; - } -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementUpdateEvent.java b/src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementUpdateEvent.java deleted file mode 100644 index d6c5dca6..00000000 --- a/src/main/java/io/oeid/mogakgo/common/event/AccumulateAchievementUpdateEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.oeid.mogakgo.common.event; - -import lombok.Builder; - -public class AccumulateAchievementUpdateEvent extends AchievementEvent { - - @Builder - public AccumulateAchievementUpdateEvent(Long userId, Long achievementId) { - super(userId, achievementId); - } -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/AchievementEvent.java b/src/main/java/io/oeid/mogakgo/common/event/AchievementEvent.java deleted file mode 100644 index 5ed51e97..00000000 --- a/src/main/java/io/oeid/mogakgo/common/event/AchievementEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.oeid.mogakgo.common.event; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class AchievementEvent { - - protected Long userId; - protected Long achievementId; -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementEvent.java b/src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementEvent.java deleted file mode 100644 index 5d6ff414..00000000 --- a/src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.oeid.mogakgo.common.event; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class SequenceAchievementEvent extends AchievementEvent { - - private final Boolean completed; - - @Builder - public SequenceAchievementEvent(Long userId, Long achievementId, Boolean completed) { - super(userId, achievementId); - this.completed = completed; - } -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementUpdateEvent.java b/src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementUpdateEvent.java deleted file mode 100644 index b89e1242..00000000 --- a/src/main/java/io/oeid/mogakgo/common/event/SequenceAchievementUpdateEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.oeid.mogakgo.common.event; - -import lombok.Builder; - -public class SequenceAchievementUpdateEvent extends AchievementEvent { - - @Builder - public SequenceAchievementUpdateEvent(Long userId, Long achievementId) { - super(userId, achievementId); - } -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/UserActivityEvent.java b/src/main/java/io/oeid/mogakgo/common/event/UserActivityEvent.java deleted file mode 100644 index 1a6be91b..00000000 --- a/src/main/java/io/oeid/mogakgo/common/event/UserActivityEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.oeid.mogakgo.common.event; - -import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; -import lombok.Builder; -import lombok.Getter; - -@Getter -public class UserActivityEvent { - - private final Long userId; - private final Long achievementId; - private final ActivityType activityType; - private final Integer progressCount; - - @Builder - private UserActivityEvent(Long userId, Long achievementId, ActivityType activityType, - Integer progressCount) { - this.userId = userId; - this.achievementId = achievementId; - this.activityType = activityType; - this.progressCount = progressCount; - } - -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementCompletionEvent.java b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementCompletionEvent.java new file mode 100644 index 00000000..7463bdb0 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementCompletionEvent.java @@ -0,0 +1,17 @@ +package io.oeid.mogakgo.common.event.domain.vo; + +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class AchievementCompletionEvent extends Event { + + private final Object target; + + @Builder + private AchievementCompletionEvent(Long userId, ActivityType activityType, Object target) { + super(userId, activityType); + this.target = target; + } +} diff --git a/src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementNotificationEvent.java b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementNotificationEvent.java new file mode 100644 index 00000000..e9e3fb7a --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/AchievementNotificationEvent.java @@ -0,0 +1,17 @@ +package io.oeid.mogakgo.common.event.domain.vo; + +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class AchievementNotificationEvent extends Event { + + private final Object target; + + @Builder + private AchievementNotificationEvent(Long userId, ActivityType activityType, Object target) { + super(userId, activityType); + this.target = target; + } +} diff --git a/src/main/java/io/oeid/mogakgo/common/event/domain/vo/Event.java b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/Event.java new file mode 100644 index 00000000..9dd69031 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/Event.java @@ -0,0 +1,15 @@ +package io.oeid.mogakgo.common.event.domain.vo; + +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class Event { + + protected Long userId; + protected ActivityType activityType; + +} diff --git a/src/main/java/io/oeid/mogakgo/common/event/domain/vo/UserActivityEvent.java b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/UserActivityEvent.java new file mode 100644 index 00000000..df0e1b24 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/event/domain/vo/UserActivityEvent.java @@ -0,0 +1,15 @@ +package io.oeid.mogakgo.common.event.domain.vo; + +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class UserActivityEvent extends Event { + + @Builder + private UserActivityEvent(Long userId, ActivityType activityType) { + super(userId, activityType); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/common/event/handler/AchievementCheckListener.java b/src/main/java/io/oeid/mogakgo/common/event/handler/AchievementCheckListener.java new file mode 100644 index 00000000..8a000705 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/event/handler/AchievementCheckListener.java @@ -0,0 +1,152 @@ +package io.oeid.mogakgo.common.event.handler; + +import static io.oeid.mogakgo.exception.code.ErrorCode400.NON_ACHIEVED_USER_ACHIEVEMENT; + +import io.oeid.mogakgo.common.event.domain.vo.AchievementCompletionEvent; +import io.oeid.mogakgo.domain.achievement.application.AchievementFacadeService; +import io.oeid.mogakgo.domain.achievement.application.AchievementProgressService; +import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; +import io.oeid.mogakgo.domain.achievement.domain.entity.AchievementMessage; +import io.oeid.mogakgo.domain.achievement.domain.entity.UserAchievement; +import io.oeid.mogakgo.domain.achievement.domain.entity.UserActivity; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.RequirementType; +import io.oeid.mogakgo.domain.achievement.exception.UserAchievementException; +import io.oeid.mogakgo.domain.achievement.infrastructure.AchievementJpaRepository; +import io.oeid.mogakgo.domain.achievement.infrastructure.UserAchievementJpaRepository; +import io.oeid.mogakgo.domain.achievement.infrastructure.UserActivityJpaRepository; +import io.oeid.mogakgo.domain.user.application.UserCommonService; +import io.oeid.mogakgo.domain.user.domain.User; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class AchievementCheckListener { + + private static final Integer MIN_PROGRESS_SIZE = 1; + private static final String SUBSCRIBE_DESTINATIONN = "/topic/achievement/"; + + private final UserCommonService userCommonService; + private final AchievementJpaRepository achievementRepository; + private final AchievementFacadeService achievementFacadeService; + private final UserAchievementJpaRepository userAchievementRepository; + private final UserActivityJpaRepository userActivityRepository; + private final AchievementProgressService achievementProgressService; + private final SimpMessagingTemplate messagingTemplate; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void executeEvent(final AchievementCompletionEvent event) { + + Long achievementId = achievementFacadeService + .getAvailableAchievementId(event.getUserId(), event.getActivityType()); + + if (achievementId != null) { + + User user = userCommonService.getUserById(event.getUserId()); + Achievement achievement = achievementFacadeService.getById(achievementId); + + Boolean isExist = achievementFacadeService + .validateAchivementAlreadyInProgress(event.getUserId(), achievementId); + + // -- 업적에 대한 진행도가 존재하지 않을 경우 + if (isExist.equals(Boolean.FALSE)) { + userAchievementRepository.save( + UserAchievement.builder() + .user(user) + .achievement(achievement) + .completed(Boolean.FALSE) + .build() + ); + } + + Object progressCount = event.getTarget() == null + ? getProgressCountForAchievement(event.getUserId(), achievement) + 1 + : event.getTarget(); + + // -- 업적이 달성 가능한 조건을 만족했을 경우 + if (validateAvailabilityToAchieve(progressCount, achievement)) { + + UserAchievement userAchievement = getByUserAndAchievement(event.getUserId(), achievementId); + userAchievement.updateCompleted(); + + // -- 해당 업적이 연속적으로 달성 가능한 업적인 경우 + if (achievement.getRequirementType().equals(RequirementType.SEQUENCE)) { + List history = userActivityRepository.getActivityHistoryByActivityType( + event.getUserId(), + event.getActivityType(), + achievement.getRequirementValue() + ); + history.forEach(UserActivity::delete); + } + + // -- 업적 달성에 대한 STOMP 통신 + messagingTemplate.convertAndSend(SUBSCRIBE_DESTINATIONN + event.getUserId(), + AchievementMessage.builder() + .userId(event.getUserId()) + .achievementId(achievementId) + .progressCount(achievement.getRequirementValue()) + .requirementValue(achievement.getRequirementValue()) + .completed(Boolean.TRUE) + .build() + ); + + } else { + + if (!isAvailableToAchieveOnce(event.getActivityType())) { + + // 업적 진행에 대한 STOMP 통신 + messagingTemplate.convertAndSend(SUBSCRIBE_DESTINATIONN + event.getUserId(), + AchievementMessage.builder() + .userId(event.getUserId()) + .achievementId(achievementId) + .progressCount((Integer) progressCount) + .requirementValue(achievement.getRequirementValue()) + .build() + ); + } + } + } + } + + private UserAchievement getByUserAndAchievement(Long userId, Long achievementId) { + return userAchievementRepository.findByUserAndAchievementId(userId, achievementId) + .orElseThrow(() -> new UserAchievementException(NON_ACHIEVED_USER_ACHIEVEMENT)); + } + + private Integer getProgressCountForAchievement(Long userId, Achievement achievement) { + if (achievement.getRequirementType().equals(RequirementType.ACCUMULATE)) { + return achievementProgressService.getAccumulatedProgressCount(userId, achievement.getActivityType()); + } + return achievementProgressService.getProgressCountMapWithoutToday(userId, + List.of(achievement.getActivityType())).get(achievement.getActivityType()); + } + + private boolean validateAvailabilityToAchieve(Object target, Achievement achievement) { + if (target instanceof Integer) { + return Objects.equals(achievement.getRequirementValue(), target); + } else if (target instanceof Double) { + return achievement.getRequirementValue() <= (Double) target; + } else { + throw new IllegalArgumentException("Unsupported target type"); + } + } + + private boolean isAvailableToAchieveOnce(ActivityType activityType) { + return getProgressLevelSize(activityType).equals(MIN_PROGRESS_SIZE); + } + + private Integer getProgressLevelSize(ActivityType activityType) { + return achievementRepository.findMaxProgressLevelByActivityType(activityType); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/common/event/handler/AchievementEventHandler.java b/src/main/java/io/oeid/mogakgo/common/event/handler/AchievementEventHandler.java deleted file mode 100644 index e5b91714..00000000 --- a/src/main/java/io/oeid/mogakgo/common/event/handler/AchievementEventHandler.java +++ /dev/null @@ -1,275 +0,0 @@ -package io.oeid.mogakgo.common.event.handler; - -import static io.oeid.mogakgo.exception.code.ErrorCode400.EVENT_LISTENER_REQUEST_FAILED; -import static io.oeid.mogakgo.exception.code.ErrorCode400.NON_ACHIEVED_USER_ACHIEVEMENT; -import static io.oeid.mogakgo.exception.code.ErrorCode404.ACHIEVEMENT_NOT_FOUND; - -import io.oeid.mogakgo.common.event.AccumulateAchievementEvent; -import io.oeid.mogakgo.common.event.AccumulateAchievementUpdateEvent; -import io.oeid.mogakgo.common.event.AchievementEvent; -import io.oeid.mogakgo.common.event.SequenceAchievementEvent; -import io.oeid.mogakgo.common.event.SequenceAchievementUpdateEvent; -import io.oeid.mogakgo.common.event.UserActivityEvent; -import io.oeid.mogakgo.common.event.exception.EventListenerProcessingException; -import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; -import io.oeid.mogakgo.domain.achievement.domain.entity.AchievementMessage; -import io.oeid.mogakgo.domain.achievement.domain.entity.UserAchievement; -import io.oeid.mogakgo.domain.achievement.domain.entity.UserActivity; -import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; -import io.oeid.mogakgo.domain.achievement.exception.AchievementException; -import io.oeid.mogakgo.domain.achievement.exception.UserAchievementException; -import io.oeid.mogakgo.domain.achievement.infrastructure.AchievementJpaRepository; -import io.oeid.mogakgo.domain.achievement.infrastructure.UserAchievementJpaRepository; -import io.oeid.mogakgo.domain.achievement.infrastructure.UserActivityJpaRepository; -import io.oeid.mogakgo.domain.notification.application.NotificationService; -import io.oeid.mogakgo.domain.user.application.UserCommonService; -import io.oeid.mogakgo.domain.user.domain.User; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Recover; -import org.springframework.retry.annotation.Retryable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.event.TransactionPhase; -import org.springframework.transaction.event.TransactionalEventListener; - -@Slf4j -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class AchievementEventHandler { - - private static final String SUBSCRIBE_DESTINATIONN = "/topic/achievement/"; - - private final UserAchievementJpaRepository userAchievementRepository; - private final AchievementJpaRepository achievementRepository; - private final UserActivityJpaRepository userActivityRepository; - private final UserCommonService userCommonService; - private final NotificationService notificationService; - private final SimpMessagingTemplate messagingTemplate; - - @Retryable(retryFor = EventListenerProcessingException.class, backoff = @Backoff(1000)) - @Transactional - @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) - public void executeActivity(final UserActivityEvent event) { - - log.info("call activity event of {} on Thread:{}", event.getAchievementId(), - Thread.currentThread().getName()); - - try { - - User user = userCommonService.getUserById(event.getUserId()); - userActivityRepository.save(UserActivity.builder() - .user(user) - .activityType(event.getActivityType()) - .build()); - - Achievement achievement = getById(event.getAchievementId()); - if (!achievement.getRequirementValue().equals(event.getProgressCount()) - || !getProgressLevelSize(event.getActivityType()).equals(1)) { - - log.info("call socket for event {} in progress", event.getAchievementId()); - - messagingTemplate.convertAndSend(SUBSCRIBE_DESTINATIONN + event.getUserId(), - AchievementMessage.builder() - .userId(event.getUserId()) - .achievementId(event.getAchievementId()) - .progressCount(event.getProgressCount()) - .requirementValue(achievement.getRequirementValue()) - .build() - ); - - log.info("call completed for socket event"); - } - - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - - @Retryable(retryFor = EventListenerProcessingException.class, backoff = @Backoff(1000)) - @Transactional - @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) - public void executeEvent(final SequenceAchievementEvent event) { - - try { - User user = userCommonService.getUserById(event.getUserId()); - Achievement achievement = getById(event.getAchievementId()); - - userAchievementRepository.save( - UserAchievement.builder() - .user(user) - .achievement(achievement) - .completed(event.getCompleted()) - .build() - ); - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - - @Retryable(retryFor = EventListenerProcessingException.class, maxAttempts = 3, backoff = @Backoff(1000)) - @Transactional - @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) - public void executeEvent(final AccumulateAchievementEvent event) { - - log.info("call activity event of {} on Thread:{}", event.getAchievementId(), - Thread.currentThread().getName()); - - try { - User user = userCommonService.getUserById(event.getUserId()); - Achievement achievement = getById(event.getAchievementId()); - - userAchievementRepository.save( - UserAchievement.builder() - .user(user) - .achievement(achievement) - .completed(event.getCompleted()) - .build() - ); - - // 업적 진행 or 달성 후, 클라이언트에게 socket 통신 - if (event.getCompleted().equals(Boolean.TRUE)) { - - log.info("call socket for event {} completion", event.getAchievementId()); - notificationService.createAchievementNotification(user.getId(), achievement); - - messagingTemplate.convertAndSend(SUBSCRIBE_DESTINATIONN + event.getUserId(), - AchievementMessage.builder() - .userId(event.getUserId()) - .achievementId(event.getAchievementId()) - .progressCount(event.getProgressCount()) - .requirementValue(achievement.getRequirementValue()) - .completed(event.getCompleted()) - .build() - ); - - } - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - - @Retryable(retryFor = EventListenerProcessingException.class, backoff = @Backoff(1000)) - @Transactional - @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) - public void executeEvent(final AccumulateAchievementUpdateEvent event) { - - log.info("call activity event of {} on Thread:{}", event.getAchievementId(), - Thread.currentThread().getName()); - - try { - // 진행중인 업적에 대해 '달성' 업데이트 - UserAchievement userAchievement = getByUserAndAchievementId(event); - userAchievement.updateCompleted(); - - log.info("call socket for event {} completion", event.getAchievementId()); - notificationService.createAchievementNotification(userAchievement.getUser().getId(), userAchievement.getAchievement()); - - // 업적 달성 후, 클라이언트에게 socket 통신 - Achievement achievement = getById(event.getAchievementId()); - - messagingTemplate.convertAndSend(SUBSCRIBE_DESTINATIONN + event.getUserId(), - AchievementMessage.builder() - .userId(event.getUserId()) - .achievementId(event.getAchievementId()) - .progressCount(achievement.getRequirementValue()) - .requirementValue(achievement.getRequirementValue()) - .completed(Boolean.TRUE) - .build() - ); - - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - - @Retryable(retryFor = EventListenerProcessingException.class, backoff = @Backoff(1000)) - @Transactional - @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) - public void executeEvent(final SequenceAchievementUpdateEvent event) { - - log.info("call activity event of {} on Thread:{}", event.getAchievementId(), - Thread.currentThread().getName()); - - try { - // 진행중인 업적에 대해 '달성' 업데이트 - UserAchievement userAchievement = getByUserAndAchievementId(event); - userAchievement.updateCompleted(); - - // -- 'SEQUENCE' 타입 업적에 한해, 달성 조건을 위해 사용된 히스토리 soft delete 처리 - Achievement achievement = getById(event.getAchievementId()); - List history = userActivityRepository.getActivityHistoryByActivityType( - event.getUserId(), achievement.getActivityType(), achievement.getRequirementValue()); - history.forEach(UserActivity::delete); - - log.info("call socket for event {} completion", event.getAchievementId()); - notificationService.createAchievementNotification(userAchievement.getUser().getId(), achievement); - - // 업적 달성 후, 클라이언트에게 socket 통신 - messagingTemplate.convertAndSend(SUBSCRIBE_DESTINATIONN + event.getUserId(), - AchievementMessage.builder() - .userId(event.getUserId()) - .achievementId(event.getAchievementId()) - .progressCount(achievement.getRequirementValue()) - .requirementValue(achievement.getRequirementValue()) - .completed(Boolean.TRUE) - .build() - ); - - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - - log.info("call socket completion"); - } - - @Recover - public void recoverForActivityEvent(EventListenerProcessingException e, - final UserActivityEvent event) { - throw new AchievementException(EVENT_LISTENER_REQUEST_FAILED); - } - - @Recover - public void recoverForAccumulatedEvent(EventListenerProcessingException e, - final AccumulateAchievementEvent event) { - throw new AchievementException(EVENT_LISTENER_REQUEST_FAILED); - } - - @Recover - public void recoverForSequencedEvent(EventListenerProcessingException e, - final SequenceAchievementEvent event) { - throw new AchievementException(EVENT_LISTENER_REQUEST_FAILED); - } - - @Recover - public void recoverForAccumulatedUpdateEvent(EventListenerProcessingException e, - final AccumulateAchievementUpdateEvent event) { - throw new AchievementException(EVENT_LISTENER_REQUEST_FAILED); - } - - @Recover - public void recoverForSequencedUpdateEvent(EventListenerProcessingException e, - final AccumulateAchievementUpdateEvent event) { - throw new AchievementException(EVENT_LISTENER_REQUEST_FAILED); - } - - public Achievement getById(Long achievementId) { - return achievementRepository.findById(achievementId) - .orElseThrow(() -> new AchievementException(ACHIEVEMENT_NOT_FOUND)); - } - - public Integer getProgressLevelSize(ActivityType activityType) { - return achievementRepository.findMaxProgressLevelByActivityType(activityType); - } - - public UserAchievement getByUserAndAchievementId(final AchievementEvent event) { - return userAchievementRepository - .findByUserAndAchievementId(event.getUserId(), event.getAchievementId()) - .orElseThrow(() -> new UserAchievementException(NON_ACHIEVED_USER_ACHIEVEMENT)); - } - -} diff --git a/src/main/java/io/oeid/mogakgo/common/event/handler/NotificationListener.java b/src/main/java/io/oeid/mogakgo/common/event/handler/NotificationListener.java new file mode 100644 index 00000000..0646212c --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/event/handler/NotificationListener.java @@ -0,0 +1,68 @@ +package io.oeid.mogakgo.common.event.handler; + + +import io.oeid.mogakgo.common.event.domain.vo.AchievementNotificationEvent; +import io.oeid.mogakgo.domain.achievement.application.AchievementFacadeService; +import io.oeid.mogakgo.domain.achievement.application.AchievementProgressService; +import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.RequirementType; +import io.oeid.mogakgo.domain.notification.application.NotificationService; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class NotificationListener { + + private final NotificationService notificationService; + private final AchievementProgressService achievementProgressService; + private final AchievementFacadeService achievementFacadeService; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void executeEvent(final AchievementNotificationEvent event) { + + // 사용자가 현재 달성할 수 있는 업적 ID + Long achievementId = achievementFacadeService + .getAvailableAchievementId(event.getUserId(), event.getActivityType()); + + if (achievementId != null) { + + Achievement achievement = achievementFacadeService.getById(achievementId); + Object progressCount = event.getTarget() == null + ? getProgressCountForAchievement(event.getUserId(), achievement) + 1 + : event.getTarget(); + + // -- 업적이 달성 가능한 조건을 만족했을 경우 + if (validateAvailabilityToAchieve(progressCount, achievement)) { + notificationService.createAchievementNotification(event.getUserId(), achievement); + } + } + } + + private Integer getProgressCountForAchievement(Long userId, Achievement achievement) { + if (achievement.getRequirementType().equals(RequirementType.ACCUMULATE)) { + return achievementProgressService.getAccumulatedProgressCount(userId, achievement.getActivityType()); + } + return achievementProgressService.getProgressCountMapWithoutToday(userId, + List.of(achievement.getActivityType())).get(achievement.getActivityType()); + } + + private boolean validateAvailabilityToAchieve(Object target, Achievement achievement) { + if (target instanceof Integer) { + return Objects.equals(achievement.getRequirementValue(), target); + } else if (target instanceof Double) { + return achievement.getRequirementValue() <= (Double) target; + } else { + throw new IllegalArgumentException("Unsupported target type"); + } + } + +} diff --git a/src/main/java/io/oeid/mogakgo/common/event/handler/UserActivityListener.java b/src/main/java/io/oeid/mogakgo/common/event/handler/UserActivityListener.java new file mode 100644 index 00000000..57635d86 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/event/handler/UserActivityListener.java @@ -0,0 +1,78 @@ +package io.oeid.mogakgo.common.event.handler; + +import io.oeid.mogakgo.common.event.domain.vo.UserActivityEvent; +import io.oeid.mogakgo.domain.achievement.application.AchievementFacadeService; +import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; +import io.oeid.mogakgo.domain.achievement.domain.entity.UserActivity; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.RequirementType; +import io.oeid.mogakgo.domain.achievement.infrastructure.AchievementJpaRepository; +import io.oeid.mogakgo.domain.achievement.infrastructure.UserActivityJpaRepository; +import io.oeid.mogakgo.domain.user.application.UserCommonService; +import io.oeid.mogakgo.domain.user.domain.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class UserActivityListener { + + private static final Integer MIN_PROGRESS_SIZE = 1; + + private final UserCommonService userCommonService; + private final AchievementJpaRepository achievementRepository; + private final UserActivityJpaRepository userActivityRepository; + private final AchievementFacadeService achievementFacadeService; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void executeEvent(final UserActivityEvent event) { + + // 사용자가 현재 달성할 수 있는 업적 ID + Long achievementId = achievementFacadeService + .getAvailableAchievementId(event.getUserId(), event.getActivityType()); + + if (achievementId != null) { + + // -- 업적이 연속적으로 달성 가능한 업적인 경우 + Achievement achievement = achievementFacadeService.getById(achievementId); + if (achievement.getRequirementType().equals(RequirementType.SEQUENCE)) { + + // -- 당일에 연속적으로 달성 가능한 업적에 대한 이벤트를 발행한 적이 없는 경우 + if (achievementFacadeService.validateActivityDuplicate(event.getUserId(), event.getActivityType())) { + saveActivity(event); + } + } else { + + // -- 한 번에 달성 가능한 업적이 아닌 경우 + if (!isAvailableToAchieveOnce(event.getActivityType())) { + saveActivity(event); + } + } + } + } + + private void saveActivity(final UserActivityEvent event) { + User user = userCommonService.getUserById(event.getUserId()); + userActivityRepository.save( + UserActivity.builder() + .user(user) + .activityType(event.getActivityType()) + .build() + ); + } + + private boolean isAvailableToAchieveOnce(ActivityType activityType) { + return getProgressLevelSize(activityType).equals(MIN_PROGRESS_SIZE); + } + + private Integer getProgressLevelSize(ActivityType activityType) { + return achievementRepository.findMaxProgressLevelByActivityType(activityType); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementEventService.java b/src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementEventService.java deleted file mode 100644 index 8659c9d2..00000000 --- a/src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementEventService.java +++ /dev/null @@ -1,281 +0,0 @@ -package io.oeid.mogakgo.domain.achievement.application; - -import io.oeid.mogakgo.common.event.AccumulateAchievementEvent; -import io.oeid.mogakgo.common.event.AccumulateAchievementUpdateEvent; -import io.oeid.mogakgo.common.event.SequenceAchievementUpdateEvent; -import io.oeid.mogakgo.common.event.UserActivityEvent; -import io.oeid.mogakgo.common.event.exception.EventListenerProcessingException; -import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; -import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; -import io.oeid.mogakgo.domain.achievement.exception.AchievementException; -import io.oeid.mogakgo.exception.code.ErrorCode400; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Recover; -import org.springframework.retry.annotation.Retryable; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@Transactional -@RequiredArgsConstructor -public class AchievementEventService { - - // 작업과 동시에 업적 달성이 가능한, progressCount 최솟값 - private static final Integer MIN_PROGRESS_COUNT = 1; - - private final AchievementProgressService achievementProgressService; - private final AchievementFacadeService achievementFacadeService; - private final ApplicationEventPublisher eventPublisher; - - // 달성 자격요건의 검증 없이 한 번에 달성 가능한 업적에 대한 이벤트 발행 - @Async("threadPoolTaskExecutor") - @Retryable(retryFor = EventListenerProcessingException.class, maxAttempts = 3, backoff = @Backoff(1000)) - public void publishCompletedEventWithoutVerify(Long userId, ActivityType activityType) { - - Long achievementId = achievementFacadeService.getAvailableAchievementId(userId, activityType); - - if (achievementId != null) { - - try { - - // 업적 이벤트 발행 - UserActivity - eventPublisher.publishEvent( - UserActivityEvent.builder() - .userId(userId) - .activityType(activityType) - .achievementId(achievementId) - .progressCount(MIN_PROGRESS_COUNT) - .build() - ); - - // 업적 달성 이벤트 발행 - UserAchievement - eventPublisher.publishEvent( - AccumulateAchievementEvent.builder() - .userId(userId) - .achievementId(achievementId) - .completed(Boolean.TRUE) - .build() - ); - - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - } - - // 달성 자격요건의 검증과 함께 한 번에 달성 가능한 업적에 대한 이벤트 발행 - @Async("threadPoolTaskExecutor") - @Retryable(retryFor = EventListenerProcessingException.class, maxAttempts = 3, backoff = @Backoff(1000)) - public void publishCompletedEventWithVerify(Long userId, ActivityType activityType, - Object target) { - - log.info("call event of {} on Thread:{}", activityType, Thread.currentThread().getName()); - - // 사용자가 현재 달성할 수 있는 업적 ID - // -- 현재 사용자가 달성할 수 있는 없적이 없다면 이벤트 발행 X -- - Long achievementId = achievementFacadeService.getAvailableAchievementId(userId, activityType); - - if (achievementId != null) { - - // 사용자가 업적 달성 조건을 만족했을 경우 - // -- 사용자의 잔디력이 requirementValue 이상을 달성했다면 이벤트 발행 O - if (validateAvailabilityToAchieve(target, achievementId)) { - - try { - - // 업적 히스토리 이벤트 발행 - UserActivity - eventPublisher.publishEvent( - UserActivityEvent.builder() - .userId(userId) - .activityType(activityType) - .achievementId(achievementId) - .progressCount((Integer) target) - .build() - ); - - // 업적 달성 이벤트 발행 - UserAchievement - eventPublisher.publishEvent( - AccumulateAchievementEvent.builder() - .userId(userId) - .achievementId(achievementId) - .completed(Boolean.TRUE) - .build() - ); - - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - } - } - - // 달성 자격요건의 검증과 함께 여러 번에 걸쳐 달성 가능한 업적에 대한 이벤트 발행 - @Async("threadPoolTaskExecutor") - @Retryable(retryFor = EventListenerProcessingException.class, maxAttempts = 3, backoff = @Backoff(1000)) - public void publishAccumulateEventWithVerify(Long userId, ActivityType activityType, - Integer progressCount) { - - // 사용자가 현재 달성할 수 있는 업적 ID - // -- 현재 사용자가 달성할 수 있는 업적이 없는 경우 이벤트 발행 X - Long achievementId = achievementFacadeService - .getAvailableAchievementId(userId, activityType); - - if (achievementId != null) { - - try { - - // 업적에 대한 이벤트 발행 - UserActivity - // -- 누적 횟수 이벤트 - eventPublisher.publishEvent( - UserActivityEvent.builder() - .userId(userId) - .activityType(activityType) - .achievementId(achievementId) - .progressCount(progressCount) - .build() - ); - - Boolean isExist = achievementFacadeService - .validateAchivementAlreadyInProgress(userId, achievementId); - - if (isExist.equals(Boolean.FALSE)) { - eventPublisher.publishEvent( - AccumulateAchievementEvent.builder() - .userId(userId) - .achievementId(achievementId) - .progressCount(progressCount) - .completed(Boolean.FALSE) - .build() - ); - } - - // 해당 업적에 대한 달성 조건을 만족했을 경우 - if (validateAvailabilityToAchieve(progressCount + 1, achievementId)) { - - // 업적 달성에 대한 이벤트 발행 - UserAchievement - eventPublisher.publishEvent( - AccumulateAchievementUpdateEvent.builder() - .userId(userId) - .achievementId(achievementId) - .build() - ); - } - - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - } - - // 달성 자격요건의 검증과 함께 여러 번에 걸쳐 달성 가능한 연속성 업적에 대한 이벤트 발행 - @Async("threadPoolTaskExecutor") - @Retryable(retryFor = EventListenerProcessingException.class, maxAttempts = 3, backoff = @Backoff(1000)) - public void publishSequenceEventWithVerify(Long userId, ActivityType activityType) { - - // 사용자가 현재 달성할 수 있는 업적 ID - // -- 현재 사용자가 달성할 수 있는 업적이 없는 경우 이벤트 발행 X - Long achievementId = achievementFacadeService - .getAvailableAchievementId(userId, activityType); - - // 오늘을 제외한, 업적의 진행도 조회 - Map map = achievementProgressService - .getProgressCountMap(userId, List.of(activityType)); - - if (achievementId != null) { - - try { - - // 업적에 대한 이벤트 발행 - UserActivity - // -- 연속 횟수 이벤트 - if (achievementFacadeService.validateActivityDuplicate(userId, activityType)) { - - log.info("call publishEvent for history of {} on thread:{}", - activityType, Thread.currentThread().getName()); - - eventPublisher.publishEvent( - UserActivityEvent.builder() - .userId(userId) - .achievementId(achievementId) - .activityType(activityType) - .progressCount(map.get(activityType) + 1) - .build() - ); - - log.info("call completed publishEvent for history of {} on thread:{}", - activityType, Thread.currentThread().getName()); - } - - Boolean isExist = achievementFacadeService - .validateAchivementAlreadyInProgress(userId, achievementId); - - // 업적 진행에 대한 이벤트 발행 - UserAchievement - if (isExist.equals(Boolean.FALSE)) { - eventPublisher.publishEvent( - AccumulateAchievementEvent.builder() - .userId(userId) - .achievementId(achievementId) - .completed(Boolean.FALSE) - .build() - ); - } - - // 해당 업적에 대한 달성 조건을 만족했을 경우 - if (validateAvailabilityToAchieve(map.get(activityType) + 1, achievementId)) { - - log.info("call publishEvent for completionn of {} on thread:{}", - achievementId, Thread.currentThread().getName()); - - // 업적 달성에 대한 이벤트 발행 - UserAchievement - eventPublisher.publishEvent( - SequenceAchievementUpdateEvent.builder() - .userId(userId) - .achievementId(achievementId) - .build() - ); - - log.info("call completed publishEvent for completionn of {} on thread:{}", - achievementId, Thread.currentThread().getName()); - } - } catch (RuntimeException e) { - throw new EventListenerProcessingException(e.getMessage()); - } - } - } - - @Recover - public void recoverForEventListenerProcess( - EventListenerProcessingException e, Long userId, ActivityType activityType) { - throw new AchievementException(ErrorCode400.EVENT_LISTENER_REQUEST_FAILED); - } - - @Recover - public void recoverForEventListenerProcess( - EventListenerProcessingException e, Long userId, ActivityType activityType, Integer progressCount) { - throw new AchievementException(ErrorCode400.EVENT_LISTENER_REQUEST_FAILED); - } - - @Recover - public void recoverForEventListenerProcess( - EventListenerProcessingException e, Long userId, ActivityType activityType, Object target) { - throw new AchievementException(ErrorCode400.EVENT_LISTENER_REQUEST_FAILED); - } - - private boolean validateAvailabilityToAchieve(Object target, Long achievementId) { - Achievement achievement = achievementFacadeService.getById(achievementId); - if (target instanceof Integer) { - return Objects.equals(achievement.getRequirementValue(), target); - } else if (target instanceof Double) { - return achievement.getRequirementValue() <= (Double) target; - } else { - throw new IllegalArgumentException("Unsupported target type"); - } - } -} diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementProgressService.java b/src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementProgressService.java index 270b6ea1..662d6a17 100644 --- a/src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementProgressService.java +++ b/src/main/java/io/oeid/mogakgo/domain/achievement/application/AchievementProgressService.java @@ -26,6 +26,7 @@ public Integer getAccumulatedProgressCount(Long userId, ActivityType activityTyp return userAchievementRepository.getAccumulatedProgressCountByActivity(userId, activityType); } + // -- 업적 상세 조회를 위한 progressCount 조회 public Map getProgressCountMap(Long userId, List activityList) { return activityList.stream().collect(Collectors.toMap( activityType -> activityType, @@ -45,6 +46,26 @@ private Integer getSeqProgressCountFromToday(List activityList) { return validateContinuous(today, 0, activityList, 0); } + // -- 연속으로 달성 가능한 업적에 대한 달성조건 검증을 위한 progressCount 조회 + public Map getProgressCountMapWithoutToday(Long userId, List activityList) { + return activityList.stream().collect(Collectors.toMap( + activityType -> activityType, + activityType -> { + List history = userActivityRepository + .findByUserIdAndActivityType(userId, activityType); + return !history.isEmpty() ? getSeqProgressCountFromYesterday(history) : 0; + } + )); + } + + private Integer getSeqProgressCountFromYesterday(List activityList) { + LocalDate today = LocalDate.now(); + if (equalToLocalDate(today, activityList.get(0).getCreatedAt())) { + activityList.remove(0); + } + return validateContinuous(today.minusDays(1), 0, activityList, 0); + } + private int validateContinuous(LocalDate date, int idx, List activityList, int count) { if (idx == activityList.size() || !equalToLocalDate(date, activityList.get(idx).getCreatedAt())) { return count; diff --git a/src/main/java/io/oeid/mogakgo/domain/matching/application/MatchingEventHelper.java b/src/main/java/io/oeid/mogakgo/domain/matching/application/MatchingEventHelper.java new file mode 100644 index 00000000..ef83b2ef --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/matching/application/MatchingEventHelper.java @@ -0,0 +1,80 @@ +package io.oeid.mogakgo.domain.matching.application; + +import io.oeid.mogakgo.common.event.domain.vo.AchievementCompletionEvent; +import io.oeid.mogakgo.common.event.domain.vo.AchievementNotificationEvent; +import io.oeid.mogakgo.common.event.domain.vo.UserActivityEvent; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(propagation = Propagation.REQUIRES_NEW) +@RequiredArgsConstructor +public class MatchingEventHelper { + + private static final int MAX_SERVICE_AREA = 26; + private static final int SAME_MATCHING_COUNT = 2; + + private final MatchingService matchingService; + private final ApplicationEventPublisher eventPublisher; + + public void publishEvent(Long userId, Long participantId) { + + // -- '생성자' 매칭 요청을 수락한 사용자에 대한 업적 이벤트 발행 + publishEvent(userId, ActivityType.GOOD_PERSON_GOOD_MEETUP, null); + publishEvent(userId, ActivityType.LIKE_E, null); + publishEvent(userId, ActivityType.NOMAD_CODER, checkMatchedProjectCountByRegion(userId)); + publishEvent(userId, ActivityType.MY_DESTINY, checkMatchingCountWithSameUser(userId, participantId)); + + // -- '참여자' 매칭 요청을 생성한 사용자에 대한 업적 이벤트 발행 + publishEvent(participantId, ActivityType.GOOD_PERSON_GOOD_MEETUP, null); + publishEvent(participantId, ActivityType.LIKE_E, null); + publishEvent(participantId, ActivityType.NOMAD_CODER, checkMatchedProjectCountByRegion(userId)); + publishEvent(participantId, ActivityType.MY_DESTINY, checkMatchingCountWithSameUser(userId, participantId)); + } + + @Async("threadPoolTaskExecutor") + @Transactional + public void publishEvent(Long userId, ActivityType activityType, Object target) { + + // -- 업적 이력에 대한 이벤트 발행 + eventPublisher.publishEvent(UserActivityEvent.builder() + .userId(userId) + .activityType(activityType) + .build() + ); + + // -- 업적 달성 검증 및 처리에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementCompletionEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + + // -- 업적 달성 알림에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementNotificationEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + } + + private Integer checkMatchedProjectCountByRegion(Long userId) { + Integer progressCount = matchingService.getRegionCountByMatching(userId); + return progressCount.equals(MAX_SERVICE_AREA) ? 1 : 0; + } + + private Integer checkMatchingCountWithSameUser(Long userId, Long participantId) { + Integer progressCount = matchingService.getDuplicateMatching(userId, participantId); + return progressCount.equals(SAME_MATCHING_COUNT) ? 1 : 0; + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/matching/infrastructure/MatchingJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/matching/infrastructure/MatchingJpaRepository.java index 19585b7a..cced38f2 100644 --- a/src/main/java/io/oeid/mogakgo/domain/matching/infrastructure/MatchingJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/matching/infrastructure/MatchingJpaRepository.java @@ -16,10 +16,10 @@ public interface MatchingJpaRepository extends JpaRepository, List findProgressOneByUserId(Long userId, Pageable pageable); @Query(value = """ - select sum(if(p.region, 1, 0)) + select COALESCE(SUM(IF(p.region, 1, 0)), 0) from matching_tb as m inner join project_tb as p on m.project_id = p.id - where m.sender_id = 11 or p.creator_id = 11 + where m.sender_id = :userId or p.creator_id = :userId and m.matching_status = 'FINISHED' or m.matching_status = 'PROGRESS'; """, nativeQuery = true) Integer findRegionCountByMatching(Long userId); diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeEventHelper.java b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeEventHelper.java new file mode 100644 index 00000000..73af1621 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeEventHelper.java @@ -0,0 +1,60 @@ +package io.oeid.mogakgo.domain.profile.application; + +import io.oeid.mogakgo.common.event.domain.vo.AchievementCompletionEvent; +import io.oeid.mogakgo.common.event.domain.vo.AchievementNotificationEvent; +import io.oeid.mogakgo.common.event.domain.vo.UserActivityEvent; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true) +@RequiredArgsConstructor +public class ProfileCardLikeEventHelper { + + private final ApplicationEventPublisher eventPublisher; + + public void publishEvent(Long userId, Long receiverId) { + + // -- '찔러보기' 요청을 생성한 사용자에 대한 업적 이벤트 발행 + publishEvent(userId, ActivityType.LEAVE_YOUR_MARK, null); + + // -- '찔러보기' 요청을 수신한 사용자에 대한 업적 이벤트 발행 + publishEvent(receiverId, ActivityType.WHAT_A_POPULAR_PERSON, null); + } + + @Async("threadPoolTaskExecutor") + @Transactional + public void publishEvent(Long userId, ActivityType activityType, Object target) { + + // -- 업적 이력에 대한 이벤트 발행 + eventPublisher.publishEvent(UserActivityEvent.builder() + .userId(userId) + .activityType(activityType) + .build() + ); + + // -- 업적 달성 검증 및 처리에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementCompletionEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + + // -- 업적 달성 알림에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementNotificationEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java index 1e2dd645..363d8d61 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java @@ -29,6 +29,7 @@ public class ProfileCardLikeService { private final ProfileCardLikeJpaRepository profileCardLikeRepository; private final ProfileCardJpaRepository profileCardRepository; private final UserCommonService userCommonService; + private final ProfileCardLikeEventHelper eventHelper; @Transactional public Long create(Long userId, UserProfileLikeCreateAPIReq request) { @@ -46,6 +47,8 @@ public Long create(Long userId, UserProfileLikeCreateAPIReq request) { ProfileCardLike profileCardLike = request.toEntity(user, profileCard.getUser()); profileCardLikeRepository.save(profileCardLike); + eventHelper.publishEvent(userId, request.getReceiverId()); + return profileCardLike.getId(); } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectEventHelper.java b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectEventHelper.java new file mode 100644 index 00000000..310e022f --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectEventHelper.java @@ -0,0 +1,67 @@ +package io.oeid.mogakgo.domain.project.application; + +import io.oeid.mogakgo.common.event.domain.vo.AchievementCompletionEvent; +import io.oeid.mogakgo.common.event.domain.vo.AchievementNotificationEvent; +import io.oeid.mogakgo.common.event.domain.vo.UserActivityEvent; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import io.oeid.mogakgo.domain.project.infrastructure.ProjectJpaRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(propagation = Propagation.REQUIRES_NEW) +@RequiredArgsConstructor +public class ProjectEventHelper { + + private static final int MAX_SERVICE_AREA = 26; + + private final ProjectJpaRepository projectRepository; + private final ApplicationEventPublisher eventPublisher; + + public void publishEvent(Long userId) { + + // -- '생성자' 프로젝트를 생성한 사용자에 대한 업적 이벤트 발행 + publishEvent(userId, ActivityType.PLEASE_GIVE_ME_MOGAK, null); + publishEvent(userId, ActivityType.BRAVE_EXPLORER, checkCreatedProjectCountByRegion(userId)); + } + + @Async("threadPoolTaskExecutor") + @Transactional + public void publishEvent(Long userId, ActivityType activityType, Object target) { + + // -- 업적 이력에 대한 이벤트 발행 + eventPublisher.publishEvent(UserActivityEvent.builder() + .userId(userId) + .activityType(activityType) + .build() + ); + + // -- 업적 달성 검증 및 처리에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementCompletionEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + + // -- 업적 달성 알림에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementNotificationEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + } + + private Integer checkCreatedProjectCountByRegion(Long userId) { + Integer progressCount = projectRepository.getRegionCountByUserId(userId); + return progressCount.equals(MAX_SERVICE_AREA) ? 1 : 0; + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java index aef4c18d..1c3705fa 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java @@ -33,10 +33,12 @@ import java.util.Collections; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Transactional(readOnly = true) @RequiredArgsConstructor @Service @@ -49,6 +51,7 @@ public class ProjectService { private final GeoService geoService; private final ProjectJoinRequestJpaRepository projectJoinRequestJpaRepository; private final UserMatchingService userMatchingService; + private final ProjectEventHelper eventHelper; @Transactional public Long create(Long userId, ProjectCreateReq request) { @@ -76,6 +79,8 @@ public Long create(Long userId, ProjectCreateReq request) { Project project = request.toEntity(tokenUser); projectJpaRepository.save(project); + eventHelper.publishEvent(userId); + return project.getId(); } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectJpaRepository.java index 76ff127f..838ea2b6 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectJpaRepository.java @@ -23,7 +23,7 @@ public interface ProjectJpaRepository extends JpaRepository, Proj List findNotEndProjectOneByCreatorId(Long creatorId, Pageable pageable); @Query(value = """ - select sum(if(p.region, 1, 0)) from project_tb as p where p.creator_id = :userId + select COALESCE(SUM(IF(p.region, 1, 0)), 0) from project_tb as p where p.creator_id = :userId """, nativeQuery = true) Integer getRegionCountByUserId(Long userId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestEventHelper.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestEventHelper.java new file mode 100644 index 00000000..0bf6c8be --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestEventHelper.java @@ -0,0 +1,58 @@ +package io.oeid.mogakgo.domain.project_join_req.application; + +import io.oeid.mogakgo.common.event.domain.vo.AchievementCompletionEvent; +import io.oeid.mogakgo.common.event.domain.vo.AchievementNotificationEvent; +import io.oeid.mogakgo.common.event.domain.vo.UserActivityEvent; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(propagation = Propagation.REQUIRES_NEW) +@RequiredArgsConstructor +public class ProjectJoinRequestEventHelper { + + private final ApplicationEventPublisher eventPublisher; + + // -- 비동기 (@Async) 로 호출되는 메서드에 대해 트랜잭션으로 묶으면, 예외가 발생해도 롤백되지 않음! + public void publishEvent(Long userId) { + + // -- '생성자' 매칭 요청을 수신한 사용자에 대한 업적 이벤트 발행 + publishEvent(userId, ActivityType.CATCH_ME_IF_YOU_CAN, null); + } + + @Async("threadPoolTaskExecutor") + @Transactional + public void publishEvent(Long userId, ActivityType activityType, Object target) { + + // -- 업적 이력에 대한 이벤트 발행 + eventPublisher.publishEvent(UserActivityEvent.builder() + .userId(userId) + .activityType(activityType) + .build() + ); + + // -- 업적 달성 검증 및 처리에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementCompletionEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + + // -- 업적 달성 알림에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementNotificationEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java index 02976439..520ac083 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java @@ -12,6 +12,7 @@ import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.matching.application.MatchingEventHelper; import io.oeid.mogakgo.domain.matching.application.MatchingService; import io.oeid.mogakgo.domain.matching.application.UserMatchingService; import io.oeid.mogakgo.domain.notification.application.FCMNotificationService; @@ -45,6 +46,8 @@ public class ProjectJoinRequestService { private final FCMNotificationService fcmNotificationService; private final UserCommonService userCommonService; private final NotificationService notificationService; + private final ProjectJoinRequestEventHelper eventHelper; + private final MatchingEventHelper matchingEventHelper; @Transactional public Long create(Long userId, ProjectJoinCreateReq request) { @@ -58,6 +61,9 @@ public Long create(Long userId, ProjectJoinCreateReq request) { // 프로젝트 매칭 요청 생성 ProjectJoinRequest joinRequest = request.toEntity(tokenUser, project); projectJoinRequestRepository.save(joinRequest); + + eventHelper.publishEvent(project.getCreator().getId()); + // 매칭 요청 생성시 프로젝트 생성자에게 알림 전달 notificationService.createRequestArrivalNotification(project.getCreator().getId()); return joinRequest.getId(); @@ -102,6 +108,8 @@ public Long accept(Long userId, Long projectRequestId) { // TODO: 로그 처리 } + matchingEventHelper.publishEvent(userId, projectJoinRequest.getSender().getId()); + fcmNotificationService.sendNotification(projectJoinRequest.getSender().getId(), MATCHING_SUCCESS_MESSAGE.getTitle(), MATCHING_SUCCESS_MESSAGE.getMessage(), MATCHING_SUCCEEDED); diff --git a/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewEventHelper.java b/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewEventHelper.java new file mode 100644 index 00000000..b9b26f9a --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewEventHelper.java @@ -0,0 +1,63 @@ +package io.oeid.mogakgo.domain.review.application; + +import io.oeid.mogakgo.common.event.domain.vo.AchievementCompletionEvent; +import io.oeid.mogakgo.common.event.domain.vo.AchievementNotificationEvent; +import io.oeid.mogakgo.common.event.domain.vo.UserActivityEvent; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import io.oeid.mogakgo.domain.user.application.UserCommonService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(propagation = Propagation.REQUIRES_NEW) +@RequiredArgsConstructor +public class ReviewEventHelper { + + private final UserCommonService userCommonService; + private final ApplicationEventPublisher eventPublisher; + + public void publishEvent(Long userId, Double jandiRate) { + + // -- '리뷰' 잔디력이 업데이트되는 사용자에 대한 업적 이벤트 발행 + publishEvent(userId, ActivityType.FRESH_DEVELOPER, checkUserJandiRate(userId) + jandiRate); + } + + @Async("threadPoolTaskExecutor") + @Transactional + public void publishEvent(Long userId, ActivityType activityType, Object target) { + + // -- 업적 이력에 대한 이벤트 발행 + eventPublisher.publishEvent(UserActivityEvent.builder() + .userId(userId) + .activityType(activityType) + .build() + ); + + // -- 업적 달성 검증 및 처리에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementCompletionEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + + // -- 업적 달성 알림에 대한 이벤트 발행 + eventPublisher.publishEvent(AchievementNotificationEvent.builder() + .userId(userId) + .activityType(activityType) + .target(target) + .build() + ); + } + + private Double checkUserJandiRate(Long userId) { + return userCommonService.getUserById(userId).getJandiRate(); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewService.java b/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewService.java index c6af3e0b..206a22e0 100644 --- a/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewService.java +++ b/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewService.java @@ -19,9 +19,12 @@ @RequiredArgsConstructor public class ReviewService { + private static final double JANDI_WEIGHT = 2.5; + private final ReviewJpaRepository reviewRepository; private final ProjectJpaRepository projectRepository; private final UserCommonService userCommonService; + private final ReviewEventHelper eventHelper; @Transactional public ReviewCreateRes createNewReview(Long userId, ReviewCreateReq request) { @@ -41,9 +44,16 @@ public ReviewCreateRes createNewReview(Long userId, ReviewCreateReq request) { .rating(request.getRating()) .build() ); - receiver.updateJandiRateByReview(review.getRating(), - calculateProjectTime(project.getMeetingInfo().getMeetStartTime(), - project.getMeetingInfo().getMeetEndTime())); + + double time = calculateProjectTime( + project.getMeetingInfo().getMeetStartTime(), + project.getMeetingInfo().getMeetEndTime() + ); + receiver.updateJandiRateByReview(review.getRating(), time); + + // -- '잔디력 업데이트' 로직이 커밋되기 전, 새로운 트랜잭션으로 인해 변경 전의 데이터에 대해 조회됨 + eventHelper.publishEvent(receiver.getId(), calculateUpdatedJandiRate(review, time)); + return ReviewCreateRes.from(review); } @@ -54,6 +64,10 @@ private double calculateProjectTime(LocalDateTime meetStartTime, LocalDateTime m return hours + minutes / 60; } + private double calculateUpdatedJandiRate(Review review, double time) { + return review.getRating().getJandiValue() * time * JANDI_WEIGHT; + } + private void validateUser(Long userId, Long senderId) { if (!userId.equals(senderId)) { throw new ReviewException(ErrorCode400.REVIEW_USER_NOT_MATCH);