Merge pull request #480 from nagisa77/codex/fix-finalizelottery-execution-issue

Fix lottery finalization scheduling
This commit is contained in:
Tim
2025-08-11 10:59:20 +08:00
committed by GitHub
2 changed files with 19 additions and 9 deletions

View File

@@ -24,6 +24,7 @@ import com.openisle.model.Role;
import com.openisle.exception.RateLimitException; import com.openisle.exception.RateLimitException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import com.openisle.service.EmailSender; import com.openisle.service.EmailSender;
@@ -34,7 +35,6 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.ZoneId;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -65,6 +65,7 @@ public class PostService {
private final ImageUploader imageUploader; private final ImageUploader imageUploader;
private final TaskScheduler taskScheduler; private final TaskScheduler taskScheduler;
private final EmailSender emailSender; private final EmailSender emailSender;
private final ApplicationContext applicationContext;
private final ConcurrentMap<Long, ScheduledFuture<?>> scheduledFinalizations = new ConcurrentHashMap<>(); private final ConcurrentMap<Long, ScheduledFuture<?>> scheduledFinalizations = new ConcurrentHashMap<>();
@org.springframework.beans.factory.annotation.Autowired @org.springframework.beans.factory.annotation.Autowired
@@ -84,6 +85,7 @@ public class PostService {
ImageUploader imageUploader, ImageUploader imageUploader,
TaskScheduler taskScheduler, TaskScheduler taskScheduler,
EmailSender emailSender, EmailSender emailSender,
ApplicationContext applicationContext,
@Value("${app.post.publish-mode:DIRECT}") PublishMode publishMode) { @Value("${app.post.publish-mode:DIRECT}") PublishMode publishMode) {
this.postRepository = postRepository; this.postRepository = postRepository;
this.userRepository = userRepository; this.userRepository = userRepository;
@@ -101,6 +103,7 @@ public class PostService {
this.imageUploader = imageUploader; this.imageUploader = imageUploader;
this.taskScheduler = taskScheduler; this.taskScheduler = taskScheduler;
this.emailSender = emailSender; this.emailSender = emailSender;
this.applicationContext = applicationContext;
this.publishMode = publishMode; this.publishMode = publishMode;
} }
@@ -109,12 +112,12 @@ public class PostService {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
for (LotteryPost lp : lotteryPostRepository.findByEndTimeAfterAndWinnersIsEmpty(now)) { for (LotteryPost lp : lotteryPostRepository.findByEndTimeAfterAndWinnersIsEmpty(now)) {
ScheduledFuture<?> future = taskScheduler.schedule( ScheduledFuture<?> future = taskScheduler.schedule(
() -> finalizeLottery(lp.getId()), () -> applicationContext.getBean(PostService.class).finalizeLottery(lp.getId()),
java.util.Date.from(lp.getEndTime().atZone(java.time.ZoneOffset.UTC).toInstant())); java.util.Date.from(java.sql.Timestamp.valueOf(lp.getEndTime()).atZone(java.time.ZoneOffset.UTC).toInstant()));
scheduledFinalizations.put(lp.getId(), future); scheduledFinalizations.put(lp.getId(), future);
} }
for (LotteryPost lp : lotteryPostRepository.findByEndTimeBeforeAndWinnersIsEmpty(now)) { for (LotteryPost lp : lotteryPostRepository.findByEndTimeBeforeAndWinnersIsEmpty(now)) {
finalizeLottery(lp.getId()); applicationContext.getBean(PostService.class).finalizeLottery(lp.getId());
} }
} }
@@ -209,8 +212,9 @@ public class PostService {
if (post instanceof LotteryPost lp && lp.getEndTime() != null) { if (post instanceof LotteryPost lp && lp.getEndTime() != null) {
ScheduledFuture<?> future = taskScheduler.schedule( ScheduledFuture<?> future = taskScheduler.schedule(
() -> finalizeLottery(lp.getId()), () -> applicationContext.getBean(PostService.class).finalizeLottery(lp.getId()),
java.util.Date.from(lp.getEndTime().atZone(java.time.ZoneOffset.UTC).toInstant())); scheduledFinalizations.put(lp.getId(), future); java.util.Date.from(java.sql.Timestamp.valueOf(lp.getEndTime()).atZone(java.time.ZoneOffset.UTC).toInstant()));
scheduledFinalizations.put(lp.getId(), future);
} }
return post; return post;
} }
@@ -224,7 +228,8 @@ public class PostService {
lotteryPostRepository.save(post); lotteryPostRepository.save(post);
} }
private void finalizeLottery(Long postId) { @Transactional
public void finalizeLottery(Long postId) {
log.info("start to finalizeLottery for {}", postId); log.info("start to finalizeLottery for {}", postId);
scheduledFinalizations.remove(postId); scheduledFinalizations.remove(postId);
lotteryPostRepository.findById(postId).ifPresent(lp -> { lotteryPostRepository.findById(postId).ifPresent(lp -> {

View File

@@ -5,6 +5,7 @@ import com.openisle.repository.*;
import com.openisle.exception.RateLimitException; import com.openisle.exception.RateLimitException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.context.ApplicationContext;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@@ -32,11 +33,13 @@ class PostServiceTest {
ImageUploader imageUploader = mock(ImageUploader.class); ImageUploader imageUploader = mock(ImageUploader.class);
TaskScheduler taskScheduler = mock(TaskScheduler.class); TaskScheduler taskScheduler = mock(TaskScheduler.class);
EmailSender emailSender = mock(EmailSender.class); EmailSender emailSender = mock(EmailSender.class);
ApplicationContext context = mock(ApplicationContext.class);
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo, PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
notifService, subService, commentService, commentRepo, notifService, subService, commentService, commentRepo,
reactionRepo, subRepo, notificationRepo, postReadService, reactionRepo, subRepo, notificationRepo, postReadService,
imageUploader, taskScheduler, emailSender, PublishMode.DIRECT); imageUploader, taskScheduler, emailSender, context, PublishMode.DIRECT);
when(context.getBean(PostService.class)).thenReturn(service);
Post post = new Post(); Post post = new Post();
post.setId(1L); post.setId(1L);
@@ -76,11 +79,13 @@ class PostServiceTest {
ImageUploader imageUploader = mock(ImageUploader.class); ImageUploader imageUploader = mock(ImageUploader.class);
TaskScheduler taskScheduler = mock(TaskScheduler.class); TaskScheduler taskScheduler = mock(TaskScheduler.class);
EmailSender emailSender = mock(EmailSender.class); EmailSender emailSender = mock(EmailSender.class);
ApplicationContext context = mock(ApplicationContext.class);
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo, PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
notifService, subService, commentService, commentRepo, notifService, subService, commentService, commentRepo,
reactionRepo, subRepo, notificationRepo, postReadService, reactionRepo, subRepo, notificationRepo, postReadService,
imageUploader, taskScheduler, emailSender, PublishMode.DIRECT); imageUploader, taskScheduler, emailSender, context, PublishMode.DIRECT);
when(context.getBean(PostService.class)).thenReturn(service);
when(postRepo.countByAuthorAfter(eq("alice"), any())).thenReturn(1L); when(postRepo.countByAuthorAfter(eq("alice"), any())).thenReturn(1L);