最终版 #2

Merged
ppy4sjqvf merged 26 commits from wsf_branch into develop 4 months ago

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA" serialisationVersion="2">
<checkstyleVersion>10.26.1</checkstyleVersion>
<checkstyleVersion>11.0.1</checkstyleVersion>
<scanScope>JavaOnly</scanScope>
<copyLibs>true</copyLibs>
<option name="thirdPartyClasspath" />

@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -4,42 +4,60 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ybw</groupId>
<groupId>com.wsf</groupId>
<artifactId>MathApp</artifactId>
<version>1.0-SNAPSHOT</version>
<name>MathApp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<junit.version>5.10.0</junit.version>
<javafx.version>17.0.6</javafx.version>
</properties>
<dependencies>
<!-- JavaFX 基础模块 (必需) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>17.0.6</version>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>17.0.6</version>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- 其他依赖保持不变 -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>17.0.6</version>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>17.0.6</version>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>17.0.6</version>
<version>${javafx.version}</version>
</dependency>
<!-- 第三方库 -->
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
@ -99,6 +117,8 @@
</exclusion>
</exclusions>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
@ -111,6 +131,11 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<build>
@ -122,24 +147,23 @@
<configuration>
<source>17</source>
<target>17</target>
<!-- 移除了 -enable-preview 选项 -->
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<!-- 根据你的实际主类修改 -->
<mainClass>com.wsf.mathapp.Main</mainClass>
<!-- 移除了 preview 选项 -->
</configuration>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>com.ybw.mathapp/com.ybw.mathapp.HelloApplication</mainClass>
<launcher>app</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
<mainClass>com.wsf.mathapp.Main</mainClass>
</configuration>
</execution>
</executions>

@ -0,0 +1,17 @@
package com.wsf.mathapp;
import com.wsf.mathapp.controller.SceneManager;
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
SceneManager sceneManager = new SceneManager(primaryStage);
sceneManager.showLoginView();
}
public static void main(String[] args) {
launch(args);
}
}

@ -0,0 +1,84 @@
package com.wsf.mathapp.controller;
import com.wsf.mathapp.view.*;
import javafx.stage.Stage;
public class SceneManager {
private final Stage primaryStage;
private final LoginView loginView;
private final RegisterView registerView;
private final MainMenuView mainMenuView;
private final LevelSelectionView levelSelectionView;
private final QuestionCountView questionCountView;
private final QuizView quizView;
private final ResultView resultView;
private String currentUserName;
public SceneManager(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("数学学习软件");
this.primaryStage.setResizable(false);
// 初始化所有视图
this.loginView = new LoginView(this);
this.registerView = new RegisterView(this);
this.mainMenuView = new MainMenuView(this);
this.levelSelectionView = new LevelSelectionView(this);
this.questionCountView = new QuestionCountView(this);
this.quizView = new QuizView(this);
this.resultView = new ResultView(this);
}
public void showLoginView() {
primaryStage.setScene(loginView.getScene());
primaryStage.show();
}
public void showRegisterView() {
primaryStage.setScene(registerView.getScene());
primaryStage.show();
}
public void showMainMenuView() {
// 在显示主菜单前更新用户名
if (mainMenuView != null) {
mainMenuView.updateUsername(currentUserName);
}
if (mainMenuView != null) {
primaryStage.setScene(mainMenuView.getScene());
}
}
public void showLevelSelectionView() {
// 在显示级别选择界面前更新用户名
if (levelSelectionView != null) {
levelSelectionView.updateUsername(currentUserName);
}
if (levelSelectionView != null) {
primaryStage.setScene(levelSelectionView.getScene());
}
}
public void showQuestionCountView() {
primaryStage.setScene(questionCountView.getScene());
}
public void showQuizView() {
primaryStage.setScene(quizView.getScene());
}
public void showResultView(double score) {
resultView.setScore(score);
primaryStage.setScene(resultView.getScene());
}
// Getter methods for views
public LoginView getLoginView() { return loginView; }
public RegisterView getRegisterView() { return registerView; }
public MainMenuView getMainMenuView() { return mainMenuView; }
public LevelSelectionView getLevelSelectionView() { return levelSelectionView; }
public QuestionCountView getQuestionCountView() { return questionCountView; }
public QuizView getQuizView() { return quizView; }
public ResultView getResultView() { return resultView; }
public void setCurrentUserName(String currentUserName){ this.currentUserName = currentUserName;}
public String getCurrentUserName(){return this.currentUserName;}
}

@ -0,0 +1,21 @@
package com.wsf.mathapp.service;
import com.ybw.mathapp.service.PrimarySchoolGenerator;
import com.ybw.mathapp.service.JuniorHighGenerator;
import com.ybw.mathapp.service.SeniorHighGenerator;
import com.ybw.mathapp.service.QuestionGenerator;
public class QuestionService {
public static QuestionGenerator createGenerator(String level) {
return switch (level) {
case "小学" -> new PrimarySchoolGenerator();
case "初中" -> new JuniorHighGenerator();
case "高中" -> new SeniorHighGenerator();
default -> null;
};
}
}

