// 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>