Compare commits

..

1 Commits
main ... mfq

Author SHA1 Message Date
fangqiongMa 95cdd5636f feat:上传代码到mfq分支
4 months ago

25
.gitignore vendored

@ -1,23 +1,7 @@
<<<<<<< HEAD
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
=======
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
>>>>>>> e6095b163ace92284def92e1eb7e1fc6439a1814
### Eclipse ###
.apt_generated
@ -27,12 +11,9 @@ out/
.settings
.springBeans
.sts4-cache
<<<<<<< HEAD
=======
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
>>>>>>> e6095b163ace92284def92e1eb7e1fc6439a1814
### NetBeans ###
/nbproject/private/
@ -40,12 +21,6 @@ bin/
/dist/
/nbdist/
/.nb-gradle/
<<<<<<< HEAD
build/
!**/src/main/**/build/
!**/src/test/**/build/
=======
>>>>>>> e6095b163ace92284def92e1eb7e1fc6439a1814
### VS Code ###
.vscode/

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

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

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

@ -1,12 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="corretto-1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="19.0" project-jdk-type="JavaSDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/MyMathQuiz_UI.iml" filepath="$PROJECT_DIR$/MyMathQuiz_UI.iml" />
</modules>
</component>
</project>

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -1,150 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<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" />
<<<<<<< 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/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/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$/user_data/users.json" beforeDir="false" afterPath="$PROJECT_DIR$/user_data/users.json" afterDir="false" />
>>>>>>> develop
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="33xNvNV8fXz57AI79RP4IvSV0wL" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</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">{
&quot;keyToString&quot;: {
&quot;Maven.mathquiz [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.mathquiz [compile].executor&quot;: &quot;Run&quot;,
&quot;Maven.mathquiz [org.openjfx:javafx-maven-plugin:0.0.8:run].executor&quot;: &quot;Run&quot;,
&quot;Maven.mathquiz [package].executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;develop&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/lyq20/OneDrive/桌面/Finally/MathQuiz&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
>>>>>>> develop
}
}</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-28b599e66164-intellij.indexing.shared.core-IU-242.23339.11" />
<option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-IU-242.23339.11" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="72b0954a-217b-4c2e-9f54-fc09e16134d2" name="更改" comment="" />
<created>1760254993744</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1760254993744</updated>
<workItem from="1760254996651" duration="1905000" />
<workItem from="1760258236771" duration="2172000" />
<workItem from="1760260471312" duration="41000" />
<workItem from="1760269808557" duration="303000" />
<workItem from="1760270394012" duration="2320000" />
<workItem from="1760274631654" duration="784000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.student</groupId>
<artifactId>mathquiz</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>${main.class}</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>module-info.class</exclude>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>17.0.2</javafx.version>
<maven.compiler.target>17</maven.compiler.target>
<main.class>com.student.mathquiz.Launcher</main.class>
<maven.compiler.source>17</maven.compiler.source>
</properties>
</project>

