728x90
이전시간까지 채팅에 필요한 화면 및 이론을 알아봤다.
이제 본격적으로 알아보자!
이론 및 설정 하는 방법은 아래 포스팅을 먼저 보고 오길 바란다.
[ Spring ] 채팅 구현하기 - 이론편 ①
💬 구현 하기 전에 오늘 알아 할 내용 JSON / SET 💬 파이널 프로젝트에서 채팅구현을 하기로 해서 찾아보고 있던 중 수업시간에 운 좋게 배우게 되었다. 우선 채팅 구현을 위해서는 웹소켓이라는
jnaa.tistory.com
[ Spring ] 채팅 구현하기 - 실전편 ②
이전 시간에 채팅 구현하기 앞서 어떻게 세팅해야할지 어떤 식으로 돌아가는지 콘솔을 통해 알아보았다. 오늘은 직접 채팅을 구현해보자! 🔮 출력 화면 미리보기 체팅 기능을 실행하기 위한 DB
jnaa.tistory.com
💻 코드로만 보기
📚 VS Code
📕 chatting.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 lang=ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>채팅방</title>
<link rel="stylesheet" href="/resources/css/main-style.css">
<link rel="stylesheet" href="/resources/css/board/boardDetail-style.css">
<link rel="stylesheet" href="/resources/css/chatting/chatting-style.css">
<script src="https://kit.fontawesome.com/a2e8ca0ae3.js"
crossorigin="anonymous"></script>
</head>
<body>
<main>
<jsp:include page="../common/header.jsp"></jsp:include>
<button id="addTarget">추가</button>
<div id="addTargetPopupLayer" class="popup-layer-close">
<span id="closeBtn">×</span>
<div class="target-input-area">
<input type="search" id="targetInput"
placeholder="닉네임 또는 이메일을 입력하세요" autocomplete="off">
</div>
<ul id="resultArea">
<%-- <li class="result-row" data-id="1">
<img class="result-row-img" src="/resources/images/user.png">
<span> <mark>유저</mark>일</span>
</li>
<li class="result-row" data-id="2">
<img class="result-row-img" src="/resources/images/user.png">
<span><mark>유저</mark>이</span>
</li>
<li class="result-row">일치하는 회원이 없습니다</li> --%>
</ul>
</div>
<div class="chatting-area">
<ul class="chatting-list">
<c:forEach var="room" items="${roomList}">
<li class="chatting-item" chat-no="${room.chattingNo}"
target-no="${room.targetNo}">
<div class="item-header">
<c:if test="${not empty room.targetProfile}">
<img class="list-profile" src="${room.targetProfile}">
</c:if>
<c:if test="${empty room.targetProfile}">
<img class="list-profile" src="/resources/images/user.png">
</c:if>
</div>
<div class="item-body">
<p>
<span class="target-name">${room.targetNickName}</span>
<span class="recent-send-time">${room.sendTime}</span>
</p>
<div>
<p class="recent-message">${room.lastMessage}</p>
<c:if test="${room.notReadCount > 0}">
<p class="not-read-count">${room.notReadCount}</p>
</c:if>
</div>
</div>
</li>
</c:forEach>
</ul>
<div class="chatting-content">
<ul class="display-chatting">
<%-- <li class="my-chat">
<span class="chatDate">14:01</span>
<p class="chat">가나다라마바사</p>
</li>
<li class="target-chat">
<img src="/resources/images/user.png">
<div>
<b>이번유저</b> <br>
<p class="chat">
안녕하세요?? 반갑습니다.<br>
ㅎㅎㅎㅎㅎ
</p>
<span class="chatDate">14:05</span>
</div>
</li> --%>
</ul>
<div class="input-area">
<textarea id="inputChatting" rows="3"></textarea>
<button id="send">보내기</button>
</div>
</div>
</div>
</main>
<jsp:include page="../common/footer.jsp"></jsp:include>
<!---------- sockjs를 이용한 WebSocket 구현을 위해 라이브러리 추가 ---------->
<!-- https://github.com/sockjs/sockjs-client -->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script>
// 로그인한 회원 번호
const loginMemberNo = "${loginMember.memberNo}";
</script>
<script src="/resources/js/chatting//chatting.js"></script>
</body>
</html>
📕 chatting.js
const addTarget = document.querySelector("#addTarget"); // 추가 버튼
const addTargetPopupLayer = document.querySelector("#addTargetPopupLayer"); // 팝업 레이어
const closeBtn = document.querySelector("#closeBtn"); // 닫기 버튼
const targetInput = document.querySelector("#targetInput"); // 사용자 검색
const resultArea = document.querySelector("#resultArea"); // 검색 결과
let selectChattingNo; // 선택한 채팅방 번호
let selectTargetNo; // 현재 채팅 대상
let selectTargetName; // 대상의 이름
let selectTargetProfile; // 대상의 프로필
// 검색 팝업 레이어 열기
addTarget.addEventListener("click", e => {
addTargetPopupLayer.classList.toggle("popup-layer-close");
targetInput.focus();
});
// 검색 팝업 레이어 닫기
closeBtn.addEventListener("click", e => {
addTargetPopupLayer.classList.toggle("popup-layer-close");
resultArea.innerHTML = "";
});
// 사용자 검색(ajax)
targetInput.addEventListener("input", e => {
const query = e.target.value.trim();
// 입력된게 없을 때
if(query.length == 0){
resultArea.innerHTML = ""; // 이전 검색 결과 비우기
return;
}
// 입력된게 있을 때
if(query.length > 0){
fetch("/chatting/selectTarget?query="+query)
.then(resp => resp.json())
.then(list => {
//console.log(list);
resultArea.innerHTML = ""; // 이전 검색 결과 비우기
if(list.length == 0){
const li = document.createElement("li");
li.classList.add("result-row");
li.innerText = "일치하는 회원이 없습니다";
resultArea.append(li);
}
for(let member of list){
// li요소 생성(한 행을 감싸는 요소)
const li = document.createElement("li");
li.classList.add("result-row");
li.setAttribute("data-id", member.memberNo);
// 프로필 이미지 요소
const img = document.createElement("img");
img.classList.add("result-row-img");
// 프로필 이미지 여부에 따른 src 속성 선택
if(member.profileImage == null)
img.setAttribute("src", "/resources/images/user.png");
else
img.setAttribute("src", member.profileImage);
let nickname = member.memberNickname;
let email = member.memberEmail;
const span = document.createElement("span");
span.innerHTML = `${nickname} ${email}`.replace(query, `<mark>${query}</mark>`);
// 요소 조립(화면에 추가)
li.append(img, span);
resultArea.append(li);
// li요소에 클릭 시 채팅방에 입장하는 이벤트 추가
li.addEventListener('click', chattingEnter);
}
})
.catch(err => console.log(err) );
}
});
// 채팅방 입장 또는 선택 함수
function chattingEnter(e){
console.log(e.target); // 실제 클릭된 요소
console.log(e.currentTarget); // 이벤트 리스트가 설정된 요소
const targetNo = e.currentTarget.getAttribute("data-id");
fetch("/chatting/enter?targetNo="+targetNo)
.then(resp => resp.text())
.then(chattingNo => {
console.log(chattingNo);
selectRoomList(); // 채팅방 목록 조회
setTimeout(()=>{
// 만약 채팅방 목록 중 이미 존재하는 채팅방이 있으면 클릭해서 입장
const itemList = document.querySelectorAll(".chatting-item")
for(let item of itemList) {
if(item.getAttribute("chat-no") == chattingNo){
item.focus();
item.click();
addTargetPopupLayer.classList.toggle("popup-layer-close");
targetInput.value = "";
resultArea.innerHTML = "";
return;
}
}
}, 200);
/* 02초뒤에 출력하는 이유 */
})
.catch(err => console.log(err));
}
// 비동기로 채팅방 목록 조회
function selectRoomList(){
fetch("/chatting/roomList")
.then(resp => resp.json())
.then(roomList => {
console.log(roomList);
// 채팅방 목록 출력 영역 선택
const chattingList = document.querySelector(".chatting-list");
// 채팅방 목록 지우기
chattingList.innerHTML = "";
// 조회한 채팅방 목록을 화면에 추가
for(let room of roomList){
const li = document.createElement("li");
li.classList.add("chatting-item");
li.setAttribute("chat-no", room.chattingNo);
li.setAttribute("target-no", room.targetNo);
if(room.chattingNo == selectChattingNo){
li.classList.add("select");
}
// item-header 부분
const itemHeader = document.createElement("div");
itemHeader.classList.add("item-header");
const listProfile = document.createElement("img");
listProfile.classList.add("list-profile");
if(room.targetProfile == undefined)
listProfile.setAttribute("src", "/resources/images/user.png");
else
listProfile.setAttribute("src", room.targetProfile);
itemHeader.append(listProfile);
// item-body 부분
const itemBody = document.createElement("div");
itemBody.classList.add("item-body");
const p = document.createElement("p");
const targetName = document.createElement("span");
targetName.classList.add("target-name");
targetName.innerText = room.targetNickName;
const recentSendTime = document.createElement("span");
recentSendTime.classList.add("recent-send-time");
recentSendTime.innerText = room.sendTime;
p.append(targetName, recentSendTime);
const div = document.createElement("div");
const recentMessage = document.createElement("p");
recentMessage.classList.add("recent-message");
if(room.lastMessage != undefined){
recentMessage.innerHTML = room.lastMessage;
}
div.append(recentMessage);
itemBody.append(p,div);
// 현재 채팅방을 보고있는게 아니고 읽지 않은 개수가 0개 이상인 경우
-> 읽지 않은 메세지 개수 출력
if(room.notReadCount > 0 && room.chattingNo != selectChattingNo ){
// if(room.chattingNo != selectChattingNo ){
const notReadCount = document.createElement("p");
notReadCount.classList.add("not-read-count");
notReadCount.innerText = room.notReadCount;
div.append(notReadCount);
}else{
// 현재 채팅방을 보고있는 경우
// 비동기로 해당 채팅방 글을 읽음으로 표시
fetch("/chatting/updateReadFlag",{
method : "PUT",
headers : {"Content-Type": "application/json"},
body : JSON.stringify({"chattingNo" : selectChattingNo,
"memberNo" : loginMemberNo})
})
.then(resp => resp.text())
.then(result => console.log(result))
.catch(err => console.log(err));
}
li.append(itemHeader, itemBody);
chattingList.append(li);
}
roomListAddEvent();
})
.catch(err => console.log(err));
/*$.ajax({
url: "/chatting/roomList",
data : {"memberNo" : loginMemberNo},
dataType : "JSON",
success : roomList => {
}
})*/
}
// 채팅 메세지 영역
const display = document.getElementsByClassName("display-chatting")[0];
// 채팅방 목록에 이벤트를 추가하는 함수
function roomListAddEvent(){
const chattingItemList = document.getElementsByClassName("chatting-item");
for(let item of chattingItemList){
item.addEventListener("click", e => {
// 클릭한 채팅방의 번호 얻어오기
//const id = item.getAttribute("id");
//const arr = id.split("-");
// 전역변수에 채팅방 번호, 상대 번호, 상태 프로필, 상대 이름 저장
selectChattingNo = item.getAttribute("chat-no");
selectTargetNo = item.getAttribute("target-no");
selectTargetProfile = item.children[0].children[0].getAttribute("src");
selectTargetName = item.children[1].children[0].children[0].innerText;
if(item.children[1].children[1].children[1] != undefined){
item.children[1].children[1].children[1].remove();
}
// 모든 채팅방에서 select 클래스를 제거
for(let it of chattingItemList) it.classList.remove("select")
// 현재 클릭한 채팅방에 select 클래스 추가
item.classList.add("select");
// 비동기로 메세지 목록을 조회하는 함수 호출
selectChattingFn();
});
}
}
// 비동기로 메세지 목록을 조회하는 함수
function selectChattingFn() {
fetch("/chatting/selectMessage?"
+`chattingNo=${selectChattingNo}&memberNo=${loginMemberNo}`)
.then(resp => resp.json())
.then(messageList => {
console.log(messageList);
// <ul class="display-chatting">
const ul = document.querySelector(".display-chatting");
ul.innerHTML = ""; // 이전 내용 지우기
// 메세지 만들어서 출력하기
for(let msg of messageList){
//<li>, <li class="my-chat">
const li = document.createElement("li");
// 보낸 시간
const span = document.createElement("span");
span.classList.add("chatDate");
span.innerText = msg.sendTime;
// 메세지 내용
const p = document.createElement("p");
p.classList.add("chat");
p.innerHTML = msg.messageContent; // br태그 해석을 위해 innerHTML
// 내가 작성한 메세지인 경우
if(loginMemberNo == msg.senderNo){
li.classList.add("my-chat");
li.append(span, p);
}else{ // 상대가 작성한 메세지인 경우
li.classList.add("target-chat");
// 상대 프로필
// <img src="/resources/images/user.png">
const img = document.createElement("img");
img.setAttribute("src", selectTargetProfile);
const div = document.createElement("div");
// 상대 이름
const b = document.createElement("b");
b.innerText = selectTargetName; // 전역변수
const br = document.createElement("br");
div.append(b, br, p, span);
li.append(img,div);
}
ul.append(li);
display.scrollTop = display.scrollHeight; // 스크롤 제일 밑으로
}
})
.catch(err => console.log(err));
}
// -----------------------------------------------
// sockjs를 이용한 WebSocket 구현
// 로그인이 되어 있을 경우에만
// /chattingSock 이라는 요청 주소로 통신할 수 있는 WebSocket 객체 생성
let chattingSock;
if(loginMemberNo != ""){
chattingSock = new SockJS("/chattingSock");
}
// 채팅 입력
const send = document.getElementById("send");
const sendMessage = () => {
const inputChatting = document.getElementById("inputChatting");
if (inputChatting.value.trim().length == 0) {
alert("채팅을 입력해주세요.");
inputChatting.value = "";
} else {
var obj = {
"senderNo": loginMemberNo,
"targetNo": selectTargetNo,
"chattingNo": selectChattingNo,
"messageContent": inputChatting.value,
};
console.log(obj)
// JSON.stringify() : 자바스크립트 객체를 JSON 문자열로 변환
chattingSock.send(JSON.stringify(obj));
inputChatting.value = "";
}
}
// 엔터 == 제출
// 쉬프트 + 엔터 == 줄바꿈
inputChatting.addEventListener("keyup", e => {
if(e.key == "Enter"){
if (!e.shiftKey) {
sendMessage();
}
}
})
// WebSocket 객체 chattingSock이 서버로 부터 메세지를 통지 받으면 자동으로 실행될 콜백 함수
chattingSock.onmessage = function(e) {
// 메소드를 통해 전달받은 객체값을 JSON객체로 변환해서 obj 변수에 저장.
const msg = JSON.parse(e.data);
console.log(msg);
// 현재 채팅방을 보고있는 경우
if(selectChattingNo == msg.chattingNo){
const ul = document.querySelector(".display-chatting");
// 메세지 만들어서 출력하기
//<li>, <li class="my-chat">
const li = document.createElement("li");
// 보낸 시간
const span = document.createElement("span");
span.classList.add("chatDate");
span.innerText = msg.sendTime;
// 메세지 내용
const p = document.createElement("p");
p.classList.add("chat");
p.innerHTML = msg.messageContent; // br태그 해석을 위해 innerHTML
// 내가 작성한 메세지인 경우
if(loginMemberNo == msg.senderNo){
li.classList.add("my-chat");
li.append(span, p);
}else{ // 상대가 작성한 메세지인 경우
li.classList.add("target-chat");
// 상대 프로필
// <img src="/resources/images/user.png">
const img = document.createElement("img");
img.setAttribute("src", selectTargetProfile);
const div = document.createElement("div");
// 상대 이름
const b = document.createElement("b");
b.innerText = selectTargetName; // 전역변수
const br = document.createElement("br");
div.append(b, br, p, span);
li.append(img,div);
}
ul.append(li)
display.scrollTop = display.scrollHeight; // 스크롤 제일 밑으로
}
selectRoomList();
}
// 문서 로딩 완료 후 수행할 기능
document.addEventListener("DOMContentLoaded", ()=>{
// 채팅방 목록에 클릭 이벤트 추가
roomListAddEvent();
// 보내기 버튼에 이벤트 추가
send.addEventListener("click", sendMessage);
});
📚 Spring
📗 ChattingWebsocketHandler
public class ChattingWebsocketHandler extends TextWebSocketHandler{
private Logger logger = LoggerFactory.getLogger(ChattingWebsocketHandler.class);
@Autowired
private ChattingService service;
private Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<WebSocketSession>());
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
//logger.info("{}연결됨", session.getId());
// System.out.println(session.getId() + "연결됨");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 전달받은 내용은 JSON 형태의 String
logger.info("전달받은 내용 : " + message.getPayload());
// Jackson에서 제공하는 객체
// JSON String -> VO Object
ObjectMapper objectMapper = new ObjectMapper();
Message msg = objectMapper.readValue( message.getPayload(), Message.class);
// Message 객체 확인
System.out.println(msg);
// DB 삽입 서비스 호출
int result = service.insertMessage(msg);
if(result > 0 ) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd hh:mm");
msg.setSendTime(sdf.format(new Date()) );
// 전역변수로 선언된 sessions에는 접속중인 모든 회원의 세션 정보가 담겨 있음
for(WebSocketSession s : sessions) {
// WebSocketSession은 HttpSession의 속성을 가로채서 똑같이 가지고 있기 때문에
// 회원 정보를 나타내는 loginMember도 가지고 있음.
// 로그인된 회원 정보 중 회원 번호 얻어오기
int loginMemberNo = ((Member)s.getAttributes().get("loginMember")).getMemberNo();
logger.debug("loginMemberNo : " + loginMemberNo);
// 로그인 상태인 회원 중 targetNo가 일티하는 회원에게 메세지 전달
if(loginMemberNo == msg.getTargetNo() || loginMemberNo == msg.getSenderNo()) {
s.sendMessage(new TextMessage(new Gson().toJson(msg)));
}
}
}
}
// afterConnectionClosed - 클라이언트와 연결이 종료되면 실행된다.
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
//logger.info("{}연결끊김",session.getId());
}
}
❗ handleTextMessage :
jsp 파일에서 클라이언트가 현재 접속중인 아이디를 웹소켓을 통해 서버로 보내면 메소드가 실행된다.
❗ message.getPayload() : 함수를 통해 메세지에 담긴 텍스트 값을 얻어올 수 있다.
📗 ChattingController
@SessionAttributes({"loginMember"})
@Controller
public class ChattingController {
@Autowired
private ChattingService service;
// 채팅 페이지
@GetMapping("/chatting")
public String chatting(@SessionAttribute("loginMember") Member loginMember, Model model) {
List<ChattingRoom> roomList = service.selectRoomList(loginMember.getMemberNo());
model.addAttribute("roomList", roomList);
return "chatting/chatting";
}
// 채팅 상대 검색
@GetMapping(value="/chatting/selectTarget", produces="application/json; charset=UTF-8")
@ResponseBody
public List<Member> selectTarget(String query, @SessionAttribute("loginMember") Member loginMember){
Map<String, Object> map = new HashMap<>();
map.put("memberNo", loginMember.getMemberNo());
map.put("query", query);
return service.selectTarget(map);
// 만약 나에게 체팅 기능을 활성화하고 싶다면 @SessionAttribute("loginMember") Member loginMember 삭제
}
// 채팅방 입장(없으면 생성)
@GetMapping("/chatting/enter")
@ResponseBody
public int chattingEnter(int targetNo, @SessionAttribute("loginMember") Member loginMember) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("targetNo", targetNo);
map.put("loginMemberNo", loginMember.getMemberNo());
int chattingNo = service.checkChattingNo(map);
//기존에 체팅한 사람이 없다면
if(chattingNo == 0) {
chattingNo = service.createChattingRoom(map);
}
return chattingNo;
}
// 채팅방 목록 조회
@GetMapping(value="/chatting/roomList", produces="application/json; charset=UTF-8")
@ResponseBody
public List<ChattingRoom> selectRoomList(@SessionAttribute("loginMember") Member loginMember) {
return service.selectRoomList(loginMember.getMemberNo());
}
// 채팅 읽음 표시
@PutMapping("/chatting/updateReadFlag")
@ResponseBody
public int updateReadFlag(@RequestBody Map<String, Object> paramMap) {
return service.updateReadFlag(paramMap);
}
@GetMapping(value="/chatting/selectMessage", produces="application/json; charset=UTF-8")
@ResponseBody
public List<Message> selectMessageList(@RequestParam Map<String, Object> paramMap) {
return service.selectMessageList(paramMap);
}
}
📗 ChattingService
public interface ChattingService {
List<ChattingRoom> selectRoomList(int memberNo);
int checkChattingNo(Map<String, Integer> map);
int createChattingRoom(Map<String, Integer> map);
int insertMessage(Message msg);
int updateReadFlag(Map<String, Object> paramMap);
List<Message> selectMessageList( Map<String, Object> paramMap);
/** 채팅 상대 검색
* @param map
* @return memberList
*/
List<Member> selectTarget(Map<String, Object> map);
}
📗 ChattingServiceImpl
@Service
public class ChattingServiceImpl implements ChattingService{
@Autowired
private ChattingDAO dao;
@Override
public List<ChattingRoom> selectRoomList(int memberNo) {
return dao.selectRoomList(memberNo);
}
@Override
public int checkChattingNo(Map<String, Integer> map) {
return dao.checkChattingNo(map);
}
@Override
public int createChattingRoom(Map<String, Integer> map) {
return dao.createChattingRoom(map);
}
@Override
public int insertMessage(Message msg) {
msg.setMessageContent(Util.XSSHandling(msg.getMessageContent()));
// msg.setMessageContent(Util.newLineHandling(msg.getMessageContent()));
return dao.insertMessage(msg);
}
@Override
public int updateReadFlag(Map<String, Object> paramMap) {
return dao.updateReadFlag(paramMap);
}
@Override
public List<Message> selectMessageList( Map<String, Object> paramMap) {
System.out.println(paramMap);
List<Message> messageList = dao.selectMessageList( Integer.parseInt( String.valueOf(paramMap.get("chattingNo") )));
if(!messageList.isEmpty()) {
int result = dao.updateReadFlag(paramMap);
}
return messageList;
}
// 채팅 상대 검색
@Override
public List<Member> selectTarget(Map<String, Object> map) {
return dao.selectTarget(map);
}
}
📗 ChattingDAO
@Repository
public class ChattingDAO {
@Autowired
private SqlSessionTemplate sqlSession;
public List<ChattingRoom> selectRoomList(int memberNo) {
return sqlSession.selectList("chattingMapper.selectRoomList", memberNo);
}
public int checkChattingNo(Map<String, Integer> map) {
return sqlSession.selectOne("chattingMapper.checkChattingNo", map);
}
public int createChattingRoom(Map<String, Integer> map) {
int result = sqlSession.insert("chattingMapper.createChattingRoom", map);
int chattingNo = 0;
if(result > 0) chattingNo = (int)map.get("chattingNo");
return chattingNo;
}
public int insertMessage(Message msg) {
return sqlSession.insert("chattingMapper.insertMessage", msg);
}
public int updateReadFlag(Map<String, Object> paramMap) {
return sqlSession.update("chattingMapper.updateReadFlag", paramMap);
}
public List<Message> selectMessageList(int chattingNo) {
return sqlSession.selectList("chattingMapper.selectMessageList", chattingNo);
}
public List<Member> selectTarget(Map<String, Object> map) {
return sqlSession.selectList("chattingMapper.selectTarget", map);
}
}
📗 chatting-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="chattingMapper">
<resultMap type="ChattingRoom" id="chattingRoom_rm">
<id property="chattingNo" column="CHATTING_NO" />
<result property="lastMessage" column="LAST_MESSAGE" />
<result property="sendTime" column="SEND_TIME" />
<result property="targetNo" column="TARGET_NO" />
<result property="targetNickName" column="TARGET_NICKNAME" />
<result property="targetProfile" column="TARGET_PROFILE" />
<result property="notReadCount" column="NOT_READ_COUNT" />
</resultMap>
<resultMap type="Message" id="message_rm">
<id property="messageNo" column="MESSAGE_NO" />
<result property="messageContent" column="MESSAGE_CONTENT" />
<result property="readFlag" column="READ_FL" />
<result property="senderNo" column="SENDER_NO" />
<result property="chattingNo" column="CHATTING_NO" />
<result property="sendTime" column="SEND_TIME" />
</resultMap>
<resultMap type="Member" id="member_rm">
<id property="memberNo" column="MEMBER_NO"/>
<result property="memberEmail" column="MEMBER_EMAIL"/>
<result property="memberNickname" column="MEMBER_NICKNAME"/>
<result property="profileImage" column="PROFILE_IMG"/>
</resultMap>
<!--=========================================================================================-->
<!-- 채팅방 목록 조회 / 화면에 추가 했을때 나오는 유저목록 표시부분/ 최근 메세지 상단 / -->
<select id="selectRoomList" resultMap="chattingRoom_rm">
SELECT CHATTING_NO
,(SELECT MESSAGE_CONTENT FROM (
SELECT * FROM MESSAGE M2
WHERE M2.CHATTING_NO = R.CHATTING_NO
ORDER BY MESSAGE_NO DESC)
WHERE ROWNUM = 1) LAST_MESSAGE
,TO_CHAR(NVL((SELECT MAX(SEND_TIME) SEND_TIME
FROM MESSAGE M
WHERE R.CHATTING_NO = M.CHATTING_NO), CH_CREATE_DATE),
'YYYY.MM.DD') SEND_TIME
,NVL2((SELECT OPEN_MEMBER FROM CHATTING_ROOM R2
WHERE R2.CHATTING_NO = R.CHATTING_NO
AND R2.OPEN_MEMBER = #{memberNo}),
R.PARTICIPANT,
R.OPEN_MEMBER
) TARGET_NO
,NVL2((SELECT OPEN_MEMBER FROM CHATTING_ROOM R2
WHERE R2.CHATTING_NO = R.CHATTING_NO
AND R2.OPEN_MEMBER = #{memberNo}),
(SELECT MEMBER_NICKNAME FROM MEMBER WHERE MEMBER_NO = R.PARTICIPANT),
(SELECT MEMBER_NICKNAME FROM MEMBER WHERE MEMBER_NO = R.OPEN_MEMBER)
) TARGET_NICKNAME
,NVL2((SELECT OPEN_MEMBER FROM CHATTING_ROOM R2
WHERE R2.CHATTING_NO = R.CHATTING_NO
AND R2.OPEN_MEMBER = #{memberNo}),
(SELECT PROFILE_IMG FROM MEMBER WHERE MEMBER_NO = R.PARTICIPANT),
(SELECT PROFILE_IMG FROM MEMBER WHERE MEMBER_NO = R.OPEN_MEMBER)
) TARGET_PROFILE
,(SELECT COUNT(*) FROM MESSAGE M WHERE M.CHATTING_NO = R.CHATTING_NO AND READ_FL = 'N' AND SENDER_NO != #{memberNo}) NOT_READ_COUNT
,(SELECT MAX(MESSAGE_NO) SEND_TIME FROM MESSAGE M WHERE R.CHATTING_NO = M.CHATTING_NO) MAX_MESSAGE_NO
FROM CHATTING_ROOM R
WHERE OPEN_MEMBER = #{memberNo}
OR PARTICIPANT = #{memberNo}
ORDER BY MAX_MESSAGE_NO DESC NULLS LAST
</select>
<!-- 채팅 확인 -->
<select id="checkChattingNo" resultType="_int">
SELECT NVL(SUM(CHATTING_NO),0) CHATTING_NO FROM CHATTING_ROOM
WHERE (OPEN_MEMBER = #{loginMemberNo} AND PARTICIPANT = #{targetNo})
OR (OPEN_MEMBER = #{targetNo} AND PARTICIPANT = #{loginMemberNo})
</select>
<!-- 채팅방 생성 -->
<insert id="createChattingRoom" parameterType="map" useGeneratedKeys="true">
<selectKey keyProperty="chattingNo" order="BEFORE" resultType="_int">
SELECT SEQ_ROOM_NO.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO CHATTING_ROOM
VALUES(#{chattingNo}, DEFAULT, #{loginMemberNo}, #{targetNo})
</insert>
<!-- 채팅 메세지 삽입 -->
<insert id="insertMessage">
INSERT INTO "MESSAGE"
VALUES(SEQ_MESSAGE_NO.NEXTVAL, #{messageContent}, DEFAULT, DEFAULT, #{senderNo}, #{chattingNo})
</insert>
<!-- 채팅 메세지 중 내가 보내지 않은 글을 읽음으로 표시 -->
<update id="updateReadFlag">
UPDATE "MESSAGE" SET
READ_FL = 'Y'
WHERE CHATTING_NO = #{chattingNo}
AND SENDER_NO != #{memberNo}
</update>
<!-- 채팅방 메세지 조회 -->
<select id="selectMessageList" resultMap="message_rm">
SELECT MESSAGE_NO, MESSAGE_CONTENT, READ_FL, SENDER_NO, CHATTING_NO,
TO_CHAR(SEND_TIME, 'YYYY.MM.DD HH24:MI') SEND_TIME
FROM MESSAGE
WHERE CHATTING_NO = #{chattingNo}
ORDER BY MESSAGE_NO
</select>
<!-- 채팅 상대 검색 -->
<select id="selectTarget" resultMap="member_rm">
SELECT MEMBER_NO, MEMBER_EMAIL, MEMBER_NICKNAME, PROFILE_IMG FROM "MEMBER"
WHERE (MEMBER_EMAIL LIKE '%${query}%' OR MEMBER_NICKNAME LIKE '%${query}%')
AND MEMBER_DEL_FL = 'N'
AND MEMBER_NO != ${memberNo}
</select>
</mapper>
1. 채팅 클릭 시 목록 화면 출력하기
📚 VS Code
📕 header.jsp (필요한 부분말 발췌)
<%-- 로그인 했을때 채팅 보여짐 --%>
<c:if test="${!empty loginMember}" >
<li><a href = "/chatting">채팅</a></li>
</c:if>
📚 Spring
📗 ChattingController.java (필요한 부분말 발췌)
@SessionAttributes({"loginMember"})
@Controller
public class ChattingController {
@Autowired
private ChattingService service;
// 채팅 페이지
@GetMapping("/chatting")
public String chatting(@SessionAttribute("loginMember") Member loginMember, Model model) {
List<ChattingRoom> roomList = service.selectRoomList(loginMember.getMemberNo());
model.addAttribute("roomList", roomList);
return "chatting/chatting";
}
728x90
'ON > spring' 카테고리의 다른 글
[Spring Boot] 설치하기 (0) | 2023.10.04 |
---|---|
[ Spring ] 채팅 구현하기 - 실전편 ② (화면 구현 DB설정) (0) | 2023.09.04 |
[ Spring ] 채팅 구현하기 - 이론편 ① (0) | 2023.09.04 |
[ Spring ] SpringAOP 사용하기 ⑳ (0) | 2023.09.02 |
[ Spring ] SpringAOP 이론 ⑱ (0) | 2023.09.01 |