Spring/게시판API서버

[Spring] 게시판 API (9) PushAlarm FCM 활용

wans10 2023. 3. 27. 17:50

FCM과 PushAlarm의 동작원리를 알고 싶다면 이전 포스팅 해둔 내용을 보면 됩니다.

 

2023.03.26 - [Spring] - 앱 푸시알림동작 원리와 FCM(Firebase Cloud Messaging)을 알아보자

 

앱 푸시알림동작 원리와 FCM(Firebase Cloud Messaging)을 알아보자

게시판 앱과 서버에서 푸시알림을 구현하기 전에 푸시알람과 이를 구현해주는 FCM의 원리를 공부해보고 이를 정리해보았습니다. 푸시알림이란 데스크탑 브라우저, 모바일 홈 화면, 모바일 앱의

wans1027.tistory.com

 

구현해야할 기능

  • 새로운 기기가 로그인으로 접근이 되면
  • 멤버테이블에 있는 로그인 한 회원에 FCMTOKEN을 추가한다.

 

순서설계

  1. 앱은 앱 실행 시점에 FCM에 Binding 
  2. 만약 내부저장소에 토큰이 이미 존재한다면 파라미터 변수인 isNew를 false로
  3. 내부저장소에 토큰이 없다면 새로운 토큰을 FCM으로부터 받아오고 isNew를 true로 설정합니다.
  4. 로그인 시점에 isNew가 true라면 FCM토큰 GET요청 헤더에 실어 서버로 전송
  5. 서버는 이를 받아 회원테이블과 일대다로 연결된 FCMTABLE에 토큰을 추가

 

이렇게 설계한 이유는 앱을 재설치하거나 등록 기기를 바꾸는게 아닌이상 매번 같은 Token이 전송되므로 서버에서 이 토큰을 항상 대조시키는 로직을 사용하는 것은 비용낭비입니다.

그래서 새로운 토큰인지 판단하는 로직은 앱에서 하는 것이 효율적입니다.

서버는 단지 새로운 요청이들어오면 이를 저장만 하면 됩니다.

 

 

 

Spring에 FCM적용

 

FireBase에서 받은 비공개 키 json파일을 main/resources/ 에 추가합니다.

 

build.gradle과 application.yml에 추가합니다.

# build.gradle
implementation group: 'com.google.firebase', name: 'firebase-admin', version: '6.8.1'

# application.yml
fcm:
  key:
    path: Firebase에서받은키.json
    scope: https://www.googleapis.com/auth/cloud-platform

 

Firbase계정 인증 및 알림서비스 로직

@Slf4j
@Service
public class NotificationServiceImpl {

    @Value("${fcm.key.path}")
    private String FCM_PRIVATE_KEY_PATH;

    //
    // 메시징만 권한 설정
    @Value("${fcm.key.scope}")
    private String fireBaseScope;

    // fcm 기본 설정 진행
    @PostConstruct
    public void init() {
        try {
            FirebaseOptions options = new FirebaseOptions.Builder()
                    .setCredentials(
                            GoogleCredentials
                                    .fromStream(new ClassPathResource(FCM_PRIVATE_KEY_PATH).getInputStream())
                                    .createScoped(List.of(fireBaseScope)))
                    .build();
            if (FirebaseApp.getApps().isEmpty()) {
                FirebaseApp.initializeApp(options);
                log.info("Firebase application has been initialized");
            }
        } catch (IOException e) {
            log.error(e.getMessage());
            // spring 뜰때 알림 서버가 잘 동작하지 않는 것이므로 바로 죽임
            throw new RuntimeException(e.getMessage());
        }
    }