@ -0,0 +1,190 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
public class LevelSelectionView {
private Scene scene;
private final SceneManager sceneManager;
private String currentUsername;
// 添加界面组件引用
private Label usernameLabel;
private Text avatarText;
public LevelSelectionView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
this.currentUsername = sceneManager.getCurrentUserName();
createScene();
}
private void createScene() {
// 创建主容器
VBox mainContainer = new VBox();
mainContainer.setPadding(new Insets(20));
// 创建顶部用户信息栏
HBox userInfoBar = createUserInfoBar();
// 创建级别选择内容区域
VBox selectionContent = createSelectionContent();
mainContainer.getChildren().addAll(userInfoBar, selectionContent);
scene = new Scene(mainContainer, 450, 550);
}
private HBox createUserInfoBar() {
HBox userInfoBar = new HBox(15);
userInfoBar.setAlignment(Pos.CENTER_LEFT);
userInfoBar.setPadding(new Insets(0, 0, 30, 0));
userInfoBar.setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 0 0 1 0; -fx-padding: 0 0 15 0;");
// 创建圆形头像容器
VBox avatarContainer = new VBox();
avatarContainer.setAlignment(Pos.CENTER);
avatarContainer.setPrefSize(50, 50);
// 创建圆形头像背景
Circle avatarCircle = new Circle(22);
avatarCircle.setFill(Color.web("#4CAF50")); // 统一的绿色背景
avatarCircle.setStroke(Color.WHITE);
avatarCircle.setStrokeWidth(2);
// 添加阴影效果
avatarCircle.setStyle("-fx-effect: drop shadow(gaussian, rgba(0,0,0,0.2), 5, 0.3, 2, 2);");
// 添加首字母文本
avatarText = new Text(getFirstLetter());
avatarText.setFill(Color.WHITE);
avatarText.setFont(Font.font("Arial", FontWeight.BOLD, 16));
// 使用StackPane将文本放在圆形中心
javafx.scene.layout.StackPane avatarStack = new javafx.scene.layout.StackPane();
avatarStack.getChildren().addAll(avatarCircle, avatarText);
avatarStack.setPrefSize(44, 44);
avatarStack.setAlignment(Pos.CENTER);
avatarContainer.getChildren().add(avatarStack);
// 用户名标签
usernameLabel = new Label(currentUsername != null ? currentUsername : "用户");
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 16));
usernameLabel.setStyle("-fx-text-fill: #2c3e50;");
// 间隔
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
userInfoBar.getChildren().addAll(avatarContainer, usernameLabel, spacer);
return userInfoBar;
}
private VBox createSelectionContent() {
VBox selectionContent = new VBox(25);
selectionContent.setPadding(new Insets(30, 20, 20, 20));
selectionContent.setAlignment(Pos.CENTER);
Label titleLabel = new Label("选择题目级别");
titleLabel.setFont(Font.font("Arial", FontWeight.BOLD, 26));
titleLabel.setStyle("-fx-text-fill: #2c3e50;");
// 添加副标题
Label subtitleLabel = new Label("请选择适合您的学习级别");
subtitleLabel.setFont(Font.font("Arial", 14));
subtitleLabel.setStyle("-fx-text-fill: #7f8c8d; -fx-padding: 0 0 10 0;");
Button primaryButton = createLevelButton("小学题目", "#4CAF50", "#45a049");
Button juniorButton = createLevelButton("初中题目", "#2196F3", "#1976D2");
Button seniorButton = createLevelButton("高中题目", "#9C27B0", "#7B1FA2");
Button backButton = new Button("返回主菜单");
backButton.setStyle("-fx-background-color: #95a5a6; -fx-text-fill: white; -fx-font-size: 14px; -fx-background-radius: 8; -fx-padding: 8 20;");
backButton.setPrefSize(180, 45);
backButton.setOnMouseEntered(e -> backButton.setStyle("-fx-background-color: #7f8c8d; -fx-text-fill: white; -fx-font-size: 14px; -fx-background-radius: 8; -fx-padding: 8 20;"));
backButton.setOnMouseExited(e -> backButton.setStyle("-fx-background-color: #95a5a6; -fx-text-fill: white; -fx-font-size: 14px; -fx-background-radius: 8; -fx-padding: 8 20;"));
primaryButton.setOnAction(e -> {
String selectedLevel = "小学";
sceneManager.getQuestionCountView().setLevel(selectedLevel);
sceneManager.showQuestionCountView();
});
juniorButton.setOnAction(e -> {
String selectedLevel = "初中";
sceneManager.getQuestionCountView().setLevel(selectedLevel);
sceneManager.showQuestionCountView();
});
seniorButton.setOnAction(e -> {
String selectedLevel = "高中";
sceneManager.getQuestionCountView().setLevel(selectedLevel);
sceneManager.showQuestionCountView();
});
backButton.setOnAction(e -> {
sceneManager.showMainMenuView();
});
selectionContent.getChildren().addAll(
titleLabel, subtitleLabel, primaryButton, juniorButton, seniorButton, backButton
);
return selectionContent;
}
private Button createLevelButton(String text, String color, String hoverColor) {
Button button = new Button(text);
button.setStyle(String.format(
"-fx-background-color: %s; -fx-text-fill: white; -fx-font-size: 16px; -fx-font-weight: bold; " +
"-fx-background-radius: 12; -fx-padding: 12 30; -fx-effect: drop shadow(gaussian, rgba(0,0,0,0.2), 8, 0.3, 2, 2);",
color
));
button.setPrefSize(220, 60);
button.setOnMouseEntered(e -> button.setStyle(String.format(
"-fx-background-color: %s; -fx-text-fill: white; -fx-font-size: 16px; -fx-font-weight: bold; " +
"-fx-background-radius: 12; -fx-padding: 12 30; -fx-effect: drop shadow(gaussian, rgba(0,0,0,0.3), 10, 0.4, 3, 3);",
hoverColor
)));
button.setOnMouseExited(e -> button.setStyle(String.format(
"-fx-background-color: %s; -fx-text-fill: white; -fx-font-size: 16px; -fx-font-weight: bold; " +
"-fx-background-radius: 12; -fx-padding: 12 30; -fx-effect: drop shadow(gaussian, rgba(0,0,0,0.2), 8, 0.3, 2, 2);",
color
)));
return button;
}
private String getFirstLetter() {
return currentUsername != null && !currentUsername.isEmpty() ?
currentUsername.substring(0, 1).toUpperCase() : "U";
}
// 添加更新用户名的方法
public void updateUsername(String username) {
this.currentUsername = username;
if (usernameLabel != null) {
usernameLabel.setText(username != null ? username : "用户");
}
if (avatarText != null) {
avatarText.setText(getFirstLetter());
}
}
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,113 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.ybw.mathapp.util.Login;
import com.ybw.mathapp.util.LoginFileUtils;
import com.ybw.mathapp.util.Register;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
public class LoginView {
private Scene scene;
private final SceneManager sceneManager;
public LoginView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
private void createScene() {
VBox root = new VBox(20);
root.setPadding(new Insets(40));
root.setAlignment(Pos.CENTER);
Label titleLabel = new Label("数学学习软件");
titleLabel.setFont(Font.font(24));
Label subtitleLabel = new Label("用户登录");
subtitleLabel.setFont(Font.font(18));
// 修改提示文本
TextField usernameOrEmailField = new TextField();
usernameOrEmailField.setPromptText("请输入用户名或邮箱");
usernameOrEmailField.setMaxWidth(300);
usernameOrEmailField.setPrefHeight(40);
PasswordField passwordField = new PasswordField();
passwordField.setPromptText("请输入密码");
passwordField.setMaxWidth(300);
passwordField.setPrefHeight(40);
Button loginButton = new Button("登录");
loginButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 14px;");
loginButton.setPrefSize(300, 40);
Button registerButton = new Button("注册账号");
registerButton.setStyle("-fx-background-color: #2196F3; -fx-text-fill: white; -fx-font-size: 14px;");
registerButton.setPrefSize(300, 40);
Label statusLabel = new Label();
loginButton.setOnAction(e -> {
String usernameOrEmail = usernameOrEmailField.getText().trim();
String password = passwordField.getText();
if (usernameOrEmail.isEmpty() || password.isEmpty()) {
showError(statusLabel, "用户名/邮箱和密码不能为空!");
return;
}
// 调用后端的登录方法,支持用户名或邮箱登录
boolean success = Login.login(usernameOrEmail, password);
System.out.println(usernameOrEmail);
System.out.println(password);
if (success) {
showSuccess(statusLabel);
String username;
if (LoginFileUtils.emailFindName(usernameOrEmail) == null){
username = usernameOrEmail;
}
else {
username = LoginFileUtils.emailFindName(usernameOrEmail);
}
sceneManager.setCurrentUserName(username);
sceneManager.showMainMenuView();
} else {
showError(statusLabel, "用户名/邮箱或密码错误!");
}
});
registerButton.setOnAction(e -> {
sceneManager.showRegisterView();
});
// 添加回车键登录支持
usernameOrEmailField.setOnAction(e -> loginButton.fire());
passwordField.setOnAction(e -> loginButton.fire());
root.getChildren().addAll(
titleLabel, subtitleLabel, usernameOrEmailField, passwordField,
loginButton, registerButton, statusLabel
);
scene = new Scene(root, 400, 500);
}
private void showError(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: red;");
}
private void showSuccess(Label label) {
label.setText("登录成功!");
label.setStyle("-fx-text-fill: green;");
}
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,257 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.ybw.mathapp.util.ChangePassword;
import com.ybw.mathapp.util.Login;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
public class MainMenuView {
private Scene scene;
private final SceneManager sceneManager;
private String currentUsername;
// 添加界面组件引用,用于动态更新
private Label usernameLabel;
private Text avatarText;
private Label welcomeLabel;
public MainMenuView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
this.currentUsername = sceneManager.getCurrentUserName();
createScene();
}
private void createScene() {
// 创建主容器
VBox mainContainer = new VBox();
mainContainer.setPadding(new Insets(20));
// 创建顶部用户信息栏
HBox userInfoBar = createUserInfoBar();
// 创建主菜单内容区域
VBox menuContent = createMenuContent();
mainContainer.getChildren().addAll(userInfoBar, menuContent);
scene = new Scene(mainContainer, 450, 550);
}
private HBox createUserInfoBar() {
HBox userInfoBar = new HBox(15);
userInfoBar.setAlignment(Pos.CENTER_LEFT);
userInfoBar.setPadding(new Insets(0, 0, 30, 0));
userInfoBar.setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 0 0 1 0; -fx-padding: 0 0 15 0;");
// 创建圆形头像容器
VBox avatarContainer = new VBox();
avatarContainer.setAlignment(Pos.CENTER);
avatarContainer.setPrefSize(50, 50);
// 创建圆形头像背景
Circle avatarCircle = new Circle(22);
avatarCircle.setFill(Color.web("#4CAF50"));
avatarCircle.setStroke(Color.WHITE);
avatarCircle.setStrokeWidth(2);
// 添加阴影效果
avatarCircle.setStyle("-fx-effect: drop shadow(gaussian, rgba(0,0,0,0.2), 5, 0.3, 2, 2);");
// 添加首字母文本
avatarText = new Text(getFirstLetter());
avatarText.setFill(Color.WHITE);
avatarText.setFont(Font.font("Arial", FontWeight.BOLD, 16));
// 使用StackPane将文本放在圆形中心
javafx.scene.layout.StackPane avatarStack = new javafx.scene.layout.StackPane();
avatarStack.getChildren().addAll(avatarCircle, avatarText);
avatarStack.setPrefSize(44, 44);
avatarStack.setAlignment(Pos.CENTER);
avatarContainer.getChildren().add(avatarStack);
// 用户名标签
usernameLabel = new Label(currentUsername != null ? currentUsername : "用户");
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 16));
usernameLabel.setStyle("-fx-text-fill: #2c3e50;");
// 间隔
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
userInfoBar.getChildren().addAll(avatarContainer, usernameLabel, spacer);
return userInfoBar;
}
private VBox createMenuContent() {
VBox menuContent = new VBox(25);
menuContent.setPadding(new Insets(20));
menuContent.setAlignment(Pos.CENTER);
Label titleLabel = new Label("数学学习软件");
titleLabel.setFont(Font.font(28));
titleLabel.setStyle("-fx-text-fill: #2c3e50;");
// 修改欢迎标签,显示用户名
welcomeLabel = new Label("欢迎," + (currentUsername != null ? currentUsername : "用户") + "");
welcomeLabel.setFont(Font.font(18));
welcomeLabel.setStyle("-fx-text-fill: #7f8c8d;");
Button startButton = new Button("开始练习");
startButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;");
startButton.setPrefSize(220, 55);
startButton.setOnMouseEntered(e -> startButton.setStyle("-fx-background-color: #45a049; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;"));
startButton.setOnMouseExited(e -> startButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;"));
Button changePasswordButton = new Button("修改密码");
changePasswordButton.setStyle("-fx-background-color: #2196F3; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;");
changePasswordButton.setPrefSize(220, 55);
changePasswordButton.setOnMouseEntered(e -> changePasswordButton.setStyle("-fx-background-color: #1976D2; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;"));
changePasswordButton.setOnMouseExited(e -> changePasswordButton.setStyle("-fx-background-color: #2196F3; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;"));
Button logoutButton = new Button("退出登录");
logoutButton.setStyle("-fx-background-color: #f44336; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;");
logoutButton.setPrefSize(220, 55);
logoutButton.setOnMouseEntered(e -> logoutButton.setStyle("-fx-background-color: #d32f2f; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;"));
logoutButton.setOnMouseExited(e -> logoutButton.setStyle("-fx-background-color: #f44336; -fx-text-fill: white; -fx-font-size: 16px; -fx-background-radius: 10;"));
startButton.setOnAction(e -> {
sceneManager.showLevelSelectionView();
});
changePasswordButton.setOnAction(e -> {
showChangePasswordDialog();
});
logoutButton.setOnAction(e -> {
sceneManager.showLoginView();
});
menuContent.getChildren().addAll(
titleLabel, welcomeLabel, startButton,
changePasswordButton, logoutButton
);
return menuContent;
}
private String getFirstLetter() {
return currentUsername != null && !currentUsername.isEmpty() ?
currentUsername.substring(0, 1).toUpperCase() : "U";
}
// 添加更新用户名的方法
public void updateUsername(String username) {
this.currentUsername = username;
if (usernameLabel != null) {
usernameLabel.setText(username != null ? username : "用户");
}
if (avatarText != null) {
avatarText.setText(getFirstLetter());
}
if (welcomeLabel != null) {
welcomeLabel.setText("欢迎," + (username != null ? username : "用户") + "");
}
}
private void showChangePasswordDialog() {
// 创建修改密码对话框
javafx.scene.control.Dialog<Void> dialog = new javafx.scene.control.Dialog<>();
dialog.setTitle("修改密码");
dialog.setHeaderText("请输入密码信息");
dialog.setGraphic(null); // 移除默认图标
// 创建表单
javafx.scene.layout.GridPane grid = new javafx.scene.layout.GridPane();
grid.setHgap(15);
grid.setVgap(15);
grid.setPadding(new Insets(20, 30, 10, 30));
javafx.scene.control.PasswordField oldPassword = new javafx.scene.control.PasswordField();
oldPassword.setPromptText("请输入原密码");
oldPassword.setPrefWidth(200);
javafx.scene.control.PasswordField newPassword = new javafx.scene.control.PasswordField();
newPassword.setPromptText("请输入新密码");
newPassword.setPrefWidth(200);
javafx.scene.control.PasswordField confirmPassword = new javafx.scene.control.PasswordField();
confirmPassword.setPromptText("请确认新密码");
confirmPassword.setPrefWidth(200);
grid.add(new Label("原密码:"), 0, 0);
grid.add(oldPassword, 1, 0);
grid.add(new Label("新密码:"), 0, 1);
grid.add(newPassword, 1, 1);
grid.add(new Label("确认密码:"), 0, 2);
grid.add(confirmPassword, 1, 2);
dialog.getDialogPane().setContent(grid);
// 添加按钮
dialog.getDialogPane().getButtonTypes().addAll(
javafx.scene.control.ButtonType.OK,
javafx.scene.control.ButtonType.CANCEL
);
dialog.setResultConverter(dialogButton -> {
if (dialogButton == javafx.scene.control.ButtonType.OK) {
// 这里可以添加修改密码的逻辑
String oldPwd = oldPassword.getText();
String newPwd = newPassword.getText();
String confirmPwd = confirmPassword.getText();
if (oldPwd.isEmpty() || newPwd.isEmpty() || confirmPwd.isEmpty()) {
showAlert("错误", "请填写所有密码字段!");
return null;
}
if (!Login.login(currentUsername,oldPwd)){
showAlert("错误", "原密码错误!");
return null;
}
if (!newPwd.equals(confirmPwd)) {
showAlert("错误", "两次输入的新密码不一致!");
return null;
}
// 调用修改密码的后端逻辑
if(!ChangePassword.changePassword(currentUsername, confirmPwd)){
showAlert("错误", "密码修改失败");
}
else{
showAlert("成功", "密码修改成功!");
}
}
return null;
});
dialog.showAndWait();
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
}
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,100 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
public class QuestionCountView {
private Scene scene;
private final SceneManager sceneManager;
private String level;
public QuestionCountView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
private void createScene() {
VBox root = new VBox(20);
root.setPadding(new Insets(40));
root.setAlignment(Pos.CENTER);
Label titleLabel = new Label("选择题目数量");
titleLabel.setFont(Font.font(20));
Label levelLabel = new Label();
levelLabel.setFont(Font.font(16));
TextField countField = new TextField();
countField.setPromptText("请输入题目数量 (10-30)");
countField.setMaxWidth(250);
countField.setPrefHeight(40);
Button startButton = new Button("开始答题");
startButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 14px;");
startButton.setPrefSize(250, 45);
Button backButton = new Button("返回");
backButton.setStyle("-fx-background-color: #757575; -fx-text-fill: white;");
backButton.setPrefSize(250, 40);
Label statusLabel = new Label();
startButton.setOnAction(e -> {
try {
String countText = countField.getText().trim();
if (countText.isEmpty()) {
showError(statusLabel, "请输入题目数量!");
return;
}
int count = Integer.parseInt(countText);
if (count < 10 || count > 30) {
showError(statusLabel, "题目数量必须在10-30之间");
return;
}
// 设置题目数量和级别,然后开始答题
sceneManager.getQuizView().setQuizParameters(level, count);
sceneManager.showQuizView();
} catch (NumberFormatException ex) {
showError(statusLabel, "请输入有效的数字!");
}
});
backButton.setOnAction(e -> {
sceneManager.showLevelSelectionView();
});
root.getChildren().addAll(
titleLabel, levelLabel, countField, startButton, backButton, statusLabel
);
scene = new Scene(root, 400, 400);
}
public void setLevel(String level) {
this.level = level;
// 更新界面显示当前选择的级别
if (scene != null) {
VBox root = (VBox) scene.getRoot();
Label levelLabel = (Label) root.getChildren().get(1);
levelLabel.setText("当前级别: " + level);
levelLabel.setStyle("-fx-text-fill: #2196F3;");
}
}
private void showError(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: red;");
}
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,152 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.wsf.mathapp.service.QuestionService;
import com.ybw.mathapp.entity.QuestionWithOptions;
import com.ybw.mathapp.service.MultipleChoiceGenerator;
import com.ybw.mathapp.service.QuestionGenerator;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import java.util.List;
public class QuizView {
private Scene scene;
private final SceneManager sceneManager;
private MultipleChoiceGenerator multipleChoiceGenerator = null;
private String currentLevel;
private int questionCount;
private List<QuestionWithOptions> questions;
private int correctAnswers = 0;
private Label questionLabel;
private ToggleGroup optionsGroup;
private VBox optionsContainer;
private Button nextButton;
private Label progressLabel;
private Label questionNumberLabel;
public QuizView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
private void createScene() {
VBox root = new VBox(20);
root.setPadding(new Insets(30));
root.setAlignment(Pos.TOP_CENTER);
progressLabel = new Label();
progressLabel.setFont(Font.font(14));
questionNumberLabel = new Label();
questionNumberLabel.setFont(Font.font(16));
questionNumberLabel.setStyle("-fx-text-fill: #666;");
questionLabel = new Label();
questionLabel.setFont(Font.font(18));
questionLabel.setWrapText(true);
questionLabel.setStyle("-fx-padding: 10 0 20 0;");
optionsGroup = new ToggleGroup();
optionsContainer = new VBox(15);
optionsContainer.setAlignment(Pos.CENTER_LEFT);
optionsContainer.setPadding(new Insets(10));
nextButton = new Button("下一题");
nextButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 14px;");
nextButton.setPrefSize(150, 40);
nextButton.setDisable(true);
root.getChildren().addAll(progressLabel, questionNumberLabel, questionLabel, optionsContainer, nextButton);
scene = new Scene(root, 600, 500);
}
public void setQuizParameters(String level, int count) {
this.currentLevel = level;
this.questionCount = count;
this.correctAnswers = 0;
// 生成题目
generateQuestions();
showQuestion(0);
}
private void generateQuestions() {
QuestionGenerator questionGenerator = QuestionService.createGenerator(currentLevel);
multipleChoiceGenerator = new MultipleChoiceGenerator(questionGenerator,currentLevel);
questions = multipleChoiceGenerator.generateMultipleChoiceQuestions(questionCount);
}
private void showQuestion(int index) {
if (index >= questions.size()) {
// 所有题目已回答,显示结果
double score = (double) correctAnswers / questions.size();
sceneManager.showResultView(score);
return;
}
QuestionWithOptions question = questions.get(index);
// 更新进度和题号
progressLabel.setText("进度: " + (index + 1) + "/" + questions.size());
questionNumberLabel.setText("第 " + (index + 1) + " 题");
// 设置题目内容
String questionText = question.getQuestionText();
// 移除末尾的 "="
if (questionText.endsWith(" =")) {
questionText = questionText.substring(0, questionText.length() - 2);
}
questionLabel.setText(questionText + " = ?");
// 清空选项容器
optionsContainer.getChildren().clear();
optionsGroup = new ToggleGroup();
// 添加选项
List<String> options = question.getOptions();
for (int i = 0; i < options.size(); i++) {
RadioButton radioButton = new RadioButton((char)('A' + i) + ". " + options.get(i));
radioButton.setToggleGroup(optionsGroup);
radioButton.setFont(Font.font(14));
radioButton.setWrapText(true);
radioButton.setPrefWidth(500);
optionsContainer.getChildren().add(radioButton);
}
// 设置下一题按钮
nextButton.setDisable(true);
nextButton.setText(index == questions.size() - 1 ? "提交答案" : "下一题");
nextButton.setOnAction(e -> {
checkAnswer(question);
showQuestion(index + 1);
});
// 启用下一题按钮当选择了一个选项时
optionsGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) -> {
nextButton.setDisable(newVal == null);
});
}
private void checkAnswer(QuestionWithOptions question) {
RadioButton selectedRadioButton = (RadioButton) optionsGroup.getSelectedToggle();
if (selectedRadioButton != null) {
int selectedIndex = optionsContainer.getChildren().indexOf(selectedRadioButton);
if (selectedIndex == question.getCorrectAnswerIndex()) {
correctAnswers++;
}
}
}
public Scene getScene() {
return scene;
}
public MultipleChoiceGenerator getMultipleChoiceGenerator() {
return multipleChoiceGenerator;
}
}

