본문 바로가기

카테고리 없음

Spring 12. 채팅(채팅하기 및 채팅방 나가기)

 

// chatRoom.jsp

<div class="chatting-area">
    <div id="exit-area">
        <button class="btn btn-outline-danger" id="exit-btn" 
         onclick="exitChatRoom(chatRoomNo)">나가기</button>
    </div>

    <ul class="display-chatting">
        <c:forEach var="msg" items="${list}">
            <c:if test="${msg.userNo eq loginUser.userNo }"> //내가 작성한 글
                <li class="myChat">
                    <span class="chatDate">${msg.createDate}</span>
                    <p class="chat">${msg.message}</p>
                </li>
            </c:if>

            <c:if test="${msg.userNo ne loginUser.userNo}"> //상대방이 작성한 글
                <li>
                    <b>${msg.userName}</b>
                    <p class="chat">${msg.message}</p>
                    <span class="chatDate">${msg.createDate}</span>
                </li>
            </c:if>
        </c:forEach>
    </ul>

    <div class="input-area">
        <textarea id="inputChatting" rows="3"></textarea>
        <button id="send">보내기</button>
    </div>
</div>


<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script>
    // chat.js에서 사용하기 위한 전역변수 등록
    const userNo = '${loginUser.userNo}';
    const userName = '${loginUser.userName}';
    const chatRoomNo = '${chatRoomNo}';
    const contextPath = '${contextPath}';

    // 웹소켓객체 생성 (/chat이란 요청주소를 통해 웹소켓 객체를 생성)
    let chattingSocket = new SockJS(contextPath +"/chat");

    /* 
        WebSocket
        브라우저와 웹서버간의 통신을 지원하는 프로토콜

        * 전 이중 통신(Full Duplex) : 두 대의 단말기가 데이터를 송수신하기 위해
                                    각각 독립된 회선을 사용하는 방식 ex)전화기
        - HTML5부터 지원
        - JAVA7버전부터 지원(8이상부터 사용권장)
        - Spring Framework 4버전 이상부터 지원
    */
</script>
<script src="${contextPath}/resources/js/chat.js" >
</script>

<script>
    function exitChatRoom(chatRoomNo) { //get방식
        location.href='${contextPath}/chat/exitChatRoom/${chatRoomNo}'
    }
</script>

 

 

// chat.js

// 페이지 로딩 완료 후 => 채팅창을 맨 아래로 내리는 작업(ex.window, onload) 
// 즉시실행함수(IIFE, 속도 빠름, 변수명 중복문제 해결)
(function () {
    // 채팅창 요소 가져오기
    const display = document.querySelector(".display-chatting")

    // 채팅창 맨 아래로 내리기
    display.scrollTop = display.scrollHeight;
    // scrollTop : 스크롤의 위치
    // scrollHeight : 스크롤되는 요소(display)의 전체 높이
})();


// 채팅메세지 보내기 기능 만들기
document.getElementById("send").addEventListener("click", sendMessage);

// 채팅메세지 보내기 함수
function sendMessage() {

    // DB에 저장할 채팅메세지 정보 얻어오기
    const inputChatting = document.getElementById("inputChatting"); // textarea

    // 입력하지 않은 경우(공백제거시 길이가 0일때)
    if (inputChatting.value.trim().length === 0) {
        alert("입력해주세요");

        inputChatting.value = ""; // 입력창 비워주기
        inputChatting.focus(); // 입력창에 포커스 처리
    } else {
        // 한글자 이상 입력한 경우
        // 메세지 객체 생성한 후 반드시 JSON으로 변환 해야 한다.
        const chatMessame = {
            message: inputChatting.value,
            chatRoomNo,
            userNo,
            userName
        };
        // 데이터 전달시 js의 객체형태로는 전달 못함! (JAVA에서 JS 해독 못함)
        const jsonParsedMessage = JSON.stringify(chatMessame);
	
        chattingSocket.send(jsonParsedMessage);
        // send(값) : 웹소켓 핸들러로 값을 보내는 역할을 하는 함수
        // send로 전달된 데이터는 웹소켓 핸들러 내부의 handleTextMessage함수가 수신함
        
        inputChatting.value = "";
    }
}

