1 #5

Merged
hnu202326010331 merged 9 commits from develop into main 4 months ago

@ -0,0 +1,336 @@
# 小初高数学学习软件 - 完整系统说明文档
## 项目简介
本项目是一个面向小学、初中和高中学生的数学学习软件采用Java Swing构建图形用户界面提供用户注册、登录、数学题目生成、答题练习和成绩统计等功能。系统采用模块化设计具有良好的可扩展性和维护性。
## 系统架构
### MVC设计模式
本项目采用经典的MVCModel-View-Controller架构模式
- **Model模型层**:处理核心业务逻辑和数据存储
- **View视图层**:负责用户界面展示
- **Controller控制器层**:协调模型和视图的交互
## 模块详细说明
### 1. 用户界面模块 (View)
#### **MathLearningApp.java** - 主程序入口
```java
public class MathLearningApp extends JFrame {
// 主界面管理,卡片布局切换
}
```
**功能特性:**
- 欢迎界面(渐变背景、美观按钮)
- 用户注册界面(邮箱验证、表单验证)
- 登录界面(账户验证)
- 难度选择界面(小学/初中/高中)
- 答题界面(题目展示、选项选择)
- 成绩展示界面(正确率统计)
- 密码修改界面
#### **MathLearningAppMethods.java** - 界面方法扩展
```java
public class MathLearningAppMethods {
// 所有界面事件处理和工具方法
}
```
**核心功能:**
- 样式化按钮创建
- 用户注册流程处理
- 登录验证逻辑
- 密码强度验证
- 题目生成调度
- 用户数据持久化
### 2. 用户管理模块 (Model)
#### **RegisteredUser.java** - 注册用户类
```java
public class RegisteredUser {
private String email;
private String password;
private String verificationCode;
private boolean isVerified;
}
```
**功能:**
- 用户信息存储
- 验证码管理
- 密码验证
- 用户状态跟踪
#### **User.java** - 基础用户类
```java
public class User {
private String username;
private String password;
private String accountType;
}
```
**功能:**
- 用户基础信息
- 账户类型管理(小学/初中/高中)
### 3. 核心计算引擎模块 (Model)
#### **ExpressionEvaluator.java** - 数学表达式计算器
```java
public class ExpressionEvaluator {
public static double evaluate(String expression) {
// 高级数学表达式计算
}
}
```
**计算能力:**
- 四则运算(支持优先级)
- 括号表达式解析
- 平方(²)和开根号(√)运算
- 三角函数计算sin/cos/tan
- 错误答案智能生成
- 表达式语法验证
#### **MathQuestionGenerator.java** - 题目生成器
```java
public class MathQuestionGenerator {
public List<MathQuestion> generateQuestions(String accountType, int count) {
// 分级题目生成
}
}
```
**题目生成策略:**
- **小学题目**基础四则运算1-5个操作符支持括号
- **初中题目**包含平方和开根号1-5个操作数
- **高中题目**包含三角函数1-5个操作数
#### **MathQuestion.java** - 数学题目类
```java
public class MathQuestion {
private String expression;
private String answer;
}
```
#### **MultipleChoiceQuestion.java** - 选择题类
```java
public class MultipleChoiceQuestion {
private String question;
private String[] options;
private int correctAnswer;
}
```
### 4. 题目管理模块 (Model)
#### **QuestionManager.java** - 题目管理器
```java
public class QuestionManager {
// 题目重复检查和文件保存
}
```
**管理功能:**
- 题目去重检查
- 文件系统管理
- 历史题目加载
- 智能题目生成
### 5. 邮件服务模块 (Controller)
#### **QQEmailService.java** - QQ邮箱服务
```java
public class QQEmailService {
public static boolean sendVerificationCode(String toEmail, String verificationCode) {
// 验证码邮件发送
}
}
```
**特性:**
- SMTP协议实现
- TLS加密传输
- Base64编码认证
- 邮件模板管理
#### **RealEmailSender.java** - 真实邮件发送器
```java
public class RealEmailSender {
// 原生Java SMTP实现
}
```
## 系统功能流程
### 1. 用户注册流程
```
用户输入邮箱 → 发送验证码 → 邮箱接收验证码 →
输入验证码验证 → 设置密码 → 注册完成
```
### 2. 用户登录流程
```
输入邮箱密码 → 身份验证 → 进入主界面 → 选择学习难度
```
### 3. 答题练习流程
```
选择难度 → 输入题目数量 → 生成题目 → 逐题作答 →
显示成绩 → 继续练习或退出
```
### 4. 密码管理流程
```
验证原密码 → 输入新密码 → 确认新密码 → 密码更新
```
## 技术特色
### 1. 界面设计
- **现代化UI**:渐变背景、圆角按钮、阴影效果
- **响应式交互**:鼠标悬停效果、加载状态提示
- **用户体验**:清晰的导航流程、即时反馈
### 2. 数学计算
- **智能解析**:递归下降表达式解析器
- **全面支持**:涵盖小初高全部数学运算
- **错误处理**:完善的异常处理和边界检查
### 3. 题目生成
- **分级难度**:针对不同学段定制题目
- **避免重复**:智能去重算法
- **合理分布**:运算符和特殊符号的均衡分布
### 4. 数据管理
- **文件存储**:按学段分类存储题目
- **用户持久化**:用户数据本地保存
- **内存优化**:高效的数据结构设计
## 文件结构
```
MathLearningSystem/
├── 用户界面/
│ ├── MathLearningApp.java # 主程序界面
│ └── MathLearningAppMethods.java # 界面方法扩展
├── 用户管理/
│ ├── RegisteredUser.java # 注册用户类
│ └── User.java # 基础用户类
├── 核心计算/
│ ├── ExpressionEvaluator.java # 表达式计算器
│ ├── MathQuestionGenerator.java # 题目生成器
│ ├── MathQuestion.java # 数学题目类
│ └── MultipleChoiceQuestion.java # 选择题类
├── 题目管理/
│ └── QuestionManager.java # 题目管理器
├── 邮件服务/
│ ├── QQEmailService.java # QQ邮箱服务
│ └── RealEmailSender.java # 邮件发送器
└── 数据文件/
├── user_data.txt # 用户数据存储
├── 小学/ # 小学题目存储
├── 初中/ # 初中题目存储
└── 高中/ # 高中题目存储
```
## 运行环境要求
### 系统要求
- **操作系统**Windows 11
- **Java环境**JDK 17
- **内存**:最低 512MB推荐 1GB
- **磁盘空间**:至少 100MB 可用空间
### 依赖项
- **核心依赖**纯Java实现无外部依赖
- **网络要求**:邮件发送需要互联网连接
- **邮件服务**需要有效的QQ邮箱账户配置
## 配置说明
### QQ邮箱配置
系统使用默认的QQ邮箱配置如需修改可在 `QQEmailService.java` 中更新:
```java
private static String senderEmail = "your-email@qq.com";
private static String senderPassword = "your-authorization-code";
```
### 题目数量配置
在答题时可根据需要输入题目数量:
- **建议范围**10-30题
- **默认设置**:用户自定义
## 使用指南
### 首次使用
1. 运行 `MathLearningApp.java` 启动程序
2. 点击"用户注册"创建新账户
3. 通过邮箱验证完成注册
4. 设置符合要求的密码
5. 开始数学学习之旅
### 常规使用
1. 使用注册的邮箱和密码登录
2. 选择适合的学段难度
3. 输入想要练习的题目数量
4. 认真作答每一道选择题
5. 查看成绩并分析学习效果
### 功能特点
- **个性化学习**:根据学段提供针对性题目
- **即时反馈**:答题后立即显示正确与否
- **进度跟踪**:记录答题历史和成绩
- **安全可靠**:密码加密、邮箱验证保障账户安全
## 扩展开发
### 功能扩展建议
1. **题目类型扩展**:添加几何、代数等更多数学题型
2. **学习进度分析**:增加学习数据统计和分析功能
3. **错题本功能**:自动收集和复习错题
4. **多人竞赛模式**:添加在线答题竞赛功能
5. **移动端适配**开发Android和iOS版本
### 技术优化方向
1. **数据库集成**使用MySQL或SQLite替代文件存储
2. **性能优化**:题目生成算法和界面渲染优化
3. **安全增强**:密码加密存储和更安全的认证机制
4. **国际化**:支持多语言界面
## 故障排除
### 常见问题
**1. 邮件发送失败**
- 检查网络连接
- 验证QQ邮箱配置是否正确
- 确认授权码而非QQ密码
**2. 题目生成异常**
- 检查输入题目数量是否合理
- 确认Java环境版本兼容性
- 验证磁盘空间是否充足
**3. 界面显示问题**
- 确认系统支持Java Swing
- 检查屏幕分辨率设置
- 验证Java运行时环境完整性
### 技术支持
如遇技术问题,请检查:
1. Java环境变量配置
2. 系统防火墙设置
3. 磁盘读写权限
4. 网络连接状态
## 版本信息
- **当前版本**v1.0
- **开发语言**Java
- **界面框架**Swing
- **最后更新**2025年10月
---
本系统为小初高学生提供了一个完整的数学学习解决方案,结合了现代化的用户界面和强大的数学计算能力,旨在通过有趣的交互方式提升学生的数学学习兴趣和能力。

