合并 #1

Merged
hnu202326010207 merged 1 commits from yinhaoming_branch into develop 5 months ago

@ -1,2 +0,0 @@
# math-learing

@ -0,0 +1,330 @@
package com.personalproject;
import com.personalproject.controller.MathLearningController;
import com.personalproject.generator.HighSchoolQuestionGenerator;
import com.personalproject.generator.MiddleSchoolQuestionGenerator;
import com.personalproject.generator.PrimaryQuestionGenerator;
import com.personalproject.generator.QuestionGenerator;
import com.personalproject.model.DifficultyLevel;
import com.personalproject.model.ExamSession;
import com.personalproject.service.QuestionGenerationService;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
/**
* . MVC.
*/
public final class MathExamApplication {
private final MathLearningController controller;
/**
* .
*/
public MathExamApplication() {
Map<DifficultyLevel, QuestionGenerator> generatorMap = new EnumMap<>(DifficultyLevel.class);
generatorMap.put(DifficultyLevel.PRIMARY, new PrimaryQuestionGenerator());
generatorMap.put(DifficultyLevel.MIDDLE, new MiddleSchoolQuestionGenerator());
generatorMap.put(DifficultyLevel.HIGH, new HighSchoolQuestionGenerator());
QuestionGenerationService questionGenerationService = new QuestionGenerationService(
generatorMap);
this.controller = new MathLearningController(generatorMap, questionGenerationService);
}
/**
* .
*
* @param args 使.
*/
public static void main(String[] args) {
MathExamApplication application = new MathExamApplication();
application.run();
}
/**
* .
*/
private void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("=== 欢迎使用数学学习软件 ===");
System.out.println("1. 登录");
System.out.println("2. 注册");
System.out.println("3. 退出");
System.out.print("请选择操作 (1-3): ");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1" -> handleLogin(scanner);
case "2" -> handleRegistration(scanner);
case "3" -> {
System.out.println("感谢使用,再见!");
return;
}
default -> System.out.println("无效选择,请重新输入。");
}
}
}
/**
* .
*
* @param scanner
*/
private void handleLogin(Scanner scanner) {
System.out.print("请输入用户名: ");
String username = scanner.nextLine().trim();
System.out.print("请输入密码: ");
String password = scanner.nextLine().trim();
Optional<com.personalproject.auth.UserAccount> userAccount =
controller.authenticate(username, password);
if (userAccount.isPresent()) {
System.out.println("登录成功!欢迎, " + userAccount.get().username());
handleUserSession(scanner, userAccount.get());
} else {
System.out.println("登录失败:用户名或密码错误。");
}
}
/**
* .
*
* @param scanner
*/
private void handleRegistration(Scanner scanner) {
System.out.print("请输入用户名: ");
final String username = scanner.nextLine().trim();
System.out.print("请输入邮箱: ");
final String email = scanner.nextLine().trim();
if (!controller.isValidEmail(email)) {
System.out.println("邮箱格式不正确。");
return;
}
System.out.println("请选择难度级别:");
System.out.println("1. 小学");
System.out.println("2. 初中");
System.out.println("3. 高中");
System.out.print("请选择 (1-3): ");
int levelChoice = -1;
try {
levelChoice = Integer.parseInt(scanner.nextLine().trim());
} catch (NumberFormatException e) {
System.out.println("输入格式不正确。");
return;
}
DifficultyLevel difficultyLevel;
switch (levelChoice) {
case 1 -> difficultyLevel = DifficultyLevel.PRIMARY;
case 2 -> difficultyLevel = DifficultyLevel.MIDDLE;
case 3 -> difficultyLevel = DifficultyLevel.HIGH;
default -> {
System.out.println("无效的选择。");
return;
}
}
// 发送注册码
if (controller.initiateRegistration(username, email, difficultyLevel)) {
System.out.println("注册码已发送至您的邮箱,请查收。");
System.out.print("请输入收到的注册码: ");
String registrationCode = scanner.nextLine().trim();
if (controller.verifyRegistrationCode(username, registrationCode)) {
System.out.println("注册码验证成功!");
// 设置密码
while (true) {
System.out.print("请设置密码 (6-10位包含大小写字母和数字): ");
String password = scanner.nextLine().trim();
if (!controller.isValidPassword(password)) {
System.out.println("密码不符合要求,请重新设置。");
continue;
}
if (controller.setPassword(username, password)) {
System.out.println("注册成功!请登录。");
break;
} else {
System.out.println("设置密码失败,请重试。");
}
}
} else {
System.out.println("注册码验证失败,请检查后重试。");
}
} else {
System.out.println("注册失败:用户名或邮箱可能已存在。");
}
}
/**
* .
*
* @param scanner
* @param userAccount
*/
private void handleUserSession(Scanner scanner,
com.personalproject.auth.UserAccount userAccount) {
while (true) {
System.out.println("\n=== 用户菜单 ===");
System.out.println("1. 开始考试");
System.out.println("2. 修改密码");
System.out.println("3. 退出账号");
System.out.print("请选择操作 (1-3): ");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1" -> handleExamSession(scanner, userAccount);
case "2" -> handleChangePassword(scanner, userAccount);
case "3" -> {
System.out.println("已退出账号。");
return;
}
default -> System.out.println("无效选择,请重新输入。");
}
}
}
/**
* .
*
* @param scanner
* @param userAccount
*/
private void handleExamSession(Scanner scanner,
com.personalproject.auth.UserAccount userAccount) {
System.out.println("当前选择难度: " + userAccount.difficultyLevel().getDisplayName());
System.out.print("请输入生成题目数量 (10-30): ");
int questionCount = -1;
try {
questionCount = Integer.parseInt(scanner.nextLine().trim());
} catch (NumberFormatException e) {
System.out.println("输入格式不正确。");
return;
}
if (questionCount < 10 || questionCount > 30) {
System.out.println("题目数量必须在10到30之间。");
return;
}
// 创建考试会话
ExamSession examSession =
controller.createExamSession(userAccount.username(), userAccount.difficultyLevel(),
questionCount);
// 进行考试
conductExam(scanner, examSession);
// 保存结果
controller.saveExamResults(examSession);
System.out.printf("考试结束!您的得分: %.2f%%\n", examSession.calculateScore());
// 询问用户是否继续或退出
System.out.println("1. 继续考试");
System.out.println("2. 退出");
System.out.print("请选择 (1-2): ");
String choice = scanner.nextLine().trim();
if (choice.equals("1")) {
handleExamSession(scanner, userAccount);
}
}
/**
* .
*
* @param scanner
* @param examSession
*/
private void conductExam(Scanner scanner, ExamSession examSession) {
while (!examSession.isComplete()) {
var currentQuestion = examSession.getCurrentQuestion();
System.out.printf("\n第 %d 题: %s\n", examSession.getCurrentQuestionIndex() + 1,
currentQuestion.getQuestionText());
// 打印选项
for (int i = 0; i < currentQuestion.getOptions().size(); i++) {
System.out.printf("%d. %s\n", i + 1, currentQuestion.getOptions().get(i));
}
System.out.print(
"请选择答案 (1-" + currentQuestion.getOptions().size() + ", 0 返回上一题): ");
int choice = -1;
try {
choice = Integer.parseInt(scanner.nextLine().trim());
} catch (NumberFormatException e) {
System.out.println("输入格式不正确,请重新输入。");
continue;
}
if (choice == 0) {
// 如果可能,转到上一个问题
if (!examSession.goToPreviousQuestion()) {
System.out.println("已经是第一题了。");
}
continue;
}
if (choice < 1 || choice > currentQuestion.getOptions().size()) {
System.out.println("无效的选择,请重新输入。");
continue;
}
// 设置答案从基于1的索引调整为基于0的索引
examSession.setAnswer(choice - 1);
// 转到下一题
if (!examSession.goToNextQuestion()) {
// 如果无法转到下一题,意味着已到达末尾
break;
}
}
}
/**
* .
*
* @param scanner
* @param userAccount
*/
private void handleChangePassword(Scanner scanner,
com.personalproject.auth.UserAccount userAccount) {
System.out.print("请输入当前密码: ");
String oldPassword = scanner.nextLine().trim();
if (!userAccount.password().equals(oldPassword)) {
System.out.println("当前密码错误。");
return;
}
System.out.print("请输入新密码 (6-10位包含大小写字母和数字): ");
String newPassword = scanner.nextLine().trim();
if (!controller.isValidPassword(newPassword)) {
System.out.println("新密码不符合要求。");
return;
}
if (controller.changePassword(userAccount.username(), oldPassword, newPassword)) {
System.out.println("密码修改成功!");
} else {
System.out.println("密码修改失败。");
}
}
}

