options) {
+ this.options = options;
+ }
+
+ public int getCorrectAnswerIndex() {
+ return correctAnswerIndex;
+ }
+
+ public void setCorrectAnswerIndex(int correctAnswerIndex) {
+ this.correctAnswerIndex = correctAnswerIndex;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Question: ").append(questionText).append("\n");
+ for (int i = 0; i < options.size(); i++) {
+ sb.append("Option ").append((char)('A' + i)).append(": ").append(options.get(i)).append("\n");
+ }
+ sb.append("Correct Answer: ").append((char)('A' + correctAnswerIndex));
+ return sb.toString();
+ }
+}
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..fa07684
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/entity/User.java
@@ -0,0 +1,100 @@
+package com.ybw.mathapp.entity;
+
+/**
+ * 用户实体类,表示系统中的用户信息。
+ *
+ * 该类包含用户的基本信息,如用户名、密码和学习级别。
+ * 用户级别可以是小学、初中或高中。
+ *
+ * @author 杨博文
+ * @version 1.0
+ * @since 2025
+ */
+public class User {
+
+ public String getName() {
+ return name;
+ }
+
+ /** 用户名,不可修改。 */
+ private final String name;
+
+ /** 邮箱,不可修改。 */
+ private final String email;
+
+ /** 用户密码,不可修改。 */
+ private final String password;
+
+ /** 用户当前的学习级别,可以修改。 */
+ private String level;
+
+ /**
+ * 构造一个新的用户对象。
+ *
+ * @param email 邮箱,不能为空 用户名,不能为空
+ * @param password 用户密码,不能为空
+ */
+ public User(String name, String email, String password) {
+ this.name = name;
+ this.password = password;
+ this.email = email;
+ }
+
+ /**
+ * 获取用户密码。
+ *
+ * @return 用户密码
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * 获取用户当前的学习级别。
+ *
+ * @return 用户学习级别
+ */
+ public String getLevel() {
+ return level;
+ }
+
+ /**
+ * 获取用户邮箱。
+ *
+ * @return 用户邮箱
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * 设置用户的学习级别。
+ *
+ * @param newLevel 新的学习级别,支持"小学"、"初中"、"高中"
+ */
+ public void setLevel(String newLevel) {
+ level = newLevel;
+ }
+
+ /**
+ * 保存邮箱+密码。
+ *
+ * @return 邮箱+密码
+ */
+ @Override
+ public String toString() {
+ return name + "," + email + "," + password;
+ }
+
+ public static User fromString(String line) {
+ if (line == null || line.trim().isEmpty()) {
+ return null;
+ }
+
+ String[] parts = line.split(",", 3); // 最多分割成3部分
+ if (parts.length == 3) {
+ return new User(parts[0].trim(), parts[1].trim(), parts[2].trim());
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/service/AdvancedCaculate.java b/src/main/java/com/ybw/mathapp/service/AdvancedCaculate.java
new file mode 100644
index 0000000..d69d11b
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/AdvancedCaculate.java
@@ -0,0 +1,180 @@
+package com.ybw.mathapp.service;
+
+import java.util.*;
+
+/**
+ * 扩展的计算类,支持基础四则运算、括号、平方、开根号、三角函数 (sin, cos, tan)。
+ * 括号优先级最高。
+ * 高级运算符 (平方, 开根号, sin, cos, tan) 优先级高于四则运算。
+ * 平方是后置运算符,开根号和三角函数是前置运算符。
+ * 例如: "3 + 开根号 16 平方" 解释为 "3 + (sqrt(16))^2" = "3 + 4^2" = "3 + 16" = 19
+ * "开根号 ( 4 + 5 ) 平方" 解释为 "(sqrt(4+5))^2" = "sqrt(9)^2" = "3^2" = 9
+ * "sin 30 + 5" 解释为 "sin(30) + 5" (假设30是度数)
+ * 修正:在计算开根号时,若操作数为负数,则抛出 ArithmeticException。
+ */
+public class AdvancedCaculate {
+
+ private static final Set OPERATORS = new HashSet<>(Arrays.asList("+", "-", "*", "/"));
+ private static final Set ADVANCED_OPERATORS = new HashSet<>(Arrays.asList("平方", "开根号", "sin", "cos", "tan"));
+ private static final Map PRECEDENCE = new HashMap<>();
+
+ static {
+ PRECEDENCE.put("+", 1);
+ PRECEDENCE.put("-", 1);
+ PRECEDENCE.put("*", 2);
+ PRECEDENCE.put("/", 2);
+ // 高级运算符优先级更高
+ PRECEDENCE.put("平方", 3); // 后置
+ PRECEDENCE.put("开根号", 3); // 前置
+ PRECEDENCE.put("sin", 3); // 前置
+ PRECEDENCE.put("cos", 3); // 前置
+ PRECEDENCE.put("tan", 3); // 前置
+ }
+
+ /**
+ * 计算给定表达式的值。
+ *
+ * @param expressionTokens 表达式分词列表,例如 ["3", "+", "开根号", "16", "平方"]
+ * @return 计算结果
+ * @throws IllegalArgumentException 如果表达式无效
+ * @throws ArithmeticException 如果计算过程中出现错误(如除零、负数开根号)
+ */
+ public static double calculate(List expressionTokens) {
+ Stack numberStack = new Stack<>();
+ Stack operatorStack = new Stack<>();
+
+ for (int i = 0; i < expressionTokens.size(); i++) {
+ String token = expressionTokens.get(i);
+
+ if (isNumeric(token)) {
+ numberStack.push(Double.parseDouble(token));
+ } else if (token.equals("(")) {
+ operatorStack.push(token);
+ } else if (token.equals(")")) {
+ while (!operatorStack.isEmpty() && !operatorStack.peek().equals("(")) {
+ processOperator(numberStack, operatorStack.pop());
+ }
+ if (!operatorStack.isEmpty()) { // Pop the "("
+ operatorStack.pop();
+ }
+ } else if (ADVANCED_OPERATORS.contains(token)) {
+ // 前置运算符 (开根号, sin, cos, tan) 直接入栈
+ // 后置运算符 (平方) 需要等待其操作数先计算出来
+ // 在标准调度场算法中,后置运算符通常在遇到时立即处理其栈顶的操作数
+ // 这里我们可以在遇到 "平方" 时,立即对 numberStack 的顶部元素进行平方操作
+ if ("平方".equals(token)) {
+ if (numberStack.isEmpty()) {
+ throw new IllegalArgumentException("Invalid expression: '平方' lacks an operand.");
+ }
+ double operand = numberStack.pop();
+ numberStack.push(Math.pow(operand, 2));
+ } else { // "开根号", "sin", "cos", "tan" 是前置运算符
+ operatorStack.push(token);
+ }
+ } else if (OPERATORS.contains(token)) {
+ // 处理四则运算符,遵循优先级
+ while (!operatorStack.isEmpty() &&
+ !operatorStack.peek().equals("(") &&
+ PRECEDENCE.get(token) <= PRECEDENCE.getOrDefault(operatorStack.peek(), 0)) {
+ processOperator(numberStack, operatorStack.pop());
+ }
+ operatorStack.push(token);
+ } else {
+ throw new IllegalArgumentException("Unknown token: " + token);
+ }
+ }
+
+ // 处理栈中剩余的所有运算符
+ while (!operatorStack.isEmpty()) {
+ String op = operatorStack.pop();
+ if (op.equals("(") || op.equals(")")) {
+ throw new IllegalArgumentException("Mismatched parentheses in expression.");
+ }
+ processOperator(numberStack, op);
+ }
+
+ if (numberStack.size() != 1 || !operatorStack.isEmpty()) {
+ throw new IllegalArgumentException("Invalid expression: " + String.join(" ", expressionTokens));
+ }
+
+ return numberStack.pop();
+ }
+
+ /**
+ * 执行一次运算操作。
+ *
+ * @param numberStack 数字栈
+ * @param operator 运算符
+ */
+ private static void processOperator(Stack numberStack, String operator) {
+ if (numberStack.size() < 1) {
+ throw new IllegalArgumentException("Invalid expression: operator '" + operator + "' lacks operand(s).");
+ }
+
+ if (ADVANCED_OPERATORS.contains(operator)) {
+ double operand = numberStack.pop();
+ switch (operator) {
+ case "开根号":
+ if (operand < 0) {
+ // 抛出异常,让调用者(MultipleChoiceGenerator)处理
+ throw new ArithmeticException("Cannot take square root of negative number: " + operand);
+ }
+ numberStack.push(Math.sqrt(operand));
+ break;
+ case "sin":
+ numberStack.push(Math.sin(Math.toRadians(operand))); // 假设输入是度数
+ break;
+ case "cos":
+ numberStack.push(Math.cos(Math.toRadians(operand)));
+ break;
+ case "tan":
+ // tan(90 + n*180) 会趋向无穷,这里不特别处理,让其返回 Infinity 或 -Infinity
+ numberStack.push(Math.tan(Math.toRadians(operand)));
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown advanced operator: " + operator);
+ }
+ } else if (OPERATORS.contains(operator)) {
+ if (numberStack.size() < 2) {
+ throw new IllegalArgumentException("Invalid expression: operator '" + operator + "' lacks operand(s).");
+ }
+ double b = numberStack.pop();
+ double a = numberStack.pop();
+ double result;
+ switch (operator) {
+ case "+":
+ result = a + b;
+ break;
+ case "-":
+ result = a - b;
+ break;
+ case "*":
+ result = a * b;
+ break;
+ case "/":
+ if (b == 0) {
+ throw new ArithmeticException("Division by zero");
+ }
+ result = a / b;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown operator: " + operator);
+ }
+ numberStack.push(result);
+ } else {
+ throw new IllegalArgumentException("Unexpected operator in process: " + operator);
+ }
+ }
+
+ public static boolean isNumeric(String str) {
+ if (str == null || str.isEmpty()) {
+ return false;
+ }
+ try {
+ Double.parseDouble(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ybw/mathapp/service/EmailService.java b/src/main/java/com/ybw/mathapp/service/EmailService.java
new file mode 100644
index 0000000..4728a47
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/EmailService.java
@@ -0,0 +1,149 @@
+package com.ybw.mathapp.service;
+
+import com.ybw.mathapp.config.EmailConfig;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Random;
+//import javax.mail.*;
+//import javax.mail.internet.*;
+
+public class EmailService {
+ private static Map verificationCodes = new HashMap<>();
+
+ private static class VerificationCodeInfo {
+ String code;
+ long timestamp;
+
+ VerificationCodeInfo(String code, long timestamp) {
+ this.code = code;
+ this.timestamp = timestamp;
+ }
+ }
+
+ public static String generateVerificationCode() {
+ Random random = new Random();
+ int code = 100000 + random.nextInt(900000);
+ return String.valueOf(code);
+ }
+
+ public static boolean sendVerificationCode(String recipientEmail, String code) {
+ /*
+ try {
+ // 创建邮件会话
+ Properties props = new Properties();
+ props.put("mail.smtp.host", EmailConfig.SMTP_HOST);
+ props.put("mail.smtp.port", EmailConfig.SMTP_PORT);
+ props.put("mail.smtp.auth", "true");
+ props.put("mail.smtp.starttls.enable", "true");
+
+ // 创建认证器
+ Authenticator auth = new Authenticator() {
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(
+ EmailConfig.SENDER_EMAIL,
+ EmailConfig.SENDER_PASSWORD
+ );
+ }
+ };
+
+ Session session = Session.getInstance(props, auth);
+
+ // 创建邮件消息
+ Message message = new MimeMessage(session);
+ message.setFrom(new InternetAddress(EmailConfig.SENDER_EMAIL));
+ message.setRecipients(Message.RecipientType.TO,
+ InternetAddress.parse(recipientEmail));
+ message.setSubject(EmailConfig.EMAIL_SUBJECT);
+
+ // 创建邮件内容
+ String emailContent = createEmailContent(code);
+ message.setContent(emailContent, "text/html; charset=utf-8");
+
+ // 发送邮件
+ Transport.send(message);
+
+ // 存储验证码信息
+ verificationCodes.put(recipientEmail,
+ new VerificationCodeInfo(code, System.currentTimeMillis()));
+
+ System.out.println("验证码已发送到邮箱: " + recipientEmail);
+ return true;
+
+ } catch (Exception e) {
+ System.err.println("发送邮件失败: " + e.getMessage());
+ // 在开发环境中,直接返回true用于测试
+ verificationCodes.put(recipientEmail,
+ new VerificationCodeInfo(code, System.currentTimeMillis()));
+ return true;
+ }
+ */
+ return true;
+ }
+
+ private static String createEmailContent(String code) {
+ return "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "
" +
+ "
您好!
" +
+ "
您正在注册数学学习软件账户,验证码如下:
" +
+ "
" + 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();
+ }
+ }
+ }
+}
\ 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..853218c
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/JuniorHighGenerator.java
@@ -0,0 +1,186 @@
+package com.ybw.mathapp.service;
+
+import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 初中题目生成器,负责生成包含平方或开根号运算的初中级别数学题目。
+ *
+ * 该生成器确保每道题目都包含至少一个高级运算符(平方或开根号),
+ * 题目结构包含基本的四则运算和高级运算的组合。
+ *
+ * @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/MultipleChoiceGenerator.java b/src/main/java/com/ybw/mathapp/service/MultipleChoiceGenerator.java
new file mode 100644
index 0000000..8c5c6ff
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/MultipleChoiceGenerator.java
@@ -0,0 +1,199 @@
+package com.ybw.mathapp.service;
+
+import com.ybw.mathapp.entity.QuestionWithOptions;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * 通用选择题生成器,将基础题目生成器的结果转换为带选项的选择题。
+ * 确保生成的题目列表中不包含重复的题干。
+ * 特别处理:1. 初中题避免负数开根号;2. 小学题选项避免负数。
+ *
+ * @author 你的名字
+ * @since 2025
+ */
+public class MultipleChoiceGenerator {
+
+ private final QuestionGenerator baseGenerator;
+ private final Random random = new Random();
+ private static final DecimalFormat df = new DecimalFormat("#0.00");
+ private final String level; // 记录当前题目类型,用于特殊处理
+
+ public MultipleChoiceGenerator(QuestionGenerator baseGenerator, String level) {
+ this.baseGenerator = baseGenerator;
+ this.level = level;
+ }
+
+ /**
+ * 生成指定数量的选择题,确保题干不重复。
+ *
+ * @param count 题目数量
+ * @return 选择题列表 (去重后)
+ */
+ public List generateMultipleChoiceQuestions(int count) {
+ List mcQuestions = new ArrayList<>();
+ Set seenQuestionTexts = new HashSet<>(); // 用于存储已生成的题干,保证唯一性
+
+ while (mcQuestions.size() < count) {
+ String baseQuestion = generateUniqueBaseQuestion(seenQuestionTexts);
+ if (baseQuestion == null) {
+ // 如果无法生成不重复的基础题目,可能需要退出或处理
+ // 例如,如果基础生成器的可能组合用尽了
+ break; // 或者抛出异常
+ }
+
+ QuestionWithOptions mcq = generateSingleMCQ(baseQuestion);
+ if (mcq != null) {
+ mcQuestions.add(mcq);
+ seenQuestionTexts.add(baseQuestion); // 添加成功生成的题干
+ }
+ // 如果 generateSingleMCQ 返回 null,说明计算或生成选项失败,循环会继续尝试下一个基础题目
+ }
+ return mcQuestions;
+ }
+
+ /**
+ * 生成一个唯一的、未处理过的基础题目。
+ */
+ private String generateUniqueBaseQuestion(Set seenQuestionTexts) {
+ int attempts = 0;
+ int maxAttempts = 1000; // 防止无限循环,如果生成太多重复题则退出
+ while (attempts < maxAttempts) {
+ List baseQuestionList = baseGenerator.generateQuestions(1);
+ String baseQuestion = baseQuestionList.get(0);
+ if (!seenQuestionTexts.contains(baseQuestion)) {
+ return baseQuestion;
+ }
+ attempts++;
+ }
+ return null; // 达到最大尝试次数仍未找到唯一题目
+ }
+
+ /**
+ * 从单个基础题目生成选择题对象。
+ */
+ private QuestionWithOptions generateSingleMCQ(String baseQuestion) {
+ try {
+ String expression = baseQuestion.substring(0, baseQuestion.lastIndexOf(" =")).trim();
+ List tokens = tokenizeExpression(expression);
+ double correctAnswer = AdvancedCaculate.calculate(tokens);
+
+ List options = generateOptions(correctAnswer);
+ if (options == null) {
+ // 无法生成足够的选项
+ return null;
+ }
+
+ Collections.shuffle(options);
+ int correctIndex = options.indexOf(df.format(correctAnswer));
+
+ return new QuestionWithOptions(baseQuestion, options, correctIndex);
+
+ } catch (ArithmeticException | IllegalArgumentException e) {
+ // System.out.println("计算或表达式错误,跳过题目: " + baseQuestion + ", Error: " + e.getMessage());
+ return null; // 返回 null 表示生成失败
+ }
+ }
+
+ /**
+ * 生成选项列表 (正确答案 + 错误答案)。
+ */
+ private List generateOptions(double correctAnswer) {
+ Set wrongAnswers = new HashSet<>();
+ int attempts = 0;
+ int maxAttempts = 100; // 防止无限循环
+ int numWrongOptions = 3; // 假设总共4个选项,需要3个错误答案
+
+ while (wrongAnswers.size() < numWrongOptions && attempts < maxAttempts) {
+ int offset = random.nextInt(20) + 1;
+ if (random.nextBoolean()) {
+ offset = -offset;
+ }
+ double wrongAnswer = correctAnswer + offset;
+
+ if (Math.abs(df.format(wrongAnswer).compareTo(df.format(correctAnswer))) != 0) {
+ if (!level.equals("小学") || wrongAnswer >= 0) {
+ wrongAnswers.add(wrongAnswer);
+ }
+ }
+ attempts++;
+ }
+
+ if (wrongAnswers.size() < numWrongOptions) {
+ // System.out.println("Warning: Could not generate enough distinct wrong answers for: " + correctAnswer);
+ return null; // 无法生成足够选项
+ }
+
+ List allAnswers = new ArrayList<>();
+ allAnswers.add(correctAnswer);
+ allAnswers.addAll(wrongAnswers);
+
+ List options = new ArrayList<>();
+ for (Double ans : allAnswers) {
+ options.add(df.format(ans));
+ }
+ return options;
+ }
+
+ // ... (其他类成员和方法保持不变)
+
+ // --- 表达式分词逻辑 ---
+ // 将 "3 + 开根号 ( 4 ) 平方 - sin 30" 分割成 ["3", "+", "开根号", "(", "4", ")", "平方", "-", "sin", "30"]
+ private List tokenizeExpression(String expression) {
+ List tokens = new ArrayList<>();
+ int i = 0;
+ while (i < expression.length()) {
+ String token = _findNextToken(expression, i);
+ if (token != null) {
+ tokens.add(token);
+ i += token.length();
+ } else {
+ // 如果找不到匹配的 token,可能是单个字符或未知格式
+ tokens.add(String.valueOf(expression.charAt(i)));
+ i++;
+ }
+ }
+ return tokens;
+ }
+
+ /**
+ * 查找从指定位置开始的下一个 token。
+ * @param expression 表达式字符串
+ * @param startPos 起始查找位置
+ * @return 找到的 token,如果未找到则返回 null
+ */
+ private String _findNextToken(String expression, int startPos) {
+ String[] advancedOps = {"平方", "开根号", "sin", "cos", "tan"};
+ char c = expression.charAt(startPos);
+
+ if (Character.isWhitespace(c)) {
+ return null; // 空格由调用者处理
+ }
+ if (c == '(' || c == ')') {
+ return String.valueOf(c);
+ }
+ if (Character.isDigit(c) || c == '.') {
+ // 查找连续的数字或小数点
+ int j = startPos;
+ while (j < expression.length() && (Character.isDigit(expression.charAt(j)) || expression.charAt(j) == '.')) {
+ j++;
+ }
+ return expression.substring(startPos, j);
+ }
+
+ // 检查多字符运算符
+ for (String op : advancedOps) {
+ if (expression.startsWith(op, startPos)) {
+ return op;
+ }
+ }
+
+ // 如果不是多字符运算符,则认为是单字符运算符 (+, -, *, / 等)
+ return String.valueOf(c);
+ }
+}
\ 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..9374b69
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/PrimarySchoolGenerator.java
@@ -0,0 +1,102 @@
+package com.ybw.mathapp.service;
+
+import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 小学题目生成器,负责生成包含四则运算和括号的小学级别数学题目。
+ *
+ * 该生成器专门用于生成适合小学生的数学题目,题目仅包含加减乘除四则运算
+ * 和括号,确保计算结果为非负数。
+ *
+ * @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() {
+ 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 (AdvancedCaculate.calculate(parts) >= 0) {
+ return String.join(" ", parts) + " =";
+ } else {
+ parts.clear();
+ }
+ }
+ }
+
+ /**
+ * 生成基本的四则运算表达式部分。
+ *
+ * 该方法生成指定数量的操作数和运算符,构成基础的数学表达式。
+ *
+ * @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/QuestionGenerator.java b/src/main/java/com/ybw/mathapp/service/QuestionGenerator.java
new file mode 100644
index 0000000..91c9436
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/QuestionGenerator.java
@@ -0,0 +1,29 @@
+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/SeniorHighGenerator.java b/src/main/java/com/ybw/mathapp/service/SeniorHighGenerator.java
new file mode 100644
index 0000000..62ab5bb
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/SeniorHighGenerator.java
@@ -0,0 +1,127 @@
+package com.ybw.mathapp.service;
+
+import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 高中题目生成器,负责生成包含三角函数运算的高中级别数学题目。
+ *
+ * 该生成器确保每道题目都包含至少一个三角函数运算符(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..00b99cc
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/service/StartController.java
@@ -0,0 +1,166 @@
+package com.ybw.mathapp.service;
+
+import com.ybw.mathapp.entity.User;
+import java.io.IOException;
+import java.util.List;
+import java.util.Scanner;
+
+public class StartController {
+
+ private double score;
+
+ public double getScore() {
+ return score;
+ }
+
+ public void setScore(double score) {
+ this.score = score;
+ }
+
+ public void start() {
+ Scanner scanner = new Scanner(System.in);
+ while (true) {
+ System.out.println("=== 数学学习软件 ===");
+ System.out.println("1. 登录");
+ System.out.println("2. 注册");
+ System.out.print("请选择: ");
+
+ String choice = scanner.nextLine();
+ if ("1".equals(choice)) {
+ if (login()) {
+ showMainMenu(scanner);
+ }
+ } else if ("2".equals(choice)) {
+ if (register()) {
+ showMainMenu(scanner);
+ }
+ } else {
+ System.out.println("无效选择,请重新输入!");
+ }
+ }
+ }
+
+ private boolean login() {
+ System.out.println("\n=== 用户登录 ===");
+ Scanner scanner = new Scanner(System.in);
+
+ System.out.print("请输入邮箱: ");
+ String email = scanner.nextLine().trim();
+
+ System.out.print("请输入密码: ");
+ String password = scanner.nextLine();
+
+ // 这里应该调用真实的登录逻辑
+ System.out.println("登录功能待实现");
+ return true;
+ }
+
+ private boolean register() {
+ System.out.println("\n=== 用户注册 ===");
+ System.out.println("注册功能待实现");
+ return true;
+ }
+
+ private void showMainMenu(Scanner scanner) {
+ while (true) {
+ System.out.println("\n=== 主菜单 ===");
+ System.out.println("1. 开始练习");
+ System.out.println("2. 修改密码");
+ System.out.println("3. 退出登录");
+ System.out.print("请选择: ");
+
+ String choice = scanner.nextLine();
+ if ("1".equals(choice)) {
+ showLevelSelection(scanner);
+ } else if ("2".equals(choice)) {
+ changePassword(scanner);
+ } else if ("3".equals(choice)) {
+ System.out.println("退出登录成功!");
+ return;
+ } else {
+ System.out.println("无效选择,请重新输入!");
+ }
+ }
+ }
+
+ private void showLevelSelection(Scanner scanner) {
+ System.out.println("\n=== 选择题目级别 ===");
+ System.out.println("1. 小学");
+ System.out.println("2. 初中");
+ System.out.println("3. 高中");
+ System.out.println("4. 返回");
+ System.out.print("请选择: ");
+
+ String choice = scanner.nextLine();
+ String level = null;
+ switch (choice) {
+ case "1": level = "小学"; break;
+ case "2": level = "初中"; break;
+ case "3": level = "高中"; break;
+ case "4": return;
+ default:
+ System.out.println("无效选择!");
+ return;
+ }
+
+ showQuestionCount(scanner, level);
+ }
+
+ private void showQuestionCount(Scanner scanner, String level) {
+ System.out.println("\n=== 选择题目数量 ===");
+ System.out.print("请输入题目数量 (10-30): ");
+
+ try {
+ int count = Integer.parseInt(scanner.nextLine());
+ if (count < 10 || count > 30) {
+ System.out.println("题目数量必须在10-30之间!");
+ return;
+ }
+
+ startQuiz(level, count);
+
+ } catch (NumberFormatException e) {
+ System.out.println("请输入有效的数字!");
+ }
+ }
+
+ private void startQuiz(String level, int count) {
+ System.out.println("\n开始 " + level + " 级别答题,共 " + count + " 题");
+ // 这里应该调用题目生成和答题逻辑
+ System.out.println("答题功能待实现");
+
+ // 模拟答题结果
+ this.score = 0.8; // 模拟80%的正确率
+ System.out.println("答题完成!得分: " + (score * 100) + "%");
+ }
+
+ private void changePassword(Scanner scanner) {
+ System.out.println("\n=== 修改密码 ===");
+ System.out.println("修改密码功能待实现");
+ }
+
+ public QuestionGenerator createGenerator(String level) {
+ switch (level) {
+ case "小学":
+ return new PrimarySchoolGenerator();
+ case "初中":
+ return new JuniorHighGenerator();
+ case "高中":
+ return new SeniorHighGenerator();
+ default:
+ return null;
+ }
+ }
+
+ public boolean isCorrectAnswer(String input, int correctAnswerIndex, List 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/util/ChangePassword.java b/src/main/java/com/ybw/mathapp/util/ChangePassword.java
new file mode 100644
index 0000000..72af3df
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/util/ChangePassword.java
@@ -0,0 +1,91 @@
+package com.ybw.mathapp.util;
+
+import static com.ybw.mathapp.util.LoginFileUtils.USER_FILE;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChangePassword {
+ static final List lines = new ArrayList<>();
+ static String userLine = null;
+ static int userLineNumber = -1;
+
+ // 前端接口-修改密码
+ public static boolean changePassword(String name, String newPassword) {
+ File file = new File(USER_FILE);
+ if (!file.exists()) {
+ System.out.println("用户文件不存在: " + USER_FILE);
+ return false;
+ }
+
+ // 1. 读取文件,查找用户
+ if(!findUserLine(name, file)) {
+ return false;
+ }
+
+ if (userLine == null || userLineNumber == -1) {
+ // 用户未找到
+ System.out.println("用户 '" + name + "' 不存在,修改失败。");
+ return false;
+ }
+
+ // 2. 更新找到的用户行中的密码
+ String[] parts = userLine.split(",");
+ if (parts.length != 3) {
+ return false;
+ }
+ parts[2] = newPassword; // 假设密码是第三个字段
+ String updatedLine = String.join(",", parts);
+
+ lines.set(userLineNumber, updatedLine); // 替换列表中的旧行
+
+ // 3. 将更新后的内容写回文件
+ if(!writeBack(lines, file)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static boolean writeBack(List lines, File file) {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ for (String l : lines) {
+ writer.write(l);
+ writer.newLine();
+ }
+ } catch (IOException e) {
+ System.err.println("写入文件时出错: " + e.getMessage());
+ return false; // 如果写回失败,认为修改未成功
+ }
+ return true;
+ }
+
+ public static boolean findUserLine(String name, File file) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+ int currentLineNum = 0;
+ while ((line = reader.readLine()) != null) {
+ lines.add(line);
+ String[] parts = line.split(",");
+ // 假设格式为: username,email,password
+ if (parts.length >= 3 && parts[0].equals(name)) {
+ userLine = line; // 找到用户行
+ userLineNumber = currentLineNum;
+ break; // 找到后可以退出循环
+ }
+ currentLineNum++;
+ }
+ } catch (IOException e) {
+ System.err.println("读取文件时出错: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+}
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..eba77a8
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/util/EmailService.java
@@ -0,0 +1,189 @@
+package com.ybw.mathapp.util;
+
+import com.ybw.mathapp.config.EmailConfig;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Random;
+import jakarta.mail.Authenticator;
+import jakarta.mail.Message;
+import jakarta.mail.MessagingException;
+import jakarta.mail.PasswordAuthentication;
+import jakarta.mail.Session;
+import jakarta.mail.Transport;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeMessage;
+
+public class EmailService {
+ private static Map 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();
+ }
+ }
+ }
+
+ /**
+ * 前端接口-邮箱格式验证
+ * @param email 待验证的邮箱地址
+ * @return true表示邮箱格式正确,false表示邮箱格式错误
+ */
+ public static boolean isValidEmail(String email) {
+ if (email.isEmpty()) {
+ System.out.println("邮箱地址不能为空!");
+ return false;
+ }
+ if (!(email.contains("@") && email.contains("."))) {
+ System.out.println("邮箱格式不正确!");
+ return false;
+ }
+ return true;
+ }
+
+ // 前端接口-发送验证码
+ public static boolean sendCode(String email) {
+ // 发送真实邮件验证码
+ String verificationCode = EmailService.generateVerificationCode();
+ // 验证验证码是否成功发送
+ if (!EmailService.sendVerificationCode(email, verificationCode)) {
+ return false;
+ }
+ return true;
+ }
+
+
+
+}
diff --git a/src/main/java/com/ybw/mathapp/util/Login.java b/src/main/java/com/ybw/mathapp/util/Login.java
new file mode 100644
index 0000000..83f18c0
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/util/Login.java
@@ -0,0 +1,16 @@
+package com.ybw.mathapp.util;
+
+public class Login {
+
+ // 前端接口-登录成功or失败
+ public static boolean login(String name, String password) {
+ if (LoginFileUtils.validateUser(name, password)) {
+ System.out.println("登录成功!欢迎回来," + name);
+ return true;
+ } else {
+ System.out.println("邮箱或密码错误!");
+ return false;
+ }
+ }
+
+}
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..830fad2
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/util/LoginFileUtils.java
@@ -0,0 +1,95 @@
+package com.ybw.mathapp.util;
+
+import com.ybw.mathapp.entity.User;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LoginFileUtils {
+ static final String USER_FILE = "users.txt";
+
+ // 读取所有用户
+ public static List 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 isNameRegistered(String name) {
+ List users = readUsers();
+ for (User user : users) {
+ if (user.getName().equalsIgnoreCase(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // 前端接口-验证用户登录
+ public static boolean validateUser(String emailOrName, String password) {
+ List users = readUsers();
+ for (User user : users) {
+ if ((user.getEmail().equalsIgnoreCase(emailOrName)
+ || user.getName().equalsIgnoreCase(emailOrName) )
+ && user.getPassword().equals(password)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // 前端接口-通过邮箱查找用户名
+ public static String emailFindName (String email) {
+ List users = readUsers();
+ for (User user : users) {
+ if (user.getEmail().equalsIgnoreCase(email)) {
+ return user.getName();
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/ybw/mathapp/util/Register.java b/src/main/java/com/ybw/mathapp/util/Register.java
new file mode 100644
index 0000000..5085316
--- /dev/null
+++ b/src/main/java/com/ybw/mathapp/util/Register.java
@@ -0,0 +1,44 @@
+package com.ybw.mathapp.util;
+
+import com.ybw.mathapp.entity.User;
+import java.util.regex.Pattern;
+
+public class Register {
+
+ // 前端接口-完成注册
+ public static boolean register(String name, String email, String password1) {
+ User user = new User(name, email, password1);
+ LoginFileUtils.saveUser(user);
+ System.out.println("注册成功!您可以使用邮箱和密码登录了。");
+ return true;
+ }
+
+ /**
+ * 前端接口-密码格式验证
+ * @param password1 第一次输入的密码
+ * @return true表示符合要求,false表示不符合
+ */
+ public static boolean isVaildPassword(String password1) {
+ // 使用正则表达式验证:长度6-10,只包含字母数字,且包含大小写字母和数字
+ String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$";
+ if (!Pattern.matches(regex, password1)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 前端接口-两次密码匹配
+ * @param password1 第一次输入的密码
+ * @param password2 第二次输入的密码
+ * @return true表示符合要求,false表示不符合
+ */
+ public static boolean isEqualPassword(String password1, String password2) {
+ if (!password1.equals(password2)) {
+ System.out.println("两次输入的密码不一致!");
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 2b5b80f..1b7863b 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,16 +1,10 @@
module com.ybw.mathapp {
requires javafx.controls;
requires javafx.fxml;
- requires javafx.web;
+ requires javafx.base;
+ requires javafx.graphics;
+ requires jakarta.mail;
- requires org.controlsfx.controls;
- requires com.dlsc.formsfx;
- requires net.synedra.validatorfx;
- requires org.kordamp.ikonli.javafx;
- requires org.kordamp.bootstrapfx.core;
- requires eu.hansolo.tilesfx;
- requires com.almasb.fxgl.all;
-
- opens com.ybw.mathapp to javafx.fxml;
- exports com.ybw.mathapp;
+ opens com.wsf.mathapp to javafx.fxml;
+ exports com.wsf.mathapp;
}
\ No newline at end of file
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
diff --git a/src/main/resources/com/ybw/mathapp/hello-view.fxml b/src/main/resources/com/ybw/mathapp/hello-view.fxml
deleted file mode 100644
index 11142e0..0000000
--- a/src/main/resources/com/ybw/mathapp/hello-view.fxml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/users.txt b/users.txt
new file mode 100644
index 0000000..63150c2
--- /dev/null
+++ b/users.txt
@@ -0,0 +1,2 @@
+
+wsf,3310207578@qq.com,Wsf1234