diff --git a/src/main/java/com/threestar/trainus/domain/lesson/issue/LegacyLessonAdmissionScheduler.java b/src/main/java/com/threestar/trainus/domain/lesson/issue/LegacyLessonAdmissionScheduler.java deleted file mode 100644 index 25dbec2d..00000000 --- a/src/main/java/com/threestar/trainus/domain/lesson/issue/LegacyLessonAdmissionScheduler.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.threestar.trainus.domain.lesson.issue; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Profile; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Profile("consumer & legacy") -@Slf4j -@Component -@RequiredArgsConstructor -public class LegacyLessonAdmissionScheduler { - - @Qualifier("coreRedisTemplate") - private final StringRedisTemplate coreRedisTemplate; - - @Qualifier("mqRedisTemplate") - private final StringRedisTemplate mqRedisTemplate; - - private final LessonWaitingRoomService waitingRoomService; - - private static final long ADMIT_BATCH_SIZE = 1000L; - - @Scheduled(fixedDelay = 500) - public void admitUsers() { - Set activeLessonIds = coreRedisTemplate.opsForSet().members(LessonApplyStreamConstant.DIRTY_SET_KEY); - - if (activeLessonIds == null || activeLessonIds.isEmpty()) { - return; - } - - for (String lessonIdStr : activeLessonIds) { - Long lessonId = Long.parseLong(lessonIdStr); - processAdmissionForLesson(lessonId); - } - } - - private void processAdmissionForLesson(Long lessonId) { - // 해당 레슨 대기열에서 인원 추출 - List requestIds = waitingRoomService.dequeue(lessonId, ADMIT_BATCH_SIZE); - - if (requestIds.isEmpty()) { - return; - } - - // MGET 방식으로 벌크 GET - List statusKeys = requestIds.stream().map(id -> LessonApplyStreamConstant.STATUS_PREFIX + id).toList(); - List statusInfos = mqRedisTemplate.opsForValue().multiGet(statusKeys); - - if (statusInfos == null) - return; - - // 상태 변경 및 스트림 추가 - for (int i = 0; i < statusKeys.size(); i++) { - String info = statusInfos.get(i); - if (info == null) - continue; - - String[] parts = info.split(":"); - if (parts.length < 3) - continue; - - String requestId = requestIds.get(i); - Long userId = Long.parseLong(parts[2]); - Long originalTimestamp = parts.length >= 4 ? Long.parseLong(parts[3]) : System.currentTimeMillis(); - - // 상태 변경 - String statusKey = statusKeys.get(i); - String processingInfo = String.format("%s:%d:%d:%d", LessonApplyStreamConstant.STATUS_PROCESSING, lessonId, - userId, originalTimestamp); - mqRedisTemplate.opsForValue() - .set(statusKey, processingInfo, - java.time.Duration.ofMinutes(LessonApplyStreamConstant.STATUS_TTL_MINUTE)); - - // 스트림 추가 - Map content = new HashMap<>(); - content.put("lessonId", String.valueOf(lessonId)); - content.put("userId", String.valueOf(userId)); - content.put("requestId", requestId); - content.put("timestamp", String.valueOf(originalTimestamp)); - mqRedisTemplate.opsForStream().add(LessonApplyStreamConstant.STREAM_KEY, content); - } - - log.info("[Legacy] Admitted {} users to MQ via iteration for lesson: {}", requestIds.size(), lessonId); - } -} diff --git a/src/main/java/com/threestar/trainus/domain/lesson/issue/LegacyLessonApplyConsumer.java b/src/main/java/com/threestar/trainus/domain/lesson/issue/LegacyLessonApplyConsumer.java deleted file mode 100644 index e0b582b7..00000000 --- a/src/main/java/com/threestar/trainus/domain/lesson/issue/LegacyLessonApplyConsumer.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.threestar.trainus.domain.lesson.issue; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Profile; -import org.springframework.data.redis.connection.stream.MapRecord; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.stream.StreamListener; -import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; - -@Profile("consumer & legacy") -@Slf4j -@Component -@RequiredArgsConstructor -public class LegacyLessonApplyConsumer implements StreamListener> { - - @Qualifier("mqRedisTemplate") - private final StringRedisTemplate mqRedisTemplate; - - private final LessonApplyService lessonApplyService; - - private static final String STREAM_KEY = LessonApplyStreamConstant.STREAM_KEY; - private static final String GROUP = LessonApplyStreamConstant.GROUP; - - @Override - public void onMessage(MapRecord message) { - Map value = message.getValue(); - - Long lessonId = Long.parseLong(value.get("lessonId")); - Long userId = Long.parseLong(value.get("userId")); - String requestId = value.get("requestId"); - Long timestamp = Long.parseLong(value.get("timestamp")); - - try { - // DB 저장 - lessonApplyService.apply(lessonId, userId, requestId, timestamp); - - // Redis ACK 및 DELETE - mqRedisTemplate.opsForStream().acknowledge(STREAM_KEY, GROUP, message.getId()); - mqRedisTemplate.opsForStream().delete(STREAM_KEY, message.getId()); - - log.info("[Legacy] Consumer processed single message: {}", requestId); - } catch (Exception e) { - log.error("[Legacy] Consumer failed: {}. Error: {}", requestId, e.getMessage()); - mqRedisTemplate.opsForStream().acknowledge(STREAM_KEY, GROUP, message.getId()); - mqRedisTemplate.opsForStream().delete(STREAM_KEY, message.getId()); - } - } -} diff --git a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonAdmissionScheduler.java b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonAdmissionScheduler.java index 9219319f..0cfc5e8d 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonAdmissionScheduler.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonAdmissionScheduler.java @@ -17,7 +17,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -@Profile("consumer & !legacy") +@Profile("consumer") @Slf4j @Component @RequiredArgsConstructor diff --git a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java index 452e30c3..ee1934e0 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java @@ -18,7 +18,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; -@Profile("consumer & !legacy") +@Profile("consumer") @Slf4j @Component @RequiredArgsConstructor diff --git a/src/main/java/com/threestar/trainus/domain/lesson/student/controller/StudentLessonController.java b/src/main/java/com/threestar/trainus/domain/lesson/student/controller/StudentLessonController.java index 2408c452..60da8251 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/student/controller/StudentLessonController.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/student/controller/StudentLessonController.java @@ -24,14 +24,11 @@ import com.threestar.trainus.domain.lesson.teacher.entity.Category; import com.threestar.trainus.global.annotation.LoginUser; import com.threestar.trainus.global.dto.PageRequestDto; -import com.threestar.trainus.global.exception.domain.ErrorCode; -import com.threestar.trainus.global.exception.handler.BusinessException; import com.threestar.trainus.global.unit.BaseResponse; import com.threestar.trainus.global.unit.PagedResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; @@ -137,13 +134,8 @@ public ResponseEntity> getMyLes @RequestParam(defaultValue = "5") @Min(value = 1, message = "limit는 1 이상이어야 합니다.") @Max(value = 100, message = "limit는 100 이하여야 합니다.") int limit, @RequestParam(defaultValue = "ALL") String status, - HttpSession session + @LoginUser Long userId ) { - Long userId = (Long)session.getAttribute("LOGIN_USER"); - if (userId == null) { - throw new BusinessException(ErrorCode.AUTHENTICATION_REQUIRED); - } - MyLessonApplicationListResponseDto serviceResponse = studentLessonService.getMyLessonApplications(userId, page, limit, status); MyLessonApplicationListWrapperDto response = new MyLessonApplicationListWrapperDto( diff --git a/src/main/java/com/threestar/trainus/domain/test/controller/TestConcurrencyController.java b/src/main/java/com/threestar/trainus/domain/test/controller/TestConcurrencyController.java index 99926f58..a5b3bf28 100644 --- a/src/main/java/com/threestar/trainus/domain/test/controller/TestConcurrencyController.java +++ b/src/main/java/com/threestar/trainus/domain/test/controller/TestConcurrencyController.java @@ -4,6 +4,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.context.annotation.Profile; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,6 +28,7 @@ import lombok.RequiredArgsConstructor; @Tag(name = "동시성 테스트 API", description = "선착순 기능 테스트를 위한 API") +@Profile({"local", "test"}) @RestController @RequestMapping("/test") @RequiredArgsConstructor diff --git a/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java b/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java index 5d0604c0..eabbf6a9 100644 --- a/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java +++ b/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java @@ -8,6 +8,7 @@ import java.util.Set; import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -26,7 +27,6 @@ import com.threestar.trainus.domain.metadata.entity.ProfileMetadata; import com.threestar.trainus.domain.metadata.mapper.ProfileMetadataMapper; import com.threestar.trainus.domain.metadata.repository.ProfileMetadataRepository; -import com.threestar.trainus.domain.profile.entity.Profile; import com.threestar.trainus.domain.profile.mapper.ProfileMapper; import com.threestar.trainus.domain.profile.repository.ProfileRepository; import com.threestar.trainus.domain.review.entity.Review; @@ -40,7 +40,7 @@ @Slf4j @Component -@org.springframework.context.annotation.Profile("dev") // dev 프로필에서만 실행 +@Profile({"local", "test"}) @RequiredArgsConstructor public class MockDataInitializer implements CommandLineRunner { @@ -111,7 +111,7 @@ private List createInstructors() { User savedInstructor = userRepository.save(instructor); // Profile 생성 - Profile profile = ProfileMapper.toDefaultEntity(savedInstructor); + var profile = ProfileMapper.toDefaultEntity(savedInstructor); profile.updateProfileImage("https://example.com/instructor" + (i + 1) + ".jpg"); profile.updateProfileIntro(instructorNames[i] + "입니다. 최고의 레슨을 제공합니다!"); profileRepository.save(profile); @@ -138,7 +138,7 @@ private List createInstructors() { User savedAdmin = userRepository.save(admin); // 관리자 Profile 생성 - Profile adminProfile = ProfileMapper.toDefaultEntity(savedAdmin); + var adminProfile = ProfileMapper.toDefaultEntity(savedAdmin); adminProfile.updateProfileImage("https://example.com/admin.jpg"); profileRepository.save(adminProfile); @@ -165,7 +165,7 @@ private List createStudents() { User savedStudent = userRepository.save(student); // Profile 생성 - Profile profile = ProfileMapper.toDefaultEntity(savedStudent); + var profile = ProfileMapper.toDefaultEntity(savedStudent); profile.updateProfileImage("https://example.com/student" + (i + 1) + ".jpg"); profileRepository.save(profile); diff --git a/src/main/java/com/threestar/trainus/global/config/SwaggerConfig.java b/src/main/java/com/threestar/trainus/global/config/SwaggerConfig.java index 464a8940..823c071c 100644 --- a/src/main/java/com/threestar/trainus/global/config/SwaggerConfig.java +++ b/src/main/java/com/threestar/trainus/global/config/SwaggerConfig.java @@ -14,12 +14,12 @@ public class SwaggerConfig { @Bean public OpenAPI openApi() { return new OpenAPI() - .addSecurityItem(new SecurityRequirement().addList("session")) + .addSecurityItem(new SecurityRequirement().addList("bearerAuth")) .components(new Components() - .addSecuritySchemes("session", new SecurityScheme() - .type(SecurityScheme.Type.APIKEY) - .in(SecurityScheme.In.COOKIE) - .name("JSESSIONID") + .addSecuritySchemes("bearerAuth", new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") ) ) .info(apiInfo()); @@ -27,7 +27,7 @@ public OpenAPI openApi() { private Info apiInfo() { return new Info() - .title("FitMate API 문서") // API의 제목 + .title("TrainUs API 문서") // API의 제목 .description("운동 메이트 매칭 플랫폼의 API 명세서") // API에 대한 설명 .version("1.0.0"); // API의 버전 } diff --git a/src/main/java/com/threestar/trainus/global/config/security/SecurityConfig.java b/src/main/java/com/threestar/trainus/global/config/security/SecurityConfig.java index 63a65065..74862406 100644 --- a/src/main/java/com/threestar/trainus/global/config/security/SecurityConfig.java +++ b/src/main/java/com/threestar/trainus/global/config/security/SecurityConfig.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -29,10 +30,26 @@ public PasswordEncoder passwordEncoder() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests( - auth -> auth.requestMatchers("/api/v1/users/**", "/api/lessons/test-auth", "/swagger-ui/**", - "/v3/api-docs/**", "/api/v1/profiles/**", "/api/v1/lessons/**", "api/v1/coupons/**", - "/api/v1/comments/**", "/api/v1/reviews/**", "/api/v1/admin/**", "/api/v1/rankings/**", - "/api/v1/payments/**", "/test/**", "/health", "/actuator/**") + auth -> auth.requestMatchers( + "/api/v1/users/signup", + "/api/v1/users/login", + "/api/v1/users/verify/**", + "/swagger-ui/**", + "/v3/api-docs/**", + "/health", + "/actuator/**") + .permitAll() + .requestMatchers(HttpMethod.GET, + "/api/v1/profiles/*", + "/api/v1/profiles/*/created-lessons", + "/api/v1/lessons", + "/api/v1/lessons/search/nearby", + "/api/v1/lessons/*", + "/api/v1/lessons/summary/*", + "/api/v1/lessons/apply/status/*", + "/api/v1/comments/**", + "/api/v1/reviews/**", + "/api/v1/rankings/**") .permitAll() .requestMatchers("/api/v1/admin/**") .hasRole("ADMIN") diff --git a/src/main/java/com/threestar/trainus/global/controller/S3TestController.java b/src/main/java/com/threestar/trainus/global/controller/S3TestController.java index a8adab4a..a9d66d25 100644 --- a/src/main/java/com/threestar/trainus/global/controller/S3TestController.java +++ b/src/main/java/com/threestar/trainus/global/controller/S3TestController.java @@ -2,6 +2,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.context.annotation.Profile; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -15,6 +16,7 @@ import lombok.RequiredArgsConstructor; @Tag(name = "S3 테스트 컨트롤러", description = "S3 업로더 테스트용 컨트롤러입니다.") +@Profile({"local", "test"}) @RestController @RequestMapping("/api/v1/test/s3") @RequiredArgsConstructor @@ -38,4 +40,4 @@ public ResponseEntity upload( return ResponseEntity.badRequest().body("업로드 실패: " + e.getMessage()); } } -} \ No newline at end of file +}