이전 포스팅 마지막 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 {
}