본문 바로가기

프로젝트 개발기/Status 200 ( Team Project )

12. TEAM 프로젝트: 캘린더 기능 구현 - 실시간 알림


상세한 개발 내역을 작성할려고 했으나, 여러 번의 디버그가 있었고 개발 단계에서 변경되는 점 또한 많았다. 

때문에 완성된 캘린더를 기능 별로 정리하고, 개선점을 작성하려고 한다. 

(TimeLine에 작성해 봤으나 글이 한눈에 들어오지 않는다는 단점이 있어서 방법을 바꿨다.)

 


1. 실시간 알림 기능 

실시간 알림기능은 Socket 네트워크를 통해 구현하였다. 

기존에 알던 Http 방식은 Request와 Response가 1:1로 대응하지만, Socket의 경우 1:N으로 대응하도록 통신이 가능하다.

HTML5 부터 Socket 기능이 표준화되어 Spring을 사용하지 않아도 구현이 가능하다. 

때문에 이를 이용하여 간단하게 기능을 구현하였다. 

 

1) Servlet 

package ServletCalendar;

import java.io.*;
import java.util.*;
import javax.websocket.*;
import javax.websocket.server.*;

import javax.servlet.http.*;

@ServerEndpoint(value="/socketAlert", configurator=httpSessionJejang.class)
public class groupMemberSocket  {
	//private static List<Session> userList = Collections.synchronizedList(new ArrayList<>());
	private static Map<Session, HttpSession> sessionMap = new HashMap<Session, HttpSession>();
	
	@OnOpen
	public void skOpen(Session session, EndpointConfig confi) {
		HttpSession hsession=(HttpSession) confi.getUserProperties().get("UserHttpSession");
		sessionMap.put(session, hsession);
		System.out.println("socket connetion");
	}
	
	@OnMessage
	public void skCheckData(Session session, String changeUser) throws IOException {
		Iterator<Session> keys = sessionMap.keySet().iterator();

		while( keys.hasNext() ){ 
			Session key = keys.next(); 
			key.getBasicRemote().sendText(changeUser);
		}
		System.out.println("socket onmessage from server");
	}
	
	@OnClose
	public void  skClose(Session session) {
		sessionMap.remove(session);
		System.out.println("Socket Close: Client Close");
	}
	
	@OnError
	public void skError(Throwable e) {
		System.out.println("Socket Error: "+e);
	}
	
}
package ServletCalendar;

import javax.servlet.http.*;
import javax.websocket.*;
import javax.websocket.server.*;

public class httpSessionJejang extends ServerEndpointConfig.Configurator{
	public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response ) {
		HttpSession session = (HttpSession) request.getHttpSession();
		
		if(session!=null) {
			sec.getUserProperties().put("UserHttpSession", session);
		}
	}
}

 

Socket 을 생성하여 User의 데이터를 세션에 저장하였다.

2번째 코드는 추후 호환을 위해 Http Session 정보를 가져온 것인데, 실제로 사용하지는 않았다.

해당 코드를 사용하면 Http Session에 접근하여 좀더 다양한 기능이나 데이터 검증이 가능하다. 

Socket에 대한 이해는 추후 JSP 파트 혹은 네트워크 통신을 공부하면서 따로 정리할 것이다.

 

2) Ajax 

// 세션 데이터를 전역 변수로 할당하여 사용함 
	let userKey = "${loginUser.getId()}+";
	let alertHave = false; 
	
/* ====================================================================
 * ==== 알림 ===========================================================
 * ====================================================================*/  	
 		
	//Invite XHR 
	var XHRInvite;
	
	function createXHRInvite(){
		if(window.ActiveXObject){ 
			XHRInvite=new ActiveXObject("Microsoft.XMLHTTP");
		}
		else if(window.XMLHttpRequest){
			XHRInvite=new XMLHttpRequest();
		}
	}
	
	function checkInvite(){
		createXHRInvite();
		
		XHRInvite.onreadystatechange=function(){
			if(XHRInvite.readyState==4){
	            if(XHRInvite.status==200){
	            	let jsons = JSON.parse(XHRInvite.responseText, "text/json");
	            	
	            	if(jsons.alert==true){
	            		alertHave = true;
	            	}
	            	else{
	            		alertHave = false;
	            	}
	            	youHaveAlert();
	            }
			}
		}
		XHRInvite.open("POST", "checkInvite", true);
		XHRInvite.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
		XHRInvite.send("userKey="+userKey);
	}
	
	// 소켓 사용을 위한 자바스크립트
	// 추후에 jsp include에 사용되어 모든 페이지에 적용할 수도 있음
	
	let webSocket = new WebSocket("ws://localhost:8080/team1jo/socketAlert");
	
	webSocket.onopen=function(){
		
	};
	
	webSocket.onclose=function(){
		
	};
	
	webSocket.onmessage = function inviteAlert(message){
		let data = message.data; 
		console.log(message);
		if(data==userKey){
			alertHave = true;
		}
		youHaveAlert();
	};
	checkInvite();
	function youHaveAlert(){
		let profile = document.getElementById("headerProfileIcon");
		if(alertHave==true){
			profile.setAttribute("style", "background-image: url(./imgSource/profileIconAlert.png);")
		}
		else{
			profile.removeAttribute("style");
		}
	}

 

