Complete quiz attempt service with result and history queries
This commit is contained in:
1 parent
1db02c4e15
commit
b539a9a99a
1 file changed
+52
-13
@@ -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.AnswerResult;
|
||||||
import com.example.questionbank.web.rest.dto.ChoiceView;
|
import com.example.questionbank.web.rest.dto.ChoiceView;
|
||||||
import com.example.questionbank.web.rest.dto.ExamAttemptStartResult;
|
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.ExamQuestionView;
|
||||||
import com.example.questionbank.web.rest.dto.ExamResult;
|
import com.example.questionbank.web.rest.dto.ExamResult;
|
||||||
import com.example.questionbank.web.rest.dto.SubmitAnswerRequest;
|
import com.example.questionbank.web.rest.dto.SubmitAnswerRequest;
|
||||||
@@ -140,22 +141,18 @@ public class QuizAttemptService {
|
|||||||
int totalScore = 0;
|
int totalScore = 0;
|
||||||
|
|
||||||
for (SubmitAnswerRequest answerRequest : request.answers()) {
|
for (SubmitAnswerRequest answerRequest : request.answers()) {
|
||||||
// 동일한 문제를 중복 제출했는지 확인
|
|
||||||
if (!seenQuestionIds.add(answerRequest.questionId())) {
|
if (!seenQuestionIds.add(answerRequest.questionId())) {
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "중복 제출된 문항이 있습니다.");
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "중복 제출된 문항이 있습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 제출한 문제 조회
|
|
||||||
Question question = questionRepository
|
Question question = questionRepository
|
||||||
.findById(answerRequest.questionId())
|
.findById(answerRequest.questionId())
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "문항을 찾을 수 없습니다."));
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "문항을 찾을 수 없습니다."));
|
||||||
|
|
||||||
// 현재 과목에 속한 활성 문제인지 확인
|
|
||||||
if (!Boolean.TRUE.equals(question.getActive()) || !question.getSubject().getId().equals(attempt.getSubject().getId())) {
|
if (!Boolean.TRUE.equals(question.getActive()) || !question.getSubject().getId().equals(attempt.getSubject().getId())) {
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "응시할 수 없는 문항입니다.");
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "응시할 수 없는 문항입니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 보기 4개와 정답 1개가 올바르게 등록됐는지 확인
|
|
||||||
List<Choice> choices = new ArrayList<>(question.getChoices());
|
List<Choice> choices = new ArrayList<>(question.getChoices());
|
||||||
|
|
||||||
List<Choice> correctChoices = choices
|
List<Choice> correctChoices = choices
|
||||||
@@ -169,7 +166,6 @@ public class QuizAttemptService {
|
|||||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "문항의 선택지 구성이 올바르지 않습니다.");
|
throw new ResponseStatusException(HttpStatus.CONFLICT, "문항의 선택지 구성이 올바르지 않습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사용자가 선택한 보기 찾기
|
|
||||||
OptionKey selectedOption = answerRequest.selectedOption();
|
OptionKey selectedOption = answerRequest.selectedOption();
|
||||||
|
|
||||||
Choice selectedChoice =
|
Choice selectedChoice =
|
||||||
@@ -183,10 +179,8 @@ public class QuizAttemptService {
|
|||||||
new ResponseStatusException(HttpStatus.BAD_REQUEST, "해당 문항에 존재하지 않는 선택지입니다.")
|
new ResponseStatusException(HttpStatus.BAD_REQUEST, "해당 문항에 존재하지 않는 선택지입니다.")
|
||||||
);
|
);
|
||||||
|
|
||||||
// 앞에서 만든 ExamScoringService로 채점
|
|
||||||
ExamScoringService.ScoringResult score = scoringService.score(correctChoices.getFirst().getOptionKey(), selectedOption);
|
ExamScoringService.ScoringResult score = scoringService.score(correctChoices.getFirst().getOptionKey(), selectedOption);
|
||||||
|
|
||||||
// 문제·해설·선택 답안의 스냅샷 저장
|
|
||||||
ExamAnswer answer = new ExamAnswer();
|
ExamAnswer answer = new ExamAnswer();
|
||||||
answer.setAttempt(attempt);
|
answer.setAttempt(attempt);
|
||||||
answer.setQuestion(question);
|
answer.setQuestion(question);
|
||||||
@@ -199,10 +193,8 @@ public class QuizAttemptService {
|
|||||||
|
|
||||||
answerRepository.save(answer);
|
answerRepository.save(answer);
|
||||||
|
|
||||||
// 총점 계산
|
|
||||||
totalScore += score.awardedPoints();
|
totalScore += score.awardedPoints();
|
||||||
|
|
||||||
// 응시자에게 반환할 채점 결과 만들기
|
|
||||||
results.add(
|
results.add(
|
||||||
new AnswerResult(
|
new AnswerResult(
|
||||||
question.getId(),
|
question.getId(),
|
||||||
@@ -216,14 +208,11 @@ public class QuizAttemptService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 모든 활성 문제를 제출했는지 확인
|
|
||||||
if (!seenQuestionIds.equals(requiredQuestionIds)) {
|
if (!seenQuestionIds.equals(requiredQuestionIds)) {
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "과목의 모든 활성 문항을 빠짐없이 제출해야 합니다.");
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "과목의 모든 활성 문항을 빠짐없이 제출해야 합니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 시험 제출 완료 처리
|
|
||||||
Instant submittedAt = Instant.now();
|
Instant submittedAt = Instant.now();
|
||||||
|
|
||||||
attempt.setScore(totalScore);
|
attempt.setScore(totalScore);
|
||||||
attempt.setSubmittedAt(submittedAt);
|
attempt.setSubmittedAt(submittedAt);
|
||||||
attempt.setStatus(AttemptStatus.SUBMITTED);
|
attempt.setStatus(AttemptStatus.SUBMITTED);
|
||||||
@@ -231,7 +220,57 @@ public class QuizAttemptService {
|
|||||||
return new ExamResult(attempt.getId(), totalScore, submittedAt, results);
|
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<AnswerResult> 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<ExamAttemptSummary> 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) {
|
private ExamAttempt getOwnedAttempt(Long attemptId, String login) {
|
||||||
ExamAttempt attempt = attemptRepository
|
ExamAttempt attempt = attemptRepository
|
||||||
.findById(attemptId)
|
.findById(attemptId)
|
||||||
|
|||||||
Reference in new issue
Block a user