Skip to content

Commit

Permalink
[CHORE] Scheduler API 서버로 이동 #93
Browse files Browse the repository at this point in the history
  • Loading branch information
jun02160 committed Aug 14, 2023
1 parent e0e494e commit baefb19
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 155 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
firebase/
test/DBConnectionTest.java,
test/TestConfig.java

HELP.md
.gradle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sopt.org.umbba.api.controller.qna.dto.request.TodayAnswerRequestDto;
import sopt.org.umbba.api.controller.qna.dto.response.*;
import sopt.org.umbba.api.service.qna.QnAService;
import sopt.org.umbba.api.service.scheduler.FCMScheduler;
import sopt.org.umbba.common.exception.SuccessType;
import sopt.org.umbba.common.exception.dto.ApiResponse;

Expand All @@ -19,6 +20,8 @@
public class QnAController {

private final QnAService qnAService;
private final FCMScheduler fcmScheduler;


@GetMapping("/qna/today")
@ResponseStatus(HttpStatus.OK)
Expand Down Expand Up @@ -73,4 +76,15 @@ public ApiResponse<GetInvitationResponseDto> invitation(Principal principal) {
return ApiResponse.success(SuccessType.GET_INVITE_CODE_SUCCESS, qnAService.getInvitation(JwtProvider.getUserFromPrincial(principal)));
}


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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package sopt.org.umbba.api.service.scheduler;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import sopt.org.umbba.api.config.sqs.producer.SqsProducer;
import sopt.org.umbba.common.sqs.dto.FCMPushRequestDto;
import sopt.org.umbba.domain.domain.parentchild.Parentchild;
import sopt.org.umbba.domain.domain.parentchild.dao.ParentchildDao;
import sopt.org.umbba.domain.domain.parentchild.repository.ParentchildRepository;
import sopt.org.umbba.domain.domain.qna.QnA;
import sopt.org.umbba.domain.domain.user.SocialPlatform;
import sopt.org.umbba.domain.domain.user.User;
import sopt.org.umbba.domain.domain.user.repository.UserRepository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PessimisticLockException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;

@Slf4j
@Component
@RequiredArgsConstructor
public class FCMScheduler {

private final ParentchildRepository parentchildRepository;
private final UserRepository userRepository;
private final ParentchildDao parentchildDao;
private final SqsProducer sqsProducer;

private static ScheduledFuture<?> scheduledFuture;
private final TaskScheduler taskScheduler;

private final PlatformTransactionManager transactionManager;

@PersistenceContext
private EntityManager em;

@Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Seoul") // 초기값
public String pushTodayQna() {

log.info("오늘의 질문 알람 - 유저마다 보내는 시간 다름");
// List<String> tokenList = parentchildDao.findFcmTokensById(parentchildId);


parentchildRepository.findAll().stream()
.filter(pc -> {
List<String> tokenList = parentchildDao.findFcmTokensById(pc.getId());
List<User> parentChildUsers = userRepository.findUserByParentChild(pc);
return tokenList != null &&
tokenList.size() == 2 &&
parentChildUsers.stream()
.allMatch(user -> user.validateParentchild(parentChildUsers) && !user.getSocialPlatform().equals(SocialPlatform.WITHDRAW));
})
.forEach(pc -> {
log.info(pc.getId() + "번째 Parentchild");
String cronExpression = String.format("0 %s %s * * ?", pc.getPushTime().getMinute(), pc.getPushTime().getHour());
// String cronExpression = String.format("*/20 * * * * *");
log.info("cron: {}", cronExpression);
schedulePushAlarm(cronExpression, pc.getId());
})
;
return "Today QnA messages were sent successfully";
}

private void schedulePushAlarm(String cronExpression, Long parentchildId) {

scheduledFuture = taskScheduler.schedule(() -> {

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

Parentchild parentchild = parentchildRepository.findById(parentchildId).get();

TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);

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

try {
if (!parentchild.getQnaList().isEmpty()) {

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

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

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

QnA todayQnA = parentchild.getQnaList().get(parentchild.getCount() - 1);
// em.close();

log.info("\n Current QnA: {} \n Today QnA: {}", currentQnA.getId(), todayQnA.getId());
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);
}
}
}
} catch (PessimisticLockingFailureException | PessimisticLockException e) {
transactionManager.rollback(transactionStatus);
} finally {
em.close();
}

// 현재 실행중인 쓰레드 확인
log.info("Current Thread : {}", Thread.currentThread().getName());

}, new CronTrigger(cronExpression));
}



// 스케줄러에서 예약된 작업을 제거하는 메서드
public static void clearScheduledTasks() {
if (scheduledFuture != null) {
log.info("이전 스케줄링 예약 취소!");
scheduledFuture.cancel(false);
}
log.info("ScheduledFuture: {}", scheduledFuture);
}



