diff --git a/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java b/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java index 8504e49..4205d22 100644 --- a/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java +++ b/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java @@ -16,6 +16,7 @@ import com.example.questionbank.repository.UserRepository; import com.example.questionbank.web.rest.dto.AnswerResult; import com.example.questionbank.web.rest.dto.ChoiceView; import com.example.questionbank.web.rest.dto.ExamAttemptStartResult; +import com.example.questionbank.web.rest.dto.ExamAttemptSummary; import com.example.questionbank.web.rest.dto.ExamQuestionView; import com.example.questionbank.web.rest.dto.ExamResult; import com.example.questionbank.web.rest.dto.SubmitAnswerRequest; @@ -140,22 +141,18 @@ public class QuizAttemptService { int totalScore = 0; for (SubmitAnswerRequest answerRequest : request.answers()) { - // 동일한 문제를 중복 제출했는지 확인 if (!seenQuestionIds.add(answerRequest.questionId())) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "중복 제출된 문항이 있습니다."); } - // 제출한 문제 조회 Question question = questionRepository .findById(answerRequest.questionId()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "문항을 찾을 수 없습니다.")); - // 현재 과목에 속한 활성 문제인지 확인 if (!Boolean.TRUE.equals(question.getActive()) || !question.getSubject().getId().equals(attempt.getSubject().getId())) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "응시할 수 없는 문항입니다."); } - // 보기 4개와 정답 1개가 올바르게 등록됐는지 확인 List choices = new ArrayList<>(question.getChoices()); List correctChoices = choices @@ -169,7 +166,6 @@ public class QuizAttemptService { throw new ResponseStatusException(HttpStatus.CONFLICT, "문항의 선택지 구성이 올바르지 않습니다."); } - // 사용자가 선택한 보기 찾기 OptionKey selectedOption = answerRequest.selectedOption(); Choice selectedChoice = @@ -183,10 +179,8 @@ public class QuizAttemptService { new ResponseStatusException(HttpStatus.BAD_REQUEST, "해당 문항에 존재하지 않는 선택지입니다.") ); - // 앞에서 만든 ExamScoringService로 채점 ExamScoringService.ScoringResult score = scoringService.score(correctChoices.getFirst().getOptionKey(), selectedOption); - // 문제·해설·선택 답안의 스냅샷 저장 ExamAnswer answer = new ExamAnswer(); answer.setAttempt(attempt); answer.setQuestion(question); @@ -199,10 +193,8 @@ public class QuizAttemptService { answerRepository.save(answer); - // 총점 계산 totalScore += score.awardedPoints(); - // 응시자에게 반환할 채점 결과 만들기 results.add( new AnswerResult( question.getId(), @@ -216,14 +208,11 @@ public class QuizAttemptService { ); } - // 모든 활성 문제를 제출했는지 확인 if (!seenQuestionIds.equals(requiredQuestionIds)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "과목의 모든 활성 문항을 빠짐없이 제출해야 합니다."); } - // 시험 제출 완료 처리 Instant submittedAt = Instant.now(); - attempt.setScore(totalScore); attempt.setSubmittedAt(submittedAt); attempt.setStatus(AttemptStatus.SUBMITTED); @@ -231,7 +220,57 @@ public class QuizAttemptService { return new ExamResult(attempt.getId(), totalScore, submittedAt, results); } - // 4. 응시 기록 소유자 확인 + // 4. 제출 완료된 시험 결과 조회 + @Transactional(readOnly = true) + public ExamResult getResult(Long attemptId, String login) { + ExamAttempt attempt = getOwnedAttempt(attemptId, login); + + if (attempt.getStatus() != AttemptStatus.SUBMITTED) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "아직 제출되지 않은 응시입니다."); + } + + List results = attempt + .getAnswers() + .stream() + .sorted(Comparator.comparing(a -> a.getQuestion().getId())) + .map(answer -> + new AnswerResult( + answer.getQuestion().getId(), + answer.getQuestionTextSnapshot(), + answer.getSelectedOption(), + answer.getSelectedContentSnapshot(), + Boolean.TRUE.equals(answer.getCorrect()), + answer.getAwardedPoints() == null ? 0 : answer.getAwardedPoints(), + answer.getExplanationSnapshot() + ) + ) + .toList(); + + return new ExamResult(attempt.getId(), attempt.getScore() == null ? 0 : attempt.getScore(), attempt.getSubmittedAt(), results); + } + + // 5. 로그인한 사용자의 응시 이력 조회 + @Transactional(readOnly = true) + public List getMyAttempts(String login) { + return attemptRepository + .findAllByUserLogin(login) + .stream() + .sorted(Comparator.comparing(ExamAttempt::getStartedAt, Comparator.nullsLast(Comparator.reverseOrder()))) + .map(attempt -> + new ExamAttemptSummary( + attempt.getId(), + attempt.getSubject().getId(), + attempt.getSubject().getName(), + attempt.getStartedAt(), + attempt.getSubmittedAt(), + attempt.getStatus(), + attempt.getScore() + ) + ) + .toList(); + } + + // 6. 응시 기록 소유자 확인 private ExamAttempt getOwnedAttempt(Long attemptId, String login) { ExamAttempt attempt = attemptRepository .findById(attemptId)