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 @@ -15,6 +15,8 @@
import com.jiwon.mylog.global.common.error.exception.NotFoundException;
import com.jiwon.mylog.domain.post.repository.PostRepository;
import com.jiwon.mylog.domain.user.repository.UserRepository;
import java.util.HashSet;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -49,12 +51,18 @@ public CommentResponse createComment(Long userId, Long postId, CommentCreateRequ
Comment comment = Comment.create(request, parent, user, post);
Comment savedComment = commentRepository.save(comment);

Long receiverId = post.getUser().getId();
Set<Long> receiverIds = new HashSet<>();
receiverIds.add(post.getUser().getId());
if (parent != null) {
receiverIds.add(parent.getUser().getId());
}
receiverIds.remove(userId);

eventPublisher.publishEvent(
new CommentCreatedEvent(
postId,
receiverId,
post.getUser().getId(),
receiverIds,
savedComment.getId(),
userId,
user.getUsername(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,48 @@
package com.jiwon.mylog.domain.event;

import com.jiwon.mylog.domain.event.dto.NotificationSendEvent;
import com.jiwon.mylog.domain.event.dto.comment.CommentCreatedEvent;
import com.jiwon.mylog.domain.event.dto.follow.FollowCreatedEvent;
import com.jiwon.mylog.domain.event.dto.follow.FollowDeletedEvent;
import com.jiwon.mylog.domain.event.dto.like.LikeCreatedEvent;
import com.jiwon.mylog.domain.notification.entity.Notification;
import com.jiwon.mylog.domain.notification.repository.NotificationRepository;
import com.jiwon.mylog.domain.notification.service.NotificationService;
import com.jiwon.mylog.domain.notification.entity.NotificationType;
import com.jiwon.mylog.domain.user.entity.User;
import com.jiwon.mylog.domain.user.repository.UserRepository;
import com.jiwon.mylog.global.common.error.ErrorCode;
import com.jiwon.mylog.global.common.error.exception.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Slf4j
@RequiredArgsConstructor
@Component
public class NotificationEventListener {

private final NotificationService notificationService;
private final NotificationRepository notificationRepository;
private final UserRepository userRepository;

@Transactional
@EventListener
@Async("Async")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleCommentCreated(CommentCreatedEvent event) {
if (event.postWriterId().equals(event.commentWriterId())) {
return;
}

try {
handleNotification(
event.postWriterId(),
event.commentWriterName() + "왹이 외계 수집물에 발 사를 남기다!",
"/posts/" + event.postId(),
NotificationType.COMMENT
);
} catch (Exception e) {
log.warn("댓글 생성 이벤트 알림 처리 실패: {}", event, e);
for (Long receiverId : event.receiverIds()) {
try {
notificationService.saveNotification(
receiverId,
event.commentWriterName() + "왹이 외계 수집물에 발 사를 남기다!",
"/posts/" + event.postId(),
NotificationType.COMMENT
);
} catch (Exception e) {
log.warn("댓글 생성 이벤트 알림 처리 실패: {}", event, e);
}
}
}

@Transactional
@EventListener
@Async("Async")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleLikeCreated(LikeCreatedEvent event) {
try {
handleNotification(
notificationService.saveNotification(
event.receiverId(),
event.senderName() + "왹이 외계 수집물에 푸 딩을 달았다!",
"/posts/" + event.targetId(),
Expand All @@ -61,11 +53,11 @@ public void handleLikeCreated(LikeCreatedEvent event) {
}
}

@Transactional
@EventListener
@Async("Async")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleFollowCreated(FollowCreatedEvent event) {
try {
handleNotification(
notificationService.saveNotification(
event.receiverId(),
event.followerName() + "왹이 잡 았다! 너 잡혔다!",
"/" + event.followerId(),
Expand All @@ -76,37 +68,26 @@ public void handleFollowCreated(FollowCreatedEvent event) {
}
}

@Transactional
@EventListener
@Async("Async")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleUnFollowCreated(FollowDeletedEvent event) {
try {
handleNotification(
notificationService.saveNotification(
event.receiverId(),
"오오자비로운" + event.followerName() + "왹께서널놓아주시니",
"/" + event.followerId(),
NotificationType.FOLLOW
NotificationType.UNFOLLOW
);
} catch (Exception e) {
log.warn("언팔로우 이벤트 알림 처리 실패: {}", event, e);
}
}

private void handleNotification(Long receiverId, String content, String url, NotificationType type) {
User receiver = getReceiver(receiverId);
Notification notification = Notification.create(receiver, content, url, type);
notificationRepository.save(notification);

sendSSE(receiverId, notification);
}

private User getReceiver(Long receiverId) {
return userRepository.findById(receiverId)
.orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_USER));
}

private void sendSSE(Long receiverId, Notification notification) {
@Async("Async")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendSSE(NotificationSendEvent event) {
try {
notificationService.sendNotification(receiverId, notification);
notificationService.sendNotification(event.receiverId(), event.content());
} catch(Exception e) {
log.warn("SSE 전송 실패");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jiwon.mylog.domain.event.dto;

public record NotificationSendEvent(
Long receiverId,
String content
) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.jiwon.mylog.domain.event.dto.comment;

import java.time.LocalDateTime;
import java.util.Set;

public record CommentCreatedEvent(
Long postId, Long postWriterId,
Set<Long> receiverIds,
Long commentId, Long commentWriterId, String commentWriterName,
LocalDateTime createdAt) {
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.jiwon.mylog.domain.notification.service;

import com.jiwon.mylog.domain.event.dto.NotificationSendEvent;
import com.jiwon.mylog.domain.notification.dto.NotificationCountResponse;
import com.jiwon.mylog.domain.notification.dto.NotificationResponse;
import com.jiwon.mylog.domain.notification.entity.Notification;
import com.jiwon.mylog.domain.notification.entity.NotificationType;
import com.jiwon.mylog.domain.notification.repository.NotificationRepository;
import com.jiwon.mylog.domain.notification.repository.SseEmitterRepository;
import com.jiwon.mylog.domain.user.entity.User;
import com.jiwon.mylog.domain.user.repository.UserRepository;
import com.jiwon.mylog.global.common.entity.PageResponse;
import com.jiwon.mylog.global.common.error.ErrorCode;
import com.jiwon.mylog.global.common.error.exception.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
Expand All @@ -26,8 +33,20 @@ public class NotificationService {

private static final Long DEFAULT_TIMEOUT = 30 * 60 * 1000L;

private final ApplicationEventPublisher eventPublisher;

private final SseEmitterRepository emitterRepository;
private final NotificationRepository notificationRepository;
private final UserRepository userRepository;

@Transactional
public void saveNotification(Long receiverId, String content, String url, NotificationType type) {
User receiver = getReceiver(receiverId);
Notification notification = Notification.create(receiver, content, url, type);
notificationRepository.save(notification);

eventPublisher.publishEvent(new NotificationSendEvent(receiverId, content));
}

@CacheEvict(value = "notification::count", key="'userId:' + #userId", condition = "#userId != null")
@Transactional
Expand Down Expand Up @@ -71,7 +90,7 @@ public SseEmitter subscribe(Long userId) {
return emitter;
}

public void sendNotification(Long userId, Notification notification) {
public void sendNotification(Long userId, String content) {
List<String> emitterIds = emitterRepository.findEmitterIdsByUserId(userId);

for (String emitterId : emitterIds) {
Expand All @@ -82,7 +101,7 @@ public void sendNotification(Long userId, Notification notification) {
}

log.info("notification: {}", emitterId);
send(emitter, emitterId, "notification", notification.getContent());
send(emitter, emitterId, "notification", content);
log.info("notification: {} 성공", emitterId);
}
}
Expand All @@ -99,4 +118,9 @@ private void send(SseEmitter emitter, String emitterId, String name, String cont
throw new RuntimeException();
}
}

private User getReceiver(Long receiverId) {
return userRepository.findById(receiverId)
.orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_USER));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@Configuration
public class AsyncConfig implements AsyncConfigurer {

@Bean("mailExecutor")
@Bean("Async")
public Executor customTaskExecutor() {
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
ex.setCorePoolSize(5);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void verifyEmailCode(String email, String code) {
redisUtil.delete(email);
}

@Async("mailExecutor")
@Async("Async")
public void sendCodeMailAsync(String email) {
if (redisUtil.exist(email)) {
redisUtil.delete(email);
Expand Down