@ -1,128 +0,0 @@
> # 小初高数学学习软件 (Math Learning Software for K-12)
>
> 这是一个专为小学、初中、高中学生设计的桌面数学学习与测验软件。本项目作为软件工程导论课程的结对编程项目,旨在提供一个用户友好、功能完善的图形化学习工具,帮助学生通过练习来巩固数学知识。
>
> 本软件实现了前后端完全分离的架构,前端负责界面展示与用户交互,后端负责业务逻辑、数据处理与持久化,最终通过 `jpackage` 工具打包成一个免安装的 Windows 可执行文件。
>
> ---
>
> ## ✨ 功能特性 (Features)
>
> - **用户注册**:通过邮箱验证码进行安全注册,确保用户身份的真实性。
> - **双重登录方式**:支持 **邮箱** 或 **用户名** 登录,为用户提供便利。
> - **密码管理**:支持用户在登录状态下修改密码,所有密码均经过加密存储,保障账户安全。
> - **分级题库**:内置 **小学**、**初中**、**高中** 三个不同难度的数学题库,题目类型符合对应学段的教学大纲。
> - **动态生成试卷**:用户可自定义需要生成的题目数量,系统会根据所选难度,动态生成一张不含重复题目的试卷。
> - **交互式答题界面**:清爽的图形化界面,以单项选择题的形式呈现,用户可以方便地进行选择和提交。
> - **自动评分**:所有题目回答完毕后,系统会立即自动计算并显示本次测验的分数。
> - **数据持久化**:用户信息与用户的历史题目记录均通过本地 JSON 和文本文件进行存储(遵循项目要求,不使用数据库),程序关闭后数据不会丢失。
>
> ---
>
> ## 🛠️ 技术栈 (Technology Stack)
>
> - **前端 (Frontend)**: JavaFX, FXML
> - **后端 (Backend)**: Java 17
> - **构建工具 (Build Tool)**: Apache Maven
> - **打包工具 (Packaging)**: JDK `jpackage`
> - **依赖库 (Libraries)**:
> - `Gson`: 用于处理用户数据的 JSON 序列化与反序列化。
> - `Jakarta Mail`: 用于实现注册时的邮件验证码功能。
>
> ---
>
> ## 🚀 如何运行 (Getting Started)
>
> ### 面向开发者 (For Developers)
>
> 1. **环境准备**:
> - JDK 17 或更高版本
> - Apache Maven 3.6+
> - IntelliJ IDEA
>
> 2. **克隆项目**:
> ```bash
> git clone https://bdgit.educoder.net/hnu202326010416/MyMathQuiz_UI.git
> cd MyMathQuiz_UI
> ```
>
> 3. **配置邮箱服务**:
>
> - **重要**: 请修改 `src/main/java/com/student/mathquiz/service/UserServiceImpl.java` 文件中的 `SENDER_EMAIL``SENDER_AUTHORIZATION_CODE` 为你自己的发件人邮箱和授权码。
>
> 4. **构建项目**:
> ```bash
> # 生成包含所有依赖的 "胖JAR"(位于 target 目录下)
> mvn clean package
> ```
>
> 5. **运行 JAR 文件**:
> ```bash
> # 构建成功后,在项目根目录执行以下命令运行 JAR
> java -jar target/mathquiz-1.0-SNAPSHOT.jar
> ```
>
> 6. **在IDE中运行**:
> ```bash
> # 直接通过 Maven 插件运行主程序
> mvn javafx:run
> ```
>
> ---
>
> ### 面向普通用户 (For Users)
>
> #### 方式一:直接运行 EXE推荐
> 找到打包后生成的 数学学习软件 文件夹。
> 双击文件夹内的 数学学习软件.exe 即可直接运行。
> 程序所需的数据文件(如 users.json会自动在您的用户主目录下的 .mathquiz 文件夹中创建。
>
> #### 方式二:运行 JAR 文件
> 如果您已经安装了 JDK 17 或更高版本,可以直接运行 JAR 文件:
> 1. 确保您的电脑已安装 [JDK 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)。
> 2. 打开命令行工具Windows 上是 CMD 或 PowerShell
> 3. 进入 JAR 文件所在目录。
> 4. 执行以下命令:
> ```bash
> java -jar mathquiz-1.0-SNAPSHOT.jar
> ```
> 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,20 +0,0 @@
1. 73 * 12 = ?
2. 576 / 64 = ?
3. 27 + 49 - 29 = ?
4. 57 / 19 = ?
5. 94 * 82 = ?
6. 152 / 19 - 7 = ?
7. 305 / 61 = ?
8. 85 + 12 * 77 = ?
9. 38 * 49 = ?
10. 540 / 60 = ?

@ -1,20 +0,0 @@
1. 34 - 18 = ?
2. 192 / 96 * 14 = ?
3. 89 + 52 - 4 = ?
4. 85 - 75 = ?
5. 41 - 460 / 46 = ?
6. 15 + 2 + 95 = ?
7. 89 * 99 = ?
8. 51 - 22 = ?
9. 25 * 43 * 2 = ?
10. 99 - 5 - 18 = ?

@ -1,20 +0,0 @@
1. 94 * 92 = ?
2. 85 + 28 * 3 = ?
3. 5 - 1 * 81 = ?
4. 315 / 45 * 70 = ?
5. 57 - 5 - 60 = ?
6. 488 / 61 = ?
7. 94 * 91 * 12 = ?
8. 88 + 59 * 4 = ?
9. 62 * 79 = ?
10. 92 + 61 + 27 = ?

@ -1,20 +0,0 @@
1. sqrt(9) / 3 = ?
2. sqrt(81) + 5 = ?
3. sqrt(36) + 3 = ?
4. 2^2 + 6 = ?
5. sqrt(25) * 3 = ?
6. 2^2 / 2 = ?
7. sqrt(4) / 2 = ?
8. sqrt(36) + 9 = ?
9. 3^2 / 1 = ?
10. sqrt(4) * 10 = ?

@ -1,20 +0,0 @@
1. sqrt(16) / 4 = ?
2. sqrt(25) / 1 = ?
3. sqrt(100) * 10 = ?
4. sqrt(9) / 1 = ?
5. sqrt(49) - 6 = ?
6. 16^2 / 4 = ?
7. sqrt(36) + 7 = ?
8. 12^2 + 10 = ?
9. 15^2 + 4 = ?
10. 15^2 + 9 = ?

@ -1,20 +0,0 @@
1. 47 + 77 * 28 = ?
2. 11 - 20 / 5 = ?
3. 56 + 27 = ?
4. 8 - 22 - 74 = ?
5. 54 * 92 + 10 = ?
6. 10 * 83 = ?
7. 87 + 58 = ?
8. 388 / 97 = ?
9. 10 + 100 * 42 = ?
10. 56 + 32 = ?

@ -1,20 +0,0 @@
1. sqrt(36) / 2 = ?
2. sqrt(9) + 5 = ?
3. sqrt(9) * 4 = ?
4. 11^2 - 10 = ?
5. 9^2 + 10 = ?
6. 18^2 / 3 = ?
7. sqrt(1) * 8 = ?
8. 9^2 * 1 = ?
9. 4^2 * 4 = ?
10. sqrt(225) / 5 = ?

@ -1,18 +0,0 @@
1. tan(45°) = ?
2. sin(30°) = ?
3. sin(45°) = ?
4. sin(60°) = ?
5. cos(30°) = ?
6. cos(60°) = ?
7. tan(60°) = ?
8. cos(45°) = ?
9. tan(30°) = ?

@ -1,20 +0,0 @@
1. 14^2 - 7 = ?
2. 7^2 + 1 = ?
3. sqrt(16) + 7 = ?
4. 12^2 * 9 = ?
5. sqrt(16) - 2 = ?
6. sqrt(1) / 1 = ?
7. 8^2 * 8 = ?
8. 19^2 - 7 = ?
9. 12^2 / 3 = ?
10. 17^2 + 1 = ?

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.student</groupId>
<artifactId>mathquiz</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<javafx.version>17.0.2</javafx.version>
<!-- ★★★ 关键修改1把主类指向新的启动器 ★★★ -->
<main.class>com.student.mathquiz.Launcher</main.class>
</properties>
<dependencies>
<!-- ... 你的 dependencies 部分保持不变 ... -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<!-- ★★★ 这里的 mainClass 会自动使用上面 properties 里的新值 ★★★ -->
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!-- ★★★ 关键修改2这里的 mainClass 也会自动使用新值 ★★★ -->
<mainClass>${main.class}</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>module-info.class</exclude>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

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

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

@ -1,31 +0,0 @@
package account;
import java.util.HashMap;
import java.util.Map;
public class SimpleAccountService implements AccountService {
private static final Map<String, Account> accounts = new HashMap<>();
static {
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
public Account login(String username, String password) {
Account account = accounts.get(username);
if (account != null && account.getPassword().equals(password)) {
return account;
}
return null;
}
}

@ -0,0 +1,137 @@
package com.student.mathquiz;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.quiz.Quiz;
import com.student.mathquiz.quiz.QuizService;
import com.student.mathquiz.user.User;
import com.student.mathquiz.user.UserFileService;
import com.student.mathquiz.user.UserService;
import java.util.Scanner;
/**
*
*/
public class Main {
private static final Scanner SCANNER = new Scanner(System.in);
private static final UserService USER_SERVICE = new UserFileService();
private static final QuizService QUIZ_SERVICE = new QuizService();
public static void main(String[] args) {
System.out.println("=== 小初高数学学习软件(后端测试)===");
while (true) {
System.out.println("\n1. 注册 2. 登录 3. 退出");
System.out.print("请选择操作:");
String choice = SCANNER.nextLine();
switch (choice) {
case "1":
testRegister(); // 测试注册
break;
case "2":
testLoginAndQuiz(); // 测试登录+生成试卷+答题
break;
case "3":
System.out.println("程序退出!");
SCANNER.close();
System.exit(0);
default:
System.out.println("请选择1-3");
}
}
}
// 测试注册逻辑
private static void testRegister() {
System.out.println("\n=== 注册 ===");
System.out.print("请输入邮箱:");
String email = SCANNER.nextLine();
// 发送验证码
USER_SERVICE.sendVerifyCode(email);
System.out.print("请输入验证码:");
String verifyCode = SCANNER.nextLine();
// 选择学段
System.out.print("请选择学段(小学/初中/高中):");
String typeStr = SCANNER.nextLine();
UserType userType = UserType.fromDisplayName(typeStr);
if (userType == null) {
System.out.println("学段选择错误!");
return;
}
// 设置密码
System.out.print("请输入密码6-10位含大小写+数字):");
String password = SCANNER.nextLine();
System.out.print("请再次输入密码:");
String confirmPwd = SCANNER.nextLine();
if (!password.equals(confirmPwd)) {
System.out.println("两次密码不一致!");
return;
}
// 执行注册
boolean registerSuccess = USER_SERVICE.register(email, verifyCode, password, userType);
System.out.println(registerSuccess ? "注册成功!" : "注册失败!");
}
// 测试登录+生成试卷+答题
private static void testLoginAndQuiz() {
System.out.println("\n=== 登录 ===");
System.out.print("请输入邮箱:");
String email = SCANNER.nextLine();
System.out.print("请输入密码:");
String password = SCANNER.nextLine();
// 执行登录
User user = USER_SERVICE.login(email, password);
if (user == null) {
return; // 登录失败,返回
}
// 生成试卷
System.out.print("\n请输入题目数量10-30");
int questionCount = Integer.parseInt(SCANNER.nextLine());
Quiz quiz;
try {
quiz = QUIZ_SERVICE.generateQuiz(user, questionCount);
System.out.println("试卷生成成功!共" + questionCount + "道题。");
} catch (Exception e) {
System.out.println("试卷生成失败:" + e.getMessage());
return;
}
// 模拟答题
System.out.println("\n=== 开始答题 ===");
for (int i = 0; i < quiz.getQuestions().size(); i++) {
var question = quiz.getQuestions().get(i);
System.out.println("\n第" + (i + 1) + "题:" + question.getContent());
System.out.println("选项:");
for (int j = 0; j < question.getOptions().size(); j++) {
System.out.println(j + ". " + question.getOptions().get(j));
}
System.out.print("请选择选项0-3");
int optionIndex = Integer.parseInt(SCANNER.nextLine());
// 提交答案
QUIZ_SERVICE.submitAnswer(quiz, i, optionIndex);
}
// 计算分数
int score = QUIZ_SERVICE.calculateQuizScore(quiz);
System.out.println("\n=== 答题结束 ===");
System.out.println("你的分数:" + score + "分(百分制)");
// 测试修改密码
System.out.print("\n是否修改密码y/n");
String changePwdChoice = SCANNER.nextLine();
if ("y".equalsIgnoreCase(changePwdChoice)) {
System.out.print("请输入原密码:");
String oldPwd = SCANNER.nextLine();
System.out.print("请输入新密码:");
String newPwd = SCANNER.nextLine();
boolean changeSuccess = USER_SERVICE.changePassword(email, oldPwd, newPwd);
System.out.println(changeSuccess ? "密码修改成功!" : "密码修改失败!");
}
}
}

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

@ -0,0 +1,5 @@
package com.student.mathquiz.account;
public interface AccountService {
Account login(String username, String password);
}

@ -0,0 +1,30 @@
package com.student.mathquiz.account;
import java.util.HashMap;
import java.util.Map;
public class SimpleAccountService implements AccountService {
private static final Map<String, Account> accounts = new HashMap<>();
static {
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
public Account login(String username, String password) {
Account account = accounts.get(username);
if (account != null && account.getPassword().equals(password)) {
return account;
}
return null;
}
}

@ -0,0 +1,18 @@
package com.student.mathquiz.constant;
public class Constants {
// 密码规则6-10位含大小写字母+数字
public static final int PASSWORD_MIN_LENGTH = 6; // 最小长度
public static final int PASSWORD_MAX_LENGTH = 10; // 最大长度
public static final String PASSWORD_REGEX = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{" + PASSWORD_MIN_LENGTH + "," + PASSWORD_MAX_LENGTH + "}$";
// 题目数量规则10-30道
public static final int QUESTION_MIN_COUNT = 10;
public static final int QUESTION_MAX_COUNT = 30;
// 用户数据存储路径JSON文件
public static final String USER_DATA_PATH = "user_data/users.json";
// 验证码长度(模拟)
public static final int VERIFY_CODE_LENGTH = 6;
}

@ -0,0 +1,28 @@
package com.student.mathquiz.constant;
public enum UserType {
PRIMARY("小学"), // 小学
JUNIOR("初中"), // 初中
SENIOR("高中"); // 高中
private final String displayName; // 界面显示名称
UserType(String displayName) {
this.displayName = displayName;
}
// 根据显示名称获取枚举(用于前端交互)
public static UserType fromDisplayName(String displayName) {
for (UserType type : UserType.values()) {
if (type.displayName.equalsIgnoreCase(displayName)) {
return type;
}
}
return null;
}
// 获取界面显示名称
public String getDisplayName() {
return displayName;
}
}

@ -0,0 +1,29 @@
package com.student.mathquiz.persistence;
import com.student.mathquiz.question.Question;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
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(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
try (FileWriter writer = new FileWriter(new File(dir, filename))) {
for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i).getContent() + "\n\n");
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("题目已生成并保存!");
}
}

@ -0,0 +1,49 @@
package com.student.mathquiz.persistence;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.*;
import java.lang.reflect.Type;
/**
* JSONJSON/
*/
public class JsonUtils {
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting() // 格式化输出(便于调试)
.create();
// 从JSON文件读取数据泛型支持任意类型如List<User>
public static <T> T readFromJson(String filePath, Type type) {
File file = new File(filePath);
if (!file.exists()) {
return null; // 文件不存在,返回空
}
try (Reader reader = new FileReader(file)) {
return GSON.fromJson(reader, type);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 将数据写入JSON文件泛型支持任意类型
public static <T> boolean writeToJson(String filePath, T data) {
File file = new File(filePath);
// 确保父目录存在
File parentDir = file.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
try (Writer writer = new FileWriter(file)) {
GSON.toJson(data, writer);
return true; // 写入成功
} catch (IOException e) {
e.printStackTrace();
return false; // 写入失败
}
}
}

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

@ -0,0 +1,95 @@
package com.student.mathquiz.question;
import java.util.*;
/**
*
*
* - sin/cos/tan
* -
* -
*/
public class HighQuestionGenerator implements QuestionGenerator {
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<>();
static {
// 特殊角三角函数值(简化版)
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");
}
@Override
public Question generateQuestion() {
// 随机生成三角函数题
return generateTrigQuestion();
}
/**
*
*/
private Question generateTrigQuestion() {
String func = TRIG_FUNCTIONS[RANDOM.nextInt(TRIG_FUNCTIONS.length)];
String[] angles = {"30", "45", "60"};
String angle = angles[RANDOM.nextInt(angles.length)];
String content = func + "(" + angle + "°) = ?";
String correctAnswer = TRIG_VALUES.get(func + angle);
List<String> options = generateOptions(correctAnswer);
int correctIndex = shuffleOptions(options, correctAnswer);
return new Question(content, correctAnswer, options, correctIndex);
}
/**
* 1 + 3
*/
private List<String> generateOptions(String correctAnswer) {
List<String> options = new ArrayList<>();
options.add(correctAnswer);
// 从TRIG_VALUES中随机取干扰项
List<String> allValues = new ArrayList<>(TRIG_VALUES.values());
Collections.shuffle(allValues);
for (String val : allValues) {
if (!options.contains(val)) {
options.add(val);
}
if (options.size() >= 4) break;
}
return options;
}
/**
*
*/
private int shuffleOptions(List<String> options, String correctAnswer) {
int correctIndex = options.indexOf(correctAnswer);
Collections.shuffle(options);
return options.indexOf(correctAnswer);
}
// 测试
public static void main(String[] args) {
QuestionGenerator generator = new HighQuestionGenerator();
for (int i = 0; i < 5; i++) {
Question q = generator.generateQuestion();
System.out.println("题干: " + q.getContent());
System.out.println("选项: " + q.getOptions());
System.out.println("正确答案索引: " + q.getCorrectAnswerIndex());
System.out.println("正确答案: " + q.getCorrectAnswer() + "\n");
}
}
}

@ -0,0 +1,148 @@
package com.student.mathquiz.question;
import com.student.mathquiz.constant.UserType;
import java.util.*;
/**
*
* ^2sqrt
*/
public class MiddleQuestionGenerator implements QuestionGenerator {
private static final Random RANDOM = new Random();
private static final char[] OPERATORS = {'+', '-', '*', '/'};
private static final String[] SPECIAL_OPS = {"^2", "sqrt"}; // 平方、根号(初中题必须包含)
@Override
public Question generateQuestion() {
// 关键修改:删除随机分支,强制生成含特殊运算(平方/根号)的题目
// 不再生成普通四则运算题,确保符合初中题要求
return generateSpecialQuestion();
}
/**
*
*/
private Question generateSpecialQuestion() {
// 随机选择“平方”或“根号”
String specialOp = SPECIAL_OPS[RANDOM.nextInt(SPECIAL_OPS.length)];
if ("^2".equals(specialOp)) {
// 生成平方题如“5^2 + 3 = ?”(结合四则运算,增加难度)
int num1 = RANDOM.nextInt(20) + 1; // 1~20平方后结果不会太大
int num2 = RANDOM.nextInt(10) + 1; // 1~10参与四则运算的数
char op = OPERATORS[RANDOM.nextInt(OPERATORS.length)];
// 处理除法(确保整除)
if (op == '/') {
num2 = RANDOM.nextInt(5) + 1; // 除数1~5
num1 = num2 * (RANDOM.nextInt(10) + 1); // 被除数 = 除数 × 倍数
}
// 题干如“5^2 + 3 = ?”
String content = num1 + "^2 " + op + " " + num2 + " = ?";
// 计算正确答案:先算平方,再算四则运算
int squareResult = num1 * num1;
int correctNum = calculateNormalAnswer(squareResult, num2, op);
String correctAnswer = String.valueOf(correctNum);
List<String> options = generateOptions(correctAnswer);
int correctIndex = shuffleOptions(options, correctAnswer);
return new Question(content, correctAnswer, options, correctIndex);
} else { // sqrt根号
// 生成根号题如“sqrt(25) - 2 = ?”(确保根号结果为整数)
int num1 = (int) Math.pow(RANDOM.nextInt(10) + 1, 2); // 1~10的平方如25=5²
int num2 = RANDOM.nextInt(10) + 1; // 1~10参与四则运算的数
char op = OPERATORS[RANDOM.nextInt(OPERATORS.length)];
// 处理除法(确保整除)
if (op == '/') {
int sqrtResult = (int) Math.sqrt(num1);
num2 = RANDOM.nextInt(5) + 1; // 除数1~5
// 调整根号内的数,确保根号结果 ÷ 除数 为整数
num1 = (num2 * (RANDOM.nextInt(5) + 1)) * (num2 * (RANDOM.nextInt(5) + 1));
}
// 题干如“sqrt(25) - 2 = ?”
String content = "sqrt(" + num1 + ") " + op + " " + num2 + " = ?";
// 计算正确答案:先算根号,再算四则运算
int sqrtResult = (int) Math.sqrt(num1);
int correctNum = calculateNormalAnswer(sqrtResult, num2, op);
String correctAnswer = String.valueOf(correctNum);
List<String> options = generateOptions(correctAnswer);
int correctIndex = shuffleOptions(options, correctAnswer);
return new Question(content, correctAnswer, options, correctIndex);
}
}
/**
*
*/
private int calculateNormalAnswer(int num1, int num2, char op) {
switch (op) {
case '+': return num1 + num2;
case '-': return num1 - num2;
case '*': return num1 * num2;
case '/': return num1 / num2; // 已确保整除
default: return 0;
}
}
/**
* 1 + 3
*/
private List<String> generateOptions(String correctAnswer) {
List<String> options = new ArrayList<>();
options.add(correctAnswer);
int correctNum = Integer.parseInt(correctAnswer);
// 增加最大尝试次数,避免死循环
int maxTries = 100;
int tries = 0;
while (options.size() < 4 && tries < maxTries) {
tries++;
int offset = RANDOM.nextInt(20) + 1; // 扩大偏移量,减少重复
int wrongNum = RANDOM.nextBoolean() ? correctNum + offset : correctNum - offset;
if (wrongNum <= 0) continue; // 避免负数
String wrongAnswer = String.valueOf(wrongNum);
if (!options.contains(wrongAnswer)) {
options.add(wrongAnswer);
}
}
// 兜底:若尝试多次仍不够,强制补充不重复的数
while (options.size() < 4) {
int maxNum = options.stream()
.mapToInt(Integer::parseInt)
.max()
.orElse(correctNum);
options.add(String.valueOf(maxNum + 1));
}
return options;
}
/**
*
*/
private int shuffleOptions(List<String> options, String correctAnswer) {
int correctIndex = options.indexOf(correctAnswer);
Collections.shuffle(options);
return options.indexOf(correctAnswer);
}
// 测试:验证初中题是否都含平方/根号
public static void main(String[] args) {
QuestionGenerator generator = new MiddleQuestionGenerator();
for (int i = 0; i < 5; i++) {
Question q = generator.generateQuestion();
System.out.println("第" + (i+1) + "题:" + q.getContent());
System.out.println("是否含平方/根号:" + (q.getContent().contains("^2") || q.getContent().contains("sqrt")));
}
}
}

@ -0,0 +1,159 @@
package com.student.mathquiz.question;
import com.student.mathquiz.constant.UserType;
import java.util.*;
/**
* 4+
*/
public class PrimaryQuestionGenerator implements QuestionGenerator {
private static final Random RANDOM = new Random();
private static final char[] OPERATORS = {'+', '-', '*', '/'}; // 小学支持的运算符
@Override
public Question generateQuestion() {
// 1. 生成基础四则运算题干2-3个操作数避免过于复杂
int operandCount = RANDOM.nextInt(2) + 2; // 2或3个操作数
List<Integer> operands = new ArrayList<>();
List<Character> ops = new ArrayList<>();
// 生成操作数1-100的整数
for (int i = 0; i < operandCount; i++) {
operands.add(RANDOM.nextInt(100) + 1);
}
// 生成运算符
for (int i = 0; i < operandCount - 1; i++) {
ops.add(OPERATORS[RANDOM.nextInt(OPERATORS.length)]);
}
// 2. 处理除法:确保能整除(小学需求)
handleDivision(operands, ops);
// 3. 构建题干字符串3 + 5 × 2
StringBuilder contentSb = new StringBuilder();
for (int i = 0; i < operandCount; i++) {
contentSb.append(operands.get(i));
if (i < ops.size()) {
contentSb.append(" ").append(ops.get(i)).append(" ");
}
}
String content = contentSb.toString() + " = ?"; // 题干末尾加“= ?”
// 4. 计算正确答案
String correctAnswer = calculateAnswer(operands, ops);
// 5. 生成选项1个正确3个错误
List<String> options = generateOptions(correctAnswer, operandCount, ops);
// 6. 打乱选项顺序,并记录正确答案索引
int correctIndex = shuffleOptions(options, correctAnswer);
// 7. 返回选择题模型
return new Question(content, correctAnswer, options, correctIndex);
}
// ------------------------------ 辅助方法:处理除法(确保整除) ------------------------------
private void handleDivision(List<Integer> operands, List<Character> ops) {
for (int i = 0; i < ops.size(); i++) {
if (ops.get(i) == '/') {
int divisor = operands.get(i + 1);
if (divisor == 0) {
divisor = 1; // 避免除零
operands.set(i + 1, divisor);
}
// 重新计算被除数,确保整除(被除数 = 除数 × 随机商1-10
int quotient = RANDOM.nextInt(10) + 1;
int dividend = divisor * quotient;
operands.set(i, dividend);
}
}
}
// ------------------------------ 辅助方法:计算正确答案 ------------------------------
private String calculateAnswer(List<Integer> operands, List<Character> ops) {
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;
}
}
return String.valueOf(result);
}
// ------------------------------ 辅助方法生成4个选项1正3错 ------------------------------
private List<String> generateOptions(String correctAnswer, int operandCount, List<Character> ops) {
List<String> options = new ArrayList<>();
// 添加正确答案
options.add(correctAnswer);
int correctNum = Integer.parseInt(correctAnswer);
// 关键修改1增加最大尝试次数防止死循环尝试100次还不够就强制生成
int maxTries = 100;
int tries = 0;
while (options.size() < 4 && tries < maxTries) {
tries++; // 每次循环计数+1
// 关键修改2扩大偏移量范围1-20减少重复概率
int offset = RANDOM.nextInt(20) + 1;
int wrongNum = RANDOM.nextBoolean() ? correctNum + offset : correctNum - offset;
// 确保错误答案为正数,且不在已有选项中
if (wrongNum <= 0) {
continue;
}
String wrongAnswer = String.valueOf(wrongNum);
if (!options.contains(wrongAnswer)) {
options.add(wrongAnswer);
}
}
// 关键修改3如果尝试100次后仍不够4个选项强制补充不重复的数兜底方案
while (options.size() < 4) {
// 生成比当前最大数大1的数确保不重复
int maxNum = options.stream()
.mapToInt(Integer::parseInt)
.max()
.orElse(correctNum);
String forceWrong = String.valueOf(maxNum + 1);
options.add(forceWrong);
}
return options;
}
// ------------------------------ 辅助方法:打乱选项顺序,返回正确答案索引 ------------------------------
private int shuffleOptions(List<String> options, String correctAnswer) {
// 记录正确答案原始索引
int correctIndex = options.indexOf(correctAnswer);
// 打乱选项
Collections.shuffle(options);
// 返回打乱后的正确答案索引
return options.indexOf(correctAnswer);
}
// 测试:生成一道小学选择题
public static void main(String[] args) {
QuestionGenerator generator = new PrimaryQuestionGenerator();
for (int i = 0; i < 5; i++) { // 生成5道题验证是否卡住
Question question = generator.generateQuestion();
System.out.println("题干:" + question.getContent());
System.out.println("选项:" + question.getOptions());
System.out.println("正确答案索引:" + question.getCorrectAnswerIndex());
System.out.println("正确答案:" + question.getCorrectAnswer());
}
}
}

@ -0,0 +1,61 @@
package com.student.mathquiz.question;
import java.util.List;
/**
*
*/
public class Question {
private String content; // 题干3 + 5 = ?
private String correctAnswer; // 正确答案(原始值,用于计算)
private List<String> options; // 选项列表4个选项1正3错
private int correctAnswerIndex; // 正确答案在选项中的索引0-3
// 无参构造保留用于JSON序列化
public Question() {}
// 修改全参构造新增options和correctAnswerIndex
public Question(String content, String correctAnswer, List<String> options, int correctAnswerIndex) {
this.content = content;
this.correctAnswer = correctAnswer;
this.options = options;
this.correctAnswerIndex = correctAnswerIndex;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getCorrectAnswer() {
return correctAnswer;
}
public void setCorrectAnswer(String correctAnswer) {
this.correctAnswer = correctAnswer;
}
// 新增选项列表Getter
public List<String> getOptions() {
return options;
}
// 新增选项列表Setter
public void setOptions(List<String> options) {
this.options = options;
}
// 新增正确答案索引Getter
public int getCorrectAnswerIndex() {
return correctAnswerIndex;
}
// 新增正确答案索引Setter
public void setCorrectAnswerIndex(int correctAnswerIndex) {
this.correctAnswerIndex = correctAnswerIndex;
}
}

@ -0,0 +1,24 @@
package com.student.mathquiz.question;
import com.student.mathquiz.constant.UserType;
/**
*
*/
public interface QuestionGenerator {
Question generateQuestion();
// 新增:根据用户类型获取对应的生成器(工厂方法,简化调用)
static QuestionGenerator getGeneratorByUserType(UserType userType) {
switch (userType) {
case PRIMARY:
return new PrimaryQuestionGenerator();
case JUNIOR:
return new MiddleQuestionGenerator();
case SENIOR:
return new HighQuestionGenerator();
default:
throw new IllegalArgumentException("不支持的用户类型:" + userType.getDisplayName());
}
}
}

@ -0,0 +1,72 @@
package com.student.mathquiz.quiz;
import com.student.mathquiz.question.Question;
import com.student.mathquiz.user.User;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class Quiz {
private String quizId; // 试卷唯一ID时间戳+随机数)
private User user; // 关联的用户
private List<Question> questions; // 题目列表10-30道
private List<Integer> userAnswers; // 用户答题记录(存储选项索引)
private int score; // 最终分数(百分制)
private long createTime; // 试卷创建时间
// 构造方法
public Quiz(User user, List<Question> questions) {
this.quizId = System.currentTimeMillis() + "-" + (int)(Math.random()*1000); // 唯一ID
this.user = user;
this.questions = questions;
this.userAnswers = new ArrayList<>(); // 初始化答题记录
this.createTime = System.currentTimeMillis();
this.score = 0; // 初始分数0
}
// 提交答案(新增:记录用户选择的选项索引)
public void submitAnswer(int optionIndex) {
userAnswers.add(optionIndex);
}
// 计算分数(新增:根据答题记录与正确答案对比)
public void calculateScore() {
if (questions.size() != userAnswers.size()) {
score = 0; // 题目未答完分数0
return;
}
int correctCount = 0;
for (int i = 0; i < questions.size(); i++) {
Question question = questions.get(i);
int userAnswerIndex = userAnswers.get(i);
// 对比用户答案与正确答案索引
if (userAnswerIndex == question.getCorrectAnswerIndex()) {
correctCount++;
}
}
// 分数 = 正确率 × 100百分制
score = (int) (((double) correctCount / questions.size()) * 100);
}
// Getter/Setter按需生成
public String getQuizId() {
return quizId;
}
public List<Question> getQuestions() {
return questions;
}
public List<Integer> getUserAnswers() {
return userAnswers;
}
public int getScore() {
return score;
}
}

@ -0,0 +1,99 @@
package com.student.mathquiz.quiz;
import com.student.mathquiz.constant.Constants;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.question.Question;
import com.student.mathquiz.question.QuestionGenerator;
import com.student.mathquiz.user.User;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*
*/
public class QuizService {
// 生成试卷(指定用户、题目数量,确保同一张卷子无重复题目)
public Quiz generateQuiz(User user, int questionCount) {
// 1. 验证题目数量10-30道
if (questionCount < Constants.QUESTION_MIN_COUNT || questionCount > Constants.QUESTION_MAX_COUNT) {
throw new IllegalArgumentException("题目数量需在10-30之间");
}
// 2. 获取对应学段的题目生成器
UserType userType = user.getUserType();
QuestionGenerator generator = QuestionGenerator.getGeneratorByUserType(userType);
// 3. 生成题目去重用Set存储题干避免重复
Set<String> questionContentSet = new HashSet<>();
List<Question> questions = new ArrayList<>();
int maxTries = questionCount * 200; // 最大尝试次数(避免死循环)
int tries = 0;
while (questions.size() < questionCount && tries < maxTries) {
Question question = generator.generateQuestion();
String content = question.getContent();
// 若题干未重复,添加到试卷
if (!questionContentSet.contains(content)) {
questionContentSet.add(content);
questions.add(question);
}
tries++;
}
// 4. 检查是否生成足够题目
if (questions.size() < questionCount) {
throw new RuntimeException("生成题目不足,建议减少题目数量!");
}
// 5. 创建试卷
return new Quiz(user, questions);
}
// 提交答案(指定试卷、题目索引、用户选择的选项索引)
public void submitAnswer(Quiz quiz, int questionIndex, int optionIndex) {
// 验证题目索引合法性
if (questionIndex < 0 || questionIndex >= quiz.getQuestions().size()) {
throw new IllegalArgumentException("题目索引无效!");
}
// 验证选项索引合法性0-3
if (optionIndex < 0 || optionIndex >= 4) {
throw new IllegalArgumentException("选项索引需为0-3");
}
// 提交答案(覆盖已有答案,支持修改)
List<Integer> userAnswers = quiz.getUserAnswers();
if (userAnswers.size() > questionIndex) {
userAnswers.set(questionIndex, optionIndex); // 修改已有答案
} else {
userAnswers.add(optionIndex); // 新增答案
}
}
// 计算试卷分数
public int calculateQuizScore(Quiz quiz) {
quiz.calculateScore();
return quiz.getScore();
}
// 测试:生成试卷并答题
public static void main(String[] args) {
// 模拟用户
User user = new User("test@primary.com", "encryptedPwd", UserType.PRIMARY);
// 生成10道题的试卷
QuizService quizService = new QuizService();
Quiz quiz = quizService.generateQuiz(user, 10);
System.out.println("生成试卷ID" + quiz.getQuizId());
System.out.println("题目数量:" + quiz.getQuestions().size());
// 模拟答题每道题选第0个选项
for (int i = 0; i < quiz.getQuestions().size(); i++) {
quizService.submitAnswer(quiz, i, 0);
}
// 计算分数
int score = quizService.calculateQuizScore(quiz);
System.out.println("最终分数:" + score);
}
}

@ -0,0 +1,62 @@
package com.student.mathquiz.test;
import com.google.gson.reflect.TypeToken;
import com.student.mathquiz.persistence.JsonUtils;
import com.student.mathquiz.user.User;
import com.student.mathquiz.constant.UserType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* JsonUtils +
*/
public class JsonUtilsTest {
public static void main(String[] args) {
// 测试1写用户列表到JSON文件
testWriteJson();
// 测试2从JSON文件读用户列表
testReadJson();
}
// 测试1写JSON文件
private static void testWriteJson() {
System.out.println("=== 测试写JSON文件 ===");
// 1. 创建测试用户列表
List<User> testUsers = new ArrayList<>();
testUsers.add(new User("test1@qq.com", "EncPwd123", UserType.PRIMARY)); // 小学用户
testUsers.add(new User("test2@qq.com", "EncPwd456", UserType.JUNIOR)); // 初中用户
// 2. 调用JsonUtils写文件路径user_data/test_users.json
boolean writeSuccess = JsonUtils.writeToJson("user_data/test_users.json", testUsers);
// 3. 验证结果
if (writeSuccess) {
System.out.println("✅ 写文件成功!可在项目根目录下查看 user_data/test_users.json");
} else {
System.out.println("❌ 写文件失败!检查路径或权限");
}
}
// 测试2读JSON文件
private static void testReadJson() {
System.out.println("\n=== 测试读JSON文件 ===");
// 1. 获取List<User>的真实类型(解决泛型擦除)
Type userListType = new TypeToken<List<User>>() {}.getType();
// 2. 调用JsonUtils读文件
List<User> readUsers = JsonUtils.readFromJson("user_data/test_users.json", userListType);
// 3. 验证结果
if (readUsers == null || readUsers.isEmpty()) {
System.out.println("❌ 读文件失败!文件不存在或格式错误");
} else {
System.out.println("✅ 读文件成功!读取到 " + readUsers.size() + " 个用户:");
for (User user : readUsers) {
System.out.println("邮箱:" + user.getEmail() + ",学段:" + user.getUserType().getDisplayName());
}
}
}
}

@ -0,0 +1,75 @@
package com.student.mathquiz.test;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.question.Question;
import com.student.mathquiz.question.QuestionGenerator;
/**
*
*/
public class QuestionGeneratorTest {
public static void main(String[] args) {
System.out.println("=== 开始测试题目生成 ===");
// 测试1小学题目四则运算+括号)
testQuestionGenerator(UserType.PRIMARY, "小学");
// 测试2初中题目平方/根号)
testQuestionGenerator(UserType.JUNIOR, "初中");
// 测试3高中题目三角函数
testQuestionGenerator(UserType.SENIOR, "高中");
System.out.println("\n=== 题目生成测试结束 ===");
}
/**
*
* @param userType
* @param typeName
*/
private static void testQuestionGenerator(UserType userType, String typeName) {
System.out.println("\n【" + typeName + "题目测试】");
// 获取对应学段的生成器(你的业务代码)
QuestionGenerator generator = QuestionGenerator.getGeneratorByUserType(userType);
// 生成5道题验证每道题的合法性
for (int i = 0; i < 5; i++) {
Question question = generator.generateQuestion();
// 验证1题干符合学段要求
boolean isContentValid = checkContentValid(question.getContent(), userType);
// 验证2选项数量为4个
boolean isOptionCountValid = question.getOptions().size() == 4;
// 验证3正确答案索引在0-3之间
boolean isIndexValid = question.getCorrectAnswerIndex() >= 0 && question.getCorrectAnswerIndex() <= 3;
// 输出结果
System.out.println("第" + (i+1) + "题:");
System.out.println(" 题干:" + question.getContent());
System.out.println(" 选项:" + question.getOptions());
System.out.println(" 正确索引:" + question.getCorrectAnswerIndex());
System.out.println(" 验证结果:" + (isContentValid && isOptionCountValid && isIndexValid ? "✅ 合法" : "❌ 非法"));
}
}
/**
*
*/
private static boolean checkContentValid(String content, UserType userType) {
switch (userType) {
case PRIMARY:
// 小学:含 +-*/,可含(),不含平方/根号/三角函数
return (content.contains("+") || content.contains("-") || content.contains("*") || content.contains("/"))
&& !content.contains("^2") && !content.contains("sqrt") && !content.contains("sin") && !content.contains("cos") && !content.contains("tan");
case JUNIOR:
// 初中:含平方(^2或根号sqrt
return content.contains("^2") || content.contains("sqrt");
case SENIOR:
// 高中含三角函数sin/cos/tan
return content.contains("sin") || content.contains("cos") || content.contains("tan");
default:
return false;
}
}
}

@ -0,0 +1,114 @@
package com.student.mathquiz.test;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.question.Question;
import com.student.mathquiz.quiz.Quiz;
import com.student.mathquiz.quiz.QuizService;
import com.student.mathquiz.user.User;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*
*/
public class QuizServiceTest {
public static void main(String[] args) {
System.out.println("=== 开始测试试卷服务 ===");
// 1. 模拟一个登录用户(实际项目中从登录接口获取)
User testUser = new User("quiz_test@163.com", "EncPwd123", UserType.JUNIOR);
System.out.println("测试用户:" + testUser.getEmail() + "" + testUser.getUserType().getDisplayName() + "\n");
// 2. 初始化试卷服务(你的业务代码)
QuizService quizService = new QuizService();
// 3. 生成试卷10道题验证去重
Quiz quiz = testGenerateQuiz(quizService, testUser, 10);
// 4. 模拟答题每道题选第0个选项
if (quiz != null) {
testSubmitAnswer(quizService, quiz);
// 5. 计算分数
testCalculateScore(quizService, quiz);
}
System.out.println("\n=== 试卷服务测试结束 ===");
}
// 场景1生成试卷验证是否去重
private static Quiz testGenerateQuiz(QuizService quizService, User user, int questionCount) {
System.out.println("【场景1生成试卷" + questionCount + "道题)】");
try {
Quiz quiz = quizService.generateQuiz(user, questionCount);
List<Question> questions = quiz.getQuestions();
// 验证1题目数量正确
boolean isCountValid = questions.size() == questionCount;
// 验证2题目无重复用Set存题干判断大小是否等于题目数量
Set<String> contentSet = new HashSet<>();
for (Question q : questions) {
contentSet.add(q.getContent());
}
boolean isNoDuplicate = contentSet.size() == questionCount;
// 输出结果
System.out.println("生成题目数量:" + questions.size() + "(预期:" + questionCount + "");
System.out.println("题目是否去重:" + (isNoDuplicate ? "✅ 无重复" : "❌ 有重复"));
if (isCountValid && isNoDuplicate) {
System.out.println("✅ 试卷生成成功!\n");
return quiz;
} else {
System.out.println("❌ 试卷生成失败!\n");
return null;
}
} catch (IllegalArgumentException e) {
System.out.println("❌ 生成失败:" + e.getMessage() + "\n");
return null;
}
}
// 场景2模拟答题每道题选第0个选项
private static void testSubmitAnswer(QuizService quizService, Quiz quiz) {
System.out.println("【场景2模拟答题】");
List<Question> questions = quiz.getQuestions();
for (int i = 0; i < questions.size(); i++) {
// 提交第i题的答案选第0个选项
quizService.submitAnswer(quiz, i, 0);
System.out.println("第" + (i+1) + "题提交选项0题干" + questions.get(i).getContent() + "");
}
// 验证:答题记录数量是否等于题目数量
boolean isAnswerCountValid = quiz.getUserAnswers().size() == questions.size();
System.out.println("答题记录验证:" + (isAnswerCountValid ? "✅ 记录完整" : "❌ 记录缺失") + "\n");
}
// 场景3计算分数
private static void testCalculateScore(QuizService quizService, Quiz quiz) {
System.out.println("【场景3计算分数】");
int score = quizService.calculateQuizScore(quiz);
List<Question> questions = quiz.getQuestions();
List<Integer> userAnswers = quiz.getUserAnswers();
// 统计答对数量
int correctCount = 0;
for (int i = 0; i < questions.size(); i++) {
Question q = questions.get(i);
if (userAnswers.get(i) == q.getCorrectAnswerIndex()) {
correctCount++;
}
}
int expectedScore = (int) (((double) correctCount / questions.size()) * 100);
// 验证:计算的分数是否符合预期
boolean isScoreValid = score == expectedScore;
System.out.println("答对题目:" + correctCount + "/" + questions.size());
System.out.println("最终分数:" + score + "分(预期:" + expectedScore + "分)");
System.out.println("分数验证:" + (isScoreValid ? "✅ 分数正确" : "❌ 分数错误"));
}
}

@ -0,0 +1,119 @@
package com.student.mathquiz.test;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.user.User;
import com.student.mathquiz.user.UserFileService;
import com.student.mathquiz.user.UserService;
/**
*
*/
public class UserServiceTest {
// 初始化用户服务(你的业务代码)
private static final UserService userService = new UserFileService();
// 测试用邮箱(可替换成自己的邮箱)
private static final String TEST_EMAIL = "test_student@163.com";
// 测试用密码符合规则6-10位+大小写+数字)
private static final String TEST_PWD = "Test1234";
// 错误密码(不符合规则:无大写)
private static final String WRONG_PWD = "test1234";
public static void main(String[] args) {
System.out.println("=== 开始测试用户模块 ===\n");
// 场景1测试邮箱格式校验输入非法邮箱
testInvalidEmail();
// 场景2测试发送验证码
String verifyCode = testSendVerifyCode();
// 场景3测试正常注册合法邮箱+正确验证码+合规密码)
testNormalRegister(verifyCode);
// 场景4测试重复注册同一邮箱再次注册
testDuplicateRegister(verifyCode);
// 场景5测试登录正确邮箱+正确密码)
User loginUser = testNormalLogin();
// 场景6测试修改密码正确原密码+合规新密码)
if (loginUser != null) {
testChangePassword();
}
System.out.println("\n=== 用户模块测试结束 ===");
}
// 场景1测试非法邮箱如无@、无后缀)
private static void testInvalidEmail() {
System.out.println("【场景1非法邮箱校验】");
String invalidEmail = "test163.com"; // 无@
boolean isvalid = userService.isValidEmail(invalidEmail);
if (!isvalid) {
System.out.println("✅ 正确:非法邮箱被拦截(" + invalidEmail + "\n");
} else {
System.out.println("❌ 错误:非法邮箱未被拦截(" + invalidEmail + "\n");
}
}
// 场景2发送验证码模拟前端获取验证码
private static String testSendVerifyCode() {
System.out.println("【场景2发送验证码】");
String verifyCode = userService.sendVerifyCode(TEST_EMAIL);
System.out.println("✅ 验证码已发送(控制台查看),测试用验证码:" + verifyCode + "\n");
return verifyCode; // 返回验证码,用于后续注册
}
// 场景3正常注册符合所有规则
private static void testNormalRegister(String verifyCode) {
System.out.println("【场景3正常注册】");
// 调用注册方法(邮箱+验证码+合规密码+小学学段)
boolean isSuccess = userService.register(TEST_EMAIL, verifyCode, TEST_PWD, UserType.PRIMARY);
if (isSuccess) {
System.out.println("✅ 注册成功!(邮箱:" + TEST_EMAIL + ",密码:" + TEST_PWD + "\n");
} else {
System.out.println("❌ 注册失败!检查验证码或邮箱是否已注册\n");
}
}
// 场景4重复注册同一邮箱再次注册
private static void testDuplicateRegister(String verifyCode) {
System.out.println("【场景4重复注册】");
boolean isSuccess = userService.register(TEST_EMAIL, verifyCode, TEST_PWD, UserType.PRIMARY);
if (!isSuccess) {
System.out.println("✅ 正确:重复注册被拦截(邮箱已存在)\n");
} else {
System.out.println("❌ 错误:允许重复注册(违反评分细则)\n");
}
}
// 场景5正常登录正确邮箱+密码)
private static User testNormalLogin() {
System.out.println("【场景5正常登录】");
User user = userService.login(TEST_EMAIL, TEST_PWD);
if (user != null) {
System.out.println("✅ 登录成功!用户信息:邮箱=" + user.getEmail() + ",学段=" + user.getUserType().getDisplayName() + "\n");
return user;
} else {
System.out.println("❌ 登录失败!检查邮箱或密码\n");
return null;
}
}
// 场景6修改密码正确原密码+新密码)
private static void testChangePassword() {
System.out.println("【场景6修改密码】");
String newPwd = "NewTest567"; // 新密码(合规)
// 调用改密码方法(原密码正确)
boolean isSuccess = userService.changePassword(TEST_EMAIL, TEST_PWD, newPwd);
if (isSuccess) {
// 验证新密码是否能登录
User user = userService.login(TEST_EMAIL, newPwd);
if (user != null) {
System.out.println("✅ 密码修改成功!新密码可正常登录(新密码:" + newPwd + "\n");
}
} else {
System.out.println("❌ 密码修改失败!检查原密码是否正确或新密码是否合规\n");
}
}
}

@ -0,0 +1,59 @@
package com.student.mathquiz.user;
import com.student.mathquiz.constant.UserType;
import java.io.Serializable;
/**
*
*/
public class User implements Serializable {
private String email; // 用户名(邮箱)
private String encryptedPwd; // 加密后的密码(避免明文存储)
private UserType userType; // 用户选择的学段(小学/初中/高中)
private long registerTime; // 注册时间(时间戳,用于追踪)
// 无参构造Gson序列化需要
public User() {}
// 全参构造
public User(String email, String encryptedPwd, UserType userType) {
this.email = email;
this.encryptedPwd = encryptedPwd;
this.userType = userType;
this.registerTime = System.currentTimeMillis(); // 自动记录注册时间
}
// Getter/Setter必须Gson需要通过Getter读取属性
public String getEmail() {
return email;
}
public void setEmail(String email) {
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;
}
public long getRegisterTime() {
return registerTime;
}
public void setRegisterTime(long registerTime) {
this.registerTime = registerTime;
}
}

@ -0,0 +1,228 @@
package com.student.mathquiz.user;
import com.google.gson.reflect.TypeToken;
import com.student.mathquiz.constant.Constants;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.persistence.JsonUtils;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Base64;
/**
*
*/
public class UserFileService implements UserService {
// 存储用户数据的JSON文件路径从常量类获取
private static final String USER_DATA_PATH = Constants.USER_DATA_PATH;
// 存储用户列表的类型Gson反序列化需要
private static final Type USER_LIST_TYPE = new TypeToken<List<User>>() {}.getType();
// 验证码缓存模拟实际项目可用Redis这里用内存缓存
private final List<VerifyCodeCache> verifyCodeCache = new ArrayList<>();
// 随机数生成器(用于生成验证码)
private final Random random = new Random();
// ------------------------------ 1. 发送验证码 ------------------------------
@Override
public String sendVerifyCode(String email) {
// 1. 生成6位随机验证码数字
StringBuilder code = new StringBuilder();
for (int i = 0; i < Constants.VERIFY_CODE_LENGTH; i++) {
code.append(random.nextInt(10));
}
String verifyCode = code.toString();
// 2. 缓存验证码有效期5分钟避免重复使用
verifyCodeCache.add(new VerifyCodeCache(email, verifyCode, System.currentTimeMillis() + 5 * 60 * 1000));
// 3. 模拟发送(控制台输出,实际项目替换为邮件发送逻辑)
System.out.println("[验证码发送] 邮箱:" + email + ",验证码:" + verifyCode + "有效期5分钟");
return verifyCode;
}
// ------------------------------ 2. 验证邮箱格式 ------------------------------
@Override
public boolean isValidEmail(String email) {
if (email == null || email.isEmpty()) {
return false;
}
// 邮箱格式正则(简单校验:包含@和.
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
return email.matches(emailRegex);
}
// ------------------------------ 3. 用户注册 ------------------------------
@Override
public boolean register(String email, String verifyCode, String password, UserType userType) {
// 步骤1验证邮箱格式
if (!isValidEmail(email)) {
System.out.println("[注册失败] 邮箱格式不正确!");
return false;
}
// 步骤2检查邮箱是否已注册
if (isEmailRegistered(email)) {
System.out.println("[注册失败] 该邮箱已注册!");
return false;
}
// 步骤3验证验证码是否存在、未过期、匹配
VerifyCodeCache validCache = verifyCodeCache.stream()
.filter(cache -> cache.getEmail().equals(email)
&& cache.getExpireTime() > System.currentTimeMillis()
&& cache.getVerifyCode().equals(verifyCode))
.findFirst()
.orElse(null);
if (validCache == null) {
System.out.println("[注册失败] 验证码无效或已过期!");
return false;
}
// 步骤4验证密码规则6-10位含大小写+数字)
if (!password.matches(Constants.PASSWORD_REGEX)) {
System.out.println("[注册失败] 密码需6-10位含大小写字母和数字");
return false;
}
// 步骤5加密密码Base64简单加密实际项目可用MD5+盐值)
String encryptedPwd = Base64.getEncoder().encodeToString(password.getBytes());
// 步骤6读取现有用户列表
List<User> userList = JsonUtils.readFromJson(USER_DATA_PATH, USER_LIST_TYPE);
if (userList == null) {
userList = new ArrayList<>(); // 首次注册,创建空列表
}
// 步骤7添加新用户
userList.add(new User(email, encryptedPwd, userType));
// 步骤8写入JSON文件
boolean writeSuccess = JsonUtils.writeToJson(USER_DATA_PATH, userList);
if (writeSuccess) {
System.out.println("[注册成功] 邮箱:" + email + ",学段:" + userType.getDisplayName());
// 移除已使用的验证码(避免重复注册)
verifyCodeCache.remove(validCache);
return true;
} else {
System.out.println("[注册失败] 数据存储失败!");
return false;
}
}
// ------------------------------ 4. 用户登录 ------------------------------
@Override
public User login(String email, String password) {
// 步骤1验证邮箱格式
if (!isValidEmail(email)) {
System.out.println("[登录失败] 邮箱格式不正确!");
return null;
}
// 步骤2读取用户列表
List<User> userList = JsonUtils.readFromJson(USER_DATA_PATH, USER_LIST_TYPE);
if (userList == null || userList.isEmpty()) {
System.out.println("[登录失败] 暂无注册用户!");
return null;
}
// 步骤3加密输入的密码与存储的加密方式一致
String encryptedInputPwd = Base64.getEncoder().encodeToString(password.getBytes());
// 步骤4匹配用户邮箱+密码)
for (User user : userList) {
if (user.getEmail().equals(email) && user.getEncryptedPwd().equals(encryptedInputPwd)) {
System.out.println("[登录成功] 欢迎:" + email + "" + user.getUserType().getDisplayName() + "");
return user; // 登录成功,返回用户信息
}
}
// 步骤5匹配失败
System.out.println("[登录失败] 邮箱或密码错误!");
return null;
}
// ------------------------------ 5. 修改密码 ------------------------------
@Override
public boolean changePassword(String email, String oldPassword, String newPassword) {
// 步骤1验证原密码先登录逻辑
User user = login(email, oldPassword);
if (user == null) {
System.out.println("[修改失败] 原密码错误!");
return false;
}
// 步骤2验证新密码规则
if (!newPassword.matches(Constants.PASSWORD_REGEX)) {
System.out.println("[修改失败] 新密码需6-10位含大小写字母和数字");
return false;
}
// 步骤3读取用户列表
List<User> userList = JsonUtils.readFromJson(USER_DATA_PATH, USER_LIST_TYPE);
if (userList == null) {
System.out.println("[修改失败] 用户数据不存在!");
return false;
}
// 步骤4更新用户密码
for (User u : userList) {
if (u.getEmail().equals(email)) {
// 加密新密码
String newEncryptedPwd = Base64.getEncoder().encodeToString(newPassword.getBytes());
u.setEncryptedPwd(newEncryptedPwd);
break;
}
}
// 步骤5写入更新后的用户列表
boolean writeSuccess = JsonUtils.writeToJson(USER_DATA_PATH, userList);
if (writeSuccess) {
System.out.println("[修改成功] 密码已更新,请重新登录!");
return true;
} else {
System.out.println("[修改失败] 数据存储失败!");
return false;
}
}
// ------------------------------ 6. 检查邮箱是否已注册 ------------------------------
@Override
public boolean isEmailRegistered(String email) {
List<User> userList = JsonUtils.readFromJson(USER_DATA_PATH, USER_LIST_TYPE);
if (userList == null) {
return false; // 无用户数据,未注册
}
// 遍历用户列表,检查邮箱是否存在
return userList.stream().anyMatch(user -> user.getEmail().equals(email));
}
// ------------------------------ 内部类:验证码缓存 ------------------------------
private static class VerifyCodeCache {
private String email; // 关联的邮箱
private String verifyCode; // 验证码
private long expireTime; // 过期时间(时间戳)
public VerifyCodeCache(String email, String verifyCode, long expireTime) {
this.email = email;
this.verifyCode = verifyCode;
this.expireTime = expireTime;
}
// Getter用于过滤缓存
public String getEmail() {
return email;
}
public String getVerifyCode() {
return verifyCode;
}
public long getExpireTime() {
return expireTime;
}
}
}

@ -0,0 +1,23 @@
package com.student.mathquiz.user;
import com.student.mathquiz.constant.UserType;
public interface UserService {
// 1. 发送注册验证码(模拟:控制台输出,实际可对接邮件服务)
String sendVerifyCode(String email);
// 2. 验证邮箱格式
boolean isValidEmail(String email);
// 3. 注册用户(需验证验证码、密码规则、避免重复注册)
boolean register(String email, String verifyCode, String password, UserType userType);
// 4. 用户登录(邮箱+密码验证)
User login(String email, String password);
// 5. 修改密码(需验证原密码,新密码符合规则)
boolean changePassword(String email, String oldPassword, String newPassword);
// 6. 检查邮箱是否已注册
boolean isEmailRegistered(String email);
}

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

@ -1,198 +0,0 @@
package com.student.mathquiz;
import com.student.mathquiz.service.IQuizService;
import com.student.mathquiz.service.IUserService;
import com.student.mathquiz.service.QuizServiceImpl;
import com.student.mathquiz.service.UserServiceImpl;
import com.student.mathquiz.view.*;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL; // 确保导入了这个
import javafx.application.Platform;
import javafx.scene.control.Label;
import javafx.scene.text.Font;
import javafx.scene.layout.Pane;
public class MainApp extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private final IUserService userService = new UserServiceImpl();
private final IQuizService quizService = new QuizServiceImpl();
private String currentUserEmail;
@Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("小初高数学学习软件");
initRootLayout();
showLoginView();
this.primaryStage.centerOnScreen();
this.primaryStage.show();
}
// 这是一个辅助方法,确保能正确找到 resources 里的文件
public static URL getResource(String path) {
return MainApp.class.getResource(path);
}
public void initRootLayout() {
try {
FXMLLoader loader = new FXMLLoader(getResource("view/RootLayout.fxml"));
rootLayout = loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
}
}
private <T> T switchScene(String fxmlFile) {
try {
FXMLLoader loader = new FXMLLoader(getResource("view/" + fxmlFile));
// ★★★ 关键修改在这里!★★★
// 我们不再假设它是 AnchorPane而是使用更通用的 Pane
Pane view = loader.load();
rootLayout.setCenter(view);
T controller = loader.getController();
// ... 后面的代码完全不变 ...
if (controller instanceof LoginViewController) {
((LoginViewController) controller).setMainApp(this);
}
if (controller instanceof RegisterViewController) {
((RegisterViewController) 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;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
<<<<<<< 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 ★★★
public void handleLoginSuccess(com.student.mathquiz.model.User user) {
// ★★★ 从 User 对象中,准确地获取并保存 email★★★
this.currentUserEmail = user.getEmail();
showMainMenuView();
}
public void showLoginView() { switchScene("LoginView.fxml"); }
public void showRegisterView() { switchScene("RegisterView.fxml"); }
public void showMainMenuView() { switchScene("MainMenuView.fxml"); }
>>>>>>> develop
// 在 MainApp.java 中
public void showQuizView(String level, int count) {
if (currentUserEmail == null) {
showLoginView();
return;
}
System.out.println("【前台UI线程】: 用户选择了 " + level + " 难度, " + count + " 道题。");
System.out.println("【前台UI线程】: 准备显示'加载中'界面...");
Label loadingLabel = new Label("正在拼命出题中,请稍候...");
loadingLabel.setFont(new Font("System", 24));
rootLayout.setCenter(loadingLabel);
System.out.println("【前台UI线程】: '加载中'界面显示完毕。准备启动后台线程...");
new Thread(() -> {
System.out.println(" -> 【后台线程】: 线程已启动!");
try {
System.out.println(
" -> 【后台线程】: 即将调用 quizService.generateQuiz(...),这可能会很耗时...");
// 【重活】让后台工人去生成试卷
quizService.generateQuiz(level, count, currentUserEmail);
System.out.println(" -> 【后台线程】: 题目生成完毕!任务完成!");
System.out.println(" -> 【后台线程】: 准备通知前台更新界面...");
Platform.runLater(() -> {
System.out.println("【前台UI线程】: 收到后台通知,准备切换到答题界面...");
QuizViewController controller = switchScene("QuizView.fxml");
if (controller != null) {
controller.startQuiz();
System.out.println("【前台UI线程】: 答题界面切换成功!");
} else {
System.err.println("【前台UI线程】: 错误切换到答题界面失败Controller为空");
}
});
} catch (Exception e) {
// 捕获任何可能导致线程死掉的错误
System.err.println(" -> 【后台线程】: 发生致命错误!线程已终止!");
e.printStackTrace();
}
}).start();
}
public void showScoreView(int score) {
ScoreViewController controller = switchScene("ScoreView.fxml");
if (controller != null) {
controller.setScore(score);
}
}
public IUserService getUserService() {
return userService;
}
public IQuizService getQuizService() {
return quizService;
}
public static void main(String[] args) {
launch(args);
}
// 在 MainApp.java 的末尾添加
public void showChangePasswordView() {
switchScene("ChangePasswordView.fxml");
}
public String getCurrentUserEmail() {
return currentUserEmail;
}
}

@ -1,26 +0,0 @@
package com.student.mathquiz.constant;
public enum UserType {
PRIMARY("小学"),
JUNIOR("初中"),
SENIOR("高中");
private final String displayName;
UserType(String displayName) {
this.displayName = displayName;
}
public static UserType fromDisplayName(String displayName) {
for (UserType type : UserType.values()) {
if (type.displayName.equalsIgnoreCase(displayName)) {
return type;
}
}
return null;
}
public String getDisplayName() {
return displayName;
}
}

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

@ -1,57 +0,0 @@
package com.student.mathquiz.model;
import com.student.mathquiz.constant.UserType;
public class User {
// ★★★ 1. 新增 username 字段 ★★★
private String username;
private String email;
private String encryptedPwd;
private UserType userType;
public User() {
} // Gson需要无参构造
// ★★★ 2. 修改构造函数,加入 username ★★★
public User(String username, String email, String encryptedPwd, UserType userType) {
this.username = username;
this.email = email;
this.encryptedPwd = encryptedPwd;
this.userType = userType;
}
// ★★★ 3. 新增 username 的 Getter 和 Setter ★★★
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
// --- 下面的代码保持不变 ---
public String getEmail() {
return email;
}
public void setEmail(String email) {
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;
}
}

@ -1,31 +0,0 @@
package com.student.mathquiz.persistence;
import com.student.mathquiz.question.Question;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
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(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
try (FileWriter writer = new FileWriter(new File(dir, filename))) {
for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i).getContent() + "\n\n");
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("题目已生成并保存!");
}
}

@ -1,37 +0,0 @@
package com.student.mathquiz.persistence;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.*;
import java.lang.reflect.Type;
public class JsonUtils {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
public static <T> T readFromJson(String filePath, Type type) {
File file = new File(filePath);
if (!file.exists()) {
return null;
}
try (Reader reader = new FileReader(file)) {
return GSON.fromJson(reader, type);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static <T> void writeToJson(String filePath, T data) {
File file = new File(filePath);
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
try (Writer writer = new FileWriter(file)) {
GSON.toJson(data, writer);
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -1,45 +0,0 @@
package com.student.mathquiz.persistence;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class QuestionChecker {
public static boolean isDuplicate(String username, String question) {
File dir = new File(username);
if (!dir.exists()) {
return false;
}
File[] files = dir.listFiles();
if (files == null) {
return false;
}
String target = question.trim();
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".txt")) {
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.isEmpty()) {
continue;
}
int dotIndex = line.indexOf('.');
if (dotIndex != -1) {
String storedQuestion = line.substring(dotIndex + 1).trim();
if (storedQuestion.equals(target)) {
return true;
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
return false;
}
}

@ -1,156 +0,0 @@
package com.student.mathquiz.question;
import java.util.*;
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 String[] FUNCTIONS = {"sin", "cos", "tan"};
private static final Map<String, String> TRIG_EXACT_VALUES = new HashMap<>();
private final Set<String> seenQuestions = new HashSet<>();
// 静态初始化三角函数精确值表(特殊角)
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°
TRIG_EXACT_VALUES.put("sin0", "0");
TRIG_EXACT_VALUES.put("cos0", "1");
TRIG_EXACT_VALUES.put("tan0", "0");
// 15°
TRIG_EXACT_VALUES.put("sin15", "(√6-√2)/4");
TRIG_EXACT_VALUES.put("cos15", "(√6+√2)/4");
TRIG_EXACT_VALUES.put("tan15", "2-√3");
// 30°
TRIG_EXACT_VALUES.put("sin30", "1/2");
TRIG_EXACT_VALUES.put("cos30", "√3/2");
TRIG_EXACT_VALUES.put("tan30", "√3/3");
// 45°
TRIG_EXACT_VALUES.put("sin45", "√2/2");
TRIG_EXACT_VALUES.put("cos45", "√2/2");
TRIG_EXACT_VALUES.put("tan45", "1");
// 60°
TRIG_EXACT_VALUES.put("sin60", "√3/2");
TRIG_EXACT_VALUES.put("cos60", "1/2");
TRIG_EXACT_VALUES.put("tan60", "√3");
// 75°
TRIG_EXACT_VALUES.put("sin75", "(√6+√2)/4");
TRIG_EXACT_VALUES.put("cos75", "(√6-√2)/4");
TRIG_EXACT_VALUES.put("tan75", "2+√3");
// 90°
TRIG_EXACT_VALUES.put("sin90", "1");
TRIG_EXACT_VALUES.put("cos90", "0");
TRIG_EXACT_VALUES.put("tan90", null); // 无定义
// 105°
TRIG_EXACT_VALUES.put("sin105", "(√6+√2)/4");
TRIG_EXACT_VALUES.put("cos105", "-(√6-√2)/4");
TRIG_EXACT_VALUES.put("tan105", "-2-√3");
// 120°
TRIG_EXACT_VALUES.put("sin120", "√3/2");
TRIG_EXACT_VALUES.put("cos120", "-1/2");
TRIG_EXACT_VALUES.put("tan120", "-√3");
// 135°
TRIG_EXACT_VALUES.put("sin135", "√2/2");
TRIG_EXACT_VALUES.put("cos135", "-√2/2");
TRIG_EXACT_VALUES.put("tan135", "-1");
// 150°
TRIG_EXACT_VALUES.put("sin150", "1/2");
TRIG_EXACT_VALUES.put("cos150", "-√3/2");
TRIG_EXACT_VALUES.put("tan150", "-√3/3");
// 165°
TRIG_EXACT_VALUES.put("sin165", "(√6-√2)/4");
TRIG_EXACT_VALUES.put("cos165", "-(√6+√2)/4");
TRIG_EXACT_VALUES.put("tan165", "-2+√3");
// 180°
TRIG_EXACT_VALUES.put("sin180", "0");
TRIG_EXACT_VALUES.put("cos180", "-1");
TRIG_EXACT_VALUES.put("tan180", "0");
// 其他角度可以继续补充...
>>>>>>> develop
}
@Override
public Question generateQuestion() {
while (true) {
String func = FUNCTIONS[RANDOM.nextInt(FUNCTIONS.length)];
int angle = 15 * RANDOM.nextInt(201); // 0° ~ 3000°步长15°
int normalizedAngle = angle % 360;
String key = func + normalizedAngle;
String correctAnswer = TRIG_EXACT_VALUES.get(key);
// 跳过无定义的角度(如 tan90°
if (correctAnswer == null) continue;
String content = func + "(" + angle + "°) = ?";
if (seenQuestions.contains(content)) continue;
seenQuestions.add(content);
List<String> options = generateOptions(correctAnswer);
int correctIndex = shuffleOptions(options, correctAnswer);
return new Question(content, correctAnswer, options, correctIndex);
}
}
/**
* 3 + 1
*/
private List<String> generateOptions(String correctAnswer) {
Set<String> options = new HashSet<>();
options.add(correctAnswer);
List<String> allValues = new ArrayList<>(TRIG_EXACT_VALUES.values());
while (options.size() < 4) {
String wrong = allValues.get(RANDOM.nextInt(allValues.size()));
if (wrong != null) options.add(wrong);
}
return new ArrayList<>(options);
}
private int shuffleOptions(List<String> options, String correctAnswer) {
Collections.shuffle(options);
return options.indexOf(correctAnswer);
}
/**
*
*/
public List<Question> generateQuestions(int count) {
List<Question> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
questions.add(generateQuestion());
}
return questions;
}
}

@ -1,90 +0,0 @@
package com.student.mathquiz.question;
import java.util.*;
public class MiddleQuestionGenerator implements QuestionGenerator {
private static final Random RANDOM = new Random();
private static final char[] OPERATORS = {'+', '-', '*', '/'};
private static final String[] SPECIAL_OPS = {"^2", "sqrt"};
@Override
public Question generateQuestion() {
return generateSpecialQuestion();
}
private Question generateSpecialQuestion() {
String specialOp = SPECIAL_OPS[RANDOM.nextInt(SPECIAL_OPS.length)];
String content;
String correctAnswer;
if ("^2".equals(specialOp)) {
int num1 = RANDOM.nextInt(20) + 1;
int num2 = RANDOM.nextInt(10) + 1;
char op = OPERATORS[RANDOM.nextInt(OPERATORS.length)];
if (op == '/') {
num2 = RANDOM.nextInt(5) + 1;
num1 = num2 * (RANDOM.nextInt(10) + 1);
}
content = num1 + "^2 " + op + " " + num2 + " = ?";
int correctNum = calculateNormalAnswer(num1 * num1, num2, op);
correctAnswer = String.valueOf(correctNum);
} else { // sqrt
int num1 = (int) Math.pow(RANDOM.nextInt(10) + 1, 2);
int num2 = RANDOM.nextInt(10) + 1;
char op = OPERATORS[RANDOM.nextInt(OPERATORS.length)];
if (op == '/') {
num2 = RANDOM.nextInt(5) + 1;
num1 = (int) Math.pow(num2 * (RANDOM.nextInt(5) + 1), 2);
}
content = "sqrt(" + num1 + ") " + op + " " + num2 + " = ?";
int correctNum = calculateNormalAnswer((int) Math.sqrt(num1), num2, op);
correctAnswer = String.valueOf(correctNum);
}
List<String> options = generateOptions(correctAnswer);
int correctIndex = shuffleOptions(options, correctAnswer);
return new Question(content, correctAnswer, options, correctIndex);
}
private int calculateNormalAnswer(int num1, int num2, char op) {
switch (op) {
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
return num1 / num2;
default:
return 0;
}
}
// 在 MiddleQuestionGenerator.java 中
private List<String> generateOptions(String correctAnswer) {
Set<String> options = new HashSet<>();
options.add(correctAnswer);
int correctNum = Integer.parseInt(correctAnswer);
while (options.size() < 4) {
int offset = RANDOM.nextInt(20) + 1;
int wrongNum = (RANDOM.nextBoolean()) ? correctNum + offset : correctNum - offset;
if (wrongNum > 0 && wrongNum != correctNum) {
options.add(String.valueOf(wrongNum));
} else {
options.add(String.valueOf(correctNum + options.size() + 1));
}
}
List<String> result = new ArrayList<>(options);
return result;
}
private int shuffleOptions(List<String> options, String correctAnswer) {
Collections.shuffle(options);
return options.indexOf(correctAnswer);
}
}

@ -1,239 +0,0 @@
package com.student.mathquiz.question;
import java.util.*;
public class PrimaryQuestionGenerator implements QuestionGenerator {
private static final Random RANDOM = new Random();
private static final char[] OPERATORS = {'+', '-', '*', '/'};
@Override
public Question generateQuestion() {
// 操作数数量2-5个
int operandCount = RANDOM.nextInt(4) + 2; // nextInt(4)→0-3+2→2-5
List<Integer> operands = new ArrayList<>();
List<Character> ops = new ArrayList<>();
// 生成合法表达式(修复后)
buildValidExpression(operands, ops, operandCount);
// 拼接题目内容
StringBuilder contentSb = new StringBuilder();
for (int i = 0; i < operandCount; i++) {
contentSb.append(operands.get(i));
if (i < ops.size()) {
contentSb.append(" ").append(ops.get(i)).append(" ");
}
}
String content = contentSb.toString() + " = ?";
// 计算正确答案
String correctAnswer = calculateAnswer(operands, ops);
// 生成选项
List<String> options = generateOptions(correctAnswer);
// 打乱选项
int correctIndex = shuffleOptions(options, correctAnswer);
return new Question(content, correctAnswer, options, correctIndex);
}
/**
* operandsopsoperands.size() = ops.size() + 1
* 1+1
*/
private void buildValidExpression(List<Integer> operands, List<Character> ops, int operandCount) {
// 第一个操作数1-50
int firstOperand = RANDOM.nextInt(50) + 1;
operands.add(firstOperand);
// 循环生成“1个运算符+1个操作数”共operandCount-1组
for (int i = 0; i < operandCount - 1; i++) {
int nextOperand;
char op;
// 循环重试:直到生成能让中间结果非负的组合
while (true) {
// 1. 只生成1个运算符无重复
op = OPERATORS[RANDOM.nextInt(OPERATORS.length)];
// 2. 计算当前中间结果此时ops还没加新opoperands也没加新数长度匹配
int currentMidResult = tempCalculate(operands, ops);
// 3. 基于当前中间结果生成合法操作数
nextOperand = generateValidNextOperand(currentMidResult, op);
// 4. 临时添加运算符和操作数(此时才开始添加,确保成对)
ops.add(op);
operands.add(nextOperand);
// 5. 检查新的中间结果是否非负
int newMidResult = tempCalculate(operands, ops);
if (newMidResult >= 0) {
// 合格,保留当前组合
break;
} else {
// 不合格,回滚(移除刚加的运算符和操作数,长度恢复匹配)
ops.remove(ops.size() - 1);
operands.remove(operands.size() - 1);
}
}
}
}
/**
*
*/
private int tempCalculate(List<Integer> tempOperands, List<Character> tempOps) {
// 防御性判断:避免极端情况下长度不匹配
if (tempOperands.size() != tempOps.size() + 1) {
return -1; // 触发重试
}
List<Integer> nums = new ArrayList<>(tempOperands);
List<Character> operators = new ArrayList<>(tempOps);
// 先处理乘除
int i = 0;
while (i < operators.size()) {
char op = operators.get(i);
if (op == '*' || op == '/') {
int left = nums.get(i);
int right = nums.get(i + 1); // 现在长度匹配,不会越界
int result = (op == '*') ? left * right : left / right;
nums.set(i, result);
nums.remove(i + 1);
operators.remove(i);
} else {
i++;
}
}
// 再处理加减
int midResult = nums.get(0);
for (i = 0; i < operators.size(); i++) {
int next = nums.get(i + 1);
midResult = (operators.get(i) == '+') ? midResult + next : midResult - next;
}
return midResult;
}
/**
*
*/
private int generateValidNextOperand(int currentMidResult, char op) {
switch (op) {
case '+':
return RANDOM.nextInt(30) + 1;
case '-':
// 基于中间结果生成,确保非负
return currentMidResult <= 0 ? 1 : RANDOM.nextInt(currentMidResult) + 1;
case '*':
return RANDOM.nextInt(5) + 1;
case '/':
List<Integer> divisors = getDivisors(currentMidResult);
return divisors.get(RANDOM.nextInt(divisors.size()));
default:
return 1;
}
}
/**
*
*/
private List<Integer> getDivisors(int num) {
List<Integer> divisors = new ArrayList<>();
for (int i = 1; i <= num / 2; i++) {
if (num % i == 0) {
divisors.add(i);
}
}
if (divisors.isEmpty()) {
divisors.add(1);
}
return divisors;
}
/**
*
*/
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<Character> operators = new ArrayList<>(ops);
// 先处理乘除
int i = 0;
while (i < operators.size()) {
char op = operators.get(i);
if (op == '*' || op == '/') {
int left = nums.get(i);
int right = nums.get(i + 1);
int result = (op == '*') ? left * right : left / right;
nums.set(i, result);
nums.remove(i + 1);
operators.remove(i);
} else {
i++;
>>>>>>> develop
}
}
// 再处理加减
int finalResult = nums.get(0);
for (i = 0; i < operators.size(); i++) {
int next = nums.get(i + 1);
switch (operators.get(i)) {
case '+':
finalResult += next;
break;
case '-':
finalResult -= next;
break;
}
}
return String.valueOf(finalResult);
}
/**
*
*/
private List<String> generateOptions(String correctAnswer) {
Set<String> options = new HashSet<>();
options.add(correctAnswer);
int correctNum = Integer.parseInt(correctAnswer);
while (options.size() < 4) {
int offset = RANDOM.nextInt(20) + 1;
int wrongNum = RANDOM.nextBoolean() ? correctNum + offset : correctNum - offset;
if (wrongNum <= 0) {
wrongNum = correctNum + offset + 10;
}
options.add(String.valueOf(wrongNum));
}
return new ArrayList<>(options);
}
/**
*
*/
private int shuffleOptions(List<String> options, String correctAnswer) {
Collections.shuffle(options);
return options.indexOf(correctAnswer);
}
}

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

@ -1,21 +0,0 @@
package com.student.mathquiz.question;
import com.student.mathquiz.constant.UserType;
public interface QuestionGenerator {
Question generateQuestion();
static QuestionGenerator getGeneratorByUserType(UserType userType) {
switch (userType) {
case PRIMARY:
return new PrimaryQuestionGenerator();
case JUNIOR:
return new MiddleQuestionGenerator();
case SENIOR:
return new HighQuestionGenerator();
default:
throw new IllegalArgumentException("不支持的用户类型:" + userType.getDisplayName());
}
}
}

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

@ -1,24 +0,0 @@
package com.student.mathquiz.service;
import com.student.mathquiz.model.User; // ★★★ 确保导入了这个类 ★★★
public interface IUserService {
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);
// ★★★ 修改这里的返回类型 ★★★
User login(String identifier, String password);
>>>>>>> develop
String changePassword(String email, String oldPassword, String newPassword1, String newPassword2);
}

@ -1,129 +0,0 @@
package com.student.mathquiz.service;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.model.Question;
import com.student.mathquiz.persistence.FileSaver;
import com.student.mathquiz.persistence.QuestionChecker;
import com.student.mathquiz.question.QuestionGenerator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class QuizServiceImpl implements IQuizService {
private List<com.student.mathquiz.question.Question> generatedQuestions;
private List<Question> quizQuestions;
private List<Integer> userAnswers;
private int currentQuestionIndex;
// 在 QuizServiceImpl.java 中
@Override
public void generateQuiz(String level, int count, String userEmail) {
System.out.println(" --> 【QuizService】: 进入 generateQuiz 方法。");
UserType userType = UserType.fromDisplayName(level);
if (userType == null) {
System.err.println(" --> 【QuizService】: 错误!无效的难度级别: " + level);
throw new IllegalArgumentException("无效的难度级别: " + level);
}
QuestionGenerator generator = QuestionGenerator.getGeneratorByUserType(userType);
Set<String> questionContentSet = new HashSet<>();
generatedQuestions = new ArrayList<>();
// 我们把 maxTries 提到外面,方便观察
int maxTries = count * 20;
int currentTry = 0;
System.out.println(" --> 【QuizService】: 准备进入循环生成题目,最大尝试次数: " + maxTries);
while (generatedQuestions.size() < count && currentTry < maxTries) {
currentTry++; // 每次循环都计数
// 每循环20次打印一次日志防止刷屏又能看到进度
if (currentTry % 20 == 0) {
System.out.println(" --> 【QuizService】: 循环中... 已尝试 " + currentTry + " 次, 已生成 "
+ generatedQuestions.size() + " 道题。");
}
com.student.mathquiz.question.Question newQuestion = generator.generateQuestion();//
String content = newQuestion.getContent();
if (!questionContentSet.contains(content) && !QuestionChecker.isDuplicate(userEmail,
content)) {
questionContentSet.add(content);
generatedQuestions.add(newQuestion);
}
}
System.out.println(" --> 【QuizService】: 循环结束!最终尝试 " + currentTry + " 次, 成功生成 "
+ generatedQuestions.size() + " 道题。");
if (generatedQuestions.size() < count) {
System.err.println(" --> 【QuizService】: 警告: 题库不足或重复率过高,只生成了 "
+ generatedQuestions.size() + " 道题。");
}
// ... 后面的代码不变 ...
this.quizQuestions = new ArrayList<>();
for (com.student.mathquiz.question.Question q : generatedQuestions) {
this.quizQuestions.add(
new Question(q.getContent(), q.getOptions(), q.getCorrectAnswerIndex()));
}
FileSaver.saveQuestions(userEmail, generatedQuestions);
this.userAnswers = new ArrayList<>();
this.currentQuestionIndex = 0;
System.out.println(" --> 【QuizService】: generateQuiz 方法执行完毕!");
}
@Override
public Question getCurrentQuestion() {
if (quizQuestions == null || currentQuestionIndex >= quizQuestions.size()) {
return null;
}
return quizQuestions.get(currentQuestionIndex);
}
@Override
public boolean isLastQuestion() {
return currentQuestionIndex == quizQuestions.size() - 1;
}
@Override
public void submitAnswer(int selectedOptionIndex) {
userAnswers.add(selectedOptionIndex);
if (!isLastQuestion()) {
currentQuestionIndex++;
}
}
@Override
public int calculateScore() {
int correctCount = 0;
for (int i = 0; i < quizQuestions.size(); i++) {
if (i < userAnswers.size() && quizQuestions.get(i).getCorrectAnswerIndex() == userAnswers.get(
i)) {
correctCount++;
}
}
if (quizQuestions.isEmpty()) {
return 0;
}
return (int) ((double) correctCount / quizQuestions.size() * 100);
}
@Override
public int getTotalQuestionCount() {
return quizQuestions != null ? quizQuestions.size() : 0;
}
@Override
public int getCurrentQuestionIndex() {
return currentQuestionIndex;
}
}

@ -1,190 +0,0 @@
package com.student.mathquiz.service;
import com.google.gson.reflect.TypeToken;
import com.student.mathquiz.constant.UserType;
import com.student.mathquiz.model.User;
import com.student.mathquiz.persistence.JsonUtils;
import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class UserServiceImpl implements IUserService {
private static final String SENDER_EMAIL = "3048494657@qq.com"; // 你的邮箱
private static final String SENDER_AUTHORIZATION_CODE = "ymnbezwjzrnfdgih"; // 你的授权码
private static final String USER_DATA_PATH = "user_data/users.json";
private static final Type USER_LIST_TYPE = new TypeToken<List<User>>() {
}.getType();
private final Map<String, User> userDatabase;
private final Map<String, String> verificationCodes = new ConcurrentHashMap<>();
public UserServiceImpl() {
this.userDatabase = loadUsersFromFile();
System.out.println(
"后端日志: 已从 " + USER_DATA_PATH + " 加载 " + userDatabase.size() + " 个用户。");
}
private Map<String, User> loadUsersFromFile() {
List<User> userList = JsonUtils.readFromJson(USER_DATA_PATH, USER_LIST_TYPE);
if (userList == null) {
return new ConcurrentHashMap<>();
}
return userList.stream().collect(Collectors.toConcurrentMap(User::getEmail, user -> user));
}
private synchronized void saveUsersToFile() {
JsonUtils.writeToJson(USER_DATA_PATH, new ArrayList<>(userDatabase.values()));
System.out.println("后端日志: 用户数据已保存到 " + USER_DATA_PATH);
}
@Override
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;
}
String code = String.valueOf(100000 + new Random().nextInt(900000));
verificationCodes.put(recipientEmail, code);
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.qq.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
Authenticator authenticator = new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(SENDER_EMAIL, SENDER_AUTHORIZATION_CODE);
}
};
Session session = Session.getInstance(props, authenticator);
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(SENDER_EMAIL));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail));
message.setSubject("【数学学习软件】您的注册验证码");
message.setText(
"尊敬的用户,您好!\n\n您的注册验证码是" + code + "\n\n(此为系统自动发送,请勿回复)");
Transport.send(message);
System.out.println("后端日志: 验证码 " + code + " 已成功发送至 " + recipientEmail);
return true;
} catch (MessagingException e) {
e.printStackTrace();
return false;
}
}
// 在 UserServiceImpl.java 中
// ★★★ 用这个新版本替换掉旧的 register 方法 ★★★
@Override
public String register(String username, String email, String code, String password,
String confirmPassword) {
// 1. 新增:校验用户名是否为空
if (username == null || username.trim().isEmpty()) {
return "用户名不能为空!";
}
// 2. 新增:校验用户名是否已被占用
if (userDatabase.values().stream()
.anyMatch(user -> user.getUsername().equalsIgnoreCase(username))) {
return "该用户名已被使用!";
}
// 3. 原有的校验逻辑保持不变
if (userDatabase.containsKey(email)) {
return "该邮箱已被注册!";
}
String storedCode = verificationCodes.get(email);
if (storedCode == null || !storedCode.equals(code)) {
return "验证码错误!";
}
if (!password.equals(confirmPassword)) {
return "两次输入的密码不一致!";
}
if (!password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$")) {
return "密码必须为6-10位且包含大小写字母和数字";
}
// 4. 创建新用户时加入用户名
String encryptedPwd = Base64.getEncoder().encodeToString(password.getBytes());
User newUser = new User(username, email, encryptedPwd, UserType.PRIMARY);
userDatabase.put(email, newUser);
saveUsersToFile();
verificationCodes.remove(email);
return null;
}
// 在 UserServiceImpl.java 中
@Override
public User login(String identifier, String password) {
User user = null;
// 智能判断是邮箱还是用户名
if (identifier.contains("@")) {
user = userDatabase.get(identifier);
} else {
for (User u : userDatabase.values()) {
if (u.getUsername().equalsIgnoreCase(identifier)) {
user = u;
break;
}
}
}
if (user == null) {
return null; // 找不到用户,返回 null
}
// 验证密码
String encryptedInputPwd = Base64.getEncoder().encodeToString(password.getBytes());
if (user.getEncryptedPwd().equals(encryptedInputPwd)) {
return user; // ★★★ 密码正确,返回整个 User 对象! ★★★
} else {
return null; // 密码错误,返回 null
}
}
@Override
public String changePassword(String email, String oldPassword, String newPassword1,
String newPassword2) {
User user = userDatabase.get(email);
if (user == null) {
return "用户不存在!请重新登录。"; // 增加健壮性
}
// 步骤1验证旧密码是否正确
String encryptedOldPwd = Base64.getEncoder().encodeToString(oldPassword.getBytes());
if (!user.getEncryptedPwd().equals(encryptedOldPwd)) {
return "旧密码错误!";
}
// 步骤2验证新密码是否一致
if (!newPassword1.equals(newPassword2)) {
return "两次输入的新密码不一致!";
}
// 步骤3验证新密码是否符合规则
if (!newPassword1.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$")) {
return "新密码必须为6-10位且包含大小写字母和数字";
}
// 步骤4加密新密码并更新
String newEncryptedPwd = Base64.getEncoder().encodeToString(newPassword1.getBytes());
user.setEncryptedPwd(newEncryptedPwd);
// 步骤5保存到文件
saveUsersToFile();
return null; // 返回 null 代表修改成功
}
}

