본문 바로가기
ICIA 수업일지

2021.10.22 수업일지(Spring Framework, Web Socket)

by 주성씨 2021. 10. 22.

웹 소켓(Web Socket)

웹 소켓은 웹서버와 웹브라우저가 지속적으로 연결된 TCP 라인을 통해 실시간으로 데이터를 주고받을 수 있도록 하는 HTML의 새로운 사양이다. 따라서 Web Socket을 이용하면 양방향 통신이 가능하다. 이와 같은 특징으로 웹에서도 게시판 프로젝트에서 댓글 알림이나 실시간 채팅, 실시간 주식 차트와 같은 응용프로그램의 개발을 한층 효과적으로 구현할 수 있게 되었다.

 

이 웹소켓을 이용해 채팅 프로그램을 만들어보겠다. 로그인한 회원에 한해서 채팅을 이용할 수 있도록 하겠다.

 

/ex13/src/main/java/com/example/controller/HomeController.java

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Model model) {
		model.addAttribute("pageName", "about.jsp");
		return "home";
	}
	
	@RequestMapping("/login")
	public String login(Model model) {
		model.addAttribute("pageName", "login.jsp");
		return "home";
	}
	
}

 

/ex13/src/main/webapp/WEB-INF/views/home.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<html>
<head>
<title>Web Socket</title>
<link rel="stylesheet" href="/resources/home.css" />
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
</head>
<body>
	<div id="page">
		<div id="header">
			<a href="/" title="홈으로"><img src="/resources/back.png" width=920 /></a>
		</div>
		<div id="center">
			<div id="menu">
				<a href="#" title="홈으로">홈으로</a>
				<a href="login" title="로그인">로그인</a>
			</div>
			<div id="content">
			<jsp:include page="${pageName}"></jsp:include>
			</div>
		</div>
		<div id="footer">
			<h3>Copyright Web Socket Practice. All rights Reserved.</h3>
		</div>
	</div>
</body>
</html>

 

/ex13/src/main/webapp/WEB-INF/views/about.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<h1>What is Web Socket?</h1>
<p>WebSocket is a computer communications protocol, providing
	full-duplex communication channels over a single TCP connection. The
	WebSocket protocol was standardized by the IETF as RFC 6455 in 2011,
	and the WebSocket API in Web IDL is being standardized by the W3C.
	WebSocket is distinct from HTTP.</p>

 

/ex13/src/main/webapp/WEB-INF/views/login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<h1>[로그인]</h1>
<style>
.loginBox {
	width: 300px;
	margin: 0px auto;
	padding: 10px;
}

input[type=text], input[type=password] {
	width: 100%;
	margin-bottom: 10px;
	font-size: 15px;
}

input[type=submit] {
	padding: 10px 20px 10px 20px;
	background: rgb(57, 109, 192);
	color: white;
}

input[type=checkbox] {
	margin: 10px 0px 10px 0px;
}
</style>
<div class="loginBox">
	<form action="post" name="frm">
		<input type="text" name="uid" placeholder="ID" /> <input
			type="password" name="upass" placeholder="PASS" />
		<input type="submit" value="LOGIN" />
	</form>
</div>
<script>
	$(frm).on('submit', function(e) {
		e.preventDefault();
		var uid = $(frm.uid).val();
		var upass = $(frm.upass).val();
		if (uid == "" || upass == "") {
			alert("회원ID와 비밀번호를 입력하세요.");
			return;
		}
		if (!confirm("로그인 하시겠습니까?"))
			return;
		$.ajax({
			type : 'post',
			url : '/login',
			data : {
				uid : uid,
				upass : upass
			},
			success : function(data) {
				if (data == 0) {
					alert("해당 유저가 존재하지 않습니다.")
				} else if (data == 2) {
					alert("비밀번호를 확인하세요.")
				} else {
					alert("로그인합니다.")
					location.href = "/"

				}
			}
		})
	})
</script>

 

- 로그인 확인

/ex13/src/main/resources/mapper/UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
 	<select id="login" resultType="com.example.domain.UserVO">
 		select * from tbl_user
 		where uid=#{uid}
 	</select>
