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 @@ -18,13 +18,20 @@ public enum MessageType {
DM // 사용자 개인 DM
}

private MessageType type; // 메시지 타입 구분
private String message; // 보낼 메시지 내용
private MessageType type;
private String message;

// Webhook용 필드
private String webhookUrl;

// DM용 필드
private String username;
private String email;

@Builder.Default
private int retryCount = 0;

public void incrementRetryCount() {
this.retryCount++;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
/**
* Consumer / Worker
* Redis 큐에 쌓인 알림 요청을 하나씩 꺼내서 실제로 처리하는 Consumer입니다.
* 전송 실패 시 최대 MAX_RETRY_COUNT회 재시도합니다.
*/
@Component
@RequiredArgsConstructor
Expand All @@ -24,15 +25,22 @@ public class SlackNotificationWorker {
private final ObjectMapper objectMapper;

private static final String SLACK_QUEUE_KEY = "slack:notification:queue";
private static final int MAX_RETRY_COUNT = 3;

@Scheduled(fixedDelay = 1000)
public void processSlackQueue() {
try {
Object messageObj = redisTemplate.opsForList().leftPop(SLACK_QUEUE_KEY);
if (messageObj == null) return;
Object messageObj = redisTemplate.opsForList().leftPop(SLACK_QUEUE_KEY);
if (messageObj == null) return;

SlackMessageDto dto = objectMapper.convertValue(messageObj, SlackMessageDto.class);
SlackMessageDto dto;
try {
dto = objectMapper.convertValue(messageObj, SlackMessageDto.class);
} catch (Exception e) {
log.error("Slack 큐 메시지 역직렬화 실패 (폐기): {}", e.getMessage());
return;
}

try {
if (dto.getType() == SlackMessageDto.MessageType.WEBHOOK) {
slackApiService.sendWebhook(dto.getWebhookUrl(), dto.getMessage());
log.info("Slack Webhook 전송 성공 (Queue)");
Expand All @@ -43,10 +51,23 @@ public void processSlackQueue() {
}

} catch (BusinessException e) {
log.warn("Slack 알림 처리 실패 (Business): {}", e.getMessage());
// 비즈니스 예외(유저 없음 등)는 재시도해도 동일하게 실패하므로 바로 폐기
log.warn("Slack 알림 처리 실패 (Business, 폐기): {}", e.getMessage());

} catch (Exception e) {
log.error("Slack 큐 처리 중 시스템 오류 (재시도 필요 시 큐 복귀 고려)", e);
// 일시적 장애(네트워크, Slack API 다운 등)는 재시도
requeue(dto, e);
}
}

private void requeue(SlackMessageDto dto, Exception cause) {
if (dto.getRetryCount() < MAX_RETRY_COUNT) {
dto.incrementRetryCount();
redisTemplate.opsForList().rightPush(SLACK_QUEUE_KEY, dto);
log.warn("Slack 메시지 재시도 예약 ({}/{}회): {}", dto.getRetryCount(), MAX_RETRY_COUNT, cause.getMessage());
} else {
log.error("Slack 메시지 최대 재시도({}) 초과, 폐기: {} | 원인: {}",
MAX_RETRY_COUNT, dto.getMessage(), cause.getMessage());
}
}
}
}