@ -0,0 +1,260 @@
# 数学学习软件 - 核心计算与题目生成模块
## 模块概述
本模块是数学学习软件的核心计算引擎,负责数学题目的生成、表达式计算、题目管理和数据存储等功能。该模块采用面向对象的设计理念,提供了完整的数学题目生成和计算解决方案。
## 功能架构
### 1. 核心计算引擎
#### 主要类:
#### **ExpressionEvaluator.java** - 数学表达式计算器
```java
public class ExpressionEvaluator {
public static double evaluate(String expression) {
// 计算数学表达式的值
}
public static String[] generateWrongAnswers(double correctAnswer, int count) {
// 生成错误答案选项
}
}
```
**功能说明:**
- 支持四则运算、括号优先级计算
- 处理平方(²)、开根号(√)运算
- 三角函数计算sin、cos、tan
- 表达式语法验证和错误处理
- 智能生成干扰错误答案
**技术特点:**
- 递归下降解析器实现表达式解析
- 支持运算符优先级处理
- 自动格式化答案显示(整数去小数位)
### 2. 题目生成系统
#### 主要类:
#### **MathQuestionGenerator.java** - 题目生成器
```java
public class MathQuestionGenerator {
public List<MathQuestion> generateQuestions(String accountType, int count) {
// 根据账户类型生成题目
}
}
```
**功能说明:**
- **小学题目**基础四则运算支持括号1-4个操作符
- **初中题目**包含平方和开根号运算1-5个操作数
- **高中题目**包含三角函数运算1-5个操作数
**生成算法:**
- 随机操作符和操作数生成
- 智能括号添加,避免语法错误
- 题目复杂度控制
- 特殊运算符号的合理分布
#### **MathQuestion.java** - 数学题目类
```java
public class MathQuestion {
private String expression;
private String answer;
// 题目表达式和答案存储
}
```
#### **MultipleChoiceQuestion.java** - 选择题类
```java
public class MultipleChoiceQuestion {
private String question;
private String[] options;
private int correctAnswer;
// 选择题管理和验证
}
```
### 3. 题目管理系统
#### 主要类:
#### **QuestionManager.java** - 题目管理器
```java
public class QuestionManager {
public List<MathQuestion> generateUniqueQuestions(String accountType, int count,
MathQuestionGenerator generator) {
// 生成不重复题目
}
public String saveQuestionsToFile(String accountType, List<MathQuestion> questions) {
// 保存题目到文件
}
}
```
**功能说明:**
- 题目重复性检查
- 文件系统管理(按学段分类存储)
- 历史题目加载和去重
- 智能题目生成(避免重复)
**文件管理:**
- 自动创建小学、初中、高中目录
- 时间戳文件名格式:`yyyy-MM-dd-HH-mm-ss.txt`
- 题目编号和格式化存储
### 4. 用户数据模型
#### 主要类:
#### **User.java** - 基础用户类
```java
public class User {
private String username;
private String password;
private String accountType; // 小学、初中、高中
// 用户信息和权限管理
}
```
## 技术实现
### 表达式计算算法
1. **预处理阶段**
- 移除空格,统一格式
- 替换特殊符号(²→平方计算,√→开根号)
- 三角函数角度转弧度计算
2. **解析阶段**
- 递归下降解析器处理运算符优先级
- 支持嵌套括号表达式
- 错误处理和边界条件检查
3. **计算阶段**
- 加减法(最低优先级)
- 乘除法(中等优先级)
- 因子解析(数字和括号表达式)
### 题目生成策略
#### 小学题目生成:
```java
private MathQuestion generateElementaryQuestion() {
// 生成1-4个操作符的基础运算
// 可能添加括号增强复杂度
}
```
#### 初中题目生成:
```java
private MathQuestion generateMiddleSchoolQuestion() {
// 至少包含一个平方或开根号运算
// 1-5个操作数的复杂表达式
}
```
#### 高中题目生成:
```java
private MathQuestion generateHighSchoolQuestion() {
// 至少包含一个三角函数运算
// 角度计算和复杂表达式组合
}
```
### 错误答案生成算法
```java
public static String[] generateWrongAnswers(double correctAnswer, int count) {
// 类型1加减随机数干扰
// 类型2乘以小数干扰
// 类型3除以数字干扰
// 类型4完全随机数值
}
```
## 数据设计
### 内存数据结构
- `Map<String, Set<String>> userQuestions` - 用户历史题目存储
- `List<MathQuestion>` - 题目列表管理
- `Set<String>` - 题目去重检查
### 文件存储结构
```
小学/
2025-10-05-10-30-25.txt
初中/
2025-10-02-11-15-40.txt
高中/
2025-10-01-14-20-35.txt
```
## 接口设计
### 对外提供的主要方法
1. **题目生成接口**
```java
List<MathQuestion> generateQuestions(String accountType, int count)
```
2. **表达式计算接口**
```java
double evaluate(String expression)
```
3. **选择题生成接口**
```java
MultipleChoiceQuestion createMultipleChoice(MathQuestion mathQuestion)
```
4. **文件管理接口**
```java
String saveQuestions(String accountType, List<MathQuestion> questions)
```
## 质量保证
### 输入验证
- 表达式语法检查
- 括号匹配验证
- 数字格式验证
- 运算符位置验证
### 错误处理
- 数学计算异常捕获
- 文件IO异常处理
- 内存越界保护
- 无效输入提示
### 性能优化
- 题目去重算法优化
- 表达式计算性能提升
- 文件读写批量处理
- 内存使用效率优化
## 扩展性设计
### 可扩展的题目类型
- 支持添加新的数学运算类型
- 可配置的题目难度参数
- 模块化的题目生成策略
### 接口标准化
- 统一的题目数据格式
- 标准化的计算接口
- 灵活的文件存储方案
该核心模块为整个数学学习软件提供了稳定可靠的数学计算和题目生成能力,采用模块化设计便于维护和扩展,为上层用户界面提供了完整的数学题目处理解决方案。

