Mongo Db - Web Socket - STOMP Chatting Process (웹소캣으로 채팅방 구현하기 )

SUUUI·2025년 2월 22일
0

Chatting Process

  1. 대화 상대를 선택하면 채팅룸이 만들어지는 api 호출 (createChatRoom) + 구독 주소를 식별하는 roomId 생성됨

  2. stomp 헤더의 로그인인 유저의 토큰을 담아 서버에 전송

  3. 서버는 stomp 헤더에 담긴 토큰으로 유저 인증 처리 후 web socket 연결 허용

  4. 유저와 대화 상대는 해당 채팅룸을 구독하는 상태가 됨

  5. 유저가 메세지를 보내면 stomp 프로토콜의 body 에 메세지가 담겨서 서버( /topic/chat/message/{roomId}" 주소 )에 전달됨 ( STOMP )

  6. 서버에 온 메세지는 roomId 에 맞는 chatRoom 이 있는지 조회 후 도큐먼트로 변환 하여 mongo db 에 roomId와 함께 저장

  7. roomId에 해당하는 chatMessage 들을 List 화 하여 클라이언트에 전달( http )

WebSocket 과 mongoDb , STOMP 를 사용하기 위한 기본 의존성

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

    //mongodb 드라이버
    implementation 'org.mongodb:mongodb-driver-sync:4.11.1'

    implementation 'org.mongodb:mongodb-driver-core:4.11.1'


    implementation 'org.mongodb:bson:4.11.1'
    //webSocket 구현을 위한 기본적인 패키지
    implementation 'org.springframework.boot:spring-boot-starter-websocket'

    //STOMP(Simple Text Oriented Messaging Protocol) 구현체
    implementation 'org.webjars:stomp-websocket:2.3.4'

    //WebSocket을 지원하지 않는 브라우저를 위한 대체 솔루션
    implementation 'org.webjars:sockjs-client:1.5.1'
    

Config 파일(설정파일)

package com.himedia.luckydokiapi.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.himedia.luckydokiapi.interceptor.WebSocketAuthChannelInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

import java.text.SimpleDateFormat;
import java.util.List;

@RequiredArgsConstructor
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {


    @Autowired
    private final WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/queue"); //구독 주소
        config.setApplicationDestinationPrefixes("/app"); // 메시지 발행 경로
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp")
                .setAllowedOrigins("http://localhost:3000")
                .withSockJS();  //필수 설정값
    }

    //인터셉터를 통한 인증처리 ,jwt 토큰으로 인증
    //stomp 헤더에서 토큰을 꺼내어 인증 처리 
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(webSocketAuthChannelInterceptor);

    }

    @Override
    public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        converter.setObjectMapper(objectMapper);
        messageConverters.add(converter);
        return false;
    }


}

ChatController

package com.himedia.luckydokiapi.domain.chat.controller;

import com.himedia.luckydokiapi.domain.chat.dto.ChatHistoryDTO;
import com.himedia.luckydokiapi.domain.chat.dto.ChatMessageDTO;
import com.himedia.luckydokiapi.domain.chat.dto.ChatRoomDTO;
import com.himedia.luckydokiapi.domain.chat.service.ChatService;
import com.himedia.luckydokiapi.exception.NotAccessChatRoom;
import com.himedia.luckydokiapi.security.MemberDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

@RestController
@RequestMapping("/api/chat")
@RequiredArgsConstructor
@Slf4j
public class ChatController {
    private final ChatService chatService;
    private final SimpMessageSendingOperations messagingTemplate;


