第四次合并 #4

Merged
hnu202326010321 merged 6 commits from wuronghai_branch into chenchenghuan_branch 4 months ago

@ -0,0 +1,21 @@
import controller.NavigationController;
import javax.swing.UIManager;
import javax.swing.SwingUtilities;
public class MathLearningApp {
public static void main(String[] args) {
// Set system look and feel
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
// Start the application
SwingUtilities.invokeLater(() -> {
// 获取 NavigationController 实例并显示登录界面
NavigationController navigationController = NavigationController.getInstance();
navigationController.showLoginView();
});
}
}

@ -0,0 +1,41 @@
package controller;
import javax.swing.JOptionPane;
public abstract class BaseController {
protected void validateNotNull(Object obj, String fieldName) {
if (obj == null) {
throw new IllegalArgumentException(fieldName + "不能为空");
}
}
protected void validateStringNotEmpty(String str, String fieldName) {
if (str == null || str.trim().isEmpty()) {
throw new IllegalArgumentException(fieldName + "不能为空");
}
}
protected void validateStringLength(String str, String fieldName, int min, int max) {
validateStringNotEmpty(str, fieldName);
if (str.length() < min || str.length() > max) {
throw new IllegalArgumentException(fieldName + "长度应在" + min + "-" + max + "之间");
}
}
protected void showError(String message) {
JOptionPane.showMessageDialog(null, message, "错误", JOptionPane.ERROR_MESSAGE);
}
protected void showSuccess(String message) {
JOptionPane.showMessageDialog(null, message, "成功", JOptionPane.INFORMATION_MESSAGE);
}
protected void logInfo(String message) {
//System.out.println(" " + message);
}
protected void logError(String message) {
System.err.println("❌ " + message);
}
}

@ -0,0 +1,127 @@
package controller;
import model.User;
import view.LoginView;
import view.RegisterView;
import view.MainView;
import view.ExamView;
import view.ResultView;
public class NavigationController extends BaseController implements NavigationService {
private static NavigationController instance;
private LoginView loginView;
private RegisterView registerView;
private MainView mainView;
private ExamView examView;
private ResultView resultView;
private User currentUser;
// 单例模式
public static NavigationController getInstance() {
if (instance == null) {
instance = new NavigationController();
}
return instance;
}
private NavigationController() {
// 私有构造函数
}
@Override
public void showLoginView() {
if (loginView == null) {
loginView = new LoginView();
}
loginView.setVisible(true);
hideAllExcept(loginView);
logInfo("显示登录界面");
}
@Override
public void showRegisterView() {
if (registerView == null) {
registerView = new RegisterView();
}
registerView.setVisible(true);
hideAllExcept(registerView);
logInfo("显示注册界面");
}
@Override
public void showMainView(User user) {
setCurrentUser(user);
if (mainView != null) {
mainView.dispose();
}
mainView = new MainView(user);
mainView.setVisible(true);
hideAllExcept(mainView);
logInfo("显示主界面 - 用户: " + user.getUsername());
}
@Override
public void showMainView() {
if (currentUser != null) {
showMainView(currentUser);
} else {
showLoginView();
}
}
@Override
public void showExamView(String difficulty, int questionCount) {
try {
validateStringNotEmpty(difficulty, "难度");
if (questionCount <= 0) {
throw new IllegalArgumentException("题目数量必须大于0");
}
if (examView == null) {
examView = new ExamView();
}
examView.startNewExam(difficulty, questionCount);
examView.setVisible(true);
hideAllExcept(examView);
logInfo("显示考试界面 - " + difficulty + "难度, " + questionCount + "道题");
} catch (Exception e) {
logError("显示考试界面异常: " + e.getMessage());
showError("无法开始考试: " + e.getMessage());
}
}
@Override
public void showResultView(int score, int total, double percentage) {
try {
if (resultView == null) {
resultView = new ResultView();
}
resultView.setResults(score, total, percentage);
resultView.setVisible(true);
hideAllExcept(resultView);
logInfo("显示成绩界面 - 得分: " + score + "/" + total);
} catch (Exception e) {
logError("显示成绩界面异常: " + e.getMessage());
}
}
@Override
public void setCurrentUser(User user) {
this.currentUser = user;
logInfo("设置当前用户: " + (user != null ? user.getUsername() : "null"));
}
@Override
public User getCurrentUser() {
return currentUser;
}
private void hideAllExcept(javax.swing.JFrame visibleFrame) {
javax.swing.JFrame[] frames = {loginView, registerView, mainView, examView, resultView};
for (javax.swing.JFrame frame : frames) {
if (frame != null && frame != visibleFrame) {
frame.setVisible(false);
}
}
}
}

