[AWS LAMP] 파이어베이스(FCM) 자동 푸시알림 발송을 위한 환경 설정 및 발송 코드 그리고 CRONTAB
푸시 알림 자동 발송 (PHP -> Firebase)
웹사이트(PHP)에서 게시글이 등록되거나, 특정 조건이 맞았을 때 자동으로 사용자의 폰으로 알림을 쏘는 기능입니다. 원리: PHP 서버가 구글 파이어베이스(FCM) 서버로 “이러이러한 내용으로 알림 보내줘!”라고 API 요청을 보냅니다.
주의사항 (필독): 과거에 쓰던 ‘서버 키(Server Key)’ 방식은 구글에서 곧 지원을 중단됨으로 반드시 최신 방식인 FCM HTTP v1 API를 사용해야 합니다.
구현 방법 (PHP)
PHP에서 이 최신 API를 직접 짜려면 인증 토큰 생성 등 과정이 복잡하므로, 전 세계 PHP 개발자들이 가장 많이 쓰는 공식 수준의 라이브러리인 “kreait/firebase-php” 를 사용하는 것을 강력히 추천합니다.
- 서버(SSH/터미널)에서 Composer를 이용해 라이브러리를 설치
- 파이어베이스 콘솔 -> 프로젝트 설정 -> 서비스 계정에서 ‘새 비공개 키 생성’을 눌러 .json 파일을 서버로 다운로드
- PHP 발송 코드 작성
- 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);
// 서버로 토큰을 전송하는 로직을 나중에 여기에 추가할 수 있습니다.
}
}