    //메세지를 전송하는 api
    //양방향 통신이므로 return 값이 따로 없고 messagingTemplate.convertAndSend 를 통해 stomp 형식으로 클라이언트에 전송된다
    @MessageMapping("/message")
    public void handleMessage(ChatMessageDTO chatMessageDTO, StompHeaderAccessor stompHeaderAccessor) {
        log.info("chatMessageDTO {}", chatMessageDTO);
        Authentication authentication = (Authentication) stompHeaderAccessor.getUser();
        MemberDTO member = (MemberDTO) authentication.getPrincipal();
        // roomId가 null 인 경우 (첫 메시지) 채팅방 생성 후 메시지 저장
        // 생성된 채팅방 ID 설정

        ChatMessageDTO savedMessage = chatService.saveMessage(chatMessageDTO, member.getEmail());
        messagingTemplate.convertAndSend("/topic/chat/message/" + chatMessageDTO.getRoomId(), savedMessage);
        //구독자에게 메세지 전송
        //클라이언트 구독주소(destination) + 채팅방 번호 + 전송되고 mongodb 애 저장될 메세지(payload)
        //mu sql 테이블에도 insert
        // /topic/chat/room/{roomId} 에게 전송

        //해당 룸 아이디를 구독하고 있는 유저에게 메세지 알림 전송
        Set<String> roomMembers = chatService.getRoomMembers(chatMessageDTO.getRoomId());
        for (String roomMember : roomMembers) {
            if (!roomMember.equals(member.getEmail())) { // 발신자가 아니라면 ?
                //roomMember : 알림을 받을 사람
//                MessageNotificationDTO notificationDTO = MessageNotificationDTO.builder()
//                        .notificationMessage("새 메세지가 도차했습니다")
//                        .roomId(chatMessageDTO.getRoomId())
//                        .timestamp(chatMessageDTO.getSendTime())
//                        .email(roomMember)
//                        .isRead(false)
//                        .build();

                messagingTemplate.convertAndSendToUser(roomMember, "/queue/notification/", chatMessageDTO);
                log.info("roomMember {} , 알림 수신 완료 ", roomMember);
            } ///queue 는 1;1 개인 알림 메세지에 사용됨
            // 프론트 단에서는 /queue/notification/ 앞에 user (roomMember)가 추가됨
        }

    }


    //roomId와 email 로 채팅 대화내역  상세 조회
    @GetMapping("/history/{roomId}")
    public ResponseEntity<List<ChatHistoryDTO>> getChatHistory(@AuthenticationPrincipal final MemberDTO memberDTO,
                                                               @PathVariable Long roomId) {
        log.info("roomId {}", roomId);
        log.info("memberDTO {}", memberDTO);
        return ResponseEntity.ok(chatService.getChattingHistory(memberDTO.getEmail(), roomId));
    }

    //유저의 채팅방 리스트 보기
    @GetMapping("/history")
    public ResponseEntity<List<ChatRoomDTO>> getChatRooms(@AuthenticationPrincipal final MemberDTO memberDTO) {
        log.info("memberDTO {}", memberDTO);
        return ResponseEntity.ok(chatService.findAllChatRooms(memberDTO.getEmail()));
    }

    //확인 완료
    //초기 채팅방 생성
    @PostMapping
    public ResponseEntity<ChatRoomDTO> createChatRoom(@RequestBody ChatRoomDTO chatRoomDTO, @AuthenticationPrincipal final MemberDTO memberDTO) {
        log.info("chatRoomDTO {}", chatRoomDTO);
        log.info("memberDTO {}", memberDTO);
        //로그인 한 회원 (보낸 회원의 이메일을 회원 필드에 주입)
        chatRoomDTO.setSender(memberDTO.getEmail());
        if (chatRoomDTO.getId() != null) {
            throw new NotAccessChatRoom("이미 존재하는 채팅방 입니다 ");
        }
        ChatRoomDTO newRoom = chatService.createChatRoom(chatRoomDTO, memberDTO.getEmail());
        return ResponseEntity.ok(newRoom);
    }

    //안읽은 알림 리스트 /history 와 다른점 : isRead 값 false 인걸로 조회
//    @GetMapping("/notifications")
//    public ResponseEntity<List<MessageNotificationDTO>> getMessageNotifications(@AuthenticationPrincipal final MemberDTO memberDTO) {
//        log.info("memberDTO {}", memberDTO);
//        return ResponseEntity.ok(chatService.getUnreadNotifications(memberDTO.getEmail()));
//    }

    //읽음 상태 바꾸기
//    @PatchMapping("/{roomId}")
//    public ResponseEntity<?> changeReadStatus(@AuthenticationPrincipal final MemberDTO memberDTO, @PathVariable Long roomId) {
//        log.info("notificationId {}", roomId);
//        chatService.changeRead(memberDTO.getEmail(), roomId);
//        return ResponseEntity.ok().build();
//    }