@ -0,0 +1,140 @@
package com.personalproject.auth;
import com.personalproject.model.DifficultyLevel;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* .
*/
public final class AccountRepository {
private final Map<String, UserAccount> accounts = new HashMap<>();
private final Map<String, String> registrationCodes = new HashMap<>();
/**
* .
*
* @param username .
* @param password .
* @return .
*/
public Optional<UserAccount> authenticate(String username, String password) {
if (username == null || password == null) {
return Optional.empty();
}
UserAccount account = accounts.get(username.trim());
if (account == null || !account.isRegistered()) {
return Optional.empty();
}
if (!account.password().equals(password.trim())) {
return Optional.empty();
}
return Optional.of(account);
}
/**
* Registers a new user account with email.
*
* @param username The username
* @param email The email address
* @param difficultyLevel The selected difficulty level
* @return true if registration was successful, false if username already exists
*/
public boolean registerUser(String username, String email, DifficultyLevel difficultyLevel) {
String trimmedUsername = username.trim();
String trimmedEmail = email.trim();
if (accounts.containsKey(trimmedUsername)) {
return false; // Username already exists
}
// Check if email is already used by another account
for (UserAccount account : accounts.values()) {
if (account.email().equals(trimmedEmail) && account.isRegistered()) {
return false; // Email already registered
}
}
UserAccount newAccount = new UserAccount(
trimmedUsername,
trimmedEmail,
"", // Empty password initially
difficultyLevel,
LocalDateTime.now(),
false); // Not registered until password is set
accounts.put(trimmedUsername, newAccount);
return true;
}
/**
* Sets the password for a user after registration.
*
* @param username The username
* @param password The password to set
* @return true if successful, false if user doesn't exist
*/
public boolean setPassword(String username, String password) {
UserAccount account = accounts.get(username.trim());
if (account == null) {
return false;
}
UserAccount updatedAccount = new UserAccount(
account.username(),
account.email(),
password,
account.difficultyLevel(),
account.registrationDate(),
true); // Now registered
accounts.put(username.trim(), updatedAccount);
return true;
}
/**
* Changes the password for an existing user.
*
* @param username The username
* @param oldPassword The current password
* @param newPassword The new password
* @return true if successful, false if old password is incorrect or user doesn't exist
*/
public boolean changePassword(String username, String oldPassword, String newPassword) {
UserAccount account = accounts.get(username.trim());
if (account == null || !account.password().equals(oldPassword) || !account.isRegistered()) {
return false;
}
UserAccount updatedAccount = new UserAccount(
account.username(),
account.email(),
newPassword,
account.difficultyLevel(),
account.registrationDate(),
true);
accounts.put(username.trim(), updatedAccount);
return true;
}
/**
* Checks if a user exists in the system.
*
* @param username The username to check
* @return true if user exists, false otherwise
*/
public boolean userExists(String username) {
return accounts.containsKey(username.trim());
}
/**
* Gets a user account by username.
*
* @param username The username
* @return Optional containing the user account if found
*/
public Optional<UserAccount> getUser(String username) {
return Optional.ofNullable(accounts.get(username.trim()));
}
}

@ -0,0 +1,62 @@
package com.personalproject.auth;
import java.util.Random;
/**
* Interface for sending emails with registration codes.
*/
public final class EmailService {
private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final int CODE_LENGTH = 6;
private static final Random RANDOM = new Random();
private EmailService() {
// Prevent instantiation of utility class
}
/**
* Generates a random registration code.
*
* @return A randomly generated registration code
*/
public static String generateRegistrationCode() {
StringBuilder code = new StringBuilder();
for (int i = 0; i < CODE_LENGTH; i++) {
code.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length())));
}
return code.toString();
}
/**
* Sends a registration code to the specified email address. In a real implementation, this would
* connect to an email server.
*
* @param email The email address to send the code to
* @param registrationCode The registration code to send
* @return true if successfully sent (in this mock implementation, always true)
*/
public static boolean sendRegistrationCode(String email, String registrationCode) {
// In a real implementation, this would connect to an email server
// For the mock implementation, we'll just print to console
System.out.println("Sending registration code " + registrationCode + " to " + email);
return true;
}
/**
* Validates if an email address has a valid format.
*
* @param email The email address to validate
* @return true if the email has valid format, false otherwise
*/
public static boolean isValidEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
// Simple email validation using regex
String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@"
+ "(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
return email.matches(emailRegex);
}
}