</mapper>

 

/ex13/src/main/java/com/example/mapper/UserDAO.java

package com.example.mapper;

import com.example.domain.UserVO;

public interface UserDAO {
	public UserVO login(String uid);
}

 

/ex13/src/main/java/com/example/mapper/UserDAOImpl.java

package com.example.mapper;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.domain.UserVO;

@Repository
public class UserDAOImpl implements UserDAO{
	@Autowired
	SqlSession session;
	
	String namespace="com.example.mapper.UserMapper";
	
	@Override
	public UserVO login(String uid) {
		return session.selectOne(namespace+".login", uid);
	}

}

 

/ex13/src/main/java/com/example/controller/HomeController.java

...
	@RequestMapping(value = "/login", method = RequestMethod.POST)
	@ResponseBody
	public int loginPost(String uid, String upass, HttpSession session) {
		int result = 0;
		UserVO vo = udao.login(uid);
		if (vo != null) {
			if (vo.getUpass().equals(upass)) {
				result = 1;
				session.setAttribute("uid", uid);
			} else {
				result = 2;
			}
		}
		return result;
	}
...

 

로그인 확인

 

- 이제 로그인 시 유저에 아이디아 session에 담겨 있으니 로그인 시에만 채팅 페이지와 유저 아이디, 로그아웃이 보일 수 있도록 하겠다.

/ex13/src/main/webapp/WEB-INF/views/home.jsp

...
<body>
	<div id="page">
		<div id="header">
			<a href="/" title="홈으로"><img src="/resources/back.png" width=920 /></a>
		</div>
		<div id="center">
			<div id="menu">
				<c:if test="${uid==null}">
					<a href="/login">로그인</a>
				</c:if>
				<c:if test="${uid!=null}">
					<a href="#" id="chat" title="채팅">채팅</a>
					<a href="">${uid}님</a>
					<a href="/logout">로그아웃</a>
				</c:if>
			</div>
			<div id="content">
			<jsp:include page="${pageName}"></jsp:include>
			</div>
		</div>
		<div id="footer">
			<h3>Copyright Web Socket Practice. All rights Reserved.</h3>
		</div>
	</div>
</body>
...

 

- 채팅 페이지 클릭 시 채팅 페이지가 새로 열리도록 하겟다.

/ex13/src/main/webapp/WEB-INF/views/home.jsp

...
<script>
	$("#chat").on('click',function(e){
		e.preventDefault();
		window.open("/chat","chat","width=500, height=800, top=200, left=200");
        // 경로, 파일, 너비, 높이, 위치 지정
	})
</script>
</html>

 

- 채팅 페이지 연결

/ex13/src/main/java/com/example/controller/HomeController.java

...
	@RequestMapping("/logout")
	public String logout(HttpSession session) {
		session.invalidate();
		return "redirect:/";
	}

	@RequestMapping(value = "/login", method = RequestMethod.POST)
	@ResponseBody
	public int loginPost(String uid, String upass, HttpSession session) {
		int result = 0;
		UserVO vo = udao.login(uid);
		if (vo != null) {
			if (vo.getUpass().equals(upass)) {
				result = 1;
				session.setAttribute("uid", uid);
			} else {
				result = 2;
			}
		}
		return result;
	}

	@RequestMapping(value = "/chat", method = RequestMethod.GET)
	public String chat() {
		return "chat";
	}

}

 

- 채팅 페이지 만들기

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.5/sockjs.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/resources/chat.css" />
<title>Chat Application</title>
<style>
div.header {
	position: sticky;
	top: 0;
	background-color: blue;
}
</style>
</head>
<body>
	<div class="chat_wrap">
		<div class="header">
			<h3>채팅방(${uid})</h3>
		</div>
		<div id="chat"></div>
		<script id="temp" type="text/x-handlebars-template">
       <div class="{{printLeftRight sender}}">
          <div class="sender">{{sender}}</div>
          <div class="message">{{message}}</div>
          <div class="date">{{date}}</div>
       </div>
       </script>
		<div class="input-div">
			<textarea id="txtMessage" cols="30" rows="10"
				placeholder="메시지를 입력한 후에 엔터키를 누르세요."></textarea>
		</div>
	</div>
