Skip to content

Commit

Permalink
feat: WebSocket 채팅 기초 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
airoca committed Jan 24, 2024
1 parent 2858443 commit ba0edcb
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 8 deletions.
8 changes: 3 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ dependencies {

//websocket (채팅)
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.5.1'
implementation 'org.webjars:stomp-websocket:2.3.4'
implementation 'org.webjars:bootstrap:5.2.3'
implementation 'org.webjars:jquery:3.6.4'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.2'
developmentOnly 'org.springframework.boot:spring-boot-devtools'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/com/example/sign/websocket/ChatController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.example.sign.websocket;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequiredArgsConstructor
public class ChatController {
private final ChatService chatService;


@RequestMapping("/chat/chatList")
public String chatList(Model model){
List<ChatRoom> roomList = chatService.findAllRoom();
model.addAttribute("roomList",roomList);
return "chatList";
}


@PostMapping("/chat/createRoom") //방을 만들었으면 해당 방으로 가야지.
public String createRoom(Model model, @RequestParam("user1") String user1, @RequestParam("user2") String user2) {
ChatRoom room = chatService.createRoom(user1, user2);
model.addAttribute("room",room);
model.addAttribute("user1",user1);
model.addAttribute("user2",user2);
return "chatRoom"; //만든사람이 채팅방 1빠로 들어가게 됩니다
}

@GetMapping("/chat/chatRoom")
public String chatRoom(Model model, @RequestParam("roomId") String roomId){
ChatRoom room = chatService.findRoomById(roomId);
model.addAttribute("room",room); //현재 방에 들어오기위해서 필요한데...... 접속자 수 등등은 실시간으로 보여줘야 돼서 여기서는 못함
return "chatRoom";
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/example/sign/websocket/ChatMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.sign.websocket;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ChatMessage {
// 메시지 타입 : 입장, 채팅, 나감
public enum MessageType {
ENTER, TALK,QUIT
}
private MessageType type; // 메시지 타입
private String roomId; // 방번호
private String sender; // 메시지 보낸사람
private String message; // 메시지
}
33 changes: 33 additions & 0 deletions src/main/java/com/example/sign/websocket/ChatRoom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.sign.websocket;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import lombok.*;
import org.springframework.web.socket.WebSocketSession;
import java.util.HashSet;
import java.util.Set;

@Entity
@Getter
@Setter
public class ChatRoom {
@Id
private String roomId;
private String user1;
private String user2;
@Transient
private Set<WebSocketSession> sessions = new HashSet<>();

//hibernate 사용을 위한 생성자
public ChatRoom() {
}

@Builder
public ChatRoom(String roomId, String user1, String user2) {
this.roomId = roomId;
this.user1 = user1;
this.user2 = user2;
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/example/sign/websocket/ChatService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.sign.websocket;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@RequiredArgsConstructor
@Service
public class ChatService {

private final RoomRepository roomRepository;

private final ObjectMapper objectMapper;
private Map<String, ChatRoom> chatRooms;

@PostConstruct
private void init() {
chatRooms = new LinkedHashMap<>();
}

public List<ChatRoom> findAllRoom() {
return new ArrayList<>(chatRooms.values());
}

public ChatRoom findRoomById(String roomId) {
return chatRooms.get(roomId);
}

public ChatRoom createRoom(String user1, String user2) {
String randomId = UUID.randomUUID().toString();
ChatRoom chatRoom = ChatRoom.builder()
.roomId(randomId)
.user1(user1)
.user2(user2)
.build();

roomRepository.save(chatRoom);

chatRooms.put(randomId, chatRoom);
return chatRoom;
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/example/sign/websocket/RoomRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.sign.websocket;

import org.springframework.data.jpa.repository.JpaRepository;

public interface RoomRepository extends JpaRepository<ChatRoom, String> {
}

69 changes: 69 additions & 0 deletions src/main/java/com/example/sign/websocket/WebSockChatHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example.sign.websocket;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Set;

@Slf4j
@RequiredArgsConstructor
@Component
public class WebSockChatHandler extends TextWebSocketHandler {
private final ObjectMapper objectMapper;
private final ChatService chatService;


@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {

}


@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class);
ChatRoom room = chatService.findRoomById(chatMessage.getRoomId());
Set<WebSocketSession> sessions=room.getSessions(); //방에 있는 현재 사용자 한명이 WebsocketSession
if (chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) {
//사용자가 방에 입장하면 Enter메세지를 보내도록 해놓음. 이건 새로운사용자가 socket 연결한 것이랑은 다름.
//socket연결은 이 메세지 보내기전에 이미 되어있는 상태
sessions.add(session);
chatMessage.setMessage(chatMessage.getSender() + "님이 입장했습니다."); //TALK일 경우 msg가 있을 거고, ENTER일 경우 메세지 없으니까 message set
sendToEachSocket(sessions,new TextMessage(objectMapper.writeValueAsString(chatMessage)) );
}else if (chatMessage.getType().equals(ChatMessage.MessageType.QUIT)) {
sessions.remove(session);
chatMessage.setMessage(chatMessage.getSender() + "님이 퇴장했습니다..");
sendToEachSocket(sessions,new TextMessage(objectMapper.writeValueAsString(chatMessage)) );
}else {
sendToEachSocket(sessions,message ); //입장,퇴장 아닐 때는 클라이언트로부터 온 메세지 그대로 전달.
}
}
private void sendToEachSocket(Set<WebSocketSession> sessions, TextMessage message){
sessions.parallelStream().forEach( roomSession -> {
try {
roomSession.sendMessage(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}



@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
//javascript에서 session.close해서 연결 끊음. 그리고 이 메소드 실행.
//session은 연결 끊긴 session을 매개변수로 이거갖고 뭐 하세요.... 하고 제공해주는 것 뿐
}



}
22 changes: 22 additions & 0 deletions src/main/java/com/example/sign/websocket/WebSockConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.sign.websocket;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@RequiredArgsConstructor
@Configuration
@EnableWebSocket //이게 websocket 서버로서 동작하겠다는 어노테이션
public class WebSockConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/ws/chat").setAllowedOrigins("*");
// handler 등록, js에서 new Websocket할 때 경로 지정
//다른 url에서도 접속할 수있게(CORS방지)
}
}
6 changes: 3 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://15.164.107.161:3306/signal
username: root
password: root
url: jdbc:mysql://localhost/sign
username: testuser
password: rj7783208232
jpa:
hibernate:
ddl-auto: update
Expand Down
24 changes: 24 additions & 0 deletions src/main/resources/templates/chatList.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<form action="/chat/createRoom" method="post">
<input type="text" name="user1" placeholder="user1">
<input type="text" name="user2" placeholder="user2"> <!-- 사용자 이름 입력 필드 추가 -->
<button type="submit">방 만들기</button>
</form>

<table>
<tr th:each="room : ${roomList}">
<td>
<a th:href="@{chatRoom(roomId=${room.roomId})}" th:text="${room.name}"></a>
</td>
</tr>
</table>

</body>
</html>
62 changes: 62 additions & 0 deletions src/main/resources/templates/chatRoom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" placeholder="보낼 메세지를 입력하세요." class="content">
<button type="button" value="전송" class="sendBtn" onclick="sendMsg()">전송</button>
<button type="button" value="방나가기" class="quit" onclick="quit()">방 나가기 </button>
<div>
<span>메세지</span>
<div class="msgArea"></div>
</div>
</body>

<script th:inline="javascript">
function enterRoom(socket){
var enterMsg={"type" : "ENTER","roomId":[[${room.roomId}]],"sender":"chee","msg":""};
socket.send(JSON.stringify(enterMsg));
}
let socket = new WebSocket("ws://192.249.29.43:8080/ws/chat"); //ip 변경 부분

socket.onopen = function (e) {
console.log('open server!')
enterRoom(socket);
};
socket.onclose=function(e){
console.log('disconnet');
}

socket.onerror = function (e){
console.log(e);
}

//메세지 수신했을 때 이벤트.
socket.onmessage = function (e) {
console.log(e.data);
let msgArea = document.querySelector('.msgArea');
let newMsg = document.createElement('div');
newMsg.innerText=e.data;
msgArea.append(newMsg);
}


//메세지 보내기 버튼 눌렀을 떄..
function sendMsg() {
let content=document.querySelector('.content').value;
var talkMsg={"type" : "TALK","roomId":[[${room.roomId}]] ,"sender":"chee","msg":content};
socket.send(JSON.stringify(talkMsg));
}

function quit(){
var quitMsg={"type" : "QUIT","roomId":[[${room.roomId}]] ,"sender":"chee","msg":""};
socket.send(JSON.stringify(quitMsg));
socket.close();
location.href="/chat/chatList";
}

</script>

</html>

0 comments on commit ba0edcb

Please sign in to comment.