고래씌

[Spring] 10-1. 채팅방 목록, 채팅방 생성 본문

Server/Spring

[Spring] 10-1. 채팅방 목록, 채팅방 생성

고래씌 2024. 1. 29. 12:54

1. 채팅방 서비스 하기 위한 세팅

 
- 메이븐 레파지토리에서 복사

 
=> 복붙 후 일단 버전은 지워주고 ${org.springframework-version} 작성(pom.xml 맨 위에 가면 properties 에서 키값 복사)
이것은 웹소켓의 자료파일 묶음임!
 
 
▶ pom.xml

 

		<!-- 웹소켓 의존성 -->
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-websocket -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-websocket</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>

 
 
▶ servlet-context.xml

	<!-- ==============웹소켓 시작 ====================== -->
	<!-- 웹소켓 요청시 해당 요청내용들을 처리해줄 클래스. (메세지 전송, 커넥션 연결요청, 해제 -->
	<beans:bean id="chatHandler" class="com.kh.spring.chat.model.websocket.ChatWebsocket"></beans:bean>
	
	<websocket:handlers>
		
		<!-- 웹 소켓 요청 주소를 처리할 bean객체 -->
		<websocket:mapping handler="chatHandler" path="/chat"/>	
		<!-- 
			http통신에서 request와 response를 가로챈 후 httpSession정보를 webSocketSession에 넣어준다.
		 -->
		<websocket:handshake-interceptors>
			<beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"></beans:bean>
		</websocket:handshake-interceptors>
		<!-- SockJs라이브러리를 이용해서 만들어진 웹소켓 객체임을 의미한다.* -->
		<websocket:sockjs></websocket:sockjs>
	
	
	</websocket:handlers>

	<!-- ==============웹소켓 끝 ====================== -->

 
 
 
 
=> Chat관련 클래스 추가

 
 
 
▶ ChatWebsocket.java

1) 동기화 처리

- HashSet 같은 경우 동기화가 안되어있음 => 그래서 synchronizedSet 를 이용하면 동기화된 set 객체를 반환할 수 있음.
- synchronizedSet : 동기화된 set을 반환해주는 메소드. 멀티스레드에서 하나의 컬렉션요소에 스레드가 동시에 접근하게 되면 충돌이 발생할 수 있으므로 동기화 처리를 진행함

 

private Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<WebSocketSession>());

 
 
 

2) 클라이언트와 웹소켓 연결을 위한 메소드 3개 추가

 
TextWebSocketHandler : 웹소켓을 위한 메소드를 지원하는 인터페이스, 웹소켓 핸들러 인터페이스를 구현한 클래스이며 문자열(text)과 관련된 기능을 다룰 때 사용
 
- alt + shift + s 누른 후, Override/Implement Method 클릭후, 아래 3개 선택하여 추가

 

	/* 클라이언트와 웹소켓 연결이 완료된 이후, 통신할 준비가 되면 실행되는 함수 */
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		
		// 매개변수로 전달받은 WebSocketSession : 웹소켓에 접속요청을 한 "클라이언트"의 세션
		log.info("session ?? {}" + session.getId());  // 세션 id 확인
		
		sessions.add(session);
	}
	

	/* 클라이언트로부터 메시지(message)가 도착했을 시 실행되는 함수 */
	@Override
	public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
		super.handleMessage(session, message);
	}

	
	/* 클라이언트와 웹소켓 연결이 종료되면 실행되는 함수 */
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		sessions.remove(session); // 웹소켓 연결이 종료되는 경우 sessions내부에 있는 클라이언트의 session정보를 삭제할 예정
	}

 
 
 

3) views/chat 폴더 아래에 chatRoom.jsp 파일 생성 후, css 파일 넣음

 
 
▶ chatRoomList.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="${contextPath }/resources/css/chat-style.css" rel="stylesheet">
<link href="${contextPath }/resources/css/main-style.css" rel="stylesheet">
<style>
	.content{
		height : 100%;
	}
