1 #5

Closed
hnu202326010422 wants to merge 7 commits from yuxiuhui_branch into wangtengyun_branch

@ -1,2 +1 @@
# partner_project

@ -0,0 +1,208 @@
# 小初高数学学习软件 - 结对编程项目介绍
## 一、系统概述
本系统是一个基于 Java 和 JavaFX 技术实现的桌面应用程序旨在为小学、初中及高中学生提供一个交互式的数学学习与练习平台。系统完全通过图形化界面UI进行操作涵盖了从用户注册、登录、密码管理到生成个性化试卷、在线答题和自动评分的全过程。项目严格遵循无数据库要求所有用户数据和生成的试卷均通过本地文件进行存储和管理。
## 二、项目目录结构
```
partner_project/
├── .gitignore # Git忽略配置文件用于排除IDE配置和编译产物
├── pom.xml # Maven配置文件 (如果使用Maven)
├── src/ # 源代码目录
│ ├── auth/ # 用户认证与管理模块
│ │ ├── User.java
│ │ └── AuthService.java
│ ├── generator/ # 题目生成模块
│ │ ├── Problem.java
│ │ ├── ProblemGenerator.java
│ │ ├── PrimaryGenerator.java
│ │ ├── MiddleGenerator.java
│ │ └── HighGenerator.java
│ ├── service/ # 业务逻辑服务模块
│ │ └── ExamService.java
│ ├── ui/ # JavaFX界面控制器模块
│ │ ├── MainApplication.java
│ │ ├── LoginController.java
│ │ ├── RegisterController.java
│ │ ├── ModifyPasswordController.java
│ │ └── ExamController.java
│ ├── util/ # 工具类模块
│ │ ├── EmailService.java
│ │ ├── ExpressionUtils.java
│ │ ├── FileUtils.java
│ │ └── ModifyUtils.java
│ └── Main.java # 主程序入口
└── resources/ # 资源文件目录
├── fxml/ # FXML界面布局文件
│ ├── Login.fxml
│ ├── register-email.fxml
│ └── ...
├── images/ # 存放应用的图片资源
│ ├── logo.png
│ └── background.png
└── styles/ # CSS样式文件
└── style.css
```
## 三、核心功能
1. **用户管理模块**
- **邮箱注册**: 新用户需通过邮箱接收验证码以完成注册 。
- **登录与密码**: 用户使用用户名或邮箱和密码登录。密码长度为6-10位且必须包含大小写字母和数字 2。
- **密码修改**: 用户在登录状态下,可通过验证旧密码来设置新密码 。
2. **试卷生成与答题模块**
- **难度选择**: 用户登录后可选择“小学”、“初中”、“高中”三种难度级别 。
- **个性化试卷**: 用户可指定生成的题目数量 5。系统确保同一张试卷内的题目不会重复且题目均以选择题形式呈现 6。
- **在线答题**: 界面逐一展示题目及其选项,用户选择答案后进入下一题,直至完成所有题目 。
3. **评分与反馈模块**
- **自动评分**: 完成答题后,系统会根据用户的答题正确率自动计算并显示最终得分 。
- **流程选择**: 在分数界面,用户可以选择继续做题(返回难度选择界面)或退出程序 。
## 四、主要包结构与核心类介绍
### (一) 主要包结构
- `auth`: 负责用户认证与管理,包括用户信息的读取、验证、注册和修改。
- `generator`: 负责数学题目的生成逻辑,定义了题目生成的标准接口和针对不同难度的具体实现。
- `service`: 封装核心业务逻辑,协调 `auth``generator` 包,提供生成试卷、计算分数等服务。
- `ui`: 包含所有 JavaFX 的界面控制器,负责处理用户交互和界面跳转。
- `util`: 提供项目所需的通用工具类,如文件操作、邮件发送和表达式计算等。
- `resources/fxml`: 存放所有界面的 FXML 布局文件。
### (二) 核心类介绍
#### 1. 认证与数据模块 (`auth`)
- `User.java`: 用户数据模型,用于封装用户的基本信息,如用户名、密码、学习阶段和邮箱。
- `AuthService.java`: 用户服务核心类。负责处理所有与用户相关的操作,包括登录验证、新用户注册、检查用户名或邮箱是否被占用、以及更新密码和用户难度等级。
#### 2. 题目生成模块 (`generator`)
- `Problem.java`: 题目数据模型,封装了一道题目的所有信息,包括题干表达式、四个选项、正确答案和计算结果。
- `ProblemGenerator.java`: **题目生成器接口**,定义了 `generateProblems(int count)` 的标准方法,确保所有生成器都具有统一的行为。
- `PrimaryGenerator.java`, `MiddleGenerator.java`, `HighGenerator.java`: `ProblemGenerator` 接口的具体实现类,分别用于生成小学、初中和高中难度的数学题目。
#### 3. 业务服务模块 (`service`)
- `ExamService.java`: 考试服务类。它是业务逻辑的中心,负责调用 `ProblemGenerator` 生成指定数量且不重复的试卷,并根据用户的答案计算最终得分。
#### 4. 界面控制模块 (`ui`)
- `MainApplication.java`: JavaFX 应用的入口,负责加载初始界面 (Login.fxml) 并启动窗口。
- `LoginController.java`: 控制登录、跳转注册和修改密码界面的逻辑。
- `RegisterController.java`: 控制用户注册的全过程,包括发送验证码、验证、设置用户名和密码等多个步骤。
- `ExamController.java`: 最核心的界面控制器,负责处理难度选择、试卷生成、题目展示、答案提交、界面跳转以及最终分数展示的所有逻辑。
#### 5. 工具类模块 (`util`)
- `ExpressionUtils.java`: 表达式工具类,提供了计算字符串表达式结果、生成干扰选项等核心数学计算功能。
- `FileUtils.java`: 文件操作工具类,封装了对用户历史题目文件的读取和新试卷的保存功能,是实现题目去重的基础。
- `EmailService.java`: 邮件服务类,负责生成验证码并通过 SMTP 服务器发送邮件给注册用户。
- `ModifyUtils.java`: 一个通用的文件修改工具,用于根据用户名精确地修改 `user.txt` 文件中特定字段(如密码或难度)。
## 五、数据存储
- **用户信息**: 存储在项目根目录下的 `user.txt` 文件中,每行代表一个用户,格式为:`用户名 密码 学习阶段 邮箱`。
- **用户试卷**: 每个用户生成的试卷和答题历史都保存在 `exams/` 目录下以其用户名命名的子文件夹中,用于实现题目去重功能。
## 六、题目特点
- **小学难度**: 包含2到5个操作数的加、减、乘、除运算并随机引入括号以增加复杂性。确保答案为非负整数。
- **初中难度**: 在小学基础上,增加了平方(`^2`)和开平方根(`sqrt()`)运算。
- **高中难度**: 在初中基础上,引入了三角函数 (`sin`, `cos`, `tan`) 运算。
## 七、使用流程
1. 启动 `Main.java` 中的 `MainApplication`,应用显示登录界面。
2. **首次使用**: 点击“注册账户”按钮,通过邮箱验证流程创建新账户并设置密码 。
3. **登录**: 输入用户名/邮箱和密码进行登录。
4. **难度与数量选择**: 登录成功后,进入难度选择界面。选择一个学段(小学/初中/高中并输入希望生成的题目数量10-30
5. **开始答题**: 点击“生成试卷”按钮,系统会加载并显示第一道题。依次回答所有题目 。
6. **查看分数**: 完成最后一题后,界面跳转至分数展示页面 。
7. **后续操作**: 在分数页面,可选择“继续做题”返回难度选择界面,或选择“退出登录”、“退出程序” 。

@ -0,0 +1,8 @@
import ui.MainApplication;
public class Main {
public static void main(String[] args) {
// R1: 启动图形化界面应用 (JavaFX)
MainApplication.launch(MainApplication.class, args);
}
}

@ -0,0 +1,206 @@
package auth;
import util.ModifyUtils;
import util.EmailService;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;
public class AuthService {
private static final String USER_FILE_PATH = "user.txt";
private final EmailService emailService = new EmailService();
public AuthService() {
// 检查用户文件是否存在
File userFile = new File(USER_FILE_PATH);
if (!userFile.exists()) {
System.err.println("用户文件不存在: " + USER_FILE_PATH);
try {
// 尝试创建文件
if (userFile.createNewFile()) {
System.out.println("已创建用户文件: " + USER_FILE_PATH);
}
} catch (IOException e) {
System.err.println("创建用户文件失败: " + e.getMessage());
}
}
}
// --- 校验和辅助方法 ---
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$");
// R3: 密码校验: 6-10 位, 必须含大小写字母和数字
public static boolean isPasswordValid(String password) {
if (password == null || password.length() < 6 ||
password.length() > 10) {
return false;
}
Pattern pattern =
Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$");
return pattern.matcher(password).matches();
}
public boolean validateEmail(String email) {
if (email == null){
return false;
}
return EMAIL_PATTERN.matcher(email).matches();
}
// --- 用户加载和保存 ---
private User parseLine(String line, int lineNumber) {
line = line.trim();
if (line.isEmpty()) {
return null;
}
String[] parts = line.split("\\s+");
if (parts.length != 4) {
System.out.println("警告: 第" + lineNumber +
"行数据格式不正确需要4个数据。");
return null;
}
return new User(parts[0], parts[1], parts[2], parts[3]);
}
public List<User> loadUsers() {
List<User> users = new ArrayList<>();
int lineNumber = 1;
try (BufferedReader br = new BufferedReader(new FileReader(USER_FILE_PATH))) {
String line;
while ((line = br.readLine()) != null) {
User user = parseLine(line, lineNumber);
if (user != null) {
users.add(user);
}
lineNumber++;
}
} catch (IOException e) {
System.out.println("读取文件时发生错误: " + e.getMessage());
}
return users;
}
// --- 认证和注册 ---
public User login(String username, String password) {
List<User> users = loadUsers();
for (User user : users) {
if ((user.getUsername().equals(username) || user.getEmail().equals(username)) &&
user.getPassword().equals(password)) {
return user;
}
}
return null;
}
public boolean isUsernameOrEmailTaken(String username, String email) {
List<User> users = loadUsers();
for (User user : users) {
if ((username != null && user.getUsername().equals(username)) ||
(email != null && user.getEmail().equals(email))) {
return true;
}
}
return false;
}
// R2: 实际发送验证邮件
public String sendVerificationCode(String email) {
if (!validateEmail(email) || isUsernameOrEmailTaken(null, email)) {
return null;
}
String code = emailService.generateVerificationCode();
if (emailService.sendVerificationEmail(email, code)) {
return code;
}
return null;
}
private void writeUserData(String userData) throws IOException {
try (BufferedWriter writer =
new BufferedWriter(new FileWriter(USER_FILE_PATH, true))) {
writer.write(userData);
writer.newLine();
}
}
// R2: 注册用户
public boolean registerUser(String username, String password, String email) {
if (isUsernameOrEmailTaken(username, email)) {
return false;
}
// 格式: username password type email
String userData = username + " " + password + " 小学 " + email;
try {
writeUserData(userData);
return true;
} catch (IOException e) {
System.out.println("写入文件时发生错误: " + e.getMessage());
return false;
}
}
// --- 用户修改 ---
public boolean updatePassword(String username, String newPassword) {
// 索引 1 是密码
int result = ModifyUtils.modifyFileField(USER_FILE_PATH, username, 1, newPassword);
return result != -1;
}
public boolean updateDifficulty(String username, String newDifficulty) {
List<String> lines = new ArrayList<>();
boolean found = false;
try (BufferedReader br = new BufferedReader(new FileReader(USER_FILE_PATH))) {
String line;
while ((line = br.readLine()) != null) {
String[] parts = line.trim().split("\\s+");
if (parts.length >= 4 && parts[0].equals(username)) {
// 找到目标用户,更新难度
parts[2] = newDifficulty;
line = String.join(" ", parts);
found = true;
}
lines.add(line);
}
} catch (IOException e) {
System.err.println("读取用户文件错误: " + e.getMessage());
return false;
}
if (!found) {
System.err.println("未找到用户: " + username);
return false;
}
// 写回文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter(USER_FILE_PATH))) {
for (String line : lines) {
bw.write(line);
bw.newLine();
}
return true;
} catch (IOException e) {
System.err.println("写入用户文件错误: " + e.getMessage());
return false;
}
}
}

