Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,9 @@ private void createChatRoom(Board board, User loginUser) {

// 채팅방 입장
UserChatRoom userChatRoom = userChatRoomConverter.toEntity(loginUser, chatRoom);

// 채팅방 구독
subscribeToChatRoomTopic(loginUser.getFcmToken(), chatRoom.getId());

userChatRoomRepository.save(userChatRoom);
}

private void subscribeToChatRoomTopic(String fcmToken, Long chatRoomId) {
fcmService.subscribeToTopic(fcmToken, chatRoomId);
}

private Club findOrDefaultClub(Long clubId) {
return (clubId != 0)
? clubRepository.findById(clubId).orElseThrow(() -> new BaseException(ErrorCode.CLUB_NOT_FOUND))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import com.back.catchmate.domain.chat.dto.ChatResponse.PagedChatMessageInfo;
import com.back.catchmate.domain.chat.service.ChatService;
import com.back.catchmate.global.jwt.JwtValidation;
import com.google.firebase.messaging.FirebaseMessagingException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand All @@ -15,10 +17,13 @@
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@Tag(name = "채팅 관련 API")
@RestController
@RequestMapping("/chats")
Expand All @@ -28,7 +33,8 @@ public class ChatController {

@MessageMapping("/chat.{chatRoomId}")
@SendTo("/topic/chat.{chatRoomId}")
public void sendMessage(@DestinationVariable Long chatRoomId, ChatMessageRequest request) {
public void sendMessage(@DestinationVariable Long chatRoomId,
@RequestBody ChatMessageRequest request) throws IOException, FirebaseMessagingException {
chatService.sendChatMessage(chatRoomId, request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
@Service
@RequiredArgsConstructor
public class ChatRoomServiceImpl implements ChatRoomService {
private final FCMService fcmService;
private final ChatService chatService;
private final S3Service s3Service;
private final UserRepository userRepository;
Expand Down Expand Up @@ -63,8 +62,6 @@ public StateResponse leaveChatRoom(Long userId, Long chatRoomId) {
// 채팅방에서 참여자 수 감소
chatRoom.decrementParticipantCount();

unsubscribeFromTopic(user.getFcmToken(), chatRoomId);

// 퇴장 메시지 보내기
String content = user.getNickName() + " 님이 채팅을 떠났어요"; // 퇴장 메시지 내용
chatService.sendEnterLeaveMessage(chatRoom.getId(), content, user.getId(), MessageType.LEAVE);
Expand Down Expand Up @@ -110,14 +107,8 @@ public StateResponse kickUserFromChatRoom(Long loginUserId, Long chatRoomId, Lon
// 채팅방에서 참여자 수 감소
chatRoom.decrementParticipantCount();

unsubscribeFromTopic(user.getFcmToken(), chatRoomId);

String content = "방장의 결정으로 " + user.getNickName() + " 님이 채팅방에서 나갔습니다.";
chatService.sendEnterLeaveMessage(chatRoomId, content, userId, MessageType.LEAVE);
return new StateResponse(true);
}

private void unsubscribeFromTopic(String fcmToken, Long chatRoomId) {
fcmService.unsubscribeFromTopic(fcmToken, chatRoomId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import com.back.catchmate.domain.chat.dto.ChatRequest.ChatMessageRequest;
import com.back.catchmate.domain.chat.dto.ChatResponse.PagedChatMessageInfo;
import com.google.firebase.messaging.FirebaseMessagingException;
import org.springframework.data.domain.Pageable;

import java.io.IOException;

public interface ChatService {
void sendChatMessage(Long chatRoomId, ChatMessageRequest request);
void sendChatMessage(Long chatRoomId, ChatMessageRequest request) throws IOException, FirebaseMessagingException;

void sendEnterLeaveMessage(Long chatRoomId, String content, Long senderId, ChatMessageRequest.MessageType messageType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
import com.back.catchmate.domain.chat.repository.ChatRoomRepository;
import com.back.catchmate.domain.chat.repository.UserChatRoomRepository;
import com.back.catchmate.domain.notification.service.FCMService;
import com.back.catchmate.domain.user.entity.User;
import com.back.catchmate.domain.user.repository.UserRepository;
import com.back.catchmate.global.error.ErrorCode;
import com.back.catchmate.global.error.exception.BaseException;
import com.google.firebase.messaging.FirebaseMessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
Expand All @@ -19,6 +22,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;

Expand All @@ -34,11 +38,15 @@ public class ChatServiceImpl implements ChatService {
private final ChatRoomRepository chatRoomRepository;
private final UserChatRoomRepository userChatRoomRepository;
private final ChatMessageConverter chatMessageConverter;
private final UserRepository userRepository;

// 메시지를 특정 채팅방으로 전송
@Override
@Transactional
public void sendChatMessage(Long chatRoomId, ChatMessageRequest request) {
public void sendChatMessage(Long chatRoomId, ChatMessageRequest request) throws IOException, FirebaseMessagingException {
User user = userRepository.findById(request.getSenderId())
.orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_FOUND));

String destination = "/topic/chat." + chatRoomId;

if (request.getMessageType() == MessageType.TALK) {
Expand All @@ -54,13 +62,11 @@ public void sendChatMessage(Long chatRoomId, ChatMessageRequest request) {

ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId)
.orElseThrow(() -> new BaseException(ErrorCode.CHATROOM_NOT_FOUND));

chatRoom.updateLastMessageContent(request.getContent());
chatRoom.updateLastMessageTime();

// 채팅방에 알림 전송 (FCM 토픽을 사용)
String topic = "chat_room_" + chatRoomId;
fcmService.sendMessageToTopic(topic, chatRoom.getBoard().getTitle(), request.getContent());
// 자신을 제외한 채팅방에 FCM 알림 전송
fcmService.sendMessagesByTokens(chatRoomId, chatRoom.getBoard().getTitle(), request.getContent(), user.getFcmToken());
}

log.info("Sending message to: {}", destination);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,7 @@ public UpdateEnrollInfo acceptEnroll(Long enrollId, Long userId) throws IOExcept
throw new BaseException(ErrorCode.ENROLL_ACCEPT_INVALID);
}

Long chatRoomId = enterChatRoom(enrollApplicant, board);
subscribeToChatRoomTopic(enrollApplicant.getFcmToken(), chatRoomId);
enterChatRoom(enrollApplicant, board);

String title = ENROLLMENT_ACCEPT_TITLE;
String body = ENROLLMENT_ACCEPT_BODY;
Expand All @@ -195,7 +194,7 @@ public UpdateEnrollInfo acceptEnroll(Long enrollId, Long userId) throws IOExcept
return enrollConverter.toUpdateEnrollInfo(enroll, AcceptStatus.ACCEPTED);
}

private Long enterChatRoom(User user, Board board) {
private void enterChatRoom(User user, Board board) {
ChatRoom chatRoom = chatRoomRepository.findByBoardId(board.getId())
.orElseThrow(() -> new BaseException(ErrorCode.CHATROOM_NOT_FOUND));

Expand All @@ -206,12 +205,6 @@ private Long enterChatRoom(User user, Board board) {

String content = user.getNickName() + " 님이 채팅에 참여했어요";
chatService.sendEnterLeaveMessage(chatRoom.getId(), content, user.getId(), MessageType.ENTER);

return chatRoom.getId();
}

private void subscribeToChatRoomTopic(String fcmToken, Long chatRoomId) {
fcmService.subscribeToTopic(fcmToken, chatRoomId);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.back.catchmate.domain.notification.service;

import com.back.catchmate.domain.chat.repository.UserChatRoomRepository;
import com.back.catchmate.domain.enroll.entity.AcceptStatus;
import com.back.catchmate.domain.notification.dto.FCMMessageRequest;
import com.back.catchmate.global.error.ErrorCode;
import com.back.catchmate.global.error.exception.BaseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.messaging.BatchResponse;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.MulticastMessage;
import com.google.firebase.messaging.Notification;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -25,7 +27,6 @@
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

@Slf4j
Expand All @@ -38,6 +39,7 @@ public class FCMService {
private String FIREBASE_ALARM_SEND_API_URI;

private final ObjectMapper objectMapper;
private final UserChatRoomRepository userChatRoomRepository;

// Firebase로 부터 Access Token을 가져오는 메서드
private String getAccessToken() throws IOException {
Expand All @@ -50,8 +52,8 @@ private String getAccessToken() throws IOException {
return googleCredentials.getAccessToken().getTokenValue();
}

// 알림 파라미터들을 요구하는 body 형태로 가공
public String makeMessage(String targetToken, String title, String body, Long boardId, AcceptStatus acceptStatus) throws JsonProcessingException {
// 신청 알림 파라미터들을 요구하는 body 형태로 가공
public String makeEnrollMessage(String targetToken, String title, String body, Long boardId, AcceptStatus acceptStatus) throws JsonProcessingException {
FCMMessageRequest fcmMessage = FCMMessageRequest.builder()
.message(
FCMMessageRequest.Message.builder()
Expand Down Expand Up @@ -79,7 +81,7 @@ public String makeMessage(String targetToken, String title, String body, Long bo
// 사용자의 FCM 토큰을 사용하여 푸쉬 알림을 보내는 역할을 하는 메서드
@Async("asyncTask")
public void sendMessageByToken(String targetToken, String title, String body, Long boardId, AcceptStatus acceptStatus) throws IOException {
String message = makeMessage(targetToken, title, body, boardId, acceptStatus);
String message = makeEnrollMessage(targetToken, title, body, boardId, acceptStatus);

OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message, MediaType.get("application/json; charset=utf-8"));
Expand All @@ -100,38 +102,35 @@ public void sendMessageByToken(String targetToken, String title, String body, Lo
log.info(response.body().string());
}

public void sendMessageToTopic(String topic, String title, String body) {
Message message = Message.builder()
// 특정 채팅방의 모든 사용자에게 FCM 메시지 전송
@Async("asyncTask")
public void sendMessagesByTokens(Long chatRoomId, String title, String body, String senderToken) throws IOException, FirebaseMessagingException {
List<String> targetTokenList = userChatRoomRepository.findByChatRoomId(chatRoomId)
.stream()
.map(userChatRoom -> userChatRoom.getUser().getFcmToken()) // User 엔티티에서 FCM 토큰 가져오기
.filter(token -> token != null && !token.isEmpty())
.filter(token -> !token.equals(senderToken))
.toList();

MulticastMessage message = MulticastMessage.builder()
.setNotification(Notification.builder()
.setTitle(title)
.setBody(body)
.build())
.setTopic(topic)
.putData("chatRoomId", String.valueOf(chatRoomId))
.addAllTokens(targetTokenList)
.build();

try {
String response = FirebaseMessaging.getInstance().send(message);
log.info("Successfully sent message: {}", response);
} catch (FirebaseMessagingException e) {
throw new BaseException(ErrorCode.FCM_SUBSCRIBE_BAD_REQUEST);
}
}
// FCM에 메시지 전송
BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast(message);

public void subscribeToTopic(String fcmToken, Long chatRoomId) {
try {
String topic = "chat_room_" + chatRoomId;
FirebaseMessaging.getInstance().subscribeToTopic(Collections.singletonList(fcmToken), topic);
} catch (FirebaseMessagingException e) {
throw new BaseException(ErrorCode.FCM_SUBSCRIBE_BAD_REQUEST);
// 전송 결과 확인
if (response.getFailureCount() > 0) {
log.error("일부 메시지 전송에 실패했습니다. 성공한 메시지 수: {}, 실패한 메시지 수: {}",
response.getSuccessCount(), response.getFailureCount());
throw new BaseException(ErrorCode.FCM_TOKEN_SEND_BAD_REQUEST);
}
}

public void unsubscribeFromTopic(String fcmToken, Long chatRoomId) {
try {
String topic = "chat_room_" + chatRoomId;
FirebaseMessaging.getInstance().unsubscribeFromTopic(Collections.singletonList(fcmToken), topic);
} catch (FirebaseMessagingException e) {
throw new BaseException(ErrorCode.FCM_UNSUBSCRIBE_BAD_REQUEST);
}
log.info("FCM 응답: {}개의 메시지가 성공적으로 전송되었습니다.", response.getSuccessCount());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public enum ErrorCode {
NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 알림입니다."),
EMPTY_FCM_RESPONSE(HttpStatus.BAD_REQUEST, "알림 데이터가 존재하지 않습니다."),
FCM_TOPIC_SEND_BAD_REQUEST(HttpStatus.BAD_REQUEST, "토픽 알람 전송중 에러가 발생했습니다."),
FCM_TOKEN_SEND_BAD_REQUEST(HttpStatus.BAD_REQUEST, "토픽 알람 전송중 에러가 발생했습니다."),
FCM_SUBSCRIBE_BAD_REQUEST(HttpStatus.BAD_REQUEST, "토픽 구독중 에러가 발생했습니다."),
FCM_UNSUBSCRIBE_BAD_REQUEST(HttpStatus.BAD_REQUEST, "토픽 구독 취소중 에러가 발생했습니다."),

Expand Down