From 3ab25c3213cca8c2ccfc0415eeef54fce10c9eb5 Mon Sep 17 00:00:00 2001 From: tkv00 Date: Wed, 17 Dec 2025 21:07:44 +0900 Subject: [PATCH] =?UTF-8?q?chore/#47:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20import=EB=AC=B8=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=B3=84=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../batch/common/CalculateSnapShotDate.java | 1 - .../slackjudge/batch/config/BatchConfig.java | 96 ++++++++++++++----- .../batch/config/BatchJobListener.java | 24 ++++- .../DateToLocalDateTimeKstConverter.java | 11 +++ .../LocalDateTimeToDateKstConverter.java | 15 ++- .../store/slackjudge/batch/dto/UserInfo.java | 6 ++ .../infra/mongo/document/SnapShotId.java | 3 + .../document/UserSolvedSnapShotDocument.java | 3 + .../batch/infra/mongo/dto/SaveSnapshot.java | 7 ++ .../infra/slack/SlackNotificationService.java | 49 +++++++++- .../slack/logging/LogEventMessageSpec.java | 8 ++ .../infra/slack/logging/SlackLogAppender.java | 26 ++++- .../slack/logging/SlackLogFormatter.java | 42 +++++++- .../logging/SlackLoggingWebhookSender.java | 53 +++++++++- .../slack/message/BatchEndMessageSpec.java | 11 +++ .../slack/message/BatchStartMessageSpec.java | 6 ++ .../slack/message/SlackMessageFactory.java | 27 +++++- .../slack/sender/SlackWebhookSender.java | 14 ++- .../solvedac/dto/ProblemInfoResponse.java | 4 + .../solvedac/dto/ProblemSearchResponse.java | 6 ++ .../infra/solvedac/dto/UserInfoResponse.java | 6 +- .../repository/ProblemJdbcRepository.java | 15 +++ .../batch/service/DetectionContext.java | 8 ++ .../batch/service/ProblemChangeDetector.java | 2 +- ...ectAndUpdateUserTierAndProblemTasklet.java | 20 +++- .../tasklet/FetchSolvedAcUserInfoTasklet.java | 19 +++- .../batch/tasklet/LoadAllUsersTasklet.java | 16 ++++ .../batch/tasklet/LoadSnapshotTasklet.java | 18 +++- .../batch/tasklet/SaveSnapshotTasklet.java | 19 +++- 29 files changed, 487 insertions(+), 48 deletions(-) diff --git a/slackjudge/src/main/java/store/slackjudge/batch/common/CalculateSnapShotDate.java b/slackjudge/src/main/java/store/slackjudge/batch/common/CalculateSnapShotDate.java index d374558..ca3bb54 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/common/CalculateSnapShotDate.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/common/CalculateSnapShotDate.java @@ -5,7 +5,6 @@ import java.time.Clock; import java.time.LocalDateTime; -import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; diff --git a/slackjudge/src/main/java/store/slackjudge/batch/config/BatchConfig.java b/slackjudge/src/main/java/store/slackjudge/batch/config/BatchConfig.java index 5f64c29..ffdaba4 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/config/BatchConfig.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/config/BatchConfig.java @@ -11,10 +11,6 @@ import org.springframework.transaction.PlatformTransactionManager; import store.slackjudge.batch.tasklet.*; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - @Configuration @RequiredArgsConstructor public class BatchConfig { @@ -26,12 +22,17 @@ public class BatchConfig { private final SaveSnapshotTasklet saveSnapshotTasklet; private final BatchJobListener jobListener; - private final BatchLogger logger; - - - /* =========================================== - * Job 정의 - * =========================================== */ + /*========================== + * + *BatchConfig + * Job 정의 + * @parm + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Bean public Job slackJudgeBatch( JobRepository jobRepository, @@ -52,9 +53,18 @@ public Job slackJudgeBatch( } - /* =========================================== - * Step01: RDB에서 유저 조회 - * =========================================== */ + /*========================== + * + *BatchConfig + * Step01: RDB에서 유저 조회 + * @parm jobRepository : Batch job repository + * transactionManager: 트랜잭션 엔진 공통 인터페이스 + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Bean public Step loadAllUsersStep( JobRepository jobRepository, @@ -74,9 +84,18 @@ public ExecutionContextPromotionListener promoteUsers() { } - /* =========================================== - * Step02: Mongo Snapshot 조회 - * =========================================== */ + /*========================== + * + *BatchConfig + * Step02: Mongo Snapshot 조회 + * @parm jobRepository : Batch job repository + * transactionManager: 트랜잭션 엔진 공통 인터페이스 + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Bean public Step loadSnapshotStep( JobRepository jobRepository, @@ -96,9 +115,18 @@ public ExecutionContextPromotionListener promoteSnapshots() { } - /* =========================================== - * Step03: solved.ac 유저 정보 조회 - * =========================================== */ + /*========================== + * + *BatchConfig + * Step03: solved.ac 유저 정보 조회 + * @parm jobRepository : Batch job repository + * transactionManager: 트랜잭션 엔진 공통 인터페이스 + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Bean public Step fetchSolvedAcUserInfoStep( JobRepository jobRepository, @@ -118,9 +146,18 @@ public ExecutionContextPromotionListener promoteUserSolvedInfo() { } - /* =========================================== - * Step04: 변경 감지 - * =========================================== */ + /*========================== + * + *BatchConfig + * Step04: 변경 감지 + * @parm jobRepository : Batch job repository + * transactionManager: 트랜잭션 엔진 공통 인터페이스 + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Bean public Step detectAndUpdateUserTierAndProblemStep( JobRepository jobRepository, @@ -140,9 +177,18 @@ public ExecutionContextPromotionListener promoteCurrentSnapshot() { } - /* =========================================== - * Step05: Snapshot 저장 - * =========================================== */ + /*========================== + * + *BatchConfig + * Step05: Snapshot 저장 + * @parm jobRepository : Batch job repository + * transactionManager: 트랜잭션 엔진 공통 인터페이스 + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Bean public Step saveSnapshotStep( JobRepository jobRepository, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/config/BatchJobListener.java b/slackjudge/src/main/java/store/slackjudge/batch/config/BatchJobListener.java index 96c36a4..2adc94c 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/config/BatchJobListener.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/config/BatchJobListener.java @@ -12,7 +12,7 @@ import java.time.Duration; import java.time.LocalDateTime; -import java.util.Objects; + @Component @RequiredArgsConstructor @@ -24,6 +24,17 @@ public class BatchJobListener { private final SlackNotificationService notificationService; private final CalculateSnapShotDate calculateSnapShotDate; + /*========================== + * + * BatchJobListener + * BatchJob 실행 이전 수행 작업 정의 + * @parm jobExecution : Job 실행 중에 발생 정보 저장 객체 + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @BeforeJob public void beforeJob(JobExecution jobExecution) { logger.jobStart(jobExecution.getJobInstance().getJobName()); @@ -39,6 +50,17 @@ public void beforeJob(JobExecution jobExecution) { ); } + /*========================== + * + * BatchJobListener + * BatchJob 실행 이후 수행 작업 정의 + * @parm jobExecution : Job 실행 중에 발생 정보 저장 객체 + * @return + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @AfterJob public void afterJob(JobExecution jobExecution) { ExecutionContext ctx = jobExecution.getExecutionContext(); diff --git a/slackjudge/src/main/java/store/slackjudge/batch/config/converter/DateToLocalDateTimeKstConverter.java b/slackjudge/src/main/java/store/slackjudge/batch/config/converter/DateToLocalDateTimeKstConverter.java index 382b9b1..c0cc250 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/config/converter/DateToLocalDateTimeKstConverter.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/config/converter/DateToLocalDateTimeKstConverter.java @@ -11,6 +11,17 @@ @Component @ReadingConverter public class DateToLocalDateTimeKstConverter implements Converter { + /*========================== + * + *DateToLocalDateTimeKstConverter + * UTC->KST로 변환합니다. + * @parm source : 입력 날짜 + * @return UTC로 변환된 LocalDateTime + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public LocalDateTime convert(Date source) { //UTC->KST diff --git a/slackjudge/src/main/java/store/slackjudge/batch/config/converter/LocalDateTimeToDateKstConverter.java b/slackjudge/src/main/java/store/slackjudge/batch/config/converter/LocalDateTimeToDateKstConverter.java index a9c1035..1dfe4c2 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/config/converter/LocalDateTimeToDateKstConverter.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/config/converter/LocalDateTimeToDateKstConverter.java @@ -1,11 +1,8 @@ package store.slackjudge.batch.config.converter; -import org.bson.json.StrictJsonWriter; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.WritingConverter; import org.springframework.stereotype.Component; - -import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; @@ -16,7 +13,17 @@ @Component @WritingConverter public class LocalDateTimeToDateKstConverter implements Converter { - + /*========================== + * + *LocalDateTimeToDateKstConverter + * UTC -> KST로 변환합니다. + * @parm source:UTC 기준 시간대 + * @return KST 변환된 시간 + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public Date convert(LocalDateTime source) { //KST->UTC diff --git a/slackjudge/src/main/java/store/slackjudge/batch/dto/UserInfo.java b/slackjudge/src/main/java/store/slackjudge/batch/dto/UserInfo.java index 5148513..b8ba574 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/dto/UserInfo.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/dto/UserInfo.java @@ -3,6 +3,12 @@ import java.io.Serializable; +/** + * 유저 정보 DTO + * @param baekJoonId 백준 아이디 + * @param userId 유저 PK + * @param baekJoonTier 백준 티어 + */ public record UserInfo( String baekJoonId, Long userId, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/SnapShotId.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/SnapShotId.java index 2c830c7..fa3f178 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/SnapShotId.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/SnapShotId.java @@ -10,6 +10,9 @@ @AllArgsConstructor(access = AccessLevel.PACKAGE) @RequiredArgsConstructor @Getter +/** + * Snapshot document 복합키 + */ public class SnapShotId implements Serializable { private String bojId; private LocalDateTime snapShotAt; diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/UserSolvedSnapShotDocument.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/UserSolvedSnapShotDocument.java index 71c0262..4dbd9ce 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/UserSolvedSnapShotDocument.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/document/UserSolvedSnapShotDocument.java @@ -18,6 +18,9 @@ @Getter @RequiredArgsConstructor @AllArgsConstructor(access = AccessLevel.PACKAGE) +/** + * 유저가 푼 문제 스냅샷 document + */ public class UserSolvedSnapShotDocument implements Serializable { //(백준 아이디, 배치 동작 시간) 복합 키 @Id diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/dto/SaveSnapshot.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/dto/SaveSnapshot.java index 3caf858..cbddf76 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/dto/SaveSnapshot.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/mongo/dto/SaveSnapshot.java @@ -8,6 +8,13 @@ /** * Document 저장용 DTO + * @param bojId 백준아이디 + * @param snapShotAt 스냅셧 기준일 + * @param solvedProblemIds 유저가 푼 문제 번호 리스트 + * @param solvedCount 유저가 푼 문제 수 + * @param tier 백준 티어 + * @param userId 유저 아이디 PK + * @param rating 유저 점수(solved.ac 기준) */ public record SaveSnapshot( String bojId, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/SlackNotificationService.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/SlackNotificationService.java index 7f7da46..7749fec 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/SlackNotificationService.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/SlackNotificationService.java @@ -15,7 +15,21 @@ public class SlackNotificationService { private final SlackSender sender; private final SlackMessageFactory messageFactory; - + /*========================== + * + * notifyBatchStart + * + * @parm jobName 배치 Job 이름 + * @parm batchTime 배치 실행 기준 시간 + * @parm workerNode 배치를 실행한 워커 노드 정보 + * @return void + * 배치 시작 시 Slack 알림을 전송 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ public void notifyBatchStart(String jobName, LocalDateTime batchTime,String workerNode){ Attachment message=messageFactory.batchStart(new BatchStartMessageSpec( jobName, batchTime, workerNode) @@ -24,6 +38,24 @@ public void notifyBatchStart(String jobName, LocalDateTime batchTime,String work sender.sendMessage(message); } + /*========================== + * + * notifyBatchSuccess + * + * @parm duration 배치 수행 시간(ms) + * @parm totalUsers 전체 처리 대상 사용자 수 + * @parm newUsers 신규 사용자 수 + * @parm updated 업데이트된 사용자 수 + * @parm failed 실패한 사용자 수 + * @parm time 배치 종료 시각 + * @return void + * 배치 성공 시 처리 결과를 Slack으로 전송 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ public void notifyBatchSuccess(long duration,int totalUsers,int newUsers,int updated,int failed,LocalDateTime time){ Attachment message=messageFactory.batchEnd(new BatchEndMessageSpec( "SUCCESS",duration,totalUsers,newUsers,updated,failed,time,"" @@ -31,7 +63,20 @@ public void notifyBatchSuccess(long duration,int totalUsers,int newUsers,int upd sender.sendMessage(message); } - + /*========================== + * + * notifyBatchFailed + * + * @parm occurredAt 배치 실패 발생 시각 + * @parm reason 배치 실패 사유 + * @return void + * 배치 실패 시 원인 정보를 포함한 Slack 알림을 전송 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ public void notifyBatchFailed(LocalDateTime occurredAt,String reason){ Attachment message = messageFactory.batchEnd( new BatchEndMessageSpec( diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/LogEventMessageSpec.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/LogEventMessageSpec.java index 9caeb73..ad39f04 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/LogEventMessageSpec.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/LogEventMessageSpec.java @@ -6,6 +6,14 @@ import java.time.LocalDateTime; import java.time.ZoneId; +/** + * Batch log event DTO + * @param level 로그 레벨 + * @param occurredAt 로그 발생 시각 + * @param logger 로그 객체 + * @param message 로그 메시지 + * @param stackTrace 로그가 호출 메서드 메타 데이터 + */ public record LogEventMessageSpec( String level, LocalDateTime occurredAt, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogAppender.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogAppender.java index 9ce353d..365036c 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogAppender.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogAppender.java @@ -13,6 +13,18 @@ public class SlackLogAppender extends AppenderBase { private SlackLoggingWebhookSender slackSender; // 구체 타입으로 변경 + /*========================== + * + *SlackLogAppender + * + * @parm event 로그백에서 전달된 로깅 이벤트 + * @return void + * WARN 또는 ERROR 로그 발생 시 StackTrace를 가공 후 슬랙으로 전송 + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override protected void append(ILoggingEvent event) { if (slackSender == null) { @@ -48,7 +60,19 @@ protected void append(ILoggingEvent event) { } } - //stackTrece 초기화 메서드 + /*========================== + * + * formatStackTrace + * + * @parm throwable 로그 이벤트에 포함된 예외 정보 + * @return String 예외 클래스, 메시지 및 StackTrace 상위 최대 10줄을 포맷 + * 예외 발생 원인을 슬랙 전송용 문자열 형태로 가공 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ private String formatStackTrace(IThrowableProxy throwable) { StringBuilder sb = new StringBuilder(); diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogFormatter.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogFormatter.java index 5d39a80..43ea6cf 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogFormatter.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLogFormatter.java @@ -7,7 +7,19 @@ import java.util.List; public class SlackLogFormatter { - + /*========================== + * + * format + * + * @parm messageSpec Slack 전송용 가공된 로그 이벤트 정보 + * @return Attachment Slack Webhook 전송에 사용되는 Attachment 객체 + * 로그 레벨에 따라 색상을 구분, 메시지 및 StackTrace를 필드로 구성 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ //로그 포맷팅 메서드 public Attachment format(LogEventMessageSpec messageSpec) { List fields = new ArrayList<>(); @@ -29,6 +41,21 @@ public Attachment format(LogEventMessageSpec messageSpec) { .build(); } + /*========================== + * + * createField + * + * @parm title 슬랙 필드 제목 + * @parm value 슬랙 필드 값 + * @parm shortField 한 줄 표시 여부 + * @return Field Slack Attachment에 포함될 Field 객체 + * Slack 메시지의 각 항목을 표준화된 필드 형태로 생성 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ private Field createField(String title, String value, boolean shortField) { return Field.builder() .title(title) @@ -37,6 +64,19 @@ private Field createField(String title, String value, boolean shortField) { .build(); } + /*========================== + * + * truncate + * + * @parm text 원본 문자열 + * @parm maxLength 최대 허용 길이 + * @return String 최대 길이를 초과할 경우 잘린 문자열 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ private String truncate(String text, int maxLength) { if (text == null) return ""; if (text.length() <= maxLength) return text; diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLoggingWebhookSender.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLoggingWebhookSender.java index 739050c..82df34d 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLoggingWebhookSender.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/logging/SlackLoggingWebhookSender.java @@ -9,13 +9,33 @@ public class SlackLoggingWebhookSender implements SlackSender { private String webhookUrl; private final SlackLogFormatter formatter = new SlackLogFormatter(); - + /*========================== + * + * sendMessage + * + * @parm attachment Slack으로 전송할 Attachment 객체 + * @return void + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public void sendMessage(Attachment attachment) { } - - // LogAppender에서 직접 호출할 메서드 + /*========================== + * + * send + * + * @parm messageSpec Slack 전송용 로그 이벤트 정보 + * @return void + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + ==========================**/ public void send(LogEventMessageSpec messageSpec) { if (webhookUrl == null || webhookUrl.isEmpty()) { return; @@ -31,6 +51,20 @@ public void send(LogEventMessageSpec messageSpec) { } } + /*========================== + * + * sendMessageInternal + * + * @parm emoji 로그 심각도 표시용 이모지 + * @parm level 로그 레벨 + * @parm attachment Slack으로 전송할 Attachment + * @return void + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ private void sendMessageInternal(String emoji, String level, Attachment attachment) { try { com.slack.api.Slack slack = com.slack.api.Slack.getInstance(); @@ -45,6 +79,19 @@ private void sendMessageInternal(String emoji, String level, Attachment attachme } } + /*========================== + * + * setWebhookUrl + * + * @parm webhookUrl Slack Incoming Webhook URL + * @return void + * logback 설정 파일에서 주입되며 슬랙 전송 대상 Webhook을 설정 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ public void setWebhookUrl(String webhookUrl) { this.webhookUrl = webhookUrl; } diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchEndMessageSpec.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchEndMessageSpec.java index 748e1fd..53daa8d 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchEndMessageSpec.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchEndMessageSpec.java @@ -2,6 +2,17 @@ import java.time.LocalDateTime; +/** + * 배치 종료 메시지 DTO + * @param status 상태 + * @param duration 배치 종료 시간 - 시작 시간 + * @param totalUsers 현재 총 유저 수 + * @param newUsers 새로 가입한 유저의 배치 처리 수 + * @param updatedUsers 변경이 감지된 유저의 배치 처리 수 + * @param failedUsers 실패한 유저의 배치 처리 수 + * @param time 현재 시간 + * @param reason 배치 실패 이유 (성공적으로 종료시는 반 겂) + */ public record BatchEndMessageSpec( String status, //SUCCESS, FAILED long duration, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchStartMessageSpec.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchStartMessageSpec.java index e0e1450..e9dd3ea 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchStartMessageSpec.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/BatchStartMessageSpec.java @@ -2,6 +2,12 @@ import java.time.LocalDateTime; +/** + * 배치 시작 시 전송 메시지 DTO + * @param jobName Batch Job 이름 + * @param batchTime 배치 시간 + * @param workerNode 구분자 + */ public record BatchStartMessageSpec( String jobName, LocalDateTime batchTime, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/SlackMessageFactory.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/SlackMessageFactory.java index 1c43c41..31e1097 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/SlackMessageFactory.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/message/SlackMessageFactory.java @@ -29,6 +29,19 @@ public class SlackMessageFactory { @Value("${slack.color.red}") private String RED; + /*========================== + * + * batchStart + * + * @parm spec 배치 시작 이벤트 정보 (Job명, 배치 시간, 워커 노드) + * @return Attachment 배치 시작 알림용 Slack Attachment + * 배치 시작 시점을 Slack 메시지로 구성하여 반환 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ public Attachment batchStart(BatchStartMessageSpec spec) { String text = layout.render( layout.title("Slack Judge Batch - START"), @@ -45,7 +58,19 @@ public Attachment batchStart(BatchStartMessageSpec spec) { .mrkdwnIn(List.of("text")) .build(); } - + /*========================== + * + * batchEnd + * + * @parm spec 배치 종료 이벤트 정보 (상태, 처리 결과, 소요 시간) + * @return Attachment 배치 종료 알림용 Slack Attachment + * 배치 성공/실패 여부에 따라 색상을 구분하여 결과 메시지를 생성 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ public Attachment batchEnd(BatchEndMessageSpec spec) { String text = layout.render( layout.title("SlackJudge Batch - " + spec.status()), diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/sender/SlackWebhookSender.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/sender/SlackWebhookSender.java index c36bf9a..1f14038 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/sender/SlackWebhookSender.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/slack/sender/SlackWebhookSender.java @@ -15,7 +15,19 @@ public class SlackWebhookSender implements SlackSender{ @Value("${slack.webhook.url}") private String webhookUrl; private final Slack slack=Slack.getInstance(); - + /*========================== + * + * sendMessage + * + * @parm attachment Slack으로 전송할 Attachment 메시지 + * @return void + * Slack Webhook URL을 이용해 메시지를 전송 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ //배치 시작 , 종료(성공,실패) 전송 @Override public void sendMessage(Attachment attachment) { diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemInfoResponse.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemInfoResponse.java index 9a2e2bb..b6ddcd5 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemInfoResponse.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemInfoResponse.java @@ -4,6 +4,10 @@ import java.io.Serializable; +/** + * 문제 번호 DTO + * @param problemId 문제 번호 + */ @JsonIgnoreProperties(ignoreUnknown = true) public record ProblemInfoResponse( int problemId //백준 문제 번호 diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemSearchResponse.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemSearchResponse.java index 0c243ca..70d5b24 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemSearchResponse.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/ProblemSearchResponse.java @@ -4,6 +4,12 @@ import java.io.Serializable; import java.util.List; + +/** + * solved.ac API 문제 정보 조회 DTO + * @param count + * @param items + */ @JsonIgnoreProperties(ignoreUnknown = true) public record ProblemSearchResponse( int count, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/UserInfoResponse.java b/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/UserInfoResponse.java index 82b950e..3cd72f6 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/UserInfoResponse.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/infra/solvedac/dto/UserInfoResponse.java @@ -6,7 +6,11 @@ import java.io.Serializable; /** - * solved.ac API 유저 정보 파싱용 dto + * solved.ac API 유저 정보 파싱용 DTO + * @param solvedCount 푼 문제 수 + * @param handle 백준 아이디 + * @param tier 백준 티어 + * @param rating 백준 점수 */ @JsonIgnoreProperties(ignoreUnknown = true) public record UserInfoResponse( diff --git a/slackjudge/src/main/java/store/slackjudge/batch/repository/ProblemJdbcRepository.java b/slackjudge/src/main/java/store/slackjudge/batch/repository/ProblemJdbcRepository.java index 6220ec3..72de9a0 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/repository/ProblemJdbcRepository.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/repository/ProblemJdbcRepository.java @@ -39,6 +39,21 @@ ON CONFLICT (user_id, problem_id) jdbcTemplate.update(sql, userId, problemNumber, batchTime); } + /*========================== + * + * ProblemJdbcRepository + * + * @parm snapshotAt 문제 풀이 스냅샷 기준 시각 + * @parm userId 문제를 푼 사용자 ID + * @parm problemIds 사용자가 푼 문제 ID 목록 + * @return void + * 사용자별 문제 풀이 정보를 중복 없이 일괄 삽입 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ public void batchInsertProblems(LocalDateTime snapshotAt, Long userId, List problemIds) { String sql = """ INSERT INTO users_problem (user_id, problem_id, is_solved, solved_time) diff --git a/slackjudge/src/main/java/store/slackjudge/batch/service/DetectionContext.java b/slackjudge/src/main/java/store/slackjudge/batch/service/DetectionContext.java index 0ef5a34..0c11181 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/service/DetectionContext.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/service/DetectionContext.java @@ -4,6 +4,14 @@ import store.slackjudge.batch.infra.mongo.document.UserSolvedSnapShotDocument; import java.time.LocalDateTime; + +/** + * @parm 현재 데이터 타입 + * @parm previous 이전 스냅샷 도큐먼트 + * @parm snapshotAt 스냅샷 기준 시각 + * @parm userId 사용자 ID + * @parm bojId 백준 사용자 ID + */ @Builder public record DetectionContext ( T current, diff --git a/slackjudge/src/main/java/store/slackjudge/batch/service/ProblemChangeDetector.java b/slackjudge/src/main/java/store/slackjudge/batch/service/ProblemChangeDetector.java index f167437..5998d93 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/service/ProblemChangeDetector.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/service/ProblemChangeDetector.java @@ -62,7 +62,7 @@ public void update(DetectionContext> context) { }); } - /*========================== + /*========================== * *ProblemChangeDetector * 신규 유저의 문제를 데이터베이스에 저장 diff --git a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/DetectAndUpdateUserTierAndProblemTasklet.java b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/DetectAndUpdateUserTierAndProblemTasklet.java index 91e7e5c..e753cb6 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/DetectAndUpdateUserTierAndProblemTasklet.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/DetectAndUpdateUserTierAndProblemTasklet.java @@ -1,6 +1,6 @@ package store.slackjudge.batch.tasklet; -import lombok.RequiredArgsConstructor; + import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; @@ -60,7 +60,23 @@ public DetectAndUpdateUserTierAndProblemTasklet( this.batchTime = batchTime; } - + /*========================== + * + * execute + * + * @parm contribution Step 실행 기여도 정보 + * @parm chunkContext Step/Job 실행 컨텍스트 + * @return RepeatStatus Tasklet 실행 완료 여부 + * + * JobExecutionContext로부터 사용자·스냅샷 데이터를 조회한 후 + * 티어 및 문제 풀이 변경 사항을 감지·업데이트하고 + * 현재 스냅샷과 통계 정보를 다음 Step으로 전달. + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { logger.stepStart("DetectAndUpdateUserTierAndProblemTasklet"); diff --git a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/FetchSolvedAcUserInfoTasklet.java b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/FetchSolvedAcUserInfoTasklet.java index d708791..b026dd5 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/FetchSolvedAcUserInfoTasklet.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/FetchSolvedAcUserInfoTasklet.java @@ -5,7 +5,6 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.item.ExecutionContext; @@ -20,7 +19,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * [ step 03 ] @@ -39,6 +37,23 @@ public class FetchSolvedAcUserInfoTasklet implements Tasklet { private final RetryTemplate retryTemplate; + /*========================== + * + * execute + * + * @parm contribution Step 실행 기여도 정보 + * @parm chunkContext Step/Job 실행 컨텍스트 + * @return RepeatStatus Tasklet 실행 완료 여부 + * + * JobExecutionContext에서 사용자 목록을 조회한 후 + * solved.ac API를 호출하여 사용자 정보를 수집하고 + * 조회 결과를 다음 Step에서 사용할 수 있도록 ExecutionContext에 저장 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { logger.stepStart("FetchSolvedAcUserInfoTasklet"); diff --git a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadAllUsersTasklet.java b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadAllUsersTasklet.java index 6708be2..62827f7 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadAllUsersTasklet.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadAllUsersTasklet.java @@ -32,6 +32,22 @@ public class LoadAllUsersTasklet implements Tasklet { * next step : LoadSnapshotTasklet */ + /*========================== + * + * execute + * + * @parm contribution Step 실행 기여도 정보 + * @parm chunkContext Step/Job 실행 컨텍스트 + * @return RepeatStatus Tasklet 실행 완료 여부 + * + * RDB에서 전체 사용자 정보를 조회한 후 + * 다음 Step에서 사용할 수 있도록 ExecutionContext에 저장 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext){ StepExecution stepExecution = chunkContext.getStepContext().getStepExecution(); diff --git a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadSnapshotTasklet.java b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadSnapshotTasklet.java index 37bdcc4..588fa04 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadSnapshotTasklet.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/LoadSnapshotTasklet.java @@ -57,7 +57,23 @@ public LoadSnapshotTasklet( this.batchTime = batchTime; } - + /*========================== + * + * execute + * + * @parm contribution Step 실행 기여도 정보 + * @parm chunkContext Step/Job 실행 컨텍스트 + * @return RepeatStatus Tasklet 실행 완료 여부 + * + * JobExecutionContext로부터 사용자 목록을 조회한 뒤 + * 기준 시각의 스냅샷을 MongoDB에서 조회 + * 사용자별 스냅샷 맵 형태로 ExecutionContext에 저장 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { logger.stepStart("LoadSnapshotTasklet"); diff --git a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/SaveSnapshotTasklet.java b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/SaveSnapshotTasklet.java index e48c0b9..7567244 100644 --- a/slackjudge/src/main/java/store/slackjudge/batch/tasklet/SaveSnapshotTasklet.java +++ b/slackjudge/src/main/java/store/slackjudge/batch/tasklet/SaveSnapshotTasklet.java @@ -28,7 +28,24 @@ public class SaveSnapshotTasklet implements Tasklet { private final UserSnapShotService service; private final BatchLogger logger; - + /*========================== + * + * execute + * + * @parm contribution Step 실행 기여도 정보 + * @parm chunkContext Step/Job 실행 컨텍스트 + * @return RepeatStatus Tasklet 실행 완료 여부 + * + * JobExecutionContext에서 최신 스냅샷 데이터를 조회한 후 + * MongoDB에 사용자별 스냅샷을 저장 + * + * 저장할 데이터가 없는 경우 별도 처리 없이 Step을 종료 + * + * @author kimdoyeon + * @version 1.0.0 + * @date 25. 12. 17. + * + ==========================**/ @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { logger.stepStart("SaveSnapshotTasklet");