-
Notifications
You must be signed in to change notification settings - Fork 1
[FIX] 행사 종료 시, 출석 체크가 자동으로 종료되지 않는 오류를 수정합니다. #250
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
Changes from 7 commits
d6a2394
2636968
d673d49
105748a
8a12680
562c283
a78e08d
440bb47
67854ca
4b9bd99
fdf24f7
245b944
3b767f5
fedeb83
61be00e
a965482
eb9c78e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,56 @@ | ||
| package com.blackcompany.eeos.program.application.service; | ||
|
|
||
| import com.blackcompany.eeos.common.utils.DateConverter; | ||
| import com.blackcompany.eeos.program.application.model.ProgramModel; | ||
| import com.blackcompany.eeos.program.application.support.DelayedQueue; | ||
| import com.blackcompany.eeos.program.application.usecase.ProgramQuitUsecase; | ||
| import com.blackcompany.eeos.program.persistence.ProgramRepository; | ||
| import com.blackcompany.eeos.program.persistence.RedisDelayedQueue; | ||
| import java.time.Instant; | ||
| import com.blackcompany.eeos.target.application.event.EndAttendModeEvent; | ||
| import java.time.LocalDate; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.context.ApplicationEventPublisher; | ||
| import org.springframework.scheduling.annotation.Scheduled; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class ProgramQuitService implements ProgramQuitUsecase { | ||
|
|
||
| private final RedisDelayedQueue redisDelayedQueue; | ||
| private final ProgramRepository programRepository; | ||
| private final DelayedQueue delayedQueue; | ||
| private final ApplicationEventPublisher eventPublisher; | ||
| private final String KEY = "quit_program_reservation"; | ||
|
|
||
| @Override | ||
| public void pushQuitAttendJob(ProgramModel model) { | ||
| long delayedTime = model.getProgramDate().getTime() - Instant.now().toEpochMilli(); | ||
| redisDelayedQueue.addTask(model.getId(), delayedTime); | ||
| // programDate 를 score 로 사용 | ||
| long programDate = model.getProgramDate().getTime() / 1000; | ||
|
|
||
| delayedQueue.addTask(KEY, model.getId(), programDate); | ||
| } | ||
|
|
||
| @Transactional | ||
| @Scheduled(cron = "0 0 0 * * *") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3. 스케줄링을 사용할 경우, 두 가지 방식이 가능할 것 같아요.
GPT에게 물어보니, 데이터가 많을 경우에는 큐 방식이 더 적절하다고 추천하더라고요. 😆 |
||
| public void quitAttend() { | ||
| try { | ||
| Set<Long> jobs = getReadyTasks(); | ||
| eventPublisher.publishEvent(EndAttendModeEvent.of(jobs)); | ||
| } catch (Exception e) { | ||
| log.error("[ProgramQuitService] 행사 자동 종료 중 에러가 발생하였습니다. {}", e.getMessage()); | ||
| throw e; | ||
| } | ||
| } | ||
|
|
||
| private Set<Long> getReadyTasks() { | ||
| long programDate = DateConverter.toEpochSecond(LocalDate.now()).getTime(); | ||
|
|
||
| Set<Long> jobs = | ||
| delayedQueue.getReadyTasks(KEY, (double) programDate).stream() | ||
| .map(id -> (Long) id) | ||
| .collect(Collectors.toSet()); | ||
| return jobs; | ||
| } | ||
rlajm1203 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package com.blackcompany.eeos.program.application.support; | ||
|
|
||
| import java.util.Set; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class DelayedQueue { | ||
|
|
||
| private final RedisTemplate<String, Object> redisTemplate; | ||
|
|
||
| public void addTask(String key, Object value, double score) { | ||
| redisTemplate.opsForZSet().add(key, value, score); | ||
| } | ||
|
|
||
| public Set<Object> getReadyTasks(String key, double score) { | ||
| Set<Object> tasks = redisTemplate.opsForZSet().rangeByScore(key, 0, score); | ||
|
|
||
| if (tasks != null && !tasks.isEmpty()) { | ||
| redisTemplate.opsForZSet().removeRangeByScore(key, 0, score); | ||
| } | ||
|
|
||
| return tasks; | ||
| } | ||
| } |
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,9 +10,6 @@ | |
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.transaction.event.TransactionPhase; | ||
| import org.springframework.transaction.event.TransactionalEventListener; | ||
| import org.springframework.transaction.support.TransactionSynchronizationManager; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
|
|
@@ -23,14 +20,11 @@ public class EndAttendModeEventListener { | |
| private final ProgramRepository programRepository; | ||
|
|
||
| @Async | ||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void handleDeletedProgram(EndAttendModeEvent event) { | ||
| log.info( | ||
| "출석 체크 종료 Transaction committed: {}", | ||
| TransactionSynchronizationManager.isActualTransactionActive()); | ||
|
|
||
| public void handle(EndAttendModeEvent event) { | ||
|
||
| log.info("출석 체크 자동 종료 시작"); | ||
| for (Long id : event.getProgramIds()) { | ||
| log.info("출석 체크 자동 종료 (programId : {})", id); | ||
| programRepository.changeAttendMode(id, ProgramAttendMode.END); | ||
| attendRepository.updateAttendStatusByProgramId( | ||
| id, AttendStatus.NONRESPONSE, AttendStatus.ABSENT); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p3.
현재 구조에서는 행사 자동 종료 중 하나에서 에러가 발생하면, 상위에서 트랜잭션이 관리되기 때문에 모든 행사의 자동 종료가 중단될 가능성이 있을 것 같아요.
하지만 한 프로그램의 자동 종료가 실패했다고 해서 다른 프로그램까지 롤백되거나 시도조차 못 할 이유는 없다고 생각해요.
그래서 개별 단위로 트랜잭션을 관리하는 방식도 좋은 대안이 될 것 같아요!