
해당 프로젝트는 2023/01/25 ~ 2023/03/12 내에 진행되는
아카데미 내 수강생들끼리 팀을 나누어 진행한 모의 프로젝트입니다.
팀원은 5명이었으며, 프로젝트 리더를 맡았습니다.
이전 글 목록
1) 주어진 RFP를 바탕으로 주제 선정 - Spring Project(OTT 서비스)
2) ERD 설계 - Spring Project(OTT 서비스)
3) 회원 가입 기능 구현 - Spring Project (OTT 서비스)
4) 로그인, 로그아웃 기능 구현 - Spring Project (OTT 서비스)
5) 상세 페이지 및 회원 정보 수정 - Spring Project (OTT 서비스)
6) CRUD를 한번에 → 게시판 만들기(QNA게시판) - Spring Project(Mybatis) (OTT 서비스)
7) 게시판 페이징 처리 - Spring Project (OTT 서비스)
8) 카카오 지도 API 사용하기 - Spring Project (OTT 서비스)
9) (네아로) 네이버 로그인 API 활용 사이트 로그인 및 회원가입 - SPRING Project(OTT 서비스)
10) 카카오 로그인 API 사용하기(내 사이트 로그인 및 회원가입) - Spring Project(OTT Service)
11) 아임포트(포트원) API를 이용한 결제처리 - Spring Project(OTT Service)
12) 관리자 페이지 만들기(데이터 통계 및 chart.js, 유저 알고리즘) - Spring Project(OTT Service)
13) 관리자 페이지 (영상 정보 업로드 시 여러 테이블에 insert 및 update) - Spring Project(OTT Service)
14) 웹 소켓(Web Socket)을 활용한 실시간 알림 - Spring Service(OTT Service)
웹소켓을 다루는 것에 흥미를 느껴 이번엔 관리자와 유저간의 채팅기능을 구현해 보았다.
유저 입장에서 QNA의 답변만 기다리기에는 너무 시간이 지체될 수 있다고 생각하였고, 유저의 편의성을 위해 추가하였다.
소켓 및 JS코드 작성
UserSocket
package com.test.test1.chat.util;
(...생략...)
@ServerEndpoint("/userchat")
public class UserSocket {
//searchUser 함수의 filter 표현식을 위한 인터페이스
private interface SearchExpression {
//람다식을 위한 함수
boolean expression(User user);
}
//서버와 유저간의 접속을 key로 구분하기 위한 이너 클래스
private class User {
Session session;
String key;
}
//유저와 서버간의 접속 리스트 -> 동기화처리
private static List<User> sessionUsers = Collections.synchronizedList(new ArrayList<>());
//리스트에서 탐색(session)
private static User getUser(Session session) {
return searchUser(x -> x.session == session);
}
//리스트에서 탐색(key)
private static User getUser(String key) {
return searchUser(x -> x.key.equals(key));
}
//접속 리스트 탐색
private static User searchUser(SearchExpression func) {
Optional<User> op = sessionUsers.stream().filter(x -> func.expression(x)).findFirst();
if(op.isPresent()) {
return op.get();
}
return null;
}
//접속
@OnOpen
public void handleOpen(Session userSession) throws IOException {
User user = new User();
user.key = UUID.randomUUID().toString().replace("-", "");
//User에 websocektsession 부여
user.session = userSession;
//유저 리스트에 등록한다. (방 유지)
sessionUsers.add(user);
user.session.getBasicRemote().sendText("uuid:" + user.key);
//운영자 Client에 유저가 접속한 것을 알린다. -> admin 방 생성 처리
AdminSocket.visit(user.key);
}
//JS에서 전달받을 때
@OnMessage
public void handleMessage(String message, Session userSession) throws IOException {
User user = getUser(userSession);
if (user != null) {
//어떤 유저가 메세지를 보냈는지 admin에게 전달
AdminSocket.sendMessage(user.key, message);
}
}
//종료 시
@OnClose
public void handleClose(Session userSession) {
User user = getUser(userSession);
if (user != null) {
//admin에 종료 전송 -> 방 닫힘
AdminSocket.bye(user.key);
sessionUsers.remove(user);
}
}
//운영자 -> user 메세지
public static void sendMessage(String key, String message) {
User user = getUser(key);
if (user != null) {
try {
//메세지 받음(기존 usersession = 웹소켓세션)
user.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//유저 UK get -> admin에 보낼용도
public static String[] getUserKeys() {
String[] ret = new String[sessionUsers.size()];
for (int i = 0; i < ret.length; i++) {
ret[i] = sessionUsers.get(i).key;
}
return ret;
}
}
/* ********************************************************** */
AdminSocket
package com.test.test1.chat.util;
(...생략...)
@ServerEndpoint("/adminchat")
public class AdminSocket {
//admin 한명 (둘 이상의 세션에서 접속을 하면 마지막 세션만 작동) -> 추가 가능
private static Session admin = null;
//접속
@OnOpen
public void handleOpen(Session userSession) throws IOException {
//운영자 유저의 세션을 바꾼다.
admin = userSession;
//기존에 접속해 있는 유저의 정보를 운영자 client로 보낸다.
for(String key : UserSocket.getUserKeys()) {
//전송
visit(key);
}
}
//메세지 보낼 때
@OnMessage
public void handleMessage(String message, Session userSession) throws IOException {
String[] split = message.split("#####", 2);
//유저 UK를 조건으로 메세지 전송
String key = split[0];
String msg = split[1];
UserSocket.sendMessage(key, msg);
}
//접속종료
@OnClose
public void handleClose(Session userSession) {
admin = null;
}
//admin view로
private static void send(String message) {
if (admin != null) {
try {
admin.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//아래의 조건들로 방생성, 방종료, 메세지전송
//유저 입장
public static void visit(String key) {
send("{\"status\":\"visit\", \"key\":\"" + key + "\"}");
}
//유저 message 받음
public static void sendMessage(String key, String message) {
send("{\"status\":\"message\", \"key\":\"" + key + "\", \"message\":\"" + message + "\"}");
}
//유저 나감
public static void bye(String key) {
send("{\"status\":\"bye\", \"key\":\"" + key + "\"}");
}
}
/* ********************************************************** */
관리자나 유저는 서버의 입장에서 둘 다 소켓의 클라이언트이기 때문에 유저 소켓과 관리자 소켓으로 나누어 사용했다.
웹소켓은 자바에서 서블릿으로 구분이 된다.
@ServerEndPoint 에너테이션을 선언하여 웹소켓으로 접속 가능한 URL 매핑값을 명시할 수 있다.
유저는 관리자와의 1:1 채팅이 되어야 하고, 관리자는 유저와 1:N채팅이 되어야 한다.
일반 유저 접속 시 UUID를 설정 해 고유 키를 부여하고 서버에서 관리자와 유저 간 채팅 구분을 위해 고유 키를 통해 데이터를 주고 받게 설정하였다.
userchat.js
/* 0225 장재호 */
var url = "ws://localhost:8080/userchat";
var webSocket = null;
var messageTextArea = document.getElementById("messageTextArea");
var sessionUserId = document.querySelector('#sessionUserId').value;
var chatIcon = document.querySelector('#chatIcon');
var chatArea = document.querySelector(".chatArea");
var uuid = null;
chatIcon.addEventListener('click', function(){
chatArea.classList.toggle("hidden");
//첫 클릭 시 소켓 생성
if(webSocket == null){
webSocket = new WebSocket(url);
//접속 시
webSocket.onopen = function(message) {
//기존 데이터 찾기
$.ajax({
url : 'getChatLog',
type : 'post',
data : {"id" : sessionUserId},
dataType : 'json',
success : function(data){
if(data.length>0){//기존 채팅이 있을 때
data.forEach(function(d){
let user_id = d.user_id;
let sender = d.sender;
let msg = d.msg;
if(!msg.includes("입장")){
if(user_id == sender){ //내 메세지
messageTextArea.value += "(나) : "+msg+"\n";
}
if(sender == 1){ //관리자 메세지
messageTextArea.value += "(admin) : "+msg+"\n";
}
}
})
}
else{//첫 입장
messageTextArea.value += "문의사항을 남겨주세요. \n";
}
},
error : function(){
alert("에러");
}
})
console.log("접속");
};
//종료
webSocket.onclose = function(message) {
console.log("종료");
};
//에러 발생
webSocket.onerror = function(message) {
messageTextArea.value += "error";
};
//서버-> 뷰 메세지 도착
webSocket.onmessage = function(message) {
if(message.data.includes("uuid:")){
uuid = message.data.split(':')[1];
$.ajax({
url : 'chatCreate',
data : {"uuid" : uuid, "id" : sessionUserId, "msg" : "입장"},
type : 'post'
});
}
else {
messageTextArea.value += "(admin) : " + message.data + "\n";
}
};
}
});
//뷰 -> 서버 메세지 전송
function sendMessage() {
let message = document.getElementById("textMessage");
messageTextArea.value += "(나) : " + message.value + "\n";
$.ajax({
url : 'chatCreate',
data : {"uuid" : uuid, "id" : sessionUserId, "msg" : message.value},
type : 'post'
});
webSocket.send(sessionUserId+","+message.value);
message.value = "";
}
//종료
function closeChat(){
if(confirm("종료 시 채팅 로그는 삭제됩니다.")){
messageTextArea.value += "이용해 주셔서 감사합니다.";
$.ajax({
url : 'chatDelete',
data : {"id" : sessionUserId},
type : 'post'
});
chatArea.classList.add("hidden");
webSocket.close();
}
else return;
}
function enter() {
if(event.keyCode === 13) {
sendMessage();
return false;
}
return true;
}
/* ************************************************************ */
adminchat.js
/* 02.25 장재호 */
var webSocket = new WebSocket("ws://localhost:8080/adminchat");
var userUuid = null;
/* 필요하면 가공 */
webSocket.onopen = function(message) {
console.log("열림")
};
webSocket.onclose = function(message) {
console.log("닫힘");
};
webSocket.onerror = function(message) {
console.log("에러발생");
};
/************************************/
//adminsocket에서 send 할 때(정제해서 조건 보냄)
webSocket.onmessage = function(message) {
let node = JSON.parse(message.data);
console.log(node);
// message.status = 접속 형태
if(node.status === "visit") { //유저 접속 시
//알맞은 자리에 새 채팅영역 추가
let form = $(".template").html();
form = $("<div class='float-left'></div>").attr("data-key",node.key).append(form);
$(".container-fluid").append(form);
$.ajax({
url : '/getChatLog',
type : 'post',
data : {"uuid" : node.key},
dataType : 'json',
success : function(data){
if(data.length>0){//기존 채팅이 있을 때
data.forEach(function(d){
let id = d.id;
let msg = d.msg;
if(!msg.includes("입장")){
$('textarea').append("("+id+") : " +msg+ "\n");
}
})
}
},
error : function(){
alert("에러");
}
})
console.log("접속");
}
else if(node.status === "message") { //유저메세지 받을 때
//UK를 가지고 해당 div영역에 메세지 뿌림
let $div = $("[data-key='"+node.key+"']");
let log = $div.find(".console").val();
//받아온 유저 세션, 메세지 정제해서 사용
let user_id = node.message.split(",")[0];
let msg = node.message.split(",")[1];
$div.find(".console").val(log + "(" + user_id + ") : " +msg + "\n");
}
else if(node.status === "bye") { //퇴장
$("[data-key='"+node.key+"']").remove();
}
};
//관리자가 메세지 전송
$(document).on("click", ".sendBtn", function(){
//해당 자리에 view
let $div = $(this).closest(".float-left");
let message = $div.find(".message").val();
let key = $div.data("key"); //유저 UK
let log = $div.find(".console").val();
$div.find(".console").val(log + "(me) : " + message + "\n");
$div.find(".message").val("");
//js->adminsocket->usersocket으로 UK별 메세지 분류해서 보냄
webSocket.send(key+"#####"+message);
//db저장
$.ajax({
url : '/adminChatCreate',
data : {"uuid" : key, "id" : "admin", "msg" : message},
type : 'post'
});
});
$(document).on("keydown", ".message", function(){
if(event.keyCode === 13) {
$(this).closest(".float-left").find(".sendBtn").trigger("click");
return false;
}
return true;
});
/* ************************************************************ */
유저 입장에서의 채팅
우선 /chat URL 접근 시 채팅이 되게 설정 해 놓았다. 다른 JSP에서 include할 수 있게 준비는 완료 된 상태이다.
프로젝트 검수 단계에서 로그인 한 사용자에게 어느 페이지든 사용할 수 있게 뿌려 줄 예정이다.
1. 유저가 소켓 생성
채팅 아이콘을 클릭 시 소켓이 생성되며 채팅방에 입장이 가능하다.
설명이 빠진 부분이 있는데, CHATLOG 테이블을 생성했고 해당 테이블에서는 유저의 채팅로그가 남는다. 유저가 먼저 채팅을 쳤을 경우 관리자가 뒤늦게 들어왔을 때 확인이 안되기 때문이다. 또한 유저가 대화방을 나가지 않았고 단순 채팅만 비활성화 하거나 페이지 이동의 경우도 있기 때문에 따로 채팅 로그를 관리하는 테이블이 필요했다.
또한 ajax를 통해 해당 유저의 ID를 전송하여 기존 채팅로그가 있는지 확인한다.
채팅로그가 있을 경우, 로그를 View해 줄 수 있게 설계하였다.
버튼 클릭 시 소켓 생성을 위해 UserSocket의 handleOpen메서드를 실행한다.
UserSocket
@OnOpen
public void handleOpen(Session userSession) throws IOException {
User user = new User();
user.key = UUID.randomUUID().toString().replace("-", "");
//User에 websocektsession 부여
user.session = userSession;
//유저 리스트에 등록한다. (방 유지)
sessionUsers.add(user);
user.session.getBasicRemote().sendText("uuid:" + user.key);
//운영자 Client에 유저가 접속한 것을 알린다. -> admin 방 생성 처리
AdminSocket.visit(user.key);
}
UUID를 파일 업로드를 제외하고 다른곳에 처음 사용했는데, Unique Key라고 생각하고 사용했다.
UserSocket안의 User클래스 내에 key와 session을 셋팅 해 주고, user클래스를 컬렉션 리스트에 추가했다.
그 아래의 sendText메서드를 사용하여 메서드 내의 파라미터 값을 JS의 onmessage 함수로 보냈다.
실행 되고 난 후 Adminsocket의 visit메서드를 호출할 텐데, 순차적으로 포스팅 해 보겠다.
userchat.js
//서버-> 뷰 메세지 도착
webSocket.onmessage = function(message) {
if(message.data.includes("uuid:")){
uuid = message.data.split(':')[1];
$.ajax({
url : 'chatCreate',
data : {"uuid" : uuid, "id" : sessionUserId, "msg" : "입장"},
type : 'post'
});
}
else {
messageTextArea.value += "(admin) : " + message.data + "\n";
}
};
ajax를 활용하여 단순 채팅방 "입장"이라는 텍스트를 db에 입력하기 위해 사용했고, 입장 시에 도착하는 메세지가 아닌 경우는 상대방, 즉 관리자의 메세지일 경우 밖에 존재하지 않으니 아래의 조건은 관리자의 메세지다. 라고 명시해 주었다.
ChatController
@ResponseBody
@RequestMapping(value="chatCreate", method=RequestMethod.POST)
public void chatCreate(ChatDto chatDto) {
chatService.userChatCreate(chatDto);
}
입장 시 DB에 담길 데이터들을 chatDto에 셋팅하여 Dao까지 보내면 된다.
Service / Impl / Dao
//Service
void userChatCreate(ChatDto chatDto);
//ServiceImpl
@Override
public void userChatCreate(ChatDto chatDto) {
chatDao.userChatCreate(chatDto);
}
//Dao
public void userChatCreate(ChatDto chatDto) {
ss.insert("chat.userChatCreate", chatDto);
}
SQL.xml
<insert id="userChatCreate">
insert into CHATLOG (USER_ID, SENDER, MSG, UUID)
values ((select USER_ID from USER where ID=#{id}), (select USER_ID from USER where ID=#{id}), #{msg}, #{uuid})
</insert>
유저가 입장했다는 데이터가 DB에 생성되었다. 조회 시 "입장" 이라는 데이터만 제외하고 출력할 것이다.
유저가 재 입장을 했을 경우를 위해 만들었는데 의미가 약간 퇴색된 것 같긴 하다. 차후 기능개선 때 관리자에게 유저가 재 입장을 했다고 View해 주어야 할 것 같다.
AdminSocket.visit(user.key);
insert가 끝났고, 리턴값이 없기 때문에 UserSocket의 handleOpen메서드의 마지막 코드를 실행 할 차례이다.
AdminSocket
public static void visit(String key) {
send("{\"status\":\"visit\", \"key\":\"" + key + "\"}");
}
보내는 값은 json형태로, status는 소켓에 전송 시 visit, message, bye와 같은 세 조건을 나누기 위해 사용하였고
key는 유저의 키값이다.
private static void send(String message) {
if (admin != null) {
try {
admin.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
json형태의 메세지를 파라미터로 받아와서 adminchat.js로 전송 해 준다.
adminchat.js
//adminsocket에서 send 할 때(정제해서 조건 보냄)
webSocket.onmessage = function(message) {
let node = JSON.parse(message.data);
// message.status = 접속 형태
if(node.status === "visit") { //유저 접속 시
//알맞은 자리에 새 채팅영역 추가
let form = $(".template").html();
form = $("<div class='float-left'></div>").attr("data-key",node.key).append(form);
$(".container-fluid").append(form);
$.ajax({
url : '/getChatLog',
type : 'post',
data : {"uuid" : node.key},
dataType : 'json',
success : function(data){
if(data.length>0){//기존 채팅이 있을 때
data.forEach(function(d){
let id = d.id;
let msg = d.msg;
if(!msg.includes("입장")){
$('textarea').append("("+id+") : " +msg+ "\n");
}
})
}
},
error : function(){
alert("에러");
}
});
}
(...생략...)
};
adminchat.jsp
<div class="template" style="display:none;">
<form>
<input type="text" class="message" onkeydown="if(event.keyCode === 13) return false;">
<input value="Send" type="button" class="sendBtn">
</form>
<br />
<textarea id="messageArea"rows="10" cols="50" class="console" disabled="disabled"></textarea>
</div>
위의 코드는 관리자 채팅영역이다.
새 채팅방이 생성될 때마다, template의 클래스명을 바꿔주었다. 해당 클래스의 css속성이 float : left이다.
또한 해당 div의 data값에 현재 유저의 key값을 담았다. 이제 해당 키값을 가진 유저가 채팅을 보낼 때, 같은 키 값을 가진 div영역 안에 채팅이 들어갈 수 있게 message 전송 시 조건만 잘 주면 된다.
유저가 채팅방에 입장 했을 때, 새 div생성과 함께 ajax를 통해 해당 uuid를 가진 채팅로그를 찾아서 textarea에 넣어주었다. 이렇게 해야 유저가 먼저 채팅을 입력하고, 나중에 관리자가 확인했을 때 채팅로그가 남아있을 것이다.
2. 메세지 송신
유저가 채팅을 진행할 때, 아래의 스크립트 함수가 실행된다.
userchat.js
//뷰 -> 서버 메세지 전송
function sendMessage() {
let message = document.getElementById("textMessage");
messageTextArea.value += "(나) : " + message.value + "\n";
$.ajax({
url : 'chatCreate',
data : {"uuid" : uuid, "id" : sessionUserId, "msg" : message.value},
type : 'post'
});
webSocket.send(sessionUserId+","+message.value);
message.value = "";
}
위에서 입장했을 때와 똑같다, 다른점은 메세지가 입력되어 넘어가기 때문에 CHATLOG 테이블에는 유저가 전송한 메세지가 담긴다는 점이다.
데이터를 보내 테이블에 채팅로그를 등록하고 UserSocket의 handleMessage메서드를 호출한다.
UserSocket
//JS에서 전달받을 때
@OnMessage
public void handleMessage(String message, Session userSession) throws IOException {
User user = getUser(userSession);
if (user != null) {
//어떤 유저가 메세지를 보냈는지 admin에게 전달
AdminSocket.sendMessage(user.key, message);
}
}
getUser메서드를 통해 User클래스가 담겨있는 컬렉션 리스트에서 세션을 가져나와 user객체에 담는다.
그 후 AdminSocket에 해당 유저의 key와 보낸 메세지를 전달한다.
AdminSocket
//유저 message 받음
public static void sendMessage(String key, String message) {
send("{\"status\":\"message\", \"key\":\"" + key + "\", \"message\":\"" + message + "\"}");
}
AdminSocket의 sendMessage 메서드이다.
위의 visit와 동일하게, status값과 유저의 key, 그리고 유저가 보낸 message가 담겨져 send 메서드로 간다.
//admin view로
private static void send(String message) {
if (admin != null) {
try {
admin.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
해당 send메서드에서 sendMessage에서 담겨있던 json형태의 텍스트가 adminchat.js로 전송된다.
adminchat.js
webSocket.onmessage = function(message){
(...생략...)
else if(node.status === "message") { //유저메세지 받을 때
//UK를 가지고 해당 div영역에 메세지 뿌림
let $div = $("[data-key='"+node.key+"']");
let log = $div.find(".console").val();
//받아온 유저 세션, 메세지 정제해서 사용
let user_id = node.message.split(",")[0];
let msg = node.message.split(",")[1];
$div.find(".console").val(log + "(" + user_id + ") : " +msg + "\n");
}
(...생략...)
}
$div는 앞서 visit시 생성했던 div중 메세지를 보낸 유저의 키값과 동일한 div이다.
log는 jQuery의 find()함수를 이용하여, $div 하위요소 중 console클래스의 value값을 가져온다. console은 textArea이다.
유저가 보낸 키와 일치하는 div의 textArea에 유저가 보낸 메세지를 기존메세지 + (ID) : 메세지(줄바꿈) 형태로 담는다.
3. 퇴장
userchat.js
//종료
function closeChat(){
if(confirm("종료 시 채팅 로그는 삭제됩니다.")){
messageTextArea.value += "이용해 주셔서 감사합니다.";
$.ajax({
url : 'chatDelete',
data : {"id" : sessionUserId},
type : 'post'
});
chatArea.classList.add("hidden");
webSocket.close();
}
else return;
}
채팅 종료 시 ajax를 이용하여 해당 유저의 채팅로그 CHATLOG 테이블에서 가 전부 지워진다. 관리자의 채팅로그 또한 해당 유저에 대한 채팅이기 때문에 같이 지워지도록 설계했다.
UserSocket
//종료 시
@OnClose
public void handleClose(Session userSession) {
User user = getUser(userSession);
if (user != null) {
//admin에 종료 전송 -> 방 닫힘
AdminSocket.bye(user.key);
sessionUsers.remove(user);
}
}
종료 시 유저의 세션정보들을 담았던 배열에서 지워주어야 한다.
또한 AdminSocket에 유저 키를 보내 유저가 퇴장했음을 알려주어야 한다.
AdminSocket
//유저 나감
public static void bye(String key) {
send("{\"status\":\"bye\", \"key\":\"" + key + "\"}");
}
visit, message와 동일한 형태로, send메서드를 통해 JS에 status와 유저의 key값을 보내 관리자 채팅 페이지에서 해당 유저의 채팅방을 삭제한다.
webSocket.onmessage = function(message) {
(...생략...)
else if(node.status === "bye") { //퇴장
$("[data-key='"+node.key+"']").remove();
}
}
관리자 입장에서의 채팅
1. 채팅방 접속
관리자의 adminchat.js에서 소켓 생성 시 제어해야 할 것들이 없기 때문에 작성하지 않았다.
AdminSocket
private static Session admin = null;
//접속
@OnOpen
public void handleOpen(Session userSession) throws IOException {
//운영자 유저의 세션을 바꾼다.
admin = userSession;
//기존에 접속해 있는 유저의 정보를 운영자 client로 보낸다.
for(String key : UserSocket.getUserKeys()) {
//전송
visit(key);
}
}
접속 시 받아온 userSession을 admin 필드에 넣는다.
UserSocket의 getUserKeys()메서드를 이용하여 관리자가 접속했을 때, 유저의 key들을 받아와 해당 유저 수 만큼의 채팅방을 생성해야 한다.
UserSocket
//유저 UK get -> admin에 보낼용도
public static String[] getUserKeys() {
String[] ret = new String[sessionUsers.size()];
for (int i = 0; i < ret.length; i++) {
ret[i] = sessionUsers.get(i).key;
}
return ret;
}
현재 접속해 있는 유저의 key값들을 리턴해 주는 메서드이다. 해당 메서드가 실행되어 key들을 가져나온다.
그 후 위의 visit메서드를 활용해 채팅방들이 생성 될 것이다. 채팅 로그가 있다면 알아서 해당 채팅방에 채팅로그들이 나타날 것이다.
위의 내용에 있으니 생략하도록 하겠다.
2. 채팅 전송
//관리자가 메세지 전송
$(document).on("click", ".sendBtn", function(){
//해당 자리에 view
let $div = $(this).closest(".float-left");
let message = $div.find(".message").val();
let key = $div.data("key"); //유저 UK
let log = $div.find(".console").val();
$div.find(".console").val(log + "(me) : " + message + "\n");
$div.find(".message").val("");
//js->adminsocket->usersocket으로 UK별 메세지 분류해서 보냄
webSocket.send(key+"#####"+message);
//db저장
$.ajax({
url : '/adminChatCreate',
data : {"uuid" : key, "id" : "admin", "msg" : message},
type : 'post'
});
});
여기서의 this는 sendBtn이며 $div는 전송버튼이 클릭 된 채팅방을 말한다.
message는 채팅 입력 란인 input text의 value를 말한며, log는 textArea전체의 텍스트값이다.
채팅을 보냈기 때문에, textArea에 (me) : 메세지를 추가하여 view해 주었다.
ajax를 통해 해당 key값을 가진 USER_ID에 SENDER을 관리자의 PK로 설정하여, message를 담았다.
해당 채팅방을 리로드 시 USER_ID에 대한 모든 message값을 보여지게 하기 위해서이다.
AdminSocket
//전달 받을 때
@OnMessage
public void handleMessage(String message, Session userSession) throws IOException {
String[] split = message.split("#####", 2);
//유저 UK를 조건으로 메세지 전송
String key = split[0];
String msg = split[1];
UserSocket.sendMessage(key, msg);
}
받을 유저의 key와 관리자의 메세지를 UserSocket의 sendMessage메서드로 보냈다.
UserSocket
//운영자 -> user 메세지
public static void sendMessage(String key, String message) {
User user = getUser(key);
if (user != null) {
try {
//메세지 받음(기존 usersession = 웹소켓세션)
user.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
해당 유저의 key를 받아 그에 맞는 유저에게 운영자의 메세지를 전송했다.
userchat.js
//서버-> 뷰 메세지 도착
webSocket.onmessage = function(message) {
if(message.data.includes("uuid:")){
uuid = message.data.split(':')[1];
$.ajax({
url : 'chatCreate',
data : {"uuid" : uuid, "id" : sessionUserId, "msg" : "입장"},
type : 'post'
});
}
else {
messageTextArea.value += "(admin) : " + message.data + "\n";
}
};
관리자의 채팅 시 uuid가 없기 때문에 아래 else문이 실행 될 것이다. 관리자의 채팅로그는 이미 이전에 추가됐기 때문에 아래 조건만 타면 정상적으로 유저에게 view될 것이다.
3. 퇴장
adminchat.js
webSocket.onclose = function(message) {
};
퇴장시에 제어할 것이 없어 소켓만 닫아주었다.
소켓을 활용하는 것이 너무 재밌었지만, 소켓을 다루면서 내가 이걸 내것으로 만들 수 있을까 의문이 들었지만
어느정도는 내 것으로 만든 것 같다.
추가로 아직 관리자가 접속 시 기존 유저의 데이터 로그가 남지 않는 문제가 발생했다.
해당 문제는 얼른 수정 한 후 추가 포스팅을 남기도록 하겠다.
참조 (아래 블로그의 코드를 활용하여 해당 프로젝트에 녹여냈습니다)
2023.04 ~ 백엔드 개발자의 기록
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!