yxh to develop #8

Merged
hnu202326010422 merged 6 commits from yuxiuhui_branch into develop 4 months ago

@ -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