diff --git a/src/main/java/org/cotato/csquiz/api/socket/controller/SocketController.java b/src/main/java/org/cotato/csquiz/api/socket/controller/SocketController.java index 45b634a7..ece29e4e 100644 --- a/src/main/java/org/cotato/csquiz/api/socket/controller/SocketController.java +++ b/src/main/java/org/cotato/csquiz/api/socket/controller/SocketController.java @@ -7,8 +7,7 @@ import org.cotato.csquiz.api.socket.dto.QuizOpenRequest; import org.cotato.csquiz.api.socket.dto.QuizSocketRequest; import org.cotato.csquiz.api.socket.dto.SocketTokenDto; -import org.cotato.csquiz.domain.education.service.RecordService; -import org.cotato.csquiz.domain.education.service.SocketService; +import org.cotato.csquiz.domain.education.service.*; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -25,43 +24,45 @@ public class SocketController { private final SocketService socketService; + private final EducationService educationService; private final RecordService recordService; + private final QuizSolveService quizSolveService; @PatchMapping("/start/csquiz") public ResponseEntity openCSQuiz(@RequestBody @Valid QuizOpenRequest request) { - socketService.openCSQuiz(request); + educationService.openCSQuiz(request); recordService.saveAnswersToCache(request); return ResponseEntity.noContent().build(); } @PatchMapping("/access") public ResponseEntity accessQuiz(@RequestBody @Valid QuizSocketRequest request) { - socketService.accessQuiz(request); + quizSolveService.accessQuiz(request); recordService.saveAnswer(request); return ResponseEntity.noContent().build(); } @PatchMapping("/start") public ResponseEntity startQuizSolve(@RequestBody @Valid QuizSocketRequest request) { - socketService.startQuizSolve(request); + quizSolveService.startQuizSolve(request); return ResponseEntity.noContent().build(); } @PatchMapping("/deny") public ResponseEntity denyQuiz(@RequestBody @Valid QuizSocketRequest request) { - socketService.denyQuiz(request); + quizSolveService.denyQuiz(request); return ResponseEntity.noContent().build(); } @PatchMapping("/stop") public ResponseEntity stopQuizSolve(@RequestBody @Valid QuizSocketRequest request) { - socketService.stopQuizSolve(request); + quizSolveService.stopQuizSolve(request); return ResponseEntity.noContent().build(); } @PatchMapping("/close/csquiz") - public ResponseEntity stopAllQuiz(@RequestBody @Valid EducationCloseRequest request) { - socketService.stopAllQuiz(request); + public ResponseEntity closeEducation(@RequestBody @Valid EducationCloseRequest request) { + educationService.closeEducation(request); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/cotato/csquiz/common/SchedulerService.java b/src/main/java/org/cotato/csquiz/common/SchedulerService.java index fed7bb5a..ddf97bf6 100644 --- a/src/main/java/org/cotato/csquiz/common/SchedulerService.java +++ b/src/main/java/org/cotato/csquiz/common/SchedulerService.java @@ -5,6 +5,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.cotato.csquiz.domain.auth.entity.RefusedMember; +import org.cotato.csquiz.domain.education.service.EducationService; +import org.cotato.csquiz.domain.education.service.QuizService; import org.cotato.csquiz.domain.education.service.SocketService; import org.cotato.csquiz.domain.auth.enums.MemberRole; import org.cotato.csquiz.domain.auth.repository.MemberRepository; @@ -21,7 +23,7 @@ public class SchedulerService { private final RefusedMemberRepository refusedMemberRepository; private final MemberRepository memberRepository; - private final SocketService socketService; + private final EducationService educationService; @Transactional @Scheduled(cron = "0 0 0 * * *") @@ -42,7 +44,7 @@ public void updateRefusedMember() { @Transactional @Scheduled(cron = "0 0 2 * * SAT") public void closeAllCsQuiz() { - socketService.closeAllFlags(); + educationService.closeAllFlags(); log.info("[ CS 퀴즈 모두 닫기 Scheduler 완료 ]"); } } diff --git a/src/main/java/org/cotato/csquiz/common/websocket/WebSocketHandler.java b/src/main/java/org/cotato/csquiz/common/websocket/WebSocketHandler.java index 00f65333..88b545f9 100644 --- a/src/main/java/org/cotato/csquiz/common/websocket/WebSocketHandler.java +++ b/src/main/java/org/cotato/csquiz/common/websocket/WebSocketHandler.java @@ -132,8 +132,8 @@ public void startQuiz(Long quizId) { log.info("[풀이 신호 전송 후 사용자 : {}]", CLIENTS.keySet()); } - public void stopQuiz(Quiz quiz) { - QuizStopResponse response = QuizStopResponse.from(quiz.getId()); + public void stopQuiz(Long quizId) { + QuizStopResponse response = QuizStopResponse.from(quizId); for (WebSocketSession clientSession : CLIENTS.values()) { sendMessage(clientSession, response); } @@ -155,7 +155,7 @@ public void sendWinnerCommand(Long educationId) { } } - public void stopAllQuiz(Long educationId) { + public void stopEducation(Long educationId) { CsQuizStopResponse response = CsQuizStopResponse.from(EXIT_COMMAND, educationId); for (WebSocketSession clientSession : CLIENTS.values()) { sendMessage(clientSession, response); diff --git a/src/main/java/org/cotato/csquiz/domain/education/repository/QuizRepository.java b/src/main/java/org/cotato/csquiz/domain/education/repository/QuizRepository.java index dee1e5ed..c7451d0c 100644 --- a/src/main/java/org/cotato/csquiz/domain/education/repository/QuizRepository.java +++ b/src/main/java/org/cotato/csquiz/domain/education/repository/QuizRepository.java @@ -20,18 +20,14 @@ public interface QuizRepository extends JpaRepository { List findAllByEducationId(Long educationId); - List findAllByStatus(QuizStatus status); - Optional findByStatusAndEducationId(QuizStatus status, Long educationId); - List findAllByStart(QuizStatus quizStatus); + List findAllByStatusOrStart(QuizStatus status, QuizStatus start); @Transactional @Modifying @Query("select q from Quiz q where q.education.id in :educationIds") List findAllByEducationIdsInQuery(@Param("educationIds") List educationIds); - Optional findByEducationIdAndNumber(Long educationId, Integer i); - Optional findFirstByEducationOrderByNumberDesc(Education education); } diff --git a/src/main/java/org/cotato/csquiz/domain/education/service/EducationService.java b/src/main/java/org/cotato/csquiz/domain/education/service/EducationService.java index 3d6f2be1..72214014 100644 --- a/src/main/java/org/cotato/csquiz/domain/education/service/EducationService.java +++ b/src/main/java/org/cotato/csquiz/domain/education/service/EducationService.java @@ -11,8 +11,12 @@ import org.cotato.csquiz.api.education.dto.EducationIdOfQuizResponse; import org.cotato.csquiz.api.education.dto.FindEducationStatusResponse; import org.cotato.csquiz.api.education.dto.UpdateEducationRequest; +import org.cotato.csquiz.api.socket.dto.EducationCloseRequest; +import org.cotato.csquiz.api.socket.dto.QuizOpenRequest; import org.cotato.csquiz.domain.education.entity.Education; import org.cotato.csquiz.domain.education.entity.Quiz; +import org.cotato.csquiz.domain.education.enums.EducationStatus; +import org.cotato.csquiz.domain.education.enums.QuizStatus; import org.cotato.csquiz.domain.education.repository.EducationRepository; import org.cotato.csquiz.domain.education.repository.QuizRepository; import org.cotato.csquiz.domain.generation.entity.Session; @@ -31,6 +35,8 @@ public class EducationService { private final EducationRepository educationRepository; private final QuizRepository quizRepository; private final SessionRepository sessionRepository; + private final SocketService socketService; + @Transactional public CreateEducationResponse createEducation(CreateEducationRequest request) { @@ -80,12 +86,51 @@ private void validateNotEmpty(String newSubject) { .orElseThrow(() -> new AppException(ErrorCode.SUBJECT_INVALID)); } + @Transactional + public void openCSQuiz(QuizOpenRequest request) { + Education education = findEducationById(request.educationId()); + + checkEducationBefore(education); + + education.updateStatus(EducationStatus.ONGOING); + } + + private void checkEducationBefore(Education education) { + if (EducationStatus.BEFORE != education.getStatus()) { + throw new AppException(ErrorCode.EDUCATION_STATUS_NOT_BEFORE); + } + } + + @Transactional + public void closeAllFlags() { + quizRepository.findAllByStatusOrStart(QuizStatus.QUIZ_ON, QuizStatus.QUIZ_ON) + .forEach(quiz -> { + quiz.updateStatus(QuizStatus.QUIZ_OFF); + quiz.updateStart(QuizStatus.QUIZ_OFF); + }); + } + + @Transactional + public void closeEducation(EducationCloseRequest request) { + closeAllFlags(); + + Education education = findEducationById(request.educationId()); + + education.updateStatus(EducationStatus.FINISHED); + socketService.stopEducation(education.getId()); + } + public List findEducationListByGeneration(Long generationId) { return findAllEducationByGenerationId(generationId).stream() .map(AllEducationResponse::from) .toList(); } + private Education findEducationById(Long educationId) { + return educationRepository.findById(educationId) + .orElseThrow(() -> new EntityNotFoundException("해당 교육을 찾을 수 없습니다.")); + } + public EducationIdOfQuizResponse findEducationIdOfQuizId(Long quizId) { Quiz quiz = quizRepository.findById(quizId) .orElseThrow(() -> new EntityNotFoundException("해당 문제를 찾을 수 없습니다.")); diff --git a/src/main/java/org/cotato/csquiz/domain/education/service/QuizSolveService.java b/src/main/java/org/cotato/csquiz/domain/education/service/QuizSolveService.java new file mode 100644 index 00000000..2472e3ff --- /dev/null +++ b/src/main/java/org/cotato/csquiz/domain/education/service/QuizSolveService.java @@ -0,0 +1,101 @@ +package org.cotato.csquiz.domain.education.service; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.cotato.csquiz.api.socket.dto.QuizSocketRequest; +import org.cotato.csquiz.common.error.ErrorCode; +import org.cotato.csquiz.common.error.exception.AppException; +import org.cotato.csquiz.domain.education.entity.Education; +import org.cotato.csquiz.domain.education.entity.Quiz; +import org.cotato.csquiz.domain.education.enums.EducationStatus; +import org.cotato.csquiz.domain.education.enums.QuizStatus; +import org.cotato.csquiz.domain.education.repository.EducationRepository; +import org.cotato.csquiz.domain.education.repository.QuizRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class QuizSolveService { + + private final SocketService socketService; + private final EducationRepository educationRepository; + private final QuizRepository quizRepository; + private final EducationService educationService; + + @Transactional + public void accessQuiz(QuizSocketRequest request) { + Quiz quiz = findQuizById(request.quizId()); + + checkEducationOpen(quiz.getEducation()); + + educationService.closeAllFlags(); + + quiz.updateStatus(QuizStatus.QUIZ_ON); + socketService.accessQuiz(quiz.getId()); + } + + @Transactional + public void denyQuiz(QuizSocketRequest request) { + Quiz quiz = findQuizById(request.quizId()); + + checkEducationOpen(quiz.getEducation()); + + quiz.updateStatus(QuizStatus.QUIZ_OFF); + quiz.updateStart(QuizStatus.QUIZ_OFF); + } + + @Transactional + public void startQuizSolve(QuizSocketRequest request) { + Quiz quiz = findQuizById(request.quizId()); + + checkEducationOpen(quiz.getEducation()); + checkQuizIsStarted(quiz); + + sleepRandomTime(quiz); + quiz.updateStart(QuizStatus.QUIZ_ON); + + socketService.startQuizSolve(quiz.getId()); + } + + private void checkQuizIsStarted(Quiz quiz) { + if (quiz.getStatus().equals(QuizStatus.QUIZ_OFF)) { + throw new AppException(ErrorCode.QUIZ_ACCESS_DENIED); + } + } + + private void sleepRandomTime(Quiz quiz) { + try { + Thread.sleep(1000L * quiz.getAppearSecond()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @Transactional + public void stopQuizSolve(QuizSocketRequest request) { + Quiz quiz = findQuizById(request.quizId()); + checkEducationOpen(quiz.getEducation()); + + quiz.updateStart(QuizStatus.QUIZ_OFF); + socketService.stopQuizSolve(quiz.getId()); + } + + private void checkEducationOpen(Education education) { + if (EducationStatus.ONGOING != education.getStatus()) { + throw new AppException(ErrorCode.EDUCATION_CLOSED); + } + } + + private Quiz findQuizById(Long quizId) { + return quizRepository.findById(quizId) + .orElseThrow(() -> new EntityNotFoundException("해당 퀴즈를 찾을 수 없습니다.")); + } + + private Education findEducationById(Long educationId) { + return educationRepository.findById(educationId) + .orElseThrow(() -> new EntityNotFoundException("해당 교육을 찾을 수 없습니다.")); + } +} diff --git a/src/main/java/org/cotato/csquiz/domain/education/service/SocketService.java b/src/main/java/org/cotato/csquiz/domain/education/service/SocketService.java index 08cf36d1..1addf938 100644 --- a/src/main/java/org/cotato/csquiz/domain/education/service/SocketService.java +++ b/src/main/java/org/cotato/csquiz/domain/education/service/SocketService.java @@ -1,132 +1,36 @@ package org.cotato.csquiz.domain.education.service; -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.cotato.csquiz.api.socket.dto.EducationCloseRequest; -import org.cotato.csquiz.api.socket.dto.QuizOpenRequest; -import org.cotato.csquiz.api.socket.dto.QuizSocketRequest; import org.cotato.csquiz.api.socket.dto.SocketTokenDto; import org.cotato.csquiz.common.config.jwt.JwtTokenProvider; -import org.cotato.csquiz.domain.education.entity.Education; -import org.cotato.csquiz.domain.education.entity.Quiz; -import org.cotato.csquiz.domain.education.enums.QuizStatus; -import org.cotato.csquiz.domain.education.repository.EducationRepository; -import org.cotato.csquiz.domain.education.repository.QuizRepository; -import org.cotato.csquiz.domain.education.enums.EducationStatus; -import org.cotato.csquiz.common.error.exception.AppException; -import org.cotato.csquiz.common.error.ErrorCode; import org.cotato.csquiz.common.websocket.WebSocketHandler; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; @Service @RequiredArgsConstructor -@Transactional(readOnly = true) @Slf4j public class SocketService { private final WebSocketHandler webSocketHandler; - private final QuizRepository quizRepository; - private final EducationRepository educationRepository; private final JwtTokenProvider jwtTokenProvider; - @Transactional - public void openCSQuiz(QuizOpenRequest request) { - Education education = findEducationById(request.educationId()); - - checkEducationBefore(education); - - education.updateStatus(EducationStatus.ONGOING); - } - - private void checkEducationBefore(Education education) { - if (EducationStatus.BEFORE != education.getStatus()) { - throw new AppException(ErrorCode.EDUCATION_STATUS_NOT_BEFORE); - } - } - - @Transactional - public void accessQuiz(QuizSocketRequest request) { - Quiz quiz = findQuizById(request.quizId()); - - checkEducationOpen(quiz.getEducation()); - - makeAllStartFalse(); - makeAllStatusFalse(); - - quiz.updateStatus(QuizStatus.QUIZ_ON); - webSocketHandler.accessQuiz(quiz.getId()); - } - - @Transactional - public void startQuizSolve(QuizSocketRequest request) { - Quiz quiz = findQuizById(request.quizId()); - - checkEducationOpen(quiz.getEducation()); - checkQuizIsStarted(quiz); - - sleepRandomTime(quiz); - quiz.updateStart(QuizStatus.QUIZ_ON); - - webSocketHandler.startQuiz(quiz.getId()); - } - - private void checkQuizIsStarted(Quiz quiz) { - if (quiz.getStatus().equals(QuizStatus.QUIZ_OFF)) { - throw new AppException(ErrorCode.QUIZ_ACCESS_DENIED); - } - } - - private void sleepRandomTime(Quiz quiz) { - try { - Thread.sleep(1000L * quiz.getAppearSecond()); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + public void accessQuiz(Long quizId) { + webSocketHandler.accessQuiz(quizId); } - @Transactional - public void denyQuiz(QuizSocketRequest request) { - Quiz quiz = findQuizById(request.quizId()); - - checkEducationOpen(quiz.getEducation()); - - quiz.updateStatus(QuizStatus.QUIZ_OFF); - quiz.updateStart(QuizStatus.QUIZ_OFF); + public void startQuizSolve(Long quizId) { + webSocketHandler.startQuiz(quizId); } - @Transactional - public void stopQuizSolve(QuizSocketRequest request) { - Quiz quiz = findQuizById(request.quizId()); - checkEducationOpen(quiz.getEducation()); - - quiz.updateStart(QuizStatus.QUIZ_OFF); - - webSocketHandler.stopQuiz(quiz); + public void stopQuizSolve(Long quizId) { + webSocketHandler.stopQuiz(quizId); } - @Transactional - public void stopAllQuiz(EducationCloseRequest request) { - closeAllFlags(); - - Education education = findEducationById(request.educationId()); - - education.updateStatus(EducationStatus.FINISHED); - webSocketHandler.stopAllQuiz(education.getId()); - } - - @Transactional - public void closeAllFlags() { - makeAllStatusFalse(); - makeAllStartFalse(); + public void stopEducation(Long educationId) { + webSocketHandler.stopEducation(educationId); } - @Transactional public SocketTokenDto createSocketToken(String authorizationHeader) { String token = jwtTokenProvider.resolveAccessToken(authorizationHeader); String role = jwtTokenProvider.getRole(token); @@ -138,32 +42,6 @@ public SocketTokenDto createSocketToken(String authorizationHeader) { return SocketTokenDto.from(socketToken); } - private void makeAllStatusFalse() { - quizRepository.findAllByStatus(QuizStatus.QUIZ_ON) - .forEach(quiz -> quiz.updateStatus(QuizStatus.QUIZ_OFF)); - } - - private void makeAllStartFalse() { - quizRepository.findAllByStart(QuizStatus.QUIZ_ON) - .forEach(quiz -> quiz.updateStart(QuizStatus.QUIZ_OFF)); - } - - private void checkEducationOpen(Education education) { - if (EducationStatus.ONGOING != education.getStatus()) { - throw new AppException(ErrorCode.EDUCATION_CLOSED); - } - } - - private Quiz findQuizById(Long quizId) { - return quizRepository.findById(quizId) - .orElseThrow(() -> new EntityNotFoundException("해당 퀴즈를 찾을 수 없습니다.")); - } - - private Education findEducationById(Long educationId) { - return educationRepository.findById(educationId) - .orElseThrow(() -> new EntityNotFoundException("해당 교육을 찾을 수 없습니다.")); - } - public void sendKingCommand(Long educationId) { webSocketHandler.sendKingMemberCommand(educationId); }