@ -1,60 +0,0 @@
package com.student.mathquiz.view;
import com.student.mathquiz.MainApp;
import com.student.mathquiz.service.IUserService;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
public class ChangePasswordController {
@FXML
private PasswordField oldPasswordField;
@FXML
private PasswordField newPasswordField1;
@FXML
private PasswordField newPasswordField2;
@FXML
private Label statusLabel;
private MainApp mainApp;
private IUserService userService;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
this.userService = mainApp.getUserService();
}
@FXML
private void handleConfirmChange() {
String oldPass = oldPasswordField.getText();
String newPass1 = newPasswordField1.getText();
String newPass2 = newPasswordField2.getText();
// ★★★ 调用后端的修改密码服务 ★★★
String result = userService.changePassword(mainApp.getCurrentUserEmail(), oldPass, newPass1,
newPass2);
if (result == null) { // null 代表成功
statusLabel.setText("密码修改成功!请重新登录。");
// 延迟2秒后自动跳转到登录界面
new Thread(() -> {
try {
Thread.sleep(2000);
Platform.runLater(mainApp::showLoginView);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
} else {
// 显示后端返回的错误信息
statusLabel.setText(result);
}
}
@FXML
private void handleBackToMenu() {
mainApp.showMainMenuView();
}
}

@ -1,49 +0,0 @@
package com.student.mathquiz.view;
import com.student.mathquiz.MainApp;
import com.student.mathquiz.service.IUserService;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
public class LoginViewController {
@FXML
private TextField emailField;
@FXML
private PasswordField passwordField;
@FXML
private Label statusLabel;
private MainApp mainApp;
private IUserService userService;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
this.userService = mainApp.getUserService();
}
// 在 LoginViewController.java 中
@FXML
private void handleLogin() {
String identifier = emailField.getText(); // 我们把变量名改得更准确
String password = passwordField.getText();
// ★★★ 调用新的 login 方法,它会返回一个 User 对象 ★★★
com.student.mathquiz.model.User loggedInUser = userService.login(identifier, password);
if (loggedInUser != null) { // 如果返回的不是 null说明登录成功
// ★★★ 把完整的 User 对象传给 MainApp ★★★
mainApp.handleLoginSuccess(loggedInUser);
} else {
statusLabel.setText("用户名/邮箱或密码错误!");
}
}
@FXML
private void handleGoToRegister() {
mainApp.showRegisterView();
}
}

@ -1,64 +0,0 @@
package com.student.mathquiz.view;
import com.student.mathquiz.MainApp;
import javafx.fxml.FXML;
import javafx.scene.control.TextInputDialog;
import java.util.Optional;
public class MainMenuViewController {
private MainApp mainApp;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
}
@FXML
private void handlePrimary() {
showQuizWithOptions("小学");
}
@FXML
private void handleMiddle() {
showQuizWithOptions("初中");
}
@FXML
private void handleHigh() {
showQuizWithOptions("高中");
}
// ★★★ 新增的方法在这里!★★★
@FXML
private void handleChangePassword() {
mainApp.showChangePasswordView();
}
// ★★★ 新增的方法在这里!★★★
@FXML
private void handleLogout() {
mainApp.showLoginView();
}
private void showQuizWithOptions(String level) {
TextInputDialog dialog = new TextInputDialog("10");
dialog.setTitle("设置题目数量");
dialog.setHeaderText("您选择了 " + level + " 难度");
dialog.setContentText("请输入需要生成的题目数量:");
Optional<String> result = dialog.showAndWait();
result.ifPresent(countStr -> {
try {
int count = Integer.parseInt(countStr);
if (count > 0 && count <= 50) {
mainApp.showQuizView(level, count);
} else {
// You can add an Alert here for invalid input
System.out.println("题目数量输入无效");
}
} catch (NumberFormatException e) {
System.out.println("输入的不是数字");
}
});
}
}

