Skip to content

Commit

Permalink
MATE-150 : [REFACTOR] 굿즈거래 채팅 엔티티 MongoDB로 마이그레이션 (#136)
Browse files Browse the repository at this point in the history
* MATE-150 : [FEAT] 굿즈거래 채팅 엔티티를 MongoDB 도큐먼트로 변경

* MATE-150 : [FEAT] 굿즈거래 채팅 레포지토리 리팩토링
- MongoRepository 구현체로 변경
- 채팅 조회 메서드(쿼리) 구현
- Spring Data MongoDB 를 사용해 채팅 삭제 기능 구현

* MATE-150 : [REFACTOR] 엔티티 변경에 따른 DTO 및 Service 레이어 리팩토링

* MATE-150 : [FEAT] 테스트용 인메모리 MongoDB 사용을 위한 bwaldvogel 의존성 추가

* MATE-150 : [TEST] 인메모리 MongoDB 사용을 위한 설정 클래스 구현

- 가짜 MongoDB 서버 설정 정보를 담는 MongoTestServerConfig 클래스 구현
- MongoTestServerConfig 를 Import 하는 EnableMongoTestServer 어노테이션 구현

* MATE-150 : [TEST] MongoDB 통합테스트에 사용하는 AcceptanceTestWithMongo 추상 클래스 구현

* MATE-150 : [CHORE] 시큐리티 관련 테스트 설정 클래스를 securityConfig 패키지로 이관

* MATE-150 : [TEST] 패키지 이관 및 DB 변경에 따른 테스트 코드 수정

* MATE-150 : [REFACTOR] GoodsChatMessageService 클래스에서 GoodsParts 엔티티 의존성 제거

* MATE-150 : [TEST] 의존성 제거에 따른 테스트 코드 수정

* MATE-150 : [!BUGFIX] 코드 충돌 해결
  • Loading branch information
hongjeZZ authored Jan 14, 2025
1 parent c3310c9 commit 03f80d7
Show file tree
Hide file tree
Showing 37 changed files with 284 additions and 285 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'de.bwaldvogel:mongo-java-server:1.46.0'

// querydsl for spring boot 3.x
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public ResponseEntity<ApiResponse<PageResponse<GoodsChatMessageResponse>>> getGo
@Parameter(description = "채팅방 ID", required = true) @PathVariable Long chatRoomId,
@Parameter(description = "페이징 정보") @ValidPageable(page = 1, size = 20) Pageable pageable
) {
PageResponse<GoodsChatMessageResponse> response = goodsChatService.getMessagesForChatRoom(chatRoomId, member.getMemberId(), pageable);
PageResponse<GoodsChatMessageResponse> response = goodsChatService.getChatRoomMessages(chatRoomId, member.getMemberId(), pageable);
return ResponseEntity.ok(ApiResponse.success(response));
}