</style>
</head>
<body>
	<jsp:include page="/WEB-INF/views/common/header.jsp" />
	
	<div class="content">
		<br><br>
		<div class="innerOuter" style="padding: 5% 10%; width: 100%;">
			<h2>채팅방목록</h2>
            <br>
            <br>
            <table class="table table-hover" align="center">
                <thead>
                    <tr>
                        <th>방번호</th>
                        <th>채팅방 제목(주제)</th>
                        <th>개설자</th>
                        <th>참여인수</th>
                    </tr>
                </thead>
                <tbody>
                <c:choose>
                	<c:when test="${empty list}">
                		<tr>
                			<td colspan="4">존재하는 채팅방이 없습니다.</td>
                		</tr>
                	</c:when>
                	<c:otherwise>
                		<c:forEach items="${list}" var="chatRoom">
                			<tr>
                				<td>${chatRoom.chatRoomNo}</td>
                				<td>
                					${chatRoom.title}
                					<c:if test='${!empty loginUser}'>
                						<button class="btn btn-primary"
                						onclick="location.href='${contextPath}/chat/room/${chatRoom.chatRoomNo}'">참여</button>
                					</c:if>
                				</td>
                				<td>${chatRoom.userName}</td>
                				<td>${chatRoom.cnt}</td>
                			</tr>
                		</c:forEach>
                	</c:otherwise>
                </c:choose>
                </tbody>
            </table>
            
            <!-- 로그인이 되어있는 경우 보이는 버튼 -->
            <c:if test="${!empty loginUser }">
            	<div class='btn-area'>
            		<button data-toggle="modal" data-target="#chatModal"
            		class="btn btn-danger">채팅방 만들기</button>
            	</div>
            </c:if>
		</div>
	</div>
	
	
	<div class="modal fade" id="chatModal">
        <div class="modal-dialog modal-sm">
            <div class="modal-content">
                <!-- 모달 해더 -->
                <div class="modal-header">
                    <h4 class="modal-title">채팅방 만들기</h4>
                    <button type="button" class="close" data-dismiss="modal">&times;</button>
                </div>
                <form action="${contextPath}/chat/openChatRoom"
                    method="post">
                    <!--  모달 바디 -->
                    <div class="modal-body">
                        <label for="title" class="mr-sm-2">제목</label> <input type="text"
                            class="form-controll mb-2 mr-sm-2" placeholder="채팅방 제목"
                            id="chatRoomTitle" name="title">
                    </div>
                    <!-- 모달 푸터 -->
                    <div class="modal-footer">
                        <button type="submit" id="open-form" class="btn btn-primary">만들기</button>
                        <button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
	
	
	<jsp:include page="/WEB-INF/views/common/footer.jsp" />


</body>
</html>

 
 

4) vo 클래스 추가

▶ ChatRoom.java

package com.kh.spring.chat.model.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class ChatRoom {
	
	private int chatRoomNo;
	private String title;
	private String status;
	private int userNo;
	
//	사용자의 이름과 참여한 수도 표시하기 위해 추가해야함(DB에는 없지만..)
	private String userName;
	private int cnt;

}

 
 
▶ ChatRoomJoin.java

package com.kh.spring.chat.model.vo;

import lombok.Data;

@Data
public class ChatRoomJoin {
	
	private int userNo;
	private int chatRoomNo;

}

 
 
▶ ChatMessage.java

package com.kh.spring.chat.model.vo;

import lombok.Data;

@Data
public class ChatMessage {
	
	private int cmNo;
	private String message;
	private String createDate;
	private int chatRoomNo;
	private int userNo;
	
	// DB상에서는 없는 데이터이지만 채팅할때 이름을 가져오기 위해 추가
	private String userName;

}

 
 

2. 채팅방 목록 조회 / 채팅방 생성

▶ ChatController.java

package com.kh.spring.chat.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.kh.spring.chat.model.service.ChatService;
import com.kh.spring.chat.model.vo.ChatRoom;
import com.kh.spring.member.model.vo.Member;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/chat")
@SessionAttributes({"loginUser"})
public class ChatController {
	
