프로그래밍 공부/Spring Boot

[Java] spring boot - firebase message server

뚜벅이! 2021. 7. 27. 19:23
728x90

이제는 없으면 안되는 firebase.

 

 

웹에서 작성한 알림메세지를 서버로 전송하여 각각의 디바이스로 뿌려주는 간단한 코드를 작성하고자 한다.

대략 플로우는 이런식이다.

 

Web ( notification UI/UX ) -> Spring Boot( WAS ) -> Firebase -> Android ( device )

 

Firebase를 경유 해야하기 때문에 폐쇄망에서는 Firebase를 구축하거나,

방화벽 허용 등으로 해결해야 된다.

 

 

 

https://console.firebase.google.com/

 

로그인 - Google 계정

하나의 계정으로 모든 Google 서비스를 Google 계정으로 로그인

accounts.google.com

 

 

 

우선 Firebase -> console 로 이동해서 프로젝트 생성을 해준다.

 

 

 

그 후에 사진과 같은 방법으로 비공개 키를 생성해준다.

사용자 인증할 때 필요한 친구이고,

 

 

 

 

 

여기서 sdk를 다운받아서 프로젝트를 연결 시켜준다.

화면에 보이듯이, SDK 안내 보기를 누르면 쉽게 설명해주기 때문에 따로 설명은 필요 없을것같다.

 

 

 

 

 

 

그 다음 Spring Boot에서 설정을 시작해보자.

SDK를 제대로 연결했다면 Spring Boot 폴더에 .json 파일이 들어가 있을것이다.

		<dependency>
			<groupId>com.google.firebase</groupId>
			<artifactId>firebase-admin</artifactId>
			<version>7.3.0</version>
		</dependency>

maven으로 작업했기때문에 pom.xml에 작성해주고,

 

 

패키징은 단순 보내기 목적만 달성할 요량이므로 간단하게 만들어준다.

 

 

 

 

 

 

우선 initalize를 먼저 작성해주는 것이 좋을거같다.

FirebaseConfig

@Configuration
public class FirebaseConfig {
    private final Logger logger = LoggerFactory.getLogger(FirebaseConfig.class);

    @Value("${firebase-sdk-path}") // your firebase sdk path
    private String firebaseSdkPath;

    @PostConstruct
    public void initialize() {
        try {

            ClassPathResource resource = new ClassPathResource(firebaseSdkPath);
            InputStream serviceAccount = resource.getInputStream();
            // 2021.06.23 FirebaseOptions 생성자가 Deprecated 되었기 때문에 builder 수정.
            // 2022.01.04 공식홈페이지에서 제대로 수정됨
            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                    .build();
            FirebaseApp.initializeApp(options);

        } catch (FileNotFoundException e) {
            logger.error("Firebase ServiceAccountKey FileNotFoundException" + e.getMessage());
        } catch (IOException e) {
            logger.error("FirebaseOptions IOException" + e.getMessage());
        }

    }
}

나는 .yml 파일에 firebase-sdk-path 변수에 path를 저장해두었다.

 

 

 

 

그리고 보낼형식을 만들어주는 model를 작성한다.

@Builder
@AllArgsConstructor
@Data
public class FcmMessage {
    private boolean validate_only;
    private Message message;

    @Builder
    @AllArgsConstructor
    @Data
    public static class Message {
        private Notification notification;
        private String token;
    }

    @Builder
    @AllArgsConstructor
    @Data
    public static class Notification {
        private String title;
        private String body;
        private String image;
    }
}

lombok 어노테이션은 개인 취향

 

 

 

 

 

그다음 Service 작성

@Slf4j
@Service
public class PushNotificationService {
    private static final String PROJECT_ID = /* your project id */;
    private static final String BASE_URL = "https://fcm.googleapis.com";
    private static final String FCM_SEND_ENDPOINT = "/v1/projects/" + PROJECT_ID + "/messages:send";
    private static final String MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
    private static final String[] SCOPES = { MESSAGING_SCOPE };
    public static final String MESSAGE_KEY = "message";
   
}

일단 상수 값들을 넣어준다. firebase 홈페이지에 있는 project_id ( 또는 json파일 읽어보면 적혀있다. ) 를 적어준다.

ENDPOINT가 왜 이렇지하고 생각 될 수 있는데

firebase가 기존 HTTP 에서 HTTP v1 으로 마이그레이션을 진행했다.

작성당시에는 버그가 어마어마하게 많았는데

주기적으로 업데이트 하는 듯 하다.

( 참고로 google 답지않게 공식 페이지의 description이 최악이였다.. )

 

 

 

  
    private static HttpURLConnection getConnection() throws IOException {
        // [START use_access_token]
        URL url = new URL(BASE_URL + FCM_SEND_ENDPOINT);
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
        httpURLConnection.setRequestProperty("Authorization", "Bearer " + getAccessToken());
        httpURLConnection.setRequestProperty("Content-Type", "application/json; UTF-8");
        return httpURLConnection;
        // [END use_access_token]
    }

    private static String getAccessToken() throws IOException {
        //21.6.23 아직까지도 공식 홈페이지에서 Deprecated 된 해당 문장을 수정하지 않고있다.
        //22.01.04 공식 홈페이지에서 제대로 수정이 되었다.
        GoogleCredentials googleCredential = GoogleCredentials
                .fromStream(new ClassPathResource("/*your json file*/").getInputStream())
                .createScoped(Arrays.asList(SCOPES));
        googleCredential.refreshIfExpired();
        return googleCredential.getAccessToken();
    }

    private static String inputstreamToString(InputStream inputStream) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        Scanner scanner = new Scanner(inputStream);
        while (scanner.hasNext()) {
            stringBuilder.append(scanner.nextLine());
        }
        return stringBuilder.toString();
    }

