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