diff --git a/src/.idea/.gitignore b/.idea/.gitignore similarity index 100% rename from src/.idea/.gitignore rename to .idea/.gitignore diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml new file mode 100644 index 0000000..639e24e --- /dev/null +++ b/.idea/libraries/lib.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/.idea/misc.xml b/.idea/misc.xml similarity index 100% rename from src/.idea/misc.xml rename to .idea/misc.xml diff --git a/src/.idea/modules.xml b/.idea/modules.xml similarity index 100% rename from src/.idea/modules.xml rename to .idea/modules.xml diff --git a/src/.idea/vcs.xml b/.idea/vcs.xml similarity index 64% rename from src/.idea/vcs.xml rename to .idea/vcs.xml index 6c0b863..35eb1dd 100644 --- a/src/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/MathEaxmApp.iml b/MathEaxmApp.iml similarity index 70% rename from src/MathEaxmApp.iml rename to MathEaxmApp.iml index b107a2d..fb8e866 100644 --- a/src/MathEaxmApp.iml +++ b/MathEaxmApp.iml @@ -3,9 +3,10 @@ - + + \ No newline at end of file diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..114fdaa --- /dev/null +++ b/doc/README.md @@ -0,0 +1,227 @@ +# 数学题目生成器 (图形化界面版) 项目说明文档 + + + + + +## 1. 项目概述 + + + +该项目是一个功能完善的、拥有现代化图形用户界面(GUI)的桌面应用。它旨在为不同年级(小学、初中、高中)的学生提供一个集用户管理、题目练习、自动评分和历史存档于一体的综合性数学学习平台。用户可以通过友好的界面进行注册、登录、选择难度、进行交互式答题,并通过真实的电子邮件接收验证码,确保了账户的安全性。 + + + +## 2. 项目功能 + + + + + +### 2.1 用户管理 + + + +- **用户注册**:提供邮箱和自定义用户名进行注册。系统会向用户提供的邮箱发送一封包含**真实6位验证码**的邮件,用户需凭此验证码完成注册。 +- **密码安全**:支持用户设置符合强度要求(6-10位,含大小写字母和数字)的密码,并在登录状态下安全地修改密码。 +- **双模式登录**:系统支持用户使用**邮箱**或**用户名**两种方式配合密码进行登录,更加便捷。 + + + +### 2.2 题目与考试 + + + +- **智能题目生成**:根据用户选择的年级,动态生成不同复杂度的题目。题目包含2-5个操作数,并能智能地添加有意义的单层或双重括号。 +- **交互式答题**:题目以选择题形式在界面中逐题展示,用户可通过鼠标点击进行选择。 +- **双向导航**:在答题过程中,用户可以使用“上一题”、“下一题”按钮在题目之间自由切换,检查并修改之前的答案。 +- **提前交卷**:用户可在答题未完成时选择提前交卷,系统会进行确认提醒,并根据已完成的题目计分。 +- **自动评分与存档**:答题结束后,系统会立即计算得分(百分制)并展示。同时,每一次生成的完整试卷(包含题目、选项、正确答案)都会自动保存到以该用户命名的专属文件夹中,便于后续复习。 + + + +### 2.3 用户界面 + + + +- **现代化图形界面**:基于 `Java Swing` 和 `FlatLaf` UI库构建,提供了一个美观、清爽、响应流畅的桌面应用体验。 +- **多视图切换**:拥有登录、注册、密码设置、主菜单、考试、分数等多个独立视图,流程清晰,操作直观。 +- **人性化交互**:答题界面提供实时进度条,导航按钮状态会根据当前题号自动更新,为用户提供清晰的操作指引。 + + + +## 3. 系统架构 + + + +项目采用业界标准的 **MVC (Model-View-Controller)** 设计模式,确保了代码的高度解耦、可维护性和可扩展性。 + + + +### 3.1 模型 (Model) + + + +负责处理数据和业务逻辑,完全独立于界面。 + +- **`model` 包**: 定义核心数据结构,如 `User`, `Question`。 +- **`service` 包**: 实现所有后台服务逻辑,如 `UserManager` (用户管理)、`ExamManager` (考试管理) 和 `EmailService` (邮件发送服务)。 + + + +### 3.2 视图 (View) + + + +负责渲染用户界面,是用户直接交互的层面。 + +- **`view` 包**: 包含了所有的UI界面类,如 `LoginView`, `ExamView`, `MainMenuView` 等。这些类只负责“显示”,不包含任何业务逻辑。 + + + +### 3.3 控制器 (Controller) + + + +作为模型和视图之间的桥梁,调度整个应用程序。 + +- **`controller` 包**: 核心类 `AppController` 在此包中。它接收视图层传来的用户操作,调用模型层处理,并根据结果更新视图层的显示。 + + + +## 4. 代码详解 + + + +### 4.1 题目生成规则 + +所有生成器都经过重构,能够生成包含2-5个操作数及有意义括号的复杂表达式。 + +#### 4.1.1 `PrimaryQuestionGenerator` + +为小学用户生成复杂的四则运算题目,智能地使用括号改变运算优先级。 + +``` +((10 + 5) × 3) - 12 +``` + +#### 4.1.2 `JuniorQuestionGenerator` + +在复杂表达式中,确保至少包含一个平方 (`^2`) 或开根号 (`√`) 运算。 + +``` +(8 + √ (49)) × 3^2 +``` + +#### 4.1.3 `SeniorQuestionGenerator` + +在复杂表达式中,确保至少包含一个三角函数 (`sin`, `cos`, `tan`),并已修复 `tan(90°)` 的bug。 + +``` +5 × (sin(30°) + 4) +``` + + + +### 4.2 题目格式与计分 + +- 所有题目均为四选一的选择题。 +- 系统会智能判断正确答案是整数还是小数,并生成逻辑相符的干扰选项。 +- 计分方式为 `(答对题数 / 总题数) * 100`,结果四舍五入取整。 + +## 5. 运行说明 + + + +1. **启动程序** 运行 `Main.java` 类,启动图形化登录窗口。 +2. **用户注册/登录** + - 新用户点击“注册”,按照“输入邮箱/用户名 -> 发送验证邮件 -> 查收邮件验证码 -> 设置密码”的流程完成注册。 + - 老用户可使用邮箱或用户名直接登录。 +3. **开始考试** 在主菜单选择题目难度和数量,点击“开始答题”进入考试界面。 +4. **答题过程** 通过“上一题”、“下一题”进行导航,随时可通过“交卷并评分”按钮结束考试。 +5. **查看分数** 交卷后,分数界面会显示本次得分和答题详情。用户可选择“再来一组”或“退出登录”。 + + + +## 6. 代码结构 + + + +``` +src/ +├── Main.java +├── config.properties +├── controller/ +│ └── AppController.java +├── model/ +│ ├── Question.java +│ ├── User.java +│ └── UserType.java +├── service/ +│ ├── EmailService.java +│ ├── ExamManager.java +│ ├── ExpressionEvaluator.java +│ ├── UserManager.java +│ ├── ValidationService.java +│ └── generator/ +│ └── ... (5个题目生成器相关文件) +└── view/ + └── ... (8个界面视图相关文件) +``` + + + +## 7. 依赖与配置 + +### 软件依赖 (JAR文件) + +本项目需要以下4个外部库文件,以下依赖库以添加至总项目lib文件夹中 + +- **FlatLaf UI主题**: `flatlaf-3.4.1.jar` + +- **Jakarta Mail API**: `jakarta.mail-api-2.1.2.jar` + +- **Angus Mail (实现)**: `angus-mail-2.0.2.jar` + +- **Angus Activation (依赖)**: `angus-activation-2.0.2.jar` + + + +### Windows 系统下运行步骤 + +1. **配置 Java 22 编译环境** + + 确保系统已安装 Java 22 JDK 版本 + +2. **设置控制台编码** + + 打开 cmd 命令行工具,使用以下指令执行 .jar 文件: + + ``` + java -jar .\MathExamApp.jar + ``` + + + +### Linux 系统下运行步骤 + +1. **配置 Java 22 JDK 版本** + + 确保系统已安装 Java 22 JDK + +2. **运行程序** + + 使用以下指令执行程序: + + java -jar MathExamApp.jar + + + +## 8. 注意事项 + + + +1. **数据文件夹**:程序第一次成功注册用户或生成试卷后,会在程序运行的根目录下自动创建以下文件夹: + - `data/`:此文件夹包含 `users.txt` 文件,用于存储所有用户的账号和密码信息。 + - `exams/`:此文件夹下会以每个用户的邮箱/用户名创建子文件夹,用于存放该用户所有历史试卷的 `.txt` 文件。 +2. **请勿删除**:上述两个文件夹是程序正常运行和数据持久化的关键。**请不要手动删除或修改这些文件夹及其中的内容**,否则将导致所有用户账号信息和历史试卷记录丢失,且无法恢复。 \ No newline at end of file diff --git a/lib/activation-1.1.1.jar b/lib/activation-1.1.1.jar deleted file mode 100644 index 1b703ab..0000000 Binary files a/lib/activation-1.1.1.jar and /dev/null differ diff --git a/lib/flatlaf-3.4.1.jar b/lib/flatlaf-3.4.1.jar deleted file mode 100644 index f79f175..0000000 Binary files a/lib/flatlaf-3.4.1.jar and /dev/null differ diff --git a/lib/jakarta.activation-api-2.1.0.jar b/lib/jakarta.activation-api-2.1.0.jar deleted file mode 100644 index b125985..0000000 Binary files a/lib/jakarta.activation-api-2.1.0.jar and /dev/null differ diff --git a/lib/javax.mail-1.6.2.jar b/lib/javax.mail-1.6.2.jar deleted file mode 100644 index 0cd0528..0000000 Binary files a/lib/javax.mail-1.6.2.jar and /dev/null differ diff --git a/src/Main.java b/src/Main.java deleted file mode 100644 index 3a73467..0000000 --- a/src/Main.java +++ /dev/null @@ -1,20 +0,0 @@ -import com.formdev.flatlaf.FlatLightLaf; -import controller.AppController; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - -public class Main { - public static void main(String[] args) { - System.setProperty("awt.useSystemAAFontSettings", "on"); - System.setProperty("swing.aatext", "true"); - - try { - UIManager.setLookAndFeel(new FlatLightLaf()); - } catch (UnsupportedLookAndFeelException e) { - System.err.println("无法设置UI主题: " + e.getMessage()); - } - - SwingUtilities.invokeLater(AppController::new); - } -} \ No newline at end of file diff --git a/src/config.properties b/src/config.properties deleted file mode 100644 index 15e5284..0000000 --- a/src/config.properties +++ /dev/null @@ -1,10 +0,0 @@ - -smtp.host=smtp.qq.com -smtp.port=465 -smtp.ssl.enable=true -smtp.auth=true - - -mail.sender.email=2631495488@qq.com - -mail.sender.password=vdjvddhflexgdjfd \ No newline at end of file diff --git a/src/model/Question.java b/src/model/Question.java deleted file mode 100644 index f60269f..0000000 --- a/src/model/Question.java +++ /dev/null @@ -1,19 +0,0 @@ -package model; - -import java.util.List; - -public class Question { - private final String questionText; - private final List options; - private final int correctOptionIndex; - - public Question(String questionText, List options, int correctOptionIndex) { - this.questionText = questionText; - this.options = options; - this.correctOptionIndex = correctOptionIndex; - } - - public String getQuestionText() { return questionText; } - public List getOptions() { return options; } - public int getCorrectOptionIndex() { return correctOptionIndex; } -} \ No newline at end of file diff --git a/src/model/User.java b/src/model/User.java deleted file mode 100644 index efaf0d7..0000000 --- a/src/model/User.java +++ /dev/null @@ -1,56 +0,0 @@ -package model; - -public class User { - private final String email; - private final String username; // 新增字段 - private String hashedPassword; - private UserType userType; - - public User(String email, String username, String hashedPassword, UserType userType) { - this.email = email; - this.username = username; - this.hashedPassword = hashedPassword; - this.userType = userType; - } - - public String getEmail() { return email; } - public String getUsername() { return username; } // 新增Getter - public String getHashedPassword() { return hashedPassword; } - public UserType getUserType() { return userType; } - public void setUserType(UserType userType) { this.userType = userType; } - - public void setHashedPassword(String plainPassword) { - this.hashedPassword = Integer.toString(plainPassword.hashCode()); - } - - private void setHashedPasswordDirectly(String hashedPassword) { - this.hashedPassword = hashedPassword; - } - - @Override - public String toString() { - // 新的4字段格式,用户名可以为空字符串 - return String.join("|", email, username == null ? "" : username, hashedPassword, userType.name()); - } - - - public static User fromString(String line) { - String[] parts = line.split("\\|"); - User user = null; - if (parts.length == 4) { // 新格式: email|username|password|type - String email = parts[0]; - String username = parts[1].isEmpty() ? null : parts[1]; - String password = parts[2]; - UserType type = UserType.valueOf(parts[3]); - user = new User(email, username, "", type); - user.setHashedPasswordDirectly(password); - } else if (parts.length == 3) { // 旧格式: email|password|type - String email = parts[0]; - String password = parts[1]; - UserType type = UserType.valueOf(parts[2]); - user = new User(email, null, "", type); // 旧用户没有用户名 - user.setHashedPasswordDirectly(password); - } - return user; - } -} \ No newline at end of file diff --git a/src/model/UserType.java b/src/model/UserType.java deleted file mode 100644 index e532963..0000000 --- a/src/model/UserType.java +++ /dev/null @@ -1,17 +0,0 @@ -package model; - -public enum UserType { - PRIMARY("小学"), - JUNIOR("初中"), - SENIOR("高中"); - - private final String displayName; - - UserType(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return displayName; - } -} \ No newline at end of file diff --git a/src/service/EmailService.java b/src/service/EmailService.java deleted file mode 100644 index 27fa9c5..0000000 --- a/src/service/EmailService.java +++ /dev/null @@ -1,74 +0,0 @@ -package service; - -import javax.mail.*; -import javax.mail.internet.*; -import javax.activation.*; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - - -public class EmailService { - - final Properties props = new Properties(); - private final String senderEmail; - private final String senderPassword; - - public EmailService() { - - try (InputStream input = EmailService.class.getClassLoader().getResourceAsStream("config.properties")) { - - - if (input == null) { - System.err.println("致命错误:在类路径中找不到 config.properties 文件!"); - System.err.println("请确认 'config.properties' 文件已放置在 'src' 文件夹根目录下。"); - throw new RuntimeException("加载配置文件失败: config.properties not found in classpath"); - } - - props.load(input); - - } catch (IOException e) { - System.err.println("错误:加载 config.properties 文件时发生IO错误!"); - throw new RuntimeException("加载配置文件失败", e); - } - - this.senderEmail = props.getProperty("mail.sender.email"); - this.senderPassword = props.getProperty("mail.sender.password"); - } - - - public void sendVerificationCode(String recipientEmail, String code) throws MessagingException { - - Properties mailProps = new Properties(); - mailProps.put("mail.smtp.host", props.getProperty("smtp.host")); - mailProps.put("mail.smtp.port", props.getProperty("smtp.port")); - mailProps.put("mail.smtp.ssl.enable", props.getProperty("smtp.ssl.enable")); - mailProps.put("mail.smtp.auth", props.getProperty("smtp.auth")); - - Authenticator authenticator = new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(senderEmail, senderPassword); - } - }; - - - Session session = Session.getInstance(mailProps, authenticator); - - MimeMessage message = new MimeMessage(session); - message.setFrom(new InternetAddress(senderEmail)); - message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail)); - message.setSubject("您的数学学习软件验证码", "UTF-8"); - - - String content = "