@ -1,99 +0,0 @@
package com.student.mathquiz.view;
import com.student.mathquiz.MainApp;
import com.student.mathquiz.model.Question;
import com.student.mathquiz.service.IQuizService;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import java.util.Arrays;
import java.util.List;
public class QuizViewController {
@FXML
private Label questionNumberLabel;
@FXML
private Label questionTextLabel;
@FXML
private RadioButton option1;
@FXML
private RadioButton option2;
@FXML
private RadioButton option3;
@FXML
private RadioButton option4;
@FXML
private ToggleGroup optionsGroup;
@FXML
private Button submitButton;
@FXML
private Label feedbackLabel;
private MainApp mainApp;
private IQuizService quizService;
private List<RadioButton> radioButtons;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
this.quizService = mainApp.getQuizService();
}
@FXML
private void initialize() {
radioButtons = Arrays.asList(option1, option2, option3, option4);
}
public void startQuiz() {
loadQuestion();
}
private void loadQuestion() {
Question currentQuestion = quizService.getCurrentQuestion();
if (currentQuestion == null) {
return;
}
questionNumberLabel.setText(
String.format("第 %d/%d 题", quizService.getCurrentQuestionIndex() + 1,
quizService.getTotalQuestionCount()));
questionTextLabel.setText(currentQuestion.getContent());
List<String> options = currentQuestion.getOptions();
for (int i = 0; i < radioButtons.size(); i++) {
if (i < options.size()) {
radioButtons.get(i).setText(options.get(i));
radioButtons.get(i).setVisible(true);
} else {
radioButtons.get(i).setVisible(false);
}
}
feedbackLabel.setText("");
if (optionsGroup.getSelectedToggle() != null) {
optionsGroup.getSelectedToggle().setSelected(false);
}
submitButton.setText(quizService.isLastQuestion() ? "提交并查看分数" : "下一题");
}
@FXML
private void handleSubmitAnswer() {
RadioButton selectedToggle = (RadioButton) optionsGroup.getSelectedToggle();
if (selectedToggle == null) {
feedbackLabel.setText("请选择一个答案!");
return;
}
int selectedIndex = radioButtons.indexOf(selectedToggle);
boolean isLast = quizService.isLastQuestion();
quizService.submitAnswer(selectedIndex);
if (isLast) {
int score = quizService.calculateScore();
mainApp.showScoreView(score);
} else {
loadQuestion();
}
}
}

