Skip to content

Commit

Permalink
Merge pull request #62 from Team-Umbba/fix/#61-new_question_push_alar…
Browse files Browse the repository at this point in the history
…m_twice

[FIX] 매일 같은 시간에 전송되는 푸시 알림이 2번씩 전송되는 오류 해결
  • Loading branch information
ddongseop authored Jul 19, 2023
2 parents 546345d + 72fa538 commit 414a757
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import sopt.org.umbbaServer.domain.parentchild.repository.ParentchildRepository;
import sopt.org.umbbaServer.global.util.fcm.controller.dto.FCMPushRequestDto;
Expand All @@ -28,8 +27,8 @@ public String pushTodayQna() {
parentchildRepository.findAll().stream()
.forEach(pc -> {
log.info(pc.getId() + "번째 Parentchild");
// String cronExpression = String.format("0 %s %s * * ?", pc.getPushTime().getMinute(), pc.getPushTime().getHour());
String cronExpression = String.format("*/10 * * * * *");
String cronExpression = String.format("0 %s %s * * ?", pc.getPushTime().getMinute(), pc.getPushTime().getHour());
// String cronExpression = String.format("*/10 * * * * *");
log.info("cron: {}", cronExpression);
fcmService.schedulePushAlarm(cronExpression, pc.getId());
});
Expand Down
164 changes: 77 additions & 87 deletions src/main/java/sopt/org/umbbaServer/global/util/fcm/FCMService.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
Expand All @@ -56,12 +55,9 @@ public class FCMService {
private final TaskScheduler taskScheduler;
private final PlatformTransactionManager transactionManager;


@PersistenceContext
private EntityManager em;



// Firebase에서 Access Token 가져오기
private String getAccessToken() throws IOException {

Expand All @@ -74,37 +70,6 @@ private String getAccessToken() throws IOException {
return googleCredentials.getAccessToken().getTokenValue();
}

// FCM Service에 메시지를 수신하는 함수 (헤더와 바디 직접 만들기)
@Transactional
public String pushAlarm(FCMPushRequestDto request, Long userId) throws IOException {

// TODO 같은 Parentchild ID를 가진 User를 찾은 후, 이들에 대한 토큰 리스트로 동일한 알림 메시지 전송하도록
User user = userRepository.findById(userId).orElseThrow(
() -> new CustomException(ErrorType.INVALID_USER)
);
user.updateFcmToken(request.getTargetToken());

String message = makeMessage(request);
sendPushMessage(message);
return "알림을 성공적으로 전송했습니다. targetUserId = " + request.getTargetToken();
}

public void sendPushMessage(String message) throws IOException {

OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message, MediaType.get("application/json; charset=utf-8"));
Request httpRequest = new Request.Builder()
.url(FCM_API_URL)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
.build();

Response response = client.newCall(httpRequest).execute();

log.info("알림 전송: {}", response.body().string());
}

// 요청 파라미터를 FCM의 body 형태로 만들어주는 메서드 [단일 기기]
public String makeMessage(FCMPushRequestDto request) throws JsonProcessingException {

Expand Down Expand Up @@ -133,57 +98,50 @@ String makeMessage(FCMPushRequestDto request, Long userId) throws FirebaseMessag

FCMMessage fcmMessage = FCMMessage.builder()
.message(FCMMessage.Message.builder()
.token(user.getFcmToken())
.token(user.getFcmToken())
// .topic(topic) // 토픽 구동에서 반드시 필요한 설정 (token 지정 x)
.notification(FCMMessage.Notification.builder()
.title(request.getTitle())
.body(request.getBody())
.image(null)
.build())
.build()
.notification(FCMMessage.Notification.builder()
.title(request.getTitle())
.body(request.getBody())
.image(null)
.build())
.build()
).validateOnly(false)
.build();

return objectMapper.writeValueAsString(fcmMessage);
}

// FCM Service에 메시지를 수신하는 함수 (헤더와 바디 직접 만들기) -> 상대 답변 알람 전송에 사용
@Transactional
public String pushAlarm(FCMPushRequestDto request, Long userId) throws IOException {

/**
* 단일 요청으로 최대 1000개의 기기를 Topic에 구독 등록 및 취소할 수 있다.
*/
// Topic 구독 설정 - application.yml에서 topic명 관리
public void subscribe() throws FirebaseMessagingException {
// These registration tokens come from the client FCM SDKs.
// TODO Parentchild 테이블 탐색 후 주기적으로 알림 쏴주기
List<String> registrationTokens = Arrays.asList(
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n"
// TODO 같은 Parentchild ID를 가진 User를 찾은 후, 이들에 대한 토큰 리스트로 동일한 알림 메시지 전송하도록
User user = userRepository.findById(userId).orElseThrow(
() -> new CustomException(ErrorType.INVALID_USER)
);
user.updateFcmToken(request.getTargetToken());

// Subscribe the devices corresponding to the registration tokens to the topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().subscribeToTopic(
registrationTokens, topic);

System.out.println(response.getSuccessCount() + " tokens were subscribed successfully");
String message = makeMessage(request);
sendPushMessage(message);
return "알림을 성공적으로 전송했습니다. targetUserId = " + request.getTargetToken();
}

// Topic 구독 취소
public void unsubscribe() throws FirebaseMessagingException {
// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n"
);
public void sendPushMessage(String message) throws IOException {

// Unsubscribe the devices corresponding to the registration tokens from the topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().unsubscribeFromTopic(
registrationTokens, topic);
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message, MediaType.get("application/json; charset=utf-8"));
Request httpRequest = new Request.Builder()
.url(FCM_API_URL)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
.build();

System.out.println(response.getSuccessCount() + " tokens were unsubscribed successfully");
}
Response response = client.newCall(httpRequest).execute();

log.info("알림 전송: {}", response.body().string());
}