@ -0,0 +1,20 @@
package auth;
public class User {
private String username;
private String password;
private String type;
private String email;
public User(String username, String password, String type, String email) {
this.username = username;
this.password = password;
this.type = type;
this.email = email;
}
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getType() { return type; }
public String getEmail() { return email; }
}

@ -0,0 +1,13 @@
package auth;
public class UserManager {
private final AuthService authService;
public UserManager() {
this.authService = new AuthService();
}
public User login(String username, String password) {
return authService.login(username, password);
}
}

@ -0,0 +1,28 @@
package generator;
import util.ExpressionUtils;
import java.util.*;
public class HighGenerator implements ProblemGenerator {
@Override
public List<Problem> generateProblems(int count) {
List<Problem> problems = new ArrayList<>();
int attempts = 0;
final int maxAttempts = count * 3; // 最多尝试3倍数量
while (problems.size() < count && attempts < maxAttempts) {
Problem problem = ExpressionUtils.generateHighExpr();
if (problem != null) {
problems.add(problem);
}
attempts++;
}
// 如果无法生成足够题目,用简单题目填充
while (problems.size() < count) {
problems.add(ExpressionUtils.createProblem("sin(30) + " + (problems.size() + 1)));
}
return problems;
}
}

@ -0,0 +1,15 @@
package generator;
import util.ExpressionUtils;
import java.util.*;
public class MiddleGenerator implements ProblemGenerator {
@Override
public List<Problem> generateProblems(int count) {
List<Problem> problems = new ArrayList<>();
for (int i = 0; i < count; i++) {
problems.add(ExpressionUtils.generateMiddleExpr());
}
return problems;
}
}

