2023.06.16 97일차 Team Project 웹소켓 실시간 채팅
97일차 웹소켓 실시간 채팅
사용자 페이지 나누기
1. 서버 config
구현체를 등록했던 url정보를 방번호에 따라 구분할 수 있도록 변경해준다.
@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final SocketHandler socketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry
.addHandler(socketHandler, "/chat/{roomNumber}");
}
}url에서 chat이후 들어오는 roomNumber값은 앞으로 방을 구분하는 값이 될 것이다.
2. SocketHandler
@Slf4j
@Component
public class SocketHandler extends TextWebSocketHandler {
// 웹소켓 세션을 담아둘 맵
// HashMap<String, WebSocketSession> sessionMap = new HashMap<>();
private List<HashMap<String, Object>> roomList = new ArrayList<>(); // 웹소켓 세션을 담아둘 리스트 ---roomListSessions
private Gson gson = new Gson();
// 웹소켓 연결이 되면 동작
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 소켓 연결시 session의 id를 키로 세션저장
super.afterConnectionEstablished(session);
// url에서 roomNumber를 추출해서 roomList에담을것임.
boolean flag = false;
String url = session.getUri().toString();
String roomNumber = url.split("/chat/")[1];
int idx = roomList.size(); // 방의 사이즈를 조사한다.
if (roomList.size() > 0) {
for (int i = 0; i < roomList.size(); i++) {
String roomNum = (String) roomList.get(i).get("roomNumber");
if (roomNum.equals(roomNumber)) {
flag = true;
idx = i;
break;
}
}
}
if (flag) { // 존재하는 방이라면 세션만 추가한다.
HashMap<String, Object> map = roomList.get(idx);
map.put(session.getId(), session);
} else { // 최초 생성하는 방이라면 방번호와 세션을 추가한다.
HashMap<String, Object> map = new HashMap<>();
map.put("roomNumber", roomNumber);
map.put(session.getId(), session);
roomList.add(map);
}
// 세션 등록이 끝나면 발급받은 세션 ID값의 메시지를 발송한다.
Map<String, String> messageData = new HashMap<>();
messageData.put("type", "getId");
messageData.put("sessionId", session.getId());
String json = gson.toJson(messageData);
session.sendMessage(new TextMessage(json));
}
// 메시지를 수신하면 실행
// 상속받은 TextWebSocketHandler는 handleTextMessage를 실행시키고
// 메시지 타입에따라 handleBinaryMessage또는 handleTextMessage가 실행된다.
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// 메시지 발송
String msg = message.getPayload();
JsonObject obj = jsonToObjectParser(msg);
String roomNum = obj.get("roomNumber").getAsString();
HashMap<String, Object> temp = new HashMap<>();
if (roomList.size() > 0) {
for (int i = 0; i < roomList.size(); i++) {
String roomNumber = (String) roomList.get(i).get("roomNumber"); // 세션리스트의 저장된 방번호를 가져와서
if (roomNumber.equals(roomNum)) { // 같은값의 방이 존재한다면
temp = roomList.get(i); // 해당 방번호의 세션리스트의 존재하는 모든 object값을 가져온다.
break;
}
}
// 해당 방의 세션들만 찾아서 메시지를 발송해준다.
for (String k : temp.keySet()) {
if (k.equals("roomNumber")) { // 다만 방번호일 경우에는 건너뛴다.
continue;
}
WebSocketSession wss = (WebSocketSession) temp.get(k);
if (wss != null) {
try {
String json = gson.toJson(obj);
wss.sendMessage(new TextMessage(json));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
session.close();
// 소켓 종료
if (roomList.size() > 0) { // 소켓이 종료되면 해당 세션값들을 찾아서 지운다.
for (int i = 0; i < roomList.size(); i++) {
roomList.get(i).remove(session.getId());
}
}
super.afterConnectionClosed(session, status);
}
private static JsonObject jsonToObjectParser(String jsonStr) {
JsonObject obj = null;
try {
obj = JsonParser
.parseString(jsonStr)
.getAsJsonObject();
} catch (JsonParseException e) {
e.printStackTrace();
}
return obj;
}
}구현체의 handler이다.
1.세션을 관리하던 map객체에서 list, hashmap형태로 변경되었다. hashmap의 value자료형도 WebSocketSession에서 Object형으로 변경되었습니다.
2.세션을 저장할때, 현재 접근한 방의 정보가 있는지 체크하고 존재하지 않으면 방의 번호를 입력 후 세션들을 담아주는 로직으로 변경되었다.
3.마찬가지로 종료시에도 list컬랙션을 순회하면서 해당 키값의 세션들을 삭제하도록 변경되었다.
4.메시지를 발송하는 handleTextMessage메소드에서는 현재의 방번호를 가져오고 방정보+세션정보를 관리하는 rls리스트 컬랙션에서 데이터를 조회한 후에 해당 Hashmap을 임시 맵에 파싱하여 roomNumber의 키값을 제외한 모든 세션키값들을 웹소켓을 통해 메시지를 보내준다.
3. 클라이언트단
let customerId = $('#customerId').val();
let roomNumber = $("#roomNumber").val();
//웹소켓 객체
let ws;
function wsOpen() {
try {
ws = new WebSocket("ws://" + location.host + "/chat/" + roomNumber);
const connectionTime = 3 * 60 * 1000;
if (ws !== null) {
// 일정 시간이 지난 후에 연결을 닫음
setTimeout(function() {
if (ws.readyState === WebSocket.OPEN) {
disconnect();
}
}, connectionTime);
}
wsEvt();
} catch (error) {
// 웹소켓 연결 실패 처리
$("#messageTextArea").append(`<p>웹소켓 연결에 오류가 발생했습니다...</p>`);
}
}
function wsEvt() {
//소켓이 열리면 초기화 세팅하기
ws.onopen = function(data) {
console.log("웹소켓연결됨!!");
$("#messageTextArea").append(`<p>서버에 연결되었습니다... </br>
채팅에 응답이 없을시 1대1문의를 이용해주세요...!</p>`);
}
ws.onerror = function(event) {
$("#messageTextArea").append(`<p>오류가 발생했습니다...</p>`);
};
ws.onmessage = function(data) {
let msg = data.data;
if (msg != null && msg.trim() != '') {
let serverJson = JSON.parse(msg);
if (serverJson.type === "getId") {
let sId = serverJson.sessionId != null ? serverJson.sessionId : "";
if (sId != '') {
$("#sessionId").val(sId);
}
} else if (serverJson.type === "message") {
if (serverJson.sessionId === $("#sessionId").val()) {
$("#messageTextArea").append("<div class='me'>" + customerId + "님: " + serverJson.msg + "</div>");
} else {
$("#messageTextArea").append("<div class='admin'>관리자: " + serverJson.msg + "</div>");
}
} else {
console.warn("unknown type!")
}
}
}
ws.onclose = function(event) {
$("#messageTextArea").append(`<p>채팅 연결이 해제 되었습니다.</p>`);
console.log("연결해제됨!!!");
};
// enter눌리면 send() 실행
$(document).keypress(function(e) {
if (e.which == 13) {
e.preventDefault(); // 엔터 키의 기본 동작 방지
send();
}
});
$("#sendMessageBtn").click(function() {
send();
});
}
function send() {
let data = {
type: "message",
roomNumber: roomNumber,
sessionId: $("#sessionId").val(),
userName: customerId,
msg: $("#textMessage").val()
}
ws.send(JSON.stringify(data));
$('#textMessage').val("");
}
function disconnect() {
if (ws !== null) {
let data = {
type: "close",
roomNumber: roomNumber,
sessionId: $("#sessionId").val(),
userName: customerId,
msg: $("#textMessage").val()
}
//닫았다는 메시지 보내기
ws.send(JSON.stringify(data));
//닫기
ws.close();
ws = null;
$.ajax("/chat/deleteRoom", {
method: "post",
data: {
roomNumber: roomNumber
}
});
}
}
//초기화
wsOpen();<div id="chatmodal" class="modal">
<div class="modal-content">
<div class="chatcontainer">
<div id="roomContainer" class="roomContainer">
<table id="roomList" class="roomList mx-auto"></table>
</div>
</div>
<iframe id="customerFrame" frameborder="0"></iframe>
<button class="close btn btn-danger">닫기</button>
</div>
</div>모달 창을 통해서 chatroom이 나오게 설정했다.
1.방의 번호값을 모델에서 저장한 값을 jstl을 통해 파싱한다.
2.접속한 방의 이름을 모델에서 저장한 값을 가져와서 채팅방 이름을 추가해준다. ${roomName}
3.메시지를 보내는 send함수에 roomNumber 키 값이 추가되었다.
방의 번호를 보내줌으로써 소켓서버는 어느방에서 메시지를 보냈는지 구분한다.
2023.08.02
이다음 부터는 블로그에 없이 직접 커스텀해나가는 것이다.
이전 커밋기록이라 정확하게 작성하기는 어렵지만 이어서 작성해보도록 하겠다.