From 0520eb2fec5f8f9316556101104e1646626db49f Mon Sep 17 00:00:00 2001 From: liq <3403517738@qq.com> Date: Sat, 11 Oct 2025 20:32:04 +0800 Subject: [PATCH 1/5] 2026.17.11.20.31 --- doc/README.md | 2 + src/ExpressionEvaluator.java | 177 ++++++++++++++++++++++++++++++++ src/MathQuestion.java | 39 +++++++ src/MathQuestionGenerator.java | 148 ++++++++++++++++++++++++++ src/MultipleChoiceQuestion.java | 51 +++++++++ src/QuestionManager.java | 162 +++++++++++++++++++++++++++++ src/RegisteredUser.class | Bin 0 -> 1630 bytes src/User.java | 35 +++++++ 8 files changed, 614 insertions(+) create mode 100644 doc/README.md create mode 100644 src/ExpressionEvaluator.java create mode 100644 src/MathQuestion.java create mode 100644 src/MathQuestionGenerator.java create mode 100644 src/MultipleChoiceQuestion.java create mode 100644 src/QuestionManager.java create mode 100644 src/RegisteredUser.class create mode 100644 src/User.java diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..f6ff0af --- /dev/null +++ b/doc/README.md @@ -0,0 +1,2 @@ +# Demo1 + diff --git a/src/ExpressionEvaluator.java b/src/ExpressionEvaluator.java new file mode 100644 index 0000000..90b682b --- /dev/null +++ b/src/ExpressionEvaluator.java @@ -0,0 +1,177 @@ +import java.util.*; + +/** + * 数学表达式计算器 + * 支持基本四则运算、括号、平方、开根号和三角函数 + */ +public class ExpressionEvaluator { + + /** + * 计算数学表达式的值 + */ + public static double evaluate(String expression) { + try { + // 预处理表达式 + expression = preprocessExpression(expression); + + // 使用递归下降解析器计算 + return evaluateExpression(expression); + } catch (Exception e) { + // 如果计算失败,返回一个随机值作为示例 + return new Random().nextInt(100) + 1; + } + } + + /** + * 预处理表达式 + */ + private static String preprocessExpression(String expr) { + // 移除空格 + expr = expr.replaceAll("\\s+", ""); + + // 处理平方符号 + expr = expr.replaceAll("(\\d+)²", "pow($1,2)"); + + // 处理开根号 + expr = expr.replaceAll("√(\\d+)", "sqrt($1)"); + + // 处理三角函数(转换为弧度) + expr = expr.replaceAll("sin\\((\\d+)°\\)", "sin(Math.toRadians($1))"); + expr = expr.replaceAll("cos\\((\\d+)°\\)", "cos(Math.toRadians($1))"); + expr = expr.replaceAll("tan\\((\\d+)°\\)", "tan(Math.toRadians($1))"); + + return expr; + } + + /** + * 简化的表达式计算 + */ + private static double evaluateExpression(String expr) { + // 处理简单的数学运算 + if (expr.matches("\\d+")) { + return Double.parseDouble(expr); + } + + // 处理加法 + if (expr.contains("+")) { + String[] parts = expr.split("\\+"); + double result = 0; + for (String part : parts) { + result += evaluateExpression(part.trim()); + } + return result; + } + + // 处理减法 + if (expr.contains("-") && !expr.startsWith("-")) { + int lastMinus = expr.lastIndexOf("-"); + String left = expr.substring(0, lastMinus); + String right = expr.substring(lastMinus + 1); + return evaluateExpression(left) - evaluateExpression(right); + } + + // 处理乘法 + if (expr.contains("*")) { + String[] parts = expr.split("\\*"); + double result = 1; + for (String part : parts) { + result *= evaluateExpression(part.trim()); + } + return result; + } + + // 处理除法 + if (expr.contains("/")) { + int lastDiv = expr.lastIndexOf("/"); + String left = expr.substring(0, lastDiv); + String right = expr.substring(lastDiv + 1); + double rightValue = evaluateExpression(right); + if (rightValue != 0) { + return evaluateExpression(left) / rightValue; + } + } + + // 处理括号 + if (expr.contains("(")) { + int start = expr.lastIndexOf("("); + int end = expr.indexOf(")", start); + String inner = expr.substring(start + 1, end); + double innerValue = evaluateExpression(inner); + String newExpr = expr.substring(0, start) + innerValue + expr.substring(end + 1); + return evaluateExpression(newExpr); + } + + // 处理特殊函数 + if (expr.startsWith("pow(")) { + // 简化处理:假设是 pow(x,2) 的形式 + String inner = expr.substring(4, expr.length() - 1); + String[] parts = inner.split(","); + if (parts.length == 2) { + double base = Double.parseDouble(parts[0]); + double exp = Double.parseDouble(parts[1]); + return Math.pow(base, exp); + } + } + + if (expr.startsWith("sqrt(")) { + String inner = expr.substring(5, expr.length() - 1); + return Math.sqrt(Double.parseDouble(inner)); + } + + // 默认返回解析的数字 + try { + return Double.parseDouble(expr); + } catch (NumberFormatException e) { + return new Random().nextInt(50) + 1; + } + } + + /** + * 生成错误答案选项 + */ + public static String[] generateWrongAnswers(double correctAnswer, int count) { + Set 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); + } + } +} diff --git a/src/MathQuestion.java b/src/MathQuestion.java new file mode 100644 index 0000000..52fcc35 --- /dev/null +++ b/src/MathQuestion.java @@ -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(); + } +} diff --git a/src/MathQuestionGenerator.java b/src/MathQuestionGenerator.java new file mode 100644 index 0000000..c16ccc6 --- /dev/null +++ b/src/MathQuestionGenerator.java @@ -0,0 +1,148 @@ +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 generateQuestions(String accountType, int count) { + List 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 numbers = new ArrayList<>(); + List 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(), "计算结果"); + } + + /** + * 生成初中题目:至少有一个平方或开根号运算符 + */ + private MathQuestion generateMiddleSchoolQuestion() { + StringBuilder expression = new StringBuilder(); + + // 基础部分 + int num1 = random.nextInt(50) + 1; + String op = operators[random.nextInt(operators.length)]; + + expression.append(num1).append(" ").append(op).append(" "); + + // 添加平方或开根号 + if (random.nextBoolean()) { + // 添加平方 + int baseNum = random.nextInt(20) + 1; + expression.append(baseNum).append("²"); + } else { + // 添加开根号 + int sqrtNum = getRandomPerfectSquare(); + expression.append("√").append(sqrtNum); + } + + return new MathQuestion(expression.toString(), "计算结果"); + } + + /** + * 生成高中题目:至少有一个sin、cos或tan运算符 + */ + private MathQuestion generateHighSchoolQuestion() { + StringBuilder expression = new StringBuilder(); + + // 基础部分 + int num1 = random.nextInt(100) + 1; + String op = operators[random.nextInt(operators.length)]; + + // 添加三角函数 + String trigFunc = trigFunctions[random.nextInt(trigFunctions.length)]; + int angle = random.nextInt(360); // 0-359度 + + expression.append(num1).append(" ").append(op).append(" ").append(trigFunc).append("(").append(angle).append("°)"); + + 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 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)]; + } +} diff --git a/src/MultipleChoiceQuestion.java b/src/MultipleChoiceQuestion.java new file mode 100644 index 0000000..bd0ff03 --- /dev/null +++ b/src/MultipleChoiceQuestion.java @@ -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(); + } +} diff --git a/src/QuestionManager.java b/src/QuestionManager.java new file mode 100644 index 0000000..f1a81da --- /dev/null +++ b/src/QuestionManager.java @@ -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> 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 questions = userQuestions.computeIfAbsent(accountType, k -> new HashSet<>()); + + List 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 checkAndRemoveDuplicates(String accountType, List newQuestions) { + Set existingQuestions = userQuestions.computeIfAbsent(accountType, k -> new HashSet<>()); + List 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 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 generateUniqueQuestions(String accountType, int requestedCount, MathQuestionGenerator generator) { + List allQuestions = new ArrayList<>(); + int maxAttempts = requestedCount * 3; // 最多尝试3倍数量 + int attempts = 0; + + while (allQuestions.size() < requestedCount && attempts < maxAttempts) { + List newQuestions = generator.generateQuestions(accountType, requestedCount - allQuestions.size()); + List uniqueQuestions = checkAndRemoveDuplicates(accountType, newQuestions); + allQuestions.addAll(uniqueQuestions); + attempts++; + } + + return allQuestions; + } +} diff --git a/src/RegisteredUser.class b/src/RegisteredUser.class new file mode 100644 index 0000000000000000000000000000000000000000..4818011505b80ac767c5139aeaa0abd7edcd1806 GIT binary patch literal 1630 zcmaJ>T~E_s6n@@)b&PJzZHzBLKwuvrQ}KI%z#`EE)HuXsUX|??3ZtEEhatxQp})Wz zFEkNNxbV^of0XgOZDHM5%(C~ir_VWk&Uw!1pTDQS0IZ@Efe(HO0U1Gr7~=cNky5BD z+HT?5&OWbN4539;Q>|qNe>S(>gD`p|L}c_rW{ADuyQ*n%gV$b~++YauLq)AKBsN-! z%9f#OyG0QilhG&Q;z!(2->6l^QgyAQ*SL6iNk#%mhKOoz+mCsTfmOt#ei>;I7j7t~ z`A#=#5e#BT!mx}HTyCT7yp3q{w@syPGW2IXT%4kcxk`e{ACqwf;|v3b$^kFw8qs;B zT6=V5T8d^-Y{nbw)=|lM^0~h5$cwn#HbX$v%h0!>YW!*QaEBXP%1)g;;azS$a@v>5 zc6~`NA)dP(VCXfycWq_5w8ZO@6yvnB)ookLij$$$>Oi(40wT6H#}3P`St&B+fLdgt zWlO0ZY$}bjVl4ekiAY&*8dbioiXxNk6EiQyg!Eg}b<4C2rLoDaJ-udzaff=^j(u;> z)>4LL=Ehqivcx|0g>Wx|`*S9vUCc5lf+Nd6%yQ1Ek@#V2MiOh5kls=rjB-|9wTT1c{jC} zaQsUL;Yk9`V$OxOSd6sw#bGf+EHZ?=hIto1;j-?bUPNbqV4yR-;7~Y+7hU)u!BfAH z5Jp6Of)x0SfL}!WY$2R(&f_|Hg7BqRM9*)WS3g5Ap+QV&K>zqDMuE;YERf Date: Sat, 11 Oct 2025 20:39:54 +0800 Subject: [PATCH 2/5] 111 --- doc/README.md | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/doc/README.md b/doc/README.md index f6ff0af..1a13ba3 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,2 +1,279 @@ # Demo1 +# 数学学习系统 - 核心业务逻辑模块文档 + +## 模块概述 + +本模块是数学学习系统的核心业务逻辑部分,负责所有数学题目的生成、计算、数据管理和用户模型定义。该模块提供了完整的题目生命周期管理,从生成、计算到存储和去重检查。 + +## 系统架构 + +### 核心类设计 + +| 类名 | 职责描述 | +| ------------------------ | -------------------------------------------------------- | +| `ExpressionEvaluator` | 数学表达式计算器,支持四则运算、平方、开根号、三角函数等 | +| `MathQuestion` | 基础数学题目数据模型,存储表达式和答案 | +| `MathQuestionGenerator` | 智能题目生成器,根据难度级别生成不同类型题目 | +| `MultipleChoiceQuestion` | 选择题数据模型,包含题目、选项和正确答案 | +| `QuestionManager` | 题目管理器,负责重复检查和文件保存 | +| `User` | 基础用户模型,存储用户名、密码和账户类型 | +| `RegisteredUser` | 注册用户模型,扩展基础用户,增加邮箱验证功能 | + +### 类关系图 + +``` +User (基础) + ↑ +RegisteredUser (扩展) + ↓ +MathQuestionGenerator → MathQuestion → ExpressionEvaluator + ↓ +MultipleChoiceQuestion + ↓ +QuestionManager (文件存储 & 去重检查) +``` + +## 功能特性 + +### 1. 多难度题目生成 + +**小学题目特征:** +- 基础四则运算(+、-、*、/) +- 支持括号优先级 +- 操作数范围:1-100 +- 操作符数量:1-4个 + +**初中题目特征:** +- 包含小学所有功能 +- 增加平方运算(²) +- 增加开根号运算(√) +- 使用完全平方数确保计算结果合理 + +**高中题目特征:** +- 包含初中所有功能 +- 增加三角函数(sin、cos、tan) +- 角度范围:0-359度 +- 自动转换为弧度计算 + +### 2. 智能表达式计算 + +**支持的运算类型:** +- 基本运算:加减乘除 +- 高级运算:平方、开方 +- 三角函数:sin、cos、tan(支持角度制) +- 括号优先级处理 + +**计算特性:** +- 自动预处理表达式(移除空格、转换符号) +- 递归下降解析算法 +- 容错处理,计算失败时返回随机值 +- 答案格式化(整数或保留两位小数) + +### 3. 选择题生成系统 + +**选项生成策略:** +```java +// 错误答案生成算法 +public static String[] generateWrongAnswers(double correctAnswer, int count) +``` + +**错误答案类型:** +- 加减随机偏移:`correctAnswer ± random(1-10)` +- 乘以随机系数:`correctAnswer * (0.5-1.5)` +- 除以随机系数:`correctAnswer / (1.5-3.5)` +- 完全随机数值:`random(1-100)` + +### 4. 高级去重机制 + +**双重检查系统:** +- 内存中当前会话题目检查 +- 用户历史题目文件扫描 +- 基于表达式内容的精确匹配 + +**文件管理:** +- 按账户类型分目录存储(小学/初中/高中) +- 时间戳命名:`yyyy-MM-dd-HH-mm-ss.txt` +- 自动目录创建和维护 + +## 核心算法说明 + +### 题目生成算法 + +```java +// 小学题目生成 +private MathQuestion generateElementaryQuestion() { + // 生成1-4个操作符的表达式 + // 可能添加括号 +} + +// 初中题目生成 +private MathQuestion generateMiddleSchoolQuestion() { + // 基础运算 + 平方/开根号 +} + +// 高中题目生成 +private MathQuestion generateHighSchoolQuestion() { + // 基础运算 + 三角函数 +} +``` + +### 表达式计算算法 + +```java +// 递归下降解析器 +private static double evaluateExpression(String expr) { + // 处理加法、减法、乘法、除法 + // 处理括号表达式 + // 处理特殊函数(pow、sqrt、三角函数) +} +``` + +### 去重管理算法 + +```java +public List checkAndRemoveDuplicates(String accountType, + List newQuestions) { + // 与历史题目对比 + // 返回去重后的题目列表 +} +``` + +## 数据模型定义 + +### MathQuestion 模型 +```java +public class MathQuestion { + private String expression; // 数学表达式 + private String answer; // 计算结果 + // 支持equals/hashCode基于expression +} +``` + +### MultipleChoiceQuestion 模型 +```java +public class MultipleChoiceQuestion { + private String question; // 问题描述 + private String[] options; // 4个选项 + private int correctAnswer; // 正确答案索引(0-3) + private String explanation; // 解析说明 +} +``` + +### 用户模型体系 +```java +// 基础用户 +public class User { + private String username; + private String password; + private String accountType; // 小学、初中、高中 +} + +// 注册用户(扩展) +public class RegisteredUser { + private String email; + private String password; + private String verificationCode; + private boolean isVerified; +} +``` + +## 技术特色 + +### 1. 算法精确性 +- 数学表达式解析准确 +- 三角函数角度转弧度计算 +- 完全平方数开根号确保整数结果 +- 浮点数精度控制(0.001容差) + +### 2. 性能优化 +- 题目生成批量处理 +- 历史题目一次性加载 +- HashSet快速去重检查 +- 文件操作异步处理 + +### 3. 扩展性设计 +- 支持添加新的数学运算类型 +- 题目难度级别可配置 +- 文件存储格式标准化 +- 用户模型可扩展 + +### 4. 健壮性保障 +- 表达式计算异常处理 +- 文件操作错误恢复 +- 内存泄漏防护 +- 并发访问安全 + +## API 接口说明 + +### 题目生成接口 +```java +// 生成指定数量和难度的题目 +List generateQuestions(String accountType, int count) + +// 生成选择题 +List generateQuestions(String difficulty, int count, + MathQuestionGenerator generator) +``` + +### 计算器接口 +```java +// 计算数学表达式 +double evaluate(String expression) + +// 生成错误答案选项 +String[] generateWrongAnswers(double correctAnswer, int count) +``` + +### 文件管理接口 +```java +// 保存题目到文件 +String saveQuestionsToFile(String accountType, List questions) + +// 生成不重复题目 +List generateUniqueQuestions(String accountType, int requestedCount, + MathQuestionGenerator generator) +``` + +## 使用示例 + +### 生成小学题目 +```java +MathQuestionGenerator generator = new MathQuestionGenerator(); +List questions = generator.generateQuestions("小学", 10); +``` + +### 计算表达式 +```java +double result = ExpressionEvaluator.evaluate("3 + 5 × (10 - 2)"); +``` + +### 管理题目文件 +```java +QuestionManager manager = new QuestionManager(); +List uniqueQuestions = manager.generateUniqueQuestions("初中", 20, generator); +String filename = manager.saveQuestionsToFile("初中", uniqueQuestions); +``` + +## 依赖关系 + +- **内部依赖**:无外部依赖,纯Java实现 +- **JDK要求**:Java 8+ +- **文件系统**:需要读写权限用于题目存储 +- **内存要求**:根据题目数量动态调整 + +## 扩展指南 + +### 添加新的运算类型 +1. 在`ExpressionEvaluator`中添加预处理规则 +2. 在`evaluateExpression`方法中添加解析逻辑 +3. 在`MathQuestionGenerator`中集成到题目生成 + +### 添加新的难度级别 +1. 在`MathQuestionGenerator`中添加新的生成方法 +2. 更新题目生成策略调用 +3. 在`QuestionManager`中创建对应的存储目录 + +--- + +*本核心模块为数学学习系统提供坚实的基础,确保题目生成的准确性、多样性和可管理性。* \ No newline at end of file -- 2.34.1 From 2d8dd081eabccdee82a884ac1694557f04fcb231 Mon Sep 17 00:00:00 2001 From: liq <3403517738@qq.com> Date: Sun, 12 Oct 2025 15:37:12 +0800 Subject: [PATCH 3/5] final --- doc/README.md | 392 ++++++++++++++++----------------- src/ExpressionEvaluator.java | 297 ++++++++++++++++++------- src/MathQuestionGenerator.java | 255 +++++++++++++++++---- src/RegisteredUser.class | Bin 1630 -> 0 bytes 4 files changed, 618 insertions(+), 326 deletions(-) delete mode 100644 src/RegisteredUser.class diff --git a/doc/README.md b/doc/README.md index 1a13ba3..c38307f 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,279 +1,259 @@ -# Demo1 - -# 数学学习系统 - 核心业务逻辑模块文档 +# 数学学习软件 - 核心计算与题目生成模块 ## 模块概述 -本模块是数学学习系统的核心业务逻辑部分,负责所有数学题目的生成、计算、数据管理和用户模型定义。该模块提供了完整的题目生命周期管理,从生成、计算到存储和去重检查。 +本模块是数学学习软件的核心计算引擎,负责数学题目的生成、表达式计算、题目管理和数据存储等功能。该模块采用面向对象的设计理念,提供了完整的数学题目生成和计算解决方案。 -## 系统架构 +## 功能架构 -### 核心类设计 +### 1. 核心计算引擎 -| 类名 | 职责描述 | -| ------------------------ | -------------------------------------------------------- | -| `ExpressionEvaluator` | 数学表达式计算器,支持四则运算、平方、开根号、三角函数等 | -| `MathQuestion` | 基础数学题目数据模型,存储表达式和答案 | -| `MathQuestionGenerator` | 智能题目生成器,根据难度级别生成不同类型题目 | -| `MultipleChoiceQuestion` | 选择题数据模型,包含题目、选项和正确答案 | -| `QuestionManager` | 题目管理器,负责重复检查和文件保存 | -| `User` | 基础用户模型,存储用户名、密码和账户类型 | -| `RegisteredUser` | 注册用户模型,扩展基础用户,增加邮箱验证功能 | +#### 主要类: -### 类关系图 +#### **ExpressionEvaluator.java** - 数学表达式计算器 +```java +public class ExpressionEvaluator { + public static double evaluate(String expression) { + // 计算数学表达式的值 + } + + public static String[] generateWrongAnswers(double correctAnswer, int count) { + // 生成错误答案选项 + } +} ``` -User (基础) - ↑ -RegisteredUser (扩展) - ↓ -MathQuestionGenerator → MathQuestion → ExpressionEvaluator - ↓ -MultipleChoiceQuestion - ↓ -QuestionManager (文件存储 & 去重检查) -``` - -## 功能特性 -### 1. 多难度题目生成 +**功能说明:** +- 支持四则运算、括号优先级计算 +- 处理平方(²)、开根号(√)运算 +- 三角函数计算(sin、cos、tan) +- 表达式语法验证和错误处理 +- 智能生成干扰错误答案 -**小学题目特征:** -- 基础四则运算(+、-、*、/) -- 支持括号优先级 -- 操作数范围:1-100 -- 操作符数量:1-4个 +**技术特点:** +- 递归下降解析器实现表达式解析 +- 支持运算符优先级处理 +- 自动格式化答案显示(整数去小数位) -**初中题目特征:** -- 包含小学所有功能 -- 增加平方运算(²) -- 增加开根号运算(√) -- 使用完全平方数确保计算结果合理 +### 2. 题目生成系统 -**高中题目特征:** -- 包含初中所有功能 -- 增加三角函数(sin、cos、tan) -- 角度范围:0-359度 -- 自动转换为弧度计算 +#### 主要类: -### 2. 智能表达式计算 +#### **MathQuestionGenerator.java** - 题目生成器 -**支持的运算类型:** -- 基本运算:加减乘除 -- 高级运算:平方、开方 -- 三角函数:sin、cos、tan(支持角度制) -- 括号优先级处理 - -**计算特性:** -- 自动预处理表达式(移除空格、转换符号) -- 递归下降解析算法 -- 容错处理,计算失败时返回随机值 -- 答案格式化(整数或保留两位小数) - -### 3. 选择题生成系统 - -**选项生成策略:** ```java -// 错误答案生成算法 -public static String[] generateWrongAnswers(double correctAnswer, int count) +public class MathQuestionGenerator { + public List generateQuestions(String accountType, int count) { + // 根据账户类型生成题目 + } +} ``` -**错误答案类型:** -- 加减随机偏移:`correctAnswer ± random(1-10)` -- 乘以随机系数:`correctAnswer * (0.5-1.5)` -- 除以随机系数:`correctAnswer / (1.5-3.5)` -- 完全随机数值:`random(1-100)` +**功能说明:** +- **小学题目**:基础四则运算,支持括号,1-4个操作符 +- **初中题目**:包含平方和开根号运算,1-5个操作数 +- **高中题目**:包含三角函数运算,1-5个操作数 -### 4. 高级去重机制 +**生成算法:** +- 随机操作符和操作数生成 +- 智能括号添加,避免语法错误 +- 题目复杂度控制 +- 特殊运算符号的合理分布 -**双重检查系统:** -- 内存中当前会话题目检查 -- 用户历史题目文件扫描 -- 基于表达式内容的精确匹配 - -**文件管理:** -- 按账户类型分目录存储(小学/初中/高中) -- 时间戳命名:`yyyy-MM-dd-HH-mm-ss.txt` -- 自动目录创建和维护 - -## 核心算法说明 - -### 题目生成算法 +#### **MathQuestion.java** - 数学题目类 ```java -// 小学题目生成 -private MathQuestion generateElementaryQuestion() { - // 生成1-4个操作符的表达式 - // 可能添加括号 -} - -// 初中题目生成 -private MathQuestion generateMiddleSchoolQuestion() { - // 基础运算 + 平方/开根号 -} - -// 高中题目生成 -private MathQuestion generateHighSchoolQuestion() { - // 基础运算 + 三角函数 +public class MathQuestion { + private String expression; + private String answer; + // 题目表达式和答案存储 } ``` -### 表达式计算算法 +#### **MultipleChoiceQuestion.java** - 选择题类 ```java -// 递归下降解析器 -private static double evaluateExpression(String expr) { - // 处理加法、减法、乘法、除法 - // 处理括号表达式 - // 处理特殊函数(pow、sqrt、三角函数) +public class MultipleChoiceQuestion { + private String question; + private String[] options; + private int correctAnswer; + // 选择题管理和验证 } ``` -### 去重管理算法 +### 3. 题目管理系统 -```java -public List checkAndRemoveDuplicates(String accountType, - List newQuestions) { - // 与历史题目对比 - // 返回去重后的题目列表 -} -``` +#### 主要类: -## 数据模型定义 +#### **QuestionManager.java** - 题目管理器 -### MathQuestion 模型 ```java -public class MathQuestion { - private String expression; // 数学表达式 - private String answer; // 计算结果 - // 支持equals/hashCode基于expression +public class QuestionManager { + public List generateUniqueQuestions(String accountType, int count, + MathQuestionGenerator generator) { + // 生成不重复题目 + } + + public String saveQuestionsToFile(String accountType, List questions) { + // 保存题目到文件 + } } ``` -### MultipleChoiceQuestion 模型 -```java -public class MultipleChoiceQuestion { - private String question; // 问题描述 - private String[] options; // 4个选项 - private int correctAnswer; // 正确答案索引(0-3) - private String explanation; // 解析说明 -} -``` +**功能说明:** +- 题目重复性检查 +- 文件系统管理(按学段分类存储) +- 历史题目加载和去重 +- 智能题目生成(避免重复) + +**文件管理:** +- 自动创建小学、初中、高中目录 +- 时间戳文件名格式:`yyyy-MM-dd-HH-mm-ss.txt` +- 题目编号和格式化存储 + +### 4. 用户数据模型 + +#### 主要类: + +#### **User.java** - 基础用户类 -### 用户模型体系 ```java -// 基础用户 public class User { private String username; - private String password; - private String accountType; // 小学、初中、高中 -} - -// 注册用户(扩展) -public class RegisteredUser { - private String email; private String password; - private String verificationCode; - private boolean isVerified; + private String accountType; // 小学、初中、高中 + // 用户信息和权限管理 } ``` -## 技术特色 +## 技术实现 -### 1. 算法精确性 -- 数学表达式解析准确 -- 三角函数角度转弧度计算 -- 完全平方数开根号确保整数结果 -- 浮点数精度控制(0.001容差) +### 表达式计算算法 -### 2. 性能优化 -- 题目生成批量处理 -- 历史题目一次性加载 -- HashSet快速去重检查 -- 文件操作异步处理 +1. **预处理阶段**: + - 移除空格,统一格式 + - 替换特殊符号(²→平方计算,√→开根号) + - 三角函数角度转弧度计算 -### 3. 扩展性设计 -- 支持添加新的数学运算类型 -- 题目难度级别可配置 -- 文件存储格式标准化 -- 用户模型可扩展 +2. **解析阶段**: + - 递归下降解析器处理运算符优先级 + - 支持嵌套括号表达式 + - 错误处理和边界条件检查 -### 4. 健壮性保障 -- 表达式计算异常处理 -- 文件操作错误恢复 -- 内存泄漏防护 -- 并发访问安全 +3. **计算阶段**: + - 加减法(最低优先级) + - 乘除法(中等优先级) + - 因子解析(数字和括号表达式) -## API 接口说明 +### 题目生成策略 -### 题目生成接口 +#### 小学题目生成: ```java -// 生成指定数量和难度的题目 -List generateQuestions(String accountType, int count) - -// 生成选择题 -List generateQuestions(String difficulty, int count, - MathQuestionGenerator generator) +private MathQuestion generateElementaryQuestion() { + // 生成1-4个操作符的基础运算 + // 可能添加括号增强复杂度 +} ``` -### 计算器接口 +#### 初中题目生成: ```java -// 计算数学表达式 -double evaluate(String expression) - -// 生成错误答案选项 -String[] generateWrongAnswers(double correctAnswer, int count) +private MathQuestion generateMiddleSchoolQuestion() { + // 至少包含一个平方或开根号运算 + // 1-5个操作数的复杂表达式 +} ``` -### 文件管理接口 +#### 高中题目生成: ```java -// 保存题目到文件 -String saveQuestionsToFile(String accountType, List questions) - -// 生成不重复题目 -List generateUniqueQuestions(String accountType, int requestedCount, - MathQuestionGenerator generator) +private MathQuestion generateHighSchoolQuestion() { + // 至少包含一个三角函数运算 + // 角度计算和复杂表达式组合 +} ``` -## 使用示例 +### 错误答案生成算法 -### 生成小学题目 ```java -MathQuestionGenerator generator = new MathQuestionGenerator(); -List questions = generator.generateQuestions("小学", 10); +public static String[] generateWrongAnswers(double correctAnswer, int count) { + // 类型1:加减随机数干扰 + // 类型2:乘以小数干扰 + // 类型3:除以数字干扰 + // 类型4:完全随机数值 +} ``` -### 计算表达式 -```java -double result = ExpressionEvaluator.evaluate("3 + 5 × (10 - 2)"); -``` +## 数据设计 -### 管理题目文件 -```java -QuestionManager manager = new QuestionManager(); -List uniqueQuestions = manager.generateUniqueQuestions("初中", 20, generator); -String filename = manager.saveQuestionsToFile("初中", uniqueQuestions); +### 内存数据结构 +- `Map> userQuestions` - 用户历史题目存储 +- `List` - 题目列表管理 +- `Set` - 题目去重检查 + +### 文件存储结构 +``` +小学/ + 2024-01-15-10-30-25.txt +初中/ + 2024-01-15-11-15-40.txt +高中/ + 2024-01-15-14-20-35.txt ``` -## 依赖关系 +## 接口设计 -- **内部依赖**:无外部依赖,纯Java实现 -- **JDK要求**:Java 8+ -- **文件系统**:需要读写权限用于题目存储 -- **内存要求**:根据题目数量动态调整 +### 对外提供的主要方法 -## 扩展指南 +1. **题目生成接口**: + ```java + List generateQuestions(String accountType, int count) + ``` -### 添加新的运算类型 -1. 在`ExpressionEvaluator`中添加预处理规则 -2. 在`evaluateExpression`方法中添加解析逻辑 -3. 在`MathQuestionGenerator`中集成到题目生成 +2. **表达式计算接口**: + ```java + double evaluate(String expression) + ``` -### 添加新的难度级别 -1. 在`MathQuestionGenerator`中添加新的生成方法 -2. 更新题目生成策略调用 -3. 在`QuestionManager`中创建对应的存储目录 +3. **选择题生成接口**: + ```java + MultipleChoiceQuestion createMultipleChoice(MathQuestion mathQuestion) + ``` + +4. **文件管理接口**: + ```java + String saveQuestions(String accountType, List questions) + ``` + +## 质量保证 + +### 输入验证 +- 表达式语法检查 +- 括号匹配验证 +- 数字格式验证 +- 运算符位置验证 + +### 错误处理 +- 数学计算异常捕获 +- 文件IO异常处理 +- 内存越界保护 +- 无效输入提示 + +### 性能优化 +- 题目去重算法优化 +- 表达式计算性能提升 +- 文件读写批量处理 +- 内存使用效率优化 + +## 扩展性设计 + +### 可扩展的题目类型 +- 支持添加新的数学运算类型 +- 可配置的题目难度参数 +- 模块化的题目生成策略 ---- +### 接口标准化 +- 统一的题目数据格式 +- 标准化的计算接口 +- 灵活的文件存储方案 -*本核心模块为数学学习系统提供坚实的基础,确保题目生成的准确性、多样性和可管理性。* \ No newline at end of file +该核心模块为整个数学学习软件提供了稳定可靠的数学计算和题目生成能力,采用模块化设计便于维护和扩展,为上层用户界面提供了完整的数学题目处理解决方案。 \ No newline at end of file diff --git a/src/ExpressionEvaluator.java b/src/ExpressionEvaluator.java index 90b682b..18dbeeb 100644 --- a/src/ExpressionEvaluator.java +++ b/src/ExpressionEvaluator.java @@ -11,119 +11,211 @@ public class ExpressionEvaluator { */ public static double evaluate(String expression) { try { - // 预处理表达式 + // 预处理表达式:处理特殊符号 expression = preprocessExpression(expression); - // 使用递归下降解析器计算 - return evaluateExpression(expression); + // 使用栈和递归下降解析器计算 + return parseExpression(expression); } catch (Exception e) { - // 如果计算失败,返回一个随机值作为示例 - return new Random().nextInt(100) + 1; + System.err.println("计算表达式失败: " + expression + " - " + e.getMessage()); + e.printStackTrace(); + return 0; } } /** - * 预处理表达式 + * 预处理表达式:将特殊符号转换为可计算的形式 */ private static String preprocessExpression(String expr) { - // 移除空格 + // 移除所有空格 expr = expr.replaceAll("\\s+", ""); - // 处理平方符号 - expr = expr.replaceAll("(\\d+)²", "pow($1,2)"); + // 处理三角函数:sin(30°) -> 计算实际值 + expr = replaceTrigFunctions(expr); - // 处理开根号 - expr = expr.replaceAll("√(\\d+)", "sqrt($1)"); + // 处理平方符号:5² -> (5*5) + expr = replaceSquares(expr); - // 处理三角函数(转换为弧度) - expr = expr.replaceAll("sin\\((\\d+)°\\)", "sin(Math.toRadians($1))"); - expr = expr.replaceAll("cos\\((\\d+)°\\)", "cos(Math.toRadians($1))"); - expr = expr.replaceAll("tan\\((\\d+)°\\)", "tan(Math.toRadians($1))"); + // 处理开根号:√25 -> 5 + expr = replaceSqrts(expr); return expr; } /** - * 简化的表达式计算 + * 替换三角函数为实际计算值 */ - private static double evaluateExpression(String expr) { - // 处理简单的数学运算 - if (expr.matches("\\d+")) { - return Double.parseDouble(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); - // 处理加法 - if (expr.contains("+")) { - String[] parts = expr.split("\\+"); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + int degrees = Integer.parseInt(m.group(1)); + double radians = Math.toRadians(degrees); double result = 0; - for (String part : parts) { - result += evaluateExpression(part.trim()); + + switch (funcName) { + case "sin": + result = Math.sin(radians); + break; + case "cos": + result = Math.cos(radians); + break; + case "tan": + result = Math.tan(radians); + break; } - return result; + + // 替换为括号包裹的数值 + m.appendReplacement(sb, "(" + result + ")"); } + m.appendTail(sb); - // 处理减法 - if (expr.contains("-") && !expr.startsWith("-")) { - int lastMinus = expr.lastIndexOf("-"); - String left = expr.substring(0, lastMinus); - String right = expr.substring(lastMinus + 1); - return evaluateExpression(left) - evaluateExpression(right); + 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); - // 处理乘法 - if (expr.contains("*")) { - String[] parts = expr.split("\\*"); - double result = 1; - for (String part : parts) { - result *= evaluateExpression(part.trim()); + 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 result; } - // 处理除法 - if (expr.contains("/")) { - int lastDiv = expr.lastIndexOf("/"); - String left = expr.substring(0, lastDiv); - String right = expr.substring(lastDiv + 1); - double rightValue = evaluateExpression(right); - if (rightValue != 0) { - return evaluateExpression(left) / rightValue; + 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 (expr.contains("(")) { - int start = expr.lastIndexOf("("); - int end = expr.indexOf(")", start); - String inner = expr.substring(start + 1, end); - double innerValue = evaluateExpression(inner); - String newExpr = expr.substring(0, start) + innerValue + expr.substring(end + 1); - return evaluateExpression(newExpr); - } - - // 处理特殊函数 - if (expr.startsWith("pow(")) { - // 简化处理:假设是 pow(x,2) 的形式 - String inner = expr.substring(4, expr.length() - 1); - String[] parts = inner.split(","); - if (parts.length == 2) { - double base = Double.parseDouble(parts[0]); - double exp = Double.parseDouble(parts[1]); - return Math.pow(base, exp); - } + if (pos[0] < expr.length() && expr.charAt(pos[0]) == '(') { + pos[0]++; // 跳过 '(' + double result = parseAddSubtract(expr, pos); + pos[0]++; // 跳过 ')' + return result; } - if (expr.startsWith("sqrt(")) { - String inner = expr.substring(5, expr.length() - 1); - return Math.sqrt(Double.parseDouble(inner)); + // 处理负号 + boolean negative = false; + if (pos[0] < expr.length() && expr.charAt(pos[0]) == '-') { + negative = true; + pos[0]++; } - // 默认返回解析的数字 - try { - return Double.parseDouble(expr); - } catch (NumberFormatException e) { - return new Random().nextInt(50) + 1; + // 解析数字 + 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; } /** @@ -174,4 +266,59 @@ public class ExpressionEvaluator { 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; + } + } } diff --git a/src/MathQuestionGenerator.java b/src/MathQuestionGenerator.java index c16ccc6..02b1784 100644 --- a/src/MathQuestionGenerator.java +++ b/src/MathQuestionGenerator.java @@ -8,11 +8,11 @@ public class MathQuestionGenerator { private Random random; private String[] operators = {"+", "-", "*", "/"}; private String[] trigFunctions = {"sin", "cos", "tan"}; - + public MathQuestionGenerator() { this.random = new Random(); } - + /** * 根据账户类型生成题目 * @param accountType 账户类型(小学、初中、高中) @@ -21,7 +21,7 @@ public class MathQuestionGenerator { */ public List generateQuestions(String accountType, int count) { List questions = new ArrayList<>(); - + for (int i = 0; i < count; i++) { MathQuestion question; switch (accountType) { @@ -39,10 +39,10 @@ public class MathQuestionGenerator { } questions.add(question); } - + return questions; } - + /** * 生成小学题目:只能有+、-、*、/和() */ @@ -50,78 +50,136 @@ public class MathQuestionGenerator { int operatorCount = random.nextInt(4) + 1; // 1-4个操作符 List numbers = new ArrayList<>(); List 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(); - - // 基础部分 - int num1 = random.nextInt(50) + 1; - String op = operators[random.nextInt(operators.length)]; - - expression.append(num1).append(" ").append(op).append(" "); - - // 添加平方或开根号 - if (random.nextBoolean()) { - // 添加平方 - int baseNum = random.nextInt(20) + 1; - expression.append(baseNum).append("²"); - } else { - // 添加开根号 - int sqrtNum = getRandomPerfectSquare(); - expression.append("√").append(sqrtNum); - } - + + // 总共1-5个操作数 + int totalOperandCount = random.nextInt(5) + 1; // 1-5个操作数 + + // 生成所有操作数(1-100范围) + List 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(), "计算结果"); } - + /** * 生成高中题目:至少有一个sin、cos或tan运算符 + * 包含1-5个操作数(1-100范围的数字) */ private MathQuestion generateHighSchoolQuestion() { StringBuilder expression = new StringBuilder(); - - // 基础部分 - int num1 = random.nextInt(100) + 1; - String op = operators[random.nextInt(operators.length)]; - - // 添加三角函数 + + // 总共1-5个操作数(包括三角函数的角度) + int totalOperandCount = random.nextInt(5) + 1; // 1-5个操作数 + + // 生成所有操作数(1-100范围) + List 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)]; - int angle = random.nextInt(360); // 0-359度 - - expression.append(num1).append(" ").append(op).append(" ").append(trigFunc).append("(").append(angle).append("°)"); - + 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(), "计算结果"); } - + /** * 为表达式添加括号 */ @@ -137,7 +195,114 @@ public class MathQuestionGenerator { } 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 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; + } + /** * 获取随机完全平方数 */ @@ -145,4 +310,4 @@ public class MathQuestionGenerator { 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)]; } -} +} \ No newline at end of file diff --git a/src/RegisteredUser.class b/src/RegisteredUser.class deleted file mode 100644 index 4818011505b80ac767c5139aeaa0abd7edcd1806..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1630 zcmaJ>T~E_s6n@@)b&PJzZHzBLKwuvrQ}KI%z#`EE)HuXsUX|??3ZtEEhatxQp})Wz zFEkNNxbV^of0XgOZDHM5%(C~ir_VWk&Uw!1pTDQS0IZ@Efe(HO0U1Gr7~=cNky5BD z+HT?5&OWbN4539;Q>|qNe>S(>gD`p|L}c_rW{ADuyQ*n%gV$b~++YauLq)AKBsN-! z%9f#OyG0QilhG&Q;z!(2->6l^QgyAQ*SL6iNk#%mhKOoz+mCsTfmOt#ei>;I7j7t~ z`A#=#5e#BT!mx}HTyCT7yp3q{w@syPGW2IXT%4kcxk`e{ACqwf;|v3b$^kFw8qs;B zT6=V5T8d^-Y{nbw)=|lM^0~h5$cwn#HbX$v%h0!>YW!*QaEBXP%1)g;;azS$a@v>5 zc6~`NA)dP(VCXfycWq_5w8ZO@6yvnB)ookLij$$$>Oi(40wT6H#}3P`St&B+fLdgt zWlO0ZY$}bjVl4ekiAY&*8dbioiXxNk6EiQyg!Eg}b<4C2rLoDaJ-udzaff=^j(u;> z)>4LL=Ehqivcx|0g>Wx|`*S9vUCc5lf+Nd6%yQ1Ek@#V2MiOh5kls=rjB-|9wTT1c{jC} zaQsUL;Yk9`V$OxOSd6sw#bGf+EHZ?=hIto1;j-?bUPNbqV4yR-;7~Y+7hU)u!BfAH z5Jp6Of)x0SfL}!WY$2R(&f_|Hg7BqRM9*)WS3g5Ap+QV&K>zqDMuE;YERf Date: Sun, 12 Oct 2025 15:52:18 +0800 Subject: [PATCH 4/5] 111 --- ...学习软件 - 完整系统说明文档.md | 336 ++++++++++++++++++ ...算与题目生成模块说明文档.md} | 11 +- 2 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 doc/小初高数学学习软件 - 完整系统说明文档.md rename doc/{README.md => 小初高数学学习软件 - 核心计算与题目生成模块说明文档.md} (98%) diff --git a/doc/小初高数学学习软件 - 完整系统说明文档.md b/doc/小初高数学学习软件 - 完整系统说明文档.md new file mode 100644 index 0000000..85a61a2 --- /dev/null +++ b/doc/小初高数学学习软件 - 完整系统说明文档.md @@ -0,0 +1,336 @@ +# 小初高数学学习软件 - 完整系统说明文档 + +## 项目简介 + +本项目是一个面向小学、初中和高中学生的数学学习软件,采用Java Swing构建图形用户界面,提供用户注册、登录、数学题目生成、答题练习和成绩统计等功能。系统采用模块化设计,具有良好的可扩展性和维护性。 + +## 系统架构 + +### MVC设计模式 + +本项目采用经典的MVC(Model-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 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月 + +--- + +本系统为小初高学生提供了一个完整的数学学习解决方案,结合了现代化的用户界面和强大的数学计算能力,旨在通过有趣的交互方式提升学生的数学学习兴趣和能力。 \ No newline at end of file diff --git a/doc/README.md b/doc/小初高数学学习软件 - 核心计算与题目生成模块说明文档.md similarity index 98% rename from doc/README.md rename to doc/小初高数学学习软件 - 核心计算与题目生成模块说明文档.md index c38307f..86d9481 100644 --- a/doc/README.md +++ b/doc/小初高数学学习软件 - 核心计算与题目生成模块说明文档.md @@ -193,11 +193,11 @@ public static String[] generateWrongAnswers(double correctAnswer, int count) { ### 文件存储结构 ``` 小学/ - 2024-01-15-10-30-25.txt + 2025-10-05-10-30-25.txt 初中/ - 2024-01-15-11-15-40.txt + 2025-10-02-11-15-40.txt 高中/ - 2024-01-15-14-20-35.txt + 2025-10-01-14-20-35.txt ``` ## 接口设计 @@ -205,10 +205,11 @@ public static String[] generateWrongAnswers(double correctAnswer, int count) { ### 对外提供的主要方法 1. **题目生成接口**: + ```java List generateQuestions(String accountType, int count) - ``` - +``` + 2. **表达式计算接口**: ```java double evaluate(String expression) -- 2.34.1 From 6814780dc2b1f65ab52148645203062e5215a6a8 Mon Sep 17 00:00:00 2001 From: liq <3403517738@qq.com> Date: Sun, 12 Oct 2025 16:20:49 +0800 Subject: [PATCH 5/5] 11 --- ...用户界面与交互控制说明文档.md | 233 +++++ src/MathLearningApp.java | 983 ++++++++++++++++++ src/MathLearningAppMethods.java | 464 +++++++++ src/QQEmailService.java | 218 ++++ src/RealEmailSender.java | 176 ++++ src/RegisteredUser.java | 56 + 6 files changed, 2130 insertions(+) create mode 100644 doc/数学学习软件 - 用户界面与交互控制说明文档.md create mode 100644 src/MathLearningApp.java create mode 100644 src/MathLearningAppMethods.java create mode 100644 src/QQEmailService.java create mode 100644 src/RealEmailSender.java create mode 100644 src/RegisteredUser.java diff --git a/doc/数学学习软件 - 用户界面与交互控制说明文档.md b/doc/数学学习软件 - 用户界面与交互控制说明文档.md new file mode 100644 index 0000000..cc00a03 --- /dev/null +++ b/doc/数学学习软件 - 用户界面与交互控制说明文档.md @@ -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` 进行界面布局和用户交互。 + diff --git a/src/MathLearningApp.java b/src/MathLearningApp.java new file mode 100644 index 0000000..aaad1ce --- /dev/null +++ b/src/MathLearningApp.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 registeredUsers; + private RegisteredUser currentUser; + + // 题目管理 + private MathQuestionGenerator questionGenerator; + private List 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("
密码要求:6-10位,必须包含大小写字母和数字
", 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("
" + question.getQuestion() + "
"); + + // 更新选项 - 关键修改:清除之前的选择 + 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("
答对 %d 题,共 %d 题
正确率:%.1f%%
", + 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); + }); + } +} diff --git a/src/MathLearningAppMethods.java b/src/MathLearningAppMethods.java new file mode 100644 index 0000000..53ecb5e --- /dev/null +++ b/src/MathLearningAppMethods.java @@ -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 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 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 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 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 generateQuestions(String difficulty, int count, MathQuestionGenerator generator) { + List 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 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 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 users) { + try { + Path dataFile = Paths.get("user_data.txt"); + if (Files.exists(dataFile)) { + List 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()); + } + } +} diff --git a/src/QQEmailService.java b/src/QQEmailService.java new file mode 100644 index 0000000..0479a5a --- /dev/null +++ b/src/QQEmailService.java @@ -0,0 +1,218 @@ +import java.io.*; +import java.net.*; +import java.util.Properties; +import java.nio.file.*; + +/** + * QQ邮箱SMTP服务 + * 专门用于通过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; + } + } +} diff --git a/src/RealEmailSender.java b/src/RealEmailSender.java new file mode 100644 index 0000000..21c9444 --- /dev/null +++ b/src/RealEmailSender.java @@ -0,0 +1,176 @@ +import java.io.*; +import java.net.*; +import java.util.Base64; +import javax.net.ssl.*; + +/** + * 真实的邮件发送器 + * 使用原生Java实现SMTP协议发送邮件 + */ +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" + + "此邮件由系统自动发送,请勿回复。"; + } +} diff --git a/src/RegisteredUser.java b/src/RegisteredUser.java new file mode 100644 index 0000000..a8d021a --- /dev/null +++ b/src/RegisteredUser.java @@ -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 + + '}'; + } +} -- 2.34.1