diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java b/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java index dfd93ab6..c9d93a52 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java @@ -1,8 +1,10 @@ package com.nexters.goalpanzi.application.mission; +import com.nexters.goalpanzi.application.firebase.TopicGenerator; import com.nexters.goalpanzi.application.mission.dto.response.MemberRankResponse; import com.nexters.goalpanzi.application.mission.dto.response.MissionDetailResponse; import com.nexters.goalpanzi.application.mission.dto.response.MissionsResponse; +import com.nexters.goalpanzi.application.mission.event.JoinMissionEvent; import com.nexters.goalpanzi.domain.common.BaseEntity; import com.nexters.goalpanzi.domain.member.Member; import com.nexters.goalpanzi.domain.member.repository.MemberRepository; @@ -12,6 +14,7 @@ import com.nexters.goalpanzi.exception.AlreadyExistsException; import com.nexters.goalpanzi.exception.ErrorCode; import com.nexters.goalpanzi.exception.NotFoundException; +import com.nexters.goalpanzi.infrastructure.firebase.PushNotificationSender; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @@ -19,6 +22,9 @@ import java.util.List; +import static com.nexters.goalpanzi.domain.firebase.PushNotificationMessage.MISSION_CANCELLATION_WARNING; +import static com.nexters.goalpanzi.domain.firebase.PushNotificationMessage.MISSION_READY; + @Transactional(readOnly = true) @RequiredArgsConstructor @Service @@ -31,6 +37,7 @@ public class MissionMemberService { private final MemberRepository memberRepository; private final ApplicationEventPublisher eventPublisher; + private final PushNotificationSender pushNotificationSender; public MissionDetailResponse getJoinableMission(final InvitationCode invitationCode) { missionValidator.validateJoinableMission(invitationCode); @@ -45,8 +52,9 @@ public void joinMission(final Long memberId, final InvitationCode invitationCode missionValidator.validateMaxPersonnel(mission); missionMemberRepository.save(MissionMember.join(member, mission)); -// TODO -// eventPublisher.publishEvent(new JoinMissionEvent(mission.getId(), "TODO deviceToken", member.getNickname())); + if (member.getDeviceToken() != null) { + eventPublisher.publishEvent(new JoinMissionEvent(mission.getId(), member.getDeviceToken(), member.getNickname())); + } } private Mission getMissionByCode(final InvitationCode invitationCode) { @@ -118,4 +126,34 @@ public void viewMissionRank(final Long missionId, final Long memberId) { MissionMember missionMember = missionMemberRepository.getMissionMember(memberId, missionId); missionMember.checkCompleted(); } + + @Transactional + public void sendReadyPushMessage() { + List missions = missionRepository.getReadyMissions(); + missions.forEach(mission -> { + if (mission.isReadyTime() && missionValidator.hasEnoughMember(mission.getId())) { + String topic = TopicGenerator.getTopic(mission.getId()); + pushNotificationSender.sendGroupMessage( + MISSION_READY.getTitle(), + MISSION_READY.getBody(), + topic + ); + } + }); + } + + @Transactional + public void sendCancellationWarningPushMessage() { + List missions = missionRepository.getReadyMissions(); + missions.forEach(mission -> { + if (mission.isReadyTime() && !missionValidator.hasEnoughMember(mission.getId())) { + String topic = TopicGenerator.getTopic(mission.getId()); + pushNotificationSender.sendGroupMessage( + MISSION_CANCELLATION_WARNING.getTitle(), + MISSION_CANCELLATION_WARNING.getBody(), + topic + ); + } + }); + } } diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/MissionValidator.java b/src/main/java/com/nexters/goalpanzi/application/mission/MissionValidator.java index 4cdd4ca3..a9d67129 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/MissionValidator.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/MissionValidator.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Component; import static com.nexters.goalpanzi.domain.mission.Mission.MAX_MISSION_MEMBER; +import static com.nexters.goalpanzi.domain.mission.Mission.MIN_MISSION_MEMBER; @RequiredArgsConstructor @Component @@ -38,6 +39,10 @@ public void validateMissionPeriod(final Mission mission) { } } + public boolean hasEnoughMember(final Long missionId) { + return getMissionMemberSize(missionId) >= MIN_MISSION_MEMBER; + } + private int getMissionMemberSize(final Long missionId) { return missionMemberRepository.findAllByMissionId(missionId).size(); } diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/MissionVerificationService.java b/src/main/java/com/nexters/goalpanzi/application/mission/MissionVerificationService.java index 580b3c3d..7534d04f 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/MissionVerificationService.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/MissionVerificationService.java @@ -1,5 +1,6 @@ package com.nexters.goalpanzi.application.mission; +import com.nexters.goalpanzi.application.firebase.TopicGenerator; import com.nexters.goalpanzi.application.mission.dto.request.CreateMissionVerificationCommand; import com.nexters.goalpanzi.application.mission.dto.request.MissionVerificationQuery; import com.nexters.goalpanzi.application.mission.dto.request.MyMissionVerificationQuery; @@ -8,23 +9,27 @@ import com.nexters.goalpanzi.application.mission.dto.response.MissionVerificationsResponse; import com.nexters.goalpanzi.application.upload.ObjectStorageClient; import com.nexters.goalpanzi.domain.common.BaseEntity; +import com.nexters.goalpanzi.domain.firebase.PushNotificationMessage; import com.nexters.goalpanzi.domain.member.Member; import com.nexters.goalpanzi.domain.member.repository.MemberRepository; -import com.nexters.goalpanzi.domain.mission.MissionMember; -import com.nexters.goalpanzi.domain.mission.MissionMembers; -import com.nexters.goalpanzi.domain.mission.MissionVerification; -import com.nexters.goalpanzi.domain.mission.MissionVerificationView; +import com.nexters.goalpanzi.domain.mission.*; import com.nexters.goalpanzi.domain.mission.repository.MissionMemberRepository; +import com.nexters.goalpanzi.domain.mission.repository.MissionRepository; import com.nexters.goalpanzi.domain.mission.repository.MissionVerificationRepository; import com.nexters.goalpanzi.domain.mission.repository.MissionVerificationViewRepository; import com.nexters.goalpanzi.exception.ErrorCode; import com.nexters.goalpanzi.exception.NotFoundException; +import com.nexters.goalpanzi.infrastructure.firebase.PushNotificationSender; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; + +import static com.nexters.goalpanzi.domain.firebase.PushNotificationMessage.*; @Transactional(readOnly = true) @RequiredArgsConstructor @@ -32,11 +37,13 @@ public class MissionVerificationService { private final MissionVerificationRepository missionVerificationRepository; + private final MissionRepository missionRepository; private final MissionMemberRepository missionMemberRepository; private final MissionVerificationViewRepository missionVerificationViewRepository; private final MemberRepository memberRepository; private final ObjectStorageClient objectStorageClient; + private final PushNotificationSender pushNotificationSender; private final MissionVerificationValidator missionVerificationValidator; private final MissionVerificationResponseSorter missionVerificationResponseSorter; @@ -89,4 +96,66 @@ public void viewMissionVerification(final ViewMissionVerificationCommand command missionVerificationViewRepository.save(new MissionVerificationView(missionVerification, member)); } + + @Transactional + public void sendVerificationPushMessage() { + LocalDate today = LocalDate.now(); + int hour = LocalDateTime.now().getHour(); + List missions = missionRepository.getInProgressMissions(); + + missions.forEach(mission -> { + if (mission.isMissionDay() && mission.isPushTime(hour)) { + List verifications = missionVerificationRepository.findAllByMissionIdAndDate(mission.getId(), today); + int verificationCount = verifications.size(); + String topic = TopicGenerator.getTopic(mission.getId()); + + if (verificationCount == 0) { + sendNoOneVerifiedPushMessage(MISSION_NO_ONE_VERIFIED, topic); + } else { + sendVerifiedPushMessage(MISSION_VERIFIED, topic, verificationCount); + } + } + }); + } + + private void sendVerifiedPushMessage(final PushNotificationMessage message, final String topic, final int verificationCount) { + pushNotificationSender.sendGroupMessage( + message.getTitle(verificationCount), + message.getBody(), + topic + ); + } + + private void sendNoOneVerifiedPushMessage(final PushNotificationMessage message, final String topic) { + pushNotificationSender.sendGroupMessage( + message.getTitle(), + message.getBody(), + topic + ); + } + + @Transactional + public void sendVerificationWarningPushMessage() { + LocalDate today = LocalDate.now(); + int hour = LocalDateTime.now().getHour(); + List missions = missionRepository.getInProgressMissions(); + + missions.forEach(mission -> { + if (mission.isMissionDay() && mission.isPushTime(hour)) { + List missionMembers = missionMemberRepository.findAllByMissionId(mission.getId()); + + missionMembers.forEach(missionMember -> { + Member member = missionMember.getMember(); + Optional verification = missionVerificationRepository.findByMemberIdAndMissionIdAndDate(member.getId(), mission.getId(), today); + if (verification.isEmpty() && member.getDeviceToken() != null) { + pushNotificationSender.sendIndividualMessage( + MISSION_VERIFICATION_WARNING.getTitle(), + MISSION_VERIFICATION_WARNING.getBody(), + member.getDeviceToken() + ); + } + }); + } + }); + } } diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java b/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java index 0333d93a..d9339943 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java @@ -20,9 +20,7 @@ import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; -import java.util.List; - -import static com.nexters.goalpanzi.application.firebase.PushNotificationMessage.*; +import static com.nexters.goalpanzi.domain.firebase.PushNotificationMessage.*; @Slf4j @Component @@ -37,7 +35,7 @@ public class MissionMemberEventHandler { @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) void handleCreateMissionEvent(final CreateMissionEvent event) { missionMemberService.joinMission(event.memberId(), new InvitationCode(event.invitationCode())); - log.info("Handled JoinMissionEvent for memberId: {}", event.memberId()); + log.info("Handled CreateMissionEvent for memberId: {}", event.memberId()); } @Async @@ -55,7 +53,7 @@ void handleDeleteMemberEvent(final DeleteMemberEvent event) { void handleDeleteMissionEvent(final DeleteMissionEvent event) { missionMemberService.deleteAllByMissionId(event.missionId()); missionVerificationService.deleteAllByMissionId(event.missionId()); - + pushNotificationSender.sendGroupMessage( MISSION_DELETED.getTitle(), MISSION_DELETED.getBody(), @@ -68,13 +66,11 @@ void handleDeleteMissionEvent(final DeleteMissionEvent event) { @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) void handleJoinMissionEvent(final JoinMissionEvent event) { - String topic = TopicGenerator.getTopic(event.missionId()); - pushNotificationSender.sendGroupMessage( + pushNotificationSender.sendIndividualMessage( MISSION_JOINED.getTitle(), MISSION_JOINED.getBody(event.nickname()), - topic + event.deviceToken() ); - topicSubscriber.subscribeToTopic(List.of(event.deviceToken()), topic); log.info("Handled JoinMissionEvent for missionId: {}", event.missionId()); } diff --git a/src/main/java/com/nexters/goalpanzi/common/aop/JobLoggingAspect.java b/src/main/java/com/nexters/goalpanzi/common/aop/JobLoggingAspect.java new file mode 100644 index 00000000..2bacb724 --- /dev/null +++ b/src/main/java/com/nexters/goalpanzi/common/aop/JobLoggingAspect.java @@ -0,0 +1,33 @@ +package com.nexters.goalpanzi.common.aop; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.StopWatch; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +public class JobLoggingAspect { + + @Around("execution(* com.nexters.goalpanzi.schedule.*.executeInternal(..))") + public void execute(final ProceedingJoinPoint joinPoint) throws Throwable { + String jobName = joinPoint.getTarget().getClass().getSimpleName(); + + log.info("{} started.", jobName); + + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + try { + joinPoint.proceed(); + } catch (Exception e) { + log.error("Error occurred while executing {}", jobName, e); + } + + stopWatch.stop(); + log.info("{} finished. Elapsed time: {} ms", jobName, 0); + } +} diff --git a/src/main/java/com/nexters/goalpanzi/application/firebase/PushNotificationMessage.java b/src/main/java/com/nexters/goalpanzi/domain/firebase/PushNotificationMessage.java similarity index 94% rename from src/main/java/com/nexters/goalpanzi/application/firebase/PushNotificationMessage.java rename to src/main/java/com/nexters/goalpanzi/domain/firebase/PushNotificationMessage.java index d69da853..234f117f 100644 --- a/src/main/java/com/nexters/goalpanzi/application/firebase/PushNotificationMessage.java +++ b/src/main/java/com/nexters/goalpanzi/domain/firebase/PushNotificationMessage.java @@ -1,4 +1,4 @@ -package com.nexters.goalpanzi.application.firebase; +package com.nexters.goalpanzi.domain.firebase; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -14,7 +14,7 @@ public enum PushNotificationMessage { // 미션 진행 중 MISSION_VERIFICATION_WARNING("\u23F0 마감임박! 1시간 남았어요!\uD83E\uDDE8\uD83D\uDCA5", "지금 인증 안 하면 오늘은 인증 실패!ㅠㅠ"), - MISSION_VERIFIED("˗ˋˏ 와 ˎˊ˗ %s명이 벌써 인증 완료 ˗ˋˏ 와 ˎˊ˗ ", "지금 누가 앞서가는지 확인해볼까요?"), + MISSION_VERIFIED("˗ˋˏ 와 ˎˊ˗ %d명이 벌써 인증 완료 ˗ˋˏ 와 ˎˊ˗ ", "지금 누가 앞서가는지 확인해볼까요?"), MISSION_NO_ONE_VERIFIED("잊었니?..\uD83C\uDF42", "아직 아무도 인증 안 했어요! 1빠로 인증해 모두를 앞서갈 타이밍!"), MISSION_COMPLETED("아니 글쎄..걔가 결국 1등 했다고?! \uD83D\uDDEF\uFE0F", "첫 번째 미션 완수자 등장! 빠르게 확인해 보세요!"), MISSION_DELETED("뭐? 미션 끝났다고? 너 누군데? \uD83D\uDC40", "방장이 미션을 끝냈어요! 다음 미션에서 새롭게 만나요!"), diff --git a/src/main/java/com/nexters/goalpanzi/domain/firebase/PushTime.java b/src/main/java/com/nexters/goalpanzi/domain/firebase/PushTime.java new file mode 100644 index 00000000..20da630d --- /dev/null +++ b/src/main/java/com/nexters/goalpanzi/domain/firebase/PushTime.java @@ -0,0 +1,16 @@ +package com.nexters.goalpanzi.domain.firebase; + +import lombok.Getter; + +@Getter +public enum PushTime { + MORNING(9), + AFTERNOON(15), + EVERYDAY(15); + + private final int hour; + + PushTime(final int hour) { + this.hour = hour; + } +} diff --git a/src/main/java/com/nexters/goalpanzi/domain/mission/Mission.java b/src/main/java/com/nexters/goalpanzi/domain/mission/Mission.java index 790ed110..671efd34 100644 --- a/src/main/java/com/nexters/goalpanzi/domain/mission/Mission.java +++ b/src/main/java/com/nexters/goalpanzi/domain/mission/Mission.java @@ -2,16 +2,18 @@ import com.nexters.goalpanzi.common.time.TimeUtil; import com.nexters.goalpanzi.domain.common.BaseEntity; +import com.nexters.goalpanzi.domain.firebase.PushTime; import com.nexters.goalpanzi.infrastructure.jpa.DaysOfWeekConverter; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.SQLRestriction; -import org.joda.time.LocalTime; +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import java.util.Objects; @@ -22,6 +24,7 @@ @Getter public class Mission extends BaseEntity { + public static final Integer MIN_MISSION_MEMBER = 2; public static final Integer MAX_MISSION_MEMBER = 10; @Id @@ -121,10 +124,12 @@ public boolean isMissionPeriod() { return !today.isBefore(missionStart) && !today.isAfter(missionEnd); } + // 오늘이 미션 인증 요일인지 검증 public boolean isMissionDay() { return this.missionDays.contains(DayOfWeek.valueOf(LocalDate.now().getDayOfWeek().name())); } + // 현재 시간이 미션 인증 시간인지 검증 public boolean isMissionTime() { String now = LocalTime.now().toString().substring(0, 5); return now.compareTo(uploadStartTime) >= 0 && now.compareTo(uploadEndTime) <= 0; @@ -147,6 +152,28 @@ public LocalDateTime getMissionUploadEndDateTime() { ); } + // 현재 시간이 미션 시작 예고 시간인지 검증 + // 미션 시작 예고 시간 == 미션 시작 1시간 전 + public boolean isReadyTime() { + LocalDateTime startTime = LocalDateTime.of(this.missionStartDate.toLocalDate(), LocalTime.parse(this.uploadStartTime)); + Duration duration = Duration.between(startTime, LocalDate.now()); + + return duration.isNegative() && duration.toHours() <= 1; + } + + // 현재 시간이 푸시 시간인지 검증 + // 1. 인증 시간이 오전인 경우, 09시에 푸시 + // 2. 인증 시간이 오후이거나 종일인 경우, 15시에 푸시 + public boolean isPushTime(final int hour) { + if (this.uploadStartTime.equals(TimeOfDay.MORNING.getStartTime())) { + return hour == PushTime.MORNING.getHour(); + } + if (this.uploadStartTime.equals(TimeOfDay.AFTERNOON.getStartTime())) { + return hour == PushTime.AFTERNOON.getHour(); + } + return hour == PushTime.EVERYDAY.getHour(); + } + @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/src/main/java/com/nexters/goalpanzi/domain/mission/MissionStatus.java b/src/main/java/com/nexters/goalpanzi/domain/mission/MissionStatus.java index 199b552b..d9066cab 100644 --- a/src/main/java/com/nexters/goalpanzi/domain/mission/MissionStatus.java +++ b/src/main/java/com/nexters/goalpanzi/domain/mission/MissionStatus.java @@ -4,6 +4,7 @@ import java.time.LocalDateTime; +import static com.nexters.goalpanzi.domain.mission.Mission.MIN_MISSION_MEMBER; import static com.nexters.goalpanzi.exception.ErrorCode.UNKNOWN_MISSION; @Getter @@ -44,11 +45,11 @@ public static MissionStatus fromMission( return CREATED; } - if (mission.isMissionPeriod() && currentMemberCount <= 1) { + if (mission.isMissionPeriod() && currentMemberCount < MIN_MISSION_MEMBER) { return CANCELED; } - if (mission.isMissionPeriod() && currentMemberCount > 1) { + if (mission.isMissionPeriod() && currentMemberCount >= MIN_MISSION_MEMBER) { return IN_PROGRESS; } diff --git a/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionMemberRepository.java b/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionMemberRepository.java index 3d9d25a3..97ced59a 100644 --- a/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionMemberRepository.java +++ b/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionMemberRepository.java @@ -3,10 +3,11 @@ import com.nexters.goalpanzi.domain.mission.MissionMember; import com.nexters.goalpanzi.exception.ErrorCode; import com.nexters.goalpanzi.exception.NotFoundException; +import jakarta.persistence.LockModeType; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; @@ -21,6 +22,7 @@ public interface MissionMemberRepository extends JpaRepository findAllWithMissionByMemberId(final Long memberId); + @Lock(LockModeType.PESSIMISTIC_WRITE) default MissionMember getMissionMember(final Long memberId, final Long missionId) { return findByMemberIdAndMissionId(memberId, missionId) .orElseThrow(() -> new NotFoundException(ErrorCode.NOT_JOINED_MISSION_MEMBER)); diff --git a/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionRepository.java b/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionRepository.java index c9087506..72a7d182 100644 --- a/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionRepository.java +++ b/src/main/java/com/nexters/goalpanzi/domain/mission/repository/MissionRepository.java @@ -2,19 +2,35 @@ import com.nexters.goalpanzi.domain.mission.InvitationCode; import com.nexters.goalpanzi.domain.mission.Mission; -import com.nexters.goalpanzi.exception.BaseException; import com.nexters.goalpanzi.exception.ErrorCode; import com.nexters.goalpanzi.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; public interface MissionRepository extends JpaRepository { Optional findByInvitationCode(final InvitationCode invitationCode); + List findByMissionStartDateGreaterThanEqual(final LocalDateTime todayStart); + + List findByMissionStartDateGreaterThanEqualAndMissionEndDateLessThanEqual(final LocalDateTime startDate, final LocalDateTime endDate); + default Mission getMission(final Long missionId) { return findById(missionId) .orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_MISSION, missionId)); } + + default List getReadyMissions() { + LocalDateTime todayStart = LocalDate.now().atStartOfDay(); + return findByMissionStartDateGreaterThanEqual(todayStart); + } + + default List getInProgressMissions() { + LocalDateTime todayStart = LocalDate.now().atStartOfDay(); + return findByMissionStartDateGreaterThanEqualAndMissionEndDateLessThanEqual(todayStart, todayStart); + } } diff --git a/src/main/java/com/nexters/goalpanzi/schedule/MissionCancellationWarningPushJob.java b/src/main/java/com/nexters/goalpanzi/schedule/MissionCancellationWarningPushJob.java new file mode 100644 index 00000000..9a8b822d --- /dev/null +++ b/src/main/java/com/nexters/goalpanzi/schedule/MissionCancellationWarningPushJob.java @@ -0,0 +1,30 @@ +package com.nexters.goalpanzi.schedule; + +import com.nexters.goalpanzi.application.mission.MissionMemberService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@RequiredArgsConstructor +@DisallowConcurrentExecution +@Component +public class MissionCancellationWarningPushJob extends AbstractJob implements CustomAutomationJob { + + private final MissionMemberService missionMemberService; + + @Override + protected ScheduleBuilder getScheduleBuilder() { + // 11:30, 23:30 마다 실행 + return CronScheduleBuilder.cronSchedule("0 30 11,23 * * ?") + .withMisfireHandlingInstructionDoNothing(); + } + + @Override + @Transactional + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + missionMemberService.sendCancellationWarningPushMessage(); + } +} diff --git a/src/main/java/com/nexters/goalpanzi/schedule/MissionReadyPushJob.java b/src/main/java/com/nexters/goalpanzi/schedule/MissionReadyPushJob.java new file mode 100644 index 00000000..1f7949d6 --- /dev/null +++ b/src/main/java/com/nexters/goalpanzi/schedule/MissionReadyPushJob.java @@ -0,0 +1,28 @@ +package com.nexters.goalpanzi.schedule; + +import com.nexters.goalpanzi.application.mission.MissionMemberService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@DisallowConcurrentExecution +@Component +public class MissionReadyPushJob extends AbstractJob implements CustomAutomationJob { + + private final MissionMemberService missionMemberService; + + @Override + protected ScheduleBuilder getScheduleBuilder() { + // 11:00, 23:00 마다 실행 + return CronScheduleBuilder.cronSchedule("0 0 11,23 * * ?") + .withMisfireHandlingInstructionDoNothing(); + } + + @Override + protected void executeInternal(final JobExecutionContext context) throws JobExecutionException { + missionMemberService.sendReadyPushMessage(); + } +} diff --git a/src/main/java/com/nexters/goalpanzi/schedule/MissionStatusJob.java b/src/main/java/com/nexters/goalpanzi/schedule/MissionStatusJob.java index 7e03b3e6..1c7f76de 100644 --- a/src/main/java/com/nexters/goalpanzi/schedule/MissionStatusJob.java +++ b/src/main/java/com/nexters/goalpanzi/schedule/MissionStatusJob.java @@ -3,12 +3,7 @@ import com.nexters.goalpanzi.application.mission.MissionMemberService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.time.StopWatch; -import org.quartz.CronScheduleBuilder; -import org.quartz.CronTrigger; -import org.quartz.DisallowConcurrentExecution; -import org.quartz.JobExecutionContext; -import org.quartz.ScheduleBuilder; +import org.quartz.*; import org.springframework.stereotype.Component; @Slf4j @@ -28,18 +23,6 @@ protected ScheduleBuilder getScheduleBuilder() { @Override protected void executeInternal(final JobExecutionContext context) { - log.info("MissionStatusJob started."); - - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - - try { - missionMemberService.batchUpdateStatus(); - } catch (Exception e) { - log.error("Error occurred while executing MissionStatusJob", e); - } - - stopWatch.stop(); // 타이머 종료 - log.info("MissionStatusJob finished. Elapsed time: {} ms", stopWatch.getTime()); + missionMemberService.batchUpdateStatus(); } } diff --git a/src/main/java/com/nexters/goalpanzi/schedule/MissionVerificationPushJob.java b/src/main/java/com/nexters/goalpanzi/schedule/MissionVerificationPushJob.java new file mode 100644 index 00000000..bc1b004a --- /dev/null +++ b/src/main/java/com/nexters/goalpanzi/schedule/MissionVerificationPushJob.java @@ -0,0 +1,28 @@ +package com.nexters.goalpanzi.schedule; + +import com.nexters.goalpanzi.application.mission.MissionVerificationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@DisallowConcurrentExecution +@Component +public class MissionVerificationPushJob extends AbstractJob implements CustomAutomationJob { + + private final MissionVerificationService missionVerificationService; + + @Override + protected ScheduleBuilder getScheduleBuilder() { + // 09:00, 15:00 마다 실행 + return CronScheduleBuilder.cronSchedule("0 0 9,15 * * ?") + .withMisfireHandlingInstructionDoNothing(); + } + + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + missionVerificationService.sendVerificationPushMessage(); + } +} diff --git a/src/main/java/com/nexters/goalpanzi/schedule/MissionVerificationWarningPushJob.java b/src/main/java/com/nexters/goalpanzi/schedule/MissionVerificationWarningPushJob.java new file mode 100644 index 00000000..ff2812d1 --- /dev/null +++ b/src/main/java/com/nexters/goalpanzi/schedule/MissionVerificationWarningPushJob.java @@ -0,0 +1,28 @@ +package com.nexters.goalpanzi.schedule; + +import com.nexters.goalpanzi.application.mission.MissionVerificationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@DisallowConcurrentExecution +@Component +public class MissionVerificationWarningPushJob extends AbstractJob implements CustomAutomationJob { + + private final MissionVerificationService missionVerificationService; + + @Override + protected ScheduleBuilder getScheduleBuilder() { + // 11:00, 23:00 마다 실행 + return CronScheduleBuilder.cronSchedule("0 0 11,23 * * ?") + .withMisfireHandlingInstructionDoNothing(); + } + + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + missionVerificationService.sendVerificationWarningPushMessage(); + } +}