diff --git a/build.gradle b/build.gradle index e9fe60c..8d24db6 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,10 @@ dependencies { //Websocket implementation 'org.springframework.boot:spring-boot-starter-websocket' + // rabbitmq + implementation 'com.rabbitmq:amqp-client:5.12.0' + implementation 'org.springframework.boot:spring-boot-starter-amqp' + //Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.amqp:spring-rabbit-test' diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/business/ChatBusiness.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/business/ChatBusiness.java new file mode 100644 index 0000000..2faf606 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/business/ChatBusiness.java @@ -0,0 +1,140 @@ +package com.aliens.friendship.backend_chatting_server.chatting.business; + +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSummariesResponse; +import com.aliens.friendship.backend_chatting_server.chatting.service.ChatService; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import com.aliens.friendship.backend_chatting_server.fcm.service.FcmService; +import com.aliens.friendship.backend_chatting_server.global.common.Business; +import com.aliens.friendship.backend_chatting_server.global.util.jwt.JwtTokenUtil; +import com.aliens.friendship.backend_chatting_server.websocket.converter.WebsocketConverter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.BulkReadRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.ChatSendRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.SingleReadRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.BulkReadWebsocketResponse; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.ChatSendWebsocketResponse; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.SingleReadWebsocketResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.firebase.messaging.FirebaseMessagingException; +import lombok.RequiredArgsConstructor; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.List; + +@Business +@RequiredArgsConstructor +public class ChatBusiness { + private final ChatService chatService; + private final JwtTokenUtil jwtTokenUtil; + private final ObjectMapper objectMapper; + private final FcmService fcmService; + private final WebsocketConverter websocketConverter; + + public void sendChat(WebSocketSession session, TextMessage message, List roomIds) throws FirebaseMessagingException, IOException { + // 요청 역직렬화 + ChatSendRequest chatSendRequest = objectMapper.readValue(message.getPayload(), ChatSendRequest.class); + + // roomId 검증 + chatService.validateRoomId(chatSendRequest.getRoomId(), roomIds); + + // 채팅 저장 + ChatSendResponse chatSendResponse = chatService.saveChat(chatSendRequest); + + // 상대방에게 새로운 채팅 FCM 전송 + fcmService.sendSingleChatToReceiverByToken(chatSendResponse); + + // 채팅 전송 요청에 대한 응답 생성 + ChatSendWebsocketResponse chatWebSocketResponse = websocketConverter.toChatSendWebsocketResponseWithRequest(chatSendRequest, chatSendResponse); + + // 응답 직렬화 + String response = objectMapper.writeValueAsString(chatWebSocketResponse); + + // response를 WebSocketSession에 전송 + session.sendMessage(new TextMessage(response)); + } + + public void updateSingleRead(WebSocketSession session, TextMessage message, List roomIds) throws FirebaseMessagingException, IOException { + // 요청 역직렬화 + SingleReadRequest singleReadRequest = objectMapper.readValue(message.getPayload(), SingleReadRequest.class); + + // roomId 검증 + chatService.validateRoomId(singleReadRequest.getRoomId(), roomIds); + + // chatId로 chatEntity 조회 + ChatEntity chatEntity = chatService.getChatEntity(singleReadRequest.getChatId()); + + // 단일 메시지 읽음 상태 업데이트 + chatService.updateReadStateByChatId(chatEntity); + + // 상대방에게 읽음 처리 FCM 전송 + fcmService.sendSingleChatReadToSenderByToken(chatEntity); + + // 읽기 처리 요청에 대한 응답 생성 + SingleReadWebsocketResponse singleReadWebsocketResponse = websocketConverter.toSingleReadWebsocketResponseWithRequest(singleReadRequest); + + // 응답 직렬화 + String response = objectMapper.writeValueAsString(singleReadWebsocketResponse); + + // response를 WebSocketSession에 전송 + session.sendMessage(new TextMessage(response)); + } + + public void updateBulkRead(WebSocketSession session, TextMessage message, List roomIds) throws FirebaseMessagingException, IOException { + // 요청 역직렬화 + BulkReadRequest bulkReadRequest = objectMapper.readValue(message.getPayload(), BulkReadRequest.class); + + // roomId 검증 + chatService.validateRoomId(bulkReadRequest.getRoomId(), roomIds); + + // 채팅방기준 일괄 읽음 상태 업데이트 + Long chatId = chatService.updateReadStateByRoomId(bulkReadRequest.getRoomId(), bulkReadRequest.getPartnerId()); + + // 상대방에게 채팅방 기준 일괄 읽음 처리 FCM 전송 + fcmService.sendBulkChatReadToSenderByToken(bulkReadRequest); + + // 일괄 처리 요청에 대한 웹소켓 응답 생성 + BulkReadWebsocketResponse readByRoomWebsocketResponse = websocketConverter.toBulkReadWebsocketResponseWithRequest(bulkReadRequest); + + // 응답 직렬화 + String response = objectMapper.writeValueAsString(readByRoomWebsocketResponse); + + // response를 WebSocketSession에 전송 + session.sendMessage(new TextMessage(response)); + } + + public List afterWebsocketConnectionEstablished(WebSocketSession session) { + // header JWT 토큰 가져오기 + String JWTToken = session.getHandshakeHeaders().get("Authorization").get(0); + + // 토큰 검증 + jwtTokenUtil.validateToken(JWTToken); + + // header token에서 roomId 추출후 반환 + return jwtTokenUtil.getRoomIdsFromToken(JWTToken); + } + + public List getUnreadChatsByRoomId(String jwtToken, Long roomId) throws JsonProcessingException { + // 유효 roomId 추출 + List roomIdsFromJwt = jwtTokenUtil.getRoomIdsFromToken(jwtToken); + + // roomId 검증 + chatService.validateRoomId(roomId, roomIdsFromJwt); + + // 읽지 않은 채팅 목록 반환 + return chatService.getUnreadChatsByRoomId(roomId); + } + + public ChatSummariesResponse getSummaryChats(String jwtToken) { + // 유효 roomId 추출 + List roomIdsFromJwt = jwtTokenUtil.getRoomIdsFromToken(jwtToken); + + // 요청한 멤버의 memberId 추출 + Long memberId = jwtTokenUtil.getCurrentMemberIdFromToken(jwtToken); + + // 채팅 요약 정보 반환 + return chatService.getChatSummaries(roomIdsFromJwt, memberId); + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/controller/ChatController.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/controller/ChatController.java index 4d25659..2c399ee 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/controller/ChatController.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/controller/ChatController.java @@ -1,65 +1,31 @@ package com.aliens.friendship.backend_chatting_server.chatting.controller; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatRequestDto; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatResponseDto; -import com.aliens.friendship.backend_chatting_server.chatting.dto.NewChatWithCountOfUnreadChats; -import com.aliens.friendship.backend_chatting_server.fcm.service.FCMService; -import com.aliens.friendship.backend_chatting_server.jwt.JwtTokenProvider; +import com.aliens.friendship.backend_chatting_server.chatting.business.ChatBusiness; +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSummariesResponse; +import com.aliens.friendship.backend_chatting_server.fcm.service.FcmService; +import com.aliens.friendship.backend_chatting_server.global.util.jwt.JwtTokenUtil; import com.aliens.friendship.backend_chatting_server.chatting.service.ChatService; +import com.fasterxml.jackson.core.JsonProcessingException; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; import java.util.List; @RestController @RequestMapping("/api/v1") @RequiredArgsConstructor public class ChatController { + private final ChatBusiness chatBusiness; - private final SimpMessageSendingOperations msgTemplate; - private final ChatService chatService; - private final JwtTokenProvider jwtTokenProvider; - private final FCMService FCMService; - - //채팅 전송 및 저장 - @MessageMapping("/chat/msg") - public void sendMessage(@RequestHeader("ChattingToken") String jwtToken, @RequestBody @Valid ChatRequestDto chatRequestDto) { - jwtTokenProvider.validateToken(jwtToken); - FCMService.sendSingleChatToReceiverByToken(chatService.addChat(chatRequestDto)); - } - - @MessageMapping("/chat/msg/{chatId}") - public void changeReadStatus(@RequestHeader("ChattingToken") String jwtToken, @PathVariable Long chatId) { - jwtTokenProvider.validateToken(jwtToken); - FCMService.sendReadChatToSenderByToken(chatService.changeChatToReadByChatId(chatId)); - } - - // 현재 사용자의 jwt 기반으로 룸들의 새로운 채팅과 안읽은 채팅개수를 반환시켜준다 . - @GetMapping("/chat") - public ResponseEntity> getNewChatAndCountOfUnreadChats(@RequestHeader("ChattingToken") String jwtToken) { - jwtTokenProvider.validateToken(jwtToken); - List roomIdsFromToken = jwtTokenProvider.getRoomIdsFromToken(jwtToken); - Long currentMemberId = jwtTokenProvider.getCurrentMemberIdFromToken(jwtToken); - return new ResponseEntity<>(chatService.getNewChatAndNotReadCountOfChatInEachRoomsByRoomIds(currentMemberId, roomIdsFromToken), HttpStatus.OK); - } - - //현재 사용자의 jwt 안에 RoomId와 현재 얻고자하는 roomId를 비교하여 - // 접근이 가능하다면 해당 채팅방의 100개 채팅내역을 가져온다. @GetMapping("/chat/{roomId}") - public ResponseEntity> getHundredChatsByRoomId(@RequestHeader("ChattingToken") String jwtToken, @PathVariable Long roomId) { - jwtTokenProvider.validateToken(jwtToken); - List roomIdsFromToken = jwtTokenProvider.getRoomIdsFromToken(jwtToken); - - boolean validRoomId = roomIdsFromToken.contains(roomId); - - return validRoomId - ? ResponseEntity.ok(chatService.getHundredChatsByRoomId(roomId)) - : ResponseEntity.badRequest().build(); + public ResponseEntity> getUnreadChatsByRoomId(@RequestHeader("ChattingToken") String jwtToken, @PathVariable Long roomId) throws JsonProcessingException { + return ResponseEntity.ok(chatBusiness.getUnreadChatsByRoomId(jwtToken, roomId)); } + @GetMapping("/chat/summary") + public ResponseEntity getSummaryChatsByRoomId(@RequestHeader("ChattingToken") String jwtToken) { + return ResponseEntity.ok(chatBusiness.getSummaryChats(jwtToken)); + } } diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/converter/ChatConverter.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/converter/ChatConverter.java new file mode 100644 index 0000000..655ee5c --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/converter/ChatConverter.java @@ -0,0 +1,38 @@ +package com.aliens.friendship.backend_chatting_server.chatting.converter; + +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatType; +import com.aliens.friendship.backend_chatting_server.global.common.Converter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.ChatSendRequest; + +@Converter +public class ChatConverter { + + public ChatEntity toChatEntityWithRequest(ChatSendRequest chatSendRequest) { + return ChatEntity.builder() + .roomId(chatSendRequest.getRoomId()) + .senderId(chatSendRequest.getSenderId()) + .senderName(chatSendRequest.getSenderName()) + .receiverId(chatSendRequest.getReceiverId()) + .chatType(ChatType.valueOf(chatSendRequest.getChatType())) + .chatContent(chatSendRequest.getChatContent()) + .sendTime(chatSendRequest.getSendTime()) + .unreadCount(1) + .build(); + } + + public ChatSendResponse toChatSendResponseWithEntity(ChatEntity chatEntity) { + return ChatSendResponse.builder() + .chatId(chatEntity.getChatId()) + .chatType(chatEntity.getChatType().getValue()) + .chatContent(chatEntity.getChatContent()) + .roomId(chatEntity.getRoomId()) + .senderId(chatEntity.getSenderId()) + .senderName(chatEntity.getSenderName()) + .receiverId(chatEntity.getReceiverId()) + .sendTime(chatEntity.getSendTime()) + .unreadCount(chatEntity.getUnreadCount()) + .build(); + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/ChatMessageCategory.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/ChatMessageCategory.java deleted file mode 100644 index f45fbd1..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/ChatMessageCategory.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.chatting.domain; - -public enum ChatMessageCategory { - VS_GAME_MESSAGE, - NORMAL_MESSAGE, - ALL_NOTICE_MESSAGE // 채팅이 곧 끝난다는 마감 공지, 처벌 주의 공지 메시지 용도 -} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/ChatRequestDto.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/ChatRequestDto.java deleted file mode 100644 index a8e57a0..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/ChatRequestDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.chatting.dto; - -import com.aliens.friendship.backend_chatting_server.chatting.domain.Chat; -import com.aliens.friendship.backend_chatting_server.chatting.domain.ChatMessageCategory; -import lombok.*; - -import java.time.Instant; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class ChatRequestDto { - - private Long roomId; - private Long senderId; - private Long receiverId; - private String message; - private ChatMessageCategory messageCategory; - - /* Dto -> Entity */ - public Chat toEntity() { - return Chat.builder() - .roomId(this.getRoomId()) - .senderId(this.getSenderId()) - .receiverId(this.getReceiverId()) - .message(this.getMessage()) - .messageCategory(this.getMessageCategory()) - .createTime(Instant.now()) - .read(false) - .build(); - } -} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/ChatResponseDto.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/ChatResponseDto.java deleted file mode 100644 index 9d8b84c..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/ChatResponseDto.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.chatting.dto; - -import com.aliens.friendship.backend_chatting_server.chatting.domain.Chat; -import com.aliens.friendship.backend_chatting_server.chatting.domain.ChatMessageCategory; -import lombok.*; - - -@Data -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class ChatResponseDto { - private Long roomId; - private Long senderId; - private Long receiverId; - private String message; - private ChatMessageCategory messageCategory; - - private Long chatId; - private String createTime; - private String read; - - /* Entity -> Dto*/ - public ChatResponseDto(Chat chat) { - this.roomId = chat.getRoomId(); - this.senderId = chat.getSenderId(); - this.receiverId = chat.getReceiverId(); - this.message = chat.getMessage(); - this.messageCategory = chat.getMessageCategory(); - this.chatId = chat.getChatId(); - this.createTime = chat.getCreateTime().toString(); - this.read = chat.isRead() ? "true" : "false"; - } -} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/response/ChatSendResponse.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/response/ChatSendResponse.java new file mode 100644 index 0000000..75fd479 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/response/ChatSendResponse.java @@ -0,0 +1,23 @@ +package com.aliens.friendship.backend_chatting_server.chatting.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ChatSendResponse { + private Long chatId; + private Integer chatType; + private String chatContent; + private Long roomId; + private Long senderId; + private String senderName; + private Long receiverId; + private String sendTime; + private Integer unreadCount; +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/response/ChatSummariesResponse.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/response/ChatSummariesResponse.java new file mode 100644 index 0000000..4f2f92b --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/response/ChatSummariesResponse.java @@ -0,0 +1,31 @@ +package com.aliens.friendship.backend_chatting_server.chatting.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ChatSummariesResponse { + private List ChatSummaries = new ArrayList<>(); + public void addChatSummary(ChatSummary chatSummary) { + this.ChatSummaries.add(chatSummary); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class ChatSummary { + private Long roomId; + private String lastChatContent; + private String lastChatTime; + private Long numberOfUnreadChat; + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/repository/ChatRepository.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/repository/ChatRepository.java deleted file mode 100644 index 92a5477..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/repository/ChatRepository.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.chatting.repository; - -import com.aliens.friendship.backend_chatting_server.chatting.domain.Chat; -import org.springframework.data.mongodb.repository.Aggregation; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.mongodb.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface ChatRepository extends MongoRepository { - @Aggregation(pipeline = { - "{ '$match': { 'roomId' : ?0 } }", - "{ '$sort' : { 'chatId' : -1 } }", - "{ '$limit' : 1 }" - }) - Optional findNewOneChatByRoomId(Long roomId); - - @Aggregation(pipeline = { - "{ '$match': { 'roomId' : ?0 } }", - "{ '$sort' : { 'chatId' : -1 } }", - "{ '$limit' : 100 }" - }) - Optional> findHundredChatsByRoomId(Long roomId); - - - @Query(value = "{roomId : ?0, read : false, receiverId : ?2}") - long countUnreadChatsByRoomIdAndReceiverId(Long roomId,Long receiverId ); - - @Query(value = "{chatId : ?0") - Optional findByChatId(Long chatId); -} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/service/ChatService.java b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/service/ChatService.java index 3117474..7ce3412 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/service/ChatService.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/service/ChatService.java @@ -1,21 +1,26 @@ package com.aliens.friendship.backend_chatting_server.chatting.service; -import com.aliens.friendship.backend_chatting_server.chatting.domain.Chat; -import com.aliens.friendship.backend_chatting_server.chatting.domain.ChatMessageCategory; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatRequestDto; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatResponseDto; -import com.aliens.friendship.backend_chatting_server.chatting.dto.NewChatWithCountOfUnreadChats; -import com.aliens.friendship.backend_chatting_server.chatting.repository.ChatRepository; +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSummariesResponse; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import com.aliens.friendship.backend_chatting_server.global.util.jwt.JwtTokenUtil; +import com.aliens.friendship.backend_chatting_server.chatting.converter.ChatConverter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.ChatSendRequest; +import com.aliens.friendship.backend_chatting_server.db.chat.repository.ChatRepository; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @Slf4j @Service @@ -24,55 +29,93 @@ public class ChatService { private final ChatRepository chatRepository; - //채팅 저장 - public ChatResponseDto addChat(ChatRequestDto chatRequestDto) { - Chat singleChat = chatRepository.save(chatRequestDto.toEntity()); - return new ChatResponseDto(singleChat); - } + private final ChatConverter chatConverter; + + private final MongoTemplate mongoTemplate; + + private final MongoOperations mongoOperations; + + private final JwtTokenUtil jwtTokenUtil; - // 단일 읽음처리 - public ChatResponseDto changeChatToReadByChatId(Long chatId) { - Optional singleChat = chatRepository.findByChatId(chatId); - singleChat.ifPresent(t -> { - t.changeReadState(); - this.chatRepository.save(t); - }); - return new ChatResponseDto(chatRepository.findByChatId(chatId).get()); + + public ChatSendResponse saveChat(ChatSendRequest chatSendRequest) { + ChatEntity singleChatEntity = chatRepository.save(chatConverter.toChatEntityWithRequest(chatSendRequest)); + return chatConverter.toChatSendResponseWithEntity(singleChatEntity); + } + + public List getUnreadChatsByRoomId(Long roomId) { + Optional> chats = chatRepository.findUnreadChatsByRoomId(roomId); + List result = new ArrayList<>(); + for (ChatEntity chatEntity : chats.orElse(new ArrayList<>())) { + result.add(chatConverter.toChatSendResponseWithEntity(chatEntity)); + } + return result; } + public Long updateReadStateByRoomId(Long roomId, Long partnerId) { + Query query = new Query(Criteria.where("roomId").is(roomId).and("senderId").is(partnerId)); + Update update = new Update().set("unreadCount", 0); + mongoTemplate.updateMulti(query, update, ChatEntity.class); + return roomId; + } - //각 채팅방의 가장 최근 채팅과 안읽은 채팅개수 반환 - public List getNewChatAndNotReadCountOfChatInEachRoomsByRoomIds(Long currentMemberId, List roomIds) { - return roomIds.stream() - .map(roomId -> { - NewChatWithCountOfUnreadChats dto = new NewChatWithCountOfUnreadChats(); - Optional oneChat = chatRepository.findNewOneChatByRoomId(roomId); - dto.setRoomId(roomId); - dto.setNewChat(oneChat.map(Chat::getMessage).orElse("채팅을 시작하세요!")); - dto.setCountOfUnreadChats(chatRepository.countUnreadChatsByRoomIdAndReceiverId(roomId, currentMemberId)); - return dto; - }) - .collect(Collectors.toList()); + public void updateReadStateByChatId(ChatEntity chatEntity) { + Query query = new Query(Criteria.where("_id").is(chatEntity.getChatId())); + Update update = new Update().set("unreadCount", 0); + mongoTemplate.updateFirst(query, update, ChatEntity.class); } + public ChatSummariesResponse getChatSummaries(List roomIds, Long memberId) { + ChatSummariesResponse chatSummariesResponse = new ChatSummariesResponse(); + for(Long roomId: roomIds){ + String lastChatContent = chatRepository.findNewOneChatByRoomId(roomId).isPresent() ? chatRepository.findNewOneChatByRoomId(roomId).get().getChatContent() : "채팅을 시작하세요!"; + String lastChatTime = chatRepository.findNewOneChatByRoomId(roomId).isPresent() ? chatRepository.findNewOneChatByRoomId(roomId).get().getSendTime() : "기록 없음"; + Long numberOfUnreadChats = getNumberOfUnreadChats(roomId,memberId); - // 채팅방 하나의 최근 채팅 최대 100개 반환 - public List getHundredChatsByRoomId(Long roomId) { - Optional> chats = chatRepository.findHundredChatsByRoomId(roomId); - List result = new ArrayList<>(); + ChatSummariesResponse.ChatSummary chatSummary = ChatSummariesResponse.ChatSummary.builder() + .roomId(roomId) + .lastChatContent(lastChatContent) + .lastChatTime(lastChatTime) + .numberOfUnreadChat(numberOfUnreadChats) + .build(); + chatSummariesResponse.addChatSummary(chatSummary); + } + return chatSummariesResponse; + } - chats.ifPresentOrElse( - c -> c.stream() - .map(ChatResponseDto::new) - .forEach(result::add), - () -> { - Chat firstChat = new Chat(0L, 0L, 0L, 0L, "채팅을 시작하세요!", ChatMessageCategory.ALL_NOTICE_MESSAGE, Instant.now(), true); - result.add(new ChatResponseDto(firstChat)); - } - ); + public Long getNumberOfUnreadChats(Long roomId, Long memberId) { + Query query = new Query(Criteria.where("roomId").is(roomId) + .and("unreadCount").is(1) + .and("receiverId").is(memberId)); + long count = mongoOperations.count(query, ChatEntity.class); + return count; + } - return result; + public ChatEntity getChatEntity(Long chatId) { + return chatRepository.findByChatId(chatId).get(); + } + + public void validateRoomId(Long roomIdFromPayload, List roomIds) throws JsonProcessingException { + if (!roomIds.contains(roomIdFromPayload)) { + throw new IllegalArgumentException("roomId가 일치하지 않습니다."); + } } +// public Object getUnreadChatsByRoomId(Long roomId, Long lastPartnerChatId, Long lastMyChatId, Long partnerId, Long memberId) { +// // 상대의 채팅 아이디중 가장 최근 값 이후의 채팅이 있다면 가져온다. +// // 내 채팅 아이디중 가장 최근 값 이후의 채팅이 있다면 가져온다. +// // 각각의 배열로 넣어 반환한다. +// List partnerChats = chatRepository.findNextChatsByRoomId(roomId, lastPartnerChatId,partnerId).orElse(new ArrayList<>()); +// List myChats = chatRepository.findNextChatsByRoomId(roomId, lastMyChatId,memberId).orElse(new ArrayList<>()); +// List partnerChatSendResponses = new ArrayList<>(); +// List myChatSendResponses = new ArrayList<>(); +// for (ChatEntity chatEntity : partnerChats) { +// partnerChatSendResponses.add(chatConverter.toChatSendResponseWithEntity(chatEntity)); +// } +// for (ChatEntity chatEntity : myChats) { +// myChatSendResponses.add(chatConverter.toChatSendResponseWithEntity(chatEntity)); +// } +// return new Object[]{partnerChatSendResponses, myChatSendResponses}; +// } } \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/Chat.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/ChatEntity.java similarity index 62% rename from src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/Chat.java rename to src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/ChatEntity.java index 031076f..22af387 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/Chat.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/ChatEntity.java @@ -1,42 +1,35 @@ -package com.aliens.friendship.backend_chatting_server.chatting.domain; +package com.aliens.friendship.backend_chatting_server.db.chat.entity; import lombok.*; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.mongodb.core.mapping.Document; -import java.time.Instant; - @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder @ToString -@Document(collection = "chat") -public class Chat { +@Document(collection = "chats") +public class ChatEntity { @Transient public static final String SEQUENCE_NAME = "chat_sequence"; @Id private Long chatId; - + private ChatType chatType; + private String chatContent; private Long roomId; private Long senderId; + private String senderName; private Long receiverId; - private String message; - private ChatMessageCategory messageCategory; - private Instant createTime; - private boolean read; + private String sendTime; + private int unreadCount; public void setId(Long generateSequence) { this.chatId = generateSequence; } - public boolean isRead(){ - return read; - } - public void changeReadState() { - this.read = true; + this.unreadCount = 0; } - -} +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/ChatType.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/ChatType.java new file mode 100644 index 0000000..15b7c8a --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/ChatType.java @@ -0,0 +1,26 @@ +package com.aliens.friendship.backend_chatting_server.db.chat.entity; + +public enum ChatType { + NORMAL_MESSAGE(0), + VS_GAME_MESSAGE(1), + ALL_NOTICE_MESSAGE(2); + + private int value; + + ChatType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ChatType valueOf(int value) { + for (ChatType chatType : ChatType.values()) { + if (chatType.getValue() == value) { + return chatType; + } + } + throw new IllegalArgumentException("Invalid ChatType value: " + value); + } +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/AutoIncrementSequence.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/AutoIncrementSequence.java similarity index 78% rename from src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/AutoIncrementSequence.java rename to src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/AutoIncrementSequence.java index 21d872b..30b8599 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/AutoIncrementSequence.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/AutoIncrementSequence.java @@ -1,4 +1,4 @@ -package com.aliens.friendship.backend_chatting_server.chatting.domain.autoincrement; +package com.aliens.friendship.backend_chatting_server.db.chat.entity.autoincrement; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/ChatListener.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/ChatListener.java similarity index 64% rename from src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/ChatListener.java rename to src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/ChatListener.java index 8f3a0cc..73b0243 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/ChatListener.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/ChatListener.java @@ -1,6 +1,6 @@ -package com.aliens.friendship.backend_chatting_server.chatting.domain.autoincrement; +package com.aliens.friendship.backend_chatting_server.db.chat.entity.autoincrement; -import com.aliens.friendship.backend_chatting_server.chatting.domain.Chat; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; import lombok.RequiredArgsConstructor; import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; @@ -8,12 +8,12 @@ @RequiredArgsConstructor @Component -public class ChatListener extends AbstractMongoEventListener { +public class ChatListener extends AbstractMongoEventListener { private final SequenceGeneratorService generatorService; @Override - public void onBeforeConvert(BeforeConvertEvent event) { - event.getSource().setId(generatorService.generateSequence(Chat.SEQUENCE_NAME)); + public void onBeforeConvert(BeforeConvertEvent event) { + event.getSource().setId(generatorService.generateSequence(ChatEntity.SEQUENCE_NAME)); } } \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/SequenceGeneratorService.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/SequenceGeneratorService.java similarity index 91% rename from src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/SequenceGeneratorService.java rename to src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/SequenceGeneratorService.java index 2099bb7..38cfbfb 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/domain/autoincrement/SequenceGeneratorService.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/entity/autoincrement/SequenceGeneratorService.java @@ -1,4 +1,4 @@ -package com.aliens.friendship.backend_chatting_server.chatting.domain.autoincrement; +package com.aliens.friendship.backend_chatting_server.db.chat.entity.autoincrement; import lombok.RequiredArgsConstructor; import org.springframework.data.mongodb.core.MongoOperations; diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/repository/ChatRepository.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/repository/ChatRepository.java new file mode 100644 index 0000000..1c2ed76 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/repository/ChatRepository.java @@ -0,0 +1,49 @@ +package com.aliens.friendship.backend_chatting_server.db.chat.repository; + +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSummariesResponse; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import org.springframework.data.mongodb.repository.Aggregation; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Repository +public interface ChatRepository extends MongoRepository { + @Aggregation(pipeline = { + "{ '$match': { 'roomId' : ?0 } }", + "{ '$sort' : { 'chatId' : -1 } }", + "{ '$limit' : 1 }" + }) + Optional findNewOneChatByRoomId(Long roomId); + + @Aggregation(pipeline = { + "{ '$match': { 'roomId' : ?0 } }", + "{ '$sort' : { 'chatId' : -1 } }", + "{ '$limit' : 100 }" + }) + Optional> findChatsByRoomId(Long roomId); + + @Aggregation(pipeline = { + "{ '$match': { 'roomId' : ?0, 'chatId' : { '$lt' : ?1 } } }", + "{ '$sort' : { 'chatId' : -1 } }", + "{ '$limit' : 100 }" + }) + + @Query(value = "{roomId : ?0, chatId : {$lt : ?1}, senderId : ?2}") + Optional> findNextChatsByRoomId(Long roomId, Long latestChatId, Long memberId); + + @Query(value = "{_id : ?0}") + Optional findByChatId(Long chatId); + + @Query("{'_id': ?0}") + void updateUnReadCount(Long chatId, int unReadCount); + + @Query(value = "{roomId : ?0, unreadCount : 1}") + Optional> findUnreadChatsByRoomId(Long roomId); + + Optional findChatSummariesByRoomId(Long memberId); +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/repository/ChatRepositoryImpl.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/repository/ChatRepositoryImpl.java new file mode 100644 index 0000000..e822ab4 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/chat/repository/ChatRepositoryImpl.java @@ -0,0 +1,27 @@ +package com.aliens.friendship.backend_chatting_server.db.chat.repository; + +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Repository; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +@Repository +public class ChatRepositoryImpl { + private final MongoTemplate mongoTemplate; + + @Autowired + public ChatRepositoryImpl(MongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + } + + public void setUnReadCountToZero(String chatId) { + ChatEntity chatEntity = mongoTemplate.findOne(query(where("chatId").is(chatId)), ChatEntity.class); + if (chatEntity != null) { + chatEntity.changeReadState(); + mongoTemplate.save(chatEntity); + } + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/db/fcm/entity/FcmTokenEntity.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/fcm/entity/FcmTokenEntity.java new file mode 100644 index 0000000..bcc206d --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/fcm/entity/FcmTokenEntity.java @@ -0,0 +1,25 @@ +package com.aliens.friendship.backend_chatting_server.db.fcm.entity; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Document(collection = "fcm_token") +public class FcmTokenEntity { + @Id + private String id; + private Long memberId; + private String value; + + public FcmTokenEntity( + String memberPersonalId, + String value + ) { + this.memberId = Long.parseLong(memberPersonalId); + this.value = value; + } +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/db/fcm/repository/FcmTokenRepository.java b/src/main/java/com/aliens/friendship/backend_chatting_server/db/fcm/repository/FcmTokenRepository.java new file mode 100644 index 0000000..5620730 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/db/fcm/repository/FcmTokenRepository.java @@ -0,0 +1,12 @@ +package com.aliens.friendship.backend_chatting_server.db.fcm.repository; + +import com.aliens.friendship.backend_chatting_server.db.fcm.entity.FcmTokenEntity; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface FcmTokenRepository extends MongoRepository { + List findAllByMemberId(Long memberId); +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/controller/FCMController.java b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/controller/FCMController.java deleted file mode 100644 index f4046a8..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/controller/FCMController.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.fcm.controller; - -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatResponseDto; -import com.aliens.friendship.backend_chatting_server.fcm.dto.MemberIdWithFCMTokenDto; -import com.aliens.friendship.backend_chatting_server.fcm.service.FCMService; -import com.google.firebase.messaging.FirebaseMessagingException; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/fcm") -public class FCMController { - - private final FCMService FCMService; - - @PostMapping("/token") - public void setFcmToken(@RequestBody MemberIdWithFCMTokenDto memberWithFCMTokenDto) throws FirebaseMessagingException { - FCMService.addFCMToken(memberWithFCMTokenDto); - } - - @PostMapping("/all-notice") - public void pushMessageToAll(@RequestBody Map allNoticeMessage) throws FirebaseMessagingException { - FCMService.sendNoticeToAll(allNoticeMessage.get("allNoticeMessage")); - } - -} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/converter/FcmMessageConverter.java b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/converter/FcmMessageConverter.java new file mode 100644 index 0000000..a8726fd --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/converter/FcmMessageConverter.java @@ -0,0 +1,58 @@ +package com.aliens.friendship.backend_chatting_server.fcm.converter; + +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import com.aliens.friendship.backend_chatting_server.global.common.Converter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.BulkReadRequest; +import com.google.firebase.messaging.MulticastMessage; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@RequiredArgsConstructor +@Converter +public class FcmMessageConverter { + + public MulticastMessage toChat(ChatSendResponse chatSendResponse, List memberFcmTokens) { + return MulticastMessage.builder() + .putData("title", chatSendResponse.getSenderName()) + .putData("body", chatSendResponse.getChatContent()) + .putData("type", "chat") + .putData("roomId", chatSendResponse.getRoomId().toString()) + .putData("senderId", chatSendResponse.getSenderId().toString()) + .putData("receiverId", chatSendResponse.getReceiverId().toString()) + .putData("chatContent", chatSendResponse.getChatContent()) + .putData("chatType", chatSendResponse.getChatType().toString()) + .putData("chatId", chatSendResponse.getChatId().toString()) + .putData("sendTime", chatSendResponse.getSendTime()) + .putData("unreadCount", chatSendResponse.getUnreadCount().toString()) + .addAllTokens(memberFcmTokens) + .build(); + } + + public MulticastMessage toSingleRead(ChatEntity chatEntity, List memberFcmTokens) { + return MulticastMessage.builder() + .addAllTokens(memberFcmTokens) + .putData("type", "read") + .putData("roomId", chatEntity.getRoomId().toString()) + .putData("chatId", chatEntity.getChatId().toString()) + .build(); + } + + public MulticastMessage toBulkRead(BulkReadRequest bulkReadRequest, List memberFcmTokens) { + return MulticastMessage.builder() + .addAllTokens(memberFcmTokens) + .putData("type", "bulkRead") + .putData("roomId", bulkReadRequest.getRoomId().toString()) + .build(); + } + + public MulticastMessage toNotice(String title, String content, List registeredTokens) { + return MulticastMessage.builder() + .putData("type", "notice") + .putData("title", title) + .putData("body", content) + .addAllTokens(registeredTokens) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/domain/FcmToken.java b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/domain/FcmToken.java deleted file mode 100644 index b0b9ec9..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/domain/FcmToken.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.fcm.domain; - - -import lombok.*; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -@Builder -@ToString -@Document(collection = "fcm_token") -public class FcmToken { - - @Id - private Long memberId; - - private String token; - -} - diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/dto/MemberIdWithFCMTokenDto.java b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/dto/MemberIdWithFCMTokenDto.java deleted file mode 100644 index 451c9f2..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/dto/MemberIdWithFCMTokenDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.fcm.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class MemberIdWithFCMTokenDto { - private Long memberId; - private String fcmToken; -} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/repository/FcmRepository.java b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/repository/FcmRepository.java deleted file mode 100644 index 3fe361b..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/repository/FcmRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.fcm.repository; - -import com.aliens.friendship.backend_chatting_server.fcm.domain.FcmToken; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface FcmRepository extends MongoRepository { - -} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/service/FCMService.java b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/service/FCMService.java deleted file mode 100644 index 9956982..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/service/FCMService.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.fcm.service; - -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatResponseDto; -import com.aliens.friendship.backend_chatting_server.fcm.config.FirebaseMessagingWrapper; -import com.aliens.friendship.backend_chatting_server.fcm.domain.FcmToken; -import com.aliens.friendship.backend_chatting_server.fcm.dto.MemberIdWithFCMTokenDto; -import com.aliens.friendship.backend_chatting_server.fcm.repository.FcmRepository; -import com.google.firebase.messaging.*; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@Slf4j -@Service -@RequiredArgsConstructor -public class FCMService { - - private final FcmRepository fcmRepository; - private final FirebaseMessagingWrapper firebaseMessagingWrapper; - - public String getFcmToken(Long receiverId) { - Optional fcmToken = fcmRepository.findById(receiverId); - return fcmToken.get().getToken(); - } - - public void addFCMToken(MemberIdWithFCMTokenDto memberWithFCMTokenDto) { - fcmRepository.save(new FcmToken(memberWithFCMTokenDto.getMemberId(), memberWithFCMTokenDto.getFcmToken())); - } - - // 수신자에게 푸시 알림 - public void sendSingleChatToReceiverByToken(ChatResponseDto chatResponseDto) { - String fcmToken = getFcmToken(chatResponseDto.getReceiverId()); - if (fcmToken != null) { - Notification notification = Notification.builder() - .setTitle("발신자 이름") - .setBody(chatResponseDto.getMessage()) - .build(); - - Message message = Message.builder() - .setToken(fcmToken) - .setNotification(notification) - .putData("roomId", chatResponseDto.getRoomId().toString()) - .putData("senderId", chatResponseDto.getSenderId().toString()) - .putData("receiverId", chatResponseDto.getReceiverId().toString()) - .putData("message", chatResponseDto.getMessage()) - .putData("chatMessageCategory", chatResponseDto.getMessageCategory().toString()) - .putData("chatId", chatResponseDto.getChatId().toString()) - .putData("createTime", chatResponseDto.getCreateTime()) - .putData("read", chatResponseDto.getRead()) - .build(); - - firebaseMessagingWrapper.sendAsync(message); - } - } - - // 송신자에게 푸시 알림으로 수신자가 채팅을 읽었음을 알림. - public void sendReadChatToSenderByToken(ChatResponseDto chatResponseDto) { - String fcmToken = getFcmToken(chatResponseDto.getSenderId()); - if (fcmToken != null) { - Notification notification = Notification.builder() - .setTitle("읽음 처리") - .setBody(chatResponseDto.getMessage()) - .build(); - - Message message = Message.builder() - .setToken(fcmToken) - .setNotification(notification) - .putData("roomId", chatResponseDto.getRoomId().toString()) - .putData("senderId", chatResponseDto.getSenderId().toString()) - .putData("receiverId", chatResponseDto.getReceiverId().toString()) - .putData("message", chatResponseDto.getMessage()) - .putData("chatMessageCategory", chatResponseDto.getMessageCategory().toString()) - .putData("chatId", chatResponseDto.getChatId().toString()) - .putData("createTime", chatResponseDto.getCreateTime()) - .putData("read", chatResponseDto.getRead()) - .build(); - - firebaseMessagingWrapper.sendAsync(message); - } - } - - // 전체 공지 푸시 알림 - public void sendNoticeToAll(String allNoticeMessage) throws FirebaseMessagingException { - List registeredTokens = getAllTokens(); - - Notification notification = Notification.builder() - .setTitle("[FriendShip] 전체 공지") - .setBody(allNoticeMessage) - .build(); - - MulticastMessage message = MulticastMessage.builder() - .setNotification(notification) - .addAllTokens(registeredTokens) - .build(); - - firebaseMessagingWrapper.sendMulticast(message); - } - - public List getAllTokens() { - List fcmTokens = fcmRepository.findAll(); - List tokens = new ArrayList<>(); - for (FcmToken fcmToken : fcmTokens) { - tokens.add(fcmToken.getToken()); - } - return tokens; - } - -} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/service/FcmService.java b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/service/FcmService.java new file mode 100644 index 0000000..5e325ff --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/fcm/service/FcmService.java @@ -0,0 +1,64 @@ +package com.aliens.friendship.backend_chatting_server.fcm.service; + +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import com.aliens.friendship.backend_chatting_server.db.fcm.entity.FcmTokenEntity; +import com.aliens.friendship.backend_chatting_server.fcm.config.FirebaseMessagingWrapper; +import com.aliens.friendship.backend_chatting_server.db.fcm.repository.FcmTokenRepository; +import com.aliens.friendship.backend_chatting_server.fcm.converter.FcmMessageConverter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.BulkReadRequest; +import com.google.firebase.messaging.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FcmService { + + private final FcmTokenRepository fcmTokenRepository; + private final FirebaseMessagingWrapper firebaseMessagingWrapper; + private final FcmMessageConverter fcmMessageConverter; + + public void sendSingleChatToReceiverByToken(ChatSendResponse chatSendResponse) throws FirebaseMessagingException { + List fcmTokens = getFcmTokens(chatSendResponse.getReceiverId()); + MulticastMessage message = fcmMessageConverter.toChat(chatSendResponse,fcmTokens); + firebaseMessagingWrapper.sendMulticast(message); + } + + public void sendSingleChatReadToSenderByToken(ChatEntity chatEntity) throws FirebaseMessagingException { + List fcmTokens = getFcmTokens(chatEntity.getSenderId()); + MulticastMessage message = fcmMessageConverter.toSingleRead(chatEntity, fcmTokens); + firebaseMessagingWrapper.sendMulticast(message); + } + + public void sendBulkChatReadToSenderByToken(BulkReadRequest bulkReadRequest) throws FirebaseMessagingException { + List fcmTokens = getFcmTokens(bulkReadRequest.getPartnerId()); + MulticastMessage message = fcmMessageConverter.toBulkRead(bulkReadRequest, fcmTokens); + firebaseMessagingWrapper.sendMulticast(message); + } + + public void sendNoticeToAll(String title, String content) throws FirebaseMessagingException { + List registeredTokens = getAllTokens(); + MulticastMessage message = fcmMessageConverter.toNotice(title, content, registeredTokens); + firebaseMessagingWrapper.sendMulticast(message); + } + + public List getFcmTokens(Long memberId) { + return fcmTokenRepository.findAllByMemberId(memberId) + .stream() + .map(FcmTokenEntity::getValue) + .collect(Collectors.toList()); + } + + public List getAllTokens() { + return fcmTokenRepository.findAll() + .stream() + .map(FcmTokenEntity::getValue) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/global/common/Business.java b/src/main/java/com/aliens/friendship/backend_chatting_server/global/common/Business.java new file mode 100644 index 0000000..df21b29 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/global/common/Business.java @@ -0,0 +1,17 @@ +package com.aliens.friendship.backend_chatting_server.global.common; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Service; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Service +public @interface Business { + @AliasFor(annotation = Service.class) + String value() default ""; +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/global/common/Converter.java b/src/main/java/com/aliens/friendship/backend_chatting_server/global/common/Converter.java new file mode 100644 index 0000000..1577b13 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/global/common/Converter.java @@ -0,0 +1,17 @@ +package com.aliens.friendship.backend_chatting_server.global.common; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Service; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Service +public @interface Converter { + @AliasFor(annotation = Service.class) + String value() default ""; +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/jwt/JwtTokenProvider.java b/src/main/java/com/aliens/friendship/backend_chatting_server/global/util/jwt/JwtTokenUtil.java similarity index 60% rename from src/main/java/com/aliens/friendship/backend_chatting_server/jwt/JwtTokenProvider.java rename to src/main/java/com/aliens/friendship/backend_chatting_server/global/util/jwt/JwtTokenUtil.java index c90bab0..4ba86a9 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/jwt/JwtTokenProvider.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/global/util/jwt/JwtTokenUtil.java @@ -1,4 +1,4 @@ -package com.aliens.friendship.backend_chatting_server.jwt; +package com.aliens.friendship.backend_chatting_server.global.util.jwt; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; @@ -8,49 +8,25 @@ import java.nio.charset.StandardCharsets; import java.security.Key; -import java.util.Date; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @Slf4j @Component -public class JwtTokenProvider { +public class JwtTokenUtil { - @Value("${spring.jwt.secret}") + @Value("${spring.jwt.chatting-access-token-secret-key}") private String secret; - long tokenValidityInSeconds = 172800; - - - public String generateToken(Long memberId,List roomIds ) { - long now = (new Date()).getTime(); - Date validity = new Date(now + tokenValidityInSeconds); - - Claims claims = Jwts.claims(); - claims.put("memberId",memberId); - // 매칭된 인원에 수에 대해서 가변적이기 때문에 다음과 같이 숫자를 기반으로 추가 - for(int i = 0 ; i < roomIds.size(); i ++ ){ - claims.put(String.valueOf(i),roomIds.get(i)); - } - - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(validity) - .signWith(getSigningKey(secret), SignatureAlgorithm.HS256) - .compact(); - } - public Long getCurrentMemberIdFromToken(String token) { return extractAllClaims(token).get("memberId", Long.class); } public List getRoomIdsFromToken(String token) { - Claims claim = extractAllClaims(token); - - return IntStream.range(1, claim.size()) - .mapToObj(i -> claim.get(String.valueOf(i), Long.class)) + Claims claims = extractAllClaims(token); + return IntStream.range(0, claims.size() - 3) + .mapToObj(i -> claims.get(String.valueOf(i), Long.class)) .collect(Collectors.toList()); } @@ -82,5 +58,4 @@ private Key getSigningKey(String secretKey) { byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); return Keys.hmacShaKeyFor(keyBytes); } - } \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/global/util/rabbitMQ/MatchCompletionConsumer.java b/src/main/java/com/aliens/friendship/backend_chatting_server/global/util/rabbitMQ/MatchCompletionConsumer.java new file mode 100644 index 0000000..04095cd --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/global/util/rabbitMQ/MatchCompletionConsumer.java @@ -0,0 +1,21 @@ +package com.aliens.friendship.backend_chatting_server.global.util.rabbitMQ; + +import com.aliens.friendship.backend_chatting_server.fcm.service.FcmService; +import com.google.firebase.messaging.FirebaseMessagingException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class MatchCompletionConsumer { + + private final FcmService fcmService; + @RabbitListener(queues = "match.completion.queue") + public void receiveMatchCompletionMessage(String message) throws FirebaseMessagingException { + fcmService.sendNoticeToAll("FriendShip", message); + log.info("MatchCompletionConsumer: {}", message); + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/StompHandler.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/StompHandler.java deleted file mode 100644 index 15d5417..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/StompHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.websocket; - - -import com.aliens.friendship.backend_chatting_server.jwt.JwtTokenProvider; -import lombok.RequiredArgsConstructor; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.simp.stomp.StompCommand; -import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.stereotype.Component; - -import java.util.Objects; - -@Component -@RequiredArgsConstructor -public class StompHandler implements ChannelInterceptor { - private final JwtTokenProvider jwtTokenProvider; - - @Override - public Message preSend(Message message, MessageChannel channel) { - StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); - if (StompCommand.CONNECT.equals(accessor.getCommand())) { - jwtTokenProvider.validateToken(Objects.requireNonNull(accessor.getFirstNativeHeader("Authorization")).substring(7)); - } - return message; - } -} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/WebSocketConfig.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/WebSocketConfig.java deleted file mode 100644 index 97ce7d9..0000000 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/WebSocketConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.websocket; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.messaging.simp.config.ChannelRegistration; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -@Configuration -@RequiredArgsConstructor -@EnableWebSocketMessageBroker -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - - private final StompHandler stompHandler; - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry - .addEndpoint("/ws") - .setAllowedOriginPatterns("*") - .withSockJS(); - } - - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(stompHandler); - } - - - -} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/config/JacksonConfig.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/config/JacksonConfig.java new file mode 100644 index 0000000..b54f1fe --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/config/JacksonConfig.java @@ -0,0 +1,13 @@ +package com.aliens.friendship.backend_chatting_server.websocket.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/config/WebSocketConfig.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/config/WebSocketConfig.java new file mode 100644 index 0000000..8f4fa65 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/config/WebSocketConfig.java @@ -0,0 +1,27 @@ +package com.aliens.friendship.backend_chatting_server.websocket.config; + + +import com.aliens.friendship.backend_chatting_server.websocket.handler.BulkReadWebSocketHandler; +import com.aliens.friendship.backend_chatting_server.websocket.handler.ChatSendWebSocketHandler; +import com.aliens.friendship.backend_chatting_server.websocket.handler.SingleReadWebSocketHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +@RequiredArgsConstructor +public class WebSocketConfig implements WebSocketConfigurer { + final private ChatSendWebSocketHandler chatSendWebSocketHandler; + final private BulkReadWebSocketHandler bulkReadWebSocketHandler; + final private SingleReadWebSocketHandler singleReadWebSocketHandler; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(chatSendWebSocketHandler, "/ws/chat/message/send").setAllowedOrigins("*"); + registry.addHandler(singleReadWebSocketHandler, "/ws/chat/message/read").setAllowedOrigins("*"); + registry.addHandler(bulkReadWebSocketHandler, "/ws/chat/room/read").setAllowedOrigins("*"); + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/converter/WebsocketConverter.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/converter/WebsocketConverter.java new file mode 100644 index 0000000..996594a --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/converter/WebsocketConverter.java @@ -0,0 +1,61 @@ +package com.aliens.friendship.backend_chatting_server.websocket.converter; + +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.global.common.Converter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.BulkReadRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.ChatSendRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.SingleReadRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.BulkReadWebsocketResponse; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.ChatSendWebsocketResponse; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.SingleReadWebsocketResponse; + +@Converter +public class WebsocketConverter { + public ChatSendWebsocketResponse toChatSendWebsocketResponseWithRequest(ChatSendRequest chatSendRequest, ChatSendResponse chatSendResponse) { + return ChatSendWebsocketResponse.builder() + .requestId(chatSendRequest.getRequestId()) + .chatId(chatSendResponse.getChatId()) + .status("success") + .build(); + } + + public ChatSendWebsocketResponse toFailChatSendWebsocketResponseWithRequest(ChatSendRequest chatSendRequest, String status) { + return ChatSendWebsocketResponse.builder() + .requestId(chatSendRequest.getRequestId()) + .chatId(-1L) + .status(status) + .build(); + } + + public SingleReadWebsocketResponse toSingleReadWebsocketResponseWithRequest(SingleReadRequest singleReadRequest) { + return SingleReadWebsocketResponse.builder() + .requestId(singleReadRequest.getRequestId()) + .chatId(singleReadRequest.getChatId()) + .status("success") + .build(); + } + + public SingleReadWebsocketResponse toFailSingleReadWebsocketResponseWithRequest(SingleReadRequest singleReadRequest, String status) { + return SingleReadWebsocketResponse.builder() + .requestId(singleReadRequest.getRequestId()) + .chatId(-1L) + .status(status) + .build(); + } + + public BulkReadWebsocketResponse toBulkReadWebsocketResponseWithRequest(BulkReadRequest bulkReadRequest) { + return BulkReadWebsocketResponse.builder() + .requestId(bulkReadRequest.getRequestId()) + .roomId(bulkReadRequest.getRoomId()) + .status("success") + .build(); + } + + public BulkReadWebsocketResponse toFailBulkReadWebsocketResponseWithRequest(BulkReadRequest bulkReadRequest, String status) { + return BulkReadWebsocketResponse.builder() + .requestId(bulkReadRequest.getRequestId()) + .roomId(-1L) + .status(status) + .build(); + } +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/BulkReadRequest.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/BulkReadRequest.java new file mode 100644 index 0000000..d6c0473 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/BulkReadRequest.java @@ -0,0 +1,15 @@ +package com.aliens.friendship.backend_chatting_server.websocket.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BulkReadRequest { + private String requestId; + private Long partnerId; + private Long roomId; +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/ChatSendRequest.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/ChatSendRequest.java new file mode 100644 index 0000000..500d68d --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/ChatSendRequest.java @@ -0,0 +1,20 @@ +package com.aliens.friendship.backend_chatting_server.websocket.dto.request; + +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatType; +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ChatSendRequest { + private String requestId; + private Integer chatType; + private Long roomId; + private Long senderId; + private String senderName; + private Long receiverId; + private String chatContent; + private String sendTime; +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/SingleReadRequest.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/SingleReadRequest.java new file mode 100644 index 0000000..94ec46b --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/request/SingleReadRequest.java @@ -0,0 +1,16 @@ +package com.aliens.friendship.backend_chatting_server.websocket.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SingleReadRequest { + private String requestId; + private Long chatId; + private Long RoomId; +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/NewChatWithCountOfUnreadChats.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/BulkReadWebsocketResponse.java similarity index 51% rename from src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/NewChatWithCountOfUnreadChats.java rename to src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/BulkReadWebsocketResponse.java index 9da7ec8..2d1266f 100644 --- a/src/main/java/com/aliens/friendship/backend_chatting_server/chatting/dto/NewChatWithCountOfUnreadChats.java +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/BulkReadWebsocketResponse.java @@ -1,5 +1,4 @@ -package com.aliens.friendship.backend_chatting_server.chatting.dto; - +package com.aliens.friendship.backend_chatting_server.websocket.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; @@ -10,8 +9,8 @@ @AllArgsConstructor @NoArgsConstructor @Builder -public class NewChatWithCountOfUnreadChats { +public class BulkReadWebsocketResponse { + private String requestId; private Long roomId; - private String newChat; - private Long countOfUnreadChats; + private String status; } diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/ChatSendWebsocketResponse.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/ChatSendWebsocketResponse.java new file mode 100644 index 0000000..2031b6a --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/ChatSendWebsocketResponse.java @@ -0,0 +1,16 @@ +package com.aliens.friendship.backend_chatting_server.websocket.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ChatSendWebsocketResponse { + private String requestId; + private Long chatId; + private String status; +} \ No newline at end of file diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/SingleReadWebsocketResponse.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/SingleReadWebsocketResponse.java new file mode 100644 index 0000000..e055791 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/dto/response/SingleReadWebsocketResponse.java @@ -0,0 +1,16 @@ +package com.aliens.friendship.backend_chatting_server.websocket.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SingleReadWebsocketResponse { + private String requestId; + private Long chatId; + private String status; +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/BulkReadWebSocketHandler.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/BulkReadWebSocketHandler.java new file mode 100644 index 0000000..386570c --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/BulkReadWebSocketHandler.java @@ -0,0 +1,89 @@ +package com.aliens.friendship.backend_chatting_server.websocket.handler; + +import com.aliens.friendship.backend_chatting_server.chatting.service.ChatService; +import com.aliens.friendship.backend_chatting_server.fcm.service.FcmService; +import com.aliens.friendship.backend_chatting_server.global.util.jwt.JwtTokenUtil; +import com.aliens.friendship.backend_chatting_server.websocket.converter.WebsocketConverter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.BulkReadRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.BulkReadWebsocketResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.firebase.messaging.FirebaseMessagingException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.util.List; + +@Component +@Slf4j +@RequiredArgsConstructor +public class BulkReadWebSocketHandler extends TextWebSocketHandler { + private List roomIds; + private final ChatService chatService; + private final JwtTokenUtil jwtTokenUtil; + private final ObjectMapper objectMapper; + private final FcmService fcmService; + private final WebsocketConverter websocketConverter; + + private void updateBulkRead(WebSocketSession session, TextMessage message, List roomIds) throws FirebaseMessagingException, IOException { + // 요청 역직렬화 + BulkReadRequest bulkReadRequest = objectMapper.readValue(message.getPayload(), BulkReadRequest.class); + + // roomId 검증 + chatService.validateRoomId(bulkReadRequest.getRoomId(), roomIds); + + // 채팅방기준 일괄 읽음 상태 업데이트 + Long chatId = chatService.updateReadStateByRoomId(bulkReadRequest.getRoomId(), bulkReadRequest.getPartnerId()); + + // 상대방에게 채팅방 기준 일괄 읽음 처리 FCM 전송 + fcmService.sendBulkChatReadToSenderByToken(bulkReadRequest); + + // 일괄 처리 요청에 대한 웹소켓 응답 생성 + BulkReadWebsocketResponse readByRoomWebsocketResponse = websocketConverter.toBulkReadWebsocketResponseWithRequest(bulkReadRequest); + + // 응답 직렬화 + String response = objectMapper.writeValueAsString(readByRoomWebsocketResponse); + + // response를 WebSocketSession에 전송 + session.sendMessage(new TextMessage(response)); + + // log + log.info("BulkReadWebSocketHandler.updateBulkRead(): " + response); + } + + private List afterWebsocketConnectionEstablished(WebSocketSession session) { + // header JWT 토큰 가져오기 + String JWTToken = session.getHandshakeHeaders().get("Authorization").get(0); + + // 토큰 검증 + jwtTokenUtil.validateToken(JWTToken); + + // log + log.info("BulkReadWebSocketHandler.afterWebsocketConnectionEstablished(): " + JWTToken); + + // header token에서 roomId 추출후 반환 + return jwtTokenUtil.getRoomIdsFromToken(JWTToken); + } + + + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException, FirebaseMessagingException { + updateBulkRead(session, message, roomIds); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + roomIds = afterWebsocketConnectionEstablished(session); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + } + +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/ChatSendWebSocketHandler.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/ChatSendWebSocketHandler.java new file mode 100644 index 0000000..07d20fe --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/ChatSendWebSocketHandler.java @@ -0,0 +1,88 @@ +package com.aliens.friendship.backend_chatting_server.websocket.handler; + +import com.aliens.friendship.backend_chatting_server.chatting.dto.response.ChatSendResponse; +import com.aliens.friendship.backend_chatting_server.chatting.service.ChatService; +import com.aliens.friendship.backend_chatting_server.fcm.service.FcmService; +import com.aliens.friendship.backend_chatting_server.global.util.jwt.JwtTokenUtil; +import com.aliens.friendship.backend_chatting_server.websocket.converter.WebsocketConverter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.ChatSendRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.ChatSendWebsocketResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.firebase.messaging.FirebaseMessagingException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.util.List; + +@Component +@Slf4j +@RequiredArgsConstructor +public class ChatSendWebSocketHandler extends TextWebSocketHandler { + private List roomIds; + private final ChatService chatService; + private final JwtTokenUtil jwtTokenUtil; + private final ObjectMapper objectMapper; + private final FcmService fcmService; + private final WebsocketConverter websocketConverter; + + private void sendChat(WebSocketSession session, TextMessage message, List roomIds) throws FirebaseMessagingException, IOException { + // 요청 역직렬화 + ChatSendRequest chatSendRequest = objectMapper.readValue(message.getPayload(), ChatSendRequest.class); + + // roomId 검증 + chatService.validateRoomId(chatSendRequest.getRoomId(), roomIds); + + // 채팅 저장 + ChatSendResponse chatSendResponse = chatService.saveChat(chatSendRequest); + + // 상대방에게 새로운 채팅 FCM 전송 + fcmService.sendSingleChatToReceiverByToken(chatSendResponse); + + // 채팅 전송 요청에 대한 응답 생성 + ChatSendWebsocketResponse chatWebSocketResponse = websocketConverter.toChatSendWebsocketResponseWithRequest(chatSendRequest, chatSendResponse); + + // 응답 직렬화 + String response = objectMapper.writeValueAsString(chatWebSocketResponse); + + // response를 WebSocketSession에 전송 + session.sendMessage(new TextMessage(response)); + + // log + log.info("ChatSendWebSocketHandler.sendChat() response: {}", response); + } + + private List afterWebsocketConnectionEstablished(WebSocketSession session) { + // header JWT 토큰 가져오기 + String JWTToken = session.getHandshakeHeaders().get("Authorization").get(0); + + // 토큰 검증 + jwtTokenUtil.validateToken(JWTToken); + + // log + log.info("ChatSendWebSocketHandler.afterWebsocketConnectionEstablished() JWTToken: {}", JWTToken); + + // header token에서 roomId 추출후 반환 + return jwtTokenUtil.getRoomIdsFromToken(JWTToken); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException, FirebaseMessagingException { + sendChat(session, message, roomIds); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + roomIds = afterWebsocketConnectionEstablished(session); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + } + +} diff --git a/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/SingleReadWebSocketHandler.java b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/SingleReadWebSocketHandler.java new file mode 100644 index 0000000..5079461 --- /dev/null +++ b/src/main/java/com/aliens/friendship/backend_chatting_server/websocket/handler/SingleReadWebSocketHandler.java @@ -0,0 +1,91 @@ +package com.aliens.friendship.backend_chatting_server.websocket.handler; + +import com.aliens.friendship.backend_chatting_server.chatting.service.ChatService; +import com.aliens.friendship.backend_chatting_server.db.chat.entity.ChatEntity; +import com.aliens.friendship.backend_chatting_server.fcm.service.FcmService; +import com.aliens.friendship.backend_chatting_server.global.util.jwt.JwtTokenUtil; +import com.aliens.friendship.backend_chatting_server.websocket.converter.WebsocketConverter; +import com.aliens.friendship.backend_chatting_server.websocket.dto.request.SingleReadRequest; +import com.aliens.friendship.backend_chatting_server.websocket.dto.response.SingleReadWebsocketResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.firebase.messaging.FirebaseMessagingException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.util.List; + +@Component +@Slf4j +@RequiredArgsConstructor +public class SingleReadWebSocketHandler extends TextWebSocketHandler { + private List roomIds; + private final ChatService chatService; + private final JwtTokenUtil jwtTokenUtil; + private final ObjectMapper objectMapper; + private final FcmService fcmService; + private final WebsocketConverter websocketConverter; + + private void updateSingleRead(WebSocketSession session, TextMessage message, List roomIds) throws FirebaseMessagingException, IOException { + // 요청 역직렬화 + SingleReadRequest singleReadRequest = objectMapper.readValue(message.getPayload(), SingleReadRequest.class); + + // roomId 검증 + chatService.validateRoomId(singleReadRequest.getRoomId(), roomIds); + + // chatId로 chatEntity 조회 + ChatEntity chatEntity = chatService.getChatEntity(singleReadRequest.getChatId()); + + // 단일 메시지 읽음 상태 업데이트 + chatService.updateReadStateByChatId(chatEntity); + + // 상대방에게 읽음 처리 FCM 전송 + fcmService.sendSingleChatReadToSenderByToken(chatEntity); + + // 읽기 처리 요청에 대한 응답 생성 + SingleReadWebsocketResponse singleReadWebsocketResponse = websocketConverter.toSingleReadWebsocketResponseWithRequest(singleReadRequest); + + // 응답 직렬화 + String response = objectMapper.writeValueAsString(singleReadWebsocketResponse); + + // response를 WebSocketSession에 전송 + session.sendMessage(new TextMessage(response)); + + // log + log.info("SingleReadWebSocketHandler.updateSingleRead() response: {}", response); + } + + private List afterWebsocketConnectionEstablished(WebSocketSession session) { + // header JWT 토큰 가져오기 + String JWTToken = session.getHandshakeHeaders().get("Authorization").get(0); + + // 토큰 검증 + jwtTokenUtil.validateToken(JWTToken); + + // log + log.info("SingleReadWebSocketHandler.afterWebsocketConnectionEstablished() JWTToken: {}", JWTToken); + + // header token에서 roomId 추출후 반환 + return jwtTokenUtil.getRoomIdsFromToken(JWTToken); + } + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException, FirebaseMessagingException { + updateSingleRead(session, message, roomIds); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + roomIds = afterWebsocketConnectionEstablished(session); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + } +} + + diff --git a/src/test/java/com/aliens/friendship/backend_chatting_server/ChatControllerIntegrationTest.java b/src/test/java/com/aliens/friendship/backend_chatting_server/ChatControllerIntegrationTest.java deleted file mode 100644 index 073abf4..0000000 --- a/src/test/java/com/aliens/friendship/backend_chatting_server/ChatControllerIntegrationTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.aliens.friendship.backend_chatting_server; - -import com.aliens.friendship.backend_chatting_server.chatting.domain.ChatMessageCategory; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatRequestDto; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@ActiveProfiles("test") -public class ChatControllerIntegrationTest { - - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - - @DisplayName("성공_채팅 전송 및 저장") - @Test - public void testSendMessage() throws Exception { - // 테스트에 필요한 데이터 설정 - String jwtToken = "dummyToken"; - ChatRequestDto chatRequestDto = ChatRequestDto.builder() - .senderId(0L) - .receiverId(0L) - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .roomId(1L) - .message("안녕") - .build(); - - // 요청 헤더 설정 - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.set("ChattingToken", jwtToken); - - // API 호출 - restTemplate.postForObject( - "http://localhost:" + port + "/ws/api/chat/msg", - new HttpEntity<>(chatRequestDto, headers), - Void.class - ); - - } - - @DisplayName("성공_채팅 읽음 상태 변경") - @Test - public void testChangeReadStatus() throws Exception { - // 테스트에 필요한 데이터 설정 - String jwtToken = "dummyToken"; - Long chatId = 123L; - - // 요청 헤더 설정 - HttpHeaders headers = new HttpHeaders(); - headers.set("ChattingToken", jwtToken); - - // API 호출 - restTemplate.postForObject( - "http://localhost:" + port + "/ws/api/chat/msg/" + chatId, - new HttpEntity<>(headers), - Void.class - ); - - } -} \ No newline at end of file diff --git a/src/test/java/com/aliens/friendship/backend_chatting_server/ChatControllerTest.java b/src/test/java/com/aliens/friendship/backend_chatting_server/ChatControllerTest.java deleted file mode 100644 index 2320207..0000000 --- a/src/test/java/com/aliens/friendship/backend_chatting_server/ChatControllerTest.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.aliens.friendship.backend_chatting_server; - -import com.aliens.friendship.backend_chatting_server.chatting.controller.ChatController; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatResponseDto; -import com.aliens.friendship.backend_chatting_server.chatting.dto.NewChatWithCountOfUnreadChats; -import com.aliens.friendship.backend_chatting_server.jwt.JwtTokenProvider; -import com.aliens.friendship.backend_chatting_server.chatting.service.ChatService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import java.util.Arrays; -import java.util.List; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -public class ChatControllerTest { - - private MockMvc mockMvc; - - @Mock - private ChatService chatService; - - @Mock - private JwtTokenProvider jwtTokenProvider; - - @InjectMocks - private ChatController chatController; - - @BeforeEach - public void setup() { - MockitoAnnotations.openMocks(this); - mockMvc = MockMvcBuilders.standaloneSetup(chatController).build(); - } - - @Test - @DisplayName("성공_컨트롤러 새로운 채팅과 읽지 않은 채팅 개수 반환") - public void testGetNewChatAndCountOfUnreadChats() throws Exception { - String jwtToken = "dummyToken"; - Long currentMemberId = 123L; - List roomIdsFromToken = Arrays.asList(1L, 2L, 3L); - List expectedResponse = Arrays.asList( - NewChatWithCountOfUnreadChats.builder() - .roomId(1L) - .countOfUnreadChats(1L) - .newChat("Hello") - .build(), - NewChatWithCountOfUnreadChats.builder() - .roomId(2L) - .countOfUnreadChats(2L) - .newChat("Hi") - .build() - ); - - when(jwtTokenProvider.validateToken(jwtToken)).thenReturn(true); - when(jwtTokenProvider.getRoomIdsFromToken(jwtToken)).thenReturn(roomIdsFromToken); - when(jwtTokenProvider.getCurrentMemberIdFromToken - (jwtToken)).thenReturn(currentMemberId); - when(chatService.getNewChatAndNotReadCountOfChatInEachRoomsByRoomIds(currentMemberId, roomIdsFromToken)).thenReturn(expectedResponse); - - mockMvc.perform(get("/api/v1/chat") - .header("ChattingToken", jwtToken)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$[0].roomId", is(1))) - .andExpect(jsonPath("$[0].countOfUnreadChats", is(1))) - .andExpect(jsonPath("$[0].newChat", is("Hello"))) - .andExpect(jsonPath("$[1].roomId", is(2))) - .andExpect(jsonPath("$[1].countOfUnreadChats", is(2))) - .andExpect(jsonPath("$[1].newChat", is("Hi"))); - - verify(jwtTokenProvider, times(1)).validateToken(jwtToken); - verify(jwtTokenProvider, times(1)).getRoomIdsFromToken(jwtToken); - verify(jwtTokenProvider, times(1)).getCurrentMemberIdFromToken(jwtToken); - verify(chatService, times(1)).getNewChatAndNotReadCountOfChatInEachRoomsByRoomIds(currentMemberId, roomIdsFromToken); - } - - @Test - @DisplayName("성공_컨트롤러 채팅방 정보를 통한 신규채팅 100개 반환") - public void testGetHundredChatsByRoomId() throws Exception { - String jwtToken = "dummyToken"; - Long roomId = 1L; - Long currentMemberId = 123L; - List roomIdsFromToken = Arrays.asList(1L, 2L, 3L); - List expectedResponse = Arrays.asList( - ChatResponseDto.builder() - .chatId(1L) - .senderId(1L) - .receiverId(2L) - .message("Hello") - .build(), - ChatResponseDto.builder() - .chatId(2L) - .senderId(2L) - .receiverId(1L) - .message("Hi") - .build() - ); - - when(jwtTokenProvider.validateToken(jwtToken)).thenReturn(true); - when(jwtTokenProvider.getRoomIdsFromToken(jwtToken)).thenReturn(roomIdsFromToken); - when(chatService.getHundredChatsByRoomId(roomId)).thenReturn(expectedResponse); - - mockMvc.perform(get("/api/v1/chat/{roomId}", roomId) - .header("ChattingToken", jwtToken)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$[0].chatId", is(1))) - .andExpect(jsonPath("$[0].senderId", is(1))) - .andExpect(jsonPath("$[0].receiverId", is(2))) - .andExpect(jsonPath("$[0].message", is("Hello"))) - .andExpect(jsonPath("$[1].chatId", is(2))) - .andExpect(jsonPath("$[1].senderId", is(2))) - .andExpect(jsonPath("$[1].receiverId", is(1))) - .andExpect(jsonPath("$[1].message", is("Hi"))); - - verify(jwtTokenProvider, times(1)).validateToken(jwtToken); - verify(jwtTokenProvider, times(1)).getRoomIdsFromToken(jwtToken); - verify(chatService, times(1)).getHundredChatsByRoomId(roomId); - } - - // Helper method to convert objects to JSON string - private String asJsonString(Object obj) { - try { - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.writeValueAsString(obj); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} - diff --git a/src/test/java/com/aliens/friendship/backend_chatting_server/ChatServiceTest.java b/src/test/java/com/aliens/friendship/backend_chatting_server/ChatServiceTest.java deleted file mode 100644 index 665d9bb..0000000 --- a/src/test/java/com/aliens/friendship/backend_chatting_server/ChatServiceTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.aliens.friendship.backend_chatting_server; - -import com.aliens.friendship.backend_chatting_server.chatting.domain.Chat; -import com.aliens.friendship.backend_chatting_server.chatting.domain.ChatMessageCategory; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatRequestDto; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatResponseDto; -import com.aliens.friendship.backend_chatting_server.chatting.dto.NewChatWithCountOfUnreadChats; -import com.aliens.friendship.backend_chatting_server.chatting.repository.ChatRepository; -import com.aliens.friendship.backend_chatting_server.chatting.service.ChatService; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class ChatServiceTest { - - @Mock - private ChatRepository chatRepository; - - @InjectMocks - private ChatService chatService; - - @DisplayName("성공_채팅 저장") - @Test - public void testAddChat() { - ChatRequestDto chatRequestDto = ChatRequestDto.builder() - .roomId(125L) - .message("안녕") - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .receiverId(0L) - .senderId(1L) - .build(); - Chat savedChat = chatRequestDto.toEntity(); - - when(chatRepository.save(any(Chat.class))).thenReturn(savedChat); - - ChatResponseDto result = chatService.addChat(chatRequestDto); - - assertNotNull(result); - } - - @DisplayName("성공_ChatId를 통한 읽음처리") - @Test - public void testChangeChatToReadByChatId() { - // 테스트에 필요한 데이터 설정 - Long chatId = 123L; - Chat chat = Chat.builder() - .read(false) - .chatId(123L) - .createTime(Instant.now()) - .receiverId(1L) - .senderId(0L) - .message("읽었니?") - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .roomId(3L) - .build(); - when(chatRepository.findByChatId(chatId)).thenReturn(Optional.of(chat)); - - ChatResponseDto result = chatService.changeChatToReadByChatId(chatId); - - assertNotNull(result); - verify(chatRepository, times(1)).save(chat); - } - - @DisplayName("성공_최신채팅과 읽지않은 개수 반환") - @Test - public void testGetNewChatAndNotReadCountOfChatInEachRoomsByRoomIds() { - Long currentMemberId = 123L; - List roomIds = Arrays.asList(1L, 2L, 3L); - - when(chatRepository.findNewOneChatByRoomId(anyLong())).thenReturn(Optional.empty()); - when(chatRepository.countUnreadChatsByRoomIdAndReceiverId(anyLong(), anyLong())).thenReturn(0L); - - List result = chatService.getNewChatAndNotReadCountOfChatInEachRoomsByRoomIds(currentMemberId, roomIds); - - assertNotNull(result); - } - - @DisplayName("성공_RoomId를 통한100개의 신규채팅 반환") - @Test - public void testGetHundredChatsByRoomId() { - Long roomId = 123L; - Chat chat1 = Chat.builder() - .roomId(1L) - .chatId(1L) - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .senderId(1L) - .receiverId(1L) - .createTime(Instant.now()) - .read(false) - .message("안녕") - .build(); - Chat chat2 = Chat.builder() - .roomId(2L) - .chatId(2L) - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .senderId(1L) - .receiverId(1L) - .createTime(Instant.now()) - .read(false) - .message("안녕") - .build(); - Chat chat3 = Chat.builder() - .roomId(3L) - .chatId(3L) - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .senderId(1L) - .receiverId(1L) - .createTime(Instant.now()) - .read(false) - .message("안녕") - .build(); - List chats = Arrays.asList(chat1, chat2, chat3); - when(chatRepository.findHundredChatsByRoomId(roomId)).thenReturn(Optional.of(chats)); - - List result = chatService.getHundredChatsByRoomId(roomId); - - assertNotNull(result); - } -} \ No newline at end of file diff --git a/src/test/java/com/aliens/friendship/backend_chatting_server/fcm/controller/FCMControllerTest.java b/src/test/java/com/aliens/friendship/backend_chatting_server/fcm/controller/FCMControllerTest.java deleted file mode 100644 index f6b094f..0000000 --- a/src/test/java/com/aliens/friendship/backend_chatting_server/fcm/controller/FCMControllerTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.fcm.controller; - -import com.aliens.friendship.backend_chatting_server.fcm.dto.MemberIdWithFCMTokenDto; -import com.aliens.friendship.backend_chatting_server.fcm.service.FCMService; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; - -@WebMvcTest(FCMController.class) -class FCMControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private FCMService FCMService; - - @Test - void setFcmToken_Success() throws Exception { - //given - MemberIdWithFCMTokenDto memberIdWithFCMTokenDto = MemberIdWithFCMTokenDto.builder() - .fcmToken("testFcmToken") - .memberId(1L) - .build(); - - //when & then - mockMvc.perform(post("/api/v1/fcm/token") - .contentType(MediaType.APPLICATION_JSON) - .content(new ObjectMapper().writeValueAsString(memberIdWithFCMTokenDto))) - .andExpect(status().isOk()); - verify(FCMService, times(1)).addFCMToken(memberIdWithFCMTokenDto); - } - - @Test - void pushMessageToAll_Success() throws Exception { - //given - Map allNoticeMessage = new HashMap<>(); - allNoticeMessage.put("allNoticeMessage", "매칭이 완료되었습니다!"); - - //when & then - mockMvc.perform(post("/api/v1/fcm/all-notice") - .contentType(MediaType.APPLICATION_JSON) - .content(new ObjectMapper().writeValueAsString(allNoticeMessage))) - .andExpect(status().isOk()); - verify(FCMService, times(1)).sendNoticeToAll(allNoticeMessage.get("allNoticeMessage")); - - } -} \ No newline at end of file diff --git a/src/test/java/com/aliens/friendship/backend_chatting_server/fcm/service/FCMServiceTest.java b/src/test/java/com/aliens/friendship/backend_chatting_server/fcm/service/FCMServiceTest.java deleted file mode 100644 index e206a7f..0000000 --- a/src/test/java/com/aliens/friendship/backend_chatting_server/fcm/service/FCMServiceTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.aliens.friendship.backend_chatting_server.fcm.service; - -import com.aliens.friendship.backend_chatting_server.chatting.domain.ChatMessageCategory; -import com.aliens.friendship.backend_chatting_server.chatting.dto.ChatResponseDto; -import com.aliens.friendship.backend_chatting_server.fcm.config.FirebaseMessagingWrapper; -import com.aliens.friendship.backend_chatting_server.fcm.domain.FcmToken; -import com.aliens.friendship.backend_chatting_server.fcm.dto.MemberIdWithFCMTokenDto; -import com.aliens.friendship.backend_chatting_server.fcm.repository.FcmRepository; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.MulticastMessage; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.mockito.Mockito.*; - -@Transactional -@ExtendWith(MockitoExtension.class) -class FCMServiceTest { - - @InjectMocks - FCMService FCMService; - - @Mock - FirebaseMessagingWrapper firebaseMessagingWrapper; - - @Mock - FcmRepository fcmRepository; - - @Test - @DisplayName("fcm 토큰 등록 성공") - void AddFCMToken_Success_When_GivenValidMemberIdAndFCMToken() { - //given - MemberIdWithFCMTokenDto memberIdWithFCMTokenDto = MemberIdWithFCMTokenDto.builder() - .fcmToken("testFcmToken") - .memberId(1L) - .build(); - - //when - FCMService.addFCMToken(memberIdWithFCMTokenDto); - - //then - verify(fcmRepository, times(1)).save(any(FcmToken.class)); - } - - @Test - @DisplayName("수신자에게 하나의 채팅 푸시 알림 성공") - void sendSingleChatToReceiverByToken_Success_When_GivenValidChatResponseDto() { - //given - FcmToken receiverFcmToken = FcmToken.builder() - .token("receiverFcmToken") - .memberId(1L) - .build(); - ChatResponseDto chatResponseDto = ChatResponseDto.builder() - .roomId(1L) - .senderId(2L) - .receiverId(1L) - .message("testMessage") - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .chatId(1L) - .createTime(Instant.now().toString()) - .read("false") - .build(); - when(fcmRepository.findById(chatResponseDto.getReceiverId())).thenReturn(Optional.ofNullable(receiverFcmToken)); - doNothing().when(firebaseMessagingWrapper).sendAsync(any(Message.class)); - - //when - FCMService.sendSingleChatToReceiverByToken(chatResponseDto); - - //then - verify(firebaseMessagingWrapper, times(1)).sendAsync(any(Message.class)); - } - - @Test - @DisplayName("수신자가 읽은 하나의 채팅을 송신자에게 푸시 알림 성공") - void sendReadChatToSenderByToken_Success_When_GivenValidChatResponseDto() { - //given - FcmToken senderFcmToken = FcmToken.builder() - .token("senderFcmToken") - .memberId(1L) - .build(); - ChatResponseDto chatResponseDto = ChatResponseDto.builder() - .roomId(1L) - .senderId(1L) - .receiverId(2L) - .message("testMessage") - .messageCategory(ChatMessageCategory.NORMAL_MESSAGE) - .chatId(1L) - .createTime(Instant.now().toString()) - .read("true") - .build(); - when(fcmRepository.findById(chatResponseDto.getSenderId())).thenReturn(Optional.ofNullable(senderFcmToken)); - doNothing().when(firebaseMessagingWrapper).sendAsync(any(Message.class)); - - //when - FCMService.sendReadChatToSenderByToken(chatResponseDto); - - //then - verify(firebaseMessagingWrapper, times(1)).sendAsync(any(Message.class)); - } - - @Test - @DisplayName("전체 공지 메시지 푸시 알림 성공") - void sendNoticeToAll_Success_When_GivenNoticeAllMessage() throws FirebaseMessagingException { - //given - List fcmTokens = new ArrayList<>(); - for (int i = 0; i < 6; i++) { - fcmTokens.add(FcmToken.builder() - .token("senderFcmToken") - .memberId((long) i) - .build()); - } - String noticeAllMessage = "매칭이 완료되었습니다!"; - when(fcmRepository.findAll()).thenReturn(fcmTokens); - - //when - FCMService.sendNoticeToAll(noticeAllMessage); - - //then - verify(firebaseMessagingWrapper, times(1)).sendMulticast(any(MulticastMessage.class)); - } -} \ No newline at end of file