Skip to content

Commit

Permalink
feat: ✨ Send Chat Message Usecase (#186)
Browse files Browse the repository at this point in the history
* rename: transfer command to command package

* chore: add jackson validation dependency in to the socket module

* chore: add spring-validation dependency in to the socket module

* rename: package path modification

* fix: add type field in the rabbit_listener exchange property's type field

* fix: delete constants in the listener

* feat: chat_message_dto

* feat: chat_message_controller

* feat: add dto validation

* fix: chat_message entity's @redis_hash is chatroom

* fix: dto wrap final class

* fix: when message send to rabbitmq, using response dto

* rename: delete send_message_commend unused annotation

* fix: get_chat_room_id and get_chat_id method in the chat_message fix index

* refactor: when chat message send to rabbitmq, it's will be dto type

* test: dao test expected redis key fix
  • Loading branch information
psychology50 authored Oct 31, 2024
1 parent 444ad64 commit 19be782
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Redis에 저장되는 채팅 메시지의 기본 단위입니다.
*/
@Getter
@RedisHash
@RedisHash(value = "chatroom")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChatMessage {
/**
Expand All @@ -36,7 +36,7 @@ public class ChatMessage {
private Long sender;

protected ChatMessage(ChatMessageBuilder builder) {
this.id = "chatroom:" + builder.getChatRoomId() + ":message:" + builder.getChatId();
this.id = builder.getChatRoomId() + ":message:" + builder.getChatId();
this.content = builder.getContent();
this.contentType = builder.getContentType();
this.categoryType = builder.getCategoryType();
Expand All @@ -46,11 +46,11 @@ protected ChatMessage(ChatMessageBuilder builder) {
}

public Long getChatRoomId() {
return Long.parseLong(id.split(":")[1]);
return Long.parseLong(id.split(":")[0]);
}

public Long getChatId() {
return Long.parseLong(id.split(":")[3]);
return Long.parseLong(id.split(":")[2]);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ void successSaveChatMessage() {
log.info("Saved message: {}", savedMessage);
assertAll(
() -> assertNotNull(savedMessage, "저장된 메시지는 null이 아니어야 합니다"),
() -> assertTrue(savedMessage.getId().matches("chatroom:\\d+:message:\\d+"), "ID는 'chatroom:{roomId}:message:{messageId}' 형태여야 합니다"),
() -> assertTrue(savedMessage.getId().matches("\\d+:message:\\d+"), "ID는 'chatroom:{roomId}:message:{messageId}' 형태여야 합니다 ('chatroom:'은 hash key로 제외)"),
() -> assertEquals(chatMessage.getId(), savedMessage.getId(), "저장 전후의 ID가 동일해야 합니다")
);
}
Expand Down
5 changes: 5 additions & 0 deletions pennyway-socket/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ dependencies {

/* RabbitMQ (for listener) */
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-amqp', version: '3.3.4'

/* jackson */
implementation group: 'org.openapitools', name: 'jackson-databind-nullable', version: '0.2.6'

implementation 'org.springframework.boot:spring-boot-starter-validation:3.2.3'
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kr.co.pennyway.socket.dto;
package kr.co.pennyway.socket.command;

import kr.co.pennyway.domain.common.redis.message.type.MessageCategoryType;
import kr.co.pennyway.domain.common.redis.message.type.MessageContentType;
Expand All @@ -8,11 +8,11 @@
* 채팅 메시지 전송을 위한 Command 클래스
*/
public record SendMessageCommand(
long chatRoomId, // 채팅방 아이디는 음수일 수 없다.
String content, // 메시지 내용은 5000자를 초과할 수 없다.
MessageContentType contentType, // 메시지 타입은 NULL일 수 없다.
MessageCategoryType categoryType, // 메시지 카테고리는 NULL일 수 없다.
long senderId // 발신자 아이디는 음수일 수 없다.
long chatRoomId,
String content,
MessageContentType contentType,
MessageCategoryType categoryType,
long senderId
) {
public SendMessageCommand {
if (chatRoomId <= 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package kr.co.pennyway.socket.controller;

import kr.co.pennyway.socket.command.SendMessageCommand;
import kr.co.pennyway.socket.common.annotation.PreAuthorize;
import kr.co.pennyway.socket.common.security.authenticate.UserPrincipal;
import kr.co.pennyway.socket.dto.ChatMessageDto;
import kr.co.pennyway.socket.service.ChatMessageSendService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;

@Slf4j
@Controller
@RequiredArgsConstructor
public class ChatMessageController {
private final ChatMessageSendService chatMessageSendService;

@MessageMapping("chat.message.{chatRoomId}")
@PreAuthorize("#isAuthenticated(#principal) and @chatRoomAccessChecker.hasPermission(#chatRoomId, #principal)")
public void sendMessage(@DestinationVariable Long chatRoomId, @Validated ChatMessageDto.Request payload, UserPrincipal principal) {
chatMessageSendService.execute(SendMessageCommand.createUserMessage(chatRoomId, payload.content(), payload.contentType(), principal.getUserId()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package kr.co.pennyway.socket.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import kr.co.pennyway.domain.common.redis.message.domain.ChatMessage;
import kr.co.pennyway.domain.common.redis.message.type.MessageCategoryType;
import kr.co.pennyway.domain.common.redis.message.type.MessageContentType;

import java.time.LocalDateTime;

public final class ChatMessageDto {
public record Request(
@NotNull(message = "메시지 내용은 null을 허용하지 않습니다.")
@Size(min = 1, max = 1000, message = "메시지 내용은 1자 이상 1000자 이하로 입력해주세요.")
String content,
@NotNull(message = "메시지 타입은 null을 허용하지 않습니다.")
MessageContentType contentType
) {
}

public record Response(
Long chatRoomId,
Long chatId,
String content,
MessageContentType contentType,
MessageCategoryType categoryType,
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime createdAt,
Long senderId
) {
public static Response from(ChatMessage message) {
return new Response(
message.getChatRoomId(),
message.getChatId(),
message.getContent(),
message.getContentType(),
message.getCategoryType(),
message.getCreatedAt(),
message.getSender()
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import kr.co.pennyway.infra.common.event.ChatRoomJoinEvent;
import kr.co.pennyway.infra.common.properties.ChatExchangeProperties;
import kr.co.pennyway.socket.command.SendMessageCommand;
import kr.co.pennyway.socket.common.constants.SystemMessageTemplate;
import kr.co.pennyway.socket.dto.SendMessageCommand;
import kr.co.pennyway.socket.service.ChatMessageSendService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -19,15 +19,13 @@
@RequiredArgsConstructor
@EnableConfigurationProperties({ChatExchangeProperties.class})
public class ChatJoinEventListener {
private static final String JOIN_MESSAGE_SUFFIX = "님이 입장하셨습니다.";

private final ChatMessageSendService chatMessageSendService;

@RabbitListener(
containerFactory = "simpleRabbitListenerContainerFactory",
bindings = @QueueBinding(
value = @Queue("${pennyway.rabbitmq.chat-join-event.queue}"),
exchange = @Exchange(value = "${pennyway.rabbitmq.chat.exchange}"),
exchange = @Exchange(value = "${pennyway.rabbitmq.chat.exchange}", type = "topic"),
key = "${pennyway.rabbitmq.chat-join-event.routing-key}"
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import kr.co.pennyway.infra.client.broker.MessageBrokerAdapter;
import kr.co.pennyway.infra.client.guid.IdGenerator;
import kr.co.pennyway.infra.common.properties.ChatExchangeProperties;
import kr.co.pennyway.socket.dto.SendMessageCommand;
import kr.co.pennyway.socket.command.SendMessageCommand;
import kr.co.pennyway.socket.dto.ChatMessageDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand Down Expand Up @@ -37,13 +38,14 @@ public void execute(SendMessageCommand command) {
.categoryType(command.categoryType())
.sender(command.senderId())
.build();

chatMessageService.save(message);
message = chatMessageService.save(message);

ChatMessageDto.Response response = ChatMessageDto.Response.from(message);

messageBrokerAdapter.convertAndSend(
chatExchangeProperties.getExchange(),
"chat.room." + command.chatRoomId(),
message
response
);
}
}

0 comments on commit 19be782

Please sign in to comment.