ㄴ> 밍글 알림 예시
커뮤니티 앱에서 필수적인 기능인 푸시알림을 Firebase Cloud Messaging을 이용해 구현해보았다.
하지만 댓글 작성 시 푸시알림을 보낼 때 알림이 중복되어 보내진다는 문제가 있었다.
알림을 보낼 경우는 1. 댓글 작성자에 의해 댓글이 작성될 때,
2. 게시물 작성자에게, 대댓글이라면 3. 부모 댓글 작성자와 4. 멘션 된 유저에게까지 알림을 보내야한다. 또한 자기 자신에게 답글을 다는 경우도 있을 것이다.
만약 이 4명의 사용자가 각기 다 다른 사용자라면 문제가 없겠지만, 만일 겹치는 사용자가 있다면 그 사용자에게는 알림이 중복되어 갈 것이다.
그래서 댓글이 작성될 때, 게시물 작성자, (대댓글일 시)부모 댓글 작성자, 멘션 된 댓글 작성자, 그리고 댓글은 다는 작성자를 모두 고려해 겹치는 유저가 있다면 그 유저에게는 푸시알림을 한번만 보내야한다.
이에 대한 경우의 수는 총 12가지로 이 모든 경우를 if문으로 구현하기엔 너무 비효율적이고 코드 가독성이 떨어질거라 판단해 고민한 결과, HashMap 자료구조를 활용해 문제를 해결할 수 있었다.
이제 문제점을 살펴보았으니, 알림을 보내는 코드를 살펴보도록 하겠습니다.
public void sendTotalPush(TotalPost post, PostTotalCommentRequest postTotalCommentRequest, Member creatorMember) throws IOException {
Member postMember = post.getMember();
TotalComment parentTotalComment = commentRepository.findTotalCommentById(postTotalCommentRequest.getParentCommentId());
TotalComment mentionTotalComment = commentRepository.findTotalCommentById(postTotalCommentRequest.getMentionId());
String messageTitle = "알림 제목";
**// [1]. 댓글일 경우**
if (parentTotalComment == null) {
if (postMember.getId() == creatorMember.getId()) {
return;
} else {
firebaseCloudMessageService.sendMessageTo(postMember.getFcmToken(), messageTitle, "새로운 댓글이 달렸어요" + postTotalCommentRequest.getContent());
}
**// [2] 대댓글일 경우**
} else if (parentTotalComment != null) {
Member parentMember = parentTotalComment.getMember();
Member mentionMember = mentionTotalComment.getMember();
Map<Member, String> map = new HashMap<>();
map.put(postMember, "postMemberId");
map.put(parentMember, "parentMemberId");
map.put(mentionMember, "mentionMemberId");
map.put(creatorMember, "creatorMemberId"); //현재 댓글 작성자와 겹치는지 확인 용도
map.remove(creatorMember); //겹치지 않는다면 알림을 보내지 않아야하기에 remove해준다.
for (Member member : map.keySet()) {
firebaseCloudMessageService.sendMessageTo(member.getFcmToken(), messageTitle, postTotalCommentRequest.getContent());
}
}
경우의 수를 고려한 아래 Service 코드 로직
@Component
@RequiredArgsConstructor
public class FirebaseCloudMessageService {
private final String API_URL = "https://fcm.googleapis.com/v1/projects/***/messages:send";
private final ObjectMapper objectMapper;
**/**
* This method send the message to firebase server so that it could deliver it to target user's device.
* @param targetToken the token that indicate the target device to send notification
* @param title the title of notification
* @param body the body of notification
* @throws IOException
*/**
public void sendMessageTo(String targetToken, String title, String body) throws IOException{
String message = makeMessage(targetToken, title, body);
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message, MediaType.get("application/json; charset=utf-8"));
Request request = new Request.Builder().url(API_URL).post(requestBody).addHeader("AUTHORIZATION", "Bearer " + getAcccessToken())
.addHeader("CONTENT_TYPE", "application/json; UTF-8")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
**/**
* This method builds title and body into FCM required message format
* @param targetToken
* @param title
* @param body
* @return String of formated message
* @throws com.fasterxml.jackson.core.JsonProcessingException
*/**
private String makeMessage(String targetToken, String title, String body) throws com.fasterxml.jackson.core.JsonProcessingException {
FcmMessage fcmMessage = FcmMessage.builder().message(FcmMessage.Message.builder().token(targetToken)
.notification(FcmMessage.Notification.builder().title(title).body(body).image(null).build()).build())
.validate_only(false).build();
return objectMapper.writeValueAsString(fcmMessage);
}
private String getAcccessToken() throws IOException {
String firebaseConfigPath = "firebase/firebase_service_key.json";
GoogleCredentials googleCredentials = GoogleCredentials.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/cloud-platform"));
googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
}
}
@Builder
@AllArgsConstructor
@Getter
public class FcmMessage {
private boolean validate_only;
private Message message;
@Builder
@AllArgsConstructor
@Getter
public static class Message {
private Notification notification;
private String token;
}
@Builder
@AllArgsConstructor
@Getter
public static class Notification {
private String title;
private String body;
private String image;
}
}