</body>
<!-- 메시지 입력시 오른쪽 왼쪽으로 기입되는 방식 지정 -->
<script>
	var uid = "${uid}";
	Handlebars.registerHelper("printLeftRight", function(sender) {
		if (uid != sender) {
			return "left";
		} else {
			return "right";
		}
	});
</script>
<script>
	var uid = "${uid}"
	$("#txtMessage").on("keypress", function(e) {
		if (e.keyCode == 13 && !e.shiftKey) {
			e.preventDefault();
			var message = $("#txtMessage").val();
			if (message == "") {
				alert("메시지를 입력하세요.");
				$("#txtMessage").focus();
				return;
			}
			
			// 서버로 메시지 보내기
			sock.send(uid + "|" + message);
			$("#txtMessage").val("");
		}
	})
</script>
</html>

 

 

- 이제 웹소캣 라이브러리를 설치하고 양방향 채팅을 할 수 있도록 하겠다.

/ex13/pom.xml

...
		<!-- 새로운 라이브러리 추가 시작 -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>4.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
			<version>4.1.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>4.1.0.RELEASE</version>
		</dependency>
		<!-- 웹소캣 라이브러리 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-websocket</artifactId>
			<version>4.1.7.RELEASE</version>
		</dependency>
...

 

확인

 

- 웹 소캣 컨트롤러

/ex13/src/main/java/com/example/controller/ChatHandler.java

package com.example.controller;

import org.springframework.web.socket.handler.TextWebSocketHandler;

public class ChatHandler extends TextWebSocketHandler {

}

 

- 웹 소캣 등록

- namespace 부분에 해당 부분 클릭

/ex13/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml

check

 

...
	<!-- 웹소캣 등록 -->
	<beans:bean id="echoHandler" class="com.example.controller.ChatHandler"/>
	<websocket:handlers>
		<websocket:mapping handler="echoHandler" path="/echo"/>
		<websocket:sockjs/>
	</websocket:handlers>
</beans:beans>

 

- 웹 소캣 컨트롤러

/ex13/src/main/java/com/example/controller/ChatHandler.java

package com.example.controller;

import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class ChatHandler extends TextWebSocketHandler {

	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		// 연결되었을떄
		System.out.println("연결됨 : " + session.getId());
		super.afterConnectionEstablished(session);
	}

}

 

- 웹 소캣 컨넥트 부분

- 소캣 클라이언트 스크립트 추가

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.5/sockjs.min.js"></script>

 

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
	// 웹소캣 생성
	var sock = new SockJS("http://localhost:8088/echo");
</script>
</html>

 

소캣 연결 확인

 

- 연결이 끊길때 발생하는 메서드

...
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		System.out.println("연결끊김 : " + session.getId());
		super.afterConnectionClosed(session, status);
	}

}

 

연결끊김 확인

 

- 여러 개의 웹소켓 세션을 담도록 리스트를 생성한다.

/ex13/src/main/java/com/example/controller/ChatHandler.java

package com.example.controller;

import java.util.ArrayList;
import java.util.List;

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;

public class ChatHandler extends TextWebSocketHandler {
	// 여러개의 웹소켓 세션을 담도록 리스트를 생성한다.
	List<WebSocketSession> sessionList = new ArrayList<WebSocketSession>();

	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		// 연결되었을떄
		System.out.println("연결됨 : " + session.getId());
		sessionList.add(session);
		System.out.println("연결된갯수 : " + sessionList.size());
		super.afterConnectionEstablished(session);
	}

	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		// 연결끊겼을때
		System.out.println("연결끊김 : " + session.getId());
		super.afterConnectionClosed(session, status);
	}

	// 클라이언트(브라우저)에서 서버로 메시지를 보냈을때
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		// 메시지가 들어오는 부분
		String strMessage = message.getPayload();
		System.out.println("메시지 : " + strMessage);
		super.handleTextMessage(session, message);
	}

}

 

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
			var temp = Handlebars.compile($("#temp").html());
			$("#chat").append(temp(data));
			
			// 서버로 메시지 보내기
			sock.send(uid+"|"+message);
			
			$("#txtMessage").val("");
			window.scrollTo(0, $("#chat").prop("scrollHeight"))
		}
	})
	
	// 웹소캣 생성
	var sock = new SockJS("http://localhost:8088/echo");
	
