Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 이벤트 시간 체크 로직 추가 #25

Merged
merged 8 commits into from
Aug 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public enum ErrorCode {

//event_arrival
PHONENUM_EXISTS_ERROR(false, HttpStatus.BAD_REQUEST.value(),"이미 응모한 전화번호입니다."),
QUIZ_NOT_FOUND(false, HttpStatus.NOT_FOUND.value(), "오늘 날짜에 퀴즈가 없습니다.")
QUIZ_NOT_FOUND(false, HttpStatus.NOT_FOUND.value(), "오늘 날짜에 퀴즈가 없습니다."),
EVENT_CLOSED(false, HttpStatus.FORBIDDEN.value(), "이벤트 시간이 아닙니다.")
;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ public CommonResponse<?> dailyQuizNotFoundException(DailyQuizNotExistsException
log.warn("ARRIVAL-003> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage());
return new CommonResponse<>(ErrorCode.QUIZ_NOT_FOUND);
}

@ExceptionHandler(EventClosedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public CommonResponse<?> eventClosedException(EventClosedException e, HttpServletRequest request) {
log.warn("ARRIVAL-004> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage());
return new CommonResponse<>(ErrorCode.EVENT_CLOSED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.softeer.podoarrival.event.exception;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class EventClosedException extends RuntimeException {
private String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

/**
Expand All @@ -24,18 +25,19 @@
@Slf4j
@Component
@RequiredArgsConstructor
public class ArrivalEventMaxArrivalScheduler {
public class ArrivalEventInformationScheduler {

private final EventRepository eventRepository;
private final EventTypeRepository eventTypeRepository;
private final EventRewardRepository eventRewardRepository;

/**
* 특정 시간에 Mysql에서 금일 진행될 선착순 이벤트의 당첨자 수를 읽어옴
* 특정 시간에 Mysql에서 금일 진행될 선착순 이벤트의 절보를 세팅함.
* 당첨자 수, 이벤트 시작 시간, 이벤트 요일 설정
*
*/
@Scheduled(cron = "0 25 03 * * *")
public void setEventArrivalCount() {
public void setArrivalEventInformation() {
// 시작일자, 이벤트 종류만 고려하여 이벤트 추출
LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
LocalDateTime endOfDay = startOfDay.plusDays(1).minusNanos(1);
Expand All @@ -44,14 +46,39 @@ public void setEventArrivalCount() {
EventType eventType = eventTypeRepository.findById(1L).orElseThrow(() -> new EventTypeNotExistsException("이벤트 타입이 존재하지 않습니다."));
Event findEvent = eventRepository.findFirstByEventTypeAndStartAtBetween(eventType, startOfDay, endOfDay);

if(findEvent == null) {
log.warn("오늘 날짜에 이벤트가 없습니다.");
ArrivalEventReleaseServiceRedisImpl.setMaxArrival(0);
ArrivalEventReleaseServiceJavaImpl.setMaxArrival(0);
return;
}

// 찾은 이벤트에 해당하는 reward개수 조회
int rewardCount = 0;
List<EventReward> eventRewards = eventRewardRepository.findAllByEvent(findEvent);
for (EventReward eventReward : eventRewards) {
rewardCount += eventReward.getNumWinners();
}

// 찾은 이벤트에 해당하는 반복 시간 확인
LocalTime repeatTime = findEvent.getRepeatTime();

// 찾은 이벤트에 해당하는 반복 요일 확인 및 저장
String repeatDate = findEvent.getRepeatDay();
int today = startOfDay.getDayOfWeek().getValue();
if(repeatDate.length() >= 7 && repeatDate.charAt(today - 1) == '1') {
ArrivalEventReleaseServiceRedisImpl.setStartDate(true);
ArrivalEventReleaseServiceJavaImpl.setStartDate(true);
}else{
ArrivalEventReleaseServiceRedisImpl.setStartDate(false);
ArrivalEventReleaseServiceJavaImpl.setStartDate(false);
}

// service에 이벤트 내용 저장
ArrivalEventReleaseServiceRedisImpl.setMaxArrival(rewardCount);
ArrivalEventReleaseServiceJavaImpl.setMaxArrival(rewardCount);

ArrivalEventReleaseServiceRedisImpl.setStartTime(repeatTime);
ArrivalEventReleaseServiceJavaImpl.setStartTime(repeatTime);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.softeer.podoarrival.event.service;

import com.softeer.podoarrival.event.exception.EventClosedException;
import com.softeer.podoarrival.event.exception.ExistingUserException;
import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto;
import com.softeer.podoarrival.event.model.entity.ArrivalUser;
Expand All @@ -13,6 +14,7 @@
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
Expand All @@ -26,6 +28,8 @@ public class ArrivalEventReleaseServiceJavaImpl implements ArrivalEventReleaseSe

private static int MAX_ARRIVAL = 100; // default
private boolean CHECK = false;
private static LocalTime START_TIME = LocalTime.of(0, 0);
private static boolean START_DATE = true;

private static AtomicInteger count = new AtomicInteger(1);
private static ConcurrentHashMap<String, Integer> hashMap = new ConcurrentHashMap<>();
Expand All @@ -38,6 +42,8 @@ public class ArrivalEventReleaseServiceJavaImpl implements ArrivalEventReleaseSe
@Override
public CompletableFuture<ArrivalApplicationResponseDto> applyEvent(AuthInfo authInfo) {
return CompletableFuture.supplyAsync(() -> {
if(!START_DATE) throw new EventClosedException("이벤트 요일이 아닙니다.");
if(LocalTime.now().isBefore(START_TIME)) throw new EventClosedException("이벤트 시간이 아닙니다.");

if(CHECK){
return new ArrivalApplicationResponseDto(false, authInfo.getName(), authInfo.getPhoneNum(), -1);
Expand Down Expand Up @@ -72,7 +78,24 @@ public static void setMaxArrival(int val) {
MAX_ARRIVAL = val;
}

public static void setStartTime(LocalTime val) {
START_TIME = val;
}

public static void setStartDate(Boolean val) {
START_DATE = val;
}

public static int getMaxArrival() {
return MAX_ARRIVAL;
}


public static LocalTime getStartTime() {
return START_TIME;
}

public static boolean getStartDate() {
return START_DATE;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.softeer.podoarrival.event.service;

import com.softeer.podoarrival.event.exception.EventClosedException;
import com.softeer.podoarrival.event.exception.ExistingUserException;
import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto;
import com.softeer.podoarrival.event.model.entity.ArrivalUser;
Expand All @@ -17,6 +18,7 @@
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.concurrent.CompletableFuture;

@Slf4j
Expand All @@ -32,6 +34,8 @@ public class ArrivalEventReleaseServiceRedisImpl implements ArrivalEventReleaseS
private final String ARRIVAL_SET = "arrivalset";
private boolean CHECK = false;
private static int MAX_ARRIVAL = 100; // default
private static LocalTime START_TIME = LocalTime.of(0, 0);
private static boolean START_DATE = true;

/**
* 비동기로 Redis 호출하는 메서드
Expand All @@ -44,6 +48,9 @@ public CompletableFuture<ArrivalApplicationResponseDto> applyEvent(AuthInfo auth
return CompletableFuture.supplyAsync(() -> {
String redisKey = LocalDate.now() + ARRIVAL_SET;

if(!START_DATE) throw new EventClosedException("이벤트 요일이 아닙니다.");
if(LocalTime.now().isBefore(START_TIME)) throw new EventClosedException("이벤트 시간이 아닙니다.");

if(CHECK){
return new ArrivalApplicationResponseDto(false, authInfo.getName(), authInfo.getPhoneNum(), -1);
}
Expand All @@ -58,9 +65,6 @@ public CompletableFuture<ArrivalApplicationResponseDto> applyEvent(AuthInfo auth
throw new ExistingUserException("이미 응모한 전화번호입니다.");
}

// 로깅 추가
specialLogger.info("[응모] 유저 전화번호: {}", authInfo.getPhoneNum());

int grade = (int) res.getResponses().get(1);
// 선착순 순위에 들었다면
if(grade <= MAX_ARRIVAL){
Expand All @@ -85,7 +89,23 @@ public static void setMaxArrival(int val) {
MAX_ARRIVAL = val;
}

public static void setStartTime(LocalTime val) {
START_TIME = val;
}

public static void setStartDate(Boolean val) {
START_DATE = val;
}

public static int getMaxArrival() {
return MAX_ARRIVAL;
}

public static LocalTime getStartTime() {
return START_TIME;
}

public static boolean getStartDate() {
return START_DATE;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.softeer.podoarrival.integration.event;

import com.softeer.podoarrival.event.exception.EventClosedException;
import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto;
import com.softeer.podoarrival.event.model.entity.Role;
import com.softeer.podoarrival.event.repository.ArrivalUserRepository;
Expand All @@ -10,6 +11,7 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
Expand All @@ -19,15 +21,22 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mockStatic;

@SpringBootTest
@ContextConfiguration(classes = {ArrivalEventServiceTest.TestConfig.class})
Expand All @@ -48,6 +57,9 @@ class ArrivalEventServiceTest {
@Autowired
private ArrivalUserRepository arrivalUserRepository;

@MockBean
private LocalTime localTime;

@AfterEach
void tearDown() {
redissonClient.getKeys().deleteByPattern("*arrivalset");
Expand All @@ -73,7 +85,7 @@ void redisApplyTest() throws InterruptedException {
try {
CompletableFuture<ArrivalApplicationResponseDto> futureResponse = redisEventService.applyEvent(
new AuthInfo(
"teat" + userId,
"test" + userId,
"010-1234-5678-" + userId,
Role.ROLE_USER
)
Expand Down Expand Up @@ -108,7 +120,7 @@ void javaApplyTest() throws InterruptedException {
try {
CompletableFuture<ArrivalApplicationResponseDto> futureResponse = javaEventService.applyEvent(
new AuthInfo(
"teat" + userId,
"test" + userId,
"010-1234-5678-" + userId,
Role.ROLE_USER
)
Expand All @@ -127,6 +139,30 @@ void javaApplyTest() throws InterruptedException {
assertEquals(MAX_COUNT, count.get());
}

@Test
@DisplayName("선착순 api 시간 외 오류 테스트")
void eventOutOfTimeTest() throws NoSuchFieldException, IllegalAccessException {
//given
Field startDate = ArrivalEventReleaseServiceRedisImpl.class.getDeclaredField("START_DATE");
Field startTime = ArrivalEventReleaseServiceRedisImpl.class.getDeclaredField("START_TIME");
startDate.setAccessible(true); // private 필드를 접근 가능하도록 설정
startTime.setAccessible(true);
startDate.set(redisEventService, true); // private 필드 값을 변경
startTime.set(redisEventService, LocalTime.now().plusHours(1));

//when
CompletableFuture<ArrivalApplicationResponseDto> futureResponse = redisEventService.applyEvent(
new AuthInfo(
"test",
"010-1234-5678-",
Role.ROLE_USER
)
);

//then
assertThrows(ExecutionException.class, futureResponse::get);
}

@Configuration
static class TestConfig {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
import com.softeer.podoarrival.event.repository.EventRepository;
import com.softeer.podoarrival.event.repository.EventRewardRepository;
import com.softeer.podoarrival.event.repository.EventTypeRepository;
import com.softeer.podoarrival.event.scheduler.ArrivalEventMaxArrivalScheduler;
import com.softeer.podoarrival.event.scheduler.ArrivalEventInformationScheduler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

@ExtendWith(MockitoExtension.class)
public class ArrivalEventMaxCountSchedulerBase {
public class ArrivalEventInformationBase {

@Mock
protected EventRepository eventRepository;
Expand All @@ -28,7 +30,7 @@ public class ArrivalEventMaxCountSchedulerBase {
protected EventTypeRepository eventTypeRepository;

@InjectMocks
protected ArrivalEventMaxArrivalScheduler arrivalEventMaxArrivalScheduler;
protected ArrivalEventInformationScheduler arrivalEventInformationScheduler;

protected Event eventSample;
protected EventReward eventReward1;
Expand All @@ -42,6 +44,8 @@ public void setUp() {
.description("The 2025 셀토스 출시 기념 선착순 이벤트")
.startAt(LocalDateTime.now())
.endAt(LocalDateTime.now().plusDays(5))
.repeatTime(LocalTime.of(15, 0))
.repeatDay("1111111")
.build();

eventReward1 = EventReward.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ public class ArrivalEventComplexServiceTest {
private ArrivalEventService arrivalEventService;

@Mock
private ArrivalEventReleaseServiceJavaImpl redisService;
private ArrivalEventReleaseServiceJavaImpl javaService;

@Mock
private ArrivalEventReleaseServiceRedisImpl javaService;
private ArrivalEventReleaseServiceRedisImpl redisService;

@Mock
private RedissonClient redissonClient;
Expand Down
Loading
Loading