Expand Down Expand Up @@ -90,7 +90,7 @@ public ResponseEntity<ApiResponse<List<MemberSummaryResponse>>> getGoodsChatRoom
@AuthenticationPrincipal AuthMember member,
@Parameter(description = "채팅방 ID", required = true) @PathVariable Long chatRoomId
) {
List<MemberSummaryResponse> responses = goodsChatService.getChatRoomMembers(member.getMemberId(), chatRoomId);
List<MemberSummaryResponse> responses = goodsChatService.getMembersInChatRoom(member.getMemberId(), chatRoomId);
return ResponseEntity.ok(ApiResponse.success(responses));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.mate.domain.goodsChat.document;

import com.example.mate.domain.constant.MessageType;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Getter
@Builder
@Document(collection = "goods_chat_message")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class GoodsChatMessage {

@Id
private String id;

@Field(name = "chat_room_id")
private Long chatRoomId;

@Field(name = "member_id")
private Long memberId;

@Field(name = "content")
private String content;

@Field(name = "sent_at")
private LocalDateTime sentAt;

@Field(name = "message_type")
private MessageType messageType;

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.example.mate.domain.goodsChat.dto.response;

import com.example.mate.domain.file.FileUtils;
import com.example.mate.domain.goodsChat.entity.GoodsChatMessage;
import com.example.mate.domain.goodsChat.entity.GoodsChatPart;
import com.example.mate.domain.goodsChat.document.GoodsChatMessage;
import com.example.mate.domain.member.entity.Member;
import java.time.LocalDateTime;
import lombok.Builder;
Expand All @@ -14,7 +13,7 @@
@RequiredArgsConstructor
public class GoodsChatMessageResponse {

private final Long chatMessageId;
private final String chatMessageId;
private final Long roomId;
private final Long senderId;
private final String senderNickname;
Expand All @@ -23,13 +22,10 @@ public class GoodsChatMessageResponse {
private final String senderImageUrl;
private final LocalDateTime sentAt;

public static GoodsChatMessageResponse of(GoodsChatMessage chatMessage) {
GoodsChatPart goodsChatPart = chatMessage.getGoodsChatPart();
Member sender = goodsChatPart.getMember();

public static GoodsChatMessageResponse of(GoodsChatMessage chatMessage, Member sender) {
return GoodsChatMessageResponse.builder()
.chatMessageId(chatMessage.getId())
.roomId(goodsChatPart.getGoodsChatRoom().getId())
.roomId(chatMessage.getChatRoomId())
.senderId(sender.getId())
.senderNickname(sender.getNickname())
.senderImageUrl(FileUtils.getThumbnailImageUrl(sender.getImageUrl()))
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.example.mate.domain.goodsPost.entity.Role;
import com.example.mate.domain.member.entity.Member;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
Expand All @@ -12,10 +11,7 @@
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down Expand Up @@ -48,10 +44,6 @@ public class GoodsChatPart {
@Builder.Default
private Boolean isActive = true;

@OneToMany(mappedBy = "goodsChatPart", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
List<GoodsChatMessage> goodsChatMessages = new ArrayList<>();

public boolean leaveAndCheckRoomStatus() {
if (!goodsChatRoom.isRoomActive()) {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.example.mate.domain.goodsChat.repository;

import com.example.mate.domain.goodsChat.entity.GoodsChatMessage;
import com.example.mate.domain.goodsChat.document.GoodsChatMessage;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;

public interface GoodsChatMessageRepository extends JpaRepository<GoodsChatMessage, Long> {
public interface GoodsChatMessageRepository extends MongoRepository<GoodsChatMessage, String> {

@Query("""
SELECT cm
FROM GoodsChatMessage cm
WHERE cm.goodsChatPart.goodsChatRoom.id = :chatRoomId
ORDER BY cm.sentAt DESC
""")
Page<GoodsChatMessage> getChatMessages(@Param("chatRoomId") Long chatRoomId, Pageable pageable);
/**
* 특정 채팅방의 메시지를 페이징 처리하여 조회합니다.
* 메시지는 전송된 시간(sent_at) 기준으로 오름차순으로 정렬됩니다.
*/
@Query(value = "{ 'chat_room_id': ?0 }", sort = "{ 'sent_at': -1 }")
Page<GoodsChatMessage> getChatMessages(Long chatRoomId, Pageable pageable);

void deleteAllByChatRoomId(Long chatRoomId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
import com.example.mate.common.error.CustomException;
import com.example.mate.common.error.ErrorCode;
import com.example.mate.domain.constant.MessageType;
import com.example.mate.domain.goodsChat.event.GoodsChatEvent;
import com.example.mate.domain.goodsChat.document.GoodsChatMessage;
import com.example.mate.domain.goodsChat.dto.request.GoodsChatMessageRequest;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatMessageResponse;
import com.example.mate.domain.goodsChat.entity.GoodsChatMessage;
import com.example.mate.domain.goodsChat.entity.GoodsChatPart;
import com.example.mate.domain.goodsChat.entity.GoodsChatPartId;
import com.example.mate.domain.goodsChat.entity.GoodsChatRoom;
import com.example.mate.domain.goodsChat.event.GoodsChatEvent;
import com.example.mate.domain.goodsChat.repository.GoodsChatMessageRepository;
import com.example.mate.domain.goodsChat.repository.GoodsChatPartRepository;
import com.example.mate.domain.goodsChat.repository.GoodsChatRoomRepository;
import com.example.mate.domain.member.entity.Member;
import com.example.mate.domain.member.repository.MemberRepository;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
Expand All @@ -27,53 +25,52 @@ public class GoodsChatMessageService {

private final MemberRepository memberRepository;
private final GoodsChatRoomRepository chatRoomRepository;
private final GoodsChatPartRepository chatPartRepository;
private final GoodsChatMessageRepository messageRepository;
private final SimpMessagingTemplate messagingTemplate;

private static final String MEMBER_ENTER_MESSAGE = "님이 대화를 시작했습니다.";
private static final String MEMBER_LEAVE_MESSAGE = "님이 대화를 떠났습니다.";

public void sendMessage(GoodsChatMessageRequest message) {
Member sender = findMemberById(message.getSenderId());
Member member = findMemberById(message.getSenderId());
GoodsChatRoom chatRoom = findByChatRoomById(message.getRoomId());
GoodsChatPart chatPart = findByChatPartById(sender.getId(), chatRoom.getId());
GoodsChatMessage chatMessage = createChatMessage(chatRoom.getId(), member.getId(), message.getMessage(), message.getType());

// DB에 메시지 저장
GoodsChatMessage chatMessage
= messageRepository.save(createChatMessage(message.getMessage(), chatPart, message.getType()));
// 채팅 데이터 저장 & 최신 채팅 내역 업데이트
GoodsChatMessage savedMessage = messageRepository.save(chatMessage);
chatRoom.updateLastChat(chatMessage.getContent(), chatMessage.getSentAt());

GoodsChatMessageResponse response = GoodsChatMessageResponse.of(chatMessage);
GoodsChatMessageResponse response = GoodsChatMessageResponse.of(savedMessage, member);
sendToSubscribers(message.getRoomId(), response);
}

// 입장 및 퇴장 메시지 전송
public void sendChatEventMessage(GoodsChatEvent event) {
Member member = event.member();
Long roomId = event.chatRoomId();

GoodsChatRoom chatRoom = findByChatRoomById(roomId);
GoodsChatPart chatPart = findByChatPartById(member.getId(), roomId);
Long chatRoomId = event.chatRoomId();
GoodsChatRoom chatRoom = findByChatRoomById(chatRoomId);

// 메시지 생성
String message = member.getNickname();
switch (event.type()) {
case ENTER -> message += MEMBER_ENTER_MESSAGE;
case LEAVE -> message += MEMBER_LEAVE_MESSAGE;
}
GoodsChatMessage chatMessage = createChatMessage(member.getId(), chatRoomId, message, event.type());

// Message DB에 저장
GoodsChatMessage chatMessage = messageRepository.save(createChatMessage(message, chatPart, event.type()));
// 채팅 데이터 저장 & 최신 채팅 내역 업데이트
GoodsChatMessage savedMessage = messageRepository.save(chatMessage);
chatRoom.updateLastChat(message, chatMessage.getSentAt());

// 이벤트 메시지 전송
sendToSubscribers(roomId, GoodsChatMessageResponse.of(chatMessage));
sendToSubscribers(chatRoomId, GoodsChatMessageResponse.of(savedMessage, member));
}

private GoodsChatMessage createChatMessage(String message, GoodsChatPart chatPart, MessageType type) {
private GoodsChatMessage createChatMessage(Long chatRoomId, Long memberId, String message, MessageType type) {
return GoodsChatMessage.builder()
.goodsChatPart(chatPart)
.chatRoomId(chatRoomId)
.memberId(memberId)
.sentAt(LocalDateTime.now())
.content(message)
.messageType(type)
.build();
Expand All @@ -89,12 +86,7 @@ private GoodsChatRoom findByChatRoomById(Long chatRoomId) {
.orElseThrow(() -> new CustomException(ErrorCode.GOODS_CHAT_ROOM_NOT_FOUND));
}

private GoodsChatPart findByChatPartById(Long memberId, Long chatRoomId) {
return chatPartRepository.findById(new GoodsChatPartId(memberId, chatRoomId))
.orElseThrow(() -> new CustomException(ErrorCode.GOODS_CHAT_NOT_FOUND_CHAT_PART));
}

private void sendToSubscribers(Long roomId, GoodsChatMessageResponse message) {
messagingTemplate.convertAndSend("/sub/chat/goods/" + roomId, message);
private void sendToSubscribers(Long chatRoomId, GoodsChatMessageResponse message) {
messagingTemplate.convertAndSend("/sub/chat/goods/" + chatRoomId, message);
}
}
Loading

0 comments on commit 03f80d7

Please sign in to comment.