@ -0,0 +1,229 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.ybw.mathapp.util.EmailService;
import com.ybw.mathapp.util.Register;
import java.util.regex.Pattern;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import com.ybw.mathapp.util.LoginFileUtils;
public class RegisterView {
private Scene scene;
private final SceneManager sceneManager;
public RegisterView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
private void createScene() {
VBox root = new VBox(15);
root.setPadding(new Insets(30));
root.setAlignment(Pos.CENTER);
Label titleLabel = new Label("用户注册");
titleLabel.setFont(Font.font(20));
// 添加用户名输入框
TextField usernameField = new TextField();
usernameField.setPromptText("请输入用户名(3-20位字母、数字、下划线)");
usernameField.setMaxWidth(300);
usernameField.setPrefHeight(35);
TextField emailField = new TextField();
emailField.setPromptText("请输入邮箱");
emailField.setMaxWidth(300);
emailField.setPrefHeight(35);
Button sendCodeButton = new Button("发送验证码");
sendCodeButton.setStyle("-fx-background-color: #FF9800; -fx-text-fill: white;");
sendCodeButton.setPrefSize(120, 35);
TextField codeField = new TextField();
codeField.setPromptText("请输入验证码");
codeField.setMaxWidth(300);
codeField.setPrefHeight(35);
PasswordField passwordField = new PasswordField();
passwordField.setPromptText("请输入密码(6-10位含大小写字母和数字)");
passwordField.setMaxWidth(300);
passwordField.setPrefHeight(35);
PasswordField confirmPasswordField = new PasswordField();
confirmPasswordField.setPromptText("请再次输入密码");
confirmPasswordField.setMaxWidth(300);
confirmPasswordField.setPrefHeight(35);
Button registerButton = new Button("注册");
registerButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 14px;");
registerButton.setPrefSize(300, 40);
Button backButton = new Button("返回登录");
backButton.setStyle("-fx-background-color: #757575; -fx-text-fill: white;");
backButton.setPrefSize(300, 35);
Label statusLabel = new Label();
sendCodeButton.setOnAction(e -> {
String username = usernameField.getText().trim();
String email = emailField.getText().trim();
// 验证用户名
if (!isValidUsername(username)) {
showError(statusLabel, "用户名只包含英文字母和数字)");
return;
}
// 检查用户名是否已存在
if (LoginFileUtils.isNameRegistered(username)) {
showError(statusLabel, "用户名已存在,请选择其他用户名!");
return;
}
if (!EmailService.isValidEmail(email)) {
showError(statusLabel, "请输入有效的邮箱地址!");
return;
}
if (LoginFileUtils.isEmailRegistered(email)){
showError(statusLabel, "该邮箱已注册,请直接登录!");
return;
}
// 生成并发送验证码
boolean sent = EmailService.sendCode(email);
if (sent) {
showSuccess(statusLabel, "验证码已发送到您的邮箱!");
// 禁用发送按钮60秒
sendCodeButton.setDisable(true);
startCountdown(sendCodeButton);
} else {
showError(statusLabel, "发送验证码失败,请稍后重试!");
}
});
registerButton.setOnAction(e -> {
String username = usernameField.getText().trim();
String email = emailField.getText().trim();
String code = codeField.getText().trim();
String password = passwordField.getText();
String confirmPassword = confirmPasswordField.getText();
// 验证用户名
if (!isValidUsername(username)) {
showError(statusLabel, "用户名只包含英文字母和数字");
return;
}
// 检查用户名是否已存在
if (LoginFileUtils.isNameRegistered((username))){
showError(statusLabel, "用户名已存在,请选择其他用户名!");
return;
}
if (!EmailService.isValidEmail(email)) {
showError(statusLabel, "请输入有效的邮箱地址!");
return;
}
if (LoginFileUtils.isEmailRegistered(email)){
showError(statusLabel, "该邮箱已注册,请直接登录!");
return;
}
if (username.isEmpty() || email.isEmpty() || code.isEmpty() || password.isEmpty()) {
showError(statusLabel, "请填写所有字段!");
return;
}
// 验证验证码
if (!EmailService.verifyCode(email, code)) {
showError(statusLabel, "验证码错误或已过期!");
return;
}
if (!Register.isVaildPassword(password)) {
showError(statusLabel, "密码要6-10位包含大小写字母和数字");
return;
}
if (!Register.isEqualPassword(password,confirmPassword)){
showError(statusLabel, "两次密码不一致");
return;
}
if (Register.register(username, email, password)) {
showSuccess(statusLabel, "注册成功!用户名: " + username);
sceneManager.showLoginView();
} else {
showError(statusLabel, "注册失败,请检查信息!");
}
});
backButton.setOnAction(e -> {
sceneManager.showLoginView();
});
root.getChildren().addAll(
titleLabel,
usernameField, // 添加用户名输入框
emailField,
sendCodeButton,
codeField,
passwordField,
confirmPasswordField,
registerButton,
backButton,
statusLabel
);
scene = new Scene(root, 400, 550); // 增加高度以适应新字段
}
private void startCountdown(Button button) {
new Thread(() -> {
try {
for (int i = 60; i > 0; i--) {
int finalI = i;
javafx.application.Platform.runLater(() -> {
button.setText(finalI + "秒后重发");
});
Thread.sleep(1000);
}
javafx.application.Platform.runLater(() -> {
button.setText("发送验证码");
button.setDisable(false);
});
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}).start();
}
private void showError(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: red;");
}
private void showSuccess(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: green;");
}
public Scene getScene() {
return scene;
}
public static boolean isValidUsername(String username) {
if (username == null || username.trim().isEmpty()) {
return false;
}
// 用户名规则:只能包含英文字母和数字
String usernameRegex = "^[a-zA-Z0-9]{3,100}$";
return Pattern.matches(usernameRegex, username.trim());
}
}

@ -0,0 +1,211 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import java.util.Objects;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
public class ResultView {
private Scene scene;
private final SceneManager sceneManager;
private double score;
private String currentUsername;
public ResultView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
this.currentUsername = "用户"; // 默认用户名
createScene();
}
private void createScene() {
// 创建主容器
VBox mainContainer = new VBox();
mainContainer.setPadding(new Insets(20));
// 创建顶部用户信息栏
HBox userInfoBar = createUserInfoBar();
// 创建结果内容区域
VBox resultContent = createResultContent();
mainContainer.getChildren().addAll(userInfoBar, resultContent);
scene = new Scene(mainContainer, 500, 550); // 增加宽度和高度以适应新布局
}
private HBox createUserInfoBar() {
HBox userInfoBar = new HBox(10);
userInfoBar.setAlignment(Pos.CENTER_LEFT);
userInfoBar.setPadding(new Insets(0, 0, 20, 0));
// 创建默认头像(使用系统图标或文本)
ImageView avatar = createDefaultAvatar();
// 用户名标签
Label usernameLabel = new Label(currentUsername);
usernameLabel.setFont(Font.font(16));
usernameLabel.setStyle("-fx-text-fill: #333;");
// 添加间隔,让用户信息靠左,其他内容居中
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
userInfoBar.getChildren().addAll(avatar, usernameLabel, spacer);
return userInfoBar;
}
private ImageView createDefaultAvatar() {
// 尝试加载默认头像图片,如果失败则使用文本头像
ImageView avatar = new ImageView();
avatar.setFitWidth(40);
avatar.setFitHeight(40);
try {
// 尝试加载默认头像图片
Image defaultAvatar = new Image(
Objects.requireNonNull(getClass().getResourceAsStream("/images/default-avatar.png")));
avatar.setImage(defaultAvatar);
} catch (Exception e) {
// 如果图片加载失败,创建一个圆形文本头像
avatar.setImage(createTextAvatar());
}
// 设置头像样式
avatar.setStyle(
"-fx-background-color: #4CAF50; " +
"-fx-background-radius: 20; " +
"-fx-border-radius: 20; " +
"-fx-border-color: #ddd; " +
"-fx-border-width: 2;"
);
return avatar;
}
private Image createTextAvatar() {
// 创建一个简单的文本头像(使用首字母)
String firstLetter = !currentUsername.isEmpty() ?
currentUsername.substring(0, 1).toUpperCase() : "U";
// 这里可以创建一个包含文本的图像但为了简单起见我们使用CSS样式
// 在实际项目中你可以使用Canvas绘制或预加载图片
return null; // 返回null我们将使用CSS样式
}
private VBox createResultContent() {
VBox resultContent = new VBox(30);
resultContent.setPadding(new Insets(20));
resultContent.setAlignment(Pos.CENTER);
Label titleLabel = new Label("答题完成");
titleLabel.setFont(Font.font(24));
Label scoreLabel = new Label();
scoreLabel.setFont(Font.font(48));
Label messageLabel = new Label();
messageLabel.setFont(Font.font(18));
Button restartButton = new Button("再次练习");
restartButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 16px;");
restartButton.setPrefSize(200, 50);
Button mainMenuButton = new Button("返回主菜单");
mainMenuButton.setStyle("-fx-background-color: #2196F3; -fx-text-fill: white; -fx-font-size: 16px;");
mainMenuButton.setPrefSize(200, 50);
Button exitButton = new Button("退出系统");
exitButton.setStyle("-fx-background-color: #757575; -fx-text-fill: white; -fx-font-size: 16px;");
exitButton.setPrefSize(200, 50);
restartButton.setOnAction(e -> {
sceneManager.showLevelSelectionView();
});
mainMenuButton.setOnAction(e -> {
sceneManager.showMainMenuView();
});
exitButton.setOnAction(e -> {
System.exit(0);
});
resultContent.getChildren().addAll(
titleLabel, scoreLabel, messageLabel,
restartButton, mainMenuButton, exitButton
);
return resultContent;
}
public void setScore(double score) {
this.score = score;
updateScoreDisplay();
}
public void setCurrentUsername(String username) {
this.currentUsername = username != null ? username : "用户";
updateUserInfo();
}
private void updateUserInfo() {
if (scene != null) {
VBox mainContainer = (VBox) scene.getRoot();
HBox userInfoBar = (HBox) mainContainer.getChildren().get(0);
// 更新用户名标签
Label usernameLabel = (Label) userInfoBar.getChildren().get(1);
usernameLabel.setText(currentUsername);
// 更新头像(如果需要显示首字母)
updateAvatar();
}
}
private void updateAvatar() {
// 如果需要根据用户名更新头像样式,可以在这里实现
// 例如根据用户名首字母设置不同的背景颜色
}
private void updateScoreDisplay() {
if (scene != null) {
VBox mainContainer = (VBox) scene.getRoot();
VBox resultContent = (VBox) mainContainer.getChildren().get(1);
Label scoreLabel = (Label) resultContent.getChildren().get(1);
Label messageLabel = (Label) resultContent.getChildren().get(2);
int percentage = (int) (score * 100);
scoreLabel.setText(percentage + "分");
// 根据分数设置不同的颜色和评语
if (score >= 0.9) {
scoreLabel.setStyle("-fx-text-fill: #4CAF50;");
messageLabel.setText("优秀!表现非常出色!");
} else if (score >= 0.7) {
scoreLabel.setStyle("-fx-text-fill: #FF9800;");
messageLabel.setText("良好!继续加油!");
} else if (score >= 0.6) {
scoreLabel.setStyle("-fx-text-fill: #FFC107;");
messageLabel.setText("及格!还有进步空间!");
} else {
scoreLabel.setStyle("-fx-text-fill: #f44336;");
messageLabel.setText("需要多加练习!");
}
}
}
public Scene getScene() {
return scene;
}
}