@ -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,324 @@
import java.util.*;
/**
*
*
*/
public class ExpressionEvaluator {
/**
*
*/
public static double evaluate(String expression) {
try {
// 预处理表达式:处理特殊符号
expression = preprocessExpression(expression);
// 使用栈和递归下降解析器计算
return parseExpression(expression);
} catch (Exception e) {
System.err.println("计算表达式失败: " + expression + " - " + e.getMessage());
e.printStackTrace();
return 0;
}
}
/**
*
*/
private static String preprocessExpression(String expr) {
// 移除所有空格
expr = expr.replaceAll("\\s+", "");
// 处理三角函数sin(30°) -> 计算实际值
expr = replaceTrigFunctions(expr);
// 处理平方符号5² -> (5*5)
expr = replaceSquares(expr);
// 处理开根号√25 -> 5
expr = replaceSqrts(expr);
return expr;
}
/**
*
*/
private static String replaceTrigFunctions(String expr) {
// 处理 sin(角度°)
expr = replaceFunction(expr, "sin");
// 处理 cos(角度°)
expr = replaceFunction(expr, "cos");
// 处理 tan(角度°)
expr = replaceFunction(expr, "tan");
return expr;
}
/**
*
*/
private static String replaceFunction(String expr, String funcName) {
String pattern = funcName + "\\((\\d+)°\\)";
java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern);
java.util.regex.Matcher m = p.matcher(expr);
StringBuffer sb = new StringBuffer();
while (m.find()) {
int degrees = Integer.parseInt(m.group(1));
double radians = Math.toRadians(degrees);
double result = 0;
switch (funcName) {
case "sin":
result = Math.sin(radians);
break;
case "cos":
result = Math.cos(radians);
break;
case "tan":
result = Math.tan(radians);
break;
}
// 替换为括号包裹的数值
m.appendReplacement(sb, "(" + result + ")");
}
m.appendTail(sb);
return sb.toString();
}
/**
*
*/
private static String replaceSquares(String expr) {
java.util.regex.Pattern p = java.util.regex.Pattern.compile("(\\d+)²");
java.util.regex.Matcher m = p.matcher(expr);
StringBuffer sb = new StringBuffer();
while (m.find()) {
int num = Integer.parseInt(m.group(1));
int result = num * num;
m.appendReplacement(sb, String.valueOf(result));
}
m.appendTail(sb);
return sb.toString();
}
/**
*
*/
private static String replaceSqrts(String expr) {
java.util.regex.Pattern p = java.util.regex.Pattern.compile("√(\\d+)");
java.util.regex.Matcher m = p.matcher(expr);
StringBuffer sb = new StringBuffer();
while (m.find()) {
int num = Integer.parseInt(m.group(1));
double result = Math.sqrt(num);
m.appendReplacement(sb, String.valueOf(result));
}
m.appendTail(sb);
return sb.toString();
}
/**
*
*/
private static double parseExpression(String expr) {
return parseAddSubtract(expr, new int[]{0});
}
/**
*
*/
private static double parseAddSubtract(String expr, int[] pos) {
double left = parseMultiplyDivide(expr, pos);
while (pos[0] < expr.length()) {
char op = expr.charAt(pos[0]);
if (op == '+' || op == '-') {
pos[0]++;
double right = parseMultiplyDivide(expr, pos);
if (op == '+') {
left += right;
} else {
left -= right;
}
} else {
break;
}
}
return left;
}
/**
*
*/
private static double parseMultiplyDivide(String expr, int[] pos) {
double left = parseFactor(expr, pos);
while (pos[0] < expr.length()) {
char op = expr.charAt(pos[0]);
if (op == '*' || op == '/') {
pos[0]++;
double right = parseFactor(expr, pos);
if (op == '*') {
left *= right;
} else {
if (right != 0) {
left /= right;
}
}
} else {
break;
}
}
return left;
}
/**
*
*/
private static double parseFactor(String expr, int[] pos) {
// 跳过空格
while (pos[0] < expr.length() && expr.charAt(pos[0]) == ' ') {
pos[0]++;
}
// 处理括号
if (pos[0] < expr.length() && expr.charAt(pos[0]) == '(') {
pos[0]++; // 跳过 '('
double result = parseAddSubtract(expr, pos);
pos[0]++; // 跳过 ')'
return result;
}
// 处理负号
boolean negative = false;
if (pos[0] < expr.length() && expr.charAt(pos[0]) == '-') {
negative = true;
pos[0]++;
}
// 解析数字
int start = pos[0];
while (pos[0] < expr.length() &&
(Character.isDigit(expr.charAt(pos[0])) || expr.charAt(pos[0]) == '.')) {
pos[0]++;
}
double value = Double.parseDouble(expr.substring(start, pos[0]));
return negative ? -value : value;
}
/**
*
*/
public static String[] generateWrongAnswers(double correctAnswer, int count) {
Set<String> wrongAnswers = new HashSet<>();
Random random = new Random();
while (wrongAnswers.size() < count) {
double wrongValue;
// 生成不同类型的错误答案
int type = random.nextInt(4);
switch (type) {
case 0: // 加减一个随机数
wrongValue = correctAnswer + (random.nextInt(20) - 10);
break;
case 1: // 乘以一个小数
wrongValue = correctAnswer * (0.5 + random.nextDouble());
break;
case 2: // 除以一个数
wrongValue = correctAnswer / (1.5 + random.nextDouble() * 2);
break;
default: // 完全随机
wrongValue = random.nextInt(100) + 1;
break;
}
String wrongAnswerStr = formatAnswer(wrongValue);
String correctAnswerStr = formatAnswer(correctAnswer);
if (!wrongAnswerStr.equals(correctAnswerStr)) {
wrongAnswers.add(wrongAnswerStr);
}
}
return wrongAnswers.toArray(new String[0]);
}
/**
*
*/
public static String formatAnswer(double answer) {
if (Math.abs(answer - Math.round(answer)) < 0.001) {
return String.valueOf(Math.round(answer));
} else {
return String.format("%.2f", answer);
}
}
/**
*
*/
public static boolean isValidMathExpression(String expression) {
// 基本语法检查
if (expression == null || expression.trim().isEmpty()) {
return false;
}
// 检查括号匹配
int balance = 0;
for (char c : expression.toCharArray()) {
if (c == '(') balance++;
if (c == ')') balance--;
if (balance < 0) return false;
}
if (balance != 0) return false;
// 检查运算符位置
String[] tokens = expression.split("\\s+");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i];
// 检查连续的运算符
if (isOperator(token) && i > 0 && isOperator(tokens[i-1])) {
return false;
}
// 检查数字后面直接跟左括号
if (i > 0 && isNumber(token) && i < tokens.length - 1 && "(".equals(tokens[i+1])) {
return false;
}
// 检查右括号后面直接跟数字
if (i > 0 && ")".equals(token) && i < tokens.length - 1 && isNumber(tokens[i+1])) {
return false;
}
}
return true;
}
private static boolean isOperator(String token) {
return "+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token);
}
private static boolean isNumber(String str) {
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

@ -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,39 @@
/**
*
*
*/
public class MathQuestion {
private String expression;
private String answer;
public MathQuestion(String expression, String answer) {
this.expression = expression;
this.answer = answer;
}
public String getExpression() {
return expression;
}
public String getAnswer() {
return answer;
}
@Override
public String toString() {
return expression;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MathQuestion that = (MathQuestion) obj;
return expression.equals(that.expression);
}
@Override
public int hashCode() {
return expression.hashCode();
}
}

@ -0,0 +1,313 @@
import java.util.*;
/**
*
*
*/
public class MathQuestionGenerator {
private Random random;
private String[] operators = {"+", "-", "*", "/"};
private String[] trigFunctions = {"sin", "cos", "tan"};
public MathQuestionGenerator() {
this.random = new Random();
}
/**
*
* @param accountType
* @param count
* @return
*/
public List<MathQuestion> generateQuestions(String accountType, int count) {
List<MathQuestion> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
MathQuestion question;
switch (accountType) {
case "小学":
question = generateElementaryQuestion();
break;
case "初中":
question = generateMiddleSchoolQuestion();
break;
case "高中":
question = generateHighSchoolQuestion();
break;
default:
question = generateElementaryQuestion();
}
questions.add(question);
}
return questions;
}
/**
* +-*/()
*/
private MathQuestion generateElementaryQuestion() {
int operatorCount = random.nextInt(4) + 1; // 1-4个操作符
List<Integer> numbers = new ArrayList<>();
List<String> ops = new ArrayList<>();
// 生成数字1-100
for (int i = 0; i <= operatorCount; i++) {
numbers.add(random.nextInt(100) + 1);
}
// 生成操作符
for (int i = 0; i < operatorCount; i++) {
ops.add(operators[random.nextInt(operators.length)]);
}
// 构建表达式
StringBuilder expression = new StringBuilder();
expression.append(numbers.get(0));
for (int i = 0; i < operatorCount; i++) {
expression.append(" ").append(ops.get(i)).append(" ").append(numbers.get(i + 1));
}
// 可能添加括号
if (operatorCount >= 2 && random.nextBoolean()) {
expression = addParentheses(expression.toString());
}
return new MathQuestion(expression.toString(), "计算结果");
}
/**
* 1-5
*/
private MathQuestion generateMiddleSchoolQuestion() {
StringBuilder expression = new StringBuilder();
// 总共1-5个操作数
int totalOperandCount = random.nextInt(5) + 1; // 1-5个操作数
// 生成所有操作数1-100范围
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < totalOperandCount; i++) {
numbers.add(random.nextInt(100) + 1);
}
// 随机选择一个位置插入平方或开根号运算
int specialOpIndex = random.nextInt(totalOperandCount);
// 构建表达式
for (int i = 0; i < totalOperandCount; i++) {
// 添加运算符(除了第一个元素)
if (expression.length() > 0) {
expression.append(" ").append(operators[random.nextInt(operators.length)]).append(" ");
}
// 判断是否在当前位置插入特殊运算
if (i == specialOpIndex) {
// 随机选择平方或开根号
if (random.nextBoolean()) {
// 添加平方
expression.append(numbers.get(i)).append("²");
} else {
// 添加开根号
int sqrtNum = getRandomPerfectSquare();
expression.append("√").append(sqrtNum);
}
} else {
// 普通数字
expression.append(numbers.get(i));
}
}
// 可能添加括号
if (totalOperandCount >= 3 && random.nextBoolean()) {
StringBuilder withParentheses = addParentheses(expression.toString());
if (isValidExpression(withParentheses.toString())) {
return new MathQuestion(withParentheses.toString(), "计算结果");
}
}
return new MathQuestion(expression.toString(), "计算结果");
}
/**
* sincostan
* 1-51-100
*/
private MathQuestion generateHighSchoolQuestion() {
StringBuilder expression = new StringBuilder();
// 总共1-5个操作数包括三角函数的角度
int totalOperandCount = random.nextInt(5) + 1; // 1-5个操作数
// 生成所有操作数1-100范围
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < totalOperandCount; i++) {
numbers.add(random.nextInt(100) + 1);
}
// 随机选择一个数字作为三角函数的角度
int trigAngleIndex = random.nextInt(totalOperandCount);
int trigAngle = numbers.get(trigAngleIndex);
// 生成三角函数
String trigFunc = trigFunctions[random.nextInt(trigFunctions.length)];
String trigExpression = trigFunc + "(" + trigAngle + "°)";
// 构建表达式
for (int i = 0; i < totalOperandCount; i++) {
// 添加运算符(除了第一个元素)
if (expression.length() > 0) {
expression.append(" ").append(operators[random.nextInt(operators.length)]).append(" ");
}
// 判断当前位置是否为三角函数角度位置
if (i == trigAngleIndex) {
expression.append(trigExpression);
} else {
expression.append(numbers.get(i));
}
}
// 可能添加括号
if (totalOperandCount >= 3 && random.nextDouble() < 0.3) {
StringBuilder withParentheses = addParenthesesForHighSchool(expression.toString());
if (isValidExpression(withParentheses.toString())) {
return new MathQuestion(withParentheses.toString(), "计算结果");
}
}
return new MathQuestion(expression.toString(), "计算结果");
}
/**
*
*/
private StringBuilder addParentheses(String expression) {
String[] parts = expression.split(" ");
if (parts.length >= 5) { // 至少有3个数字和2个操作符
StringBuilder result = new StringBuilder();
result.append("(").append(parts[0]).append(" ").append(parts[1]).append(" ").append(parts[2]).append(")");
for (int i = 3; i < parts.length; i++) {
result.append(" ").append(parts[i]);
}
return result;
}
return new StringBuilder(expression);
}
/**
*
*/
private StringBuilder addParenthesesForHighSchool(String expression) {
String[] parts = expression.split(" ");
if (parts.length < 5) {
return new StringBuilder(expression);
}
// 避免在三角函数内部添加括号
for (String part : parts) {
if (part.contains("sin") || part.contains("cos") || part.contains("tan")) {
return new StringBuilder(expression);
}
}
// 智能选择括号位置
StringBuilder result = new StringBuilder();
// 找到可以添加括号的安全位置
List<Integer> safePositions = new ArrayList<>();
for (int i = 1; i < parts.length - 2; i += 2) {
if (isSafeForParentheses(parts, i)) {
safePositions.add(i);
}
}
if (safePositions.isEmpty()) {
return new StringBuilder(expression);
}
int parenStartIndex = safePositions.get(random.nextInt(safePositions.size()));
int parenLength = 2 + random.nextInt(Math.min(4, parts.length - parenStartIndex - 1));
// 构建带括号的表达式
for (int i = 0; i < parts.length; i++) {
if (i == parenStartIndex) {
result.append("(");
}
result.append(parts[i]);
if (i == parenStartIndex + parenLength - 1) {
result.append(")");
}
if (i < parts.length - 1) {
result.append(" ");
}
}
return result;
}
/**
*
*/
private boolean isSafeForParentheses(String[] parts, int position) {
// 检查不会产生像 "6(" 这样的错误模式
if (position > 0 && isNumber(parts[position - 1]) && parts[position].equals("(")) {
return false;
}
// 检查不会在三角函数内部添加括号
for (int i = Math.max(0, position - 2); i <= Math.min(parts.length - 1, position + 2); i++) {
if (parts[i].contains("sin") || parts[i].contains("cos") || parts[i].contains("tan")) {
return false;
}
}
// 检查不会产生空括号
if (position >= parts.length - 1) {
return false;
}
return true;
}
/**
*
*/
private boolean isNumber(String str) {
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
*
*/
private boolean isValidExpression(String expression) {
// 检查不会出现 "数字(" 或 "(数字" 等错误模式
if (expression.matches(".*\\d\\(.*") || expression.matches(".*\\(\\d.*")) {
return false;
}
// 检查括号匹配
int balance = 0;
for (char c : expression.toCharArray()) {
if (c == '(') balance++;
if (c == ')') balance--;
if (balance < 0) return false;
}
return balance == 0;
}
/**
*
*/
private int getRandomPerfectSquare() {
int[] perfectSquares = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400};
return perfectSquares[random.nextInt(perfectSquares.length)];
}
}

