Skip to content

dynaram-io/notification-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

notification-service

개요

알람 트리거 이벤트를 수신하여 날씨/버스 정보 기반 상세 알람 멘트를 생성하고, Expo Push Notification을 통해 사용자의 모든 디바이스로 알람을 발송하는 서비스입니다.

Phase 2 완료: DB 기반 Push Token 관리, 멀티 디바이스 지원, 상세 알람 메시지 생성

주요 책임

  • 알람 트리거 이벤트 구독
  • 날씨/버스 정보 기반 상세 알람 멘트 생성 (TTS 최적화)
  • Push Token DB 관리 (등록/갱신/삭제)
  • Expo Push Notification API 호출
  • 멀티 디바이스 푸시 알림 발송

서비스 정보

  • Port: 8085
  • Database: MySQL (Push Token 관리)
  • Kafka: 이벤트 구독
  • 의존성: Spring Web, Spring Data JPA, Spring Kafka, Validation, Lombok

Entity

PushToken

@Entity
@Table(name = "push_tokens")
public class PushToken extends BaseEntity {
    private String userId;           // 사용자 ID
    private String deviceId;         // 디바이스 ID (iPhone12, Galaxy21 등)
    private String expoPushToken;    // Expo Push Token
    // createdAt, updatedAt (BaseEntity 상속)
}

Unique 제약조건: (userId, deviceId) 조합

특징:

  • 한 사용자가 여러 디바이스 등록 가능
  • 디바이스별 토큰 갱신 가능

REST API

POST /api/push-tokens

Push Token 등록 또는 갱신 (Upsert 패턴)

Request:

{
  "userId": "user-uuid",
  "deviceId": "iPhone12_ABC123",
  "expoPushToken": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"
}

Response (201 Created):

{
  "businessId": "uuid",
  "userId": "user-uuid",
  "deviceId": "iPhone12_ABC123",
  "expoPushToken": "ExponentPushToken[xxxx]",
  "createdAt": "2025-11-24T10:00:00",
  "updatedAt": "2025-11-24T10:00:00"
}

로직:

  • 동일한 (userId, deviceId)가 존재하면 → 토큰만 업데이트
  • 존재하지 않으면 → 새로 등록

DELETE /api/push-tokens/{deviceId}

디바이스 ID로 Push Token 삭제 (로그아웃, 앱 삭제 시)

Response: 204 No Content


Kafka 이벤트

구독 이벤트

alarm.triggered (Phase 2)

record AlarmTriggered(
    // 기본 정보
    String userId,              // 사용자 ID
    String alarmTime,           // 계산된 알람 시간 (HH:mm, 예: "06:45")

    // 날씨 정보 (nullable - 타임아웃 시 null)
    String temperature,         // 기온 (℃, 예: "15")
    String weatherCondition,    // 날씨 상태 ("맑음", "흐림", "비", "눈", "눈 또는 비")

    // 버스 정보 (nullable - 타임아웃 시 null)
    String busRouteAbrv,        // 버스 노선명 (예: "3375")
    String stNm,                // 정류장명 (예: "역삼역 앞 정류장")
    Integer busArrivalMinutes,  // 버스 도착까지 남은 시간 (분, 예: 45)

    // 사용자 설정 정보 (항상 존재)
    Integer preparationTime,    // 준비 시간 (분, 예: 10)
    Integer walkingTime,        // 도보 시간 (분, 예: 5)
    String desiredBoardingTime  // 탑승 희망 시간 (HH:mm, 예: "08:00")
) {}

발행자: alarm-creation-service

처리 로직:

  1. 이벤트 수신
  2. 날씨/버스 정보 기반 상세 멘트 생성
  3. DB에서 userId로 모든 Push Token 조회
  4. 모든 디바이스에 푸시 발송

알람 멘트 생성 로직

TTS 최적화 메시지

모든 정보 있을 때 (비 오는 날):