@ -1,24 +0,0 @@
package com.ybw.mathapp;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

@ -1,15 +0,0 @@
package com.ybw.mathapp;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class HelloController {
@FXML
private Label welcomeText;
@FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
}
}

@ -0,0 +1,132 @@
package com.ybw.mathapp;
// UserService.java
import com.ybw.mathapp.entity.User;
import com.ybw.mathapp.service.EmailService;
import com.ybw.mathapp.util.LoginFileUtils;
import java.util.Scanner;
import java.util.regex.Pattern;
public class LoginAndRegister {
private static Scanner scanner = new Scanner(System.in);
// UserService.java 中的注册方法更新
public static boolean register() {
System.out.println("\n=== 用户注册 ===");
// 输入邮箱
System.out.print("请输入邮箱地址: ");
String email = scanner.nextLine().trim();
if (!isValidEmail(email)) {
return false;
}
if (LoginFileUtils.isEmailRegistered(email)) {
System.out.println("该邮箱已注册,请直接登录!");
return false;
}
// 发送、验证验证码
if (!sendAndVerifyCode(email)) {
return false;
}
// 设置密码(其余代码保持不变)
System.out.print("请输入密码: ");
String password1 = scanner.nextLine();
System.out.print("请再次输入密码: ");
String password2 = scanner.nextLine();
if(!isVaildPassword(password1, password2)) {
return false;
}
User user = new User(email, password1);
LoginFileUtils.saveUser(user);
System.out.println("注册成功!您可以使用邮箱和密码登录了。");
return true;
}
// 登录流程
public static boolean login() {
System.out.println("\n=== 用户登录 ===");
System.out.print("请输入邮箱: ");
String email = scanner.nextLine().trim();
System.out.print("请输入密码: ");
String password = scanner.nextLine();
if (LoginFileUtils.validateUser(email, password)) {
System.out.println("登录成功!欢迎回来," + email);
return true;
} else {
System.out.println("邮箱或密码错误!");
return false;
}
}
//
/**
*
* @param email
* @return truefalse
*/
private static boolean isValidEmail(String email) {
if (email.isEmpty()) {
System.out.println("邮箱地址不能为空!");
return false;
}
if (!(email.contains("@") && email.contains("."))) {
System.out.println("邮箱格式不正确!");
return false;
}
return true;
}
/**
*
* @param password1
* @param password2
* @return truefalse
*/
public static boolean isVaildPassword(String password1, String password2) {
if (password1 == null || password1.length() < 6 || password1.length() > 10) {
return false;
}
// 使用正则表达式验证长度6-10只包含字母数字且包含大小写字母和数字
String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$";
if (!Pattern.matches(regex, password1)) {
return false;
}
System.out.print("请再次输入密码: ");
if (!password1.equals(password2)) {
System.out.println("两次输入的密码不一致!");
return false;
}
return true;
}
public static boolean sendAndVerifyCode(String email) {
// 发送真实邮件验证码
String verificationCode = EmailService.generateVerificationCode();
System.out.println("正在发送验证码邮件,请稍候...");
if (!EmailService.sendVerificationCode(email, verificationCode)) {
System.out.println("发送验证码失败,请检查邮箱配置或稍后重试!");
return false;
}
// 验证验证码
System.out.print("请输入收到的验证码: ");
String inputCode = scanner.nextLine().trim();
if (!EmailService.verifyCode(email, inputCode)) {
System.out.println("验证码错误或已过期!");
return false;
}
return true;
}
}

