package com.personalproject.service; import com.personalproject.generator.QuestionGenerator; import com.personalproject.model.DifficultyLevel; import com.personalproject.model.ExamSession; import com.personalproject.model.QuizQuestion; import com.personalproject.storage.QuestionStorageService; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; /** * 用于管理考试会话和生成测验题目的服务类. */ public final class ExamService { private static final int OPTIONS_COUNT = 4; private final Map generators; private final Random random = new Random(); private final QuestionGenerationService questionGenerationService; private final QuestionStorageService questionStorageService; /** * 创建新的考试服务. * * @param generatorMap 难度级别到题目生成器的映射 * @param questionGenerationService 题目生成服务 * @param questionStorageService 题目存储服务 */ public ExamService( Map generatorMap, QuestionGenerationService questionGenerationService, QuestionStorageService questionStorageService) { this.generators = new EnumMap<>(DifficultyLevel.class); this.generators.putAll(generatorMap); this.questionGenerationService = questionGenerationService; this.questionStorageService = questionStorageService; } /** * 使用指定参数创建新的考试会话. * * @param username 考生用户名 * @param difficultyLevel 考试难度级别 * @param questionCount 考试中包含的题目数量 * @return 新的考试会话 */ public ExamSession createExamSession( String username, DifficultyLevel difficultyLevel, int questionCount) { if (questionCount < 10 || questionCount > 30) { throw new IllegalArgumentException("题目数量必须在10到30之间"); } // 根据难度级别生成题目 if (!generators.containsKey(difficultyLevel)) { throw new IllegalArgumentException("找不到难度级别的生成器: " + difficultyLevel); } try { Set existingQuestions = questionStorageService.loadExistingQuestions(username); List uniqueQuestions = questionGenerationService.generateUniqueQuestions( difficultyLevel, questionCount, existingQuestions); List quizQuestions = new ArrayList<>(); for (String questionText : uniqueQuestions) { quizQuestions.add(buildQuizQuestion(questionText)); } questionStorageService.saveQuestions(username, uniqueQuestions); return new ExamSession(username, difficultyLevel, quizQuestions); } catch (IOException exception) { throw new IllegalStateException("生成考试题目失败", exception); } } /** * 使用预定义题目创建考试会话(用于测试). * * @param username 考生用户名 * @param difficultyLevel 考试难度级别 * @param questions 预定义题目列表 * @return 新的考试会话 */ public ExamSession createExamSession( String username, DifficultyLevel difficultyLevel, List questions) { if (questions.size() < 10 || questions.size() > 30) { throw new IllegalArgumentException("题目数量必须在10到30之间"); } return new ExamSession(username, difficultyLevel, questions); } private QuizQuestion buildQuizQuestion(String questionText) { try { double correctAnswer = MathExpressionEvaluator.evaluate(questionText); String formattedCorrectAnswer = formatAnswer(correctAnswer); List options = buildOptions(correctAnswer, formattedCorrectAnswer); int correctAnswerIndex = options.indexOf(formattedCorrectAnswer); if (correctAnswerIndex < 0) { options.set(0, formattedCorrectAnswer); correctAnswerIndex = 0; } return new QuizQuestion(questionText, options, correctAnswerIndex); } catch (RuntimeException exception) { List fallbackOptions = new ArrayList<>(); for (int i = 1; i <= OPTIONS_COUNT; i++) { fallbackOptions.add("选项" + i); } return new QuizQuestion(questionText, fallbackOptions, 0); } } private List buildOptions(double correctAnswer, String formattedCorrectAnswer) { Set optionSet = new LinkedHashSet<>(); optionSet.add(formattedCorrectAnswer); int attempts = 0; while (optionSet.size() < OPTIONS_COUNT && attempts < 100) { double offset = generateOffset(correctAnswer); double candidateValue = correctAnswer + offset; if (Double.isNaN(candidateValue) || Double.isInfinite(candidateValue)) { attempts++; continue; } String candidate = formatAnswer(candidateValue); if (!candidate.equals(formattedCorrectAnswer)) { optionSet.add(candidate); } attempts++; } while (optionSet.size() < OPTIONS_COUNT) { optionSet.add(formatAnswer(correctAnswer + optionSet.size() * 1.5)); } List options = new ArrayList<>(optionSet); Collections.shuffle(options, random); return options; } private double generateOffset(double base) { double scale = Math.max(1.0, Math.abs(base) / 2.0); double offset = random.nextGaussian() * scale; if (Math.abs(offset) < 0.5) { offset += offset >= 0 ? 1.5 : -1.5; } return offset; } private String formatAnswer(double value) { return String.format("%.2f", value); } }