강의 : 메타코딩
1.환경설정
2)[MongoDB] 몽고디비 GUI 개발/관리도구 Studio 3T 설치 (Robo 3T)
2.몽고DB 장점
3.비동기 Netty 서버
4.@Tailable
Chat.java
import java.time.LocalDateTime;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
//STS 툴에 lombok 설정하는 법(인터넷)
@Data
@Document(collection="chat")
public class Chat {
@Id
private String id;
private String msg;
private String sender;//보내는 사람
private String receiver;//받는 사람
private LocalDateTime createdAt;
}
ChatRepository.java
package com.cos.chatapp;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.mongodb.repository.Tailable;
import reactor.core.publisher.Flux;
public interface ChatRepository extends ReactiveMongoRepository{
@Tailable //커서를 안닫고 계속 유지한다.
@Query("{sender:?0,receiver:?1}")
Flux mFindBySender(String sender, String receiver);//Flux(흐름) response를 유지하면서 데이터를 계속 흘러보내기
}
몽고DB
db.chat.save({
sender:'ssar',
receiver:'cos',
msg:'안녕'
});
db.chat.save({
sender:'ssar2',
receiver:'cos2',
msg:'안녕'
});
db.chat.find().pretty();
db.chat.find({sender:'ssar',receiver:'cos'});

