diff --git a/src/main/java/com/ybw/mathapp/LoginAndRegister.java b/src/main/java/com/ybw/mathapp/LoginAndRegister.java
new file mode 100644
index 0000000..b4b83c8
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/LoginAndRegister.java
@@ -0,0 +1,132 @@
+package com.ybw.mathapp;
+
+// UserService.java
+import com.ybw.mathapp.entity.User;
+import com.ybw.mathapp.util.EmailService;
+import com.ybw.mathapp.util.LoginFileUtils;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+
+public class LoginAndRegister {
+ private static Scanner scanner = new Scanner(System.in);
+
+ // UserService.java 中的注册方法更新
+ public static boolean register() {
+ System.out.println("\n=== 用户注册 ===");
+
+ // 输入邮箱
+ System.out.print("请输入邮箱地址: ");
+ String email = scanner.nextLine().trim();
+
+ if (!isValidEmail(email)) {
+ return false;
+ }
+
+ if (LoginFileUtils.isEmailRegistered(email)) {
+ System.out.println("该邮箱已注册,请直接登录!");
+ return false;
+ }
+
+ // 发送、验证验证码
+ if (!sendAndVerifyCode(email)) {
+ return false;
+ }
+
+ // 设置密码(其余代码保持不变)
+ System.out.print("请输入密码: ");
+ String password1 = scanner.nextLine();
+ System.out.print("请再次输入密码: ");
+ String password2 = scanner.nextLine();
+ if(!isVaildPassword(password1, password2)) {
+ return false;
+ }
+
+ User user = new User(email, password1);
+ LoginFileUtils.saveUser(user);
+ System.out.println("注册成功!您可以使用邮箱和密码登录了。");
+ return true;
+ }
+
+ // 登录流程
+ public static boolean login() {
+ System.out.println("\n=== 用户登录 ===");
+
+ System.out.print("请输入邮箱: ");
+ String email = scanner.nextLine().trim();
+
+ System.out.print("请输入密码: ");
+ String password = scanner.nextLine();
+
+ if (LoginFileUtils.validateUser(email, password)) {
+ System.out.println("登录成功!欢迎回来," + email);
+ return true;
+ } else {
+ System.out.println("邮箱或密码错误!");
+ return false;
+ }
+ }
+
+ //
+ /**
+ * 邮箱格式验证
+ * @param email 待验证的邮箱地址
+ * @return true表示邮箱格式正确,false表示邮箱格式错误
+ */
+ private static boolean isValidEmail(String email) {
+ if (email.isEmpty()) {
+ System.out.println("邮箱地址不能为空!");
+ return false;
+ }
+ if (!(email.contains("@") && email.contains("."))) {
+ System.out.println("邮箱格式不正确!");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 密码格式验证
+ * @param password1 第一次输入的密码
+ * @param password2 第二次输入的密码
+ * @return true表示符合要求,false表示不符合
+ */
+ public static boolean isVaildPassword(String password1, String password2) {
+ if (password1 == null || password1.length() < 6 || password1.length() > 10) {
+ return false;
+ }
+
+ // 使用正则表达式验证:长度6-10,只包含字母数字,且包含大小写字母和数字
+ String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$";
+ if (!Pattern.matches(regex, password1)) {
+ return false;
+ }
+
+ System.out.print("请再次输入密码: ");
+ if (!password1.equals(password2)) {
+ System.out.println("两次输入的密码不一致!");
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean sendAndVerifyCode(String email) {
+ // 发送真实邮件验证码
+ String verificationCode = EmailService.generateVerificationCode();
+ System.out.println("正在发送验证码邮件,请稍候...");
+
+ if (!EmailService.sendVerificationCode(email, verificationCode)) {
+ System.out.println("发送验证码失败,请检查邮箱配置或稍后重试!");
+ return false;
+ }
+
+ // 验证验证码
+ System.out.print("请输入收到的验证码: ");
+ String inputCode = scanner.nextLine().trim();
+
+ if (!EmailService.verifyCode(email, inputCode)) {
+ System.out.println("验证码错误或已过期!");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/config/EmailConfig.java b/src/main/java/com/ybw/mathapp/config/EmailConfig.java
new file mode 100644
index 0000000..8331ea5
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/config/EmailConfig.java
@@ -0,0 +1,18 @@
+package com.ybw.mathapp.config;
+
+public class EmailConfig {
+ // 发件人邮箱配置(以QQ邮箱为例)
+ public static final String SMTP_HOST = "smtp.qq.com";
+ public static final String SMTP_PORT = "587";
+ public static final String SENDER_EMAIL = "1798231811@qq.com"; // 替换为你的邮箱
+ public static final String SENDER_PASSWORD = "dzmfirotgnlceeae"; // 替换为你的授权码
+
+ // 如果使用Gmail
+ // public static final String SMTP_HOST = "smtp.gmail.com";
+ // public static final String SMTP_PORT = "587";
+ // public static final String SENDER_EMAIL = "your_email@gmail.com";
+ // public static final String SENDER_PASSWORD = "your_app_password";
+
+ public static final String EMAIL_SUBJECT = "【用户注册】验证码";
+ public static final int CODE_EXPIRY_MINUTES = 5;
+}
diff --git a/src/main/java/com/ybw/mathapp/entity/User.java b/src/main/java/com/ybw/mathapp/entity/User.java
new file mode 100644
index 0000000..91e46c0
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/entity/User.java
@@ -0,0 +1,95 @@
+package com.ybw.mathapp.entity;
+
+/**
+ * 用户实体类,表示系统中的用户信息。
+ *
+ * 该类包含用户的基本信息,如用户名、密码和学习级别。
+ * 用户级别可以是小学、初中或高中。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public class User {
+
+ /** 用户名,不可修改。 */
+ // private final String name;
+
+ /** 邮箱,不可修改。 */
+ private final String email;
+
+ /** 用户密码,不可修改。 */
+ private final String password;
+
+ /** 用户当前的学习级别,可以修改。 */
+ private String level;
+
+ /**
+ * 构造一个新的用户对象。
+ *
+ * @param email 邮箱,不能为空 用户名,不能为空
+ * @param password 用户密码,不能为空
+ */
+ public User(String email, String password) {
+ this.password = password;
+ this.email = email;
+ }
+
+ /**
+ * 获取用户密码。
+ *
+ * @return 用户密码
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * 获取用户当前的学习级别。
+ *
+ * @return 用户学习级别
+ */
+ public String getLevel() {
+ return level;
+ }
+
+ /**
+ * 获取用户邮箱。
+ *
+ * @return 用户邮箱
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * 设置用户的学习级别。
+ *
+ * @param newLevel 新的学习级别,支持"小学"、"初中"、"高中"
+ */
+ public void setLevel(String newLevel) {
+ level = newLevel;
+ }
+
+ /**
+ * 保存邮箱+密码。
+ *
+ * @return 邮箱+密码
+ */
+ @Override
+ public String toString() {
+ return email + "," + password;
+ }
+
+ public static User fromString(String line) {
+ if (line == null || line.trim().isEmpty()) {
+ return null;
+ }
+
+ String[] parts = line.split(",", 2); // 最多分割成2部分
+ if (parts.length == 2) {
+ return new User(parts[0].trim(), parts[1].trim());
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/service/Caculate.java b/src/main/java/com/ybw/mathapp/service/Caculate.java
new file mode 100644
index 0000000..9592645
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/Caculate.java
@@ -0,0 +1,198 @@
+package com.ybw.mathapp.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * 用于计算包含四则运算、括号、平方、开根号、三角函数的表达式。
+ * 注意:三角函数的输入通常认为是度数 (Degree)。
+ */
+public class Caculate {
+
+ /**
+ * 计算表达式的值。
+ * @param parts 表达式分解后的列表,例如 ["(", "2", "+", "3", ")", "*", "4"]
+ * @return 计算结果
+ */
+ public double caculate(List parts) {
+ // 将中缀表达式转换为后缀表达式(逆波兰表示法)
+ List postfix = infixToPostfix(parts);
+ // 计算后缀表达式的值
+ return evaluatePostfix(postfix);
+ }
+
+ private List infixToPostfix(List infix) {
+ Map precedence = new HashMap<>();
+ precedence.put("+", 1);
+ precedence.put("-", 1);
+ precedence.put("*", 2);
+ precedence.put("/", 2);
+ // "平方" 和 "开根号" 作为后缀运算符,优先级最高
+ // "sin", "cos", "tan" 作为前缀函数,优先级也很高
+ // 这里简化处理,将它们都视为高优先级,但需要特殊处理其结合性
+ // 实际上,"平方", "开根号" 是后缀一元运算符
+ // "sin", "cos", "tan" 是前缀一元函数
+ // 标准的调度场算法需要扩展来处理一元运算符和函数
+ // 为了简化,我们假设它们的优先级为 3
+ precedence.put("平方", 3);
+ precedence.put("开根号", 3);
+ precedence.put("sin", 3);
+ precedence.put("cos", 3);
+ precedence.put("tan", 3);
+
+ Stack operatorStack = new Stack<>();
+ List postfix = new ArrayList<>();
+
+ for (String token : infix) {
+ if (isNumeric(token)) {
+ postfix.add(token);
+ } else if ("(".equals(token)) {
+ operatorStack.push(token);
+ } else if (")".equals(token)) {
+ while (!operatorStack.isEmpty() && !"(".equals(operatorStack.peek())) {
+ postfix.add(operatorStack.pop());
+ }
+ if (!operatorStack.isEmpty()) { // Pop the '('
+ operatorStack.pop();
+ }
+ } else if (precedence.containsKey(token)) {
+ // 处理运算符和函数
+ // 对于前缀函数 (sin, cos, tan),它们没有左操作数,直接入栈
+ // 对于后缀运算符 (平方, 开根号),它们作用于前面的一个操作数
+ // 这里简化处理,将它们都当作普通二元运算符入栈,然后在计算时特殊处理
+ // 实际上,对于后缀运算符,应该立即处理它前面的一个操作数
+ // 对于前缀函数,应该立即处理它后面的一个操作数
+ // 这需要修改调度场算法或在 evaluatePostfix 中处理
+ // 最好的方式是:在生成器生成时,将 "x平方" -> ["x", "平方"],将 "sin(x)" -> ["sin", "x"]
+ // 这样 "平方", "开根号" 就是后缀,"sin", "cos", "tan" 就是前缀
+ // 但生成器可能生成 "开根号(x)" 或 "(x)平方"
+ // 这使得解析变得复杂
+ // 我们尝试一种方法:在解析时,如果遇到 "开根号",它后面必须跟 "(" 和表达式 ")"
+ // 将 "开根号(...)" 视为一个整体 token
+ // 同理,"sin(...)", "cos(...)", "tan(...)" 也是如此
+ // 或者,在调度场算法中,当遇到 "开根号", "平方" 时,它们是后缀,立即处理
+ // 当遇到 "sin", "cos", "tan" 时,它们是前缀,立即处理
+ // 这需要修改算法
+ // 一个简化的处理方法是:假设 "开根号", "平方" 总是作用于紧随其后的表达式(可能用括号包围)
+ // "sin", "cos", "tan" 也是作用于紧随其后的表达式(通常用括号包围)
+ // 但这与标准的 "x平方" 或 "开根号(x)" 不同
+ // 标准的 "x平方" -> ["x", "平方"] (后缀)
+ // 标准的 "sin(x)" -> ["sin", "x"] (前缀)
+ // 生成器生成的 "开根号(16)" -> ["开根号", "(", "16", ")"]
+ // 生成器生成的 "(2+3)平方" -> ["(", "2", "+", "3", ")", "平方"]
+ // 生成器生成的 "sin 30" -> ["sin", "30"] (如果生成器是这样分词的)
+ // 生成器生成的 "sin(30)" -> ["sin", "(", "30", ")"] (如果生成器是这样分词的)
+ // 这里我们假设 parts 已经是标准的后缀/前缀形式,或者调度场算法能处理 "开根号", "平方", "sin", "cos", "tan" 作为特殊运算符
+ // 我们尝试一个近似方法:将 "开根号", "平方", "sin", "cos", "tan" 视为高优先级的特殊运算符
+ // 在调度场算法中,遇到它们就立即处理(如果它们作用于前面或后面的操作数)
+ // 这在某些复杂嵌套情况下可能不准确,但对于大多数情况应该足够
+ // 更好的方法是使用 Shunting-yard 的扩展版本,或者使用递归下降解析器
+
+ // 暂时按照标准调度场算法处理,但在 evaluatePostfix 中特殊处理
+ // 将 "平方", "开根号", "sin", "cos", "tan" 放入运算符栈
+ while (!operatorStack.isEmpty() &&
+ precedence.containsKey(operatorStack.peek()) &&
+ precedence.get(operatorStack.peek()) >= precedence.get(token)) {
+ postfix.add(operatorStack.pop());
+ }
+ operatorStack.push(token);
+ } else {
+ // 其他token,例如数字的一部分(如果格式错误)
+ throw new IllegalArgumentException("Unknown token: " + token);
+ }
+ }
+
+ while (!operatorStack.isEmpty()) {
+ postfix.add(operatorStack.pop());
+ }
+
+ return postfix;
+ }
+
+ private double evaluatePostfix(List postfix) {
+ Stack stack = new Stack<>();
+ for (String token : postfix) {
+ if (isNumeric(token)) {
+ stack.push(Double.parseDouble(token));
+ } else {
+ double result = 0;
+ double operand = 0; // 用于一元运算符
+ double operand2 = 0; // 用于二元运算符
+ double operand1 = 0; // 用于二元运算符
+
+ switch (token) {
+ case "+":
+ operand2 = stack.pop();
+ operand1 = stack.pop();
+ stack.push(operand1 + operand2);
+ break;
+ case "-":
+ operand2 = stack.pop();
+ operand1 = stack.pop();
+ stack.push(operand1 - operand2);
+ break;
+ case "*":
+ operand2 = stack.pop();
+ operand1 = stack.pop();
+ stack.push(operand1 * operand2);
+ break;
+ case "/":
+ operand2 = stack.pop();
+ operand1 = stack.pop();
+ if (operand2 == 0) {
+ throw new ArithmeticException("Division by zero");
+ }
+ stack.push(operand1 / operand2);
+ break;
+ case "平方": // 一元后缀运算符
+ operand = stack.pop();
+ stack.push(operand * operand);
+ break;
+ case "开根号": // 一元后缀运算符
+ operand = stack.pop();
+ if (operand < 0) {
+ throw new ArithmeticException("Square root of negative number");
+ }
+ stack.push(Math.sqrt(operand));
+ break;
+ case "sin": // 一元前缀函数
+ operand = stack.pop();
+ // 假设输入是度数 (Degree)
+ stack.push(Math.sin(Math.toRadians(operand)));
+ break;
+ case "cos": // 一元前缀函数
+ operand = stack.pop();
+ // 假设输入是度数 (Degree)
+ stack.push(Math.cos(Math.toRadians(operand)));
+ break;
+ case "tan": // 一元前缀函数
+ operand = stack.pop();
+ // 假设输入是度数 (Degree)
+ stack.push(Math.tan(Math.toRadians(operand)));
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown operator in postfix: " + token);
+ }
+ }
+ }
+ if (stack.size() != 1) {
+ throw new IllegalStateException("Invalid expression evaluation - stack size: " + stack.size());
+ }
+ return stack.peek();
+ }
+
+ private static boolean isNumeric(String str) {
+ if (str == null || str.isEmpty()) {
+ return false;
+ }
+ try {
+ Double.parseDouble(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/ChoiceGenerator.java b/src/main/java/com/ybw/mathapp/service/ChoiceGenerator.java
new file mode 100644
index 0000000..dae8fc3
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/ChoiceGenerator.java
@@ -0,0 +1,191 @@
+package com.ybw.mathapp.service;
+
+// File: mathpuzzle/service/MultipleChoiceGenerator.java
+import java.util.*;
+
+/**
+ * 选择题生成器,负责为给定的题目生成器生成的题目添加选项,并在本次生成中去重。
+ *
+ * 该生成器会调用传入的 {@link QuestionGenerator} 生成原始题目,
+ * 计算正确答案,并生成指定数量的干扰项,最终形成选择题。
+ * 干扰项的生成方式是基于正确答案添加一个随机的小误差。
+ * 查重逻辑确保本次生成的题目列表中没有重复。
+ *
+ * @author 你的名字
+ * @version 1.0
+ * @since 2025
+ */
+public class ChoiceGenerator {
+
+ private final QuestionGenerator generator;
+ private final Random random = new Random();
+ private static final int OPTIONS_COUNT = 4; // 默认选项数量
+
+ /**
+ * 构造函数,指定题目生成器。
+ *
+ * @param generator 用于生成题目的 {@link QuestionGenerator} 实例。
+ */
+ public ChoiceGenerator(QuestionGenerator generator) {
+ this.generator = generator;
+ }
+
+ /**
+ * 生成指定数量的选择题。
+ * 该方法会生成原始题目和答案,然后为每道题生成干扰项,
+ * 并将选项打乱顺序。同时,确保生成的题目在本次调用中不重复。
+ *
+ * @param count 需要生成的选择题数量。
+ * @return 包含选择题的列表,每个元素包含题目、选项和正确答案索引。
+ */
+ public List generateMultipleChoiceQuestions(int count) {
+ List mcQuestions = new ArrayList<>();
+ Set generatedQuestionTexts = new HashSet<>(); // 用于本次生成过程中的查重
+ Caculate calculator = new Caculate(); // 使用计算器实例
+
+ int attempts = 0;
+ int maxAttempts = count * 100; // 设置最大尝试次数,防止无限循环
+
+ while (mcQuestions.size() < count && attempts < maxAttempts) {
+ attempts++;
+ // 生成原始题目
+ List rawQuestions = generator.generateQuestions(1);
+ if (rawQuestions.isEmpty()) {
+ continue; // 如果生成器返回空,跳过
+ }
+
+ String rawQuestion = rawQuestions.get(0);
+ String questionTextForDedup = rawQuestion.endsWith(" =") ?
+ rawQuestion.substring(0, rawQuestion.length() - 2) : rawQuestion;
+
+ // 检查是否重复
+ if (generatedQuestionTexts.contains(questionTextForDedup)) {
+ continue; // 如果重复,重新生成
+ }
+
+ // 计算正确答案
+ double correctAnswer = calculateAnswer(rawQuestion, calculator);
+ if (Double.isNaN(correctAnswer) || Double.isInfinite(correctAnswer)) {
+ continue; // 如果计算出错,跳过这道题
+ }
+
+ // 生成选项
+ List options = generateOptions(correctAnswer);
+ // 随机打乱选项
+ Collections.shuffle(options);
+ // 找到正确答案在打乱后列表中的索引
+ int correctIndex = options.indexOf(correctAnswer);
+
+ // 添加到结果列表和查重集合
+ mcQuestions.add(new MultipleChoiceQuestion(rawQuestion, options, correctIndex));
+ generatedQuestionTexts.add(questionTextForDedup);
+ }
+
+ if (mcQuestions.size() < count) {
+ System.out.println("警告:在尝试了 " + maxAttempts + " 次后,仅生成了 " + mcQuestions.size() + " 道不重复的选择题。");
+ }
+
+ return mcQuestions;
+ }
+
+ /**
+ * 计算给定题目的答案。
+ * @param question 题目字符串,例如 "2 + 3 * 4 ="
+ * @param calc 计算器实例
+ * @return 计算得出的答案。
+ */
+ private double calculateAnswer(String question, Caculate calc) {
+ // 移除 " ="
+ String expression = question.substring(0, question.length() - 2).trim();
+ // 将表达式字符串分割成部分
+ // 这里需要根据 QuestionGenerator 生成的格式来决定如何分割
+ // 如果 QuestionGenerator 生成的是 "开根号(16)" 或 "(2+3)平方",则按空格分割可能不够
+ // 但通常,生成器会生成 "开根号 ( 16 )" 或 "开根号 16" 或 "( 2 + 3 ) 平方" 这样的格式
+ // 按空格分割 ["开根号", "(", "16", ")"] 或 ["开根号", "16"] 或 ["(", "2", "+", "3", ")", "平方"]
+ // CaculatePrimary 需要能处理这些格式
+ List parts = Arrays.asList(expression.split("\\s+")); // 按空格分割
+
+ try {
+ return calc.caculate(parts);
+ } catch (Exception e) {
+ System.err.println("计算表达式失败: " + expression + ", 错误: " + e.getMessage());
+ return Double.NaN; // 或者抛出异常
+ }
+ }
+
+ /**
+ * 生成选项列表,包含一个正确答案和若干干扰项。
+ *
+ * @param correctAnswer 正确答案。
+ * @return 包含正确答案和干扰项的列表。
+ */
+ private List generateOptions(double correctAnswer) {
+ List options = new ArrayList<>();
+ options.add(correctAnswer); // 添加正确答案
+
+ for (int i = 1; i < OPTIONS_COUNT; i++) {
+ // 生成干扰项:在正确答案基础上加一个随机误差
+ // 误差范围可以根据需要调整,例如 +/- 10% 或固定范围
+ double error = correctAnswer * (random.nextDouble() * 0.2 - 0.1); // +/- 10% 的误差
+ // 为了避免干扰项过于接近或重复,可以添加一些逻辑
+ double incorrectAnswer = correctAnswer + error;
+ // 确保干扰项不等于正确答案,且不重复
+ while (Math.abs(incorrectAnswer - correctAnswer) < 0.001 || options.contains(incorrectAnswer)) { // 使用一个小的容差比较
+ error = correctAnswer * (random.nextDouble() * 0.2 - 0.1);
+ incorrectAnswer = correctAnswer + error;
+ // 防止无限循环,如果误差太小,强制加一个最小值
+ if (Math.abs(error) < 0.001) {
+ incorrectAnswer = correctAnswer + (random.nextBoolean() ? 0.01 : -0.01);
+ }
+ }
+ options.add(incorrectAnswer);
+ }
+
+ return options;
+ }
+
+ /**
+ * 用于封装一道选择题的内部类。
+ */
+ public static class MultipleChoiceQuestion {
+ private final String question; // 题目文本
+ private final List options; // 选项列表
+ private final int correctIndex; // 正确选项的索引
+
+ public MultipleChoiceQuestion(String question, List options, int correctIndex) {
+ this.question = question;
+ this.options = new ArrayList<>(options); // 创建副本以防外部修改
+ this.correctIndex = correctIndex;
+ }
+
+ public String getQuestion() {
+ return question;
+ }
+
+ public List getOptions() {
+ return new ArrayList<>(this.options); // 返回副本
+ }
+
+ public int getCorrectIndex() {
+ return correctIndex;
+ }
+
+ public Double getCorrectAnswer() {
+ if (correctIndex >= 0 && correctIndex < options.size()) {
+ return options.get(correctIndex);
+ }
+ return null; // 或抛出异常
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Question: ").append(question).append("\n");
+ for (int i = 0; i < options.size(); i++) {
+ sb.append((char)('A' + i)).append(". ").append(options.get(i)).append("\n");
+ }
+ sb.append("Correct Answer: ").append((char)('A' + correctIndex)).append(" (").append(options.get(correctIndex)).append(")\n");
+ return sb.toString();
+ }
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/service/FileHandler.java b/src/main/java/com/ybw/mathapp/service/FileHandler.java
new file mode 100644
index 0000000..8cd486a
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/FileHandler.java
@@ -0,0 +1,69 @@
+package com.ybw.mathapp.service;
+
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import mathpuzzle.entity.User;
+
+/**
+ * 文件处理器,负责创建用户目录和保存试卷文件。
+ *
+ * 该类提供用户目录管理功能和试卷文件保存功能。
+ * 每次生成的试卷会以时间戳命名保存到对应用户的目录中。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public class FileHandler {
+
+ /**
+ * 确保用户目录存在,如果不存在则创建。
+ *
+ *
该方法检查用户对应的目录是否存在,如果不存在则创建该目录。
+ * 用户目录以用户名命名,位于当前工作目录下。
+ *
+ * @param user 需要创建目录的用户对象
+ * @throws IOException 当目录创建过程中发生错误时抛出
+ */
+ public void ensureUserDirectory(User user) throws IOException {
+ String dirPath = "./" + user.getName();
+ Path path = Paths.get(dirPath);
+ if (!Files.exists(path)) {
+ Files.createDirectories(path);
+ }
+ }
+
+ /**
+ * 保存试卷到用户目录中。
+ *
+ *
该方法将生成的题目列表保存到文件中,文件名包含时间戳信息,
+ * 以确保每次生成的试卷都有唯一的文件名。每个题目前添加题号, 题目之间用空行分隔。
+ *
+ * @param user 试卷所属的用户对象
+ * @param questions 需要保存的题目列表
+ * @throws IOException 当文件写入过程中发生错误时抛出
+ */
+ public void savePaper(User user, List questions) throws IOException {
+ ensureUserDirectory(user);
+ // 生成文件名:年-月-日-时-分-秒.txt
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
+ String fileName = LocalDateTime.now().format(formatter) + ".txt";
+ String filePath = "./" + user.getName() + "/" + fileName;
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
+ for (int i = 0; i < questions.size(); i++) {
+ writer.write((i + 1) + ". " + questions.get(i)); // 添加题号
+ writer.newLine();
+ writer.newLine(); // 每题之间空一行
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/JuniorHighGenerator.java b/src/main/java/com/ybw/mathapp/service/JuniorHighGenerator.java
new file mode 100644
index 0000000..11bc7b1
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/JuniorHighGenerator.java
@@ -0,0 +1,187 @@
+package com.ybw.mathapp.service;
+
+import static com.ybw.mathapp.service.PrimarySchoolGenerator.isNumeric;
+import static com.ybw.mathapp.service.PrimarySchoolGenerator.isNumeric;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 初中题目生成器,负责生成包含平方或开根号运算的初中级别数学题目。
+ *
+ * 该生成器确保每道题目都包含至少一个高级运算符(平方或开根号),
+ * 题目结构包含基本的四则运算和高级运算的组合。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public class JuniorHighGenerator implements QuestionGenerator {
+
+ /**
+ * 高级运算符数组,包含"平方"和"开根号"。
+ */
+ private static final String[] ADVANCED_OPS = {"平方", "开根号"};
+
+ /**
+ * 基本运算符数组,包含四则运算符号。
+ */
+ private static final String[] OPERATORS = {"+", "-", "*", "/"};
+
+ /**
+ * 随机数生成器,用于生成随机题目。
+ */
+ private final Random random = new Random();
+
+ @Override
+ public List generateQuestions(int count) {
+ List questions = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ String question = generateSingleQuestion();
+ questions.add(question);
+ }
+ return questions;
+ }
+
+ /**
+ * 生成单个初中级别的数学题目。
+ *
+ * 该方法确保生成的题目包含至少一个高级运算符(平方或开根号),
+ * 并根据操作数数量采用不同的生成策略。
+ *
+ * @return 生成的数学题目字符串
+ */
+ private String generateSingleQuestion() {
+ List parts = new ArrayList<>();
+ int operandCount = random.nextInt(5) + 1;
+ parts = generateBase(operandCount, parts);
+ // hasAdvancedOp用以检测下面的循环是否加入了高级运算符,如果没有就启动保底
+ boolean hasAdvancedOp = false;
+ if (operandCount == 1) {
+ if ("平方".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) {
+ parts.add("平方");
+ } else {
+ parts.add(0, "开根号");
+ }
+ hasAdvancedOp = true;
+ } else {
+ // 遍历查找左括号的合理位置
+ for (int i = 0; i < parts.size() - 2; i++) {
+ // 该位置要为操作数且随机添加括号
+ if (isNumeric(parts.get(i)) && random.nextBoolean()) {
+ // 随机数看取出来的是不是开根号运算符
+ if ("开根号".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) {
+ parts = generateRoot(parts, i);
+ } else { // 如果不是开根号就是平方运算
+ parts = generateSquare(parts, i);
+ }
+ hasAdvancedOp = true;
+ break;
+ }
+ }
+ }
+ // 启动保底强制加入一个高级运算符
+ if (!hasAdvancedOp) {
+ parts = forceAddAdvancedOp(parts);
+ }
+ return String.join(" ", parts) + " =";
+ }
+
+ /**
+ * 生成基本的四则运算表达式部分。
+ *
+ * 该方法生成指定数量的操作数和运算符,构成基础的数学表达式。
+ *
+ * @param operandCount 操作数的数量
+ * @param parts 用于存储表达式各部分的列表
+ * @return 包含基本运算表达式的列表
+ */
+ public List generateBase(int operandCount, List parts) {
+ for (int i = 0; i < operandCount; i++) {
+ int num = random.nextInt(100) + 1;
+ parts.add(String.valueOf(num));
+ if (i < operandCount - 1) {
+ parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
+ }
+ }
+ return parts;
+ }
+
+ /**
+ * 强制在表达式中添加一个高级运算符作为保底机制。
+ *
+ * 当随机生成过程中没有添加高级运算符时,使用此方法确保
+ * 每道题目都包含至少一个高级运算符。
+ *
+ * @param parts 包含表达式各部分的列表
+ * @return 添加了高级运算符的表达式列表
+ */
+ public List forceAddAdvancedOp(List parts) {
+ String advancedOp = ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)];
+ if ("平方".equals(advancedOp)) {
+ parts.add("平方");
+ } else { // 开根号
+ parts.set(0, "开根号(" + parts.get(0));
+ parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")");
+ }
+ return parts;
+ }
+
+ /**
+ * 在指定位置生成开根号运算。
+ *
+ * 该方法在表达式指定位置添加开根号运算,可能只对单个操作数
+ * 进行开根号,或者对一段子表达式进行开根号。
+ *
+ * @param parts 包含表达式各部分的列表
+ * @param i 开根号运算的起始位置
+ * @return 添加了开根号运算的表达式列表
+ */
+ public List generateRoot(List parts, int i) {
+ if (random.nextBoolean()) {
+ parts.set(i, "开根号(" + parts.get(i) + ")");
+ } else {
+ parts.set(i, "开根号(" + parts.get(i));
+ // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况
+ if (i == parts.size() - 3) {
+ parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")");
+ } else {
+ while (true) {
+ int i2 = random.nextInt(parts.size() - 3 - i) + 2;
+ if (isNumeric(parts.get(i + i2))) {
+ parts.set(i + i2, parts.get(i + i2) + ")");
+ break;
+ }
+ }
+ }
+ }
+ return parts;
+ }
+
+ /**
+ * 在指定位置生成平方运算。
+ *
+ * 该方法在表达式指定位置添加平方运算,对一段子表达式进行平方运算。
+ *
+ * @param parts 包含表达式各部分的列表
+ * @param i 平方运算的起始位置
+ * @return 添加了平方运算的表达式列表
+ */
+ public List generateSquare(List parts, int i) {
+ parts.set(i, "(" + parts.get(i));
+ // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况
+ if (i == parts.size() - 3) {
+ parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")");
+ } else {
+ while (true) {
+ int i2 = random.nextInt(parts.size() - 3 - i) + 2;
+ if (isNumeric(parts.get(i + i2))) {
+ parts.set(i + i2, parts.get(i + i2) + ")平方");
+ break;
+ }
+ }
+ }
+ return parts;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/PrimarySchoolGenerator.java b/src/main/java/com/ybw/mathapp/service/PrimarySchoolGenerator.java
new file mode 100644
index 0000000..e70946f
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/PrimarySchoolGenerator.java
@@ -0,0 +1,121 @@
+package com.ybw.mathapp.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 小学题目生成器,负责生成包含四则运算和括号的小学级别数学题目。
+ *
+ * 该生成器专门用于生成适合小学生的数学题目,题目仅包含加减乘除四则运算
+ * 和括号,确保计算结果为非负数。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public class PrimarySchoolGenerator implements QuestionGenerator {
+
+ /** 运算符数组,包含四则运算符号。 */
+ private static final String[] OPERATORS = {"+", "-", "*", "/"};
+
+ /** 随机数生成器,用于生成随机题目。 */
+ private final Random random = new Random();
+
+ @Override
+ public List generateQuestions(int count) {
+ List questions = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ String question = generateSingleQuestion();
+ questions.add(question);
+ }
+ return questions;
+ }
+
+ /**
+ * 生成单个小级别的数学题目.
+ *
+ * 该方法生成包含2-5个操作数的四则运算表达式,可能包含括号,
+ * 并确保计算结果为非负数。如果计算结果为负数,则重新生成。
+ *
+ * @return 生成的小学数学题目字符串
+ */
+ private String generateSingleQuestion() {
+ Caculate caculate = new Caculate();
+ int operandCount = random.nextInt(4) + 2; // 2-5个操作数
+ List parts = new ArrayList<>();
+ while (true) {
+ // 生成基础操作
+ parts = generateBase(operandCount, parts);
+ // 简单添加括号逻辑:随机加一个括号
+ if (operandCount > 2 && random.nextBoolean()) {
+ // 遍历查找左括号的合理位置
+ for (int i = 0; i < parts.size() - 2; i++) {
+ // 该位置要为操作数且随机添加括号
+ if (isNumeric(parts.get(i)) && random.nextBoolean()) {
+ parts.add(i, "(");
+ i++;
+ // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况
+ if (i == parts.size() - 3) {
+ parts.add(")");
+ } else {
+ while (true) {
+ int i2 = random.nextInt(parts.size() - 3 - i) + 2;
+ if (isNumeric(parts.get(i + i2))) {
+ parts.add(i + i2 + 1, ")");
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (caculate.caculate(parts) >= 0) {
+ return String.join(" ", parts) + " =";
+ } else {
+ parts.clear();
+ }
+ }
+ }
+
+ /**
+ * 判断给定字符串是否为数字。
+ *
+ * 该方法检查字符串是否可以转换为数字格式。
+ *
+ * @param str 待检查的字符串
+ * @return 如果字符串是数字则返回true,否则返回false
+ */
+ public static boolean isNumeric(String str) {
+ if (str == null || str.isEmpty()) {
+ return false;
+ }
+ try {
+ Double.parseDouble(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 生成基本的四则运算表达式部分。
+ *
+ *
该方法生成指定数量的操作数和运算符,构成基础的数学表达式。
+ *
+ * @param operandCount 操作数的数量
+ * @param parts 用于存储表达式各部分的列表
+ * @return 包含基本运算表达式的列表
+ */
+ public List generateBase(int operandCount, List parts) {
+ for (int i = 0; i < operandCount; i++) {
+ int num = random.nextInt(100) + 1;
+ parts.add(String.valueOf(num));
+ if (i < operandCount - 1) {
+ parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
+ }
+ }
+ return parts;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/QuestionDeduplicator.java b/src/main/java/com/ybw/mathapp/service/QuestionDeduplicator.java
new file mode 100644
index 0000000..b4319ee
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/QuestionDeduplicator.java
@@ -0,0 +1,91 @@
+package com.ybw.mathapp.service;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import mathpuzzle.entity.User;
+
+/**
+ * 题目查重器,负责加载历史题目并检查新题目是否重复。
+ *
+ * 该类维护一个题目集合,用于检测当前生成的题目是否与用户的历史题目重复。
+ * 通过加载用户目录下的所有历史试卷文件,提取题目内容进行去重检查。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public class QuestionDeduplicator {
+
+ /**
+ * 存储用户历史题目的集合。
+ */
+ private final Set existingQuestions = new HashSet<>();
+
+ /**
+ * 加载指定用户的所有历史题目。
+ *
+ * 该方法遍历用户目录下的所有.txt文件,读取并解析题目内容,
+ * 将历史题目添加到去重集合中。此操作会清空之前的题目记录。
+ *
+ * @param user 需要加载历史题目的用户对象
+ */
+ public void loadExistingQuestions(User user) {
+ existingQuestions.clear();
+ String userDir = "./" + user.getName();
+ File dir = new File(userDir);
+
+ if (!dir.exists()) {
+ return; // 目录不存在,无历史题目
+ }
+
+ File[] files = dir.listFiles((d, name) -> name.endsWith(".txt"));
+ if (files == null) {
+ return;
+ }
+
+ for (File file : files) {
+ try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ // 只加载题目行,忽略题号和空行
+ if (line.trim().isEmpty() || line.matches("\\d+\\. .*")) {
+ String questionContent = line.replaceFirst("\\d+\\. ", "").trim();
+ if (!questionContent.isEmpty() && !questionContent.equals("=")) {
+ existingQuestions.add(questionContent);
+ }
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("读取历史文件时出错: " + file.getName());
+ }
+ }
+ }
+
+ /**
+ * 检查指定题目是否为重复题目。
+ *
+ *
该方法检查给定的题目是否已经存在于历史题目集合中。
+ *
+ * @param question 待检查的题目内容
+ * @return 如果题目重复则返回true,否则返回false
+ */
+ public boolean isDuplicate(String question) {
+ return existingQuestions.contains(question);
+ }
+
+ /**
+ * 将新题目添加到去重集合中。
+ *
+ *
该方法将当前生成的题目添加到去重集合中,用于防止
+ * 在同一次生成过程中出现重复题目。
+ *
+ * @param question 需要添加的题目内容
+ */
+ public void addQuestion(String question) {
+ existingQuestions.add(question);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/QuestionGenerator.java b/src/main/java/com/ybw/mathapp/service/QuestionGenerator.java
new file mode 100644
index 0000000..104466d
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/QuestionGenerator.java
@@ -0,0 +1,28 @@
+package com.ybw.mathapp.service;
+
+import java.util.List;
+
+/**
+ * 题目生成器接口,定义了题目生成器的标准方法。
+ *
+ *
所有具体的题目生成器都应该实现此接口,提供统一的题目生成功能。
+ * 不同级别的题目生成器(如小学、初中、高中)可以根据各自的特点
+ * 实现不同的生成算法。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public interface QuestionGenerator {
+
+ /**
+ * 生成指定数量的数学题目。
+ *
+ *
该方法根据实现类的特定规则生成指定数量的数学题目。
+ * 生成的题目应该符合对应教育级别的难度要求。
+ *
+ * @param count 需要生成的题目数量
+ * @return 包含生成题目的字符串列表
+ */
+ List generateQuestions(int count);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/QuestionWithAnswer.java b/src/main/java/com/ybw/mathapp/service/QuestionWithAnswer.java
new file mode 100644
index 0000000..336a461
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/QuestionWithAnswer.java
@@ -0,0 +1,31 @@
+package com.ybw.mathapp.service;
+
+// File: mathpuzzle/entity/QuestionWithAnswer.java
+/**
+ * 用于封装一道题目及其正确答案。
+ */
+public class QuestionWithAnswer {
+ private final String question; // 题目字符串,例如 "2 + 3 ="
+ private final double correctAnswer; // 计算得出的正确答案
+
+ public QuestionWithAnswer(String question, double correctAnswer) {
+ this.question = question;
+ this.correctAnswer = correctAnswer;
+ }
+
+ public String getQuestion() {
+ return question;
+ }
+
+ public double getCorrectAnswer() {
+ return correctAnswer;
+ }
+
+ @Override
+ public String toString() {
+ return "QuestionWithAnswer{" +
+ "question='" + question + '\'' +
+ ", correctAnswer=" + correctAnswer +
+ '}';
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/service/SeniorHighGenerator.java b/src/main/java/com/ybw/mathapp/service/SeniorHighGenerator.java
new file mode 100644
index 0000000..074e5d7
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/SeniorHighGenerator.java
@@ -0,0 +1,126 @@
+package com.ybw.mathapp.service;
+
+import static mathpuzzle.service.PrimarySchoolGenerator.isNumeric;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 高中题目生成器,负责生成包含三角函数运算的高中级别数学题目。
+ *
+ * 该生成器确保每道题目都包含至少一个三角函数运算符(sin、cos或tan),
+ * 题目结构包含基本的四则运算和三角函数运算的组合。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public class SeniorHighGenerator implements QuestionGenerator {
+
+ /** 三角函数运算符数组,包含"sin"、"cos"和"tan"。 */
+ private static final String[] TRIG_FUNCS = {"sin", "cos", "tan"};
+
+ /** 基本运算符数组,包含四则运算符号。 */
+ private static final String[] OPERATORS = {"+", "-", "*", "/"};
+
+ /** 随机数生成器,用于生成随机题目。 */
+ private final Random random = new Random();
+
+ @Override
+ public List generateQuestions(int count) {
+ List questions = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ String question = generateSingleQuestion();
+ questions.add(question);
+ }
+ return questions;
+ }
+
+ /**
+ * 生成单个高中级别的数学题目。
+ *
+ * 该方法确保生成的题目包含至少一个三角函数运算符,
+ * 并根据操作数数量采用不同的生成策略。
+ *
+ * @return 生成的数学题目字符串
+ */
+ private String generateSingleQuestion() {
+ List parts = new ArrayList<>();
+ int operandCount = random.nextInt(5) + 1;
+ parts = generateBase(operandCount, parts);
+ String advancedOp;
+ if (operandCount == 1) {
+ advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
+ parts.set(0, advancedOp + parts.get(0));
+ } else {
+ // 遍历查找左括号的合理位置
+ for (int i = 0; i < parts.size(); i++) {
+ // 最后一次循环保底生成高中三角函数
+ if (i == parts.size() - 1) {
+ advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
+ parts.set(i, advancedOp + parts.get(i));
+ } else if (isNumeric(parts.get(i)) && random.nextBoolean()) { // 随机数看是否为操作数且随即进入生成程序
+ // 进入随机生成tan\sin\cos的程序
+ parts = generateTrig(parts, i);
+ break;
+ }
+ }
+ }
+ return String.join(" ", parts) + " =";
+ }
+
+ /**
+ * 生成基本的四则运算表达式部分。
+ *
+ * 该方法生成指定数量的操作数和运算符,构成基础的数学表达式。
+ *
+ * @param operandCount 操作数的数量
+ * @param parts 用于存储表达式各部分的列表
+ * @return 包含基本运算表达式的列表
+ */
+ // 产生基本操作
+ public List generateBase(int operandCount, List parts) {
+ for (int i = 0; i < operandCount; i++) {
+ int num = random.nextInt(100) + 1;
+ parts.add(String.valueOf(num));
+ if (i < operandCount - 1) {
+ parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
+ }
+ }
+ return parts;
+ }
+
+ /**
+ * 在指定位置生成三角函数运算。
+ *
+ * 该方法在表达式指定位置添加三角函数运算,可能只对单个操作数
+ * 进行三角函数运算,或者对一段子表达式进行三角函数运算。
+ *
+ * @param parts 包含表达式各部分的列表
+ * @param i 三角函数运算的位置
+ * @return 添加了三角函数运算的表达式列表
+ */
+ // 产生三角函数运算符
+ public List generateTrig(List parts, int i) {
+ String trigOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
+ if (random.nextBoolean()) {
+ parts.set(i, trigOp + parts.get(i));
+ } else {
+ parts.set(i, trigOp + "(" + parts.get(i));
+ // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况
+ if (i == parts.size() - 3) {
+ parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")");
+ } else {
+ while (true) {
+ int i2 = random.nextInt(parts.size() - 3 - i) + 2;
+ if (isNumeric(parts.get(i + i2))) {
+ parts.set(i + i2, parts.get(i + i2) + ")");
+ break;
+ }
+ }
+ }
+ }
+ return parts;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/StartController.java b/src/main/java/com/ybw/mathapp/service/StartController.java
new file mode 100644
index 0000000..ef0ff3c
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/StartController.java
@@ -0,0 +1,132 @@
+package com.ybw.mathapp.service;
+
+import com.ybw.mathapp.LoginAndRegister;
+import com.ybw.mathapp.service.ChoiceGenerator.MultipleChoiceQuestion;
+import com.ybw.mathapp.system.LogSystem;
+import java.io.IOException;
+import java.util.List;
+import java.util.Scanner;
+
+public class StartController {
+
+ private LogSystem logSystem = new LogSystem();
+ private FileHandler fileHandler = new FileHandler();
+ private double score;
+
+ public double getScore() {
+ return score;
+ }
+
+ public void setScore(double score) {
+ this.score = score;
+ }
+
+ public void start() {
+ Scanner scanner = new Scanner(System.in);
+ while (true) {
+ // 此处应添加初始界面(登录、注册界面)函数
+ if (registerButtonPressed()) {
+ // 此处添加加载注册界面函数
+ if (!LoginAndRegister.register()) {
+ continue;
+ }
+ } else {
+ if (!LoginAndRegister.login()) {
+ continue;
+ }
+ }
+ while (true) {
+ // 此处添加小学、初中、高中选择界面函数 (修改密码按钮也应在此界面)
+ if (changePasswordButtonPressed()) {
+ ChangePassword();
+ } else {
+ while (true) {
+ // 此处添加小学、初中、高中按钮点击响应函数,creatGenerate函数,返回值为QuestionGenerator
+ QuestionGenerator generator = null;
+ // 出题数目页面
+ String input = " ";
+ try {
+ int count = Integer.parseInt(input);
+ if (count < 10 || count > 30) {
+ System.out.println("题目数量必须在10-30之间!");
+ continue;
+ }
+ try {
+ handleMultipleChoiceGeneration(generator, count); // 调用生成题目函数
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ } catch (NumberFormatException e) {
+ System.out.println("请输入数字!");
+ continue;
+ }
+ System.out.println(score);
+ score = 0;
+ }
+ }
+ }
+ }
+ }
+
+ // ... (handleLevelSwitch 和 createGenerator 保持不变)
+
+ // 处理选择题生成,进行本次生成内的去重工作
+ private void handleMultipleChoiceGeneration(QuestionGenerator generator, int count) throws IOException {
+
+ ChoiceGenerator mcGenerator = new ChoiceGenerator(generator);
+
+ // 生成选择题列表,内部已处理去重
+ List finalQuestions = mcGenerator.generateMultipleChoiceQuestions(count);
+ int rightCount = 0;
+ // 显示选择题
+ for (int i = 0; i < finalQuestions.size(); i++) {
+ MultipleChoiceQuestion mcq = finalQuestions.get(i);
+ System.out.println((i + 1) + ". " + mcq.getQuestion());
+ List options = mcq.getOptions();
+ int correctAnswerIndex = mcq.getCorrectIndex();
+ for (int j = 0; j < options.size(); j++) {
+ // 此处可以转换成ABCD
+ System.out.println(" " + (char)('A' + j) + ". " + options.get(j));
+ }
+ // 显示选择题界面,等待用户选择
+ while (true) {
+ if(nextButtonPressed()) {
+ if (isCorrectAnswer (input, correctAnswerIndex, options)) {
+ rightCount++;
+ System.out.println("正确!");
+ } else {
+ System.out.println("答案错误!");
+ }
+ break;
+ }
+ }
+ }
+ setScore(caculateScore(rightCount, finalQuestions.size()));
+ }
+
+ public QuestionGenerator createGenerator(String level) {
+ switch (level) {
+ case "小学":
+ return new PrimarySchoolGenerator();
+ case "初中":
+ return new JuniorHighGenerator();
+ case "高中":
+ return new SeniorHighGenerator();
+ default:
+ return null;
+ }
+ }
+
+ public boolean isCorrectAnswer(String input, int correctAnswerIndex, List options) {
+ if(input.equals(String.valueOf(options.get(correctAnswerIndex)))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public double caculateScore(int rightCount, int totalCount) {
+ return rightCount / (double) totalCount;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/system/LogSystem.java b/src/main/java/com/ybw/mathapp/system/LogSystem.java
new file mode 100644
index 0000000..c867a63
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/system/LogSystem.java
@@ -0,0 +1,39 @@
+package com.ybw.mathapp.system;
+
+import java.util.HashMap;
+import java.util.Scanner;
+import com.ybw.mathapp.entity.User;
+
+public class LogSystem {
+ private final HashMap userHashMap = new HashMap<>();
+ public void userHashMapInit() {
+ // 小学
+ userHashMap.put("1798231811@qq.com", new User("1798231811@qq.com", "1234567"));
+
+ }
+
+ public User login() {
+ System.out.println("请输入用户名和密码,两者之间用空格隔开,用户名为邮箱账号");
+ while(true) {
+ Scanner scanner = new Scanner(System.in);
+ String[] info = scanner.nextLine().split(" ");
+ if(info.length != 2) {
+ System.out.println("请输入正确格式");
+ } else {
+ String name = info[0];
+ String password = info[1];
+ User user = userHashMap.get(name);
+ if (user == null) {
+ System.out.println("邮箱未注册");
+ }
+ else if (!user.getPassword().equals(password)) {
+ System.out.println("请输入正确的用户名、密码");
+ }
+ else {
+ System.out.println("当前选择为" + user.getLevel() + "出题");
+ return user;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/util/EmailService.java b/src/main/java/com/ybw/mathapp/util/EmailService.java
new file mode 100644
index 0000000..7de816d
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/util/EmailService.java
@@ -0,0 +1,158 @@
+package com.ybw.mathapp.util;
+
+import com.ybw.mathapp.config.EmailConfig;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Random;
+import jakarta.mail.Authenticator;
+import jakarta.mail.Message;
+import jakarta.mail.MessagingException;
+import jakarta.mail.PasswordAuthentication;
+import jakarta.mail.Session;
+import jakarta.mail.Transport;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeMessage;
+
+public class EmailService {
+ private static Map verificationCodes = new HashMap<>();
+
+ // 验证码信息内部类
+ private static class VerificationCodeInfo {
+ String code;
+ long timestamp;
+
+ VerificationCodeInfo(String code, long timestamp) {
+ this.code = code;
+ this.timestamp = timestamp;
+ }
+ }
+
+ // 生成6位随机验证码
+ public static String generateVerificationCode() {
+ Random random = new Random();
+ int code = 100000 + random.nextInt(900000);
+ return String.valueOf(code);
+ }
+
+ // 发送真实邮件验证码
+ public static boolean sendVerificationCode(String recipientEmail, String code) {
+ try {
+ // 创建邮件会话
+ Properties props = new Properties();
+ props.put("mail.smtp.host", EmailConfig.SMTP_HOST);
+ props.put("mail.smtp.port", EmailConfig.SMTP_PORT);
+ props.put("mail.smtp.auth", "true");
+ props.put("mail.smtp.starttls.enable", "true");
+ props.put("mail.smtp.ssl.protocols", "TLSv1.2");
+
+ // 创建认证器
+ Authenticator auth = new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(
+ EmailConfig.SENDER_EMAIL,
+ EmailConfig.SENDER_PASSWORD
+ );
+ }
+ };
+
+ Session session = Session.getInstance(props, auth);
+
+ // 创建邮件消息
+ Message message = new MimeMessage(session);
+ message.setFrom(new InternetAddress(EmailConfig.SENDER_EMAIL));
+ message.setRecipients(Message.RecipientType.TO,
+ InternetAddress.parse(recipientEmail));
+ message.setSubject(EmailConfig.EMAIL_SUBJECT);
+
+ // 创建邮件内容
+ String emailContent = createEmailContent(code);
+ message.setContent(emailContent, "text/html; charset=utf-8");
+
+ // 发送邮件
+ Transport.send(message);
+
+ // 存储验证码信息
+ verificationCodes.put(recipientEmail,
+ new VerificationCodeInfo(code, System.currentTimeMillis()));
+
+ System.out.println("验证码已发送到邮箱: " + recipientEmail);
+ return true;
+
+ } catch (MessagingException e) {
+ System.err.println("发送邮件失败: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ // 创建HTML格式的邮件内容
+ private static String createEmailContent(String code) {
+ return "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "
" +
+ "
您好!
" +
+ "
您正在注册账户,验证码如下:
" +
+ "
" + code + "
" +
+ "
验证码有效期为 " + EmailConfig.CODE_EXPIRY_MINUTES + " 分钟,请勿泄露给他人。
" +
+ "
如果这不是您本人的操作,请忽略此邮件。
" +
+ "
" +
+ "" +
+ "
" +
+ "" +
+ "";
+ }
+
+ // 验证验证码
+ public static boolean verifyCode(String email, String inputCode) {
+ VerificationCodeInfo codeInfo = verificationCodes.get(email);
+ if (codeInfo == null) {
+ return false;
+ }
+
+ // 检查验证码是否过期
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - codeInfo.timestamp > EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
+ verificationCodes.remove(email);
+ return false;
+ }
+
+ return codeInfo.code.equals(inputCode);
+ }
+
+ // 清理过期的验证码(可选)
+ public static void cleanupExpiredCodes() {
+ long currentTime = System.currentTimeMillis();
+ Iterator> iterator =
+ verificationCodes.entrySet().iterator();
+
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ if (currentTime - entry.getValue().timestamp >
+ EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
+ iterator.remove();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/util/LoginFileUtils.java b/src/main/java/com/ybw/mathapp/util/LoginFileUtils.java
new file mode 100644
index 0000000..f99a56f
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/util/LoginFileUtils.java
@@ -0,0 +1,74 @@
+package com.ybw.mathapp.util;
+
+import com.ybw.mathapp.entity.User;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LoginFileUtils {
+ private static final String USER_FILE = "users.txt";
+
+ // 读取所有用户
+ // FileUtils.java 中的 readUsers 方法(简化版)
+ public static List readUsers() {
+ List users = new ArrayList<>();
+ File file = new File(USER_FILE);
+
+ if (!file.exists()) {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ System.err.println("创建用户文件失败: " + e.getMessage());
+ }
+ return users;
+ }
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.isEmpty()) continue;
+
+ User user = User.fromString(line);
+ if (user != null) {
+ users.add(user);
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("读取用户文件失败: " + e.getMessage());
+ }
+ return users;
+ }
+
+ // 保存用户到文件
+ public static void saveUser(User user) {
+ try (PrintWriter writer = new PrintWriter(new FileWriter(USER_FILE, true))) {
+ writer.println(user.toString());
+ } catch (IOException e) {
+ System.err.println("保存用户信息失败: " + e.getMessage());
+ }
+ }
+
+ // 检查邮箱是否已注册
+ public static boolean isEmailRegistered(String email) {
+ List users = readUsers();
+ for (User user : users) {
+ if (user.getEmail().equalsIgnoreCase(email)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // 验证用户登录
+ public static boolean validateUser(String email, String password) {
+ List users = readUsers();
+ for (User user : users) {
+ if (user.getEmail().equalsIgnoreCase(email) &&
+ user.getPassword().equals(password)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/resources/META-INF/javamail.default.address.map b/src/main/resources/META-INF/javamail.default.address.map
new file mode 100644
index 0000000..e3baea0
--- /dev/null
+++ b/src/main/resources/META-INF/javamail.default.address.map
@@ -0,0 +1,2 @@
+rfc822=smtp
+smtp=smtp
\ No newline at end of file