</script>
</html>

 

메시지 전송 확인

 

 

- 이제 메시지를 받아서 연결되어 있는 세션들에게 보내도록 하겠다.

/ex13/src/main/java/com/example/controller/ChatHandler.java

...
	// 클라이언트(브라우저)에서 서버로 메시지를 보냈을때
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		// 메시지가 들어오는 부분
		String strMessage = message.getPayload();
		System.out.println("메시지 : " + strMessage);
		
		// 연결된세션들에게 메시지를 보낼때
		// 현재 시간도 보내보자.
		SimpleDateFormat stf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
		String strDate=stf.format(new Date());
		strMessage+="|"+strDate;
		for (WebSocketSession webSocketSession:sessionList) {
			webSocketSession.sendMessage(new TextMessage(strMessage));
		}
		super.handleTextMessage(session, message);
	}

}

 

- 이제 클라이언트가 메시지를 받아보도록 하겠다.

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
<script>
	var uid = "${uid}"
	$("#txtMessage").on("keypress", function(e) {
		if (e.keyCode == 13 && !e.shiftKey) {
			e.preventDefault();
			var message = $("#txtMessage").val();
			if (message == "") {
				alert("메시지를 입력하세요.");
				$("#txtMessage").focus();
				return;
			}
			
			// 서버로 메시지 보내기
			sock.send(uid + "|" + message);
			$("#txtMessage").val("");
		}
	})

	// 웹소캣 생성
	var sock = new SockJS("http://localhost:8088/echo");
	sock.onmessage = onMessage;

	// 서버로부터 메시지 받기
	function onMessage(msg) {
		var items = msg.data.split("|");
		var sender = items[0];
		var message = items[1];
		var date = items[2];
		var data = {
			"message" : message,
			"sender" : sender,
			"date" : date
		}
		var temp = Handlebars.compile($("#temp").html());
		$("#chat").append(temp(data));
		window.scrollTo(0, $("#chat").prop("scrollHeight"))
	}
</script>
</html>

 

양뱡향 채팅 확인

 

 

- 채팅 데이터가 채팅 테이블에 입력되도록 해보겠다.

- go to mysql

create table tbl_chat(
	id int auto_increment primary key,
    sender nvarchar(20) not null,
    message nvarchar(1000) not null,
    regdate datetime default now(),
    foreign key(sender) references tbl_user(uid)
);

insert into tbl_chat(sender,message)
values('user01','Hello Stranger');
insert into tbl_chat(sender,message)
values('user02','Yes');

select * from tbl_chat;

 

- 채팅관련 mapper를 만들겠다.

/ex13/src/main/resources/mapper/ChatMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.ChatMapper">
 	<select id="list" resultType="com.example.domain.ChatVO">
 		select * from tbl_chat
 	</select>
</mapper>

 

/ex13/src/main/java/com/example/domain/ChatVO.java

package com.example.domain;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

public class ChatVO {
	private int id;
	private String sender;
	private String message;
	
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Seoul")
	private Date regdate;
    
    ... getter, setter, toString

 

/ex13/src/main/java/com/example/mapper/ChatDAO.java

package com.example.mapper;

import java.util.List;

import com.example.domain.ChatVO;

public interface ChatDAO {
	public List<ChatVO> list();
}

 

/ex13/src/main/java/com/example/mapper/ChatDAOImpl.java

package com.example.mapper;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.domain.ChatVO;

@Repository
public class ChatDAOImpl implements ChatDAO {

	@Autowired
	SqlSession session;

	String namespace = "com.example.mapper.ChatMapper";

