Compare commits

...

No commits in common. 'jinzhibo_branch' and 'main' have entirely different histories.

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: MathLearningApp
Class-Path: .

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,21 @@
# 数学题学习应用
********
## 1.项目使用
项目目录下有一个MathLearningApp.jar使用jdk-22在windows11 powershell下运行。
在运行前,先输入命令
`[Console]::InputEncoding=[System.Text.Encoding]::UTF8`
`[Console]::OutputEncoding=[System.Text.Encoding]::UTF8`
再确保安装了curl可以在powershell中输入
`curl.exe --version`
来查看curl的版本信息我使用的是8.14.1
然后使用命令
`java -jar MathLeaningApp.jar` 运行目录下会出现一个存储用户数据的文件
********
## 2.项目说明
controller、view目录负责前端
model、service、util目录负责后端
MathLearningApp.java是程序入口

@ -1,5 +1,5 @@
import controller.MainController;
import javax.swing.*;
import javax.swing.SwingUtilities;
public class MathLearningApp {
public static void main(String[] args) {

@ -0,0 +1,32 @@
import service.QuestionService;
import model.Question;
import service.OptionsResult;
import java.util.List;
class Test {
public static void main(String[] args) {
System.out.println("=== 测试题目生成 ===\n");
QuestionService questionService = new QuestionService();
List<Question> questions = questionService.generateQuestions("高中", 30);
for (int i = 0; i < questions.size(); i++) {
Question question = questions.get(i);
System.out.println((i + 1) + ". " + question.getContent());
// 通过 OptionsResult 获取选项列表
OptionsResult optionsResult = question.getOptions();
List<String> options = optionsResult.getOptions();
System.out.print(" 选项: ");
for (int j = 0; j < options.size(); j++) {
char optionChar = (char) ('A' + j);
System.out.print(optionChar + "." + options.get(j) + " ");
}
char correctChar = (char) ('A' + optionsResult.getCorrectIndex());
System.out.println(" [答案:" + correctChar + "]");
System.out.println();
}
}
}

@ -1,6 +1,9 @@
package controller;
import service.*;
import view.*;
import service.UserService;
import service.EmailService;
import view.LoginFrame;
import view.RegisterFrame;
public class AuthController {
private UserService userService = new UserService();

@ -1,4 +1,5 @@
package controller;
import service.QuestionService;
import view.ExamFrame;
import java.util.List;

@ -1,7 +1,9 @@
package controller;
import view.*;
import javax.swing.*;
import view.LoginFrame;
import view.MainFrame;
import view.ChangePasswordFrame;
import javax.swing.JOptionPane;
public class MainController {
private AuthController authController = new AuthController(this);

@ -0,0 +1,21 @@
package model;
import java.util.List;
public class Exam {
private List<Question> questions;
private String difficulty;
public Exam(List<Question> questions, String difficulty) {
this.questions = questions;
this.difficulty = difficulty;
}
public List<Question> getQuestions() {
return questions;
}
public String getDifficulty() {
return difficulty;
}
}

@ -0,0 +1,34 @@
package model;
import java.util.List;
import service.OptionsResult;
public class Question {
private String content;
private OptionsResult options;
private int correctPos;
private String difficulty;
public Question(String content, OptionsResult options, int correctPos, String difficulty) {
this.content = content;
this.options = options;
this.correctPos = correctPos;
this.difficulty = difficulty;
}
public String getContent() {
return content;
}
public OptionsResult getOptions() {
return options;
}
public int getCorrectAnswer() {
return correctPos;
}
public String getDifficulty() {
return difficulty;
}
}

@ -0,0 +1,41 @@
package model;
import java.io.Serializable;
public class User implements Serializable {
private String email;
private String password;
private String registrationCode;
private boolean isRegistered;
public User(String email, String registrationCode) {
this.email = email;
this.registrationCode = registrationCode;
this.isRegistered = false;
this.password = null;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRegistrationCode() {
return registrationCode;
}
public boolean isRegistered() {
return isRegistered;
}
public void setRegistered(boolean registered) {
isRegistered = registered;
}
}

@ -0,0 +1,51 @@
package service;
import model.Question;
import java.util.List;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.Collections;
import java.util.Random;
public abstract class AbstractQuestionGenerator implements QuestionGenerator {
protected String difficulty;
protected Random random = new Random();
public AbstractQuestionGenerator(String difficulty) {
this.difficulty = difficulty;
}
@Override
public OptionsResult generateOptions(int correct) {
List<String> options = new ArrayList<>();
Set<Integer> used = new HashSet<>();
used.add(correct);
// 先生成3个错误选项
while (options.size() < 3) {
int wrong = correct + (int)(Math.random() * 10) - 5;
if (!used.contains(wrong) && wrong != correct) {
options.add(String.valueOf(wrong));
used.add(wrong);
}
}
// 在随机位置插入正确答案
int correctPosition = (int)(Math.random() * 4); // 0-3的随机位置
options.add(correctPosition, String.valueOf(correct));
return new OptionsResult(options, correctPosition);
}
public String getDifficulty() {
return difficulty;
}
protected int getRandomOperand() {
return random.nextInt(20)+1;
}
protected String getRandomOperator(String[] operators) {
return operators[random.nextInt(operators.length)];
}
}

@ -0,0 +1,80 @@
package service;
import utils.EmailValidator;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class EmailService {
public int sendRegistrationCode(String email, String code) {
System.out.println("=== CommandLineEmailService ===");
if (!EmailValidator.isValid(email)) {
return 0; //0表示邮箱格式错误
}
try {
// 创建临时文件
File tempFile = File.createTempFile("email", ".txt");
tempFile.deleteOnExit();
// 写入邮件内容
String emailContent = createEmailContent(email, code);
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write(emailContent);
}
String[] command = {
"curl.exe",
"--url", "smtps://smtp.163.com:465",
"--ssl-reqd",
"--mail-from", "17857002985@163.com",
"--mail-rcpt", email,
"--user", "17857002985@163.com:EMqvpAhrvDw5rwuD", //EMqvpAhrvDw5rwuD授权码
"-T", tempFile.getAbsolutePath(), // 从文件读取内容
"--insecure"
};
System.out.println("执行命令: " + String.join(" ", command));
Process process = Runtime.getRuntime().exec(command);
// 读取输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int exitCode = process.waitFor();
System.out.println("退出码: " + exitCode);
if (exitCode == 0) {
System.out.println("邮件发送成功");
return 1; //1表示成功发送
} else {
System.out.println("邮件发送失败");
return 2; //2表示邮箱格式正确但发送失败
}
} catch (Exception e) {
System.out.println("执行命令失败: " + e.getMessage());
return 2;
}
}
private String createEmailContent(String toEmail, String code) {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
String date = sdf.format(new Date());
return "From: 17857002985@163.com\n" +
"To: " + toEmail + "\n" +
"Subject: 注册验证码\n" +
"Date: " + date + "\n" +
"\n" +
"您的注册验证码是: " + code + "\n" +
"验证码有效期为15分钟请尽快使用。\n" +
"\n" +
"如果不是您本人操作,请忽略此邮件。\n";
}
}

@ -0,0 +1,379 @@
package service;
import model.Question;
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
public class HighSchoolQuestionGenerator extends AbstractQuestionGenerator {
// 修正三角函数角度选择,确保所有角度都能产生合理的结果
private static final int[] SAFE_SIN_ANGLES = {0, 30, 90, 150, 180, 210, 270, 330};
private static final int[] SAFE_COS_ANGLES = {0, 60, 90, 120, 180, 240, 270, 300};
private static final int[] SAFE_TAN_ANGLES = {0, 180, 360}; // tan只使用结果为0的角度
private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"};
public HighSchoolQuestionGenerator() {
super("高中");
}
@Override
public Question generateQuestion() {
boolean valid = false;
String content = "";
double answer = 0;
int attempts = 0;
int maxAttempts = 50;
while (!valid && attempts < maxAttempts) {
attempts++;
// 生成2-4个操作数的题目
int operandCount = random.nextInt(3) + 2; // 2-4个操作数
List<Object> operands = new ArrayList<>();
List<String> operators = new ArrayList<>();
List<Boolean> isTrigFunction = new ArrayList<>();
// 生成操作数,确保至少有一个三角函数,可以有多个
int trigCount = 0;
for (int i = 0; i < operandCount; i++) {
// 至少有一个三角函数,其他位置随机决定是否使用三角函数
if (trigCount == 0 || (trigCount < 2 && random.nextDouble() < 0.5)) {
// 生成三角函数(确保不会产生无理数)
operands.add(generateSafeTrigExpression());
isTrigFunction.add(true);
trigCount++;
} else {
// 生成普通数字1-10的小整数
operands.add(random.nextInt(10) + 1);
isTrigFunction.add(false);
}
}
// 生成运算符(使用+、-、×)
for (int i = 0; i < operandCount - 1; i++) {
String[] possibleOps = {"+", "-", "×"};
String op = getRandomOperator(possibleOps);
operators.add(op);
}
content = buildHighSchoolContent(operands, operators, isTrigFunction);
answer = calculateHighSchoolExpression(content);
valid = !Double.isNaN(answer) && !Double.isInfinite(answer); // 允许负数结果
}
if (!valid) {
return generateSimpleTrigQuestion();
}
OptionsResult options = generateOptions(answer);
return new Question(content, options, options.getCorrectIndex(), difficulty);
}
/**
*
*/
private String generateSafeTrigExpression() {
String trigFunc = getRandomOperator(TRIG_FUNCTIONS);
int angle;
// 根据三角函数类型选择安全的角度
switch (trigFunc) {
case "sin":
angle = SAFE_SIN_ANGLES[random.nextInt(SAFE_SIN_ANGLES.length)];
break;
case "cos":
angle = SAFE_COS_ANGLES[random.nextInt(SAFE_COS_ANGLES.length)];
break;
case "tan":
angle = SAFE_TAN_ANGLES[random.nextInt(SAFE_TAN_ANGLES.length)];
break;
default:
angle = 0;
}
return trigFunc + "(" + angle + ")";
}
/**
*
*/
private String buildHighSchoolContent(List<Object> operands, List<String> operators,
List<Boolean> isTrigFunction) {
StringBuilder content = new StringBuilder();
boolean useParentheses = random.nextDouble() < 0.6 && operands.size() > 2;
if (useParentheses) {
int parenLength = random.nextInt(operands.size() - 1) + 2;
int start = random.nextInt(operands.size() - parenLength + 1);
int end = start + parenLength - 1;
for (int i = 0; i < operands.size(); i++) {
if (i == start) content.append("(");
if (isTrigFunction.get(i)) {
content.append(operands.get(i)); // 显示三角函数表达式
} else {
content.append(operands.get(i)); // 显示数字
}
if (i == end) content.append(")");
if (i < operators.size()) content.append(" ").append(operators.get(i)).append(" ");
}
} else {
for (int i = 0; i < operands.size(); i++) {
if (isTrigFunction.get(i)) {
content.append(operands.get(i));
} else {
content.append(operands.get(i));
}
if (i < operators.size()) content.append(" ").append(operators.get(i)).append(" ");
}
}
content.append(" = ?");
return content.toString();
}
/**
*
*/
private double calculateTrigFunction(String trigFunc, int angle) {
switch (trigFunc) {
case "sin":
return calculateExactSin(angle);
case "cos":
return calculateExactCos(angle);
case "tan":
return calculateExactTan(angle);
default:
return 0;
}
}
/**
* sin
*/
private double calculateExactSin(int angle) {
angle = angle % 360;
if (angle < 0) angle += 360;
switch (angle) {
case 0: case 180: case 360: return 0.0;
case 30: case 150: return 0.5;
case 90: return 1.0;
case 210: case 330: return -0.5;
case 270: return -1.0;
default: return 0.0;
}
}
/**
* cos
*/
private double calculateExactCos(int angle) {
angle = angle % 360;
if (angle < 0) angle += 360;
switch (angle) {
case 0: case 360: return 1.0;
case 60: case 300: return 0.5;
case 90: case 270: return 0.0;
case 120: case 240: return -0.5;
case 180: return -1.0;
default: return 1.0;
}
}
/**
* tan
*/
private double calculateExactTan(int angle) {
angle = angle % 360;
if (angle < 0) angle += 360;
switch (angle) {
case 0: case 180: case 360: return 0.0;
default: return 0.0;
}
}
/**
*
*/
private double calculateHighSchoolExpression(String expression) {
try {
String mathExpr = expression.replace(" = ?", "").trim();
return evaluateHighSchool(mathExpr);
} catch (Exception e) {
return Double.NaN;
}
}
/**
*
*/
private double evaluateHighSchool(String expression) {
Stack<Double> numbers = new Stack<>();
Stack<Character> ops = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == ' ') continue;
if (Character.isDigit(c)) {
// 读取数字
double num = 0;
while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
num = num * 10 + (expression.charAt(i) - '0');
i++;
}
i--;
numbers.push(num);
} else if (c == 's' || c == 'c' || c == 't') {
// 处理三角函数
String trigFunc = parseTrigFunction(expression, i);
i += trigFunc.length();
// 跳过左括号
while (i < expression.length() && expression.charAt(i) != '(') {
i++;
}
i++; // 跳过 '('
// 读取角度
double angle = 0;
while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
angle = angle * 10 + (expression.charAt(i) - '0');
i++;
}
// 跳过右括号
while (i < expression.length() && expression.charAt(i) != ')') {
i++;
}
// 计算三角函数值
double trigValue = calculateTrigFunction(trigFunc, (int)angle);
numbers.push(trigValue);
} else if (c == '(') {
ops.push(c);
} else if (c == ')') {
while (!ops.isEmpty() && ops.peek() != '(') {
numbers.push(applyDoubleOp(ops.pop(), numbers.pop(), numbers.pop()));
}
ops.pop();
} else if (isOperator(c)) {
while (!ops.isEmpty() && hasPrecedence(c, ops.peek())) {
numbers.push(applyDoubleOp(ops.pop(), numbers.pop(), numbers.pop()));
}
ops.push(c);
}
}
while (!ops.isEmpty()) {
numbers.push(applyDoubleOp(ops.pop(), numbers.pop(), numbers.pop()));
}
return numbers.pop(); // 允许返回任何数值,包括负数
}
/**
*
*/
private String parseTrigFunction(String expression, int start) {
if (expression.startsWith("sin", start)) {
return "sin";
} else if (expression.startsWith("cos", start)) {
return "cos";
} else if (expression.startsWith("tan", start)) {
return "tan";
}
return "";
}
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '×' || c == '÷';
}
private boolean hasPrecedence(char op1, char op2) {
if (op2 == '(' || op2 == ')') {
return false;
}
// 如果栈顶运算符(op2)的优先级高于当前运算符(op1),则先计算栈顶的
if ((op2 == '×' || op2 == '÷') && (op1 == '+' || op1 == '-')) {
return true;
}
// 同级运算符从左到右计算
if ((op2 == '+' || op2 == '-') && (op1 == '+' || op1 == '-')) {
return true;
}
if ((op2 == '×' || op2 == '÷') && (op1 == '×' || op1 == '÷')) {
return true;
}
return false;
}
/**
*
*/
private double applyDoubleOp(char op, double b, double a) {
switch (op) {
case '+': return a + b;
case '-': return a - b; // 允许负数
case '×': return a * b;
case '÷':
if (b == 0) return Double.NaN;
return a / b;
default: return Double.NaN;
}
}
/**
* generateOptionsdouble1
*/
public OptionsResult generateOptions(double correct) {
List<String> options = new ArrayList<>();
java.util.Set<String> used = new java.util.HashSet<>();
used.add(formatDouble(correct));
// 生成3个错误选项使用有理数偏移
while (options.size() < 3) {
double offset = (random.nextInt(3) + 1) * 0.5; // 0.5, 1.0, 1.5
if (random.nextBoolean()) offset = -offset;
double wrongValue = correct + offset;
String wrongStr = formatDouble(wrongValue);
if (!used.contains(wrongStr)) {
options.add(wrongStr);
used.add(wrongStr);
}
}
// 在随机位置插入正确答案
int correctPosition = random.nextInt(4);
options.add(correctPosition, formatDouble(correct));
return new OptionsResult(options, correctPosition);
}
/**
* double1
*/
private String formatDouble(double value) {
// 处理-0.0的情况
if (Math.abs(value) < 0.0001) {
return "0.0";
}
return String.format("%.1f", value);
}
// 简单的保底题目
private Question generateSimpleTrigQuestion() {
int angle = random.nextBoolean() ? 0 : 180;
String trigFunc = "sin";
String content = trigFunc + "(" + angle + ") = ?";
double answer = calculateTrigFunction(trigFunc, angle);
OptionsResult options = generateOptions(answer);
return new Question(content, options, options.getCorrectIndex(), difficulty);
}
}

