Skip to content

Commit

Permalink
Merge pull request #106 from 100-hours-a-week/feat-chat
Browse files Browse the repository at this point in the history
feat: 채팅 메시지 기능 추가
  • Loading branch information
Namgyu11 authored Aug 30, 2024
2 parents 0d5ba1b + 034fdef commit 5980371
Show file tree
Hide file tree
Showing 14 changed files with 307 additions and 131 deletions.
10 changes: 8 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,20 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

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

// RabbitMQ
implementation 'org.springframework.boot:spring-boot-starter-amqp'

// STOMP
// websocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'

// Messaging
implementation 'org.springframework:spring-messaging'


}

tasks.named('bootBuildImage') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package connectripbe.connectrip_be.chat.config;

import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 클라이언트가 WebSocket에 연결할 수 있는 엔드포인트를 등록.
registry.addEndpoint("/ws") // 클라이언트가 "/ws" 엔드포인트로 WebSocket 연결을 시도할 수 있도록 설정.
.setAllowedOrigins("*"); // 모든 도메인에서의 CORS (Cross-Origin) 요청을 허용.
}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 메시지를 라우팅할 때 사용할 애플리케이션 목적지 프리픽스를 설정합니다.
// 클라이언트가 메시지를 보낼 때 "/pub" 프리픽스를 사용.
registry.setApplicationDestinationPrefixes("/pub");

// "/sub"로 시작하는 경로를 구독한 클라이언트에게 메시지를 전달.
// 메시지를 구독하는 목적지 경로의 프리픽스를 "/sub"으로 설정.
registry.enableSimpleBroker("/sub");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package connectripbe.connectrip_be.chat.dto;

import connectripbe.connectrip_be.chat.entity.ChatMessage;
import connectripbe.connectrip_be.chat.entity.type.MessageType;
import java.time.LocalDateTime;
import lombok.Builder;

@Builder
public record ChatMessageDto(
String id,
MessageType type,
Long chatRoomId,
Long senderId,
String senderNickname,
String senderProfileImage,
String content,
LocalDateTime createdAt
) {
public static ChatMessageDto fromEntity(ChatMessage chatMessage) {
return ChatMessageDto.builder()
.id(chatMessage.getId())
.type(chatMessage.getType())
.chatRoomId(chatMessage.getChatRoomId())
.senderId(chatMessage.getSenderId())
.senderNickname(chatMessage.getSenderNickname())
.senderProfileImage(chatMessage.getSenderProfileImage())
.content(chatMessage.getContent())
.createdAt(chatMessage.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,55 @@
package connectripbe.connectrip_be.chat.entity;

import connectripbe.connectrip_be.chat.entity.type.MessageType;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;

@Document(collection = "chat_message")
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ChatMessage {

/**
* 몽고디비 설정해서 연결 확인 1. application.properties에 몽고디비 설정 추가 2. MognoDBConfig 클래스 생성
*/
@Id
@Field(value = "_id", targetType = FieldType.OBJECT_ID)
private String id;

@Field
@Enumerated(EnumType.STRING)
private MessageType type;

@Field("chat_room_id")
private Long chatRoomId;

@Field("sender_id")
private Long senderId;

@Field("sender_nickname")
private String senderNickname;

@Field("sender_profile_image")
private String senderProfileImage;

@Field("content")
private String content;

@Field("created_at")
@CreatedDate
private LocalDateTime createdAt;


}
Original file line number Diff line number Diff line change
Expand Up @@ -29,54 +29,59 @@
@Builder
public class ChatRoomEntity extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "accompany_post_id")
private AccompanyPostEntity accompanyPost;

// 방장 설정: ChatRoomMember의 ID를 참조
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "leader_id")
private ChatRoomMemberEntity currentLeader;

@Enumerated(EnumType.STRING)
private ChatRoomType chatRoomType;

private String lastChatMessage;

// 마지막 채팅 시간. 정렬을 위해 기본적으로 CreatedAt 값을 사용하고, 채팅이 발생할 때마다 업데이트
@CreatedDate
private LocalDateTime lastChatTime;

@Builder.Default
@OneToMany(mappedBy = "chatRoom")
private List<ChatRoomMemberEntity> chatRoomMembers = new ArrayList<>();

/**
* ChatRoomMember 추가 및 양방향 관계 설정 메서드
* @param chatRoomMember 채팅참여자 객체
*/
public void addChatRoomMember(ChatRoomMemberEntity chatRoomMember) {
this.chatRoomMembers.add(chatRoomMember);
chatRoomMember.assignChatRoom(this);
}

// 방장 설정 메서드 (초기 방장 설정)
public void setInitialLeader(ChatRoomMemberEntity leader) {
this.currentLeader = leader;
this.addChatRoomMember(leader);
}


// 채팅방 상태 변경 메서드
public void changeChatRoomType(ChatRoomType chatRoomType) {
this.chatRoomType = chatRoomType;
}

//TODO 마지막 채팅 메시지 및 시간 업데이트 메서드
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "accompany_post_id")
private AccompanyPostEntity accompanyPost;

// 방장 설정: ChatRoomMember의 ID를 참조
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "leader_id")
private ChatRoomMemberEntity currentLeader;