	@Override
	public List<ChatVO> list() {
		return session.selectList(namespace+".list");
	}

}

 

데이터 확인

 

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.5/sockjs.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/resources/chat.css" />
<title>Chat Application</title>
<style>
div.header {
	position: sticky;
	top: 0;
	background-color: blue;
}
</style>
</head>
<body>
	<div class="chat_wrap">
		<div class="header">
			<h3>채팅방(${uid})</h3>
		</div>
		<div id="chat"></div>
		<!-- 채팅저장출력 -->
		<script id="temp" type="text/x-handlebars-template">
		{{#each .}}
       <div class="{{printLeftRight sender}}">
          <div class="sender">{{sender}}</div>
          <div class="message">{{message}}</div>
          <div class="date">{{regdate}}</div>
       </div>
		{{/each}}
       </script>
		<!-- 새로운채팅출력 -->
		<script id="temp1" type="text/x-handlebars-template">
       <div class="{{printLeftRight sender}}">
          <div class="sender">{{sender}}</div>
          <div class="message">{{message}}</div>
          <div class="date">{{regdate}}</div>
       </div>
       </script>
		<div class="input-div">
			<textarea id="txtMessage" cols="30" rows="10"
				placeholder="메시지를 입력한 후에 엔터키를 누르세요."></textarea>
		</div>
	</div>
</body>
<!-- 메시지 입력시 오른쪽 왼쪽으로 기입되는 방식 지정 -->
<script>
	var uid = "${uid}";
	Handlebars.registerHelper("printLeftRight", function(sender) {
		if (uid != sender) {
			return "left";
		} else {
			return "right";
		}
	});
</script>
<script>
	var uid = "${uid}"
	getList();
	$("#txtMessage").on("keypress", function(e) {
		if (e.keyCode == 13 && !e.shiftKey) {
			e.preventDefault();
			var message = $("#txtMessage").val();
			if (message == "") {
				alert("메시지를 입력하세요.");
				$("#txtMessage").focus();
				return;
			}

			// 서버로 메시지 보내기
			sock.send(uid + "|" + message);
			$("#txtMessage").val("");
		}
	})

	// 채팅 데이터 받아오기
	function getList() {
		$.ajax({
			type : 'get',
			url : '/chat.json',
			dataType : 'json',
			success : function(data) {
				var temp = Handlebars.compile($("#temp").html());
				$("#chat").append(temp(data));
			}
		});
	}

	// 웹소캣 생성
	var sock = new SockJS("http://localhost:8088/echo");
	sock.onmessage = onMessage;

	// 서버로부터 메시지 받기
	function onMessage(msg) {
		var items = msg.data.split("|");
		var sender = items[0];
		var message = items[1];
		var date = items[2];
		var data = {
			"message" : message,
			"sender" : sender,
			"regdate" : date
		}
		var temp = Handlebars.compile($("#temp1").html());
		$("#chat").append(temp(data));
		window.scrollTo(0, $("#chat").prop("scrollHeight"))
	}
</script>
</html>

 

확인

 

 

- 세션을 남기지 않기 위해서 닫으면 지워주도록 하겠다.

/ex13/src/main/java/com/example/controller/ChatHandler.java

	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		// 연결끊겼을때
		System.out.println("연결끊김 : " + session.getId());
		sessionList.remove(session);
		super.afterConnectionClosed(session, status);
	}

 

 

- 이제 채팅 입력시 DB에 저장되는 작업을 해보겠다.

/ex13/src/main/resources/mapper/ChatMapper.xml

...
 	<insert id="insert">
 		insert into tbl_chat(sender, message)
 		values(#{sender},#{message})
 	</insert>
</mapper>

 

/ex13/src/main/java/com/example/mapper/ChatDAO.java

	public void insert(ChatVO vo);
}

 

/ex13/src/main/java/com/example/mapper/ChatDAOImpl.java

	@Override
	public void insert(ChatVO vo) {
		session.insert(namespace+".insert", vo);
	}

}

 

/ex13/src/main/java/com/example/controller/ChatController.java

