diff --git a/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java b/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java index e3bb3d8..179b210 100644 --- a/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java +++ b/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java @@ -1,6 +1,7 @@ package org.terning.notification.application.writer; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.terning.message.domain.Message; @@ -20,6 +21,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class NotificationWriterImpl implements NotificationWriter { private static final String USERNAME = "username"; @@ -31,24 +33,72 @@ public class NotificationWriterImpl implements NotificationWriter { @Override @Transactional public void write(MessageTemplateType template, ScheduledTime sendTime) { + log.info("πŸ”” [NotificationWriter] μ•Œλ¦Ό 생성 μ‹œμž‘ - ν…œν”Œλ¦Ώ: {}", template); List targetUsers = fetchTargetUsers(template.targetType()); + log.info("πŸ‘₯ [NotificationWriter] λŒ€μƒ μœ μ € 수 (ν‘Έμ‹œ μˆ˜μ‹  ν—ˆμš© + 토큰 보유): {}", targetUsers.size()); +// List filteredUsers = targetUsers.stream() +// .filter(user -> user.getToken() != null) +// .filter(user -> user.getToken().value() != null) +// .filter(user -> !user.getToken().value().trim().isEmpty()) +// .toList(); List filteredUsers = targetUsers.stream() - .filter(user -> user.getToken() != null) - .filter(user -> user.getToken().value() != null) - .filter(user -> !user.getToken().value().trim().isEmpty()) + .filter(user -> { + boolean valid = user.getToken() != null && user.getToken().value() != null && !user.getToken().value().trim().isEmpty(); + if (!valid) { + log.warn("⚠️ [NotificationWriter] FCM 토큰 λˆ„λ½ - user(oUserId={}, name={})", user.getOUserId(), user.getName() != null ? user.getName().value() : "null"); + } + return valid; + }) .toList(); + log.info("βœ… [NotificationWriter] μ΅œμ’… μ•Œλ¦Ό λŒ€μƒ μœ μ € 수: {}", filteredUsers.size()); + List notifications = createNotifications(filteredUsers, template, sendTime); + log.info("πŸ’Ύ [NotificationWriter] μƒμ„±λœ μ•Œλ¦Ό 수: {}", notifications.size()); + saveNotifications(notifications); + log.info("πŸŽ‰ [NotificationWriter] μ•Œλ¦Ό μ €μž₯ μ™„λ£Œ"); } private List fetchTargetUsers(MessageTargetType targetType) { - List users = switch (targetType) { - case SCRAPPED_USER -> scrapRepository.findDistinctScrappedUsers(); - case ALL_USERS -> userRepository.findAll(); - default -> throw new NotificationException(NotificationErrorCode.INVALID_TARGET_TYPE); - }; + log.info("πŸ” [fetchTargetUsers] λŒ€μƒ νƒ€μž…: {}", targetType); +// List users = switch (targetType) { +// case SCRAPPED_USER -> scrapRepository.findDistinctScrappedUsers(); +// case ALL_USERS -> userRepository.findAll(); +// default -> throw new NotificationException(NotificationErrorCode.INVALID_TARGET_TYPE); +// }; +// return users.stream() +// .filter(User::canReceivePushNotification) +// .toList(); + List users; + + try { + users = switch (targetType) { + case SCRAPPED_USER -> { + List scrappedUsers = scrapRepository.findDistinctScrappedUsers(); + log.info("πŸ“¦ [fetchTargetUsers] 슀크랩 μœ μ € 수: {}", scrappedUsers.size()); + yield scrappedUsers; + } + case ALL_USERS -> { + List allUsers = userRepository.findAll(); + log.info("πŸ“¦ [fetchTargetUsers] 전체 μœ μ € 수: {}", allUsers.size()); + yield allUsers; + } + default -> throw new NotificationException(NotificationErrorCode.INVALID_TARGET_TYPE); + }; + } catch (Exception e) { + log.error("πŸ”₯ [fetchTargetUsers] μœ μ € 쑰회 쀑 μ˜ˆμ™Έ λ°œμƒ", e); + throw e; + } return users.stream() + .peek(user -> { + if (user.getPushStatus() == null) { + log.warn("⚠️ [fetchTargetUsers] μœ μ €(oUserId={})의 pushStatusκ°€ null", user.getOUserId()); + } + if (user.getToken() == null) { + log.warn("⚠️ [fetchTargetUsers] μœ μ €(oUserId={})의 FCM 토큰이 null", user.getOUserId()); + } + }) .filter(User::canReceivePushNotification) .toList(); } diff --git a/src/main/java/org/terning/scrap/application/ScrapSyncManager.java b/src/main/java/org/terning/scrap/application/ScrapSyncManager.java index 7f3e36d..5d439d2 100644 --- a/src/main/java/org/terning/scrap/application/ScrapSyncManager.java +++ b/src/main/java/org/terning/scrap/application/ScrapSyncManager.java @@ -18,15 +18,30 @@ public class ScrapSyncManager { private final UserRepository userRepository; private final ScrapRepository scrapRepository; - public void sync(List userIds) { - List distinctUserIds = userIds.stream().distinct().toList(); + public void sync(List oUserIds) { + List distinctOUserIds = oUserIds.stream().distinct().toList(); + Map usersByOUserId = userRepository.findUsersByOUserIds(distinctOUserIds); - Map usersById = userRepository.findUsersByIds(distinctUserIds); - Map scrapsByUserId = scrapRepository.findScrapsByUserIds(distinctUserIds).stream() + List internalUserIds = usersByOUserId.values().stream() + .map(User::getId) + .toList(); + + Map scrapsByUserId = scrapRepository.findScrapsByUserIds(internalUserIds).stream() .collect(Collectors.toMap(scrap -> scrap.getUser().getId(), Function.identity())); - List scrapsToPersist = distinctUserIds.stream() - .map(userId -> createOrUpdateScrap(userId, usersById, scrapsByUserId)) + List scrapsToPersist = distinctOUserIds.stream() + .map(oUserId -> { + User user = usersByOUserId.get(oUserId); + if (user == null) return null; + + Scrap existingScrap = scrapsByUserId.get(user.getId()); + if (existingScrap == null) return Scrap.of(user); + if (existingScrap.isUnscrapped()) { + existingScrap.scrap(); + return existingScrap; + } + return null; + }) .filter(Objects::nonNull) .toList(); @@ -34,27 +49,4 @@ public void sync(List userIds) { scrapRepository.saveAll(scrapsToPersist); } } - - private Scrap createOrUpdateScrap( - Long userId, - Map usersById, - Map scrapsByUserId - ) { - User user = usersById.get(userId); - if (user == null) { - return null; - } - - Scrap existingScrap = scrapsByUserId.get(userId); - if (existingScrap == null) { - return Scrap.of(user); - } - - if (existingScrap.isUnscrapped()) { - existingScrap.scrap(); - return existingScrap; - } - - return null; - } } \ No newline at end of file diff --git a/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java b/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java index 289ab24..5bad55c 100644 --- a/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java +++ b/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java @@ -31,7 +31,7 @@ public List findDistinctScrappedUsers() { return queryFactory .selectDistinct(scrap.user) .from(scrap) - .join(scrap.user, user).fetchJoin() + .join(scrap.user, user) .where(scrap.status.eq(ScrapStatus.SCRAPPED)) .fetch(); } diff --git a/src/main/java/org/terning/user/domain/UserRepositoryCustom.java b/src/main/java/org/terning/user/domain/UserRepositoryCustom.java index 64dd543..52b1cc8 100644 --- a/src/main/java/org/terning/user/domain/UserRepositoryCustom.java +++ b/src/main/java/org/terning/user/domain/UserRepositoryCustom.java @@ -7,6 +7,8 @@ public interface UserRepositoryCustom { Map findUsersByIds(List userIds); + Map findUsersByOUserIds(List oUserIds); + Optional findByOUserId(Long oUserId); } diff --git a/src/main/java/org/terning/user/domain/UserRepositoryImpl.java b/src/main/java/org/terning/user/domain/UserRepositoryImpl.java index d7b2dd7..80f7cb9 100644 --- a/src/main/java/org/terning/user/domain/UserRepositoryImpl.java +++ b/src/main/java/org/terning/user/domain/UserRepositoryImpl.java @@ -26,6 +26,16 @@ public Map findUsersByIds(List userIds) { .collect(Collectors.toMap(User::getId, Function.identity())); } + @Override + public Map findUsersByOUserIds(List oUserIds) { + return queryFactory + .selectFrom(QUser.user) + .where(QUser.user.oUserId.in(oUserIds)) + .fetch() + .stream() + .collect(Collectors.toMap(User::getOUserId, Function.identity())); + } + @Override public Optional findByOUserId(Long oUserId) { QUser user = QUser.user;