Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
import com.example.solidconnection.application.domain.Gpa;
import com.example.solidconnection.common.VerifyStatus;
import com.example.solidconnection.common.discord.DiscordNotificationType;
import com.example.solidconnection.common.discord.DiscordNotifier;
import com.example.solidconnection.common.discord.DiscordReviewMarker;
import com.example.solidconnection.common.discord.event.DiscordReviewCompletedEvent;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.score.domain.GpaScore;
import com.example.solidconnection.score.repository.GpaScoreRepository;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
Expand All @@ -29,7 +30,7 @@ public class AdminGpaScoreService {

private final GpaScoreRepository gpaScoreRepository;
private final SiteUserRepository siteUserRepository;
private final DiscordNotifier discordNotifier;
private final ApplicationEventPublisher eventPublisher;

@Transactional(readOnly = true)
public Page<GpaScoreSearchResponse> searchGpaScores(ScoreSearchCondition scoreSearchCondition, Pageable pageable) {
Expand Down Expand Up @@ -64,11 +65,11 @@ private void publishReviewResult(GpaScore gpaScore, VerifyStatus verifyStatus) {
}
SiteUser siteUser = siteUserRepository.findById(gpaScore.getSiteUserId())
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
discordNotifier.markReviewResult(
eventPublisher.publishEvent(new DiscordReviewCompletedEvent(
DiscordNotificationType.GPA_SCORE,
gpaScore.getId(),
siteUser.getNickname(),
marker
);
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
import com.example.solidconnection.application.domain.LanguageTest;
import com.example.solidconnection.common.VerifyStatus;
import com.example.solidconnection.common.discord.DiscordNotificationType;
import com.example.solidconnection.common.discord.DiscordNotifier;
import com.example.solidconnection.common.discord.DiscordReviewMarker;
import com.example.solidconnection.common.discord.event.DiscordReviewCompletedEvent;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.score.domain.LanguageTestScore;
import com.example.solidconnection.score.repository.LanguageTestScoreRepository;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
Expand All @@ -29,7 +30,7 @@ public class AdminLanguageTestScoreService {

private final LanguageTestScoreRepository languageTestScoreRepository;
private final SiteUserRepository siteUserRepository;
private final DiscordNotifier discordNotifier;
private final ApplicationEventPublisher eventPublisher;

@Transactional(readOnly = true)
public Page<LanguageTestScoreSearchResponse> searchLanguageTestScores(ScoreSearchCondition scoreSearchCondition, Pageable pageable) {
Expand Down Expand Up @@ -64,11 +65,11 @@ private void publishReviewResult(LanguageTestScore languageTestScore, VerifyStat
}
SiteUser siteUser = siteUserRepository.findById(languageTestScore.getSiteUserId())
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
discordNotifier.markReviewResult(
eventPublisher.publishEvent(new DiscordReviewCompletedEvent(
DiscordNotificationType.LANGUAGE_TEST_SCORE,
languageTestScore.getId(),
siteUser.getNickname(),
marker
);
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import com.example.solidconnection.admin.dto.MentorApplicationSearchCondition;
import com.example.solidconnection.admin.dto.MentorApplicationSearchResponse;
import com.example.solidconnection.common.discord.DiscordNotificationType;
import com.example.solidconnection.common.discord.DiscordNotifier;
import com.example.solidconnection.common.discord.DiscordReviewMarker;
import com.example.solidconnection.common.discord.event.DiscordReviewCompletedEvent;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.mentor.domain.Mentor;
import com.example.solidconnection.mentor.domain.MentorApplication;
Expand All @@ -25,6 +25,7 @@
import java.util.List;
import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
Expand All @@ -38,7 +39,7 @@ public class AdminMentorApplicationService {
private final HostUniversityRepository hostUniversityRepository;
private final SiteUserRepository siteUserRepository;
private final MentorRepository mentorRepository;
private final DiscordNotifier discordNotifier;
private final ApplicationEventPublisher eventPublisher;

@Transactional(readOnly = true)
public Page<MentorApplicationSearchResponse> searchMentorApplications(
Expand Down Expand Up @@ -91,12 +92,12 @@ public void rejectMentorApplication(
}

private void publishReviewResult(long mentorApplicationId, String applicantInfo, String marker) {
discordNotifier.markReviewResult(
eventPublisher.publishEvent(new DiscordReviewCompletedEvent(
DiscordNotificationType.MENTOR_APPLICATION,
mentorApplicationId,
applicantInfo,
marker
);
));
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.example.solidconnection.common.discord;

import com.example.solidconnection.common.discord.event.DiscordReportCreatedEvent;
import com.example.solidconnection.common.discord.event.DiscordReviewCompletedEvent;
import com.example.solidconnection.common.discord.event.DiscordReviewRequestedEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;

@Component
@EnableAsync
@RequiredArgsConstructor
@Slf4j
public class DiscordNotificationEventListener {

private final DiscordNotifier discordNotifier;

@Async("asyncExecutor")
@TransactionalEventListener
public void handleReviewRequested(DiscordReviewRequestedEvent event) {
try {
discordNotifier.notify(event.type(), event.reviewId(), event.applicantInfo());
} catch (Exception e) {
log.error("검수 요청 알림을 전송하지 못했습니다. reviewType={}, reviewId={}, exception={}",
event.type(), event.reviewId(), e.getClass().getSimpleName());
}
}

@Async("asyncExecutor")
@TransactionalEventListener
public void handleReviewCompleted(DiscordReviewCompletedEvent event) {
try {
discordNotifier.markReviewResult(event.type(), event.reviewId(), event.applicantInfo(), event.marker());
Comment on lines +32 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve per-review ordering for result notifications

When an administrator updates the same score twice, for example approving it and then correcting it to rejected, both committed events can execute concurrently because asyncExecutor has five core threads. If the earlier event's Discord PATCH finishes last, it overwrites the newer result with an approved marker even though the database contains REJECTED. This can now happen even for sequential HTTP requests; previously each request completed its Discord edit before returning. Serialize result updates per review, or otherwise prevent older events from overwriting newer results.

Useful? React with 👍 / 👎.

} catch (Exception e) {
log.error("검수 결과를 표시하지 못했습니다. reviewType={}, reviewId={}, exception={}",
event.type(), event.reviewId(), e.getClass().getSimpleName());
}
}

@Async("asyncExecutor")
@TransactionalEventListener
public void handleReportCreated(DiscordReportCreatedEvent event) {
discordNotifier.notify(DiscordNotificationType.REPORT, "신고자 ID: " + event.reporterId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@
import com.example.solidconnection.common.discord.service.DiscordNotificationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;

@Component
@EnableAsync
@Slf4j
public class DiscordNotifier {

Expand All @@ -33,7 +30,6 @@ public DiscordNotifier(
this.environment = environment;
}

@Async
public void notify(DiscordNotificationType type, String applicantInfo) {
if (isNotifyDisabled()) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.solidconnection.common.discord.event;

public record DiscordReportCreatedEvent(
long reporterId
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.solidconnection.common.discord.event;

import com.example.solidconnection.common.discord.DiscordNotificationType;

public record DiscordReviewCompletedEvent(
DiscordNotificationType type,
long reviewId,
String applicantInfo,
String marker
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.solidconnection.common.discord.event;

import com.example.solidconnection.common.discord.DiscordNotificationType;

public record DiscordReviewRequestedEvent(
DiscordNotificationType type,
long reviewId,
String applicantInfo
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND;

import com.example.solidconnection.common.discord.DiscordNotificationType;
import com.example.solidconnection.common.discord.DiscordNotifier;
import com.example.solidconnection.common.discord.event.DiscordReviewRequestedEvent;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.mentor.domain.MentorApplication;
import com.example.solidconnection.mentor.domain.MentorApplicationStatus;
Expand All @@ -22,6 +22,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -37,7 +38,7 @@ public class MentorApplicationService {
private final SiteUserRepository siteUserRepository;
private final S3Service s3Service;
private final TermRepository termRepository;
private final DiscordNotifier discordNotifier;
private final ApplicationEventPublisher eventPublisher;

@Transactional
public void submitMentorApplication(
Expand All @@ -63,11 +64,11 @@ public void submitMentorApplication(
mentorApplicationRequest.exchangeStatus()
);
MentorApplication savedMentorApplication = mentorApplicationRepository.save(mentorApplication);
discordNotifier.notify(
eventPublisher.publishEvent(new DiscordReviewRequestedEvent(
DiscordNotificationType.MENTOR_APPLICATION,
savedMentorApplication.getId(),
siteUser.getNickname()
);
));
}

private void ensureNoPendingOrApprovedMentorApplication(long siteUserId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import com.example.solidconnection.chat.domain.ChatParticipant;
import com.example.solidconnection.chat.repository.ChatMessageRepository;
import com.example.solidconnection.chat.repository.ChatParticipantRepository;
import com.example.solidconnection.common.discord.DiscordNotificationType;
import com.example.solidconnection.common.discord.DiscordNotifier;
import com.example.solidconnection.common.discord.event.DiscordReportCreatedEvent;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.common.exception.ErrorCode;
import com.example.solidconnection.community.post.domain.Post;
Expand All @@ -19,6 +18,7 @@
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -30,7 +30,7 @@ public class ReportService {
private final PostRepository postRepository;
private final ChatMessageRepository chatMessageRepository;
private final ChatParticipantRepository chatParticipantRepository;
private final DiscordNotifier discordNotifier;
private final ApplicationEventPublisher eventPublisher;

@Transactional
public void createReport(long reporterId, ReportRequest request) {
Expand All @@ -42,7 +42,7 @@ public void createReport(long reporterId, ReportRequest request) {

Report report = new Report(reporterId, reportedId, request.reportType(), request.targetType(), request.targetId());
reportRepository.save(report);
discordNotifier.notify(DiscordNotificationType.REPORT, "신고자 ID: " + reporterId);
eventPublisher.publishEvent(new DiscordReportCreatedEvent(reporterId));
}

private void validateReporterAndReportedExists(long reporterId, long reportedId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.example.solidconnection.application.domain.Gpa;
import com.example.solidconnection.application.domain.LanguageTest;
import com.example.solidconnection.common.discord.DiscordNotificationType;
import com.example.solidconnection.common.discord.DiscordNotifier;
import com.example.solidconnection.common.discord.event.DiscordReviewRequestedEvent;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.s3.domain.UploadPath;
import com.example.solidconnection.s3.dto.UploadedFileUrlResponse;
Expand All @@ -26,6 +26,7 @@
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -39,7 +40,7 @@ public class ScoreService {
private final LanguageTestScoreRepository languageTestScoreRepository;
private final SiteUserRepository siteUserRepository;
private final HomeUniversityQueryService homeUniversityQueryService;
private final DiscordNotifier discordNotifier;
private final ApplicationEventPublisher eventPublisher;

@Transactional
public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, MultipartFile file) {
Expand All @@ -49,7 +50,11 @@ public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, Mul
Gpa gpa = new Gpa(gpaScoreRequest.gpa(), gpaScoreRequest.gpaCriteria(), uploadedFile.fileUrl());
GpaScore newGpaScore = new GpaScore(gpa, siteUser);
GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore);
discordNotifier.notify(DiscordNotificationType.GPA_SCORE, savedNewGpaScore.getId(), siteUser.getNickname());
eventPublisher.publishEvent(new DiscordReviewRequestedEvent(
DiscordNotificationType.GPA_SCORE,
savedNewGpaScore.getId(),
siteUser.getNickname()
));
return savedNewGpaScore.getId();
}

Expand All @@ -62,7 +67,11 @@ public Long submitLanguageTestScore(long siteUserId, LanguageTestScoreRequest la
languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl());
LanguageTestScore newScore = new LanguageTestScore(languageTest, siteUser);
LanguageTestScore savedNewScore = languageTestScoreRepository.save(newScore);
discordNotifier.notify(DiscordNotificationType.LANGUAGE_TEST_SCORE, savedNewScore.getId(), siteUser.getNickname());
eventPublisher.publishEvent(new DiscordReviewRequestedEvent(
DiscordNotificationType.LANGUAGE_TEST_SCORE,
savedNewScore.getId(),
siteUser.getNickname()
));
return savedNewScore.getId();
}

Expand Down
Loading
Loading