mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-05-08 03:37:28 +08:00
fix: 修复代码合并问题
This commit is contained in:
@@ -1,10 +1,17 @@
|
|||||||
package com.openisle.service;
|
package com.openisle.service;
|
||||||
|
|
||||||
import com.openisle.config.CachingConfig;
|
import com.openisle.config.CachingConfig;
|
||||||
import com.openisle.exception.NotFoundException;
|
|
||||||
import com.openisle.exception.RateLimitException;
|
import com.openisle.exception.RateLimitException;
|
||||||
import com.openisle.model.*;
|
import com.openisle.model.*;
|
||||||
import com.openisle.repository.*;
|
import com.openisle.repository.*;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@@ -21,36 +28,6 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.util.*;
|
|
||||||
import com.openisle.exception.RateLimitException;
|
|
||||||
import com.openisle.mapper.PostMapper;
|
|
||||||
import com.openisle.model.*;
|
|
||||||
import com.openisle.repository.CategoryRepository;
|
|
||||||
import com.openisle.repository.CommentRepository;
|
|
||||||
import com.openisle.repository.LotteryPostRepository;
|
|
||||||
import com.openisle.repository.NotificationRepository;
|
|
||||||
import com.openisle.repository.PointHistoryRepository;
|
|
||||||
import com.openisle.repository.PollPostRepository;
|
|
||||||
import com.openisle.repository.PollVoteRepository;
|
|
||||||
import com.openisle.repository.PostRepository;
|
|
||||||
import com.openisle.repository.PostSubscriptionRepository;
|
|
||||||
import com.openisle.repository.ReactionRepository;
|
|
||||||
import com.openisle.repository.TagRepository;
|
|
||||||
import com.openisle.repository.UserRepository;
|
|
||||||
import com.openisle.service.EmailSender;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class PostService {
|
public class PostService {
|
||||||
@@ -121,6 +98,7 @@ public class PostService {
|
|||||||
this.tagRepository = tagRepository;
|
this.tagRepository = tagRepository;
|
||||||
this.lotteryPostRepository = lotteryPostRepository;
|
this.lotteryPostRepository = lotteryPostRepository;
|
||||||
this.pollPostRepository = pollPostRepository;
|
this.pollPostRepository = pollPostRepository;
|
||||||
|
this.categoryProposalPostRepository = categoryProposalPostRepository;
|
||||||
this.pollVoteRepository = pollVoteRepository;
|
this.pollVoteRepository = pollVoteRepository;
|
||||||
this.notificationService = notificationService;
|
this.notificationService = notificationService;
|
||||||
this.subscriptionService = subscriptionService;
|
this.subscriptionService = subscriptionService;
|
||||||
@@ -165,18 +143,23 @@ public class PostService {
|
|||||||
for (PollPost pp : pollPostRepository.findByEndTimeBeforeAndResultAnnouncedFalse(now)) {
|
for (PollPost pp : pollPostRepository.findByEndTimeBeforeAndResultAnnouncedFalse(now)) {
|
||||||
applicationContext.getBean(PostService.class).finalizePoll(pp.getId());
|
applicationContext.getBean(PostService.class).finalizePoll(pp.getId());
|
||||||
}
|
}
|
||||||
for (CategoryProposalPost cp : categoryProposalPostRepository
|
for (CategoryProposalPost cp : categoryProposalPostRepository.findByEndTimeAfterAndProposalStatus(
|
||||||
.findByEndTimeAfterAndProposalStatus(now, CategoryProposalStatus.PENDING)) {
|
now,
|
||||||
if (cp.getEndTime() != null) {
|
CategoryProposalStatus.PENDING
|
||||||
ScheduledFuture<?> future = taskScheduler.schedule(
|
)) {
|
||||||
() -> applicationContext.getBean(PostService.class).finalizeProposal(cp.getId()),
|
if (cp.getEndTime() != null) {
|
||||||
java.util.Date.from(cp.getEndTime().atZone(ZoneId.systemDefault()).toInstant()));
|
ScheduledFuture<?> future = taskScheduler.schedule(
|
||||||
scheduledFinalizations.put(cp.getId(), future);
|
() -> applicationContext.getBean(PostService.class).finalizeProposal(cp.getId()),
|
||||||
}
|
java.util.Date.from(cp.getEndTime().atZone(ZoneId.systemDefault()).toInstant())
|
||||||
|
);
|
||||||
|
scheduledFinalizations.put(cp.getId(), future);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (CategoryProposalPost cp : categoryProposalPostRepository
|
for (CategoryProposalPost cp : categoryProposalPostRepository.findByEndTimeBeforeAndProposalStatus(
|
||||||
.findByEndTimeBeforeAndProposalStatus(now, CategoryProposalStatus.PENDING)) {
|
now,
|
||||||
applicationContext.getBean(PostService.class).finalizeProposal(cp.getId());
|
CategoryProposalStatus.PENDING
|
||||||
|
)) {
|
||||||
|
applicationContext.getBean(PostService.class).finalizeProposal(cp.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,41 +285,41 @@ public class PostService {
|
|||||||
pp.setMultiple(multiple != null && multiple);
|
pp.setMultiple(multiple != null && multiple);
|
||||||
post = pp;
|
post = pp;
|
||||||
} else if (actualType == PostType.PROPOSAL) {
|
} else if (actualType == PostType.PROPOSAL) {
|
||||||
CategoryProposalPost cp = new CategoryProposalPost();
|
CategoryProposalPost cp = new CategoryProposalPost();
|
||||||
if (proposedName == null || proposedName.isBlank()) {
|
if (proposedName == null || proposedName.isBlank()) {
|
||||||
throw new IllegalArgumentException("Proposed name required");
|
throw new IllegalArgumentException("Proposed name required");
|
||||||
|
}
|
||||||
|
if (proposedSlug == null || proposedSlug.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Proposed slug required");
|
||||||
|
}
|
||||||
|
if (categoryProposalPostRepository.existsByProposedSlug(proposedSlug)) {
|
||||||
|
throw new IllegalArgumentException("Proposed slug already exists: " + proposedSlug);
|
||||||
|
}
|
||||||
|
cp.setProposedName(proposedName);
|
||||||
|
cp.setProposedSlug(proposedSlug);
|
||||||
|
cp.setDescription(proposalDescription);
|
||||||
|
if (approveThreshold != null) {
|
||||||
|
if (approveThreshold < 0 || approveThreshold > 100) {
|
||||||
|
throw new IllegalArgumentException("approveThreshold must be between 0 and 100");
|
||||||
}
|
}
|
||||||
if (proposedSlug == null || proposedSlug.isBlank()) {
|
cp.setApproveThreshold(approveThreshold);
|
||||||
throw new IllegalArgumentException("Proposed slug required");
|
}
|
||||||
|
if (quorum != null) {
|
||||||
|
if (quorum < 0) {
|
||||||
|
throw new IllegalArgumentException("quorum must be >= 0");
|
||||||
}
|
}
|
||||||
if (categoryProposalPostRepository.existsByProposedSlug(proposedSlug)) {
|
cp.setQuorum(quorum);
|
||||||
throw new IllegalArgumentException("Proposed slug already exists: " + proposedSlug);
|
}
|
||||||
}
|
cp.setStartAt(startTime);
|
||||||
cp.setProposedName(proposedName);
|
cp.setEndTime(endTime);
|
||||||
cp.setProposedSlug(proposedSlug);
|
// default yes/no options if not provided
|
||||||
cp.setDescription(proposalDescription);
|
if (options == null || options.size() < 2) {
|
||||||
if (approveThreshold != null) {
|
cp.setOptions(List.of("同意", "反对"));
|
||||||
if (approveThreshold < 0 || approveThreshold > 100) {
|
} else {
|
||||||
throw new IllegalArgumentException("approveThreshold must be between 0 and 100");
|
cp.setOptions(options);
|
||||||
}
|
}
|
||||||
cp.setApproveThreshold(approveThreshold);
|
cp.setMultiple(false);
|
||||||
}
|
post = cp;
|
||||||
if (quorum != null) {
|
|
||||||
if (quorum < 0) {
|
|
||||||
throw new IllegalArgumentException("quorum must be >= 0");
|
|
||||||
}
|
|
||||||
cp.setQuorum(quorum);
|
|
||||||
}
|
|
||||||
cp.setStartAt(startTime);
|
|
||||||
cp.setEndTime(endTime);
|
|
||||||
// default yes/no options if not provided
|
|
||||||
if (options == null || options.size() < 2) {
|
|
||||||
cp.setOptions(List.of("同意", "反对"));
|
|
||||||
} else {
|
|
||||||
cp.setOptions(options);
|
|
||||||
}
|
|
||||||
cp.setMultiple(false);
|
|
||||||
post = cp;
|
|
||||||
} else {
|
} else {
|
||||||
post = new Post();
|
post = new Post();
|
||||||
}
|
}
|
||||||
@@ -349,8 +332,8 @@ public class PostService {
|
|||||||
post.setStatus(publishMode == PublishMode.REVIEW ? PostStatus.PENDING : PostStatus.PUBLISHED);
|
post.setStatus(publishMode == PublishMode.REVIEW ? PostStatus.PENDING : PostStatus.PUBLISHED);
|
||||||
if (post instanceof LotteryPost) {
|
if (post instanceof LotteryPost) {
|
||||||
post = lotteryPostRepository.save((LotteryPost) post);
|
post = lotteryPostRepository.save((LotteryPost) post);
|
||||||
}else if (post instanceof CategoryProposalPost categoryProposalPost) {
|
} else if (post instanceof CategoryProposalPost categoryProposalPost) {
|
||||||
post = categoryProposalPostRepository.save(categoryProposalPost);
|
post = categoryProposalPostRepository.save(categoryProposalPost);
|
||||||
} else if (post instanceof PollPost) {
|
} else if (post instanceof PollPost) {
|
||||||
post = pollPostRepository.save((PollPost) post);
|
post = pollPostRepository.save((PollPost) post);
|
||||||
} else {
|
} else {
|
||||||
@@ -406,10 +389,11 @@ public class PostService {
|
|||||||
);
|
);
|
||||||
scheduledFinalizations.put(lp.getId(), future);
|
scheduledFinalizations.put(lp.getId(), future);
|
||||||
} else if (post instanceof CategoryProposalPost cp && cp.getEndTime() != null) {
|
} else if (post instanceof CategoryProposalPost cp && cp.getEndTime() != null) {
|
||||||
ScheduledFuture<?> future = taskScheduler.schedule(
|
ScheduledFuture<?> future = taskScheduler.schedule(
|
||||||
() -> applicationContext.getBean(PostService.class).finalizeProposal(cp.getId()),
|
() -> applicationContext.getBean(PostService.class).finalizeProposal(cp.getId()),
|
||||||
java.util.Date.from(cp.getEndTime().atZone(ZoneId.systemDefault()).toInstant()));
|
java.util.Date.from(cp.getEndTime().atZone(ZoneId.systemDefault()).toInstant())
|
||||||
scheduledFinalizations.put(cp.getId(), future);
|
);
|
||||||
|
scheduledFinalizations.put(cp.getId(), future);
|
||||||
} else if (post instanceof PollPost pp && pp.getEndTime() != null) {
|
} else if (post instanceof PollPost pp && pp.getEndTime() != null) {
|
||||||
ScheduledFuture<?> future = taskScheduler.schedule(
|
ScheduledFuture<?> future = taskScheduler.schedule(
|
||||||
() -> applicationContext.getBean(PostService.class).finalizePoll(pp.getId()),
|
() -> applicationContext.getBean(PostService.class).finalizePoll(pp.getId()),
|
||||||
@@ -420,47 +404,72 @@ public class PostService {
|
|||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(
|
@CacheEvict(value = CachingConfig.POST_CACHE_NAME, allEntries = true)
|
||||||
value = CachingConfig.POST_CACHE_NAME, allEntries = true
|
|
||||||
)
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void finalizeProposal(Long postId) {
|
public void finalizeProposal(Long postId) {
|
||||||
scheduledFinalizations.remove(postId);
|
scheduledFinalizations.remove(postId);
|
||||||
categoryProposalPostRepository.findById(postId).ifPresent(cp -> {
|
categoryProposalPostRepository
|
||||||
if (cp.getProposalStatus() != CategoryProposalStatus.PENDING) {
|
.findById(postId)
|
||||||
return;
|
.ifPresent(cp -> {
|
||||||
}
|
if (cp.getProposalStatus() != CategoryProposalStatus.PENDING) {
|
||||||
int totalParticipants = cp.getParticipants() != null ? cp.getParticipants().size() : 0;
|
return;
|
||||||
int approveVotes = 0;
|
}
|
||||||
if (cp.getVotes() != null) {
|
int totalParticipants = cp.getParticipants() != null ? cp.getParticipants().size() : 0;
|
||||||
approveVotes = cp.getVotes().getOrDefault(0, 0);
|
int approveVotes = 0;
|
||||||
}
|
if (cp.getVotes() != null) {
|
||||||
boolean quorumMet = totalParticipants >= cp.getQuorum();
|
approveVotes = cp.getVotes().getOrDefault(0, 0);
|
||||||
int approvePercent = totalParticipants > 0 ? (approveVotes * 100) / totalParticipants : 0;
|
}
|
||||||
boolean thresholdMet = approvePercent >= cp.getApproveThreshold();
|
boolean quorumMet = totalParticipants >= cp.getQuorum();
|
||||||
if (quorumMet && thresholdMet) {
|
int approvePercent = totalParticipants > 0 ? (approveVotes * 100) / totalParticipants : 0;
|
||||||
cp.setProposalStatus(CategoryProposalStatus.APPROVED);
|
boolean thresholdMet = approvePercent >= cp.getApproveThreshold();
|
||||||
} else {
|
if (quorumMet && thresholdMet) {
|
||||||
cp.setProposalStatus(CategoryProposalStatus.REJECTED);
|
cp.setProposalStatus(CategoryProposalStatus.APPROVED);
|
||||||
String reason;
|
} else {
|
||||||
if (!quorumMet && !thresholdMet) {
|
cp.setProposalStatus(CategoryProposalStatus.REJECTED);
|
||||||
reason = "未达到法定人数且赞成率不足";
|
String reason;
|
||||||
} else if (!quorumMet) {
|
if (!quorumMet && !thresholdMet) {
|
||||||
reason = "未达到法定人数";
|
reason = "未达到法定人数且赞成率不足";
|
||||||
} else {
|
} else if (!quorumMet) {
|
||||||
reason = "赞成率不足";
|
reason = "未达到法定人数";
|
||||||
}
|
} else {
|
||||||
cp.setRejectReason(reason);
|
reason = "赞成率不足";
|
||||||
}
|
}
|
||||||
cp.setResultSnapshot("approveVotes=" + approveVotes + ", totalParticipants=" + totalParticipants + ", approvePercent=" + approvePercent);
|
cp.setRejectReason(reason);
|
||||||
categoryProposalPostRepository.save(cp);
|
}
|
||||||
if (cp.getAuthor() != null) {
|
cp.setResultSnapshot(
|
||||||
notificationService.createNotification(cp.getAuthor(), NotificationType.POLL_RESULT_OWNER, cp, null, null, null, null, null);
|
"approveVotes=" +
|
||||||
}
|
approveVotes +
|
||||||
for (User participant : cp.getParticipants()) {
|
", totalParticipants=" +
|
||||||
notificationService.createNotification(participant, NotificationType.POLL_RESULT_PARTICIPANT, cp, null, null, null, null, null);
|
totalParticipants +
|
||||||
}
|
", approvePercent=" +
|
||||||
postChangeLogService.recordVoteResult(cp);
|
approvePercent
|
||||||
|
);
|
||||||
|
categoryProposalPostRepository.save(cp);
|
||||||
|
if (cp.getAuthor() != null) {
|
||||||
|
notificationService.createNotification(
|
||||||
|
cp.getAuthor(),
|
||||||
|
NotificationType.POLL_RESULT_OWNER,
|
||||||
|
cp,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (User participant : cp.getParticipants()) {
|
||||||
|
notificationService.createNotification(
|
||||||
|
participant,
|
||||||
|
NotificationType.POLL_RESULT_PARTICIPANT,
|
||||||
|
cp,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
postChangeLogService.recordVoteResult(cp);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,15 @@ class PostControllerTest {
|
|||||||
@MockBean
|
@MockBean
|
||||||
private MedalService medalService;
|
private MedalService medalService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private CategoryService categoryService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private TagService tagService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private PointService pointService;
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private com.openisle.repository.PollVoteRepository pollVoteRepository;
|
private com.openisle.repository.PollVoteRepository pollVoteRepository;
|
||||||
|
|
||||||
@@ -275,6 +284,7 @@ class PostControllerTest {
|
|||||||
any(),
|
any(),
|
||||||
any(),
|
any(),
|
||||||
any(),
|
any(),
|
||||||
|
any(),
|
||||||
any()
|
any()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,3 +46,4 @@ app.avatar.base-url=${AVATAR_BASE_URL:https://api.dicebear.com/6.x}
|
|||||||
# Web push configuration
|
# Web push configuration
|
||||||
app.webpush.public-key=${WEBPUSH_PUBLIC_KEY:}
|
app.webpush.public-key=${WEBPUSH_PUBLIC_KEY:}
|
||||||
app.webpush.private-key=${WEBPUSH_PRIVATE_KEY:}
|
app.webpush.private-key=${WEBPUSH_PRIVATE_KEY:}
|
||||||
|
app.snippet-length=${SNIPPET_LENGTH:200}
|
||||||
|
|||||||
Reference in New Issue
Block a user