@ -0,0 +1,36 @@
package com.ybw.mathapp;
import com.ybw.mathapp.entity.QuestionWithOptions;
import com.ybw.mathapp.service.JuniorHighGenerator;
import com.ybw.mathapp.service.MultipleChoiceGenerator;
import com.ybw.mathapp.service.PrimarySchoolGenerator;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 生成小学选择题
System.out.println("--- Primary School MCQs ---");
PrimarySchoolGenerator primaryGen = new PrimarySchoolGenerator();
MultipleChoiceGenerator primaryMC = new MultipleChoiceGenerator(primaryGen, "小学"); // 传入级别
List<QuestionWithOptions> primaryMCQs = primaryMC.generateMultipleChoiceQuestions(10);
primaryMCQs.forEach(q -> System.out.println(q + "\n"));
// 生成初中选择题
System.out.println("--- Junior High MCQs ---");
JuniorHighGenerator juniorGen = new JuniorHighGenerator();
MultipleChoiceGenerator juniorMC = new MultipleChoiceGenerator(juniorGen, "初中"); // 传入级别
List<QuestionWithOptions> juniorMCQs = juniorMC.generateMultipleChoiceQuestions(10);
juniorMCQs.forEach(q -> System.out.println(q + "\n"));
System.out.println();
/*
// 生成高中选择题
System.out.println("--- Senior High MCQs ---");
SeniorHighGenerator seniorGen = new SeniorHighGenerator();
MultipleChoiceGenerator seniorMC = new MultipleChoiceGenerator(seniorGen, "高中"); // 传入级别
List<QuestionWithOptions> seniorMCQs = seniorMC.generateMultipleChoiceQuestions(2, 4);
seniorMCQs.forEach(q -> System.out.println(q + "\n"));
*/
}
}

@ -0,0 +1,18 @@
package com.ybw.mathapp.config;
public class EmailConfig {
// 发件人邮箱配置以QQ邮箱为例
public static final String SMTP_HOST = "smtp.qq.com";
public static final String SMTP_PORT = "587";
public static final String SENDER_EMAIL = "1798231811@qq.com"; // 替换为你的邮箱
public static final String SENDER_PASSWORD = "dzmfirotgnlceeae"; // 替换为你的授权码
// 如果使用Gmail
// public static final String SMTP_HOST = "smtp.gmail.com";
// public static final String SMTP_PORT = "587";
// public static final String SENDER_EMAIL = "your_email@gmail.com";
// public static final String SENDER_PASSWORD = "your_app_password";
public static final String EMAIL_SUBJECT = "【用户注册】验证码";
public static final int CODE_EXPIRY_MINUTES = 5;
}

@ -0,0 +1,56 @@
package com.ybw.mathapp.entity;
import java.util.List;
/**
*
*
* @author
* @since 2025
*/
public class QuestionWithOptions {
private String questionText; // 题干
private List<String> options; // 选项列表
private int correctAnswerIndex; // 正确答案的索引 (0-based)
public QuestionWithOptions(String questionText, List<String> options, int correctAnswerIndex) {
this.questionText = questionText;
this.options = options;
this.correctAnswerIndex = correctAnswerIndex;
}
public String getQuestionText() {
return questionText;
}
public void setQuestionText(String questionText) {
this.questionText = questionText;
}
public List<String> getOptions() {
return options;
}
public void setOptions(List<String> options) {
this.options = options;
}
public int getCorrectAnswerIndex() {
return correctAnswerIndex;
}
public void setCorrectAnswerIndex(int correctAnswerIndex) {
this.correctAnswerIndex = correctAnswerIndex;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Question: ").append(questionText).append("\n");
for (int i = 0; i < options.size(); i++) {
sb.append("Option ").append((char)('A' + i)).append(": ").append(options.get(i)).append("\n");
}
sb.append("Correct Answer: ").append((char)('A' + correctAnswerIndex));
return sb.toString();
}
}

@ -0,0 +1,100 @@
package com.ybw.mathapp.entity;
/**
*
*
* <p>
*
*
* @author
* @version 1.0
* @since 2025
*/
public class User {
public String getName() {
return name;
}
/** 用户名,不可修改。 */
private final String name;
/** 邮箱,不可修改。 */
private final String email;
/** 用户密码,不可修改。 */
private final String password;
/** 用户当前的学习级别,可以修改。 */
private String level;
/**
*
*
* @param email
* @param password
*/
public User(String name, String email, String password) {
this.name = name;
this.password = password;
this.email = email;
}
/**
*
*
* @return
*/
public String getPassword() {
return password;
}
/**
*
*
* @return
*/
public String getLevel() {
return level;
}
/**
*
*
* @return
*/
public String getEmail() {
return email;
}
/**
*
*
* @param newLevel "小学""初中""高中"
*/
public void setLevel(String newLevel) {
level = newLevel;
}
/**
* +
*
* @return +
*/
@Override
public String toString() {
return name + "," + email + "," + password;
}
public static User fromString(String line) {
if (line == null || line.trim().isEmpty()) {
return null;
}
String[] parts = line.split(",", 3); // 最多分割成3部分
if (parts.length == 3) {
return new User(parts[0].trim(), parts[1].trim(), parts[2].trim());
}
return null;
}
}

