1. 라이브러리 설정
<!-- 알림 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
2. servlet-context.xml 설정
<beans:bean id="alarmHandler" class="패키지.AlarmHandler" /> <websocket:handlers allowed-origins="*"> <websocket:mapping handler="alarmHandler" path="/alarm" /> <websocket:handshake-interceptors> <beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor" /> </websocket:handshake-interceptors> <websocket:sockjs client-library-url="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js" /> </websocket:handlers>
3. AlarmHandler
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
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 com.google.gson.Gson;
import com.gudi.board.dto.InformDTO;
import com.gudi.board.service.InformService;
@Component
public class AlarmHandler extends TextWebSocketHandler {
// 로그인 한 전체
List<WebSocketSession> sessions = new ArrayList<WebSocketSession>();
// 1대1
Map<String, WebSocketSession> userSessionsMap = new HashMap<String, WebSocketSession>();
@Autowired
InformService informService;
// 서버에 접속이 성공 했을때
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//접속한 전체 유저 아이디
sessions.add(session);
//로그인한 개별 유저 아이디를 가져온다.
String senderID = getId(session);
//userSessionsMap 에 개별유저아이디를 넣는다.
userSessionsMap.put(senderID, session);
}
// 소켓에 메세지를 보냈을때
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String userId=getId(session);
Map<String, Object> httpSession = session.getAttributes();
String loginId = (String) httpSession.get("loginId");
if(loginId!=null) {
//접속한 해당 유저 읽지않은 알림 데이터 전체 카운트 만 가져올 경우
//int countInform=informService.countInform(userId);
//1.해당 유저 알림데이터 전체 가져오기
//List<InformDTO> getInform= informService.selectInformDTO(userId);
//for(InformDTO informDTO :getInform) {
//System.out.println("getInform : "+informDTO.toString());
//}
//2.해당 유저 알림데이터 마지막 데이터만 가져올 경우
InformDTO selectLsatInform= informService.selectLsatInform(userId);
WebSocketSession webSocketSession = userSessionsMap.get(userId);
Gson gson = new Gson();
//1.해당 유저 알림데이터 전체 가져오기일 경우 JSON 으로 전환 후 TextMessage 변환
//TextMessage textMessage = new TextMessage(gson.toJson(getInform));
//2.해당 유저 알림데이터 전체 가져오기일 경우 JSON 으로 전환 후 TextMessage 변환
TextMessage textMessage = new TextMessage(gson.toJson(selectLsatInform));
webSocketSession.sendMessage(textMessage);
}
}
// 연결 해제될때
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
//System.out.println("afterConnectionClosed " + session + ", " + status);
//소켓 연결이 끊겼을 때 개별 접속아이디 해체 저리한다.
userSessionsMap.remove(session.getId());
//소켓 연결이 끊겼을 때 전체 접속자 아이디 해체 저리한다.
sessions.remove(session);
}
// 웹소켓 id 가져오기
private String getId(WebSocketSession session) {
/*
(String)request.getSession().getAttribute("loginId");
또는 ,
session.getAttribute("loginId");
이렇게 세션값을 가져오나 여기 웹소켓에서는 세션값을 WebSocketSession session 형태로 가져옵니다.
따라서 , 다음과 코드 형태로 세션값을 가져옵니다.
*/
Map<String, Object> httpSession = session.getAttributes();
String loginId = (String) httpSession.get("loginId");
if (loginId == null) {
//System.out.println("로그인 loginID 가 널일경우 :" + session.getId());
//랜덤 아이디 생성, 사이트 접속한 사람 전체
//ex ) vawpuj5h, 5qw40sff
return session.getId();
} else {
//로그인한 유저 반환
return loginId;
}
}
}
4. alarm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<div class="toast align-items-center fade" role="alert" id='alert-toast' aria-live="assertive" aria-atomic="true" style="color: white;background: #dc5151; position: absolute;
top: 78px;right: 0px;">
<div class="d-flex">
<div class="toast-body">
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
<%-- <script src="${pageContext.request.contextPath}/resources/js/sockjs.min.js"></script> --%>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script>
//소켓 전역변수 선언
var socket = null;
//한번만 메시지 알림을 위해 alarmCount 변수 설정
var alarmCount=0;
$(document).ready(function(){
connectWs();
setTimeout(() => {
//시작후 0.3초후 알림데이터 가져오기
sock.send("${loginId}");
}, 300);
//5초마다 알림데이터 가져오기
setInterval("autoScript()", 5000);
});
function autoScript() {
sock.send("${loginId}");
}
function connectWs(){
//console.log("getContextPath :" +getContextPath());
// 웹소켓 주소
// var wsUri = "ws://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/alarm";
//var wsUri = "${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/alarm";
//sock = new SockJS(wsUri);
sock = new SockJS(getContextPath()+'/alarm');
socket = sock;
sock.onopen = function() {
//console.log('connect onopen');
};
sock.onmessage = function(evt) {
var data = evt.data;
//console.log("ReceivMessage : " + data + "\n");
if(data!="null"){
const obj =JSON.parse(data);
//멀티 알림일 경우
//multiAlarmData(obj);
//알림 마지막 데이터만
alarmData(obj);
}else{
$("#alarm-span").hide();
}
};
sock.onclose = function() {
// console.log('connect close');
};
sock.onerror = function (err) {console.log('Errors : ' , err);};
}
function alarmListMove(){
location.href="${pageContext.request.contextPath}/alarmAllRead";
}
function getContextPath() {
var hostIndex = location.href.indexOf( location.host ) + location.host.length;
return location.href.substring( hostIndex, location.href.indexOf('/', hostIndex + 1) );
};
//멀티 알림일 경우
function multiAlarmData(obj){
//마지막 것만 알림을 위해 카운트 비교
if(alarmCount!=obj.length){
//console.log(obj[obj.length-1]);
var html='<a style="color:#fff; text-decoration: none;" href="${pageContext.request.contextPath}/alarmLinkMove?type=fbdetail&postId='+obj[obj.length-1].relatedId+'&informid='+obj[obj.length-1].informId+'">' +obj[obj.length-1].informContent +'</a>';
$("#alert-toast .toast-body").html(html);
$("#alert-toast").addClass("show");
$("#alert-toast .d-flex").css("display", "block")
;
setTimeout(() => {
$("#alert-toast").removeClass("show");
$("#alert-toast .d-flex").css("display", "none");
}, 5000);
}
//alarmCount 에 값이 일치하게 만든다.
alarmCount=obj.length;
//console.dir(obj);
if(obj.length>0){
$("#alarm-span").show();
if(obj.length>1){
$("#alarm-span-count").text(obj.length);
if($("#alarm-span-count").hasClass("visually-hidden") === true){
$("#alarm-span-count").removeClass("visually-hidden");
}
}
}
}
function alarmData(obj){
//알람 보이기
if(parseInt(obj.countInform)>0){
$("#alarm-span-count").text(obj.countInform);
$("#alarm-span").show();
//토스트 메시지 보이기
if(alarmCount==0){
var html='<a style="color:#fff; text-decoration: none;" href="${pageContext.request.contextPath}/alarmLinkMove?type=fbdetail&postId='+obj.relatedId+'&informid='+obj.informId+'">' +obj.informContent +'</a>';
$("#alert-toast .toast-body").html(html);
$("#alert-toast").addClass("show");
$("#alert-toast .d-flex").css("display", "block");
setTimeout(() => {
$("#alert-toast").removeClass("show");
$("#alert-toast .d-flex").css("display", "none");
}, 5000);
}
alarmCount++;
}else{
$("#alarm-span").hide();
}
}
</script>
기타 참조
html
<button type="button" onclick="alarmListMove()" class="btn btn-sm btn-#3c3c3c; position-relative"> <i class="bi bi-bell-fill" style="font-size: 1.8rem; color: white"></i><span id="alarm-span" class="position-absoluteposition-absolute top-0 end-0 translate-middle badge border border-light rounded-circle bg-danger p-2"><span class="" id="alarm-span-count"></span></span> </button>
@Data
public class InformDTO {
private Integer informId;
private String userId;
private Date informDate;
private String informField;
private Integer relatedId;
private String informContent;
private String isRead;
private int countInform;
}
<select id="selectLsatInform" resultType="com.gudi.board.dto.InformDTO">
SELECT R.* , (SELECT count(INFORMID) FROM INFORM WHERE userid=#{userid} AND ISREAD='N' ) countInform FROM (
SELECT * FROM INFORM i WHERE userid=#{userid} AND ISREAD='N' ORDER BY INFORMID DESC
) R WHERE ROWNUM = 1
</select>

















댓글 ( 6)
댓글 남기기