Skip to content

Commit

Permalink
Feat : DIG-92 채팅 기능 구현
Browse files Browse the repository at this point in the history
- StompHandler 수정 : CONNECT,SUBSCRIBE,DISCONNET 추가
- ChatMessageService
    - loadMessage 로직 수정 : redis에 저장된 메세지 값에도 인원수에 따라 readCount값을 수정, update된 redis값 추가 설정
    - updateReadCount 추가 : 채팅방에 들어온 인원수에 따라 readCount값을 set 한다. ex) 채팅방에 들어온 인원수가 2일경우 readCount는 0이되고, 그 외의 경우 readCount는 1이된다.
- JwtUtils : token값으로 email 가져오는 로직 추가
  • Loading branch information
YEJINGO committed Nov 13, 2023
1 parent 313201f commit 8d41ede
Show file tree
Hide file tree
Showing 17 changed files with 208 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.Objects;

@Slf4j
@RestController
Expand All @@ -31,9 +32,12 @@ public void message(ChatMessageDto chatMessageDto) {
log.info("채팅 메시지");
chatRoomService.enterChatRoom(chatMessageDto.getRedisRoomId());
chatMessageDto.setCreatedAt(LocalDateTime.now());
ChatMessageDto savedChatMessageDto = chatMessageDto;

ChannelTopic topic = chatRoomService.getTopic(chatMessageDto.getRedisRoomId());
ChatMessageDto savedChatMessageDto = messageService.save(chatMessageDto);
if (!Objects.equals(chatMessageDto.getMessageType(), "ENTER")) {
savedChatMessageDto = messageService.save(chatMessageDto);
}
redisPublisher.publish(topic, savedChatMessageDto);
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/ogjg/daitgym/chat/dto/ChatMessageDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.ogjg.daitgym.domain.ChatMessage;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
Expand All @@ -17,6 +16,7 @@
@NoArgsConstructor
public class ChatMessageDto {

private String messageType;
private Long chatMessageId;
private String sender;
private String message;
Expand All @@ -34,7 +34,7 @@ public ChatMessageDto(ChatMessage chatMessage) {
this.sender = chatMessage.getSender();
this.message = chatMessage.getMessage();
this.readCount = chatMessage.getReadCount();
this.createdAt = LocalDateTime.now();
this.createdAt = chatMessage.getCreatedAt();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
@NoArgsConstructor
public class ChatMessageRequestDto {
private String receiver;
private String receiverImageUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
public class ChatMessageResponseDto {
Expand All @@ -12,13 +14,17 @@ public class ChatMessageResponseDto {
private String redisRoomId;
private String receiver;
private String message;
private String imageUrl;
private LocalDateTime createdAt;

public ChatMessageResponseDto(Long id, String roomName, String redisRoomId, String sender, String receiver, String message) {
public ChatMessageResponseDto(Long id, String roomName, String redisRoomId, String sender, String receiver, String message, String imageUrl,LocalDateTime createdAt) {
this.id = id;
this.roomName = roomName;
this.redisRoomId = redisRoomId;
this.sender = sender;
this.receiver = receiver;
this.message = message;
this.imageUrl = imageUrl;
this.createdAt = createdAt;
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/ogjg/daitgym/chat/dto/ChatRoomDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static ChatRoomDto create(ChatMessageRequestDto messageRequestDto, User u
chatRoomDto.roomName = messageRequestDto.getReceiver();
chatRoomDto.redisRoomId = UUID.randomUUID().toString();
chatRoomDto.sender = user.getNickname();
chatRoomDto.imageUrl = user.getImageUrl();
chatRoomDto.imageUrl = messageRequestDto.getReceiverImageUrl();
chatRoomDto.receiver = messageRequestDto.getReceiver();

return chatRoomDto;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ public ChatRoomResponse(ChatRoom chatRoom) {
this.sender = chatRoom.getSender();
this.redisRoomId = chatRoom.getRedisRoomId();
this.receiver = chatRoom.getReceiver();
this.createdAt = LocalDateTime.now();
this.createdAt = chatRoom.getCreatedAt();
}

public ChatRoomResponse(String redisRoomId) {
this.redisRoomId = redisRoomId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,9 @@

import com.ogjg.daitgym.common.exception.CustomException;
import com.ogjg.daitgym.common.exception.ErrorCode;
import com.ogjg.daitgym.common.exception.ErrorData;

public class NotFoundChattingRoom extends CustomException {
public NotFoundChattingRoom() {
super(ErrorCode.NOT_FOUND_CHATTING_ROOM);
}

public NotFoundChattingRoom( String message) {
public NotFoundChattingRoom(String message) {
super(ErrorCode.NOT_FOUND_CHATTING_ROOM, message);
}

public NotFoundChattingRoom( ErrorData errorData) {
super(ErrorCode.NOT_FOUND_CHATTING_ROOM, errorData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@RequiredArgsConstructor
public class RedisPublisher {

private final RedisTemplate<String, Object> redisTemplate;
private final RedisTemplate<String, ChatMessageDto> redisTemplate;

/**
* 메시지를 redis 서버로 발행
Expand Down
16 changes: 0 additions & 16 deletions src/main/java/com/ogjg/daitgym/chat/pubsub/RedisSubscriber.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ogjg.daitgym.chat.dto.ChatMessageDto;
import com.ogjg.daitgym.chat.repository.ChatMessageRepository;
import com.ogjg.daitgym.domain.ChatMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@Service
Expand All @@ -40,18 +36,6 @@ public void onMessage(Message message, byte[] pattern) {
try {
String publishMessage = (String) redisTemplate.getStringSerializer().deserialize(message.getBody());
ChatMessageDto chatMessageDto = objectMapper.readValue(publishMessage, ChatMessageDto.class);

Long chatMessageId = chatMessageDto.getChatMessageId();
String redisRoomId = chatMessageDto.getRedisRoomId();
ChatMessage chatMessage = chatMessageRepository.findByRedisRoomIdAndId(redisRoomId, chatMessageId);
chatMessageDto.setReadCount(chatMessage.setReadCount());

redisTemplateMessage.setValueSerializer(new Jackson2JsonRedisSerializer<>(ChatMessageDto.class));
redisTemplateMessage.opsForList().rightPush(chatMessage.getRedisRoomId(), chatMessageDto);
redisTemplateMessage.expire(chatMessage.getRedisRoomId(), 60, TimeUnit.MINUTES);
chatMessageRepository.save(chatMessage);


messagingTemplate.convertAndSend("/sub/chat/room/" + chatMessageDto.getRedisRoomId(), chatMessageDto);

} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long>

ChatMessage findTop1ByRedisRoomIdOrderByCreatedAtDesc(String roomId);

ChatMessage findByRedisRoomIdAndId(String redisRoomId, Long chatMessageId);
List<ChatMessage> findAllByRedisRoomId(String redisRoomId);

}
101 changes: 66 additions & 35 deletions src/main/java/com/ogjg/daitgym/chat/service/ChatMessageService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.ogjg.daitgym.chat.service;

import com.ogjg.daitgym.chat.dto.ChatMessageDto;
import com.ogjg.daitgym.chat.exception.NotFoundChattingRoom;
import com.ogjg.daitgym.chat.repository.ChatMessageRepository;
import com.ogjg.daitgym.chat.repository.ChatRoomRepository;
import com.ogjg.daitgym.domain.ChatMessage;
Expand All @@ -12,20 +11,24 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class ChatMessageService {

private final ChatMessageRepository chatMessageRepository;
private final UserRepository userRepository;
private final ChatMessageRepository chatMessageRepository;
private final ChatRoomRepository chatRoomRepository;
private final RedisTemplate<String, Object> redisTemplate;
private final RedisTemplate<String, ChatMessageDto> redisTemplateMessage;

/**
Expand All @@ -36,77 +39,86 @@ public class ChatMessageService {
* 이때 상대방도 sub이 되면 readCount는 0이 되므로, readCount가 0일 때 읽음 표시를 해주면 된다.
*/

@Transactional
public ChatMessageDto save(ChatMessageDto chatMessageDto) {
String sender = chatMessageDto.getSender();
User user = userRepository.findByNickname(sender).orElseThrow(NotFoundUser::new);

ChatRoom chatroom = chatRoomRepository.findByRedisRoomId(chatMessageDto.getRedisRoomId());
int size = Math.toIntExact(redisTemplate.opsForSet().size(chatroom.getRedisRoomId() + "set"));
if (size == 2) {
chatMessageDto.setReadCount(0);
} else {
chatMessageDto.setReadCount(1);
}

ChatMessage chatMessage = ChatMessage.builder()
.sender(chatMessageDto.getSender())
.chatRoom(chatroom)
.message(chatMessageDto.getMessage())
.redisRoomId(chatMessageDto.getRedisRoomId())
.imageUrl(user.getImageUrl())
.build();

chatMessageRepository.save(chatMessage);
chatMessageDto.setChatMessageId(chatMessage.getId());
chatMessageDto.setReadCount(2);

redisTemplateMessage.setValueSerializer(new Jackson2JsonRedisSerializer<>(ChatMessageDto.class));
redisTemplateMessage.opsForList().rightPush(chatMessageDto.getRedisRoomId(), chatMessageDto);
redisTemplateMessage.expire(chatMessageDto.getRedisRoomId(), 60, TimeUnit.MINUTES);
return chatMessageDto;
}

/**
* 전체 메세지 로드하기
* 1. 먼저 Redisd에 저장된 100개의 값을 가져온다.
* 2. 만약 Redis에 값이 비어있다면, chatMessageRepository에서 100개까지의 값을 가져온다.
* 3. 그리고 메세지가 로드되었을 때, readCount값을 추가적으로 설정해주어야 한다.
* 4. 로드된 메세지의 readCouont값이 1일 때, 메신저를 보낸 사람과 유저가 같지 않다면 읽었다는 의미로 1의 값을 0으로 세팅해줘야 한다.
* 1. 먼저 Redisd에 저장된 50개의 값을 가져온다.
* 2. 레디스에 저장되어있는 값이 10개밖에 없다면, 10개만 가져오는 현상이 발생하기에 레디스에 저장된 값이 50개보다 작으면 RDB 에서 가져옴
* 3. Connect 되어 두사람이 채팅방에 있을 때, readCount가 0이 되어야하기 때문에 redis에 저장된 readCount값이 1이라면 0으로 바꿔준다.
* 4. Long size 란 채팅방에 접속해있는 인원을 의미한다.
*/
@Transactional
public List<ChatMessageDto> loadMessage(String roomId, String email) {
User user = userRepository.findByEmail(email).orElseThrow(NotFoundUser::new);
String nickName = user.getNickname();

SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
Long size = setOperations.size(roomId + "set");
updateReadCount(roomId, size);

List<ChatMessageDto> chatMessageDtos = new ArrayList<>();

List<ChatMessageDto> redisMessageList = redisTemplateMessage.opsForList().range(roomId, 0, 99);

if (redisMessageList == null || redisMessageList.isEmpty() || redisMessageList.size()<50) {
if (redisMessageList == null || redisMessageList.isEmpty() || redisMessageList.size() < 100) {
List<ChatMessage> dbMessageList = chatMessageRepository.findTop100ByRedisRoomIdOrderByCreatedAtAsc(roomId);

for (ChatMessage chatMessage : dbMessageList) {
ChatMessageDto chatMessageDto = new ChatMessageDto(chatMessage);
for (int i = 0; i < redisMessageList.size(); i++) {
ChatMessageDto chatMessageDto = new ChatMessageDto(dbMessageList.get(i));
chatMessageDtos.add(chatMessageDto);
redisTemplateMessage.opsForList().set(roomId, i, chatMessageDto);
}
for (int i = redisMessageList.size(); i < dbMessageList.size(); i++) {
ChatMessageDto chatMessageDto = new ChatMessageDto(dbMessageList.get(i));
chatMessageDtos.add(chatMessageDto);

redisTemplateMessage.opsForList().rightPush(roomId, chatMessageDto);
}
} else {
chatMessageDtos.addAll(redisMessageList);
}

List<ChatMessageDto> modifiedChatMessageDtos = new ArrayList<>(chatMessageDtos);

Iterator<ChatMessageDto> iterator = modifiedChatMessageDtos.iterator();
while (iterator.hasNext()) {
ChatMessageDto chatMessageDto = iterator.next();
Long chatMessageId = chatMessageDto.getChatMessageId();
String redisRoomId = chatMessageDto.getRedisRoomId();
ChatMessage chatMessage = chatMessageRepository.findById(chatMessageId).orElseThrow(NotFoundChattingRoom::new);

if (!nickName.equals(chatMessageDto.getSender())) {
if (chatMessageDto.getReadCount() == 1) {
chatMessageDto.setReadCount(chatMessageDto.getReadCount() - 1);

redisTemplateMessage.opsForList().set(redisRoomId, chatMessageDtos.indexOf(chatMessageDto), chatMessageDto);
chatMessage.setReadCount(0);
chatMessageRepository.save(chatMessage);

} else {
for (int i = 0; i < redisMessageList.size(); i++) {
if (redisMessageList.get(i).getReadCount() == 1 && size == 2) {
redisMessageList.get(i).setReadCount(0);
redisTemplateMessage.opsForList().set(roomId, i, redisMessageList.get(i));
}
}
chatMessageDtos.addAll(redisMessageList);
}

return modifiedChatMessageDtos;
return chatMessageDtos;
}

/**
* 채팅 목록 가져올 때, 가장 최신 메시지 하나만 보여주기 위한 로직
*/

@Transactional
public ChatMessageDto latestMessage(String roomId) {

ChatMessageDto latestMessage = redisTemplateMessage.opsForList().index(roomId, -1);
Expand All @@ -121,4 +133,23 @@ public ChatMessageDto latestMessage(String roomId) {
}
return latestMessage;
}

@Transactional
public void updateReadCount(String redisRoomId, Long size) {
List<ChatMessage> chatMessages = chatMessageRepository.findAllByRedisRoomId(redisRoomId);
if (!chatMessages.isEmpty()) {
if (size == 2) {
for (ChatMessage chatMessage : chatMessages) {
chatMessage.setReadCount(0);
chatMessageRepository.save(chatMessage);
}
} else {
for (ChatMessage chatMessage : chatMessages) {
chatMessage.setReadCount(1);
chatMessageRepository.save(chatMessage);

}
}
}
}
}
Loading

0 comments on commit 8d41ede

Please sign in to comment.