	@RequestMapping(value="/chat/insert",method=RequestMethod.POST)
	public void insert(ChatVO vo){
		cdao.insert(vo);
	}
}

 

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
<script>
	var uid = "${uid}"
	getList();
	$("#txtMessage").on("keypress", function(e) {
		if (e.keyCode == 13 && !e.shiftKey) {
			e.preventDefault();
			var message = $("#txtMessage").val();
			if (message == "") {
				alert("메시지를 입력하세요.");
				$("#txtMessage").focus();
				return;
			}

			// 서버로 메시지 보내기
			sock.send(uid + "|" + message);
			$("#txtMessage").val("");
			
			// DB로 데이터 보내기
			$.ajax({
				type:'post',
				url:'/chat/insert',
				data:{
					sender:uid,
					message:message
				},
				success:function(){
					
				}
			})
		}
	})
...

 

확인

 

 

- 내가 작성한 채팅만 삭제할 수 있도록 하겠다.

...
<body>
	<div class="chat_wrap">
		<div class="header">
			<h3>채팅방(${uid})</h3>
		</div>
		<div id="chat"></div>
		<!-- 채팅저장출력 -->
		<script id="temp" type="text/x-handlebars-template">
		{{#each .}}
       <div class="{{printLeftRight sender}}">
          <div class="sender">{{sender}}</div>
          <div class="message">
			{{message}}
			<a href="{{id}}" style="display:{{printNone sender}}">X</a>
			</div>
          <div class="date">{{regdate}}</div>
       </div>
		{{/each}}
       </script>
		<!-- 새로운채팅출력 -->
		<script id="temp1" type="text/x-handlebars-template">
       <div class="{{printLeftRight sender}}">
          <div class="sender">{{sender}}</div>
          <div class="message">{{message}}</div>
          <div class="date">{{regdate}}</div>
       </div>
       </script>
		<div class="input-div">
			<textarea id="txtMessage" cols="30" rows="10"
				placeholder="메시지를 입력한 후에 엔터키를 누르세요."></textarea>
		</div>
	</div>
</body>
<!-- 메시지 입력시 오른쪽 왼쪽으로 기입되는 방식 지정 -->
<script>
	var uid = "${uid}";
	Handlebars.registerHelper("printLeftRight", function(sender) {
		if (uid != sender) {
			return "left";
		} else {
			return "right";
		}
	});
	Handlebars.registerHelper("printNone", function(sender) {
		if (uid != sender) {
			return "none";
		}
	});
</script>
...

 

 

- 삭제하는 경우의 이벤트를 만들어보도록 하겠다.

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
<script>
	var uid = "${uid}"
	getList();
	
	// 채팅 삭제
	$("#chat").on('click','.message a',function(e){
		e.preventDefault();
		var id=$(this).attr("href");
		if(!confirm(id+"을(를) 삭제하시겠습니까?")) return;
	})
	
...

컨펌확인

 

- 이제 삭제해보도록 하겠다.

 	<delete id="delete">
 		delete from tbl_chat
 		where id=#{id}
 	</delete>
</mapper>

 

	public void delete(int id);
}

 

	@Override
	public void delete(int id) {
		session.delete(namespace+".delete", id);
	}

}

 

	@RequestMapping(value="/chat/delete",method=RequestMethod.POST)
	public void delete(int id){
		cdao.delete(id);
	}

}

 

...
	// 채팅 삭제
	$("#chat").on('click','.message a',function(e){
		e.preventDefault();
		var id=$(this).attr("href");
		if(!confirm(id+"을(를) 삭제하시겠습니까?")) return;
		$.ajax({
			type:'post',
			url:'/chat/delete',
			data:{id:id},
			success:function(){
				alert("삭제되었습니다.");
				getList();
			}
		})
	})
...

 

삭제 확인

 

- 바로 작성한 채팅글은 이와 같이 아이디가 없어서 바로 삭제하지 못한다.

이와 같이

 

- 가장 최신 id를 구해서 바로 지울 수 있도록 하겠다.

/ex13/src/main/resources/mapper/ChatMapper.xml

...
 	<select resultType="int" id="last">
 		select max(id) from tbl_chat
 	</select>
</mapper>

 

/ex13/src/main/java/com/example/mapper/ChatDAO.java

	public int last();
}

 

/ex13/src/main/java/com/example/mapper/ChatDAOImpl.java

	@Override
	public int last() {
		return session.selectOne(namespace+".last");
	}

}

 

