diff --git a/src/main/java/kr/co/knuserver/application/booth/BoothLikeService.java b/src/main/java/kr/co/knuserver/application/booth/BoothLikeService.java index 2c87d92..5118c9b 100644 --- a/src/main/java/kr/co/knuserver/application/booth/BoothLikeService.java +++ b/src/main/java/kr/co/knuserver/application/booth/BoothLikeService.java @@ -1,6 +1,8 @@ package kr.co.knuserver.application.booth; import java.time.Duration; +import java.time.LocalTime; +import java.time.ZoneId; import java.util.Collections; import java.util.Set; import kr.co.knuserver.domain.booth.entity.Booth; @@ -27,6 +29,15 @@ public class BoothLikeService { @Value("${like.rate-limit.ttl-seconds}") private long rateLimitTtlSeconds; + @Value("${like.double-event.enabled:false}") + private boolean doubleEventEnabled; + + @Value("${like.double-event.start-time:13:00}") + private String doubleEventStart; + + @Value("${like.double-event.end-time:15:00}") + private String doubleEventEnd; + private final StringRedisTemplate redisTemplate; private final BoothRepository boothRepository; @@ -39,7 +50,9 @@ public long like(Long boothId, String deviceId, String clientIp) { try { checkRateLimit(clientIp, deviceId, boothId); - Double score = redisTemplate.opsForZSet().incrementScore(RANKING_KEY, String.valueOf(boothId), 1); + int multiplier = getLikeMultiplier(); + Double score = redisTemplate.opsForZSet().incrementScore(RANKING_KEY, String.valueOf(boothId), multiplier); + log.debug("[Like] boothId={} multiplier={} score={}", boothId, multiplier, score); return score == null ? 0 : score.longValue(); } catch (BusinessException e) { throw e; @@ -71,6 +84,20 @@ public Set> getRanking() { } } + private int getLikeMultiplier() { + if (!doubleEventEnabled) { + return 1; + } + LocalTime now = LocalTime.now(ZoneId.of("Asia/Seoul")); + LocalTime start = LocalTime.parse(doubleEventStart); + LocalTime end = LocalTime.parse(doubleEventEnd); + boolean isEventTime = !now.isBefore(start) && now.isBefore(end); + if (isEventTime) { + log.debug("[DoubleEvent] 2배 이벤트 적용 중 now={}", now); + } + return isEventTime ? 2 : 1; + } + private void checkRateLimit(String clientIp, String deviceId, Long boothId) { String rateLimitKey = RATE_LIMIT_KEY.formatted(clientIp, deviceId, boothId); log.debug("[RateLimit] key={}", rateLimitKey); diff --git a/src/main/java/kr/co/knuserver/global/handler/GlobalExceptionHandler.java b/src/main/java/kr/co/knuserver/global/handler/GlobalExceptionHandler.java index 604ec5f..905ad0c 100644 --- a/src/main/java/kr/co/knuserver/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/kr/co/knuserver/global/handler/GlobalExceptionHandler.java @@ -14,6 +14,7 @@ import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.context.request.async.AsyncRequestNotUsableException; import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @Slf4j @RestControllerAdvice @@ -26,6 +27,13 @@ protected ResponseEntity handleMethodArgumentNotValidException(Meth return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + protected ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { + log.debug("[400] Type Mismatch: {}", e.getMessage()); + final ApiResponse response = ApiResponse.error(BusinessErrorCode.INVALID_INPUT_VALUE); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + @ExceptionHandler(ServletRequestBindingException.class) protected ResponseEntity handleServletRequestBindingException(ServletRequestBindingException e) { log.debug("[400] Invalid Access: {}", e.getMessage());