-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[feat] 실시간 채팅 기능 구현
- Loading branch information
Showing
26 changed files
with
781 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
src/main/java/com/leets/xcellentbe/domain/chatRoom/controller/ChatRoomController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.leets.xcellentbe.domain.chatRoom.controller; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PatchMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import com.leets.xcellentbe.domain.chatRoom.dto.ChatRoomDto; | ||
import com.leets.xcellentbe.domain.chatRoom.service.ChatRoomService; | ||
import com.leets.xcellentbe.domain.dm.dto.request.DMRequest; | ||
import com.leets.xcellentbe.domain.dm.dto.response.DMResponse; | ||
import com.leets.xcellentbe.global.response.GlobalResponseDto; | ||
|
||
import io.swagger.v3.oas.annotations.Operation; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RestController | ||
@RequestMapping("/api/chat-room") | ||
@RequiredArgsConstructor | ||
public class ChatRoomController { | ||
|
||
private final ChatRoomService chatRoomService; | ||
|
||
@PostMapping() | ||
@Operation(summary = "채팅방 생성", description = "채팅방을 생성합니다.") | ||
public ResponseEntity<GlobalResponseDto<DMResponse>> createChatRoom(@RequestBody DMRequest dmRequest, | ||
@AuthenticationPrincipal UserDetails userDetails) { | ||
return ResponseEntity.status(HttpStatus.CREATED) | ||
.body(GlobalResponseDto.success(chatRoomService.createChatRoom(dmRequest, userDetails))); | ||
} | ||
|
||
@GetMapping("/all") | ||
@Operation(summary = "사용자 채팅방 전체 조회", description = "사용자의 모든 채팅방을 조회합니다.") | ||
public ResponseEntity<GlobalResponseDto<List<DMResponse>>> findAllChatRoomByUser( | ||
@AuthenticationPrincipal UserDetails userDetails) { | ||
return ResponseEntity.status(HttpStatus.OK) | ||
.body(GlobalResponseDto.success(chatRoomService.findAllChatRoomByUser(userDetails))); | ||
} | ||
|
||
@GetMapping("/{chatRoomId}") | ||
@Operation(summary = "사용자 채팅방 조회", description = "사용자의 채팅방을 조회합니다.") | ||
public ResponseEntity<GlobalResponseDto<ChatRoomDto>> findChatRoom(@PathVariable UUID chatRoomId, | ||
@AuthenticationPrincipal UserDetails userDetails) { | ||
return ResponseEntity.status(HttpStatus.OK) | ||
.body(GlobalResponseDto.success(chatRoomService.findChatRoom(chatRoomId, userDetails))); | ||
} | ||
|
||
@PatchMapping("/{chatRoomId}") | ||
@Operation(summary = "채팅방 삭제", description = "채팅방을 삭제합니다.") | ||
public ResponseEntity<GlobalResponseDto<String>> deleteChatRoom(@PathVariable UUID chatRoomId, | ||
@AuthenticationPrincipal UserDetails userDetails) { | ||
return ResponseEntity.status(HttpStatus.OK) | ||
.body(GlobalResponseDto.success(chatRoomService.deleteChatRoom(chatRoomId, userDetails))); | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
src/main/java/com/leets/xcellentbe/domain/chatRoom/domain/ChatRoom.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package com.leets.xcellentbe.domain.chatRoom.domain; | ||
|
||
import java.util.UUID; | ||
|
||
import com.leets.xcellentbe.domain.shared.BaseTimeEntity; | ||
import com.leets.xcellentbe.domain.shared.DeletedStatus; | ||
import com.leets.xcellentbe.domain.user.domain.User; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.validation.constraints.NotNull; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class ChatRoom extends BaseTimeEntity { | ||
|
||
@Id | ||
@Column(name = "chatRoom_id") | ||
@GeneratedValue(strategy = GenerationType.UUID) | ||
private UUID chatRoomId; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "sender_id") | ||
private User sender; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "receiver_id") | ||
private User receiver; | ||
|
||
@Column | ||
private String lastMessage; | ||
|
||
@NotNull | ||
@Column | ||
@Enumerated(EnumType.STRING) | ||
private DeletedStatus deletedStatus; | ||
|
||
public static ChatRoom create(User sender, User receiver) { | ||
return ChatRoom.builder() | ||
.sender(sender) | ||
.receiver(receiver) | ||
.build(); | ||
} | ||
|
||
@Builder | ||
private ChatRoom(User sender, User receiver) { | ||
this.sender = sender; | ||
this.receiver = receiver; | ||
this.deletedStatus = DeletedStatus.NOT_DELETED; | ||
} | ||
|
||
public void updateReceiver(User receiver) { | ||
this.receiver = receiver; | ||
} | ||
|
||
public void updateLastMessage(String lastMessage) { | ||
this.lastMessage = lastMessage; | ||
} | ||
|
||
public void delete() { | ||
this.deletedStatus = DeletedStatus.DELETED; | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/com/leets/xcellentbe/domain/chatRoom/domain/repository/ChatRoomRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.leets.xcellentbe.domain.chatRoom.domain.repository; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import com.leets.xcellentbe.domain.chatRoom.domain.ChatRoom; | ||
import com.leets.xcellentbe.domain.shared.DeletedStatus; | ||
import com.leets.xcellentbe.domain.user.domain.User; | ||
|
||
public interface ChatRoomRepository extends JpaRepository<ChatRoom, UUID> { | ||
|
||
List<ChatRoom> findBySenderOrReceiverAndDeletedStatusNot(User sender, User receiver, DeletedStatus deletedStatus); | ||
|
||
Optional<ChatRoom> findByChatRoomIdAndSenderOrChatRoomIdAndReceiverAndDeletedStatusNot(UUID ChatRoomId, User sender, | ||
UUID ChatRoomId1, User receiver, DeletedStatus deletedStatus); | ||
|
||
ChatRoom findBySenderAndReceiverAndDeletedStatusNot(User sender, User receiver, DeletedStatus deletedStatus); | ||
|
||
Optional<ChatRoom> findByChatRoomIdAndDeletedStatusNot(UUID charRoomId, DeletedStatus deletedStatus); | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/com/leets/xcellentbe/domain/chatRoom/dto/ChatRoomDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.leets.xcellentbe.domain.chatRoom.dto; | ||
|
||
import java.io.Serial; | ||
import java.io.Serializable; | ||
import java.util.UUID; | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
import com.leets.xcellentbe.domain.dm.dto.request.DMRequest; | ||
import com.leets.xcellentbe.domain.user.domain.User; | ||
|
||
@JsonInclude(JsonInclude.Include.NON_NULL) | ||
public record ChatRoomDto(UUID chatRoomId, Long senderId, Long receiverId) implements Serializable { | ||
|
||
@Serial | ||
private static final long serialVersionUID = 6494678977089006639L; | ||
|
||
public static ChatRoomDto of(DMRequest dmRequest, User user) { | ||
return new ChatRoomDto(null, user.getUserId(), dmRequest.receiverId()); | ||
} | ||
|
||
public static ChatRoomDto of(UUID chatRoomId, User sender, User receiver) { | ||
return new ChatRoomDto(chatRoomId, sender.getUserId(), receiver.getUserId()); | ||
} | ||
} |
4 changes: 2 additions & 2 deletions
4
.../exception/ChatRoomNotFoundException.java → .../exception/ChatRoomNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
package com.leets.xcellentbe.domain.chatroom.domain.exception; | ||
package com.leets.xcellentbe.domain.chatRoom.exception; | ||
|
||
import com.leets.xcellentbe.global.error.ErrorCode; | ||
import com.leets.xcellentbe.global.error.exception.CommonException; | ||
|
||
public class ChatRoomNotFoundException extends CommonException { | ||
public ChatRoomNotFoundException() { | ||
|
||
public ChatRoomNotFoundException() { | ||
super(ErrorCode.CHAT_ROOM_NOT_FOUND); | ||
} | ||
} |
148 changes: 148 additions & 0 deletions
148
src/main/java/com/leets/xcellentbe/domain/chatRoom/service/ChatRoomService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package com.leets.xcellentbe.domain.chatRoom.service; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
|
||
import org.springframework.data.redis.core.HashOperations; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.data.redis.listener.ChannelTopic; | ||
import org.springframework.data.redis.listener.RedisMessageListenerContainer; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.stereotype.Service; | ||
|
||
import com.leets.xcellentbe.domain.chatRoom.domain.ChatRoom; | ||
import com.leets.xcellentbe.domain.chatRoom.domain.repository.ChatRoomRepository; | ||
import com.leets.xcellentbe.domain.chatRoom.dto.ChatRoomDto; | ||
import com.leets.xcellentbe.domain.chatRoom.exception.ChatRoomNotFoundException; | ||
import com.leets.xcellentbe.domain.dm.domain.DM; | ||
import com.leets.xcellentbe.domain.dm.domain.repository.DMRepository; | ||
import com.leets.xcellentbe.domain.dm.dto.request.DMRequest; | ||
import com.leets.xcellentbe.domain.dm.dto.response.DMResponse; | ||
import com.leets.xcellentbe.domain.dm.redis.RedisSubscriber; | ||
import com.leets.xcellentbe.domain.shared.DeletedStatus; | ||
import com.leets.xcellentbe.domain.user.domain.User; | ||
import com.leets.xcellentbe.domain.user.domain.repository.UserRepository; | ||
import com.leets.xcellentbe.domain.user.exception.UserNotFoundException; | ||
|
||
import jakarta.annotation.PostConstruct; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ChatRoomService { | ||
|
||
private final ChatRoomRepository chatRoomRepository; | ||
private final DMRepository dmRepository; | ||
private final UserRepository userRepository; | ||
private final RedisMessageListenerContainer redisMessageListener; | ||
private final RedisSubscriber redisSubscriber; | ||
|
||
private static final String Message_Rooms = "MESSAGE_ROOM"; | ||
private final RedisTemplate<String, Object> redisTemplate; | ||
private HashOperations<String, String, ChatRoomDto> opsHashMessageRoom; | ||
|
||
private Map<String, ChannelTopic> topics; | ||
|
||
@PostConstruct | ||
private void init() { | ||
opsHashMessageRoom = redisTemplate.opsForHash(); | ||
topics = new HashMap<>(); | ||
} | ||
|
||
public DMResponse createChatRoom(DMRequest dmRequest, UserDetails userDetails) { | ||
User sender = userRepository.findByEmail(userDetails.getUsername()).orElseThrow(UserNotFoundException::new); | ||
|
||
User receiver = userRepository.findById(dmRequest.receiverId()).orElseThrow(UserNotFoundException::new); | ||
|
||
ChatRoom chatRoom = chatRoomRepository.findBySenderAndReceiverAndDeletedStatusNot(sender, receiver, | ||
DeletedStatus.DELETED); | ||
|
||
if ((chatRoom == null) || (!sender.equals(chatRoom.getSender()) && !receiver.equals( | ||
chatRoom.getReceiver()))) { | ||
ChatRoomDto chatRoomDto = ChatRoomDto.of(dmRequest, sender); | ||
opsHashMessageRoom.put(Message_Rooms, sender.getUserName(), chatRoomDto); | ||
|
||
chatRoom = chatRoomRepository.save(ChatRoom.create(sender, receiver)); | ||
|
||
return DMResponse.from(chatRoom); | ||
} else { | ||
return DMResponse.from(chatRoom.getChatRoomId()); | ||
} | ||
} | ||
|
||
public List<DMResponse> findAllChatRoomByUser(UserDetails userDetails) { | ||
User user = userRepository.findByEmail(userDetails.getUsername()).orElseThrow(UserNotFoundException::new); | ||
|
||
List<ChatRoom> chatRooms = chatRoomRepository.findBySenderOrReceiverAndDeletedStatusNot(user, user, | ||
DeletedStatus.DELETED); | ||
|
||
List<DMResponse> dmResponses = new ArrayList<>(); | ||
|
||
for (ChatRoom chatRoom : chatRooms) { | ||
DMResponse messageRoomDto; | ||
|
||
messageRoomDto = DMResponse.of(chatRoom.getChatRoomId(), chatRoom.getSender(), chatRoom.getReceiver()); | ||
|
||
DM latestMessage = dmRepository.findTopByChatRoomAndDeletedStatusNotOrderByCreatedAtDesc(chatRoom, | ||
DeletedStatus.DELETED); | ||
|
||
if (latestMessage != null) { | ||
messageRoomDto.updateLatestMessageCreatedAt(latestMessage.getCreatedAt()); | ||
messageRoomDto.updateLatestMessageContent(latestMessage.getMessage()); | ||
} | ||
|
||
dmResponses.add(messageRoomDto); | ||
} | ||
|
||
return dmResponses; | ||
} | ||
|
||
public ChatRoomDto findChatRoom(UUID chatRoomId, UserDetails userDetails) { | ||
User user = userRepository.findByEmail(userDetails.getUsername()).orElseThrow(UserNotFoundException::new); | ||
|
||
ChatRoom chatRoom = chatRoomRepository.findByChatRoomIdAndDeletedStatusNot(chatRoomId, DeletedStatus.DELETED) | ||
.orElseThrow(ChatRoomNotFoundException::new); | ||
|
||
User receiver = chatRoom.getReceiver(); | ||
|
||
chatRoom = chatRoomRepository.findByChatRoomIdAndSenderOrChatRoomIdAndReceiverAndDeletedStatusNot(chatRoomId, | ||
user, chatRoomId, receiver, DeletedStatus.DELETED).orElseThrow(ChatRoomNotFoundException::new); | ||
|
||
return ChatRoomDto.of(chatRoom.getChatRoomId(), chatRoom.getSender(), chatRoom.getReceiver()); | ||
} | ||
|
||
public String deleteChatRoom(UUID chatRoomId, UserDetails userDetails) { | ||
User user = userRepository.findByEmail(userDetails.getUsername()).orElseThrow(UserNotFoundException::new); | ||
|
||
ChatRoom chatRoom = chatRoomRepository.findByChatRoomIdAndSenderOrChatRoomIdAndReceiverAndDeletedStatusNot( | ||
chatRoomId, user, chatRoomId, user, DeletedStatus.DELETED).orElseThrow(ChatRoomNotFoundException::new); | ||
|
||
chatRoom.delete(); | ||
chatRoomRepository.save(chatRoom); | ||
|
||
if (user.equals(chatRoom.getSender())) { | ||
opsHashMessageRoom.delete(Message_Rooms, chatRoom.getChatRoomId()); | ||
} | ||
|
||
return "대화방을 삭제했습니다."; | ||
} | ||
|
||
public void enterChatRoom(Long receiverId) { | ||
String receiverName = userRepository.findById(receiverId).orElseThrow(UserNotFoundException::new).getUserName(); | ||
ChannelTopic topic = topics.get(receiverName); | ||
|
||
if (topic == null) { | ||
topic = new ChannelTopic(receiverName); | ||
redisMessageListener.addMessageListener(redisSubscriber, topic); | ||
topics.put(receiverName, topic); | ||
} | ||
} | ||
|
||
public ChannelTopic getTopic(Long receiverId) { | ||
String receiverName = userRepository.findById(receiverId).orElseThrow(UserNotFoundException::new).getUserName(); | ||
return topics.get(receiverName); | ||
} | ||
} |
30 changes: 0 additions & 30 deletions
30
src/main/java/com/leets/xcellentbe/domain/chatroom/domain/Chatroom.java
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.