@ -0,0 +1,15 @@
package generator;
import util.ExpressionUtils;
import java.util.*;
public class PrimaryGenerator implements ProblemGenerator {
@Override
public List<Problem> generateProblems(int count) {
List<Problem> problems = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
problems.add( ExpressionUtils.generatePrimaryExpr());
}
return problems;
}
}

@ -0,0 +1,61 @@
package generator;
import java.util.List;
import java.util.Collections;
public class Problem {
private final String expression;
private final double result;
private final List<String> options;
private final String correctAnswerOption;
public Problem(String expression, double result, List<String> options,
String correctAnswerOption) {
this.expression = expression;
this.result = result;
this.options = options;
this.correctAnswerOption = correctAnswerOption;
}
public String getQuestionText() {
return "计算: " + expression + " = ?";
}
public String getExpression() {
return expression;
}
public List<String> getOptions() {
return Collections.unmodifiableList(options);
}
public String getCorrectAnswerOption() {
return formatResult(result);
}
private String formatResult(double result) {
if (Double.isNaN(result)) {
return "Error";
}
if (Math.abs(result - Math.round(result)) < 0.0001) {
return String.valueOf((int) Math.round(result));
}
return String.format("%.2f", result);
}
public double getResult() {
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getQuestionText()).append("\n");
char optionChar = 'A';
for (String option : options) {
sb.append(optionChar++).append(". ").append(option).append(" ");
}
sb.append("\n正确答案: ").append(correctAnswerOption);
return sb.toString();
}
}