@ -0,0 +1,14 @@
package controller;
import model.User;
public interface NavigationService {
void showLoginView();
void showRegisterView();
void showMainView(User user);
void showMainView();
void showExamView(String difficulty, int questionCount);
void showResultView(int score, int total, double percentage);
void setCurrentUser(User user);
User getCurrentUser();
}

@ -0,0 +1,117 @@
package controller;
import model.Question;
import model.QuestionGenerator;
import utils.FileUtil;
import java.util.List;
import java.util.Arrays;
public class QuestionController extends BaseController implements QuestionService {
private QuestionGenerator generator = new QuestionGenerator();
private List<Question> currentQuestions;
private int currentQuestionIndex = 0;
private int score = 0;
private int[] userAnswers;
private String currentDifficulty;
private int currentQuestionCount;
@Override
public void startNewExam(String difficulty, int questionCount) {
try {
validateStringNotEmpty(difficulty, "难度");
if (questionCount <= 0 || questionCount > 100) {
throw new IllegalArgumentException("题目数量必须在1-100之间");
}
currentQuestions = generator.generateQuestions(questionCount, difficulty);
currentQuestionIndex = 0;
score = 0;
userAnswers = new int[questionCount];
Arrays.fill(userAnswers, -1);
currentDifficulty = difficulty;
currentQuestionCount = questionCount;
FileUtil.saveCurrentExam(currentQuestions, difficulty, questionCount);
logInfo("生成新试卷: " + difficulty + " 难度, " + questionCount + " 道题");
} catch (Exception e) {
logError("开始新考试异常: " + e.getMessage());
throw e;
}
}
@Override
public String getCurrentExamInfo() {
if (currentDifficulty != null && currentQuestionCount > 0) {
return currentDifficulty + "难度 - " + currentQuestionCount + "道题";
}
return "暂无试卷";
}
@Override
public Question getCurrentQuestion() {
if (currentQuestions == null || currentQuestionIndex >= currentQuestions.size()) {
return null;
}
return currentQuestions.get(currentQuestionIndex);
}
@Override
public void submitAnswer(int answer) {
if (currentQuestionIndex < userAnswers.length) {
userAnswers[currentQuestionIndex] = answer;
logInfo("提交答案: 第" + getCurrentQuestionNumber() + "题, 答案: " + answer);
}
}
@Override
public boolean nextQuestion() {
currentQuestionIndex++;
boolean hasNext = currentQuestionIndex < currentQuestions.size();
logInfo("下一题: " + (hasNext ? "有" : "无"));
return hasNext;
}
@Override
public boolean previousQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
logInfo("上一题: 第" + getCurrentQuestionNumber() + "题");
return true;
}
return false;
}
@Override
public int calculateScore() {
score = 0;
for (int i = 0; i < currentQuestions.size(); i++) {
if (userAnswers[i] != -1 && currentQuestions.get(i).isCorrect(userAnswers[i])) {
score++;
}
}
logInfo("计算得分: " + score + "/" + currentQuestions.size());
return score;
}
@Override
public double getPercentage() {
double percentage = (double) score / currentQuestions.size() * 100;
logInfo("正确率: " + String.format("%.1f%%", percentage));
return percentage;
}
@Override
public int getCurrentQuestionNumber() {
return currentQuestionIndex + 1;
}
@Override
public int getTotalQuestions() {
return currentQuestions != null ? currentQuestions.size() : 0;
}
@Override
public int getUserAnswerForCurrentQuestion() {
return userAnswers[currentQuestionIndex];
}
}

@ -0,0 +1,17 @@
package controller;
import model.Question;
public interface QuestionService {
void startNewExam(String difficulty, int questionCount);
Question getCurrentQuestion();
void submitAnswer(int answer);
boolean nextQuestion();
boolean previousQuestion();
int calculateScore();
double getPercentage();
int getCurrentQuestionNumber();
int getTotalQuestions();
int getUserAnswerForCurrentQuestion();
String getCurrentExamInfo();
}

