Skip to content

Commit

Permalink
Merge pull request #216 from TeamDon-tBe/feat/#215
Browse files Browse the repository at this point in the history
[FEAT] Fcm 푸시 알림 서비스 구현
  • Loading branch information
Hong0329 authored May 20, 2024
2 parents 8ff91a6 + 88a8bc9 commit defcad9
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 4 deletions.
1 change: 1 addition & 0 deletions DontBeServer/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ out/
.DS_Store
../../.DS_Store
*.DS_Store
/src/main/resources/fire-base.json
3 changes: 3 additions & 0 deletions DontBeServer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ dependencies {
implementation("software.amazon.awssdk:bom:2.21.0")
implementation("software.amazon.awssdk:s3:2.21.0")

//FCM sdk
implementation 'com.google.firebase:firebase-admin:9.2.0'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public class AuthResponseDto {
private String memberProfileUrl;

private Boolean isNewUser;

private Boolean isPushAlarmAllowed;
}

Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public AuthResponseDto socialLogin(String socialAccessToken, AuthRequestDto auth
String accessToken = jwtTokenProvider.generateAccessToken(authentication);
member.updateRefreshToken(refreshToken);

return AuthResponseDto.of(member.getNickname(), member.getId(), accessToken, refreshToken, member.getProfileUrl(), true);
return AuthResponseDto.of(member.getNickname(), member.getId(), accessToken, refreshToken, member.getProfileUrl(), true, member.isPushAlarmAllowed());

}
else {
Expand All @@ -91,7 +91,7 @@ public AuthResponseDto socialLogin(String socialAccessToken, AuthRequestDto auth

String accessToken = jwtTokenProvider.generateAccessToken(authentication);

return AuthResponseDto.of(signedMember.getNickname(), signedMember.getId(), accessToken, refreshToken, signedMember.getProfileUrl(), false);
return AuthResponseDto.of(signedMember.getNickname(), signedMember.getId(), accessToken, refreshToken, signedMember.getProfileUrl(), false, signedMember.isPushAlarmAllowed());
}
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(ErrorStatus.ANOTHER_ACCESS_TOKEN.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import com.dontbe.www.DontBeServer.common.exception.BadRequestException;
import com.dontbe.www.DontBeServer.common.response.ErrorStatus;
import com.dontbe.www.DontBeServer.common.util.GhostUtil;
import com.dontbe.www.DontBeServer.external.fcm.dto.FcmMessageDto;
import com.dontbe.www.DontBeServer.external.fcm.service.FcmService;
import com.dontbe.www.DontBeServer.external.s3.service.S3Service;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
Expand All @@ -32,6 +34,7 @@ public class CommentCommendService {
private final CommentLikedRepository commentLikedRepository;
private final NotificationRepository notificationRepository;
private final S3Service s3Service;
private final FcmService fcmService;
private static final String POST_IMAGE_FOLDER_NAME = "contents/";

public void postComment(Long memberId, Long contentId, CommentPostRequestDto commentPostRequestDto){
Expand Down Expand Up @@ -102,6 +105,28 @@ public void postCommentVer2(Long memberId, Long contentId, MultipartFile comment
.build();
Notification savedNotification = notificationRepository.save(notification);
}

if(contentWritingMember.isPushAlarmAllowed()) {
String FcmMessageTitle = usingMember.getNickname() + "님이 답글을 작성했습니다.";

FcmMessageDto commentFcmMessage = FcmMessageDto.builder()
.validateOnly(false)
.message(FcmMessageDto.Message.builder()
.notificationDetails(FcmMessageDto.NotificationDetails.builder()
.title(FcmMessageTitle)
.body(content.getContentText())
.build())
.token(contentWritingMember.getFcmToken())
.data(FcmMessageDto.Data.builder()
.name("comment")
.description("답글 푸시 알림")
.relateContentId(contentId)
.build())
.build())
.build();

fcmService.sendMessage(commentFcmMessage);
}
}

public void deleteComment(Long memberId, Long commentId) {
Expand Down Expand Up @@ -153,6 +178,28 @@ public void likeComment(Long memberId, Long commentId, CommentLikedRequestDto co
.build();
Notification savedNotification = notificationRepository.save(notification);
}

if(targetMember.isPushAlarmAllowed()) {
String FcmMessageTitle = triggerMember.getNickname() + "님이" + targetMember.getNickname() + "님의 답글을 좋아합니다.";

FcmMessageDto commentLikeFcmMessage = FcmMessageDto.builder()
.validateOnly(false)
.message(FcmMessageDto.Message.builder()
.notificationDetails(FcmMessageDto.NotificationDetails.builder()
.title(FcmMessageTitle)
.body("")
.build())
.token(targetMember.getFcmToken())
.data(FcmMessageDto.Data.builder()
.name("commentLike")
.description("답글 좋아요 푸시 알림")
.relateContentId(contentId)
.build())
.build())
.build();

fcmService.sendMessage(commentLikeFcmMessage);
}
}

public void unlikeComment(Long memberId, Long commentId){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import com.dontbe.www.DontBeServer.common.exception.BadRequestException;
import com.dontbe.www.DontBeServer.common.response.ErrorStatus;
import com.dontbe.www.DontBeServer.common.util.GhostUtil;
import com.dontbe.www.DontBeServer.external.fcm.dto.FcmMessageDto;
import com.dontbe.www.DontBeServer.external.fcm.service.FcmService;
import com.dontbe.www.DontBeServer.external.s3.service.S3Service;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
Expand All @@ -34,6 +36,7 @@ public class ContentCommandService {
private final NotificationRepository notificationRepository;
private final CommentRepository commentRepository;
private final S3Service s3Service;
private final FcmService fcmService;
private static final String POST_IMAGE_FOLDER_NAME = "contents/";

public void postContent(Long memberId, ContentPostRequestDto contentPostRequestDto) {
Expand Down Expand Up @@ -111,6 +114,28 @@ public void likeContent(Long memberId, Long contentId, ContentLikedRequestDto co
.build();
Notification savedNotification = notificationRepository.save(notification);
}

if(targetMember.isPushAlarmAllowed()) {
String FcmMessageTitle = triggerMember.getNickname() + "님이" + targetMember.getNickname() + "님의 글을 좋아합니다.";

FcmMessageDto contentLikeFcmMessage = FcmMessageDto.builder()
.validateOnly(false)
.message(FcmMessageDto.Message.builder()
.notificationDetails(FcmMessageDto.NotificationDetails.builder()
.title(FcmMessageTitle)
.body("")
.build())
.token(targetMember.getFcmToken())
.data(FcmMessageDto.Data.builder()
.name("contentLike")
.description("답글 좋아요 푸시 알림")
.relateContentId(contentId)
.build())
.build())
.build();

fcmService.sendMessage(contentLikeFcmMessage);
}
}

public void unlikeContent(Long memberId, Long contentId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public class Member extends BaseTimeEntity {
@Column(name = "is_alarm_allowed")
private boolean isAlarmAllowed;

@Column(name = "is_push_alarm_allowed")
private boolean isPushAlarmAllowed;

@Column(name = "fcm_token")
private String fcmToken;

@Column(nullable = false, name = "social_id")
private String socialId;

Expand Down Expand Up @@ -121,5 +127,10 @@ public void updateDeletedReason(String withdrawlReason){
public void softDelete() {
this.isDeleted = true;
this.deleteAt = LocalDateTime.now().plusDays(ACCOUNT_RETENTION_PERIOD);
this.isPushAlarmAllowed = false;
this.fcmToken = null;
}
public void updateMemberIsPushAlarmAllowed(boolean newIsPushAlarmAllowed) { this.isPushAlarmAllowed = newIsPushAlarmAllowed; }

public void updateMemberFcmToken(String newFcmToken) { this.fcmToken = newFcmToken; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
public record ProfilePatchRequestDto (
String nickname,
Boolean isAlarmAllowed,
String memberIntro
String memberIntro,
Boolean isPushAlarmAllowed,
String fcmToken
){
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ public void updateMemberProfile2(Long memberId, MultipartFile multipartFile, Pro
if (profilePatchRequestDto.isAlarmAllowed() != null) {
existingMember.updateMemberIsAlarmAllowed(profilePatchRequestDto.isAlarmAllowed());
}
if (profilePatchRequestDto.isPushAlarmAllowed() != null) {
existingMember.updateMemberIsPushAlarmAllowed(profilePatchRequestDto.isPushAlarmAllowed());
}
if (profilePatchRequestDto.fcmToken() != null) {
existingMember.updateMemberFcmToken(profilePatchRequestDto.fcmToken());
}
Member savedMember = memberRepository.save(existingMember);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.dontbe.www.DontBeServer.api.member.repository.MemberRepository;
import com.dontbe.www.DontBeServer.api.notification.domain.Notification;
import com.dontbe.www.DontBeServer.api.notification.repository.NotificationRepository;
import com.dontbe.www.DontBeServer.external.fcm.dto.FcmMessageDto;
import com.dontbe.www.DontBeServer.external.fcm.service.FcmService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -25,14 +27,16 @@ public class PopularContentScheduler {
private final CommentRepository commentRepository;
private final NotificationRepository notificationRepository;
private final MemberRepository memberRepository;
private final FcmService fcmService;

public PopularContentScheduler(ContentRepository contentRepository, ContentLikedRepository contentLikedRepository,
CommentRepository commentRepository, NotificationRepository notificationRepository, MemberRepository memberRepository){
CommentRepository commentRepository, NotificationRepository notificationRepository, MemberRepository memberRepository, FcmService fcmService){
this.contentRepository = contentRepository;
this.contentLikedRepository = contentLikedRepository;
this.commentRepository = commentRepository;
this.notificationRepository = notificationRepository;
this.memberRepository = memberRepository;
this.fcmService = fcmService;
}

@Scheduled(cron = "0 0 4 * * ?")
Expand Down Expand Up @@ -63,6 +67,28 @@ public void popularContentNotification() {
.build();
Notification savedPopularWriterNotification = notificationRepository.save(popularWriterNotification);

if(topContentWriter.isPushAlarmAllowed()) {
String FcmMessageTitle = topContentWriter.getNickname() + "님이 작성하신 글이 인기들로 선정 되었어요.";

FcmMessageDto popularContentFcmMessage = FcmMessageDto.builder()
.validateOnly(false)
.message(FcmMessageDto.Message.builder()
.notificationDetails(FcmMessageDto.NotificationDetails.builder()
.title(FcmMessageTitle)
.body("")
.build())
.token(topContentWriter.getFcmToken())
.data(FcmMessageDto.Data.builder()
.name("popularContent")
.description("인기글 관련 푸시 알림")
.relateContentId(topContent.getId())
.build())
.build())
.build();

fcmService.sendMessage(popularContentFcmMessage);
}

List<Member> activeMembers = memberRepository.findAllActiveMembers();

for (Member activeMember : activeMembers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum ErrorStatus {
WITHDRAWAL_MEMBER("계정 삭제 후 30일 이내 회원입니다."),
UNVALID_PROFILEIMAGE_TYPE("이미지 확장자는 jpg, png, webp만 가능합니다."),
PROFILE_IMAGE_DATA_SIZE("이미지 사이즈는 5MB를 넘을 수 없습니다."),
FCM_SERVICE_ERROR("푸시 알림 발생 과정에 오류가 생겼습니다."),


/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.dontbe.www.DontBeServer.external.fcm.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@AllArgsConstructor
@Getter
public class FcmMessageDto {
private boolean validateOnly;
private Message message;

@Builder
@AllArgsConstructor
@Getter
public static class Message {
@JsonProperty("notification")
private NotificationDetails notificationDetails;
private String token;
private Data data;
}

@Builder
@AllArgsConstructor
@Getter
public static class NotificationDetails {
private String title;
private String body;
}

@Builder
@AllArgsConstructor
@Getter
public static class Data {
private String name;
private String description;
private Long relateContentId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.dontbe.www.DontBeServer.external.fcm.service;

import com.dontbe.www.DontBeServer.common.exception.BadRequestException;
import com.dontbe.www.DontBeServer.external.fcm.dto.FcmMessageDto;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class FcmService {

private final ObjectMapper objectMapper;

@PostConstruct
public void initialize() throws IOException {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(new ClassPathResource("fire-base.json").getInputStream()))
.build();
if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
}
}

public void sendMessage(FcmMessageDto fcmMessageDto) {
Message message = Message.builder()
.setToken(fcmMessageDto.getMessage().getToken())
.setNotification(com.google.firebase.messaging.Notification.builder()
.setTitle(fcmMessageDto.getMessage().getNotificationDetails().getTitle())
.setBody(fcmMessageDto.getMessage().getNotificationDetails().getBody())
.build()
)
.putAllData(objectMapper.convertValue(fcmMessageDto.getMessage().getData(), Map.class))
.build();

try{
FirebaseMessaging.getInstance().send(message);
} catch (FirebaseMessagingException e) {
throw new BadRequestException(e.getMessage());
}
}
}

0 comments on commit defcad9

Please sign in to comment.