From 295a5ab81821d72d0940408f88f4cc8475a9f95f Mon Sep 17 00:00:00 2001 From: soohyun Date: Wed, 6 Mar 2024 13:29:16 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[Feat]#238=20feature:=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=20=ED=91=B8=EC=8B=9C=EC=95=8C=EB=A6=BC=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BroadCastController.java | 35 ++++++++++ .../broadcast/BroadCastAllUserDto.java | 15 ++++ .../org/winey/server/exception/Error.java | 1 + .../org/winey/server/exception/Success.java | 2 + .../server/infrastructure/UserRepository.java | 4 ++ .../server/service/BroadCastService.java | 50 +++++++++++++ .../org/winey/server/service/FcmService.java | 70 +++++++++++++++++-- .../server/service/message/SendAllFcmDto.java | 23 ++++++ 8 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/winey/server/controller/BroadCastController.java create mode 100644 src/main/java/org/winey/server/controller/request/broadcast/BroadCastAllUserDto.java create mode 100644 src/main/java/org/winey/server/service/BroadCastService.java create mode 100644 src/main/java/org/winey/server/service/message/SendAllFcmDto.java diff --git a/src/main/java/org/winey/server/controller/BroadCastController.java b/src/main/java/org/winey/server/controller/BroadCastController.java new file mode 100644 index 0000000..296f072 --- /dev/null +++ b/src/main/java/org/winey/server/controller/BroadCastController.java @@ -0,0 +1,35 @@ +package org.winey.server.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.winey.server.common.dto.ApiResponse; +import org.winey.server.controller.request.broadcast.BroadCastAllUserDto; +import org.winey.server.exception.Success; +import org.winey.server.service.BroadCastService; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.firebase.messaging.FirebaseMessagingException; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/broadcast") +@Tag(name = "BroadCast", description = "위니 전체 푸시 API Document") +public class BroadCastController { + private final BroadCastService broadCastService; + + @PostMapping("/send-all") + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "전체 유저에게 메시지 발송 API", description = "전체 유저에게 메시지를 발송합니다.") + public ApiResponse sendMessageToEntireUser(@RequestBody BroadCastAllUserDto broadCastAllUserDto) throws JsonProcessingException, FirebaseMessagingException { + return ApiResponse.success(Success.SEND_ENTIRE_MESSAGE_SUCCESS, broadCastService.broadAllUser(broadCastAllUserDto)); + } + +} diff --git a/src/main/java/org/winey/server/controller/request/broadcast/BroadCastAllUserDto.java b/src/main/java/org/winey/server/controller/request/broadcast/BroadCastAllUserDto.java new file mode 100644 index 0000000..4baa93a --- /dev/null +++ b/src/main/java/org/winey/server/controller/request/broadcast/BroadCastAllUserDto.java @@ -0,0 +1,15 @@ +package org.winey.server.controller.request.broadcast; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor +public class BroadCastAllUserDto { + String title; + + String message; +} diff --git a/src/main/java/org/winey/server/exception/Error.java b/src/main/java/org/winey/server/exception/Error.java index 8d90356..69decce 100644 --- a/src/main/java/org/winey/server/exception/Error.java +++ b/src/main/java/org/winey/server/exception/Error.java @@ -66,6 +66,7 @@ public enum Error { * 422 UNPROCESSABLE ENTITY */ UNPROCESSABLE_ENTITY_DELETE_EXCEPTION(HttpStatus.UNPROCESSABLE_ENTITY, "클라의 요청을 이해했지만 삭제하지 못했습니다."), + UNPROCESSABLE_FIND_USERS(HttpStatus.UNPROCESSABLE_ENTITY, "요청을 이해했지만 유저들을 찾을 수 없었습니다."), /** * 500 INTERNAL SERVER ERROR diff --git a/src/main/java/org/winey/server/exception/Success.java b/src/main/java/org/winey/server/exception/Success.java index 0b27d67..02492a4 100644 --- a/src/main/java/org/winey/server/exception/Success.java +++ b/src/main/java/org/winey/server/exception/Success.java @@ -29,6 +29,8 @@ public enum Success { BLOCK_USER_SUCCESS(HttpStatus.OK, "유저 차단 성공"), GET_ACHIEVEMENT_STATUS_SUCCESS(HttpStatus.OK, "레벨 달성 현황 조회 성공"), + SEND_ENTIRE_MESSAGE_SUCCESS(HttpStatus.OK, "전체 메시지 전송 성공"), + /** * 201 CREATED */ diff --git a/src/main/java/org/winey/server/infrastructure/UserRepository.java b/src/main/java/org/winey/server/infrastructure/UserRepository.java index 9203dde..97fbd8a 100644 --- a/src/main/java/org/winey/server/infrastructure/UserRepository.java +++ b/src/main/java/org/winey/server/infrastructure/UserRepository.java @@ -2,6 +2,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; +import org.springframework.lang.Nullable; import org.winey.server.domain.feed.Feed; import org.winey.server.domain.goal.Goal; import org.winey.server.domain.recommend.Recommend; @@ -18,6 +19,9 @@ public interface UserRepository extends Repository { // READ Optional findByUserId(Long userId); + List findByFcmTokenNotNull(); + + Boolean existsBySocialIdAndSocialType(String socialId, SocialType socialType); Optional findBySocialIdAndSocialType(String socialId, SocialType socialType); diff --git a/src/main/java/org/winey/server/service/BroadCastService.java b/src/main/java/org/winey/server/service/BroadCastService.java new file mode 100644 index 0000000..b85a1b7 --- /dev/null +++ b/src/main/java/org/winey/server/service/BroadCastService.java @@ -0,0 +1,50 @@ +package org.winey.server.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.winey.server.common.dto.ApiResponse; +import org.winey.server.controller.request.broadcast.BroadCastAllUserDto; +import org.winey.server.domain.user.User; +import org.winey.server.exception.Error; +import org.winey.server.exception.Success; +import org.winey.server.exception.model.CustomException; +import org.winey.server.infrastructure.UserRepository; +import org.winey.server.service.message.SendAllFcmDto; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.sun.net.httpserver.Authenticator; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BroadCastService { + + private final FcmService fcmService; + + private final UserRepository userRepository; + + public ApiResponse broadAllUser(BroadCastAllUserDto broadCastAllUserDto) throws + JsonProcessingException, + FirebaseMessagingException { + List allUser = userRepository.findByFcmTokenNotNull(); + List tokenList; + if (!allUser.isEmpty()){ + tokenList = allUser.stream().map( + User::getFcmToken).collect(Collectors.toList()); + System.out.println(tokenList); + fcmService.sendAllByTokenList(SendAllFcmDto.of(tokenList,broadCastAllUserDto.getTitle(), broadCastAllUserDto.getMessage())); + return ApiResponse.success(Success.SEND_ENTIRE_MESSAGE_SUCCESS, Success.SEND_ENTIRE_MESSAGE_SUCCESS.getMessage()); + } + return ApiResponse.error(Error.UNPROCESSABLE_FIND_USERS, Error.UNPROCESSABLE_FIND_USERS.getMessage()); + } + + + + +} diff --git a/src/main/java/org/winey/server/service/FcmService.java b/src/main/java/org/winey/server/service/FcmService.java index 404be38..b58d8c8 100644 --- a/src/main/java/org/winey/server/service/FcmService.java +++ b/src/main/java/org/winey/server/service/FcmService.java @@ -15,16 +15,20 @@ import okhttp3.Response; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; import org.winey.server.service.message.FcmMessage; import org.winey.server.service.message.FcmRequestDto; +import org.winey.server.service.message.SendAllFcmDto; import javax.annotation.PostConstruct; import java.io.IOException; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -104,13 +108,6 @@ public void sendByTokenList(List tokenList) { @Async public CompletableFuture sendByToken(FcmRequestDto wineyNotification) throws JsonProcessingException { // 메시지 만들기 - // Message message = Message.builder() - // .putData("feedId", String.valueOf(wineyNotification.getFeedId())) - // .putData("notiType", String.valueOf(wineyNotification.getType())) - // .putData("title", "위니 제국의 편지가 도착했어요.") - // .putData("message" ,wineyNotification.getMessage()) - // .setToken(wineyNotification.getToken()) <- 만약 여기 destination 부분만 수정해도 될 수도 있음. 가능성 생각하기 - // .build(); String jsonMessage = makeSingleMessage(wineyNotification); // 요청에 대한 응답을 받을 response Response response; @@ -182,5 +179,64 @@ private String makeSingleMessage(FcmRequestDto wineyNotification) throws JsonPro } } + private List makeCustomMessages(SendAllFcmDto wineyNotification) throws JsonProcessingException { + try { + List messages = wineyNotification.getTokenList() + .stream() + .map(token -> FcmMessage.builder() + .message(FcmMessage.Message.builder() + .token(token) // 1:1 전송 시 반드시 필요한 대상 토큰 설정 + .data(FcmMessage.Data.builder() + .title("위니 제국의 편지가 도착했어요.") + .message(wineyNotification.getMessage()) + .feedId(null) + .notiType(null) + .build()) + .notification(FcmMessage.Notification.builder() + .title("위니 제국의 편지가 도착했어요.") + .body(wineyNotification.getMessage()) + .build()) + .build() + ).validateOnly(false) + .build()).collect(Collectors.toList()); + return messages; + } catch (Exception e) { + throw new IllegalArgumentException("JSON 처리 도중에 예외가 발생했습니다."); + } + } + + public CompletableFuture sendAllByTokenList(SendAllFcmDto wineyNotification) throws JsonProcessingException, FirebaseMessagingException { + // These registration tokens come from the client FCM SDKs. + List registrationTokens = wineyNotification.getTokenList(); + try { + MulticastMessage message = MulticastMessage.builder() + .putData("title", wineyNotification.getTitle()) + .putData("message", wineyNotification.getMessage()) + .setNotification(new Notification(wineyNotification.getTitle(), wineyNotification.getMessage())) + .addAllTokens(registrationTokens) + .build(); + BatchResponse response = FirebaseMessaging.getInstance().sendMulticast(message); + if (response.getFailureCount() > 0) { + List responses = response.getResponses(); + List failedTokens = new ArrayList<>(); + for (int i = 0; i < responses.size(); i++) { + if (!responses.get(i).isSuccessful()) { + // The order of responses corresponds to the order of the registration tokens. + failedTokens.add(registrationTokens.get(i)); + } + } + + System.out.println("List of tokens that caused failures: " + failedTokens); + } + return CompletableFuture.completedFuture(response); + } catch (Exception e){ + log.info(e.getMessage()); + } + return null; + } + private Notification convertToFirebaseNotification(FcmMessage.Notification customNotification) { + return new Notification(customNotification.getTitle(), customNotification.getBody()); + } + } diff --git a/src/main/java/org/winey/server/service/message/SendAllFcmDto.java b/src/main/java/org/winey/server/service/message/SendAllFcmDto.java new file mode 100644 index 0000000..091df36 --- /dev/null +++ b/src/main/java/org/winey/server/service/message/SendAllFcmDto.java @@ -0,0 +1,23 @@ +package org.winey.server.service.message; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.winey.server.domain.notification.NotiType; + +import java.io.Serializable; +import java.util.List; +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class SendAllFcmDto { + private List tokenList; + + private String title; + + private String message; + + public static SendAllFcmDto of(List tokenList, String title, String message){ + return new SendAllFcmDto(tokenList, title, message); + } +} From a7af2f670b8683dba6a660b1afb95ed813b7159e Mon Sep 17 00:00:00 2001 From: soohyun Date: Wed, 13 Mar 2024 17:27:52 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[Feature]#238:=20try=20catch=20=EB=AC=B8=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 --- .../controller/BroadCastController.java | 2 +- .../org/winey/server/exception/Error.java | 1 + .../server/service/BroadCastService.java | 20 +++++--- .../org/winey/server/service/FcmService.java | 50 +++++++++---------- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/winey/server/controller/BroadCastController.java b/src/main/java/org/winey/server/controller/BroadCastController.java index 296f072..b140814 100644 --- a/src/main/java/org/winey/server/controller/BroadCastController.java +++ b/src/main/java/org/winey/server/controller/BroadCastController.java @@ -28,7 +28,7 @@ public class BroadCastController { @PostMapping("/send-all") @ResponseStatus(HttpStatus.OK) @Operation(summary = "전체 유저에게 메시지 발송 API", description = "전체 유저에게 메시지를 발송합니다.") - public ApiResponse sendMessageToEntireUser(@RequestBody BroadCastAllUserDto broadCastAllUserDto) throws JsonProcessingException, FirebaseMessagingException { + public ApiResponse sendMessageToEntireUser(@RequestBody BroadCastAllUserDto broadCastAllUserDto){ return ApiResponse.success(Success.SEND_ENTIRE_MESSAGE_SUCCESS, broadCastService.broadAllUser(broadCastAllUserDto)); } diff --git a/src/main/java/org/winey/server/exception/Error.java b/src/main/java/org/winey/server/exception/Error.java index 69decce..a9baf48 100644 --- a/src/main/java/org/winey/server/exception/Error.java +++ b/src/main/java/org/winey/server/exception/Error.java @@ -67,6 +67,7 @@ public enum Error { */ UNPROCESSABLE_ENTITY_DELETE_EXCEPTION(HttpStatus.UNPROCESSABLE_ENTITY, "클라의 요청을 이해했지만 삭제하지 못했습니다."), UNPROCESSABLE_FIND_USERS(HttpStatus.UNPROCESSABLE_ENTITY, "요청을 이해했지만 유저들을 찾을 수 없었습니다."), + UNPROCESSABLE_SEND_TO_FIREBASE(HttpStatus.UNPROCESSABLE_ENTITY, "파이어베이스로 전송하는 과정에서 에러가 발생했습니다."), /** * 500 INTERNAL SERVER ERROR diff --git a/src/main/java/org/winey/server/service/BroadCastService.java b/src/main/java/org/winey/server/service/BroadCastService.java index b85a1b7..14913fc 100644 --- a/src/main/java/org/winey/server/service/BroadCastService.java +++ b/src/main/java/org/winey/server/service/BroadCastService.java @@ -29,17 +29,21 @@ public class BroadCastService { private final UserRepository userRepository; - public ApiResponse broadAllUser(BroadCastAllUserDto broadCastAllUserDto) throws - JsonProcessingException, - FirebaseMessagingException { + public ApiResponse broadAllUser(BroadCastAllUserDto broadCastAllUserDto){ List allUser = userRepository.findByFcmTokenNotNull(); List tokenList; if (!allUser.isEmpty()){ - tokenList = allUser.stream().map( - User::getFcmToken).collect(Collectors.toList()); - System.out.println(tokenList); - fcmService.sendAllByTokenList(SendAllFcmDto.of(tokenList,broadCastAllUserDto.getTitle(), broadCastAllUserDto.getMessage())); - return ApiResponse.success(Success.SEND_ENTIRE_MESSAGE_SUCCESS, Success.SEND_ENTIRE_MESSAGE_SUCCESS.getMessage()); + try { + tokenList = allUser.stream().map( + User::getFcmToken).collect(Collectors.toList()); + System.out.println(tokenList); + fcmService.sendAllByTokenList( + SendAllFcmDto.of(tokenList, broadCastAllUserDto.getTitle(), broadCastAllUserDto.getMessage())); + return ApiResponse.success(Success.SEND_ENTIRE_MESSAGE_SUCCESS, + Success.SEND_ENTIRE_MESSAGE_SUCCESS.getMessage()); + }catch (FirebaseMessagingException | JsonProcessingException e){ + return ApiResponse.error(Error.UNPROCESSABLE_SEND_TO_FIREBASE, Error.UNPROCESSABLE_SEND_TO_FIREBASE.getMessage()); + } } return ApiResponse.error(Error.UNPROCESSABLE_FIND_USERS, Error.UNPROCESSABLE_FIND_USERS.getMessage()); } diff --git a/src/main/java/org/winey/server/service/FcmService.java b/src/main/java/org/winey/server/service/FcmService.java index b58d8c8..373db71 100644 --- a/src/main/java/org/winey/server/service/FcmService.java +++ b/src/main/java/org/winey/server/service/FcmService.java @@ -179,31 +179,31 @@ private String makeSingleMessage(FcmRequestDto wineyNotification) throws JsonPro } } - private List makeCustomMessages(SendAllFcmDto wineyNotification) throws JsonProcessingException { - try { - List messages = wineyNotification.getTokenList() - .stream() - .map(token -> FcmMessage.builder() - .message(FcmMessage.Message.builder() - .token(token) // 1:1 전송 시 반드시 필요한 대상 토큰 설정 - .data(FcmMessage.Data.builder() - .title("위니 제국의 편지가 도착했어요.") - .message(wineyNotification.getMessage()) - .feedId(null) - .notiType(null) - .build()) - .notification(FcmMessage.Notification.builder() - .title("위니 제국의 편지가 도착했어요.") - .body(wineyNotification.getMessage()) - .build()) - .build() - ).validateOnly(false) - .build()).collect(Collectors.toList()); - return messages; - } catch (Exception e) { - throw new IllegalArgumentException("JSON 처리 도중에 예외가 발생했습니다."); - } - } + // private List makeCustomMessages(SendAllFcmDto wineyNotification) throws JsonProcessingException { + // try { + // List messages = wineyNotification.getTokenList() + // .stream() + // .map(token -> FcmMessage.builder() + // .message(FcmMessage.Message.builder() + // .token(token) // 1:1 전송 시 반드시 필요한 대상 토큰 설정 + // .data(FcmMessage.Data.builder() + // .title("위니 제국의 편지가 도착했어요.") + // .message(wineyNotification.getMessage()) + // .feedId(null) + // .notiType(null) + // .build()) + // .notification(FcmMessage.Notification.builder() + // .title("위니 제국의 편지가 도착했어요.") + // .body(wineyNotification.getMessage()) + // .build()) + // .build() + // ).validateOnly(false) + // .build()).collect(Collectors.toList()); + // return messages; + // } catch (Exception e) { + // throw new IllegalArgumentException("JSON 처리 도중에 예외가 발생했습니다."); + // } + // } public CompletableFuture sendAllByTokenList(SendAllFcmDto wineyNotification) throws JsonProcessingException, FirebaseMessagingException { // These registration tokens come from the client FCM SDKs.