@ -0,0 +1,258 @@
package service;
import model.Question;
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
public class MiddleSchoolQuestionGenerator extends AbstractQuestionGenerator {
public MiddleSchoolQuestionGenerator() {
super("初中");
}
@Override
public Question generateQuestion() {
// 随机选择生成平方题目或开根号题目
if (random.nextBoolean()) {
return generateSquareQuestion();
} else {
return generateSquareRootQuestion();
}
}
private Question generateSquareQuestion() {
boolean valid = false;
String content = "";
int answer = 0;
int attempts = 0;
int maxAttempts = 50;
while (!valid && attempts < maxAttempts) {
attempts++;
// 生成2-4个操作数
int operandCount = random.nextInt(3) + 2;
List<Integer> operands = new ArrayList<>();
List<String> operators = new ArrayList<>();
List<Boolean> isSquared = new ArrayList<>(); // 记录哪些操作数要显示为平方形式
// 生成操作数,确保平方数是完全平方数
for (int i = 0; i < operandCount; i++) {
operands.add(getRandomOperand());
isSquared.add(false);
}
// 确定至少一个位置显示平方符号,并确保它是完全平方数
int squarePosition = random.nextInt(operandCount);
// 将选中的操作数改为完全平方数
int base = random.nextInt(10) + 1; // 1-10
operands.set(squarePosition, base * base);
isSquared.set(squarePosition, true);
// 生成运算符
for (int i = 0; i < operandCount - 1; i++) {
String op = getRandomOperator(new String[]{"+", "-", "×"});
operators.add(op);
}
content = buildMiddleSchoolContent(operands, operators, isSquared, true);
answer = calculateExpression(content);
valid = true; // 初中题目允许任何整数结果,包括负数
}
if (!valid) {
return generateSimpleQuestion();
}
OptionsResult options = generateOptions(answer);
return new Question(content, options, options.getCorrectIndex(), difficulty);
}
private Question generateSquareRootQuestion() {
boolean valid = false;
String content = "";
int answer = 0;
int attempts = 0;
int maxAttempts = 50;
while (!valid && attempts < maxAttempts) {
attempts++;
// 生成2-4个操作数
int operandCount = random.nextInt(3) + 2;
List<Integer> operands = new ArrayList<>();
List<String> operators = new ArrayList<>();
List<Boolean> isSquareRoot = new ArrayList<>(); // 记录哪些操作数要显示为根号形式
// 生成操作数,确保至少有一个完全平方数用于开根号
boolean hasPerfectSquare = false;
for (int i = 0; i < operandCount; i++) {
if (!hasPerfectSquare) {
// 生成一个完全平方数
int base = random.nextInt(10) + 1; // 1-10的平方
operands.add(base * base);
isSquareRoot.add(true); // 这个数显示为根号形式
hasPerfectSquare = true;
} else {
operands.add(getRandomOperand());
isSquareRoot.add(false);
}
}
// 生成运算符
for (int i = 0; i < operandCount - 1; i++) {
String op = getRandomOperator(new String[]{"+", "-", "×"});
operators.add(op);
}
content = buildMiddleSchoolContent(operands, operators, isSquareRoot, false);
answer = calculateExpression(content);
valid = true; // 初中题目允许任何整数结果
}
if (!valid) {
return generateSimpleQuestion();
}
OptionsResult options = generateOptions(answer);
return new Question(content, options, options.getCorrectIndex(), difficulty);
}
private String buildMiddleSchoolContent(List<Integer> operands, List<String> operators,
List<Boolean> showSpecial, boolean isSquareQuestion) {
StringBuilder content = new StringBuilder();
boolean useParentheses = random.nextBoolean() && operands.size() > 2;
if (useParentheses) {
int parenLength = random.nextInt(operands.size() - 1) + 2;
int start = random.nextInt(operands.size() - parenLength + 1);
int end = start + parenLength - 1;
for (int i = 0; i < operands.size(); i++) {
if (i == start) content.append("(");
// 强制显示特殊符号
if (showSpecial.get(i)) {
if (isSquareQuestion) { // 显示为平方形式
int base = (int) Math.sqrt(operands.get(i));
content.append(base).append("²");
} else {
content.append("√").append(operands.get(i)); // 显示为开根号形式√a
}
} else {
content.append(operands.get(i));
}
if (i == end) content.append(")");
if (i < operators.size()) content.append(" ").append(operators.get(i)).append(" ");
}
} else {
for (int i = 0; i < operands.size(); i++) {
// 强制显示特殊符号
if (showSpecial.get(i)) {
if (isSquareQuestion) {
int base = (int) Math.sqrt(operands.get(i));
content.append(base).append("²");
} else {
content.append("√").append(operands.get(i));
}
} else {
content.append(operands.get(i));
}
if (i < operators.size()) content.append(" ").append(operators.get(i)).append(" ");
}
}
content.append(" = ?");
return content.toString();
}
private int calculateExpression(String expression) {
try {
String mathExpr = expression.replace(" = ?", "").trim();
return evaluateMiddleSchool(mathExpr);
} catch (Exception e) {
return Integer.MIN_VALUE; // 使用特殊值表示计算失败
}
}
private int evaluateMiddleSchool(String expression) {
Stack<Integer> numbers = new Stack<>();
Stack<Character> ops = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == ' ') continue;
if (Character.isDigit(c)) {
int num = 0;
while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
num = num * 10 + (expression.charAt(i) - '0');
i++;
}
i--;
numbers.push(num);
} else if (c == '²') { // 处理平方:弹出栈顶数字,计算平方后压回
if (!numbers.isEmpty()) {
int num = numbers.pop();
numbers.push(num * num);
}
} else if (c == '√') { // 处理开根号:读取后面的数字并开根
i++; // 跳过 '√'
int num = 0;
while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
num = num * 10 + (expression.charAt(i) - '0');
i++;
}
i--;
int sqrt = (int) Math.sqrt(num);
numbers.push(sqrt);
} else if (c == '(') {
ops.push(c);
} else if (c == ')') {
while (!ops.isEmpty() && ops.peek() != '(') {
numbers.push(applyOp(ops.pop(), numbers.pop(), numbers.pop()));
}
ops.pop();
} else if (isOperator(c)) {
while (!ops.isEmpty() && hasPrecedence(c, ops.peek())) {
numbers.push(applyOp(ops.pop(), numbers.pop(), numbers.pop()));
}
ops.push(c);
}
}
while (!ops.isEmpty()) {
numbers.push(applyOp(ops.pop(), numbers.pop(), numbers.pop()));
}
return numbers.pop(); // 允许返回任何整数,包括负数
}
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '×';
}
private boolean hasPrecedence(char op1, char op2) {
if (op2 == '(' || op2 == ')') {
return false;
}
// 如果栈顶运算符(op2)的优先级高于当前运算符(op1),则先计算栈顶的
if ((op2 == '×') && (op1 == '+' || op1 == '-')) {
return true;
}
// 同级运算符从左到右计算
if ((op2 == '+' || op2 == '-') && (op1 == '+' || op1 == '-')) {
return true;
}
if ((op2 == '×') && (op1 == '×')) {
return true;
}
return false;
}
private int applyOp(char op, int b, int a) {
switch (op) {
case '+': return a + b;
case '-': return a - b; // 允许负数结果
case '×': return a * b;
default: return 0;
}
}
// 简单的保底题目
private Question generateSimpleQuestion() {
int a = random.nextInt(10) + 1;
int b = random.nextInt(10) + 1;
String content = a + "² - " + b + "² = ?";
int answer = a*a - b*b;
OptionsResult options = generateOptions(answer);
return new Question(content, options, options.getCorrectIndex(), difficulty);
}
}