    //대화방 나가기
    @DeleteMapping("/{roomId}")
    public ResponseEntity<Long> deleteChatRoom(@AuthenticationPrincipal final MemberDTO memberDTO, @PathVariable Long roomId) {
        log.info("memberDTO {}", memberDTO);
        Long deleteRoomId = chatService.deleteChatRoom(memberDTO.getEmail(), roomId);
        return ResponseEntity.ok(deleteRoomId);
    }
//    @GetMapping
//    public ResponseEntity<Boolean> existsChatRoom(@AuthenticationPrincipal final MemberDTO memberDTO, @RequestParam Long shopId) {
//        log.info("memberDTO {}", memberDTO);
//        log.info("shopId {}", shopId);
//        return ResponseEntity.ok(chatService.findChatRoom(memberDTO.getEmail(), shopId));
//    }

ChatService(interface)

package com.himedia.luckydokiapi.domain.chat.service;

import com.himedia.luckydokiapi.domain.chat.document.ChatMessage;
import com.himedia.luckydokiapi.domain.chat.dto.ChatHistoryDTO;
import com.himedia.luckydokiapi.domain.chat.dto.ChatMessageDTO;
import com.himedia.luckydokiapi.domain.chat.dto.ChatRoomDTO;

import com.himedia.luckydokiapi.domain.chat.entity.ChatRoom;
import com.himedia.luckydokiapi.domain.member.entity.Member;
import com.himedia.luckydokiapi.domain.shop.entity.Shop;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

public interface ChatService {
    ChatMessageDTO saveMessage(ChatMessageDTO chatMessageDTO, String email);

    List<ChatHistoryDTO> getChattingHistory(String email, Long roomId);

    ChatRoomDTO createChatRoom(ChatRoomDTO chatRoomDTO, String email);

    List<ChatRoomDTO> findAllChatRooms(String email);

    default ChatMessage convertToDocument(ChatMessageDTO chatMessageDTO, Member member, Shop shop, Long chatRoomId) {
        return ChatMessage.builder()
                .roomId(chatRoomId)
                .email(member.getEmail())
                .shopId(shop.getId())
                .message(chatMessageDTO.getMessage())
                .sendTime(chatMessageDTO.getSendTime())
                .build();

    }

    //클라이언트에 보낼 채팅 메세지 기록
    default ChatMessageDTO convertToDTO(ChatMessage chatMessage,String sender) {
        return ChatMessageDTO.builder()
                .roomId(chatMessage.getRoomId())
                .shopId(chatMessage.getShopId())
                .sender(sender)
                .email(chatMessage.getEmail())
                .message(chatMessage.getMessage())
                .sendTime((chatMessage.getSendTime()))
                .build();
    }

    default ChatRoom createChatRoomEntity(Member member, Shop shop, Long chatRoomId) {
        return ChatRoom.builder()
                .id(chatRoomId) //null 이면 자동으로 생성된다
                .shop(shop)
                .member(member)
                .shopImage(shop.getImage())
                .lastMessageTime(LocalDateTime.now())
                .build();
    }

    default ChatRoomDTO convertToChatRoomDTO(ChatRoom chatRoom, String sender, String message) {
        return ChatRoomDTO.builder()
                .id(chatRoom.getId())
                .sender(sender)
                .shopId(chatRoom.getShop().getId())
                .shopImage(chatRoom.getShopImage())
                .shopName(chatRoom.getShop().getMember().getNickName())
                .lastMessage(message == null ? null : message)
                .lastMessageTime(chatRoom.getLastMessageTime())
                .build();
    }



    Set<String> getRoomMembers(Long roomId);


//    List<MessageNotificationDTO> getUnreadNotifications(String email);


//    void changeRead(String email, Long roomId);