@ -0,0 +1,30 @@
package com.personalproject.auth;
import java.util.regex.Pattern;
/**
* Utility class for password validation.
*/
public final class PasswordValidator {
private static final Pattern PASSWORD_PATTERN =
Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$");
private PasswordValidator() {
// Prevent instantiation of utility class
}
/**
* Validates if a password meets the requirements: - 6-10 characters - Contains at least one
* uppercase letter - Contains at least one lowercase letter - Contains at least one digit.
*
* @param password The password to validate
* @return true if password meets requirements, false otherwise
*/
public static boolean isValidPassword(String password) {
if (password == null) {
return false;
}
return PASSWORD_PATTERN.matcher(password).matches();
}
}

@ -0,0 +1,40 @@
package com.personalproject.auth;
import com.personalproject.model.DifficultyLevel;
import java.time.LocalDateTime;
/**
* .
*/
public record UserAccount(
String username,
String email,
String password,
DifficultyLevel difficultyLevel,
LocalDateTime registrationDate,
boolean isRegistered) {
/**
* Creates a new user account with registration date set to now.
*
* @param username The username
* @param email The email address
* @param password The password
* @param difficultyLevel The selected difficulty level
* @param isRegistered Whether the user has completed registration
*/
public UserAccount {
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("Username cannot be null or empty");
}
if (email == null || email.trim().isEmpty()) {
throw new IllegalArgumentException("Email cannot be null or empty");
}
if (password == null) {
throw new IllegalArgumentException("Password cannot be null");
}
if (difficultyLevel == null) {
throw new IllegalArgumentException("Difficulty level cannot be null");
}
}
}

@ -0,0 +1,145 @@
package com.personalproject.controller;
import com.personalproject.generator.QuestionGenerator;
import com.personalproject.model.DifficultyLevel;
import com.personalproject.model.ExamSession;
import com.personalproject.service.ExamResultService;
import com.personalproject.service.MathLearningService;
import com.personalproject.service.QuestionGenerationService;
import java.util.Map;
import java.util.Optional;
/**
* MVC.
*/
public final class MathLearningController {
private final MathLearningService mathLearningService;
/**
* .
*
* @param generatorMap
* @param questionGenerationService
*/
public MathLearningController(
Map<DifficultyLevel, QuestionGenerator> generatorMap,
QuestionGenerationService questionGenerationService) {
this.mathLearningService = new MathLearningService(generatorMap, questionGenerationService);
}
/**
* .
*
* @param username
* @param email
* @param difficultyLevel
* @return truefalse
*/
public boolean initiateRegistration(String username, String email,
DifficultyLevel difficultyLevel) {
return mathLearningService.initiateRegistration(username, email, difficultyLevel);
}
/**
* .
*
* @param username
* @param registrationCode
* @return truefalse
*/
public boolean verifyRegistrationCode(String username, String registrationCode) {
return mathLearningService.verifyRegistrationCode(username, registrationCode);
}
/**
* .
*
* @param username
* @param password
* @return truefalse
*/
public boolean setPassword(String username, String password) {
return mathLearningService.setPassword(username, password);
}
/**
* .
*
* @param username
* @param password
* @return Optional
*/
public Optional<com.personalproject.auth.UserAccount> authenticate(String username,
String password) {
return mathLearningService.authenticate(username, password);
}
/**
* .
*
* @param username
* @param difficultyLevel
* @param questionCount
* @return
*/
public ExamSession createExamSession(String username, DifficultyLevel difficultyLevel,
int questionCount) {
return mathLearningService.createExamSession(username, difficultyLevel, questionCount);
}
/**
* .
*
* @param examSession
*/
public void saveExamResults(ExamSession examSession) {
mathLearningService.saveExamResults(examSession);
}
/**
* .
*
* @param examSession
* @param continueWithSameLevel
* @param newDifficultyLevel null
* @return
*/
public ExamResultService.ExamContinuationAction processExamResult(
ExamSession examSession, boolean continueWithSameLevel, DifficultyLevel newDifficultyLevel) {
return mathLearningService.processExamResult(examSession, continueWithSameLevel,
newDifficultyLevel);
}
/**
* .
*
* @param username
* @param oldPassword
* @param newPassword
* @return truefalse
*/
public boolean changePassword(String username, String oldPassword, String newPassword) {
return mathLearningService.changePassword(username, oldPassword, newPassword);
}
/**
* .
*
* @param password
* @return truefalse
*/
public boolean isValidPassword(String password) {
return MathLearningService.isValidPassword(password);
}
/**
* .
*
* @param email
* @return truefalse
*/
public boolean isValidEmail(String email) {
return MathLearningService.isValidEmail(email);
}
}

@ -0,0 +1,37 @@
package com.personalproject.generator;
import java.util.Random;
/**
* .
*/
public final class HighSchoolQuestionGenerator implements QuestionGenerator {
private static final String[] OPERATORS = {"+", "-", "*", "/"};
private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"};
@Override
public String generateQuestion(Random random) {
int operandCount = random.nextInt(5) + 1;
String[] operands = new String[operandCount];
for (int index = 0; index < operandCount; index++) {
operands[index] = String.valueOf(random.nextInt(100) + 1);
}
int specialIndex = random.nextInt(operandCount);
String function = TRIG_FUNCTIONS[random.nextInt(TRIG_FUNCTIONS.length)];
operands[specialIndex] = function + '(' + operands[specialIndex] + ')';
StringBuilder builder = new StringBuilder();
for (int index = 0; index < operandCount; index++) {
if (index > 0) {
String operator = OPERATORS[random.nextInt(OPERATORS.length)];
builder.append(' ').append(operator).append(' ');
}
builder.append(operands[index]);
}
String expression = builder.toString();
if (operandCount > 1 && random.nextBoolean()) {
return '(' + expression + ')';
}
return expression;
}
}