@ -0,0 +1,21 @@
package service;
import java.util.List;
public class OptionsResult {
private List<String> options;
private int correctIndex;
public OptionsResult(List<String> options, int correctIndex) {
this.options = options;
this.correctIndex = correctIndex;
}
public List<String> getOptions() {
return options;
}
public int getCorrectIndex() {
return correctIndex;
}
}

@ -0,0 +1,238 @@
package service;
import model.Question;
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
public class PrimaryQuestionGenerator extends AbstractQuestionGenerator {
private static final String[] OPERATORS = {"+", "-" , "×", "÷"};
public PrimaryQuestionGenerator() {
super("小学");
}
@Override
public Question generateQuestion() {
int operandCount = random.nextInt(4) + 2; // 2-5个操作数
List<Integer> operands = new ArrayList<>();
List<String> operators = new ArrayList<>();
// 生成操作数和运算符,确保结果为非负整数
boolean valid = false;
String content = "";
int answer = 0;
int attempts = 0;
int maxAttempts = 100; // 添加最大尝试次数
while (!valid && attempts < maxAttempts) {
attempts++; // 计数
operands.clear();
operators.clear();
// 生成操作数
for (int i = 0; i < operandCount; i++) {
operands.add(getRandomOperand());
}
// 生成运算符,限制最多只有一个除法
boolean hasDivision = false;
for (int i = 0; i < operandCount - 1; i++) {
String op;
// 如果已经有除法了,就只生成其他运算符
if (hasDivision) {
String[] otherOperators = {"+", "-", "×"};
op = getRandomOperator(otherOperators);
} else {
op = getRandomOperator(OPERATORS);
if (op.equals("÷")) {
hasDivision = true;
// 确保除法能整除:调整被除数
int divisor = operands.get(i + 1);
int dividend = operands.get(i);
// 如果当前不能整除,调整被除数
if (divisor == 0 || dividend % divisor != 0) {
// 如果除数为0重新生成除数
if (divisor == 0) {
divisor = random.nextInt(9) + 2; // 2-10
operands.set(i + 1, divisor);
}
// 调整被除数为除数的倍数
int multiple = random.nextInt(3) + 1; // 1-3倍
operands.set(i, divisor * multiple);
}
}
}
operators.add(op);
}
content = buildPrimaryContent(operands, operators);
answer = calculateExpression(content);
valid = answer >= 0; // 验证答案是否非负整数
}
// 如果多次尝试都失败,生成一个简单的保底题目
if (!valid) {
return generateSimpleQuestion();
}
OptionsResult options = generateOptions(answer);
return new Question(content, options, options.getCorrectIndex(), difficulty);
}
// 生成简单的保底题目
private Question generateSimpleQuestion() {
int a = random.nextInt(10) + 5; // 5-14
int b = random.nextInt(5) + 1; // 1-5
String[] simpleOps = {"+", "×"};
String op = simpleOps[random.nextInt(simpleOps.length)];
String content;
int answer;
if (op.equals("+")) {
content = a + " + " + b + " = ?";
answer = a + b;
} else {
content = a + " × " + b + " = ?";
answer = a * b;
}
OptionsResult options = generateOptions(answer);
return new Question(content, options, options.getCorrectIndex(), difficulty);
}
private String buildPrimaryContent(List<Integer> operands, List<String> operators) {
StringBuilder content = new StringBuilder();
// 随机决定是否加括号只在有3个及以上操作数时
boolean useParentheses = random.nextDouble() < 0.6 && operands.size() > 2;
if (useParentheses) {
// 随机决定括号包含的操作数数量至少2个最多 operands.size()-1 个)
int parenLength = random.nextInt(operands.size() - 1) + 2;
// 随机决定括号的起始位置
int start = random.nextInt(operands.size() - parenLength + 1);
int end = start + parenLength - 1;
for (int i = 0; i < operands.size(); i++) {
if (i == start) {
content.append("(");
}
content.append(operands.get(i));
if (i == end) {
content.append(")");
}
if (i < operators.size()) {
content.append(" ").append(operators.get(i)).append(" ");
}
}
} else {
for (int i = 0; i < operands.size(); i++) {
content.append(operands.get(i));
if (i < operators.size()) {
content.append(" ").append(operators.get(i)).append(" ");
}
}
}
content.append(" = ?");
return content.toString();
}
private int calculateExpression(String expression) {
try {
// 移除 "= ?" 部分
String mathExpr = expression.replace(" = ?", "").trim();
return evaluate(mathExpr);
} catch (Exception e) {
return -1; // 计算失败
}
}
private int evaluate(String expression) {
Stack<Integer> numbers = new Stack<>();
Stack<Character> ops = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == ' ') {
continue; // 跳过空格
}
if (Character.isDigit(c)) {
// 读取完整数字
int num = 0;
while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
num = num * 10 + (expression.charAt(i) - '0');
i++;
}
i--;
numbers.push(num);
} else if (c == '(') {
ops.push(c);
} else if (c == ')') {
// 计算括号内的所有运算
while (ops.peek() != '(') {
int result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
if (result < 0) return -1; // 检查中间结果是否负数
numbers.push(result);
}
ops.pop(); // 弹出 '('
} else if (isOperator(c)) {
// 处理运算符优先级:如果栈顶运算符优先级更高或相等,先计算栈顶的
while (!ops.isEmpty() && hasPrecedence(ops.peek(), c)) {
int result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
if (result < 0) return -1; // 检查中间结果是否负数
numbers.push(result);
}
ops.push(c);
}
}
// 计算剩余表达式
while (!ops.isEmpty()) {
int result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
if (result < 0) return -1; // 检查中间结果是否负数
numbers.push(result);
}
return numbers.pop();
}
private boolean hasPrecedence(char op1, char op2) {
if (op1 == '(' || op1 == ')') {
return false;
}
// op1是栈顶运算符op2是当前运算符
// 如果栈顶运算符优先级高于或等于当前运算符返回true先计算栈顶的
if ((op1 == '×' || op1 == '÷') && (op2 == '+' || op2 == '-')) {
return true;
}
// 同级运算符从左到右计算
if ((op1 == '+' || op1 == '-') && (op2 == '+' || op2 == '-')) {
return true;
}
if ((op1 == '×' || op1 == '÷') && (op2 == '×' || op2 == '÷')) {
return true;
}
return false;
}
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '×' || c == '÷';
}
private int applyOp(char op, int b, int a) {
switch (op) {
case '+':
return a + b;
case '-':
int result = a - b;
return result >= 0 ? result : -1; // 确保非负
case '×':
return a * b;
case '÷':
if (b == 0 || a % b != 0) {
return -1; // 除数为0或不整除
}
return a / b;
default:
return -1;
}
}
}