欢迎使用数学学习软件!

" + - "

您的注册验证码是:" + code + "

" + - "

该验证码5分钟内有效,请勿泄露给他人。

"; - message.setContent(content, "text/html; charset=UTF-8"); - - - Transport.send(message); - System.out.println("验证码邮件已成功发送至 " + recipientEmail); - } -} \ No newline at end of file diff --git a/src/service/ExamManager.java b/src/service/ExamManager.java deleted file mode 100644 index b57fb34..0000000 --- a/src/service/ExamManager.java +++ /dev/null @@ -1,169 +0,0 @@ -package service; - -import model.Question; -import model.User; -import model.UserType; -import service.generator.*; - -import java.io.PrintWriter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.util.*; - -public class ExamManager { - private final Map generators = new HashMap<>(); - - public ExamManager() { - generators.put(UserType.PRIMARY, new PrimaryQuestionGenerator()); - generators.put(UserType.JUNIOR, new JuniorQuestionGenerator()); - generators.put(UserType.SENIOR, new SeniorQuestionGenerator()); - } - - public List generateExam(User user, UserType userType, int count) { - QuestionGenerator generator = generators.get(userType); - if (generator == null) throw new IllegalArgumentException("不支持的用户类型"); - - List questions = new ArrayList<>(); - Set generatedTexts = new HashSet<>(); - int maxAttempts = count * 20; - int attempts = 0; - - while (questions.size() < count && attempts < maxAttempts) { - String qText = generator.generateQuestion(); - if (generatedTexts.contains(qText)) { - attempts++; - continue; - } - try { - double answer = ExpressionEvaluator.evaluate(qText); - if (Double.isInfinite(answer) || Double.isNaN(answer)) continue; - - List options = generateOptions(answer); // 调用已优化的方法 - int correctIndex = options.indexOf(formatAnswer(answer)); - - questions.add(new Question(qText, options, correctIndex)); - generatedTexts.add(qText); - } catch (Exception e) { - - } finally { - attempts++; - } - } - - if (!questions.isEmpty()) { - saveExamToFile(user, questions); - } - - return questions; - } - - - private List generateOptions(double correctAnswer) { - Set options = new LinkedHashSet<>(); - options.add(formatAnswer(correctAnswer)); - Random rand = new Random(); - - // 判断答案是否为整数 - boolean isIntegerAnswer = Math.abs(correctAnswer - Math.round(correctAnswer)) < 1e-9; - - while (options.size() < 4) { - String distractorStr; - if (isIntegerAnswer) { - - int intAnswer = (int) Math.round(correctAnswer); - int distractor; - int type = rand.nextInt(3); // 三种整数干扰策略 - - switch(type) { - case 0: // 策略一:在答案附近加减一个较小的随机数 - distractor = intAnswer + (rand.nextInt(8) + 1) * (rand.nextBoolean() ? 1 : -1); - break; - case 1: // 策略二:模拟看错位的错误(加减10) - distractor = intAnswer + (rand.nextBoolean() ? 10 : -10); - break; - default: // 策略三:颠倒个位和十位(如果答案是两位数) - if (intAnswer >= 10 && intAnswer <= 99) { - distractor = (intAnswer % 10) * 10 + (intAnswer / 10); - } else { - // 如果不是两位数,则使用策略一 - distractor = intAnswer + (rand.nextInt(8) + 1) * (rand.nextBoolean() ? 1 : -1); - } - break; - } - - // 确保干扰项不等于正确答案 - if (distractor == intAnswer) { - distractor++; - } - distractorStr = String.valueOf(distractor); - - } else { - double distractor; - if (rand.nextBoolean()) { // 乘法干扰 - distractor = correctAnswer * (rand.nextDouble() * 1.5 + 0.5); - } else { // 临近值干扰 - distractor = correctAnswer + (rand.nextDouble() - 0.5) * Math.max(1, Math.abs(correctAnswer * 0.2)); - } - distractorStr = formatAnswer(distractor); - } - - // 确保选项不重复 - if (!options.contains(distractorStr)) { - options.add(distractorStr); - } - } - - List shuffledOptions = new ArrayList<>(options); - Collections.shuffle(shuffledOptions); - return shuffledOptions; - } - - private void saveExamToFile(User user, List questions) { - try { - Path userExamDir = Paths.get("exams", user.getEmail()); - Files.createDirectories(userExamDir); - - String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); - Path examFile = userExamDir.resolve("exam_" + timestamp + ".txt"); - - try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(examFile, StandardCharsets.UTF_8))) { - writer.println("用户: " + user.getEmail()); - writer.println("时间: " + new Date()); - writer.println("题目类型: " + user.getUserType().getDisplayName()); - writer.println("题目数量: " + questions.size()); - writer.println("========================================"); - writer.println(); - - for (int i = 0; i < questions.size(); i++) { - Question q = questions.get(i); - writer.println((i + 1) + ". " + q.getQuestionText()); - - List options = q.getOptions(); - char optionLabel = 'A'; - for (int j = 0; j < options.size(); j++) { - writer.println(" " + optionLabel + ". " + options.get(j)); - optionLabel++; - } - - writer.println(" 正确答案: " + (char)('A' + q.getCorrectOptionIndex())); - writer.println(); - } - } - System.out.println("试卷已成功保存至: " + examFile.toAbsolutePath()); - } catch (IOException e) { - System.err.println("保存试卷文件时出错: " + e.getMessage()); - } - } - - private String formatAnswer(double number) { - if (Math.abs(number - Math.round(number)) < 1e-9) { - return String.valueOf((long) Math.round(number)); - } else { - return String.format("%.2f", number); - } - } -} \ No newline at end of file diff --git a/src/service/ExpressionEvaluator.java b/src/service/ExpressionEvaluator.java deleted file mode 100644 index 912fc7f..0000000 --- a/src/service/ExpressionEvaluator.java +++ /dev/null @@ -1,120 +0,0 @@ -package service; - -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - -public class ExpressionEvaluator { - - - - public static double evaluate(String expression) { - // 1. 预处理,将特殊运算(开方、平方、三角函数)直接计算并替换为数值 - String processedExpr = preprocessSpecialOperations(expression); - - // 2. 使用双栈算法计算最终的四则运算表达式 - return calculateStandardExpression(processedExpr); - } - - - private static String preprocessSpecialOperations(String expr) { - // 替换中文运算符 - expr = expr.replace('×', '*').replace('÷', '/'); - - // 匹配并计算三角函数,例如 sin(30°) - Pattern trigPattern = Pattern.compile("(sin|cos|tan)\\((\\d+)°\\)"); - Matcher trigMatcher = trigPattern.matcher(expr); - while (trigMatcher.find()) { - String func = trigMatcher.group(1); - double angle = Double.parseDouble(trigMatcher.group(2)); - double value = 0.0; - switch (func) { - case "sin": value = Math.sin(Math.toRadians(angle)); break; - case "cos": value = Math.cos(Math.toRadians(angle)); break; - case "tan": value = Math.tan(Math.toRadians(angle)); break; - } - expr = trigMatcher.replaceFirst(String.format("%.4f", value)); - trigMatcher = trigPattern.matcher(expr); - } - - // 匹配并计算平方,例如 5^2 - Pattern squarePattern = Pattern.compile("(\\d+)\\^2"); - Matcher squareMatcher = squarePattern.matcher(expr); - while (squareMatcher.find()) { - double base = Double.parseDouble(squareMatcher.group(1)); - expr = squareMatcher.replaceFirst(String.valueOf(base * base)); - squareMatcher = squarePattern.matcher(expr); - } - - // 匹配并计算开方,例如 √(16) - Pattern sqrtPattern = Pattern.compile("√\\((\\d+)\\)"); - Matcher sqrtMatcher = sqrtPattern.matcher(expr); - while (sqrtMatcher.find()) { - double num = Double.parseDouble(sqrtMatcher.group(1)); - expr = sqrtMatcher.replaceFirst(String.valueOf(Math.sqrt(num))); - sqrtMatcher = sqrtPattern.matcher(expr); - } - - return expr; - } - - - private static double calculateStandardExpression(String expression) { - Stack values = new Stack<>(); - Stack ops = new Stack<>(); - char[] tokens = expression.toCharArray(); - - for (int i = 0; i < tokens.length; i++) { - if (tokens[i] == ' ') continue; - - if ((tokens[i] >= '0' && tokens[i] <= '9') || tokens[i] == '.') { - StringBuilder sbuf = new StringBuilder(); - while (i < tokens.length && ((tokens[i] >= '0' && tokens[i] <= '9') || tokens[i] == '.')) { - sbuf.append(tokens[i++]); - } - i--; - values.push(Double.parseDouble(sbuf.toString())); - } else if (tokens[i] == '(') { - ops.push(tokens[i]); - } else if (tokens[i] == ')') { - while (ops.peek() != '(') { - values.push(applyOp(ops.pop(), values.pop(), values.pop())); - } - ops.pop(); - } else if (isOperator(tokens[i])) { - while (!ops.empty() && hasPrecedence(tokens[i], ops.peek())) { - values.push(applyOp(ops.pop(), values.pop(), values.pop())); - } - ops.push(tokens[i]); - } - } - - while (!ops.empty()) { - values.push(applyOp(ops.pop(), values.pop(), values.pop())); - } - - return values.pop(); - } - - private static boolean isOperator(char c) { - return c == '+' || c == '-' || c == '*' || c == '/'; - } - - private static boolean hasPrecedence(char op1, char op2) { - if (op2 == '(' || op2 == ')') return false; - return (op1 != '*' && op1 != '/') || (op2 != '+' && op2 != '-'); - } - - private static double applyOp(char op, double b, double a) { - switch (op) { - case '+': return a + b; - case '-': return a - b; - case '*': return a * b; - case '/': - if (b == 0) throw new UnsupportedOperationException("不能除以零"); - return a / b; - } - return 0; - } -} \ No newline at end of file diff --git a/src/service/UserManager.java b/src/service/UserManager.java deleted file mode 100644 index 00f0a29..0000000 --- a/src/service/UserManager.java +++ /dev/null @@ -1,97 +0,0 @@ -package service; - -import model.User; -import model.UserType; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.util.*; - - - -public class UserManager { - private final Path usersFile = Paths.get("data", "users.txt"); - private final Map usersByEmail = new HashMap<>(); // 使用邮箱作为主键 - - public UserManager() { - try { - Files.createDirectories(usersFile.getParent()); - if (!Files.exists(usersFile)) Files.createFile(usersFile); - loadUsers(); - } catch (IOException e) { - System.err.println("初始化用户管理器时出错: " + e.getMessage()); - } - } - - private void loadUsers() throws IOException { - List lines = Files.readAllLines(usersFile, StandardCharsets.UTF_8); - for (String line : lines) { - User user = User.fromString(line); - if (user != null) usersByEmail.put(user.getEmail(), user); - } - } - - private void saveUsers() { - try (BufferedWriter writer = Files.newBufferedWriter(usersFile, StandardCharsets.UTF_8)) { - for (User user : usersByEmail.values()) writer.write(user.toString() + "\n"); - } catch (IOException e) { - System.err.println("保存用户时出错: " + e.getMessage()); - } - } - - - - public User authenticate(String identifier, String password) { - String hashedInput = hashPassword(password); - - // 策略一:尝试将 identifier 作为邮箱进行快速查找 - User userByEmail = usersByEmail.get(identifier); - if (userByEmail != null && userByEmail.getHashedPassword().equals(hashedInput)) { - return userByEmail; // 邮箱匹配成功 - } - - // 策略二:如果邮箱查找失败,则遍历所有用户,尝试匹配用户名 - for (User user : usersByEmail.values()) { - if (user.getUsername() != null && user.getUsername().equals(identifier)) { - if (user.getHashedPassword().equals(hashedInput)) { - return user; // 用户名匹配成功 - } - break; // 用户名是唯一的,找到后无需继续遍历 - } - } - - return null; // 认证失败 - } - - public boolean emailExists(String email) { - return usersByEmail.containsKey(email); - } - - public boolean usernameExists(String username) { - if (username == null || username.trim().isEmpty()) return false; - for (User user : usersByEmail.values()) { - if (username.equals(user.getUsername())) { - return true; - } - } - return false; - } - - public void registerUser(String email, String username, String password, UserType type) { - User newUser = new User(email, username, "", type); - newUser.setHashedPassword(password); - usersByEmail.put(email, newUser); - saveUsers(); - } - - public void updateUser(User user) { - if (usersByEmail.containsKey(user.getEmail())) { - usersByEmail.put(user.getEmail(), user); - saveUsers(); - } - } - - private String hashPassword(String password) { - return Integer.toString(password.hashCode()); - } -} \ No newline at end of file diff --git a/src/service/ValidationService.java b/src/service/ValidationService.java deleted file mode 100644 index d3f839f..0000000 --- a/src/service/ValidationService.java +++ /dev/null @@ -1,25 +0,0 @@ -package service; - - -public class ValidationService { - public static boolean isValidEmail(String email) { - if (email == null || email.trim().isEmpty()) { - return false; - } - String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; - return email.matches(emailRegex); - } - - public static boolean isPasswordStrong(String password) { - if (password == null || password.length() < 6 || password.length() > 10) { - return false; - } - boolean hasUpperCase = password.matches(".*[A-Z].*"); - boolean hasLowerCase = password.matches(".*[a-z].*"); - boolean hasDigit = password.matches(".*[0-9].*"); - - return hasUpperCase && hasLowerCase && hasDigit; - } -} - - diff --git a/src/service/generator/AbstractQuestionGenerator.java b/src/service/generator/AbstractQuestionGenerator.java deleted file mode 100644 index f230a5e..0000000 --- a/src/service/generator/AbstractQuestionGenerator.java +++ /dev/null @@ -1,61 +0,0 @@ -package service.generator; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; - -public abstract class AbstractQuestionGenerator implements QuestionGenerator { - protected final Random rand = new Random(); - private static final char[] OPS = {'+', '-', '×', '÷'}; - - protected String buildComplexExpression(List operands) { - int numOperands = operands.size(); - if (numOperands < 2) return operands.get(0); // 如果只有一个操作数,直接返回 - - List operators = new ArrayList<>(); - for (int i = 0; i < numOperands - 1; i++) { - operators.add(OPS[rand.nextInt(OPS.length)]); - } - - int parensType = rand.nextInt(5); // 增加不加括号的概率 - - switch (parensType) { - case 1: // 单括号 (a op b) op c ... - if (numOperands >= 3) { - Collections.swap(operands, 0, rand.nextInt(numOperands)); - Collections.swap(operands, 1, rand.nextInt(numOperands)); - return String.format("(%s %c %s) %c %s", operands.get(0), operators.get(0), operands.get(1), operators.get(1), buildRest(operands, operators, 2)); - } - break; - case 2: // a op (b op c) op d ... - if (numOperands >= 3) { - return String.format("%s %c (%s %c %s)", operands.get(0), operators.get(0), operands.get(1), operators.get(1), buildRest(operands, operators, 2)); - } - break; - case 3: // 双重括号 ((a op b) op c) op d ... - if (numOperands >= 4) { - return String.format("((%s %c %s) %c %s) %c %s", operands.get(0), operators.get(0), operands.get(1), operators.get(1), operands.get(2), operators.get(2), buildRest(operands, operators, 3)); - } - break; - case 4: // 双重括号 a op (b op (c op d)) ... - if (numOperands >= 4) { - return String.format("%s %c (%s %c (%s %c %s))", operands.get(0), operators.get(0), operands.get(1), operators.get(1), operands.get(2), operators.get(2), buildRest(operands, operators, 3)); - } - break; - } - - // 默认情况:无括号或无法应用括号 - return buildRest(operands, operators, 0); - } - - private String buildRest(List operands, List operators, int startIndex) { - StringBuilder sb = new StringBuilder(); - sb.append(operands.get(startIndex)); - for (int i = startIndex; i < operators.size(); i++) { - sb.append(" ").append(operators.get(i)).append(" "); - sb.append(operands.get(i + 1)); - } - return sb.toString(); - } -} \ No newline at end of file diff --git a/src/service/generator/JuniorQuestionGenerator.java b/src/service/generator/JuniorQuestionGenerator.java deleted file mode 100644 index 4a400b6..0000000 --- a/src/service/generator/JuniorQuestionGenerator.java +++ /dev/null @@ -1,49 +0,0 @@ -package service.generator; - -import java.util.Random; -public class JuniorQuestionGenerator implements QuestionGenerator { - private final Random rand = new Random(); - - @Override - public String generateQuestion() { - int numOperands = rand.nextInt(4) + 2; // 2-5个操作数 - String[] operands = new String[numOperands]; - - // 先填充普通数字 - for (int i = 0; i < numOperands; i++) { - operands[i] = String.valueOf(num(15)); - } - - // 随机替换一个为特殊运算 - int specialIndex = rand.nextInt(numOperands); - operands[specialIndex] = specialOp(); - - // 根据操作数数量选择模板 - switch (numOperands) { - case 2: - return String.format("%s %c %s", operands[0], op(), operands[1]); - case 3: - return String.format("(%s %c %s) %c %s", operands[0], opLow(), operands[1], opHigh(), operands[2]); - case 4: - return String.format("(%s %c %s) %c (%s %c %s)", operands[0], opHigh(), operands[1], opLow(), operands[2], opLow(), operands[3]); - case 5: - default: - return String.format("((%s %c %s) %c %s) %c (%s %c %s)", operands[0], opLow(), operands[1], opHigh(), operands[2], opLow(), operands[3], opLow(), operands[4]); - } - } - - // --- 辅助方法 --- - private int num(int bound) { return rand.nextInt(bound) + 1; } - private char op() { return "+-×".charAt(rand.nextInt(3)); } - private char opLow() { return "+-".charAt(rand.nextInt(2)); } - private char opHigh() { return '×'; } - - private String specialOp() { - if (rand.nextBoolean()) { - return (rand.nextInt(9) + 2) + "^2"; // 2^2 to 10^2 - } else { - int base = rand.nextInt(10) + 1; - return "√(" + (base * base) + ")"; - } - } -} \ No newline at end of file diff --git a/src/service/generator/PrimaryQuestionGenerator.java b/src/service/generator/PrimaryQuestionGenerator.java deleted file mode 100644 index 5ac4c1c..0000000 --- a/src/service/generator/PrimaryQuestionGenerator.java +++ /dev/null @@ -1,69 +0,0 @@ -package service.generator; - -import java.util.Random; - -public class PrimaryQuestionGenerator implements QuestionGenerator { - private final Random rand = new Random(); - - @Override - public String generateQuestion() { - int numOperands = rand.nextInt(4) + 2; // 随机生成2-5个操作数 - - switch (numOperands) { - case 2: - return simpleExpression(num(50), op(), num(50)); - case 3: - return generateThreeOperandExpression(); - case 4: - return generateFourOperandExpression(); - case 5: - default: - return generateFiveOperandExpression(); - } - } - - private String generateThreeOperandExpression() { - if (rand.nextBoolean()) { - // 模板: (a + b) * c - return String.format("(%d %c %d) %c %d", num(20), opLow(), num(20), opHigh(), num(10)); - } else { - // 模板: a * (b - c) - - int b = num(29) + 1; // b 的范围是 2 到 30 - int c = num(b - 1); // c 的范围是 1 到 b-1,确保 c < b - return String.format("%d %c (%d %c %d)", num(10), opHigh(), b, opLow(), c); - } - } - - private String generateFourOperandExpression() { - if (rand.nextBoolean()) { - // 模板: ((a + b) * c) - d - return String.format("((%d %c %d) %c %d) %c %d", num(10), opLow(), num(10), opHigh(), num(5), opLow(), num(20)); - } else { - // 模板: a * (b - (c + d)) - return String.format("%d %c (%d %c (%d %c %d))", num(5), opHigh(), num(50), opLow(), num(10), opLow(), num(10)); - } - } - - private String generateFiveOperandExpression() { - // 模板: (a * b) + (c - d) * e - int c = num(29) + 1; // c 的范围是 2 到 30 - int d = num(c - 1); // d 的范围是 1 到 c-1,确保 d < c - return String.format("(%d %c %d) %c (%d %c %d) %c %d", num(10), opHigh(), num(10), opLow(), c, opLow(), d, opHigh(), num(5)); - } - - // --- 辅助方法 --- - private int num(int bound) { - // 健壮性检查,防止外部调用传入无效的bound - if (bound <= 0) return 1; - return rand.nextInt(bound) + 1; - } - private char op() { return "+-×÷".charAt(rand.nextInt(4)); } - private char opLow() { return "+-".charAt(rand.nextInt(2)); } // 低优先级 - private char opHigh() { return "×÷".charAt(rand.nextInt(2)); } // 高优先级 - private String simpleExpression(int a, char op, int b) { - if (op == '÷') a = b * num(10); - if (op == '-') a = Math.max(a, b + 1); - return String.format("%d %c %d", a, op, b); - } -} \ No newline at end of file diff --git a/src/service/generator/QuestionGenerator.java b/src/service/generator/QuestionGenerator.java deleted file mode 100644 index 81d2230..0000000 --- a/src/service/generator/QuestionGenerator.java +++ /dev/null @@ -1,5 +0,0 @@ -package service.generator; - -public interface QuestionGenerator { - String generateQuestion(); -} \ No newline at end of file diff --git a/src/service/generator/SeniorQuestionGenerator.java b/src/service/generator/SeniorQuestionGenerator.java deleted file mode 100644 index 0d882b5..0000000 --- a/src/service/generator/SeniorQuestionGenerator.java +++ /dev/null @@ -1,51 +0,0 @@ -package service.generator; - -import java.util.Random; - -public class SeniorQuestionGenerator implements QuestionGenerator { - private final Random rand = new Random(); - private static final String[] TRIG_FUNCS = {"sin", "cos", "tan"}; - private static final int[] COMMON_ANGLES = {0, 30, 45, 60, 90,135,180}; - private static final int[] TAN_SAFE_ANGLES = {0, 30, 45, 60,135}; - - @Override - public String generateQuestion() { - int numOperands = rand.nextInt(3) + 2; // 2-4个操作数以控制复杂度 - String[] operands = new String[numOperands]; - - for (int i = 0; i < numOperands; i++) { - operands[i] = String.valueOf(num(10)); - } - - int specialIndex = rand.nextInt(numOperands); - operands[specialIndex] = specialOp(); - - switch (numOperands) { - case 2: - return String.format("%s %c %s", operands[0], op(), operands[1]); - case 3: - return String.format("(%s %c %s) %c %s", operands[0], opLow(), operands[1], opHigh(), operands[2]); - case 4: - default: - return String.format("%s %c (%s %c (%s %c %s))", operands[0], opHigh(), operands[1], opLow(), operands[2], opLow(), operands[3]); - } - } - - // --- 辅助方法 --- - private int num(int bound) { return rand.nextInt(bound) + 1; } - private char op() { return "+-×".charAt(rand.nextInt(3)); } - private char opLow() { return "+-".charAt(rand.nextInt(2)); } - private char opHigh() { return '×'; } - - private String specialOp() { - String func = TRIG_FUNCS[rand.nextInt(TRIG_FUNCS.length)]; - int angle; - - if (func.equals("tan")) { - angle = TAN_SAFE_ANGLES[rand.nextInt(TAN_SAFE_ANGLES.length)]; - } else { - angle = COMMON_ANGLES[rand.nextInt(COMMON_ANGLES.length)]; - } - return String.format("%s(%d°)", func, angle); - } -} \ No newline at end of file