@ -0,0 +1,39 @@
package com.personalproject.generator;
import java.util.Random;
/**
* .
*/
public final class MiddleSchoolQuestionGenerator implements QuestionGenerator {
private static final String[] OPERATORS = {"+", "-", "*", "/"};
@Override
public String generateQuestion(Random random) {
int operandCount = random.nextInt(5) + 1;
String[] operands = new String[operandCount];
for (int index = 0; index < operandCount; index++) {
operands[index] = String.valueOf(random.nextInt(100) + 1);
}
int specialIndex = random.nextInt(operandCount);
if (random.nextBoolean()) {
operands[specialIndex] = '(' + operands[specialIndex] + ")^2";
} else {
operands[specialIndex] = "sqrt(" + operands[specialIndex] + ')';
}
StringBuilder builder = new StringBuilder();
for (int index = 0; index < operandCount; index++) {
if (index > 0) {
String operator = OPERATORS[random.nextInt(OPERATORS.length)];
builder.append(' ').append(operator).append(' ');
}
builder.append(operands[index]);
}
String expression = builder.toString();
if (operandCount > 1 && random.nextBoolean()) {
return '(' + expression + ')';
}
return expression;
}
}

@ -0,0 +1,31 @@
package com.personalproject.generator;
import java.util.Random;
/**
* .
*/
public final class PrimaryQuestionGenerator implements QuestionGenerator {
private static final String[] OPERATORS = {"+", "-", "*", "/"};
@Override
public String generateQuestion(Random random) {
// 至少生成两个操作数,避免题目退化成单个数字
int operandCount = random.nextInt(4) + 2;
StringBuilder builder = new StringBuilder();
for (int index = 0; index < operandCount; index++) {
if (index > 0) {
String operator = OPERATORS[random.nextInt(OPERATORS.length)];
builder.append(' ').append(operator).append(' ');
}
int value = random.nextInt(100) + 1;
builder.append(value);
}
String expression = builder.toString();
if (operandCount > 1 && random.nextBoolean()) {
return '(' + expression + ')';
}
return expression;
}
}

@ -0,0 +1,17 @@
package com.personalproject.generator;
import java.util.Random;
/**
* .
*/
public interface QuestionGenerator {
/**
* .
*
* @param random .
* @return .
*/
String generateQuestion(Random random);
}

@ -0,0 +1,49 @@
package com.personalproject.model;
import java.util.Optional;
/**
* .
*/
public enum DifficultyLevel {
PRIMARY("小学"),
MIDDLE("初中"),
HIGH("高中");
private final String displayName;
DifficultyLevel(String displayName) {
this.displayName = displayName;
}
/**
* .
*
* @return .
*/
public String getDisplayName() {
return displayName;
}
/**
* .
*
* @param name .
* @return .
*/
public static Optional<DifficultyLevel> fromDisplayName(String name) {
if (name == null) {
return Optional.empty();
}
String trimmed = name.trim();
if (trimmed.isEmpty()) {
return Optional.empty();
}
for (DifficultyLevel level : values()) {
if (level.displayName.equals(trimmed)) {
return Optional.of(level);
}
}
return Optional.empty();
}
}

@ -0,0 +1,195 @@
package com.personalproject.model;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an exam session for a user.
*/
public final class ExamSession {
private final String username;
private final DifficultyLevel difficultyLevel;
private final List<QuizQuestion> questions;
private final List<Integer> userAnswers;
private final LocalDateTime startTime;
private int currentQuestionIndex;
/**
* Creates a new exam session.
*
* @param username The username of the test taker
* @param difficultyLevel The difficulty level of the exam
* @param questions The list of questions for the exam
*/
public ExamSession(String username, DifficultyLevel difficultyLevel,
List<QuizQuestion> questions) {
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("Username cannot be null or empty");
}
if (difficultyLevel == null) {
throw new IllegalArgumentException("Difficulty level cannot be null");
}
if (questions == null || questions.isEmpty()) {
throw new IllegalArgumentException("Questions list cannot be null or empty");
}
this.username = username;
this.difficultyLevel = difficultyLevel;
this.questions = List.copyOf(questions); // Immutable copy of questions
this.userAnswers = new ArrayList<>();
// Initialize user answers with -1 (no answer selected)
for (int i = 0; i < questions.size(); i++) {
userAnswers.add(-1);
}
this.startTime = LocalDateTime.now();
this.currentQuestionIndex = 0;
}
/**
* Gets the username of the test taker.
*
* @return The username
*/
public String getUsername() {
return username;
}
/**
* Gets the difficulty level of the exam.
*
* @return The difficulty level
*/
public DifficultyLevel getDifficultyLevel() {
return difficultyLevel;
}
/**
* Gets the list of questions in the exam.
*
* @return An unmodifiable list of questions
*/
public List<QuizQuestion> getQuestions() {
return questions;
}
/**
* Gets the user's answers to the questions.
*
* @return A list of answer indices (-1 means no answer selected)
*/
public List<Integer> getUserAnswers() {
return List.copyOf(userAnswers); // Return a copy to prevent modification
}
/**
* Gets the current question index.
*
* @return The current question index
*/
public int getCurrentQuestionIndex() {
return currentQuestionIndex;
}
/**
* Sets the user's answer for the current question.
*
* @param answerIndex The index of the selected answer
*/
public void setAnswer(int answerIndex) {
if (currentQuestionIndex < 0 || currentQuestionIndex >= questions.size()) {
throw new IllegalStateException("No valid question at current index");
}
if (answerIndex < 0 || answerIndex > questions.get(currentQuestionIndex).getOptions().size()) {
throw new IllegalArgumentException("Invalid answer index");
}
userAnswers.set(currentQuestionIndex, answerIndex);
}
/**
* Moves to the next question.
*
* @return true if successfully moved to next question, false if already at the last question
*/
public boolean goToNextQuestion() {
if (currentQuestionIndex < questions.size() - 1) {
currentQuestionIndex++;
return true;
}
return false;
}
/**
* Moves to the previous question.
*
* @return true if successfully moved to previous question, false if already at the first question
*/
public boolean goToPreviousQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
return true;
}
return false;
}
/**
* Checks if the exam is complete (all questions answered or at the end).
*
* @return true if the exam is complete, false otherwise
*/
public boolean isComplete() {
return currentQuestionIndex >= questions.size() - 1;
}
/**
* Gets the current question.
*
* @return The current quiz question
*/
public QuizQuestion getCurrentQuestion() {
if (currentQuestionIndex < 0 || currentQuestionIndex >= questions.size()) {
throw new IllegalStateException("No valid question at current index");
}
return questions.get(currentQuestionIndex);
}
/**
* Gets the user's answer for a specific question.
*
* @param questionIndex The index of the question
* @return The index of the user's answer (or -1 if no answer selected)
*/
public int getUserAnswer(int questionIndex) {
if (questionIndex < 0 || questionIndex >= questions.size()) {
throw new IllegalArgumentException("Question index out of bounds");
}
return userAnswers.get(questionIndex);
}
/**
* Calculates the score as a percentage.
*
* @return The score as a percentage (0-100)
*/
public double calculateScore() {
int correctCount = 0;
for (int i = 0; i < questions.size(); i++) {
QuizQuestion question = questions.get(i);
int userAnswer = userAnswers.get(i);
if (userAnswer != -1 && question.isAnswerCorrect(userAnswer)) {
correctCount++;
}
}
return questions.isEmpty() ? 0.0 : (double) correctCount / questions.size() * 100.0;
}
/**
* Gets the start time of the exam.
*
* @return The start time
*/
public LocalDateTime getStartTime() {
return startTime;
}
}