2. 알림 수락 및 거부 

1) HTML 

<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>    
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="utf-8">
		<title>초대 보기</title>
        <link rel="stylesheet" href="style.css">
        <style>
        	#inviteWrap{
        		width: 600px;
        		margin: 0 auto;
        		margin-top: 30px;
        		min-height: 68.7vh;
        		overflow: auto;
        		margin-bottom: 30px;
        		
        	}
        	
        	.inviteGroupName{
        		width: 434px;
        		height: 32px;
        		line-height: 32px;
       
        		border-radius: 10px;
        		display: block;
        		float: left;
        		text-overflow: ellipsis;
    
        	}
        	.inviteListWrap{
        		padding-top: 10px;
        		padding-bottom: 10px;
        	}
        	.inviteListWrap::after{
        		content: "";
        		display: block;
        		clear: both;
        	}
        	.deniedInvite, .acceptInvite{
        		border: 0;
        		border-radius: 4px;
        		height: 32px;
        		width: 80px;
        		background-color: rgb(245,245,245);
        		margin-left: 2px;
        		float: left;
        		font-size: 0.85em;
        		letter-spacing: -1px;
        	}
        	.deniedInvite:hover{
        		background-color: #e95159;
        	}
        	.acceptInvite:hover{
        		background-color: #81c147;
        	}
        	#inviteTitle{
        		font-size: 1em;
        		padding-bottom: 12px;
        		letter-spacing: -1px;
        		font-weight: 600;
        	}
        	hr{
        		opacity: 0.5;
        		width: 598px;
        		margin: 0 auto;
        	}
        	hr:last-child{
        		border: 0;
        	}
        </style>
	</head>
	<body style="margin:0;">
		<jsp:include page="header.jsp"/>
		<div id="inviteWrap">
			<div id="inviteList">
				<div id="inviteTitle">초대 리스트</div>	
				<c:forEach items="${list }" var="list">
					<div class="inviteListWrap">
						<input type="hidden" class="inviteNum" value="${list.getNum()}">
						<input type="hidden" class="inviteId" value="${list.getId()}">
						<input type="hidden" class="inviteGroupnum" value="${list.getGroupnum()}">
						<input type="hidden" class="inviteInvite" value="${list.getInvite()}">
						<div class="inviteGroupName">${list.getName() }</div>
						<input type="button" class="deniedInvite" value="거부" onclick="inviteDenied()">
						<input type="button" class="acceptInvite" value="수락" onclick="inviteAccept()">
					</div>	
					<hr>			
				</c:forEach>
			</div>
		</div>
		<jsp:include page="footer.jsp"/>
		<form method="post" action="#" id="inviteFormData">
			<input type="hidden" id="inviteFormDataNum" name="num">
		</form>
	</body>
	<script>
		
		function inviteAccept(){
			let target = event.target;
			let v = target.parentNode;
			let num = v.getElementsByClassName("inviteNum")[0].value;
			let form = document.getElementById("inviteFormData");
			let input = document.getElementById("inviteFormDataNum");
			form.removeAttribute("action");
			
			input.setAttribute("value", num);
			form.setAttribute("action", "groupMemberInviteAccept");
			
			form.submit();
		}
		
		function inviteDenied(){
			let target = event.target;
			let v = target.parentNode;
			let num = v.getElementsByClassName("inviteNum")[0].value;
			let form = document.getElementById("inviteFormData");
			let input = document.getElementById("inviteFormDataNum");
			form.removeAttribute("action");
			
			input.setAttribute("value", num);
			form.setAttribute("action", "groupMemberInviteDenied");
			
			form.submit();
		}
	</script>
</html>

알림을 수락하면 AJAX로 해당 정보에 따라 Groupmember 테이블의 Accept 상태를 조정하게 했다.

거부할 경우에는 삭제하여 알림을 없앨 수 있다.

 

2) Servlet 

package ServletCalendar;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import DAO.GroupDAO;
import DTO.GroupMemberDTO;

@WebServlet("/groupMemberInviteAccept")
public class groupMemberInviteAccept extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		
		String num = request.getParameter("num");
		GroupMemberDTO gmDTO = new GroupMemberDTO();
		
		gmDTO.setNum(num);
		
		GroupDAO gDAO = GroupDAO.getInstance();
		
		gDAO.inviteAccept(gmDTO);
		
		response.sendRedirect("InviteList");
	}
}