// 서버쪽 웹소캣핸들러에서 클라이언트 소켓으로 메세지를 전달(sendMessage)하는
// 구문을 감지하는 이벤트 핸들러
chattingSocket.onmessage = function (e) {
    console.log(e.data);
    // 전달된 메세지는 e.data내부에 (JSON)형태로 보관되어 있다.

    // 전달받은 메세지를 JS객체로 변환해주기
    const chatMessage = JSON.parse(e.data);

    const li = document.createElement("li"); // <li></li>
    const p = document.createElement("p"); // <p></p>
    p.classList.add("chat");

    // p태그 내부에 글내용 추가 및 개행처리
    p.innerHTML = chatMessage.message.replace(/\\n/gm, "<br>"); // <p class="chat">전달받은 메세지</p>
    // gm : global multi

    const span = document.createElement("span");
    span.classList.add("chatDate"); // <span class="chatDate">??</span>
    span.innerText = currentTime(); // <span class="chatDate">2024-01-30</span>

    // 내가 보낸 채팅인지 상대방이 보낸 채팅인지 확인하기
    if (chatMessage.userNo == userNo) {
        // 내가 보낸 채팅글
        li.classList.add("myChat"); //내가 쓴 글에 해당하는 스타일 적용
        li.append(span, p);
    } else {
        // 상대방이 보낸 글에 해당하는 스타일 적용
        li.innerHTML = `<b>${chatMessage.userName}</b>`
        li.append(p, span);
    }

    // 채팅창 요소 가져오기
    const display = document.querySelector(".display-chatting")

    // 채팅창에 채팅내용 추가하기
    display.append(li);

    // 채팅창 맨 아래로 내리기
    display.scrollTop = display.scrollHeight;
    // scrollTop : 스크롤의 위치
    // scrollHeight : 스크롤되는 요소(display)의 전체 높이
}

// 현재시간 출력 함수
function currentTime() {

    const now = new Date();
    return `${now.getFullYear()}
    -${now.getMonth() + 1 < 10 ?
            "0" + (now.getMonth() + 1) : (now.getMonth() + 1)}
        -${now.getDate() < 10 ? "0" + now.getDate() : now.getDate()}`;
}

 

@Slf4j
@Controller
@RequestMapping("/chat")
@SessionAttributes({"loginUser", "chatRoomNo"})
public class ChatController {
	
	@Autowired
	private ChatService chatService;
    
	// 채팅방 입장 -> 게시글 상세보기
	@GetMapping("/room/{chatRoomNo}")
	public String joinChatRoom(
			@PathVariable("chatRoomNo") int chatRoomNo,
			Model model,
			RedirectAttributes ra,
			ChatRoomJoin join,
			@ModelAttribute("loginUser") Member loginUser
			) {
		
		// CHAT_ROOM_JOIN안에 참여한 채팅방번호(chatRoomNo)와 
		// 참여한 회원번호(userNo)를 담아서 INSERT(참여인원수 증가할 예정)
		join.setChatRoomNo(chatRoomNo);
		join.setUserNo(loginUser.getUserNo());
		
		// 채팅내용 조회후 model에 담아줄 예정
		List<ChatMessage> list = chatService.joinChatRoom(join); 
		//채팅방 참여 후 해당 채팅방의 채팅메세지 조회(select)
		
		log.info("채팅내용 {}", list);
		
		if(list != null) {
			model.addAttribute("list", list);
			model.addAttribute("chatRoomNo", chatRoomNo); //session으로 이관
			return "chat/chatRoom";
		}else {
			ra.addFlashAttribute("alertMsg", "채팅방이 존재하지 않습니다!");
			return "redirect:/chat/chatRoomList";
		}
	}
	
	
	// 채팅방 나가기
	@GetMapping("/exitChatRoom/{chatRoomNo}")
	public String exitChatRoom(
			@PathVariable("chatRoomNo") int chatRoomNo,
			@ModelAttribute("loginUser") Member loginUser,
			ChatRoomJoin crj,
			RedirectAttributes ra
			) {
		
		crj.setChatRoomNo(chatRoomNo);
		crj.setUserNo(loginUser.getUserNo());
	
		int result = chatService.exitChatRoom(crj);
		if(result == 0) {
			ra.addFlashAttribute("alertMsg", "오류가 발생했습니다. 다시 시도해주세요");
			return "redirect:/";
		}else {
			ra.addFlashAttribute("alertMsg", "채팅방을 나갑니다.");
			return "redirect:/chat/chatRoomList/";
		}
	}
}

 

