[Spring] 게시판 만들기 15 – 댓글 알람 추가하기 (WebSocket, AOP)

이전 포스팅 마지막 jsp (list.jsp) 를 보면 레이아웃 수정이 있었는데 다른페이지들도 그렇게 맞추면된다.

그럼 댓글을 달았을 때 웹소켓을 사용하여 알람이 나오도록 한번 만들어 본다.

1. build.gradle 에 아래 내용을 추가한다.

implementation 'org.springframework.boot:spring-boot-starter-websocket'

//웹소켓용

    implementation 'org.springframework.boot:spring-boot-starter-aop'

    implementation 'org.aspectj:aspectjweaver:1.9.19'

//AOP 용

알람을 보낼 websocket 과, 특정 로직을 처리할때 (댓글이 달릴때) 사용하기 위한 AOP 를 위해 디펜던시를 추가해준다.

2. WebSocket 설정

com.example.post.config 아래에 새로운 Config 클래스를 작성한다.

package com.example.post.config;

import org.springframework.context.annotation.Configuration;

import org.springframework.messaging.simp.config.MessageBrokerRegistry;

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;

import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration

@EnableWebSocketMessageBroker

public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer{

    @Override

    public void configureMessageBroker(MessageBrokerRegistry config){

        config.enableSimpleBroker("/user");

        config.setApplicationDestinationPrefixes("/app");

    }

    @Override

    public void registerStompEndpoints(StompEndpointRegistry registry){

        registry.addEndpoint("/ws").setAllowedOrigins("http://localhost:8082").withSockJS();

    }

}

어떤 url 이 올때 받을것인지 prefix 를 설정해준다.

3. JS 설정

webapp 아래 script 라는 폴더를 만들어서 이곳에 알람관련한 JS 를 만들어주었다.

webapp/script/notification.js 에 위치하게끔 해주었다.

이유는 사용하는 페이지에 JS를 삽입하기 위해서다.

var stompClient = null;

    var privateStompClient = null;

    var username = "";

    function getUsernameFromServer() {

        $.ajax({

            url: '/getUsername',  // 서버의 /api/username 엔드포인트

            type: 'GET',  // HTTP GET 요청

            success: function(data) {

                username = data;

            },

            error: function(jqXHR, textStatus, errorThrown) {

                console.error('Error fetching current user:', errorThrown);

            }

        });

    }

    getUsernameFromServer();

    priSocket = new SockJS('/ws');

    privateStompClient = Stomp.over(priSocket);

    privateStompClient.connect({}, function(frame){

        console.log(frame + "pri");

            privateStompClient.subscribe('/user/'+username+'/comments', function(result){

            console.log(result.body)

            show(JSON.parse(result.body))

        });

    });

    function show(message){

        var container = document.getElementById('notification-container');

        let alarm = "<a href='/content?num="+message.postId+"'>"+message.message+"</a>";

        // 알림 메시지 설정

        container.innerHTML = `

        <span class="close-button" onclick="hideNotification()">X</span>

          <span>${alarm}</span>

        `;

        container.classList.add('show');

        setTimeout(function() {

          hideNotification();

        }, 10000);

    }

    function hideNotification() {

        var container = document.getElementById('notification-container');

        container.classList.remove('show');

      }

priSocket 부터 웹소켓관련 코드이다.

소켓을 생성하고, 아래 subscribe 를 통해 어떤 URL 의 내용을 구독할지 지정한다.

여기의 경우 /user/${username}/comments 로 오는 내용들을 웹소켓이 받아서 처리할 예정이다.

result 로 받은값은 console.log 를 통해 한번 찍어보고 (테스트용), show 함수를 통해 알람창을 띄워준다.

알람 메시지안에 가공한 alarm 메시지와 X 버튼을 통해 알람창을 구현해준다. X버튼안누르면 10초뒤 사라짐

그럼 이 /user/${username{/comments 로는 누가 어떻게 보낼까?

4. AOP 설정

저 메시지를 보내기전에 댓글이 달릴때마다 메시지를 보낼 수 있도록 AOP설정부터 진행해본다.

com.example.post.aop 아래 관련 클래스를 하나 만들어준다.

package com.example.post.aop;

import java.util.HashMap;

import java.util.Map;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.Aspect;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.messaging.simp.SimpMessagingTemplate;

import org.springframework.stereotype.Component;

import com.example.post.model.Comment;

import com.example.post.model.Post;

import com.example.post.service.PostService;

@Aspect

@Component

public class CommentNotificationAspect {

    @Autowired

    private SimpMessagingTemplate simpMessagingTemplate;

    @Autowired

    private PostService postService;

    // @Before("execution(* com.example.post.service.CommentService.writeComment(..)) && args(comment)")

    // public void beforeWriteComment(Comment comment) {

    //     System.out.println("### Before writing comment: " + comment.getContents());

    // }

    @AfterReturning("execution(* com.example.post.service.CommentService.writeComment(..)) && args(comment)")

    public void afterWriteComment(Comment comment) {

        Post post = postService.getPost(comment.getPostId());

        // 댓글 작성 후 알림 보내기

        String notificationMessage =  post.getTitle() +" 게시글에 "+ comment.getAuthor() + "님이 댓글을 남기셨습니다.";

        System.out.println(notificationMessage + " " + post.getAuthor());

        Map<String, String> notification = new HashMap<>();

        notification.put("message", notificationMessage);

        notification.put("postId", post.getNum());

        simpMessagingTemplate.convertAndSendToUser(post.getAuthor(), "/comments", notification);

    }

}

Aspect 로 AOP 설정을 해주고, Component 로 등록한다.

어노테이션으로 메서드위에 언제 수행될지 표시를 해주는데 나는 writeComment 실행 후에 해당 메서드가 수행되게 만들어주었다. 

인자값으로 받는 Comment comment 는 실제 writeComment 의 args 를 가져올 수 있어 해당 내용으로 보낼 수 있다.

        simpMessagingTemplate.convertAndSendToUser(post.getAuthor(), "/comments", notification);

마지막 이 코드를 통해 아까 구독중인 /user/${username}/comments 로 내용을 보낼 수 있다. 

/user/${username} 은 위 convertAndSendToUser 가 user를 지정해서 보내는거라 받을때 꼭 추가해주어야한다.

추가로 config 에 AppConfig 클래스를 작성한다.

package com.example.post.config;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration

@EnableAspectJAutoProxy

public class AppConfig {

}

Leave a Comment