Skip to content
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

Refactor: 웹소켓 서비스 코드 분리 #94

Merged
merged 6 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
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.EducationOpenRequest;
import org.cotato.csquiz.api.socket.dto.QuizSocketRequest;
import org.cotato.csquiz.api.socket.dto.SocketTokenDto;
import org.cotato.csquiz.domain.education.service.EducationService;
import org.cotato.csquiz.domain.education.service.QuizSolveService;
import org.cotato.csquiz.domain.education.service.RecordService;
import org.cotato.csquiz.domain.education.service.SocketService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -25,49 +27,50 @@
public class SocketController {

private final SocketService socketService;
private final EducationService educationService;
private final RecordService recordService;
private final QuizSolveService quizSolveService;

@PatchMapping("/start/csquiz")
public ResponseEntity<Void> openCSQuiz(@RequestBody @Valid QuizOpenRequest request) {
socketService.openCSQuiz(request);
recordService.saveAnswersToCache(request);
public ResponseEntity<Void> openEducation(@RequestBody @Valid EducationOpenRequest request) {
educationService.openEducation(request);
return ResponseEntity.noContent().build();
}

@PatchMapping("/access")
public ResponseEntity<Void> accessQuiz(@RequestBody @Valid QuizSocketRequest request) {
socketService.accessQuiz(request);
quizSolveService.accessQuiz(request);
recordService.saveAnswer(request);
return ResponseEntity.noContent().build();
}

@PatchMapping("/start")
public ResponseEntity<Void> startQuizSolve(@RequestBody @Valid QuizSocketRequest request) {
socketService.startQuizSolve(request);
quizSolveService.startQuizSolve(request);
return ResponseEntity.noContent().build();
}

@PatchMapping("/deny")
public ResponseEntity<Void> denyQuiz(@RequestBody @Valid QuizSocketRequest request) {
socketService.denyQuiz(request);
quizSolveService.denyQuiz(request);
return ResponseEntity.noContent().build();
}

@PatchMapping("/stop")
public ResponseEntity<Void> stopQuizSolve(@RequestBody @Valid QuizSocketRequest request) {
socketService.stopQuizSolve(request);
quizSolveService.stopQuizSolve(request);
return ResponseEntity.noContent().build();
}

@PatchMapping("/close/csquiz")
public ResponseEntity<Void> stopAllQuiz(@RequestBody @Valid EducationCloseRequest request) {
socketService.stopAllQuiz(request);
public ResponseEntity<Void> closeEducation(@RequestBody @Valid EducationCloseRequest request) {
educationService.closeEducation(request);
return ResponseEntity.noContent().build();
}

@PostMapping("/token")
public ResponseEntity<SocketTokenDto> makeSocketToken(@RequestHeader("Authorization") String authorizationHeader) {
return ResponseEntity.ok(socketService.createSocketToken(authorizationHeader));
public ResponseEntity<SocketTokenDto> makeSocketToken(@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok(socketService.createSocketToken(memberId));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import jakarta.validation.constraints.NotNull;

public record QuizOpenRequest(
public record EducationOpenRequest(
@NotNull
Long educationId
) {
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/cotato/csquiz/common/SchedulerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 * * *")
Expand All @@ -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 완료 ]");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.cotato.csquiz.common.config.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.cotato.csquiz.common.interceptor.HandshakeInterceptor;
import org.cotato.csquiz.common.websocket.HandshakeInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.cotato.csquiz.common.interceptor;
package org.cotato.csquiz.common.websocket;

import org.cotato.csquiz.common.config.jwt.JwtTokenProvider;
import org.cotato.csquiz.domain.auth.enums.MemberRole;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@ public interface QuizRepository extends JpaRepository<Quiz, Long> {

List<Quiz> findAllByEducationId(Long educationId);

List<Quiz> findAllByStatus(QuizStatus status);

Optional<Quiz> findByStatusAndEducationId(QuizStatus status, Long educationId);

List<Quiz> findAllByStart(QuizStatus quizStatus);
List<Quiz> findAllByStatusOrStart(QuizStatus status, QuizStatus start);

@Transactional
@Modifying
@Query("select q from Quiz q where q.education.id in :educationIds")
List<Quiz> findAllByEducationIdsInQuery(@Param("educationIds") List<Long> educationIds);

Optional<Quiz> findByEducationIdAndNumber(Long educationId, Integer i);

Optional<Quiz> findFirstByEducationOrderByNumberDesc(Education education);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.EducationOpenRequest;
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;
Expand All @@ -28,9 +32,12 @@
@Slf4j
public class EducationService {

private final RecordService recordService;
private final EducationRepository educationRepository;
private final QuizRepository quizRepository;
private final SessionRepository sessionRepository;
private final SocketService socketService;


@Transactional
public CreateEducationResponse createEducation(CreateEducationRequest request) {
Expand Down Expand Up @@ -80,12 +87,52 @@ private void validateNotEmpty(String newSubject) {
.orElseThrow(() -> new AppException(ErrorCode.SUBJECT_INVALID));
}

@Transactional
public void openEducation(EducationOpenRequest request) {
Education education = findEducationById(request.educationId());

checkEducationBefore(education);

education.updateStatus(EducationStatus.ONGOING);
recordService.saveAnswersToCache(education.getId());
}

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<AllEducationResponse> 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("해당 문제를 찾을 수 없습니다."));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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.QuizRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
public class QuizSolveService {
gikhoon marked this conversation as resolved.
Show resolved Hide resolved

private final SocketService socketService;
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("해당 퀴즈를 찾을 수 없습니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.cotato.csquiz.api.record.dto.ReplyRequest;
import org.cotato.csquiz.api.record.dto.ReplyResponse;
import org.cotato.csquiz.api.record.dto.ScorerResponse;
import org.cotato.csquiz.api.socket.dto.QuizOpenRequest;
import org.cotato.csquiz.api.socket.dto.QuizSocketRequest;
import org.cotato.csquiz.common.error.ErrorCode;
import org.cotato.csquiz.common.error.exception.AppException;
Expand Down Expand Up @@ -96,8 +95,8 @@ private Quiz findQuizById(Long quizId) {
.orElseThrow(() -> new EntityNotFoundException("해당 문제를 찾을 수 없습니다."));
}

public void saveAnswersToCache(QuizOpenRequest request) {
List<Quiz> quizzes = quizRepository.findAllByEducationId(request.educationId());
public void saveAnswersToCache(final Long educationId) {
List<Quiz> quizzes = quizRepository.findAllByEducationId(educationId);

quizAnswerRedisRepository.saveAllQuizAnswers(quizzes);
}
Expand Down
Loading
Loading