// ChatService.java 생략..

@Slf4j
@Service
public class ChatServiceImpl implements ChatService{
	
	@Autowired
	private ChatDao chatDao;

	// 채팅방에 참여하기
	@Override
	public List<ChatMessage> joinChatRoom(ChatRoomJoin join) {
		
		List<ChatMessage> list = null;
		
		int result = 1;
		try {
			// 참여 버튼 클릭시 CHAT_ROOM_JOIN테이블에 INSERT 해주기
			result = chatDao.joinChatRoom(join);
		}catch (Exception e) {
			// 에러발생시 실행할 코드
			log.info("result {}", result);
		}
		
		// 채팅메세지 조회후 화면에 보여주기
		if(result > 0) { // INSERT에 성공했다면 채팅내용 보여주기
			list = chatDao.selectChatMessage(join.getChatRoomNo());
		}
		return list;
	}

	// 채팅방에서 채팅내용 INSERT
	@Override
	public int insertMessage(ChatMessage chatMessage) {
		
		chatMessage.setMessage(Utils.XSSHandling(chatMessage.getMessage()));
		chatMessage.setMessage(Utils.newLineHandling(chatMessage.getMessage()));
		// 크로스스크립트 방지처리해줌
		return chatDao.insertMessage(chatMessage);
	}
    
	// 채팅방 나가기
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int exitChatRoom(ChatRoomJoin crj) {
		
		// 1) 채팅방 나가기 -> CHAT_ROOM_JOIN테이블에서 DELETE문 수행
		int result = chatDao.exitChatRoom(crj);
		
		// 2) 채팅방 인원이 0명인 경우 채팅방 삭제하기
		if(result > 0) {
			// 채팅방에 인원이 몇 명인지 확인
			int cnt = chatDao.countChatRoomMember(crj);
			
			// 내가 마지막으로 나간 경우 -> CHAT_ROOM테이블에서 status값을 변경 UPDATE
			if(cnt == 0) {
				result = chatDao.closeChatRoom(crj);
			}
		}
		return result;
	}

 

// chat-mapper.xml

<!-- 채팅내역 보여주기 -->
<select id="selectChatMessage" parameterType="int" resultType="chatMessage">
    SELECT MESSAGE, 
           TO_CHAR(CREATE_DATE, 'YYYY-MM-DD'), 
           USER_NAME, 
           USER_NO
      FROM CHAT_MESSAGE
      JOIN MEMBER USING (USER_NO)
     WHERE CHAT_ROOM_NO = #{chatRoomNo}
     ORDER BY CM_NO
</select>

<!-- 채팅 메세지 보내기 -->
<insert id="insertMessage" parameterType="chatMessage">
    INSERT INTO CHAT_MESSAGE
    VALUES (SEQ_CM_NO.NEXTVAL, #{message}, SYSDATE, #{chatRoomNo}, #{userNo} )
</insert>

<!-- 채팅방에 인원이 몇명인지 체크 -->
<select id="countChatRoomMember" parameterType="chatRoomJoin" resultType="int">
    SELECT COUNT(*)
      FROM CHAT_ROOM_JOIN
     WHERE CHAT_ROOM_NO = #{chatRoomNo}
</select>

<!-- 채팅방 나가기 -->
<update id="closeChatRoom" parameterType="chatRoom">
    UPDATE CHAT_ROOM
       SET STATUS = 'N'
     WHERE CHAT_ROOM_NO = #{chatRoomNo}
</update>