대화 상대를 선택하면 채팅룸이 만들어지는 api 호출 (createChatRoom) + 구독 주소를 식별하는 roomId 생성됨
stomp 헤더의 로그인인 유저의 토큰을 담아 서버에 전송
서버는 stomp 헤더에 담긴 토큰으로 유저 인증 처리 후 web socket 연결 허용
유저와 대화 상대는 해당 채팅룸을 구독하는 상태가 됨
유저가 메세지를 보내면 stomp 프로토콜의 body 에 메세지가 담겨서 서버( /topic/chat/message/{roomId}" 주소 )에 전달됨 ( STOMP )
서버에 온 메세지는 roomId 에 맞는 chatRoom 이 있는지 조회 후 도큐먼트로 변환 하여 mongo db 에 roomId와 함께 저장
roomId에 해당하는 chatMessage 들을 List 화 하여 클라이언트에 전달( http )
/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'
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;
}
}
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));
// }
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);
}
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);
}
}
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);
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이 연결된 상태에서 유저에게 실시간 알림을 전송 할 수 있다고 한다