@ -0,0 +1,180 @@
package com.ybw.mathapp.service;
import java.util.*;
/**
* (sin, cos, tan)
*
* (, , sin, cos, tan)
*
* : "3 + 开根号 16 平方" "3 + (sqrt(16))^2" = "3 + 4^2" = "3 + 16" = 19
* "开根号 ( 4 + 5 ) 平方" "(sqrt(4+5))^2" = "sqrt(9)^2" = "3^2" = 9
* "sin 30 + 5" "sin(30) + 5" (30)
* ArithmeticException
*/
public class AdvancedCaculate {
private static final Set<String> OPERATORS = new HashSet<>(Arrays.asList("+", "-", "*", "/"));
private static final Set<String> ADVANCED_OPERATORS = new HashSet<>(Arrays.asList("平方", "开根号", "sin", "cos", "tan"));
private static final Map<String, Integer> PRECEDENCE = new HashMap<>();
static {
PRECEDENCE.put("+", 1);
PRECEDENCE.put("-", 1);
PRECEDENCE.put("*", 2);
PRECEDENCE.put("/", 2);
// 高级运算符优先级更高
PRECEDENCE.put("平方", 3); // 后置
PRECEDENCE.put("开根号", 3); // 前置
PRECEDENCE.put("sin", 3); // 前置
PRECEDENCE.put("cos", 3); // 前置
PRECEDENCE.put("tan", 3); // 前置
}
/**
*
*
* @param expressionTokens ["3", "+", "开根号", "16", "平方"]
* @return
* @throws IllegalArgumentException
* @throws ArithmeticException
*/
public static double calculate(List<String> expressionTokens) {
Stack<Double> numberStack = new Stack<>();
Stack<String> operatorStack = new Stack<>();
for (int i = 0; i < expressionTokens.size(); i++) {
String token = expressionTokens.get(i);
if (isNumeric(token)) {
numberStack.push(Double.parseDouble(token));
} else if (token.equals("(")) {
operatorStack.push(token);
} else if (token.equals(")")) {
while (!operatorStack.isEmpty() && !operatorStack.peek().equals("(")) {
processOperator(numberStack, operatorStack.pop());
}
if (!operatorStack.isEmpty()) { // Pop the "("
operatorStack.pop();
}
} else if (ADVANCED_OPERATORS.contains(token)) {
// 前置运算符 (开根号, sin, cos, tan) 直接入栈
// 后置运算符 (平方) 需要等待其操作数先计算出来
// 在标准调度场算法中,后置运算符通常在遇到时立即处理其栈顶的操作数
// 这里我们可以在遇到 "平方" 时,立即对 numberStack 的顶部元素进行平方操作
if ("平方".equals(token)) {
if (numberStack.isEmpty()) {
throw new IllegalArgumentException("Invalid expression: '平方' lacks an operand.");
}
double operand = numberStack.pop();
numberStack.push(Math.pow(operand, 2));
} else { // "开根号", "sin", "cos", "tan" 是前置运算符
operatorStack.push(token);
}
} else if (OPERATORS.contains(token)) {
// 处理四则运算符,遵循优先级
while (!operatorStack.isEmpty() &&
!operatorStack.peek().equals("(") &&
PRECEDENCE.get(token) <= PRECEDENCE.getOrDefault(operatorStack.peek(), 0)) {
processOperator(numberStack, operatorStack.pop());
}
operatorStack.push(token);
} else {
throw new IllegalArgumentException("Unknown token: " + token);
}
}
// 处理栈中剩余的所有运算符
while (!operatorStack.isEmpty()) {
String op = operatorStack.pop();
if (op.equals("(") || op.equals(")")) {
throw new IllegalArgumentException("Mismatched parentheses in expression.");
}
processOperator(numberStack, op);
}
if (numberStack.size() != 1 || !operatorStack.isEmpty()) {
throw new IllegalArgumentException("Invalid expression: " + String.join(" ", expressionTokens));
}
return numberStack.pop();
}
/**
*
*
* @param numberStack
* @param operator
*/
private static void processOperator(Stack<Double> numberStack, String operator) {
if (numberStack.size() < 1) {
throw new IllegalArgumentException("Invalid expression: operator '" + operator + "' lacks operand(s).");
}
if (ADVANCED_OPERATORS.contains(operator)) {
double operand = numberStack.pop();
switch (operator) {
case "开根号":
if (operand < 0) {
// 抛出异常让调用者MultipleChoiceGenerator处理
throw new ArithmeticException("Cannot take square root of negative number: " + operand);
}
numberStack.push(Math.sqrt(operand));
break;
case "sin":
numberStack.push(Math.sin(Math.toRadians(operand))); // 假设输入是度数
break;
case "cos":
numberStack.push(Math.cos(Math.toRadians(operand)));
break;
case "tan":
// tan(90 + n*180) 会趋向无穷,这里不特别处理,让其返回 Infinity 或 -Infinity
numberStack.push(Math.tan(Math.toRadians(operand)));
break;
default:
throw new IllegalArgumentException("Unknown advanced operator: " + operator);
}
} else if (OPERATORS.contains(operator)) {
if (numberStack.size() < 2) {
throw new IllegalArgumentException("Invalid expression: operator '" + operator + "' lacks operand(s).");
}
double b = numberStack.pop();
double a = numberStack.pop();
double result;
switch (operator) {
case "+":
result = a + b;
break;
case "-":
result = a - b;
break;
case "*":
result = a * b;
break;
case "/":
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
result = a / b;
break;
default:
throw new IllegalArgumentException("Unknown operator: " + operator);
}
numberStack.push(result);
} else {
throw new IllegalArgumentException("Unexpected operator in process: " + operator);
}
}
public static boolean isNumeric(String str) {
if (str == null || str.isEmpty()) {
return false;
}
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

@ -0,0 +1,149 @@
package com.ybw.mathapp.service;
import com.ybw.mathapp.config.EmailConfig;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
//import javax.mail.*;
//import javax.mail.internet.*;
public class EmailService {
private static Map<String, VerificationCodeInfo> verificationCodes = new HashMap<>();
private static class VerificationCodeInfo {
String code;
long timestamp;
VerificationCodeInfo(String code, long timestamp) {
this.code = code;
this.timestamp = timestamp;
}
}
public static String generateVerificationCode() {
Random random = new Random();
int code = 100000 + random.nextInt(900000);
return String.valueOf(code);
}
public static boolean sendVerificationCode(String recipientEmail, String code) {
/*
try {
// 创建邮件会话
Properties props = new Properties();
props.put("mail.smtp.host", EmailConfig.SMTP_HOST);
props.put("mail.smtp.port", EmailConfig.SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
// 创建认证器
Authenticator auth = new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
EmailConfig.SENDER_EMAIL,
EmailConfig.SENDER_PASSWORD
);
}
};
Session session = Session.getInstance(props, auth);
// 创建邮件消息
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(EmailConfig.SENDER_EMAIL));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(recipientEmail));
message.setSubject(EmailConfig.EMAIL_SUBJECT);
// 创建邮件内容
String emailContent = createEmailContent(code);
message.setContent(emailContent, "text/html; charset=utf-8");
// 发送邮件
Transport.send(message);
// 存储验证码信息
verificationCodes.put(recipientEmail,
new VerificationCodeInfo(code, System.currentTimeMillis()));
System.out.println("验证码已发送到邮箱: " + recipientEmail);
return true;
} catch (Exception e) {
System.err.println("发送邮件失败: " + e.getMessage());
// 在开发环境中直接返回true用于测试
verificationCodes.put(recipientEmail,
new VerificationCodeInfo(code, System.currentTimeMillis()));
return true;
}
*/
return true;
}
private static String createEmailContent(String code) {
return "<!DOCTYPE html>" +
"<html>" +
"<head>" +
"<meta charset='UTF-8'>" +
"<style>" +
"body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }" +
".container { max-width: 600px; margin: 0 auto; padding: 20px; }" +
".header { background-color: #4CAF50; color: white; padding: 20px; text-align: center; }" +
".content { padding: 20px; background-color: #f9f9f9; margin: 20px 0; }" +
".code { font-size: 24px; font-weight: bold; color: #4CAF50; text-align: center; padding: 15px; background-color: white; border: 2px dashed #4CAF50; margin: 20px 0; }" +
".footer { text-align: center; color: #666; font-size: 12px; }" +
"</style>" +
"</head>" +
"<body>" +
"<div class='container'>" +
"<div class='header'>" +
"<h2>数学学习软件 - 注册验证码</h2>" +
"</div>" +
"<div class='content'>" +
"<p>您好!</p>" +
"<p>您正在注册数学学习软件账户,验证码如下:</p>" +
"<div class='code'>" + code + "</div>" +
"<p>验证码有效期为 " + EmailConfig.CODE_EXPIRY_MINUTES + " 分钟,请勿泄露给他人。</p>" +
"<p>如果这不是您本人的操作,请忽略此邮件。</p>" +
"</div>" +
"<div class='footer'>" +
"<p>此邮件为系统自动发送,请勿回复。</p>" +
"</div>" +
"</div>" +
"</body>" +
"</html>";
}
public static boolean verifyCode(String email, String inputCode) {
VerificationCodeInfo codeInfo = verificationCodes.get(email);
if (codeInfo == null) {
return false;
}
// 检查验证码是否过期
long currentTime = System.currentTimeMillis();
if (currentTime - codeInfo.timestamp > EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
verificationCodes.remove(email);
return false;
}
return codeInfo.code.equals(inputCode);
}
public static void cleanupExpiredCodes() {
long currentTime = System.currentTimeMillis();
Iterator<Entry<String, VerificationCodeInfo>> iterator =
verificationCodes.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, VerificationCodeInfo> entry = iterator.next();
if (currentTime - entry.getValue().timestamp >
EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
iterator.remove();
}
}
}
}

@ -0,0 +1,186 @@
package com.ybw.mathapp.service;
import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
*
* <p>
*
*
* @author
* @version 1.0
* @since 2025
*/
public class JuniorHighGenerator implements QuestionGenerator {
/**
* "平方""开根号"
*/
private static final String[] ADVANCED_OPS = {"平方", "开根号"};
/**
*
*/
private static final String[] OPERATORS = {"+", "-", "*", "/"};
/**
*
*/
private final Random random = new Random();
@Override
public List<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
*
*
* <p>
*
*
* @return
*/
private String generateSingleQuestion() {
List<String> parts = new ArrayList<>();
int operandCount = random.nextInt(5) + 1;
parts = generateBase(operandCount, parts);
// hasAdvancedOp用以检测下面的循环是否加入了高级运算符如果没有就启动保底
boolean hasAdvancedOp = false;
if (operandCount == 1) {
if ("平方".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) {
parts.add("平方");
} else {
parts.add(0, "开根号");
}
hasAdvancedOp = true;
} else {
// 遍历查找左括号的合理位置
for (int i = 0; i < parts.size() - 2; i++) {
// 该位置要为操作数且随机添加括号
if (isNumeric(parts.get(i)) && random.nextBoolean()) {
// 随机数看取出来的是不是开根号运算符
if ("开根号".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) {
parts = generateRoot(parts, i);
} else { // 如果不是开根号就是平方运算
parts = generateSquare(parts, i);
}
hasAdvancedOp = true;
break;
}
}
}
// 启动保底强制加入一个高级运算符
if (!hasAdvancedOp) {
parts = forceAddAdvancedOp(parts);
}
return String.join(" ", parts) + " =";
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
public List<String> generateBase(int operandCount, List<String> parts) {
for (int i = 0; i < operandCount; i++) {
int num = random.nextInt(100) + 1;
parts.add(String.valueOf(num));
if (i < operandCount - 1) {
parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
}
}
return parts;
}
/**
*
*
* <p>使
*
*
* @param parts
* @return
*/
public List<String> forceAddAdvancedOp(List<String> parts) {
String advancedOp = ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)];
if ("平方".equals(advancedOp)) {
parts.add("平方");
} else { // 开根号
parts.set(0, "开根号 ( " + parts.get(0));
parts.set(parts.size() - 1, parts.get(parts.size() - 1) + " )");
}
return parts;
}
/**
*
*
* <p>
*
*
* @param parts
* @param i
* @return
*/
public List<String> generateRoot(List<String> parts, int i) {
if (random.nextBoolean()) {
parts.set(i, "开根号 ( " + parts.get(i) + " )");
} else {
parts.set(i, "开根号 ( " + parts.get(i));
// 为避免随机数上限出现0此处要单独判断一下左括号正好括住倒数第二个操作数的情况
if (i == parts.size() - 3) {
parts.set(parts.size() - 1, parts.get(parts.size() - 1) + " )");
} else {
while (true) {
int i2 = random.nextInt(parts.size() - 3 - i) + 2;
if (isNumeric(parts.get(i + i2))) {
parts.set(i + i2, parts.get(i + i2) + " )");
break;
}
}
}
}
return parts;
}
/**
*
*
* <p>
*
* @param parts
* @param i
* @return
*/
public List<String> generateSquare(List<String> parts, int i) {
parts.set(i, "(" + parts.get(i));
// 为避免随机数上限出现0此处要单独判断一下左括号正好括住倒数第二个操作数的情况
if (i == parts.size() - 3) {
parts.set(parts.size() - 1, parts.get(parts.size() - 1) + " )");
} else {
while (true) {
int i2 = random.nextInt(parts.size() - 3 - i) + 2;
if (isNumeric(parts.get(i + i2))) {
parts.set(i + i2, parts.get(i + i2) + " ) 平方");
break;
}
}
}
return parts;
}
}

@ -0,0 +1,199 @@
package com.ybw.mathapp.service;
import com.ybw.mathapp.entity.QuestionWithOptions;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
*
*
* 1. 2.
*
* @author
* @since 2025
*/
public class MultipleChoiceGenerator {
private final QuestionGenerator baseGenerator;
private final Random random = new Random();
private static final DecimalFormat df = new DecimalFormat("#0.00");
private final String level; // 记录当前题目类型,用于特殊处理
public MultipleChoiceGenerator(QuestionGenerator baseGenerator, String level) {
this.baseGenerator = baseGenerator;
this.level = level;
}
/**
*
*
* @param count
* @return ()
*/
public List<QuestionWithOptions> generateMultipleChoiceQuestions(int count) {
List<QuestionWithOptions> mcQuestions = new ArrayList<>();
Set<String> seenQuestionTexts = new HashSet<>(); // 用于存储已生成的题干,保证唯一性
while (mcQuestions.size() < count) {
String baseQuestion = generateUniqueBaseQuestion(seenQuestionTexts);
if (baseQuestion == null) {
// 如果无法生成不重复的基础题目,可能需要退出或处理
// 例如,如果基础生成器的可能组合用尽了
break; // 或者抛出异常
}
QuestionWithOptions mcq = generateSingleMCQ(baseQuestion);
if (mcq != null) {
mcQuestions.add(mcq);
seenQuestionTexts.add(baseQuestion); // 添加成功生成的题干
}
// 如果 generateSingleMCQ 返回 null说明计算或生成选项失败循环会继续尝试下一个基础题目
}
return mcQuestions;
}
/**
*
*/
private String generateUniqueBaseQuestion(Set<String> seenQuestionTexts) {
int attempts = 0;
int maxAttempts = 1000; // 防止无限循环,如果生成太多重复题则退出
while (attempts < maxAttempts) {
List<String> baseQuestionList = baseGenerator.generateQuestions(1);
String baseQuestion = baseQuestionList.get(0);
if (!seenQuestionTexts.contains(baseQuestion)) {
return baseQuestion;
}
attempts++;
}
return null; // 达到最大尝试次数仍未找到唯一题目
}
/**
*
*/
private QuestionWithOptions generateSingleMCQ(String baseQuestion) {
try {
String expression = baseQuestion.substring(0, baseQuestion.lastIndexOf(" =")).trim();
List<String> tokens = tokenizeExpression(expression);
double correctAnswer = AdvancedCaculate.calculate(tokens);
List<String> options = generateOptions(correctAnswer);
if (options == null) {
// 无法生成足够的选项
return null;
}
Collections.shuffle(options);
int correctIndex = options.indexOf(df.format(correctAnswer));
return new QuestionWithOptions(baseQuestion, options, correctIndex);
} catch (ArithmeticException | IllegalArgumentException e) {
// System.out.println("计算或表达式错误,跳过题目: " + baseQuestion + ", Error: " + e.getMessage());
return null; // 返回 null 表示生成失败
}
}
/**
* ( + )
*/
private List<String> generateOptions(double correctAnswer) {
Set<Double> wrongAnswers = new HashSet<>();
int attempts = 0;
int maxAttempts = 100; // 防止无限循环
int numWrongOptions = 3; // 假设总共4个选项需要3个错误答案
while (wrongAnswers.size() < numWrongOptions && attempts < maxAttempts) {
int offset = random.nextInt(20) + 1;
if (random.nextBoolean()) {
offset = -offset;
}
double wrongAnswer = correctAnswer + offset;
if (Math.abs(df.format(wrongAnswer).compareTo(df.format(correctAnswer))) != 0) {
if (!level.equals("小学") || wrongAnswer >= 0) {
wrongAnswers.add(wrongAnswer);
}
}
attempts++;
}
if (wrongAnswers.size() < numWrongOptions) {
// System.out.println("Warning: Could not generate enough distinct wrong answers for: " + correctAnswer);
return null; // 无法生成足够选项
}
List<Double> allAnswers = new ArrayList<>();
allAnswers.add(correctAnswer);
allAnswers.addAll(wrongAnswers);
List<String> options = new ArrayList<>();
for (Double ans : allAnswers) {
options.add(df.format(ans));
}
return options;
}
// ... (其他类成员和方法保持不变)
// --- 表达式分词逻辑 ---
// 将 "3 + 开根号 ( 4 ) 平方 - sin 30" 分割成 ["3", "+", "开根号", "(", "4", ")", "平方", "-", "sin", "30"]
private List<String> tokenizeExpression(String expression) {
List<String> tokens = new ArrayList<>();
int i = 0;
while (i < expression.length()) {
String token = _findNextToken(expression, i);
if (token != null) {
tokens.add(token);
i += token.length();
} else {
// 如果找不到匹配的 token可能是单个字符或未知格式
tokens.add(String.valueOf(expression.charAt(i)));
i++;
}
}
return tokens;
}
/**
* token
* @param expression
* @param startPos
* @return token null
*/
private String _findNextToken(String expression, int startPos) {
String[] advancedOps = {"平方", "开根号", "sin", "cos", "tan"};
char c = expression.charAt(startPos);
if (Character.isWhitespace(c)) {
return null; // 空格由调用者处理
}
if (c == '(' || c == ')') {
return String.valueOf(c);
}
if (Character.isDigit(c) || c == '.') {
// 查找连续的数字或小数点
int j = startPos;
while (j < expression.length() && (Character.isDigit(expression.charAt(j)) || expression.charAt(j) == '.')) {
j++;
}
return expression.substring(startPos, j);
}
// 检查多字符运算符
for (String op : advancedOps) {
if (expression.startsWith(op, startPos)) {
return op;
}
}
// 如果不是多字符运算符,则认为是单字符运算符 (+, -, *, / 等)
return String.valueOf(c);
}
}