    // 알림 보내기
    public void sendByTokenList(List<String> tokenList,String title, String body) {

        // 메시지 만들기
        List<Message> messages = tokenList.stream().map(token -> Message.builder()
                .putData("time", LocalDateTime.now().toString())
                .setNotification(new Notification(title, body))
                .setToken(token)
                .build()).collect(Collectors.toList());

        // 요청에 대한 응답을 받을 response
        BatchResponse response;
        try {

            // 알림 발송
            response = FirebaseMessaging.getInstance().sendAll(messages);

            // 요청에 대한 응답 처리
            if (response.getFailureCount() > 0) {
                List<SendResponse> responses = response.getResponses();
                List<String> failedTokens = new ArrayList<>();

                for (int i = 0; i < responses.size(); i++) {
                    if (!responses.get(i).isSuccessful()) {
                        failedTokens.add(tokenList.get(i));
                    }
                }
                log.error("List of tokens are not valid FCM token : " + failedTokens);
            }
        } catch (FirebaseMessagingException e) {
            log.error("cannot send to memberList push message. error info : {}", e.getMessage());
        }
    }
}

코드출처: https://backtony.github.io/spring/2021-08-20-spring-fcm-1/

 

여기까지 하면 푸시알림을 보낼 준비가 끝났습니다.!

 

회원에 매칭시키기

이제 게시판에 활용하기 위해 테이블 관계설정을 해주겠습니다.

 

토큰 저장 테이블 추가

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FcmToken {

    @Id
    @GeneratedValue
    @Column(name = "fcm_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    private String token;
    
    public FcmToken(Member member, String token) {
        this.member = member;
        this.token = token;
    }
}

회원PK를 외래키로 가지고 토큰을 저장하는 테이블입니다. 

 

 

 

Member Entity에 추가

@OneToMany(mappedBy = "member", orphanRemoval = true)
private List<FcmToken> fcmTokens = new ArrayList<>();

회원이 사라지면 저장된 토큰도 사라져야 하기에 orphanRemoval를 true로 설정합니다.

 

MemberService에 추가

public void addFCMToken(Long memberId, String fcmToken){
    Optional<Member> member = memberRepository.findById(memberId);
    FcmToken token = new FcmToken(member.orElseThrow(), fcmToken);
    fcmTokenRepository.save(token);
}

멤버 주키와 토큰값을 받아와 저장하는 서비스 로직입니다.

 

Controller 작성

@RestController
@RequiredArgsConstructor
public class FcmController {

    private final NotificationServiceImpl notificationService;
    private final MemberService memberService;

    @GetMapping(value = "/api/fcm/{memberId}",headers = "FCM-TOKEN")
    public void pushAlarmMyDevice(@PathVariable("memberId") Long memberId, @RequestHeader("FCM-TOKEN") String token){
       memberService.addFCMToken(memberId,token);
    }
}

사용자의 GET요청 헤더에 담긴 토큰값을 가져와 저장하면 끝입니다.

 

푸시알림을 보내고 싶으면 아래 메서드를 사용하면 됩니다.

notificationService.sendByTokenList(토큰, 제목, 내용);

 

이제 이를 활용하여 앱이 꺼져 있을때도 새로운 글이 올라오거나, 누군가 내글에 댓글을 달거나 등의 상태변화를 스마트폰 백그라운드에서 확인 하는 기능을 만들 수 있습니다.

 

 

Reference:

https://backtony.github.io/spring/2021-08-20-spring-fcm-1/

 

Spring 비동기 FCM 알림서버 구현하기 (Feat.ApplicationEvent)

Java, JPA, Spring을 주로 다루고 공유합니다.

backtony.github.io

 

Flutter에서 FCM의 동작을 알고싶다면

2023.03.25 - [Flutter] - [Flutter] 푸시알람 FCM 설정하기

 

[Flutter] 푸시알람 FCM 설정하기

- FireBase Setting https://firebase.google.com/?hl=ko Firebase Firebase는 고품질 앱을 빠르게 개발하고 비즈니스를 성장시키는 데 도움이 되는 Google의 모바일 플랫폼입니다. firebase.google.com FireBase에 로그인하고

wans1027.tistory.com