    Long deleteChatRoom(String email, Long roomId);
//    Boolean findChatRoom(String email, Long shopId);
}

ChatServiceImpl

package com.himedia.luckydokiapi.domain.chat.service;

import com.himedia.luckydokiapi.domain.chat.document.ChatMessage;
import com.himedia.luckydokiapi.domain.chat.dto.ChatHistoryDTO;
import com.himedia.luckydokiapi.domain.chat.dto.ChatMessageDTO;
import com.himedia.luckydokiapi.domain.chat.dto.ChatRoomDTO;
import com.himedia.luckydokiapi.domain.chat.entity.ChatRoom;
import com.himedia.luckydokiapi.domain.chat.repository.ChatMessageRepository;
import com.himedia.luckydokiapi.domain.chat.repository.ChatRoomRepository;
import com.himedia.luckydokiapi.domain.member.entity.Member;
import com.himedia.luckydokiapi.domain.member.repository.MemberRepository;
import com.himedia.luckydokiapi.domain.shop.entity.Shop;
import com.himedia.luckydokiapi.domain.shop.repository.ShopRepository;
import com.himedia.luckydokiapi.exception.NotAccessChatRoom;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.himedia.luckydokiapi.domain.member.enums.MemberRole.SELLER;
import static com.himedia.luckydokiapi.domain.member.enums.MemberRole.USER;

@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class ChatServiceImpl implements ChatService {

    private final MongoTemplate mongoTemplate;
    private final ChatRoomRepository chatRoomRepository;
    private final ChatMessageRepository chatMessageRepository;
    private final MemberRepository memberRepository;
    private final ShopRepository shopRepository;


    @Override
    public ChatMessageDTO saveMessage(ChatMessageDTO chatMessageDTO, String email) {
        log.info("chatMessageDTO: {}", chatMessageDTO);
        //shop 조회
        Shop shop = getShop(chatMessageDTO.getShopId());
        //구매자
        Member member = getMember(email);

        return this.saveMongoAndReturnChatDTO(chatMessageDTO, member, shop);
    }


    //채팅방 아이디로 mu sql db와 mongo db 를 조회 한뒤 아이디에 해당하는 채팅방 메세지들을 전부 가져온다
    @Transactional(readOnly = true)
    @Override
    public List<ChatHistoryDTO> getChattingHistory(String email, Long roomId) {
        log.info("getChattingHistory : {}", email);
        log.info("roomId: {}", roomId);
        //회원 확인
        Member member = getMember(email);
        //채팅방이 있는지 확인
        ChatRoom chatRoom = getChatroom(roomId);

        // 파라미터로 온 이메일이 채팅방의 참여자 인지 확인
        //shop 의 셀러이거나 , 구매자의 이메일 둘 다 아니라면 ?
        if (!chatRoom.getShop().getMember().equals(member) && !chatRoom.getMember().equals(member)) {
            throw new NotAccessChatRoom("해당 채팅방의 참여자가 아닙니다.");
        }
        return getChatMessage(chatRoom);
    }


    @Override
    public ChatRoomDTO createChatRoom(ChatRoomDTO chatRoomDTO, String email) {
        //회원 조회
        log.info("createChatRoom : {}", chatRoomDTO);
        Member member = getMember(email);
        // shop 조회
        Shop shop = getShop(chatRoomDTO.getShopId());
        // sql ChatRoom 엔티티에 저장, id는 자동으로 생성되므로 null 로 전달

        ChatRoom chatRoom = createChatRoomEntity(member, shop, null);
        //새로운 chat room 생성 + 저장
        //대화 상대방
        String sender = this.getRoomMembers(chatRoom.getId()).stream()
                .filter(roomMember -> !roomMember.equals(member.getEmail()))
                .findFirst()
                .orElse(null);

        chatRoomRepository.save(chatRoom);
        //다시 dto로 변환하여 리턴
        return this.convertToChatRoomDTO(chatRoom, sender, null);

    }

    //룸 아이디로 채팅방 상세 조회
    private List<ChatHistoryDTO> getChatMessage(ChatRoom chatRoom) {
        //roomId 로 mongo db 메세지 내역을 조회
        List<ChatMessage> chatMessages = chatMessageRepository.findByRoomIdOrderBySendTimeAsc(chatRoom.getId());

        if (chatMessages.isEmpty()) {
            return Collections.emptyList();
        }

        return chatMessages.stream()
                .map(chatMessage -> ChatHistoryDTO.builder()
                        .roomId(chatRoom.getId())
                        .email(chatMessage.getEmail())
                        .shopId(chatRoom.getShop().getId())
                        .shopImage(chatRoom.getShop().getImage())
                        .message(chatMessage.getMessage())
                        .lastMessageTime(LocalDateTime.from(chatMessage.getSendTime()))
                        .build())
                .toList();
    }

    @Transactional(readOnly = true)
    @Override
    public List<ChatRoomDTO> findAllChatRooms(String email) {
        log.info("findAllChatRooms : {}", email);
        Member member = getMember(email);

        //로그인한 회원의 메세지 룸 가져오기
        List<ChatRoom> chatRoomList = chatRoomRepository.findByMemberOrShopMember(member.getEmail());


        List<Long> roomIds = chatRoomList.stream().map(ChatRoom::getId).toList();
        //메세지룸의 룸 아이디들을 가져오기
        List<ChatMessage> lastMessages = chatMessageRepository.findLastMessagesByRoomIds(roomIds);
        //룸 아이디들로 마지막 메세지 찾기
//아이디 리스트 들 + 메세지 리스트 하나씩 빼서 convertToChatRoomDTO로 변환 시키고 가시 list 처리
        Map<Long, ChatMessage> lastMessageMap = lastMessages.stream()
                .filter(msg -> msg.getRoomId() != null)
                .collect(Collectors.toMap(
                        ChatMessage::getRoomId,
                        message -> message,
                        (existing, replacement) -> replacement  // 혹시 중복이 있을 경우 처리
                ));

        return chatRoomList.stream().map(chatRoom -> {
            String sender = this.getRoomMembers(chatRoom.getId()).stream()
                    .filter(roomMember -> !roomMember.equals(member.getEmail()))
                    .findFirst()
                    .orElse(null);
            ChatMessage lastMessage = lastMessageMap.get(chatRoom.getId());
            return convertToChatRoomDTO(chatRoom, sender,
                    lastMessage != null ? lastMessage.getMessage() : null);
        }).toList();
    }

    @Override
    public Set<String> getRoomMembers(Long roomId) {
        return chatRoomRepository.findByChatMembers(roomId);
    }


//    @Transactional
//    @Override  // 읽음 상태 바꾸기
//    public void changeRead(String email, Long roomId) {
//        Member member = getMember(email);
//        ChatRoom chatRoom = getChatroom(roomId);
//        chatMessageRepository.modifyIsRead(chatRoom.getId(), member.getEmail());
//    }

    @Override
    public Long deleteChatRoom(String email, Long roomId) {
        Member member = getMember(email);
        chatRoomRepository.deleteByRoomIdAndEmail(member.getEmail(), roomId);
        return roomId;
    }

//    @Override
//    @Transactional(readOnly = true)//안읽은 알림 리스트
//    public List<MessageNotificationDTO> getUnreadNotifications(String email) {
//        Member member = getMember(email);
//        //안읽은 채티방 리스트에 참여한 멤버들을 찾기
//        // 해당 사용자의 채팅방들을 가져옴
//        List<ChatRoom> chatRooms = chatRoomRepository.findByMemberOrShopMember(member.getEmail());
//        List<Long> roomIds = chatRooms.stream().map(ChatRoom::getId).toList();
//        //메세지룸의 룸 아이디들을 가져오기
//        List<ChatMessage> lastMessages = chatMessageRepository.findLastMessagesAndIsReadFalseByRoomIds(roomIds);
//        //룸 아이디들로 마지막 메세지 찾기
////아이디 리스트 들 + 메세지 리스트 하나씩 빼서 convertToChatRoomDTO로 변환 시키고 가시 list 처리
//        Map<Long, ChatMessage> lastMessageMap = lastMessages.stream()
//                .filter(msg -> msg.getRoomId() != null)
//                .collect(Collectors.toMap(
//                        ChatMessage::getRoomId,
//                        message -> message,
//                        (existing, replacement) -> replacement  // 혹시 중복이 있을 경우 처리
//                ));
//        // 각 채팅방의 마지막 메시지 발신자 정보와 함께 DTO로 변환
//        return chatRooms.stream()
//                .map(room -> {
//                    // 채팅방의 마지막 메시지 발신자 찾기
//                    String sender = this.getRoomMembers(room.getId()).stream()
//                            .filter(roomMember -> !roomMember.equals(member.getEmail()))
//                            .findFirst()
//                            .orElse(null);
//                    ChatMessage lastMessage = lastMessageMap.get(room.getId());
//                    return MessageNotificationDTO.builder()
//                            .roomId(room.getId())
//                            .sender(sender)  // 발신자 설정
//                            .email(member.getEmail())  // 수신자(현재 사용자)
//                            .notificationMessage(lastMessage.getMessage())
//                            .timestamp(room.getLastMessageTime())
//                            .shopImages(room.getShop().getImage())
//                            .isRead(false)
//                            .build();
//                })
//                .toList();
//    }


    private Boolean getSellerAndBuyer(String email) {
        Member member = this.getMember(email);
        if (member.getMemberRoleList().contains(SELLER)) {
            return true; //핀매자
        } else if (member.getMemberRoleList().contains(USER)) {
            return false;
        }//구매자
        throw new NotAccessChatRoom("채팅 권한이 없습니다");
    }


    private Member getMember(String email) {
        return memberRepository.getWithRoles(email).orElseThrow(() -> new EntityNotFoundException("Member with email " + email + " not found"));
    }

    private ChatRoom getChatroom(Long roomId) {
        return chatRoomRepository.findById(roomId).orElseThrow(() -> new EntityNotFoundException("해당 아이디의 채팅룸은 존재하지 않습니다 "));
    }

    private Shop getShop(Long shopId) {
        return shopRepository.findById(shopId).orElseThrow(() -> new EntityNotFoundException("shop with id " + shopId + " not found"));
    }


    private ChatMessageDTO saveMongoAndReturnChatDTO(ChatMessageDTO chatMessageDTO, Member member, Shop shop) {
        //채팅룸의 아이디로 엔티티 조회
        ChatRoom chatRoom = getChatroom(chatMessageDTO.getRoomId());
        String sender = this.getRoomMembers(chatRoom.getId()).stream()
                .filter(roomMember -> !roomMember.equals(member.getEmail()))
                .findFirst()
                .orElse(null);
        // document 변환
        ChatMessage chatMessage = this.convertToDocument(chatMessageDTO, member, shop, chatRoom.getId());
        //mongodb 에 저장된 document
        mongoTemplate.save(chatMessage);
        //저장된 document 를 다시 dto 로 변환하여 전달
        return this.convertToDTO(chatMessage, sender);
    }


}

ChatMessageRepository

package com.himedia.luckydokiapi.domain.chat.repository;

import com.himedia.luckydokiapi.domain.chat.document.ChatMessage;
import com.himedia.luckydokiapi.domain.chat.entity.ChatRoom;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface ChatMessageRepository extends MongoRepository<ChatMessage, String> {
//jpa 레포지토리 처럼 이름규칙에 따라 자동으로 쿼리 생성

    //채팅방 id 로 조회하고 최신 시간순으로 정렬하기
    List<ChatMessage> findByRoomIdOrderBySendTimeAsc(Long roomId);

//해당 유저가 보낸 메세지들을 찾기 
    List<ChatMessage> findByEmail(String email);


//해당 룸 아이디 들의 마지막 메세지들을 찾기 
    @Aggregation(pipeline = {
            "{ $match: { 'roomId': { $in: ?0 }}}",
            "{ $sort: { 'sendTime': -1 } }",
            "{ $group: { " +
                    "'_id': '$roomId'," +
                    "'document': { $first: '$$CURRENT' } " +  // 현재 문서 전체를 가져옴
                    "} }",
            "{ $replaceRoot: { 'newRoot': '$document' } }"  // 원래 문서 구조로 복원
    })
    List<ChatMessage> findLastMessagesByRoomIds(@Param("roomIds") List<Long> roomIds);

ChatRoomRepository

package com.himedia.luckydokiapi.domain.chat.repository;

import com.himedia.luckydokiapi.domain.chat.entity.ChatRoom;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Set;


public interface ChatRoomRepository extends JpaRepository<ChatRoom, Long> {

   //해당 이메일 주인이 먼저보낸 채팅방 찾기 
   @Query("select e from ChatRoom e where e.member.email=:email")
    List<ChatRoom> findByEmail(@Param("email") String email);

//shop의 입장 , 일반 유저 입장 둘 중 하나에 해당되는 채팅방 찾기 
    @Query("SELECT c FROM ChatRoom c WHERE c.member.email = :email OR c.shop.member.email = :email")
    List<ChatRoom> findByMemberOrShopMember(@Param("email") String email);

//채팅 상대방 과 나 자신을 룸 아이디로 찾기 
    @Query("SELECT r.member.email FROM ChatRoom r WHERE r.id = :roomId " +
            "UNION " +
            "SELECT s.member.email FROM ChatRoom r JOIN r.shop s WHERE r.id = :roomId")
    Set<String> findByChatMembers(@Param("roomId") Long roomId);



//채팅룸 삭제 
    @Modifying
    @Query("delete ChatRoom c where c.member.email =:email and c.id =:roomId")
    void deleteByRoomIdAndEmail(@Param("email") String email, @Param("roomId") Long roomId);

}

실시간 알림과 읽음 상태를 더 구현해보고 싶은데 못하게 되어서 아쉽다
실시간 알림은 브로드 캐스트에 /queue (1 : 1 )경로를 사용하여
socket이 연결된 상태에서 유저에게 실시간 알림을 전송 할 수 있다고 한다

profile
간단한 개발 기록

0개의 댓글