우선, Firebase와 통신하기 위해 connection 을 열어주고, 서비스를 이용 하기위해

token 값을 받아주는 메소드를 먼저 작성해준다.

inputstreamToString은 메세지를 보내고나서 return값을 connection에서 읽어오기 위해 필요하다.

 

 

 

 

 

    public static void sendMessage(JsonObject fcmMessage) throws IOException {
        HttpURLConnection connection = getConnection();
        connection.setDoOutput(true);
        DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
        outputStream.writeBytes(fcmMessage.toString());
        outputStream.flush();
        outputStream.close();

        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            String response = inputstreamToString(connection.getInputStream());
            System.out.println("Message sent to Firebase for delivery, response:");
            System.out.println(response);
        } else {
            System.out.println("Unable to send message to Firebase:");
            String response = inputstreamToString(connection.getErrorStream());
            System.out.println(response);
        }
    }

firebase와 통신하고, return값을 받는 메소드

 

 

 

 

 

 

이제 메세지에 무엇이 들어가는지, 누구에게 보내는지 등을 특정해줘야 한다.

    private static JsonObject buildNotificationMessage(String title, String body) {
        JsonObject jNotification = new JsonObject();
        jNotification.addProperty("title", title);
        jNotification.addProperty("body", body);

        JsonObject jMessage = new JsonObject();
        jMessage.add("notification", jNotification);
        /*
            firebase
            1. topic
            2. token
            3. condition -> multiple topic
         */
        jMessage.addProperty("topic", "news");
        //jMessage.addProperty("token", /* your test device token */);

        JsonObject jFcm = new JsonObject();
        jFcm.add(MESSAGE_KEY, jMessage);

        return jFcm;
    }

firebase에서 누군가를 특정할 수 있는 방법은 크게 topic과 token이 있다.

 

사실 다른 사람들은 어떻게 구분해서 쓰는지 모르겠지만

나같은 경우는

 

topic은 grouping 해서 보내는 값이기 때문에,

특정 이벤트나 공지로 쓰고 있고

 

token은 각각 개개인에게 보낼 수 있게 되어있기 때문에,

사용자의 event로 무언가 알림이 갈 일이 생기면 쓰고 있다.

 

condiction은 조건식 조합법이다.

자세한건 firebase 홈페이지에서 확인 가능하다.

 

 

 

 

    public static void sendCommonMessage(String title, String body) throws IOException {
        JsonObject notificationMessage = buildNotificationMessage(title, body);
        System.out.println("FCM request body for message using common notification object:");
        prettyPrint(notificationMessage);
        sendMessage(notificationMessage);
    }
    
    private static void prettyPrint(JsonObject jsonObject) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        System.out.println(gson.toJson(jsonObject) + "\n");
    }

prettyPrint는 로그용.

 

 

 

 

 

이제 웹에서 오는 신호를 받기 위해 Controller를 작성하면 서버단은 끝이다.

@RestController
@Slf4j
public class PushApiController {

    /**
     * 푸시 알람 요청
     * @param title
     * @param body
     * @throws IOException
     */
    @PostMapping("/fcm")
    public ResponseEntity<?> reqFcm(
            @RequestParam(required = true) String title,
            @RequestParam(required = true) String body
    ) {

        log.info("** title : {}",title);
        log.info("** body : {}",body);

        CommResponse res = new CommResponse();

        try {

            PushNotificationService.sendCommonMessage(title, body);
            res.setCdResult(Constant.SUCCESS);

        } catch(Exception e) {
            log.error(e.getMessage());
            res.setCdResult(Constant.FAILED);
            res.setMsgResult("처리중 에러 발생");
            return new ResponseEntity<>(res, HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return ResponseEntity.ok(res);
    }

}

 

 

 

 

 

그럼 API Test를 해보자.

여담이지만 PostMan 쓰다가 API Tester 로 넘어왔는데 괜찮은거같다. 크롬 확장프로그램.

200값이 return 되었으며 정상적으로 메세지가 보내진것이 확인된다.

 

 

 

 

 

아직 모바일쪽은 설명안했지만 알람 테스트용으로 스샷을 찍었다.

title과 body가 잘 들어간 푸쉬 확인가능 !

 

 

 

모바일 소스는 여기

2021.12.29 - [프로그래밍 공부/Android] - [firebase] notification / message

 

[firebase] notification / message

2021.07.27 - [프로그래밍 공부/Spring Boot] - [Java] spring boot - firebase message server Spri" data-og-host="ttubeoki.tistory.com" data-og-source-url="https://ttubeoki.tistory.com/38" data-og-url="..

ttubeoki.tistory.com

 

728x90