@ -0,0 +1,73 @@
package com.personalproject.model;
import java.util.List;
/**
* Represents a quiz question with multiple choice options.
*/
public final class QuizQuestion {
private final String questionText;
private final List<String> options;
private final int correctAnswerIndex;
/**
* Creates a new quiz question.
*
* @param questionText The text of the question
* @param options The list of answer options
* @param correctAnswerIndex The index of the correct answer in the options list
*/
public QuizQuestion(String questionText, List<String> options, int correctAnswerIndex) {
if (questionText == null || questionText.trim().isEmpty()) {
throw new IllegalArgumentException("Question text cannot be null or empty");
}
if (options == null || options.size() < 2) {
throw new IllegalArgumentException("Options must contain at least 2 choices");
}
if (correctAnswerIndex < 0 || correctAnswerIndex >= options.size()) {
throw new IllegalArgumentException("Correct answer index out of bounds");
}
this.questionText = questionText;
this.options = List.copyOf(options); // Immutable copy
this.correctAnswerIndex = correctAnswerIndex;
}
/**
* Gets the question text.
*
* @return The question text
*/
public String getQuestionText() {
return questionText;
}
/**
* Gets the list of answer options.
*
* @return An unmodifiable list of answer options
*/
public List<String> getOptions() {
return options;
}
/**
* Gets the index of the correct answer in the options list.
*
* @return The index of the correct answer
*/
public int getCorrectAnswerIndex() {
return correctAnswerIndex;
}
/**
* Checks if the given answer index matches the correct answer.
*
* @param answerIndex The index of the user's answer
* @return true if the answer is correct, false otherwise
*/
public boolean isAnswerCorrect(int answerIndex) {
return answerIndex == correctAnswerIndex;
}
}

@ -0,0 +1,83 @@
package com.personalproject.service;
import com.personalproject.model.DifficultyLevel;
import com.personalproject.model.ExamSession;
/**
* 退.
*/
public final class ExamResultService {
/**
* .
*
* @param examSession
* @param continueWithSameLevel 使
* @param newDifficultyLevel null
* @return
*/
public ExamContinuationAction processExamResult(
ExamSession examSession, boolean continueWithSameLevel, DifficultyLevel newDifficultyLevel) {
if (continueWithSameLevel) {
return new ExamContinuationAction(
true, examSession.getDifficultyLevel(), (int) examSession.getQuestions().size());
} else if (newDifficultyLevel != null) {
return new ExamContinuationAction(true, newDifficultyLevel,
(int) examSession.getQuestions().size());
} else {
return new ExamContinuationAction(false, null, 0);
}
}
/**
* .
*/
public static final class ExamContinuationAction {
private final boolean shouldContinue;
private final DifficultyLevel nextDifficultyLevel;
private final int nextQuestionCount;
/**
* .
*
* @param shouldContinue
* @param nextDifficultyLevel null
* @param nextQuestionCount 0
*/
public ExamContinuationAction(
boolean shouldContinue, DifficultyLevel nextDifficultyLevel, int nextQuestionCount) {
this.shouldContinue = shouldContinue;
this.nextDifficultyLevel = nextDifficultyLevel;
this.nextQuestionCount = nextQuestionCount;
}
/**
* .
*
* @return true退false
*/
public boolean shouldContinue() {
return shouldContinue;
}
/**
* .
*
* @return null
*/
public DifficultyLevel getNextDifficultyLevel() {
return nextDifficultyLevel;
}
/**
* .
*
* @return 0
*/
public int getNextQuestionCount() {
return nextQuestionCount;
}
}
}

@ -0,0 +1,140 @@
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 java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* .
*/
public final class ExamService {
private static final int OPTIONS_COUNT = 4;
private final Map<DifficultyLevel, QuestionGenerator> generators;
private final Random random = new Random();
private final QuestionGenerationService questionGenerationService;
/**
* .
*
* @param generatorMap
* @param questionGenerationService
*/
public ExamService(
Map<DifficultyLevel, QuestionGenerator> generatorMap,
QuestionGenerationService questionGenerationService) {
this.generators = new EnumMap<>(DifficultyLevel.class);
this.generators.putAll(generatorMap);
this.questionGenerationService = questionGenerationService;
}
/**
* 使.
*
* @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之间");
}
// 根据难度级别生成题目
List<String> generatedQuestions = new ArrayList<>();
QuestionGenerator generator = generators.get(difficultyLevel);
if (generator == null) {
throw new IllegalArgumentException("找不到难度级别的生成器: " + difficultyLevel);
}
for (int i = 0; i < questionCount; i++) {
String question = generator.generateQuestion(random);
generatedQuestions.add(question);
}
// 将字符串题目转换为带有选项和答案的QuizQuestion对象
List<QuizQuestion> quizQuestions = new ArrayList<>();
for (String questionText : generatedQuestions) {
List<String> options = generateOptions(questionText);
int correctAnswerIndex = generateCorrectAnswerIndex(options.size());
QuizQuestion quizQuestion = new QuizQuestion(questionText, options, correctAnswerIndex);
quizQuestions.add(quizQuestion);
}
return new ExamSession(username, difficultyLevel, quizQuestions);
}
/**
* 使.
*
* @param username
* @param difficultyLevel
* @param questions
* @return
*/
public ExamSession createExamSession(
String username, DifficultyLevel difficultyLevel, List<QuizQuestion> questions) {
if (questions.size() < 10 || questions.size() > 30) {
throw new IllegalArgumentException("题目数量必须在10到30之间");
}
return new ExamSession(username, difficultyLevel, questions);
}
/**
* . - .
*
* @param questionText
* @return
*/
private List<String> generateOptions(String questionText) {
List<String> options = new ArrayList<>();
// 为每个题目生成4个选项
try {
// 尝试将数学表达式作为正确答案进行评估
double correctAnswer = MathExpressionEvaluator.evaluate(questionText);
// 创建正确答案选项
options.add(String.format("%.2f", correctAnswer));
// 生成3个错误选项
for (int i = 0; i < OPTIONS_COUNT - 1; i++) {
double incorrectAnswer = correctAnswer + (random.nextGaussian() * 10); // 添加一些随机偏移
if (Math.abs(incorrectAnswer - correctAnswer) < 0.1) { // 确保不同
incorrectAnswer += 1.5;
}
options.add(String.format("%.2f", incorrectAnswer));
}
} catch (Exception e) {
// 如果评估失败,创建虚拟选项
for (int i = 0; i < OPTIONS_COUNT; i++) {
options.add("选项 " + (i + 1));
}
}
// 随机打乱选项以随机化正确答案的位置
java.util.Collections.shuffle(options, random);
// 找到打乱后的正确答案索引
// 对于此模拟实现,我们将返回第一个选项(索引0)作为正确答案
// 实际实现将跟踪正确答案
return options;
}
/**
* .
*
* @param optionCount
* @return 0optionCount-1
*/
private int generateCorrectAnswerIndex(int optionCount) {
return random.nextInt(optionCount);
}
}