@ -1,78 +0,0 @@
package com.student.mathquiz.view;
import com.student.mathquiz.MainApp;
import com.student.mathquiz.service.IUserService;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
public class RegisterViewController {
// ★★★ 1. 新增 usernameField 的引用 ★★★
@FXML
private TextField usernameField;
@FXML
private TextField emailField;
@FXML
private TextField codeField;
@FXML
private PasswordField passwordField1;
@FXML
private PasswordField passwordField2;
@FXML
private Label statusLabel;
private MainApp mainApp;
private IUserService userService;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
this.userService = mainApp.getUserService();
}
@FXML
private void handleSendCode() {
String email = emailField.getText();
statusLabel.setText("正在发送验证码...");
new Thread(() -> {
boolean success = userService.sendVerificationCode(email);
Platform.runLater(() -> {
statusLabel.setText(success ? "验证码已发送,请查收!" : "发送失败,请检查邮箱地址!");
});
}).start();
}
@FXML
private void handleRegister() {
// ★★★ 2. 获取用户名 ★★★
String username = usernameField.getText();
String email = emailField.getText();
String code = codeField.getText();
String pass1 = passwordField1.getText();
String pass2 = passwordField2.getText();
// ★★★ 3. 调用新的 register 方法 ★★★
String result = userService.register(username, email, code, pass1, pass2);
if (result == null) {
statusLabel.setText("注册成功!即将跳转到登录页...");
new Thread(() -> {
try {
Thread.sleep(1500);
Platform.runLater(mainApp::showLoginView);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
} else {
statusLabel.setText(result);
}
}
@FXML
private void handleGoToLogin() {
mainApp.showLoginView();
}
}

@ -1,31 +0,0 @@
package com.student.mathquiz.view;
import com.student.mathquiz.MainApp;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class ScoreViewController {
@FXML
private Label scoreLabel;
private MainApp mainApp;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
}
public void setScore(int score) {
scoreLabel.setText("你的分数是: " + score);
}
@FXML
private void handleTryAgain() {
mainApp.showMainMenuView();
}
@FXML
private void handleExit() {
Platform.exit();
}
}

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" 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">
<children>
<Label text="修改密码">
<font>
<Font name="System Bold" size="24.0"/>
</font>
</Label>
<PasswordField fx:id="oldPasswordField" prefWidth="250.0" promptText="请输入旧密码"/>
<PasswordField fx:id="newPasswordField1" prefWidth="250.0"
promptText="请输入新密码 (6-10位,含大小写字母和数字)"/>
<PasswordField fx:id="newPasswordField2" prefWidth="250.0" promptText="请再次输入新密码"/>
<Button onAction="#handleConfirmChange" text="确认修改"/>
<Button onAction="#handleBackToMenu" text="返回主菜单"
style="-fx-background-color: transparent; -fx-underline: true;"/>
<Label fx:id="statusLabel" textFill="RED" wrapText="true">
<VBox.margin>
<Insets top="10.0"/>
</VBox.margin>
</Label>
</children>
</VBox>

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?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">
<children>
<Label text="用户登录">
<font>
<Font size="24.0"/>
</font>
</Label>
<!-- ★★★ 修改这里的提示文字 ★★★ -->
<TextField fx:id="emailField" promptText="邮箱 / 用户名" maxWidth="300"/>
<PasswordField fx:id="passwordField" promptText="密码" maxWidth="300"/>
<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="还没有账户?去注册"/>
<Label fx:id="statusLabel" textFill="RED"/>
</children>
</VBox>

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?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">
<children>
<Label text="请选择难度">
<font>
<Font size="24.0"/>
</font>
</Label>
<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="修改密码">
<VBox.margin>
<Insets top="20.0"/>
</VBox.margin>
</Button>
<Button onAction="#handleLogout" prefWidth="120.0" text="退出登录"
style="-fx-background-color: #f0f0f0;"/>
</children>
</VBox>

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<?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">
<!-- ★★★ 关键修改:把 ToggleGroup 的定义放在这里!★★★ -->
<!-- ★★★ 它不属于任何布局,只是一个在后台工作的逻辑对象 ★★★ -->
<fx:define>
<ToggleGroup fx:id="optionsGroup"/>
</fx:define>
<VBox alignment="TOP_CENTER" layoutX="50.0" layoutY="50.0" prefHeight="300.0" prefWidth="500.0"
spacing="15.0">
<Label fx:id="questionNumberLabel" text="第 1/10 题">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
<Label fx:id="questionTextLabel" alignment="CENTER" text="这里是题干1 + 1 = ?" wrapText="true">
<font>
<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 fx:id="option1" mnemonicParsing="false" text="选项 A"
toggleGroup="$optionsGroup"/>
<RadioButton fx:id="option2" mnemonicParsing="false" text="选项 B"
toggleGroup="$optionsGroup"/>
<RadioButton fx:id="option3" mnemonicParsing="false" text="选项 C"
toggleGroup="$optionsGroup"/>
<RadioButton fx:id="option4" mnemonicParsing="false" text="选项 D"
toggleGroup="$optionsGroup"/>
</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>

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?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">
<children>
<Label text="用户注册">
<font>
<Font size="24.0"/>
</font>
</Label>
<!-- ★★★ 新增的用户名字段 ★★★ -->
<TextField fx:id="usernameField" promptText="用户名" maxWidth="300"/>
<HBox alignment="CENTER" maxWidth="300" spacing="10">
<children>
<TextField fx:id="emailField" promptText="邮箱" HBox.hgrow="ALWAYS"/>
<Button onAction="#handleSendCode" text="发送验证码"/>
</children>
</HBox>
<TextField fx:id="codeField" promptText="验证码" maxWidth="300"/>
<PasswordField fx:id="passwordField1" promptText="设置密码 (6-10位,含大小写字母和数字)"
maxWidth="300"/>
<PasswordField fx:id="passwordField2" promptText="确认密码" maxWidth="300"/>
<Button onAction="#handleRegister" text="注册" prefWidth="100"/>
<Button onAction="#handleGoToLogin"
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>

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

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?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">
<VBox alignment="CENTER" layoutX="200.0" layoutY="100.0" prefHeight="200.0" prefWidth="200.0"
spacing="20.0">
<Label text="测试完成!">
<font>
<Font name="System Bold" size="24.0"/>
</font>
</Label>
<Label fx:id="scoreLabel" text="你的分数是: 100">
<font>
<Font size="18.0"/>
</font>
</Label>
<Button onAction="#handleTryAgain" text="再做一组"/>
<Button onAction="#handleExit" text="退出程序"/>
</VBox>
</AnchorPane>

@ -1,32 +0,0 @@
package persistence;
import question.Question;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
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(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
try (FileWriter writer = new FileWriter(new File(dir, filename))) {
for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i).getQuestion() + "\n\n");
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("题目已生成并保存!");
}
}

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

@ -1,27 +0,0 @@
package question;
import java.util.Random;
public class HighQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
@Override
public Question generateQuestion() {
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) {
try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval("Math." + func + "(Math.toRadians(" + expr + "))"));
} catch (Exception e) {
return "?";
}
}
}

@ -1,29 +0,0 @@
package question;
import java.util.Random;
public class MiddleQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
@Override
public Question generateQuestion() {
Question q = primaryGenerator.generateQuestion();
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) {
try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval(expr.replace("^", "**")));
} catch (Exception e) {
return "?";
}
}
}

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

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

@ -1,6 +0,0 @@
package question;
public interface QuestionGenerator {
Question generateQuestion();
}

@ -1,8 +0,0 @@
[
{
"username": "清清",
"email": "lyqqqq1214@163.com",
"encryptedPwd": "MTIzNDU2bEw\u003d",
"userType": "PRIMARY"
}
]
Loading…
Cancel
Save