@ -0,0 +1,7 @@
package generator;
import java.util.List;
public interface ProblemGenerator {
List<Problem> generateProblems(int count);
}

@ -0,0 +1,109 @@
package service;
import auth.User;
import generator.Problem;
import generator.ProblemGenerator;
import generator.PrimaryGenerator;
import generator.MiddleGenerator;
import generator.HighGenerator;
import util.FileUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ExamService {
public ProblemGenerator getGenerator(String type) {
switch (type) {
case "小学": return new PrimaryGenerator();
case "初中": return new MiddleGenerator();
case "高中": return new HighGenerator();
default: return null;
}
}
private Problem generateUniqueProblem(ProblemGenerator generator,
Set<String> historyExpressions) {
int attempts = 0;
final int maxAttemptsPerProblem = 1000;
while (attempts < maxAttemptsPerProblem) {
Problem problem = generator.generateProblems(1).get(0);
// 查重基于题干 (表达式字符串)
if (!historyExpressions.contains(problem.getExpression())) {
historyExpressions.add(problem.getExpression());
return problem;
}
attempts++;
}
return null;
}
// R5: 生成一张唯一试卷,并将其保存到用户的历史记录中。
public List<Problem> generateExam(User currentUser, int count) {
if (count < 10 || count > 30) {
throw new IllegalArgumentException("题目数量必须在 10 到 30 之间");
}
ProblemGenerator generator = getGenerator(currentUser.getType());
if (generator == null) {
System.err.println("错误: 无法获取题目生成器,用户类型: " + currentUser.getType());
return new ArrayList<>();
}
Set<String> historyExpressions = FileUtils.loadHistory(currentUser.getUsername());
List<Problem> problems = new ArrayList<>();
for(int i = 0; i < count; i++) {
Problem problem = generateUniqueProblem(generator, historyExpressions);
if (problem != null) {
problems.add(problem);
} else {
System.err.println("警告: 无法生成足够数量的唯一题目。已生成: " + problems.size() + " 题");
break;
}
}
if (!problems.isEmpty()) {
FileUtils.saveProblems(currentUser.getUsername(), problems);
} else {
System.err.println("错误: 未能生成任何题目");
}
return problems;
}
// 将这个私有方法改为公共方法,以便在其他类中调用
public boolean isAnswerCorrect(Problem problem, String userAnswer) {
if (userAnswer == null || problem.getCorrectAnswerOption() == null) {
return false;
}
boolean isCorrect = userAnswer.trim().equalsIgnoreCase(problem.getCorrectAnswerOption().trim());
return isCorrect;
}
// R6: 根据答对的百分比计算分数。
public int calculateScore(List<Problem> problems, List<String> answers) {
if (problems == null || answers == null ||
problems.size() != answers.size() ||
problems.isEmpty()) {
System.err.println("评分错误: 题目和答案数量不匹配");
return 0;
}
int correctCount = 0;
for (int i = 0; i < problems.size(); i++) {
Problem problem = problems.get(i);
String userAnswer = answers.get(i);
if (isAnswerCorrect(problem, userAnswer)) {
correctCount++;
}
}
double score = ((double) correctCount / problems.size()) * 100;
int finalScore = (int) Math.round(score);
return finalScore;
}
}

