Skip to content

Commit

Permalink
Merge pull request #25 from Leets-Official/feat/#18/채팅-비즈니스-로직
Browse files Browse the repository at this point in the history
Feat #25 채팅 비즈니스 로직 및 예외처리 구현
  • Loading branch information
hyxklee authored Jan 11, 2025
2 parents d8a19b6 + 5880332 commit 045f5cc
Show file tree
Hide file tree
Showing 26 changed files with 406 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.gachtaxi.domain.chat.controller;

import com.gachtaxi.domain.chat.dto.request.ChatMessageRequest;
import com.gachtaxi.domain.chat.dto.request.ChattingRoomResponse;
import com.gachtaxi.domain.chat.service.ChattingRoomService;
import com.gachtaxi.domain.chat.service.ChattingService;
import com.gachtaxi.global.common.response.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import static com.gachtaxi.domain.chat.controller.ResponseMessage.CREATE_CHATTING_ROOM_SUCCESS;
import static org.springframework.http.HttpStatus.OK;

@RestController
@RequiredArgsConstructor
public class ChattingController {

private final ChattingService chattingService;
private final ChattingRoomService chattingRoomService;

@PostMapping("/api/chat/room")
public ApiResponse<ChattingRoomResponse> createChattingRoom() {
ChattingRoomResponse response = chattingRoomService.save();

return ApiResponse.response(OK, CREATE_CHATTING_ROOM_SUCCESS.getMessage(), response);
}

@MessageMapping("/chat/message")
public void message(ChatMessageRequest request, SimpMessageHeaderAccessor headerAccessor) {
chattingService.chat(request, headerAccessor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.gachtaxi.domain.chat.controller;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ResponseMessage {

CREATE_CHATTING_ROOM_SUCCESS("채팅방 생성에 성공했습니다.");

private final String message;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gachtaxi.domain.chat.dto.request;

import com.gachtaxi.domain.chat.entity.enums.MessageType;
import lombok.Builder;

import java.time.LocalDateTime;
Expand All @@ -10,15 +11,28 @@ public record ChatMessage(
Long senderId,
String senderName,
String message,
LocalDateTime timeStamp
LocalDateTime timeStamp,
MessageType messageType
) {
public static ChatMessage of(ChatMessageRequest request, long senderId, LocalDateTime timeStamp) {
public static ChatMessage of(ChatMessageRequest request, long roomId, long senderId) {
return ChatMessage.builder()
.roomId(request.roomId())
.roomId(roomId)
.senderId(senderId)
.senderName(request.senderName())
.message(request.message())
.timeStamp(timeStamp)
.timeStamp(LocalDateTime.now())
.messageType(MessageType.MESSAGE)
.build();
}

public static ChatMessage subscribe(long roomId, Long senderId, String senderName, String message) {
return ChatMessage.builder()
.roomId(roomId)
.senderId(senderId)
.senderName(senderName)
.message(message)
.timeStamp(LocalDateTime.now())
.messageType(MessageType.ENTER)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.gachtaxi.domain.chat.dto.request;

public record ChatMessageRequest(
Long roomId,
String senderName,
String message
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.gachtaxi.domain.chat.dto.request;

import com.gachtaxi.domain.chat.entity.ChattingRoom;
import com.gachtaxi.domain.chat.entity.enums.Status;

public record ChattingRoomResponse(
Long roomId,
Status status
) {
public static ChattingRoomResponse from(ChattingRoom chattingRoom) {
return new ChattingRoomResponse(chattingRoom.getId(), chattingRoom.getStatus());
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/gachtaxi/domain/chat/entity/ChattingMessage.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.gachtaxi.domain.chat.entity;

import com.gachtaxi.domain.chat.dto.request.ChatMessage;
import jakarta.persistence.Id;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.LocalDateTime;

@Getter
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Document(collection = "chatting_messages")
public class ChattingMessage {
Expand All @@ -27,4 +30,14 @@ public class ChattingMessage {

@LastModifiedDate
private LocalDateTime updatedAt;

public static ChattingMessage from(ChatMessage chatMessage) {
return ChattingMessage.builder()
.senderId(chatMessage.senderId())
.senderName(chatMessage.senderName())
.roomId(chatMessage.roomId())
.message(chatMessage.message())
.timeStamp(chatMessage.timeStamp())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ public class ChattingParticipant extends BaseEntity {
@ManyToOne
@JoinColumn(name = "members_id")
private Members members;

public static ChattingParticipant of(ChattingRoom chattingRoom, Members members) {
return ChattingParticipant.builder()
.chattingRoom(chattingRoom)
.members(members)
.build();
}
}
15 changes: 12 additions & 3 deletions src/main/java/com/gachtaxi/domain/chat/entity/ChattingRoom.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package com.gachtaxi.domain.chat.entity;

import com.gachtaxi.domain.chat.entity.enums.Status;
import com.gachtaxi.global.common.entity.BaseEntity;
import jakarta.persistence.Entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.*;
import lombok.experimental.SuperBuilder;

@Getter
@Entity
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChattingRoom extends BaseEntity {

@Builder.Default
@Enumerated(EnumType.STRING)
private Status status = Status.ACTIVE;

public void delete() {
status = Status.INACTIVE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gachtaxi.domain.chat.entity.enums;

public enum MessageType {
MESSAGE, ENTER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gachtaxi.domain.chat.entity.enums;

public enum Status {
ACTIVE, INACTIVE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gachtaxi.domain.chat.exception;

import com.gachtaxi.global.common.exception.BaseException;

import static com.gachtaxi.domain.chat.exception.ErrorMessage.CHAT_SEND_END_POINT_ERROR;
import static org.springframework.http.HttpStatus.BAD_REQUEST;

public class ChatSendEndPointException extends BaseException {
public ChatSendEndPointException() {
super(BAD_REQUEST, CHAT_SEND_END_POINT_ERROR.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gachtaxi.domain.chat.exception;

import com.gachtaxi.global.common.exception.BaseException;

import static com.gachtaxi.domain.chat.exception.ErrorMessage.CHATTING_ROOM_NOT_FOUND;
import static org.springframework.http.HttpStatus.BAD_REQUEST;

public class ChattingRoomNotFoundException extends BaseException {
public ChattingRoomNotFoundException() {
super(BAD_REQUEST, CHATTING_ROOM_NOT_FOUND.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public enum ErrorMessage {
MESSAGING_ERROR("STOMP 메시지 전송에 실패했습니다"),
JSON_PROCESSING_ERROR("Json 직렬화에 실패했습니다."),
REDIS_SUB_ERROR("[Redis] 메시지 전송에 실패했습니다."),
CHAT_SUBSCRIBE_ERROR("올바르지 않은 채팅 구독 경로입니다.");
CHAT_SUBSCRIBE_ERROR("올바르지 않은 채팅 구독 경로입니다."),
CHATTING_ROOM_NOT_FOUND("존재하지 않는 채팅방입니다."),
CHAT_SEND_END_POINT_ERROR("올바르지 않은 채팅 메시지 경로입니다."),
WEB_SOCKET_SESSION_ATTR_NOT_FOUND(" 가 웹소켓 세션에 존재하지 않습니다.");
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gachtaxi.domain.chat.exception;

import com.gachtaxi.global.common.exception.BaseException;

import static com.gachtaxi.domain.chat.exception.ErrorMessage.*;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

public class WebSocketSessionException extends BaseException {
public WebSocketSessionException(String keyword) {
super(INTERNAL_SERVER_ERROR, keyword + WEB_SOCKET_SESSION_ATTR_NOT_FOUND.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.gachtaxi.domain.chat.service;

import com.gachtaxi.domain.chat.dto.request.ChatMessage;
import com.gachtaxi.domain.chat.dto.request.ChattingRoomResponse;
import com.gachtaxi.domain.chat.entity.ChattingRoom;
import com.gachtaxi.domain.chat.entity.enums.Status;
import com.gachtaxi.domain.chat.exception.ChattingRoomNotFoundException;
import com.gachtaxi.domain.chat.redis.RedisChatPublisher;
import com.gachtaxi.domain.chat.repository.ChattingParticipantRepository;
import com.gachtaxi.domain.chat.repository.ChattingRoomRepository;
import com.gachtaxi.domain.members.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ChattingRoomService {

private static final String ENTER_MESSAGE =" 님이 입장하셨습니다.";

private final ChattingRoomRepository chattingRoomRepository;
private final ChattingParticipantRepository chattingParticipantRepository;
private final MemberService memberService;
private final RedisChatPublisher redisChatPublisher;

@Value("${chat.topic}")
public String chatTopic;

@Transactional
public ChattingRoomResponse save() {
ChattingRoom chattingRoom = ChattingRoom.builder().build();

chattingRoomRepository.save(chattingRoom);
return ChattingRoomResponse.from(chattingRoom);
}

@Transactional
public void delete(long chattingRoomId) {
ChattingRoom chattingRoom = find(chattingRoomId);

chattingRoom.delete();
}

@Transactional
public void subscribeChatRoom(long roomId, long senderId, String senderName) {
ChattingRoom chattingRoom = find(roomId);
// Members members = memberService.find();

ChannelTopic topic = new ChannelTopic(chatTopic + roomId);
ChatMessage chatMessage = ChatMessage.subscribe(roomId, senderId, senderName, senderName + ENTER_MESSAGE);

// ChattingParticipant chattingParticipant = ChattingParticipant.of(chattingRoom, members);
// chattingParticipantRepository.save(chattingParticipant);

redisChatPublisher.publish(topic, chatMessage);
}

public ChattingRoom find(long chattingRoomId) {
return chattingRoomRepository.findById(chattingRoomId)
.filter(chattingRoom -> chattingRoom.getStatus() == Status.ACTIVE)
.orElseThrow(ChattingRoomNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.gachtaxi.domain.chat.service;

import com.gachtaxi.domain.chat.dto.request.ChatMessage;
import com.gachtaxi.domain.chat.dto.request.ChatMessageRequest;
import com.gachtaxi.domain.chat.entity.ChattingMessage;
import com.gachtaxi.domain.chat.exception.WebSocketSessionException;
import com.gachtaxi.domain.chat.redis.RedisChatPublisher;
import com.gachtaxi.domain.chat.repository.ChattingMessageRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Service;

import java.util.Optional;

import static com.gachtaxi.domain.chat.stomp.strategy.StompConnectStrategy.CHAT_USER_ID;
import static com.gachtaxi.domain.chat.stomp.strategy.StompSubscribeStrategy.CHAT_ROOM_ID;

@Service
@RequiredArgsConstructor
public class ChattingService {

private final ChattingMessageRepository chattingMessageRepository;
private final RedisChatPublisher redisChatPublisher;

@Value("${chat.topic}")
public String chatTopic;

public void chat(ChatMessageRequest request, SimpMessageHeaderAccessor accessor) {
long roomId = getSessionAttribute(accessor, CHAT_ROOM_ID, Long.class);
long userId = getSessionAttribute(accessor, CHAT_USER_ID, Long.class);

ChatMessage chatMessage = ChatMessage.of(request, roomId, userId);
ChannelTopic topic = new ChannelTopic(chatTopic + chatMessage.roomId());
ChattingMessage chattingMessage = ChattingMessage.from(chatMessage);

chattingMessageRepository.save(chattingMessage);
redisChatPublisher.publish(topic, chatMessage);
}

private <T> T getSessionAttribute(SimpMessageHeaderAccessor accessor, String attributeName, Class<T> type) {
return Optional.ofNullable(accessor.getSessionAttributes())
.map(attrs -> type.cast(attrs.get(attributeName)))
.orElseThrow(() -> new WebSocketSessionException(attributeName));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.gachtaxi.domain.chat.interceptor;
package com.gachtaxi.domain.chat.stomp;

import com.gachtaxi.domain.chat.interceptor.strategy.ChatStrategyHandler;
import com.gachtaxi.domain.chat.stomp.strategy.ChatStrategyHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.gachtaxi.domain.chat.interceptor.strategy;
package com.gachtaxi.domain.chat.stomp.strategy;

import lombok.RequiredArgsConstructor;
import org.springframework.messaging.Message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.gachtaxi.domain.chat.interceptor.strategy;
package com.gachtaxi.domain.chat.stomp.strategy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.gachtaxi.domain.chat.interceptor.strategy;
package com.gachtaxi.domain.chat.stomp.strategy;

import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
Expand Down
Loading

0 comments on commit 045f5cc

Please sign in to comment.