diff --git a/src/main/java/com/mathgenerator/controller/ChangePasswordController.java b/src/main/java/com/mathgenerator/controller/ChangePasswordController.java new file mode 100644 index 0000000..ceab172 --- /dev/null +++ b/src/main/java/com/mathgenerator/controller/ChangePasswordController.java @@ -0,0 +1,90 @@ +package com.mathgenerator.controller; + +import com.mathgenerator.model.User; +import com.mathgenerator.service.UserService; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.stage.Stage; +import java.io.IOException; + +public class ChangePasswordController { + + private final UserService userService = new UserService(); + private User currentUser; + + @FXML private PasswordField oldPasswordField; + @FXML private PasswordField newPasswordField; + @FXML private PasswordField confirmNewPasswordField; + @FXML private Button confirmButton; + @FXML private Button backButton; + @FXML private Label statusLabel; + + /** + * 初始化控制器,接收当前用户信息 + */ + public void initData(User user) { + this.currentUser = user; + } + + @FXML + private void handleConfirmAction(ActionEvent event) { + // 1. 获取输入 + String oldPassword = oldPasswordField.getText(); + String newPassword = newPasswordField.getText(); + String confirmNewPassword = confirmNewPasswordField.getText(); + + // 2. 输入校验 + if (oldPassword.isEmpty() || newPassword.isEmpty() || confirmNewPassword.isEmpty()) { + statusLabel.setText("所有密码字段都不能为空!"); + return; + } + if (!newPassword.equals(confirmNewPassword)) { + statusLabel.setText("两次输入的新密码不匹配!"); + return; + } + if (!UserService.isPasswordValid(newPassword)) { + statusLabel.setText("新密码格式错误!必须为6-10位,且包含大小写字母和数字。"); + return; + } + + // 3. 调用后端服务修改密码 + boolean success = userService.changePassword( + currentUser.username(), + oldPassword, + newPassword + ); + + // 4. 更新UI反馈 + if (success) { + statusLabel.setText("密码修改成功!请返回主菜单。"); + confirmButton.setDisable(true); // 防止重复点击 + } else { + statusLabel.setText("修改失败:当前密码错误。"); + } + } + + /** + * 处理返回按钮事件,返回主菜单 + */ + @FXML + private void handleBackAction(ActionEvent event) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/MainMenuView.fxml")); + Parent root = loader.load(); + MainMenuController controller = loader.getController(); + controller.initData(currentUser); // 将用户信息传回主菜单 + + Stage stage = (Stage) backButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("主菜单"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/controller/LoginController.java b/src/main/java/com/mathgenerator/controller/LoginController.java index 7c7150d..264c0c0 100644 --- a/src/main/java/com/mathgenerator/controller/LoginController.java +++ b/src/main/java/com/mathgenerator/controller/LoginController.java @@ -15,7 +15,7 @@ import javafx.stage.Stage; import java.io.IOException; import java.util.Optional; - +import com.mathgenerator.util.ValidationUtils; public class LoginController { // 依赖注入后端服务 @@ -46,7 +46,13 @@ public class LoginController { String username = usernameField.getText(); String password = passwordField.getText(); - if (username.isEmpty() || password.isEmpty()) { + // --- 2. 使用工具类进行校验 --- + if (!ValidationUtils.isUsernameValid(username)) { + statusLabel.setText("登录失败:用户名不能为空且不能包含空格。"); + return; + } + + if (password.isEmpty()) { statusLabel.setText("用户名和密码不能为空!"); return; } diff --git a/src/main/java/com/mathgenerator/controller/MainMenuController.java b/src/main/java/com/mathgenerator/controller/MainMenuController.java index 867deff..60f7930 100644 --- a/src/main/java/com/mathgenerator/controller/MainMenuController.java +++ b/src/main/java/com/mathgenerator/controller/MainMenuController.java @@ -48,11 +48,26 @@ public class MainMenuController { @FXML private void handleChangePasswordAction(ActionEvent event) { - // TODO: 跳转到修改密码界面 - statusLabel.setText("修改密码功能待实现。"); - } + try { + // 1\. 加载 FXML 文件 + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/ChangePasswordView.fxml")); + Parent root = loader.load(); + // 2\. 获取新界面的控制器 + ChangePasswordController controller = loader.getController(); - @FXML + // 3\. 调用控制器的方法,传递当前用户信息 + controller.initData(currentUser); + + // 4\. 显示新场景 + Stage stage = (Stage) logoutButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("修改密码"); + + } catch (IOException e) { + e.printStackTrace(); + } + } + @FXML private void handleLogoutAction(ActionEvent event) { // 跳转回登录界面 loadScene("/com/mathgenerator/view/LoginView.fxml"); diff --git a/src/main/java/com/mathgenerator/controller/QuizController.java b/src/main/java/com/mathgenerator/controller/QuizController.java index abce998..830e06d 100644 --- a/src/main/java/com/mathgenerator/controller/QuizController.java +++ b/src/main/java/com/mathgenerator/controller/QuizController.java @@ -62,7 +62,7 @@ public class QuizController { } /** - * 显示当前的题目和选项 + * 显示当前的题目和选项 (已更新,增加ABCD前缀) */ private void displayCurrentQuestion() { ChoiceQuestion currentQuestion = questions.get(currentQuestionIndex); @@ -72,8 +72,10 @@ public class QuizController { questionTextLabel.setText(currentQuestion.questionText()); List radioButtons = List.of(option1, option2, option3, option4); + String[] prefixes = {"A. ", "B. ", "C. ", "D. "}; // 定义选项前缀 for (int i = 0; i < radioButtons.size(); i++) { - radioButtons.get(i).setText(currentQuestion.options().get(i)); + // 将前缀和选项文本结合起来 + radioButtons.get(i).setText(prefixes[i] + currentQuestion.options().get(i)); } optionsGroup.selectToggle(null); // 清除上一次的选择 diff --git a/src/main/java/com/mathgenerator/controller/RegisterController.java b/src/main/java/com/mathgenerator/controller/RegisterController.java index 0227866..d7fd31a 100644 --- a/src/main/java/com/mathgenerator/controller/RegisterController.java +++ b/src/main/java/com/mathgenerator/controller/RegisterController.java @@ -12,7 +12,7 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Stage; import java.io.IOException; - +import com.mathgenerator.util.ValidationUtils; public class RegisterController { private final UserService userService = new UserService(); @@ -50,41 +50,52 @@ public class RegisterController { @FXML private void handleRegisterAction(ActionEvent event) { - // 1. 字段校验 - if (usernameField.getText().isEmpty() || emailField.getText().isEmpty() || - verificationCodeField.getText().isEmpty() || passwordField.getText().isEmpty()) { - statusLabel.setText("所有字段都不能为空!"); - return; - } - if (!passwordField.getText().equals(confirmPasswordField.getText())) { - statusLabel.setText("两次输入的密码不匹配!"); + // 1. 字段校验 (已简化,不再校验密码) + String username = usernameField.getText(); + String email = emailField.getText(); + + if (!ValidationUtils.isUsernameValid(username) || !ValidationUtils.isEmailValid(email) || + verificationCodeField.getText().isEmpty()) { + statusLabel.setText("所有字段都不能为空且格式正确!"); return; } if (this.sentCode == null || !this.sentCode.equals(verificationCodeField.getText())) { statusLabel.setText("验证码错误!"); return; } - if (!UserService.isPasswordValid(passwordField.getText())) { - statusLabel.setText("密码格式错误!必须为6-10位,且包含大小写字母和数字。"); - return; - } - // 2. 调用后端服务进行注册 - boolean success = userService.register( - usernameField.getText(), - emailField.getText(), - passwordField.getText() - ); + // 2. 调用后端服务进行无密码注册 + boolean success = userService.register(username, email); - // 3. 根据结果更新UI + // 3. 根据结果更新UI或跳转 if (success) { - statusLabel.setText("注册成功!请返回登录。"); - registerButton.setDisable(true); + statusLabel.setText("注册成功!请设置您的密码。"); + // 成功后,加载设置密码界面,并传递用户名 + loadSetPasswordScene(username); } else { statusLabel.setText("注册失败:用户名或邮箱已被占用。"); } } + /** + * (新增) 加载设置密码界面,并传递用户名。 + */ + private void loadSetPasswordScene(String username) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/SetPasswordView.fxml")); + Parent root = loader.load(); + + SetPasswordController controller = loader.getController(); + controller.initData(username); // 将用户名传递给新界面的控制器 + + Stage stage = (Stage) registerButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("设置密码"); + } catch (IOException e) { + e.printStackTrace(); + } + } + @FXML private void handleBackToLoginAction(ActionEvent event) { loadScene("/com/mathgenerator/view/LoginView.fxml"); diff --git a/src/main/java/com/mathgenerator/controller/SetPasswordController.java b/src/main/java/com/mathgenerator/controller/SetPasswordController.java new file mode 100644 index 0000000..278c80c --- /dev/null +++ b/src/main/java/com/mathgenerator/controller/SetPasswordController.java @@ -0,0 +1,80 @@ +package com.mathgenerator.controller; + +import com.mathgenerator.model.User; +import com.mathgenerator.service.UserService; +import com.mathgenerator.util.ValidationUtils; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.stage.Stage; + +import java.io.IOException; + +public class SetPasswordController { + + private final UserService userService = new UserService(); + private String username; + + @FXML private Label promptLabel; + @FXML private PasswordField newPasswordField; + @FXML private PasswordField confirmPasswordField; + @FXML private Button confirmButton; + @FXML private Label statusLabel; + + /** + * 接收从注册界面传递过来的用户名 + */ + public void initData(String username) { + this.username = username; + promptLabel.setText("为您的账户 " + username + " 设置密码"); + } + + @FXML + private void handleConfirmAction(ActionEvent event) { + String newPassword = newPasswordField.getText(); + String confirmPassword = confirmPasswordField.getText(); + + if (!newPassword.equals(confirmPassword)) { + statusLabel.setText("两次输入的密码不匹配!"); + return; + } + if (!ValidationUtils.isPasswordValid(newPassword)) { + statusLabel.setText("新密码格式错误!必须为6-10位,且包含大小写字母和数字。"); + return; + } + + // 调用后端服务设置密码 + boolean success = userService.setPassword(this.username, newPassword); + + if (success) { + statusLabel.setText("密码设置成功!正在进入主菜单..."); + // 密码设置成功后,获取完整的用户信息并直接跳转到主菜单 + userService.findUserByUsername(this.username).ifPresent(this::loadMainMenu); + } else { + statusLabel.setText("密码设置失败,请稍后重试或重新注册。"); + } + } + + /** + * 加载主菜单界面,并传递用户信息 (实现自动登录) + */ + private void loadMainMenu(User user) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/MainMenuView.fxml")); + Parent root = loader.load(); + MainMenuController controller = loader.getController(); + controller.initData(user); // 将完整的User对象传递给主菜单 + + Stage stage = (Stage) confirmButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("主菜单"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java b/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java index 9ed1061..530e6dc 100644 --- a/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java +++ b/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java @@ -172,25 +172,6 @@ public class PrimarySchoolGenerator implements QuestionGenerator { parts.add(startIndex, "("); } - // 在 PrimarySchoolGenerator.java 中添加这个方法 - /** - * 仅生成题目字符串,不包含答案和选项。 - * 这是为了方便子类(初中、高中)继承和修改题干。 - * @return 题目文本字符串 - */ - public String generateBasicQuestionText() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - int operandCount = random.nextInt(2, 5); - List parts = new ArrayList<>(); - parts.add(String.valueOf(getOperand())); - for (int i = 1; i < operandCount; i++) { - parts.add(getRandomOperator()); - parts.add(String.valueOf(getOperand())); - } - if (operandCount > 2 && random.nextBoolean()) { - addParentheses(parts); - } - return String.join(" ", parts); - } + } \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/service/UserService.java b/src/main/java/com/mathgenerator/service/UserService.java index 7a6375b..2c816a7 100644 --- a/src/main/java/com/mathgenerator/service/UserService.java +++ b/src/main/java/com/mathgenerator/service/UserService.java @@ -35,18 +35,20 @@ public class UserService { } private Map loadUsersFromFile() { - try { - if (Files.exists(USER_FILE_PATH) && Files.size(USER_FILE_PATH) > 0) { - try (FileReader reader = new FileReader(USER_FILE_PATH.toFile())) { - Type type = new TypeToken>() {}.getType(); - Map loadedUsers = gson.fromJson(reader, type); - return loadedUsers != null ? new ConcurrentHashMap<>(loadedUsers) : new ConcurrentHashMap<>(); - } - } + // 如果文件不存在,直接返回一个空的Map,不再创建默认用户 + if (!Files.exists(USER_FILE_PATH)) { + return new ConcurrentHashMap<>(); + } + + try (FileReader reader = new FileReader(USER_FILE_PATH.toFile())) { + Type type = new TypeToken>() {}.getType(); + Map loadedUsers = gson.fromJson(reader, type); + // 如果文件为空或格式错误,也返回一个空的Map + return loadedUsers != null ? new ConcurrentHashMap<>(loadedUsers) : new ConcurrentHashMap<>(); } catch (IOException e) { System.err.println("错误:加载用户文件失败 - " + e.getMessage()); + return new ConcurrentHashMap<>(); } - return new ConcurrentHashMap<>(); } private void saveUsers() { @@ -103,32 +105,49 @@ public class UserService { } /** - * 注册新用户。 - * @return 成功返回true, 否则返回false + * (已修正) 注册一个没有初始密码的新用户。 + * @param username 新用户的用户名 + * @param email 新用户的邮箱 + * @return 注册成功返回true, 如果用户名或邮箱已存在则返回false。 */ - public boolean register(String username, String email, String password) { - // 1. 基础校验:防止 null 或空白输入 - if (username == null || email == null || password == null || - username.trim().isEmpty() || email.trim().isEmpty() || password.trim().isEmpty()) { - return false; + public boolean register(String username, String email) { + if (userDatabase.containsKey(username)) { + return false; // 用户名已存在 } - - // 2. 检查用户名或邮箱是否已存在(使用 Objects.equals 安全比较) - boolean usernameExists = userDatabase.containsKey(username); - boolean emailExists = userDatabase.values().stream() - .anyMatch(u -> Objects.equals(u.email(), email)); - - if (usernameExists || emailExists) { - return false; // 用户名或邮箱已存在 + // 检查数据库中已存在的用户的email是否与新email相同 + // 使用 email.equals(u.email()) 可以安全地处理 u.email() 为 null 的情况 + if (userDatabase.values().stream() + .anyMatch(u -> email.equals(u.email()))) { + return false; // 邮箱已存在 } - // 3. 创建新用户并保存 - User newUser = new User(username, email, password); + // --- 核心修正在这里 --- + // 创建用户时,密码字段设为 null,表示该用户处于“待设置密码”状态 + User newUser = new User(username, email, null); userDatabase.put(username, newUser); saveUsers(); return true; } + /** + * (新增) 为指定用户设置初始密码。 + * @param username 要设置密码的用户名 + * @param password 要设置的新密码 + * @return 成功设置返回 true, 如果用户不存在则返回 false + */ + public boolean setPassword(String username, String password) { + return findUserByUsername(username) + .map(user -> { + // 只有当用户当前密码为 null 时才允许设置 + if (user.password() == null) { + User updatedUser = new User(user.username(), user.email(), password); + userDatabase.put(username, updatedUser); + saveUsers(); + return true; + } + return false; // 用户已经有密码,不能通过此方法设置 + }).orElse(false); + } /** * 验证密码是否符合复杂度要求。 * @param password 待验证的密码 diff --git a/src/main/java/com/mathgenerator/util/ValidationUtils.java b/src/main/java/com/mathgenerator/util/ValidationUtils.java new file mode 100644 index 0000000..845a2f7 --- /dev/null +++ b/src/main/java/com/mathgenerator/util/ValidationUtils.java @@ -0,0 +1,52 @@ +package com.mathgenerator.util; + +import java.util.regex.Pattern; + +/** + * 一个包含静态校验方法的工具类。 + * 用于集中管理项目中所有的数据格式验证逻辑。 + */ +public final class ValidationUtils { + + // 密码策略: 6-10位, 必须包含大小写字母和数字 + private static final Pattern PASSWORD_PATTERN = + Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$"); + + // 用户名策略: 不包含任何空白字符 + private static final Pattern USERNAME_NO_WHITESPACE_PATTERN = + Pattern.compile("^\\S+$"); + + // 私有构造函数,防止这个工具类被实例化 + private ValidationUtils() {} + + /** + * 验证用户名格式是否有效。 + * 当前规则:不允许包含任何空格或空白字符。 + * @param username 待验证的用户名 + * @return 如果有效返回true, 否则返回false + */ + public static boolean isUsernameValid(String username) { + if (username == null || username.isEmpty()) { + return false; + } + return USERNAME_NO_WHITESPACE_PATTERN.matcher(username).matches(); + } + + /** + * 验证密码是否符合复杂度要求。 + * @param password 待验证的密码 + * @return 如果符合要求返回true + */ + public static boolean isPasswordValid(String password) { + return password != null && PASSWORD_PATTERN.matcher(password).matches(); + } + + /** + * 验证邮箱格式是否有效 (简单校验)。 + * @param email 待验证的邮箱 + * @return 如果格式基本正确返回true + */ + public static boolean isEmailValid(String email) { + return email != null && !email.isEmpty() && email.contains("@"); + } +} \ No newline at end of file diff --git a/src/main/resources/com/mathgenerator/view/ChangePasswordView.fxml b/src/main/resources/com/mathgenerator/view/ChangePasswordView.fxml new file mode 100644 index 0000000..480d327 --- /dev/null +++ b/src/main/resources/com/mathgenerator/view/ChangePasswordView.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + +