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)
댓글 남기기