@ -0,0 +1,217 @@
package com.personalproject.service;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;
/**
* A mathematical expression evaluator that can handle basic arithmetic operations.
*/
public final class MathExpressionEvaluator {
private static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
private static final Map<Character, Integer> PRECEDENCE = new HashMap<>();
static {
PRECEDENCE.put('+', 1);
PRECEDENCE.put('-', 1);
PRECEDENCE.put('*', 2);
PRECEDENCE.put('/', 2);
PRECEDENCE.put('^', 3);
}
private MathExpressionEvaluator() {
// Prevent instantiation of utility class
}
/**
* Evaluates a mathematical expression string.
*
* @param expression The mathematical expression to evaluate
* @return The result of the evaluation
* @throws IllegalArgumentException If the expression is invalid
*/
public static double evaluate(String expression) {
if (expression == null) {
throw new IllegalArgumentException("Expression cannot be null");
}
expression = expression.replaceAll("\\s+", ""); // Remove whitespace
if (expression.isEmpty()) {
throw new IllegalArgumentException("Expression cannot be empty");
}
// Tokenize the expression
String[] tokens = tokenize(expression);
// Convert infix to postfix notation using Shunting Yard algorithm
String[] postfix = infixToPostfix(tokens);
// Evaluate the postfix expression
return evaluatePostfix(postfix);
}
/**
* Tokenizes the expression into numbers and operators.
*
* @param expression The expression to tokenize
* @return An array of tokens
*/
private static String[] tokenize(String expression) {
java.util.List<String> tokens = new java.util.ArrayList<>();
StringBuilder currentNumber = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (Character.isDigit(c) || c == '.') {
currentNumber.append(c);
} else if (c == '(' || c == ')') {
if (currentNumber.length() > 0) {
tokens.add(currentNumber.toString());
currentNumber.setLength(0);
}
tokens.add(String.valueOf(c));
} else if (isOperator(c)) {
if (currentNumber.length() > 0) {
tokens.add(currentNumber.toString());
currentNumber.setLength(0);
}
// Handle unary minus
if (c == '-' && (i == 0 || expression.charAt(i - 1) == '(')) {
currentNumber.append(c);
} else {
tokens.add(String.valueOf(c));
}
} else {
throw new IllegalArgumentException("Invalid character in expression: " + c);
}
}
if (currentNumber.length() > 0) {
tokens.add(currentNumber.toString());
}
return tokens.toArray(new String[0]);
}
/**
* Checks if the character is an operator.
*
* @param c The character to check
* @return true if the character is an operator, false otherwise
*/
private static boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/' || c == '^';
}
/**
* Converts infix notation to postfix notation using the Shunting Yard algorithm.
*
* @param tokens The tokens in infix notation
* @return An array of tokens in postfix notation
*/
private static String[] infixToPostfix(String[] tokens) {
java.util.List<String> output = new java.util.ArrayList<>();
Stack<String> operators = new Stack<>();
for (String token : tokens) {
if (isNumber(token)) {
output.add(token);
} else if (token.equals("(")) {
operators.push(token);
} else if (token.equals(")")) {
while (!operators.isEmpty() && !operators.peek().equals("(")) {
output.add(operators.pop());
}
if (!operators.isEmpty()) {
operators.pop(); // Remove the "("
}
} else if (isOperator(token.charAt(0))) {
while (!operators.isEmpty()
&& isOperator(operators.peek().charAt(0))
&& PRECEDENCE.get(operators.peek().charAt(0)) >= PRECEDENCE.get(token.charAt(0))) {
output.add(operators.pop());
}
operators.push(token);
}
}
while (!operators.isEmpty()) {
output.add(operators.pop());
}
return output.toArray(new String[0]);
}
/**
* Evaluates a postfix expression.
*
* @param postfix The tokens in postfix notation
* @return The result of the evaluation
*/
private static double evaluatePostfix(String[] postfix) {
Stack<Double> values = new Stack<>();
for (String token : postfix) {
if (isNumber(token)) {
values.push(Double.parseDouble(token));
} else if (isOperator(token.charAt(0))) {
if (values.size() < 2) {
throw new IllegalArgumentException("Invalid expression: insufficient operands");
}
double b = values.pop();
double a = values.pop();
double result = performOperation(a, b, token.charAt(0));
values.push(result);
}
}
if (values.size() != 1) {
throw new IllegalArgumentException("Invalid expression: too many operands");
}
return values.pop();
}
/**
* Performs the specified operation on the two operands.
*
* @param a The first operand
* @param b The second operand
* @param operator The operator to apply
* @return The result of the operation
*/
private static double performOperation(double a, double b, char operator) {
switch (operator) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
case '^':
return Math.pow(a, b);
default:
throw new IllegalArgumentException("Unknown operator: " + operator);
}
}
/**
* Checks if the token is a number.
*
* @param token The token to check
* @return true if the token is a number, false otherwise
*/
private static boolean isNumber(String token) {
return NUMBER_PATTERN.matcher(token).matches();
}
}

