diff --git a/pom.xml b/pom.xml index c62513a..70e9913 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,13 @@ ${javafx.version} + + + com.sun.mail + jakarta.mail + 2.0.1 + + org.junit.jupiter @@ -140,4 +147,4 @@ - \ No newline at end of file + diff --git a/src/com/personalproject/auth/AccountRepository.java b/src/com/personalproject/auth/AccountRepository.java deleted file mode 100644 index c5a00a2..0000000 --- a/src/com/personalproject/auth/AccountRepository.java +++ /dev/null @@ -1,140 +0,0 @@ -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 accounts = new HashMap<>(); - private final Map registrationCodes = new HashMap<>(); - - /** - * 根据用户名与密码查找匹配的账号. - * - * @param username 用户名. - * @param password 密码. - * @return 匹配成功时返回账号信息,否则返回空结果. - */ - public Optional 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 getUser(String username) { - return Optional.ofNullable(accounts.get(username.trim())); - } -} diff --git a/src/com/personalproject/auth/EmailService.java b/src/com/personalproject/auth/EmailService.java deleted file mode 100644 index d790c83..0000000 --- a/src/com/personalproject/auth/EmailService.java +++ /dev/null @@ -1,62 +0,0 @@ -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); - } -} \ No newline at end of file diff --git a/src/com/personalproject/auth/PasswordValidator.java b/src/com/personalproject/auth/PasswordValidator.java deleted file mode 100644 index acece6f..0000000 --- a/src/com/personalproject/auth/PasswordValidator.java +++ /dev/null @@ -1,30 +0,0 @@ -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(); - } -} \ No newline at end of file diff --git a/src/com/personalproject/auth/UserAccount.java b/src/com/personalproject/auth/UserAccount.java deleted file mode 100644 index 80d5acf..0000000 --- a/src/com/personalproject/auth/UserAccount.java +++ /dev/null @@ -1,40 +0,0 @@ -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"); - } - } -} diff --git a/src/com/personalproject/controller/MathLearningController.java b/src/com/personalproject/controller/MathLearningController.java deleted file mode 100644 index c847b94..0000000 --- a/src/com/personalproject/controller/MathLearningController.java +++ /dev/null @@ -1,145 +0,0 @@ -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 generatorMap, - QuestionGenerationService questionGenerationService) { - this.mathLearningService = new MathLearningService(generatorMap, questionGenerationService); - } - - /** - * 启动用户注册. - * - * @param username 所需用户名 - * @param email 用户的电子邮箱地址 - * @param difficultyLevel 选择的难度级别 - * @return 注册成功启动则返回true,否则返回false - */ - public boolean initiateRegistration(String username, String email, - DifficultyLevel difficultyLevel) { - return mathLearningService.initiateRegistration(username, email, difficultyLevel); - } - - /** - * 验证注册码. - * - * @param username 用户名 - * @param registrationCode 注册码 - * @return 码有效则返回true,否则返回false - */ - public boolean verifyRegistrationCode(String username, String registrationCode) { - return mathLearningService.verifyRegistrationCode(username, registrationCode); - } - - /** - * 注册后设置用户密码. - * - * @param username 用户名 - * @param password 要设置的密码 - * @return 密码设置成功则返回true,否则返回false - */ - public boolean setPassword(String username, String password) { - return mathLearningService.setPassword(username, password); - } - - /** - * 验证用户. - * - * @param username 用户名 - * @param password 密码 - * @return 验证成功则返回包含用户账户的Optional - */ - public Optional 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 密码更改成功则返回true,否则返回false - */ - public boolean changePassword(String username, String oldPassword, String newPassword) { - return mathLearningService.changePassword(username, oldPassword, newPassword); - } - - /** - * 验证密码. - * - * @param password 要验证的密码 - * @return 密码有效则返回true,否则返回false - */ - public boolean isValidPassword(String password) { - return MathLearningService.isValidPassword(password); - } - - /** - * 验证电子邮箱地址. - * - * @param email 要验证的邮箱 - * @return 邮箱有效则返回true,否则返回false - */ - public boolean isValidEmail(String email) { - return MathLearningService.isValidEmail(email); - } -} \ No newline at end of file diff --git a/src/com/personalproject/generator/HighSchoolQuestionGenerator.java b/src/com/personalproject/generator/HighSchoolQuestionGenerator.java deleted file mode 100644 index d22bf53..0000000 --- a/src/com/personalproject/generator/HighSchoolQuestionGenerator.java +++ /dev/null @@ -1,37 +0,0 @@ -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; - } -} diff --git a/src/com/personalproject/generator/MiddleSchoolQuestionGenerator.java b/src/com/personalproject/generator/MiddleSchoolQuestionGenerator.java deleted file mode 100644 index ad46678..0000000 --- a/src/com/personalproject/generator/MiddleSchoolQuestionGenerator.java +++ /dev/null @@ -1,39 +0,0 @@ -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; - } -} diff --git a/src/com/personalproject/generator/PrimaryQuestionGenerator.java b/src/com/personalproject/generator/PrimaryQuestionGenerator.java deleted file mode 100644 index 9073153..0000000 --- a/src/com/personalproject/generator/PrimaryQuestionGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -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; - } -} diff --git a/src/com/personalproject/generator/QuestionGenerator.java b/src/com/personalproject/generator/QuestionGenerator.java deleted file mode 100644 index a2a5ea7..0000000 --- a/src/com/personalproject/generator/QuestionGenerator.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.personalproject.generator; - -import java.util.Random; - -/** - * 负责生成单条数学题目的表达式. - */ -public interface QuestionGenerator { - - /** - * 基于提供的随机数生成器构造一道题目的表达式. - * - * @param random 用于生成随机数的实例. - * @return 生成的题目表达式. - */ - String generateQuestion(Random random); -} diff --git a/src/com/personalproject/model/DifficultyLevel.java b/src/com/personalproject/model/DifficultyLevel.java deleted file mode 100644 index bc654bb..0000000 --- a/src/com/personalproject/model/DifficultyLevel.java +++ /dev/null @@ -1,49 +0,0 @@ -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 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(); - } -} diff --git a/src/com/personalproject/model/ExamSession.java b/src/com/personalproject/model/ExamSession.java deleted file mode 100644 index 6437ba5..0000000 --- a/src/com/personalproject/model/ExamSession.java +++ /dev/null @@ -1,195 +0,0 @@ -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 questions; - private final List 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 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 getQuestions() { - return questions; - } - - /** - * Gets the user's answers to the questions. - * - * @return A list of answer indices (-1 means no answer selected) - */ - public List 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; - } -} \ No newline at end of file diff --git a/src/com/personalproject/model/QuizQuestion.java b/src/com/personalproject/model/QuizQuestion.java deleted file mode 100644 index e66f14c..0000000 --- a/src/com/personalproject/model/QuizQuestion.java +++ /dev/null @@ -1,73 +0,0 @@ -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 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 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 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; - } -} \ No newline at end of file diff --git a/src/com/personalproject/service/ExamResultService.java b/src/com/personalproject/service/ExamResultService.java deleted file mode 100644 index 55a79f6..0000000 --- a/src/com/personalproject/service/ExamResultService.java +++ /dev/null @@ -1,83 +0,0 @@ -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; - } - } -} \ No newline at end of file diff --git a/src/com/personalproject/service/ExamService.java b/src/com/personalproject/service/ExamService.java deleted file mode 100644 index 392a83a..0000000 --- a/src/com/personalproject/service/ExamService.java +++ /dev/null @@ -1,140 +0,0 @@ -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 generators; - private final Random random = new Random(); - private final QuestionGenerationService questionGenerationService; - - /** - * 创建新的考试服务. - * - * @param generatorMap 难度级别到题目生成器的映射 - * @param questionGenerationService 题目生成服务 - */ - public ExamService( - Map 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 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 quizQuestions = new ArrayList<>(); - for (String questionText : generatedQuestions) { - List 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 questions) { - if (questions.size() < 10 || questions.size() > 30) { - throw new IllegalArgumentException("题目数量必须在10到30之间"); - } - return new ExamSession(username, difficultyLevel, questions); - } - - /** - * 为给定题目生成多项选择选项. 这是一个模拟实现 - 在实际系统中,选项会根据题目生成. - * - * @param questionText 题目文本 - * @return 答案选项列表 - */ - private List generateOptions(String questionText) { - List 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 0到optionCount-1之间的随机索引 - */ - private int generateCorrectAnswerIndex(int optionCount) { - return random.nextInt(optionCount); - } -} \ No newline at end of file diff --git a/src/com/personalproject/service/MathExpressionEvaluator.java b/src/com/personalproject/service/MathExpressionEvaluator.java deleted file mode 100644 index 6e32ed1..0000000 --- a/src/com/personalproject/service/MathExpressionEvaluator.java +++ /dev/null @@ -1,217 +0,0 @@ -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 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 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 output = new java.util.ArrayList<>(); - Stack 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 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(); - } -} \ No newline at end of file diff --git a/src/com/personalproject/service/MathLearningService.java b/src/com/personalproject/service/MathLearningService.java deleted file mode 100644 index e404ddb..0000000 --- a/src/com/personalproject/service/MathLearningService.java +++ /dev/null @@ -1,170 +0,0 @@ -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提供所有功能的服务类. 此类协调各种服务以为UI提供统一的API. - */ -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 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 如果注册成功启动则返回true,如果电子邮件无效或用户名已存在则返回false - */ - public boolean initiateRegistration(String username, String email, - DifficultyLevel difficultyLevel) { - return registrationService.initiateRegistration(username, email, difficultyLevel); - } - - /** - * 验证用户输入的注册码. - * - * @param username 用户名 - * @param registrationCode 通过电子邮件收到的注册码 - * @return 如果注册码有效则返回true,否则返回false - */ - public boolean verifyRegistrationCode(String username, String registrationCode) { - return registrationService.verifyRegistrationCode(username, registrationCode); - } - - /** - * 在成功验证注册码后为用户设置密码. - * - * @param username 用户名 - * @param password 要设置的密码 - * @return 如果密码设置成功则返回true,如果验证失败或用户不存在则返回false - */ - public boolean setPassword(String username, String password) { - return registrationService.setPassword(username, password); - } - - /** - * 使用用户名和密码验证用户. - * - * @param username 用户名 - * @param password 密码 - * @return 如果验证成功则返回包含用户账户的Optional - */ - public Optional authenticate(String username, - String password) { - return registrationService.authenticate(username, password); - } - - /** - * 检查密码是否符合验证要求. - * - * @param password 要验证的密码 - * @return 如果密码有效则返回true,否则返回false - */ - public static boolean isValidPassword(String password) { - return PasswordValidator.isValidPassword(password); - } - - /** - * 更改现有用户的密码. - * - * @param username 用户名 - * @param oldPassword 当前密码 - * @param newPassword 新密码 - * @return 如果密码更改成功则返回true,如果验证失败或验证失败则返回false - */ - public boolean changePassword(String username, String oldPassword, String newPassword) { - return registrationService.changePassword(username, oldPassword, newPassword); - } - - /** - * 检查电子邮件地址是否具有有效格式. - * - * @param email 要验证的电子邮件地址 - * @return 如果电子邮件格式有效则返回true,否则返回false - */ - 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 如果用户存在则返回true,否则返回false - */ - public boolean userExists(String username) { - return registrationService.userExists(username); - } -} \ No newline at end of file diff --git a/src/com/personalproject/service/QuestionGenerationService.java b/src/com/personalproject/service/QuestionGenerationService.java deleted file mode 100644 index b536a7d..0000000 --- a/src/com/personalproject/service/QuestionGenerationService.java +++ /dev/null @@ -1,71 +0,0 @@ -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 generators; - private final Random random = new SecureRandom(); - - /** - * 构造函数:复制难度与生成器的映射,保留内部安全副本. - * - * @param generatorMap 难度到题目生成器的外部映射. - */ - public QuestionGenerationService(Map generatorMap) { - generators = new EnumMap<>(DifficultyLevel.class); - generators.putAll(generatorMap); - } - - /** - * 根据指定难度批量生成不与历史题目重复的新题目. - * - * @param level 目标题目难度. - * @param count 需要生成的题目数量. - * @param existingQuestions 已存在的题目集合,用于查重. - * @return 生成的题目列表. - * @throws IllegalArgumentException 当难度未配置时抛出. - * @throws IllegalStateException 当达到最大尝试次数仍无法生成足够题目时抛出. - */ - public List generateUniqueQuestions( - DifficultyLevel level, int count, Set existingQuestions) { - QuestionGenerator generator = generators.get(level); - if (generator == null) { - throw new IllegalArgumentException("Unsupported difficulty level: " + level); - } - Set produced = new HashSet<>(); - List 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; - } -} diff --git a/src/com/personalproject/service/RegistrationService.java b/src/com/personalproject/service/RegistrationService.java deleted file mode 100644 index 100b58d..0000000 --- a/src/com/personalproject/service/RegistrationService.java +++ /dev/null @@ -1,138 +0,0 @@ -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 pendingRegistrations; - private final Map 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 注册成功启动则返回true,如果电子邮件无效或用户名已存在则返回false - */ - 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 注册码有效则返回true,否则返回false - */ - 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 密码设置成功则返回true,如果验证失败或用户不存在则返回false - */ - 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 密码更改成功则返回true,如果验证失败或身份验证失败则返回false - */ - 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 authenticate(String username, - String password) { - return accountRepository.authenticate(username, password); - } - - /** - * 检查用户是否存在于系统中. - * - * @param username 要检查的用户名 - * @return 用户存在则返回true,否则返回false - */ - public boolean userExists(String username) { - return accountRepository.userExists(username); - } -} \ No newline at end of file diff --git a/src/com/personalproject/storage/QuestionStorageService.java b/src/com/personalproject/storage/QuestionStorageService.java deleted file mode 100644 index 4fc9870..0000000 --- a/src/com/personalproject/storage/QuestionStorageService.java +++ /dev/null @@ -1,162 +0,0 @@ -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 loadExistingQuestions(String username) throws IOException { - Path accountDirectory = getQuestionsDirectory(username); - Set questions = new HashSet<>(); - if (!Files.exists(accountDirectory)) { - return questions; - } - try (Stream 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 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 questions) { - try { - List 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() + ",将跳过该文件."); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/personalproject/auth/AccountRepository.java b/src/main/java/com/personalproject/auth/AccountRepository.java index c5a00a2..2a2f665 100644 --- a/src/main/java/com/personalproject/auth/AccountRepository.java +++ b/src/main/java/com/personalproject/auth/AccountRepository.java @@ -1,18 +1,34 @@ package com.personalproject.auth; import com.personalproject.model.DifficultyLevel; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; -import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; /** * 存放用户账号并提供认证和注册查询能力. */ public final class AccountRepository { - private final Map accounts = new HashMap<>(); - private final Map registrationCodes = new HashMap<>(); + private static final Path ACCOUNTS_DIRECTORY = Paths.get("user_data", "accounts"); + private static final String FILE_EXTENSION = ".properties"; + + private final Map accounts = new ConcurrentHashMap<>(); + + public AccountRepository() { + loadAccounts(); + } /** * 根据用户名与密码查找匹配的账号. @@ -25,116 +41,280 @@ public final class AccountRepository { if (username == null || password == null) { return Optional.empty(); } - UserAccount account = accounts.get(username.trim()); + String normalizedUsername = username.trim(); + String normalizedPassword = password.trim(); + UserAccount account = accounts.get(normalizedUsername); if (account == null || !account.isRegistered()) { return Optional.empty(); } - if (!account.password().equals(password.trim())) { + if (!account.password().equals(hashPassword(normalizedPassword))) { 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 + * @param username 用户名 + * @param email 邮箱地址 + * @param difficultyLevel 选择的难度级别 + * @return 若注册成功则返回 true,若用户名已存在则返回 false */ - public boolean registerUser(String username, String email, DifficultyLevel difficultyLevel) { - String trimmedUsername = username.trim(); - String trimmedEmail = email.trim(); + public synchronized boolean registerUser(String username, String email, + DifficultyLevel difficultyLevel) { + if (username == null || email == null || difficultyLevel == null) { + return false; + } - if (accounts.containsKey(trimmedUsername)) { - return false; // Username already exists + String normalizedUsername = username.trim(); + String normalizedEmail = email.trim(); + if (normalizedUsername.isEmpty() || normalizedEmail.isEmpty()) { + return false; } - // 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 existing = accounts.get(normalizedUsername); + if (existing != null && existing.isRegistered()) { + return false; } - UserAccount newAccount = new UserAccount( - trimmedUsername, - trimmedEmail, - "", // Empty password initially + if (isEmailInUse(normalizedEmail, normalizedUsername)) { + return false; + } + + LocalDateTime registrationDate = existing != null ? existing.registrationDate() : LocalDateTime.now(); + UserAccount account = new UserAccount( + normalizedUsername, + normalizedEmail, + "", difficultyLevel, - LocalDateTime.now(), - false); // Not registered until password is set - accounts.put(trimmedUsername, newAccount); + registrationDate, + false); + + accounts.put(normalizedUsername, account); + persistAccount(account); 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 + * @param username 用户名 + * @param password 要设置的密码 + * @return 设置成功返回 true,若用户不存在则返回 false */ - public boolean setPassword(String username, String password) { - UserAccount account = accounts.get(username.trim()); - if (account == null) { + public synchronized boolean setPassword(String username, String password) { + if (username == null || password == null) { + return false; + } + String normalizedUsername = username.trim(); + UserAccount account = accounts.get(normalizedUsername); + if (account == null || account.isRegistered()) { return false; } UserAccount updatedAccount = new UserAccount( account.username(), account.email(), - password, + hashPassword(password.trim()), account.difficultyLevel(), account.registrationDate(), - true); // Now registered - accounts.put(username.trim(), updatedAccount); + true); + accounts.put(normalizedUsername, updatedAccount); + persistAccount(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 + * @param username 用户名 + * @param oldPassword 当前密码 + * @param newPassword 新密码 + * @return 修改成功返回 true,若旧密码错误或用户不存在则返回 false */ - public boolean changePassword(String username, String oldPassword, String newPassword) { - UserAccount account = accounts.get(username.trim()); - if (account == null || !account.password().equals(oldPassword) || !account.isRegistered()) { + public synchronized boolean changePassword(String username, String oldPassword, + String newPassword) { + if (username == null || oldPassword == null || newPassword == null) { + return false; + } + String normalizedUsername = username.trim(); + UserAccount account = accounts.get(normalizedUsername); + if (account == null || !account.isRegistered()) { + return false; + } + + if (!account.password().equals(hashPassword(oldPassword.trim()))) { return false; } UserAccount updatedAccount = new UserAccount( account.username(), account.email(), - newPassword, + hashPassword(newPassword.trim()), account.difficultyLevel(), account.registrationDate(), true); - accounts.put(username.trim(), updatedAccount); + accounts.put(normalizedUsername, updatedAccount); + persistAccount(updatedAccount); return true; } /** - * Checks if a user exists in the system. + * 移除未完成注册的用户,便于重新注册。 + * + * @param username 待移除的用户名 + */ + public synchronized void removeUnverifiedUser(String username) { + if (username == null) { + return; + } + String normalizedUsername = username.trim(); + UserAccount account = accounts.get(normalizedUsername); + if (account != null && !account.isRegistered()) { + accounts.remove(normalizedUsername); + deleteAccountFile(normalizedUsername); + } + } + + /** + * 检查用户是否存在。 * - * @param username The username to check - * @return true if user exists, false otherwise + * @param username 待检查的用户名 + * @return 若存在则返回 true,否则返回 false */ public boolean userExists(String username) { + if (username == null) { + return false; + } return accounts.containsKey(username.trim()); } /** - * Gets a user account by username. + * 按用户名获取用户账户。 * - * @param username The username - * @return Optional containing the user account if found + * @param username 用户名 + * @return 若找到则返回包含用户账户的 Optional */ public Optional getUser(String username) { + if (username == null) { + return Optional.empty(); + } return Optional.ofNullable(accounts.get(username.trim())); } + + private void loadAccounts() { + try { + if (!Files.exists(ACCOUNTS_DIRECTORY)) { + Files.createDirectories(ACCOUNTS_DIRECTORY); + return; + } + + try (var paths = Files.list(ACCOUNTS_DIRECTORY)) { + paths + .filter(path -> path.getFileName().toString().endsWith(FILE_EXTENSION)) + .forEach(this::loadAccountFromFile); + } + } catch (IOException exception) { + throw new IllegalStateException("加载账户数据失败", exception); + } + } + + private void loadAccountFromFile(Path file) { + Properties properties = new Properties(); + try (Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { + properties.load(reader); + } catch (IOException exception) { + System.err.println("读取账户文件失败: " + file + ",原因: " + exception.getMessage()); + return; + } + + String username = properties.getProperty("username"); + if (username == null || username.isBlank()) { + return; + } + + String email = properties.getProperty("email", ""); + String passwordHash = properties.getProperty("passwordHash", ""); + String difficultyValue = properties.getProperty("difficulty", DifficultyLevel.PRIMARY.name()); + String registrationDateValue = properties.getProperty("registrationDate"); + String registeredValue = properties.getProperty("registered", "false"); + + try { + DifficultyLevel difficultyLevel = DifficultyLevel.valueOf(difficultyValue); + LocalDateTime registrationDate = registrationDateValue == null + ? LocalDateTime.now() + : LocalDateTime.parse(registrationDateValue); + boolean registered = Boolean.parseBoolean(registeredValue); + UserAccount account = new UserAccount( + username, + email, + passwordHash, + difficultyLevel, + registrationDate, + registered); + accounts.put(username, account); + } catch (Exception exception) { + System.err.println("解析账户文件失败: " + file + ",原因: " + exception.getMessage()); + } + } + + private void persistAccount(UserAccount account) { + Properties properties = new Properties(); + properties.setProperty("username", account.username()); + properties.setProperty("email", account.email()); + properties.setProperty("passwordHash", account.password()); + properties.setProperty("difficulty", account.difficultyLevel().name()); + properties.setProperty("registrationDate", account.registrationDate().toString()); + properties.setProperty("registered", Boolean.toString(account.isRegistered())); + + Path targetFile = accountFile(account.username()); + try { + if (!Files.exists(ACCOUNTS_DIRECTORY)) { + Files.createDirectories(ACCOUNTS_DIRECTORY); + } + try (Writer writer = Files.newBufferedWriter(targetFile, StandardCharsets.UTF_8)) { + properties.store(writer, "User account data"); + } + } catch (IOException exception) { + throw new IllegalStateException("保存账户数据失败: " + account.username(), exception); + } + } + + private void deleteAccountFile(String username) { + Path file = accountFile(username); + try { + Files.deleteIfExists(file); + } catch (IOException exception) { + System.err.println("删除账户文件失败: " + file + ",原因: " + exception.getMessage()); + } + } + + private boolean isEmailInUse(String email, String currentUsername) { + return accounts.values().stream() + .anyMatch(account -> account.email().equalsIgnoreCase(email) + && !account.username().equals(currentUsername)); + } + + private Path accountFile(String username) { + return ACCOUNTS_DIRECTORY.resolve(username + FILE_EXTENSION); + } + + private String hashPassword(String password) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(password.getBytes(StandardCharsets.UTF_8)); + return toHex(hashBytes); + } catch (NoSuchAlgorithmException exception) { + throw new IllegalStateException("当前运行环境不支持SHA-256算法", exception); + } + } + + private String toHex(byte[] bytes) { + StringBuilder builder = new StringBuilder(bytes.length * 2); + for (byte value : bytes) { + builder.append(String.format("%02x", value)); + } + return builder.toString(); + } } diff --git a/src/main/java/com/personalproject/auth/EmailService.java b/src/main/java/com/personalproject/auth/EmailService.java index d790c83..4d357bf 100644 --- a/src/main/java/com/personalproject/auth/EmailService.java +++ b/src/main/java/com/personalproject/auth/EmailService.java @@ -1,24 +1,54 @@ package com.personalproject.auth; +import jakarta.mail.Authenticator; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.PasswordAuthentication; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +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.Map; +import java.util.Optional; +import java.util.Properties; import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; /** - * 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 static final Path OUTBOX_DIRECTORY = Paths.get("user_data", "emails"); + private static final String CONFIG_RESOURCE = "email-config.properties"; + private static final DateTimeFormatter DATE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"); + private static final Map LAST_CODES = new ConcurrentHashMap<>(); + private static final AtomicReference CONFIG_CACHE = new AtomicReference<>(); + private static final String DEFAULT_SUBJECT = "数学学习软件注册验证码"; private EmailService() { - // Prevent instantiation of utility class + // 防止实例化此工具类 } /** - * Generates a random registration code. + * 生成随机注册码。 * - * @return A randomly generated registration code + * @return 随机生成的注册码 */ public static String generateRegistrationCode() { StringBuilder code = new StringBuilder(); @@ -29,34 +59,172 @@ public final class EmailService { } /** - * 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) + * @param email 收件人邮箱 + * @param registrationCode 要发送的注册码 + * @return 发送成功返回 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; + try { + MailConfiguration configuration = loadConfiguration(); + sendEmail(email, registrationCode, configuration); + persistMessage(email, registrationCode); + LAST_CODES.put(email.trim().toLowerCase(), registrationCode); + return true; + } catch (Exception exception) { + System.err.println("发送验证码失败: " + exception.getMessage()); + return false; + } } /** - * 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 + * @param email 收件人邮箱 + * @return 若存在则返回验证码 + */ + public static Optional getLastSentRegistrationCode(String email) { + if (email == null) { + return Optional.empty(); + } + return Optional.ofNullable(LAST_CODES.get(email.trim().toLowerCase())); + } + + /** + * 校验电子邮件地址的格式是否有效。 + * + * @param email 待校验的邮箱地址 + * @return 若格式有效则返回 true,否则返回 false */ 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); } -} \ No newline at end of file + + private static MailConfiguration loadConfiguration() throws IOException { + MailConfiguration cached = CONFIG_CACHE.get(); + if (cached != null) { + return cached; + } + + synchronized (CONFIG_CACHE) { + cached = CONFIG_CACHE.get(); + if (cached != null) { + return cached; + } + + Properties properties = new Properties(); + try (InputStream inputStream = + EmailService.class.getClassLoader().getResourceAsStream(CONFIG_RESOURCE)) { + if (inputStream == null) { + throw new IllegalStateException( + "类路径下缺少邮箱配置文件: " + CONFIG_RESOURCE + + ",请在 src/main/resources 下提供该文件"); + } + try (InputStreamReader reader = + new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + properties.load(reader); + } + } + + String username = require(properties, "mail.username"); + String password = require(properties, "mail.password"); + String from = properties.getProperty("mail.from", username); + String subject = properties.getProperty("mail.subject", DEFAULT_SUBJECT); + + Properties smtpProperties = new Properties(); + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey().toString(); + if (key.startsWith("mail.smtp.")) { + smtpProperties.put(key, entry.getValue()); + } + } + + if (!smtpProperties.containsKey("mail.smtp.host")) { + throw new IllegalStateException("邮箱配置缺少 mail.smtp.host"); + } + if (!smtpProperties.containsKey("mail.smtp.port")) { + throw new IllegalStateException("邮箱配置缺少 mail.smtp.port"); + } + + smtpProperties.putIfAbsent("mail.smtp.auth", "true"); + smtpProperties.putIfAbsent("mail.smtp.starttls.enable", "true"); + + MailConfiguration configuration = + new MailConfiguration(smtpProperties, username, password, from, subject); + CONFIG_CACHE.set(configuration); + return configuration; + } + } + + private static void sendEmail( + String recipientEmail, String registrationCode, MailConfiguration configuration) + throws MessagingException { + Session session = Session.getInstance(configuration.smtpProperties(), new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(configuration.username(), configuration.password()); + } + }); + + MimeMessage message = new MimeMessage(session); + message.setFrom(new InternetAddress(configuration.from())); + message.setRecipients( + Message.RecipientType.TO, + InternetAddress.parse(recipientEmail, false)); + message.setSubject(configuration.subject()); + + String body = "您好,您的注册码为:" + registrationCode + System.lineSeparator() + + "请在10分钟内使用该验证码完成注册。"; + message.setText(body, StandardCharsets.UTF_8.name()); + + Transport.send(message); + } + + private static void persistMessage(String email, String registrationCode) throws IOException { + if (!Files.exists(OUTBOX_DIRECTORY)) { + Files.createDirectories(OUTBOX_DIRECTORY); + } + + String sanitizedEmail = sanitizeEmail(email); + String timestamp = DATE_TIME_FORMATTER.format(LocalDateTime.now()); + Path messageFile = OUTBOX_DIRECTORY.resolve(sanitizedEmail + "_" + timestamp + ".txt"); + StringBuilder content = new StringBuilder(); + content.append("收件人: ").append(email).append(System.lineSeparator()); + content.append("注册码: ").append(registrationCode).append(System.lineSeparator()); + content.append("发送时间: ").append(LocalDateTime.now()).append(System.lineSeparator()); + + Files.writeString( + messageFile, + content.toString(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE_NEW, + StandardOpenOption.WRITE); + } + + private static String require(Properties properties, String key) { + String value = properties.getProperty(key); + if (value == null || value.trim().isEmpty()) { + throw new IllegalStateException("邮箱配置缺少必要字段: " + key); + } + return value.trim(); + } + + private static String sanitizeEmail(String email) { + return email.replaceAll("[^a-zA-Z0-9._-]", "_"); + } + + private record MailConfiguration( + Properties smtpProperties, + String username, + String password, + String from, + String subject) { + } +} diff --git a/src/main/java/com/personalproject/auth/PasswordValidator.java b/src/main/java/com/personalproject/auth/PasswordValidator.java index acece6f..53740d8 100644 --- a/src/main/java/com/personalproject/auth/PasswordValidator.java +++ b/src/main/java/com/personalproject/auth/PasswordValidator.java @@ -3,7 +3,7 @@ package com.personalproject.auth; import java.util.regex.Pattern; /** - * Utility class for password validation. + * 用于验证密码规则的工具类。 */ public final class PasswordValidator { @@ -11,20 +11,27 @@ public final class PasswordValidator { 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. + * 校验密码是否满足以下要求: + * - 长度 6-10 位 + * - 至少包含一个大写字母 + * - 至少包含一个小写字母 + * - 至少包含一个数字 * - * @param password The password to validate - * @return true if password meets requirements, false otherwise + * @param password 待校验的密码 + * @return 若满足要求则返回 true,否则返回 false */ public static boolean isValidPassword(String password) { if (password == null) { return false; } - return PASSWORD_PATTERN.matcher(password).matches(); + String normalized = password.trim(); + if (normalized.isEmpty()) { + return false; + } + return PASSWORD_PATTERN.matcher(normalized).matches(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/auth/UserAccount.java b/src/main/java/com/personalproject/auth/UserAccount.java index 80d5acf..07543a0 100644 --- a/src/main/java/com/personalproject/auth/UserAccount.java +++ b/src/main/java/com/personalproject/auth/UserAccount.java @@ -15,13 +15,13 @@ public record UserAccount( 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 + * @param username 用户名 + * @param email 邮箱地址 + * @param password 密码 + * @param difficultyLevel 选择的难度级别 + * @param isRegistered 用户是否已完成注册 */ public UserAccount { if (username == null || username.trim().isEmpty()) { diff --git a/src/main/java/com/personalproject/controller/MathLearningController.java b/src/main/java/com/personalproject/controller/MathLearningController.java index b791dd1..4edd694 100644 --- a/src/main/java/com/personalproject/controller/MathLearningController.java +++ b/src/main/java/com/personalproject/controller/MathLearningController.java @@ -144,12 +144,12 @@ public final class MathLearningController { } /** - * Gets a user account by username. + * 按用户名获取用户账户。 * - * @param username The username - * @return Optional containing the user account if found + * @param username 用户名 + * @return 若找到则返回包含用户账户的 Optional */ public Optional getUserAccount(String username) { return mathLearningService.getUser(username); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/generator/PrimaryQuestionGenerator.java b/src/main/java/com/personalproject/generator/PrimaryQuestionGenerator.java index 1c874b3..8041330 100644 --- a/src/main/java/com/personalproject/generator/PrimaryQuestionGenerator.java +++ b/src/main/java/com/personalproject/generator/PrimaryQuestionGenerator.java @@ -5,8 +5,10 @@ import com.personalproject.service.MathExpressionEvaluator; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Random; +import java.util.Set; /** * 生成包含基础四则运算的小学难度题目表达式. @@ -59,22 +61,18 @@ public final class PrimaryQuestionGenerator implements QuestionGenerator, QuizQu try { correctAnswer = MathExpressionEvaluator.evaluate(expression); } catch (Exception e) { - // Fallback if evaluation fails + // 如果计算失败则使用兜底值 correctAnswer = 0.0; } - // 生成选项 List options = generateOptions(correctAnswer, random); - - // 随机选择正确答案索引 - int correctAnswerIndex = random.nextInt(options.size()); - - // 确保正确答案在选项中 - String correctOption = String.format("%.2f", correctAnswer); - String oldOption = options.set(correctAnswerIndex, correctOption); - - // Add the displaced option back to maintain 4 options - options.add(oldOption); + String correctOption = formatOption(correctAnswer); + int correctAnswerIndex = options.indexOf(correctOption); + if (correctAnswerIndex < 0) { + // 兜底逻辑:确保正确答案存在于选项中 + options.set(0, correctOption); + correctAnswerIndex = 0; + } return new QuizQuestion(expression, options, correctAnswerIndex); } @@ -83,27 +81,40 @@ public final class PrimaryQuestionGenerator implements QuestionGenerator, QuizQu * 生成选择题选项 */ private List generateOptions(double correctAnswer, Random random) { - List options = new ArrayList<>(); - - // Add correct answer as one of the options - options.add(String.format("%.2f", correctAnswer)); + String correctOption = formatOption(correctAnswer); + Set optionSet = new LinkedHashSet<>(); + optionSet.add(correctOption); - // Add incorrect options - for (int i = 0; i < OPTIONS_COUNT - 1; i++) { - double incorrectAnswer = correctAnswer + (random.nextGaussian() * 10); // Add random offset - if (Math.abs(incorrectAnswer - correctAnswer) < 0.1) { // Ensure different - incorrectAnswer += 1.5; + double scale = Math.max(Math.abs(correctAnswer) * 0.2, 1.0); + int attempts = 0; + while (optionSet.size() < OPTIONS_COUNT && attempts < 100) { + double delta = random.nextGaussian() * scale; + if (Math.abs(delta) < 0.5) { + double direction = random.nextBoolean() ? 1 : -1; + delta = direction * (0.5 + random.nextDouble()) * scale; + } + double candidate = correctAnswer + delta; + if (Double.isNaN(candidate) || Double.isInfinite(candidate)) { + attempts++; + continue; } - options.add(String.format("%.2f", incorrectAnswer)); + optionSet.add(formatOption(candidate)); + attempts++; } - // Shuffle to randomize correct answer position + double step = Math.max(scale, 1.0); + while (optionSet.size() < OPTIONS_COUNT) { + double candidate = correctAnswer + step * optionSet.size(); + optionSet.add(formatOption(candidate)); + step += 1.0; + } + + List options = new ArrayList<>(optionSet); Collections.shuffle(options, random); - - // Find the correct answer index after shuffling - String correctAnswerStr = String.format("%.2f", correctAnswer); - int correctIndex = options.indexOf(correctAnswerStr); - return options; } + + private String formatOption(double value) { + return String.format("%.2f", value); + } } diff --git a/src/main/java/com/personalproject/model/ExamSession.java b/src/main/java/com/personalproject/model/ExamSession.java index 6fdb026..a57a808 100644 --- a/src/main/java/com/personalproject/model/ExamSession.java +++ b/src/main/java/com/personalproject/model/ExamSession.java @@ -2,10 +2,11 @@ package com.personalproject.model; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Represents an exam session for a user. + * 表示用户的考试会话。 */ public final class ExamSession { @@ -17,11 +18,11 @@ public final class ExamSession { 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 + * @param username 考生的用户名 + * @param difficultyLevel 考试的难度级别 + * @param questions 考试题目列表 */ public ExamSession(String username, DifficultyLevel difficultyLevel, List questions) { @@ -37,80 +38,77 @@ public final class ExamSession { 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.questions = List.copyOf(questions); // 题目的不可变副本 + this.userAnswers = new ArrayList<>(Collections.nCopies(questions.size(), -1)); this.startTime = LocalDateTime.now(); this.currentQuestionIndex = 0; } /** - * Gets the username of the test taker. + * 获取考生的用户名。 * - * @return The username + * @return 用户名 */ public String getUsername() { return username; } /** - * Gets the difficulty level of the exam. + * 获取考试的难度级别。 * - * @return The difficulty level + * @return 考试难度级别 */ public DifficultyLevel getDifficultyLevel() { return difficultyLevel; } /** - * Gets the list of questions in the exam. + * 获取考试中的题目列表。 * - * @return An unmodifiable list of questions + * @return 不可修改的题目列表 */ public List getQuestions() { return questions; } /** - * Gets the user's answers to the questions. + * 获取用户对各题的作答。 * - * @return A list of answer indices (-1 means no answer selected) + * @return 答案索引列表(-1 表示未选择) */ public List getUserAnswers() { - return List.copyOf(userAnswers); // Return a copy to prevent modification + return List.copyOf(userAnswers); // 返回副本以防止被修改 } /** - * Gets the current question index. + * 获取当前题目的索引。 * - * @return The current question index + * @return 当前题目索引 */ public int getCurrentQuestionIndex() { return currentQuestionIndex; } /** - * Sets the user's answer for the current question. + * 为当前题目记录用户的答案。 * - * @param answerIndex The index of the selected answer + * @param answerIndex 选中答案的索引 */ 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()) { + int optionCount = questions.get(currentQuestionIndex).getOptions().size(); + if (answerIndex < 0 || answerIndex >= optionCount) { 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 + * @return 若成功跳转则返回 true,若已是最后一题则返回 false */ public boolean goToNextQuestion() { if (currentQuestionIndex < questions.size() - 1) { @@ -121,9 +119,9 @@ public final class ExamSession { } /** - * Moves to the previous question. + * 返回上一题。 * - * @return true if successfully moved to previous question, false if already at the first question + * @return 若成功返回则返回 true,若已是第一题则返回 false */ public boolean goToPreviousQuestion() { if (currentQuestionIndex > 0) { @@ -134,18 +132,18 @@ public final class ExamSession { } /** - * Checks if the exam is complete (all questions answered or at the end). + * 检查考试是否完成(所有题目已作答或到达最后一题)。 * - * @return true if the exam is complete, false otherwise + * @return 若考试已完成则返回 true,否则返回 false */ public boolean isComplete() { - return currentQuestionIndex >= questions.size() - 1; + return userAnswers.stream().allMatch(answer -> answer != -1); } /** - * Gets the current question. + * 获取当前题目。 * - * @return The current quiz question + * @return 当前的测验题 */ public QuizQuestion getCurrentQuestion() { if (currentQuestionIndex < 0 || currentQuestionIndex >= questions.size()) { @@ -155,10 +153,10 @@ public final class ExamSession { } /** - * 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) + * @param questionIndex 题目索引 + * @return 用户答案的索引(未选择时为 -1) */ public int getUserAnswer(int questionIndex) { if (questionIndex < 0 || questionIndex >= questions.size()) { @@ -168,9 +166,9 @@ public final class ExamSession { } /** - * Calculates the score as a percentage. + * 计算得分百分比。 * - * @return The score as a percentage (0-100) + * @return 0-100 范围内的得分百分比 */ public double calculateScore() { int correctCount = 0; @@ -185,19 +183,19 @@ public final class ExamSession { } /** - * Gets the total number of questions in the exam. + * 获取考试中的题目总数。 * - * @return The total number of questions + * @return 题目总数 */ public int getTotalQuestions() { return questions.size(); } /** - * Checks if a specific question has been answered. + * 检查指定题目是否已作答。 * - * @param questionIndex The index of the question - * @return true if the question has been answered, false otherwise + * @param questionIndex 题目索引 + * @return 若已作答则返回 true,否则返回 false */ public boolean hasAnswered(int questionIndex) { if (questionIndex < 0 || questionIndex >= questions.size()) { @@ -207,18 +205,18 @@ public final class ExamSession { } /** - * Gets the start time of the exam. + * 获取考试开始时间。 * - * @return The start time + * @return 开始时间 */ public LocalDateTime getStartTime() { return startTime; } /** - * Gets the number of correct answers. + * 获取答对的题目数量。 * - * @return The count of correct answers + * @return 正确题目数量 */ public int getCorrectAnswersCount() { int correctCount = 0; @@ -233,9 +231,9 @@ public final class ExamSession { } /** - * Gets the number of incorrect answers. + * 获取答错的题目数量。 * - * @return The count of incorrect answers + * @return 错误题目数量 */ public int getIncorrectAnswersCount() { int totalAnswered = 0; @@ -254,4 +252,4 @@ public final class ExamSession { return totalAnswered - correctCount; } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/model/QuizQuestion.java b/src/main/java/com/personalproject/model/QuizQuestion.java index e66f14c..52c8f6b 100644 --- a/src/main/java/com/personalproject/model/QuizQuestion.java +++ b/src/main/java/com/personalproject/model/QuizQuestion.java @@ -3,7 +3,7 @@ package com.personalproject.model; import java.util.List; /** - * Represents a quiz question with multiple choice options. + * 表示一个带有多项选择的测验题目。 */ public final class QuizQuestion { @@ -12,11 +12,11 @@ public final class QuizQuestion { 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 + * @param questionText 题目文本 + * @param options 答案选项列表 + * @param correctAnswerIndex 正确答案在选项列表中的索引 */ public QuizQuestion(String questionText, List options, int correctAnswerIndex) { if (questionText == null || questionText.trim().isEmpty()) { @@ -30,44 +30,44 @@ public final class QuizQuestion { } this.questionText = questionText; - this.options = List.copyOf(options); // Immutable copy + this.options = List.copyOf(options); // 不可变副本 this.correctAnswerIndex = correctAnswerIndex; } /** - * Gets the question text. + * 获取题目文本。 * - * @return The question text + * @return 题目文本 */ public String getQuestionText() { return questionText; } /** - * Gets the list of answer options. + * 获取答案选项列表。 * - * @return An unmodifiable list of answer options + * @return 不可修改的答案选项列表 */ public List getOptions() { return options; } /** - * Gets the index of the correct answer in the options list. + * 获取正确答案在选项列表中的索引。 * - * @return The index of the correct answer + * @return 正确答案的索引 */ 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 + * @param answerIndex 用户答案的索引 + * @return 若答案正确则返回 true,否则返回 false */ public boolean isAnswerCorrect(int answerIndex) { return answerIndex == correctAnswerIndex; } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/service/ExamService.java b/src/main/java/com/personalproject/service/ExamService.java index 392a83a..f72dcd7 100644 --- a/src/main/java/com/personalproject/service/ExamService.java +++ b/src/main/java/com/personalproject/service/ExamService.java @@ -4,11 +4,16 @@ 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; /** * 用于管理考试会话和生成测验题目的服务类. @@ -19,19 +24,23 @@ public final class ExamService { 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) { + QuestionGenerationService questionGenerationService, + QuestionStorageService questionStorageService) { this.generators = new EnumMap<>(DifficultyLevel.class); this.generators.putAll(generatorMap); this.questionGenerationService = questionGenerationService; + this.questionStorageService = questionStorageService; } /** @@ -49,27 +58,25 @@ public final class ExamService { } // 根据难度级别生成题目 - List generatedQuestions = new ArrayList<>(); - QuestionGenerator generator = generators.get(difficultyLevel); - if (generator == null) { + if (!generators.containsKey(difficultyLevel)) { throw new IllegalArgumentException("找不到难度级别的生成器: " + difficultyLevel); } - for (int i = 0; i < questionCount; i++) { - String question = generator.generateQuestion(random); - generatedQuestions.add(question); - } + try { + Set existingQuestions = questionStorageService.loadExistingQuestions(username); + List uniqueQuestions = questionGenerationService.generateUniqueQuestions( + difficultyLevel, questionCount, existingQuestions); - // 将字符串题目转换为带有选项和答案的QuizQuestion对象 - List quizQuestions = new ArrayList<>(); - for (String questionText : generatedQuestions) { - List options = generateOptions(questionText); - int correctAnswerIndex = generateCorrectAnswerIndex(options.size()); - QuizQuestion quizQuestion = new QuizQuestion(questionText, options, correctAnswerIndex); - quizQuestions.add(quizQuestion); - } + List quizQuestions = new ArrayList<>(); + for (String questionText : uniqueQuestions) { + quizQuestions.add(buildQuizQuestion(questionText)); + } - return new ExamSession(username, difficultyLevel, quizQuestions); + questionStorageService.saveQuestions(username, uniqueQuestions); + return new ExamSession(username, difficultyLevel, quizQuestions); + } catch (IOException exception) { + throw new IllegalStateException("生成考试题目失败", exception); + } } /** @@ -88,53 +95,64 @@ public final class ExamService { return new ExamSession(username, difficultyLevel, questions); } - /** - * 为给定题目生成多项选择选项. 这是一个模拟实现 - 在实际系统中,选项会根据题目生成. - * - * @param questionText 题目文本 - * @return 答案选项列表 - */ - private List generateOptions(String questionText) { - List options = new ArrayList<>(); - // 为每个题目生成4个选项 + 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); + } + } - // 创建正确答案选项 - 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)); + 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; } - } catch (Exception e) { - // 如果评估失败,创建虚拟选项 - for (int i = 0; i < OPTIONS_COUNT; i++) { - options.add("选项 " + (i + 1)); + String candidate = formatAnswer(candidateValue); + if (!candidate.equals(formattedCorrectAnswer)) { + optionSet.add(candidate); } + attempts++; } - // 随机打乱选项以随机化正确答案的位置 - java.util.Collections.shuffle(options, random); + while (optionSet.size() < OPTIONS_COUNT) { + optionSet.add(formatAnswer(correctAnswer + optionSet.size() * 1.5)); + } - // 找到打乱后的正确答案索引 - // 对于此模拟实现,我们将返回第一个选项(索引0)作为正确答案 - // 实际实现将跟踪正确答案 + List options = new ArrayList<>(optionSet); + Collections.shuffle(options, random); return options; } - /** - * 为给定选项数量生成随机正确答案索引. - * - * @param optionCount 选项数量 - * @return 0到optionCount-1之间的随机索引 - */ - private int generateCorrectAnswerIndex(int optionCount) { - return random.nextInt(optionCount); + 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); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/service/MathExpressionEvaluator.java b/src/main/java/com/personalproject/service/MathExpressionEvaluator.java index 6e32ed1..ff77f94 100644 --- a/src/main/java/com/personalproject/service/MathExpressionEvaluator.java +++ b/src/main/java/com/personalproject/service/MathExpressionEvaluator.java @@ -3,15 +3,17 @@ package com.personalproject.service; import java.util.HashMap; import java.util.Map; import java.util.Stack; +import java.util.function.DoubleUnaryOperator; 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 PRECEDENCE = new HashMap<>(); + private static final Map FUNCTIONS = new HashMap<>(); static { PRECEDENCE.put('+', 1); @@ -19,44 +21,49 @@ public final class MathExpressionEvaluator { PRECEDENCE.put('*', 2); PRECEDENCE.put('/', 2); PRECEDENCE.put('^', 3); + + FUNCTIONS.put("sin", angle -> Math.sin(Math.toRadians(angle))); + FUNCTIONS.put("cos", angle -> Math.cos(Math.toRadians(angle))); + FUNCTIONS.put("tan", angle -> Math.tan(Math.toRadians(angle))); + FUNCTIONS.put("sqrt", Math::sqrt); } 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 + * @param expression 要计算的数学表达式 + * @return 计算结果 + * @throws IllegalArgumentException 如果表达式无效 */ public static double evaluate(String expression) { if (expression == null) { throw new IllegalArgumentException("Expression cannot be null"); } - expression = expression.replaceAll("\\s+", ""); // Remove whitespace + expression = expression.replaceAll("\\s+", ""); // 移除空白字符 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 + * @param expression 待拆分的表达式 + * @return 记号数组 */ private static String[] tokenize(String expression) { java.util.List tokens = new java.util.ArrayList<>(); @@ -67,6 +74,21 @@ public final class MathExpressionEvaluator { if (Character.isDigit(c) || c == '.') { currentNumber.append(c); + } else if (Character.isLetter(c)) { + if (currentNumber.length() > 0) { + tokens.add(currentNumber.toString()); + currentNumber.setLength(0); + } + StringBuilder functionBuilder = new StringBuilder(); + functionBuilder.append(c); + while (i + 1 < expression.length() && Character.isLetter(expression.charAt(i + 1))) { + functionBuilder.append(expression.charAt(++i)); + } + String function = functionBuilder.toString(); + if (!isFunction(function)) { + throw new IllegalArgumentException("Unsupported function: " + function); + } + tokens.add(function); } else if (c == '(' || c == ')') { if (currentNumber.length() > 0) { tokens.add(currentNumber.toString()); @@ -79,7 +101,7 @@ public final class MathExpressionEvaluator { currentNumber.setLength(0); } - // Handle unary minus + // 处理一元负号 if (c == '-' && (i == 0 || expression.charAt(i - 1) == '(')) { currentNumber.append(c); } else { @@ -98,20 +120,24 @@ public final class MathExpressionEvaluator { } /** - * Checks if the character is an operator. + * 检查字符是否为运算符。 * - * @param c The character to check - * @return true if the character is an operator, false otherwise + * @param c 待检查的字符 + * @return 若字符是运算符则返回 true,否则返回 false */ private static boolean isOperator(char c) { return c == '+' || c == '-' || c == '*' || c == '/' || c == '^'; } + private static boolean isFunction(String token) { + return FUNCTIONS.containsKey(token); + } + /** - * 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 + * @param tokens 中缀表达式的记号数组 + * @return 后缀表达式的记号数组 */ private static String[] infixToPostfix(String[] tokens) { java.util.List output = new java.util.ArrayList<>(); @@ -120,6 +146,8 @@ public final class MathExpressionEvaluator { for (String token : tokens) { if (isNumber(token)) { output.add(token); + } else if (isFunction(token)) { + operators.push(token); } else if (token.equals("(")) { operators.push(token); } else if (token.equals(")")) { @@ -127,7 +155,10 @@ public final class MathExpressionEvaluator { output.add(operators.pop()); } if (!operators.isEmpty()) { - operators.pop(); // Remove the "(" + operators.pop(); // 移除 "(" + } + if (!operators.isEmpty() && isFunction(operators.peek())) { + output.add(operators.pop()); } } else if (isOperator(token.charAt(0))) { while (!operators.isEmpty() @@ -140,17 +171,21 @@ public final class MathExpressionEvaluator { } while (!operators.isEmpty()) { - output.add(operators.pop()); + String operator = operators.pop(); + if (operator.equals("(") || operator.equals(")")) { + throw new IllegalArgumentException("Mismatched parentheses in expression"); + } + output.add(operator); } return output.toArray(new String[0]); } /** - * Evaluates a postfix expression. + * 计算后缀表达式的值。 * - * @param postfix The tokens in postfix notation - * @return The result of the evaluation + * @param postfix 后缀表达式的记号数组 + * @return 计算结果 */ private static double evaluatePostfix(String[] postfix) { Stack values = new Stack<>(); @@ -167,6 +202,14 @@ public final class MathExpressionEvaluator { double a = values.pop(); double result = performOperation(a, b, token.charAt(0)); values.push(result); + } else if (isFunction(token)) { + if (values.isEmpty()) { + throw new IllegalArgumentException("Invalid expression: insufficient operands for function"); + } + double value = values.pop(); + values.push(applyFunction(token, value)); + } else { + throw new IllegalArgumentException("Unknown token: " + token); } } @@ -178,12 +221,12 @@ public final class MathExpressionEvaluator { } /** - * 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 + * @param a 第一个操作数 + * @param b 第二个操作数 + * @param operator 要应用的运算符 + * @return 运算结果 */ private static double performOperation(double a, double b, char operator) { switch (operator) { @@ -205,13 +248,21 @@ public final class MathExpressionEvaluator { } } + private static double applyFunction(String function, double value) { + DoubleUnaryOperator operator = FUNCTIONS.get(function); + if (operator == null) { + throw new IllegalArgumentException("Unknown function: " + function); + } + return operator.applyAsDouble(value); + } + /** - * Checks if the token is a number. + * 检查记号是否为数字。 * - * @param token The token to check - * @return true if the token is a number, false otherwise + * @param token 待检查的记号 + * @return 若为数字则返回 true,否则返回 false */ private static boolean isNumber(String token) { return NUMBER_PATTERN.matcher(token).matches(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/service/MathLearningService.java b/src/main/java/com/personalproject/service/MathLearningService.java index 56427e1..768d98e 100644 --- a/src/main/java/com/personalproject/service/MathLearningService.java +++ b/src/main/java/com/personalproject/service/MathLearningService.java @@ -30,10 +30,10 @@ public final class MathLearningService { public MathLearningService( Map generatorMap, QuestionGenerationService questionGenerationService) { + this.storageService = new QuestionStorageService(); this.accountRepository = new AccountRepository(); this.registrationService = new RegistrationService(accountRepository); - this.examService = new ExamService(generatorMap, questionGenerationService); - this.storageService = new QuestionStorageService(); + this.examService = new ExamService(generatorMap, questionGenerationService, storageService); this.resultService = new ExamResultService(); } @@ -169,12 +169,12 @@ public final class MathLearningService { } /** - * Gets a user account by username. + * 按用户名获取用户账户。 * - * @param username The username - * @return Optional containing the user account if found + * @param username 用户名 + * @return 若找到则返回包含用户账户的 Optional */ public Optional getUser(String username) { return registrationService.getUser(username); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/service/RegistrationService.java b/src/main/java/com/personalproject/service/RegistrationService.java index 5c20f1a..099152c 100644 --- a/src/main/java/com/personalproject/service/RegistrationService.java +++ b/src/main/java/com/personalproject/service/RegistrationService.java @@ -6,6 +6,7 @@ import com.personalproject.auth.PasswordValidator; import com.personalproject.model.DifficultyLevel; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -16,6 +17,7 @@ public final class RegistrationService { private final AccountRepository accountRepository; private final Map pendingRegistrations; private final Map registrationAttempts; + private final Set verifiedUsers; /** * 创建新的注册服务. @@ -26,6 +28,7 @@ public final class RegistrationService { this.accountRepository = accountRepository; this.pendingRegistrations = new ConcurrentHashMap<>(); this.registrationAttempts = new ConcurrentHashMap<>(); + this.verifiedUsers = ConcurrentHashMap.newKeySet(); } /** @@ -38,19 +41,36 @@ public final class RegistrationService { */ public boolean initiateRegistration(String username, String email, DifficultyLevel difficultyLevel) { - if (!EmailService.isValidEmail(email)) { + if (username == null || email == null || difficultyLevel == null) { return false; } - if (!accountRepository.registerUser(username, email, difficultyLevel)) { + String normalizedUsername = username.trim(); + String normalizedEmail = email.trim(); + if (normalizedUsername.isEmpty() || normalizedEmail.isEmpty()) { + return false; + } + + if (!EmailService.isValidEmail(normalizedEmail)) { + return false; + } + + if (!accountRepository.registerUser(normalizedUsername, normalizedEmail, difficultyLevel)) { return false; // 用户名已存在或邮箱已注册 } String registrationCode = EmailService.generateRegistrationCode(); - pendingRegistrations.put(username, registrationCode); - registrationAttempts.put(username, 0); - - return EmailService.sendRegistrationCode(email, registrationCode); + pendingRegistrations.put(normalizedUsername, registrationCode); + registrationAttempts.put(normalizedUsername, 0); + verifiedUsers.remove(normalizedUsername); + + boolean sent = EmailService.sendRegistrationCode(normalizedEmail, registrationCode); + if (!sent) { + pendingRegistrations.remove(normalizedUsername); + registrationAttempts.remove(normalizedUsername); + accountRepository.removeUnverifiedUser(normalizedUsername); + } + return sent; } /** @@ -61,25 +81,34 @@ public final class RegistrationService { * @return 注册码有效则返回true,否则返回false */ public boolean verifyRegistrationCode(String username, String registrationCode) { - String storedCode = pendingRegistrations.get(username); - if (storedCode == null || !storedCode.equals(registrationCode)) { + if (username == null || registrationCode == null) { + return false; + } + String normalizedUsername = username.trim(); + String trimmedCode = registrationCode.trim(); + + String storedCode = pendingRegistrations.get(normalizedUsername); + if (storedCode == null || !storedCode.equals(trimmedCode)) { // 跟踪失败尝试 - int attempts = registrationAttempts.getOrDefault(username, 0); + int attempts = registrationAttempts.getOrDefault(normalizedUsername, 0); attempts++; - registrationAttempts.put(username, attempts); + registrationAttempts.put(normalizedUsername, attempts); if (attempts >= 3) { // 如果失败次数过多,则删除用户 - pendingRegistrations.remove(username); - registrationAttempts.remove(username); + pendingRegistrations.remove(normalizedUsername); + registrationAttempts.remove(normalizedUsername); + verifiedUsers.remove(normalizedUsername); + accountRepository.removeUnverifiedUser(normalizedUsername); return false; } return false; } // 有效码,从待处理列表中移除 - pendingRegistrations.remove(username); - registrationAttempts.remove(username); + pendingRegistrations.remove(normalizedUsername); + registrationAttempts.remove(normalizedUsername); + verifiedUsers.add(normalizedUsername); return true; } @@ -91,11 +120,23 @@ public final class RegistrationService { * @return 密码设置成功则返回true,如果验证失败或用户不存在则返回false */ public boolean setPassword(String username, String password) { + if (username == null || password == null) { + return false; + } + String normalizedUsername = username.trim(); if (!PasswordValidator.isValidPassword(password)) { return false; } - return accountRepository.setPassword(username, password); + if (!verifiedUsers.contains(normalizedUsername)) { + return false; + } + + boolean updated = accountRepository.setPassword(normalizedUsername, password); + if (updated) { + verifiedUsers.remove(normalizedUsername); + } + return updated; } /** @@ -107,11 +148,14 @@ public final class RegistrationService { * @return 密码更改成功则返回true,如果验证失败或身份验证失败则返回false */ public boolean changePassword(String username, String oldPassword, String newPassword) { + if (username == null || oldPassword == null || newPassword == null) { + return false; + } if (!PasswordValidator.isValidPassword(newPassword)) { return false; } - return accountRepository.changePassword(username, oldPassword, newPassword); + return accountRepository.changePassword(username.trim(), oldPassword, newPassword); } /** @@ -123,7 +167,10 @@ public final class RegistrationService { */ public Optional authenticate(String username, String password) { - return accountRepository.authenticate(username, password); + if (username == null || password == null) { + return Optional.empty(); + } + return accountRepository.authenticate(username.trim(), password); } /** @@ -133,16 +180,22 @@ public final class RegistrationService { * @return 用户存在则返回true,否则返回false */ public boolean userExists(String username) { - return accountRepository.userExists(username); + if (username == null) { + return false; + } + return accountRepository.userExists(username.trim()); } /** - * Gets a user account by username. + * 按用户名获取用户账户。 * - * @param username The username - * @return Optional containing the user account if found + * @param username 用户名 + * @return 若找到则返回包含用户账户的 Optional */ public Optional getUser(String username) { - return accountRepository.getUser(username); + if (username == null) { + return Optional.empty(); + } + return accountRepository.getUser(username.trim()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/storage/QuestionStorageService.java b/src/main/java/com/personalproject/storage/QuestionStorageService.java index 4fc9870..dc483c9 100644 --- a/src/main/java/com/personalproject/storage/QuestionStorageService.java +++ b/src/main/java/com/personalproject/storage/QuestionStorageService.java @@ -87,32 +87,20 @@ public final class QuestionStorageService { Files.createDirectories(resultsDirectory); StringBuilder builder = new StringBuilder(); - builder.append("考试结果报告").append(System.lineSeparator()); + 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()); } @@ -159,4 +147,4 @@ public final class QuestionStorageService { "读取题目文件失败:" + path + ",原因:" + exception.getMessage() + ",将跳过该文件."); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/MathExamGUI.java b/src/main/java/com/personalproject/ui/MathExamGUI.java index 1071b3b..272bf36 100644 --- a/src/main/java/com/personalproject/ui/MathExamGUI.java +++ b/src/main/java/com/personalproject/ui/MathExamGUI.java @@ -16,8 +16,8 @@ import java.util.EnumMap; import java.util.Map; /** - * JavaFX GUI Application for the Math Learning Software. - * This is the main entry point for the GUI application. + * 数学学习软件的 JavaFX 图形界面应用程序。 + * 这是图形界面的主要入口点。 */ public final class MathExamGUI extends Application { @@ -25,7 +25,7 @@ public final class MathExamGUI extends Application { @Override public void start(Stage primaryStage) { - // Initialize the controller with generators + // 使用题目生成器初始化控制器 Map generatorMap = new EnumMap<>(DifficultyLevel.class); generatorMap.put(DifficultyLevel.PRIMARY, new PrimaryQuestionGenerator()); generatorMap.put(DifficultyLevel.MIDDLE, new MiddleSchoolQuestionGenerator()); @@ -33,10 +33,10 @@ public final class MathExamGUI extends Application { QuestionGenerationService questionGenerationService = new QuestionGenerationService(generatorMap); this.controller = new MathLearningController(generatorMap, questionGenerationService); - // Set up the primary stage + // 配置主舞台 primaryStage.setTitle("数学学习软件"); - // Start with login scene + // 从登录界面开始 LoginScene loginScene = new LoginScene(primaryStage, controller); Scene scene = new Scene(loginScene, 600, 400); @@ -45,11 +45,11 @@ public final class MathExamGUI extends Application { } /** - * Launches the JavaFX application. + * 启动 JavaFX 应用程序。 * - * @param args Command-line arguments + * @param args 命令行参数 */ public static void main(String[] args) { launch(args); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/scenes/LoginScene.java b/src/main/java/com/personalproject/ui/scenes/LoginScene.java index 6840449..4d4d6be 100644 --- a/src/main/java/com/personalproject/ui/scenes/LoginScene.java +++ b/src/main/java/com/personalproject/ui/scenes/LoginScene.java @@ -12,7 +12,7 @@ import com.personalproject.ui.views.MainMenuView; import com.personalproject.ui.scenes.RegistrationScene; /** - * Scene for handling user login and registration. + * 负责处理用户登录与注册的场景。 */ public class LoginScene extends BorderPane { @@ -24,10 +24,10 @@ public class LoginScene extends BorderPane { private Button registerButton; /** - * Constructor for LoginScene. + * LoginScene 的构造函数。 * - * @param primaryStage The main stage of the application - * @param controller The math learning controller + * @param primaryStage 应用程序的主舞台 + * @param controller 数学学习控制器 */ public LoginScene(Stage primaryStage, MathLearningController controller) { this.primaryStage = primaryStage; @@ -36,19 +36,19 @@ public class LoginScene extends BorderPane { } /** - * Initializes the UI components. + * 初始化界面组件。 */ private void initializeUI() { - // Create the main layout + // 创建主布局 VBox mainLayout = new VBox(15); mainLayout.setAlignment(Pos.CENTER); mainLayout.setPadding(new Insets(20)); - // Title + // 标题 Label titleLabel = new Label("数学学习软件"); titleLabel.setFont(Font.font("System", FontWeight.BOLD, 24)); - // Login Form + // 登录表单 GridPane loginForm = new GridPane(); loginForm.setHgap(10); loginForm.setVgap(10); @@ -67,37 +67,37 @@ public class LoginScene extends BorderPane { loginForm.add(passwordLabel, 0, 1); loginForm.add(passwordField, 1, 1); - // Buttons + // 按钮 HBox buttonBox = new HBox(10); buttonBox.setAlignment(Pos.CENTER); loginButton = new Button("登录"); registerButton = new Button("注册"); - // Set button styles + // 设置按钮样式 loginButton.setPrefWidth(100); registerButton.setPrefWidth(100); buttonBox.getChildren().addAll(loginButton, registerButton); - // Add components to main layout + // 将组件添加到主布局 mainLayout.getChildren().addAll(titleLabel, loginForm, buttonBox); - // Set the center of the border pane + // 将主布局放到边界面板中央 setCenter(mainLayout); - // Add event handlers + // 添加事件处理器 addEventHandlers(); } /** - * Adds event handlers to UI components. + * 为界面组件添加事件处理器。 */ private void addEventHandlers() { loginButton.setOnAction(e -> handleLogin()); registerButton.setOnAction(e -> handleRegistration()); - // Allow login with Enter key + // 允许使用回车键登录 setOnKeyPressed(event -> { if (event.getCode().toString().equals("ENTER")) { handleLogin(); @@ -106,7 +106,7 @@ public class LoginScene extends BorderPane { } /** - * Handles the login process. + * 处理登录流程。 */ private void handleLogin() { String username = usernameField.getText().trim(); @@ -117,34 +117,34 @@ public class LoginScene extends BorderPane { return; } - // Authenticate user + // 验证用户 var userAccount = controller.authenticate(username, password); if (userAccount.isPresent()) { - // Login successful - navigate to main menu + // 登录成功,跳转到主菜单 MainMenuView mainMenuView = new MainMenuView(primaryStage, controller, userAccount.get()); primaryStage.getScene().setRoot(mainMenuView); } else { - // Login failed + // 登录失败 showAlert(Alert.AlertType.ERROR, "登录失败", "用户名或密码错误"); } } /** - * Handles the registration process. + * 处理注册流程。 */ private void handleRegistration() { - // Switch to registration scene + // 切换到注册界面 RegistrationScene registrationScene = new RegistrationScene(primaryStage, controller); primaryStage.getScene().setRoot(registrationScene); } /** - * Shows an alert dialog. + * 显示提示对话框。 * - * @param alertType Type of alert - * @param title Title of the alert - * @param message Message to display + * @param alertType 提示类型 + * @param title 对话框标题 + * @param message 显示的消息 */ private void showAlert(Alert.AlertType alertType, String title, String message) { Alert alert = new Alert(alertType); @@ -153,4 +153,4 @@ public class LoginScene extends BorderPane { alert.setContentText(message); alert.showAndWait(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/scenes/RegistrationScene.java b/src/main/java/com/personalproject/ui/scenes/RegistrationScene.java index f824784..c180c73 100644 --- a/src/main/java/com/personalproject/ui/scenes/RegistrationScene.java +++ b/src/main/java/com/personalproject/ui/scenes/RegistrationScene.java @@ -12,7 +12,7 @@ import com.personalproject.model.DifficultyLevel; import com.personalproject.ui.scenes.LoginScene; /** - * Scene for handling user registration. + * 负责处理用户注册的场景。 */ public class RegistrationScene extends BorderPane { @@ -31,10 +31,10 @@ public class RegistrationScene extends BorderPane { private VBox registrationForm; /** - * Constructor for RegistrationScene. + * RegistrationScene 的构造函数。 * - * @param primaryStage The main stage of the application - * @param controller The math learning controller + * @param primaryStage 应用程序的主舞台 + * @param controller 数学学习控制器 */ public RegistrationScene(Stage primaryStage, MathLearningController controller) { this.primaryStage = primaryStage; @@ -43,23 +43,23 @@ public class RegistrationScene extends BorderPane { } /** - * Initializes the UI components. + * 初始化界面组件。 */ private void initializeUI() { - // Create the main layout + // 创建主布局 VBox mainLayout = new VBox(15); mainLayout.setAlignment(Pos.CENTER); mainLayout.setPadding(new Insets(20)); - // Title + // 标题 Label titleLabel = new Label("用户注册"); titleLabel.setFont(Font.font("System", FontWeight.BOLD, 24)); - // Registration Form + // 注册表单 registrationForm = new VBox(15); registrationForm.setAlignment(Pos.CENTER); - // Step 1: Basic Info + // 步骤1:填写基础信息 GridPane basicInfoForm = new GridPane(); basicInfoForm.setHgap(10); basicInfoForm.setVgap(10); @@ -91,7 +91,7 @@ public class RegistrationScene extends BorderPane { registrationForm.getChildren().addAll(basicInfoForm, sendCodeButton); - // Step 2: Verification (hidden initially) + // 步骤2:验证码验证(初始隐藏) VBox verificationSection = new VBox(10); verificationSection.setAlignment(Pos.CENTER); verificationSection.setVisible(false); @@ -107,7 +107,7 @@ public class RegistrationScene extends BorderPane { verificationSection.getChildren().addAll(codeLabel, registrationCodeField, verifyCodeButton); registrationForm.getChildren().add(verificationSection); - // Step 3: Password Setting (hidden initially) + // 步骤3:设置密码(初始隐藏) VBox passwordSection = new VBox(10); passwordSection.setAlignment(Pos.CENTER); passwordSection.setVisible(false); @@ -128,21 +128,21 @@ public class RegistrationScene extends BorderPane { confirmPasswordField, setPasswordButton); registrationForm.getChildren().add(passwordSection); - // Back button + // 返回按钮 backButton = new Button("返回"); backButton.setPrefWidth(100); - // Add components to main layout + // 将组件添加到主布局 mainLayout.getChildren().addAll(titleLabel, registrationForm, backButton); setCenter(mainLayout); - // Add event handlers + // 添加事件处理器 addEventHandlers(sendCodeButton, verificationSection, verifyCodeButton, passwordSection); } /** - * Adds event handlers to UI components. + * 为界面组件添加事件处理器。 */ private void addEventHandlers(Button sendCodeButton, VBox verificationSection, Button verifyCodeButton, VBox passwordSection) { @@ -153,7 +153,7 @@ public class RegistrationScene extends BorderPane { } /** - * Handles sending registration code. + * 处理发送注册码的逻辑。 */ private void handleSendCode(VBox verificationSection) { String username = usernameField.getText().trim(); @@ -170,7 +170,7 @@ public class RegistrationScene extends BorderPane { return; } - // Initiate registration + // 发起注册 boolean success = controller.initiateRegistration(username, email, difficultyLevel); if (success) { @@ -183,7 +183,7 @@ public class RegistrationScene extends BorderPane { } /** - * Handles verification of registration code. + * 处理注册码验证。 */ private void handleVerifyCode(VBox passwordSection) { String username = usernameField.getText().trim(); @@ -206,7 +206,7 @@ public class RegistrationScene extends BorderPane { } /** - * Handles setting the user password. + * 处理设置用户密码的逻辑。 */ private void handleSetPassword() { String username = usernameField.getText().trim(); @@ -233,14 +233,14 @@ public class RegistrationScene extends BorderPane { if (success) { showAlert(Alert.AlertType.INFORMATION, "注册成功", "注册成功!请登录。"); - handleBack(); // Go back to login screen + handleBack(); // 返回登录界面 } else { showAlert(Alert.AlertType.ERROR, "设置密码失败", "设置密码失败,请重试。"); } } /** - * Handles the back button action. + * 处理返回按钮的操作。 */ private void handleBack() { LoginScene loginScene = new LoginScene(primaryStage, controller); @@ -248,11 +248,11 @@ public class RegistrationScene extends BorderPane { } /** - * Shows an alert dialog. + * 显示提示对话框。 * - * @param alertType Type of alert - * @param title Title of the alert - * @param message Message to display + * @param alertType 提示类型 + * @param title 对话框标题 + * @param message 显示的消息 */ private void showAlert(Alert.AlertType alertType, String title, String message) { Alert alert = new Alert(alertType); @@ -261,4 +261,4 @@ public class RegistrationScene extends BorderPane { alert.setContentText(message); alert.showAndWait(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/views/ExamResultsView.java b/src/main/java/com/personalproject/ui/views/ExamResultsView.java index 7fececb..64d0e91 100644 --- a/src/main/java/com/personalproject/ui/views/ExamResultsView.java +++ b/src/main/java/com/personalproject/ui/views/ExamResultsView.java @@ -13,7 +13,7 @@ import com.personalproject.auth.UserAccount; import com.personalproject.ui.views.MainMenuView; /** - * View for displaying exam results. + * 用于展示考试结果的界面。 */ public class ExamResultsView extends BorderPane { @@ -24,11 +24,11 @@ public class ExamResultsView extends BorderPane { private Button exitButton; /** - * Constructor for ExamResultsView. + * ExamResultsView 的构造函数。 * - * @param primaryStage The main stage of the application - * @param controller The math learning controller - * @param examSession The completed exam session + * @param primaryStage 应用程序的主舞台 + * @param controller 数学学习控制器 + * @param examSession 已完成的考试会话 */ public ExamResultsView(Stage primaryStage, MathLearningController controller, ExamSession examSession) { this.primaryStage = primaryStage; @@ -38,24 +38,24 @@ public class ExamResultsView extends BorderPane { } /** - * Initializes the UI components. + * 初始化界面组件。 */ private void initializeUI() { - // Create the main layout + // 创建主布局 VBox mainLayout = new VBox(20); mainLayout.setAlignment(Pos.CENTER); mainLayout.setPadding(new Insets(20)); - // Results title + // 结果标题 Label titleLabel = new Label("考试结果"); titleLabel.setFont(Font.font("System", FontWeight.BOLD, 24)); - // Score display + // 分数展示 double score = examSession.calculateScore(); Label scoreLabel = new Label(String.format("您的得分: %.2f%%", score)); scoreLabel.setFont(Font.font("System", FontWeight.BOLD, 18)); - // Performance breakdown + // 成绩明细 VBox breakdownBox = new VBox(10); breakdownBox.setAlignment(Pos.CENTER); @@ -65,31 +65,31 @@ public class ExamResultsView extends BorderPane { breakdownBox.getChildren().addAll(totalQuestionsLabel, correctAnswersLabel, incorrectAnswersLabel); - // Buttons + // 按钮区域 HBox buttonBox = new HBox(15); buttonBox.setAlignment(Pos.CENTER); continueButton = new Button("继续考试"); exitButton = new Button("退出"); - // Set button sizes + // 设置按钮尺寸 continueButton.setPrefSize(120, 40); exitButton.setPrefSize(120, 40); buttonBox.getChildren().addAll(continueButton, exitButton); - // Add components to main layout + // 将组件添加到主布局 mainLayout.getChildren().addAll(titleLabel, scoreLabel, breakdownBox, buttonBox); - // Set the center of the border pane + // 将主布局置于边界面板中央 setCenter(mainLayout); - // Add event handlers + // 添加事件处理器 addEventHandlers(); } /** - * Adds event handlers to UI components. + * 为界面组件添加事件处理器。 */ private void addEventHandlers() { continueButton.setOnAction(e -> handleContinue()); @@ -97,10 +97,10 @@ public class ExamResultsView extends BorderPane { } /** - * Handles the continue button action. + * 处理继续考试按钮的操作。 */ private void handleContinue() { - // Go back to main menu to start a new exam + // 返回主菜单以开始新考试 controller.getUserAccount(examSession.getUsername()) .ifPresentOrElse( userAccount -> { @@ -108,9 +108,9 @@ public class ExamResultsView extends BorderPane { primaryStage.getScene().setRoot(mainMenuView); }, () -> { - // If user account can't be found, show an error and go back to login + // 如果找不到用户信息,则提示错误并返回登录界面 showAlert(Alert.AlertType.ERROR, "错误", "用户信息无法找到,请重新登录"); - // Go back to login scene + // 返回登录场景 com.personalproject.ui.scenes.LoginScene loginScene = new com.personalproject.ui.scenes.LoginScene(primaryStage, controller); primaryStage.getScene().setRoot(loginScene); @@ -119,10 +119,10 @@ public class ExamResultsView extends BorderPane { } /** - * Handles the exit button action. + * 处理退出按钮的操作。 */ private void handleExit() { - // Go back to main menu + // 返回主菜单 controller.getUserAccount(examSession.getUsername()) .ifPresentOrElse( userAccount -> { @@ -130,9 +130,9 @@ public class ExamResultsView extends BorderPane { primaryStage.getScene().setRoot(mainMenuView); }, () -> { - // If user account can't be found, show an error and go back to login + // 如果找不到用户信息,则提示错误并返回登录界面 showAlert(Alert.AlertType.ERROR, "错误", "用户信息无法找到,请重新登录"); - // Go back to login scene + // 返回登录场景 com.personalproject.ui.scenes.LoginScene loginScene = new com.personalproject.ui.scenes.LoginScene(primaryStage, controller); primaryStage.getScene().setRoot(loginScene); @@ -141,11 +141,11 @@ public class ExamResultsView extends BorderPane { } /** - * Shows an alert dialog. + * 显示提示对话框。 * - * @param alertType Type of alert - * @param title Title of the alert - * @param message Message to display + * @param alertType 提示类型 + * @param title 对话框标题 + * @param message 显示的消息 */ private void showAlert(Alert.AlertType alertType, String title, String message) { Alert alert = new Alert(alertType); @@ -154,4 +154,4 @@ public class ExamResultsView extends BorderPane { alert.setContentText(message); alert.showAndWait(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/views/ExamSelectionView.java b/src/main/java/com/personalproject/ui/views/ExamSelectionView.java index 2b305d5..839b54f 100644 --- a/src/main/java/com/personalproject/ui/views/ExamSelectionView.java +++ b/src/main/java/com/personalproject/ui/views/ExamSelectionView.java @@ -12,7 +12,7 @@ import com.personalproject.model.DifficultyLevel; import com.personalproject.auth.UserAccount; /** - * View for selecting exam difficulty and number of questions. + * 用于选择考试难度和题目数量的界面。 */ public class ExamSelectionView extends BorderPane { @@ -25,11 +25,11 @@ public class ExamSelectionView extends BorderPane { private Button backButton; /** - * Constructor for ExamSelectionView. + * ExamSelectionView 的构造函数。 * - * @param primaryStage The main stage of the application - * @param controller The math learning controller - * @param userAccount The current user account + * @param primaryStage 应用程序的主舞台 + * @param controller 数学学习控制器 + * @param userAccount 当前用户账户 */ public ExamSelectionView(Stage primaryStage, MathLearningController controller, UserAccount userAccount) { this.primaryStage = primaryStage; @@ -39,19 +39,19 @@ public class ExamSelectionView extends BorderPane { } /** - * Initializes the UI components. + * 初始化界面组件。 */ private void initializeUI() { - // Create the main layout + // 创建主布局 VBox mainLayout = new VBox(20); mainLayout.setAlignment(Pos.CENTER); mainLayout.setPadding(new Insets(20)); - // Title + // 标题 Label titleLabel = new Label("考试设置"); titleLabel.setFont(Font.font("System", FontWeight.BOLD, 24)); - // Form for exam settings + // 考试设置表单 GridPane examSettingsForm = new GridPane(); examSettingsForm.setHgap(15); examSettingsForm.setVgap(15); @@ -60,11 +60,11 @@ public class ExamSelectionView extends BorderPane { Label difficultyLabel = new Label("选择难度:"); difficultyComboBox = new ComboBox<>(); difficultyComboBox.getItems().addAll(DifficultyLevel.PRIMARY, DifficultyLevel.MIDDLE, DifficultyLevel.HIGH); - difficultyComboBox.setValue(userAccount.difficultyLevel()); // Default to user's difficulty + difficultyComboBox.setValue(userAccount.difficultyLevel()); // 默认选中用户的难度 difficultyComboBox.setPrefWidth(200); Label questionCountLabel = new Label("题目数量 (10-30):"); - questionCountSpinner = new Spinner<>(10, 30, 10); // min, max, initial value + questionCountSpinner = new Spinner<>(10, 30, 10); // 最小值、最大值、初始值 questionCountSpinner.setPrefWidth(200); examSettingsForm.add(difficultyLabel, 0, 0); @@ -72,31 +72,31 @@ public class ExamSelectionView extends BorderPane { examSettingsForm.add(questionCountLabel, 0, 1); examSettingsForm.add(questionCountSpinner, 1, 1); - // Buttons + // 按钮区域 HBox buttonBox = new HBox(15); buttonBox.setAlignment(Pos.CENTER); startExamButton = new Button("开始考试"); backButton = new Button("返回"); - // Set button sizes + // 设置按钮尺寸 startExamButton.setPrefSize(120, 40); backButton.setPrefSize(120, 40); buttonBox.getChildren().addAll(startExamButton, backButton); - // Add components to main layout + // 将组件添加到主布局 mainLayout.getChildren().addAll(titleLabel, examSettingsForm, buttonBox); - // Set the center of the border pane + // 将主布局置于边界面板中央 setCenter(mainLayout); - // Add event handlers + // 添加事件处理器 addEventHandlers(); } /** - * Adds event handlers to UI components. + * 为界面组件添加事件处理器。 */ private void addEventHandlers() { startExamButton.setOnAction(e -> handleStartExam()); @@ -104,7 +104,7 @@ public class ExamSelectionView extends BorderPane { } /** - * Handles the start exam button action. + * 处理开始考试按钮的操作。 */ private void handleStartExam() { DifficultyLevel selectedDifficulty = difficultyComboBox.getValue(); @@ -115,7 +115,7 @@ public class ExamSelectionView extends BorderPane { return; } - // Create and start exam session + // 创建并启动考试会话 com.personalproject.model.ExamSession examSession = controller.createExamSession( userAccount.username(), selectedDifficulty, questionCount); @@ -124,7 +124,7 @@ public class ExamSelectionView extends BorderPane { } /** - * Handles the back button action. + * 处理返回按钮的操作。 */ private void handleBack() { MainMenuView mainMenuView = new MainMenuView(primaryStage, controller, userAccount); @@ -132,11 +132,11 @@ public class ExamSelectionView extends BorderPane { } /** - * Shows an alert dialog. + * 显示提示对话框。 * - * @param alertType Type of alert - * @param title Title of the alert - * @param message Message to display + * @param alertType 提示类型 + * @param title 对话框标题 + * @param message 显示的消息 */ private void showAlert(Alert.AlertType alertType, String title, String message) { Alert alert = new Alert(alertType); @@ -145,4 +145,4 @@ public class ExamSelectionView extends BorderPane { alert.setContentText(message); alert.showAndWait(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/views/ExamView.java b/src/main/java/com/personalproject/ui/views/ExamView.java index 8b30cd3..02f2363 100644 --- a/src/main/java/com/personalproject/ui/views/ExamView.java +++ b/src/main/java/com/personalproject/ui/views/ExamView.java @@ -13,7 +13,7 @@ import com.personalproject.model.QuizQuestion; import com.personalproject.ui.views.ExamResultsView; /** - * View for taking the exam with questions and answer options. + * 提供答题界面以及题目与选项的呈现。 */ public class ExamView extends BorderPane { @@ -30,11 +30,11 @@ public class ExamView extends BorderPane { private HBox buttonBox; /** - * Constructor for ExamView. + * ExamView 的构造函数。 * - * @param primaryStage The main stage of the application - * @param controller The math learning controller - * @param examSession The current exam session + * @param primaryStage 应用程序的主舞台 + * @param controller 数学学习控制器 + * @param examSession 当前的考试会话 */ public ExamView(Stage primaryStage, MathLearningController controller, ExamSession examSession) { this.primaryStage = primaryStage; @@ -44,29 +44,30 @@ public class ExamView extends BorderPane { } /** - * Initializes the UI components. + * 初始化界面组件。 */ private void initializeUI() { - // Create the main layout + // 创建主布局 VBox mainLayout = new VBox(20); mainLayout.setAlignment(Pos.CENTER); mainLayout.setPadding(new Insets(20)); - // Question number + // 题号 questionNumberLabel = new Label(); questionNumberLabel.setFont(Font.font("System", FontWeight.BOLD, 16)); - // Question text + // 题目文本 questionTextLabel = new Label(); questionTextLabel.setWrapText(true); questionTextLabel.setFont(Font.font("System", FontWeight.NORMAL, 14)); questionTextLabel.setMaxWidth(500); - // Options + // 选项容器 optionsBox = new VBox(10); optionsBox.setPadding(new Insets(10)); + answerToggleGroup = new ToggleGroup(); - // Buttons + // 按钮区域 buttonBox = new HBox(15); buttonBox.setAlignment(Pos.CENTER); @@ -74,34 +75,34 @@ public class ExamView extends BorderPane { nextButton = new Button("下一题"); finishButton = new Button("完成考试"); - // Set button sizes + // 设置按钮尺寸 previousButton.setPrefSize(100, 35); nextButton.setPrefSize(100, 35); finishButton.setPrefSize(120, 35); buttonBox.getChildren().addAll(previousButton, nextButton, finishButton); - // Add components to main layout + // 将组件添加到主布局 mainLayout.getChildren().addAll(questionNumberLabel, questionTextLabel, optionsBox, buttonBox); - // Set the center of the border pane + // 将主布局置于边界面板中央 setCenter(mainLayout); - // Load the first question + // 加载第一题 loadCurrentQuestion(); - // Add event handlers + // 添加事件处理器 addEventHandlers(); } /** - * Loads the current question into the UI. + * 将当前题目加载到界面。 */ private void loadCurrentQuestion() { try { - // Check if exam is complete before loading next question + // 在加载下一题之前检查考试是否已完成 if (examSession.isComplete()) { - // If exam is complete, the finish button should be enabled + // 如果考试已完成,则启用“完成考试”按钮 updateButtonStates(); return; } @@ -114,23 +115,23 @@ public class ExamView extends BorderPane { return; } - // Update question number and text + // 更新题号与题目文本 questionNumberLabel.setText("第 " + (currentIndex + 1) + " 题"); questionTextLabel.setText(currentQuestion.getQuestionText()); - // Clear previous options + // 清空上一题的选项 + answerToggleGroup.selectToggle(null); + answerToggleGroup.getToggles().clear(); optionsBox.getChildren().clear(); - // Create new options - answerToggleGroup = new ToggleGroup(); - + // 创建新的选项组件 for (int i = 0; i < currentQuestion.getOptions().size(); i++) { String option = currentQuestion.getOptions().get(i); RadioButton optionButton = new RadioButton((i + 1) + ". " + option); optionButton.setToggleGroup(answerToggleGroup); - optionButton.setUserData(i); // Store option index - - // If this question already has an answer, select it + optionButton.setUserData(i); // 存储选项索引 + + // 如果该题已有答案则自动选中 if (examSession.hasAnswered(currentIndex) && examSession.getUserAnswer(currentIndex) == i) { optionButton.setSelected(true); @@ -139,7 +140,7 @@ public class ExamView extends BorderPane { optionsBox.getChildren().add(optionButton); } - // Update button states + // 更新按钮状态 updateButtonStates(); } catch (Exception e) { showAlert(Alert.AlertType.ERROR, "错误", "加载题目时发生错误: " + e.getMessage()); @@ -147,50 +148,50 @@ public class ExamView extends BorderPane { } /** - * Updates the state of navigation buttons based on current position. + * 根据当前位置更新导航按钮的状态。 */ private void updateButtonStates() { try { int currentIndex = examSession.getCurrentQuestionIndex(); int totalQuestions = examSession.getTotalQuestions(); - // Handle potential edge cases + // 处理潜在极端情况 if (totalQuestions <= 0) { - // If there are no questions, disable all navigation + // 如果没有题目,则禁用所有导航按钮 previousButton.setDisable(true); nextButton.setDisable(true); - finishButton.setDisable(false); // Allow finishing exam + finishButton.setDisable(false); // 仍允许完成考试 return; } - // Previous button state + // “上一题”按钮状态 previousButton.setDisable(currentIndex < 0 || currentIndex == 0); - // Next button state + // “下一题”按钮状态 nextButton.setDisable(currentIndex < 0 || currentIndex >= totalQuestions - 1); - // Finish button state - enabled when exam is complete or at the last question + // “完成考试”按钮状态——在考试完成或到达最后一题时启用 boolean isExamComplete = examSession.isComplete(); boolean isAtLastQuestion = (currentIndex >= totalQuestions - 1); finishButton.setDisable(!(isExamComplete || isAtLastQuestion)); } catch (Exception e) { - // In case of any error, disable navigation buttons to prevent further issues + // 若出现异常,禁用导航按钮以避免进一步问题 previousButton.setDisable(true); nextButton.setDisable(true); - finishButton.setDisable(false); // Still allow finishing + finishButton.setDisable(false); // 仍允许完成考试 showAlert(Alert.AlertType.ERROR, "错误", "更新按钮状态时发生错误: " + e.getMessage()); } } /** - * Adds event handlers to UI components. + * 为界面组件添加事件处理器。 */ private void addEventHandlers() { nextButton.setOnAction(e -> handleNextQuestion()); previousButton.setOnAction(e -> handlePreviousQuestion()); finishButton.setOnAction(e -> handleFinishExam()); - // Add change listener to save answer when an option is selected + // 添加变更监听器,在选项被选择时保存答案 answerToggleGroup.selectedToggleProperty().addListener((obs, oldSelection, newSelection) -> { if (newSelection != null) { int selectedIndex = (Integer) newSelection.getUserData(); @@ -200,15 +201,15 @@ public class ExamView extends BorderPane { } /** - * Handles the next question button action. + * 处理“下一题”按钮的操作。 */ private void handleNextQuestion() { try { if (examSession.goToNextQuestion()) { loadCurrentQuestion(); } else { - // If we can't go to next question, we might be at the end - // Check if exam is complete and update button states accordingly + // 若无法跳转到下一题,可能已经到达末尾 + // 检查考试是否完成并据此更新按钮状态 updateButtonStates(); } } catch (Exception e) { @@ -217,14 +218,14 @@ public class ExamView extends BorderPane { } /** - * Handles the previous question button action. + * 处理“上一题”按钮的操作。 */ private void handlePreviousQuestion() { try { if (examSession.goToPreviousQuestion()) { loadCurrentQuestion(); } else { - // If we can't go to previous question, we might be at the beginning + // 若无法返回上一题,可能已经位于开头 updateButtonStates(); } } catch (Exception e) { @@ -233,11 +234,11 @@ public class ExamView extends BorderPane { } /** - * Shows an alert dialog. + * 显示提示对话框。 * - * @param alertType Type of alert - * @param title Title of the alert - * @param message Message to display + * @param alertType 提示类型 + * @param title 对话框标题 + * @param message 显示的消息 */ private void showAlert(Alert.AlertType alertType, String title, String message) { Alert alert = new Alert(alertType); @@ -248,14 +249,14 @@ public class ExamView extends BorderPane { } /** - * Handles the finish exam button action. + * 处理“完成考试”按钮的操作。 */ private void handleFinishExam() { - // Save exam results + // 保存考试结果 controller.saveExamResults(examSession); - // Show results + // 展示考试结果 ExamResultsView resultsView = new ExamResultsView(primaryStage, controller, examSession); primaryStage.getScene().setRoot(resultsView); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/views/MainMenuView.java b/src/main/java/com/personalproject/ui/views/MainMenuView.java index d3138c0..619ca88 100644 --- a/src/main/java/com/personalproject/ui/views/MainMenuView.java +++ b/src/main/java/com/personalproject/ui/views/MainMenuView.java @@ -14,7 +14,7 @@ import com.personalproject.auth.UserAccount; import com.personalproject.ui.scenes.LoginScene; /** - * View for the main menu where users can start exams or change settings. + * 用户可以开始考试或更改设置的主菜单界面。 */ public class MainMenuView extends BorderPane { @@ -26,11 +26,11 @@ public class MainMenuView extends BorderPane { private Button logoutButton; /** - * Constructor for MainMenuView. + * MainMenuView 的构造函数。 * - * @param primaryStage The main stage of the application - * @param controller The math learning controller - * @param userAccount The current user account + * @param primaryStage 应用程序的主舞台 + * @param controller 数学学习控制器 + * @param userAccount 当前用户账户 */ public MainMenuView(Stage primaryStage, MathLearningController controller, UserAccount userAccount) { this.primaryStage = primaryStage; @@ -40,23 +40,23 @@ public class MainMenuView extends BorderPane { } /** - * Initializes the UI components. + * 初始化界面组件。 */ private void initializeUI() { - // Create the main layout + // 创建主布局 VBox mainLayout = new VBox(20); mainLayout.setAlignment(Pos.CENTER); mainLayout.setPadding(new Insets(20)); - // Welcome message + // 欢迎信息 Label welcomeLabel = new Label("欢迎, " + userAccount.username()); welcomeLabel.setFont(Font.font("System", FontWeight.BOLD, 18)); - // Difficulty info + // 难度信息 Label difficultyLabel = new Label("当前难度: " + userAccount.difficultyLevel().getDisplayName()); difficultyLabel.setFont(Font.font("System", FontWeight.NORMAL, 14)); - // Buttons + // 按钮区域 VBox buttonBox = new VBox(15); buttonBox.setAlignment(Pos.CENTER); @@ -64,25 +64,25 @@ public class MainMenuView extends BorderPane { changePasswordButton = new Button("修改密码"); logoutButton = new Button("退出登录"); - // Set button sizes + // 设置按钮尺寸 startExamButton.setPrefSize(150, 40); changePasswordButton.setPrefSize(150, 40); logoutButton.setPrefSize(150, 40); buttonBox.getChildren().addAll(startExamButton, changePasswordButton, logoutButton); - // Add components to main layout + // 将组件添加到主布局 mainLayout.getChildren().addAll(welcomeLabel, difficultyLabel, buttonBox); - // Set the center of the border pane + // 将主布局置于边界面板中央 setCenter(mainLayout); - // Add event handlers + // 添加事件处理器 addEventHandlers(); } /** - * Adds event handlers to UI components. + * 为界面组件添加事件处理器。 */ private void addEventHandlers() { startExamButton.setOnAction(e -> handleStartExam()); @@ -91,7 +91,7 @@ public class MainMenuView extends BorderPane { } /** - * Handles the start exam button action. + * 处理开始考试按钮的操作。 */ private void handleStartExam() { ExamSelectionView examSelectionView = new ExamSelectionView(primaryStage, controller, userAccount); @@ -99,7 +99,7 @@ public class MainMenuView extends BorderPane { } /** - * Handles the change password button action. + * 处理修改密码按钮的操作。 */ private void handleChangePassword() { PasswordChangeView passwordChangeView = new PasswordChangeView(primaryStage, controller, userAccount); @@ -107,11 +107,11 @@ public class MainMenuView extends BorderPane { } /** - * Handles the logout button action. + * 处理退出登录按钮的操作。 */ private void handleLogout() { - // Go back to login screen + // 返回登录界面 LoginScene loginScene = new LoginScene(primaryStage, controller); primaryStage.getScene().setRoot(loginScene); } -} \ No newline at end of file +} diff --git a/src/main/java/com/personalproject/ui/views/PasswordChangeView.java b/src/main/java/com/personalproject/ui/views/PasswordChangeView.java index 9f5b98a..ef28098 100644 --- a/src/main/java/com/personalproject/ui/views/PasswordChangeView.java +++ b/src/main/java/com/personalproject/ui/views/PasswordChangeView.java @@ -12,7 +12,7 @@ import com.personalproject.auth.UserAccount; import com.personalproject.ui.views.MainMenuView; /** - * View for changing user password. + * 用户修改密码的界面。 */ public class PasswordChangeView extends BorderPane { @@ -26,11 +26,11 @@ public class PasswordChangeView extends BorderPane { private Button backButton; /** - * Constructor for PasswordChangeView. + * PasswordChangeView 的构造函数。 * - * @param primaryStage The main stage of the application - * @param controller The math learning controller - * @param userAccount The current user account + * @param primaryStage 应用程序的主舞台 + * @param controller 数学学习控制器 + * @param userAccount 当前用户账户 */ public PasswordChangeView(Stage primaryStage, MathLearningController controller, UserAccount userAccount) { this.primaryStage = primaryStage; @@ -40,19 +40,19 @@ public class PasswordChangeView extends BorderPane { } /** - * Initializes the UI components. + * 初始化界面组件。 */ private void initializeUI() { - // Create the main layout + // 创建主布局 VBox mainLayout = new VBox(20); mainLayout.setAlignment(Pos.CENTER); mainLayout.setPadding(new Insets(20)); - // Title + // 标题 Label titleLabel = new Label("修改密码"); titleLabel.setFont(Font.font("System", FontWeight.BOLD, 24)); - // Form for password change + // 修改密码表单 GridPane passwordForm = new GridPane(); passwordForm.setHgap(15); passwordForm.setVgap(15); @@ -77,31 +77,31 @@ public class PasswordChangeView extends BorderPane { passwordForm.add(confirmNewPasswordLabel, 0, 2); passwordForm.add(confirmNewPasswordField, 1, 2); - // Buttons + // 按钮区域 HBox buttonBox = new HBox(15); buttonBox.setAlignment(Pos.CENTER); changePasswordButton = new Button("修改密码"); backButton = new Button("返回"); - // Set button sizes + // 设置按钮尺寸 changePasswordButton.setPrefSize(120, 40); backButton.setPrefSize(120, 40); buttonBox.getChildren().addAll(changePasswordButton, backButton); - // Add components to main layout + // 将组件添加到主布局 mainLayout.getChildren().addAll(titleLabel, passwordForm, buttonBox); - // Set the center of the border pane + // 将主布局置于边界面板中央 setCenter(mainLayout); - // Add event handlers + // 添加事件处理器 addEventHandlers(); } /** - * Adds event handlers to UI components. + * 为界面组件添加事件处理器。 */ private void addEventHandlers() { changePasswordButton.setOnAction(e -> handleChangePassword()); @@ -109,7 +109,7 @@ public class PasswordChangeView extends BorderPane { } /** - * Handles the change password button action. + * 处理修改密码按钮的操作。 */ private void handleChangePassword() { String oldPassword = oldPasswordField.getText(); @@ -136,14 +136,14 @@ public class PasswordChangeView extends BorderPane { if (success) { showAlert(Alert.AlertType.INFORMATION, "修改成功", "密码修改成功!"); - handleBack(); // Go back to main menu + handleBack(); // 返回主菜单 } else { showAlert(Alert.AlertType.ERROR, "修改失败", "当前密码错误或修改失败"); } } /** - * Handles the back button action. + * 处理返回按钮的操作。 */ private void handleBack() { MainMenuView mainMenuView = new MainMenuView(primaryStage, controller, userAccount); @@ -151,11 +151,11 @@ public class PasswordChangeView extends BorderPane { } /** - * Shows an alert dialog. + * 显示提示对话框。 * - * @param alertType Type of alert - * @param title Title of the alert - * @param message Message to display + * @param alertType 提示类型 + * @param title 对话框标题 + * @param message 显示的消息 */ private void showAlert(Alert.AlertType alertType, String title, String message) { Alert alert = new Alert(alertType); @@ -164,4 +164,4 @@ public class PasswordChangeView extends BorderPane { alert.setContentText(message); alert.showAndWait(); } -} \ No newline at end of file +} diff --git a/src/main/resources/email-config.properties b/src/main/resources/email-config.properties new file mode 100644 index 0000000..71ce04e --- /dev/null +++ b/src/main/resources/email-config.properties @@ -0,0 +1,20 @@ + +# 主机不变 +mail.smtp.host=smtp.126.com + +# 关键修改:端口改为 465 +mail.smtp.port=465 + +# 关键修改:禁用 STARTTLS +mail.smtp.starttls.enable=false + +# 关键修改:启用 SSL +mail.smtp.ssl.enable=true + +# 以下不变 +mail.smtp.auth=true +mail.username=soloyouth@126.com +mail.password=ZYsjxwDXFBsWeQcX +mail.from=soloyouth@126.com +mail.subject=数学学习软件注册验证码 +mail.debug=true \ No newline at end of file