From f5e2d3630cbe95a192222b9456fcd2f7cd94cb61 Mon Sep 17 00:00:00 2001 From: leedy5521 Date: Sun, 26 Apr 2026 21:55:31 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20isAbleToSendNotification=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=B9=84=ED=9A=A8=EC=9C=A8=20=EC=B5=9C?= =?UTF-8?q?=EC=86=8C=ED=99=94,=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CodiveNotificationServiceImpl.java | 52 +++++++------------ 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java b/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java index c303e6e0..fe70f136 100644 --- a/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java +++ b/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java @@ -6,12 +6,9 @@ import com.google.firebase.messaging.Notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.clokey.comment.entitiy.Comment; import org.clokey.domain.comment.event.NewCommentEvent; import org.clokey.domain.comment.event.NewReplyEvent; -import org.clokey.domain.comment.exception.CommentErrorCode; import org.clokey.domain.comment.repository.CommentRepository; -import org.clokey.domain.history.exception.HistoryErrorCode; import org.clokey.domain.history.repository.HistoryRepository; import org.clokey.domain.like.event.NewLikeEvent; import org.clokey.domain.member.exception.MemberErrorCode; @@ -22,11 +19,9 @@ import org.clokey.domain.notification.exception.NotificationErrorCode; import org.clokey.domain.notification.repository.CodiveNotificationRepository; import org.clokey.domain.term.enums.TermInfo; -import org.clokey.domain.term.exception.TermErrorCode; import org.clokey.domain.term.repository.MemberTermRepository; import org.clokey.exception.BaseCustomException; import org.clokey.global.util.MemberUtil; -import org.clokey.history.entity.History; import org.clokey.member.entity.Member; import org.clokey.member.enums.MemberStatus; import org.clokey.notification.entity.CodiveNotification; @@ -34,6 +29,7 @@ import org.clokey.notification.enums.ReadStatus; import org.clokey.notification.enums.RedirectType; import org.clokey.response.SliceResponse; +import org.clokey.term.entity.MemberTerm; import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -327,18 +323,6 @@ private Member getMemberById(Long memberId) { .orElseThrow(() -> new BaseCustomException(MemberErrorCode.MEMBER_NOT_FOUND)); } - private History getHistoryById(Long historyId) { - return historyRepository - .findById(historyId) - .orElseThrow(() -> new BaseCustomException(HistoryErrorCode.HISTORY_NOT_FOUND)); - } - - private Comment getCommentById(Long commentId) { - return commentRepository - .findById(commentId) - .orElseThrow(() -> new BaseCustomException(CommentErrorCode.COMMENT_NOT_FOUND)); - } - private CodiveNotification getNotificationById(Long notificationId) { return codiveNotificationRepository .findById(notificationId) @@ -348,22 +332,19 @@ private CodiveNotification getNotificationById(Long notificationId) { NotificationErrorCode.NOTIFICATION_NOT_FOUND)); } - private boolean isAbleToSendNotification(Member followToMember) { - - boolean isActive = (followToMember.getMemberStatus() == MemberStatus.ACTIVE); - - boolean hasDeviceToken = - (followToMember.getDeviceToken() != null - && !followToMember.getDeviceToken().isBlank()); + private boolean isAbleToSendNotification(Member member) { + if (member.getMemberStatus() != MemberStatus.ACTIVE) { + return false; + } - boolean hasAgreed = - memberTermRepository - .findByMemberIdAndTermId( - followToMember.getId(), TermInfo.PUSH_NOTIFICATION_RECEIVE.getId()) - .orElseThrow(() -> new BaseCustomException(TermErrorCode.TERM_NOT_FOUND)) - .isAgreed(); + if (member.getDeviceToken() == null || member.getDeviceToken().isBlank()) { + return false; + } - return isActive && hasDeviceToken && hasAgreed; + return memberTermRepository + .findByMemberIdAndTermId(member.getId(), TermInfo.PUSH_NOTIFICATION_RECEIVE.getId()) + .map(MemberTerm::isAgreed) + .orElse(false); } @Async @@ -373,8 +354,13 @@ public void sendPushAfterCommit(PushSendEvent event) { try { firebaseMessaging.send(event.message()); } catch (FirebaseMessagingException e) { - log.warn("[Notification] Firebase 전송 실패", e); - throw new BaseCustomException(NotificationErrorCode.NOTIFICATION_FIREBASE_ERROR); + log.warn( + "[Notification] Firebase 전송 실패: errorCode={}, message={}", + e.getMessagingErrorCode(), + e.getMessage(), + e); + } catch (Exception e) { + log.error("[Notification] Firebase 전송 중 예기치 않은 오류 발생", e); } } From 08207325211922c4c08ecf187b1612439bf6f923 Mon Sep 17 00:00:00 2001 From: leedy5521 Date: Fri, 1 May 2026 02:28:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=EC=9D=BC=EB=B0=98=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20/=20=EC=98=A8=EB=8F=84=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EA=B8=B0=EB=B3=B8=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80URL=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev-cd.yml | 2 + .github/workflows/prod-cd.yml | 2 + clokey-api/dev-compose.yml | 2 + clokey-api/prod-compose.yml | 2 + .../CodiveNotificationServiceImpl.java | 74 ++++++++++++------- .../entity/CodiveNotification.java | 5 +- 6 files changed, 60 insertions(+), 27 deletions(-) diff --git a/.github/workflows/dev-cd.yml b/.github/workflows/dev-cd.yml index 10f3bc9d..60c18c19 100644 --- a/.github/workflows/dev-cd.yml +++ b/.github/workflows/dev-cd.yml @@ -107,6 +107,8 @@ jobs: export CLOTH_INFERENCE_PATH=${{ secrets.CLOTH_INFERENCE_PATH }} export STYLE_INFERENCE_PATH=${{ secrets.STYLE_INFERENCE_PATH }} export CLOTH_DETECT_PATH=${{ secrets.CLOTH_DETECT_PATH }} + export DEFAULT_PROFILE_IMAGE_URL=${{ secrets.DEFAULT_PROFILE_IMAGE_URL }} + export TODAY_TEMPERATURE_IMAGE_URL=${{ secrets.TODAY_TEMPERATURE_IMAGE_URL }} sudo mkdir -p /home/ubuntu/secrets echo "${{ secrets.FIREBASE_SA_JSON_B64 }}" | base64 -d | sudo tee /home/ubuntu/secrets/firebase-sa.json > /dev/null diff --git a/.github/workflows/prod-cd.yml b/.github/workflows/prod-cd.yml index 495b97a3..20b85f26 100644 --- a/.github/workflows/prod-cd.yml +++ b/.github/workflows/prod-cd.yml @@ -117,6 +117,8 @@ jobs: export CLOTH_INFERENCE_PATH=${{ secrets.CLOTH_INFERENCE_PATH }} export STYLE_INFERENCE_PATH=${{ secrets.STYLE_INFERENCE_PATH }} export CLOTH_DETECT_PATH=${{ secrets.CLOTH_DETECT_PATH }} + export DEFAULT_PROFILE_IMAGE_URL=${{ secrets.DEFAULT_PROFILE_IMAGE_URL }} + export TODAY_TEMPERATURE_IMAGE_URL=${{ secrets.TODAY_TEMPERATURE_IMAGE_URL }} sudo mkdir -p /home/ubuntu/secrets echo "${{ secrets.FIREBASE_SA_JSON_B64 }}" | base64 -d | sudo tee /home/ubuntu/secrets/firebase-sa.json > /dev/null diff --git a/clokey-api/dev-compose.yml b/clokey-api/dev-compose.yml index 46bc5775..220f6bac 100644 --- a/clokey-api/dev-compose.yml +++ b/clokey-api/dev-compose.yml @@ -35,6 +35,8 @@ services: # Firebase FIREBASE_CREDENTIALS_PATH: /run/secrets/firebase-sa.json + DEFAULT_PROFILE_IMAGE_URL: ${DEFAULT_PROFILE_IMAGE_URL} + TODAY_TEMPERATURE_IMAGE_URL: ${TODAY_TEMPERATURE_IMAGE_URL} # JWT JWT_ACCESS_TOKEN_SECRET: ${JWT_ACCESS_TOKEN_SECRET} diff --git a/clokey-api/prod-compose.yml b/clokey-api/prod-compose.yml index 5b7185de..9d397826 100644 --- a/clokey-api/prod-compose.yml +++ b/clokey-api/prod-compose.yml @@ -35,6 +35,8 @@ services: # Firebase FIREBASE_CREDENTIALS_PATH: /run/secrets/firebase-sa.json + DEFAULT_PROFILE_IMAGE_URL: ${DEFAULT_PROFILE_IMAGE_URL} + TODAY_TEMPERATURE_IMAGE_URL: ${TODAY_TEMPERATURE_IMAGE_URL} # JWT JWT_ACCESS_TOKEN_SECRET: ${JWT_ACCESS_TOKEN_SECRET} diff --git a/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java b/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java index fe70f136..135ecde5 100644 --- a/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java +++ b/clokey-api/src/main/java/org/clokey/domain/notification/service/CodiveNotificationServiceImpl.java @@ -8,8 +8,6 @@ import lombok.extern.slf4j.Slf4j; import org.clokey.domain.comment.event.NewCommentEvent; import org.clokey.domain.comment.event.NewReplyEvent; -import org.clokey.domain.comment.repository.CommentRepository; -import org.clokey.domain.history.repository.HistoryRepository; import org.clokey.domain.like.event.NewLikeEvent; import org.clokey.domain.member.exception.MemberErrorCode; import org.clokey.domain.member.repository.MemberRepository; @@ -30,6 +28,7 @@ import org.clokey.notification.enums.RedirectType; import org.clokey.response.SliceResponse; import org.clokey.term.entity.MemberTerm; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -47,8 +46,6 @@ public class CodiveNotificationServiceImpl implements CodiveNotificationService private final MemberRepository memberRepository; private final CodiveNotificationRepository codiveNotificationRepository; private final MemberTermRepository memberTermRepository; - private final HistoryRepository historyRepository; - private final CommentRepository commentRepository; private final FirebaseMessaging firebaseMessaging; private final ApplicationEventPublisher eventPublisher; @@ -62,7 +59,12 @@ public class CodiveNotificationServiceImpl implements CodiveNotificationService private static final String TODAY_TEMPERATURE_NOTIFICATION = "오늘의 기온은 %d도 입니다!\n날씨에 맞는 오늘의 옷차림이 기다리고 있어요👀"; - private static final String TODAY_TEMPERATURE_IMAGE_URL = "https://example.com/temperature.png"; + + @Value("${DEFAULT_PROFILE_IMAGE_URL:}") + private String configuredDefaultProfileImageUrl; + + @Value("${TODAY_TEMPERATURE_IMAGE_URL:}") + private String configuredTemperatureImageUrl; @Override @Transactional @@ -73,10 +75,10 @@ public void sendNewFollowerNotification(Long followFromId, Long followToId) { if (isAbleToSendNotification(followToMember)) { String content = String.format(NEW_FOLLOWER_NOTIFICATION, followFromMember.getNickname()); - String profileImageUrl = followFromMember.getProfileImageUrl(); + String profileImageUrl = + resolveNotificationImageUrl(followFromMember.getProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() @@ -111,10 +113,10 @@ public void sendNewPendingFollowerNotification(Long followFromId, Long followToI String content = String.format( NEW_PENDING_FOLLOWER_NOTIFICATION, followFromMember.getNickname()); - String profileImageUrl = followFromMember.getProfileImageUrl(); + String profileImageUrl = + resolveNotificationImageUrl(followFromMember.getProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() @@ -151,10 +153,9 @@ public void sendNewCommentNotification(NewCommentEvent event) { NEW_COMMENT_NOTIFICATION, event.commenterNickname(), event.commentContent()); - String profileImageUrl = event.commenterProfileImageUrl(); + String profileImageUrl = resolveNotificationImageUrl(event.commenterProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() @@ -187,10 +188,9 @@ public void sendNewReplyNotification(NewReplyEvent event) { String content = String.format( NEW_REPLY_NOTIFICATION, event.replierNickname(), event.replyContent()); - String profileImageUrl = event.replierProfileImageUrl(); + String profileImageUrl = resolveNotificationImageUrl(event.replierProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() .setToken(receiver.getDeviceToken()) @@ -221,10 +221,9 @@ public void sendNewLikeNotification(NewLikeEvent event) { if (isAbleToSendNotification(receiver)) { String content = String.format(NEW_LIKE_NOTIFICATION, event.likerNickname()); - String profileImageUrl = event.likerProfileImageUrl(); + String profileImageUrl = resolveNotificationImageUrl(event.likerProfileImageUrl()); - Notification notification = - Notification.builder().setBody(content).setImage(profileImageUrl).build(); + Notification notification = createPushNotification(content, profileImageUrl); Message message = Message.builder() .setToken(receiver.getDeviceToken()) @@ -253,13 +252,10 @@ public void sendNewTemperatureNotification(TemperatureNotificationRequest reques Member receiver = memberUtil.getCurrentMember(); String content = String.format(TODAY_TEMPERATURE_NOTIFICATION, Math.round(request.temperature())); + String temperatureImageUrl = configuredTemperatureImageUrl; if (isAbleToSendNotification(receiver)) { - Notification notification = - Notification.builder() - .setBody(content) - .setImage(TODAY_TEMPERATURE_IMAGE_URL) - .build(); + Notification notification = createPushNotification(content, temperatureImageUrl); Message message = Message.builder() .setToken(receiver.getDeviceToken()) @@ -270,7 +266,7 @@ public void sendNewTemperatureNotification(TemperatureNotificationRequest reques CodiveNotification.createCodiveNotification( receiver, content, - TODAY_TEMPERATURE_IMAGE_URL, + temperatureImageUrl, "", RedirectType.NONE, NotificationType.TEMPERATURE_DAILY); @@ -347,6 +343,32 @@ private boolean isAbleToSendNotification(Member member) { .orElse(false); } + private String resolveNotificationImageUrl(String profileImageUrl) { + if (profileImageUrl != null && !profileImageUrl.isBlank()) { + return profileImageUrl; + } + + return resolveDefaultImageUrl(); + } + + private String resolveDefaultImageUrl() { + if (configuredDefaultProfileImageUrl != null + && !configuredDefaultProfileImageUrl.isBlank()) { + return configuredDefaultProfileImageUrl; + } + + return ""; + } + + private Notification createPushNotification(String content, String imageUrl) { + Notification.Builder notificationBuilder = Notification.builder().setBody(content); + if (imageUrl != null && !imageUrl.isBlank()) { + notificationBuilder.setImage(imageUrl); + } + + return notificationBuilder.build(); + } + @Async @Transactional(propagation = Propagation.NOT_SUPPORTED) @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) diff --git a/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java b/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java index e81b2db5..7adfda3e 100644 --- a/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java +++ b/clokey-domain/src/main/java/org/clokey/notification/entity/CodiveNotification.java @@ -69,10 +69,13 @@ public static CodiveNotification createCodiveNotification( String redirectInfo, RedirectType redirectType, NotificationType notificationType) { + String resolvedNotificationImageUrl = + (notificationImageUrl == null) ? "" : notificationImageUrl; + return CodiveNotification.builder() .member(member) .content(content) - .notificationImageUrl(notificationImageUrl) + .notificationImageUrl(resolvedNotificationImageUrl) .redirectInfo(redirectInfo) .redirectType(redirectType) .notificationType(notificationType)