// @Scheduled(cron = "0 0 4 * * ?", zone = "Asia/Seoul")
// public String drink() {
// fcmService.multipleSendByToken(FCMPushRequestDto.sendTodayQna("술이슈", "새벽4시 술 먹을시간"), 3L);
//
// return "Today QnA messages were sent successfully";
// }
}
66 changes: 66 additions & 0 deletions umbba-api/src/test/DBConnectionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
package sopt.org.umbba.api;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Logger;
@Slf4j
@SpringBootTest
public class DBConnectionTest {
private final String DB_URL = "jdbc:mysql://umbba-db.csqsqogfqnvj.ap-northeast-2.rds.amazonaws.com:3306/umbba_db?useSSL=true&useUnicode=true&serverTimezone=Asia/Seoul";
private final String DB_USERNAME = "umbba_server";
private final String DB_PASSWORD = "umbbaServer!";
@Test
void dataSourceDriverManager() throws SQLException, InterruptedException {
//hikari pool 을 사용해서 커넥션 pooling
//DriverManagerSource - 항상 새로운 커넥션을 획득
DriverManagerDataSource dataSource = new DriverManagerDataSource(DB_URL, DB_USERNAME, DB_PASSWORD);
useDataSource(dataSource);
//커넥션에서 커넥션 풀이 생성되는 걸 보기위해
Thread.sleep(1000);
}
@Test
void dataSourceConnectionPool() throws SQLException, InterruptedException {
//DriverManagerSource - 항상 새로운 커넥션을 획득
//커넥션 풀링: HikariProxyConnection(Proxy) -> JdbcConnection(Target)
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(DB_URL);
dataSource.setUsername(DB_USERNAME);
dataSource.setPassword(DB_PASSWORD);
dataSource.setMaximumPoolSize(10);
dataSource.setPoolName("MyPool");
useDataSource(dataSource);
//커넥션에서 커넥션 풀이 생성되는 걸 보기위해
Thread.sleep(1000);
}
private void useDataSource(DataSource dataSource) throws SQLException {
Connection conn1 = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
Connection conn2 = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
log.info("connection={}, class={}", conn1, conn1.getClass());
log.info("connection={}, class={}", conn2, conn2.getClass());
}
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import sopt.org.umbba.common.exception.SuccessType;
import sopt.org.umbba.common.exception.dto.ApiResponse;
import sopt.org.umbba.common.sqs.dto.FCMPushRequestDto;
import sopt.org.umbba.notification.service.fcm.FCMService;
import sopt.org.umbba.notification.service.scheduler.FCMScheduler;

import java.io.IOException;
import java.security.Principal;
Expand All @@ -18,18 +16,6 @@
public class FCMController {

private final FCMService fcmService;
private final FCMScheduler fcmScheduler;


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

/**
* 장난용 푸시 알림 활성화 API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,9 @@ public class FCMService {
@Value("${fcm.topic}")
private String topic;

private static ScheduledFuture<?> scheduledFuture;

private final UserRepository userRepository;
private final ParentchildRepository parentchildRepository;
private final ParentchildDao parentchildDao;
private final ObjectMapper objectMapper;
private final TaskScheduler taskScheduler;
private final PlatformTransactionManager transactionManager;


@PersistenceContext
private EntityManager em;


// Firebase에서 Access Token 가져오기
Expand Down Expand Up @@ -199,78 +190,6 @@ public String multipleSendByToken(FCMPushRequestDto request, Long parentchildId)
}
}

public void schedulePushAlarm(String cronExpression, Long parentchildId) {

scheduledFuture = taskScheduler.schedule(() -> {

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

Parentchild parentchild = parentchildRepository.findById(parentchildId).get();

TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);

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

try {
if (!parentchild.getQnaList().isEmpty()) {

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

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

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

QnA todayQnA = parentchild.getQnaList().get(parentchild.getCount() - 1);
// em.close();

log.info("\n Current QnA: {} \n Today QnA: {}", currentQnA.getId(), todayQnA.getId());
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);
}
}
}
} catch (PessimisticLockingFailureException | PessimisticLockException e) {
transactionManager.rollback(transactionStatus);
} finally {
em.close();
}

// 현재 실행중인 쓰레드 확인
log.info("Current Thread : {}", Thread.currentThread().getName());

}, new CronTrigger(cronExpression));
}

// 스케줄러에서 예약된 작업을 제거하는 메서드
public static void clearScheduledTasks() {
if (scheduledFuture != null) {
log.info("이전 스케줄링 예약 취소!");
scheduledFuture.cancel(false);
}
log.info("ScheduledFuture: {}", scheduledFuture);
}



Expand Down
Loading

0 comments on commit baefb19

Please sign in to comment.