@ -0,0 +1,170 @@
package com.personalproject.service;
import com.personalproject.auth.AccountRepository;
import com.personalproject.auth.EmailService;
import com.personalproject.auth.PasswordValidator;
import com.personalproject.generator.QuestionGenerator;
import com.personalproject.model.DifficultyLevel;
import com.personalproject.model.ExamSession;
import com.personalproject.storage.QuestionStorageService;
import java.util.Map;
import java.util.Optional;
/**
* JavaFX UI. UIAPI.
*/
public final class MathLearningService {
private final AccountRepository accountRepository;
private final RegistrationService registrationService;
private final ExamService examService;
private final QuestionStorageService storageService;
private final ExamResultService resultService;
/**
* .
*
* @param generatorMap
* @param questionGenerationService
*/
public MathLearningService(
Map<DifficultyLevel, QuestionGenerator> generatorMap,
QuestionGenerationService questionGenerationService) {
this.accountRepository = new AccountRepository();
this.registrationService = new RegistrationService(accountRepository);
this.examService = new ExamService(generatorMap, questionGenerationService);
this.storageService = new QuestionStorageService();
this.resultService = new ExamResultService();
}
// 注册方法
/**
* .
*
* @param username
* @param email
* @param difficultyLevel
* @return truefalse
*/
public boolean initiateRegistration(String username, String email,
DifficultyLevel difficultyLevel) {
return registrationService.initiateRegistration(username, email, difficultyLevel);
}
/**
* .
*
* @param username
* @param registrationCode
* @return truefalse
*/
public boolean verifyRegistrationCode(String username, String registrationCode) {
return registrationService.verifyRegistrationCode(username, registrationCode);
}
/**
* .
*
* @param username
* @param password
* @return truefalse
*/
public boolean setPassword(String username, String password) {
return registrationService.setPassword(username, password);
}
/**
* 使.
*
* @param username
* @param password
* @return Optional
*/
public Optional<com.personalproject.auth.UserAccount> authenticate(String username,
String password) {
return registrationService.authenticate(username, password);
}
/**
* .
*
* @param password
* @return truefalse
*/
public static boolean isValidPassword(String password) {
return PasswordValidator.isValidPassword(password);
}
/**
* .
*
* @param username
* @param oldPassword
* @param newPassword
* @return truefalse
*/
public boolean changePassword(String username, String oldPassword, String newPassword) {
return registrationService.changePassword(username, oldPassword, newPassword);
}
/**
* .
*
* @param email
* @return truefalse
*/
public static boolean isValidEmail(String email) {
return EmailService.isValidEmail(email);
}
/**
* .
*
* @param username
* @param difficultyLevel
* @param questionCount
* @return
*/
public ExamSession createExamSession(String username, DifficultyLevel difficultyLevel,
int questionCount) {
return examService.createExamSession(username, difficultyLevel, questionCount);
}
/**
* .
*
* @param examSession
* @return
*/
public java.nio.file.Path saveExamResults(ExamSession examSession) {
try {
return storageService.saveExamResults(examSession);
} catch (java.io.IOException e) {
throw new RuntimeException("保存考试结果失败", e);
}
}
/**
* .
*
* @param examSession
* @param continueWithSameLevel 使
* @param newDifficultyLevel null
* @return
*/
public ExamResultService.ExamContinuationAction processExamResult(
ExamSession examSession, boolean continueWithSameLevel, DifficultyLevel newDifficultyLevel) {
return resultService.processExamResult(examSession, continueWithSameLevel, newDifficultyLevel);
}
/**
* .
*
* @param username
* @return truefalse
*/
public boolean userExists(String username) {
return registrationService.userExists(username);
}
}

@ -0,0 +1,71 @@
package com.personalproject.service;
import com.personalproject.generator.QuestionGenerator;
import com.personalproject.model.DifficultyLevel;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* .
*/
public final class QuestionGenerationService {
private static final int MAX_ATTEMPTS = 10_000;
private final Map<DifficultyLevel, QuestionGenerator> generators;
private final Random random = new SecureRandom();
/**
* .
*
* @param generatorMap .
*/
public QuestionGenerationService(Map<DifficultyLevel, QuestionGenerator> generatorMap) {
generators = new EnumMap<>(DifficultyLevel.class);
generators.putAll(generatorMap);
}
/**
* .
*
* @param level .
* @param count .
* @param existingQuestions .
* @return .
* @throws IllegalArgumentException .
* @throws IllegalStateException .
*/
public List<String> generateUniqueQuestions(
DifficultyLevel level, int count, Set<String> existingQuestions) {
QuestionGenerator generator = generators.get(level);
if (generator == null) {
throw new IllegalArgumentException("Unsupported difficulty level: " + level);
}
Set<String> produced = new HashSet<>();
List<String> results = new ArrayList<>();
int attempts = 0;
while (results.size() < count) {
if (attempts >= MAX_ATTEMPTS) {
throw new IllegalStateException("Unable to generate enough unique questions.");
}
attempts++;
String question = generator.generateQuestion(random).trim();
if (question.isEmpty()) {
continue;
}
if (existingQuestions.contains(question)) {
continue;
}
if (!produced.add(question)) {
continue;
}
results.add(question);
}
return results;
}
}

