AndroidWeb Hosting프로그래밍

[AWS LAMP] 파이어베이스(FCM) 자동 푸시알림 발송을 위한 환경 설정 및 발송 코드 그리고 CRONTAB

푸시 알림 자동 발송 (PHP -> Firebase)

웹사이트(PHP)에서 게시글이 등록되거나, 특정 조건이 맞았을 때 자동으로 사용자의 폰으로 알림을 쏘는 기능입니다. 원리: PHP 서버가 구글 파이어베이스(FCM) 서버로 “이러이러한 내용으로 알림 보내줘!”라고 API 요청을 보냅니다.

주의사항 (필독): 과거에 쓰던 ‘서버 키(Server Key)’ 방식은 구글에서 곧 지원을 중단됨으로 반드시 최신 방식인 FCM HTTP v1 API를 사용해야 합니다.


구현 방법 (PHP)

PHP에서 이 최신 API를 직접 짜려면 인증 토큰 생성 등 과정이 복잡하므로, 전 세계 PHP 개발자들이 가장 많이 쓰는 공식 수준의 라이브러리인 “kreait/firebase-php” 를 사용하는 것을 강력히 추천합니다.

  1. 서버(SSH/터미널)에서 Composer를 이용해 라이브러리를 설치
  2. 파이어베이스 콘솔 -> 프로젝트 설정 -> 서비스 계정에서 ‘새 비공개 키 생성’을 눌러 .json 파일을 서버로 다운로드
  3. PHP 발송 코드 작성
  4. CRONTAB 등록


1. 서버(SSH/터미널)에서 Composer를 이용해 라이브러리를 설치

아래 명령어를 실행하면 설치가 진행됩니다.

composer require kreait/firebase-php

설치위치 : vendor/kreait/firebase-php 경로에 설치됨 ( 명령어를 실행하는 폴더 위치 기준으로 설치됨)

* 비트나미 홈에서 설치한 경우
/home/bitnami/vendor/kreait/firebase-php

삭제 후 /opt/bitnami/디렉토리생성/ 후 여기에 다시 설치를 진행하였습니다.

그리고 디렉토리 생성후 권한과 그룹을 bitnami로 변경하였습니다.

sudo chown -R bitnami:bitnami /opt/bitnami/생성디렉토리명


주의 (헷갈리는 포인트)

  • 전역(global) 설치 아님 ❌
  • 각 프로젝트마다 따로 설치됨 ⭕
  • vendor/autoload.php 통해서 불러야 함
  • composer.json, vendor 폴더 등을 웹 루트(htdocs) 외부에 둘것 !! 노출되면 보안상 좋지 않음
  • 나쁜 예: /htdocs/composer.json, /htdocs/vendor/
  • 좋은 예: /make your project/composer.json, /make your project/vendor/ (웹에서 접근 불가능한 상위 폴더)


삭제방법

cd /home/bitnami/
composer remove kreait/firebase-php

삭제 명령어도 반드시 설치명령어를 실행한 경로에서 실행해야 삭제가 진행됩니다.

  • vendor/kreait/firebase-php 폴더 삭제됨
  • composer.json에서도 제거됨
  • composer.lock도 자동 업데이트됨



설치 여부 확인

composer show | grep kreait

---명령어 실행결과 버전정보까지 확인 가능
bitnami@ip-:/opt/bitnami/apache/$ composer show | grep kreait
kreait/firebase-php           8.2.0  Firebase Admin SDK
kreait/firebase-tokens        5.3.0  A library to work with Firebase tokens

2. 파이어베이스 콘솔 -> 프로젝트 설정 -> 서비스 계정에서 ‘새 비공개 키 생성’을 눌러 .json 파일을 서버로 다운로드

비공개키는 노출되면 안됩니다. ftp프로그램 파일질라를 사용하여 복사할때

반드시 /opt/bitnami/apache/htdocs 폴더에 복사하면 안되며, 절대 접근할 수 없도록 반드시 /opt/bitnami/apache/에 복사하세요.


3. PHP 발송 코드 작성

<?php
/*
    cron_push_app.php - 매일 오전 8시에 'notice' 토픽으로 푸시 알림 발송
*/
require __DIR__.'/vendor/autoload.php';

use Kreait\Firebase\Factory;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Firebase\Messaging\Notification;

// 🌟 2. 메시지 구성 (최신 버전 권장 방식)
$messageData = [
    'topic' => 'notice', // 타겟 설정
    'notification' => [
        'title' => $title,
        'body' => $body,
    ],
];

// URL이 있을 때만 data 추가
if (!empty($targetUrl)) {
    $messageData['data'] = [
        'url' => $targetUrl
    ];
}

// 배열로부터 메시지 객체 생성
$message = CloudMessage::fromArray($messageData);

