1 #4

Merged
hnu202326010331 merged 1 commits from zhengguochen_branch into develop 4 months ago

@ -0,0 +1,233 @@
# 带UI的小初高数学学习软件
## 引言
本项目旨在为小学、初中和高中学生提供一个数学学习平台通过图形用户界面GUI与用户进行交互。系统包含用户注册、登录、答题等功能旨在帮助学生通过练习题目提高数学水平。通过该项目用户可以注册账号、登录、设置密码、接收验证码、选择题目数量并参与答题最终获取成绩反馈。
## 需求分析
### 用户需求
- **用户群体**:小学、初中和高中学生。
- **主要功能**
1. **用户注册**:用户提供邮箱地址,接收注册码并完成注册。
2. **用户登录**:通过注册的用户名和密码登录。
3. **答题功能**:用户根据所选学段生成题目并进行选择题答题。
4. **密码设置与修改**:设置密码需满足长度和字符要求,且用户可修改密码。
5. **邮箱验证码**:用户注册时,通过邮箱发送验证码进行身份验证。
6. **成绩评定**:完成答题后,系统自动计算得分并显示。
### 系统功能
- **图形界面**:包括欢迎页面、注册页面、登录页面、题目页面和成绩页面等。
- **事件处理**:用户操作界面后触发相应的事件(如点击按钮提交、输入框验证)。
- **邮件服务**通过QQ邮箱服务发送验证码。
- **界面布局**:界面设计需简洁、易于操作。
## 系统设计
### 系统设计架构
本项目采用 **MVC**Model-View-Controller设计模式分为 **模型层**Model、**视图层**View、和 **控制器层**Controller。这种架构使得应用程序更加模块化易于维护和扩展。下面是对各个模块的详细分析和代码实现的具体说明。
#### 1. **Model模型层**
模型层负责处理程序的核心数据逻辑,进行业务逻辑的处理和数据的存储。在本项目中,模型层并不涉及数据库存储,所有数据都保存在内存中,程序退出时数据丢失。
##### 主要类:
##### b **User.java**(假设该类存在于项目中,负责用户信息管理)
```java
public class User {
private String username;
private String password;
private String email;
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getEmail() {
return email;
}
}
```
#### 2. **View视图层**
视图层负责用户界面的展示。它接收用户输入,并将其传递给控制器层;同时,接收控制器层传递的数据并更新用户界面。这里使用 Java Swing 库构建图形用户界面。
#### 主要类:
#### **MathLearningApp.java**(主应用程序)
```java
import javax.swing.*;
public class MathLearningApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Math Learning App");
JButton registerButton = new JButton("Register");
registerButton.addActionListener(e -> {
// 弹出注册界面
new RegistrationForm().setVisible(true);
});
frame.add(registerButton);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
```
**说明:**
- **MathLearningApp.java** 类是应用程序的主入口,创建了一个 `JFrame` 作为主窗口,并添加了一个注册按钮。点击按钮时,会弹出 `RegistrationForm`(注册表单)界面。
#### **RegistrationForm.java**(注册界面,假设代码存在)
```java
public class RegistrationForm extends JFrame {
public RegistrationForm() {
setTitle("User Registration");
setSize(300, 200);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JTextField emailField = new JTextField();
JPasswordField passwordField = new JPasswordField();
JButton submitButton = new JButton("Submit");
submitButton.addActionListener(e -> {
String email = emailField.getText();
String password = new String(passwordField.getPassword());
// 注册处理逻辑
});
// 设置布局、添加组件等...
}
}
```
**说明:**
- **RegistrationForm.java** 类用于显示用户注册界面,包含了邮箱和密码输入框,以及一个提交按钮,按钮点击后会触发注册逻辑。
#### 3. **Controller控制器层**
控制器层负责接收用户的输入并更新模型和视图。它是模型和视图之间的桥梁。
#### 主要类:
#### **MathLearningAppMethods.java**(界面方法扩展类)
```java
public class MathLearningAppMethods {
public static void handleRegistration(String email, String password) {
// 校验邮箱格式
if (!isValidEmail(email)) {
JOptionPane.showMessageDialog(null, "Invalid email address!");
return;
}
// 用户注册逻辑
User newUser = new User(email, password, email);
// 这里通常会调用模型层存储用户信息,但由于不使用数据库,暂时保存在内存中
System.out.println("User registered: " + newUser.getUsername());
// 发送注册成功邮件(通过邮件服务)
sendVerificationEmail(email);
}
private static boolean isValidEmail(String email) {
// 邮箱格式验证逻辑
return email.contains("@");
}
private static void sendVerificationEmail(String email) {
// 使用 RealEmailSender 发送邮件
new RealEmailSender().sendEmail(email, "Verification Code", "123456");
}
}
```
**说明:**
- **MathLearningAppMethods.java** 中的 `handleRegistration` 方法处理用户的注册逻辑。首先,它验证邮箱格式,如果正确则创建一个新的 `User` 实例,接着通过 `RealEmailSender` 类发送验证邮件。
---
#### 4. **邮件发送服务**
邮件服务模块用于处理邮件发送,项目中使用了 QQ 邮箱服务来发送验证码。
#### 主要类:
#### **QQEmailService.java**QQ邮箱服务
```java
public class QQEmailService {
public void sendVerificationCode(String email, String code) {
// 使用 QQ 邮箱的 SMTP 服务发送邮件
System.out.println("Sending verification code " + code + " to " + email);
}
}
```
#### 4. **邮件发送服务**
邮件服务模块用于处理邮件发送,项目中使用了 QQ 邮箱服务来发送验证码。
#### 主要类:
#### **QQEmailService.java**QQ邮箱服务
**说明:**
- **QQEmailService.java** 类负责通过 QQ 邮箱发送验证码。该服务并没有连接真实的 SMTP 服务器,实际操作中可以进一步配置邮箱服务器来实现邮件的发送。
#### **RealEmailSender.java**(邮件发送器)
```java
public class RealEmailSender {
private QQEmailService emailService = new QQEmailService();
public void sendEmail(String recipient, String subject, String body) {
// 通过 QQEmailService 实现邮件的发送
emailService.sendVerificationCode(recipient, body);
}
}
```
**说明:**
- **RealEmailSender.java** 类负责邮件的实际发送操作,它调用了 `QQEmailService` 类来发送邮件。未来可以根据需要扩展更多邮件服务支持。
### 总结
- **Model模型层**:管理核心数据,处理用户信息和业务逻辑。
- **View视图层**:构建图形用户界面,展示数据并与用户交互。
- **Controller控制器层**:处理用户输入,协调模型和视图的交互。
这种设计模式使得代码的结构更加清晰,易于维护。用户界面部分(视图)与业务逻辑部分(模型)相互独立,便于将来扩展更多功能,优化代码结构。### 数据设计
本系统不使用数据库存储数据,所有用户数据(如注册信息、成绩等)都存储在内存中,程序运行期间临时保存。
## 实现与运行
### 环境与依赖
- **开发语言**Java 确保已安装Java环境JDK 8及以上
- **GUI框架**Java Swing
- **邮件服务**QQ邮箱SMTP服务
- **运行平台**支持Windows、macOS和Linux平台
### 运行项目
1. 编译并运行 `MathLearningApp.java` 文件,即可启动应用。
2. 根据界面提示完成注册、登录和答题操作。
### 功能实现
#### 用户注册与登录:
- 用户提供邮箱注册,接收并输入验证码完成注册。
- 密码必须包含大小写字母和数字且长度为6-10位。
#### 答题流程:
- 根据选择的年级生成题目,用户逐一作答。
- 答题后自动计算成绩,并提供继续答题或退出的选项。
#### 邮件验证:
- 注册时通过 `QQEmailService.java` 发送验证码到用户邮箱。
#### 界面管理:
- 通过 `MathLearningAppMethods.java` 进行界面布局和用户交互。

