11package com .moongeul .backend .api .notification .service ;
22
3- import com .google .firebase .messaging .*;
43import com .moongeul .backend .api .notification .dto .DeviceTokenRequestDTO ;
4+ import com .moongeul .backend .api .notification .dto .ExpoPushRequestDTO ;
55import com .moongeul .backend .api .notification .entity .DeviceToken ;
66import com .moongeul .backend .api .notification .entity .NotificationType ;
77import com .moongeul .backend .api .notification .entity .Notifications ;
1515import lombok .extern .slf4j .Slf4j ;
1616import org .springframework .stereotype .Service ;
1717import org .springframework .transaction .annotation .Transactional ;
18+ import org .springframework .web .reactive .function .client .WebClient ;
1819
20+ import java .util .HashMap ;
1921import java .util .List ;
22+ import java .util .Map ;
23+ import java .util .stream .Collectors ;
2024
2125@ Service
2226@ Slf4j
@@ -27,25 +31,38 @@ public class NotificationService {
2731 private final MemberRepository memberRepository ;
2832 private final NotificationRepository notificationRepository ;
2933
34+ private final WebClient webClient ;
35+ private static final String EXPO_PUSH_URL = "https://exp.host/--/api/v2/push/send" ;
36+
3037 /* ํ ํฐ ๋ฑ๋ก/์์ */
3138 @ Transactional
32- public void registerOrUpdateToken (String email , DeviceTokenRequestDTO requestDTO ) {
39+ public void registerOrUpdateToken (String email , DeviceTokenRequestDTO deviceTokenRequestDTO ) {
3340 Member member = memberRepository .findByEmail (email )
3441 .orElseThrow (() -> new NotFoundException (ErrorStatus .USER_NOTFOUND_EXCEPTION .getMessage ()));
3542
36- deviceTokenRepository .findByToken (requestDTO .getToken ())
43+ // 1. ๊ธฐ์กด์ ํด๋น ํ ํฐ์ด ์๋์ง ํ์ธ
44+ deviceTokenRepository .findByToken (deviceTokenRequestDTO .getToken ())
3745 .ifPresentOrElse (
38- token -> token .updateMember (member ),
39- () -> deviceTokenRepository .save (DeviceToken .builder ()
40- .member (member )
41- .token (requestDTO .getToken ())
42- .platform (requestDTO .getPlatform ())
43- .build ())
46+ // 2. ์ด๋ฏธ ์๋ค๋ฉด: ํ ํฐ์ ์ฃผ์ธ์ด ๋ฐ๋์์ ์ ์์ผ๋ฏ๋ก ์
๋ฐ์ดํธ
47+ existingToken -> {
48+ existingToken .updateMember (member );
49+ },
50+ // 3. ์๋ค๋ฉด: ์๋ก์ด DeviceToken ์์ฑ ๋ฐ ์ ์ฅ
51+ () -> {
52+ DeviceToken newToken = DeviceToken .builder ()
53+ .member (member )
54+ .token (deviceTokenRequestDTO .getToken ())
55+ .platform (deviceTokenRequestDTO .getPlatform ())
56+ .build ();
57+ deviceTokenRepository .save (newToken );
58+ }
4459 );
4560 }
4661
62+ /* ์๋ฆผ ์ ์ก */
4763 @ Transactional
4864 public void send (Member receiver , Member actor , NotificationType notificationType , String message , Long relatedId ) {
65+ // 1. ์๋ฆผ ๋ด์ญ DB ์ ์ฅ
4966 Notifications notifications = Notifications .builder ()
5067 .receiver (receiver )
5168 .actor (actor )
@@ -56,37 +73,52 @@ public void send(Member receiver, Member actor, NotificationType notificationTyp
5673 .build ();
5774 notificationRepository .save (notifications );
5875
59- List <DeviceToken > tokens = deviceTokenRepository .findAllByMemberId (receiver .getId ());
76+ // 2. ์์ ์์ ๋ชจ๋ ๋๋ฐ์ด์ค ํ ํฐ ์กฐํ
77+ List <String > tokenStrings = deviceTokenRepository .findAllByMemberId (receiver .getId ())
78+ .stream ()
79+ .map (DeviceToken ::getToken )
80+ .collect (Collectors .toList ());
81+
82+ // Expo ์ ์ก์ฉ ์ถ๊ฐ ๋ฐ์ดํฐ ๊ตฌ์ฑ
83+ Map <String , Object > pushData = new HashMap <>();
84+ pushData .put ("type" , notificationType );
85+ pushData .put ("id" , relatedId );
6086
61- if (!tokens .isEmpty ()) {
62- for (DeviceToken deviceToken : tokens ) {
63- try {
64- sendMessage (deviceToken .getToken (), notificationType .getKey (), message );
65- } catch (FirebaseMessagingException e ) {
66- if (e .getMessagingErrorCode () == MessagingErrorCode .UNREGISTERED ) {
67- log .warn ("์ ํจํ์ง ์์ ํ ํฐ ์ญ์ : {}" , deviceToken .getToken ());
68- deviceTokenRepository .delete (deviceToken );
69- } else {
70- log .error ("FCM ์๋ฌ ๋ฐ์: {}" , e .getMessage ());
71- }
72- } catch (Exception e ) {
73- log .error ("์ผ๋ฐ ์ ์ก ์๋ฌ: {}" , e .getMessage ());
74- }
75- }
87+ if (!tokenStrings .isEmpty ()) {
88+ // 3. WebClient๋ก ๋น๋๊ธฐ ์ ์ก
89+ sendToExpo (tokenStrings , notificationType .getKey (), message , pushData );
7690 }
7791 }
7892
79- /* ํ ํฐ์ ๊ฐ์ง ๊ธฐ๊ธฐ์ ํธ์ ์๋ฆผ ์ ์ก */
80- public void sendMessage (String targetToken , String title , String body ) throws FirebaseMessagingException {
81- Message message = Message .builder ()
82- .setToken (targetToken )
83- .setNotification (Notification .builder ()
84- .setTitle (title ) // ์๋ฆผ ํ์
85- .setBody (body ) // ์๋ฆผ ๋ด์ฉ
86- .build ())
93+ /* Expo Push API ํธ์ถ */
94+ private void sendToExpo (List <String > targetTokens , String title , String body , Map <String , Object > pushData ) {
95+ // ํ์ด๋ก๋ ๊ตฌ์ฑ
96+ ExpoPushRequestDTO requestPayload = ExpoPushRequestDTO .builder ()
97+ .to (targetTokens )
98+ .title (title )
99+ .body (body )
100+ .sound ("default" )
101+ .pushData (pushData )
87102 .build ();
88103
89- String response = FirebaseMessaging .getInstance ().send (message ); // ์ฌ๊ธฐ์ ๋ฐ์ํ๋ ์๋ฌ๊ฐ ์์ชฝ(send ๋ฉ์๋)์ผ๋ก ์ ๋ฌ
90- log .info ("FCM ์ ์ก ์ฑ๊ณต: " + response );
104+ webClient .post ()
105+ .uri (EXPO_PUSH_URL )
106+ .bodyValue (requestPayload )
107+ .retrieve ()
108+ .bodyToMono (Map .class ) // ์๋ต์ Map ํํ๋ก ๋ฐ์
109+ .subscribe (
110+ response -> log .info ("Expo ํธ์ ์ ์ก ์ฑ๊ณต: {}" , response ),
111+ error -> log .error ("Expo ํธ์ ์ ์ก ์คํจ: {}" , error .getMessage ())
112+ );
113+ }
114+
115+ /* ๋ก๊ทธ์์ ์, ํ ํฐ ์ญ์ */
116+ @ Transactional
117+ public void removeDeviceToken (String token ) {
118+ // ํ ํฐ์ด ์กด์ฌํ ๊ฒฝ์ฐ์๋ง ์ญ์ ์งํ
119+ deviceTokenRepository .findByToken (token ).ifPresent (deviceToken -> {
120+ deviceTokenRepository .delete (deviceToken );
121+ log .info ("๋ก๊ทธ์์์ผ๋ก ์ธํ ๋๋ฐ์ด์ค ํ ํฐ ์ญ์ ์๋ฃ: {}" , token );
122+ });
91123 }
92124}
0 commit comments