// 3. 발송
try {
    $messaging->send($message);
    echo "푸시 발송 성공!";
} catch (\Exception $e) {
    echo "발송 실패: " . $e->getMessage();
}



/* 버전이 낮은경우
// 1. 다운받은 json 키 파일 연결
$factory = (new Factory)->withServiceAccount('//opt/bitnami/apache/firebase-adminsdk.json');
$messaging = $factory->createMessaging();

// 2. 메시지 구성
$title = '새로운 건강 리포트 도착!';
$body = '지금 바로 확인해보세요.';
$targetUrl = 'https://사용자도메인/view.php?id=123'; // 또는 상황에 따라 빈 값('')

// 3. 메시지 객체 생성 (기본 타겟 및 알림 설정)
$message = CloudMessage::withTarget('topic', 'notice')
    ->withNotification(Notification::create($title, $body));

// 4. URL이 있을 때만 Data 페이로드 추가
if (!empty($targetUrl)) {
    $message = $message->withData([
        'url' => $targetUrl
    ]);
}

// 5. 발송
$messaging->send($message);
*/
?>


4. CRONTAB 등록

  • 등록 명령어 : crontab -e
  • 등록확인 : crontab -l
# 월~금 : 아침 8시20분 자동 발송 및 실행 로그 안남김
20 8 * * 1-5 /opt/bitnami/php/bin/php /opt/bitnami/apache/htdocs/test/cron_push_app.php > /dev/null 2>&1

#로그 남기는 케이스
#20 8 * * 1-5 /opt/bitnami/php/bin/php /opt/bitnami/apache/htdocs/test/cron_push_app.php >> /opt/bitnami/apache/htdocs/cron_push.log >

[별책부록]

안드로이드에서 코드

package com.test;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    // 파이어베이스에서 메시지가 도착했을 때 실행되는 함수
    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        String title = "알림"; // 기본 제목
        String body = "새로운 알림이 도착했습니다."; // 기본 내용
        String url = "";

        // 1. 알림(Notification) 페이로드 확인 (제목, 내용)
        if (remoteMessage.getNotification() != null) {
            if(remoteMessage.getNotification().getTitle() != null) {
                title = remoteMessage.getNotification().getTitle();
            }
            if(remoteMessage.getNotification().getBody() != null) {
                body = remoteMessage.getNotification().getBody();
            }
        }

        // 2. 데이터(Data) 페이로드 확인 (PHP에서 보낸 url 추출)
        // 3. 스마트폰 상단 알림창에 메시지 띄우기 (방어코드추가)
        if (remoteMessage.getData().size() > 0) {
            url = remoteMessage.getData().get("url"); // 키값이 "url"인지 확인
            // 문자열이 null이 아니고, 공백을 제외한 길이가 0보다 클 때만 전달
            if (url != null && !url.trim().isEmpty()) {
                sendNotification(title, body, url);
            } else {
                sendNotification(title, body, null); // URL 없이 알림만 생성
            }
        }

    }

    // 스마트폰 상단 알림창에 메시지를 띄우는 함수
    private void sendNotification(String title, String messageBody, String url) {

        // 1. 알림을 클릭했을 때 열릴 화면(MainActivity) 설정
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        // 2. 웹뷰로 이동할 URL 데이터 넘겨주기
        if (url != null && !url.isEmpty()) {
            intent.putExtra("url", url);
        }
//        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

        // 3. PendingIntent 생성 (안드로이드 12 이상을 위해 FLAG_IMMUTABLE 필수)
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);

        // 4. 알림 채널 ID 및 시스템 서비스 설정 (안드로이드 8.0 오레오 이상 필수)
        String channelId = "test_channel"; // 알림 채널 ID
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);


        // 안드로이드 8.0(Oreo) 이상부터는 '알림 채널'이 필수입니다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    channelId,
                    "알림",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        // 5. 알림 디자인(Builder) 설정
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, channelId)
                        .setSmallIcon(R.mipmap.ic_launcher) // 앱 아이콘 (원하는 알림 아이콘으로 변경 가능)
                        .setContentTitle(title)
                        .setContentText(messageBody)
                        .setAutoCancel(true) // 누르면 알림 사라짐
                        .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                        .setContentIntent(pendingIntent);


        // 6. 최종적으로 알림 띄우기 (0은 알림 ID, 다중 알림이 필요하면 현재 시간 등으로 변경)
        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }

    // 앱의 고유 토큰이 갱신될 때 (특정 유저에게만 알림을 보낼 때 필요)
    @Override
    public void onNewToken(@NonNull String token) {
        super.onNewToken(token);
        // 서버로 토큰을 전송하는 로직을 나중에 여기에 추가할 수 있습니다.
    }
}

Hi, I’m 관리자