@ -0,0 +1,102 @@
package com.ybw.mathapp.service;
import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
*
* <p>
*
*
* @author
* @version 1.0
* @since 2025
*/
public class PrimarySchoolGenerator implements QuestionGenerator {
/** 运算符数组,包含四则运算符号。 */
private static final String[] OPERATORS = {"+", "-", "*", "/"};
/** 随机数生成器,用于生成随机题目。 */
private final Random random = new Random();
@Override
public List<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
* .
*
* <p>2-5
*
*
* @return
*/
private String generateSingleQuestion() {
int operandCount = random.nextInt(4) + 2; // 2-5个操作数
List<String> parts = new ArrayList<>();
while (true) {
// 生成基础操作
parts = generateBase(operandCount, parts);
// 简单添加括号逻辑:随机加一个括号
if (operandCount > 2 && random.nextBoolean()) {
// 遍历查找左括号的合理位置
for (int i = 0; i < parts.size() - 2; i++) {
// 该位置要为操作数且随机添加括号
if (isNumeric(parts.get(i)) && random.nextBoolean()) {
parts.add(i, "(");
i++;
// 为避免随机数上限出现0此处要单独判断一下左括号正好括住倒数第二个操作数的情况
if (i == parts.size() - 3) {
parts.add(")");
} else {
while (true) {
int i2 = random.nextInt(parts.size() - 3 - i) + 2;
if (isNumeric(parts.get(i + i2))) {
parts.add(i + i2 + 1, ")");
break;
}
}
}
break;
}
}
}
if (AdvancedCaculate.calculate(parts) >= 0) {
return String.join(" ", parts) + " =";
} else {
parts.clear();
}
}
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
public List<String> generateBase(int operandCount, List<String> parts) {
for (int i = 0; i < operandCount; i++) {
int num = random.nextInt(100) + 1;
parts.add(String.valueOf(num));
if (i < operandCount - 1) {
parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
}
}
return parts;
}
}

@ -0,0 +1,29 @@
package com.ybw.mathapp.service;
import java.util.List;
/**
*
*
* <p>
*
*
*
* @author
* @version 1.0
* @since 2025
*/
public interface QuestionGenerator {
/**
*
*
* <p>
*
*
* @param count
* @return
*/
List<String> generateQuestions(int count);
}

@ -0,0 +1,127 @@
package com.ybw.mathapp.service;
import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
*
* <p>sincostan
*
*
* @author
* @version 1.0
* @since 2025
*/
public class SeniorHighGenerator implements QuestionGenerator {
/** 三角函数运算符数组,包含"sin"、"cos"和"tan"。 */
private static final String[] TRIG_FUNCS = {"sin", "cos", "tan"};
/** 基本运算符数组,包含四则运算符号。 */
private static final String[] OPERATORS = {"+", "-", "*", "/"};
/** 随机数生成器,用于生成随机题目。 */
private final Random random = new Random();
@Override
public List<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
*
*
* <p>
*
*
* @return
*/
private String generateSingleQuestion() {
List<String> parts = new ArrayList<>();
int operandCount = random.nextInt(5) + 1;
parts = generateBase(operandCount, parts);
String advancedOp;
if (operandCount == 1) {
advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
parts.set(0, advancedOp + " " + parts.get(0));
} else {
// 遍历查找左括号的合理位置
for (int i = 0; i < parts.size(); i++) {
// 最后一次循环保底生成高中三角函数
if (i == parts.size() - 1) {
advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
parts.set(i, advancedOp + " " + parts.get(i));
} else if (isNumeric(parts.get(i)) && random.nextBoolean()) { // 随机数看是否为操作数且随即进入生成程序
// 进入随机生成tan\sin\cos的程序
parts = generateTrig(parts, i);
break;
}
}
}
return String.join(" ", parts) + " =";
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
// 产生基本操作
public List<String> generateBase(int operandCount, List<String> parts) {
for (int i = 0; i < operandCount; i++) {
int num = random.nextInt(100) + 1;
parts.add(String.valueOf(num));
if (i < operandCount - 1) {
parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
}
}
return parts;
}
/**
*
*
* <p>
*
*
* @param parts
* @param i
* @return
*/
// 产生三角函数运算符
public List<String> generateTrig(List<String> parts, int i) {
String trigOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
if (random.nextBoolean()) {
parts.set(i, trigOp + " " + parts.get(i));
} else {
parts.set(i, trigOp + " ( " + parts.get(i));
// 为避免随机数上限出现0此处要单独判断一下左括号正好括住倒数第二个操作数的情况
if (i == parts.size() - 3) {
parts.set(parts.size() - 1, parts.get(parts.size() - 1) + " )");
} else {
while (true) {
int i2 = random.nextInt(parts.size() - 3 - i) + 2;
if (isNumeric(parts.get(i + i2))) {
parts.set(i + i2, parts.get(i + i2) + " )");
break;
}
}
}
}
return parts;
}
}

@ -0,0 +1,166 @@
package com.ybw.mathapp.service;
import com.ybw.mathapp.entity.User;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class StartController {
private double score;
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public void start() {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("=== 数学学习软件 ===");
System.out.println("1. 登录");
System.out.println("2. 注册");
System.out.print("请选择: ");
String choice = scanner.nextLine();
if ("1".equals(choice)) {
if (login()) {
showMainMenu(scanner);
}
} else if ("2".equals(choice)) {
if (register()) {
showMainMenu(scanner);
}
} else {
System.out.println("无效选择,请重新输入!");
}
}
}
private boolean login() {
System.out.println("\n=== 用户登录 ===");
Scanner scanner = new Scanner(System.in);
System.out.print("请输入邮箱: ");
String email = scanner.nextLine().trim();
System.out.print("请输入密码: ");
String password = scanner.nextLine();
// 这里应该调用真实的登录逻辑
System.out.println("登录功能待实现");
return true;
}
private boolean register() {
System.out.println("\n=== 用户注册 ===");
System.out.println("注册功能待实现");
return true;
}
private void showMainMenu(Scanner scanner) {
while (true) {
System.out.println("\n=== 主菜单 ===");
System.out.println("1. 开始练习");
System.out.println("2. 修改密码");
System.out.println("3. 退出登录");
System.out.print("请选择: ");
String choice = scanner.nextLine();
if ("1".equals(choice)) {
showLevelSelection(scanner);
} else if ("2".equals(choice)) {
changePassword(scanner);
} else if ("3".equals(choice)) {
System.out.println("退出登录成功!");
return;
} else {
System.out.println("无效选择,请重新输入!");
}
}
}
private void showLevelSelection(Scanner scanner) {
System.out.println("\n=== 选择题目级别 ===");
System.out.println("1. 小学");
System.out.println("2. 初中");
System.out.println("3. 高中");
System.out.println("4. 返回");
System.out.print("请选择: ");
String choice = scanner.nextLine();
String level = null;
switch (choice) {
case "1": level = "小学"; break;
case "2": level = "初中"; break;
case "3": level = "高中"; break;
case "4": return;
default:
System.out.println("无效选择!");
return;
}
showQuestionCount(scanner, level);
}
private void showQuestionCount(Scanner scanner, String level) {
System.out.println("\n=== 选择题目数量 ===");
System.out.print("请输入题目数量 (10-30): ");
try {
int count = Integer.parseInt(scanner.nextLine());
if (count < 10 || count > 30) {
System.out.println("题目数量必须在10-30之间");
return;
}
startQuiz(level, count);
} catch (NumberFormatException e) {
System.out.println("请输入有效的数字!");
}
}
private void startQuiz(String level, int count) {
System.out.println("\n开始 " + level + " 级别答题,共 " + count + " 题");
// 这里应该调用题目生成和答题逻辑
System.out.println("答题功能待实现");
// 模拟答题结果
this.score = 0.8; // 模拟80%的正确率
System.out.println("答题完成!得分: " + (score * 100) + "%");
}
private void changePassword(Scanner scanner) {
System.out.println("\n=== 修改密码 ===");
System.out.println("修改密码功能待实现");
}
public QuestionGenerator createGenerator(String level) {
switch (level) {
case "小学":
return new PrimarySchoolGenerator();
case "初中":
return new JuniorHighGenerator();
case "高中":
return new SeniorHighGenerator();
default:
return null;
}
}
public boolean isCorrectAnswer(String input, int correctAnswerIndex, List<Double> options) {
if (input.equals(String.valueOf(options.get(correctAnswerIndex)))) {
return true;
} else {
return false;
}
}
public double caculateScore(int rightCount, int totalCount) {
return rightCount / (double) totalCount;
}
}

@ -0,0 +1,91 @@
package com.ybw.mathapp.util;
import static com.ybw.mathapp.util.LoginFileUtils.USER_FILE;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ChangePassword {
static final List<String> lines = new ArrayList<>();
static String userLine = null;
static int userLineNumber = -1;
// 前端接口-修改密码
public static boolean changePassword(String name, String newPassword) {
File file = new File(USER_FILE);
if (!file.exists()) {
System.out.println("用户文件不存在: " + USER_FILE);
return false;
}
// 1. 读取文件,查找用户
if(!findUserLine(name, file)) {
return false;
}
if (userLine == null || userLineNumber == -1) {
// 用户未找到
System.out.println("用户 '" + name + "' 不存在,修改失败。");
return false;
}
// 2. 更新找到的用户行中的密码
String[] parts = userLine.split(",");
if (parts.length != 3) {
return false;
}
parts[2] = newPassword; // 假设密码是第三个字段
String updatedLine = String.join(",", parts);
lines.set(userLineNumber, updatedLine); // 替换列表中的旧行
// 3. 将更新后的内容写回文件
if(!writeBack(lines, file)) {
return false;
}
return true;
}
public static boolean writeBack(List<String> lines, File file) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
for (String l : lines) {
writer.write(l);
writer.newLine();
}
} catch (IOException e) {
System.err.println("写入文件时出错: " + e.getMessage());
return false; // 如果写回失败,认为修改未成功
}
return true;
}
public static boolean findUserLine(String name, File file) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
int currentLineNum = 0;
while ((line = reader.readLine()) != null) {
lines.add(line);
String[] parts = line.split(",");
// 假设格式为: username,email,password
if (parts.length >= 3 && parts[0].equals(name)) {
userLine = line; // 找到用户行
userLineNumber = currentLineNum;
break; // 找到后可以退出循环
}
currentLineNum++;
}
} catch (IOException e) {
System.err.println("读取文件时出错: " + e.getMessage());
return false;
}
return true;
}
}

