diff --git a/src/.idea/.gitignore b/.idea/.gitignore
similarity index 100%
rename from src/.idea/.gitignore
rename to .idea/.gitignore
diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml
new file mode 100644
index 0000000..639e24e
--- /dev/null
+++ b/.idea/libraries/lib.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/misc.xml b/.idea/misc.xml
similarity index 100%
rename from src/.idea/misc.xml
rename to .idea/misc.xml
diff --git a/src/.idea/modules.xml b/.idea/modules.xml
similarity index 100%
rename from src/.idea/modules.xml
rename to .idea/modules.xml
diff --git a/src/.idea/vcs.xml b/.idea/vcs.xml
similarity index 64%
rename from src/.idea/vcs.xml
rename to .idea/vcs.xml
index 6c0b863..35eb1dd 100644
--- a/src/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/MathEaxmApp.iml b/MathEaxmApp.iml
similarity index 70%
rename from src/MathEaxmApp.iml
rename to MathEaxmApp.iml
index b107a2d..fb8e866 100644
--- a/src/MathEaxmApp.iml
+++ b/MathEaxmApp.iml
@@ -3,9 +3,10 @@
-
+
+
\ No newline at end of file
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000..114fdaa
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,227 @@
+# 数学题目生成器 (图形化界面版) 项目说明文档
+
+
+
+
+
+## 1. 项目概述
+
+
+
+该项目是一个功能完善的、拥有现代化图形用户界面(GUI)的桌面应用。它旨在为不同年级(小学、初中、高中)的学生提供一个集用户管理、题目练习、自动评分和历史存档于一体的综合性数学学习平台。用户可以通过友好的界面进行注册、登录、选择难度、进行交互式答题,并通过真实的电子邮件接收验证码,确保了账户的安全性。
+
+
+
+## 2. 项目功能
+
+
+
+
+
+### 2.1 用户管理
+
+
+
+- **用户注册**:提供邮箱和自定义用户名进行注册。系统会向用户提供的邮箱发送一封包含**真实6位验证码**的邮件,用户需凭此验证码完成注册。
+- **密码安全**:支持用户设置符合强度要求(6-10位,含大小写字母和数字)的密码,并在登录状态下安全地修改密码。
+- **双模式登录**:系统支持用户使用**邮箱**或**用户名**两种方式配合密码进行登录,更加便捷。
+
+
+
+### 2.2 题目与考试
+
+
+
+- **智能题目生成**:根据用户选择的年级,动态生成不同复杂度的题目。题目包含2-5个操作数,并能智能地添加有意义的单层或双重括号。
+- **交互式答题**:题目以选择题形式在界面中逐题展示,用户可通过鼠标点击进行选择。
+- **双向导航**:在答题过程中,用户可以使用“上一题”、“下一题”按钮在题目之间自由切换,检查并修改之前的答案。
+- **提前交卷**:用户可在答题未完成时选择提前交卷,系统会进行确认提醒,并根据已完成的题目计分。
+- **自动评分与存档**:答题结束后,系统会立即计算得分(百分制)并展示。同时,每一次生成的完整试卷(包含题目、选项、正确答案)都会自动保存到以该用户命名的专属文件夹中,便于后续复习。
+
+
+
+### 2.3 用户界面
+
+
+
+- **现代化图形界面**:基于 `Java Swing` 和 `FlatLaf` UI库构建,提供了一个美观、清爽、响应流畅的桌面应用体验。
+- **多视图切换**:拥有登录、注册、密码设置、主菜单、考试、分数等多个独立视图,流程清晰,操作直观。
+- **人性化交互**:答题界面提供实时进度条,导航按钮状态会根据当前题号自动更新,为用户提供清晰的操作指引。
+
+
+
+## 3. 系统架构
+
+
+
+项目采用业界标准的 **MVC (Model-View-Controller)** 设计模式,确保了代码的高度解耦、可维护性和可扩展性。
+
+
+
+### 3.1 模型 (Model)
+
+
+
+负责处理数据和业务逻辑,完全独立于界面。
+
+- **`model` 包**: 定义核心数据结构,如 `User`, `Question`。
+- **`service` 包**: 实现所有后台服务逻辑,如 `UserManager` (用户管理)、`ExamManager` (考试管理) 和 `EmailService` (邮件发送服务)。
+
+
+
+### 3.2 视图 (View)
+
+
+
+负责渲染用户界面,是用户直接交互的层面。
+
+- **`view` 包**: 包含了所有的UI界面类,如 `LoginView`, `ExamView`, `MainMenuView` 等。这些类只负责“显示”,不包含任何业务逻辑。
+
+
+
+### 3.3 控制器 (Controller)
+
+
+
+作为模型和视图之间的桥梁,调度整个应用程序。
+
+- **`controller` 包**: 核心类 `AppController` 在此包中。它接收视图层传来的用户操作,调用模型层处理,并根据结果更新视图层的显示。
+
+
+
+## 4. 代码详解
+
+
+
+### 4.1 题目生成规则
+
+所有生成器都经过重构,能够生成包含2-5个操作数及有意义括号的复杂表达式。
+
+#### 4.1.1 `PrimaryQuestionGenerator`
+
+为小学用户生成复杂的四则运算题目,智能地使用括号改变运算优先级。
+
+```
+((10 + 5) × 3) - 12
+```
+
+#### 4.1.2 `JuniorQuestionGenerator`
+
+在复杂表达式中,确保至少包含一个平方 (`^2`) 或开根号 (`√`) 运算。
+
+```
+(8 + √ (49)) × 3^2
+```
+
+#### 4.1.3 `SeniorQuestionGenerator`
+
+在复杂表达式中,确保至少包含一个三角函数 (`sin`, `cos`, `tan`),并已修复 `tan(90°)` 的bug。
+
+```
+5 × (sin(30°) + 4)
+```
+
+
+
+### 4.2 题目格式与计分
+
+- 所有题目均为四选一的选择题。
+- 系统会智能判断正确答案是整数还是小数,并生成逻辑相符的干扰选项。
+- 计分方式为 `(答对题数 / 总题数) * 100`,结果四舍五入取整。
+
+## 5. 运行说明
+
+
+
+1. **启动程序** 运行 `Main.java` 类,启动图形化登录窗口。
+2. **用户注册/登录**
+ - 新用户点击“注册”,按照“输入邮箱/用户名 -> 发送验证邮件 -> 查收邮件验证码 -> 设置密码”的流程完成注册。
+ - 老用户可使用邮箱或用户名直接登录。
+3. **开始考试** 在主菜单选择题目难度和数量,点击“开始答题”进入考试界面。
+4. **答题过程** 通过“上一题”、“下一题”进行导航,随时可通过“交卷并评分”按钮结束考试。
+5. **查看分数** 交卷后,分数界面会显示本次得分和答题详情。用户可选择“再来一组”或“退出登录”。
+
+
+
+## 6. 代码结构
+
+
+
+```
+src/
+├── Main.java
+├── config.properties
+├── controller/
+│ └── AppController.java
+├── model/
+│ ├── Question.java
+│ ├── User.java
+│ └── UserType.java
+├── service/
+│ ├── EmailService.java
+│ ├── ExamManager.java
+│ ├── ExpressionEvaluator.java
+│ ├── UserManager.java
+│ ├── ValidationService.java
+│ └── generator/
+│ └── ... (5个题目生成器相关文件)
+└── view/
+ └── ... (8个界面视图相关文件)
+```
+
+
+
+## 7. 依赖与配置
+
+### 软件依赖 (JAR文件)
+
+本项目需要以下4个外部库文件,以下依赖库以添加至总项目lib文件夹中
+
+- **FlatLaf UI主题**: `flatlaf-3.4.1.jar`
+
+- **Jakarta Mail API**: `jakarta.mail-api-2.1.2.jar`
+
+- **Angus Mail (实现)**: `angus-mail-2.0.2.jar`
+
+- **Angus Activation (依赖)**: `angus-activation-2.0.2.jar`
+
+
+
+### Windows 系统下运行步骤
+
+1. **配置 Java 22 编译环境**
+
+ 确保系统已安装 Java 22 JDK 版本
+
+2. **设置控制台编码**
+
+ 打开 cmd 命令行工具,使用以下指令执行 .jar 文件:
+
+ ```
+ java -jar .\MathExamApp.jar
+ ```
+
+
+
+### Linux 系统下运行步骤
+
+1. **配置 Java 22 JDK 版本**
+
+ 确保系统已安装 Java 22 JDK
+
+2. **运行程序**
+
+ 使用以下指令执行程序:
+
+ java -jar MathExamApp.jar
+
+
+
+## 8. 注意事项
+
+
+
+1. **数据文件夹**:程序第一次成功注册用户或生成试卷后,会在程序运行的根目录下自动创建以下文件夹:
+ - `data/`:此文件夹包含 `users.txt` 文件,用于存储所有用户的账号和密码信息。
+ - `exams/`:此文件夹下会以每个用户的邮箱/用户名创建子文件夹,用于存放该用户所有历史试卷的 `.txt` 文件。
+2. **请勿删除**:上述两个文件夹是程序正常运行和数据持久化的关键。**请不要手动删除或修改这些文件夹及其中的内容**,否则将导致所有用户账号信息和历史试卷记录丢失,且无法恢复。
\ No newline at end of file
diff --git a/lib/activation-1.1.1.jar b/lib/activation-1.1.1.jar
deleted file mode 100644
index 1b703ab..0000000
Binary files a/lib/activation-1.1.1.jar and /dev/null differ
diff --git a/lib/flatlaf-3.4.1.jar b/lib/flatlaf-3.4.1.jar
deleted file mode 100644
index f79f175..0000000
Binary files a/lib/flatlaf-3.4.1.jar and /dev/null differ
diff --git a/lib/jakarta.activation-api-2.1.0.jar b/lib/jakarta.activation-api-2.1.0.jar
deleted file mode 100644
index b125985..0000000
Binary files a/lib/jakarta.activation-api-2.1.0.jar and /dev/null differ
diff --git a/lib/javax.mail-1.6.2.jar b/lib/javax.mail-1.6.2.jar
deleted file mode 100644
index 0cd0528..0000000
Binary files a/lib/javax.mail-1.6.2.jar and /dev/null differ
diff --git a/src/Main.java b/src/Main.java
deleted file mode 100644
index 3a73467..0000000
--- a/src/Main.java
+++ /dev/null
@@ -1,20 +0,0 @@
-import com.formdev.flatlaf.FlatLightLaf;
-import controller.AppController;
-import javax.swing.SwingUtilities;
-import javax.swing.UIManager;
-import javax.swing.UnsupportedLookAndFeelException;
-
-public class Main {
- public static void main(String[] args) {
- System.setProperty("awt.useSystemAAFontSettings", "on");
- System.setProperty("swing.aatext", "true");
-
- try {
- UIManager.setLookAndFeel(new FlatLightLaf());
- } catch (UnsupportedLookAndFeelException e) {
- System.err.println("无法设置UI主题: " + e.getMessage());
- }
-
- SwingUtilities.invokeLater(AppController::new);
- }
-}
\ No newline at end of file
diff --git a/src/config.properties b/src/config.properties
deleted file mode 100644
index 15e5284..0000000
--- a/src/config.properties
+++ /dev/null
@@ -1,10 +0,0 @@
-
-smtp.host=smtp.qq.com
-smtp.port=465
-smtp.ssl.enable=true
-smtp.auth=true
-
-
-mail.sender.email=2631495488@qq.com
-
-mail.sender.password=vdjvddhflexgdjfd
\ No newline at end of file
diff --git a/src/model/Question.java b/src/model/Question.java
deleted file mode 100644
index f60269f..0000000
--- a/src/model/Question.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package model;
-
-import java.util.List;
-
-public class Question {
- private final String questionText;
- private final List options;
- private final int correctOptionIndex;
-
- public Question(String questionText, List options, int correctOptionIndex) {
- this.questionText = questionText;
- this.options = options;
- this.correctOptionIndex = correctOptionIndex;
- }
-
- public String getQuestionText() { return questionText; }
- public List getOptions() { return options; }
- public int getCorrectOptionIndex() { return correctOptionIndex; }
-}
\ No newline at end of file
diff --git a/src/model/User.java b/src/model/User.java
deleted file mode 100644
index efaf0d7..0000000
--- a/src/model/User.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package model;
-
-public class User {
- private final String email;
- private final String username; // 新增字段
- private String hashedPassword;
- private UserType userType;
-
- public User(String email, String username, String hashedPassword, UserType userType) {
- this.email = email;
- this.username = username;
- this.hashedPassword = hashedPassword;
- this.userType = userType;
- }
-
- public String getEmail() { return email; }
- public String getUsername() { return username; } // 新增Getter
- public String getHashedPassword() { return hashedPassword; }
- public UserType getUserType() { return userType; }
- public void setUserType(UserType userType) { this.userType = userType; }
-
- public void setHashedPassword(String plainPassword) {
- this.hashedPassword = Integer.toString(plainPassword.hashCode());
- }
-
- private void setHashedPasswordDirectly(String hashedPassword) {
- this.hashedPassword = hashedPassword;
- }
-
- @Override
- public String toString() {
- // 新的4字段格式,用户名可以为空字符串
- return String.join("|", email, username == null ? "" : username, hashedPassword, userType.name());
- }
-
-
- public static User fromString(String line) {
- String[] parts = line.split("\\|");
- User user = null;
- if (parts.length == 4) { // 新格式: email|username|password|type
- String email = parts[0];
- String username = parts[1].isEmpty() ? null : parts[1];
- String password = parts[2];
- UserType type = UserType.valueOf(parts[3]);
- user = new User(email, username, "", type);
- user.setHashedPasswordDirectly(password);
- } else if (parts.length == 3) { // 旧格式: email|password|type
- String email = parts[0];
- String password = parts[1];
- UserType type = UserType.valueOf(parts[2]);
- user = new User(email, null, "", type); // 旧用户没有用户名
- user.setHashedPasswordDirectly(password);
- }
- return user;
- }
-}
\ No newline at end of file
diff --git a/src/model/UserType.java b/src/model/UserType.java
deleted file mode 100644
index e532963..0000000
--- a/src/model/UserType.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package model;
-
-public enum UserType {
- PRIMARY("小学"),
- JUNIOR("初中"),
- SENIOR("高中");
-
- private final String displayName;
-
- UserType(String displayName) {
- this.displayName = displayName;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-}
\ No newline at end of file
diff --git a/src/service/EmailService.java b/src/service/EmailService.java
deleted file mode 100644
index 27fa9c5..0000000
--- a/src/service/EmailService.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package service;
-
-import javax.mail.*;
-import javax.mail.internet.*;
-import javax.activation.*;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-
-public class EmailService {
-
- final Properties props = new Properties();
- private final String senderEmail;
- private final String senderPassword;
-
- public EmailService() {
-
- try (InputStream input = EmailService.class.getClassLoader().getResourceAsStream("config.properties")) {
-
-
- if (input == null) {
- System.err.println("致命错误:在类路径中找不到 config.properties 文件!");
- System.err.println("请确认 'config.properties' 文件已放置在 'src' 文件夹根目录下。");
- throw new RuntimeException("加载配置文件失败: config.properties not found in classpath");
- }
-
- props.load(input);
-
- } catch (IOException e) {
- System.err.println("错误:加载 config.properties 文件时发生IO错误!");
- throw new RuntimeException("加载配置文件失败", e);
- }
-
- this.senderEmail = props.getProperty("mail.sender.email");
- this.senderPassword = props.getProperty("mail.sender.password");
- }
-
-
- public void sendVerificationCode(String recipientEmail, String code) throws MessagingException {
-
- Properties mailProps = new Properties();
- mailProps.put("mail.smtp.host", props.getProperty("smtp.host"));
- mailProps.put("mail.smtp.port", props.getProperty("smtp.port"));
- mailProps.put("mail.smtp.ssl.enable", props.getProperty("smtp.ssl.enable"));
- mailProps.put("mail.smtp.auth", props.getProperty("smtp.auth"));
-
- Authenticator authenticator = new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(senderEmail, senderPassword);
- }
- };
-
-
- Session session = Session.getInstance(mailProps, authenticator);
-
- MimeMessage message = new MimeMessage(session);
- message.setFrom(new InternetAddress(senderEmail));
- message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail));
- message.setSubject("您的数学学习软件验证码", "UTF-8");
-
-
- String content = "欢迎使用数学学习软件!
" +
- "您的注册验证码是:" + code + "
" +
- "该验证码5分钟内有效,请勿泄露给他人。
";
- message.setContent(content, "text/html; charset=UTF-8");
-
-
- Transport.send(message);
- System.out.println("验证码邮件已成功发送至 " + recipientEmail);
- }
-}
\ No newline at end of file
diff --git a/src/service/ExamManager.java b/src/service/ExamManager.java
deleted file mode 100644
index b57fb34..0000000
--- a/src/service/ExamManager.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package service;
-
-import model.Question;
-import model.User;
-import model.UserType;
-import service.generator.*;
-
-import java.io.PrintWriter;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.text.SimpleDateFormat;
-import java.util.*;
-
-public class ExamManager {
- private final Map generators = new HashMap<>();
-
- public ExamManager() {
- generators.put(UserType.PRIMARY, new PrimaryQuestionGenerator());
- generators.put(UserType.JUNIOR, new JuniorQuestionGenerator());
- generators.put(UserType.SENIOR, new SeniorQuestionGenerator());
- }
-
- public List generateExam(User user, UserType userType, int count) {
- QuestionGenerator generator = generators.get(userType);
- if (generator == null) throw new IllegalArgumentException("不支持的用户类型");
-
- List questions = new ArrayList<>();
- Set generatedTexts = new HashSet<>();
- int maxAttempts = count * 20;
- int attempts = 0;
-
- while (questions.size() < count && attempts < maxAttempts) {
- String qText = generator.generateQuestion();
- if (generatedTexts.contains(qText)) {
- attempts++;
- continue;
- }
- try {
- double answer = ExpressionEvaluator.evaluate(qText);
- if (Double.isInfinite(answer) || Double.isNaN(answer)) continue;
-
- List options = generateOptions(answer); // 调用已优化的方法
- int correctIndex = options.indexOf(formatAnswer(answer));
-
- questions.add(new Question(qText, options, correctIndex));
- generatedTexts.add(qText);
- } catch (Exception e) {
-
- } finally {
- attempts++;
- }
- }
-
- if (!questions.isEmpty()) {
- saveExamToFile(user, questions);
- }
-
- return questions;
- }
-
-
- private List generateOptions(double correctAnswer) {
- Set options = new LinkedHashSet<>();
- options.add(formatAnswer(correctAnswer));
- Random rand = new Random();
-
- // 判断答案是否为整数
- boolean isIntegerAnswer = Math.abs(correctAnswer - Math.round(correctAnswer)) < 1e-9;
-
- while (options.size() < 4) {
- String distractorStr;
- if (isIntegerAnswer) {
-
- int intAnswer = (int) Math.round(correctAnswer);
- int distractor;
- int type = rand.nextInt(3); // 三种整数干扰策略
-
- switch(type) {
- case 0: // 策略一:在答案附近加减一个较小的随机数
- distractor = intAnswer + (rand.nextInt(8) + 1) * (rand.nextBoolean() ? 1 : -1);
- break;
- case 1: // 策略二:模拟看错位的错误(加减10)
- distractor = intAnswer + (rand.nextBoolean() ? 10 : -10);
- break;
- default: // 策略三:颠倒个位和十位(如果答案是两位数)
- if (intAnswer >= 10 && intAnswer <= 99) {
- distractor = (intAnswer % 10) * 10 + (intAnswer / 10);
- } else {
- // 如果不是两位数,则使用策略一
- distractor = intAnswer + (rand.nextInt(8) + 1) * (rand.nextBoolean() ? 1 : -1);
- }
- break;
- }
-
- // 确保干扰项不等于正确答案
- if (distractor == intAnswer) {
- distractor++;
- }
- distractorStr = String.valueOf(distractor);
-
- } else {
- double distractor;
- if (rand.nextBoolean()) { // 乘法干扰
- distractor = correctAnswer * (rand.nextDouble() * 1.5 + 0.5);
- } else { // 临近值干扰
- distractor = correctAnswer + (rand.nextDouble() - 0.5) * Math.max(1, Math.abs(correctAnswer * 0.2));
- }
- distractorStr = formatAnswer(distractor);
- }
-
- // 确保选项不重复
- if (!options.contains(distractorStr)) {
- options.add(distractorStr);
- }
- }
-
- List shuffledOptions = new ArrayList<>(options);
- Collections.shuffle(shuffledOptions);
- return shuffledOptions;
- }
-
- private void saveExamToFile(User user, List questions) {
- try {
- Path userExamDir = Paths.get("exams", user.getEmail());
- Files.createDirectories(userExamDir);
-
- String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
- Path examFile = userExamDir.resolve("exam_" + timestamp + ".txt");
-
- try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(examFile, StandardCharsets.UTF_8))) {
- writer.println("用户: " + user.getEmail());
- writer.println("时间: " + new Date());
- writer.println("题目类型: " + user.getUserType().getDisplayName());
- writer.println("题目数量: " + questions.size());
- writer.println("========================================");
- writer.println();
-
- for (int i = 0; i < questions.size(); i++) {
- Question q = questions.get(i);
- writer.println((i + 1) + ". " + q.getQuestionText());
-
- List options = q.getOptions();
- char optionLabel = 'A';
- for (int j = 0; j < options.size(); j++) {
- writer.println(" " + optionLabel + ". " + options.get(j));
- optionLabel++;
- }
-
- writer.println(" 正确答案: " + (char)('A' + q.getCorrectOptionIndex()));
- writer.println();
- }
- }
- System.out.println("试卷已成功保存至: " + examFile.toAbsolutePath());
- } catch (IOException e) {
- System.err.println("保存试卷文件时出错: " + e.getMessage());
- }
- }
-
- private String formatAnswer(double number) {
- if (Math.abs(number - Math.round(number)) < 1e-9) {
- return String.valueOf((long) Math.round(number));
- } else {
- return String.format("%.2f", number);
- }
- }
-}
\ No newline at end of file
diff --git a/src/service/ExpressionEvaluator.java b/src/service/ExpressionEvaluator.java
deleted file mode 100644
index 912fc7f..0000000
--- a/src/service/ExpressionEvaluator.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package service;
-
-import java.util.Stack;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-
-public class ExpressionEvaluator {
-
-
-
- public static double evaluate(String expression) {
- // 1. 预处理,将特殊运算(开方、平方、三角函数)直接计算并替换为数值
- String processedExpr = preprocessSpecialOperations(expression);
-
- // 2. 使用双栈算法计算最终的四则运算表达式
- return calculateStandardExpression(processedExpr);
- }
-
-
- private static String preprocessSpecialOperations(String expr) {
- // 替换中文运算符
- expr = expr.replace('×', '*').replace('÷', '/');
-
- // 匹配并计算三角函数,例如 sin(30°)
- Pattern trigPattern = Pattern.compile("(sin|cos|tan)\\((\\d+)°\\)");
- Matcher trigMatcher = trigPattern.matcher(expr);
- while (trigMatcher.find()) {
- String func = trigMatcher.group(1);
- double angle = Double.parseDouble(trigMatcher.group(2));
- double value = 0.0;
- switch (func) {
- case "sin": value = Math.sin(Math.toRadians(angle)); break;
- case "cos": value = Math.cos(Math.toRadians(angle)); break;
- case "tan": value = Math.tan(Math.toRadians(angle)); break;
- }
- expr = trigMatcher.replaceFirst(String.format("%.4f", value));
- trigMatcher = trigPattern.matcher(expr);
- }
-
- // 匹配并计算平方,例如 5^2
- Pattern squarePattern = Pattern.compile("(\\d+)\\^2");
- Matcher squareMatcher = squarePattern.matcher(expr);
- while (squareMatcher.find()) {
- double base = Double.parseDouble(squareMatcher.group(1));
- expr = squareMatcher.replaceFirst(String.valueOf(base * base));
- squareMatcher = squarePattern.matcher(expr);
- }
-
- // 匹配并计算开方,例如 √(16)
- Pattern sqrtPattern = Pattern.compile("√\\((\\d+)\\)");
- Matcher sqrtMatcher = sqrtPattern.matcher(expr);
- while (sqrtMatcher.find()) {
- double num = Double.parseDouble(sqrtMatcher.group(1));
- expr = sqrtMatcher.replaceFirst(String.valueOf(Math.sqrt(num)));
- sqrtMatcher = sqrtPattern.matcher(expr);
- }
-
- return expr;
- }
-
-
- private static double calculateStandardExpression(String expression) {
- Stack values = new Stack<>();
- Stack ops = new Stack<>();
- char[] tokens = expression.toCharArray();
-
- for (int i = 0; i < tokens.length; i++) {
- if (tokens[i] == ' ') continue;
-
- if ((tokens[i] >= '0' && tokens[i] <= '9') || tokens[i] == '.') {
- StringBuilder sbuf = new StringBuilder();
- while (i < tokens.length && ((tokens[i] >= '0' && tokens[i] <= '9') || tokens[i] == '.')) {
- sbuf.append(tokens[i++]);
- }
- i--;
- values.push(Double.parseDouble(sbuf.toString()));
- } else if (tokens[i] == '(') {
- ops.push(tokens[i]);
- } else if (tokens[i] == ')') {
- while (ops.peek() != '(') {
- values.push(applyOp(ops.pop(), values.pop(), values.pop()));
- }
- ops.pop();
- } else if (isOperator(tokens[i])) {
- while (!ops.empty() && hasPrecedence(tokens[i], ops.peek())) {
- values.push(applyOp(ops.pop(), values.pop(), values.pop()));
- }
- ops.push(tokens[i]);
- }
- }
-
- while (!ops.empty()) {
- values.push(applyOp(ops.pop(), values.pop(), values.pop()));
- }
-
- return values.pop();
- }
-
- private static boolean isOperator(char c) {
- return c == '+' || c == '-' || c == '*' || c == '/';
- }
-
- private static boolean hasPrecedence(char op1, char op2) {
- if (op2 == '(' || op2 == ')') return false;
- return (op1 != '*' && op1 != '/') || (op2 != '+' && op2 != '-');
- }
-
- private static double applyOp(char op, double b, double a) {
- switch (op) {
- case '+': return a + b;
- case '-': return a - b;
- case '*': return a * b;
- case '/':
- if (b == 0) throw new UnsupportedOperationException("不能除以零");
- return a / b;
- }
- return 0;
- }
-}
\ No newline at end of file
diff --git a/src/service/UserManager.java b/src/service/UserManager.java
deleted file mode 100644
index 00f0a29..0000000
--- a/src/service/UserManager.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package service;
-
-import model.User;
-import model.UserType;
-import java.io.*;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.*;
-import java.util.*;
-
-
-
-public class UserManager {
- private final Path usersFile = Paths.get("data", "users.txt");
- private final Map usersByEmail = new HashMap<>(); // 使用邮箱作为主键
-
- public UserManager() {
- try {
- Files.createDirectories(usersFile.getParent());
- if (!Files.exists(usersFile)) Files.createFile(usersFile);
- loadUsers();
- } catch (IOException e) {
- System.err.println("初始化用户管理器时出错: " + e.getMessage());
- }
- }
-
- private void loadUsers() throws IOException {
- List lines = Files.readAllLines(usersFile, StandardCharsets.UTF_8);
- for (String line : lines) {
- User user = User.fromString(line);
- if (user != null) usersByEmail.put(user.getEmail(), user);
- }
- }
-
- private void saveUsers() {
- try (BufferedWriter writer = Files.newBufferedWriter(usersFile, StandardCharsets.UTF_8)) {
- for (User user : usersByEmail.values()) writer.write(user.toString() + "\n");
- } catch (IOException e) {
- System.err.println("保存用户时出错: " + e.getMessage());
- }
- }
-
-
-
- public User authenticate(String identifier, String password) {
- String hashedInput = hashPassword(password);
-
- // 策略一:尝试将 identifier 作为邮箱进行快速查找
- User userByEmail = usersByEmail.get(identifier);
- if (userByEmail != null && userByEmail.getHashedPassword().equals(hashedInput)) {
- return userByEmail; // 邮箱匹配成功
- }
-
- // 策略二:如果邮箱查找失败,则遍历所有用户,尝试匹配用户名
- for (User user : usersByEmail.values()) {
- if (user.getUsername() != null && user.getUsername().equals(identifier)) {
- if (user.getHashedPassword().equals(hashedInput)) {
- return user; // 用户名匹配成功
- }
- break; // 用户名是唯一的,找到后无需继续遍历
- }
- }
-
- return null; // 认证失败
- }
-
- public boolean emailExists(String email) {
- return usersByEmail.containsKey(email);
- }
-
- public boolean usernameExists(String username) {
- if (username == null || username.trim().isEmpty()) return false;
- for (User user : usersByEmail.values()) {
- if (username.equals(user.getUsername())) {
- return true;
- }
- }
- return false;
- }
-
- public void registerUser(String email, String username, String password, UserType type) {
- User newUser = new User(email, username, "", type);
- newUser.setHashedPassword(password);
- usersByEmail.put(email, newUser);
- saveUsers();
- }
-
- public void updateUser(User user) {
- if (usersByEmail.containsKey(user.getEmail())) {
- usersByEmail.put(user.getEmail(), user);
- saveUsers();
- }
- }
-
- private String hashPassword(String password) {
- return Integer.toString(password.hashCode());
- }
-}
\ No newline at end of file
diff --git a/src/service/ValidationService.java b/src/service/ValidationService.java
deleted file mode 100644
index d3f839f..0000000
--- a/src/service/ValidationService.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package service;
-
-
-public class ValidationService {
- public static boolean isValidEmail(String email) {
- if (email == null || email.trim().isEmpty()) {
- return false;
- }
- String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
- return email.matches(emailRegex);
- }
-
- public static boolean isPasswordStrong(String password) {
- if (password == null || password.length() < 6 || password.length() > 10) {
- return false;
- }
- boolean hasUpperCase = password.matches(".*[A-Z].*");
- boolean hasLowerCase = password.matches(".*[a-z].*");
- boolean hasDigit = password.matches(".*[0-9].*");
-
- return hasUpperCase && hasLowerCase && hasDigit;
- }
-}
-
-
diff --git a/src/service/generator/AbstractQuestionGenerator.java b/src/service/generator/AbstractQuestionGenerator.java
deleted file mode 100644
index f230a5e..0000000
--- a/src/service/generator/AbstractQuestionGenerator.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package service.generator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Random;
-
-public abstract class AbstractQuestionGenerator implements QuestionGenerator {
- protected final Random rand = new Random();
- private static final char[] OPS = {'+', '-', '×', '÷'};
-
- protected String buildComplexExpression(List operands) {
- int numOperands = operands.size();
- if (numOperands < 2) return operands.get(0); // 如果只有一个操作数,直接返回
-
- List operators = new ArrayList<>();
- for (int i = 0; i < numOperands - 1; i++) {
- operators.add(OPS[rand.nextInt(OPS.length)]);
- }
-
- int parensType = rand.nextInt(5); // 增加不加括号的概率
-
- switch (parensType) {
- case 1: // 单括号 (a op b) op c ...
- if (numOperands >= 3) {
- Collections.swap(operands, 0, rand.nextInt(numOperands));
- Collections.swap(operands, 1, rand.nextInt(numOperands));
- return String.format("(%s %c %s) %c %s", operands.get(0), operators.get(0), operands.get(1), operators.get(1), buildRest(operands, operators, 2));
- }
- break;
- case 2: // a op (b op c) op d ...
- if (numOperands >= 3) {
- return String.format("%s %c (%s %c %s)", operands.get(0), operators.get(0), operands.get(1), operators.get(1), buildRest(operands, operators, 2));
- }
- break;
- case 3: // 双重括号 ((a op b) op c) op d ...
- if (numOperands >= 4) {
- return String.format("((%s %c %s) %c %s) %c %s", operands.get(0), operators.get(0), operands.get(1), operators.get(1), operands.get(2), operators.get(2), buildRest(operands, operators, 3));
- }
- break;
- case 4: // 双重括号 a op (b op (c op d)) ...
- if (numOperands >= 4) {
- return String.format("%s %c (%s %c (%s %c %s))", operands.get(0), operators.get(0), operands.get(1), operators.get(1), operands.get(2), operators.get(2), buildRest(operands, operators, 3));
- }
- break;
- }
-
- // 默认情况:无括号或无法应用括号
- return buildRest(operands, operators, 0);
- }
-
- private String buildRest(List operands, List operators, int startIndex) {
- StringBuilder sb = new StringBuilder();
- sb.append(operands.get(startIndex));
- for (int i = startIndex; i < operators.size(); i++) {
- sb.append(" ").append(operators.get(i)).append(" ");
- sb.append(operands.get(i + 1));
- }
- return sb.toString();
- }
-}
\ No newline at end of file
diff --git a/src/service/generator/JuniorQuestionGenerator.java b/src/service/generator/JuniorQuestionGenerator.java
deleted file mode 100644
index 4a400b6..0000000
--- a/src/service/generator/JuniorQuestionGenerator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package service.generator;
-
-import java.util.Random;
-public class JuniorQuestionGenerator implements QuestionGenerator {
- private final Random rand = new Random();
-
- @Override
- public String generateQuestion() {
- int numOperands = rand.nextInt(4) + 2; // 2-5个操作数
- String[] operands = new String[numOperands];
-
- // 先填充普通数字
- for (int i = 0; i < numOperands; i++) {
- operands[i] = String.valueOf(num(15));
- }
-
- // 随机替换一个为特殊运算
- int specialIndex = rand.nextInt(numOperands);
- operands[specialIndex] = specialOp();
-
- // 根据操作数数量选择模板
- switch (numOperands) {
- case 2:
- return String.format("%s %c %s", operands[0], op(), operands[1]);
- case 3:
- return String.format("(%s %c %s) %c %s", operands[0], opLow(), operands[1], opHigh(), operands[2]);
- case 4:
- return String.format("(%s %c %s) %c (%s %c %s)", operands[0], opHigh(), operands[1], opLow(), operands[2], opLow(), operands[3]);
- case 5:
- default:
- return String.format("((%s %c %s) %c %s) %c (%s %c %s)", operands[0], opLow(), operands[1], opHigh(), operands[2], opLow(), operands[3], opLow(), operands[4]);
- }
- }
-
- // --- 辅助方法 ---
- private int num(int bound) { return rand.nextInt(bound) + 1; }
- private char op() { return "+-×".charAt(rand.nextInt(3)); }
- private char opLow() { return "+-".charAt(rand.nextInt(2)); }
- private char opHigh() { return '×'; }
-
- private String specialOp() {
- if (rand.nextBoolean()) {
- return (rand.nextInt(9) + 2) + "^2"; // 2^2 to 10^2
- } else {
- int base = rand.nextInt(10) + 1;
- return "√(" + (base * base) + ")";
- }
- }
-}
\ No newline at end of file
diff --git a/src/service/generator/PrimaryQuestionGenerator.java b/src/service/generator/PrimaryQuestionGenerator.java
deleted file mode 100644
index 5ac4c1c..0000000
--- a/src/service/generator/PrimaryQuestionGenerator.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package service.generator;
-
-import java.util.Random;
-
-public class PrimaryQuestionGenerator implements QuestionGenerator {
- private final Random rand = new Random();
-
- @Override
- public String generateQuestion() {
- int numOperands = rand.nextInt(4) + 2; // 随机生成2-5个操作数
-
- switch (numOperands) {
- case 2:
- return simpleExpression(num(50), op(), num(50));
- case 3:
- return generateThreeOperandExpression();
- case 4:
- return generateFourOperandExpression();
- case 5:
- default:
- return generateFiveOperandExpression();
- }
- }
-
- private String generateThreeOperandExpression() {
- if (rand.nextBoolean()) {
- // 模板: (a + b) * c
- return String.format("(%d %c %d) %c %d", num(20), opLow(), num(20), opHigh(), num(10));
- } else {
- // 模板: a * (b - c)
-
- int b = num(29) + 1; // b 的范围是 2 到 30
- int c = num(b - 1); // c 的范围是 1 到 b-1,确保 c < b
- return String.format("%d %c (%d %c %d)", num(10), opHigh(), b, opLow(), c);
- }
- }
-
- private String generateFourOperandExpression() {
- if (rand.nextBoolean()) {
- // 模板: ((a + b) * c) - d
- return String.format("((%d %c %d) %c %d) %c %d", num(10), opLow(), num(10), opHigh(), num(5), opLow(), num(20));
- } else {
- // 模板: a * (b - (c + d))
- return String.format("%d %c (%d %c (%d %c %d))", num(5), opHigh(), num(50), opLow(), num(10), opLow(), num(10));
- }
- }
-
- private String generateFiveOperandExpression() {
- // 模板: (a * b) + (c - d) * e
- int c = num(29) + 1; // c 的范围是 2 到 30
- int d = num(c - 1); // d 的范围是 1 到 c-1,确保 d < c
- return String.format("(%d %c %d) %c (%d %c %d) %c %d", num(10), opHigh(), num(10), opLow(), c, opLow(), d, opHigh(), num(5));
- }
-
- // --- 辅助方法 ---
- private int num(int bound) {
- // 健壮性检查,防止外部调用传入无效的bound
- if (bound <= 0) return 1;
- return rand.nextInt(bound) + 1;
- }
- private char op() { return "+-×÷".charAt(rand.nextInt(4)); }
- private char opLow() { return "+-".charAt(rand.nextInt(2)); } // 低优先级
- private char opHigh() { return "×÷".charAt(rand.nextInt(2)); } // 高优先级
- private String simpleExpression(int a, char op, int b) {
- if (op == '÷') a = b * num(10);
- if (op == '-') a = Math.max(a, b + 1);
- return String.format("%d %c %d", a, op, b);
- }
-}
\ No newline at end of file
diff --git a/src/service/generator/QuestionGenerator.java b/src/service/generator/QuestionGenerator.java
deleted file mode 100644
index 81d2230..0000000
--- a/src/service/generator/QuestionGenerator.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package service.generator;
-
-public interface QuestionGenerator {
- String generateQuestion();
-}
\ No newline at end of file
diff --git a/src/service/generator/SeniorQuestionGenerator.java b/src/service/generator/SeniorQuestionGenerator.java
deleted file mode 100644
index 0d882b5..0000000
--- a/src/service/generator/SeniorQuestionGenerator.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package service.generator;
-
-import java.util.Random;
-
-public class SeniorQuestionGenerator implements QuestionGenerator {
- private final Random rand = new Random();
- private static final String[] TRIG_FUNCS = {"sin", "cos", "tan"};
- private static final int[] COMMON_ANGLES = {0, 30, 45, 60, 90,135,180};
- private static final int[] TAN_SAFE_ANGLES = {0, 30, 45, 60,135};
-
- @Override
- public String generateQuestion() {
- int numOperands = rand.nextInt(3) + 2; // 2-4个操作数以控制复杂度
- String[] operands = new String[numOperands];
-
- for (int i = 0; i < numOperands; i++) {
- operands[i] = String.valueOf(num(10));
- }
-
- int specialIndex = rand.nextInt(numOperands);
- operands[specialIndex] = specialOp();
-
- switch (numOperands) {
- case 2:
- return String.format("%s %c %s", operands[0], op(), operands[1]);
- case 3:
- return String.format("(%s %c %s) %c %s", operands[0], opLow(), operands[1], opHigh(), operands[2]);
- case 4:
- default:
- return String.format("%s %c (%s %c (%s %c %s))", operands[0], opHigh(), operands[1], opLow(), operands[2], opLow(), operands[3]);
- }
- }
-
- // --- 辅助方法 ---
- private int num(int bound) { return rand.nextInt(bound) + 1; }
- private char op() { return "+-×".charAt(rand.nextInt(3)); }
- private char opLow() { return "+-".charAt(rand.nextInt(2)); }
- private char opHigh() { return '×'; }
-
- private String specialOp() {
- String func = TRIG_FUNCS[rand.nextInt(TRIG_FUNCS.length)];
- int angle;
-
- if (func.equals("tan")) {
- angle = TAN_SAFE_ANGLES[rand.nextInt(TAN_SAFE_ANGLES.length)];
- } else {
- angle = COMMON_ANGLES[rand.nextInt(COMMON_ANGLES.length)];
- }
- return String.format("%s(%d°)", func, angle);
- }
-}
\ No newline at end of file