@ -0,0 +1,983 @@
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.List;
import java.util.regex.Pattern;
// 邮件功能暂时使用模拟实现,不需要真实的邮件库
import java.io.*;
import java.nio.file.*;
/**
* UI -
* GUI
*/
public class MathLearningApp extends JFrame {
private CardLayout cardLayout;
private JPanel mainPanel;
// 用户管理
private Map<String, RegisteredUser> registeredUsers;
private RegisteredUser currentUser;
// 题目管理
private MathQuestionGenerator questionGenerator;
private List<MultipleChoiceQuestion> currentQuestions;
private int currentQuestionIndex;
private int correctAnswers;
private String currentDifficulty;
// 界面组件
private JTextField emailField;
private JTextField verificationCodeField;
private JPasswordField passwordField;
private JPasswordField confirmPasswordField;
private JPasswordField oldPasswordField;
private JLabel questionLabel;
private ButtonGroup answerGroup;
private JRadioButton[] answerButtons;
private JLabel scoreLabel;
public MathLearningApp() {
initializeData();
initializeGUI();
loadUserData();
}
/**
*
*/
private void initializeData() {
registeredUsers = new HashMap<>();
questionGenerator = new MathQuestionGenerator();
currentQuestions = new ArrayList<>();
currentQuestionIndex = 0;
correctAnswers = 0;
answerButtons = new JRadioButton[4];
}
/**
* GUI
*/
private void initializeGUI() {
setTitle("小初高数学学习软件");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 600);
setLocationRelativeTo(null);
cardLayout = new CardLayout();
mainPanel = new JPanel(cardLayout);
// 创建各个界面
createWelcomePanel();
createRegisterPanel();
createLoginPanel();
createPasswordSetupPanel();
createDifficultySelectionPanel();
createQuestionPanel();
createScorePanel();
createPasswordChangePanel();
add(mainPanel);
// 显示欢迎界面
cardLayout.show(mainPanel, "WELCOME");
}
/**
*
*/
private void createWelcomePanel() {
JPanel panel = new JPanel(new BorderLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建渐变背景
GradientPaint gradient = new GradientPaint(0, 0, new Color(135, 206, 250),
0, getHeight(), new Color(240, 248, 255));
g2.setPaint(gradient);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
};
// 标题面板
JPanel titlePanel = new JPanel();
titlePanel.setOpaque(false);
titlePanel.setBorder(BorderFactory.createEmptyBorder(80, 0, 50, 0));
JLabel titleLabel = new JLabel("小初高数学学习软件", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 36));
titleLabel.setForeground(new Color(25, 25, 112));
JLabel subtitleLabel = new JLabel("让数学学习变得更有趣", JLabel.CENTER);
subtitleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
subtitleLabel.setForeground(new Color(70, 130, 180));
subtitleLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.Y_AXIS));
titlePanel.add(titleLabel);
titlePanel.add(subtitleLabel);
// 按钮面板
JPanel buttonPanel = new JPanel();
buttonPanel.setOpaque(false);
buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 30, 20));
JButton registerButton = createStyledButton("用户注册", new Color(60, 179, 113));
JButton loginButton = createStyledButton("用户登录", new Color(70, 130, 180));
registerButton.addActionListener(e -> cardLayout.show(mainPanel, "REGISTER"));
loginButton.addActionListener(e -> cardLayout.show(mainPanel, "LOGIN"));
buttonPanel.add(registerButton);
buttonPanel.add(loginButton);
// 底部信息
JLabel footerLabel = new JLabel("© 2025 数学学习软件 - 专业的数学学习平台", JLabel.CENTER);
footerLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
footerLabel.setForeground(new Color(100, 100, 100));
footerLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 30, 0));
panel.add(titlePanel, BorderLayout.NORTH);
panel.add(buttonPanel, BorderLayout.CENTER);
panel.add(footerLabel, BorderLayout.SOUTH);
mainPanel.add(panel, "WELCOME");
}
/**
*
*/
private void createRegisterPanel() {
JPanel panel = new JPanel(new BorderLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建渐变背景
GradientPaint gradient = new GradientPaint(0, 0, new Color(250, 250, 250),
0, getHeight(), new Color(240, 248, 255));
g2.setPaint(gradient);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
};
// 标题面板
JPanel titlePanel = new JPanel();
titlePanel.setOpaque(false);
titlePanel.setBorder(BorderFactory.createEmptyBorder(40, 0, 30, 0));
JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 28));
titleLabel.setForeground(new Color(25, 25, 112));
JLabel subtitleLabel = new JLabel("请填写您的邮箱信息完成注册", JLabel.CENTER);
subtitleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
subtitleLabel.setForeground(new Color(100, 100, 100));
subtitleLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.Y_AXIS));
titlePanel.add(titleLabel);
titlePanel.add(subtitleLabel);
// 表单面板 - 添加卡片样式
JPanel formContainer = new JPanel();
formContainer.setOpaque(false);
formContainer.setBorder(BorderFactory.createEmptyBorder(20, 50, 20, 50));
JPanel formPanel = new JPanel(new GridBagLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 卡片阴影
g2.setColor(new Color(0, 0, 0, 20));
g2.fillRoundRect(5, 5, getWidth()-5, getHeight()-5, 20, 20);
// 卡片背景
g2.setColor(Color.WHITE);
g2.fillRoundRect(0, 0, getWidth()-5, getHeight()-5, 20, 20);
g2.dispose();
}
};
formPanel.setBorder(BorderFactory.createEmptyBorder(30, 40, 30, 40));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(15, 10, 15, 10);
// 邮箱输入
gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.EAST;
JLabel emailLabel = new JLabel("邮箱地址:");
emailLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
emailLabel.setForeground(new Color(70, 70, 70));
formPanel.add(emailLabel, gbc);
gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST;
emailField = new JTextField(20);
emailField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
emailField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200), 1),
BorderFactory.createEmptyBorder(8, 12, 8, 12)
));
formPanel.add(emailField, gbc);
// 发送验证码按钮
gbc.gridx = 2;
JButton sendCodeButton = createStyledButton("发送验证码", new Color(255, 140, 0));
sendCodeButton.addActionListener(this::sendVerificationCode);
formPanel.add(sendCodeButton, gbc);
// 验证码输入
gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST;
JLabel codeLabel = new JLabel("验证码:");
codeLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
codeLabel.setForeground(new Color(70, 70, 70));
formPanel.add(codeLabel, gbc);
gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST;
verificationCodeField = new JTextField(20);
verificationCodeField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
verificationCodeField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200), 1),
BorderFactory.createEmptyBorder(8, 12, 8, 12)
));
formPanel.add(verificationCodeField, gbc);
formContainer.add(formPanel);
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 30));
buttonPanel.setOpaque(false);
JButton registerButton = createStyledButton("注册", new Color(60, 179, 113));
JButton backButton = createStyledButton("返回", new Color(128, 128, 128));
registerButton.addActionListener(this::handleRegister);
backButton.addActionListener(e -> cardLayout.show(mainPanel, "WELCOME"));
buttonPanel.add(registerButton);
buttonPanel.add(backButton);
panel.add(titlePanel, BorderLayout.NORTH);
panel.add(formContainer, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
mainPanel.add(panel, "REGISTER");
}
/**
*
*/
private void createLoginPanel() {
JPanel panel = new JPanel(new BorderLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建渐变背景
GradientPaint gradient = new GradientPaint(0, 0, new Color(250, 250, 250),
0, getHeight(), new Color(240, 248, 255));
g2.setPaint(gradient);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
};
// 标题面板
JPanel titlePanel = new JPanel();
titlePanel.setOpaque(false);
titlePanel.setBorder(BorderFactory.createEmptyBorder(60, 0, 40, 0));
JLabel titleLabel = new JLabel("用户登录", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 28));
titleLabel.setForeground(new Color(25, 25, 112));
JLabel subtitleLabel = new JLabel("请输入您的账户信息", JLabel.CENTER);
subtitleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
subtitleLabel.setForeground(new Color(100, 100, 100));
subtitleLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.Y_AXIS));
titlePanel.add(titleLabel);
titlePanel.add(subtitleLabel);
// 表单面板 - 添加卡片样式
JPanel formContainer = new JPanel();
formContainer.setOpaque(false);
formContainer.setBorder(BorderFactory.createEmptyBorder(20, 80, 20, 80));
JPanel formPanel = new JPanel(new GridBagLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 卡片阴影
g2.setColor(new Color(0, 0, 0, 20));
g2.fillRoundRect(5, 5, getWidth()-5, getHeight()-5, 20, 20);
// 卡片背景
g2.setColor(Color.WHITE);
g2.fillRoundRect(0, 0, getWidth()-5, getHeight()-5, 20, 20);
g2.dispose();
}
};
formPanel.setBorder(BorderFactory.createEmptyBorder(40, 50, 40, 50));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(20, 10, 20, 10);
// 用户名(邮箱)输入
gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.EAST;
JLabel emailLabel = new JLabel("用户名(邮箱):");
emailLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
emailLabel.setForeground(new Color(70, 70, 70));
formPanel.add(emailLabel, gbc);
gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST;
JTextField loginEmailField = new JTextField(25);
loginEmailField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
loginEmailField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200), 1),
BorderFactory.createEmptyBorder(10, 15, 10, 15)
));
formPanel.add(loginEmailField, gbc);
// 密码输入
gbc.gridx = 0; gbc.gridy = 1; gbc.anchor = GridBagConstraints.EAST;
JLabel passwordLabel = new JLabel("密码:");
passwordLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
passwordLabel.setForeground(new Color(70, 70, 70));
formPanel.add(passwordLabel, gbc);
gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST;
JPasswordField loginPasswordField = new JPasswordField(25);
loginPasswordField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
loginPasswordField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200), 1),
BorderFactory.createEmptyBorder(10, 15, 10, 15)
));
formPanel.add(loginPasswordField, gbc);
formContainer.add(formPanel);
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 40));
buttonPanel.setOpaque(false);
JButton loginButton = createStyledButton("登录", new Color(70, 130, 180));
JButton backButton = createStyledButton("返回", new Color(128, 128, 128));
loginButton.addActionListener(e -> handleLogin(loginEmailField.getText(),
new String(loginPasswordField.getPassword())));
backButton.addActionListener(e -> cardLayout.show(mainPanel, "WELCOME"));
buttonPanel.add(loginButton);
buttonPanel.add(backButton);
panel.add(titlePanel, BorderLayout.NORTH);
panel.add(formContainer, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
mainPanel.add(panel, "LOGIN");
}
/**
*
*/
private void createPasswordSetupPanel() {
JPanel panel = new JPanel(new BorderLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建渐变背景
GradientPaint gradient = new GradientPaint(0, 0, new Color(250, 250, 250),
0, getHeight(), new Color(240, 248, 255));
g2.setPaint(gradient);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
};
// 标题面板
JPanel titlePanel = new JPanel();
titlePanel.setOpaque(false);
titlePanel.setBorder(BorderFactory.createEmptyBorder(50, 0, 30, 0));
JLabel titleLabel = new JLabel("设置密码", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 28));
titleLabel.setForeground(new Color(25, 25, 112));
JLabel subtitleLabel = new JLabel("请为您的账户设置安全密码", JLabel.CENTER);
subtitleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
subtitleLabel.setForeground(new Color(100, 100, 100));
subtitleLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
// 提示信息
JLabel hintLabel = new JLabel("<html><center>密码要求6-10位必须包含大小写字母和数字</center></html>", JLabel.CENTER);
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
hintLabel.setForeground(new Color(150, 150, 150));
hintLabel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.Y_AXIS));
titlePanel.add(titleLabel);
titlePanel.add(subtitleLabel);
titlePanel.add(hintLabel);
// 表单面板 - 添加卡片样式
JPanel formContainer = new JPanel();
formContainer.setOpaque(false);
formContainer.setBorder(BorderFactory.createEmptyBorder(30, 80, 30, 80));
JPanel formPanel = new JPanel(new GridBagLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 卡片阴影
g2.setColor(new Color(0, 0, 0, 20));
g2.fillRoundRect(5, 5, getWidth()-5, getHeight()-5, 20, 20);
// 卡片背景
g2.setColor(Color.WHITE);
g2.fillRoundRect(0, 0, getWidth()-5, getHeight()-5, 20, 20);
g2.dispose();
}
};
formPanel.setBorder(BorderFactory.createEmptyBorder(40, 50, 40, 50));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(20, 10, 20, 10);
// 密码输入
gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.EAST;
JLabel passwordLabel = new JLabel("输入密码:");
passwordLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
passwordLabel.setForeground(new Color(70, 70, 70));
formPanel.add(passwordLabel, gbc);
gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST;
passwordField = new JPasswordField(25);
passwordField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
passwordField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200), 1),
BorderFactory.createEmptyBorder(10, 15, 10, 15)
));
formPanel.add(passwordField, gbc);
// 确认密码
gbc.gridx = 0; gbc.gridy = 1; gbc.anchor = GridBagConstraints.EAST;
JLabel confirmLabel = new JLabel("确认密码:");
confirmLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
confirmLabel.setForeground(new Color(70, 70, 70));
formPanel.add(confirmLabel, gbc);
gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST;
confirmPasswordField = new JPasswordField(25);
confirmPasswordField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
confirmPasswordField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200), 1),
BorderFactory.createEmptyBorder(10, 15, 10, 15)
));
formPanel.add(confirmPasswordField, gbc);
formContainer.add(formPanel);
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 40));
buttonPanel.setOpaque(false);
JButton setPasswordButton = createStyledButton("设置密码", new Color(60, 179, 113));
setPasswordButton.addActionListener(this::handlePasswordSetup);
buttonPanel.add(setPasswordButton);
panel.add(titlePanel, BorderLayout.NORTH);
panel.add(formContainer, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
mainPanel.add(panel, "PASSWORD_SETUP");
}
/**
*
*/
private void createDifficultySelectionPanel() {
JPanel panel = new JPanel(new BorderLayout()) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建渐变背景
GradientPaint gradient = new GradientPaint(0, 0, new Color(240, 248, 255),
0, getHeight(), new Color(230, 240, 250));
g2.setPaint(gradient);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
};
// 标题面板
JPanel titlePanel = new JPanel();
titlePanel.setOpaque(false);
titlePanel.setBorder(BorderFactory.createEmptyBorder(60, 0, 40, 0));
JLabel titleLabel = new JLabel("选择学习难度", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 32));
titleLabel.setForeground(new Color(25, 25, 112));
JLabel subtitleLabel = new JLabel("选择适合您的数学学习级别", JLabel.CENTER);
subtitleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
subtitleLabel.setForeground(new Color(70, 130, 180));
subtitleLabel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.Y_AXIS));
titlePanel.add(titleLabel);
titlePanel.add(subtitleLabel);
// 按钮面板
JPanel buttonContainer = new JPanel();
buttonContainer.setOpaque(false);
buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 80, 40, 80));
JPanel buttonPanel = new JPanel(new GridLayout(3, 1, 0, 25));
buttonPanel.setOpaque(false);
JButton elementaryButton = createLargeDifficultyButton("小学数学", new Color(255, 182, 193));
JButton middleButton = createLargeDifficultyButton("初中数学", new Color(173, 216, 230));
JButton highButton = createLargeDifficultyButton("高中数学", new Color(221, 160, 221));
elementaryButton.addActionListener(e -> selectDifficulty("小学"));
middleButton.addActionListener(e -> selectDifficulty("初中"));
highButton.addActionListener(e -> selectDifficulty("高中"));
buttonPanel.add(elementaryButton);
buttonPanel.add(middleButton);
buttonPanel.add(highButton);
buttonContainer.add(buttonPanel);
// 底部面板
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 30));
bottomPanel.setOpaque(false);
JButton changePasswordButton = createStyledButton("修改密码", new Color(255, 140, 0));
JButton logoutButton = createStyledButton("退出登录", new Color(220, 20, 60));
changePasswordButton.addActionListener(e -> cardLayout.show(mainPanel, "PASSWORD_CHANGE"));
logoutButton.addActionListener(e -> {
currentUser = null;
cardLayout.show(mainPanel, "WELCOME");
});
bottomPanel.add(changePasswordButton);
bottomPanel.add(logoutButton);
panel.add(titlePanel, BorderLayout.NORTH);
panel.add(buttonContainer, BorderLayout.CENTER);
panel.add(bottomPanel, BorderLayout.SOUTH);
mainPanel.add(panel, "DIFFICULTY_SELECTION");
}
/**
*
*/
private void createQuestionPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBackground(Color.WHITE);
// 顶部信息面板
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.setBackground(Color.WHITE);
topPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JLabel progressLabel = new JLabel("", JLabel.LEFT);
progressLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
JLabel difficultyLabel = new JLabel("", JLabel.RIGHT);
difficultyLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
topPanel.add(progressLabel, BorderLayout.WEST);
topPanel.add(difficultyLabel, BorderLayout.EAST);
// 题目面板
JPanel questionPanel = new JPanel(new BorderLayout());
questionPanel.setBackground(Color.WHITE);
questionPanel.setBorder(BorderFactory.createEmptyBorder(20, 40, 20, 40));
questionLabel = new JLabel("", JLabel.CENTER);
questionLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18));
questionLabel.setBorder(BorderFactory.createEmptyBorder(20, 0, 30, 0));
// 选项面板
JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 0, 15));
optionsPanel.setBackground(Color.WHITE);
answerGroup = new ButtonGroup();
for (int i = 0; i < 4; i++) {
answerButtons[i] = new JRadioButton();
answerButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 16));
answerButtons[i].setBackground(Color.WHITE);
answerGroup.add(answerButtons[i]);
optionsPanel.add(answerButtons[i]);
}
questionPanel.add(questionLabel, BorderLayout.NORTH);
questionPanel.add(optionsPanel, BorderLayout.CENTER);
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout());
buttonPanel.setBackground(Color.WHITE);
JButton submitButton = createStyledButton("提交答案", new Color(70, 130, 180));
submitButton.addActionListener(this::handleAnswerSubmit);
buttonPanel.add(submitButton);
panel.add(topPanel, BorderLayout.NORTH);
panel.add(questionPanel, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
mainPanel.add(panel, "QUESTION");
}
/**
*
*/
private void createScorePanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBackground(new Color(240, 248, 255));
// 标题
JLabel titleLabel = new JLabel("答题结果", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
titleLabel.setBorder(BorderFactory.createEmptyBorder(50, 0, 30, 0));
// 分数显示
scoreLabel = new JLabel("", JLabel.CENTER);
scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 36));
scoreLabel.setForeground(new Color(70, 130, 180));
scoreLabel.setBorder(BorderFactory.createEmptyBorder(30, 0, 50, 0));
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout());
buttonPanel.setBackground(new Color(240, 248, 255));
JButton continueButton = createStyledButton("继续做题", new Color(60, 179, 113));
JButton exitButton = createStyledButton("退出", new Color(220, 20, 60));
continueButton.addActionListener(e -> cardLayout.show(mainPanel, "DIFFICULTY_SELECTION"));
exitButton.addActionListener(e -> {
currentUser = null;
cardLayout.show(mainPanel, "WELCOME");
});
buttonPanel.add(continueButton);
buttonPanel.add(exitButton);
panel.add(titleLabel, BorderLayout.NORTH);
panel.add(scoreLabel, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
mainPanel.add(panel, "SCORE");
}
/**
*
*/
private void createPasswordChangePanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBackground(Color.WHITE);
// 标题
JLabel titleLabel = new JLabel("修改密码", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
titleLabel.setBorder(BorderFactory.createEmptyBorder(30, 0, 30, 0));
// 表单面板
JPanel formPanel = new JPanel(new GridBagLayout());
formPanel.setBackground(Color.WHITE);
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
// 原密码
gbc.gridx = 0; gbc.gridy = 0;
formPanel.add(new JLabel("原密码:"), gbc);
gbc.gridx = 1;
oldPasswordField = new JPasswordField(20);
formPanel.add(oldPasswordField, gbc);
// 新密码
gbc.gridx = 0; gbc.gridy = 1;
formPanel.add(new JLabel("新密码:"), gbc);
gbc.gridx = 1;
JPasswordField newPasswordField = new JPasswordField(20);
formPanel.add(newPasswordField, gbc);
// 确认新密码
gbc.gridx = 0; gbc.gridy = 2;
formPanel.add(new JLabel("确认新密码:"), gbc);
gbc.gridx = 1;
JPasswordField confirmNewPasswordField = new JPasswordField(20);
formPanel.add(confirmNewPasswordField, gbc);
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout());
buttonPanel.setBackground(Color.WHITE);
JButton changeButton = createStyledButton("修改密码", new Color(60, 179, 113));
JButton backButton = createStyledButton("返回", new Color(128, 128, 128));
changeButton.addActionListener(e -> handlePasswordChange(
new String(oldPasswordField.getPassword()),
new String(newPasswordField.getPassword()),
new String(confirmNewPasswordField.getPassword())
));
backButton.addActionListener(e -> cardLayout.show(mainPanel, "DIFFICULTY_SELECTION"));
buttonPanel.add(changeButton);
buttonPanel.add(backButton);
panel.add(titleLabel, BorderLayout.NORTH);
panel.add(formPanel, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
mainPanel.add(panel, "PASSWORD_CHANGE");
}
/**
*
*/
private JButton createStyledButton(String text, Color backgroundColor) {
return MathLearningAppMethods.createStyledButton(text, backgroundColor);
}
/**
*
*/
private JButton createLargeDifficultyButton(String text, Color backgroundColor) {
return MathLearningAppMethods.createLargeDifficultyButton(text, backgroundColor);
}
/**
*
*/
private void sendVerificationCode(ActionEvent e) {
MathLearningAppMethods.sendVerificationCode(e, emailField, registeredUsers);
}
/**
*
*/
private void handleRegister(ActionEvent e) {
if (MathLearningAppMethods.handleRegister(emailField, verificationCodeField,
registeredUsers, cardLayout, mainPanel)) {
// 清空输入框
emailField.setText("");
verificationCodeField.setText("");
}
}
/**
*
*/
private void handlePasswordSetup(ActionEvent e) {
if (MathLearningAppMethods.handlePasswordSetup(passwordField, confirmPasswordField,
registeredUsers, cardLayout, mainPanel)) {
// 设置当前用户
for (RegisteredUser user : registeredUsers.values()) {
if (user.isVerified() && user.getPassword() != null) {
currentUser = user;
break;
}
}
// 清空密码框
passwordField.setText("");
confirmPasswordField.setText("");
}
}
/**
*
*/
private void handleLogin(String email, String password) {
RegisteredUser user = MathLearningAppMethods.handleLogin(email, password,
registeredUsers, cardLayout, mainPanel);
if (user != null) {
currentUser = user;
}
}
/**
*
*/
private void handlePasswordChange(String oldPassword, String newPassword, String confirmNewPassword) {
if (MathLearningAppMethods.handlePasswordChange(oldPassword, newPassword, confirmNewPassword, currentUser)) {
// 清空密码框
oldPasswordField.setText("");
cardLayout.show(mainPanel, "DIFFICULTY_SELECTION");
}
}
/**
*
*/
private void selectDifficulty(String difficulty) {
currentDifficulty = difficulty;
String input = JOptionPane.showInputDialog(this,
"请输入需要生成的题目数量建议10-30题",
"题目数量",
JOptionPane.QUESTION_MESSAGE);
if (input != null && !input.trim().isEmpty()) {
try {
int count = Integer.parseInt(input.trim());
if (count > 0 && count <= 50) {
generateQuestions(difficulty, count);
} else {
JOptionPane.showMessageDialog(this, "请输入1-50之间的数字", "错误", JOptionPane.ERROR_MESSAGE);
}
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(this, "请输入有效的数字!", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
/**
*
*/
private void generateQuestions(String difficulty, int count) {
currentQuestions = MathLearningAppMethods.generateQuestions(difficulty, count, questionGenerator);
currentQuestionIndex = 0;
correctAnswers = 0;
if (!currentQuestions.isEmpty()) {
showCurrentQuestion();
cardLayout.show(mainPanel, "QUESTION");
} else {
JOptionPane.showMessageDialog(this, "生成题目失败!", "错误", JOptionPane.ERROR_MESSAGE);
}
}
/**
*
*/
private void showCurrentQuestion() {
if (currentQuestionIndex < currentQuestions.size()) {
MultipleChoiceQuestion question = currentQuestions.get(currentQuestionIndex);
// 更新进度信息
JPanel topPanel = (JPanel) ((JPanel) mainPanel.getComponent(5)).getComponent(0);
JLabel progressLabel = (JLabel) topPanel.getComponent(0);
JLabel difficultyLabel = (JLabel) topPanel.getComponent(1);
progressLabel.setText("第 " + (currentQuestionIndex + 1) + " 题 / 共 " + currentQuestions.size() + " 题");
difficultyLabel.setText(currentDifficulty + "难度");
// 更新题目内容
questionLabel.setText("<html><center>" + question.getQuestion() + "</center></html>");
// 更新选项 - 关键修改:清除之前的选择
answerGroup.clearSelection(); // 清除单选按钮组的选择
String[] options = question.getOptions();
for (int i = 0; i < 4; i++) {
answerButtons[i].setText((char)('A' + i) + ". " + options[i]);
answerButtons[i].setSelected(false); // 确保每个按钮都是未选中状态
}
// 重置按钮状态
for (JRadioButton button : answerButtons) {
button.setSelected(false);
}
}
}
/**
*
*/
private void handleAnswerSubmit(ActionEvent e) {
// 检查是否选择了答案
int selectedAnswer = -1;
for (int i = 0; i < 4; i++) {
if (answerButtons[i].isSelected()) {
selectedAnswer = i;
break;
}
}
if (selectedAnswer == -1) {
JOptionPane.showMessageDialog(this, "请选择一个答案!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
// 检查答案是否正确
MultipleChoiceQuestion currentQuestion = currentQuestions.get(currentQuestionIndex);
if (currentQuestion.isCorrect(selectedAnswer)) {
correctAnswers++;
}
// 移动到下一题或显示结果
currentQuestionIndex++;
if (currentQuestionIndex < currentQuestions.size()) {
showCurrentQuestion();
} else {
showScore();
}
}
/**
*
*/
private void showScore() {
int totalQuestions = currentQuestions.size();
double percentage = (double) correctAnswers / totalQuestions * 100;
String scoreText = String.format("<html><center>答对 %d 题,共 %d 题<br/>正确率:%.1f%%</center></html>",
correctAnswers, totalQuestions, percentage);
scoreLabel.setText(scoreText);
cardLayout.show(mainPanel, "SCORE");
}
/**
*
*/
private void loadUserData() {
MathLearningAppMethods.loadUserData(registeredUsers);
}
/**
*
*/
private void saveUserData() {
MathLearningAppMethods.saveUserData(registeredUsers);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// 直接启动程序,使用默认外观
new MathLearningApp().setVisible(true);
});
}
}

@ -0,0 +1,464 @@
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.*;
import java.util.List;
import java.util.regex.Pattern;
import java.io.*;
import java.nio.file.*;
/**
* MathLearningApp
*
*/
public class MathLearningAppMethods {
/**
*
*/
public static JButton createStyledButton(String text, Color backgroundColor) {
JButton button = new JButton(text) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建渐变效果
GradientPaint gradient = new GradientPaint(0, 0, backgroundColor.brighter(),
0, getHeight(), backgroundColor.darker());
g2.setPaint(gradient);
g2.fillRoundRect(0, 0, getWidth(), getHeight(), 15, 15);
// 添加内阴影效果
g2.setColor(new Color(0, 0, 0, 30));
g2.drawRoundRect(1, 1, getWidth()-3, getHeight()-3, 13, 13);
g2.dispose();
super.paintComponent(g);
}
};
button.setFont(new Font("微软雅黑", Font.BOLD, 14));
button.setForeground(Color.WHITE);
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setPreferredSize(new Dimension(130, 40));
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
// 添加鼠标悬停效果
button.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseEntered(java.awt.event.MouseEvent evt) {
button.setBackground(backgroundColor.brighter());
button.repaint();
}
@Override
public void mouseExited(java.awt.event.MouseEvent evt) {
button.setBackground(backgroundColor);
button.repaint();
}
});
return button;
}
/**
*
*/
public static JButton createLargeDifficultyButton(String text, Color backgroundColor) {
JButton button = new JButton(text) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建渐变效果
GradientPaint gradient = new GradientPaint(0, 0, backgroundColor.brighter(),
0, getHeight(), backgroundColor);
g2.setPaint(gradient);
g2.fillRoundRect(0, 0, getWidth(), getHeight(), 20, 20);
// 添加外阴影效果
g2.setColor(new Color(0, 0, 0, 20));
g2.fillRoundRect(3, 3, getWidth(), getHeight(), 20, 20);
// 重新绘制按钮
g2.setPaint(gradient);
g2.fillRoundRect(0, 0, getWidth()-3, getHeight()-3, 20, 20);
// 添加边框
g2.setColor(backgroundColor.darker());
g2.setStroke(new BasicStroke(2));
g2.drawRoundRect(1, 1, getWidth()-5, getHeight()-5, 18, 18);
g2.dispose();
super.paintComponent(g);
}
};
button.setFont(new Font("微软雅黑", Font.BOLD, 22));
button.setForeground(new Color(40, 40, 40));
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setPreferredSize(new Dimension(320, 70));
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
// 添加鼠标悬停效果
button.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseEntered(java.awt.event.MouseEvent evt) {
button.setForeground(new Color(20, 20, 20));
button.repaint();
}
@Override
public void mouseExited(java.awt.event.MouseEvent evt) {
button.setForeground(new Color(40, 40, 40));
button.repaint();
}
});
return button;
}
/**
*
*/
public static void sendVerificationCode(ActionEvent e, JTextField emailField, Map<String, RegisteredUser> registeredUsers) {
String email = emailField.getText().trim();
if (!isValidEmail(email)) {
JOptionPane.showMessageDialog(null, "请输入有效的邮箱地址!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
// 检查邮箱是否已完成注册(验证码验证成功且设置了密码)
RegisteredUser existingUser = registeredUsers.get(email);
if (existingUser != null && existingUser.isVerified() && existingUser.getPassword() != null) {
JOptionPane.showMessageDialog(null, "该邮箱已注册!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
// 生成6位验证码
String verificationCode = generateVerificationCode();
// 创建或更新用户对象(仅用于临时存储验证码)
RegisteredUser user = new RegisteredUser(email, verificationCode);
registeredUsers.put(email, user);
// 显示发送中的提示
JOptionPane.showMessageDialog(null,
"正在通过QQ邮箱发送验证码到您的邮箱请稍候...",
"发送中",
JOptionPane.INFORMATION_MESSAGE);
// 在后台线程发送邮件避免阻塞UI
new Thread(() -> {
boolean success = QQEmailService.sendVerificationCode(email, verificationCode);
// 在EDT线程中显示结果
javax.swing.SwingUtilities.invokeLater(() -> {
if (success) {
JOptionPane.showMessageDialog(null,
"✅ 验证码已通过QQ邮箱发送\n" +
"📧 请查收邮件并输入6位验证码。\n" +
"📱 如果没收到,请检查垃圾邮件文件夹。",
"发送成功",
JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(null,
"❌ 验证码发送失败!\n" +
"请检查:\n" +
"• 接收邮箱地址是否正确\n" +
"• QQ邮箱配置是否正确\n" +
"• 网络连接是否正常",
"发送失败",
JOptionPane.ERROR_MESSAGE);
// 发送失败时移除用户
registeredUsers.remove(email);
}
});
}).start();
}
/**
*
*/
public static boolean handleRegister(JTextField emailField, JTextField verificationCodeField,
Map<String, RegisteredUser> registeredUsers, CardLayout cardLayout, JPanel mainPanel) {
String email = emailField.getText().trim();
String code = verificationCodeField.getText().trim();
if (email.isEmpty() || code.isEmpty()) {
JOptionPane.showMessageDialog(null, "请填写完整信息!", "错误", JOptionPane.ERROR_MESSAGE);
return false;
}
RegisteredUser user = registeredUsers.get(email);
if (user == null) {
JOptionPane.showMessageDialog(null, "请先发送验证码!", "错误", JOptionPane.ERROR_MESSAGE);
return false;
}
if (!user.verifyCode(code)) {
JOptionPane.showMessageDialog(null, "验证码错误!", "错误", JOptionPane.ERROR_MESSAGE);
return false;
}
user.setVerified(true);
JOptionPane.showMessageDialog(null, "注册成功!请设置密码。", "成功", JOptionPane.INFORMATION_MESSAGE);
cardLayout.show(mainPanel, "PASSWORD_SETUP");
return true;
}
/**
*
*/
public static boolean handlePasswordSetup(JPasswordField passwordField, JPasswordField confirmPasswordField,
Map<String, RegisteredUser> registeredUsers, CardLayout cardLayout, JPanel mainPanel) {
String password = new String(passwordField.getPassword());
String confirmPassword = new String(confirmPasswordField.getPassword());
if (!password.equals(confirmPassword)) {
JOptionPane.showMessageDialog(null, "两次输入的密码不一致!", "错误", JOptionPane.ERROR_MESSAGE);
return false;
}
if (!isValidPassword(password)) {
JOptionPane.showMessageDialog(null,
"密码不符合要求!\n要求6-10位必须包含大小写字母和数字",
"错误", JOptionPane.ERROR_MESSAGE);
return false;
}
// 找到当前注册的用户
RegisteredUser currentUser = null;
for (RegisteredUser user : registeredUsers.values()) {
if (user.isVerified() && user.getPassword() == null) {
currentUser = user;
break;
}
}
if (currentUser != null) {
currentUser.setPassword(password);
saveUserData(registeredUsers);
JOptionPane.showMessageDialog(null, "密码设置成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
cardLayout.show(mainPanel, "DIFFICULTY_SELECTION");
return true;
}
return false;
}
/**
*
*/
public static RegisteredUser handleLogin(String email, String password,
Map<String, RegisteredUser> registeredUsers,
CardLayout cardLayout, JPanel mainPanel) {
if (email.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(null, "请填写完整信息!", "错误", JOptionPane.ERROR_MESSAGE);
return null;
}
RegisteredUser user = registeredUsers.get(email);
if (user == null || !user.isVerified() || !user.verifyPassword(password)) {
JOptionPane.showMessageDialog(null, "用户名或密码错误!", "错误", JOptionPane.ERROR_MESSAGE);
return null;
}
JOptionPane.showMessageDialog(null, "登录成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
cardLayout.show(mainPanel, "DIFFICULTY_SELECTION");
return user;
}
/**
*
*/
public static boolean handlePasswordChange(String oldPassword, String newPassword, String confirmNewPassword,
RegisteredUser currentUser) {
if (!currentUser.verifyPassword(oldPassword)) {
JOptionPane.showMessageDialog(null, "原密码错误!", "错误", JOptionPane.ERROR_MESSAGE);
return false;
}
if (!newPassword.equals(confirmNewPassword)) {
JOptionPane.showMessageDialog(null, "两次输入的新密码不一致!", "错误", JOptionPane.ERROR_MESSAGE);
return false;
}
if (!isValidPassword(newPassword)) {
JOptionPane.showMessageDialog(null,
"新密码不符合要求!\n要求6-10位必须包含大小写字母和数字",
"错误", JOptionPane.ERROR_MESSAGE);
return false;
}
currentUser.setPassword(newPassword);
JOptionPane.showMessageDialog(null, "密码修改成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
return true;
}
/**
*
*/
public static List<MultipleChoiceQuestion> generateQuestions(String difficulty, int count, MathQuestionGenerator generator) {
List<MultipleChoiceQuestion> questions = new ArrayList<>();
Random random = new Random();
int attempts = 0;
int maxAttempts = count * 5; // 防止无限循环
while (questions.size() < count && attempts < maxAttempts) {
// 生成数学表达式
MathQuestion mathQuestion = generateSingleQuestion(difficulty, generator);
String expression = mathQuestion.getExpression();
// 验证表达式语法
if (!ExpressionEvaluator.isValidMathExpression(expression)) {
attempts++;
continue;
}
// 计算正确答案
double correctAnswer = evaluateExpression(expression);
// 检查答案是否有效
if (Double.isNaN(correctAnswer) || Double.isInfinite(correctAnswer)) {
attempts++;
continue;
}
// 生成选项
String[] options = new String[4];
int correctIndex = random.nextInt(4);
// 设置正确答案
options[correctIndex] = formatAnswer(correctAnswer);
// 生成3个错误答案
String[] wrongAnswers = ExpressionEvaluator.generateWrongAnswers(correctAnswer, 3);
int wrongIndex = 0;
for (int j = 0; j < 4; j++) {
if (j != correctIndex) {
options[j] = wrongAnswers[wrongIndex++];
}
}
MultipleChoiceQuestion question = new MultipleChoiceQuestion(
"计算:" + expression + " = ?", options, correctIndex);
questions.add(question);
attempts = 0; // 重置尝试计数
}
return questions;
}
/**
*
*/
private static MathQuestion generateSingleQuestion(String difficulty, MathQuestionGenerator generator) {
List<MathQuestion> questions = generator.generateQuestions(difficulty, 1);
return questions.get(0);
}
/**
*
*/
private static double evaluateExpression(String expression) {
return ExpressionEvaluator.evaluate(expression);
}
/**
*
*/
private static String formatAnswer(double answer) {
return ExpressionEvaluator.formatAnswer(answer);
}
/**
*
*/
public static boolean isValidEmail(String email) {
String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
Pattern pattern = Pattern.compile(emailRegex);
return pattern.matcher(email).matches();
}
/**
*
*/
public static boolean isValidPassword(String password) {
if (password.length() < 6 || password.length() > 10) {
return false;
}
boolean hasUpper = false, hasLower = false, hasDigit = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) hasUpper = true;
else if (Character.isLowerCase(c)) hasLower = true;
else if (Character.isDigit(c)) hasDigit = true;
}
return hasUpper && hasLower && hasDigit;
}
/**
*
*/
public static String generateVerificationCode() {
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(random.nextInt(10));
}
return code.toString();
}
/**
*
*/
public static void saveUserData(Map<String, RegisteredUser> users) {
try {
Path dataFile = Paths.get("user_data.txt");
try (BufferedWriter writer = Files.newBufferedWriter(dataFile)) {
for (RegisteredUser user : users.values()) {
if (user.isVerified() && user.getPassword() != null) {
writer.write(user.getEmail() + ":" + user.getPassword());
writer.newLine();
}
}
}
} catch (IOException e) {
System.err.println("保存用户数据失败: " + e.getMessage());
}
}
/**
*
*/
public static void loadUserData(Map<String, RegisteredUser> users) {
try {
Path dataFile = Paths.get("user_data.txt");
if (Files.exists(dataFile)) {
List<String> lines = Files.readAllLines(dataFile);
for (String line : lines) {
String[] parts = line.split(":");
if (parts.length == 2) {
RegisteredUser user = new RegisteredUser(parts[0], "");
user.setVerified(true);
user.setPassword(parts[1]);
users.put(parts[0], user);
}
}
}
} catch (IOException e) {
System.err.println("加载用户数据失败: " + e.getMessage());
}
}
}

@ -0,0 +1,218 @@
import java.io.*;
import java.net.*;
import java.util.Properties;
import java.nio.file.*;
/**
* QQSMTP
* QQ
*/
public class QQEmailService {
private static final String SMTP_HOST = "smtp.qq.com";
private static final String SMTP_PORT = "587";
private static final String CONFIG_FILE = "qq_email_config.properties";
// 默认配置 - 直接写入程序中
private static String senderEmail = "1626814667@qq.com";
private static String senderPassword = "cjgpophigkrodhib";
private static String senderName = "数学学习软件";
static {
// 不再需要加载配置文件,使用默认配置
System.out.println("使用默认QQ邮箱配置: " + senderEmail);
}
/**
* QQ
*/
private static void loadConfig() {
try {
Path configPath = Paths.get(CONFIG_FILE);
if (Files.exists(configPath)) {
Properties props = new Properties();
try (InputStream input = Files.newInputStream(configPath)) {
props.load(input);
senderEmail = props.getProperty("qq.email", "");
senderPassword = props.getProperty("qq.password", "");
senderName = props.getProperty("sender.name", "数学学习软件");
}
}
} catch (IOException e) {
System.err.println("加载QQ邮箱配置失败: " + e.getMessage());
}
}
/**
* QQ
*/
public static void saveConfig(String email, String password) {
try {
Properties props = new Properties();
props.setProperty("qq.email", email);
props.setProperty("qq.password", password);
props.setProperty("sender.name", senderName);
try (OutputStream output = Files.newOutputStream(Paths.get(CONFIG_FILE))) {
props.store(output, "QQ邮箱SMTP配置 - QQ Email SMTP Configuration");
}
// 更新内存中的配置
senderEmail = email;
senderPassword = password;
System.out.println("QQ邮箱配置已保存");
} catch (IOException e) {
System.err.println("保存QQ邮箱配置失败: " + e.getMessage());
}
}
/**
* QQ
*/
public static boolean isConfigured() {
// 始终返回true因为已经有默认配置
return true;
}
/**
* QQ
*/
public static String getSenderEmail() {
return senderEmail;
}
/**
*
*/
public static boolean sendVerificationCode(String toEmail, String verificationCode) {
if (!isConfigured()) {
System.err.println("QQ邮箱未配置");
return false;
}
try {
System.out.println("正在发送邮件...");
System.out.println("发送方: " + senderEmail);
System.out.println("接收方: " + toEmail);
System.out.println("验证码: " + verificationCode);
// 使用真实的SMTP发送邮件
String subject = "【数学学习软件】验证码";
String content = RealEmailSender.createVerificationEmailContent(verificationCode);
boolean success = RealEmailSender.sendEmail(senderEmail, senderPassword, toEmail, subject, content);
if (success) {
System.out.println("邮件发送成功!");
} else {
System.out.println("邮件发送失败!");
}
return success;
} catch (Exception e) {
System.err.println("发送邮件失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* SMTP
*/
private static boolean sendEmailViaSMTP(String toEmail, String verificationCode) {
try {
// 创建Socket连接到QQ SMTP服务器
Socket socket = new Socket(SMTP_HOST, Integer.parseInt(SMTP_PORT));
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// 读取服务器欢迎信息
String response = reader.readLine();
System.out.println("服务器响应: " + response);
// SMTP握手过程
writer.println("EHLO localhost");
response = reader.readLine();
System.out.println("EHLO响应: " + response);
// 启动TLS加密
writer.println("STARTTLS");
response = reader.readLine();
System.out.println("STARTTLS响应: " + response);
// 这里需要升级到SSL连接但为了简化我们使用HTTP API方式
socket.close();
// 改用HTTP API方式发送模拟
return sendViaHTTPAPI(toEmail, verificationCode);
} catch (Exception e) {
System.err.println("SMTP连接失败: " + e.getMessage());
return sendViaHTTPAPI(toEmail, verificationCode);
}
}
/**
* HTTP API
*/
private static boolean sendViaHTTPAPI(String toEmail, String verificationCode) {
try {
System.out.println("=== QQ邮箱发送邮件 ===");
System.out.println("发送方: " + senderEmail);
System.out.println("接收方: " + toEmail);
System.out.println("验证码: " + verificationCode);
System.out.println("邮件内容: " + createEmailContent(verificationCode));
System.out.println("===================");
// 模拟发送延迟
Thread.sleep(1000);
// 在实际项目中这里应该调用真实的SMTP库或HTTP API
// 例如使用JavaMail库或第三方邮件服务API
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
*
*/
private static String createEmailContent(String verificationCode) {
return "【数学学习软件】验证码\n\n" +
"您的注册验证码是:" + verificationCode + "\n\n" +
"验证码有效期为10分钟请及时使用。\n" +
"如果您没有注册我们的软件,请忽略此邮件。\n\n" +
"此邮件由系统自动发送,请勿回复。";
}
/**
* QQ
*/
public static boolean testConnection() {
if (!isConfigured()) {
return false;
}
try {
// 测试连接到QQ SMTP服务器
Socket socket = new Socket();
socket.connect(new InetSocketAddress(SMTP_HOST, Integer.parseInt(SMTP_PORT)), 5000);
socket.close();
System.out.println("QQ SMTP服务器连接测试成功");
return true;
} catch (Exception e) {
System.err.println("QQ SMTP服务器连接测试失败: " + e.getMessage());
return false;
}
}
}

@ -0,0 +1,176 @@
import java.io.*;
import java.net.*;
import java.util.Base64;
import javax.net.ssl.*;
/**
*
* 使JavaSMTP
*/
public class RealEmailSender {
private static final String SMTP_HOST = "smtp.qq.com";
private static final int SMTP_PORT = 587;
/**
*
*/
public static boolean sendEmail(String fromEmail, String fromPassword, String toEmail, String subject, String content) {
try {
// 1. 连接到SMTP服务器
Socket socket = new Socket(SMTP_HOST, SMTP_PORT);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// 2. 读取服务器欢迎信息
String response = reader.readLine();
System.out.println("服务器: " + response);
if (!response.startsWith("220")) {
throw new Exception("SMTP服务器连接失败");
}
// 3. 发送EHLO命令
writer.println("EHLO localhost");
response = readMultiLineResponse(reader);
System.out.println("EHLO响应: " + response);
// 4. 启动TLS加密
writer.println("STARTTLS");
response = reader.readLine();
System.out.println("STARTTLS响应: " + response);
if (!response.startsWith("220")) {
throw new Exception("启动TLS失败");
}
// 5. 升级到SSL连接
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, SMTP_HOST, SMTP_PORT, true);
sslSocket.startHandshake();
// 6. 重新创建读写器
reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
writer = new PrintWriter(sslSocket.getOutputStream(), true);
// 7. 重新发送EHLO
writer.println("EHLO localhost");
response = readMultiLineResponse(reader);
System.out.println("SSL EHLO响应: " + response);
// 8. 认证
writer.println("AUTH LOGIN");
response = reader.readLine();
System.out.println("AUTH响应: " + response);
if (!response.startsWith("334")) {
throw new Exception("认证失败");
}
// 9. 发送用户名Base64编码
String encodedUsername = Base64.getEncoder().encodeToString(fromEmail.getBytes());
writer.println(encodedUsername);
response = reader.readLine();
System.out.println("用户名响应: " + response);
if (!response.startsWith("334")) {
throw new Exception("用户名认证失败");
}
// 10. 发送密码Base64编码
String encodedPassword = Base64.getEncoder().encodeToString(fromPassword.getBytes());
writer.println(encodedPassword);
response = reader.readLine();
System.out.println("密码响应: " + response);
if (!response.startsWith("235")) {
throw new Exception("密码认证失败: " + response);
}
// 11. 发送邮件
// MAIL FROM
writer.println("MAIL FROM:<" + fromEmail + ">");
response = reader.readLine();
System.out.println("MAIL FROM响应: " + response);
if (!response.startsWith("250")) {
throw new Exception("MAIL FROM失败");
}
// RCPT TO
writer.println("RCPT TO:<" + toEmail + ">");
response = reader.readLine();
System.out.println("RCPT TO响应: " + response);
if (!response.startsWith("250")) {
throw new Exception("RCPT TO失败");
}
// DATA
writer.println("DATA");
response = reader.readLine();
System.out.println("DATA响应: " + response);
if (!response.startsWith("354")) {
throw new Exception("DATA失败");
}
// 邮件头和内容
writer.println("From: " + fromEmail);
writer.println("To: " + toEmail);
writer.println("Subject: =?UTF-8?B?" + Base64.getEncoder().encodeToString(subject.getBytes("UTF-8")) + "?=");
writer.println("MIME-Version: 1.0");
writer.println("Content-Type: text/plain; charset=UTF-8");
writer.println("Content-Transfer-Encoding: base64");
writer.println("X-Mailer: MathLearningSystem");
writer.println("X-Priority: 1");
writer.println();
writer.println(Base64.getEncoder().encodeToString(content.getBytes("UTF-8")));
writer.println(".");
response = reader.readLine();
System.out.println("邮件发送响应: " + response);
if (!response.startsWith("250")) {
throw new Exception("邮件发送失败");
}
// 12. 退出
writer.println("QUIT");
response = reader.readLine();
System.out.println("QUIT响应: " + response);
// 13. 关闭连接
sslSocket.close();
System.out.println("邮件发送成功!");
return true;
} catch (Exception e) {
System.err.println("邮件发送失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
*
*/
private static String readMultiLineResponse(BufferedReader reader) throws IOException {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line).append("\n");
// 如果行不是以'-'结尾,说明是最后一行
if (line.length() >= 4 && line.charAt(3) != '-') {
break;
}
}
return response.toString();
}
/**
*
*/
public static String createVerificationEmailContent(String verificationCode) {
return "您好!\n\n" +
"您正在注册数学学习软件,您的验证码是:\n\n" +
"验证码:" + verificationCode + "\n\n" +
"验证码有效期为10分钟请及时使用。\n" +
"如果您没有注册我们的软件,请忽略此邮件。\n\n" +
"祝您学习愉快!\n" +
"数学学习软件团队\n\n" +
"此邮件由系统自动发送,请勿回复。";
}
}

@ -0,0 +1,56 @@
/**
*
*
*/
public class RegisteredUser {
private String email;
private String password;
private String verificationCode;
private boolean isVerified;
public RegisteredUser(String email, String verificationCode) {
this.email = email;
this.verificationCode = verificationCode;
this.isVerified = false;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getVerificationCode() {
return verificationCode;
}
public boolean isVerified() {
return isVerified;
}
public void setVerified(boolean verified) {
isVerified = verified;
}
public boolean verifyCode(String code) {
return verificationCode.equals(code);
}
public boolean verifyPassword(String password) {
return this.password != null && this.password.equals(password);
}
@Override
public String toString() {
return "RegisteredUser{" +
"email='" + email + '\'' +
", isVerified=" + isVerified +
'}';
}
}
Loading…
Cancel
Save