# Conflicts:
#	src/main/java/module-info.java
pull/2/head
玖兮冉 4 months ago
commit 997acd73ec

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

@ -131,6 +131,11 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<build>

@ -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 truefalse
*/
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 truefalse
*/
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;
}
}

@ -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;
}

@ -0,0 +1,95 @@
package com.ybw.mathapp.entity;
/**
*
*
* <p>
*
*
* @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;
}
}

@ -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<String> parts) {
// 将中缀表达式转换为后缀表达式(逆波兰表示法)
List<String> postfix = infixToPostfix(parts);
// 计算后缀表达式的值
return evaluatePostfix(postfix);
}
private List<String> infixToPostfix(List<String> infix) {
Map<String, Integer> 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<String> operatorStack = new Stack<>();
List<String> 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<String> postfix) {
Stack<Double> 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;
}
}
}

@ -0,0 +1,191 @@
package com.ybw.mathapp.service;
// File: mathpuzzle/service/MultipleChoiceGenerator.java
import java.util.*;
/**
*
*
* <p> {@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<MultipleChoiceQuestion> generateMultipleChoiceQuestions(int count) {
List<MultipleChoiceQuestion> mcQuestions = new ArrayList<>();
Set<String> generatedQuestionTexts = new HashSet<>(); // 用于本次生成过程中的查重
Caculate calculator = new Caculate(); // 使用计算器实例
int attempts = 0;
int maxAttempts = count * 100; // 设置最大尝试次数,防止无限循环
while (mcQuestions.size() < count && attempts < maxAttempts) {
attempts++;
// 生成原始题目
List<String> 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<Double> 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<String> 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<Double> generateOptions(double correctAnswer) {
List<Double> 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<Double> options; // 选项列表
private final int correctIndex; // 正确选项的索引
public MultipleChoiceQuestion(String question, List<Double> options, int correctIndex) {
this.question = question;
this.options = new ArrayList<>(options); // 创建副本以防外部修改
this.correctIndex = correctIndex;
}
public String getQuestion() {
return question;
}
public List<Double> 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();
}
}
}

@ -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;
/**
*
*
* <p>
*
*
* @author
* @version 1.0
* @since 2025
*/
public class FileHandler {
/**
*
*
* <p>
*
*
* @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);
}
}
/**
*
*
* <p>
*
*
* @param user
* @param questions
* @throws IOException
*/
public void savePaper(User user, List<String> 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(); // 每题之间空一行
}
}
}
}

@ -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;
/**
*
*
* <p>
*
*
* @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<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
*
*
* <p>
*
*
* @return
*/
private String generateSingleQuestion() {
List<String> 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) + " =";
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
public List<String> generateBase(int operandCount, List<String> 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;
}
/**
*
*
* <p>使
*
*
* @param parts
* @return
*/
public List<String> forceAddAdvancedOp(List<String> 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;
}
/**
*
*
* <p>
*
*
* @param parts
* @param i
* @return
*/
public List<String> generateRoot(List<String> 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;
}
/**
*
*
* <p>
*
* @param parts
* @param i
* @return
*/
public List<String> generateSquare(List<String> 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;
}
}

@ -0,0 +1,121 @@
package com.ybw.mathapp.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
*
* <p>
*
*
* @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<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
* .
*
* <p>2-5
*
*
* @return
*/
private String generateSingleQuestion() {
Caculate caculate = new Caculate();
int operandCount = random.nextInt(4) + 2; // 2-5个操作数
List<String> 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();
}
}
}
/**
*
*
* <p>
*
* @param str
* @return truefalse
*/
public static boolean isNumeric(String str) {
if (str == null || str.isEmpty()) {
return false;
}
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
public List<String> generateBase(int operandCount, List<String> 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;
}
}

@ -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;
/**
*
*
* <p>
*
*
* @author
* @version 1.0
* @since 2025
*/
public class QuestionDeduplicator {
/**
*
*/
private final Set<String> existingQuestions = new HashSet<>();
/**
*
*
* <p>.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());
}
}
}
/**
*
*
* <p>
*
* @param question
* @return truefalse
*/
public boolean isDuplicate(String question) {
return existingQuestions.contains(question);
}
/**
*
*
* <p>
*
*
* @param question
*/
public void addQuestion(String question) {
existingQuestions.add(question);
}
}

@ -0,0 +1,28 @@
package com.ybw.mathapp.service;
import java.util.List;
/**
*
*
* <p>
*
*
*
* @author
* @version 1.0
* @since 2025
*/
public interface QuestionGenerator {
/**
*
*
* <p>
*
*
* @param count
* @return
*/
List<String> generateQuestions(int count);
}

@ -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 +
'}';
}
}

@ -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;
/**
*
*
* <p>sincostan
*
*
* @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<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
*
*
* <p>
*
*
* @return
*/
private String generateSingleQuestion() {
List<String> 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) + " =";
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
// 产生基本操作
public List<String> generateBase(int operandCount, List<String> 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;
}
/**
*
*
* <p>
*
*
* @param parts
* @param i
* @return
*/
// 产生三角函数运算符
public List<String> generateTrig(List<String> 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;
}
}

@ -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<MultipleChoiceQuestion> 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<Double> 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<Double> 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;
}
}

@ -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<String, User> 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;
}
}
}
}
}

@ -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<String, VerificationCodeInfo> 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 "<!DOCTYPE html>" +
"<html>" +
"<head>" +
"<meta charset='UTF-8'>" +
"<style>" +
"body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }" +
".container { max-width: 600px; margin: 0 auto; padding: 20px; }" +
".header { background-color: #4CAF50; color: white; padding: 20px; text-align: center; }" +
".content { padding: 20px; background-color: #f9f9f9; margin: 20px 0; }" +
".code { font-size: 24px; font-weight: bold; color: #4CAF50; text-align: center; padding: 15px; background-color: white; border: 2px dashed #4CAF50; margin: 20px 0; }" +
".footer { text-align: center; color: #666; font-size: 12px; }" +
"</style>" +
"</head>" +
"<body>" +
"<div class='container'>" +
"<div class='header'>" +
"<h2>用户注册验证码</h2>" +
"</div>" +
"<div class='content'>" +
"<p>您好!</p>" +
"<p>您正在注册账户,验证码如下:</p>" +
"<div class='code'>" + code + "</div>" +
"<p>验证码有效期为 " + EmailConfig.CODE_EXPIRY_MINUTES + " 分钟,请勿泄露给他人。</p>" +
"<p>如果这不是您本人的操作,请忽略此邮件。</p>" +
"</div>" +
"<div class='footer'>" +
"<p>此邮件为系统自动发送,请勿回复。</p>" +
"</div>" +
"</div>" +
"</body>" +
"</html>";
}
// 验证验证码
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<Entry<String, VerificationCodeInfo>> iterator =
verificationCodes.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, VerificationCodeInfo> entry = iterator.next();
if (currentTime - entry.getValue().timestamp >
EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
iterator.remove();
}
}
}
}

@ -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<User> readUsers() {
List<User> 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<User> users = readUsers();
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email)) {
return true;
}
}
return false;
}
// 验证用户登录
public static boolean validateUser(String email, String password) {
List<User> users = readUsers();
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email) &&
user.getPassword().equals(password)) {
return true;
}
}
return false;
}
}
Loading…
Cancel
Save