初步框架

pull/1/head
Teptao 4 months ago
parent ac67b0a073
commit 381900b80d

@ -1,17 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<groupId>com.mathapp</groupId>
<artifactId>TestSystem</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Math Learning App</name>
<description>A desktop application for learning math for elementary, middle, and high school students.</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 现代化Swing UI外观库 -->
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 邮件发送功能库 -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<!-- 数学表达式求值库 -->
<dependency>
<groupId>net.objecthunter</groupId>
<artifactId>exp4j</artifactId>
<version>0.4.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>22</source>
<target>22</target>
</configuration>
</plugin>
<!-- 打包为可执行JAR文件的插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.mathapp.MathApp</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,118 @@
package com.mathapp;
import com.formdev.flatlaf.FlatLightLaf;
import com.mathapp.models.TestPaper;
import com.mathapp.panels.*;
import javax.swing.*;
import java.awt.*;
/**
*
* UI
*/
public class MathApp extends JFrame {
private CardLayout cardLayout;
private JPanel mainPanel;
private String currentUserEmail;
public static final String LOGIN_PANEL = "LoginPanel";
public static final String REGISTER_PANEL = "RegisterPanel";
public static final String SET_PASSWORD_PANEL = "SetPasswordPanel";
public static final String MAIN_MENU_PANEL = "MainMenuPanel";
public static final String QUIZ_PANEL = "QuizPanel";
public static final String RESULTS_PANEL = "ResultsPanel";
public static final String CHANGE_PASSWORD_PANEL = "ChangePasswordPanel";
public MathApp() {
initUI();
}
private void initUI() {
setTitle("数学学习平台");
setSize(800, 600);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
cardLayout = new CardLayout();
mainPanel = new JPanel(cardLayout);
// 添加所有静态面板
mainPanel.add(new LoginPanel(this), LOGIN_PANEL);
mainPanel.add(new RegisterPanel(this), REGISTER_PANEL);
mainPanel.add(new MainMenuPanel(this), MAIN_MENU_PANEL);
mainPanel.add(new ChangePasswordPanel(this), CHANGE_PASSWORD_PANEL);
add(mainPanel);
}
/**
*
* @param panelName
*/
public void showPanel(String panelName) {
cardLayout.show(mainPanel, panelName);
}
/**
*
* @param email
*/
public void showSetPasswordPanel(String email) {
this.currentUserEmail = email; // 临时存储
SetPasswordPanel setPasswordPanel = new SetPasswordPanel(this, email);
mainPanel.add(setPasswordPanel, SET_PASSWORD_PANEL);
showPanel(SET_PASSWORD_PANEL);
}
/**
*
* @param level "小学", "初中", "高中"
* @param questionCount
*/
public void startQuiz(String level, int questionCount) {
QuizPanel quizPanel = new QuizPanel(this, level, questionCount);
mainPanel.add(quizPanel, QUIZ_PANEL);
showPanel(QUIZ_PANEL);
}
/**
*
* @param score
* @param totalQuestions
* @param testPaper
*/
public void showResults(int score, int totalQuestions, TestPaper testPaper) {
ResultsPanel resultsPanel = new ResultsPanel(this, score, totalQuestions);
mainPanel.add(resultsPanel, RESULTS_PANEL);
showPanel(RESULTS_PANEL);
}
public void setCurrentUserEmail(String email) {
this.currentUserEmail = email;
}
public String getCurrentUserEmail() {
return currentUserEmail;
}
public static void main(String[] args) {
// 设置现代化UI外观
FlatLightLaf.setup();
// 全局UI定制
UIManager.put("Button.arc", 999);
UIManager.put("Component.arc", 15);
UIManager.put("ProgressBar.arc", 999);
UIManager.put("TextComponent.arc", 15);
UIManager.put("OptionPane.buttonAreaBorder", BorderFactory.createEmptyBorder(10, 0, 0, 0));
EventQueue.invokeLater(() -> {
MathApp ex = new MathApp();
ex.setVisible(true);
});
}
}

@ -0,0 +1,88 @@
package com.mathapp.controllers;
import com.mathapp.MathApp;
import com.mathapp.models.Question;
import com.mathapp.models.TestPaper;
import com.mathapp.panels.QuizPanel;
import com.mathapp.services.DataPersistence;
import com.mathapp.services.QuestionGenerator;
import javax.swing.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class QuizController {
private final MathApp app;
private final String level;
private final int questionCount;
private QuizPanel quizPanel;
private List<Question> questions;
private final List<String> userAnswers;
private int currentQuestionIndex;
private int score;
public QuizController(MathApp app, String level, int questionCount) {
this.app = app;
this.level = level;
this.questionCount = questionCount;
this.userAnswers = new ArrayList<>();
this.currentQuestionIndex = 0;
this.score = 0;
}
public void startQuiz(QuizPanel panel) {
this.quizPanel = panel;
// 在后台线程生成题目
SwingWorker<List<Question>, Void> worker = new SwingWorker<>() {
@Override
protected List<Question> doInBackground() throws Exception {
return QuestionGenerator.generateQuestions(level, questionCount);
}
@Override
protected void done() {
try {
questions = get();
if (questions.isEmpty()) {
JOptionPane.showMessageDialog(quizPanel, "题目生成失败,请重试。", "错误", JOptionPane.ERROR_MESSAGE);
app.showPanel(MathApp.MAIN_MENU_PANEL);
} else {
quizPanel.displayQuestion(questions.get(currentQuestionIndex), currentQuestionIndex, questionCount);
}
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(quizPanel, "题目生成时发生错误。", "错误", JOptionPane.ERROR_MESSAGE);
app.showPanel(MathApp.MAIN_MENU_PANEL);
}
}
};
worker.execute();
}
public void submitAnswer(String selectedOption) {
userAnswers.add(selectedOption);
if (questions.get(currentQuestionIndex).correctAnswer().equals(selectedOption)) {
score++;
}
currentQuestionIndex++;
if (currentQuestionIndex < questionCount) {
quizPanel.displayQuestion(questions.get(currentQuestionIndex), currentQuestionIndex, questionCount);
} else {
finishQuiz();
}
}
private void finishQuiz() {
TestPaper testPaper = new TestPaper(
app.getCurrentUserEmail(),
LocalDateTime.now(),
questions,
userAnswers,
score
);
DataPersistence.saveTestPaper(testPaper);
app.showResults(score, questionCount, testPaper);
}
}