public void pushOpponentReply(String question, Long userId) {

Expand All @@ -205,7 +163,6 @@ public void pushOpponentReply(String question, Long userId) {
}
}


// 다수의 기기(부모자식 ID에 포함된 유저 2명)에 알림 메시지 전송 -> 주기적 알림 전송에서 사용
public String multipleSendByToken(FCMPushRequestDto request, Long parentchildId) {

Expand Down Expand Up @@ -244,7 +201,6 @@ public void schedulePushAlarm(String cronExpression, Long parentchildId) {
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);


log.info("성립된 부모자식- 초대코드: {}, 인덱스: {}", parentchild.getInviteCode(), parentchild.getCount());

// em.persist(parentchild);
Expand All @@ -254,38 +210,72 @@ public void schedulePushAlarm(String cronExpression, Long parentchildId) {

QnA currentQnA = parentchild.getQnaList().get(parentchild.getCount() - 1);
if (currentQnA.isParentAnswer() && currentQnA.isChildAnswer()) {

// tx.begin();

log.info("둘 다 답변함 다음 질문으로 ㄱ {}", parentchild.getCount());
parentchild.addCount();
Parentchild pc = em.merge(parentchild);
// pc.addCount();
// em.flush();
// em.remove(parentchild);

transactionManager.commit(transactionStatus);
log.info("스케줄링 작업 예약 내 addCount 후 count: {}", pc.getCount());

QnA todayQnA = parentchild.getQnaList().get(parentchild.getCount() - 1);
List<User> parentChildUsers = userRepository.findUserByParentChild(parentchild);

log.info("FCMService - schedulePushAlarm() 실행");
parentChildUsers.stream()
.filter(user -> user.validateParentchild(parentChildUsers) && !user.getSocialPlatform().equals(SocialPlatform.WITHDRAW))
.forEach(user -> {
log.info("FCMService-schedulePushAlarm() topic: {}", todayQnA.getQuestion().getTopic());
multipleSendByToken(FCMPushRequestDto.sendTodayQna(todayQnA.getQuestion().getSection().getValue(), todayQnA.getQuestion().getTopic()), parentchild.getId());
multipleSendByToken(FCMPushRequestDto.sendTodayQna("술이슈", "새벽4시 술 먹을시간"), 3L);
});

if (todayQnA == null) {
log.error("{}번째 Parentchild의 QnAList가 존재하지 않음!", parentchild.getId());
}

List<User> parentChildUsers = userRepository.findUserByParentChild(parentchild);
if (parentChildUsers.stream().
allMatch(user -> user.validateParentchild(parentChildUsers) && !user.getSocialPlatform().equals(SocialPlatform.WITHDRAW))) {

log.info("FCMService - schedulePushAlarm() 실행");
log.info("FCMService-schedulePushAlarm() topic: {}", todayQnA.getQuestion().getTopic());
multipleSendByToken(FCMPushRequestDto.sendTodayQna(todayQnA.getQuestion().getSection().getValue(),
todayQnA.getQuestion().getTopic()), parentchild.getId());
multipleSendByToken(FCMPushRequestDto.sendTodayQna("술이슈", "새벽4시 술 먹을시간"), 3L);
}
}
}

}, new CronTrigger(cronExpression));
}