@ -0,0 +1,156 @@
package controller;
import model.User;
import utils.FileUtil;
import utils.ValidationUtil;
import utils.EmailUtil;
public class UserController extends BaseController implements UserService {
@Override
public String registerUser(String username, String email) {
try {
validateStringLength(username, "用户名", 2, 20);
validateStringNotEmpty(email, "邮箱");
if (!ValidationUtil.isValidEmail(email)) {
return "邮箱格式不正确";
}
if (FileUtil.usernameExists(username)) {
return "用户名已被使用";
}
if (FileUtil.emailExists(email)) {
return "该邮箱已被注册";
}
String registrationCode = EmailUtil.generateRegistrationCode();
User user = new User(username, email, registrationCode);
boolean sendSuccess = EmailUtil.sendRegistrationCode(email, username, registrationCode);
if (sendSuccess) {
FileUtil.saveUser(user);
logInfo("用户注册成功: " + username);
return "注册码已发送到您的邮箱,请查收!";
} else {
return "邮件发送失败,请检查邮箱地址或稍后重试";
}
} catch (IllegalArgumentException e) {
return e.getMessage();
} catch (Exception e) {
logError("用户注册异常: " + e.getMessage());
return "系统错误,请稍后重试";
}
}
@Override
public String completeRegistration(String username, String code, String password, String confirmPassword) {
try {
validateStringNotEmpty(username, "用户名");
validateStringNotEmpty(code, "注册码");
validateStringNotEmpty(password, "密码");
validateStringNotEmpty(confirmPassword, "确认密码");
User user = FileUtil.loadUserByUsername(username);
if (user == null) {
return "用户不存在或注册码未发送";
}
if (!user.getRegistrationCode().equals(code)) {
return "注册码不正确";
}
if (!password.equals(confirmPassword)) {
return "两次输入的密码不一致";
}
if (!ValidationUtil.isValidPassword(password)) {
return "密码必须为6-10位且包含大小写字母和数字";
}
user.setPassword(password);
user.setRegistered(true);
FileUtil.saveUser(user);
logInfo("用户完成注册: " + username);
return "注册成功!";
} catch (Exception e) {
logError("完成注册异常: " + e.getMessage());
return "系统错误,请稍后重试";
}
}
@Override
public User login(String loginId, String password) {
try {
validateStringNotEmpty(loginId, "登录ID");
validateStringNotEmpty(password, "密码");
User user;
if (loginId.contains("@")) {
user = FileUtil.loadUserByEmail(loginId);
} else {
user = FileUtil.loadUserByUsername(loginId);
}
if (user == null) {
logInfo("登录失败: 用户不存在 - " + loginId);
return null;
}
if (user.getPassword() == null || !user.getPassword().equals(password)) {
logInfo("登录失败: 密码错误 - " + loginId);
return null;
}
logInfo("登录成功: " + user.getUsername());
return user;
} catch (Exception e) {
logError("登录异常: " + e.getMessage());
return null;
}
}
@Override
public String changePassword(User user, String oldPassword, String newPassword, String confirmPassword) {
try {
validateNotNull(user, "用户");
validateStringNotEmpty(oldPassword, "原密码");
validateStringNotEmpty(newPassword, "新密码");
validateStringNotEmpty(confirmPassword, "确认密码");
if (!user.getPassword().equals(oldPassword)) {
return "原密码不正确";
}
if (!newPassword.equals(confirmPassword)) {
return "两次输入的新密码不一致";
}
if (!ValidationUtil.isValidPassword(newPassword)) {
return "新密码必须为6-10位且包含大小写字母和数字";
}
user.setPassword(newPassword);
FileUtil.saveUser(user);
logInfo("密码修改成功: " + user.getUsername());
return "密码修改成功";
} catch (Exception e) {
logError("修改密码异常: " + e.getMessage());
return "系统错误,请稍后重试";
}
}
@Override
public void cleanupUnregisteredUsers() {
try {
FileUtil.cleanupUnregisteredUsers();
logInfo("执行未注册用户清理");
} catch (Exception e) {
logError("清理未注册用户异常: " + e.getMessage());
}
}
}

@ -0,0 +1,11 @@
package controller;
import model.User;
public interface UserService {
String registerUser(String username, String email);
String completeRegistration(String username, String code, String password, String confirmPassword);
User login(String loginId, String password);
String changePassword(User user, String oldPassword, String newPassword, String confirmPassword);
void cleanupUnregisteredUsers();
}

@ -0,0 +1,36 @@
package model;
import java.io.Serializable;
public class Question implements Serializable {
private static final long serialVersionUID = 1L;
private String question;
private String[] options;
private int correctAnswer;
private String difficulty;
public Question(String question, String[] options, int correctAnswer, String difficulty) {
this.question = question;
this.options = options;
this.correctAnswer = correctAnswer;
this.difficulty = difficulty;
}
// Getters
public String getQuestion() { return question; }
public String[] getOptions() { return options; }
public boolean isCorrect(int selectedAnswer) {
return selectedAnswer == correctAnswer;
}
@Override
public String toString() {
return "Question{" +
"question='" + question + '\'' +
", difficulty='" + difficulty + '\'' +
'}';
}
}