@ -0,0 +1,69 @@
package util;
import java.util.Properties;
import java.util.Random;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public class EmailService {
// 邮件配置:请修改此处或设置环境变量
private static final String SENDER_EMAIL = "2810672597@qq.com";
private static final String SENDER_PASSWORD =("acyhhpwgcqkqdgff");
private static final String SMTP_HOST = "smtp.qq.com"; // 例如: smtp.gmail.com
private static final String SMTP_PORT = "587"; // 或 465 (SSL)
private static final String SUBJECT = "数学学习软件注册验证码";
public String generateVerificationCode() {
Random rand = new Random();
return String.format("%06d", rand.nextInt(1000000));
}
private Properties getMailProperties() {
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.port", SMTP_PORT);
return props;
}
private Message createMessage(Session session, String recipientEmail,
String code) throws MessagingException {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(SENDER_EMAIL));
message.setRecipients(
Message.RecipientType.TO,
InternetAddress.parse(recipientEmail)
);
message.setSubject(SUBJECT);
message.setText("您的注册验证码是: " + code +
"\n\n请在应用中输入此代码完成注册。");
return message;
}
// R2: 实际发送验证邮件
public boolean sendVerificationEmail(String recipientEmail, String code) {
if (SENDER_EMAIL == null || SENDER_PASSWORD == null) {
System.err.println("错误: 邮件发送凭证未设置。");
return false;
}
Session session = Session.getInstance(getMailProperties(), new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(SENDER_EMAIL, SENDER_PASSWORD);
}
});
try {
Message message = createMessage(session, recipientEmail, code);
Transport.send(message);
return true;
} catch (MessagingException e) {
System.err.println("邮件发送失败: " + e.getMessage());
return false;
}
}
}

