반응형
1. 스프링부트 웹 소켓 라이브러리 추가
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-websocket'
2. EchoHandler 작성
package com.reviewer.portfolio.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.thymeleaf.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
@RequiredArgsConstructor
public class EchoHandler extends TextWebSocketHandler{
// 전체 로그인 유저
private List<WebSocketSession> sessions = new ArrayList<>();
// 1대1 매핑
private Map<String, WebSocketSession> userSessionMap = new HashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("Socket 연결");
sessions.add(session);
log.info(sendPushUsername(session)); //현재 접속한 사람의 username이 출력됨
String senderId = sendPushUsername(session);
userSessionMap.put(senderId, session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info("session = " + sendPushUsername(session));
String msg = message.getPayload(); //js에서 넘어온 메세지
log.info("msg = " + msg);
if (!StringUtils.isEmpty(msg)) {
String[] strs = msg.split(",");
if (strs != null && strs.length == 5) {
String pushCategory = strs[0]; //댓글, 좋아요 구분
String replyWriter = strs[1]; //댓글, 좋아요 보낸 유저
String sendedPushUser = strs[2]; //푸시 알림 받을 유저
String boardId = strs[3]; //게시글번호
String title = strs[4]; //게시글제목
WebSocketSession sendedPushSession = userSessionMap.get(sendedPushUser); //로그인상태일때 알람 보냄
//부모댓글
if ("reply".equals(pushCategory) && sendedPushSession != null) {
TextMessage textMsg = new TextMessage(replyWriter + " 님이 " + "<a href='/porfolDetail/" + boardId + "' style=\"color:black\"><strong>" + title + "</strong> 에 댓글을 남겼습니다.</a>");
sendedPushSession.sendMessage(textMsg);
}
//좋아요
else if ("like".equals(pushCategory) && sendedPushSession != null) {
TextMessage textMsg = new TextMessage(replyWriter + " 님이 " + "<a href='/porfolDetail/" + boardId + "' style=\"color:black\"><strong>" + title + "</strong> 을 좋아요♡ 했습니다.</a>");
sendedPushSession.sendMessage(textMsg);
}
//자식댓글
else if ("reReply".equals(pushCategory) && sendedPushSession != null) {
TextMessage textMsg = new TextMessage(replyWriter + " 님이 " + "<a href='/porfolDetail/" + boardId + "' style=\"color:black\"><strong>" + title + "</strong> 글의 회원님 댓글에 답글을 남겼습니다.</a>");
sendedPushSession.sendMessage(textMsg);
}
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
log.info("Socket 연결 해제");
sessions.remove(session);
userSessionMap.remove(sendPushUsername(session), session);
}
//알람을 보내는 유저(댓글작성, 좋아요 누르는 유저)
private String sendPushUsername(WebSocketSession session) {
String loginUsername;
if (session.getPrincipal() == null) {
loginUsername = null;
} else {
loginUsername = session.getPrincipal().getName();
}
return loginUsername;
}
}
sessions에는 로그인 상태인 전체 유저의 정보가 담기고, userSessionMap에는 1대 1로 유저사이에 알림을 보낼 때 유저 정보를 담는다.
[로그인을 스프링 시큐리티로 구현한 경우 - getPrincipal().getName() ]
session이 연결된 후 sendPushUsername() 함수를 이용하여 WebSocketSession 의 유저 정보를 getPrincipal().getName() 이용하여 user의 username을 알아낸다.
userSessionMap 에 key(username) : value(WebSocketSession)으로 넣어준다.
3. WebSocket Configuration 설정
package com.reviewer.portfolio.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import com.reviewer.portfolio.controller.EchoHandler;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableWebSocket //웹소켓 활성화
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer{
private final EchoHandler echoHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//클라이언트에서 웹소켓에 접속하기위한 경로(ex: localhost:8080/push)
//도메인이 다른 서버에서도 접속 가능하도록 CORS 설정 추가
//소켓을 지원하지 않는 브라우저의 경우 SockJS 사용하도록 설정 추가
registry.addHandler(echoHandler, "/push").setAllowedOriginPatterns("*").withSockJS();
}
}
4. 뷰
//좋아요 알림이 보이는 부분
<div id="socketAlertDiv">
<div id="socketAlert" class="alert alert-warning" role="alert"></div>
</div>
전체 페이지에서 모두 header.html와 footer.html을 가지고 있기 때문에 우선 알림을 header.html에 보여지도록 해줌
var socket = null;
$(document).ready(function(){
//소켓 연결
connectWs();
});
function connectWs(){
//WebSocketConfig에서 설정한 endPoint("/push")로 연결
var ws = new SockJS("/push");
socket = ws;
ws.onopen = function() {
console.log('open');
};
ws.onmessage = function(event) {
console.log(event.data);
let $socketAlert = $('#socketAlert');
//EchoHandler에서 설정한 메세지 넣어줌
$socketAlert.html(event.data)
$socketAlert.css('display', 'block');
//일정 시간 지나면 알림 사라짐
setTimeout(function(){
$socketAlert.css('display','none');
}, 5000);
};
ws.onclose = function() {
console.log('close');
};
};
/* 부모댓글 등록 */
$('.replyAddBtn').on('click', function(){
//웹 소켓의 알림 메시지에 필요한 정보들
var boardId = $('#boardId').val();
var boardTitle = $('#porfolTitle').text();
var boardWriter = $('#boardWriter').val();
var replyWriter = $('#replyWriter').val();
var replyText = $('#replyText').val();
var param = { "boardId" : boardId, "replyText" : replyText};
$.ajax({
url : "/reply",
type : "post",
data : param,
success : function(resp){
alert('댓글이 등록되었습니다.');
location.reload();
//웹 소켓 관련 로직 추가
if (boardWriter != replyWriter){ //글쓴이와 댓글작성자가 다를 경우 소켓으로 메세지 보냄
if (socket){
let socketMsg = "reply," + replyWriter + "," + boardWriter + "," + boardId + "," + boardTitle;
socket.send(socketMsg);
}
}
},
error : function(XMLHttpRequest, textStatus, errorThrown){
alert('댓글 등록이 실패하였습니다.');
}
});
});
댓글 작성, 좋아요 할 경우 ajax 호출 성공 후 로직에서 /push로 알림과 메세지를 전달하면 EchoHandler 의 handleTextMessage() 함수가 동작한다.
반응형
'스프링' 카테고리의 다른 글
@RestController와 @RequestMapping (0) | 2022.08.17 |
---|---|
스프링부트 이메일 발송(구글 계정 이용) (0) | 2022.06.20 |
[Spring] log4jdbc-log4j2 설정하기(콘솔에 sql문 로그 찍기) (0) | 2022.05.25 |
@Controller 와 @RestContoller (@ResponseBody) (0) | 2022.04.12 |
[Spring] 스프링부트 스케줄러 사용하기 (0) | 2022.03.29 |