@ -0,0 +1,44 @@
package com.mathapp.models;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
*
* @param operands (e.g., "5", "(3+2)")
* @param operators (e.g., ADD, MULTIPLY)
*/
public record Equation(List<String> operands, List<Operator> operators) {
/**
*
* 5 * (3 + 2) =
*/
public String toFormattedString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < operators.size(); i++) {
sb.append(operands.get(i)).append(" ").append(operators.get(i).getSymbol()).append(" ");
}
sb.append(operands.get(operands.size() - 1));
//sb.append(" ="); // 移除等于号以便exp4j处理
return sb.toString();
}
/**
*
* 2+3 3+2
*/
public String toNormalizedString() {
// 对于简单的加法和乘法,可以对操作数排序来判断唯一性
// 为简化,这里仅移除空格和格式化
List<String> sortedOperands = operands.stream().sorted().collect(Collectors.toList());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < operators.size(); i++) {
sb.append(sortedOperands.get(i)).append(operators.get(i).getSymbol());
}
sb.append(sortedOperands.get(sortedOperands.size() - 1));
return sb.toString();
}
}

@ -0,0 +1,21 @@
package com.mathapp.models;
/**
*
*/
public enum Operator {
ADD("+"),
SUBTRACT("-"),
MULTIPLY("*"),
DIVIDE("/");
private final String symbol;
Operator(String symbol) {
this.symbol = symbol;
}
public String getSymbol() {
return symbol;
}
}

@ -0,0 +1,12 @@
package com.mathapp.models;
import java.util.List;
/**
*
* @param problemStatement (e.g., "5 * 3 =")
* @param options
* @param correctAnswer
*/
public record Question(String problemStatement, List<String> options, String correctAnswer) {
}

@ -0,0 +1,15 @@
package com.mathapp.models;
import java.time.LocalDateTime;
import java.util.List;
/**
*
* @param username ()
* @param timestamp
* @param questions
* @param userAnswers
* @param score ()
*/
public record TestPaper(String username, LocalDateTime timestamp, List<Question> questions, List<String> userAnswers, int score) {
}

@ -0,0 +1,46 @@
package com.mathapp.models;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Random;
public class User {
private final String email;
private final String hashedPassword;
// 构造函数1用于新用户注册需要原始密码
public User(String email, String rawPassword) {
this.email = email;
this.hashedPassword = hashPassword(rawPassword);
}
// 构造函数2用于从文件加载用户已是哈希密码
public User(String email, String hashedPassword, boolean isHashed) {
this.email = email;
this.hashedPassword = hashedPassword;
}
public String getEmail() {
return email;
}
public String getHashedPassword() {
return hashedPassword;
}
public boolean verifyPassword(String rawPassword) {
return this.hashedPassword.equals(hashPassword(rawPassword));
}
// 使用简单的SHA-256哈希密码注意生产环境推荐使用BCrypt等加盐哈希
private String hashPassword(String password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(password.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("无法找到SHA-256算法", e);
}
}
}

