Skip to content

Commit

Permalink
Merge pull request #95 from TeamSynergyy/feature/websocket
Browse files Browse the repository at this point in the history
#94 WebSocket 을 활용한 채팅 기능
  • Loading branch information
rivkode authored May 8, 2024
2 parents 8ee24bb + 0721282 commit 0a03012
Show file tree
Hide file tree
Showing 18 changed files with 560 additions and 0 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'

// redis
// implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Expand Down Expand Up @@ -53,6 +54,9 @@ dependencies {
// database
runtimeOnly 'com.mysql:mysql-connector-j'

// mongo db
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

// swagger
// implementation 'org.springdoc:springdoc-openapi-ui:2.0.4'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.seoultech.synergybe.domain.chat;

import com.seoultech.synergybe.domain.chat.handler.ChatHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final ChatHandler chatHandler;

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatHandler, "/ws")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.setAllowedOrigins("*");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.seoultech.synergybe.domain.chat.controller;

import com.seoultech.synergybe.domain.chat.domain.ChatMessage;
import com.seoultech.synergybe.domain.chat.service.ChatMessageService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/chat")
@RequiredArgsConstructor
public class ChatMessageController {
private final ChatMessageService chatMessageService;

@GetMapping("/{chatRoomId}")
public List<ChatMessage> getChatList(@PathVariable("chatRoomId") Long chatRoomId) {

return chatMessageService.getChatListByChatRoomId(chatRoomId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.seoultech.synergybe.domain.chat.controller;

import com.seoultech.synergybe.domain.chat.domain.ChatRoom;
import com.seoultech.synergybe.domain.chat.dto.request.CreateChatRoomRequest;
import com.seoultech.synergybe.domain.chat.dto.response.GetChatRoomResponse;
import com.seoultech.synergybe.domain.chat.service.ChatRoomService;
import com.seoultech.synergybe.system.config.login.LoginUser;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/rooms")
public class ChatRoomController {
private final ChatRoomService chatRoomService;

@PostMapping
public ResponseEntity<Void> createChatRoom(@Valid @RequestBody CreateChatRoomRequest request) {
chatRoomService.createRoom(request);

return ResponseEntity.noContent().build();
}

@GetMapping
public ResponseEntity<List<GetChatRoomResponse>> getChatRoomList(@LoginUser String userId) {

return ResponseEntity.status(HttpStatus.OK).body(chatRoomService.getChatRoomsByUserId(userId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.seoultech.synergybe.domain.chat.domain;

import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.time.LocalDateTime;

@Document(collection = "chat_message")
@Getter
public class ChatMessage {
@Id
@Field("_id")
private String id;

@Indexed
private Long chatRoomId;

private String message;

private String userId;

private ChatType chatType;

private String imageName;

private String imageUrl;
private LocalDateTime createAt;

@Builder
public ChatMessage(Long chatRoomId, String message, String userId, ChatType chatType, String imageName, String imageUrl,
LocalDateTime createAt) {
this.chatRoomId = chatRoomId;
this.message = message;
this.userId = userId;
this.chatType = chatType;
this.imageName = imageName;
this.imageUrl = imageUrl;
this.createAt = createAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.seoultech.synergybe.domain.chat.domain;

import com.seoultech.synergybe.domain.user.User;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity(name = "chat_room")
@Getter
@NoArgsConstructor
public class ChatRoom {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "chat_room_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "create_user_id", nullable = false)
private User createUser;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "attend_user_id", nullable = false)
private User attendUser;

@Column(name = "name")
private String name;


@Builder
public ChatRoom(User createUser, User attendUser, String name) {
this.createUser = createUser;
this.attendUser = attendUser;
this.name = name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.seoultech.synergybe.domain.chat.domain;

public enum ChatType {
ENTER,
TEXT,
IMAGE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.seoultech.synergybe.domain.chat.dto.request;

import com.seoultech.synergybe.domain.chat.domain.ChatType;
import jakarta.validation.constraints.NotBlank;

public record ChatMessageRequest(
@NotBlank
Long chatRoomId,

@NotBlank
String userId,

@NotBlank
String message,

@NotBlank
ChatType chatType,

String imageName,
String imageUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.seoultech.synergybe.domain.chat.dto.request;

import jakarta.validation.constraints.NotBlank;

public record CreateChatRoomRequest(
@NotBlank(message = "채팅방 생성 유저 ID는 필수값입니다.")
String createUserId,
@NotBlank(message = "채팅방 참석 유저 ID는 필수값입니다.")
String attendUserId,
String roomName

) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.seoultech.synergybe.domain.chat.dto.response;

public record GetChatRoomResponse(
Long chatRoomId,
String chatRoomName
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.seoultech.synergybe.domain.chat.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.seoultech.synergybe.domain.chat.domain.ChatType;
import com.seoultech.synergybe.domain.chat.dto.request.ChatMessageRequest;
import com.seoultech.synergybe.domain.chat.service.ChatMessageService;
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.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@Component
//@RequiredArgsConstructor
public class ChatHandler extends TextWebSocketHandler {
// private static List<WebSocketSession> webSocketSessions = new ArrayList<>();
private WebSocketSessionMap webSocketSessionMap; // 채팅방별 세션리스트 모음
private ObjectMapper objectMapper;
private ChatMessageService chatMessageService;



// 채팅 핸들러 생성
public ChatHandler(ObjectMapper objectMapper, ChatMessageService chatMessageService) {
this.webSocketSessionMap = new WebSocketSessionMap();
this.objectMapper = objectMapper;
this.chatMessageService = chatMessageService;
}

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// webSocketSessions.add(session);
Long chatRoomId = webSocketSessionMap.getKeyFromSession(session);

// 여기서 request를 어떻게 알지 ?
if (chatRoomId == null) {
// 채팅방 생성
// chatRoomService.createRoom();
}


// log.info("session add : " + session.getId());
}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payLoad = message.getPayload();
log.info("pay load {}", payLoad);

// 채팅 Dto로 변환
ChatMessageRequest chatMessageRequest = objectMapper.readValue(payLoad, ChatMessageRequest.class);
log.info("session {}", chatMessageRequest.toString());

// payload에 chatroomId 가져옴
Long chatRoomId = chatMessageRequest.chatRoomId();

Map<Long, WebSocketSessionList> chatRoomSessionMap = webSocketSessionMap.getWebsocketListHashMap();


// 현재 채팅방의 세션이 존재하는지 체크
if (!chatRoomSessionMap.containsKey(chatRoomId)) {
// 만약 없다면 세션 생성
WebSocketSessionList webSocketSessionList = WebSocketSessionList.builder()
.webSocketSessions(new ArrayList<>())
.build();

// 생성한 세션에 채팅방 Id와 세션을 Map에 저장
chatRoomSessionMap.put(chatRoomId, webSocketSessionList);
}

// 해당 채팅방에 해당하는 세션을 Map에서 가져옴
WebSocketSessionList webSocketSessionList = chatRoomSessionMap.get(chatRoomId);

// 만약 입장하는 경우라면 (Type이 Enter 라면)
if (chatMessageRequest.chatType().equals(ChatType.ENTER)) {
// afterConnectionEstablished(session);
// 현재 들어온 세션을 해당 채팅방 세션리스트에 추가
webSocketSessionList.getWebSocketSessions().add(session);
log.info("session add / session Id : {}", session.getId());
}

// 만약 텍스트를 보낸다면
if (chatMessageRequest.chatType().equals(ChatType.TEXT)) {
// 채팅 전송
sendAndSaveMessageToChatRoom(chatMessageRequest, webSocketSessionList);
}

}



@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
Long chatRoomId = webSocketSessionMap.getKeyFromSession(session);



// 얘가 호출되니까 여기서 removeClosedSession을 호출해야지
// 그런데 roomId로 해당하는 채팅방의 채팅세션리스트들을 가져와야하는데 ?
// 그 내용을 넣어줄 수 있을까 ?

// 채팅방 리스트 가져오기 내가 가지고 있는 정보는 현재 session 정보임
List<WebSocketSession> webSocketSessions = getSessionListByChatRoomId(chatRoomId);
webSocketSessions.remove(session);

log.info("session remove / session Id : {}", session.getId());
}

private List<WebSocketSession> getSessionListByChatRoomId(Long chatRoomId) {
return webSocketSessionMap.getWebsocketListHashMap().get(chatRoomId).getWebSocketSessions();
}

private void sendAndSaveMessageToChatRoom(ChatMessageRequest chatMessageRequest, WebSocketSessionList webSocketSessionList) {
for (WebSocketSession session : webSocketSessionList.getWebSocketSessions()) {
sendMessage(session, chatMessageRequest.message());
saveMessage(chatMessageRequest);
}

// webSocketSessionList.getWebSocketSessions().parallelStream().forEach(sess -> sendMessage(sess, chatMessageRequest.message()));

}

private void saveMessage(ChatMessageRequest chatMessageRequest) {
chatMessageService.saveChat(chatMessageRequest);
}

private <T> void sendMessage(WebSocketSession session, T message) {
try {
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.seoultech.synergybe.domain.chat.handler;

import lombok.Builder;
import lombok.Getter;
import org.springframework.web.socket.WebSocketSession;

import java.util.List;

@Getter
public class WebSocketSessionList {
private List<WebSocketSession> webSocketSessions;

@Builder
public WebSocketSessionList(List<WebSocketSession> webSocketSessions) {
this.webSocketSessions = webSocketSessions;
}

// 해당 세션을 포함하는지 확인하는 메서드
public boolean contains(WebSocketSession session) {
return webSocketSessions.contains(session);
}
}
Loading

0 comments on commit 0a03012

Please sign in to comment.