/ex13/src/main/java/com/example/controller/ChatController.java

	@RequestMapping(value="/chat/insert",method=RequestMethod.POST)
	public void insert(ChatVO vo){
		cdao.insert(vo);
		int last=cdao.last();
		System.out.println("..............."+last);
	}

 

 

확인

 

/ex13/src/main/java/com/example/controller/ChatController.java

	@RequestMapping(value="/chat/insert",method=RequestMethod.POST)
	public int insert(ChatVO vo){
		cdao.insert(vo);
		int last=cdao.last();
		System.out.println("..............."+last);
		return last;
	}

 

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
	$("#txtMessage").on("keypress", function(e) {
		if (e.keyCode == 13 && !e.shiftKey) {
			e.preventDefault();
			var message = $("#txtMessage").val();
			if (message == "") {
				alert("메시지를 입력하세요.");
				$("#txtMessage").focus();
				return;
			}

			// 서버로 메시지 보내기
			$("#txtMessage").val("");
			
			// DB로 데이터 보내기
			$.ajax({
				type:'post',
				url:'/chat/insert',
				data:{
					sender:uid,
					message:message
				},
				success:function(data){ <<<----
					sock.send(uid + "|" + message+"|"+data); <<<----
				}
			})
		}
	})

	// 채팅 데이터 받아오기
	function getList() {
		$.ajax({
			type : 'get',
			url : '/chat.json',
			dataType : 'json',
			success : function(data) {
				var temp = Handlebars.compile($("#temp").html());
				$("#chat").html(temp(data));
			}
		});
	}

	// 웹소캣 생성
	var sock = new SockJS("http://localhost:8088/echo");
	sock.onmessage = onMessage;

	// 서버로부터 메시지 받기
	function onMessage(msg) {
		var items = msg.data.split("|");
		var sender = items[0];
		var message = items[1];
		var id = items[2]; <<<----
		var date = items[3]; <<<----
		var data = {
			"message" : message,
			"sender" : sender,
			"regdate" : date,
			"id":id <<<----
		}
		var temp = Handlebars.compile($("#temp1").html());
		$("#chat").append(temp(data));
		window.scrollTo(0, $("#chat").prop("scrollHeight"))
	}
</script>
</html>

 

확인

 

 

- 테이블에 유저에 따른 사진 업데이트를 위해 사진등록을 위한 컬럼 추가

- go to mysql

alter table tbl_user add photo nvarchar(200);

 

- 기존 데이터에 photo 추가

update tbl_user set photo='img01.png' where uid='user01';
update tbl_user set photo='img02.png' where uid='user02';
update tbl_user set photo='img03.png' where uid='user03';
update tbl_user set photo='img04.png' where uid='user04';
update tbl_user set photo='img05.png' where uid='user05';

select c.*, photo from tbl_chat c, tbl_user
where uid=sender;

 

데이터 update 확인

 

 

- 추가된 photo column을 UserVO에 추가

/ex13/src/main/java/com/example/domain/UserVO.java

package com.example.domain;

public class UserVO {
	private String uid;
	private String upass;
	private String photo; //new

 

- UserVO에 있는 photo를 사용하기 위해서 ChatVO가 상속받는다.

/ex13/src/main/java/com/example/domain/ChatVO.java

package com.example.domain;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

public class ChatVO extends UserVO {
	private int id;
	private String sender;
	private String message;

 

- photo 사용을 위해서 list 출력부 mapper를 수정

/ex13/src/main/resources/mapper/ChatMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.ChatMapper">
 	<select id="list" resultType="com.example.domain.ChatVO">
 		select c.*, photo from tbl_chat c, tbl_user
		where uid=sender;
 	</select>
    ...

 

package com.example.controller;

import java.io.FileInputStream;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.domain.ChatVO;
import com.example.mapper.ChatDAO;

@RestController
public class ChatController {
	@Autowired
	ChatDAO cdao;
	
	// 파일 저장 루트 지정
	@Resource(name = "uploadPath")
	private String path;

...