@ -0,0 +1,9 @@
package service;
import model.Question;
import java.util.List;
public interface QuestionGenerator {
Question generateQuestion();
OptionsResult generateOptions(int correctAnswer);
}

@ -0,0 +1,18 @@
package service;
import java.util.HashMap;
import java.util.Map;
public class QuestionGeneratorFactory {
private static final Map<String, QuestionGenerator> generators = new HashMap<>();
static {
generators.put("小学", new PrimaryQuestionGenerator());
generators.put("初中", new MiddleSchoolQuestionGenerator());
generators.put("高中", new HighSchoolQuestionGenerator());
}
public static QuestionGenerator getGenerator(String difficulty) {
return generators.getOrDefault(difficulty, generators.get("小学"));
}
}

@ -0,0 +1,25 @@
package service;
import model.Question;
import java.util.*;
public class QuestionService {
/*生成试卷的题目部分*/
public List<Question> generateQuestions(String difficulty, int count) {
if(count < 10 || count > 30) {
return null;
}
List<Question> questions = new ArrayList<>();
Set<String> usedQuestions = new HashSet<>();
QuestionGenerator generator = QuestionGeneratorFactory.getGenerator(difficulty);
while (questions.size() < count) {
Question q = generator.generateQuestion();
if (usedQuestions.add(q.getContent())) {
questions.add(q);
}
}
return questions;
}
}