	@Autowired
	private ChatService chatService;
	
//	채팅방 목록
	@GetMapping("chatRoomList")  // "/chat/chatRoomList"
	public String selectChatRoomList(Model model) {
		
		// 1) DB에서 채팅방 목록데이터 조회
		List<ChatRoom> list = chatService.selectChatRoomList();
		
		// 2) 조회된 데이터를 model안에 추가
		model.addAttribute("list", list);		
		
		// 3) view페이지 포워딩
		return "chat/chatRoomList";
	}
	
//	채팅방 생성
	@PostMapping("/openChatRoom")
	public String openChatRoom(
			ChatRoom room,
			RedirectAttributes ra,
			@ModelAttribute("loginUser") Member loginUser  
			// 모델안(세션스콥데이터)에 저장되어있는 값중에 loginUser를 꺼내온다!
			// ModelAttribute는 SessionAttributes와 함께 써야한다
			) {
		
		room.setUserNo(loginUser.getUserNo());
		
		int chatRoomNo = chatService.openChatRoom(room);  // 채팅방(chatRoom) 생성 및 생성된 채팅방 내부로 이동.
		
		String path = "redirect:/chat/";
		
		if(chatRoomNo > 0) {
			ra.addFlashAttribute("alertMsg", "채팅방생성 성공");
//			path += "room/" + chatRoomNo;
			path += "chatRoomList";
		}else {
			ra.addFlashAttribute("alertMsg", "채팅방생성 실패");
			path += "chatRoomList"; // 목록페이지 그대로
		}
		
		return path;
	}
	
}

 
 
▶ ChatService.java

package com.kh.spring.chat.model.service;

import java.util.List;

import com.kh.spring.chat.model.vo.ChatRoom;

public interface ChatService{

//	채팅방 목록
	List<ChatRoom> selectChatRoomList();

//	채팅방 생성
	int openChatRoom(ChatRoom room);

}

 
 
▶ ChatServiceImpl.java
@Service 어노테이션 추가

package com.kh.spring.chat.model.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.kh.spring.chat.model.dao.ChatDao;
import com.kh.spring.chat.model.vo.ChatRoom;

@Service
public class ChatServiceImpl implements ChatService{

	@Autowired
	private ChatDao dao;
	
//	채팅방 목록
	@Override
	public List<ChatRoom> selectChatRoomList() {
		return dao.selectChatRoomList();
	}

//	채팅방 생성
	@Override
	public int openChatRoom(ChatRoom room) {
		return dao.openChatRoom(room);
	}

}

 
 
▶ ChatDao.java
@Repository 어노테이션 추가

package com.kh.spring.chat.model.dao;

import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.kh.spring.chat.model.vo.ChatRoom;

@Repository
public class ChatDao {

	@Autowired
	private SqlSessionTemplate sqlSession;
	
//	채팅방 목록
	public List<ChatRoom> selectChatRoomList() {
		return sqlSession.selectList("chatMapper.selectChatRoomList");
	}

//	채팅방 생성
	public int openChatRoom(ChatRoom room) {
		
		int result = sqlSession.insert("chatMapper.openChatRoom", room);
		
		if(result > 0) {
			result = room.getChatRoomNo();    // 추가된 pk값
		}
		
		return result;
	}

}

 
 
▶ chat-mapper.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="chatMapper">

	<!-- 채팅방 목록 -->
	<select id="selectChatRoomList" resultType="chatRoom">
		  SELECT
		    CHAT_ROOM_NO ,
		    TITLE,
		    USER_NAME,
		    (SELECT COUNT(*) FROM CHAT_ROOM_JOIN CRJ WHERE CRJ.CHAT_ROOM_NO = CR.CHAT_ROOM_NO) AS CNT
		FROM CHAT_ROOM CR
		JOIN MEMBER USING(USER_NO)
		WHERE CR.STATUS = 'Y'
		ORDER BY CHAT_ROOM_NO DESC
	</select>
	
	<!-- 채팅방 생성 -->
	<insert id="openChatRoom" parameterType="chatRoom" useGeneratedKeys="true">
		
		INSERT INTO CHAT_ROOM VALUES
		(SEQ_CR_NO.NEXTVAL, #{title}, DEFAULT, #{userNo})
		
		<selectKey keyProperty="chatRoomNo" resultType="int" order="AFTER">  <!-- AFTER : 메인쿼리문이 이후에 실행되도록 조절 -->
			<!-- BEFORE이면 SEQ_CR_NO.NEXTVAL 이지만 AFTER라서 CURRVAL을 사용 -->
			<!-- CURRVAL : 마지막으로 호출된 NEXTVAL을 가져오는 예약어 -->
			SELECT SEQ_CR_NO.CURRVAL FROM DUAL
		</selectKey>
	</insert>
	
</mapper>

 
 
▶ 결과