Compare commits

..

3 Commits

Author SHA1 Message Date
LYQ 88e94d97e7 更新了md文件
4 months ago
LYQ ab76ebf447 最终版本2.0
4 months ago
LYQ c2819895b0 最终版本1.2
4 months ago

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="GoogleStyle" />
</state>
</component>

@ -6,12 +6,54 @@
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="72b0954a-217b-4c2e-9f54-fc09e16134d2" name="更改" comment=""> <list default="true" id="72b0954a-217b-4c2e-9f54-fc09e16134d2" name="更改" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<<<<<<< HEAD
<change beforePath="$PROJECT_DIR$/src/account/Account.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/account/Account.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/account/AccountService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/account/AccountService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/account/SimpleAccountService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/account/SimpleAccountService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/Launcher.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/Launcher.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/MainApp.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/MainApp.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/model/Question.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/model/Question.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/model/User.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/model/User.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/persistence/FileSaver.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/persistence/FileSaver.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/persistence/JsonUtils.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/persistence/JsonUtils.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/persistence/QuestionChecker.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/persistence/QuestionChecker.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/HighQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/HighQuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/MiddleQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/MiddleQuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/PrimaryQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/PrimaryQuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/Question.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/Question.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/QuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/QuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IQuizService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IQuizService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IUserService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IUserService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/QuizServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/QuizServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/UserServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/UserServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/ChangePasswordController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/ChangePasswordController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/LoginViewController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/LoginViewController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/MainMenuViewController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/MainMenuViewController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/QuizViewController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/QuizViewController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/RegisterViewController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/RegisterViewController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/ScoreViewController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/ScoreViewController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/ChangePasswordView.fxml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/ChangePasswordView.fxml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/LoginView.fxml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/LoginView.fxml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/MainMenuView.fxml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/MainMenuView.fxml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/QuizView.fxml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/QuizView.fxml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/RegisterView.fxml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/RegisterView.fxml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/RootLayout.fxml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/RootLayout.fxml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/ScoreView.fxml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/com/student/mathquiz/view/ScoreView.fxml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/persistence/FileSaver.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/persistence/FileSaver.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/persistence/QuestionChecker.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/persistence/QuestionChecker.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/question/HighQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/question/HighQuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/question/MiddleQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/question/MiddleQuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/question/PrimaryQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/question/PrimaryQuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/question/Question.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/question/Question.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/question/QuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/question/QuestionGenerator.java" afterDir="false" />
=======
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/MainApp.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/MainApp.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/MainApp.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/MainApp.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/PrimaryQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/PrimaryQuestionGenerator.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/PrimaryQuestionGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/question/PrimaryQuestionGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IUserService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IUserService.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IUserService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/IUserService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/UserServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/UserServiceImpl.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/UserServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/service/UserServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/LoginViewController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/LoginViewController.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/LoginViewController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/student/mathquiz/view/LoginViewController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/user_data/users.json" beforeDir="false" afterPath="$PROJECT_DIR$/user_data/users.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/user_data/users.json" beforeDir="false" afterPath="$PROJECT_DIR$/user_data/users.json" afterDir="false" />
>>>>>>> develop
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -37,6 +79,26 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<<<<<<< HEAD
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"Maven.mathquiz [clean].executor": "Run",
"Maven.mathquiz [compile].executor": "Run",
"Maven.mathquiz [org.openjfx:javafx-maven-plugin:0.0.8:run].executor": "Run",
"Maven.mathquiz [package].executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"git-widget-placeholder": "main",
"ignore.virus.scanning.warn.message": "true",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "C:/Users/lyq20/OneDrive/桌面/Finally/MathQuiz",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.sourceCode.Java",
"vue.rearranger.settings.migration": "true"
=======
<component name="PropertiesComponent">{ <component name="PropertiesComponent">{
&quot;keyToString&quot;: { &quot;keyToString&quot;: {
&quot;Maven.mathquiz [clean].executor&quot;: &quot;Run&quot;, &quot;Maven.mathquiz [clean].executor&quot;: &quot;Run&quot;,
@ -54,6 +116,7 @@
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
>>>>>>> develop
} }
}</component> }</component>
<component name="SharedIndexes"> <component name="SharedIndexes">