@ -0,0 +1,61 @@
package service;
import model.User;
import utils.FileStorage;
import utils.PasswordValidator;
import java.util.HashMap;
import java.util.Map;
public class UserService {
private Map<String, User> users = new HashMap<>();
private FileStorage fileStorage = new FileStorage();
public UserService() {
users = fileStorage.loadUsers(); //加载所有已有的用户
}
/*注册用户第一阶段-生成验证码*/
public String registerUser(String email) {
if (users.containsKey(email) && users.get(email).isRegistered()){
return "registered"; //注册过的用户不能再注册
//return null;
}
if (users.containsKey(email)) {
return "recheck your email"; //已发过验证码也不再发了
//return null;
}
String code = String.valueOf(100000 + (int)(Math.random() * 900000)); //生成6位注册码
users.put(email, new User(email, code));
fileStorage.saveUsers(users);
return code;
}
/*注册用户第二阶段-完成注册*/
public boolean completeRegistration(String email, String code, String password) {
User user = users.get(email);
if (user != null && user.getRegistrationCode().equals(code) && PasswordValidator.isValid(password)) {
user.setPassword(password);
user.setRegistered(true);
fileStorage.saveUsers(users);
return true;
}
return false;
}
/*用户登录*/
public boolean login(String email, String password) {
User user = users.get(email);
return user != null && user.isRegistered() && user.getPassword().equals(password);
}
/*修改密码*/
public boolean changePassword(String email, String oldPassword, String newPassword) {
User user = users.get(email);
if (user != null && user.getPassword().equals(oldPassword) && PasswordValidator.isValid(newPassword)) {
user.setPassword(newPassword);
fileStorage.saveUsers(users);
return true;
}
return false;
}
}

