WebSocket과 Firestore를 사용한 채팅

BbongGu·2023년 7월 29일

SpringFramework

목록 보기
3/3

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타입을 추가해서 현재 날짜를 추가하였다.

물론 가져오는것도 잘 된다.

profile
개발새내기

1개의 댓글

comment-user-thumbnail
2023년 7월 29일

좋은 글 감사합니다. 자주 올게요 :)

답글 달기