@ -0,0 +1,358 @@
package util;
import generator.Problem;
import java.util.*;
public class ExpressionUtils {
private static Random rand = new Random();
private static final String[] OPS_PRIMARY = {"+", "-", "*", "/"};
private static final String[] OPS_MIDDLE = {"+", "-", "*", "/"};
//private static final String[] OPS_HIGH = {"+", "-", "*", "/", "^2", "sqrt"};
public static String randomNumber() {
return String.valueOf(rand.nextInt(100) + 1);
}
// --- 表达式求解和选项生成辅助方法 ---
public static double solveExpression(String expr) {
// 预处理表达式,确保格式正确
String parsableExpr = expr
.replaceAll("(\\d+|\\([^)]+\\))\\^2", "pow($1, 2)") // 处理平方
.replaceAll("sqrt\\s+(\\d+)", "sqrt($1)") // 处理缺少括号的sqrt
.replaceAll("\\s+", " "); // 标准化空格
try {
return new Object() {
int pos = -1, ch;
void nextChar() {
ch = (++pos < parsableExpr.length()) ? parsableExpr.charAt(pos) : -1;
}
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < parsableExpr.length()) throw new RuntimeException("Unexpected: " + (char) ch);
return x;
}
double parseExpression() {
double x = parseTerm();
for (; ; ) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseTerm() {
double x = parseFactor();
for (; ; ) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) { // division
double divisor = parseFactor();
if (divisor == 0) throw new ArithmeticException("Division by zero");
x /= divisor;
} else return x;
}
}
double parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(parsableExpr.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = parsableExpr.substring(startPos, this.pos);
x = parseFactor();
switch (func) {
case "sqrt":
x = Math.sqrt(x);
break;
case "sin":
x = Math.sin(Math.toRadians(x));
break;
case "cos":
x = Math.cos(Math.toRadians(x));
break;
case "tan":
x = Math.tan(Math.toRadians(x));
break;
case "pow":
eat(',');
double y = parseExpression();
x = Math.pow(x, y);
eat(')');
break;
default:
throw new RuntimeException("Unknown function: " + func);
}
} else {
throw new RuntimeException("Unexpected: " + (char) ch);
}
return x;
}
}.parse();
} catch (Exception e) {
System.err.println("Error parsing expression: '" + expr + "'. Parsable version: '" + parsableExpr + "'. Error: " + e.getMessage());
return Double.NaN;
}
}
private static String formatResult(double result) {
if (Double.isNaN(result)) {
return "Error";
}
if (Math.abs(result - Math.round(result)) < 0.0001) {
return String.valueOf((int) Math.round(result));
}
return String.format("%.2f", result);
}
/**
* MODIFICATION:
*/
private static String generateDistractor(double correctResult, List<String> existingOptions) {
if (Double.isNaN(correctResult)) {
return String.valueOf(rand.nextInt(100));
}
// 检查正确答案是否为整数
boolean isIntegerResult = Math.abs(correctResult - Math.round(correctResult)) < 0.0001;
double distractor;
String distractorStr;
do {
if (isIntegerResult) {
// 生成一个整数干扰项
int offset = rand.nextInt(20) + 1; // 扩大随机范围以避免重复
distractor = rand.nextBoolean() ? Math.round(correctResult) + offset : Math.round(correctResult) - offset;
// 确保干扰项不为负数
if (distractor < 0) {
distractor = Math.round(correctResult) + offset;
}
} else {
// 答案是小数,则生成小数干扰项
distractor = correctResult +
(rand.nextInt(10) + 1) * (rand.nextBoolean() ? 1 : -1) +
(rand.nextDouble() * 0.9);
}
distractorStr = formatResult(distractor);
} while (existingOptions.contains(distractorStr)); // 确保选项不重复
return distractorStr;
}
public static List<String> generateOptions(double correctResult) {
List<String> options = new ArrayList<>();
options.add(formatResult(correctResult));
for (int i = 0; i < 3; i++) {
options.add(generateDistractor(correctResult, options));
}
Collections.shuffle(options);
return options;
}
public static Problem createProblem(String expression) {
double result = solveExpression(expression);
// 如果解析失败返回null让调用者重新生成
if (Double.isNaN(result)) {
return null;
}
String correctAnswerOption = formatResult(result);
List<String> options = generateOptions(result);
return new Problem(expression, result, options, correctAnswerOption);
}
// --- 题目生成方法 ---
private static int getDivisor(int dividend) {
List<Integer> divisors = new ArrayList<>();
for (int j = 1; j <= dividend; j++) {
if (dividend % j == 0) {
divisors.add(j);
}
}
return divisors.get(rand.nextInt(divisors.size()));
}
/**
* MODIFICATION:
*/
public static Problem generatePrimaryExpr() {
Problem problem;
double result;
do {
// 生成 2 到 4 个操作数
int operands = rand.nextInt(3) + 2;
List<String> parts = new ArrayList<>();
for (int i = 0; i < operands; i++) {
if (i > 0) {
parts.add(OPS_PRIMARY[rand.nextInt(OPS_PRIMARY.length)]);
}
// 仅当操作数 >= 3 时,才有可能生成括号
if (operands >= 3 && rand.nextBoolean() && i < operands - 1) {
int num1 = rand.nextInt(50) + 1;
String innerOp = OPS_PRIMARY[rand.nextInt(OPS_PRIMARY.length)];
int num2;
if (innerOp.equals("/")) {
num2 = getDivisor(num1);
} else {
num2 = rand.nextInt(50) + 1;
}
parts.add("(" + num1 + " " + innerOp + " " + num2 + ")");
i++; // 括号表达式计为2个操作数
} else {
int num;
if (i > 0 && parts.get(parts.size() - 1).equals("/")) {
String dividendStr = parts.get(parts.size() - 2);
if (dividendStr.startsWith("(")) {
// 为避免 (a+b)/c 这种复杂情况无法保证整数结果,直接替换运算符
parts.set(parts.size() - 1, OPS_PRIMARY[rand.nextInt(3)]); // +, -, *
num = rand.nextInt(100) + 1;
} else {
int dividend = Integer.parseInt(dividendStr);
num = getDivisor(dividend);
}
} else {
num = rand.nextInt(100) + 1;
}
parts.add(String.valueOf(num));
}
}
String expression = String.join(" ", parts);
problem = createProblem(expression);
result = problem.getResult();
// 循环直到答案为非负整数
} while (result < 0 || Double.isNaN(result) || Math.abs(result - Math.round(result)) > 0.0001);
return problem;
}
public static Problem generateMiddleExpr() {
int operands = rand.nextInt(5) + 1;
StringBuilder expr = new StringBuilder();
boolean hasSquareOrSqrt = false;
for (int i = 0; i < operands; i++) {
if (i > 0) {
expr.append(" ").append(OPS_MIDDLE[rand.nextInt(OPS_MIDDLE.length)]).append(" ");
}
int num = rand.nextInt(100) + 1;
if (!hasSquareOrSqrt && rand.nextBoolean()) {
expr.append(rand.nextBoolean() ? "sqrt(" + num + ")" : num + "^2");
hasSquareOrSqrt = true;
} else {
expr.append(num);
}
}
if (!hasSquareOrSqrt) {
expr.append(" + ").append(rand.nextInt(50) + 1).append("^2");
}
return createProblem(expr.toString());
}
public static Problem generateHighExpr() {
int attempts = 0;
final int maxAttempts = 10;
while (attempts < maxAttempts) {
int operands = rand.nextInt(3) + 2; // 2-4个操作数
StringBuilder expr = new StringBuilder();
boolean hasTrig = false;
for (int i = 0; i < operands; i++) {
if (i > 0) {
String[] validOps = {"+", "-", "*", "/"};
String op = validOps[rand.nextInt(validOps.length)];
expr.append(" ").append(op).append(" ");
}
// 强制至少有一个操作数是三角函数
if (!hasTrig && (i == operands - 1 || rand.nextBoolean())) {
String[] funcs = {"sin", "cos", "tan"};
String func = funcs[rand.nextInt(funcs.length)];
int angle = rand.nextInt(90) + 1; // 1-90度
expr.append(func).append("(").append(angle).append(")");
hasTrig = true;
} else {
// 其他操作数可以是普通数字、平方或开方
int num = rand.nextInt(100) + 1;
if (rand.nextBoolean() && hasTrig) { // 确保已经有三角函数后再添加其他函数
if (rand.nextBoolean()) {
expr.append(num).append("^2");
} else {
expr.append("sqrt(").append(num).append(")");
}
} else {
expr.append(num);
}
}
}
// 如果没有三角函数,强制添加一个
if (!hasTrig) {
String[] funcs = {"sin", "cos", "tan"};
String func = funcs[rand.nextInt(funcs.length)];
int angle = rand.nextInt(90) + 1;
if (rand.nextBoolean()) {
// 在开头添加
expr.insert(0, func + "(" + angle + ") + ");
} else {
// 在结尾添加
expr.append(" + ").append(func).append("(").append(angle).append(")");
}
hasTrig = true;
}
String expression = expr.toString();
// 验证表达式
Problem problem = createProblem(expression);
if (problem != null) {
return problem;
}
attempts++;
System.err.println("生成高中题目失败,尝试次数: " + attempts + ", 表达式: " + expression);
}
return createProblem("sin(30) + cos(60)");
}
}

