Skip to content

Commit a70d36f

Browse files
authored
feat:레슨생성시쿨타임제한추가
1 parent 9489521 commit a70d36f

File tree

4 files changed

+121
-1
lines changed

4 files changed

+121
-1
lines changed

src/main/java/com/threestar/trainus/domain/lesson/teacher/service/AdminLessonService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,15 @@ public class AdminLessonService {
5151
private final LessonImageRepository lessonImageRepository;
5252
private final LessonApplicationRepository lessonApplicationRepository;
5353
private final UserService userService;
54+
private final LessonCreationLimitService lessonCreationLimitService;
5455

5556
//레슨 생성
5657
public LessonResponseDto createLesson(LessonCreateRequestDto requestDto, Long userId) {
5758
User user = userService.getUserById(userId);
5859

60+
//레슨 생성 제한 확인 및 쿨타임 설정
61+
lessonCreationLimitService.checkAndSetCreationLimit(userId);
62+
5963
// 생성시에 필요한 검증을 진행
6064
validateLessonCreation(requestDto, userId);
6165

@@ -146,7 +150,7 @@ public ParticipantListResponseDto getLessonParticipants(
146150

147151
Page<LessonApplication> participantPage = lessonApplicationRepository
148152
.findApprovedParticipantsWithUserAndProfile(lesson, pageable);
149-
153+
150154
return LessonParticipantMapper.toParticipantsResponseDto(
151155
participantPage.getContent(),
152156
participantPage.getTotalElements()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.threestar.trainus.domain.lesson.teacher.service;
2+
3+
import java.time.Duration;
4+
5+
import org.springframework.data.redis.core.RedisTemplate;
6+
import org.springframework.stereotype.Service;
7+
8+
import com.threestar.trainus.global.exception.domain.ErrorCode;
9+
import com.threestar.trainus.global.exception.handler.BusinessException;
10+
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
14+
/**
15+
* 레슨 생성 제한 서비스
16+
* Redis를 사용해 강사의 레슨 생성에 쿨타임을 적용 ->1분
17+
*/
18+
@Slf4j
19+
@Service
20+
@RequiredArgsConstructor
21+
public class LessonCreationLimitService {
22+
23+
private final RedisTemplate<String, String> redisTemplate;
24+
25+
// 레슨 생성 쿨타임 (1분)
26+
private static final Duration CREATION_COOLTIME = Duration.ofMinutes(1);
27+
28+
//redis 키 접두사
29+
private static final String LESSON_CREATION_KEY_PREFIX = "lesson_creation_limit:";
30+
31+
//레슨 생성 가능 여부 확인 + 쿨타임 설정
32+
public void checkAndSetCreationLimit(Long userId) {
33+
String key = generateRedisKey(userId);
34+
35+
// 이미 쿨타임이 설정되어 있는지 확인
36+
if (redisTemplate.hasKey(key)) {
37+
Long remainingTtl = redisTemplate.getExpire(key);
38+
log.info("레슨 생성 제한 - 사용자 ID: {}, 남은 시간: {}초", userId, remainingTtl);
39+
throw new BusinessException(ErrorCode.LESSON_CREATION_TOO_FREQUENT);
40+
}
41+
42+
// 쿨타임 설정
43+
redisTemplate.opsForValue().set(key, "restricted", CREATION_COOLTIME);
44+
log.info("레슨 생성 쿨타임 설정 - 사용자 ID: {}, 지속시간: {}분", userId, CREATION_COOLTIME.toMinutes());
45+
}
46+
47+
// Redis 키를 생성
48+
private String generateRedisKey(Long userId) {
49+
return LESSON_CREATION_KEY_PREFIX + userId;
50+
}
51+
}

src/main/java/com/threestar/trainus/global/exception/domain/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public enum ErrorCode {
7777
LESSON_DELETE_STATUS_INVALID(HttpStatus.BAD_REQUEST, "모집중인 레슨만 삭제할 수 있습니다."),
7878
LESSON_DELETE_HAS_PARTICIPANTS(HttpStatus.BAD_REQUEST, "참가자가 있는 레슨은 삭제할 수 없습니다."),
7979
LESSON_TIME_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "레슨 시작 12시간 전이므로 수정/삭제할 수 없습니다."),
80+
LESSON_CREATION_TOO_FREQUENT(HttpStatus.TOO_MANY_REQUESTS, "레슨 생성은 1분에 1번만 가능합니다. 잠시 후 다시 시도해주세요."),
8081

8182
// 403 Forbidden
8283
LESSON_DELETE_FORBIDDEN(HttpStatus.FORBIDDEN, "레슨 삭제 권한이 없습니다. 강사만 삭제할 수 있습니다."),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.threestar.trainus.domain.lesson.teacher.service;
2+
3+
import static org.assertj.core.api.Assertions.*;
4+
import static org.mockito.Mockito.*;
5+
6+
import java.time.Duration;
7+
8+
import org.junit.jupiter.api.DisplayName;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.ExtendWith;
11+
import org.mockito.InjectMocks;
12+
import org.mockito.Mock;
13+
import org.mockito.junit.jupiter.MockitoExtension;
14+
import org.springframework.data.redis.core.RedisTemplate;
15+
import org.springframework.data.redis.core.ValueOperations;
16+
17+
import com.threestar.trainus.global.exception.domain.ErrorCode;
18+
import com.threestar.trainus.global.exception.handler.BusinessException;
19+
20+
@ExtendWith(MockitoExtension.class)
21+
class LessonCreationLimitServiceTest {
22+
@Mock
23+
private RedisTemplate<String, String> redisTemplate;
24+
25+
@Mock
26+
private ValueOperations<String, String> valueOperations;
27+
28+
@InjectMocks
29+
private LessonCreationLimitService lessonCreationLimitService;
30+
31+
@Test
32+
@DisplayName("첫 레슨 생성 시 제한 없이 성공한다")
33+
void checkAndSetCreationLimit_FirstTime_Success() {
34+
Long userId = 1L;
35+
String expectedKey = "lesson_creation_limit:" + userId;
36+
37+
when(redisTemplate.hasKey(expectedKey)).thenReturn(false);
38+
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
39+
40+
assertThatCode(() -> lessonCreationLimitService.checkAndSetCreationLimit(userId))
41+
.doesNotThrowAnyException();
42+
43+
verify(redisTemplate).hasKey(expectedKey);
44+
verify(valueOperations).set(expectedKey, "restricted", Duration.ofMinutes(1));
45+
}
46+
47+
@Test
48+
@DisplayName("쿨타임이 남아있을 때 레슨 생성을 제한한다")
49+
void checkAndSetCreationLimit_WithinCooltime_ThrowsException() {
50+
Long userId = 1L;
51+
String expectedKey = "lesson_creation_limit:" + userId;
52+
53+
when(redisTemplate.hasKey(expectedKey)).thenReturn(true);
54+
when(redisTemplate.getExpire(expectedKey)).thenReturn(30L); // 30초 남음
55+
56+
assertThatThrownBy(() -> lessonCreationLimitService.checkAndSetCreationLimit(userId))
57+
.isInstanceOf(BusinessException.class)
58+
.extracting(e -> ((BusinessException)e).getErrorCode())
59+
.isEqualTo(ErrorCode.LESSON_CREATION_TOO_FREQUENT);
60+
61+
verify(redisTemplate).hasKey(expectedKey);
62+
verify(redisTemplate, never()).opsForValue();
63+
}
64+
}

0 commit comments

Comments
 (0)