/**
* 사용 안하는 함수들
*/

// Topic 구독 설정 - application.yml에서 topic명 관리
// 단일 요청으로 최대 1000개의 기기를 Topic에 구독 등록 및 취소할 수 있다.
public void subscribe() throws FirebaseMessagingException {
// These registration tokens come from the client FCM SDKs.
// TODO Parentchild 테이블 탐색 후 주기적으로 알림 쏴주기
List<String> registrationTokens = Arrays.asList(
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n"
);

// Subscribe the devices corresponding to the registration tokens to the topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().subscribeToTopic(
registrationTokens, topic);

System.out.println(response.getSuccessCount() + " tokens were subscribed successfully");
}

// Topic 구독 취소
public void unsubscribe() throws FirebaseMessagingException {
// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n"
);

// Unsubscribe the devices corresponding to the registration tokens from the topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().unsubscribeFromTopic(
registrationTokens, topic);

System.out.println(response.getSuccessCount() + " tokens were unsubscribed successfully");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ public class FCMController {
private final FCMScheduler fcmScheduler;


/**
* 새로운 질문이 도착했음을 알리는 푸시 알림 활성화 API
* 실제로는 초대 받는측의 온보딩이 완료되었을 때 호출됨
*/
@PostMapping("/qna")
@ResponseStatus(HttpStatus.OK)
public ApiResponse sendTopicScheduledTest() {
return ApiResponse.success(SuccessType.PUSH_ALARM_PERIODIC_SUCCESS, fcmScheduler.pushTodayQna());
}

/**
* 장난용 푸시 알림 활성화 API
*/
@PostMapping("/drink")
@ResponseStatus(HttpStatus.OK)
public ApiResponse drinkAlarm() {
Expand All @@ -37,7 +44,7 @@ public ApiResponse drinkAlarm() {


/**
* 주기적 알림 전송 테스트를 위한 API
* 헤더와 바디를 직접 만들어 알림을 전송하는 테스트용 API (상대 답변 알람 전송에 사용)
*/
@PostMapping
@ResponseStatus(HttpStatus.OK)
Expand All @@ -46,6 +53,9 @@ public ApiResponse sendNotificationByToken(@RequestBody FCMPushRequestDto reques
return ApiResponse.success(SuccessType.PUSH_ALARM_SUCCESS, fcmService.pushAlarm(request, JwtProvider.getUserFromPrincial(principal)));
}

/**
* 동시에 여러 사람에게 푸시 알림을 보내보는 테스트용 API (주기적 알람 전송에 사용)
*/
@PostMapping("/parentchild")
@ResponseStatus(HttpStatus.OK)
public ApiResponse sendMultiScheduledTest() {
Expand Down

0 comments on commit 414a757

Please sign in to comment.