@Enumerated(EnumType.STRING)
private ChatRoomType chatRoomType;

private String lastChatMessage;

// 마지막 채팅 시간. 정렬을 위해 기본적으로 CreatedAt 값을 사용하고, 채팅이 발생할 때마다 업데이트
@CreatedDate
private LocalDateTime lastChatTime;

@Builder.Default
@OneToMany(mappedBy = "chatRoom")
private List<ChatRoomMemberEntity> chatRoomMembers = new ArrayList<>();

/**
* ChatRoomMember 추가 및 양방향 관계 설정 메서드
*
* @param chatRoomMember 채팅참여자 객체
*/
public void addChatRoomMember(ChatRoomMemberEntity chatRoomMember) {
this.chatRoomMembers.add(chatRoomMember);
chatRoomMember.assignChatRoom(this);
}

// 방장 설정 메서드 (초기 방장 설정)
public void setInitialLeader(ChatRoomMemberEntity leader) {
this.currentLeader = leader;
this.addChatRoomMember(leader);
}


// 채팅방 상태 변경 메서드
public void changeChatRoomType(ChatRoomType chatRoomType) {
this.chatRoomType = chatRoomType;
}

//마지막 채팅 메시지 및 시간 업데이트 메서드
public void updateLastChatMessage(String message, LocalDateTime time) {
this.lastChatMessage = message;
this.lastChatTime = time;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package connectripbe.connectrip_be.chat.entity.type;

public enum MessageType {
ENTER, TALK, LEAVE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package connectripbe.connectrip_be.chat.repository;

import connectripbe.connectrip_be.chat.entity.ChatMessage;
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ChatMessageRepository extends MongoRepository<ChatMessage, String> {
List<ChatMessage> findByChatRoomId(Long chatRoomId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package connectripbe.connectrip_be.chat.service;

import connectripbe.connectrip_be.chat.dto.ChatMessageDto;
import java.util.List;

public interface ChatMessageService {

ChatMessageDto saveMessage(ChatMessageDto chatMessage);

List<ChatMessageDto> getMessages(Long chatRoomId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package connectripbe.connectrip_be.chat.service.impl;

import connectripbe.connectrip_be.chat.dto.ChatMessageDto;
import connectripbe.connectrip_be.chat.entity.ChatMessage;
import connectripbe.connectrip_be.chat.entity.ChatRoomEntity;
import connectripbe.connectrip_be.chat.repository.ChatMessageRepository;
import connectripbe.connectrip_be.chat.repository.ChatRoomRepository;
import connectripbe.connectrip_be.chat.service.ChatMessageService;
import connectripbe.connectrip_be.global.exception.GlobalException;
import connectripbe.connectrip_be.global.exception.type.ErrorCode;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ChatMessageServiceImpl implements ChatMessageService {

private final ChatMessageRepository chatMessageRepository;
private final ChatRoomRepository chatRoomRepository;

@Override
public ChatMessageDto saveMessage(ChatMessageDto chatMessageDto) {
ChatMessage chatMessage =
ChatMessage.builder()
.id(chatMessageDto.id())
.type(chatMessageDto.type())
.chatRoomId(chatMessageDto.chatRoomId())
.senderId(chatMessageDto.senderId())
.senderNickname(chatMessageDto.senderNickname())
.senderProfileImage(chatMessageDto.senderProfileImage())
.content(chatMessageDto.content())
.createdAt(chatMessageDto.createdAt())
.build();

ChatMessage saved = chatMessageRepository.save(chatMessage);

ChatRoomEntity chatRoom = chatRoomRepository.findById(saved.getChatRoomId())
.orElseThrow(() -> new GlobalException(ErrorCode.CHAT_ROOM_NOT_FOUND));

// 채팅방 테이블에 채팅 마지막 내용과 마지막 시간 업데이트
chatRoom.updateLastChatMessage(saved.getContent(), saved.getCreatedAt());
chatRoomRepository.save(chatRoom);

return ChatMessageDto.fromEntity(saved);
}

@Override
public List<ChatMessageDto> getMessages(Long chatRoomId) {
List<ChatMessage> chatMessages = chatMessageRepository.findByChatRoomId(chatRoomId);

return chatMessages.stream()
.map(ChatMessageDto::fromEntity)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package connectripbe.connectrip_be.chat.web;

import connectripbe.connectrip_be.chat.dto.ChatMessageDto;
import connectripbe.connectrip_be.chat.service.ChatMessageService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class ChatMessageController {

private final ChatMessageService chatMessageService;
private final SimpMessagingTemplate simpMessagingTemplate;

@MessageMapping("/chat/message")
public void senMessage(@Payload ChatMessageDto chatMessage) {
ChatMessageDto savedMessage = chatMessageService.saveMessage(chatMessage);
simpMessagingTemplate.convertAndSend("/sub/chat/room/" + savedMessage.chatRoomId(), savedMessage);
}

@GetMapping("/api/v1/chatRoom/{chatRoomId}/messages")
public ResponseEntity<List<ChatMessageDto>> getChatRoomMessages(@PathVariable Long chatRoomId) {
return ResponseEntity.ok(chatMessageService.getMessages(chatRoomId));
}
}
Loading

0 comments on commit 5980371

Please sign in to comment.