diff --git a/doc/#说明.md b/doc/#说明.md new file mode 100644 index 0000000..e69de29 diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..6eea194 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.mathlearning.Main + diff --git a/src/com/mathlearning/Main.java b/src/com/mathlearning/Main.java new file mode 100644 index 0000000..7c423fa --- /dev/null +++ b/src/com/mathlearning/Main.java @@ -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(); + }); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/AuthController.java b/src/com/mathlearning/controller/AuthController.java new file mode 100644 index 0000000..548cc05 --- /dev/null +++ b/src/com/mathlearning/controller/AuthController.java @@ -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(); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/ExamController.java b/src/com/mathlearning/controller/ExamController.java new file mode 100644 index 0000000..29625a1 --- /dev/null +++ b/src/com/mathlearning/controller/ExamController.java @@ -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 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 basicQuestions = generateBasicQuestions(questionCount); + for (Question question : basicQuestions) { + currentPaper.addQuestion(question); + } + } + } + + + private List generateQuestionsWithTimeout(QuestionGenerator generator, int count, long timeoutMs) { + long startTime = System.currentTimeMillis(); + List questions = new ArrayList<>(); + + try { + List 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 basicQuestions = generateBasicQuestions(needed); + questions.addAll(basicQuestions); + } + + return questions; + } + + + private List generateBasicQuestions(int count) { + System.out.println("生成 " + count + " 道基础题目"); + List 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 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 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 questions) { + Set 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; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/NavigationController.java b/src/com/mathlearning/controller/NavigationController.java new file mode 100644 index 0000000..548a6b5 --- /dev/null +++ b/src/com/mathlearning/controller/NavigationController.java @@ -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; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/Paper.java b/src/com/mathlearning/model/Paper.java new file mode 100644 index 0000000..bdb0795 --- /dev/null +++ b/src/com/mathlearning/model/Paper.java @@ -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 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 getQuestions() { return questions; } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/Question.java b/src/com/mathlearning/model/Question.java new file mode 100644 index 0000000..7d9d12b --- /dev/null +++ b/src/com/mathlearning/model/Question.java @@ -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); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/QuestionGenerator.java b/src/com/mathlearning/model/QuestionGenerator.java new file mode 100644 index 0000000..7f4f097 --- /dev/null +++ b/src/com/mathlearning/model/QuestionGenerator.java @@ -0,0 +1,278 @@ +package com.mathlearning.model; + +import java.util.*; + +public abstract class QuestionGenerator { + protected Random random = new Random(); + + public abstract List generateQuestions(int count); + + protected String generateOptions(String correctAnswer) { + Set 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 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 generateQuestions(int count) { + System.out.println("开始生成小学题目,数量: " + count); + List questions = new ArrayList<>(); + Set 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 generateQuestions(int count) { + System.out.println("开始生成初中题目,数量: " + count); + List questions = new ArrayList<>(); + Set 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 generateQuestions(int count) { + System.out.println("开始生成高中题目,数量: " + count); + List questions = new ArrayList<>(); + Set 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; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/User.java b/src/com/mathlearning/model/User.java new file mode 100644 index 0000000..76beaac --- /dev/null +++ b/src/com/mathlearning/model/User.java @@ -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 + "}"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/UserManager.java b/src/com/mathlearning/model/UserManager.java new file mode 100644 index 0000000..8c6ded8 --- /dev/null +++ b/src/com/mathlearning/model/UserManager.java @@ -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 usersByEmail; + private Map 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; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/EmailConfig.java b/src/com/mathlearning/util/EmailConfig.java new file mode 100644 index 0000000..f35ffb9 --- /dev/null +++ b/src/com/mathlearning/util/EmailConfig.java @@ -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()); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/EmailUtil.java b/src/com/mathlearning/util/EmailUtil.java new file mode 100644 index 0000000..85975a3 --- /dev/null +++ b/src/com/mathlearning/util/EmailUtil.java @@ -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"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/ExamFileUtil.java b/src/com/mathlearning/util/ExamFileUtil.java new file mode 100644 index 0000000..efe7153 --- /dev/null +++ b/src/com/mathlearning/util/ExamFileUtil.java @@ -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")); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/FileStorage.java b/src/com/mathlearning/util/FileStorage.java new file mode 100644 index 0000000..71e38c2 --- /dev/null +++ b/src/com/mathlearning/util/FileStorage.java @@ -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 loadUsersByEmail() { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_EMAIL_FILE))) { + return (Map) 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 loadUsersByUsername() { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_USERNAME_FILE))) { + return (Map) 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 usersByEmail, Map 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()); + } + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/PasswordValidator.java b/src/com/mathlearning/util/PasswordValidator.java new file mode 100644 index 0000000..ce0526f --- /dev/null +++ b/src/com/mathlearning/util/PasswordValidator.java @@ -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; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/UsernameValidator.java b/src/com/mathlearning/util/UsernameValidator.java new file mode 100644 index 0000000..a6725cc --- /dev/null +++ b/src/com/mathlearning/util/UsernameValidator.java @@ -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位,只能包含字母、数字和下划线"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/ExamView.java b/src/com/mathlearning/view/ExamView.java new file mode 100644 index 0000000..fcf4981 --- /dev/null +++ b/src/com/mathlearning/view/ExamView.java @@ -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 worker = new SwingWorker() { + @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 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("
" + currentQuestion.getContent() + "
"); + + 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("
题目加载失败,请返回重试
"); + } + + 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); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/LevelSelectionView.java b/src/com/mathlearning/view/LevelSelectionView.java new file mode 100644 index 0000000..c536607 --- /dev/null +++ b/src/com/mathlearning/view/LevelSelectionView.java @@ -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); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/LoginView.java b/src/com/mathlearning/view/LoginView.java new file mode 100644 index 0000000..7ea09af --- /dev/null +++ b/src/com/mathlearning/view/LoginView.java @@ -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); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/QuestionCountView.java b/src/com/mathlearning/view/QuestionCountView.java new file mode 100644 index 0000000..a5448d5 --- /dev/null +++ b/src/com/mathlearning/view/QuestionCountView.java @@ -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); + } + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/RegisterView.java b/src/com/mathlearning/view/RegisterView.java new file mode 100644 index 0000000..8424831 --- /dev/null +++ b/src/com/mathlearning/view/RegisterView.java @@ -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 worker = new SwingWorker() { + @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); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/ScoreView.java b/src/com/mathlearning/view/ScoreView.java new file mode 100644 index 0000000..558decc --- /dev/null +++ b/src/com/mathlearning/view/ScoreView.java @@ -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 "不及格!需要认真学习!"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/SetPasswordView.java b/src/com/mathlearning/view/SetPasswordView.java new file mode 100644 index 0000000..7a6e81d --- /dev/null +++ b/src/com/mathlearning/view/SetPasswordView.java @@ -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); + } + } +} \ No newline at end of file diff --git a/src/email_config.properties.txt b/src/email_config.properties.txt new file mode 100644 index 0000000..76d2365 --- /dev/null +++ b/src/email_config.properties.txt @@ -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 \ No newline at end of file diff --git a/src/exam_papers/wjw/2025-10-11-13-29-32.txt b/src/exam_papers/wjw/2025-10-11-13-29-32.txt new file mode 100644 index 0000000..8dd9576 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-29-32.txt @@ -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 diff --git a/src/exam_papers/wjw/2025-10-11-13-29-45.txt b/src/exam_papers/wjw/2025-10-11-13-29-45.txt new file mode 100644 index 0000000..3594b30 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-29-45.txt @@ -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 diff --git a/src/exam_papers/wjw/2025-10-11-13-30-14.txt b/src/exam_papers/wjw/2025-10-11-13-30-14.txt new file mode 100644 index 0000000..96076ff --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-30-14.txt @@ -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 diff --git a/src/exam_papers/wjw/2025-10-11-13-40-07.txt b/src/exam_papers/wjw/2025-10-11-13-40-07.txt new file mode 100644 index 0000000..0e23104 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-40-07.txt @@ -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 diff --git a/src/users.dat b/src/users.dat new file mode 100644 index 0000000..f9addff Binary files /dev/null and b/src/users.dat differ diff --git a/src/users_email.dat b/src/users_email.dat new file mode 100644 index 0000000..b3f3d0c Binary files /dev/null and b/src/users_email.dat differ diff --git a/src/users_username.dat b/src/users_username.dat new file mode 100644 index 0000000..b40bffb Binary files /dev/null and b/src/users_username.dat differ