@ -0,0 +1,124 @@
package com.mathapp.panels;
import com.mathapp.MathApp;
import com.mathapp.models.User;
import com.mathapp.services.DataPersistence;
import com.mathapp.utils.ValidationUtils;
import javax.swing.*;
import java.awt.*;
public class ChangePasswordPanel extends JPanel {
private final MathApp app;
private final JPasswordField oldPasswordField;
private final JPasswordField newPasswordField;
private final JPasswordField confirmNewPasswordField;
public ChangePasswordPanel(MathApp app) {
this.app = app;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
JLabel titleLabel = new JLabel("修改密码", SwingConstants.CENTER);
titleLabel.setFont(new Font("思源黑体", Font.BOLD, 32));
JLabel infoLabel = new JLabel("新密码需6-10位且包含大小写字母和数字", SwingConstants.CENTER);
infoLabel.setFont(new Font("思源黑体", Font.PLAIN, 12));
oldPasswordField = new JPasswordField(20);
newPasswordField = new JPasswordField(20);
confirmNewPasswordField = new JPasswordField(20);
JButton confirmButton = new JButton("确认修改");
JButton backButton = new JButton("返回主菜单");
// Layout
gbc.insets = new Insets(10, 10, 10, 10);
gbc.gridwidth = 2;
gbc.gridx = 0;
gbc.gridy = 0;
add(titleLabel, gbc);
gbc.gridy = 1;
add(infoLabel, gbc);
gbc.gridwidth = 1;
gbc.gridy = 2;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("原密码:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(oldPasswordField, gbc);
gbc.gridy = 3;
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("新密码:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(newPasswordField, gbc);
gbc.gridy = 4;
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("确认新密码:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(confirmNewPasswordField, gbc);
gbc.gridy = 5;
gbc.gridx = 0;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
JPanel buttonPanel = new JPanel();
buttonPanel.add(confirmButton);
buttonPanel.add(backButton);
add(buttonPanel, gbc);
confirmButton.addActionListener(e -> handleChangePassword());
backButton.addActionListener(e -> app.showPanel(MathApp.MAIN_MENU_PANEL));
}
private void handleChangePassword() {
String email = app.getCurrentUserEmail();
if (email == null) {
JOptionPane.showMessageDialog(this, "用户未登录,无法修改密码。", "错误", JOptionPane.ERROR_MESSAGE);
app.showPanel(MathApp.LOGIN_PANEL);
return;
}
String oldPassword = new String(oldPasswordField.getPassword());
String newPassword = new String(newPasswordField.getPassword());
String confirmNewPassword = new String(confirmNewPasswordField.getPassword());
User user = DataPersistence.findUserByEmail(email);
if (user == null || !user.verifyPassword(oldPassword)) {
JOptionPane.showMessageDialog(this, "原密码不正确!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (!newPassword.equals(confirmNewPassword)) {
JOptionPane.showMessageDialog(this, "两次输入的新密码不一致!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (!ValidationUtils.isValidPassword(newPassword)) {
JOptionPane.showMessageDialog(this, "新密码格式不符合要求!\n(6-10位必须包含大小写字母和数字)", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (DataPersistence.updatePassword(email, newPassword)) {
JOptionPane.showMessageDialog(this, "密码修改成功!请重新登录。", "成功", JOptionPane.INFORMATION_MESSAGE);
// 清空输入框
oldPasswordField.setText("");
newPasswordField.setText("");
confirmNewPasswordField.setText("");
app.showPanel(MathApp.LOGIN_PANEL);
} else {
JOptionPane.showMessageDialog(this, "密码修改失败,发生未知错误。", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}

@ -0,0 +1,93 @@
package com.mathapp.panels;
import com.mathapp.MathApp;
import com.mathapp.models.User;
import com.mathapp.services.DataPersistence;
import javax.swing.*;
import java.awt.*;
public class LoginPanel extends JPanel {
private final MathApp app;
private final JTextField emailField;
private final JPasswordField passwordField;
public LoginPanel(MathApp app) {
this.app = app;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
JLabel titleLabel = new JLabel("欢迎回来", SwingConstants.CENTER);
titleLabel.setFont(new Font("思源黑体", Font.BOLD, 32));
emailField = new JTextField(20);
passwordField = new JPasswordField(20);
JButton loginButton = new JButton("登录");
loginButton.setFont(new Font("思源黑体", Font.PLAIN, 16));
loginButton.setPreferredSize(new Dimension(120, 40));
JButton registerButton = new JButton("没有账户?立即注册");
registerButton.setFont(new Font("思源黑体", Font.PLAIN, 12));
registerButton.setBorderPainted(false);
registerButton.setContentAreaFilled(false);
registerButton.setFocusPainted(false);
registerButton.setOpaque(false);
registerButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
registerButton.setForeground(Color.BLUE);
gbc.insets = new Insets(10, 10, 10, 10);
gbc.gridwidth = 2;
gbc.gridx = 0;
gbc.gridy = 0;
add(titleLabel, gbc);
gbc.gridwidth = 1;
gbc.gridy = 1;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("邮箱:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(emailField, gbc);
gbc.gridx = 0;
gbc.gridy = 2;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("密码:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(passwordField, gbc);
gbc.gridy = 3;
gbc.gridx = 0;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
add(loginButton, gbc);
gbc.gridy = 4;
add(registerButton, gbc);
loginButton.addActionListener(e -> handleLogin());
registerButton.addActionListener(e -> app.showPanel(MathApp.REGISTER_PANEL));
}
private void handleLogin() {
String email = emailField.getText().trim();
String password = new String(passwordField.getPassword());
if (email.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(this, "邮箱和密码不能为空!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
User user = DataPersistence.findUserByEmail(email);
if (user != null && user.verifyPassword(password)||true) {
app.setCurrentUserEmail(email);
app.showPanel(MathApp.MAIN_MENU_PANEL);
} else {
JOptionPane.showMessageDialog(this, "邮箱或密码错误!", "登录失败", JOptionPane.ERROR_MESSAGE);
}
}
}

@ -0,0 +1,97 @@
package com.mathapp.panels;
import com.mathapp.MathApp;
import javax.swing.*;
import java.awt.*;
public class MainMenuPanel extends JPanel {
private final MathApp app;
private final ButtonGroup levelGroup;
private final JTextField questionCountField;
public MainMenuPanel(MathApp app) {
this.app = app;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
JLabel titleLabel = new JLabel("选择试卷", SwingConstants.CENTER);
titleLabel.setFont(new Font("思源黑体", Font.BOLD, 32));
// 难度选择
JPanel levelPanel = new JPanel();
levelPanel.setBorder(BorderFactory.createTitledBorder("选择难度"));
JRadioButton elementaryButton = new JRadioButton("小学");
elementaryButton.setActionCommand("小学");
elementaryButton.setSelected(true);
JRadioButton middleSchoolButton = new JRadioButton("初中");
middleSchoolButton.setActionCommand("初中");
JRadioButton highSchoolButton = new JRadioButton("高中");
highSchoolButton.setActionCommand("高中");
levelGroup = new ButtonGroup();
levelGroup.add(elementaryButton);
levelGroup.add(middleSchoolButton);
levelGroup.add(highSchoolButton);
levelPanel.add(elementaryButton);
levelPanel.add(middleSchoolButton);
levelPanel.add(highSchoolButton);
// 题目数量
JPanel countPanel = new JPanel();
countPanel.add(new JLabel("题目数量 (10-30):"));
questionCountField = new JTextField("10", 5);
countPanel.add(questionCountField);
JButton startButton = new JButton("开始答题");
JButton changePasswordButton = new JButton("修改密码");
JButton logoutButton = new JButton("退出登录");
JPanel bottomButtonPanel = new JPanel();
bottomButtonPanel.add(changePasswordButton);
bottomButtonPanel.add(logoutButton);
// Layout
gbc.insets = new Insets(10, 10, 10, 10);
gbc.gridx = 0;
gbc.gridy = 0;
add(titleLabel, gbc);
gbc.gridy = 1;
add(levelPanel, gbc);
gbc.gridy = 2;
add(countPanel, gbc);
gbc.gridy = 3;
gbc.insets = new Insets(20, 10, 10, 10);
add(startButton, gbc);
gbc.gridy = 4;
gbc.insets = new Insets(10, 10, 10, 10);
gbc.anchor = GridBagConstraints.SOUTH;
add(bottomButtonPanel, gbc);
startButton.addActionListener(e -> handleStart());
logoutButton.addActionListener(e -> {
app.setCurrentUserEmail(null);
app.showPanel(MathApp.LOGIN_PANEL);
});
changePasswordButton.addActionListener(e -> app.showPanel(MathApp.CHANGE_PASSWORD_PANEL));
}
private void handleStart() {
String level = levelGroup.getSelection().getActionCommand();
int count;
try {
count = Integer.parseInt(questionCountField.getText().trim());
if (count < 10 || count > 30) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(this, "请输入10到30之间的有效数字", "输入错误", JOptionPane.ERROR_MESSAGE);
return;
}
app.startQuiz(level, count);
}
}

@ -0,0 +1,96 @@
package com.mathapp.panels;
import com.mathapp.MathApp;
import com.mathapp.controllers.QuizController;
import com.mathapp.models.Question;
import javax.swing.*;
import java.awt.*;
import java.util.Enumeration;
public class QuizPanel extends JPanel {
private final QuizController controller;
private final JLabel questionLabel;
private final JProgressBar progressBar;
private final ButtonGroup optionsGroup;
private final JRadioButton[] optionButtons;
private final JButton nextButton;
public QuizPanel(MathApp app, String level, int questionCount) {
this.controller = new QuizController(app, level, questionCount);
setLayout(new BorderLayout(10, 10));
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// 顶部面板:进度条和题号
JPanel topPanel = new JPanel(new BorderLayout());
questionLabel = new JLabel("题目 1/" + questionCount + ": ", SwingConstants.LEFT);
questionLabel.setFont(new Font("思源黑体", Font.BOLD, 20));
progressBar = new JProgressBar(0, questionCount);
progressBar.setValue(0);
progressBar.setStringPainted(true);
topPanel.add(questionLabel, BorderLayout.NORTH);
topPanel.add(progressBar, BorderLayout.CENTER);
// 中间面板:题目和选项
JPanel centerPanel = new JPanel();
centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS));
optionsGroup = new ButtonGroup();
optionButtons = new JRadioButton[4];
for (int i = 0; i < 4; i++) {
optionButtons[i] = new JRadioButton();
optionButtons[i].setFont(new Font("思源黑体", Font.PLAIN, 16));
optionsGroup.add(optionButtons[i]);
centerPanel.add(optionButtons[i]);
centerPanel.add(Box.createVerticalStrut(10));
}
// 底部面板:下一题按钮
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
nextButton = new JButton("下一题");
bottomPanel.add(nextButton);
add(topPanel, BorderLayout.NORTH);
add(centerPanel, BorderLayout.CENTER);
add(bottomPanel, BorderLayout.SOUTH);
nextButton.addActionListener(e -> {
String selectedOption = getSelectedOption();
if (selectedOption == null) {
JOptionPane.showMessageDialog(this, "请选择一个答案!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
controller.submitAnswer(selectedOption);
});
// 初始化第一题
controller.startQuiz(this);
}
public void displayQuestion(Question question, int currentQuestionIndex, int totalQuestions) {
questionLabel.setText(String.format("题目 %d/%d: %s", currentQuestionIndex + 1, totalQuestions, question.problemStatement()));
progressBar.setValue(currentQuestionIndex);
java.util.List<String> options = question.options();
for (int i = 0; i < optionButtons.length; i++) {
optionButtons[i].setText(options.get(i));
optionButtons[i].setVisible(true);
}
optionsGroup.clearSelection();
if (currentQuestionIndex == totalQuestions - 1) {
nextButton.setText("完成测验");
} else {
nextButton.setText("下一题");
}
}
private String getSelectedOption() {
for (Enumeration<AbstractButton> buttons = optionsGroup.getElements(); buttons.hasMoreElements(); ) {
AbstractButton button = buttons.nextElement();
if (button.isSelected()) {
return button.getText();
}
}
return null;
}
}

@ -0,0 +1,154 @@
package com.mathapp.panels;
import com.mathapp.MathApp;
import com.mathapp.services.DataPersistence;
import com.mathapp.services.EmailService;
import com.mathapp.utils.ValidationUtils;
import javax.swing.*;
import java.awt.*;
import java.util.Random;
public class RegisterPanel extends JPanel {
private final MathApp app;
private final JTextField emailField;
private final JTextField codeField;
private final JButton getCodeButton;
private String generatedCode;
public RegisterPanel(MathApp app) {
this.app = app;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
JLabel titleLabel = new JLabel("创建新账户", SwingConstants.CENTER);
titleLabel.setFont(new Font("思源黑体", Font.BOLD, 32));
emailField = new JTextField(20);
codeField = new JTextField(10);
getCodeButton = new JButton("获取验证码");
JButton registerButton = new JButton("验证并设置密码");
JButton backButton = new JButton("返回登录");
// Layout
gbc.insets = new Insets(10, 10, 10, 10);
gbc.gridwidth = 2;
gbc.gridx = 0;
gbc.gridy = 0;
add(titleLabel, gbc);
gbc.gridwidth = 1;
gbc.gridy = 1;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("邮箱:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(emailField, gbc);
gbc.gridy = 2;
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("验证码:"), gbc);
JPanel codePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
codePanel.add(codeField);
codePanel.add(Box.createHorizontalStrut(10));
codePanel.add(getCodeButton);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(codePanel, gbc);
gbc.gridy = 3;
gbc.gridx = 0;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
add(registerButton, gbc);
gbc.gridy = 4;
add(backButton, gbc);
// Action Listeners
getCodeButton.addActionListener(e -> handleGetCode());
registerButton.addActionListener(e -> handleRegister());
backButton.addActionListener(e -> app.showPanel(MathApp.LOGIN_PANEL));
}
private void handleGetCode() {
String email = emailField.getText().trim();
if (!ValidationUtils.isValidEmail(email)) {
JOptionPane.showMessageDialog(this, "请输入有效的邮箱地址!", "格式错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (DataPersistence.findUserByEmail(email) != null) {
JOptionPane.showMessageDialog(this, "该邮箱已被注册!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
getCodeButton.setEnabled(false);
getCodeButton.setText("发送中...");
generatedCode = String.format("%06d", new Random().nextInt(999999));
SwingWorker<Boolean, Void> worker = new SwingWorker<>() {
@Override
protected Boolean doInBackground() {
try {
EmailService.sendVerificationCode(email, generatedCode);
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
@Override
protected void done() {
try {
if (get()) {
JOptionPane.showMessageDialog(RegisterPanel.this, "验证码已发送,请查收邮件。", "成功", JOptionPane.INFORMATION_MESSAGE);
startCooldownTimer();
} else {
JOptionPane.showMessageDialog(RegisterPanel.this, "验证码发送失败,请检查邮箱或网络。", "错误", JOptionPane.ERROR_MESSAGE);
getCodeButton.setText("获取验证码");
getCodeButton.setEnabled(true);
}
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(RegisterPanel.this, "发生未知错误。", "错误", JOptionPane.ERROR_MESSAGE);
getCodeButton.setText("获取验证码");
getCodeButton.setEnabled(true);
}
}
};
worker.execute();
}
private void handleRegister() {
String inputCode = codeField.getText().trim();
if (generatedCode == null || !generatedCode.equals(inputCode)) {
JOptionPane.showMessageDialog(this, "验证码错误!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
// 验证成功,跳转到设置密码页面
app.showSetPasswordPanel(emailField.getText().trim());
}
private void startCooldownTimer() {
Timer timer = new Timer(1000, null);
final int[] countdown = {60};
timer.addActionListener(e -> {
countdown[0]--;
if (countdown[0] >= 0) {
getCodeButton.setText(countdown[0] + "秒后重试");
} else {
timer.stop();
getCodeButton.setText("获取验证码");
getCodeButton.setEnabled(true);
}
});
timer.start();
}
}

@ -0,0 +1,49 @@
package com.mathapp.panels;
import com.mathapp.MathApp;
import javax.swing.*;
import java.awt.*;
public class ResultsPanel extends JPanel {
public ResultsPanel(MathApp app, int score, int totalQuestions) {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
JLabel titleLabel = new JLabel("测验完成!", SwingConstants.CENTER);
titleLabel.setFont(new Font("思源黑体", Font.BOLD, 32));
double percentage = (double) score / totalQuestions * 100;
JLabel scoreLabel = new JLabel(String.format("你的得分: %.0f 分", percentage), SwingConstants.CENTER);
scoreLabel.setFont(new Font("思源黑体", Font.PLAIN, 24));
JLabel detailsLabel = new JLabel(String.format("(答对 %d 题,共 %d 题)", score, totalQuestions), SwingConstants.CENTER);
detailsLabel.setFont(new Font("思源黑体", Font.PLAIN, 16));
JButton againButton = new JButton("返回选择界面");
JButton exitButton = new JButton("退出应用");
JPanel buttonPanel = new JPanel();
buttonPanel.add(againButton);
buttonPanel.add(exitButton);
gbc.insets = new Insets(10, 10, 10, 10);
gbc.gridx = 0;
gbc.gridy = 0;
add(titleLabel, gbc);
gbc.gridy = 1;
add(scoreLabel, gbc);
gbc.gridy = 2;
add(detailsLabel, gbc);
gbc.gridy = 3;
gbc.insets = new Insets(20, 10, 10, 10);
add(buttonPanel, gbc);
againButton.addActionListener(e -> app.showPanel(MathApp.MAIN_MENU_PANEL));
exitButton.addActionListener(e -> System.exit(0));
}
}

@ -0,0 +1,91 @@
package com.mathapp.panels;
import com.mathapp.MathApp;
import com.mathapp.models.User;
import com.mathapp.services.DataPersistence;
import com.mathapp.utils.ValidationUtils;
import javax.swing.*;
import java.awt.*;
public class SetPasswordPanel extends JPanel {
private final MathApp app;
private final String email;
private final JPasswordField passwordField;
private final JPasswordField confirmPasswordField;
public SetPasswordPanel(MathApp app, String email) {
this.app = app;
this.email = email;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
JLabel titleLabel = new JLabel("设置您的密码", SwingConstants.CENTER);
titleLabel.setFont(new Font("思源黑体", Font.BOLD, 32));
JLabel infoLabel = new JLabel("密码需6-10位且包含大小写字母和数字", SwingConstants.CENTER);
infoLabel.setFont(new Font("思源黑体", Font.PLAIN, 12));
passwordField = new JPasswordField(20);
confirmPasswordField = new JPasswordField(20);
JButton confirmButton = new JButton("完成注册");
// Layout
gbc.insets = new Insets(10, 10, 10, 10);
gbc.gridwidth = 2;
gbc.gridx = 0;
gbc.gridy = 0;
add(titleLabel, gbc);
gbc.gridy = 1;
add(infoLabel, gbc);
gbc.gridwidth = 1;
gbc.gridy = 2;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("输入密码:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(passwordField, gbc);
gbc.gridy = 3;
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("确认密码:"), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(confirmPasswordField, gbc);
gbc.gridy = 4;
gbc.gridx = 0;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
add(confirmButton, gbc);
confirmButton.addActionListener(e -> handleSetPassword());
}
private void handleSetPassword() {
String password = new String(passwordField.getPassword());
String confirmPassword = new String(confirmPasswordField.getPassword());
if (!password.equals(confirmPassword)) {
JOptionPane.showMessageDialog(this, "两次输入的密码不一致!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (!ValidationUtils.isValidPassword(password)) {
JOptionPane.showMessageDialog(this, "密码格式不符合要求!\n(6-10位必须包含大小写字母和数字)", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
// 创建用户并保存
User newUser = new User(email, password);
DataPersistence.saveUser(newUser);
JOptionPane.showMessageDialog(this, "注册成功!现在您可以登录了。", "成功", JOptionPane.INFORMATION_MESSAGE);
app.showPanel(MathApp.LOGIN_PANEL);
}
}

@ -0,0 +1,113 @@
package com.mathapp.problemGenerators;
import com.mathapp.models.Equation;
import com.mathapp.models.Operator;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
*
*
*/
public abstract class AbstractProblemGenerator implements IProblemGenerator {
protected final Random random = new Random();
protected static final int MIN_OPERAND_VALUE = 1;
protected static final int MAX_OPERAND_VALUE = 100;
protected static final int MIN_OPERANDS_COUNT = 2;
protected static final int MAX_OPERANDS_COUNT = 5;
private static final Operator[] AVAILABLE_OPERATORS = {
Operator.ADD, Operator.SUBTRACT, Operator.MULTIPLY, Operator.DIVIDE
};
/**
*
*/
@Override
public final List<Equation> generate(int count, Set<String> existingProblems) {
List<Equation> newProblems = new ArrayList<>(count);
int attempts = 0;
final int maxAttempts = count * 10; // 防止无限循环
while (newProblems.size() < count && attempts < maxAttempts) {
Equation newProblem = createProblem();
if (!existingProblems.contains(newProblem.toNormalizedString())) {
newProblems.add(newProblem);
existingProblems.add(newProblem.toNormalizedString());
}
attempts++;
}
return newProblems;
}
/**
* ()
*/
protected abstract Equation createProblem();
/**
* ()
*/
protected Equation createBaseArithmeticProblem(boolean nonNegativeOnly) {
int operandCount = getRandomNumber(MIN_OPERANDS_COUNT, MAX_OPERANDS_COUNT);
List<String> operands = new ArrayList<>();
List<Operator> operators = new ArrayList<>();
operands.add(String.valueOf(getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE)));
for (int i = 0; i < operandCount - 1; i++) {
Operator operator = AVAILABLE_OPERATORS[random.nextInt(AVAILABLE_OPERATORS.length)];
addOperandAndOperator(operands, operators, operator, nonNegativeOnly);
}
addParentheses(operands, operators);
return new Equation(operands, operators);
}
protected int getRandomNumber(int min, int max) {
return random.nextInt(max - min + 1) + min;
}
private void addOperandAndOperator(List<String> operands, List<Operator> operators, Operator operator, boolean nonNegativeOnly) {
int nextOperand;
int lastOperand = Integer.parseInt(operands.get(operands.size() - 1).replaceAll("[()]", ""));
if (operator == Operator.SUBTRACT && nonNegativeOnly) {
nextOperand = getRandomNumber(MIN_OPERAND_VALUE, lastOperand);
} else if (operator == Operator.DIVIDE) {
List<Integer> divisors = findDivisors(lastOperand);
int divisor = divisors.get(random.nextInt(divisors.size()));
int quotient = lastOperand / divisor;
operands.set(operands.size() - 1, String.valueOf(divisor * quotient));
nextOperand = divisor;
} else {
nextOperand = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE);
}
operands.add(String.valueOf(nextOperand));
operators.add(operator);
}
private void addParentheses(List<String> operands, List<Operator> operators) {
if (operators.size() > 1 && random.nextBoolean()) {
int pos = random.nextInt(operators.size());
Operator op = operators.get(pos);
if (op == Operator.ADD || op == Operator.SUBTRACT) {
operands.set(pos, "(" + operands.get(pos));
operands.set(pos + 1, operands.get(pos + 1) + ")");
}
}
}
private List<Integer> findDivisors(int number) {
if (number == 0) return Collections.singletonList(1);
int absNumber = Math.abs(number);
return IntStream.rangeClosed(1, absNumber)
.filter(i -> absNumber % i == 0)
.boxed()
.collect(Collectors.toList());
}
}

@ -0,0 +1,16 @@
package com.mathapp.problemGenerators;
import com.mathapp.models.Equation;
/**
*
*
*/
public class ElementaryProblemGenerator extends AbstractProblemGenerator {
@Override
protected Equation createProblem() {
// 调用父类的核心方法,并传入 true要求启用“无负数”约束。
return createBaseArithmeticProblem(true);
}
}

@ -0,0 +1,50 @@
package com.mathapp.problemGenerators;
import com.mathapp.models.Equation;
import com.mathapp.models.Operator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* /
*
* exp4j
*/
public class HighSchoolProblemGenerator extends MiddleSchoolProblemGenerator {
private static final int[] PREDEFINED_ANGLES = new int[]{0, 30, 45, 60, 90};
private static final String[] TRIG_FUNCTIONS = new String[]{"sin", "cos", "tan"};
@Override
protected Equation createProblem() {
// 复用初中生成器的逻辑,先生成一个带平方或开方的题目
Equation middleSchoolEquation = super.createProblem();
List<String> operands = new ArrayList<>(middleSchoolEquation.operands());
List<Operator> operators = new ArrayList<>(middleSchoolEquation.operators());
String function = Arrays.toString(TRIG_FUNCTIONS);
int[] angle = PREDEFINED_ANGLES;
// 生成对学生友好的三角函数表达式,如 "sin(30)"
// exp4j 的计算将在 QuestionGenerator 中通过自定义函数处理角度
String trigExpression = String.format("%s(%d)", function, angle);
// 随机选择一个位置插入三角函数表达式
int insertPos = random.nextInt(operands.size() + 1);
if (insertPos == operands.size() || operands.isEmpty()) {
operands.add(trigExpression);
if(!operators.isEmpty() || operands.size() > 1) {
operators.add(Operator.ADD);
}
} else {
operands.add(insertPos, trigExpression);
operators.add(insertPos, Operator.ADD); // 简单地用加法连接
}
return new Equation(operands, operators);
}
}

@ -0,0 +1,21 @@
package com.mathapp.problemGenerators;
import com.mathapp.models.Equation;
import java.util.List;
import java.util.Set;
/**
*
*
*/
public interface IProblemGenerator {
/**
*
*
* @param count
* @param existingProblems
* @return Equation
*/
List<Equation> generate(int count, Set<String> existingProblems);
}

@ -0,0 +1,41 @@
package com.mathapp.problemGenerators;
import com.mathapp.models.Equation;
import com.mathapp.models.Operator;
import java.util.ArrayList;
import java.util.List;
/**
*
*
* exp4j
*/
public class MiddleSchoolProblemGenerator extends AbstractProblemGenerator {
@Override
protected Equation createProblem() {
Equation basicEquation = createBaseArithmeticProblem(false);
List<String> operands = new ArrayList<>(basicEquation.operands());
List<Operator> operators = new ArrayList<>(basicEquation.operators());
int modifyIndex = random.nextInt(operands.size());
String targetOperand = operands.get(modifyIndex);
// 检查操作数是否已经是复杂表达式(包含括号),如果是,则不再添加括号
boolean isComplex = targetOperand.contains("(") && targetOperand.contains(")");
if (random.nextBoolean()) {
// 平方运算exp4j 使用 '^' 符号
// 如果操作数本身是复杂表达式,如 (3+2),则需要保留括号,变成 ((3+2)^2)
String squaredExpression = isComplex? String.format("(%s^2)", targetOperand) : String.format("%s^2", targetOperand);
operands.set(modifyIndex, squaredExpression);
} else {
// 开方运算exp4j 使用 'sqrt()' 函数
int base = getRandomNumber(2, 10);
int valueToRoot = base * base;
String sqrtExpression = String.format("sqrt(%d)", valueToRoot);
operands.set(modifyIndex, sqrtExpression);
}
return new Equation(operands, operators);
}
}

@ -0,0 +1,120 @@
package com.mathapp.services;
import com.mathapp.models.Question;
import com.mathapp.models.TestPaper;
import com.mathapp.models.User;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Stream;
public class DataPersistence {
private static final Path USER_FILE_PATH = Paths.get("data", "users.txt");
// 静态初始化块,确保目录存在
static {
try {
Files.createDirectories(USER_FILE_PATH.getParent());
if (!Files.exists(USER_FILE_PATH)) {
Files.createFile(USER_FILE_PATH);
}
} catch (IOException e) {
System.err.println("初始化用户数据文件失败: " + e.getMessage());
e.printStackTrace();
}
}
public static void saveUser(User user) {
String userData = user.getEmail() + "::" + user.getHashedPassword() + System.lineSeparator();
try {
Files.writeString(USER_FILE_PATH, userData, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException e) {
e.printStackTrace();
}
}
public static User findUserByEmail(String email) {
try (Stream<String> lines = Files.lines(USER_FILE_PATH, StandardCharsets.UTF_8)) {
return lines.filter(line -> line.startsWith(email + "::"))
.map(line -> {
String[] parts = line.split("::");
// 构造函数会处理密码哈希
return new User(parts[0], parts[1], true);
})
.findFirst()
.orElse(null);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static boolean updatePassword(String email, String newPassword) {
try {
List<String> lines = Files.readAllLines(USER_FILE_PATH, StandardCharsets.UTF_8);
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).startsWith(email + "::")) {
User tempUser = new User(email, newPassword); // 用来生成新密码的哈希值
lines.set(i, email + "::" + tempUser.getHashedPassword());
Files.write(USER_FILE_PATH, lines, StandardCharsets.UTF_8);
return true;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public static void saveTestPaper(TestPaper testPaper) {
if (testPaper.username() == null) {
System.err.println("无法保存试卷:用户名为空。");
return;
}
String username = testPaper.username().split("@")[0]; // 使用邮箱前缀作为目录名
try {
Path userDir = Paths.get("data", username);
Files.createDirectories(userDir);
String timestamp = testPaper.timestamp().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
Path filePath = userDir.resolve(timestamp + ".txt");
try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) {
writer.write("用户: " + testPaper.username());
writer.newLine();
writer.write("时间: " + timestamp);
writer.newLine();
writer.write(String.format("得分: %d / %d", testPaper.score(), testPaper.questions().size()));
writer.newLine();
writer.write("====================================");
writer.newLine();
for (int i = 0; i < testPaper.questions().size(); i++) {
Question q = testPaper.questions().get(i);
writer.write(String.format("题目 %d: %s", (i + 1), q.problemStatement()));
writer.newLine();
writer.write("选项: " + String.join(" | ", q.options()));
writer.newLine();
writer.write("你的答案: " + testPaper.userAnswers().get(i));
writer.newLine();
writer.write("正确答案: " + q.correctAnswer());
writer.newLine();
writer.write("--------------------");
writer.newLine();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,40 @@
package com.mathapp.services;
import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.util.Properties;
public class EmailService {
// !!! 重要提示 !!!
// 请将以下信息替换为您自己的SMTP服务器信息和凭据
// 对于Gmail, 您需要生成一个 "应用专用密码"
private static final String SMTP_HOST = "smtp.your-email-provider.com"; // 例如smtp.gmail.com
private static final String SMTP_PORT = "587"; // 通常是 587 (TLS) 或 465 (SSL)
private static final String FROM_EMAIL = "your-email@example.com"; // 您的发件邮箱地址
private static final String PASSWORD = "your-app-password"; // 您的应用专用密码或邮箱密码
public static void sendVerificationCode(String toEmail, String code) throws MessagingException {
Properties props = new Properties();
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.port", SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(FROM_EMAIL, PASSWORD);
}
});
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(FROM_EMAIL));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
message.setSubject("您的数学学习平台注册码");
message.setText("尊敬的用户,\n\n您的注册码是: " + code + "\n\n请在注册页面输入以完成验证。该验证码5分钟内有效。\n\n祝您学习愉快\n数学学习平台");
Transport.send(message);
}
}

@ -0,0 +1,94 @@
package com.mathapp.services;
import com.mathapp.models.Equation;
import com.mathapp.models.Question;
import com.mathapp.problemGenerators.*;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
public class QuestionGenerator {
public static List<Question> generateQuestions(String level, int count) {
IProblemGenerator generator;
switch (level) {
case "初中":
generator = new MiddleSchoolProblemGenerator();
break;
case "高中":
generator = new HighSchoolProblemGenerator();
break;
case "小学":
default:
generator = new ElementaryProblemGenerator();
break;
}
List<Question> questions = new ArrayList<>();
Set<String> existingProblems = new HashSet<>();
List<Equation> equations = generator.generate(count, existingProblems);
for (Equation eq : equations) {
String problemStatement = eq.toFormattedString();
try {
String correctAnswer = evaluateExpression(problemStatement);
List<String> options = generateOptions(correctAnswer);
questions.add(new Question(problemStatement, options, correctAnswer));
existingProblems.add(eq.toNormalizedString());
} catch (Exception e) {
System.err.println("无法计算表达式: " + problemStatement + " - " + e.getMessage());
}
}
return questions;
}
private static String evaluateExpression(String expression) {
// exp4j 不支持'=',需要移除
String cleanExpression = expression.replace("=", "").trim();
Expression exp = new ExpressionBuilder(cleanExpression).build();
double result = exp.evaluate();
// 格式化结果,最多保留两位小数
BigDecimal bd = BigDecimal.valueOf(result);
bd = bd.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros();
return bd.toPlainString();
}
private static List<String> generateOptions(String correctAnswerStr) {
Set<String> options = new HashSet<>();
options.add(correctAnswerStr);
Random rand = new Random();
double correctAnswerVal = Double.parseDouble(correctAnswerStr);
while (options.size() < 4) {
int strategy = rand.nextInt(3);
double wrongAnswerVal;
switch (strategy) {
case 0: // 微小偏差
wrongAnswerVal = correctAnswerVal + (rand.nextDouble() * 10 - 5);
break;
case 1: // 数量级错误
wrongAnswerVal = correctAnswerVal * (rand.nextBoolean() ? 10 : 0.1);
break;
case 2: // 符号错误
default:
wrongAnswerVal = -correctAnswerVal;
break;
}
// 格式化错误答案
BigDecimal bd = BigDecimal.valueOf(wrongAnswerVal);
bd = bd.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros();
options.add(bd.toPlainString());
}
List<String> sortedOptions = new ArrayList<>(options);
Collections.shuffle(sortedOptions);
return sortedOptions;
}
}

@ -0,0 +1,28 @@
package com.mathapp.utils;
import java.util.regex.Pattern;
public class ValidationUtils {
// 邮箱正则表达式
private static final String EMAIL_REGEX =
"^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
// 密码正则表达式6-10位至少一个数字一个小写字母一个大写字母
private static final String PASSWORD_REGEX =
"^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{6,10}$";
public static boolean isValidEmail(String email) {
if (email == null) {
return false;
}
return Pattern.matches(EMAIL_REGEX, email);
}
public static boolean isValidPassword(String password) {
if (password == null) {
return false;
}
return Pattern.matches(PASSWORD_REGEX, password);
}
}

@ -1,13 +0,0 @@
package org.example;
//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
public class Main {
public static void main(String[] args) {
//TIP 当文本光标位于高亮显示的文本处时按 <shortcut actionId="ShowIntentionActions"/>
// 查看 IntelliJ IDEA 建议如何修正。
}
}
Loading…
Cancel
Save