@ -0,0 +1,282 @@
package model;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Random;
import java.text.DecimalFormat;
public class QuestionGenerator {
private Random random = new Random();
private DecimalFormat df = new DecimalFormat("#.##"); // 保留两位小数的格式化器
public List<Question> generateQuestions(int count, String difficulty) {
List<Question> questions = new ArrayList<>();
Set<String> existingQuestions = new HashSet<>();
while (questions.size() < count) {
Question question = generateQuestion(difficulty);
String questionKey = question.getQuestion() + Arrays.toString(question.getOptions());
if (!existingQuestions.contains(questionKey)) {
questions.add(question);
existingQuestions.add(questionKey);
}
}
return questions;
}
private Question generateQuestion(String difficulty) {
switch (difficulty) {
case "小学":
return generatePrimaryQuestion();
case "初中":
return generateMiddleSchoolQuestion();
case "高中":
return generateHighSchoolQuestion();
default:
return generatePrimaryQuestion();
}
}
private Question generatePrimaryQuestion() {
int num1 = random.nextInt(50) + 1;
int num2 = random.nextInt(50) + 1;
int num3 = random.nextInt(50) + 1;
char[] operators = {'+', '-', '*', '/'};
char operator1 = operators[random.nextInt(operators.length)];
char operator2 = operators[random.nextInt(operators.length)];
String question;
double correctAnswer;
// 随机决定是否使用括号
if (random.nextBoolean()) {
// 有括号的情况
switch (random.nextInt(3)) {
case 0: // (a op b) op c
correctAnswer = calculate(calculate(num1, num2, operator1), num3, operator2);
question = "(" + num1 + " " + getOperatorSymbol(operator1) + " " + num2 + ") " +
getOperatorSymbol(operator2) + " " + num3 + " = ?";
break;
case 1: // a op (b op c)
correctAnswer = calculate(num1, calculate(num2, num3, operator2), operator1);
question = num1 + " " + getOperatorSymbol(operator1) + " (" + num2 + " " +
getOperatorSymbol(operator2) + " " + num3 + ") = ?";
break;
default: // 无括号
correctAnswer = calculate(calculate(num1, num2, operator1), num3, operator2);
question = num1 + " " + getOperatorSymbol(operator1) + " " + num2 + " " +
getOperatorSymbol(operator2) + " " + num3 + " = ?";
break;
}
} else {
if (operator1 == '/') {
num2 = random.nextInt(10) + 1; // 避免除零
}
correctAnswer = calculate(num1, num2, operator1);
question = num1 + " " + getOperatorSymbol(operator1) + " " + num2 + " = ?";
}
correctAnswer = Double.parseDouble(df.format(correctAnswer));
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "小学");
}
private Question generateMiddleSchoolQuestion() {
String question;
double correctAnswer;
if (random.nextBoolean()) {
// 平方或平方根题目
int num = random.nextInt(20) + 1;
if (random.nextBoolean()) {
correctAnswer = Math.pow(num, 2);
question = num + "² = ?";
} else {
int squared = num * num;
correctAnswer = num;
question = "√" + squared + " = ?";
}
} else {
// 带加减乘除的题目
int num1 = random.nextInt(50) + 1;
int num2 = random.nextInt(50) + 1;
int num3 = random.nextInt(20) + 1;
char[] operators = {'+', '-', '*', '/'};
char operator1 = operators[random.nextInt(operators.length)];
char operator2 = operators[random.nextInt(operators.length)];
// 随机组合平方、平方根和基本运算
if (random.nextBoolean()) {
// 平方与基本运算组合
correctAnswer = calculate(Math.pow(num1, 2), num2, operator1);
question = num1 + "² " + getOperatorSymbol(operator1) + " " + num2 + " = ?";
} else {
// 平方根与基本运算组合
int squared = num3 * num3;
correctAnswer = calculate(num3, num2, operator1);
question = "√" + squared + " " + getOperatorSymbol(operator1) + " " + num2 + " = ?";
}
}
correctAnswer = Double.parseDouble(df.format(correctAnswer));
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "初中");
}
private Question generateHighSchoolQuestion() {
if (random.nextBoolean()) {
return HighSchoolQuestionOne();
} else {
// 三角函数与基本运算组合
return HighSchoolQuestionTwo();
}
}
private Question HighSchoolQuestionOne() {
String question;
double correctAnswer;
double angle = random.nextInt(360);
String[] trigFunctions = {"sin", "cos", "tan"};
String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)];
switch (trigFunction) {
case "sin":
correctAnswer = Math.sin(Math.toRadians(angle));
question = "sin(" + angle + "°) = ?";
break;
case "cos":
correctAnswer = Math.cos(Math.toRadians(angle));
question = "cos(" + angle + "°) = ?";
break;
case "tan":
angle = getSafeTanAngle();
correctAnswer = Math.tan(Math.toRadians(angle));
question = "tan(" + angle + "°) = ?";
break;
default:
correctAnswer = 0;
question = "";
}
correctAnswer = Double.parseDouble(df.format(correctAnswer));
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "高中");
}
private Question HighSchoolQuestionTwo() {
String question;
double correctAnswer;
double angle = random.nextInt(360);
int num = random.nextInt(20) + 1;
char[] operators = {'+', '-', '*', '/'};
char operator = operators[random.nextInt(operators.length)];
String[] trigFunctions = {"sin", "cos", "tan"};
String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)];
double trigValue;
switch (trigFunction) {
case "sin":
trigValue = Math.sin(Math.toRadians(angle));
question = "sin(" + angle + "°) " + getOperatorSymbol(operator) + " " + num + " = ?";
break;
case "cos":
trigValue = Math.cos(Math.toRadians(angle));
question = "cos(" + angle + "°) " + getOperatorSymbol(operator) + " " + num + " = ?";
break;
case "tan":
angle = getSafeTanAngle();
trigValue = Math.tan(Math.toRadians(angle));
question = "tan(" + angle + "°) " + getOperatorSymbol(operator) + " " + num + " = ?";
break;
default:
trigValue = 0;
question = "";
}
trigValue = Double.parseDouble(df.format(trigValue));
correctAnswer = calculate(trigValue, num, operator);
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "高中");
}
// 获取安全的tan角度更严格的范围
private double getSafeTanAngle() {
// 为tan函数提供更安全的角度范围避免无限大的值
// 排除85°-95°, 265°-275° 等接近90°和270°的角度
List<Double> safeTanAngles = new ArrayList<>();
for (int i = 0; i < 360; i++) {
if (isSafeForTan(i)) {
safeTanAngles.add((double) i);
}
}
return safeTanAngles.get(random.nextInt(safeTanAngles.size()));
}
// 检查角度是否对tan函数安全更严格的条件
private boolean isSafeForTan(double angle) {
// 排除接近90°、270°的角度以及排除tan值过大的角度
return !((angle >= 80 && angle <= 100) ||
(angle >= 260 && angle <= 280) ||
(angle >= 170 && angle <= 190) ||
(angle >= 350 || angle <= 10));
}
// 计算方法
private double calculate(double a, double b, char operator) {
switch (operator) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return b != 0 ? a / b : a; // 避免除零
default:
return 0;
}
}
// 获取运算符符号
private String getOperatorSymbol(char operator) {
switch (operator) {
case '+':
return "+";
case '-':
return "-";
case '*':
return "×";
case '/':
return "÷";
default:
return "";
}
}
private String[] generateOptions(double correctAnswer) {
String[] options = new String[4];
int correctIndex = random.nextInt(4);
for (int i = 0; i < 4; i++) {
if (i == correctIndex) {
options[i] = df.format(correctAnswer);
} else {
double wrongAnswer;
do {
double variation = (random.nextDouble() - 0.5) * 10;
wrongAnswer = correctAnswer + variation;
wrongAnswer = Double.parseDouble(df.format(wrongAnswer));
} while (Math.abs(wrongAnswer - correctAnswer) < 0.1);
options[i] = String.valueOf(wrongAnswer);
}
}
return options;
}
private int getCorrectOptionIndex(String[] options, double correctAnswer) {
for (int i = 0; i < options.length; i++) {
if (Double.parseDouble(options[i]) == correctAnswer) {
return i;
}
}
return 0;
}
}

@ -0,0 +1,30 @@
package model;
public class User {
private String username; // 新增:用户名
private String email;
private String password;
private String registrationCode;
private boolean isRegistered;
public User(String username, String email, String registrationCode) {
this.username = username;
this.email = email;
this.registrationCode = registrationCode;
this.isRegistered = false;
}
// Getters and Setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
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; }
}
Loading…
Cancel
Save