稳定版本 #11

Merged
hnu202326010318 merged 2 commits from liguolin_branch into develop 3 months ago

@ -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<RadioButton> 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); // 清除上一次的选择

@ -50,36 +50,52 @@ public class RegisterController {
@FXML
private void handleRegisterAction(ActionEvent event) {
// 1. 字段校验
// 1. 字段校验 (已简化,不再校验密码)
String username = usernameField.getText();
String email = emailField.getText();
// --- 2. 使用工具类进行校验 ---
if (!ValidationUtils.isUsernameValid(username)) {
statusLabel.setText("注册失败:用户名不能为空且不能包含空格");
if (!ValidationUtils.isUsernameValid(username) || !ValidationUtils.isEmailValid(email) ||
verificationCodeField.getText().isEmpty()) {
statusLabel.setText("所有字段都不能为空且格式正确");
return;
}
// ...
if (!ValidationUtils.isPasswordValid(passwordField.getText())) { // 同样可以替换密码校验
statusLabel.setText("密码格式错误必须为6-10位且包含大小写字母和数字。");
if (this.sentCode == null || !this.sentCode.equals(verificationCodeField.getText())) {
statusLabel.setText("验证码错误!");
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");

@ -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();
}
}
}

@ -35,18 +35,20 @@ public class UserService {
}
private Map<String, User> 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<Map<String, User>>() {}.getType();
Map<String, User> 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<Map<String, User>>() {}.getType();
Map<String, User> 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

@ -24,8 +24,6 @@
<Button fx:id="sendCodeButton" mnemonicParsing="false" onAction="#handleSendCodeAction" text="发送验证码" />
</children>
</HBox>
<PasswordField fx:id="passwordField" maxWidth="300.0" promptText="设置密码 (6-10位, 含大小写字母和数字)" />
<PasswordField fx:id="confirmPasswordField" maxWidth="300.0" promptText="确认密码" />
<Button fx:id="registerButton" mnemonicParsing="false" onAction="#handleRegisterAction" prefWidth="120.0" text="确认注册" />
<Button fx:id="backToLoginButton" mnemonicParsing="false" onAction="#handleBackToLoginAction" style="-fx-background-color: #6c757d;" text="返回登录" textFill="WHITE" />
<Label fx:id="statusLabel" textFill="RED" wrapText="true">

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0" prefWidth="450.0" spacing="15.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.SetPasswordController">
<children>
<Label text="设置您的初始密码">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<Label fx:id="promptLabel" text="为您的账户 [用户名] 设置密码" />
<PasswordField fx:id="newPasswordField" maxWidth="300.0" promptText="新密码 (6-10位, 含大小写字母和数字)" />
<PasswordField fx:id="confirmPasswordField" maxWidth="300.0" promptText="确认新密码" />
<Button fx:id="confirmButton" mnemonicParsing="false" onAction="#handleConfirmAction" prefWidth="120.0" text="确认并进入" />
<Label fx:id="statusLabel" textFill="RED" wrapText="true">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
Loading…
Cancel
Save