Skip to content

Commit 2a5bd10

Browse files
authored
feat: 동시성 테스트를 위한 api 생성 (#182)
* feat: 동시성 테스트를 위한 컨트롤러 작성 * feat: 동시성 테스트를 위한 DTO 작성 * feat: 동시성 테스트를 위한 유저 자동생성 로직 서비스 작성 * feat: 동시성 테스트를 위한 Spring SecurityFilterChain 수정
1 parent d719ad9 commit 2a5bd10

File tree

4 files changed

+113
-1
lines changed

4 files changed

+113
-1
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.threestar.trainus.domain.test.controller;
2+
3+
import com.threestar.trainus.domain.coupon.user.dto.CreateUserCouponResponseDto;
4+
import com.threestar.trainus.domain.coupon.user.service.CouponService;
5+
import com.threestar.trainus.domain.lesson.student.dto.LessonApplicationResponseDto;
6+
import com.threestar.trainus.domain.lesson.student.service.StudentLessonService;
7+
import com.threestar.trainus.domain.test.dto.TestRequestDto;
8+
import com.threestar.trainus.domain.test.service.TestUserService;
9+
import com.threestar.trainus.domain.user.entity.User;
10+
import com.threestar.trainus.global.unit.BaseResponse;
11+
12+
import io.swagger.v3.oas.annotations.Operation;
13+
import io.swagger.v3.oas.annotations.tags.Tag;
14+
import lombok.RequiredArgsConstructor;
15+
16+
import org.springframework.http.HttpStatus;
17+
import org.springframework.http.ResponseEntity;
18+
import org.springframework.web.bind.annotation.*;
19+
20+
@Tag(name = "동시성 테스트 API", description = "선착순 기능 테스트를 위한 API")
21+
@RestController
22+
@RequestMapping("/test")
23+
@RequiredArgsConstructor
24+
public class TestConcurrencyController {
25+
26+
private final CouponService couponService;
27+
private final StudentLessonService studentLessonService;
28+
private final TestUserService testUserService;
29+
30+
@PostMapping("/coupons/{couponId}")
31+
@Operation(summary = "쿠폰 발급 동시성 테스트", description = "쿠폰을 발급받는 테스트 API")
32+
public ResponseEntity<BaseResponse<CreateUserCouponResponseDto>> issueCouponForTest(
33+
@PathVariable Long couponId,
34+
@RequestBody TestRequestDto testRequestDto
35+
) {
36+
User user = testUserService.findOrCreateUser(testRequestDto.getUserId());
37+
CreateUserCouponResponseDto responseDto = couponService.createUserCoupon(user.getId(), couponId);
38+
return BaseResponse.ok("쿠폰 발급 완료", responseDto, HttpStatus.CREATED);
39+
}
40+
41+
@PostMapping("/lessons/{lessonId}/application")
42+
@Operation(summary = "레슨 신청 동시성 테스트", description = "레슨을 신청하는 테스트 API")
43+
public ResponseEntity<BaseResponse<LessonApplicationResponseDto>> applyToLessonForTest(
44+
@PathVariable Long lessonId,
45+
@RequestBody TestRequestDto testRequestDto
46+
) {
47+
User user = testUserService.findOrCreateUser(testRequestDto.getUserId());
48+
LessonApplicationResponseDto response = studentLessonService.applyToLessonWithLock(lessonId, user.getId());
49+
return BaseResponse.ok("레슨 신청 완료", response, HttpStatus.OK);
50+
}
51+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.threestar.trainus.domain.test.dto;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
6+
@Getter
7+
@NoArgsConstructor
8+
public class TestRequestDto {
9+
private Long userId;
10+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.threestar.trainus.domain.test.service;
2+
3+
import com.threestar.trainus.domain.profile.service.ProfileFacadeService;
4+
import com.threestar.trainus.domain.user.entity.User;
5+
import com.threestar.trainus.domain.user.entity.UserRole;
6+
import com.threestar.trainus.domain.user.repository.UserRepository;
7+
8+
import lombok.RequiredArgsConstructor;
9+
10+
import org.springframework.security.crypto.password.PasswordEncoder;
11+
import org.springframework.stereotype.Service;
12+
import org.springframework.transaction.annotation.Transactional;
13+
14+
import java.util.Optional;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
public class TestUserService {
19+
20+
private final UserRepository userRepository;
21+
private final PasswordEncoder passwordEncoder;
22+
private final ProfileFacadeService profileFacadeService;
23+
24+
@Transactional
25+
public User findOrCreateUser(Long userId) {
26+
Optional<User> existingUser = userRepository.findById(userId);
27+
if (existingUser.isPresent()) {
28+
return existingUser.get();
29+
}
30+
String email = "testuser" + userId + "@example.com";
31+
String nickname = "testuser" + userId;
32+
33+
if (userRepository.existsByEmail(email) || userRepository.existsByNickname(nickname)) {
34+
return userRepository.findByEmail(email)
35+
.orElseThrow(() -> new IllegalStateException("테스트 유저 생성 실패"));
36+
}
37+
38+
String encodedPassword = passwordEncoder.encode("password");
39+
User newUser = User.builder()
40+
.email(email)
41+
.password(encodedPassword)
42+
.nickname(nickname)
43+
.role(UserRole.USER)
44+
.build();
45+
46+
User savedUser = userRepository.save(newUser);
47+
profileFacadeService.createDefaultProfile(savedUser);
48+
49+
return savedUser;
50+
}
51+
}

src/main/java/com/threestar/trainus/global/config/security/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
2727
.authorizeHttpRequests(auth -> auth
2828
.requestMatchers("/api/v1/users/**", "/api/lessons/test-auth", "/swagger-ui/**", "/v3/api-docs/**",
2929
"/api/v1/profiles/**", "/api/v1/lessons/**", "/api/v1/comments/**", "/api/v1/reviews/**",
30-
"/api/v1/rankings/**", "/api/v1/payments/**")
30+
"/api/v1/rankings/**", "/api/v1/payments/**", "/test/**")
3131
.permitAll()
3232
.requestMatchers("/api/v1/admin/**")
3333
.hasRole("ADMIN")

0 commit comments

Comments
 (0)