From 4cf8bf6114d58f9b0b641b6e74a057c6bfb8e374 Mon Sep 17 00:00:00 2001 From: Kim So Yeon Date: Tue, 14 Jan 2025 00:14:56 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:bug:=20fix:=20=EC=95=8C=EB=9E=8C=20fcmToke?= =?UTF-8?q?n=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AlarmService/AlarmServiceImpl.java | 134 ++++++++++-------- 1 file changed, 75 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java b/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java index 136a961..99a22f1 100644 --- a/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java +++ b/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java @@ -1,6 +1,6 @@ package com.pricewagon.pricewagon.domain.alarm.service.AlarmService; -import java.util.Iterator; +import java.util.ArrayList; import java.util.List; import org.springframework.scheduling.annotation.Scheduled; @@ -39,101 +39,101 @@ public class AlarmServiceImpl implements AlarmService { private final FirebaseMessaging firebaseMessaging; @Override - @Scheduled(fixedRate = 120000) // Every 1 minute + @Transactional + @Scheduled(fixedRate = 120000) public void checkAllAlarmsAndNotify() { List activeAlarms = alarmRepository.findActiveAlarmsWithDetails(AlarmStatus.ACTIVE); for (Alarm alarm : activeAlarms) { - Product product = alarm.getProduct(); - User user = alarm.getUser(); - - if (productService.isPriceBelowDesired(alarm)) { - String messageBody = - product.getName() + "의 가격이 " + product.getCurrentPrice() + "원 이하로 떨어졌습니다."; - boolean notificationSent = sendNotification(user, messageBody); - if (notificationSent) { - updateAlarmStatus(alarm.getId(), AlarmStatus.INACTIVE, AlarmStatus.ACTIVE); - log.info("알림이 성공적으로 전송 후 비활성화로 저장하였습니다." + user.getAccount()); - } + try { + processAlarm(alarm); + } catch (Exception e) { + log.error("알람 처리 중 오류 발생: alarmId={}, error={}", alarm.getId(), e.getMessage()); } } } @Transactional - protected void updateAlarmStatus(Long id, AlarmStatus status, AlarmStatus currentStatus) { - alarmRepository.updateAlarmStatus(id, status, currentStatus); + protected void processAlarm(Alarm alarm) { + Product product = alarm.getProduct(); + User user = alarm.getUser(); + + if (productService.isPriceBelowDesired(alarm)) { + String messageBody = String.format("%s의 가격이 %d원 이하로 떨어졌습니다.", + product.getName(), product.getCurrentPrice()); + + boolean notificationSuccess = sendNotificationToUser(user, messageBody); + if (notificationSuccess) { + alarm.setStatus(AlarmStatus.INACTIVE); + alarmRepository.save(alarm); + log.info("알림 전송 성공 후 알람 비활성화: user={}", user.getAccount()); + } + } } @Transactional - protected boolean sendNotification(User user, String messageBody) { - - List fcmTokens = user.getFcmTokens(); - if (fcmTokens == null || fcmTokens.isEmpty()) { + protected boolean sendNotificationToUser(User user, String messageBody) { + List tokens = new ArrayList<>(user.getFcmTokens()); + if (tokens.isEmpty()) { log.warn("푸시 알림 전송 실패: FCM 토큰이 없습니다. 사용자: {}", user.getAccount()); return false; } - boolean allNotificationsSent = false; - - log.debug("푸시 알림 전송 시작: 사용자: {}", user.getAccount()); - - Iterator iterator = fcmTokens.iterator(); - while (iterator.hasNext()) { - FcmToken token = iterator.next(); - - Message message = Message.builder() - .setToken(token.getToken()) - .putData("title", "costFlower") - .putData("body", messageBody) - .build(); + List tokensToRemove = new ArrayList<>(); + boolean atLeastOneSuccess = false; + for (FcmToken token : tokens) { try { + Message message = Message.builder() + .setToken(token.getToken()) + .putData("title", "costFlower") + .putData("body", messageBody) + .build(); + firebaseMessaging.send(message); - allNotificationsSent = true; - log.info("푸시 알림이 성공적으로 전송되었습니다. 사용자: {}, FCM 토큰: {}", user.getAccount(), token.getToken()); + atLeastOneSuccess = true; + log.info("푸시 알림 전송 성공: user={}, token={}", user.getAccount(), token.getToken()); } catch (FirebaseMessagingException e) { - log.error("푸시 알림 전송 실패: 사용자: {}, FCM 토큰: {}, 이유: {}", user.getAccount(), token.getToken(), - e.getMessage()); + log.error("푸시 알림 전송 실패: user={}, token={}, error={}", + user.getAccount(), token.getToken(), e.getMessage()); - // UNREGISTERED 에러 처리: 무효화된 토큰 제거 if ("UNREGISTERED".equals(e.getMessagingErrorCode().name())) { - log.warn("유효하지 않은 FCM 토큰을 제거합니다. 사용자: {}, FCM 토큰: {}", user.getAccount(), token.getToken()); - iterator.remove(); - user.removeFcmToken(token); + tokensToRemove.add(token); + log.warn("유효하지 않은 FCM 토큰 발견: user={}, token={}", + user.getAccount(), token.getToken()); } - - allNotificationsSent = false; } } - if (!allNotificationsSent) { - userRepository.save(user); - log.info("FCM 토큰 변경사항이 사용자 데이터베이스에 저장되었습니다. 사용자: {}", user.getAccount()); + if (!tokensToRemove.isEmpty()) { + removeInvalidTokens(user, tokensToRemove); } - log.debug("푸시 알림 전송 완료: 사용자: {}, 결과: {}", user.getAccount(), allNotificationsSent); - return allNotificationsSent; + return atLeastOneSuccess; + } + + @Transactional + protected void removeInvalidTokens(User user, List tokensToRemove) { + tokensToRemove.forEach(token -> { + user.getFcmTokens().remove(token); + // token.setUser(null); // 이미 CascadeType.ALL과 orphanRemoval=true로 처리됨 + }); + userRepository.save(user); + log.info("유효하지 않은 FCM 토큰들 제거 완료: user={}, removedCount={}", + user.getAccount(), tokensToRemove.size()); } @Override + @Transactional public AlarmResponseDTO.registerAlarmDTO registerAlarm(AlarmRequestDTO.registerAlarm request, String username) { Product product = productRepository.findByProductNumber(request.getProductNumber()) .orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND)); + User user = userRepository.findByAccount(username) .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); - if (request.getFcmToken() != null && !request.getFcmToken().isEmpty()) { - boolean tokenExists = user.getFcmTokens().stream() - .anyMatch(token -> token.getToken().equals(request.getFcmToken())); - if (!tokenExists) { - FcmToken fcmToken = FcmToken.builder() - .token(request.getFcmToken()) - .user(user) - .build(); - user.addFcmToken(fcmToken); - userRepository.save(user); - log.info("새로운 FCM 토큰 저장: {}", request.getFcmToken()); - } + if (request.getFcmToken() != null && !request.getFcmToken().isEmpty()) { + registerFcmToken(user, request.getFcmToken()); } Alarm alarm = Alarm.builder() @@ -142,6 +142,7 @@ public AlarmResponseDTO.registerAlarmDTO registerAlarm(AlarmRequestDTO.registerA .desired_price(request.getPrice()) .status(AlarmStatus.ACTIVE) .build(); + alarmRepository.save(alarm); product.updateAlarmCount(product.getAlarmCount() + 1); @@ -152,4 +153,19 @@ public AlarmResponseDTO.registerAlarmDTO registerAlarm(AlarmRequestDTO.registerA .build(); } + @Transactional + protected void registerFcmToken(User user, String tokenString) { + boolean tokenExists = user.getFcmTokens().stream() + .anyMatch(token -> token.getToken().equals(tokenString)); + + if (!tokenExists) { + FcmToken fcmToken = FcmToken.builder() + .token(tokenString) + .user(user) + .build(); + user.addFcmToken(fcmToken); + userRepository.save(user); + log.info("새로운 FCM 토큰 등록: user={}, token={}", user.getAccount(), tokenString); + } + } } \ No newline at end of file From 9bce0893a4ab90041c39c8649a85ba6e0bc07986 Mon Sep 17 00:00:00 2001 From: Kim So Yeon Date: Tue, 14 Jan 2025 00:17:37 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:bug:=20fix:=20=EC=95=8C=EB=9E=8C=20fcmToke?= =?UTF-8?q?n=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/alarm/service/AlarmService/AlarmServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java b/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java index 99a22f1..d6a4bc7 100644 --- a/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java +++ b/src/main/java/com/pricewagon/pricewagon/domain/alarm/service/AlarmService/AlarmServiceImpl.java @@ -79,7 +79,7 @@ protected boolean sendNotificationToUser(User user, String messageBody) { return false; } - List tokensToRemove = new ArrayList<>(); + List tokensToRemove = new ArrayList<>(); // 별도의 리스트를 사용해서 삭제할 토큰 따로 저장 boolean atLeastOneSuccess = false; for (FcmToken token : tokens) { @@ -116,7 +116,7 @@ protected boolean sendNotificationToUser(User user, String messageBody) { protected void removeInvalidTokens(User user, List tokensToRemove) { tokensToRemove.forEach(token -> { user.getFcmTokens().remove(token); - // token.setUser(null); // 이미 CascadeType.ALL과 orphanRemoval=true로 처리됨 + }); userRepository.save(user); log.info("유효하지 않은 FCM 토큰들 제거 완료: user={}, removedCount={}",