반응형
목차
- SpringBoot에서 STOMP로 채팅 애플리케이션 만들기 (1)
- SpringBoot에서 STOMP로 채팅 애플리케이션 만들기 (2)
- SpringBoot에서 STOMP로 채팅 애플리케이션 만들기 (3) - 현재 게시글
이제 만들어둔 서버와 통신한 클라이언트를 만들어볼 차례다.
개략적으론 SocketJS와 STOMP Client를 이용해 통신하고, vue/bootstrap/freemarker로 화면을 그릴 예정이다.
1. 의존성 추가
dependencies {
implementation("org.webjars.bower:bootstrap:4.3.1")
implementation("org.webjars.bower:vue:2.5.16")
implementation("org.webjars.bower:axios:0.17.1")
implementation("org.webjars:sockjs-client:1.1.2")
implementation("org.webjars:stomp-websocket:2.3.3-1")
}
2. freemarker 옵션 설정
// application.yml
spring:
freemarker:
cache: false
template-loader-path: classpath:/templates
suffix: .ftlh
3. template 파일 작성
더보기
// room.ftlh
<!doctype html>
<html lang="en">
<head>
<title>Websocket Chat</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- CSS -->
<link rel="stylesheet" href="/webjars/bootstrap/4.3.1/dist/css/bootstrap.min.css">
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div class="container" id="app" v-cloak>
<div class="row">
<div class="col-md-12">
<h3>채팅방 리스트</h3>
</div>
</div>
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text">방제목</label>
</div>
<input type="text" class="form-control" v-model="room_name" @keyup.enter="createRoom">
<div class="input-group-append">
<button class="btn btn-primary" type="button" @click="createRoom">채팅방 개설</button>
</div>
</div>
<ul class="list-group">
<li class="list-group-item list-group-item-action" v-for="item in chatrooms" v-bind:key="item.id"
v-on:click="enterRoom(item.id)">
{{item.id}}
</li>
</ul>
</div>
<!-- JavaScript -->
<script src="/webjars/vue/2.5.16/dist/vue.min.js"></script>
<script src="/webjars/axios/0.17.1/dist/axios.min.js"></script>
<script src="/webjars/bootstrap/4.3.1/dist/js/bootstrap.min.js"></script>
<script src="/webjars/sockjs-client/1.1.2/sockjs.min.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
room_name: '',
chatrooms: []
},
created() {
this.findAllRoom();
},
methods: {
findAllRoom: function () {
axios.get('/api/v1/chat/room').then(response => {
console.log(response.data)
this.chatrooms = response.data;
});
},
createRoom: function () {
if ("" === this.room_name) {
alert("방 제목을 입력해 주십시요.");
return;
} else {
var params = new URLSearchParams();
params.append("name", this.room_name);
axios.post('/api/v1/chat/room', params)
.then(
response => {
alert(response.data.id + "방 개설에 성공하였습니다.")
this.room_name = '';
this.findAllRoom();
}
)
.catch(response => {
alert("채팅방 개설에 실패하였습니다.");
});
}
},
enterRoom: function (roomId) {
var sender = prompt('대화명을 입력해 주세요.');
localStorage.setItem('wschat.sender', sender);
localStorage.setItem('wschat.roomId', roomId);
location.href = "/chat/room/" + roomId;
}
}
});
</script>
</body>
</html>
더보기
// roomdetail.ftlh
<!doctype html>
<html lang="en">
<head>
<title>Websocket ChatRoom</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="/webjars/bootstrap/4.3.1/dist/css/bootstrap.min.css">
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div class="container" id="app" v-cloak>
<div>
<h2>{{room.id}}</h2>
</div>
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text">내용</label>
</div>
<input type="text" class="form-control" v-model="message" v-on:keypress.enter="sendMessage">
<div class="input-group-append">
<button class="btn btn-primary" type="button" @click="sendMessage">보내기</button>
</div>
</div>
<ul class="list-group">
<li class="list-group-item" v-for="message in messages">
{{message.sender}} - {{message.message}} - {{new Date(message.createdAt)}}</a>
</li>
</ul>
<div></div>
</div>
<!-- JavaScript -->
<script src="/webjars/vue/2.5.16/dist/vue.min.js"></script>
<script src="/webjars/axios/0.17.1/dist/axios.min.js"></script>
<script src="/webjars/sockjs-client/1.1.2/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.3-1/stomp.min.js"></script>
<script>
//alert(document.title);
// websocket & stomp initialize
var sock = new SockJS("/ws/chat");
var ws = Stomp.over(sock);
var reconnect = 0;
// vue.js
var vm = new Vue({
el: '#app',
data: {
roomId: '',
room: {},
sender: '',
message: '',
messages: []
},
created() {
this.roomId = localStorage.getItem('wschat.roomId');
this.sender = localStorage.getItem('wschat.sender');
this.findRoom();
},
methods: {
findRoom: function () {
axios.get('/api/v1/chat/room/' + this.roomId).then(response => {
this.room = response.data;
});
},
sendMessage: function () {
ws.send("/pub/chat/room/" + this.roomId, {}, JSON.stringify({
type: 'COMMENT',
sender: this.sender,
message: this.message
}));
this.message = '';
},
recvMessage: function (recv) {
if (recv.type === 'COMMENT') {
this.messages.unshift({
"type": recv.type,
"sender": recv.sender,
"message": recv.message,
"createdAt": recv.createdAt
})
}
}
}
});
function connect() {
// pub/sub event
ws.connect({}, function (frame) {
ws.subscribe("/sub/chat/room/" + vm.$data.roomId, function (message) {
var recv = JSON.parse(message.body);
vm.recvMessage(recv);
});
ws.send("/pub/chat/room/" + vm.$data.roomId, {}, JSON.stringify({
type: 'ENTER',
sender: vm.$data.sender,
message: ''
}));
}, function (error) {
if (reconnect++ <= 5) {
setTimeout(function () {
console.log("connection reconnect");
sock = new SockJS("/ws/chat");
ws = Stomp.over(sock);
connect();
}, 10 * 1000);
}
});
}
connect();
</script>
</body>
</html>
- room.ftlh: 최초 접속 시 채팅방을 생성하거나 기존에 생성된 채팅방에 접속할 수 있는 화면
- roomdetail.ftlh: 채팅방에 접속했을 때 보이는 화면, 채팅을 보내거나 기록을 볼 수 있음
4. Template과 Controller 연결
// ChatRoomController.kt
@Controller
class ChatRoomController {
@GetMapping("/chat/room")
fun room(model: Model): String {
return "/chat/room"
}
@GetMapping("/chat/room/{roomId}")
fun roomEnter(
model: Model,
@PathVariable roomId: String?
): String {
model.addAttribute("roomId", roomId)
return "/chat/roomdetail"
}
}
여기까지 진행하면 채팅방 리스트를 볼 수 있으며, 실제 채팅을 주고받을 수 있게 된다.
현재는 채팅 메시지 타입이 'COMMENT'인 경우만 화면에 그려주고 있는데, 'ENTER'일 때도 그려주는 등 다양한 활용이 가능하다.
전체 코드가 담긴 GitHub Repository
https://github.com/960813/spring-boot-stomp
반응형
☕️ Networking
기술 직군의 기술적인 교류, 커리어 이야기, 직군 무관 네트워킹 모두 환영합니다!
위클리 아카데미 오픈 채팅방(비밀번호: 9323)
kakaotalk: https://open.kakao.com/o/gyvuT5Yd