-
Notifications
You must be signed in to change notification settings - Fork 0
[BE] 대기열 1단계 구현 완 #313
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
base: develop
Are you sure you want to change the base?
[BE] 대기열 1단계 구현 완 #313
Changes from all commits
dd9cfc2
0780a5e
ed5e177
451e2cc
da1b05d
a81a99f
378ce08
f6e3e6c
a19adef
65d98a2
0099ef6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.jishop.config; | ||
|
|
||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.scheduling.annotation.EnableAsync; | ||
| import org.springframework.scheduling.annotation.EnableScheduling; | ||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||
|
|
||
| import java.util.concurrent.Executor; | ||
|
|
||
| @EnableAsync | ||
| @Configuration | ||
| @EnableScheduling | ||
| public class AsyncConfig { | ||
|
|
||
| @Bean | ||
| public Executor taskExecutor() { | ||
| // Spring 제공 스레드 풀 정의 | ||
| ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||
| // 기본 스레드 수(유지되는 최소 스레드 수) | ||
| executor.setCorePoolSize(5); | ||
| // 최대 스레드 수(동시 실행 가능한 스레드 수) | ||
| executor.setMaxPoolSize(10); | ||
| // 작업을 큐에 최대로 대기시킬수 있는 허용량 | ||
| executor.setQueueCapacity(100); | ||
| // 생성된 스레드 이름 | ||
| executor.setThreadNamePrefix("TaskExecutor-"); | ||
| // 쓰레드 풀 초기화 후 리턴 | ||
| executor.initialize(); | ||
| return executor; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,11 +10,13 @@ | |
| import jakarta.servlet.http.HttpSession; | ||
| import org.springframework.http.ResponseEntity; | ||
|
|
||
| import java.util.concurrent.ExecutionException; | ||
|
|
||
| @Tag(name = "로컬 로그인 API") | ||
| public interface AuthController { | ||
| @Operation(summary = "로컬 회원 로그인") | ||
| ResponseEntity<String> signIn(SignInFormRequest request, HttpServletRequest httpRequest, | ||
| HttpServletResponse response); | ||
| ResponseEntity<?> signIn(SignInFormRequest request, HttpServletRequest httpRequest, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 <?>를 사용하기보다는 명확한 타입을 정해놓고 메서드를 쪼개는 방식을 선호합니다. 아니면 customReponse< T > DTO를 생성해서 적절한 응답을 만들어주는것은 어떨까요? |
||
| HttpServletResponse response) throws ExecutionException, InterruptedException; | ||
| @Operation(summary = "회원 로그아웃") | ||
| ResponseEntity<Void> logout(User user,HttpServletRequest request); | ||
| @Operation(summary = "로그인 상태 체크") | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
| import com.jishop.member.dto.response.UserResponse; | ||
| import jakarta.servlet.http.HttpSession; | ||
|
|
||
| import java.util.concurrent.CompletableFuture; | ||
|
|
||
| public interface AuthService { | ||
|
|
||
| void signIn(SignInFormRequest form, HttpSession session); | ||
|
|
@@ -20,4 +22,6 @@ public interface AuthService { | |
| void updateAdSMSAgree(User user, UserAdSMSRequest request); | ||
| void updateAdEmailAgree(User user, UserAdEmailRequest request); | ||
| void logout(User user); | ||
| CompletableFuture<String> signInType(SignInFormRequest request, HttpSession session); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CompletableFuture 사용하신 이유와 무엇인지 궁금합니다.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 대기열은 무엇인지 어떻게 작동하는 건가요? |
||
| User attemptLogin(SignInFormRequest form); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,13 +7,20 @@ | |
| import com.jishop.member.dto.response.UserResponse; | ||
| import com.jishop.member.repository.UserRepository; | ||
| import com.jishop.member.service.AuthService; | ||
| import com.jishop.queue.domain.TaskType; | ||
| import com.jishop.queue.service.QueueService; | ||
| import com.jishop.queue.service.TaskProducer; | ||
| import jakarta.servlet.http.HttpSession; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.CompletableFuture; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.function.Consumer; | ||
|
|
||
|
|
@@ -22,8 +29,10 @@ | |
| @RequiredArgsConstructor | ||
| public class AuthServiceImpl implements AuthService { | ||
|
|
||
| private final UserRepository userRepository; | ||
| private final QueueService queueService; | ||
| private final PasswordEncoder passwordEncoder; | ||
| private final UserRepository userRepository; | ||
| private final TaskProducer taskProducer; | ||
| private final RedisTemplate<String, Object> redisTemplate; | ||
|
|
||
| @Override | ||
|
|
@@ -134,5 +143,33 @@ public void logout(User user) { | |
| String cacheKey = "user::" + user.getId(); | ||
| redisTemplate.delete(cacheKey); | ||
| } | ||
|
|
||
| public CompletableFuture<String> signInType(SignInFormRequest request, HttpSession session) { | ||
| if (queueService.useQueue()) { | ||
| Map<String, Object> payload = Map.of( | ||
| "loginId", request.loginId(), | ||
| "password", request.password(), | ||
| "sessionId", session.getId() // "sessionId"로 키 이름 통일 | ||
| ); | ||
| return taskProducer.submitTask(TaskType.LOGIN, payload); | ||
| } else { | ||
| // 대기열 사용하지 않을 때는 즉시 로그인 처리 | ||
| this.signIn(request, session); | ||
| return CompletableFuture.completedFuture("immediate-login-" + UUID.randomUUID()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 각 사용자를 구분하기 위해 UUID를 사용하신걸까요? |
||
| } | ||
| } | ||
|
|
||
| // 대기열을 위한 메서드 추가 | ||
| public User attemptLogin(SignInFormRequest form) { | ||
| User user = userRepository.findByLoginId(form.loginId()) | ||
| .orElseThrow(() -> new DomainException(ErrorType.USER_NOT_FOUND)); | ||
|
|
||
| if(!passwordEncoder.matches(form.password(), user.getPassword())) { | ||
| throw new DomainException(ErrorType.USER_NOT_FOUND); | ||
| } | ||
| if(user.isDeleteStatus()) throw new DomainException(ErrorType.USER_NOT_FOUND); | ||
|
|
||
| return user; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.jishop.queue.controller; | ||
|
|
||
| import com.jishop.queue.dto.TaskRequest; | ||
| import org.springframework.http.ResponseEntity; | ||
|
|
||
| public interface TaskController { | ||
|
|
||
| ResponseEntity<?> addTask(TaskRequest request); | ||
| ResponseEntity<?> getTaskStatus(String taskId); | ||
| ResponseEntity<?> getQueueStatus(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package com.jishop.queue.controller; | ||
|
|
||
| import com.jishop.queue.domain.Task; | ||
| import com.jishop.queue.dto.TaskRequest; | ||
| import com.jishop.queue.service.QueueService; | ||
| import com.jishop.queue.service.TaskProducer; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.Map; | ||
| import java.util.concurrent.CompletableFuture; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/pend") | ||
| public class TaskControllerImpl { | ||
|
|
||
| private final TaskProducer taskProducer; | ||
| private final QueueService queueService; | ||
|
|
||
| // 작업 추가 | ||
| @PostMapping("/tasks") | ||
| public ResponseEntity<?> addTask(@RequestBody TaskRequest request) { | ||
| try{ | ||
| CompletableFuture<String> future = taskProducer.submitTask( | ||
| request.type(), request.payload()); | ||
|
|
||
| if(queueService.getQueueSize()> 300) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 300은 대기자가 300명 이상일 때 인가요..? 아니면 무슨 의미일까요? 이름으로 알기 쉽게 상수로 관리하는 건 어떻게 생각하시나요? 🤔 |
||
| return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS) | ||
| .body(Map.of("taskId", future.get(), "status", "queued", | ||
| "message", "잠시 후 다시 시도해주세요!")); | ||
| } | ||
| return ResponseEntity.accepted() | ||
| .body(Map.of("taskId", future.get(), "status", "대기열 등록 완료")); | ||
| } catch (Exception e) { | ||
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
| .body(Map.of("error", e.getMessage())); | ||
| } | ||
| } | ||
|
|
||
| // 작업 상태 조회 | ||
| @GetMapping("/tasks/{taskId}/status") | ||
| public ResponseEntity<?> getTaskStatus(@PathVariable String taskId) { | ||
| Task task = queueService.getTaskById(taskId); | ||
| if(task == null){ | ||
| return ResponseEntity.status(HttpStatus.NOT_FOUND) | ||
| .body(Map.of("message", "해당 작업을 찾을 수 없습니다!")); | ||
| } | ||
| return ResponseEntity.ok(Map.of("taskId", taskId, "status", task.getStatus())); | ||
| } | ||
|
|
||
| // 큐 상태 조회 | ||
| @GetMapping("/queue/status") | ||
| public ResponseEntity<?> getQueueStatus() { | ||
| return ResponseEntity.ok(Map.of("queueSize", queueService.getQueueSize(), | ||
| "timestamp", LocalDateTime.now())); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
taskExecutor 메서드를 추후에 다른곳에서 비동기 실행하기 위해서 미리 붙여 놓으신건가요?