@ -1,93 +1,128 @@
# 小初高数学学习软件 (Math Learning Software for K-12) > # 小初高数学学习软件 (Math Learning Software for K-12)
>
这是一个专为小学、初中、高中学生设计的桌面数学学习与测验软件。本项目作为软件工程导论课程的结对编程项目,旨在提供一个用户友好、功能完善的图形化学习工具,帮助学生通过练习来巩固数学知识。 > 这是一个专为小学、初中、高中学生设计的桌面数学学习与测验软件。本项目作为软件工程导论课程的结对编程项目,旨在提供一个用户友好、功能完善的图形化学习工具,帮助学生通过练习来巩固数学知识。
>
本软件实现了前后端完全分离的架构,前端负责界面展示与用户交互,后端负责业务逻辑、数据处理与持久化,最终通过 `jpackage` 工具打包成一个免安装的 Windows 可执行文件。 > 本软件实现了前后端完全分离的架构,前端负责界面展示与用户交互,后端负责业务逻辑、数据处理与持久化,最终通过 `jpackage` 工具打包成一个免安装的 Windows 可执行文件。
>
## ✨ 功能特性 (Features) > ---
>
* **用户注册**:通过邮箱验证码进行安全注册,确保用户身份的真实性。 > ## ✨ 功能特性 (Features)
* **双重登录方式**:支持 **邮箱** 或 **用户名** 登录,为用户提供便利。 >
* **密码管理**:支持用户在登录状态下修改密码,所有密码均经过加密存储,保障账户安全。 > - **用户注册**:通过邮箱验证码进行安全注册,确保用户身份的真实性。
* **分级题库**:内置 **小学**、**初中**、**高中** 三个不同难度的数学题库,题目类型符合对应学段的教学大纲。 > - **双重登录方式**:支持 **邮箱** 或 **用户名** 登录,为用户提供便利。
* **动态生成试卷**:用户可自定义需要生成的题目数量,系统会根据所选难度,动态生成一张不含重复题目的试卷。 > - **密码管理**:支持用户在登录状态下修改密码,所有密码均经过加密存储,保障账户安全。
* **交互式答题界面**:清爽的图形化界面,以单项选择题的形式呈现,用户可以方便地进行选择和提交。 > - **分级题库**:内置 **小学**、**初中**、**高中** 三个不同难度的数学题库,题目类型符合对应学段的教学大纲。
* **自动评分**:所有题目回答完毕后,系统会立即自动计算并显示本次测验的分数。 > - **动态生成试卷**:用户可自定义需要生成的题目数量,系统会根据所选难度,动态生成一张不含重复题目的试卷。
* **数据持久化**:用户信息与用户的历史题目记录均通过本地 JSON 和文本文件进行存储(遵循项目要求,不使用数据库),程序关闭后数据不会丢失。 > - **交互式答题界面**:清爽的图形化界面,以单项选择题的形式呈现,用户可以方便地进行选择和提交。
> - **自动评分**:所有题目回答完毕后,系统会立即自动计算并显示本次测验的分数。
## 🛠️ 技术栈 (Technology Stack) > - **数据持久化**:用户信息与用户的历史题目记录均通过本地 JSON 和文本文件进行存储(遵循项目要求,不使用数据库),程序关闭后数据不会丢失。
>
* **前端 (Frontend)**: JavaFX, FXML > ---
* **后端 (Backend)**: Java 17 >
* **构建工具 (Build Tool)**: Apache Maven > ## 🛠️ 技术栈 (Technology Stack)
* **打包工具 (Packaging)**: JDK `jpackage` >
* **依赖库 (Libraries)**: > - **前端 (Frontend)**: JavaFX, FXML
* `Gson`: 用于处理用户数据的 JSON 序列化与反序列化。 > - **后端 (Backend)**: Java 17
* `Jakarta Mail`: 用于实现注册时的邮件验证码功能。 > - **构建工具 (Build Tool)**: Apache Maven
> - **打包工具 (Packaging)**: JDK `jpackage`
## 🚀 如何运行 (Getting Started) > - **依赖库 (Libraries)**:
> - `Gson`: 用于处理用户数据的 JSON 序列化与反序列化。
#### 面向用户 (For Users) > - `Jakarta Mail`: 用于实现注册时的邮件验证码功能。
>
1. 找到打包后生成的 `数学学习软件` 文件夹。 > ---
2. 双击文件夹内的 `数学学习软件.exe` 即可直接运行。 >
3. 程序所需的数据文件(如 `users.json`)会自动在您的用户主目录下的 `.mathquiz` 文件夹中创建。 > ## 🚀 如何运行 (Getting Started)
>
#### 面向开发者 (For Developers) > ### 面向开发者 (For Developers)
>
1. **环境准备**: > 1. **环境准备**:
* JDK 17 或更高版本 > - JDK 17 或更高版本
* Apache Maven 3.6+ > - Apache Maven 3.6+
* IntelliJ IDEA > - IntelliJ IDEA
>
2. **克隆项目**: > 2. **克隆项目**:
> ```bash
```bash > git clone https://bdgit.educoder.net/hnu202326010416/MyMathQuiz_UI.git
git clone https://bdgit.educoder.net/hnu202326010416/MyMathQuiz_UI.git > cd MyMathQuiz_UI
``` > ```
>
3. **配置邮箱服务**: > 3. **配置邮箱服务**:
>
* **重要**: 请修改 `src/main/java/com/student/mathquiz/service/UserServiceImpl.java` 文件中的 `SENDER_EMAIL``SENDER_AUTHORIZATION_CODE` 为你自己的发件人邮箱和授权码。 > - **重要**: 请修改 `src/main/java/com/student/mathquiz/service/UserServiceImpl.java` 文件中的 `SENDER_EMAIL``SENDER_AUTHORIZATION_CODE` 为你自己的发件人邮箱和授权码。
>
4. **构建项目**: > 4. **构建项目**:
```bash > ```bash
# 生成包含所有依赖的 "胖JAR" > # 生成包含所有依赖的 "胖JAR"(位于 target 目录下)
mvn clean package > mvn clean package
``` > ```
>
5. **在IDE中运行**: > 5. **运行 JAR 文件**:
```bash > ```bash
# 直接运行主程序 > # 构建成功后,在项目根目录执行以下命令运行 JAR
mvn javafx:run > java -jar target/mathquiz-1.0-SNAPSHOT.jar
``` > ```
>
## 📂 项目结构 (Project Structure) > 6. **在IDE中运行**:
> ```bash
本项目采用前后端分离的设计思想,将视图、业务逻辑和数据持久化清晰地分层。 > # 直接通过 Maven 插件运行主程序
> mvn javafx:run
``` > ```
src >
└── main > ---
├── java >
│ └── com/student/mathquiz > ### 面向普通用户 (For Users)
│ ├── MainApp.java // 程序主入口,负责视图切换和前后端桥接 >
│ ├── constant/ // 存放常量和枚举 (后端) > #### 方式一:直接运行 EXE推荐
│ ├── model/ // 数据模型 (User, Question) (后端) > 找到打包后生成的 数学学习软件 文件夹。
│ ├── persistence/ // 数据持久化层 (文件读写) (后端) > 双击文件夹内的 数学学习软件.exe 即可直接运行。
│ ├── question/ // 题目生成器 (后端) > 程序所需的数据文件(如 users.json会自动在您的用户主目录下的 .mathquiz 文件夹中创建。
│ ├── service/ // 业务逻辑服务层 (IUserService, IQuizService) (后端) >
│ └── view/ // 视图控制器 (xxxController.java) (前端) > #### 方式二:运行 JAR 文件
└── resources > 如果您已经安装了 JDK 17 或更高版本,可以直接运行 JAR 文件:
└── com/student/mathquiz > 1. 确保您的电脑已安装 [JDK 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)。
└── view/ // FXML 界面定义文件 (前端) > 2. 打开命令行工具Windows 上是 CMD 或 PowerShell
``` > 3. 进入 JAR 文件所在目录。
> 4. 执行以下命令:
## 👨‍💻 开发成员 (Authors) > ```bash
> java -jar mathquiz-1.0-SNAPSHOT.jar
* **组长**: 李瑜清 (学号: 202326010416) > ```
* **组员**: 马芳琼 (学号: 202326010423) > 5. 如果运行时提示 JavaFX 相关错误,请使用完整命令:
> ```bash
--- > java --module-path "path\to\javafx-sdk-17\lib" --add-modules javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing,javafx.web -jar mathquiz-1.0-SNAPSHOT.jar
> ```
> 感谢使用!希望这个软件能帮助你更好地学习数学! > 其中 `path\to\javafx-sdk-17\lib` 替换为您本地 JavaFX SDK 的实际路径。
>
> ---
>
> ## 📂 项目结构 (Project Structure)
>
> 本项目采用前后端分离的设计思想,将视图、业务逻辑和数据持久化清晰地分层。
>
> ```
> src
> └── main
> ├── java
> │ └── com/student/mathquiz
> │ ├── MainApp.java // 程序主入口,负责视图切换和前后端桥接
> │ ├── Launcher.java // 解决 JavaFX 模块化启动问题的启动器
> │ ├── constant/ // 存放常量和枚举 (后端)
> │ ├── model/ // 数据模型 (User, Question) (后端)
> │ ├── persistence/ // 数据持久化层 (文件读写) (后端)
> │ ├── question/ // 题目生成器 (后端)
> │ ├── service/ // 业务逻辑服务层 (IUserService, IQuizService) (后端)
> │ └── view/ // 视图控制器 (xxxController.java) (前端)
> └── resources
> └── com/student/mathquiz
> └── view/ // FXML 界面定义文件 (前端)
> ```
>
> ---
>
> ## 👨‍💻 开发成员 (Authors)
>
> - **组长**: 李瑜清 (学号: 202326010416)
> - **组员**: 马芳琼 (学号: 202326010423)
>
> ---
>
> > 感谢使用!希望这个软件能帮助你更好地学习数学!

@ -1,30 +1,31 @@
package account; package account;
public class Account { public class Account {
private String userName;
private String password; private String userName;
private String userType; private String password;
private String userType;
public Account(String userName, String password,String userType) {
this.userName = userName; public Account(String userName, String password, String userType) {
this.password = password; this.userName = userName;
this.userType = userType; this.password = password;
} this.userType = userType;
}
public String getUserType() {
return userType; public String getUserType() {
} return userType;
}
public String getUserName() {
return userName; public String getUserName() {
} return userName;
}
public String getPassword() {
return password; public String getPassword() {
} return password;
}
public void setUserType(String userType) {
this.userType = userType; public void setUserType(String userType) {
} this.userType = userType;
}
} }

@ -1,5 +1,6 @@
package account; package account;
public interface AccountService { public interface AccountService {
Account login(String username, String password);
Account login(String username, String password);
} }

@ -4,27 +4,28 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class SimpleAccountService implements AccountService { public class SimpleAccountService implements AccountService {
private static final Map<String, Account> accounts = new HashMap<>();
static { private static final Map<String, Account> accounts = new HashMap<>();
accounts.put("张三1", new Account("张三1", "123", "小学"));
accounts.put("张三2", new Account("张三2", "123", "小学"));
accounts.put("张三3", new Account("张三3", "123", "小学"));
accounts.put("李四1", new Account("李四1", "123", "初中"));
accounts.put("李四2", new Account("李四2", "123", "初中"));
accounts.put("李四3", new Account("李四3", "123", "初中"));
accounts.put("王五1", new Account("王五1", "123", "高中"));
accounts.put("王五2", new Account("王五2", "123", "高中"));
accounts.put("王五3", new Account("王五3", "123", "高中"));
}
@Override static {
public Account login(String username, String password) { accounts.put("张三1", new Account("张三1", "123", "小学"));
Account account = accounts.get(username); accounts.put("张三2", new Account("张三2", "123", "小学"));
if (account != null && account.getPassword().equals(password)) { accounts.put("张三3", new Account("张三3", "123", "小学"));
return account; accounts.put("李四1", new Account("李四1", "123", "初中"));
} accounts.put("李四2", new Account("李四2", "123", "初中"));
return null; accounts.put("李四3", new Account("李四3", "123", "初中"));
accounts.put("王五1", new Account("王五1", "123", "高中"));
accounts.put("王五2", new Account("王五2", "123", "高中"));
accounts.put("王五3", new Account("王五3", "123", "高中"));
}
@Override
public Account login(String username, String password) {
Account account = accounts.get(username);
if (account != null && account.getPassword().equals(password)) {
return account;
} }
return null;
}
} }

@ -1,11 +1,11 @@
package com.student.mathquiz; package com.student.mathquiz;
/** /**
* JavaFX 11+ * JavaFX 11+ `java -jar` "胖JAR" JavaFX main
* `java -jar` "胖JAR" JavaFX * MainApp main
* main MainApp main
*/ */
public class Launcher { public class Launcher {
public static void main(String[] args) { public static void main(String[] args) {
MainApp.main(args); MainApp.main(args);
} }

@ -47,7 +47,9 @@ public class MainApp extends Application {
rootLayout = loader.load(); rootLayout = loader.load();
Scene scene = new Scene(rootLayout); Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene); primaryStage.setScene(scene);
} catch (IOException e) { e.printStackTrace(); } } catch (IOException e) {
e.printStackTrace();
}
} }
private <T> T switchScene(String fxmlFile) { private <T> T switchScene(String fxmlFile) {
@ -62,12 +64,24 @@ public class MainApp extends Application {
T controller = loader.getController(); T controller = loader.getController();
// ... 后面的代码完全不变 ... // ... 后面的代码完全不变 ...
if (controller instanceof LoginViewController) ((LoginViewController) controller).setMainApp(this); if (controller instanceof LoginViewController) {
if (controller instanceof RegisterViewController) ((RegisterViewController) controller).setMainApp(this); ((LoginViewController) controller).setMainApp(this);
if (controller instanceof MainMenuViewController) ((MainMenuViewController) controller).setMainApp(this); }
if (controller instanceof QuizViewController) ((QuizViewController) controller).setMainApp(this); if (controller instanceof RegisterViewController) {
if (controller instanceof ScoreViewController) ((ScoreViewController) controller).setMainApp(this); ((RegisterViewController) controller).setMainApp(this);
if (controller instanceof ChangePasswordController) ((ChangePasswordController) controller).setMainApp(this); }
if (controller instanceof MainMenuViewController) {
((MainMenuViewController) controller).setMainApp(this);
}
if (controller instanceof QuizViewController) {
((QuizViewController) controller).setMainApp(this);
}
if (controller instanceof ScoreViewController) {
((ScoreViewController) controller).setMainApp(this);
}
if (controller instanceof ChangePasswordController) {
((ChangePasswordController) controller).setMainApp(this);
}
return controller; return controller;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -75,6 +89,25 @@ public class MainApp extends Application {
} }
} }
<<<<<<< HEAD
// ... (所有其他方法保持不变)
public void handleLoginSuccess(String email) {
this.currentUserEmail = email;
showMainMenuView();
}
public void showLoginView() {
switchScene("LoginView.fxml");
}
public void showRegisterView() {
switchScene("RegisterView.fxml");
}
public void showMainMenuView() {
switchScene("MainMenuView.fxml");
}
=======
// ★★★ 修改这个方法的参数类型,从 String 改为 User ★★★ // ★★★ 修改这个方法的参数类型,从 String 改为 User ★★★
public void handleLoginSuccess(com.student.mathquiz.model.User user) { public void handleLoginSuccess(com.student.mathquiz.model.User user) {
// ★★★ 从 User 对象中,准确地获取并保存 email★★★ // ★★★ 从 User 对象中,准确地获取并保存 email★★★
@ -85,6 +118,7 @@ public class MainApp extends Application {
public void showLoginView() { switchScene("LoginView.fxml"); } public void showLoginView() { switchScene("LoginView.fxml"); }
public void showRegisterView() { switchScene("RegisterView.fxml"); } public void showRegisterView() { switchScene("RegisterView.fxml"); }
public void showMainMenuView() { switchScene("MainMenuView.fxml"); } public void showMainMenuView() { switchScene("MainMenuView.fxml"); }
>>>>>>> develop
// 在 MainApp.java 中 // 在 MainApp.java 中
public void showQuizView(String level, int count) { public void showQuizView(String level, int count) {
@ -105,7 +139,8 @@ public class MainApp extends Application {
new Thread(() -> { new Thread(() -> {
System.out.println(" -> 【后台线程】: 线程已启动!"); System.out.println(" -> 【后台线程】: 线程已启动!");
try { try {
System.out.println(" -> 【后台线程】: 即将调用 quizService.generateQuiz(...),这可能会很耗时..."); System.out.println(
" -> 【后台线程】: 即将调用 quizService.generateQuiz(...),这可能会很耗时...");
// 【重活】让后台工人去生成试卷 // 【重活】让后台工人去生成试卷
quizService.generateQuiz(level, count, currentUserEmail); quizService.generateQuiz(level, count, currentUserEmail);
@ -134,11 +169,23 @@ public class MainApp extends Application {
public void showScoreView(int score) { public void showScoreView(int score) {
ScoreViewController controller = switchScene("ScoreView.fxml"); ScoreViewController controller = switchScene("ScoreView.fxml");
if (controller != null) controller.setScore(score); if (controller != null) {
controller.setScore(score);
}
}
public IUserService getUserService() {
return userService;
}
public IQuizService getQuizService() {
return quizService;
} }
public IUserService getUserService() { return userService; }
public IQuizService getQuizService() { return quizService; } public static void main(String[] args) {
public static void main(String[] args) { launch(args); } launch(args);
}
// 在 MainApp.java 的末尾添加 // 在 MainApp.java 的末尾添加
public void showChangePasswordView() { public void showChangePasswordView() {
switchScene("ChangePasswordView.fxml"); switchScene("ChangePasswordView.fxml");

@ -3,6 +3,7 @@ package com.student.mathquiz.model;
import java.util.List; import java.util.List;
public class Question { public class Question {
private String content; private String content;
private List<String> options; private List<String> options;
private int correctAnswerIndex; private int correctAnswerIndex;
@ -13,7 +14,15 @@ public class Question {
this.correctAnswerIndex = correctAnswerIndex; this.correctAnswerIndex = correctAnswerIndex;
} }
public String getContent() { return content; } public String getContent() {
public List<String> getOptions() { return options; } return content;
public int getCorrectAnswerIndex() { return correctAnswerIndex; } }
public List<String> getOptions() {
return options;
}
public int getCorrectAnswerIndex() {
return correctAnswerIndex;
}
} }

@ -3,13 +3,15 @@ package com.student.mathquiz.model;
import com.student.mathquiz.constant.UserType; import com.student.mathquiz.constant.UserType;
public class User { public class User {
// ★★★ 1. 新增 username 字段 ★★★ // ★★★ 1. 新增 username 字段 ★★★
private String username; private String username;
private String email; private String email;
private String encryptedPwd; private String encryptedPwd;
private UserType userType; private UserType userType;
public User() {} // Gson需要无参构造 public User() {
} // Gson需要无参构造
// ★★★ 2. 修改构造函数,加入 username ★★★ // ★★★ 2. 修改构造函数,加入 username ★★★
public User(String username, String email, String encryptedPwd, UserType userType) { public User(String username, String email, String encryptedPwd, UserType userType) {
@ -20,14 +22,36 @@ public class User {
} }
// ★★★ 3. 新增 username 的 Getter 和 Setter ★★★ // ★★★ 3. 新增 username 的 Getter 和 Setter ★★★
public String getUsername() { return username; } public String getUsername() {
public void setUsername(String username) { this.username = username; } return username;
}
public void setUsername(String username) {
this.username = username;
}
// --- 下面的代码保持不变 --- // --- 下面的代码保持不变 ---
public String getEmail() { return email; } public String getEmail() {
public void setEmail(String email) { this.email = email; } return email;
public String getEncryptedPwd() { return encryptedPwd; } }
public void setEncryptedPwd(String encryptedPwd) { this.encryptedPwd = encryptedPwd; }
public UserType getUserType() { return userType; } public void setEmail(String email) {
public void setUserType(UserType userType) { this.userType = userType; } this.email = email;
}
public String getEncryptedPwd() {
return encryptedPwd;
}
public void setEncryptedPwd(String encryptedPwd) {
this.encryptedPwd = encryptedPwd;
}
public UserType getUserType() {
return userType;
}
public void setUserType(UserType userType) {
this.userType = userType;
}
} }

@ -9,12 +9,15 @@ import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
public class FileSaver { public class FileSaver {
public static void saveQuestions(String username, List<Question> questions) { public static void saveQuestions(String username, List<Question> questions) {
File dir = new File(username); File dir = new File(username);
if (!dir.exists()) dir.mkdirs(); if (!dir.exists()) {
dir.mkdirs();
}
String filename = LocalDateTime.now().format( String filename = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt"; DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
try (FileWriter writer = new FileWriter(new File(dir, filename))) { try (FileWriter writer = new FileWriter(new File(dir, filename))) {
for (int i = 0; i < questions.size(); i++) { for (int i = 0; i < questions.size(); i++) {

@ -6,11 +6,14 @@ import java.io.*;
import java.lang.reflect.Type; import java.lang.reflect.Type;
public class JsonUtils { public class JsonUtils {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
public static <T> T readFromJson(String filePath, Type type) { public static <T> T readFromJson(String filePath, Type type) {
File file = new File(filePath); File file = new File(filePath);
if (!file.exists()) return null; if (!file.exists()) {
return null;
}
try (Reader reader = new FileReader(file)) { try (Reader reader = new FileReader(file)) {
return GSON.fromJson(reader, type); return GSON.fromJson(reader, type);
} catch (IOException e) { } catch (IOException e) {

@ -5,12 +5,17 @@ import java.io.FileNotFoundException;
import java.util.Scanner; import java.util.Scanner;
public class QuestionChecker { public class QuestionChecker {
public static boolean isDuplicate(String username, String question) { public static boolean isDuplicate(String username, String question) {
File dir = new File(username); File dir = new File(username);
if (!dir.exists()) return false; if (!dir.exists()) {
return false;
}
File[] files = dir.listFiles(); File[] files = dir.listFiles();
if (files == null) return false; if (files == null) {
return false;
}
String target = question.trim(); String target = question.trim();
@ -19,7 +24,9 @@ public class QuestionChecker {
try (Scanner scanner = new Scanner(file)) { try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) { while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim(); String line = scanner.nextLine().trim();
if (line.isEmpty()) continue; if (line.isEmpty()) {
continue;
}
int dotIndex = line.indexOf('.'); int dotIndex = line.indexOf('.');
if (dotIndex != -1) { if (dotIndex != -1) {
String storedQuestion = line.substring(dotIndex + 1).trim(); String storedQuestion = line.substring(dotIndex + 1).trim();

@ -3,6 +3,13 @@ package com.student.mathquiz.question;
import java.util.*; import java.util.*;
public class HighQuestionGenerator implements QuestionGenerator { public class HighQuestionGenerator implements QuestionGenerator {
<<<<<<< HEAD
private static final Random RANDOM = new Random();
private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"};
private static final Map<String, String> TRIG_VALUES = new HashMap<>();
=======
>>>>>>> develop
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final String[] FUNCTIONS = {"sin", "cos", "tan"}; private static final String[] FUNCTIONS = {"sin", "cos", "tan"};
@ -11,6 +18,17 @@ public class HighQuestionGenerator implements QuestionGenerator {
// 静态初始化三角函数精确值表(特殊角) // 静态初始化三角函数精确值表(特殊角)
static { static {
<<<<<<< HEAD
TRIG_VALUES.put("sin30", "1/2");
TRIG_VALUES.put("sin45", "√2/2");
TRIG_VALUES.put("sin60", "√3/2");
TRIG_VALUES.put("cos30", "√3/2");
TRIG_VALUES.put("cos45", "√2/2");
TRIG_VALUES.put("cos60", "1/2");
TRIG_VALUES.put("tan30", "√3/3");
TRIG_VALUES.put("tan45", "1");
TRIG_VALUES.put("tan60", "√3");
=======
// 0° // 0°
TRIG_EXACT_VALUES.put("sin0", "0"); TRIG_EXACT_VALUES.put("sin0", "0");
TRIG_EXACT_VALUES.put("cos0", "1"); TRIG_EXACT_VALUES.put("cos0", "1");
@ -77,6 +95,7 @@ public class HighQuestionGenerator implements QuestionGenerator {
TRIG_EXACT_VALUES.put("tan180", "0"); TRIG_EXACT_VALUES.put("tan180", "0");
// 其他角度可以继续补充... // 其他角度可以继续补充...
>>>>>>> develop
} }
@Override @Override

@ -3,6 +3,7 @@ package com.student.mathquiz.question;
import java.util.*; import java.util.*;
public class MiddleQuestionGenerator implements QuestionGenerator { public class MiddleQuestionGenerator implements QuestionGenerator {
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final char[] OPERATORS = {'+', '-', '*', '/'}; private static final char[] OPERATORS = {'+', '-', '*', '/'};
private static final String[] SPECIAL_OPS = {"^2", "sqrt"}; private static final String[] SPECIAL_OPS = {"^2", "sqrt"};
@ -48,11 +49,16 @@ public class MiddleQuestionGenerator implements QuestionGenerator {
private int calculateNormalAnswer(int num1, int num2, char op) { private int calculateNormalAnswer(int num1, int num2, char op) {
switch (op) { switch (op) {
case '+': return num1 + num2; case '+':
case '-': return num1 - num2; return num1 + num2;
case '*': return num1 * num2; case '-':
case '/': return num1 / num2; return num1 - num2;
default: return 0; case '*':
return num1 * num2;
case '/':
return num1 / num2;
default:
return 0;
} }
} }

@ -3,6 +3,7 @@ package com.student.mathquiz.question;
import java.util.*; import java.util.*;
public class PrimaryQuestionGenerator implements QuestionGenerator { public class PrimaryQuestionGenerator implements QuestionGenerator {
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final char[] OPERATORS = {'+', '-', '*', '/'}; private static final char[] OPERATORS = {'+', '-', '*', '/'};
@ -153,6 +154,25 @@ public class PrimaryQuestionGenerator implements QuestionGenerator {
* *
*/ */
private String calculateAnswer(List<Integer> operands, List<Character> ops) { private String calculateAnswer(List<Integer> operands, List<Character> ops) {
<<<<<<< HEAD
// Simplified calculation for primary school (no operator precedence)
int result = operands.get(0);
for (int i = 0; i < ops.size(); i++) {
int nextOperand = operands.get(i + 1);
switch (ops.get(i)) {
case '+':
result += nextOperand;
break;
case '-':
result -= nextOperand;
break;
case '*':
result *= nextOperand;
break;
case '/':
result /= nextOperand;
break;
=======
List<Integer> nums = new ArrayList<>(operands); List<Integer> nums = new ArrayList<>(operands);
List<Character> operators = new ArrayList<>(ops); List<Character> operators = new ArrayList<>(ops);
@ -169,6 +189,7 @@ public class PrimaryQuestionGenerator implements QuestionGenerator {
operators.remove(i); operators.remove(i);
} else { } else {
i++; i++;
>>>>>>> develop
} }
} }

@ -3,19 +3,29 @@ package com.student.mathquiz.question;
import java.util.List; import java.util.List;
public class Question { public class Question {
private String content; private String content;
private String correctAnswer; private String correctAnswer;
private List<String> options; private List<String> options;
private int correctAnswerIndex; private int correctAnswerIndex;
public Question(String content, String correctAnswer, List<String> options, int correctAnswerIndex) { public Question(String content, String correctAnswer, List<String> options,
int correctAnswerIndex) {
this.content = content; this.content = content;
this.correctAnswer = correctAnswer; this.correctAnswer = correctAnswer;
this.options = options; this.options = options;
this.correctAnswerIndex = correctAnswerIndex; this.correctAnswerIndex = correctAnswerIndex;
} }
public String getContent() { return content; } public String getContent() {
public List<String> getOptions() { return options; } return content;
public int getCorrectAnswerIndex() { return correctAnswerIndex; } }
public List<String> getOptions() {
return options;
}
public int getCorrectAnswerIndex() {
return correctAnswerIndex;
}
} }

@ -3,6 +3,7 @@ package com.student.mathquiz.question;
import com.student.mathquiz.constant.UserType; import com.student.mathquiz.constant.UserType;
public interface QuestionGenerator { public interface QuestionGenerator {
Question generateQuestion(); Question generateQuestion();
static QuestionGenerator getGeneratorByUserType(UserType userType) { static QuestionGenerator getGeneratorByUserType(UserType userType) {

@ -3,11 +3,18 @@ package com.student.mathquiz.service;
import com.student.mathquiz.model.Question; import com.student.mathquiz.model.Question;
public interface IQuizService { public interface IQuizService {
void generateQuiz(String level, int count, String userEmail); void generateQuiz(String level, int count, String userEmail);
Question getCurrentQuestion(); Question getCurrentQuestion();
boolean isLastQuestion(); boolean isLastQuestion();
void submitAnswer(int selectedOptionIndex); void submitAnswer(int selectedOptionIndex);
int calculateScore(); int calculateScore();
int getTotalQuestionCount(); int getTotalQuestionCount();
int getCurrentQuestionIndex(); int getCurrentQuestionIndex();
} }

@ -3,11 +3,22 @@ package com.student.mathquiz.service;
import com.student.mathquiz.model.User; // ★★★ 确保导入了这个类 ★★★ import com.student.mathquiz.model.User; // ★★★ 确保导入了这个类 ★★★
public interface IUserService { public interface IUserService {
boolean sendVerificationCode(String email); boolean sendVerificationCode(String email);
<<<<<<< HEAD
// ★★★ 修改 register 方法,加入 username 参数 ★★★
String register(String username, String email, String code, String password,
String confirmPassword);
// ★★★ 修改 login 方法,参数从 email 改为 identifier (标识符) ★★★
boolean login(String identifier, String password);
=======
String register(String username, String email, String code, String password, String confirmPassword); String register(String username, String email, String code, String password, String confirmPassword);
// ★★★ 修改这里的返回类型 ★★★ // ★★★ 修改这里的返回类型 ★★★
User login(String identifier, String password); User login(String identifier, String password);
>>>>>>> develop
String changePassword(String email, String oldPassword, String newPassword1, String newPassword2); String changePassword(String email, String oldPassword, String newPassword1, String newPassword2);
} }

@ -45,28 +45,33 @@ public class QuizServiceImpl implements IQuizService {
// 每循环20次打印一次日志防止刷屏又能看到进度 // 每循环20次打印一次日志防止刷屏又能看到进度
if (currentTry % 20 == 0) { if (currentTry % 20 == 0) {
System.out.println(" --> 【QuizService】: 循环中... 已尝试 " + currentTry + " 次, 已生成 " + generatedQuestions.size() + " 道题。"); System.out.println(" --> 【QuizService】: 循环中... 已尝试 " + currentTry + " 次, 已生成 "
+ generatedQuestions.size() + " 道题。");
} }
com.student.mathquiz.question.Question newQuestion = generator.generateQuestion();// com.student.mathquiz.question.Question newQuestion = generator.generateQuestion();//
String content = newQuestion.getContent(); String content = newQuestion.getContent();
if (!questionContentSet.contains(content) && !QuestionChecker.isDuplicate(userEmail, content)) { if (!questionContentSet.contains(content) && !QuestionChecker.isDuplicate(userEmail,
content)) {
questionContentSet.add(content); questionContentSet.add(content);
generatedQuestions.add(newQuestion); generatedQuestions.add(newQuestion);
} }
} }
System.out.println(" --> 【QuizService】: 循环结束!最终尝试 " + currentTry + " 次, 成功生成 " + generatedQuestions.size() + " 道题。"); System.out.println(" --> 【QuizService】: 循环结束!最终尝试 " + currentTry + " 次, 成功生成 "
+ generatedQuestions.size() + " 道题。");
if (generatedQuestions.size() < count) { if (generatedQuestions.size() < count) {
System.err.println(" --> 【QuizService】: 警告: 题库不足或重复率过高,只生成了 " + generatedQuestions.size() + " 道题。"); System.err.println(" --> 【QuizService】: 警告: 题库不足或重复率过高,只生成了 "
+ generatedQuestions.size() + " 道题。");
} }
// ... 后面的代码不变 ... // ... 后面的代码不变 ...
this.quizQuestions = new ArrayList<>(); this.quizQuestions = new ArrayList<>();
for (com.student.mathquiz.question.Question q : generatedQuestions) { for (com.student.mathquiz.question.Question q : generatedQuestions) {
this.quizQuestions.add(new Question(q.getContent(), q.getOptions(), q.getCorrectAnswerIndex())); this.quizQuestions.add(
new Question(q.getContent(), q.getOptions(), q.getCorrectAnswerIndex()));
} }
FileSaver.saveQuestions(userEmail, generatedQuestions); FileSaver.saveQuestions(userEmail, generatedQuestions);
@ -78,7 +83,9 @@ public class QuizServiceImpl implements IQuizService {
@Override @Override
public Question getCurrentQuestion() { public Question getCurrentQuestion() {
if (quizQuestions == null || currentQuestionIndex >= quizQuestions.size()) return null; if (quizQuestions == null || currentQuestionIndex >= quizQuestions.size()) {
return null;
}
return quizQuestions.get(currentQuestionIndex); return quizQuestions.get(currentQuestionIndex);
} }
@ -90,23 +97,33 @@ public class QuizServiceImpl implements IQuizService {
@Override @Override
public void submitAnswer(int selectedOptionIndex) { public void submitAnswer(int selectedOptionIndex) {
userAnswers.add(selectedOptionIndex); userAnswers.add(selectedOptionIndex);
if (!isLastQuestion()) currentQuestionIndex++; if (!isLastQuestion()) {
currentQuestionIndex++;
}
} }
@Override @Override
public int calculateScore() { public int calculateScore() {
int correctCount = 0; int correctCount = 0;
for (int i = 0; i < quizQuestions.size(); i++) { for (int i = 0; i < quizQuestions.size(); i++) {
if (i < userAnswers.size() && quizQuestions.get(i).getCorrectAnswerIndex() == userAnswers.get(i)) { if (i < userAnswers.size() && quizQuestions.get(i).getCorrectAnswerIndex() == userAnswers.get(
i)) {
correctCount++; correctCount++;
} }
} }
if (quizQuestions.isEmpty()) return 0; if (quizQuestions.isEmpty()) {
return 0;
}
return (int) ((double) correctCount / quizQuestions.size() * 100); return (int) ((double) correctCount / quizQuestions.size() * 100);
} }
@Override @Override
public int getTotalQuestionCount() { return quizQuestions != null ? quizQuestions.size() : 0; } public int getTotalQuestionCount() {
return quizQuestions != null ? quizQuestions.size() : 0;
}
@Override @Override
public int getCurrentQuestionIndex() { return currentQuestionIndex; } public int getCurrentQuestionIndex() {
return currentQuestionIndex;
}
} }

@ -14,22 +14,27 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class UserServiceImpl implements IUserService { public class UserServiceImpl implements IUserService {
private static final String SENDER_EMAIL = "3048494657@qq.com"; // 你的邮箱 private static final String SENDER_EMAIL = "3048494657@qq.com"; // 你的邮箱
private static final String SENDER_AUTHORIZATION_CODE = "ymnbezwjzrnfdgih"; // 你的授权码 private static final String SENDER_AUTHORIZATION_CODE = "ymnbezwjzrnfdgih"; // 你的授权码
private static final String USER_DATA_PATH = "user_data/users.json"; private static final String USER_DATA_PATH = "user_data/users.json";
private static final Type USER_LIST_TYPE = new TypeToken<List<User>>() {}.getType(); private static final Type USER_LIST_TYPE = new TypeToken<List<User>>() {
}.getType();
private final Map<String, User> userDatabase; private final Map<String, User> userDatabase;
private final Map<String, String> verificationCodes = new ConcurrentHashMap<>(); private final Map<String, String> verificationCodes = new ConcurrentHashMap<>();
public UserServiceImpl() { public UserServiceImpl() {
this.userDatabase = loadUsersFromFile(); this.userDatabase = loadUsersFromFile();
System.out.println("后端日志: 已从 " + USER_DATA_PATH + " 加载 " + userDatabase.size() + " 个用户。"); System.out.println(
"后端日志: 已从 " + USER_DATA_PATH + " 加载 " + userDatabase.size() + " 个用户。");
} }
private Map<String, User> loadUsersFromFile() { private Map<String, User> loadUsersFromFile() {
List<User> userList = JsonUtils.readFromJson(USER_DATA_PATH, USER_LIST_TYPE); List<User> userList = JsonUtils.readFromJson(USER_DATA_PATH, USER_LIST_TYPE);
if (userList == null) return new ConcurrentHashMap<>(); if (userList == null) {
return new ConcurrentHashMap<>();
}
return userList.stream().collect(Collectors.toConcurrentMap(User::getEmail, user -> user)); return userList.stream().collect(Collectors.toConcurrentMap(User::getEmail, user -> user));
} }
@ -40,7 +45,10 @@ public class UserServiceImpl implements IUserService {
@Override @Override
public boolean sendVerificationCode(String recipientEmail) { public boolean sendVerificationCode(String recipientEmail) {
if (recipientEmail == null || !recipientEmail.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$")) return false; if (recipientEmail == null || !recipientEmail.matches(
"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$")) {
return false;
}
String code = String.valueOf(100000 + new Random().nextInt(900000)); String code = String.valueOf(100000 + new Random().nextInt(900000));
verificationCodes.put(recipientEmail, code); verificationCodes.put(recipientEmail, code);
@ -60,33 +68,47 @@ public class UserServiceImpl implements IUserService {
message.setFrom(new InternetAddress(SENDER_EMAIL)); message.setFrom(new InternetAddress(SENDER_EMAIL));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail));
message.setSubject("【数学学习软件】您的注册验证码"); message.setSubject("【数学学习软件】您的注册验证码");
message.setText("尊敬的用户,您好!\n\n您的注册验证码是" + code + "\n\n(此为系统自动发送,请勿回复)"); message.setText(
"尊敬的用户,您好!\n\n您的注册验证码是" + code + "\n\n(此为系统自动发送,请勿回复)");
Transport.send(message); Transport.send(message);
System.out.println("后端日志: 验证码 " + code + " 已成功发送至 " + recipientEmail); System.out.println("后端日志: 验证码 " + code + " 已成功发送至 " + recipientEmail);
return true; return true;
} catch (MessagingException e) { e.printStackTrace(); return false; } } catch (MessagingException e) {
e.printStackTrace();
return false;
}
} }
// 在 UserServiceImpl.java 中 // 在 UserServiceImpl.java 中
// ★★★ 用这个新版本替换掉旧的 register 方法 ★★★ // ★★★ 用这个新版本替换掉旧的 register 方法 ★★★
@Override @Override
public String register(String username, String email, String code, String password, String confirmPassword) { public String register(String username, String email, String code, String password,
String confirmPassword) {
// 1. 新增:校验用户名是否为空 // 1. 新增:校验用户名是否为空
if (username == null || username.trim().isEmpty()) { if (username == null || username.trim().isEmpty()) {
return "用户名不能为空!"; return "用户名不能为空!";
} }
// 2. 新增:校验用户名是否已被占用 // 2. 新增:校验用户名是否已被占用
if (userDatabase.values().stream().anyMatch(user -> user.getUsername().equalsIgnoreCase(username))) { if (userDatabase.values().stream()
.anyMatch(user -> user.getUsername().equalsIgnoreCase(username))) {
return "该用户名已被使用!"; return "该用户名已被使用!";
} }
// 3. 原有的校验逻辑保持不变 // 3. 原有的校验逻辑保持不变
if (userDatabase.containsKey(email)) return "该邮箱已被注册!"; if (userDatabase.containsKey(email)) {
return "该邮箱已被注册!";
}
String storedCode = verificationCodes.get(email); String storedCode = verificationCodes.get(email);
if (storedCode == null || !storedCode.equals(code)) return "验证码错误!"; if (storedCode == null || !storedCode.equals(code)) {
if (!password.equals(confirmPassword)) return "两次输入的密码不一致!"; return "验证码错误!";
if (!password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$")) return "密码必须为6-10位且包含大小写字母和数字"; }
if (!password.equals(confirmPassword)) {
return "两次输入的密码不一致!";
}
if (!password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$")) {
return "密码必须为6-10位且包含大小写字母和数字";
}
// 4. 创建新用户时加入用户名 // 4. 创建新用户时加入用户名
String encryptedPwd = Base64.getEncoder().encodeToString(password.getBytes()); String encryptedPwd = Base64.getEncoder().encodeToString(password.getBytes());
@ -132,7 +154,8 @@ public class UserServiceImpl implements IUserService {
@Override @Override
public String changePassword(String email, String oldPassword, String newPassword1, String newPassword2) { public String changePassword(String email, String oldPassword, String newPassword1,
String newPassword2) {
User user = userDatabase.get(email); User user = userDatabase.get(email);
if (user == null) { if (user == null) {
return "用户不存在!请重新登录。"; // 增加健壮性 return "用户不存在!请重新登录。"; // 增加健壮性

@ -9,10 +9,14 @@ import javafx.scene.control.PasswordField;
public class ChangePasswordController { public class ChangePasswordController {
@FXML private PasswordField oldPasswordField; @FXML
@FXML private PasswordField newPasswordField1; private PasswordField oldPasswordField;
@FXML private PasswordField newPasswordField2; @FXML
@FXML private Label statusLabel; private PasswordField newPasswordField1;
@FXML
private PasswordField newPasswordField2;
@FXML
private Label statusLabel;
private MainApp mainApp; private MainApp mainApp;
private IUserService userService; private IUserService userService;
@ -29,7 +33,8 @@ public class ChangePasswordController {
String newPass2 = newPasswordField2.getText(); String newPass2 = newPasswordField2.getText();
// ★★★ 调用后端的修改密码服务 ★★★ // ★★★ 调用后端的修改密码服务 ★★★
String result = userService.changePassword(mainApp.getCurrentUserEmail(), oldPass, newPass1, newPass2); String result = userService.changePassword(mainApp.getCurrentUserEmail(), oldPass, newPass1,
newPass2);
if (result == null) { // null 代表成功 if (result == null) { // null 代表成功
statusLabel.setText("密码修改成功!请重新登录。"); statusLabel.setText("密码修改成功!请重新登录。");

@ -8,9 +8,13 @@ import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
public class LoginViewController { public class LoginViewController {
@FXML private TextField emailField;
@FXML private PasswordField passwordField; @FXML
@FXML private Label statusLabel; private TextField emailField;
@FXML
private PasswordField passwordField;
@FXML
private Label statusLabel;
private MainApp mainApp; private MainApp mainApp;
private IUserService userService; private IUserService userService;

@ -6,6 +6,7 @@ import javafx.scene.control.TextInputDialog;
import java.util.Optional; import java.util.Optional;
public class MainMenuViewController { public class MainMenuViewController {
private MainApp mainApp; private MainApp mainApp;
public void setMainApp(MainApp mainApp) { public void setMainApp(MainApp mainApp) {

@ -10,15 +10,24 @@ import java.util.List;
public class QuizViewController { public class QuizViewController {
@FXML private Label questionNumberLabel; @FXML
@FXML private Label questionTextLabel; private Label questionNumberLabel;
@FXML private RadioButton option1; @FXML
@FXML private RadioButton option2; private Label questionTextLabel;
@FXML private RadioButton option3; @FXML
@FXML private RadioButton option4; private RadioButton option1;
@FXML private ToggleGroup optionsGroup; @FXML
@FXML private Button submitButton; private RadioButton option2;
@FXML private Label feedbackLabel; @FXML
private RadioButton option3;
@FXML
private RadioButton option4;
@FXML
private ToggleGroup optionsGroup;
@FXML
private Button submitButton;
@FXML
private Label feedbackLabel;
private MainApp mainApp; private MainApp mainApp;
private IQuizService quizService; private IQuizService quizService;
@ -40,9 +49,13 @@ public class QuizViewController {
private void loadQuestion() { private void loadQuestion() {
Question currentQuestion = quizService.getCurrentQuestion(); Question currentQuestion = quizService.getCurrentQuestion();
if (currentQuestion == null) return; if (currentQuestion == null) {
return;
}
questionNumberLabel.setText(String.format("第 %d/%d 题", quizService.getCurrentQuestionIndex() + 1, quizService.getTotalQuestionCount())); questionNumberLabel.setText(
String.format("第 %d/%d 题", quizService.getCurrentQuestionIndex() + 1,
quizService.getTotalQuestionCount()));
questionTextLabel.setText(currentQuestion.getContent()); questionTextLabel.setText(currentQuestion.getContent());
List<String> options = currentQuestion.getOptions(); List<String> options = currentQuestion.getOptions();

@ -9,13 +9,20 @@ import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
public class RegisterViewController { public class RegisterViewController {
// ★★★ 1. 新增 usernameField 的引用 ★★★ // ★★★ 1. 新增 usernameField 的引用 ★★★
@FXML private TextField usernameField; @FXML
@FXML private TextField emailField; private TextField usernameField;
@FXML private TextField codeField; @FXML
@FXML private PasswordField passwordField1; private TextField emailField;
@FXML private PasswordField passwordField2; @FXML
@FXML private Label statusLabel; private TextField codeField;
@FXML
private PasswordField passwordField1;
@FXML
private PasswordField passwordField2;
@FXML
private Label statusLabel;
private MainApp mainApp; private MainApp mainApp;
private IUserService userService; private IUserService userService;
@ -55,7 +62,9 @@ public class RegisterViewController {
try { try {
Thread.sleep(1500); Thread.sleep(1500);
Platform.runLater(mainApp::showLoginView); Platform.runLater(mainApp::showLoginView);
} catch (InterruptedException e) { e.printStackTrace(); } } catch (InterruptedException e) {
e.printStackTrace();
}
}).start(); }).start();
} else { } else {
statusLabel.setText(result); statusLabel.setText(result);

@ -7,7 +7,8 @@ import javafx.scene.control.Label;
public class ScoreViewController { public class ScoreViewController {
@FXML private Label scoreLabel; @FXML
private Label scoreLabel;
private MainApp mainApp; private MainApp mainApp;
public void setMainApp(MainApp mainApp) { public void setMainApp(MainApp mainApp) {

@ -7,22 +7,26 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="15.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.ChangePasswordController"> <VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="15.0"
<children> xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
<Label text="修改密码"> fx:controller="com.student.mathquiz.view.ChangePasswordController">
<font> <children>
<Font name="System Bold" size="24.0" /> <Label text="修改密码">
</font> <font>
</Label> <Font name="System Bold" size="24.0"/>
<PasswordField fx:id="oldPasswordField" prefWidth="250.0" promptText="请输入旧密码" /> </font>
<PasswordField fx:id="newPasswordField1" prefWidth="250.0" promptText="请输入新密码 (6-10位,含大小写字母和数字)" /> </Label>
<PasswordField fx:id="newPasswordField2" prefWidth="250.0" promptText="请再次输入新密码" /> <PasswordField fx:id="oldPasswordField" prefWidth="250.0" promptText="请输入旧密码"/>
<Button onAction="#handleConfirmChange" text="确认修改" /> <PasswordField fx:id="newPasswordField1" prefWidth="250.0"
<Button onAction="#handleBackToMenu" text="返回主菜单" style="-fx-background-color: transparent; -fx-underline: true;" /> promptText="请输入新密码 (6-10位,含大小写字母和数字)"/>
<Label fx:id="statusLabel" textFill="RED" wrapText="true"> <PasswordField fx:id="newPasswordField2" prefWidth="250.0" promptText="请再次输入新密码"/>
<VBox.margin> <Button onAction="#handleConfirmChange" text="确认修改"/>
<Insets top="10.0" /> <Button onAction="#handleBackToMenu" text="返回主菜单"
</VBox.margin> style="-fx-background-color: transparent; -fx-underline: true;"/>
</Label> <Label fx:id="statusLabel" textFill="RED" wrapText="true">
</children> <VBox.margin>
<Insets top="10.0"/>
</VBox.margin>
</Label>
</children>
</VBox> </VBox>

@ -7,20 +7,24 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="20.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.LoginViewController"> <VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="20.0"
<children> xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
<Label text="用户登录"> fx:controller="com.student.mathquiz.view.LoginViewController">
<font> <children>
<Font size="24.0" /> <Label text="用户登录">
</font> <font>
</Label> <Font size="24.0"/>
</font>
</Label>
<!-- ★★★ 修改这里的提示文字 ★★★ --> <!-- ★★★ 修改这里的提示文字 ★★★ -->
<TextField fx:id="emailField" promptText="邮箱 / 用户名" maxWidth="300"/> <TextField fx:id="emailField" promptText="邮箱 / 用户名" maxWidth="300"/>
<PasswordField fx:id="passwordField" promptText="密码" maxWidth="300"/> <PasswordField fx:id="passwordField" promptText="密码" maxWidth="300"/>
<Button onAction="#handleLogin" text="登录" prefWidth="100" /> <Button onAction="#handleLogin" text="登录" prefWidth="100"/>
<Button onAction="#handleGoToRegister" style="-fx-background-color: transparent; -fx-border-color: transparent; -fx-underline: true; -fx-cursor: hand;" text="还没有账户?去注册" /> <Button onAction="#handleGoToRegister"
<Label fx:id="statusLabel" textFill="RED" /> style="-fx-background-color: transparent; -fx-border-color: transparent; -fx-underline: true; -fx-cursor: hand;"
</children> text="还没有账户?去注册"/>
<Label fx:id="statusLabel" textFill="RED"/>
</children>
</VBox> </VBox>

@ -6,24 +6,27 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="20.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.MainMenuViewController"> <VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="20.0"
<children> xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
<Label text="请选择难度"> fx:controller="com.student.mathquiz.view.MainMenuViewController">
<font> <children>
<Font size="24.0" /> <Label text="请选择难度">
</font> <font>
</Label> <Font size="24.0"/>
<Button onAction="#handlePrimary" prefWidth="120.0" text="小学" /> </font>
<Button onAction="#handleMiddle" prefWidth="120.0" text="初中" /> </Label>
<Button onAction="#handleHigh" prefWidth="120.0" text="高中" /> <Button onAction="#handlePrimary" prefWidth="120.0" text="小学"/>
<Button onAction="#handleMiddle" prefWidth="120.0" text="初中"/>
<Button onAction="#handleHigh" prefWidth="120.0" text="高中"/>
<!-- ★★★ 新增的按钮在这里! ★★★ --> <!-- ★★★ 新增的按钮在这里! ★★★ -->
<Button onAction="#handleChangePassword" prefWidth="120.0" text="修改密码"> <Button onAction="#handleChangePassword" prefWidth="120.0" text="修改密码">
<VBox.margin> <VBox.margin>
<Insets top="20.0" /> <Insets top="20.0"/>
</VBox.margin> </VBox.margin>
</Button> </Button>
<Button onAction="#handleLogout" prefWidth="120.0" text="退出登录" style="-fx-background-color: #f0f0f0;" /> <Button onAction="#handleLogout" prefWidth="120.0" text="退出登录"
style="-fx-background-color: #f0f0f0;"/>
</children> </children>
</VBox> </VBox>

@ -9,34 +9,51 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.QuizViewController"> <AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.QuizViewController">
<!-- ★★★ 关键修改:把 ToggleGroup 的定义放在这里!★★★ --> <!-- ★★★ 关键修改:把 ToggleGroup 的定义放在这里!★★★ -->
<!-- ★★★ 它不属于任何布局,只是一个在后台工作的逻辑对象 ★★★ --> <!-- ★★★ 它不属于任何布局,只是一个在后台工作的逻辑对象 ★★★ -->
<fx:define> <fx:define>
<ToggleGroup fx:id="optionsGroup" /> <ToggleGroup fx:id="optionsGroup"/>
</fx:define> </fx:define>
<VBox alignment="TOP_CENTER" layoutX="50.0" layoutY="50.0" prefHeight="300.0" prefWidth="500.0" spacing="15.0"> <VBox alignment="TOP_CENTER" layoutX="50.0" layoutY="50.0" prefHeight="300.0" prefWidth="500.0"
<Label fx:id="questionNumberLabel" text="第 1/10 题"> spacing="15.0">
<font><Font name="System Bold" size="18.0" /></font> <Label fx:id="questionNumberLabel" text="第 1/10 题">
</Label> <font>
<Label fx:id="questionTextLabel" alignment="CENTER" text="这里是题干1 + 1 = ?" wrapText="true"> <Font name="System Bold" size="18.0"/>
<font><Font size="20.0" /></font> </font>
<VBox.margin><Insets bottom="10.0" /></VBox.margin> </Label>
</Label> <Label fx:id="questionTextLabel" alignment="CENTER" text="这里是题干1 + 1 = ?" wrapText="true">
<VBox fx:id="optionsBox" alignment="CENTER_LEFT" prefWidth="100.0" spacing="10.0"> <font>
<padding><Insets left="150.0" /></padding> <Font size="20.0"/>
</font>
<VBox.margin>
<Insets bottom="10.0"/>
</VBox.margin>
</Label>
<VBox fx:id="optionsBox" alignment="CENTER_LEFT" prefWidth="100.0" spacing="10.0">
<padding>
<Insets left="150.0"/>
</padding>
<!-- ★★★ RadioButton 像以前一样,通过 $optionsGroup 引用它 ★★★ --> <!-- ★★★ RadioButton 像以前一样,通过 $optionsGroup 引用它 ★★★ -->
<RadioButton fx:id="option1" mnemonicParsing="false" text="选项 A" toggleGroup="$optionsGroup" /> <RadioButton fx:id="option1" mnemonicParsing="false" text="选项 A"
<RadioButton fx:id="option2" mnemonicParsing="false" text="选项 B" toggleGroup="$optionsGroup" /> toggleGroup="$optionsGroup"/>
<RadioButton fx:id="option3" mnemonicParsing="false" text="选项 C" toggleGroup="$optionsGroup" /> <RadioButton fx:id="option2" mnemonicParsing="false" text="选项 B"
<RadioButton fx:id="option4" mnemonicParsing="false" text="选项 D" toggleGroup="$optionsGroup" /> toggleGroup="$optionsGroup"/>
</VBox> <RadioButton fx:id="option3" mnemonicParsing="false" text="选项 C"
<Button fx:id="submitButton" mnemonicParsing="false" onAction="#handleSubmitAnswer" text="提交/下一题" /> toggleGroup="$optionsGroup"/>
<Label fx:id="feedbackLabel" textFill="RED"> <RadioButton fx:id="option4" mnemonicParsing="false" text="选项 D"
<font><Font name="System Bold" size="14.0" /></font> toggleGroup="$optionsGroup"/>
</Label>
</VBox> </VBox>
<Button fx:id="submitButton" mnemonicParsing="false" onAction="#handleSubmitAnswer"
text="提交/下一题"/>
<Label fx:id="feedbackLabel" textFill="RED">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
</VBox>
</AnchorPane> </AnchorPane>

@ -8,28 +8,33 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="15.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.RegisterViewController"> <VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="15.0"
<children> xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
<Label text="用户注册"> fx:controller="com.student.mathquiz.view.RegisterViewController">
<font> <children>
<Font size="24.0" /> <Label text="用户注册">
</font> <font>
</Label> <Font size="24.0"/>
</font>
</Label>
<!-- ★★★ 新增的用户名字段 ★★★ --> <!-- ★★★ 新增的用户名字段 ★★★ -->
<TextField fx:id="usernameField" promptText="用户名" maxWidth="300"/> <TextField fx:id="usernameField" promptText="用户名" maxWidth="300"/>
<HBox alignment="CENTER" maxWidth="300" spacing="10"> <HBox alignment="CENTER" maxWidth="300" spacing="10">
<children> <children>
<TextField fx:id="emailField" promptText="邮箱" HBox.hgrow="ALWAYS" /> <TextField fx:id="emailField" promptText="邮箱" HBox.hgrow="ALWAYS"/>
<Button onAction="#handleSendCode" text="发送验证码" /> <Button onAction="#handleSendCode" text="发送验证码"/>
</children> </children>
</HBox> </HBox>
<TextField fx:id="codeField" promptText="验证码" maxWidth="300"/> <TextField fx:id="codeField" promptText="验证码" maxWidth="300"/>
<PasswordField fx:id="passwordField1" promptText="设置密码 (6-10位,含大小写字母和数字)" maxWidth="300"/> <PasswordField fx:id="passwordField1" promptText="设置密码 (6-10位,含大小写字母和数字)"
<PasswordField fx:id="passwordField2" promptText="确认密码" maxWidth="300"/> maxWidth="300"/>
<Button onAction="#handleRegister" text="注册" prefWidth="100" /> <PasswordField fx:id="passwordField2" promptText="确认密码" maxWidth="300"/>
<Button onAction="#handleGoToLogin" style="-fx-background-color: transparent; -fx-border-color: transparent; -fx-underline: true; -fx-cursor: hand;" text="已有账户?去登录" /> <Button onAction="#handleRegister" text="注册" prefWidth="100"/>
<Label fx:id="statusLabel" textFill="RED" wrapText="true" /> <Button onAction="#handleGoToLogin"
</children> style="-fx-background-color: transparent; -fx-border-color: transparent; -fx-underline: true; -fx-cursor: hand;"
text="已有账户?去登录"/>
<Label fx:id="statusLabel" textFill="RED" wrapText="true"/>
</children>
</VBox> </VBox>

@ -1,3 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" /> <BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"/>

@ -2,15 +2,21 @@
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.ScoreViewController"> <AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17"
<VBox alignment="CENTER" layoutX="200.0" layoutY="100.0" prefHeight="200.0" prefWidth="200.0" spacing="20.0"> xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.student.mathquiz.view.ScoreViewController">
<Label text="测试完成!"> <VBox alignment="CENTER" layoutX="200.0" layoutY="100.0" prefHeight="200.0" prefWidth="200.0"
<font><Font name="System Bold" size="24.0" /></font> spacing="20.0">
</Label> <Label text="测试完成!">
<Label fx:id="scoreLabel" text="你的分数是: 100"> <font>
<font><Font size="18.0" /></font> <Font name="System Bold" size="24.0"/>
</Label> </font>
<Button onAction="#handleTryAgain" text="再做一组" /> </Label>
<Button onAction="#handleExit" text="退出程序" /> <Label fx:id="scoreLabel" text="你的分数是: 100">
</VBox> <font>
<Font size="18.0"/>
</font>
</Label>
<Button onAction="#handleTryAgain" text="再做一组"/>
<Button onAction="#handleExit" text="退出程序"/>
</VBox>
</AnchorPane> </AnchorPane>

@ -10,20 +10,23 @@ import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
public class FileSaver { public class FileSaver {
public static void saveQuestions(String username, List<Question> questions) {
File dir = new File(username);
if (!dir.exists()) dir.mkdirs();
String filename = LocalDateTime.now().format( public static void saveQuestions(String username, List<Question> questions) {
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt"; File dir = new File(username);
if (!dir.exists()) {
dir.mkdirs();
}
String filename = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
try (FileWriter writer = new FileWriter(new File(dir, filename))) { try (FileWriter writer = new FileWriter(new File(dir, filename))) {
for (int i = 0; i < questions.size(); i++) { for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i).getQuestion() + "\n\n"); writer.write((i + 1) + ". " + questions.get(i).getQuestion() + "\n\n");
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
}
System.out.println("题目已生成并保存!");
} }
System.out.println("题目已生成并保存!");
}
} }

@ -5,39 +5,46 @@ import java.io.FileNotFoundException;
import java.util.Scanner; import java.util.Scanner;
public class QuestionChecker { public class QuestionChecker {
public static boolean isDuplicate(String username, String question) {
File dir = new File(username); public static boolean isDuplicate(String username, String question) {
if (!dir.exists()) return false; File dir = new File(username);
if (!dir.exists()) {
File[] files = dir.listFiles();/*每次调用都获取一次文件列表*/ return false;
if (files == null) return false; }
// 待查题目去掉空格,方便精确匹配 File[] files = dir.listFiles();/*每次调用都获取一次文件列表*/
String target = question.trim(); if (files == null) {
return false;
for (File file : files) {/* 遍历所有历史文件 */ }
if (file.isFile() && file.getName().endsWith(".txt")) {
try (Scanner scanner = new Scanner(file)) { // 待查题目去掉空格,方便精确匹配
while (scanner.hasNextLine()) { String target = question.trim();
String line = scanner.nextLine().trim(); /*遍历文件中的每一行 */
for (File file : files) {/* 遍历所有历史文件 */
if (line.isEmpty()) continue;// 跳过空行 if (file.isFile() && file.getName().endsWith(".txt")) {
try (Scanner scanner = new Scanner(file)) {
// 去掉题号部分(如 "1. " while (scanner.hasNextLine()) {
int dotIndex = line.indexOf('.'); String line = scanner.nextLine().trim(); /*遍历文件中的每一行 */
if (dotIndex != -1) {
String storedQuestion = line.substring(dotIndex + 1).trim(); if (line.isEmpty()) {
// 精确匹配 continue;// 跳过空行
if (storedQuestion.equals(target)) { }
return true;
} // 去掉题号部分(如 "1. "
} int dotIndex = line.indexOf('.');
} if (dotIndex != -1) {
} catch (FileNotFoundException e) { String storedQuestion = line.substring(dotIndex + 1).trim();
e.printStackTrace(); // 精确匹配
} if (storedQuestion.equals(target)) {
return true;
}
} }
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} }
return false; }
} }
return false;
}
} }

@ -3,24 +3,25 @@ package question;
import java.util.Random; import java.util.Random;
public class HighQuestionGenerator implements QuestionGenerator { public class HighQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
@Override private static final Random random = new Random();
public Question generateQuestion() { private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
Question q = primaryGenerator.generateQuestion();
String[] funcs = {"sin", "cos", "tan"}; @Override
String func = funcs[random.nextInt(funcs.length)]; public Question generateQuestion() {
return new Question(func + "(" + q.getQuestion() + ")", evaluate(func, q.getQuestion())); Question q = primaryGenerator.generateQuestion();
} String[] funcs = {"sin", "cos", "tan"};
String func = funcs[random.nextInt(funcs.length)];
return new Question(func + "(" + q.getQuestion() + ")", evaluate(func, q.getQuestion()));
}
private String evaluate(String func, String expr) { private String evaluate(String func, String expr) {
try { try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager(); javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript"); javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval("Math." + func + "(Math.toRadians(" + expr + "))")); return String.valueOf(engine.eval("Math." + func + "(Math.toRadians(" + expr + "))"));
} catch (Exception e) { } catch (Exception e) {
return "?"; return "?";
}
} }
}
} }

@ -3,26 +3,27 @@ package question;
import java.util.Random; import java.util.Random;
public class MiddleQuestionGenerator implements QuestionGenerator { public class MiddleQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
@Override private static final Random random = new Random();
public Question generateQuestion() { private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
Question q = primaryGenerator.generateQuestion();
if (random.nextBoolean()) { @Override
return new Question(q.getQuestion() + " ^ 2", evaluate(q.getQuestion() + "^2")); public Question generateQuestion() {
} else { Question q = primaryGenerator.generateQuestion();
return new Question("√" + q.getQuestion(), evaluate("Math.sqrt(" + q.getQuestion() + ")")); if (random.nextBoolean()) {
} return new Question(q.getQuestion() + " ^ 2", evaluate(q.getQuestion() + "^2"));
} else {
return new Question("√" + q.getQuestion(), evaluate("Math.sqrt(" + q.getQuestion() + ")"));
} }
}
private String evaluate(String expr) { private String evaluate(String expr) {
try { try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager(); javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript"); javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval(expr.replace("^", "**"))); return String.valueOf(engine.eval(expr.replace("^", "**")));
} catch (Exception e) { } catch (Exception e) {
return "?"; return "?";
}
} }
}
} }

@ -3,63 +3,80 @@ package question;
import java.util.Random; import java.util.Random;
public class PrimaryQuestionGenerator implements QuestionGenerator { public class PrimaryQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
@Override private static final Random random = new Random();
public Question generateQuestion() {
int numOperands = random.nextInt(5) + 1;
StringBuilder sb = new StringBuilder();
int currentValue = random.nextInt(100) + 1;
sb.append(currentValue);
for (int i = 1; i < numOperands; i++) { @Override
String[] ops = {"+", "-", "*", "/"}; public Question generateQuestion() {
String op; int numOperands = random.nextInt(5) + 1;
int nextVal; StringBuilder sb = new StringBuilder();
do { int currentValue = random.nextInt(100) + 1;
op = ops[random.nextInt(ops.length)]; sb.append(currentValue);
nextVal = random.nextInt(100) + 1;
} while ((op.equals("-") && currentValue < nextVal) ||
(op.equals("/") && (nextVal == 0 || currentValue % nextVal != 0)));
sb.append(" ").append(op).append(" ").append(nextVal); for (int i = 1; i < numOperands; i++) {
switch (op) { String[] ops = {"+", "-", "*", "/"};
case "+": currentValue += nextVal; break; String op;
case "-": currentValue -= nextVal; break; int nextVal;
case "*": currentValue *= nextVal; break; do {
case "/": currentValue /= nextVal; break; op = ops[random.nextInt(ops.length)];
} nextVal = random.nextInt(100) + 1;
} } while ((op.equals("-") && currentValue < nextVal) ||
(op.equals("/") && (nextVal == 0 || currentValue % nextVal != 0)));
String expr = addParentheses(sb.toString()); sb.append(" ").append(op).append(" ").append(nextVal);
return new Question(expr, evaluate(expr)); switch (op) {
case "+":
currentValue += nextVal;
break;
case "-":
currentValue -= nextVal;
break;
case "*":
currentValue *= nextVal;
break;
case "/":
currentValue /= nextVal;
break;
}
} }
private String addParentheses(String expr) { String expr = addParentheses(sb.toString());
String[] tokens = expr.split(" "); return new Question(expr, evaluate(expr));
if (tokens.length < 3) return expr; }
int start = random.nextInt(tokens.length / 2) * 2; private String addParentheses(String expr) {
int end = start + 2 + random.nextInt((tokens.length - start) / 2) * 2; String[] tokens = expr.split(" ");
end = Math.min(end, tokens.length - 1); if (tokens.length < 3) {
return expr;
}
int start = random.nextInt(tokens.length / 2) * 2;
int end = start + 2 + random.nextInt((tokens.length - start) / 2) * 2;
end = Math.min(end, tokens.length - 1);
StringBuilder newExpr = new StringBuilder(); StringBuilder newExpr = new StringBuilder();
for (int i = 0; i < tokens.length; i++) { for (int i = 0; i < tokens.length; i++) {
if (i == start) newExpr.append("("); if (i == start) {
newExpr.append(tokens[i]); newExpr.append("(");
if (i == end) newExpr.append(")"); }
if (i != tokens.length - 1) newExpr.append(" "); newExpr.append(tokens[i]);
} if (i == end) {
return newExpr.toString(); newExpr.append(")");
}
if (i != tokens.length - 1) {
newExpr.append(" ");
}
} }
return newExpr.toString();
}
private String evaluate(String expr) { private String evaluate(String expr) {
try { try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager(); javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript"); javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval(expr)); return String.valueOf(engine.eval(expr));
} catch (Exception e) { } catch (Exception e) {
return "?"; return "?";
}
} }
}
} }

@ -1,20 +1,21 @@
package question; package question;
public class Question { public class Question {
private String question;
private String answer;
public Question(String question, String answer) { private String question;
this.question = question; private String answer;
this.answer = answer;
}
public String getQuestion() { public Question(String question, String answer) {
return question; this.question = question;
} this.answer = answer;
}
public String getAnswer() { public String getQuestion() {
return answer; return question;
} }
public String getAnswer() {
return answer;
}
} }

@ -1,5 +1,6 @@
package question; package question;
public interface QuestionGenerator { public interface QuestionGenerator {
Question generateQuestion();
Question generateQuestion();
} }
Loading…
Cancel
Save