@ -0,0 +1,79 @@
package util;
import generator.Problem;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class FileUtils {
private static final String FOLDER_PATH = "exams/";
public static String getUserFolder(String username) {
String folder = FOLDER_PATH + username;
File dir = new File(folder);
if (!dir.exists()) {
dir.mkdirs();
}
return folder;
}
private static void processHistoryFile(File file, Set<String> history) {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
if (!line.trim().isEmpty() && line.matches("^\\d+\\..*")) {
// 仅提取题干 (表达式字符串) 用于查重
history.add(line.substring(line.indexOf('.') + 1).trim());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static Set<String> loadHistory(String username) {
Set<String> history = new HashSet<>();
File dir = new File(getUserFolder(username));
File[] files = dir.listFiles((d, name) -> name.endsWith(".txt"));
if (files != null) {
for (File file : files) {
processHistoryFile(file, history);
}
}
return history;
}
private static void writeProblem(BufferedWriter bw, int index, Problem problem) throws IOException {
bw.write(index + "." + problem.getExpression());
bw.newLine();
char optionChar = 'A';
StringBuilder optionsLine = new StringBuilder();
for (String option : problem.getOptions()) {
optionsLine.append(optionChar++).append(". ").append(option).append(" ");
}
bw.write(optionsLine.toString().trim());
bw.newLine();
bw.write("答案: " + problem.getCorrectAnswerOption());
bw.newLine();
bw.newLine();
}
public static void saveProblems(String username, List<Problem> problems) {
String folder = getUserFolder(username);
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
String filename = folder + "/" + timestamp + ".txt";
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename))) {
int index = 1;
for (Problem problem : problems) {
writeProblem(bw, index++, problem);
}
System.out.println("卷子已保存到: " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,76 @@
package util;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class to modify specific fields in user.txt.
* ModifyClass.java ModifyPassword.java
*/
public class ModifyUtils {
private static List<String> readAllLines(String filePath, List<String> lines) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
private static void writeAllLines(String filePath, List<String> lines) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
for (String l : lines) {
writer.write(l + System.lineSeparator());
}
}
}
private static int findTargetLine(List<String> lines, String targetName) {
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.isEmpty()) continue;
String[] parts = line.split("\\s+");
if (parts.length > 0 && parts[0].equals(targetName)) {
return i; // 0-based index
}
}
return -1;
}
// 核心逻辑:修改文件中特定用户的某个字段 (0-based)
public static int modifyFileField(String filePath, String targetName,
int fieldIndex, String newValue) {
List<String> lines = new ArrayList<>();
try {
readAllLines(filePath, lines);
} catch (IOException e) {
System.err.println("读取文件错误: " + e.getMessage());
return -1;
}
int idx = findTargetLine(lines, targetName);
if (idx == -1) {
return -1;
}
String[] parts = lines.get(idx).trim().split("\\s+");
if (parts.length > fieldIndex) {
parts[fieldIndex] = newValue;
lines.set(idx, String.join(" ", parts));
try {
writeAllLines(filePath, lines);
return idx + 1; // 返回 1-based 行号
} catch (IOException e) {
System.err.println("写入文件错误: " + e.getMessage());
return -1;
}
} else {
System.out.println("目标行元素不足。");
return -1;
}
}
}
Loading…
Cancel
Save