wangjiawei_branch
wang421 4 months ago
parent 04d4f2745c
commit adf512bee2

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.mathlearning.Main

@ -1,196 +0,0 @@
# src/app.py
import customtkinter as ctk
import random
import string
from user_manager import UserManager, send_verification_email
from quiz_generator import get_generator
class MathApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("小初高数学学习软件")
self.geometry("500x450")
ctk.set_appearance_mode("System")
self.user_manager = UserManager()
self.current_user = None
container = ctk.CTkFrame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
# 还原:只包含基础的五个页面
for F in (LoginPage, RegisterPage, MainPage, QuizPage, ScorePage):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("LoginPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class LoginPage(ctk.CTkFrame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
ctk.CTkLabel(self, text="登录", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=20)
self.email_entry = ctk.CTkEntry(self, placeholder_text="邮箱", width=200)
self.email_entry.pack(pady=10)
self.password_entry = ctk.CTkEntry(self, placeholder_text="密码", show="*", width=200)
self.password_entry.pack(pady=10)
self.error_label = ctk.CTkLabel(self, text="", text_color="red")
self.error_label.pack(pady=5)
ctk.CTkButton(self, text="登录", command=self.login).pack(pady=10)
ctk.CTkButton(self, text="没有账户?去注册", fg_color="transparent", command=lambda: controller.show_frame("RegisterPage")).pack()
def login(self):
email, password = self.email_entry.get(), self.password_entry.get()
if self.controller.user_manager.validate_login(email, password):
self.controller.current_user = email
self.error_label.configure(text="")
self.controller.show_frame("MainPage")
else:
self.error_label.configure(text="邮箱或密码错误")
class RegisterPage(ctk.CTkFrame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.code = ""
self.cooldown_time = 0
ctk.CTkLabel(self, text="注册新账户", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=10)
self.email_entry = ctk.CTkEntry(self, placeholder_text="邮箱", width=250)
self.email_entry.pack(pady=5)
self.send_code_button = ctk.CTkButton(self, text="发送验证码", command=self.send_code, width=250)
self.send_code_button.pack(pady=5)
self.code_entry = ctk.CTkEntry(self, placeholder_text="输入收到的邮件验证码", width=250)
self.code_entry.pack(pady=5)
self.password_entry = ctk.CTkEntry(self, placeholder_text="密码 (6-10位,含大小写和数字)", show="*", width=250)
self.password_entry.pack(pady=5)
self.confirm_password_entry = ctk.CTkEntry(self, placeholder_text="确认密码", show="*", width=250)
self.confirm_password_entry.pack(pady=5)
self.info_label = ctk.CTkLabel(self, text="", text_color="red")
self.info_label.pack(pady=5)
ctk.CTkButton(self, text="注册", command=self.register).pack(pady=10)
ctk.CTkButton(self, text="返回登录", fg_color="transparent", command=lambda: controller.show_frame("LoginPage")).pack()
def update_cooldown(self):
if self.cooldown_time > 0:
self.send_code_button.configure(text=f"{self.cooldown_time}秒后可重试")
self.cooldown_time -= 1
self.after(1000, self.update_cooldown)
else:
self.send_code_button.configure(text="发送验证码", state="normal")
def send_code(self):
if self.cooldown_time > 0: return
email = self.email_entry.get().strip()
if not email:
self.info_label.configure(text="请输入邮箱地址。", text_color="red")
return
if self.controller.user_manager.is_email_registered(email):
self.info_label.configure(text="该邮箱已被注册。", text_color="red")
return
self.code = ''.join(random.choices(string.digits, k=6))
self.info_label.configure(text="正在发送邮件,请稍候...", text_color="gray")
success = send_verification_email(email, self.code)
if success:
self.info_label.configure(text="邮件验证码已发送至您的邮箱,请查收。", text_color="green")
self.cooldown_time = 60
self.send_code_button.configure(state="disabled")
self.update_cooldown()
else:
self.info_label.configure(text="邮件发送失败,请检查邮箱或网络。", text_color="red")
self.code = ""
def register(self):
email, user_code, password, confirm_password = self.email_entry.get().strip(), self.code_entry.get().strip(), self.password_entry.get(), self.confirm_password_entry.get()
if not self.code or user_code != self.code:
self.info_label.configure(text="邮件验证码错误", text_color="red")
return
if password != confirm_password:
self.info_label.configure(text="两次输入的密码不一致", text_color="red")
return
if not self.controller.user_manager.validate_password_format(password):
self.info_label.configure(text="密码格式不符合要求", text_color="red")
return
success, msg = self.controller.user_manager.register_user(email, password)
self.info_label.configure(text=msg, text_color="green" if success else "red")
if success: self.code = ""
class MainPage(ctk.CTkFrame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
ctk.CTkLabel(self, text="选择学习阶段", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=20)
ctk.CTkButton(self, text="小学", command=lambda: self.start_quiz("小学"), height=40, width=150).pack(pady=10)
ctk.CTkButton(self, text="初中", command=lambda: self.start_quiz("初中"), height=40, width=150).pack(pady=10)
ctk.CTkButton(self, text="高中", command=lambda: self.start_quiz("高中"), height=40, width=150).pack(pady=10)
ctk.CTkButton(self, text="退出登录", fg_color="transparent", command=lambda: controller.show_frame("LoginPage")).pack(side="bottom", pady=20)
def start_quiz(self, level):
dialog = ctk.CTkInputDialog(text="请输入题目数量 (1-20):", title="设置题目数量")
num_str = dialog.get_input()
if num_str:
try:
num = int(num_str)
if 1 <= num <= 20:
self.controller.frames["QuizPage"].setup_quiz(level, num)
self.controller.show_frame("QuizPage")
except ValueError: pass
class QuizPage(ctk.CTkFrame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.question_label = ctk.CTkLabel(self, text="问题", font=ctk.CTkFont(size=18))
self.question_label.pack(pady=20, padx=20)
self.radio_var = ctk.IntVar(value=-1)
self.radio_buttons = []
for i in range(4):
rb = ctk.CTkRadioButton(self, text="", variable=self.radio_var, value=i, font=ctk.CTkFont(size=14))
rb.pack(anchor="w", padx=100, pady=5)
self.radio_buttons.append(rb)
self.submit_button = ctk.CTkButton(self, text="下一题", command=self.next_question)
self.submit_button.pack(pady=20)
def setup_quiz(self, level, num_questions):
self.questions = get_generator(level).generate_bunch(num_questions)
self.current_question_index = 0
self.user_answers = []
self.display_question()
def display_question(self):
self.radio_var.set(-1)
q = self.questions[self.current_question_index]
self.question_label.configure(text=f"{self.current_question_index + 1}. {q.text} = ?")
for i, option in enumerate(q.options): self.radio_buttons[i].configure(text=str(option))
self.submit_button.configure(text="提交试卷" if self.current_question_index == len(self.questions) - 1 else "下一题")
def next_question(self):
if self.radio_var.get() == -1: return
self.user_answers.append(self.radio_var.get())
self.current_question_index += 1
if self.current_question_index < len(self.questions): self.display_question()
else: self.calculate_score()
def calculate_score(self):
correct_count = sum(1 for i, q in enumerate(self.questions) if self.user_answers[i] == q.correct_answer_index)
total = len(self.questions)
score_percent = (correct_count / total) * 100
self.controller.frames["ScorePage"].show_score(correct_count, total, score_percent)
self.controller.show_frame("ScorePage")
class ScorePage(ctk.CTkFrame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.score_label = ctk.CTkLabel(self, text="", font=ctk.CTkFont(size=24))
self.score_label.pack(pady=40, padx=20)
ctk.CTkButton(self, text="继续做题", command=lambda: controller.show_frame("MainPage")).pack(pady=10)
ctk.CTkButton(self, text="退出程序", command=controller.quit).pack(pady=10)
def show_score(self, correct, total, percent):
self.score_label.configure(text=f"答对了 {correct} / {total}\n\n得分: {percent:.2f}%")

@ -0,0 +1,15 @@
package com.mathlearning;
import com.mathlearning.controller.NavigationController;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
// 直接启动程序,不使用特殊的外观设置
SwingUtilities.invokeLater(() -> {
NavigationController navigationController = new NavigationController();
navigationController.showLoginView();
});
}
}

@ -0,0 +1,149 @@
package com.mathlearning.controller;
import com.mathlearning.model.User;
import com.mathlearning.model.UserManager;
import com.mathlearning.util.EmailUtil;
import com.mathlearning.util.PasswordValidator;
import com.mathlearning.util.UsernameValidator;
public class AuthController {
private UserManager userManager;
public AuthController() {
this.userManager = UserManager.getInstance();
}
/**
*
*/
public String sendVerificationCode(String email, String username) {
if (!isValidEmail(email)) {
return null;
}
if (!UsernameValidator.isValid(username)) {
return null;
}
// 检查邮箱是否已被注册
if (userManager.userExistsByEmail(email)) {
User existingUser = userManager.getUserByEmail(email);
if (existingUser.isRegistered()) {
return null; // 邮箱已被注册
}
// 如果用户存在但未注册,可以重新发送验证码
}
// 检查用户名是否已被使用
if (userManager.userExistsByUsername(username)) {
User existingUser = userManager.getUserByUsername(username);
if (existingUser.isRegistered() || !existingUser.getEmail().equals(email)) {
return null; // 用户名已被其他用户使用
}
// 如果是同一邮箱重新发送验证码,允许使用相同的用户名
}
// 发送验证码
String verificationCode = EmailUtil.sendVerificationCode(email);
return verificationCode;
}
/**
*
*/
public boolean createUnregisteredUser(String email, String username, String verificationCode) {
if (verificationCode == null) {
return false;
}
// 检查是否已存在未注册用户
User existingUser = userManager.getUserByEmail(email);
if (existingUser != null && !existingUser.isRegistered()) {
// 更新现有未注册用户的验证码
existingUser.setUsername(username);
existingUser.setVerificationCode(verificationCode);
} else {
// 创建新用户
User user = new User(email, username);
user.setVerificationCode(verificationCode);
userManager.addUser(user);
}
return true;
}
public boolean verifyCode(String email, String code) {
User user = userManager.getUserByEmail(email);
return user != null && code.equals(user.getVerificationCode());
}
public boolean setPassword(String email, String password, String confirmPassword) {
if (!password.equals(confirmPassword)) {
return false;
}
if (!PasswordValidator.isValid(password)) {
return false;
}
User user = userManager.getUserByEmail(email);
if (user != null) {
user.setPassword(password);
user.setRegistered(true);
userManager.saveUsers();
return true;
}
return false;
}
public boolean login(String identifier, String password) {
User user = userManager.getUser(identifier);
return user != null && user.isRegistered() &&
password.equals(user.getPassword());
}
public boolean changePassword(String identifier, String oldPassword, String newPassword, String confirmPassword) {
if (!newPassword.equals(confirmPassword)) {
return false;
}
if (!PasswordValidator.isValid(newPassword)) {
return false;
}
User user = userManager.getUser(identifier);
if (user != null && oldPassword.equals(user.getPassword())) {
user.setPassword(newPassword);
userManager.saveUsers();
return true;
}
return false;
}
private boolean isValidEmail(String email) {
return EmailUtil.isValidEmail(email);
}
public String getUserEmail(String identifier) {
User user = userManager.getUser(identifier);
return user != null ? user.getEmail() : identifier;
}
/**
*
*/
public boolean isEmailAvailable(String email) {
User user = userManager.getUserByEmail(email);
return user == null || !user.isRegistered();
}
/**
*
*/
public boolean isUsernameAvailable(String username) {
User user = userManager.getUserByUsername(username);
return user == null || !user.isRegistered();
}
}

@ -0,0 +1,247 @@
package com.mathlearning.controller;
import com.mathlearning.model.*;
import com.mathlearning.util.ExamFileUtil;
import java.util.*;
public class ExamController {
private Paper currentPaper;
private String currentUser;
private String currentLevel;
public ExamController() {
}
public void createExam(String level, int questionCount) {
System.out.println("=== 开始创建试卷 ===");
System.out.println("级别: " + level + ", 题目数量: " + questionCount);
long startTime = System.currentTimeMillis();
this.currentLevel = level;
try {
// 使用工厂方法创建对应的生成器
QuestionGenerator generator = QuestionGenerator.createGenerator(level);
System.out.println("生成器创建完成: " + generator.getClass().getSimpleName());
// 设置超时保护
List<Question> questions = generateQuestionsWithTimeout(generator, questionCount, 15000); // 15秒超时
System.out.println("题目生成完成,生成了 " + questions.size() + " 道题目");
// 额外的重复检查
int duplicateCount = checkForDuplicates(questions);
if (duplicateCount > 0) {
System.out.println("警告: 发现 " + duplicateCount + " 道重复题目");
}
currentPaper = new Paper(level);
for (Question question : questions) {
currentPaper.addQuestion(question);
}
long endTime = System.currentTimeMillis();
System.out.println("试卷创建完成,耗时: " + (endTime - startTime) + "ms");
System.out.println("=== 试卷创建结束 ===");
} catch (Exception e) {
System.err.println("创建试卷时发生错误: " + e.getMessage());
e.printStackTrace();
// 创建空试卷避免空指针
currentPaper = new Paper(level);
// 补充基础题目
List<Question> basicQuestions = generateBasicQuestions(questionCount);
for (Question question : basicQuestions) {
currentPaper.addQuestion(question);
}
}
}
/**
*
*/
private List<Question> generateQuestionsWithTimeout(QuestionGenerator generator, int count, long timeoutMs) {
long startTime = System.currentTimeMillis();
List<Question> questions = new ArrayList<>();
try {
List<Question> generated = generator.generateQuestions(count);
questions.addAll(generated);
// 检查是否超时
if ((System.currentTimeMillis() - startTime) > timeoutMs) {
System.out.println("生成题目超时,使用已生成的部分题目");
}
// 如果生成的题目超过所需数量,截取
if (questions.size() > count) {
questions = questions.subList(0, count);
}
} catch (Exception e) {
System.err.println("生成题目时出错: " + e.getMessage());
}
// 如果题目数量不足,补充基础题目
if (questions.size() < count) {
System.out.println("题目数量不足,补充基础题目...");
int needed = count - questions.size();
List<Question> basicQuestions = generateBasicQuestions(needed);
questions.addAll(basicQuestions);
}
return questions;
}
/**
*
*/
private List<Question> generateBasicQuestions(int count) {
System.out.println("生成 " + count + " 道基础题目");
List<Question> basicQuestions = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < count; i++) {
int a = random.nextInt(10) + 1;
int b = random.nextInt(10) + 1;
int operation = random.nextInt(2); // 0: 加法, 1: 减法
String expression;
int answer;
if (operation == 0) {
// 加法
expression = a + " + " + b;
answer = a + b;
} else {
// 减法,确保结果为正
if (a < b) {
int temp = a;
a = b;
b = temp;
}
expression = a + " - " + b;
answer = a - b;
}
// 生成选项
Set<String> options = new HashSet<>();
options.add(String.valueOf(answer));
while (options.size() < 4) {
int variation = random.nextInt(5) + 1;
int wrongAnswer;
if (random.nextBoolean()) {
wrongAnswer = answer + variation;
} else {
wrongAnswer = Math.max(1, answer - variation);
}
options.add(String.valueOf(wrongAnswer));
}
List<String> optionList = new ArrayList<>(options);
Collections.shuffle(optionList);
String optionsStr = String.join(",", optionList);
basicQuestions.add(new Question(expression + " = ?", optionsStr, String.valueOf(answer)));
}
return basicQuestions;
}
/**
*
* @return
*/
public boolean saveCurrentPaper() {
if (currentPaper == null || currentUser == null) {
System.err.println("无法保存试卷: 试卷或用户信息为空");
return false;
}
return ExamFileUtil.savePaperToFile(currentUser, currentPaper);
}
// 检查重复题目的辅助方法
private int checkForDuplicates(List<Question> questions) {
Set<String> expressions = new HashSet<>();
int duplicateCount = 0;
for (Question question : questions) {
String expression = extractExpression(question.getContent());
if (expressions.contains(expression)) {
duplicateCount++;
System.out.println("发现重复题目: " + question.getContent());
} else {
expressions.add(expression);
}
}
return duplicateCount;
}
// 从题目内容中提取表达式(去掉"= ?"等后缀)
private String extractExpression(String content) {
if (content == null) return "";
// 去掉"= ?"和可能的空格
return content.replace(" = ?", "").trim();
}
// 其他方法保持不变...
public Question getCurrentQuestion() {
return currentPaper != null ? currentPaper.getCurrentQuestion() : null;
}
public boolean hasNextQuestion() {
return currentPaper != null && currentPaper.hasNextQuestion();
}
public void nextQuestion() {
if (currentPaper != null) {
currentPaper.nextQuestion();
}
}
public boolean hasPreviousQuestion() {
return currentPaper != null && currentPaper.hasPreviousQuestion();
}
public void previousQuestion() {
if (currentPaper != null) {
currentPaper.previousQuestion();
}
}
public void submitAnswer(String answer) {
Question currentQuestion = getCurrentQuestion();
if (currentQuestion != null) {
currentQuestion.setUserAnswer(answer);
System.out.println("题目 " + getCurrentQuestionNumber() + " 答案设置为: " + answer);
}
}
public int calculateScore() {
return currentPaper != null ? currentPaper.calculateScore() : 0;
}
public int getCurrentQuestionNumber() {
return currentPaper != null ? currentPaper.getCurrentQuestionNumber() : 0;
}
public int getTotalQuestions() {
return currentPaper != null ? currentPaper.getTotalQuestions() : 0;
}
public String getCurrentLevel() {
return currentLevel;
}
public void setCurrentUser(String user) {
this.currentUser = user;
}
public Paper getCurrentPaper() {
return currentPaper;
}
}

@ -0,0 +1,86 @@
package com.mathlearning.controller;
import com.mathlearning.view.*;
import javax.swing.*;
import java.awt.*;
public class NavigationController {
private JFrame mainFrame;
private CardLayout cardLayout;
private JPanel mainPanel;
private String currentUser;
private String currentUserEmail;
public NavigationController() {
initializeMainFrame();
}
private void initializeMainFrame() {
mainFrame = new JFrame("数学学习软件");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setSize(600, 500);
mainFrame.setLocationRelativeTo(null);
cardLayout = new CardLayout();
mainPanel = new JPanel(cardLayout);
// Initialize with login view
LoginView loginView = new LoginView(this);
mainPanel.add(loginView, "LOGIN");
mainFrame.add(mainPanel);
}
public void showLoginView() {
cardLayout.show(mainPanel, "LOGIN");
mainFrame.setVisible(true);
}
public void showRegisterView() {
RegisterView registerView = new RegisterView(this);
mainPanel.add(registerView, "REGISTER");
cardLayout.show(mainPanel, "REGISTER");
}
public void showSetPasswordView(String email, String verificationCode) {
SetPasswordView setPasswordView = new SetPasswordView(this, email, verificationCode);
mainPanel.add(setPasswordView, "SET_PASSWORD");
cardLayout.show(mainPanel, "SET_PASSWORD");
}
public void showLevelSelectionView(String userIdentifier, String email) {
this.currentUser = userIdentifier;
this.currentUserEmail = email;
LevelSelectionView levelSelectionView = new LevelSelectionView(this, userIdentifier, email);
mainPanel.add(levelSelectionView, "LEVEL_SELECTION");
cardLayout.show(mainPanel, "LEVEL_SELECTION");
}
public void showQuestionCountView(String level) {
QuestionCountView questionCountView = new QuestionCountView(this, currentUser, currentUserEmail, level);
mainPanel.add(questionCountView, "QUESTION_COUNT");
cardLayout.show(mainPanel, "QUESTION_COUNT");
}
public void showExamView(String level, int questionCount) {
ExamView examView = new ExamView(this, currentUser, currentUserEmail, level, questionCount);
mainPanel.add(examView, "EXAM");
cardLayout.show(mainPanel, "EXAM");
}
public void showScoreView(String level, int score, int totalQuestions) {
ScoreView scoreView = new ScoreView(this, currentUser, currentUserEmail, level, score, totalQuestions);
mainPanel.add(scoreView, "SCORE");
cardLayout.show(mainPanel, "SCORE");
}
public void showChangePasswordView(String identifier) {
// 实现修改密码视图的显示
JOptionPane.showMessageDialog(mainFrame, "修改密码功能待实现", "提示", JOptionPane.INFORMATION_MESSAGE);
}
public JFrame getMainFrame() {
return mainFrame;
}
}

@ -0,0 +1,73 @@
package com.mathlearning.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Paper implements Serializable {
private static final long serialVersionUID = 1L;
private List<Question> questions;
private int currentQuestionIndex;
private String level;
public Paper(String level) {
this.questions = new ArrayList<>();
this.currentQuestionIndex = 0;
this.level = level;
}
public void addQuestion(Question question) {
questions.add(question);
}
public Question getCurrentQuestion() {
if (currentQuestionIndex < questions.size()) {
return questions.get(currentQuestionIndex);
}
return null;
}
public boolean hasNextQuestion() {
return currentQuestionIndex < questions.size() - 1;
}
public void nextQuestion() {
if (hasNextQuestion()) {
currentQuestionIndex++;
}
}
public boolean hasPreviousQuestion() {
return currentQuestionIndex > 0;
}
public void previousQuestion() {
if (hasPreviousQuestion()) {
currentQuestionIndex--;
}
}
public int getTotalQuestions() {
return questions.size();
}
public int getCurrentQuestionNumber() {
return currentQuestionIndex + 1;
}
public int calculateScore() {
int correctCount = 0;
for (Question q : questions) {
if (q.isCorrect()) {
correctCount++;
}
}
return (int) ((correctCount * 100.0) / questions.size());
}
public String getLevel() { return level; }
public void setLevel(String level) { this.level = level; }
public List<Question> getQuestions() { return questions; }
}

@ -0,0 +1,39 @@
package com.mathlearning.model;
import java.io.Serializable;
public class Question implements Serializable {
private static final long serialVersionUID = 1L;
private String content;
private String[] options;
private String correctAnswer;
private String userAnswer;
public Question(String content, String options, String correctAnswer) {
this.content = content;
this.options = options.split(",");
this.correctAnswer = correctAnswer;
}
// Getters and setters
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String[] getOptions() { return options; }
public void setOptions(String[] options) { this.options = options; }
public String getCorrectAnswer() { return correctAnswer; }
public void setCorrectAnswer(String correctAnswer) { this.correctAnswer = correctAnswer; }
public String getUserAnswer() { return userAnswer; }
public void setUserAnswer(String userAnswer) { this.userAnswer = userAnswer; }
public boolean isCorrect() {
return correctAnswer != null && correctAnswer.equals(userAnswer);
}
public String getOptionsAsString() {
return String.join(",", options);
}
}

@ -0,0 +1,296 @@
package com.mathlearning.model;
import java.util.*;
public abstract class QuestionGenerator {
protected Random random = new Random();
public abstract List<Question> generateQuestions(int count);
protected String generateOptions(String correctAnswer) {
Set<String> options = new HashSet<>();
options.add(correctAnswer);
// 生成错误选项
while (options.size() < 4) {
try {
double correctValue = Double.parseDouble(correctAnswer);
double variation = correctValue * 0.2 + 1; // 20% 变化至少1
double wrongValue = correctValue + (random.nextDouble() * 2 - 1) * variation;
// 避免负数和零
if (wrongValue <= 0) {
wrongValue = Math.abs(wrongValue) + 1;
}
// 格式化选项
String formattedValue;
if (correctAnswer.contains(".")) {
formattedValue = String.format("%.2f", wrongValue);
} else {
formattedValue = String.valueOf((int) wrongValue);
}
options.add(formattedValue);
} catch (NumberFormatException e) {
// 如果解析失败,使用整数方法
int wrongValue = random.nextInt(20) + 1;
options.add(String.valueOf(wrongValue));
}
}
List<String> optionList = new ArrayList<>(options);
Collections.shuffle(optionList);
return String.join(",", optionList);
}
// 工厂方法
public static QuestionGenerator createGenerator(String level) {
switch (level) {
case "小学":
return new OptimizedPrimaryQuestionGenerator();
case "初中":
return new OptimizedMiddleSchoolQuestionGenerator();
case "高中":
return new OptimizedHighSchoolQuestionGenerator();
default:
return new OptimizedPrimaryQuestionGenerator();
}
}
// 生成随机操作数 (1-50) 减小范围提高效率
protected int generateOperand() {
return random.nextInt(50) + 1;
}
// 生成小范围操作数用于乘法
protected int generateSmallOperand() {
return random.nextInt(12) + 1;
}
}
// 优化的小学题目生成器
class OptimizedPrimaryQuestionGenerator extends QuestionGenerator {
@Override
public List<Question> generateQuestions(int count) {
System.out.println("开始生成小学题目,数量: " + count);
List<Question> questions = new ArrayList<>();
Set<String> usedExpressions = new HashSet<>();
String[][] templates = {
{"{0} + {1} = ?", "+"},
{"{0} - {1} = ?", "-"},
{"{0} × {1} = ?", "×"}
};
int maxAttempts = count * 5; // 减少尝试次数
int attempts = 0;
while (questions.size() < count && attempts < maxAttempts) {
attempts++;
String[] template = templates[random.nextInt(templates.length)];
String expression;
int answer;
switch (template[1]) {
case "+":
int a1 = generateOperand();
int b1 = generateOperand();
expression = a1 + " + " + b1;
answer = a1 + b1;
break;
case "-":
int a2 = generateOperand();
int b2 = generateOperand();
// 确保结果为正数
if (a2 < b2) {
int temp = a2;
a2 = b2;
b2 = temp;
}
expression = a2 + " - " + b2;
answer = a2 - b2;
break;
case "×":
int a3 = generateSmallOperand();
int b3 = generateSmallOperand();
expression = a3 + " × " + b3;
answer = a3 * b3;
break;
default:
continue;
}
// 检查表达式是否已使用
if (usedExpressions.contains(expression)) {
continue;
}
usedExpressions.add(expression);
String options = generateOptions(String.valueOf(answer));
questions.add(new Question(expression + " = ?", options, String.valueOf(answer)));
if (questions.size() % 10 == 0) {
System.out.println("已生成 " + questions.size() + " 道小学题目");
}
}
System.out.println("小学题目生成完成,实际生成: " + questions.size() + " 道题目");
// 如果因为重复问题无法生成足够的题目,补充简单题目
if (questions.size() < count) {
System.out.println("补充简单小学题目...");
for (int i = questions.size(); i < count; i++) {
int a = random.nextInt(10) + 1;
int b = random.nextInt(10) + 1;
String expression = a + " + " + b;
if (!usedExpressions.contains(expression)) {
int answer = a + b;
String options = generateOptions(String.valueOf(answer));
questions.add(new Question(expression + " = ?", options, String.valueOf(answer)));
usedExpressions.add(expression);
}
}
}
return questions;
}
}
// 优化的初中题目生成器
class OptimizedMiddleSchoolQuestionGenerator extends QuestionGenerator {
@Override
public List<Question> generateQuestions(int count) {
System.out.println("开始生成初中题目,数量: " + count);
List<Question> questions = new ArrayList<>();
Set<String> usedExpressions = new HashSet<>();
int maxAttempts = count * 5;
int attempts = 0;
while (questions.size() < count && attempts < maxAttempts) {
attempts++;
int type = random.nextInt(4); // 0-3: 平方、开方、混合、简单运算
String expression;
int answer;
switch (type) {
case 0: // 平方
int num = random.nextInt(15) + 1; // 1-15的平方
expression = num + "²";
answer = num * num;
break;
case 1: // 开根号
int root = random.nextInt(12) + 1; // 1-12
expression = "√" + (root * root);
answer = root;
break;
case 2: // 混合运算
int x = random.nextInt(8) + 2; // 2-9
int y = random.nextInt(8) + 2; // 2-9
expression = "(" + x + " + " + y + ")²";
answer = (x + y) * (x + y);
break;
default: // 简单运算
int a = generateOperand();
int b = generateOperand();
if (random.nextBoolean()) {
expression = a + " + " + b;
answer = a + b;
} else {
if (a < b) {
int temp = a;
a = b;
b = temp;
}
expression = a + " - " + b;
answer = a - b;
}
break;
}
if (usedExpressions.contains(expression)) {
continue;
}
usedExpressions.add(expression);
String options = generateOptions(String.valueOf(answer));
questions.add(new Question(expression + " = ?", options, String.valueOf(answer)));
if (questions.size() % 10 == 0) {
System.out.println("已生成 " + questions.size() + " 道初中题目");
}
}
System.out.println("初中题目生成完成,实际生成: " + questions.size() + " 道题目");
return questions;
}
}
// 优化的高中题目生成器
class OptimizedHighSchoolQuestionGenerator extends QuestionGenerator {
@Override
public List<Question> generateQuestions(int count) {
System.out.println("开始生成高中题目,数量: " + count);
List<Question> questions = new ArrayList<>();
Set<String> usedExpressions = new HashSet<>();
// 使用常见角度,避免复杂计算
int[] angles = {0, 30, 45, 60, 90, 120, 135, 150, 180};
String[] functions = {"sin", "cos", "tan"};
int maxAttempts = count * 5;
int attempts = 0;
while (questions.size() < count && attempts < maxAttempts) {
attempts++;
String function = functions[random.nextInt(functions.length)];
int angle = angles[random.nextInt(angles.length)];
String expression = function + "(" + angle + "°)";
double result;
switch (function) {
case "sin":
result = Math.sin(Math.toRadians(angle));
break;
case "cos":
result = Math.cos(Math.toRadians(angle));
break;
case "tan":
// 避免tan(90°)等未定义的情况
if (angle % 90 == 0 && angle % 180 != 0) {
continue;
}
result = Math.tan(Math.toRadians(angle));
// 限制tan值范围
if (Math.abs(result) > 10) {
continue;
}
break;
default:
result = 0;
}
String formattedAnswer = String.format("%.2f", result);
if (usedExpressions.contains(expression)) {
continue;
}
usedExpressions.add(expression);
String options = generateOptions(formattedAnswer);
questions.add(new Question(expression + " = ?", options, formattedAnswer));
if (questions.size() % 10 == 0) {
System.out.println("已生成 " + questions.size() + " 道高中题目");
}
}
System.out.println("高中题目生成完成,实际生成: " + questions.size() + " 道题目");
return questions;
}
}

@ -0,0 +1,42 @@
package com.mathlearning.model;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String email;
private String username; // 新增用户名字段
private String password;
private String verificationCode;
private boolean isRegistered;
public User(String email, String username) {
this.email = email;
this.username = username;
this.isRegistered = false;
}
// Getters and setters
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getVerificationCode() { return verificationCode; }
public void setVerificationCode(String verificationCode) {
this.verificationCode = verificationCode;
}
public boolean isRegistered() { return isRegistered; }
public void setRegistered(boolean registered) { isRegistered = registered; }
@Override
public String toString() {
return "User{username='" + username + "', email='" + email + "', registered=" + isRegistered + "}";
}
}

@ -0,0 +1,118 @@
package com.mathlearning.model;
import com.mathlearning.util.FileStorage;
import java.util.Map;
import java.util.HashMap;
public class UserManager {
private static UserManager instance;
private Map<String, User> usersByEmail; // 按邮箱存储
private Map<String, User> usersByUsername; // 按用户名存储
private UserManager() {
this.usersByEmail = FileStorage.loadUsersByEmail();
this.usersByUsername = FileStorage.loadUsersByUsername();
}
public static synchronized UserManager getInstance() {
if (instance == null) {
instance = new UserManager();
}
return instance;
}
public void addUser(User user) {
usersByEmail.put(user.getEmail(), user);
usersByUsername.put(user.getUsername(), user);
FileStorage.saveUsers(usersByEmail, usersByUsername);
}
public User getUserByEmail(String email) {
return usersByEmail.get(email);
}
public User getUserByUsername(String username) {
return usersByUsername.get(username);
}
// 通过邮箱或用户名查找用户
public User getUser(String identifier) {
User user = usersByEmail.get(identifier);
if (user == null) {
user = usersByUsername.get(identifier);
}
return user;
}
public boolean userExistsByEmail(String email) {
return usersByEmail.containsKey(email);
}
public boolean userExistsByUsername(String username) {
return usersByUsername.containsKey(username);
}
// 检查邮箱或用户名是否已存在
public boolean userExists(String identifier) {
return usersByEmail.containsKey(identifier) || usersByUsername.containsKey(identifier);
}
public boolean verifyUser(String identifier, String password) {
User user = getUser(identifier);
return user != null && user.isRegistered() &&
password.equals(user.getPassword());
}
public void saveUsers() {
FileStorage.saveUsers(usersByEmail, usersByUsername);
}
/**
*
*/
public boolean isEmailAvailable(String email) {
User user = usersByEmail.get(email);
return user == null || !user.isRegistered();
}
/**
*
*/
public boolean isUsernameAvailable(String username) {
User user = usersByUsername.get(username);
return user == null || !user.isRegistered();
}
/**
*
*/
public void removeUnregisteredUser(String email) {
User user = usersByEmail.get(email);
if (user != null && !user.isRegistered()) {
usersByEmail.remove(email);
usersByUsername.remove(user.getUsername());
saveUsers();
}
}
/**
*
*/
public int getUserCount() {
return usersByEmail.size();
}
/**
*
*/
public int getRegisteredUserCount() {
int count = 0;
for (User user : usersByEmail.values()) {
if (user.isRegistered()) {
count++;
}
}
return count;
}
}

@ -0,0 +1,62 @@
package com.mathlearning.util;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class EmailConfig {
private static final String CONFIG_FILE = "email_config.properties";
private static Properties properties;
static {
properties = new Properties();
try {
// 尝试从配置文件加载
properties.load(new FileInputStream(CONFIG_FILE));
} catch (IOException e) {
// 如果配置文件不存在,使用默认值
System.out.println("未找到配置文件,使用默认邮箱配置(需要修改为您的真实邮箱)");
setDefaultProperties();
}
}
private static void setDefaultProperties() {
// 这里设置您的邮箱配置 - 请修改为您的真实邮箱信息
properties.setProperty("email.from", "your-email@qq.com"); // 发件邮箱
properties.setProperty("email.password", "your-authorization-code"); // 授权码
properties.setProperty("email.smtp.host", "smtp.qq.com"); // SMTP服务器
properties.setProperty("email.smtp.port", "465"); // 端口
properties.setProperty("email.smtp.ssl", "true"); // 启用SSL
properties.setProperty("email.smtp.auth", "true"); // 需要认证
}
public static String getFrom() {
return properties.getProperty("email.from");
}
public static String getPassword() {
return properties.getProperty("email.password");
}
public static String getSmtpHost() {
return properties.getProperty("email.smtp.host");
}
public static String getSmtpPort() {
return properties.getProperty("email.smtp.port");
}
public static boolean isSslEnabled() {
return "true".equals(properties.getProperty("email.smtp.ssl"));
}
public static boolean isAuthEnabled() {
return "true".equals(properties.getProperty("email.smtp.auth"));
}
// 验证配置是否已设置(不是默认值)
public static boolean isConfigured() {
return !"your-email@qq.com".equals(getFrom()) &&
!"your-authorization-code".equals(getPassword());
}
}

@ -0,0 +1,133 @@
package com.mathlearning.util;
import javax.mail.*;
import javax.mail.internet.*;
import javax.swing.JOptionPane;
import java.util.Properties;
import java.util.Random;
public class EmailUtil {
private static final Random random = new Random();
/**
* -
*/
public static String sendVerificationCode(String toEmail) {
// 检查邮箱配置
if (!EmailConfig.isConfigured()) {
JOptionPane.showMessageDialog(null,
"邮箱配置未完成!\n请修改 email_config.properties 文件,配置您的邮箱信息。",
"配置错误",
JOptionPane.ERROR_MESSAGE);
return null;
}
// 生成6位验证码
String verificationCode = generateVerificationCode();
try {
// 发送邮件
boolean sendSuccess = sendEmail(toEmail, verificationCode);
if (sendSuccess) {
JOptionPane.showMessageDialog(
null,
"验证码已发送到您的邮箱!\n\n" +
"收件邮箱: " + toEmail + "\n" +
"请查看您的邮箱并输入验证码。",
"验证码发送成功",
JOptionPane.INFORMATION_MESSAGE
);
return verificationCode;
} else {
JOptionPane.showMessageDialog(null,
"验证码发送失败,请检查邮箱地址是否正确或稍后重试",
"发送失败",
JOptionPane.ERROR_MESSAGE);
return null;
}
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"发送验证码时发生错误: " + e.getMessage(),
"错误",
JOptionPane.ERROR_MESSAGE);
return null;
}
}
/**
*
*/
private static boolean sendEmail(String toEmail, String verificationCode) {
Properties props = new Properties();
props.put("mail.smtp.host", EmailConfig.getSmtpHost());
props.put("mail.smtp.port", EmailConfig.getSmtpPort());
props.put("mail.smtp.auth", "true");
if (EmailConfig.isSslEnabled()) {
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", EmailConfig.getSmtpPort());
}
// 创建会话
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(EmailConfig.getFrom(), EmailConfig.getPassword());
}
});
try {
// 创建邮件
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(EmailConfig.getFrom()));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
message.setSubject("数学学习软件 - 邮箱验证码");
String emailContent = "尊敬的用户:\n\n" +
"您正在注册数学学习软件,验证码为:" + verificationCode + "\n\n" +
"验证码有效期为10分钟请尽快完成注册。\n\n" +
"如果不是您本人操作,请忽略此邮件。\n\n" +
"数学学习软件团队";
message.setText(emailContent);
// 发送邮件
Transport.send(message);
System.out.println("验证码邮件已发送到: " + toEmail);
return true;
} catch (MessagingException e) {
System.err.println("发送邮件失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* 6
*/
private static String generateVerificationCode() {
return String.format("%06d", random.nextInt(1000000));
}
/**
*
*/
public static boolean isValidEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
// 简单的邮箱格式验证
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
return email.matches(emailRegex);
}
/**
*
*/
public static String getEmailRequirements() {
return "请输入有效的邮箱地址例如username@example.com";
}
}

@ -0,0 +1,86 @@
package com.mathlearning.util;
import com.mathlearning.model.Paper;
import com.mathlearning.model.Question;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExamFileUtil {
/**
*
* @param username
* @param paper
* @return
*/
public static boolean savePaperToFile(String username, Paper paper) {
// 创建用户文件夹
File userDir = new File("exam_papers/" + username);
if (!userDir.exists()) {
if (!userDir.mkdirs()) {
System.err.println("无法创建用户文件夹: " + userDir.getAbsolutePath());
return false;
}
}
// 生成文件名
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
String filename = timestamp + ".txt";
File file = new File(userDir, filename);
try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
// 写入试卷信息
writer.println("试卷级别: " + paper.getLevel());
writer.println("生成时间: " + new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(new Date()));
writer.println("题目数量: " + paper.getTotalQuestions());
writer.println();
// 写入题目
int questionNumber = 1;
for (Question question : paper.getQuestions()) {
writer.println(questionNumber + ". " + question.getContent());
// 写入选项
String[] options = question.getOptions();
for (int i = 0; i < options.length; i++) {
writer.println(" " + (char)('A' + i) + ". " + options[i]);
}
writer.println(); // 题目之间空一行
questionNumber++;
}
// 写入答案
writer.println("=== 参考答案 ===");
questionNumber = 1;
for (Question question : paper.getQuestions()) {
writer.println(questionNumber + ". " + question.getCorrectAnswer());
questionNumber++;
}
System.out.println("试卷已保存到: " + file.getAbsolutePath());
return true;
} catch (IOException e) {
System.err.println("保存试卷时出错: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
*
* @param username
* @return
*/
public static File[] getUserExamFiles(String username) {
File userDir = new File("exam_papers/" + username);
if (!userDir.exists()) {
return new File[0];
}
return userDir.listFiles((dir, name) -> name.endsWith(".txt"));
}
}

@ -0,0 +1,52 @@
package com.mathlearning.util;
import com.mathlearning.model.User;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class FileStorage {
private static final String USER_DATA_EMAIL_FILE = "users_email.dat";
private static final String USER_DATA_USERNAME_FILE = "users_username.dat";
@SuppressWarnings("unchecked")
public static Map<String, User> loadUsersByEmail() {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_EMAIL_FILE))) {
return (Map<String, User>) ois.readObject();
} catch (FileNotFoundException e) {
// 文件不存在返回空Map
return new HashMap<>();
} catch (IOException | ClassNotFoundException e) {
System.err.println("加载用户数据失败: " + e.getMessage());
return new HashMap<>();
}
}
@SuppressWarnings("unchecked")
public static Map<String, User> loadUsersByUsername() {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_USERNAME_FILE))) {
return (Map<String, User>) ois.readObject();
} catch (FileNotFoundException e) {
// 文件不存在返回空Map
return new HashMap<>();
} catch (IOException | ClassNotFoundException e) {
System.err.println("加载用户数据失败: " + e.getMessage());
return new HashMap<>();
}
}
public static void saveUsers(Map<String, User> usersByEmail, Map<String, User> usersByUsername) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(USER_DATA_EMAIL_FILE))) {
oos.writeObject(usersByEmail);
} catch (IOException e) {
System.err.println("保存用户数据失败: " + e.getMessage());
}
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(USER_DATA_USERNAME_FILE))) {
oos.writeObject(usersByUsername);
} catch (IOException e) {
System.err.println("保存用户数据失败: " + e.getMessage());
}
}
}

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

@ -0,0 +1,16 @@
package com.mathlearning.util;
public class UsernameValidator {
public static boolean isValid(String username) {
if (username == null || username.length() < 3 || username.length() > 20) {
return false;
}
// 用户名只能包含字母、数字和下划线
return username.matches("^[a-zA-Z0-9_]+$");
}
public static String getRequirements() {
return "用户名要求: 3-20位只能包含字母、数字和下划线";
}
}

@ -0,0 +1,397 @@
package com.mathlearning.view;
import com.mathlearning.controller.ExamController;
import com.mathlearning.controller.NavigationController;
import com.mathlearning.model.Question;
import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class ExamView extends JPanel {
private NavigationController navigationController;
private ExamController examController;
private String userIdentifier;
private String email;
private String level;
private int questionCount;
// UI组件
private JLabel questionNumberLabel;
private JLabel questionContentLabel;
private ButtonGroup optionsGroup;
private JRadioButton[] optionButtons;
private JButton previousButton;
private JButton nextButton;
private JButton submitButton;
private JProgressBar progressBar;
private JLabel statusLabel;
private JButton cancelButton;
// 进度跟踪
private int generationProgress = 0;
public ExamView(NavigationController navigationController, String userIdentifier, String email, String level, int questionCount) {
this.navigationController = navigationController;
this.userIdentifier = userIdentifier;
this.email = email;
this.level = level;
this.questionCount = questionCount;
this.examController = new ExamController();
this.examController.setCurrentUser(userIdentifier);
initializeUI();
startExamGeneration();
}
private void initializeUI() {
setLayout(new BorderLayout(10, 10));
// 顶部信息面板
JPanel infoPanel = new JPanel(new BorderLayout());
String userInfo = "用户: " + userIdentifier;
if (!userIdentifier.equals(email)) {
userInfo += " (" + email + ")";
}
JLabel userInfoLabel = new JLabel(userInfo, JLabel.LEFT);
userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
infoPanel.add(userInfoLabel, BorderLayout.WEST);
JLabel levelLabel = new JLabel(level + "数学", JLabel.RIGHT);
levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
infoPanel.add(levelLabel, BorderLayout.EAST);
add(infoPanel, BorderLayout.NORTH);
// 进度显示面板
JPanel progressPanel = new JPanel(new BorderLayout());
progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
statusLabel = new JLabel("正在生成试卷,请稍候...", JLabel.CENTER);
statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
progressPanel.add(statusLabel, BorderLayout.NORTH);
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
progressBar.setPreferredSize(new Dimension(300, 20));
progressPanel.add(progressBar, BorderLayout.CENTER);
// 取消按钮
JPanel buttonPanel = new JPanel();
cancelButton = new JButton("取消生成");
cancelButton.addActionListener(e -> {
// 返回题目数量选择界面
navigationController.showQuestionCountView(level);
});
buttonPanel.add(cancelButton);
progressPanel.add(buttonPanel, BorderLayout.SOUTH);
add(progressPanel, BorderLayout.CENTER);
}
private void startExamGeneration() {
System.out.println("开始生成试卷,级别: " + level + ", 题目数量: " + questionCount);
// 使用SwingWorker在后台生成题目
SwingWorker<Boolean, String> worker = new SwingWorker<Boolean, String>() {
@Override
protected Boolean doInBackground() throws Exception {
publish("正在初始化生成器...");
Thread.sleep(100); // 短暂延迟让UI更新
try {
publish("开始生成题目...");
long startTime = System.currentTimeMillis();
// 设置进度条为确定模式
SwingUtilities.invokeLater(() -> {
progressBar.setIndeterminate(false);
progressBar.setMinimum(0);
progressBar.setMaximum(100);
progressBar.setValue(0);
});
// 模拟进度更新
for (int i = 0; i <= 100; i += 10) {
if (isCancelled()) {
return false;
}
publish("生成题目中... " + i + "%");
progressBar.setValue(i);
Thread.sleep(50);
}
examController.createExam(level, questionCount);
long endTime = System.currentTimeMillis();
publish("题目生成完成,耗时: " + (endTime - startTime) + "ms");
return true;
} catch (Exception e) {
System.err.println("生成题目时出错: " + e.getMessage());
e.printStackTrace();
publish("生成题目时出错: " + e.getMessage());
return false;
}
}
@Override
protected void process(List<String> chunks) {
// 更新状态信息
if (!chunks.isEmpty()) {
String latestMessage = chunks.get(chunks.size() - 1);
statusLabel.setText(latestMessage);
// 更新进度条文本显示
if (latestMessage.contains("%")) {
progressBar.setString(latestMessage);
progressBar.setStringPainted(true);
}
}
}
@Override
protected void done() {
try {
Boolean success = get();
if (success) {
// 题目生成成功,切换到考试界面
statusLabel.setText("题目生成成功!正在加载界面...");
progressBar.setValue(100);
// 短暂延迟后初始化考试界面
Timer timer = new Timer(500, e -> {
initializeExamUI();
loadQuestion();
});
timer.setRepeats(false);
timer.start();
} else {
// 生成失败,显示错误信息
JOptionPane.showMessageDialog(ExamView.this,
"生成试卷失败,将使用基础题目继续考试",
"生成失败",
JOptionPane.WARNING_MESSAGE);
// 即使生成失败也尝试继续
initializeExamUI();
loadQuestion();
}
} catch (InterruptedException e) {
// 用户取消了操作
navigationController.showQuestionCountView(level);
} catch (ExecutionException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(ExamView.this,
"生成试卷时发生错误: " + e.getCause().getMessage(),
"错误",
JOptionPane.ERROR_MESSAGE);
navigationController.showQuestionCountView(level);
}
}
};
worker.execute();
}
private void initializeExamUI() {
// 移除进度显示
removeAll();
setLayout(new BorderLayout(10, 10));
// Header Panel
JPanel headerPanel = new JPanel(new BorderLayout());
questionNumberLabel = new JLabel("", JLabel.CENTER);
questionNumberLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
headerPanel.add(questionNumberLabel, BorderLayout.CENTER);
JLabel levelLabel = new JLabel(level + "数学", JLabel.RIGHT);
levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
headerPanel.add(levelLabel, BorderLayout.EAST);
add(headerPanel, BorderLayout.NORTH);
// Question Panel
JPanel questionPanel = new JPanel(new BorderLayout(10, 10));
questionPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
questionContentLabel = new JLabel("", JLabel.CENTER);
questionContentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18));
questionPanel.add(questionContentLabel, BorderLayout.NORTH);
// Options Panel - 修复选项状态管理
JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10));
optionsGroup = new ButtonGroup();
optionButtons = new JRadioButton[4];
for (int i = 0; i < 4; i++) {
optionButtons[i] = new JRadioButton();
optionButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 16));
// 添加选项变化监听器,实时保存答案
final int index = i;
optionButtons[i].addActionListener(e -> {
if (optionButtons[index].isSelected()) {
saveCurrentAnswer();
}
});
optionsGroup.add(optionButtons[i]);
optionsPanel.add(optionButtons[i]);
}
questionPanel.add(optionsPanel, BorderLayout.CENTER);
add(questionPanel, BorderLayout.CENTER);
// Navigation Panel
JPanel navPanel = new JPanel(new FlowLayout());
previousButton = new JButton("上一题");
previousButton.addActionListener(e -> handlePrevious());
navPanel.add(previousButton);
nextButton = new JButton("下一题");
nextButton.addActionListener(e -> handleNext());
navPanel.add(nextButton);
submitButton = new JButton("提交试卷");
submitButton.addActionListener(e -> handleSubmit());
navPanel.add(submitButton);
// 添加返回按钮
JButton backButton = new JButton("返回");
backButton.addActionListener(e -> {
int result = JOptionPane.showConfirmDialog(ExamView.this,
"确定要返回吗?当前进度将丢失。",
"确认返回",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
navigationController.showQuestionCountView(level);
}
});
navPanel.add(backButton);
add(navPanel, BorderLayout.SOUTH);
updateNavigationButtons();
revalidate();
repaint();
}
private void loadQuestion() {
Question currentQuestion = examController.getCurrentQuestion();
if (currentQuestion != null) {
// 更新题号
questionNumberLabel.setText("第 " + examController.getCurrentQuestionNumber() + " 题 / 共 " +
examController.getTotalQuestions() + " 题");
// 更新题目内容
questionContentLabel.setText("<html><div style='text-align: center;'>" + currentQuestion.getContent() + "</div></html>");
// 更新选项 - 关键修复:先清空所有选择
optionsGroup.clearSelection();
String[] options = currentQuestion.getOptions();
for (int i = 0; i < 4; i++) {
if (i < options.length) {
optionButtons[i].setText((char)('A' + i) + ". " + options[i]);
optionButtons[i].setVisible(true);
} else {
optionButtons[i].setText(""); // 清空多余的选项
optionButtons[i].setVisible(false);
}
}
// 如果有用户答案,选中对应的选项
String userAnswer = currentQuestion.getUserAnswer();
if (userAnswer != null && !userAnswer.trim().isEmpty()) {
for (int i = 0; i < 4; i++) {
if (optionButtons[i].isVisible() && optionButtons[i].getText().contains(userAnswer)) {
optionButtons[i].setSelected(true);
break;
}
}
}
} else {
// 如果没有题目,显示错误信息
questionContentLabel.setText("<html><div style='text-align: center; color: red;'>题目加载失败,请返回重试</div></html>");
}
updateNavigationButtons();
}
private void handlePrevious() {
// 保存当前答案
saveCurrentAnswer();
examController.previousQuestion();
loadQuestion();
}
private void handleNext() {
// 保存当前答案
saveCurrentAnswer();
examController.nextQuestion();
loadQuestion();
}
private void handleSubmit() {
// 保存当前答案
saveCurrentAnswer();
int result = JOptionPane.showConfirmDialog(this,
"确认提交试卷?提交后将显示成绩并保存试卷。",
"确认提交",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
// 保存试卷到文件
boolean saveSuccess = examController.saveCurrentPaper();
int score = examController.calculateScore();
int totalQuestions = examController.getTotalQuestions();
// 显示保存结果
if (saveSuccess) {
JOptionPane.showMessageDialog(this,
"试卷已保存到您的文件夹中!",
"保存成功",
JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(this,
"试卷保存失败,但成绩计算完成。",
"保存失败",
JOptionPane.WARNING_MESSAGE);
}
navigationController.showScoreView(level, score, totalQuestions);
}
}
private void saveCurrentAnswer() {
for (int i = 0; i < 4; i++) {
if (optionButtons[i].isSelected() && optionButtons[i].isVisible()) {
try {
// 提取答案内容(去掉"A. "等前缀)
String fullText = optionButtons[i].getText();
String answer = fullText.substring(fullText.indexOf(". ") + 2);
examController.submitAnswer(answer);
System.out.println("保存答案: " + answer);
break;
} catch (Exception e) {
System.err.println("保存答案时出错: " + e.getMessage());
}
}
}
}
private void updateNavigationButtons() {
boolean hasPrevious = examController.hasPreviousQuestion();
boolean hasNext = examController.hasNextQuestion();
previousButton.setEnabled(hasPrevious);
nextButton.setEnabled(hasNext);
submitButton.setEnabled(!hasNext); // 只有最后一题才能提交
System.out.println("导航按钮状态 - 上一题: " + hasPrevious + ", 下一题: " + hasNext + ", 提交: " + !hasNext);
}
}

@ -0,0 +1,75 @@
package com.mathlearning.view;
import com.mathlearning.controller.NavigationController;
import javax.swing.*;
import java.awt.*;
public class LevelSelectionView extends JPanel {
private NavigationController navigationController;
private String userIdentifier;
private String email;
private JButton primaryButton;
private JButton middleButton;
private JButton highButton;
private JButton logoutButton;
public LevelSelectionView(NavigationController navigationController, String userIdentifier, String email) {
this.navigationController = navigationController;
this.userIdentifier = userIdentifier;
this.email = email;
initializeUI();
}
private void initializeUI() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(15, 15, 15, 15);
gbc.fill = GridBagConstraints.HORIZONTAL;
// Title
JLabel titleLabel = new JLabel("选择学习阶段", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
add(titleLabel, gbc);
// User info
gbc.gridy = 1;
String userInfo = "当前用户: " + userIdentifier;
if (!userIdentifier.equals(email)) {
userInfo += " (" + email + ")";
}
JLabel userLabel = new JLabel(userInfo, JLabel.CENTER);
userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
add(userLabel, gbc);
// Primary School Button
gbc.gridwidth = 1;
gbc.gridx = 0; gbc.gridy = 2;
primaryButton = new JButton("小学");
primaryButton.setFont(new Font("微软雅黑", Font.PLAIN, 18));
primaryButton.addActionListener(e -> navigationController.showQuestionCountView("小学"));
add(primaryButton, gbc);
// Middle School Button
gbc.gridx = 1; gbc.gridy = 2;
middleButton = new JButton("初中");
middleButton.setFont(new Font("微软雅黑", Font.PLAIN, 18));
middleButton.addActionListener(e -> navigationController.showQuestionCountView("初中"));
add(middleButton, gbc);
// High School Button
gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2;
highButton = new JButton("高中");
highButton.setFont(new Font("微软雅黑", Font.PLAIN, 18));
highButton.addActionListener(e -> navigationController.showQuestionCountView("高中"));
add(highButton, gbc);
// Logout Button
gbc.gridy = 4;
logoutButton = new JButton("退出登录");
logoutButton.addActionListener(e -> navigationController.showLoginView());
add(logoutButton, gbc);
}
}

@ -0,0 +1,110 @@
package com.mathlearning.view;
import com.mathlearning.controller.AuthController;
import com.mathlearning.controller.NavigationController;
import javax.swing.*;
import java.awt.*;
public class LoginView extends JPanel {
private NavigationController navigationController;
private AuthController authController;
private JTextField identifierField; // 改为通用标识符字段
private JPasswordField passwordField;
private JButton loginButton;
private JButton registerButton;
private JButton changePasswordButton;
private JLabel identifierLabel;
public LoginView(NavigationController navigationController) {
this.navigationController = navigationController;
this.authController = new AuthController();
initializeUI();
}
private void initializeUI() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
// Title
JLabel titleLabel = new JLabel("数学学习软件", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
add(titleLabel, gbc);
// Identifier (用户名或邮箱)
gbc.gridwidth = 1;
gbc.gridx = 0; gbc.gridy = 1;
identifierLabel = new JLabel("用户名/邮箱:");
add(identifierLabel, gbc);
gbc.gridx = 1; gbc.gridy = 1;
identifierField = new JTextField(20);
add(identifierField, gbc);
// Password
gbc.gridx = 0; gbc.gridy = 2;
add(new JLabel("密码:"), gbc);
gbc.gridx = 1; gbc.gridy = 2;
passwordField = new JPasswordField(20);
add(passwordField, gbc);
// Login Button
gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2;
loginButton = new JButton("登录");
loginButton.addActionListener(e -> handleLogin());
add(loginButton, gbc);
// Register Button
gbc.gridy = 4;
registerButton = new JButton("注册新账号");
registerButton.addActionListener(e -> navigationController.showRegisterView());
add(registerButton, gbc);
// Change Password Button
gbc.gridy = 5;
changePasswordButton = new JButton("修改密码");
changePasswordButton.addActionListener(e -> handleChangePassword());
add(changePasswordButton, gbc);
// 提示信息
gbc.gridy = 6;
JLabel hintLabel = new JLabel("提示: 可以使用用户名或邮箱登录", JLabel.CENTER);
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
hintLabel.setForeground(Color.GRAY);
add(hintLabel, gbc);
}
private void handleLogin() {
String identifier = identifierField.getText().trim();
String password = new String(passwordField.getPassword());
if (identifier.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(this, "请输入用户名/邮箱和密码", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (authController.login(identifier, password)) {
// 获取用户信息用于显示
String email = authController.getUserEmail(identifier);
navigationController.showLevelSelectionView(identifier, email);
} else {
JOptionPane.showMessageDialog(this, "用户名/邮箱或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE);
}
}
private void handleChangePassword() {
String identifier = identifierField.getText().trim();
if (identifier.isEmpty()) {
JOptionPane.showMessageDialog(this, "请输入用户名/邮箱", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
// 在实际应用中,这里应该验证用户身份
navigationController.showChangePasswordView(identifier);
}
}

@ -0,0 +1,85 @@
package com.mathlearning.view;
import com.mathlearning.controller.NavigationController;
import javax.swing.*;
import java.awt.*;
public class QuestionCountView extends JPanel {
private NavigationController navigationController;
private String userIdentifier;
private String email;
private String level;
private JTextField countField;
private JButton submitButton;
private JButton backButton;
public QuestionCountView(NavigationController navigationController, String userIdentifier, String email, String level) {
this.navigationController = navigationController;
this.userIdentifier = userIdentifier;
this.email = email;
this.level = level;
initializeUI();
}
private void initializeUI() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
// Title
JLabel titleLabel = new JLabel(level + "数学题目生成", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
add(titleLabel, gbc);
// User info
gbc.gridy = 1;
String userInfo = "当前用户: " + userIdentifier;
if (!userIdentifier.equals(email)) {
userInfo += " (" + email + ")";
}
JLabel userLabel = new JLabel(userInfo, JLabel.CENTER);
userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(userLabel, gbc);
// Instruction
gbc.gridy = 2;
JLabel instructionLabel = new JLabel("请输入要生成的题目数量 (10-30):", JLabel.CENTER);
add(instructionLabel, gbc);
// Count Input
gbc.gridy = 3;
countField = new JTextField(10);
countField.setHorizontalAlignment(JTextField.CENTER);
add(countField, gbc);
// Submit Button
gbc.gridy = 4;
submitButton = new JButton("生成试卷");
submitButton.addActionListener(e -> handleSubmit());
add(submitButton, gbc);
// Back Button
gbc.gridy = 5;
backButton = new JButton("返回");
backButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email));
add(backButton, gbc);
}
private void handleSubmit() {
try {
int count = Integer.parseInt(countField.getText().trim());
if (count < 10 || count > 30) {
JOptionPane.showMessageDialog(this, "请输入10-30之间的数字", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
navigationController.showExamView(level, count);
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(this, "请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}

@ -0,0 +1,210 @@
package com.mathlearning.view;
import com.mathlearning.controller.AuthController;
import com.mathlearning.controller.NavigationController;
import com.mathlearning.util.EmailUtil;
import com.mathlearning.util.UsernameValidator;
import javax.swing.*;
import java.awt.*;
public class RegisterView extends JPanel {
private NavigationController navigationController;
private AuthController authController;
private JTextField emailField;
private JTextField usernameField;
private JButton sendCodeButton;
private JButton verifyButton;
private JButton backButton;
private JLabel statusLabel;
private JTextField codeField;
private String currentVerificationCode;
public RegisterView(NavigationController navigationController) {
this.navigationController = navigationController;
this.authController = new AuthController();
initializeUI();
}
private void initializeUI() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
// Title
JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
add(titleLabel, gbc);
// Username
gbc.gridwidth = 1;
gbc.gridx = 0; gbc.gridy = 1;
add(new JLabel("用户名:"), gbc);
gbc.gridx = 1; gbc.gridy = 1;
usernameField = new JTextField(20);
add(usernameField, gbc);
// Email
gbc.gridx = 0; gbc.gridy = 2;
add(new JLabel("邮箱地址:"), gbc);
gbc.gridx = 1; gbc.gridy = 2;
emailField = new JTextField(20);
add(emailField, gbc);
// Verification Code
gbc.gridx = 0; gbc.gridy = 3;
add(new JLabel("验证码:"), gbc);
gbc.gridx = 1; gbc.gridy = 3;
JPanel codePanel = new JPanel(new BorderLayout());
codeField = new JTextField(10);
codePanel.add(codeField, BorderLayout.CENTER);
sendCodeButton = new JButton("发送验证码");
sendCodeButton.addActionListener(e -> handleSendCode());
codePanel.add(sendCodeButton, BorderLayout.EAST);
add(codePanel, gbc);
// Requirements
gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2;
JLabel usernameReqLabel = new JLabel(UsernameValidator.getRequirements(), JLabel.CENTER);
usernameReqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
usernameReqLabel.setForeground(Color.GRAY);
add(usernameReqLabel, gbc);
gbc.gridy = 5;
JLabel emailReqLabel = new JLabel(EmailUtil.getEmailRequirements(), JLabel.CENTER);
emailReqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
emailReqLabel.setForeground(Color.GRAY);
add(emailReqLabel, gbc);
// Status label
gbc.gridy = 6;
statusLabel = new JLabel("", JLabel.CENTER);
statusLabel.setForeground(Color.BLUE);
add(statusLabel, gbc);
// Verify Button
gbc.gridy = 7;
verifyButton = new JButton("验证并继续");
verifyButton.addActionListener(e -> handleVerify());
add(verifyButton, gbc);
// Back Button
gbc.gridy = 8;
backButton = new JButton("返回登录");
backButton.addActionListener(e -> navigationController.showLoginView());
add(backButton, gbc);
}
private void handleSendCode() {
String email = emailField.getText().trim();
String username = usernameField.getText().trim();
// 验证输入
if (username.isEmpty() || email.isEmpty()) {
showError("请输入用户名和邮箱地址");
return;
}
if (!EmailUtil.isValidEmail(email)) {
showError("请输入有效的邮箱地址");
return;
}
if (!UsernameValidator.isValid(username)) {
showError(UsernameValidator.getRequirements());
return;
}
// 检查邮箱和用户名是否可用
if (!authController.isEmailAvailable(email)) {
showError("该邮箱已被注册");
return;
}
if (!authController.isUsernameAvailable(username)) {
showError("该用户名已被使用");
return;
}
// 禁用按钮,显示发送中状态
sendCodeButton.setEnabled(false);
sendCodeButton.setText("发送中...");
statusLabel.setText("正在发送验证码,请稍候...");
statusLabel.setForeground(Color.BLUE);
// 在后台线程中执行发送验证码
SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
@Override
protected String doInBackground() throws Exception {
return authController.sendVerificationCode(email, username);
}
@Override
protected void done() {
try {
currentVerificationCode = get();
if (currentVerificationCode != null) {
statusLabel.setText("验证码已发送!请在下方输入验证码");
statusLabel.setForeground(Color.GREEN);
codeField.setEnabled(true);
verifyButton.setEnabled(true);
} else {
showError("发送验证码失败,请检查邮箱地址或稍后重试");
}
} catch (Exception e) {
showError("发送验证码时发生错误: " + e.getMessage());
} finally {
// 恢复按钮状态
sendCodeButton.setEnabled(true);
sendCodeButton.setText("发送验证码");
}
}
};
worker.execute();
}
private void handleVerify() {
String email = emailField.getText().trim();
String username = usernameField.getText().trim();
String code = codeField.getText().trim();
if (code.isEmpty()) {
showError("请输入验证码");
return;
}
// 验证验证码
if (currentVerificationCode != null && code.equals(currentVerificationCode)) {
// 验证码正确,创建未注册用户
boolean success = authController.createUnregisteredUser(email, username, currentVerificationCode);
if (success) {
showSuccess("验证成功!");
// 跳转到设置密码页面
navigationController.showSetPasswordView(email, currentVerificationCode);
} else {
showError("创建用户失败");
}
} else {
showError("验证码错误,请重新输入");
}
}
private void showError(String message) {
statusLabel.setText(message);
statusLabel.setForeground(Color.RED);
}
private void showSuccess(String message) {
statusLabel.setText(message);
statusLabel.setForeground(Color.GREEN);
}
}

@ -0,0 +1,111 @@
package com.mathlearning.view;
import com.mathlearning.controller.NavigationController;
import javax.swing.*;
import java.awt.*;
public class ScoreView extends JPanel {
private NavigationController navigationController;
private String userIdentifier;
private String email;
private String level;
private int score;
private int totalQuestions;
private JLabel scoreLabel;
private JLabel commentLabel;
private JButton retryButton;
private JButton newExamButton;
private JButton logoutButton;
public ScoreView(NavigationController navigationController, String userIdentifier, String email, String level, int score, int totalQuestions) {
this.navigationController = navigationController;
this.userIdentifier = userIdentifier;
this.email = email;
this.level = level;
this.score = score;
this.totalQuestions = totalQuestions;
initializeUI();
}
private void initializeUI() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(15, 15, 15, 15);
gbc.fill = GridBagConstraints.HORIZONTAL;
// Title
JLabel titleLabel = new JLabel("考试结果", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
add(titleLabel, gbc);
// User info
gbc.gridy = 1;
String userInfo = "用户: " + userIdentifier;
if (!userIdentifier.equals(email)) {
userInfo += " (" + email + ")";
}
JLabel userLabel = new JLabel(userInfo, JLabel.CENTER);
userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(userLabel, gbc);
// Level info
gbc.gridy = 2;
JLabel levelLabel = new JLabel("级别: " + level, JLabel.CENTER);
levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(levelLabel, gbc);
// Score Display
gbc.gridy = 3;
scoreLabel = new JLabel("得分: " + score + " / 100", JLabel.CENTER);
scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
scoreLabel.setForeground(getScoreColor());
add(scoreLabel, gbc);
// Comment
gbc.gridy = 4;
commentLabel = new JLabel(getScoreComment(), JLabel.CENTER);
commentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
add(commentLabel, gbc);
// Stats
gbc.gridy = 5;
int correctCount = (score * totalQuestions) / 100;
JLabel statsLabel = new JLabel("正确题数: " + correctCount + " / " + totalQuestions, JLabel.CENTER);
statsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
add(statsLabel, gbc);
// Retry Button
gbc.gridy = 6;
retryButton = new JButton("重新答题");
retryButton.addActionListener(e -> navigationController.showExamView(level, totalQuestions));
add(retryButton, gbc);
// New Exam Button
gbc.gridy = 7;
newExamButton = new JButton("新的测试");
newExamButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email));
add(newExamButton, gbc);
// Logout Button
gbc.gridy = 8;
logoutButton = new JButton("退出登录");
logoutButton.addActionListener(e -> navigationController.showLoginView());
add(logoutButton, gbc);
}
private Color getScoreColor() {
if (score >= 80) return new Color(0, 128, 0); // 绿色
if (score >= 60) return new Color(255, 165, 0); // 橙色
return Color.RED;
}
private String getScoreComment() {
if (score >= 90) return "优秀!继续保持!";
if (score >= 80) return "良好!还有进步空间!";
if (score >= 60) return "及格!需要多加练习!";
return "不及格!需要认真学习!";
}
}

@ -0,0 +1,100 @@
package com.mathlearning.view;
import com.mathlearning.controller.AuthController;
import com.mathlearning.controller.NavigationController;
import com.mathlearning.util.PasswordValidator;
import javax.swing.*;
import java.awt.*;
public class SetPasswordView extends JPanel {
private NavigationController navigationController;
private AuthController authController;
private String email;
private JPasswordField passwordField;
private JPasswordField confirmPasswordField;
private JButton submitButton;
private JButton backButton;
public SetPasswordView(NavigationController navigationController, String email, String verificationCode) {
this.navigationController = navigationController;
this.authController = new AuthController();
this.email = email;
initializeUI();
}
private void initializeUI() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
// Title
JLabel titleLabel = new JLabel("设置密码", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
add(titleLabel, gbc);
// User info
gbc.gridy = 1;
JLabel userInfoLabel = new JLabel("邮箱: " + email, JLabel.CENTER);
userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(userInfoLabel, gbc);
// Password requirements
gbc.gridy = 2;
JLabel requirementsLabel = new JLabel("密码要求: 6-10位包含大小写字母和数字", JLabel.CENTER);
requirementsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(requirementsLabel, gbc);
// Password
gbc.gridwidth = 1;
gbc.gridx = 0; gbc.gridy = 3;
add(new JLabel("密码:"), gbc);
gbc.gridx = 1; gbc.gridy = 3;
passwordField = new JPasswordField(20);
add(passwordField, gbc);
// Confirm Password
gbc.gridx = 0; gbc.gridy = 4;
add(new JLabel("确认密码:"), gbc);
gbc.gridx = 1; gbc.gridy = 4;
confirmPasswordField = new JPasswordField(20);
add(confirmPasswordField, gbc);
// Submit Button
gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 2;
submitButton = new JButton("设置密码");
submitButton.addActionListener(e -> handleSetPassword());
add(submitButton, gbc);
// Back Button
gbc.gridy = 6;
backButton = new JButton("返回");
backButton.addActionListener(e -> navigationController.showLoginView());
add(backButton, gbc);
}
private void handleSetPassword() {
String password = new String(passwordField.getPassword());
String confirmPassword = new String(confirmPasswordField.getPassword());
if (!PasswordValidator.isValid(password)) {
JOptionPane.showMessageDialog(this,
"密码不符合要求!\n请确保密码\n- 长度6-10位\n- 包含大写字母\n- 包含小写字母\n- 包含数字",
"密码错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (authController.setPassword(email, password, confirmPassword)) {
JOptionPane.showMessageDialog(this, "密码设置成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
// 登录成功后使用邮箱作为标识符
navigationController.showLevelSelectionView(email, email);
} else {
JOptionPane.showMessageDialog(this, "密码设置失败,请检查输入", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}

@ -0,0 +1,5 @@
email.from=1393110457@qq.com
email.password=yuslygpkwwbtfedh
email.smtp.host=smtp.qq.com
email.smtp.port=465
email.smtp.ssl=true

@ -0,0 +1,145 @@
试卷级别: 小学
生成时间: 2025年10月11日 13时29分32秒
题目数量: 20
1. 21 - 4 + 70 = ?
A. 75
B. 87
C. 72
D. 101
2. 76 - 32 + 23 - 21 = ?
A. 39
B. 38
C. 46
D. 53
3. 64 + 86 + 25 × 21 = ?
A. 675
B. 792
C. 628
D. 771
4. 8 + 75 + 74 + 81 = ?
A. 238
B. 198
C. 241
D. 244
5. 79 - 6 + 86 = ?
A. 159
B. 138
C. 137
D. 185
6. 85 + 9 - 24 = ?
A. 73
B. 70
C. 58
D. 78
7. 91 + 20 + 42 = ?
A. 134
B. 125
C. 143
D. 153
8. 11 + 71 × 8 + 52 = ?
A. 675
B. 631
C. 601
D. 511
9. 64 + 54 - 75 = ?
A. 43
B. 44
C. 39
D. 38
10. 30 + 85 - 42 = ?
A. 84
B. 64
C. 58
D. 73
11. 22 + 94 + 89 - 97 = ?
A. 108
B. 88
C. 105
D. 94
12. 62 + 27 - 21 = ?
A. 70
B. 68
C. 63
D. 79
13. 74 + 17 - 5 = ?
A. 85
B. 86
C. 92
D. 90
14. 94 - 6 + 61 = ?
A. 120
B. 135
C. 149
D. 121
15. 89 + 77 + 60 + 26 = ?
A. 227
B. 291
C. 299
D. 252
16. 100 + 22 - 50 = ?
A. 86
B. 72
C. 84
D. 80
17. 69 ÷ 1 + 92 = ?
A. 129
B. 161
C. 135
D. 146
18. 98 + 44 + 97 × 4 = ?
A. 530
B. 463
C. 482
D. 433
19. 98 + 80 - 68 = ?
A. 107
B. 110
C. 119
D. 127
20. 32 + 5 × 36 = ?
A. 243
B. 179
C. 240
D. 212
=== 参考答案 ===
1. 87
2. 46
3. 675
4. 238
5. 159
6. 70
7. 153
8. 631
9. 43
10. 73
11. 108
12. 68
13. 86
14. 149
15. 252
16. 72
17. 161
18. 530
19. 110
20. 212

@ -0,0 +1,75 @@
试卷级别: 初中
生成时间: 2025年10月11日 13时29分45秒
题目数量: 10
1. 64² = ?
A. 4468
B. 4122
C. 4256
D. 4096
2. √36 = ?
A. 7
B. 5
C. 6
D. 4
3. 13² = ?
A. 144
B. 178
C. 200
D. 169
4. 40² = ?
A. 1567
B. 1439
C. 1600
D. 1369
5. √49 = ?
A. 6
B. 5
C. 7
D. 8
6. (16 + 1)² = ?
A. 339
B. 289
C. 326
D. 346
7. 97 × 57 + 15 = ?
A. 6620
B. 5544
C. 5391
D. 5893
8. 66² = ?
A. 4107
B. 4356
C. 4323
D. 4763
9. √100 = ?
A. 9
B. 8
C. 11
D. 10
10. (22 + 31)² = ?
A. 2942
B. 2654
C. 2648
D. 2809
=== 参考答案 ===
1. 4096
2. 6
3. 169
4. 1600
5. 7
6. 289
7. 5544
8. 4356
9. 10
10. 2809

@ -0,0 +1,75 @@
试卷级别: 高中
生成时间: 2025年10月11日 13时30分14秒
题目数量: 10
1. 70 + cos(0°) - 91 = ?
A. -18.61
B. -21.55
C. -20.00
D. -23.39
2. cos(150°) = ?
A. -0.87
B. -0.74
C. -0.89
D. -1.01
3. 53 + tan(60°) - 54 = ?
A. 0.63
B. 0.62
C. 0.73
D. 0.77
4. sin(150°) = ?
A. 0.53
B. 0.42
C. 0.50
D. 0.60
5. 8 + sin(45°) - 82 = ?
A. -61.24
B. -65.98
C. -87.77
D. -73.29
6. 73 - cos(180°) - 24 = ?
A. 50.00
B. 41.82
C. 59.18
D. 56.02
7. cos(45°) = ?
A. 0.71
B. 0.83
C. 0.70
D. 0.61
8. cos(120°) = ?
A. -0.53
B. -0.50
C. -0.59
D. -0.42
9. 59 - cos(120°) - 29 = ?
A. 30.50
B. 30.45
C. 33.74
D. 27.00
10. cos(135°) = ?
A. -0.77
B. -0.70
C. -0.71
D. -0.61
=== 参考答案 ===
1. -20.00
2. -0.87
3. 0.73
4. 0.50
5. -73.29
6. 50.00
7. 0.71
8. -0.50
9. 30.50
10. -0.71

@ -0,0 +1,75 @@
试卷级别: 小学
生成时间: 2025年10月11日 13时40分07秒
题目数量: 10
1. 5 + 60 = ?
A. 63
B. 71
C. 59
D. 65
2. 68 + 22 = ?
A. 87
B. 90
C. 78
D. 76
3. 77 + 26 = ?
A. 103
B. 84
C. 108
D. 106
4. 62 + 59 = ?
A. 142
B. 121
C. 123
D. 103
5. 6 × 6 = ?
A. 32
B. 36
C. 38
D. 42
6. 96 + 35 = ?
A. 127
B. 120
C. 131
D. 106
7. 96 + 12 = ?
A. 89
B. 120
C. 108
D. 115
8. 66 + 2 = ?
A. 55
B. 77
C. 74
D. 68
9. 99 - 37 = ?
A. 68
B. 50
C. 59
D. 62
10. 81 - 11 = ?
A. 57
B. 56
C. 75
D. 70
=== 参考答案 ===
1. 65
2. 90
3. 103
4. 121
5. 36
6. 131
7. 108
8. 68
9. 62
10. 70

@ -1,5 +0,0 @@
from app import MathApp
if __name__ == "__main__":
app = MathApp()
app.mainloop()

@ -1,96 +0,0 @@
import random
from abc import ABC, abstractmethod
# 安全的表达式求值函数
def safe_eval(expression):
try:
return eval(expression, {"__builtins__": None}, {})
except Exception:
return None
class Question:
def __init__(self, text, options, correct_answer_index):
self.text = text
self.options = options
self.correct_answer_index = correct_answer_index
class QuestionGenerator(ABC):
def generate_bunch(self, count):
questions = []
generated_texts = set()
while len(questions) < count:
text, answer = self._create_question_and_answer()
if text not in generated_texts:
options = self._generate_options(answer)
correct_index = options.index(answer)
questions.append(Question(text, options, correct_index))
generated_texts.add(text)
return questions
def _generate_options(self, correct_answer):
options = {correct_answer}
while len(options) < 4:
offset = random.randint(-10, 10)
if offset == 0:
offset = random.choice([-1, 1]) * 10
distractor = correct_answer + offset
options.add(distractor)
option_list = list(options)
random.shuffle(option_list)
return option_list
@abstractmethod
def _create_question_and_answer(self):
pass
class PrimarySchoolGenerator(QuestionGenerator):
def _create_question_and_answer(self):
ops = ['+', '-', '*', '/']
op = random.choice(ops)
if op == '+':
num1, num2 = random.randint(1, 100), random.randint(1, 100)
expr = f"{num1} + {num2}"
elif op == '-':
num1, num2 = random.randint(1, 100), random.randint(1, 100)
if num1 < num2: num1, num2 = num2, num1
expr = f"{num1} - {num2}"
elif op == '*':
num1, num2 = random.randint(1, 20), random.randint(1, 20)
expr = f"{num1} * {num2}"
else: # op == '/'
divisor = random.randint(2, 10)
answer = random.randint(2, 20)
dividend = divisor * answer
expr = f"{dividend} / {divisor}"
return expr, int(safe_eval(expr))
class MiddleSchoolGenerator(PrimarySchoolGenerator):
def _create_question_and_answer(self):
if random.choice([True, False]):
return super()._create_question_and_answer()
else:
base = random.randint(2, 10)
expr = f"{base}**2"
return expr, int(safe_eval(expr))
class HighSchoolGenerator(MiddleSchoolGenerator):
def _create_question_and_answer(self):
# 简化版高中题,可扩展为三角函数等
return super()._create_question_and_answer()
def get_generator(level):
generators = {
"小学": PrimarySchoolGenerator,
"初中": MiddleSchoolGenerator,
"高中": HighSchoolGenerator,
}
generator_class = generators.get(level)
if generator_class:
return generator_class()
raise ValueError("无效的等级")

@ -1,105 +0,0 @@
# src/user_manager.py
import json
import hashlib
import os
import re
import smtplib
import ssl
from email.message import EmailMessage
def send_verification_email(recipient_email, code):
"""
发送验证码邮件的函数
"""
# --- 配置区域 ---
SENDER_EMAIL = "876413020@qq.com"
APP_PASSWORD = "wbeyuimdbvkxbdgf"
SMTP_SERVER = "smtp.qq.com"
SMTP_PORT = 465
# --- 构建邮件内容 ---
msg = EmailMessage()
msg['Subject'] = "【数学学习软件】您的注册验证码"
msg['From'] = SENDER_EMAIL
msg['To'] = recipient_email
msg.set_content(
f"您好!\n\n"
f"感谢您注册数学学习软件。您的验证码是:\n\n"
f"{code}\n\n"
f"请在5分钟内使用。如果不是您本人操作请忽略此邮件。"
)
# --- 发送邮件(健壮的结构)---
smtp_server = None
try:
context = ssl.create_default_context()
smtp_server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context)
smtp_server.login(SENDER_EMAIL, APP_PASSWORD)
smtp_server.send_message(msg)
print(f"邮件已成功发送至 {recipient_email}")
return True
except smtplib.SMTPAuthenticationError:
print("邮件发送失败: 认证错误。请检查邮箱或授权码是否正确。")
return False
except Exception as e:
print(f"邮件发送时发生错误: {e}")
return False
finally:
if smtp_server:
try:
smtp_server.quit()
except Exception:
pass
class UserManager:
def __init__(self):
self.users = self._load_users()
def _load_users(self):
if os.path.exists("users.json"):
try:
with open("users.json", 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return {}
return {}
def _save_users(self):
with open("users.json", 'w', encoding='utf-8') as f:
json.dump(self.users, f, indent=4)
def _hash_password(self, password, salt):
return hashlib.sha256((password + salt).encode()).hexdigest()
def is_email_registered(self, email):
return email in self.users
def validate_password_format(self, password):
"""密码必须 6-10 位,包含大小写字母和数字"""
if 6 <= len(password) <= 10 and \
re.search(r"[a-z]", password) and \
re.search(r"[A-Z]", password) and \
re.search(r"\d", password):
return True
return False
def register_user(self, email, password):
if self.is_email_registered(email):
return False, "该邮箱已被注册。"
salt = os.urandom(16).hex()
password_hash = self._hash_password(password, salt)
self.users[email] = {"password_hash": password_hash, "salt": salt}
self._save_users()
return True, "注册成功!"
def validate_login(self, email, password):
if not self.is_email_registered(email):
return False
user_data = self.users[email]
salt = user_data["salt"]
password_hash = self._hash_password(password, salt)
return password_hash == user_data["password_hash"]

Binary file not shown.

Binary file not shown.

Binary file not shown.
Loading…
Cancel
Save