당근마켓을 이용해본 적 있다면 익숙할 기능.
채팅하기 기능이다.
어떠한 게시물에서 '채팅하기'버튼을 누르면
글 작성자와 1대1 채팅을 시작할 수 있다.
그리고, /chat 접속시 내가 참여중인 채팅방을 보여주자.
채팅기능 구현을 위한 단계는 이렇다.
채팅방 개설.
1. list.ejs 에 '채팅하기'버튼을 추가하고, 버튼 클릭시 post요청(채팅방 개설)
2. server.js에서 1번의 post요청 처리
3. chat.ejs 디자인
4. server.js에서 /chat으로의 get요청 처리(이 때, 현재 로그인한 사용자가 속해있는 채팅방들의 정보를 전달)
여기까지 했다면 이제부터 채팅방의 메세지들을 처리하는 코드를 추가.
5. /chat에서 메세지 전송시 post요청(어떤 채팅방에서,언제,어떤 내용인지 등을 DB에 저장)
6. server.js에서 5번의 post요청 처리(message콜렉션에 데이터저장)
이제 각 채팅방을 열 면 해당하는 메세지가 나오도록 하는데, 이 때 SSE방식을 이용한 실시간소통을 한다.
7. server.js에서 실시간채널 열고 데이터 보내기
8. chat.ejs에서 실시간채널 접속하여 데이터 받아 처리하기
이제 한 단계씩 살펴보자.
1. list.ejs 에 '채팅하기'버튼을 추가하고, 버튼 클릭시 /chatroom으로 post요청(채팅방 개설)
(list.ejs)
. . .
<button class="btn btn-danger chatting" data-id="<%=posts[i].writer %>" data-title="<%=posts[i].title %>">채팅하기</button>
. . .
<script>
$('.chatting').click(function (e) {
var 글작성자 = e.target.dataset.id;
var 현재요소 = $(this);
var 제목 = e.target.dataset.title;
$.post('/chatroom',{'글작성자':글작성자,'제목':제목}).then(()=>{
console.log('완료')
})
})
</script>
버튼 태그를 이용하여 채팅하기 버튼을 추가했고,
태그 안에는 data-id, data-title 데이터를 넣어서
글 작성자(writer), 글 제목(title)을 담았다.
스크립트태그 내에서는 .chatting 요소의 이벤트리스너를 추가하여 채팅하기 버튼의 클릭을 감지한다.
버튼태그에 심어둔 dataset으로 글작성자,제목 데이터를 가져와서
/chatroom경로로 데이터와함께 post요청을 한다.
2. server.js에서 1번의 post요청 처리
(server.js)
app.post('/chatroom',로그인했니,function(요청,응답){
var 저장할거 = {
member:[ObjectId(요청.body.글작성자), 요청.user._id],
date: new Date(),
title: 요청.body.제목
}
db.collection('chatroom').insertOne(저장할거).then((결과)=>{
응답.send('성공')
})
})
/chatroom으로의 post요청을 처리한다. 이때 로그인상태인지 검증하는 미들웨어 '로그인했니'를 사용한다.
이제 DB의 chatroom 콜렉션에 채팅방 정보를 저장하는데,
저장할 내용은 다음과 같다.
member: [글작성자(요청.body.글작성자), 채팅 건 사람(요청.user._id)]
date: new Date()를 이용하여 현재날짜 저장
title: 글제목(요청.body.제목)
3. chat.ejs 디자인
<%- include('nav.html') %>
<div class="container p-4 detail">
<div class="row">
<div class="col-3">
<ul class="list-group chat-list">
<p><%= currentUserId %></p>
<% for(let i=0;i< data.length ;i++){ %>
<li class="list-group-item" data-id="<%=data[i]._id%>" data-user="<%= currentUserId %>">
<h6><%= data[i].title %></h6>
<h6 class="text-small"><%= data[i].member[0]%></h6>
</li>
<% } %>
</ul>
</div>
<div class="col-9 p-0">
<div class="chat-room">
<ul class="list-group chat-content">
</ul>
<div class="input-group">
<input class="form-control" id="chat-input">
<button class="btn btn-secondary" id="send">전송</button>
</div>
</div>
</div>
</div>
</div>
ejs문법을 이용하여 자바스크립트 for문을 활용,
전달받은 데이터를 채팅방 목록으로 출력한다.
(dataset으로 넣어둔 데이터들은 추후 이용하기 위함이다)
4. server.js에서 /chat으로의 get요청 처리(이 때, 현재 로그인한 사용자가 속해있는 채팅방들의 정보를 전달)
app.get('/chat',로그인했니,function(요청,응답){
db.collection('chatroom').find({member:요청.user._id}).toArray(function(에러,결과){
// console.log(결과)
응답.render('chat.ejs',{data:결과, currentUserId: 요청.user._id})
})
})
chat.ejs파일을 만들어두었으니
/chat경로로 접속시 chat.ejs파일을 렌더링해준다.
이 때 채팅방은 로그인한 유저만 사용할 수 있으므로, 미들웨어에 '로그인했니'라는 함수를 사용했다.
또한 렌더링시 현재 유저가 속한 채팅방들의 정보를 넘겨주어야 하므로
db의 find함수를 사용하여 데이터를 찾고 toArray를 이용하여 배열로 변환해서 그 결과값을 data로 보내주었다.
(currentUserId는 chat.ejs파일에서 현재유저가 누구인지 알기 위해 추가해 넣었다. 왜 알고싶었냐면..나중에 메세지들을 불러올 때, 이 메세지가 내가 보낸 메세지인지 상대방이 보낸 메세지인지 판별하고싶었기 때문이다.)
여기까지 했다면 이제부터 채팅방의 메세지들을 처리하는 코드를 추가.
5. /chat에서 메세지 전송시 post요청(어떤 채팅방에서,언제,어떤 내용인지 등을 DB에 저장)
(chat.ejs)
<script>
$('#send').click(function(){
var 채팅내용 = $('#chat-input').val();
var 보낼거 = {
parent : 채팅방id,
content: 채팅내용,
}
$.post('/message',보낼거).then((결과)=>{
console.log('전송완');
})
})
</script>
메세지 전송버튼 클릭을 감지하는 이벤트리스너를 추가하고,
/message경로로 post요청을 한다.
이 때 보낼 데이터에는 {parent: 채팅방의 id, content: 채팅내용}을 넣어야 한다.
이 메세지정보는 DB상의 별도의 콜렉션: message에 저장될 것이기 때문에
메세지정보를 꺼내서 사용할 때, 채팅방id를 저장해두어야
각 채팅방마다의 메세지를 구별해 사용할 수 있다.
6. server.js에서 5번의 post요청 처리(message콜렉션에 데이터저장)
app.post('/message',로그인했니,function(요청,응답){
var 저장할거={
parent: 요청.body.parent,
content : 요청.body.content,
userid: 요청.user._id,
date: new Date()
}
db.collection('message').insertOne(저장할거).then(()=>{
})
})
아까전 5번 과정에서 /message경로로 {parent,content}의 정보를 post요청했다.
여기에 현재 시각(date)와 메세지를 발행한 유저 정보(요청.user._id)를 추가로 묶어 db에 저장한다.
이제 채팅방 메세지들을 보내서 chat.ejs에서 보여주면 될 것 같은데..
일반적인 get,post요청은 1번 요청하면 끝이기때문에 실시간으로 만들기 힘들다.
그래서 SSE방식으로 서버에서 실시간채널을 열어본다.
app.get('/message/:id',로그인했니,function(요청,응답){
응답.writeHead(200,{
"Connection":"keep-alive",
"Content-Type": "text/event-stream",
"Cache-Control":"no-cache"
});
//console.log(요청.params.id)
db.collection('message').find({parent: 요청.params.id }).toArray((에러,결과)=>{
// console.log(결과+","+요청.user._id)
var 보낼거 = 결과;
console.log(보낼거)
응답.write('event: test\n')//보낼데이터이름
응답.write(`data: ${JSON.stringify(결과)}\n\n`);//데이터내용. 문자형만 전송가능하므로 JSON.stringify사용
})
const pipeline = [
{$match:{'fullDocument.parent': 요청.params.id }}
];
const collection = db.collection('message');
const changeStream = collection.watch(pipeline);
changeStream.on('change',(result)=>{
응답.write('event: test\n')//보낼데이터이름
응답.write('data:'+JSON.stringify([result.fullDocument])+'\n\n');
})
})
/message/:id로 채팅방id를 파라미터로 받는다.
writeHead()를 위와같이 작성하면 지속적인 응답이 가능하다.
(작성 후
응답.write('event: abc\n')
응답.write('data: {defg}\n\n')
형식으로 보내면, abc라는 이벤트명으로 {defg}데이터를 보낸다. )
그런데 응답으로 데이터를 보낼 때, 문자열 형태여야 하기 때문에
JSON.stringify를 이용하여 문자열형태로 변환하고 보내야 한다.
그리하여 서버에서 채팅방id로 검색한 메시지내용들을 가지고 와 응답.write로 데이터를 보내주게 된다.
그런데 db에 메세지가 추가되는 경우에 서버가 자동으로 응답을 보내주는것은 아니다.
그래서 MongoDB의 changeStream을 이용해 db의 데이터가 변동될 때 데이터를 추가로 보내주어야 한다.
const pipeline = [
{$match:{'fullDocument.parent': 요청.params.id }}
];
const collection = db.collection('message');
const changeStream = collection.watch(pipeline);
changeStream.on('change',(result)=>{
응답.write('event: test\n')//보낼데이터이름
응답.write('data:'+JSON.stringify([result.fullDocument])+'\n\n');
})
그것이 이 부분이다.
(참고: changeStream의 result는 괄호에 싸서 보내야함. 원래 괄호에 싸여있는 형태여야 하는데 얘는 기본적으로 없어서..)
이렇게 작성하면 'message'라는 콜렉션에서 변동사항이 생기면 데이터를 보내준다.
chat.ejs에서 실시간 채널에 입장하기
let 채팅방id;
let eventSource;
$('.list-group-item').click(function(){
채팅방id = this.dataset.id;
//console.log('현재채팅방id'+채팅방id)
var currentUser = this.dataset.user;//현재유저
console.log(currentUser+"이것이 현재유저")
if(eventSource != undefined){
eventSource.close();//소통채널이 이미 열려있으면 닫기
}
$('.chat-content').html('');//기존내용 삭제
eventSource = new EventSource('/message/'+채팅방id);
eventSource.addEventListener('test',function(e){
console.log(JSON.parse(e.data));
var data = JSON.parse(e.data)
data.forEach(function(element){
if(currentUser == element.userid)
{$('.chat-content').append(`<li><span class="chat-box mine">${element.content}</span></li>`);
}else{
$('.chat-content').append(`<li><span class="chat-box">${element.content}</span></li>`);
}
});
})
})
실시간채널 접속은
new EventSource('경로').addEventListener('이벤트명',콜백함수)로 할 수 있다.
이 때 서버에서 보낸 데이터는 e.data에 있는데(e는 콜백함수의 인자)
서버에서 데이터를 보낼 때 JSON.stringify를 이용하여 문자열 형태로 변환하여 보냈기 때문에
JSON.parse()함수를 이용하여 다시 원래대로 바꿔 사용하면 된다.
그리고 이렇게 받은 데이터들은
chat-content요소에 html을 추가하는 형식으로 메세지를 띄울 수 있다.
'웹개발 > Node.js' 카테고리의 다른 글
[Node.js] Socket.io를 이용하여 실시간 통신하기 (0) | 2023.03.03 |
---|---|
[Node.js] 환경변수로 민감한 정보 가리기(.env) (0) | 2023.02.26 |
[Node.js] 이미지 서버 만들기(multer 라이브러리 사용) (0) | 2023.02.26 |
[Node.js] router 폴더 및 파일로 API관리하기 (0) | 2023.02.26 |
[Node.js] 회원가입, 회원기능 만들기 (0) | 2023.02.25 |
댓글