@ -0,0 +1,12 @@
package utils;
import java.util.regex.Pattern;
public class EmailValidator {
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
/*只检查邮箱的格式是否正确,但不保证这个邮箱存在*/
public static boolean isValid(String email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
}

@ -0,0 +1,30 @@
package utils;
import model.User;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class FileStorage {
/*完全擦除原来的文件并重新写入*/
public void saveUsers(Map<String, User> users) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users.dat"))) {
oos.writeObject(users);
} catch (IOException e) {
e.printStackTrace();
}
}
/*从文件中载入*/
@SuppressWarnings("unchecked")
public Map<String, User> loadUsers() {
File file = new File("users.dat");
if (!file.exists()) return new HashMap<>();
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users.dat"))) {
return (Map<String, User>) ois.readObject();
} catch (Exception e) {
return new HashMap<>();
}
}
}

@ -0,0 +1,18 @@
package utils;
public class PasswordValidator {
public static boolean isValid(String password) {
if (password == null || password.length() < 6 || password.length() > 10) {
return false;
}
boolean hasUpper = false;
boolean hasLower = false;
boolean hasDigit = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) hasUpper = true;
if (Character.isLowerCase(c)) hasLower = true;
if (Character.isDigit(c)) hasDigit = true;
}
return hasUpper && hasLower && hasDigit;
}
}

@ -1,7 +1,21 @@
package view;
import controller.AuthController;
import javax.swing.*;
import java.awt.*;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.Font;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Color;
public class ChangePasswordFrame extends JDialog {
private AuthController controller;

@ -1,8 +1,16 @@
package view;
import controller.ExamController;
import model.Question;
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.util.List;
public class ExamFrame extends JFrame {

@ -1,7 +1,20 @@
package view;
import controller.AuthController;
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.Font;
import java.awt.Dimension;
import java.awt.FlowLayout;
public class LoginFrame extends JFrame {
public LoginFrame(AuthController controller) {

@ -1,7 +1,18 @@
package view;
import controller.MainController;
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Font;
import java.awt.Dimension;
import java.awt.Color;
public class MainFrame extends JFrame {
private MainController controller;

@ -1,7 +1,22 @@
package view;
import controller.AuthController;
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.Font;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Color;
public class RegisterFrame extends JFrame {
public RegisterFrame(AuthController controller) {

@ -1,7 +1,13 @@
package view;
import controller.ExamController;
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.Font;
public class ResultFrame extends JFrame {
public ResultFrame(ExamController controller, int score, int total) {

Loading…
Cancel
Save