[TIL] 230901 SSE 를 이용한 알림 기능 만들기

CountryGirl·2023년 9월 1일
0

TIL

목록 보기
55/80

사용자가 작성한 게시글에 "나도 불편해요", "해결됐어요" 버튼이 눌리거나 댓글이 달리거나~ 게시글이 해결완료 처리가 되는 이벤트가 발생하면 게시글 작성자에게 알림을 보내기로 하였다.

클라이언트가 주기적으로 서버에 요청을 보내는 건 이벤트가 없을 때 불필요한 요청이 발생할 수 도 있기 때문에 비효율적이라고 판단했고, SSE 를 사용해서 실시간 통신을 통해 이벤트가 발생했을 때 알림을 보낼 수 있도록 했다.

📌 Notification Entity

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String type;
private Boolean readStatus = false;

@ManyToOne
private User recipient;

@ManyToOne
private User actor;

@ManyToOne
private Post post;
  • type: 댓글, 나도 불편해요, 해결됐어요, 해결 완료를 구분하기 위한 필드
  • readStatus: 읽음 상태 (기본값 = false)

User 는 2개를 사용한다.

  • recipient: 이벤트를 당한(?) 사람
  • actor: 이벤트 발생시킨 사람
  • post: 이벤트가 발생한 게시글

📌 Notificaiton Subscribe

public SseEmitter subscribe(User user) {
    SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE);
    try {
        sseEmitter.send(SseEmitter.event()
                .name("Connect").data("연결되었습니다."));
        log.info("[OPEN] - SSE connection opened for user {}", user.getNickname());
    } catch (IOException e) {
        e.printStackTrace();
    }

    sseEmitters.put(user.getUserId(), sseEmitter);
    sseEmitter.onCompletion(() -> sseEmitters.remove(user.getUserId()));
    sseEmitter.onTimeout(() -> sseEmitters.remove(user.getUserId()));
    sseEmitter.onError((e) -> sseEmitters.remove(user.getUserId()));

    return sseEmitter;
}

Postman


📌 Notification Unsubscribe

public void unsubscribe(User user) {
    SseEmitter sseEmitter = sseEmitters.get(user.getUserId());
    if (sseEmitter != null) {
        sseEmitter.complete();
        sseEmitters.remove(user.getUserId());
        log.info("[CLOSE] - SSE connection closed for user {}", user.getNickname());
    }
}

Postman

Unsubscribe API 을 요청하면 Subscribe 에서 Connection closed 라는 메시지를 확인할 수 있다.


📌 댓글 알림

public void commentNotification(Long commentId) {
    Comment comment = commentRepository.findById(commentId).orElseThrow(
            () -> new CommentException(ClientErrorCode.NO_COMMENT));
    Long postUserId = comment.getPost().getUser().getUserId();

    Notification notification = new Notification(
            title, "comment",
            comment.getPost().getUser(),
            comment.getPost(),
            comment.getUser()
    );

    notificationRepository.save(notification);
    if (sseEmitters.containsKey(postUserId)) {
        SseEmitter sseEmitter = sseEmitters.get(postUserId);
        try {
            // JSON 형태로 변환
            String dataJsonString = convertToJson(		// JSON 로 변환해주는 메서드 (custom)
        			comment.getUser().getNickname(),
                    comment.getPost().getTitle(),
                    comment.getPost().getPostId(),
                    comment.getCreatedAt(),
        			comment.getPost().getUser().getNickname()
			);

            assert dataJsonString != null;
            sseEmitter.send(SseEmitter.event()
                    .name("AddComment").data(dataJsonString));
        } catch (Exception e) {
            sseEmitters.remove(postUserId);
        }
    }
}

Postman

"나도 불편해요", "해결됐어요", "해결완료" 되었다는 알림도 위와 같이 구현하였다.

JSON 을 보내면 클라이언트에서도 받을 수 있고, 더 자세한 알림을 표시해줄 수 있다.


📌 알림 읽음 상태

@Transactional
public CommonResponse changeReadStatus(Long notificationId, User user) {
    Notification notification = notificationRepository.findById(notificationId).orElseThrow(
            () -> new NotificationException(ClientErrorCode.NO_NOTIFICATION));
    if (notification.getRecipient().getNickname().equals(user.getNickname())) {
        notification.setRead();
    } else {
        throw new NotificationException(ClientErrorCode.NO_REMISSION_READ);
    }
    return new CommonResponse<>(READ_NOTIFICATION);
}

set 메서드를 사용하긴 싫지만 어쩔 수 없는 상황이었다.
기본값으로 false 라고 되어있는 것을 true 로 바꾸는 메서드이다.


이렇게 알림을 구현하였다!! 끝~~~~!!!!
profile
💻🌾시골소녀의 엉망징창 개발 성장일지🌾💻 (2023.05.23 ~)

0개의 댓글