5. SSE프로토콜과 TextEventStream
//@Tailable 사용하기 위해서 Collection 사이즈를 높여 줘야 한다.
//db.createCollection("컬렉션이름",{capped: true, size: 16384, max:100})
//최대 크기가 16384바이트고, 최대 100도큐먼트까지 저장되는 컬렉션
db.runCommand({convertToCapped:'chat', size:8192});
ChatController.java
package com.cos.chatapp;
import java.time.LocalDateTime;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@RequiredArgsConstructor
@RestController//데이터 리턴 서버
public class ChatController {
private final ChatRepository chatRepository;
//Flux 지속적으로 데이터를 계속 흘러보내기
//SSE프로토콜과 TextEventStream 데이터가 생길때마다 지속적으로 보냄
@GetMapping(value="/sender/{sender}/receiver/{receiver}", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux getMsg(@PathVariable String sender, @PathVariable String receiver) {
return chatRepository.mFindBySender(sender, receiver)
.subscribeOn(Schedulers.boundedElastic());
}
//Mono 한번만 실행
@PostMapping("/chat")
public Mono setMsg(@RequestBody Chat chat){
chat.setCreatedAt(LocalDateTime.now());
return chatRepository.save(chat);
}
}
postman 으로 테스트

6. 채팅화면 디자인하기
7. 채팅화면 자바스크립트 이벤트 적용
chat.js
function getMsg(msg) {
let today = new Date();
let year = today.getFullYear(); // 년도
let month = (("00" + (today.getMonth() + 1)).slice(-2)); // 월
let date = (("00" + (today.getDate() + 1)).slice(-2)); // 날짜
let day = today.getDay(); // 요일
let hours = (("00" + today.getHours()).slice(-2)); // 시
let minutes = (("00" + today.getMinutes()).slice(-2)); // 분
let seconds = (("00" + today.getSeconds()).slice(-2)); // 초
let milliseconds = today.getMilliseconds(); // 밀리초
console.log(year + '-' + month + '-' + date);
console.log(hours + ':' + minutes + ':' + seconds + ':' + milliseconds);
let dayDiffer = getDateDiff("2022-08-15", year + '-' + month + '-' + date);
if (dayDiffer === 0) {
dayDiffer = "오늘";
} else {
dayDiffer = dayDiffer + "일전";
}
//엔터키를 br 태그로 전환
msg = msg.replace(/(?:\r\n|\r|\n)/g, '
');
return `
${msg}
${hours}:${minutes} | ${dayDiffer}`; } const getDateDiff = (d1, d2) => { const date1 = new Date(d1); const date2 = new Date(d2); const diffDate = date1.getTime() - date2.getTime(); return Math.abs(diffDate / (1000 * 60 * 60 * 24)); // 밀리세컨 * 초 * 분 * 시 = 일 } document.querySelector("#chat-outgoing-button").addEventListener("click", () => { //alert("클릭"); let chatBox = document.querySelector("#chat-box"); let chatOutgoinBox = document.createElement("div"); chatOutgoinBox.className = "outgoing_msg"; chatOutgoinBox.innerHTML = getMsg(document.querySelector("#chat-outgoing-msg").value); chatBox.append(chatOutgoinBox); document.querySelector("#chat-outgoing-msg").value = ""; }); document.querySelector("#chat-outgoing-msg").addEventListener("keyup", (e) => { console.log(e.target.value); if (e.keyCode === 13) { console.log("enter key"); } });

8. EventSource 객체 사용하기
ChatController
크로스 도메인 허용 @CrossOrigin
import java.time.LocalDateTime;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@RequiredArgsConstructor
@RestController // 데이터 리턴 서버
public class ChatController {
private final ChatRepository chatRepository;
// Flux 지속적으로 데이터를 계속 흘러보내기
// SSE프로토콜과 TextEventStream 데이터가 생길때마다 지속적으로 보냄
@CrossOrigin
@GetMapping(value = "/sender/{sender}/receiver/{receiver}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Chat> getMsg(@PathVariable String sender, @PathVariable String receiver) {
return chatRepository.mFindBySender(sender, receiver)
.subscribeOn(Schedulers.boundedElastic());
}
// Mono 한번만 실행
@CrossOrigin
@PostMapping("/chat")
public Mono<Chat> setMsg(@RequestBody Chat chat) {
chat.setCreatedAt(LocalDateTime.now());
return chatRepository.save(chat);
}
}
chat.js
const eventSource = new EventSource("http://localhost:8080/sender/ssar/receiver/cos");
eventSource.onmessage = (event) => {
console.log(1, event);
const data = JSON.parse(event.data);
console.log(2, data);
}
9. 기존 채팅 내역 출력하기
chat.js
const eventSource = new EventSource("http://localhost:8080/sender/ssar/receiver/cos");
eventSource.onmessage = (event) => {
// console.log(1, event);
const data = JSON.parse(event.data);
// console.log(2, data);
//서버에서 데이터를 가져온 후 데이터 파싱 함수호출
initMessage(data.msg, data);
}
function getSendMsgBox(msg, data) {
let today = new Date();
let year = today.getFullYear(); // 년도
let month = (("00" + (today.getMonth() + 1)).slice(-2)); // 월
let date = (("00" + (today.getDate() + 1)).slice(-2)); // 날짜
let day = today.getDay(); // 요일
let hours = (("00" + today.getHours()).slice(-2)); // 시
let minutes = (("00" + today.getMinutes()).slice(-2)); // 분
let seconds = (("00" + today.getSeconds()).slice(-2)); // 초
let milliseconds = today.getMilliseconds(); // 밀리초
// console.log(year + '-' + month + '-' + date);
// console.log(hours + ':' + minutes + ':' + seconds + ':' + milliseconds);
//console.log(data);
let todayDate = year + '-' + month + '-' + date; //오늘 날짜
if (data != undefined) { //데이터를 서버에서 가져올때
console.log(data.createdAt);
if (data.createdAt != undefined && data.createdAt != null && data.createdAt != "") {
let dbDate = new Date(data.createdAt);
year = dbDate.getFullYear(); // 년도
month = (("00" + (dbDate.getMonth() + 1)).slice(-2)); // 월
date = (("00" + (dbDate.getDate() + 1)).slice(-2)); // 날짜
minutes = (("00" + dbDate.getMinutes()).slice(-2)); // 분
seconds = (("00" + dbDate.getSeconds()).slice(-2)); // 초
}
} else {
//엔터키를 br 태그로 전환
msg = msg.replace(/(?:\r\n|\r|\n)/g, '<br />');
}
let dayDiffer = getDateDiff(todayDate, year + '-' + month + '-' + date);
if (dayDiffer === 0) dayDiffer = "오늘";
else dayDiffer = dayDiffer + "일전";
return `
<div class="sent_msg">
<p>${msg}</p>
<span class="time_date"> ${hours}:${minutes} | ${dayDiffer}</span>
</div>
`;
}
const getDateDiff = (d1, d2) => {
const date1 = new Date(d1);
const date2 = new Date(d2);
const diffDate = date1.getTime() - date2.getTime();
return Math.abs(diffDate / (1000 * 60 * 60 * 24)); // 밀리세컨 * 초 * 분 * 시 = 일
}
function initMessage(historyMsg, data) {
let chatBox = document.querySelector("#chat-box");
let chatOutgoinBox = document.createElement("div");
chatOutgoinBox.className = "outgoing_msg";
chatOutgoinBox.innerHTML = getSendMsgBox(historyMsg, data);
chatBox.append(chatOutgoinBox);
}
function addMessage() {
let chatBox = document.querySelector("#chat-box");
let chatOutgoinBox = document.createElement("div");
let msgInput = document.querySelector("#chat-outgoing-msg");
chatOutgoinBox.className = "outgoing_msg";
chatOutgoinBox.innerHTML = getSendMsgBox(msgInput.value);
chatBox.append(chatOutgoinBox);
msgInput.value = "";
}
document.querySelector("#chat-outgoing-button").addEventListener("click", () => {
//alert("클릭");
addMessage();
});
document.querySelector("#chat-outgoing-msg").addEventListener("keyup", (e) => {
// console.log(e.target.value);
if (e.keyCode === 13) {
// console.log("enter key");
}
});
10. 채팅 메시지 전송 및 저장
chat.js
async function addMessage() {
let chatBox = document.querySelector("#chat-box");
let chatOutgoinBox = document.createElement("div");
let msgInput = document.querySelector("#chat-outgoing-msg");
//엔터시 <br/> 변경
msg = msgInput.value.replace(/(?:\r\n|\r|\n)/g, '<br />');
let chat = {
sender: "ssar",
receiver: "cos",
msg: msg
}
await fetch("http://localhost:8080/chat", {
method: "POST",
headers: {
"Content-type": "application/json; charset=utf-8"
},
body: JSON.stringify(chat)
})
.then((res) => res.json())
.then(data => {
console.log("data ", data);
})
.catch(error => {
console.log("에러 :", error);
})
// chatOutgoinBox.className = "outgoing_msg";
// chatOutgoinBox.innerHTML = getSendMsgBox(msgInput.value);
// chatBox.append(chatOutgoinBox);
msgInput.value = "";
}
11. HTTP, SSE, WebSocket 비교
12. 채팅 유튜브 강의 완료
Chat.java
package com.cos.chatapp;
import java.time.LocalDateTime;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
//STS 툴에 lombok 설정하는 법(인터넷)
@Data
@Document(collection="chat")
public class Chat {
@Id
private String id;
private String msg;
private String sender;//보내는 사람
private String receiver;//받는 사람
private Integer roomNum;//방 번호
private LocalDateTime createdAt;
}
ChatRepository.java
package com.cos.chatapp;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.mongodb.repository.Tailable;
import reactor.core.publisher.Flux;
public interface ChatRepository extends ReactiveMongoRepository<Chat, String>{
@Tailable //커서를 안닫고 계속 유지한다.
@Query("{sender:?0,receiver:?1}")
Flux<Chat> mFindBySender(String sender, String receiver);//Flux(흐름) response를 유지하면서 데이터를 계속 흘러보내기
@Tailable
@Query("{roomNum:?0}")
Flux<Chat> mFindByRoomNum(Integer roomNum);
}
ChatController.java
package com.cos.chatapp;
import java.time.LocalDateTime;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@RequiredArgsConstructor
@RestController // 데이터 리턴 서버
public class ChatController {
private final ChatRepository chatRepository;
//귓속말시 사용
// Flux 지속적으로 데이터를 계속 흘러보내기
// SSE프로토콜과 TextEventStream 데이터가 생길때마다 지속적으로 보냄
@CrossOrigin
@GetMapping(value = "/sender/{sender}/receiver/{receiver}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Chat> getMsg(@PathVariable String sender, @PathVariable String receiver) {
return chatRepository.mFindBySender(sender, receiver)
.subscribeOn(Schedulers.boundedElastic());
}
@CrossOrigin
@GetMapping(value = "/chat/roomNum/{roomNum}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Chat> findByRoomNum(@PathVariable Integer roomNum) {
return chatRepository.mFindByRoomNum(roomNum)
.subscribeOn(Schedulers.boundedElastic());
}
// Mono 한번만 실행
@CrossOrigin
@PostMapping("/chat")
public Mono<Chat> setMsg(@RequestBody Chat chat) {
chat.setCreatedAt(LocalDateTime.now());
return chatRepository.save(chat);
}
}
chat.html
<!DOCTYPE html>
<html>
<head>
<title>What's App</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
<link rel="stylesheet" type="text/css" href="./css/style.css">
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div id="user_chat_data" class="user_chat_data">
<div class="profile_name">
<h1 id="title"></h1>
<img src="./img/profile.png" class="mr-3 rounded-circle">
<span class="username">Sankar Mahadevan</span>
</div>
<div class="container-fluid chat_section" id="chat-box">
</div>
<div class="type_msg">
<div class="input_msg_write">
<!-- <input id="chat-outgoing-msg" type="text" class="write_msg" placeholder="Type a message" /> -->
<textarea id="chat-outgoing-msg" type="text" class="write_msg" placeholder="메시지를 입력해 주세요." ></textarea>
<button id="chat-outgoing-button" class="msg_send_btn" type="button"><i class="fa fa-paper-plane"
aria-hidden="true"></i></button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="js/chat.js"></script>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</body>
</html>
chat.js
//로그인 시스템 대신 임시 방편
let username = prompt("아이디를 입력하세요.");
let roomNum = prompt("채팅방 번호를 입력하세요.");
//SSE 연결하기
//const eventSource = new EventSource("http://localhost:8080/sender/ssar/receiver/cos");
const eventSource = new EventSource(`http://localhost:8080/chat/roomNum/${roomNum}`);
eventSource.onmessage = (event) => {
// console.log(1, event);
const data = JSON.parse(event.data);
document.querySelector("#title").innerHTML = roomNum + "번 방";
document.querySelector(".username").innerText = username;
if (data.sender === username) { //로그인한 유저가 보낸 메시지
//파란박스 (오른쪽)
initMyMessage(data);
} else {
//회색밗(왼쪽)
initYourMessage(data);
}
//서버에서 데이터를 가져온 후 데이터 파싱 함수호출
// initMyMessage(data);
}
//파란박스 만들기
function getSendMsgBox(data) {
//날짜 가공
let mDate = manufactureDate(data);
return `
<div class="sent_msg">
<p>${data.msg}</p>
<span class="time_date"> ${mDate.day} ${mDate.hours}:${mDate.minutes} | ${mDate.dayDiffer} / ${data.sender} </span>
</div>
`;
}
//회색박스 만들기
function getReceiveMsgBox(data) {
//날짜 가공
let mDate = manufactureDate(data);
return `
<div class="received_withd_msg">
<p>${data.msg}</p>
<span class="time_date"> ${mDate.day} ${mDate.hours}:${mDate.minutes} | ${mDate.dayDiffer} / ${data.sender}</span>
</div>
`;
}
//최초 초기화뙬 때 1번방 3건이 있으면 3건을 다 가져옴
//addMessage() 함수 호출시 DB에 insert 되고, 그 데이터가 자동으로 흘러들어감(SSE)
//파란박스 초기화 하기
function initMyMessage(data) {
let chatBox = document.querySelector("#chat-box");
let sendBox = document.createElement("div");
sendBox.className = "outgoing_msg";
sendBox.innerHTML = getSendMsgBox(data);
chatBox.append(sendBox);
document.documentElement.scrollTop = document.body.scrollHeight;
}
//회색박스 초기화 하기
function initYourMessage(data) {
let chatBox = document.querySelector("#chat-box");
let receivedBox = document.createElement("div");
receivedBox.className = "received_msg";
receivedBox.innerHTML = getReceiveMsgBox(data);
chatBox.append(receivedBox);
document.documentElement.scrollTop = document.body.scrollHeight;
}
//DB 에 등록시 자동으로 Flux 처리되어 채팅 목록으로 표시됨
async function addMessage() {
let msgInput = document.querySelector("#chat-outgoing-msg");
//엔터시 <br/> 변경
msg = msgInput.value.replace(/(?:\r\n|\r|\n)/g, '<br />');
let chat = {
sender: username,
roomNum: roomNum,
msg: msg
}
await fetch("http://localhost:8080/chat", {
method: "POST",
headers: {
"Content-type": "application/json; charset=utf-8"
},
body: JSON.stringify(chat)
})
.then((res) => res.json())
.then(data => {
//console.log("data ", data);
})
.catch(error => {
console.log("에러 :", error);
})
// chatOutgoinBox.className = "outgoing_msg";
// chatOutgoinBox.innerHTML = getSendMsgBox(msgInput.value);
// chatBox.append(chatOutgoinBox);
msgInput.value = "";
}
//날짜 가공 함수
function manufactureDate(data) {
let today = new Date();
let year = today.getFullYear(); // 년도
let month = (("00" + (today.getMonth() + 1)).slice(-2)); // 월
let date = (("00" + (today.getDate() + 1)).slice(-2)); // 날짜
let day = today.getDay(); // 요일
let hours = (("00" + today.getHours()).slice(-2)); // 시
let minutes = (("00" + today.getMinutes()).slice(-2)); // 분
let seconds = (("00" + today.getSeconds()).slice(-2)); // 초
let milliseconds = today.getMilliseconds(); // 밀리초
let todayDate = year + '-' + month + '-' + date; //오늘 날짜
//console.log("data.createdAt : ", data.createdAt);
let dbDate = new Date(data.createdAt);
year = dbDate.getFullYear(); // 년도
month = (("00" + (dbDate.getMonth() + 1)).slice(-2)); // 월
date = (("00" + (dbDate.getDate() + 1)).slice(-2)); // 날짜
hours = (("00" + dbDate.getHours()).slice(-2)); // 분
minutes = (("00" + dbDate.getMinutes()).slice(-2)); // 분
seconds = (("00" + dbDate.getSeconds()).slice(-2)); // 초
let dayDiffer = getDateDiff(todayDate, year + '-' + month + '-' + date);
if (dayDiffer === 0) dayDiffer = "오늘";
else dayDiffer = dayDiffer + "일전";
const mDate = {
"day": year + '-' + month + '-' + date,
"hours": hours,
"minutes": minutes,
"dayDiffer": dayDiffer
}
return mDate;
}
const getDateDiff = (d1, d2) => {
const date1 = new Date(d1);
const date2 = new Date(d2);
const diffDate = date1.getTime() - date2.getTime();
return Math.abs(diffDate / (1000 * 60 * 60 * 24)); // 밀리세컨 * 초 * 분 * 시 = 일
}
document.querySelector("#chat-outgoing-button").addEventListener("click", () => {
//alert("클릭");
addMessage();
});
document.querySelector("#chat-outgoing-msg").addEventListener("keyup", (e) => {
// console.log(e.target.value);
if (e.keyCode === 13) {
// console.log("enter key");
addMessage();
}
});
1. Spring R2DBC CRUD 예제Spring R2DBC CRUD 예제

















댓글 ( 4)
댓글 남기기