Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
db947d5
fix : @DBref로 연관짓던 부분을 ObjectId만 저장하도록 설정
X1n9fU Mar 13, 2025
9abd320
fix : chatting 알림을 eventListener로 처리
X1n9fU Mar 13, 2025
eb480c1
fix : UserEntity -> ObjectId로 수정
X1n9fU Mar 13, 2025
d6c2bcb
fix : containsKey로 key의 유무 판단
X1n9fU Mar 14, 2025
d76d666
fix : 최근 메세지가 업데이트 된 시각을 update_at이 아닌 따로 처리, 채팅방들을 해당 시각으로 정렬하여 반환
X1n9fU Mar 14, 2025
03884e2
Merge pull request #172 from CodIN-INU/SC-64-알림서비스
X1n9fU Mar 14, 2025
f87ea62
Merge branch 'develop' of https://github.com/CodIN-INU/BACKEND into f…
X1n9fU Mar 14, 2025
e8b2a6e
fix : 채팅방 목록에서 실시간 안읽은 메세지 확인
X1n9fU Mar 14, 2025
66eb328
Merge pull request #173 from CodIN-INU/feat/appleLogin - test
gisu1102 Mar 14, 2025
412fa0e
Merge pull request #174 from CodIN-INU/feat/appleLogin
gisu1102 Mar 14, 2025
50962db
Merge branch 'main' of https://github.com/CodIN-INU/BACKEND into fix/…
X1n9fU Mar 15, 2025
2d0d15b
perf : 채팅을 받는 유저의 채팅방 목록 업데이트를 위한 로직 추가
X1n9fU Mar 15, 2025
b152f61
fix : 채팅방 구독, 구독취소에 대한 처리로 분기
X1n9fU Mar 15, 2025
df40958
feat : 채팅방 리스트 반환 test를 위한 MVC 설정
X1n9fU Mar 15, 2025
ef3f065
fix : 최근 대화 내용이 없을 경우(null) 해당 채팅방이 마지막으로 반환되도록 처리
X1n9fU Mar 15, 2025
b4c2e6c
fix : 채팅방에 접속이 되지 않은 상태에서만 fcm 알림 발동
X1n9fU Mar 15, 2025
234be9d
fix : 채팅방 생성 시 currentMessageDate도 초기화
X1n9fU Mar 15, 2025
09fc4cc
Update resources-release
X1n9fU Mar 15, 2025
bd86818
Update resources-release
X1n9fU Mar 15, 2025
15d1c98
Merge pull request #176 from CodIN-INU/feat/appleLogin
gisu1102 Mar 15, 2025
df611be
Merge pull request #175 from CodIN-INU/fix/chat-topic&comment
X1n9fU Mar 17, 2025
d2a00c9
Update resources - release
X1n9fU Mar 17, 2025
4b26330
Update resources - release
X1n9fU Mar 17, 2025
b27e9ef
Merge remote-tracking branch 'origin/develop' into develop
X1n9fU Mar 17, 2025
cb1f41a
Merge pull request #179 from CodIN-INU/feat/appleLogin
X1n9fU Mar 17, 2025
ab5b72b
Merge pull request #180 from CodIN-INU/develop
X1n9fU Mar 17, 2025
0a232bc
Update resources-main
X1n9fU Mar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public void configureMessageBroker(MessageBrokerRegistry registry) {
//메세지를 브로커로 라우팅
registry.setApplicationDestinationPrefixes("/pub");
//클라이언트에서 보낸 메세지를 받을 prefix, controller의 @MessageMapping과 이어짐
registry.setUserDestinationPrefix("/user");
//convertAndSendToUser 사용할 prefix
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,24 @@ public void handleMessage(StompHeaderAccessor headerAccessor){
throw new MessageDeliveryException(HttpStatus.BAD_REQUEST.toString());
}

switch (headerAccessor.getCommand()){
case CONNECT -> {
stompMessageService.connectSession(headerAccessor);
}
case SUBSCRIBE -> {
stompMessageService.enterToChatRoom(headerAccessor);
}
case UNSUBSCRIBE -> {
stompMessageService.exitToChatRoom(headerAccessor);
}
case DISCONNECT -> {
stompMessageService.exitToChatRoom(headerAccessor);
stompMessageService.disconnectSession(headerAccessor);
/*
채팅방 구독, 구독취소에 대한 destination만 처리
*/
if (headerAccessor.getDestination()!=null && headerAccessor.getDestination().matches("/queue(/unread)?/[^/]+")) {
switch (headerAccessor.getCommand()) {
case CONNECT -> {
stompMessageService.connectSession(headerAccessor);
}
case SUBSCRIBE -> {
stompMessageService.enterToChatRoom(headerAccessor);
}
case UNSUBSCRIBE -> {
stompMessageService.exitToChatRoom(headerAccessor);
}
case DISCONNECT -> {
stompMessageService.exitToChatRoom(headerAccessor);
stompMessageService.disconnectSession(headerAccessor);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private Result getResult(StompHeaderAccessor headerAccessor) {
log.info(headerAccessor.toString());
String chatroomId;
if (Objects.equals(headerAccessor.getCommand(), StompCommand.DISCONNECT)){ //UNSCRIBE 하지 않은 상태에서 DISCONNECT라면 UNSCRIBE도 같이
if (!sessionStore.get(headerAccessor.getSessionId()).isEmpty()) {
if (sessionStore.containsKey(headerAccessor.getSessionId())) {
chatroomId = sessionStore.get(headerAccessor.getSessionId());
sessionStore.remove(headerAccessor.getSessionId());
} else return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static ChatRoomListResponseDto of(ChatRoom chatRoom, ObjectId userId) {
.chatRoomId(chatRoom.get_id().toString())
.roomName(chatRoom.getRoomName())
.lastMessage(chatRoom.getLastMessage()==null ? null : chatRoom.getLastMessage())
.currentMessageDate(chatRoom.getUpdatedAt()==null ? null : chatRoom.getUpdatedAt())
.currentMessageDate(chatRoom.getCurrentMessageDate()==null ? null : chatRoom.getCurrentMessageDate())
.unread(chatRoom.getParticipants().getInfo().get(userId).getUnreadMessage())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.LocalDateTime;

@Document(collection = "chatroom")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
Expand All @@ -30,13 +32,16 @@ public class ChatRoom extends BaseTimeEntity {

private String lastMessage;

private LocalDateTime currentMessageDate;


@Builder
public ChatRoom(String roomName, ObjectId referenceId, Participants participants, String lastMessage) {
public ChatRoom(String roomName, ObjectId referenceId, Participants participants, String lastMessage, LocalDateTime currentMessageDate) {
this.roomName = roomName;
this.referenceId = referenceId;
this.participants = participants;
this.lastMessage = lastMessage;
this.currentMessageDate = currentMessageDate;
}

public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, ObjectId senderId){
Expand All @@ -47,10 +52,12 @@ public static ChatRoom of(ChatRoomCreateRequestDto chatRoomCreateRequestDto, Obj
.roomName(chatRoomCreateRequestDto.getRoomName())
.referenceId(new ObjectId(chatRoomCreateRequestDto.getReferenceId()))
.participants(participants)
.currentMessageDate(LocalDateTime.now())
.build();
}

public void updateLastMessage(String message){
this.lastMessage = message;
this.currentMessageDate = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package inu.codin.codin.domain.chat.chatroom.service;

import inu.codin.codin.common.dto.BaseTimeEntity;
import inu.codin.codin.common.exception.NotFoundException;
import inu.codin.codin.common.security.util.SecurityUtils;
import inu.codin.codin.domain.block.service.BlockService;
Expand Down Expand Up @@ -84,7 +83,7 @@ public List<ChatRoomListResponseDto> getAllChatRoomByUser() {
return chatRooms.stream()
.filter(chatRoom -> chatRoom.getParticipants().getInfo().keySet().stream()
.noneMatch(blockedUsersId::contains))
.sorted(Comparator.comparing(BaseTimeEntity::getUpdatedAt,Comparator.reverseOrder()))
.sorted(Comparator.comparing(ChatRoom::getCurrentMessageDate,Comparator.nullsLast(Comparator.reverseOrder())))
.map(chatRoom -> ChatRoomListResponseDto.of(chatRoom, userId)).toList();

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ public String chatImageHtml(){
return "chatImage";
}

@GetMapping("/chat/room")
public String chatroomHtml(){
return "chatroom";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package inu.codin.codin.domain.chat.chatting.dto.event;

import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom;
import lombok.Getter;
import org.bson.types.ObjectId;
import org.springframework.context.ApplicationEvent;
@Getter
public class ChattingNotificationEvent extends ApplicationEvent {

private final ChatRoom chatRoom;
private final ObjectId userId;


public ChattingNotificationEvent(Object source, ObjectId userId, ChatRoom chatRoom) {
super(source);
this.userId = userId;
this.chatRoom = chatRoom;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,96 @@

import inu.codin.codin.common.exception.NotFoundException;
import inu.codin.codin.domain.chat.chatroom.entity.ChatRoom;
import inu.codin.codin.domain.chat.chatroom.entity.ParticipantInfo;
import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository;
import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent;
import inu.codin.codin.domain.chat.chatting.dto.event.ChattingNotificationEvent;
import inu.codin.codin.domain.chat.chatting.dto.event.UpdateUnreadCountEvent;
import inu.codin.codin.domain.chat.chatting.entity.Chatting;
import inu.codin.codin.domain.chat.chatting.repository.ChattingRepository;
import inu.codin.codin.domain.notification.service.NotificationService;
import inu.codin.codin.domain.user.entity.UserEntity;
import inu.codin.codin.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;

@Service
@RequiredArgsConstructor
public class ChattingEventListener {

private final ChatRoomRepository chatRoomRepository;
private final ChattingRepository chattingRepository;
private final UserRepository userRepository;
private final SimpMessageSendingOperations template;
private final NotificationService notificationService;

/*
채팅을 발신했을 경우,
1. 상대방이 접속한 상태가 아니라면 상대방의 unread 값 +1
2. 채팅방의 마지막 메세지 업데이트
3. /queue/chatroom/unread 를 통해 상대방의 채팅방 목록 실시간 업데이트
*/
@Async
@EventListener
public void handleChattingArrivedEvent(ChattingArrivedEvent event){
Chatting chatting = event.getChatting();
ChatRoom chatRoom = chatRoomRepository.findById(chatting.getChatRoomId())
.orElseThrow(() -> new NotFoundException("채팅방을 찾을 수 없습니다. ID: "+ chatting.getChatRoomId()));
chatRoom.getParticipants().getInfo().forEach(
(id, participantInfo) -> {
if (!participantInfo.isConnected()) {
participantInfo.plusUnread();
}
}
);
chatRoom.updateLastMessage(chatting.getContent());

updateUnread(event, chatRoom);
chatRoomRepository.save(chatRoom);
chattingRepository.save(chatting);

}

private void updateUnread(ChattingArrivedEvent event, ChatRoom chatRoom) {
Map<String, String> result = new HashMap<>();
ObjectId receiverId = null;
for (Map.Entry<ObjectId, ParticipantInfo> entry : chatRoom.getParticipants().getInfo().entrySet()) {
ParticipantInfo participantInfo = entry.getValue();

if (!participantInfo.getUserId().equals(event.getChatting().getSenderId())) {
if (!participantInfo.isConnected()) {
receiverId = participantInfo.getUserId();
participantInfo.plusUnread();
result = getLastMessageAndUnread(event, participantInfo);
}
}
}
chatRoom.updateLastMessage(event.getChatting().getContent());
if (receiverId!=null) { //받는 사람이 없다는 것은 채팅에 연결 중인 상태, 채팅방 업데이트할 필요 X
Optional<UserEntity> user = userRepository.findByUserId(receiverId);
if (user.isPresent())
template.convertAndSendToUser(user.get().getEmail(), "/queue/chatroom/unread", result);
}
}

private static Map<String, String> getLastMessageAndUnread(ChattingArrivedEvent event, ParticipantInfo participantInfo) {
return Map.of(
"chatRoomId", event.getChatting().get_id().toString(),
"lastMessage", event.getChatting().getContent(),
"unread", String.valueOf(participantInfo.getUnreadMessage())
);
}

@Async
@EventListener
public void handleChattingNotificationEvent(ChattingNotificationEvent event){
event.getChatRoom().getParticipants().getInfo().values().stream()
.filter(participantInfo -> !participantInfo.getUserId().equals(event.getUserId()) && participantInfo.isNotificationsEnabled() & !participantInfo.isConnected())
.peek(participantInfo -> notificationService.sendNotificationMessageByChat(participantInfo.getUserId(), event.getChatRoom().get_id()));
}

/*
유저가 채팅방 입장 시, 읽지 않은 채팅에 대하여 새로운 unread 값 송신
클라이언트 : chat_id 와 일치하는 채팅값의 unread 값 업데이트
*/
@EventListener
public void updateUnreadCountEvent(UpdateUnreadCountEvent updateUnreadCountEvent){
List<Map<String, String>> result = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import inu.codin.codin.domain.chat.chatroom.repository.ChatRoomRepository;
import inu.codin.codin.domain.chat.chatroom.service.ChatRoomService;
import inu.codin.codin.domain.chat.chatting.dto.event.ChattingArrivedEvent;
import inu.codin.codin.domain.chat.chatting.dto.event.ChattingNotificationEvent;
import inu.codin.codin.domain.chat.chatting.dto.request.ChattingRequestDto;
import inu.codin.codin.domain.chat.chatting.dto.response.ChattingAndUserIdResponseDto;
import inu.codin.codin.domain.chat.chatting.dto.response.ChattingResponseDto;
Expand Down Expand Up @@ -62,16 +63,10 @@ public ChattingResponseDto sendMessage(String id, ChattingRequestDto chattingReq
.forEach(ParticipantInfo::remain);
chatRoomRepository.save(chatRoom);

eventPublisher.publishEvent(new ChattingArrivedEvent(this, chatting));
//상대 유저가 접속하지 않은 상태라면 unread 개수 업데이트 및 마지막 대화 내용 업데이트

// //Receiver의 알림 체크 후, 메세지 전송
// for (Participants participant : chatRoom.getParticipants()){
// if (participant.getUserId() != userId && participant.isNotificationsEnabled()){
// notificationService.sendNotificationMessageByChat(participant.getUserId(), chattingRequestDto, chatRoom);
// }
// }

eventPublisher.publishEvent(new ChattingArrivedEvent(this, chatting));
//알림 보내기
eventPublisher.publishEvent(new ChattingNotificationEvent(this, userId, chatRoom));

return ChattingResponseDto.of(chatting);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ public class NotificationEntity extends BaseTimeEntity {
@Id @NotBlank
private ObjectId id;

@DBRef(lazy = true)
private UserEntity user;
private ObjectId userId;

private ObjectId targetId;

Expand All @@ -43,8 +42,8 @@ public class NotificationEntity extends BaseTimeEntity {
private String priority;

@Builder
public NotificationEntity(UserEntity user, ObjectId targetId, String title, String message, String type, String priority) {
this.user = user;
public NotificationEntity(ObjectId userId, ObjectId targetId, String title, String message, String type, String priority) {
this.userId = userId;
this.targetId = targetId;
this.title = title;
this.message = message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package inu.codin.codin.domain.notification.repository;

import inu.codin.codin.domain.notification.entity.NotificationEntity;
import inu.codin.codin.domain.user.entity.UserEntity;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
Expand All @@ -11,8 +10,8 @@

@Repository
public interface NotificationRepository extends MongoRepository<NotificationEntity, ObjectId> {
@Query("{ 'user': ?0, 'isRead': false, deletedAt: null }")
long countUnreadNotificationsByUser(UserEntity user);
@Query("{ 'userId': ?0, 'isRead': false, deletedAt: null }")
long countUnreadNotificationsByUserId(ObjectId userId);

List<NotificationEntity> findAllByUser(UserEntity user);
List<NotificationEntity> findAllByUserId(ObjectId userId);
}
Loading
Loading