현재 시각 오전 6시 45분입니다. 오늘 기온은 약 15도 오전 날씨는 비입니다. 우산을 챙기세요. 역삼역 앞 정류장에 3375번 버스는 약 45분 뒤 도착 예정입니다.

맑은 날:

현재 시각 오전 6시 45분입니다. 오늘 기온은 약 20도 오전 날씨는 맑음입니다. 역삼역 앞 정류장에 3375번 버스는 약 45분 뒤 도착 예정입니다.

날씨만 없을 때:

현재 시각 오전 6시 45분입니다. 역삼역 앞 정류장에 3375번 버스는 약 45분 뒤 도착 예정입니다.

버스만 없을 때:

현재 시각 오전 6시 45분입니다. 오늘 기온은 약 15도 오전 날씨는 비입니다. 우산을 챙기세요.

둘 다 없을 때:

현재 시각 오전 6시 45분입니다.

메시지 생성 규칙

  1. 시간 포맷: "06:45" → "오전 6시 45분" (TTS 최적화)
  2. 날씨 조언:
    • 비/눈/눈 또는 비 → "우산을 챙기세요" 추가
    • 맑음/흐림 → 추가 멘트 없음
  3. 버스 표기: "3375번 버스", "약 45분 뒤"
  4. Null 처리: 정보가 없으면 해당 부분 생략

Expo Push Notification 연동

Expo Push API

  • Endpoint: https://exp.host/--/api/v2/push/send
  • Method: POST
  • Content-Type: application/json

요청 페이로드 예시

{
  "to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
  "sound": "default",
  "title": "Dynaram 알람",
  "body": "현재 시각 오전 6시 45분입니다. 오늘 기온은 약 15도...",
  "data": {
    "userId": "user-uuid",
    "alarmTime": "06:45"
  }
}

멀티 디바이스 발송 로직

List<PushToken> tokens = pushTokenRepository.findByUserId(userId);
for (PushToken token : tokens) {
    expoPushClient.sendPushNotification(
        token.getExpoPushToken(),
        userId,
        message,
        alarmTime
    );
}

참고: 실제 알람 소리(TTS + 알람음)는 Frontend에서 재생합니다.


Push Token 관리

프론트엔드 통합 시나리오

1. 앱 실행 시 (자동):

const expoPushToken = await Notifications.getExpoPushTokenAsync();
await fetch('http://localhost:8085/api/push-tokens', {
  method: 'POST',
  body: JSON.stringify({
    userId: currentUserId,
    deviceId: getDeviceId(),
    expoPushToken: expoPushToken.data
  })
});

2. 로그아웃 시:

await fetch(`http://localhost:8085/api/push-tokens/${deviceId}`, {
  method: 'DELETE'
});

멀티 디바이스 예시

push_tokens 테이블:
+----+---------+------------+---------------------------+
| id | user_id | device_id  | expo_push_token           |
+----+---------+------------+---------------------------+
| 1  | user123 | iPhone12   | ExponentPushToken[xxxx]   |
| 2  | user123 | Galaxy21   | ExponentPushToken[yyyy]   |
+----+---------+------------+---------------------------+

user123이 알람을 설정하면 아이폰과 갤럭시 모두에서 알람이 울림


기술 스택

  • Spring Boot 3.x
  • Spring Web (REST API, Expo Push API 호출)
  • Spring Data JPA (Push Token 관리)
  • MySQL (push_tokens 테이블)
  • Spring Kafka (이벤트 구독)
  • Validation (DTO 검증)
  • Lombok

구현 체크리스트

Phase 1 ✅ 완료

  • build.gradle 의존성 추가 (Web, Kafka)
  • application.yml 설정 (Kafka, Expo Push Token 하드코딩)
  • 이벤트 스키마 작성 (AlarmTriggered)
  • Kafka Consumer 구현 (alarm.triggered 구독)
  • 알람 멘트 생성 서비스 (AlarmMessageGenerator)
  • Expo Push Notification 클라이언트 구현 (ExpoPushClient)
  • RestTemplate Bean 설정
  • NotificationService 전체 로직 연결
  • 빌드 및 실행 테스트 완료