@ -0,0 +1,138 @@
package com.personalproject.service;
import com.personalproject.auth.AccountRepository;
import com.personalproject.auth.EmailService;
import com.personalproject.auth.PasswordValidator;
import com.personalproject.model.DifficultyLevel;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* .
*/
public final class RegistrationService {
private final AccountRepository accountRepository;
private final Map<String, String> pendingRegistrations;
private final Map<String, Integer> registrationAttempts;
/**
* .
*
* @param accountRepository 使
*/
public RegistrationService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
this.pendingRegistrations = new ConcurrentHashMap<>();
this.registrationAttempts = new ConcurrentHashMap<>();
}
/**
* .
*
* @param username
* @param email
* @param difficultyLevel
* @return truefalse
*/
public boolean initiateRegistration(String username, String email,
DifficultyLevel difficultyLevel) {
if (!EmailService.isValidEmail(email)) {
return false;
}
if (!accountRepository.registerUser(username, email, difficultyLevel)) {
return false; // 用户名已存在或邮箱已注册
}
String registrationCode = EmailService.generateRegistrationCode();
pendingRegistrations.put(username, registrationCode);
registrationAttempts.put(username, 0);
return EmailService.sendRegistrationCode(email, registrationCode);
}
/**
* .
*
* @param username
* @param registrationCode
* @return truefalse
*/
public boolean verifyRegistrationCode(String username, String registrationCode) {
String storedCode = pendingRegistrations.get(username);
if (storedCode == null || !storedCode.equals(registrationCode)) {
// 跟踪失败尝试
int attempts = registrationAttempts.getOrDefault(username, 0);
attempts++;
registrationAttempts.put(username, attempts);
if (attempts >= 3) {
// 如果失败次数过多,则删除用户
pendingRegistrations.remove(username);
registrationAttempts.remove(username);
return false;
}
return false;
}
// 有效码,从待处理列表中移除
pendingRegistrations.remove(username);
registrationAttempts.remove(username);
return true;
}
/**
* .
*
* @param username
* @param password
* @return truefalse
*/
public boolean setPassword(String username, String password) {
if (!PasswordValidator.isValidPassword(password)) {
return false;
}
return accountRepository.setPassword(username, password);
}
/**
* .
*
* @param username
* @param oldPassword
* @param newPassword
* @return truefalse
*/
public boolean changePassword(String username, String oldPassword, String newPassword) {
if (!PasswordValidator.isValidPassword(newPassword)) {
return false;
}
return accountRepository.changePassword(username, oldPassword, newPassword);
}
/**
* 使.
*
* @param username
* @param password
* @return Optional
*/
public Optional<com.personalproject.auth.UserAccount> authenticate(String username,
String password) {
return accountRepository.authenticate(username, password);
}
/**
* .
*
* @param username
* @return truefalse
*/
public boolean userExists(String username) {
return accountRepository.userExists(username);
}
}

@ -0,0 +1,162 @@
package com.personalproject.storage;
import com.personalproject.model.ExamSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
/**
* .
*/
public final class QuestionStorageService {
private static final String BASE_DIRECTORY = "user_data";
private static final String QUESTIONS_SUBDIR = "questions";
private static final String RESULTS_SUBDIR = "results";
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
/**
* .
*
* @param username .
* @return .
* @throws IOException I/O .
*/
public Set<String> loadExistingQuestions(String username) throws IOException {
Path accountDirectory = getQuestionsDirectory(username);
Set<String> questions = new HashSet<>();
if (!Files.exists(accountDirectory)) {
return questions;
}
try (Stream<Path> paths = Files.list(accountDirectory)) {
paths
.filter(path -> path.getFileName().toString().endsWith(".txt"))
.sorted()
.forEach(path -> readQuestionsFromFile(path, questions));
}
return questions;
}
/**
* .
*
* @param username .
* @param questions .
* @return .
* @throws IOException .
*/
public Path saveQuestions(String username, List<String> questions) throws IOException {
Path accountDirectory = getQuestionsDirectory(username);
Files.createDirectories(accountDirectory);
String fileName = FORMATTER.format(LocalDateTime.now()) + ".txt";
Path outputFile = accountDirectory.resolve(fileName);
StringBuilder builder = new StringBuilder();
for (int index = 0; index < questions.size(); index++) {
String question = questions.get(index);
builder
.append(index + 1)
.append(". ")
.append(question)
.append(System.lineSeparator())
.append(System.lineSeparator());
}
Files.writeString(
outputFile, builder.toString(), StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW);
return outputFile;
}
/**
* .
*
* @param examSession
* @return
* @throws IOException
*/
public Path saveExamResults(ExamSession examSession) throws IOException {
Path resultsDirectory = getResultsDirectory(examSession.getUsername());
Files.createDirectories(resultsDirectory);
StringBuilder builder = new StringBuilder();
builder.append("考试结果报告").append(System.lineSeparator());
builder.append("用户名: ").append(examSession.getUsername()).append(System.lineSeparator());
builder.append("难度: ").append(examSession.getDifficultyLevel().getDisplayName())
.append(System.lineSeparator());
builder.append("开始时间: ").append(examSession.getStartTime()).append(System.lineSeparator());
builder.append("题目数量: ").append(examSession.getQuestions().size())
.append(System.lineSeparator());
builder.append("得分: ").append(String.format("%.2f", examSession.calculateScore())).append("%")
.append(System.lineSeparator());
builder.append(System.lineSeparator());
// 添加逐题结果
for (int i = 0; i < examSession.getQuestions().size(); i++) {
var question = examSession.getQuestions().get(i);
int userAnswer = examSession.getUserAnswer(i);
boolean isCorrect = question.isAnswerCorrect(userAnswer);
builder.append("题目 ").append(i + 1).append(": ").append(question.getQuestionText())
.append(System.lineSeparator());
builder.append("您的答案: ").append(userAnswer == -1 ? "未回答" :
(userAnswer < question.getOptions().size() ? question.getOptions().get(userAnswer)
: "无效")).append(System.lineSeparator());
builder.append("正确答案: ").append(
question.getOptions().get(question.getCorrectAnswerIndex()))
.append(System.lineSeparator());
builder.append("结果: ").append(isCorrect ? "正确" : "错误").append(System.lineSeparator());
builder.append(System.lineSeparator());
}
String fileName = "exam_result_" + FORMATTER.format(LocalDateTime.now()) + ".txt";
Path outputFile = resultsDirectory.resolve(fileName);
Files.writeString(
outputFile, builder.toString(), StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW);
return outputFile;
}
private Path getAccountDirectory(String username) {
return Paths.get(BASE_DIRECTORY, username);
}
private Path getQuestionsDirectory(String username) {
return getAccountDirectory(username).resolve(QUESTIONS_SUBDIR);
}
private Path getResultsDirectory(String username) {
return getAccountDirectory(username).resolve(RESULTS_SUBDIR);
}
private void readQuestionsFromFile(Path path, Set<String> questions) {
try {
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
for (String line : lines) {
String trimmed = line.trim();
if (trimmed.isEmpty()) {
continue;
}
int dotIndex = trimmed.indexOf('.');
if (dotIndex >= 0 && dotIndex + 1 < trimmed.length()) {
String question = trimmed.substring(dotIndex + 1).trim();
if (!question.isEmpty()) {
questions.add(question);
}
} else {
questions.add(trimmed);
}
}
} catch (IOException exception) {
System.err.println(
"读取题目文件失败:" + path + ",原因:" + exception.getMessage() + ",将跳过该文件.");
}
}
}
Loading…
Cancel
Save