WebSocket과 Firestore를 사용해서 springboot에서 채팅을 구현해보고자 했다. (WebSocket은 Postman으로 테스트)
가장 먼저 gradle에 implementation 'com.google.firebase:firebase-admin:9.2.0' implementation 'org.springframework.boot:spring-boot-starter-websocket'firebase와 websocket을 추가하여주었다.
이 후 config를 설정해 주어야 하는데 먼저 websocket 부터 구현을 해보자
@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "ws/messenger").setAllowedOrigins("*");
}
}
나는 ws/messenger로 통신을 할 예정, 그 다음 WebSocketHandler를 작성해주자
@Component
@RequiredArgsConstructor
@Slf4j
public class WebSocketHandler extends TextWebSocketHandler {
private final ObjectMapper objectMapper;
private final MessengerService messengerService;
// firestor에서 chat 컬렉션을 사용할 것이다.
private static final String COLLECTION_NAME = "chat";
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
log.info("{}", payload);
ChatMessageDto chatMessageDto = objectMapper.readValue(payload, ChatMessageDto.class);
ChatRoom chatRoom = messengerService.findRoomById(chatMessageDto.getSessionId());
chatRoom.handlerAction(session, chatMessageDto, messengerService);
// 아래쪽으로는 firestore에 저장하는 부분
FirebaseDto firebaseDto = new FirebaseDto(chatMessageDto.getChatId(), chatMessageDto.getMemberId(), chatMessageDto.getContent());
Firestore firestore = FirestoreClient.getFirestore();
ApiFuture<DocumentReference> add = firestore.collection(COLLECTION_NAME).add(firebaseDto);
System.out.println(add.get().toString());
}
}
chatMessageDto는 자신이 입력받을 객체를 생성하면 되고
@Getter
public class ChatRoom {
private String sessionId; // 방 번호
private Set<WebSocketSession> sessions = new HashSet<>();
@Builder
public ChatRoom(String chatId) {
this.sessionId = chatId;
}
// 액션이 일어나면 실행
public void handlerAction(WebSocketSession session, ChatMessageDto chatMessageDto, MessengerService messengerService) {
sessions.add(session);
sendMessage(chatMessageDto, messengerService);
}
// 액션이 생기면 메시지를 보낸다.
private <T> void sendMessage(T message, MessengerService messengerService) {
sessions.parallelStream()
.forEach(session -> messengerService.sendMessage(session, message));
}
}
이렇게 액션이 발생하면 실행할 부분을 작성해준다.
@Service
@RequiredArgsConstructor
@Slf4j
public class MessengerServiceImpl implements MessengerService{
private final ObjectMapper objectMapper;
private Map<String, ChatRoom> chatRooms;
private final ChatRepository chatRepository;
private final MemberRepository memberRepository;
private final AttendRepository attendRepository;
@PostConstruct
private void init() {
chatRooms = new LinkedHashMap<>();
}
// 처음에 세션을 생성하는 부분
@Override
public CreateMessengerResponse createRoom(CreateMessengerRequest createMessengerRequest) {
String email = createMessengerRequest.getMyMemberEmail();
Member myMember = memberRepository.findByEmail(email).orElseThrow();
Long myMemberId = myMember.getId();
String myId = myMemberId.toString();
String yourId = createMessengerRequest.getYourMemberId().toString();
Member yourMember = memberRepository.findById(createMessengerRequest.getYourMemberId()).orElseThrow();
String sessionId = myId + yourId;
ChatRoom chatRoom = ChatRoom.builder()
.chatId(sessionId)
.build();
chatRooms.put(sessionId, chatRoom);
Chat chat = new Chat(sessionId);
chatRepository.save(chat);
Chat findChat = chatRepository.findBySessionId(sessionId);
attendRepository.save(new Attend(myMember, findChat));
attendRepository.save(new Attend(yourMember, findChat));
return new CreateMessengerResponse("생성 성공", findChat.getId());
}
@Override
public <T> void sendMessage(WebSocketSession session, T message) {
try {
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
} catch (IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
나는 JPA를 사용해서 Attend와 Chat에 데이터를 넣어놨다. 원래 세션 아이디를 UUID를 통해 암호화를 하는게 일반적이지만 일단 사용자 아이디를 합친 것으로 만들어 db에 저장해 놓았다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/messenger")
public class MessengerController {
private final MessengerService messengerService;
@PostMapping("")
public CreateMessengerResponse createRoom(@RequestBody CreateMessengerRequest createMessengerRequest) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String email = authentication.getName();
createMessengerRequest.setMyMemberEmail(email);
return messengerService.createRoom(createMessengerRequest);
}
}
방을 생성하여 세션을 만들고 만들어진 세션으로

위의 주소와 같이 입력하고 연결한 후 메시지를 주고받으면 된다.
💡 이 때 현재 내 서버는 https서버이기 때문에 ws:// 가 아닌 wss:// 로 해야한다.
이 후 같은 세션에 있는 사람들끼리 통신이 잘 되는것을 볼 수 있다.
이렇게 주고받은 데이터를 firestore에 저장하는데 아까 웹소켓 핸들러에서
FirebaseDto firebaseDto = new FirebaseDto(chatMessageDto.getChatId(), chatMessageDto.getMemberId(), chatMessageDto.getContent());
Firestore firestore = FirestoreClient.getFirestore();
ApiFuture<DocumentReference> add = firestore.collection(COLLECTION_NAME).add(firebaseDto);
System.out.println(add.get().toString());
이 코드를 통해 firestore에 저장하면

이렇게 저장이 잘 되는것을 볼 수 있다.
💡 나중에 Date타입을 추가해서 현재 날짜를 추가하였다.
물론 가져오는것도 잘 된다.
좋은 글 감사합니다. 자주 올게요 :)