WebSocket, SockJs, STOMP를 이용한 웹 채팅 구현 (3) - 기타

메밀·2022년 11월 14일
0

6. 기타 트러블 슈팅

1) ModelAndView

RestChatController

@GetMapping("")
public ModelAndView chat(ModelAndView mv, HttpSession session, Map<String, Object> paramMap) {
  paramMap.put("workNo", (int)session.getAttribute("workNo"));
  paramMap.put("workMemberEmail", (String)session.getAttribute("login"));

  Map<String, Object> map = chatService.getChatListByWorkNo(paramMap);

  // ModelAndView 세팅 
  mv.setViewName("chat/chat");
  mv.addObject("chatRoomList", map.get("chatRoomList"));
  mv.addObject("workspaceMemberList", map.get("workspaceMemberList"));

  log.debug(TeamColor.CSK + "workspaceMemberList: " + map.get("workspaceMemberList"));

  return mv;
}

(채팅이 아닌) 다른 파트를 구현할 때엔 동기와 비동기가 섞여 있기도 했고, 일부분만을 ajax로 처리한 탓에 view는 @Controller에서, 기타 json을 리턴받아야 할 경우에만 @RestController의 메소드를 사용했다. 그러나 채팅을 구현할 땐 (우리 조의 템플릿 특성상) 최초로 채팅 메인 페이지를 띄울 때를 제외하면 view의 이동이 발생하지 않는다.

이에 @RestController에서 DB에서 가져온 데이터와 view 이동을 동시에 처리하기 위해 ModelAndView를 리턴하는 메소드를 작성하였다. ModelAndView를 써보는 것은 처음이었는데, 주요 메소드는 다음과 같다.

메소드역할
setViewName(String view)응답할 view이름 설정
addObject(String name, Object value)view에 전달할 값을 설정
addAllObject(Map values)view에 전달할 값을 Map에 name-value로 저장하여 한번에 설정


2) 기존 채팅방 목록 쿼리

SELECT
	cr.chat_room_no chatRoomNo
	, work_member_name chatRoomName
	, filename
	, file_ext fileExt
	, (SELECT
			chat_msg
		FROM
			chat
		WHERE
			chat_room_no = chatRoomNo
		AND
			chat_msg_no
		IN
			(
			SELECT
				MAX(chat_msg_no)
			FROM
				chat
			GROUP BY
				chat_room_no
			)
	) lastMsg
FROM
	chat_room cr
INNER JOIN
	chat_member cm
ON
	cr.chat_room_no = cm.chat_room_no
INNER JOIN
	(SELECT
		cr.chat_room_no 
		, work_member_name
		, filename
		, file_ext
	FROM 
		chat_member cm
	INNER JOIN
		v_workspace_member v
	ON
		cm.chat_member_email = v.work_member_email
	INNER JOIN
		chat_room cr
	ON
		cr.chat_room_no = cm.chat_room_no
	WHERE
		v.work_no = #{workNo}
	AND
		chat_member_email != #{workMemberEmail}) crn
ON
	crn.chat_room_no = cr.chat_room_no
WHERE
	cm.chat_member_email = #{workMemberEmail}
AND
	cr.work_no = #{workNo};

Controller에서 넘겨받는 데이터는 해당 워크스페이스 번호와 본인의 email이다. 이것을 사용해 다음 데이터를 구해야 한다.

  1. chatRoomNo: 채팅방 번호
  2. chatRoomName: 1대1 채팅만을 구현하였으므로 간단히 상대방 이름을 채팅방 이름으로 구해온다.
  3. filename: 상대방 프로필 이미지의 파일명
  4. file_ext: 상대방 프로필 이미지의 확장자
  5. lastMsg: 마지막 메시지

기본적으로 chat_room, chat_member를 조인하여 본인이 속한 채팅방 목록의 chatRoomNo를 구한다. 나는 1:1 채팅만을 구현했으므로, 같은 채팅방의 내가 아닌 사람의 정보를 구하는 쿼리를 INNER JOIN하여 채팅방 이름(상대의 이름), 상대방 프로필 이미지를 구한다.

마지막 메시지는 내가 보낸 것일 수도 있고 상대의 것일 수도 있다. 간단히 스칼라 쿼리를 이용해 해당 채팅방에서 chat_msg_no(PK, auto_increment)가 가장 높은 메시지 내용을 구했다.

괜찮은 쿼리인지는 모르겠다. 1:1 채팅방만을 구현했기 때문에 가능한 SQL문이라는 점을 염두에 두어야 한다.



3) 상대와의 채팅방이 이미 존재하는지 확인하기

채팅 사이드바를 살펴보자. CHATS 리스트 아래로는 이미 존재하는 채팅방이, CONTACTS 리스트 아래로는 해당 워크스페이스에 속한 멤버 리스트가 보인다. 나는 1) 채팅방 중복 생성을 막고 싶었고, 2) CONTACTS의 멤버 정보를 클릭하면 기존 채팅방이 리턴되도록 하고 싶었다.

ChatService

	@Override
	@Transactional
	public Map<String, Object> addChatRoom(Map<String, Object> paramMap) {
	    // 기존 채팅방이 존재하는지 확인
        Integer chatRoomNo = chatMemberMapper.selectChatRoomNoByWorkMemberEmail(paramMap);

        // 기존 채팅방이 존재하면
        if(chatRoomNo != null) {
            paramMap.put("chatRoomNo", chatRoomNo);
            paramMap.put("insert", "");
            return paramMap;
        }

		// 존재하지 않으면 chatRoom과 chatMember를 INSERT
        // 코드는 생략
	}

이를 위해 작성한 SQL문은 다음과 같다.

// chatMemberMapper.selectChatRoomNoByWorkMemberEmail(paramMap)

SELECT
	cm.chat_room_no chatRoomNo
FROM
	chat_member cm
INNER JOIN
	chat_member cm2
ON
	cm.chat_room_no = cm2.chat_room_no
INNER JOIN
	chat_room cr
ON
	cm.chat_room_no = cr.chat_room_no
WHERE
	cm.chat_member_email = #{login}
AND
	cm2.chat_member_email = #{workMemberEmail}
AND
	cr.work_no = #{workNo};

chat_member 테이블을 두 번 INNER JOIN하여 login 세션에 담긴 email 값(자기 자신의 email)과 클릭한 멤버의 email로 이루어진 채팅방의 번호를 구했다.

이후 javascript를 사용해 이미 존재하는 채팅방을 CHATS 리스트에서 찾아 클릭하는 코드를 추가했다.

// selector뒤에 빈칸을 넣고 selector를 쓰면, 하위를 찾겠다는 말
// chat-list에서 해당 채팅방을 찾은 다음 클릭 trigger
$('#chat-list [value="' + json.chatRoomNo + '"]').trigger('click');

기존 채팅방 정보가 없어서 INSERT한 경우에도 동일하다. 새로 만든 채팅방을 CHATS 리스트 가장 상단에 append한 후 $('#chat-list li').first().trigger('click');을 통해 클릭을 발생시킨다.




7. 나가며

1:1 채팅만을, 그것도 가장 기본적인 기능 이외의 것은 전혀 구현하지 못했다. 채팅방 수정, 삭제 기능과 멀티채팅을 시도하지 못한 것이 아쉽지만 시간 관계 상 웹 메신저 구현은 이쯤에서 마무리짓도록 한다.

0개의 댓글