@ -0,0 +1,51 @@
/**
*
*
*/
public class MultipleChoiceQuestion {
private String question;
private String[] options;
private int correctAnswer; // 0-3表示正确选项的索引
private String explanation;
public MultipleChoiceQuestion(String question, String[] options, int correctAnswer) {
this.question = question;
this.options = options.clone();
this.correctAnswer = correctAnswer;
}
public MultipleChoiceQuestion(String question, String[] options, int correctAnswer, String explanation) {
this(question, options, correctAnswer);
this.explanation = explanation;
}
public String getQuestion() {
return question;
}
public String[] getOptions() {
return options.clone();
}
public int getCorrectAnswer() {
return correctAnswer;
}
public String getExplanation() {
return explanation;
}
public boolean isCorrect(int selectedOption) {
return selectedOption == correctAnswer;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(question).append("\n");
for (int i = 0; i < options.length; i++) {
sb.append((char)('A' + i)).append(". ").append(options[i]).append("\n");
}
return sb.toString();
}
}

@ -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,162 @@
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
*
*
*/
public class QuestionManager {
private Map<String, Set<String>> userQuestions; // 存储每个用户的历史题目
public QuestionManager() {
this.userQuestions = new HashMap<>();
loadExistingQuestions();
}
/**
*
*/
private void loadExistingQuestions() {
try {
// 为每个用户类型创建目录
createDirectoryIfNotExists("小学");
createDirectoryIfNotExists("初中");
createDirectoryIfNotExists("高中");
// 扫描已存在的文件并加载题目
scanExistingFiles("小学");
scanExistingFiles("初中");
scanExistingFiles("高中");
} catch (Exception e) {
System.err.println("加载历史题目时出错: " + e.getMessage());
}
}
/**
*
*/
private void createDirectoryIfNotExists(String accountType) throws IOException {
Path path = Paths.get(accountType);
if (!Files.exists(path)) {
Files.createDirectories(path);
}
}
/**
*
*/
private void scanExistingFiles(String accountType) {
try {
Path accountDir = Paths.get(accountType);
if (Files.exists(accountDir)) {
Files.walk(accountDir)
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(this::loadQuestionsFromFile);
}
} catch (IOException e) {
System.err.println("扫描文件时出错: " + e.getMessage());
}
}
/**
*
*/
private void loadQuestionsFromFile(Path filePath) {
try {
String accountType = filePath.getParent().getFileName().toString();
Set<String> questions = userQuestions.computeIfAbsent(accountType, k -> new HashSet<>());
List<String> lines = Files.readAllLines(filePath);
for (String line : lines) {
line = line.trim();
if (line.matches("\\d+\\..*")) { // 题号格式1. 题目内容
String question = line.substring(line.indexOf('.') + 1).trim();
questions.add(question);
}
}
} catch (IOException e) {
System.err.println("读取文件时出错: " + e.getMessage());
}
}
/**
*
* @param accountType
* @param newQuestions
* @return
*/
public List<MathQuestion> checkAndRemoveDuplicates(String accountType, List<MathQuestion> newQuestions) {
Set<String> existingQuestions = userQuestions.computeIfAbsent(accountType, k -> new HashSet<>());
List<MathQuestion> uniqueQuestions = new ArrayList<>();
for (MathQuestion question : newQuestions) {
if (!existingQuestions.contains(question.getExpression())) {
uniqueQuestions.add(question);
existingQuestions.add(question.getExpression());
}
}
return uniqueQuestions;
}
/**
*
* @param accountType
* @param questions
* @return
*/
public String saveQuestionsToFile(String accountType, List<MathQuestion> questions) {
try {
// 生成文件名:年-月-日-时-分-秒.txt
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
String fileName = now.format(formatter) + ".txt";
// 创建文件路径
Path accountDir = Paths.get(accountType);
Path filePath = accountDir.resolve(fileName);
// 写入文件
try (BufferedWriter writer = Files.newBufferedWriter(filePath)) {
for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i).getExpression());
writer.newLine();
if (i < questions.size() - 1) {
writer.newLine(); // 题目之间空一行
}
}
}
return fileName;
} catch (IOException e) {
System.err.println("保存文件时出错: " + e.getMessage());
return null;
}
}
/**
*
* @param accountType
* @param requestedCount
* @param generator
* @return
*/
public List<MathQuestion> generateUniqueQuestions(String accountType, int requestedCount, MathQuestionGenerator generator) {
List<MathQuestion> allQuestions = new ArrayList<>();
int maxAttempts = requestedCount * 3; // 最多尝试3倍数量
int attempts = 0;
while (allQuestions.size() < requestedCount && attempts < maxAttempts) {
List<MathQuestion> newQuestions = generator.generateQuestions(accountType, requestedCount - allQuestions.size());
List<MathQuestion> uniqueQuestions = checkAndRemoveDuplicates(accountType, newQuestions);
allQuestions.addAll(uniqueQuestions);
attempts++;
}
return allQuestions;
}
}

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

@ -0,0 +1,35 @@
/**
*
*
*/
public class User {
private String username;
private String password;
private String accountType; // 小学、初中、高中
public User(String username, String password, String accountType) {
this.username = username;
this.password = password;
this.accountType = accountType;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getAccountType() {
return accountType;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", accountType='" + accountType + '\'' +
'}';
}
}
Loading…
Cancel
Save