사용자가 작성한 게시글에 "나도 불편해요", "해결됐어요" 버튼이 눌리거나 댓글이 달리거나~ 게시글이 해결완료 처리가 되는 이벤트가 발생하면 게시글 작성자에게 알림을 보내기로 하였다.
클라이언트가 주기적으로 서버에 요청을 보내는 건 이벤트가 없을 때 불필요한 요청이 발생할 수 도 있기 때문에 비효율적이라고 판단했고, SSE
를 사용해서 실시간 통신을 통해 이벤트가 발생했을 때 알림을 보낼 수 있도록 했다.
@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
: 이벤트가 발생한 게시글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;
}
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());
}
}
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);
}
}
}
"나도 불편해요", "해결됐어요", "해결완료" 되었다는 알림도 위와 같이 구현하였다.
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
로 바꾸는 메서드이다.