@ -0,0 +1,189 @@
package com.ybw.mathapp.util;
import com.ybw.mathapp.config.EmailConfig;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
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;
public class EmailService {
private static Map<String, VerificationCodeInfo> verificationCodes = new HashMap<>();
// 验证码信息内部类
private static class VerificationCodeInfo {
String code;
long timestamp;
VerificationCodeInfo(String code, long timestamp) {
this.code = code;
this.timestamp = timestamp;
}
}
// 生成6位随机验证码
public static String generateVerificationCode() {
Random random = new Random();
int code = 100000 + random.nextInt(900000);
return String.valueOf(code);
}
// 发送真实邮件验证码
public static boolean sendVerificationCode(String recipientEmail, String code) {
try {
// 创建邮件会话
Properties props = new Properties();
props.put("mail.smtp.host", EmailConfig.SMTP_HOST);
props.put("mail.smtp.port", EmailConfig.SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.ssl.protocols", "TLSv1.2");
// 创建认证器
Authenticator auth = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
EmailConfig.SENDER_EMAIL,
EmailConfig.SENDER_PASSWORD
);
}
};
Session session = Session.getInstance(props, auth);
// 创建邮件消息
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(EmailConfig.SENDER_EMAIL));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(recipientEmail));
message.setSubject(EmailConfig.EMAIL_SUBJECT);
// 创建邮件内容
String emailContent = createEmailContent(code);
message.setContent(emailContent, "text/html; charset=utf-8");
// 发送邮件
Transport.send(message);
// 存储验证码信息
verificationCodes.put(recipientEmail,
new VerificationCodeInfo(code, System.currentTimeMillis()));
System.out.println("验证码已发送到邮箱: " + recipientEmail);
return true;
} catch (MessagingException e) {
System.err.println("发送邮件失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
// 创建HTML格式的邮件内容
private static String createEmailContent(String code) {
return "<!DOCTYPE html>" +
"<html>" +
"<head>" +
"<meta charset='UTF-8'>" +
"<style>" +
"body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }" +
".container { max-width: 600px; margin: 0 auto; padding: 20px; }" +
".header { background-color: #4CAF50; color: white; padding: 20px; text-align: center; }" +
".content { padding: 20px; background-color: #f9f9f9; margin: 20px 0; }" +
".code { font-size: 24px; font-weight: bold; color: #4CAF50; text-align: center; padding: 15px; background-color: white; border: 2px dashed #4CAF50; margin: 20px 0; }" +
".footer { text-align: center; color: #666; font-size: 12px; }" +
"</style>" +
"</head>" +
"<body>" +
"<div class='container'>" +
"<div class='header'>" +
"<h2>用户注册验证码</h2>" +
"</div>" +
"<div class='content'>" +
"<p>您好!</p>" +
"<p>您正在注册账户,验证码如下:</p>" +
"<div class='code'>" + code + "</div>" +
"<p>验证码有效期为 " + EmailConfig.CODE_EXPIRY_MINUTES + " 分钟,请勿泄露给他人。</p>" +
"<p>如果这不是您本人的操作,请忽略此邮件。</p>" +
"</div>" +
"<div class='footer'>" +
"<p>此邮件为系统自动发送,请勿回复。</p>" +
"</div>" +
"</div>" +
"</body>" +
"</html>";
}
// 前端接口-验证验证码
public static boolean verifyCode(String email, String inputCode) {
VerificationCodeInfo codeInfo = verificationCodes.get(email);
if (codeInfo == null) {
return false;
}
// 检查验证码是否过期
long currentTime = System.currentTimeMillis();
if (currentTime - codeInfo.timestamp > EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
verificationCodes.remove(email);
return false;
}
return codeInfo.code.equals(inputCode);
}
// 清理过期的验证码(可选)
public static void cleanupExpiredCodes() {
long currentTime = System.currentTimeMillis();
Iterator<Entry<String, VerificationCodeInfo>> iterator =
verificationCodes.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, VerificationCodeInfo> entry = iterator.next();
if (currentTime - entry.getValue().timestamp >
EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
iterator.remove();
}
}
}
/**
* -
* @param email
* @return truefalse
*/
public static boolean isValidEmail(String email) {
if (email.isEmpty()) {
System.out.println("邮箱地址不能为空!");
return false;
}
if (!(email.contains("@") && email.contains("."))) {
System.out.println("邮箱格式不正确!");
return false;
}
return true;
}
// 前端接口-发送验证码
public static boolean sendCode(String email) {
// 发送真实邮件验证码
String verificationCode = EmailService.generateVerificationCode();
// 验证验证码是否成功发送
if (!EmailService.sendVerificationCode(email, verificationCode)) {
return false;
}
return true;
}
}

@ -0,0 +1,16 @@
package com.ybw.mathapp.util;
public class Login {
// 前端接口-登录成功or失败
public static boolean login(String name, String password) {
if (LoginFileUtils.validateUser(name, password)) {
System.out.println("登录成功!欢迎回来," + name);
return true;
} else {
System.out.println("邮箱或密码错误!");
return false;
}
}
}

@ -0,0 +1,95 @@
package com.ybw.mathapp.util;
import com.ybw.mathapp.entity.User;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class LoginFileUtils {
static final String USER_FILE = "users.txt";
// 读取所有用户
public static List<User> readUsers() {
List<User> users = new ArrayList<>();
File file = new File(USER_FILE);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
System.err.println("创建用户文件失败: " + e.getMessage());
}
return users;
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
User user = User.fromString(line);
if (user != null) {
users.add(user);
}
}
} catch (IOException e) {
System.err.println("读取用户文件失败: " + e.getMessage());
}
return users;
}
// 保存用户到文件
public static void saveUser(User user) {
try (PrintWriter writer = new PrintWriter(new FileWriter(USER_FILE, true))) {
writer.println(user.toString());
} catch (IOException e) {
System.err.println("保存用户信息失败: " + e.getMessage());
}
}
// 前端接口-检查邮箱是否已注册
public static boolean isEmailRegistered(String email) {
List<User> users = readUsers();
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email)) {
return true;
}
}
return false;
}
// 前端接口-检查用户名是否已注册
public static boolean isNameRegistered(String name) {
List<User> users = readUsers();
for (User user : users) {
if (user.getName().equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
// 前端接口-验证用户登录
public static boolean validateUser(String emailOrName, String password) {
List<User> users = readUsers();
for (User user : users) {
if ((user.getEmail().equalsIgnoreCase(emailOrName)
|| user.getName().equalsIgnoreCase(emailOrName) )
&& user.getPassword().equals(password)) {
return true;
}
}
return false;
}
// 前端接口-通过邮箱查找用户名
public static String emailFindName (String email) {
List<User> users = readUsers();
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email)) {
return user.getName();
}
}
return null;
}
}

@ -0,0 +1,44 @@
package com.ybw.mathapp.util;
import com.ybw.mathapp.entity.User;
import java.util.regex.Pattern;
public class Register {
// 前端接口-完成注册
public static boolean register(String name, String email, String password1) {
User user = new User(name, email, password1);
LoginFileUtils.saveUser(user);
System.out.println("注册成功!您可以使用邮箱和密码登录了。");
return true;
}
/**
* -
* @param password1
* @return truefalse
*/
public static boolean isVaildPassword(String password1) {
// 使用正则表达式验证长度6-10只包含字母数字且包含大小写字母和数字
String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$";
if (!Pattern.matches(regex, password1)) {
return false;
}
return true;
}
/**
* -
* @param password1
* @param password2
* @return truefalse
*/
public static boolean isEqualPassword(String password1, String password2) {
if (!password1.equals(password2)) {
System.out.println("两次输入的密码不一致!");
return false;
}
return true;
}
}

@ -1,16 +1,10 @@
module com.ybw.mathapp {
requires javafx.controls;
requires javafx.fxml;
requires javafx.web;
requires javafx.base;
requires javafx.graphics;
requires jakarta.mail;
requires org.controlsfx.controls;
requires com.dlsc.formsfx;
requires net.synedra.validatorfx;
requires org.kordamp.ikonli.javafx;
requires org.kordamp.bootstrapfx.core;
requires eu.hansolo.tilesfx;
requires com.almasb.fxgl.all;
opens com.ybw.mathapp to javafx.fxml;
exports com.ybw.mathapp;
opens com.wsf.mathapp to javafx.fxml;
exports com.wsf.mathapp;
}

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.ybw.mathapp.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>

@ -0,0 +1,2 @@
wsf,3310207578@qq.com,Wsf1234
Loading…
Cancel
Save