	// 이미지파일 출력
	@RequestMapping("/display")
	public byte[] display(String file) throws Exception {
		FileInputStream in = new FileInputStream(path + "/" + file);
		byte[] image = IOUtils.toByteArray(in);
		in.close();
		return image;
	}

}

 

- DB에 저장되어 있는 photo 파일 경로를 통해서이 출력될 수 있도록 한다.

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
<body>
	<div class="chat_wrap">
		<div class="header">
			<h3>채팅방(${uid})</h3>
		</div>
		<div id="chat"></div>
		<!-- 채팅저장출력 -->
		<script id="temp" type="text/x-handlebars-template">
		{{#each .}}
       <div class="{{printLeftRight sender}}">
			<div class="sender" style="display:{{printDone sender}}">
				<img src="/display?file={{photo}}" style="width:50px; height:50px;" />
			</div>
          <div class="sender">{{sender}}</div>
          <div class="message">
			{{message}}
			<a href="{{id}}" style="display:{{printNone sender}}">X</a>
			</div>
          <div class="date">{{regdate}}</div>
       </div>
		{{/each}}
       </script>
...

 

- 그리고 본인을 제외한 상대방만 이미지가 출력될 수 있도록 registerhepler를 이용

/ex13/src/main/webapp/WEB-INF/views/chat.jsp

...
<script>
	// 메시지 입력시 오른쪽 왼쪽으로 기입되는 방식 지정
	var uid = "${uid}";
	Handlebars.registerHelper("printLeftRight", function(sender) {
		if (uid != sender) {
			return "left";
		} else {
			return "right";
		}
	});
	// 메시지 부분에 본인이 작성한 글을 삭제할 수 있게 a tag 활성화
	Handlebars.registerHelper("printNone", function(sender) {
		if (uid != sender) {
			return "none";
		}
	});
	
	// 본인 외에 유저의 사진만 보이도록 함
	Handlebars.registerHelper("printDone", function(sender) {
		if (uid == sender) {
			return "none";
		}
	});
</script>
...

 

...
<script>
	var uid = "${uid}"
	var photo = "${photo}"
	getList();

	// 채팅 삭제
	$("#chat").on('click', '.message a', function(e) {
		e.preventDefault();
		var id = $(this).attr("href");
		if (!confirm(id + "을(를) 삭제하시겠습니까?"))
			return;
		$.ajax({
			type : 'post',
			url : '/chat/delete',
			data : {
				id : id
			},
			success : function() {
				alert("삭제되었습니다.");
				sock.send("delete");
			}
		})
	})

	$("#txtMessage").on("keypress", function(e) {
		if (e.keyCode == 13 && !e.shiftKey) {
			e.preventDefault();
			var message = $("#txtMessage").val();
			if (message == "") {
				alert("메시지를 입력하세요.");
				$("#txtMessage").focus();
				return;
			}

			// 서버로 메시지 보내기
			$("#txtMessage").val("");

			// DB로 데이터 보내기
			$.ajax({
				type : 'post',
				url : '/chat/insert',
				data : {
					sender : uid,
					message : message
				},
				success : function(data) {
					sock.send(uid + "|" + message + "|" + data + "|" + photo);
					getList();
				}
			})
		}
	})

	// 채팅 데이터 받아오기
	function getList() {
		$.ajax({
			type : 'get',
			url : '/chat.json',
			dataType : 'json',
			success : function(data) {
				var temp = Handlebars.compile($("#temp").html());
				$("#chat").html(temp(data));
			}
		});
	}

	// 웹소캣 생성
	var sock = new SockJS("http://localhost:8088/echo");
	sock.onmessage = onMessage;

	// 서버로부터 메시지 받기
	function onMessage(msg) {
		var items = msg.data.split("|");
		var sender = items[0];

		if (sender=="delete") {
			getList();
			return;
		}

		var message = items[1];
		var id = items[2];
		var photo = items[3];
		var date = items[4];
		var data = {
			"message" : message,
			"sender" : sender,
			"regdate" : date,
			"id" : id,
			"photo" :photo
		}
		var temp = Handlebars.compile($("#temp1").html());
		$("#chat").append(temp(data));
		window.scrollTo(0, $('#chat').prop('scrollHeight'));
	}
</script>
</html>

 

ex13.zip
0.33MB