Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

서포터가 선택완료 된 후 기간 만료가 되면 3일 뒤 자동 리뷰 완료로 상태 변경하는 기능 구현 #727

Open
wants to merge 7 commits into
base: dev/BE
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package touch.baton.common.schedule.deadline.command;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import touch.baton.common.schedule.deadline.command.exception.DeadlineOutboxException;
import touch.baton.domain.common.BaseEntity;

import java.time.Instant;

import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

@Getter
@NoArgsConstructor(access = PROTECTED)
@Entity
public class DeadlineOutbox extends BaseEntity {

@GeneratedValue(strategy = IDENTITY)
@Id
private Long id;

private Long runnerPostId;

private Instant instantToRun;

@Builder
public DeadlineOutbox(final Long runnerPostId, final Instant instantToRun) {
validateNotNull(runnerPostId, instantToRun);
this.runnerPostId = runnerPostId;
this.instantToRun = instantToRun;
}

private void validateNotNull(final Long runnerPostId, final Instant instantToRun) {
if (runnerPostId == null) {
throw new DeadlineOutboxException("DeadlineOutBox의 runnerPostId는 null이 될 수 없습니다.");
}

if (instantToRun == null) {
throw new DeadlineOutboxException("DeadlineOutBox의 instantToRun은 null이 될 수 없습니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package touch.baton.common.schedule.deadline.command.event;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import touch.baton.common.schedule.deadline.command.service.RunnerPostDeadlineCheckScheduler;
import touch.baton.common.schedule.deadline.command.DeadlineOutbox;
import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository;
import touch.baton.domain.runnerpost.command.RunnerPost;
import touch.baton.domain.runnerpost.command.event.RunnerPostApplySupporterEvent;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT;

@RequiredArgsConstructor
public class TaskSchedulerEventListener {

private static final int GRACE_PERIODS_DAYS = 3;

private final TaskScheduler taskScheduler;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 일단 해보고, 불편하면 우테코 금지되었던거 써볼까...?

private final RunnerPostDeadlineCheckScheduler runnerPostDeadlineCheckScheduler;
private final DeadlineOutboxCommandRepository deadlineOutboxCommandRepository;

@TransactionalEventListener(phase = AFTER_COMMIT)
@Transactional
public void subscribeAppliedRunnerPostDeadline(final RunnerPostApplySupporterEvent event) {
final RunnerPost findRunnerPost = runnerPostDeadlineCheckScheduler.joinBySupporter(event.runnerPostId());
final Instant instantToRun = calculateInstant(findRunnerPost);

final DeadlineOutbox deadlineOutBox = DeadlineOutbox.builder()
.runnerPostId(event.runnerPostId())
.instantToRun(instantToRun)
.build();
deadlineOutboxCommandRepository.save(deadlineOutBox);
taskScheduler.schedule(() -> runnerPostDeadlineCheckScheduler.finishReview(event.runnerPostId()), instantToRun);
}

private Instant calculateInstant(final RunnerPost findRunnerPost) {
final LocalDateTime gracePeriods = findRunnerPost.getDeadline().getValue().plusDays(GRACE_PERIODS_DAYS);
return gracePeriods.atZone(ZoneId.systemDefault()).toInstant();
}

@EventListener(ApplicationStartedEvent.class)
public void setUpSchedules() {
deadlineOutboxCommandRepository.findAll().forEach(deadlineOutbox ->
taskScheduler.schedule(() -> runnerPostDeadlineCheckScheduler.finishReview(
deadlineOutbox.getRunnerPostId()), deadlineOutbox.getInstantToRun()
));
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package touch.baton.common.schedule.deadline.command.exception;

import touch.baton.domain.common.exception.BusinessException;

public class DeadlineOutboxException extends BusinessException {

public DeadlineOutboxException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package touch.baton.common.schedule.deadline.command.exception;

import touch.baton.domain.common.exception.BusinessException;

public class ScheduleBusinessException extends BusinessException {

public ScheduleBusinessException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package touch.baton.common.schedule.deadline.command.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import touch.baton.common.schedule.deadline.command.DeadlineOutbox;

public interface DeadlineOutboxCommandRepository extends JpaRepository<DeadlineOutbox, Long> {

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
delete from DeadlineOutbox do
where do.runnerPostId = :id
""")
void deleteByRunnerPostId(@Param("id") Long runnerPostId);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package touch.baton.common.schedule;
package touch.baton.common.schedule.deadline.command.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import touch.baton.domain.runnerpost.command.RunnerPost;

public interface ScheduleRunnerPostRepository extends JpaRepository<RunnerPost, Long> {
public interface RunnerPostDeadlineCommandRepository extends JpaRepository<RunnerPost, Long> {

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package touch.baton.common.schedule.deadline.command.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import touch.baton.common.schedule.deadline.command.exception.ScheduleBusinessException;
import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository;
import touch.baton.common.schedule.deadline.command.repository.RunnerPostDeadlineCommandRepository;
import touch.baton.common.schedule.deadline.query.repository.RunnerPostDeadlineQueryRepository;
import touch.baton.domain.runnerpost.command.RunnerPost;

@RequiredArgsConstructor
@Transactional
@Component
public class RunnerPostDeadlineCheckScheduler {

private final RunnerPostDeadlineCommandRepository runnerPostDeadlineCommandRepository;
private final RunnerPostDeadlineQueryRepository runnerPostDeadlineQueryRepository;
private final DeadlineOutboxCommandRepository deadlineOutboxCommandRepository;

public void updateReviewStatus() {
runnerPostDeadlineCommandRepository.updateAllPassedDeadline();
}

@Transactional(readOnly = true)
public RunnerPost joinBySupporter(final Long runnerPostId) {
return runnerPostDeadlineQueryRepository.joinSupporterByRunnerPostId(runnerPostId)
.orElseThrow(() -> new ScheduleBusinessException(String.format("RunnerPost를 찾을 수 없습니다. runnerPostId=%s",runnerPostId)));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마지막 컴마 뒤에 띄어쓰기
밑에 함수도 마찬가지

}

public void finishReview(final Long runnerPostId) {
final RunnerPost findRunnerPost = runnerPostDeadlineQueryRepository.joinSupporterByRunnerPostIdWithLock(runnerPostId)
.orElseThrow(() -> new ScheduleBusinessException(String.format("RunnerPost를 찾을 수 없습니다. runnerPostId=%s",runnerPostId)));
if (findRunnerPost.isReviewStatusDone()) {
return;
}

findRunnerPost.finishReview();
deadlineOutboxCommandRepository.deleteByRunnerPostId(runnerPostId);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package touch.baton.common.schedule;
package touch.baton.common.schedule.deadline.command.service;

import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class SchedulerService {
public class RunnerPostDeadlineSchedulerService {

private static final int CYCLE = 60000;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package touch.baton.common.schedule.deadline.query.repository;

import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import touch.baton.domain.runnerpost.command.RunnerPost;

import java.util.Optional;

public interface RunnerPostDeadlineQueryRepository extends JpaRepository<RunnerPost, Long> {

@Query("""
select rp
from RunnerPost rp
join fetch rp.supporter
where rp.id = :id
""")
Optional<RunnerPost> joinSupporterByRunnerPostId(@Param("id") Long runnerPostId);

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("""
select rp
from RunnerPost rp
join fetch rp.supporter
where rp.id = :id
""")
Optional<RunnerPost> joinSupporterByRunnerPostIdWithLock(@Param("id") Long runnerPostId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ public boolean isReviewStatusNotStarted() {
return reviewStatus.isNotStarted();
}

public boolean isReviewStatusDone() {
return reviewStatus.isSame(ReviewStatus.DONE);
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public interface RunnerPostQueryRepository extends JpaRepository<RunnerPost, Lon
@Query("""
select rp
from RunnerPost rp
join fetch Supporter s on s.id = rp.supporter.id
join fetch Member m on m.id = s.member.id
join fetch rp.supporter s
join fetch s.member
where rp.id = :runnerPostId
""")
Optional<RunnerPost> joinSupporterByRunnerPostId(@Param("runnerPostId") final Long runnerPostId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package touch.baton.common.fixture;

import touch.baton.common.schedule.deadline.command.DeadlineOutbox;

import java.time.Instant;

public abstract class DeadlineOutboxFixture {

private DeadlineOutboxFixture() {
}

public static DeadlineOutbox deadlineOutbox(final Long runnerPostId, final Instant instantToRun) {
return DeadlineOutbox.builder()
.runnerPostId(runnerPostId)
.instantToRun(instantToRun)
.build();
}
}

This file was deleted.

Loading
Loading