Skip to content

Commit

Permalink
Merge pull request #23 from dnd-side-project/feat/fcm
Browse files Browse the repository at this point in the history
[Feat#22] FCM을 활용한 알림 시스템 구현
  • Loading branch information
FacerAin authored Feb 16, 2024
2 parents ac04ad7 + 912ce97 commit 507a634
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Build

on: [pull_request, workflow_dispatch]
on: [ pull_request, workflow_dispatch ]


jobs:
Expand All @@ -17,6 +17,10 @@ jobs:
java-version: 17
- name: Run Checkstyle
run: ./gradlew checkstyleMain checkstyleTest
- name: Make firebase-adminsdk.json
run: |
touch src/main/resources/timeet-firebase-adminsdk.json
echo "${{ secrets.FIREBASE_ADMINSDK }}" | base64 --decode > src/main/resources/timeet-firebase-adminsdk.json
- name: Make application-prod.yml
run: |
touch src/main/resources/application-prod.yml
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
timeet-firebase-adminsdk.json
HELP.md
.gradle
build/
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation("org.springframework.boot:spring-boot-devtools")
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'com.google.firebase:firebase-admin:9.2.0'
implementation 'org.springframework.boot:spring-boot-starter-websocket'


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dnd.timeet.common.security.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.core.annotation.AuthenticationPrincipal;

@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member")
public @interface ReqUser {

}
41 changes: 41 additions & 0 deletions src/main/java/org/dnd/timeet/config/FCMConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.dnd.timeet.config;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.FirebaseMessaging;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@Configuration
public class FCMConfig {

@Bean
FirebaseMessaging firebaseMessaging() throws IOException {
ClassPathResource resource = new ClassPathResource("timeet-firebase-adminsdk.json");

InputStream refreshToken = resource.getInputStream();

FirebaseApp firebaseApp = null;
List<FirebaseApp> firebaseAppList = FirebaseApp.getApps();

if (firebaseAppList != null && !firebaseAppList.isEmpty()) {
for (FirebaseApp app : firebaseAppList) {
if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) {
firebaseApp = app;
}
}
} else {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(refreshToken)).build();
firebaseApp = FirebaseApp.initializeApp(options);
}
return FirebaseMessaging.getInstance(firebaseApp);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.dnd.timeet.fcm.application;

import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import java.util.Collections;
import lombok.RequiredArgsConstructor;
import org.dnd.timeet.common.exception.InternalServerError;
import org.dnd.timeet.common.exception.NotFoundError;
import org.dnd.timeet.fcm.domain.FCMNotificationRequestDto;
import org.dnd.timeet.member.domain.Member;
import org.dnd.timeet.member.domain.MemberRepository;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class FCMNotificationService {

private final FirebaseMessaging firebaseMessaging;
private final MemberRepository memberRepository;

public void sendNotificationByToken(FCMNotificationRequestDto requestDto) {
Member member = memberRepository.findById(requestDto.getTargetMemberId())
.orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND,
Collections.singletonMap("MemberId", "Member not found")));
if (member.getFcmToken() == null) {
throw new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND,
Collections.singletonMap("fcmToken", "fcmToken not exist"));
}

Notification notification = Notification.builder()
.setTitle(requestDto.getTitle())
.setBody(requestDto.getBody())
.build();

Message message = Message.builder()
.setToken(member.getFcmToken())
.setNotification(notification)
.build();

try {
firebaseMessaging.send(message);
} catch (Exception e) {
throw new InternalServerError(InternalServerError.ErrorCode.INTERNAL_SERVER_ERROR,
Collections.singletonMap("fcmSend", "Fail to send fcm"));
}


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.dnd.timeet.fcm.domain;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class FCMNotificationRequestDto {

private Long targetMemberId;
private String title;
private String body;

@Builder
public FCMNotificationRequestDto(Long targetMemberId, String title, String body) {
this.targetMemberId = targetMemberId;
this.title = title;
this.body = body;
}

}
13 changes: 13 additions & 0 deletions src/main/java/org/dnd/timeet/member/application/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.dnd.timeet.member.application;


import java.util.Collections;
import lombok.RequiredArgsConstructor;
import org.dnd.timeet.common.exception.NotFoundError;
import org.dnd.timeet.member.domain.Member;
import org.dnd.timeet.member.domain.MemberRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
Expand All @@ -14,4 +17,14 @@ public class MemberService {

private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;

@Transactional
public void upsertFcmToken(Long id, String fcmToken) {
Member member = memberRepository.findById(id)
.orElseThrow(() -> new NotFoundError(NotFoundError.ErrorCode.RESOURCE_NOT_FOUND,
Collections.singletonMap("MemberId", "Member not found")));
member.setFcmToken(fcmToken);


}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package org.dnd.timeet.member.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.dnd.timeet.common.security.annotation.ReqUser;
import org.dnd.timeet.member.application.MemberService;
import org.dnd.timeet.member.domain.Member;
import org.dnd.timeet.member.dto.RegisterFcmRequest;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -14,5 +20,12 @@ public class MemberController {

private final MemberService memberService;

@PatchMapping
@Operation(summary = "fcmToken 등록", description = "fcmToken을 등록한다.")
public void registerFcmToken(@RequestBody RegisterFcmRequest registerFcmRequest,
@ReqUser Member member) {
memberService.upsertFcmToken(member.getId(), registerFcmRequest.getFcmToken());

}
}

8 changes: 8 additions & 0 deletions src/main/java/org/dnd/timeet/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public class Member extends BaseEntity {
@Column(length = 50, nullable = false)
private OAuth2Provider provider;

@Column(length = 255)
private String fcmToken;

@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private Set<Participant> participations = new HashSet<>();

Expand All @@ -56,4 +59,9 @@ public Member(MemberRole role, String name, String imageUrl, Long oauthId, OAuth
this.oauthId = oauthId;
this.provider = provider;
}


public void setFcmToken(String fcmToken) {
this.fcmToken = fcmToken;
}
}
16 changes: 16 additions & 0 deletions src/main/java/org/dnd/timeet/member/dto/RegisterFcmRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.dnd.timeet.member.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Schema(description = "fcmToken 등록 요청")
@Getter
@Setter
@NoArgsConstructor
public class RegisterFcmRequest {

@Schema(description = "fcmToken", nullable = false, example = "fcmToken")
private String fcmToken;
}

0 comments on commit 507a634

Please sign in to comment.