Phase 2 ✅ 완료

  • MySQL 의존성 추가 (JPA, MySQL Connector)
  • application.yml MySQL 설정 추가
  • BaseEntity 작성 (alarm-setting-service 패턴 일치)
  • PushToken Entity 작성
  • PushTokenRepository 작성
  • REST API 구현
    • POST /api/push-tokens (등록/갱신)
    • DELETE /api/push-tokens/{deviceId} (삭제)
    • DTO 작성 (PushTokenRequest, PushTokenResponse)
    • PushTokenService 작성
    • PushTokenController 작성
  • AlarmTriggered 이벤트 스키마 Phase 2 확장 (10개 필드)
  • AlarmMessageGenerator Phase 2 구현
    • 시간 포맷팅 (TTS 최적화)
    • 날씨 정보 포맷팅
    • 버스 정보 포맷팅
    • Null 처리
  • NotificationService DB 기반 조회로 변경
  • ExpoPushClient 멀티 디바이스 지원
  • 문서화 (README.md 업데이트)

실행 방법

# MySQL 실행 (Docker)
docker-compose up -d mysql

# 서비스 실행
./gradlew :notification-service:bootRun

# 또는
cd notification-service
./gradlew bootRun

테스트 시나리오

Push Token 등록

curl -X POST http://localhost:8085/api/push-tokens \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user-uuid",
    "deviceId": "iPhone12",
    "expoPushToken": "ExponentPushToken[xxxxxx]"
  }'

alarm.triggered 이벤트 발행 (Kafka)

{
  "userId": "user-uuid",
  "alarmTime": "06:45",
  "temperature": "15",
  "weatherCondition": "",
  "busRouteAbrv": "3375",
  "stNm": "역삼역 앞 정류장",
  "busArrivalMinutes": 45,
  "preparationTime": 10,
  "walkingTime": 5,
  "desiredBoardingTime": "08:00"
}

→ 결과: user-uuid의 모든 디바이스에 상세 알람 푸시 발송


알려진 문제점

⚠️ 알람 스케줄링 미구현 (테스트 목적)

현재 동작:

  • alarm.triggered 이벤트를 받으면 즉시 푸시 알림 발송
  • 알림 메시지는 alarmTime 기준으로 생성되어 미래 시각 표시

문제 상황:

테스트 시나리오:
- 현재 실제 시각: 20:42
- 이벤트 수신: 20:43
- 계산된 알람 시간 (alarmTime): 21:06
- 버스 도착 예정 (alarmTime 기준): 21:16 (10분 후)

실제 발생:
→ 20:43에 즉시 알림 발송 (21:06에 발송되어야 함)
→ 메시지: "현재 시각 21시 06분입니다. 버스 도착까지 약 10분 남았습니다"
   (실제로는 20:43인데 21:06이라고 표시됨)

기대 동작:

  • alarmTime (21:06)까지 대기 후 푸시 알림 발송
  • 발송 시점에 실제 현재 시각과 메시지 내용이 일치

현재 구현 이유:

  • 이벤트 수신 즉시 알림이 발송되는지 테스트 목적
  • 전체 이벤트 플로우 검증용 (alarm-creation → notification)

해결 방안:

  • Spring @Scheduled 또는 Quartz Scheduler를 사용한 알람 스케줄링
  • alarm.triggered 이벤트 수신 시:
    1. 알람 정보를 DB에 저장
    2. alarmTime까지 대기하도록 스케줄러 등록
    3. 정확한 시간에 푸시 알림 발송

현재 상태:

  • MVP 테스트 환경에서 즉시 발송 방식으로 구현
  • 프로덕션 배포 전 스케줄링 기능 구현 필요

About

푸시 알림 및 TTS 알람 메시지 발송을 처리하는 알림 서비스

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages