Compare commits

...

8 Commits

@ -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,14 @@
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,133 @@
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,225 @@
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;
}
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,84 @@
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);
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,278 @@
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();
}
}
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);
String expression;
int answer;
switch (type) {
case 0:
int num = random.nextInt(15) + 1;
expression = num + "²";
answer = num * num;
break;
case 1:
int root = random.nextInt(12) + 1;
expression = "√" + (root * root);
answer = root;
break;
case 2:
int x = random.nextInt(8) + 2;
int y = random.nextInt(8) + 2;
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":
if (angle % 90 == 0 && angle % 180 != 0) {
continue;
}
result = Math.tan(Math.toRadians(angle));
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,41 @@
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;
}
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,106 @@
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,58 @@
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");
properties.setProperty("email.smtp.port", "465");
properties.setProperty("email.smtp.ssl", "true");
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,116 @@
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;
}
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;
}
}
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,71 @@
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 {
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;
}
}
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,50 @@
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) {
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) {
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,15 @@
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,363 @@
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;
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<Boolean, String> worker = new SwingWorker<Boolean, String>() {
@Override
protected Boolean doInBackground() throws Exception {
publish("正在初始化生成器...");
Thread.sleep(100);
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));
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);
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);
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);
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 {
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,69 @@
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;
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);
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);
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);
gbc.gridx = 1; gbc.gridy = 2;
middleButton = new JButton("初中");
middleButton.setFont(new Font("微软雅黑", Font.PLAIN, 18));
middleButton.addActionListener(e -> navigationController.showQuestionCountView("初中"));
add(middleButton, gbc);
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);
gbc.gridy = 4;
logoutButton = new JButton("退出登录");
logoutButton.addActionListener(e -> navigationController.showLoginView());
add(logoutButton, gbc);
}
}

@ -0,0 +1,101 @@
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;
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);
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);
gbc.gridx = 0; gbc.gridy = 2;
add(new JLabel("密码:"), gbc);
gbc.gridx = 1; gbc.gridy = 2;
passwordField = new JPasswordField(20);
add(passwordField, gbc);
gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2;
loginButton = new JButton("登录");
loginButton.addActionListener(e -> handleLogin());
add(loginButton, gbc);
gbc.gridy = 4;
registerButton = new JButton("注册新账号");
registerButton.addActionListener(e -> navigationController.showRegisterView());
add(registerButton, gbc);
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,79 @@
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;
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);
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);
gbc.gridy = 2;
JLabel instructionLabel = new JLabel("请输入要生成的题目数量 (10-30):", JLabel.CENTER);
add(instructionLabel, gbc);
gbc.gridy = 3;
countField = new JTextField(10);
countField.setHorizontalAlignment(JTextField.CENTER);
add(countField, gbc);
gbc.gridy = 4;
submitButton = new JButton("生成试卷");
submitButton.addActionListener(e -> handleSubmit());
add(submitButton, gbc);
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,194 @@
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;
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);
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);
gbc.gridx = 0; gbc.gridy = 2;
add(new JLabel("邮箱地址:"), gbc);
gbc.gridx = 1; gbc.gridy = 2;
emailField = new JTextField(20);
add(emailField, gbc);
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);
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);
gbc.gridy = 6;
statusLabel = new JLabel("", JLabel.CENTER);
statusLabel.setForeground(Color.BLUE);
add(statusLabel, gbc);
gbc.gridy = 7;
verifyButton = new JButton("验证并继续");
verifyButton.addActionListener(e -> handleVerify());
add(verifyButton, gbc);
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,102 @@
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;
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);
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);
gbc.gridy = 2;
JLabel levelLabel = new JLabel("级别: " + level, JLabel.CENTER);
levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(levelLabel, gbc);
gbc.gridy = 3;
scoreLabel = new JLabel("得分: " + score + " / 100", JLabel.CENTER);
scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
scoreLabel.setForeground(getScoreColor());
add(scoreLabel, gbc);
gbc.gridy = 4;
commentLabel = new JLabel(getScoreComment(), JLabel.CENTER);
commentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
add(commentLabel, gbc);
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);
gbc.gridy = 6;
retryButton = new JButton("重新答题");
retryButton.addActionListener(e -> navigationController.showExamView(level, totalQuestions));
add(retryButton, gbc);
gbc.gridy = 7;
newExamButton = new JButton("新的测试");
newExamButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email));
add(newExamButton, gbc);
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,92 @@
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;
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);
gbc.gridy = 1;
JLabel userInfoLabel = new JLabel("邮箱: " + email, JLabel.CENTER);
userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(userInfoLabel, gbc);
gbc.gridy = 2;
JLabel requirementsLabel = new JLabel("密码要求: 6-10位包含大小写字母和数字", JLabel.CENTER);
requirementsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
add(requirementsLabel, gbc);
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);
gbc.gridx = 0; gbc.gridy = 4;
add(new JLabel("确认密码:"), gbc);
gbc.gridx = 1; gbc.gridy = 4;
confirmPasswordField = new JPasswordField(20);
add(confirmPasswordField, gbc);
gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 2;
submitButton = new JButton("设置密码");
submitButton.addActionListener(e -> handleSetPassword());
add(submitButton, gbc);
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