From 8015cbdb12cb153dc4316b64a806969791a2d86e Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:36:50 +0800 Subject: [PATCH 01/44] ADD file via upload --- src/main/java/com/example/myapp/Main.java | 103 ++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/main/java/com/example/myapp/Main.java diff --git a/src/main/java/com/example/myapp/Main.java b/src/main/java/com/example/myapp/Main.java new file mode 100644 index 0000000..cb20cc6 --- /dev/null +++ b/src/main/java/com/example/myapp/Main.java @@ -0,0 +1,103 @@ +package com.example.myapp; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class Main extends Application { + private static Stage primaryStage; + + @Override + public void start(Stage stage) throws Exception{ + primaryStage = stage; + showLogin(); + } + + public static void showLogin() throws Exception{ + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/login.fxml")); + primaryStage.setScene(new Scene(loader.load())); + primaryStage.setTitle("登录"); + primaryStage.show(); + } + + public static void showRegister() throws Exception{ + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/register.fxml")); + primaryStage.setScene(new Scene(loader.load())); + primaryStage.setTitle("注册 - 输入邮箱"); + } + + public static void showSetPassword(String email) throws Exception{ + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/set_password.fxml")); + // 传递 email 给 controller + Parent root = loader.load(); + var ctrl = loader.getController(); + ((com.example.myapp.controller.SetPasswordController)ctrl).setEmail(email); + primaryStage.setScene(new Scene(root)); + primaryStage.setTitle("设置密码"); + } + + public static void showDashboard(String email) throws Exception{ + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/dashboard.fxml")); + Parent root = loader.load(); + var ctrl = loader.getController(); + ((com.example.myapp.controller.DashboardController)ctrl).initUser(email); + primaryStage.setScene(new Scene(root)); + primaryStage.setTitle("用户界面"); + } + + // 在 Main.java 中添加以下方法 + public static void showGradeSelection(String email) throws Exception { + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/grade_selection.fxml")); + Parent root = loader.load(); + var ctrl = loader.getController(); + ((com.example.myapp.controller.GradeSelectionController)ctrl).initUser(email); + primaryStage.setScene(new Scene(root)); + primaryStage.setTitle("选择学段"); + } + + public static void showExamPage(String email) throws Exception { + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/exam.fxml")); + Parent root = loader.load(); + + // 获取控制器并初始化 + var ctrl = loader.getController(); + ((com.example.myapp.controller.ExamController)ctrl).initExam(email); + + primaryStage.setScene(new Scene(root)); + primaryStage.setTitle("答题界面"); + } + + public static void showScorePage(String email, int score) throws Exception { + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/score.fxml")); + Parent root = loader.load(); + var ctrl = loader.getController(); + ((com.example.myapp.controller.ScoreController)ctrl).initScore(email, score); + primaryStage.setScene(new Scene(root)); + primaryStage.setTitle("考试成绩"); + } + + public static void showChangePassword(String email) throws Exception { + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/change_password.fxml")); + Parent root = loader.load(); + var ctrl = loader.getController(); + ((com.example.myapp.controller.ChangePasswordController)ctrl).initUser(email); + primaryStage.setScene(new Scene(root)); + primaryStage.setTitle("修改密码"); + } + + + // 在 Main.java 中添加 + public static void showForgotPassword(String email) throws Exception { + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/fxml/forgot_password.fxml")); + Parent root = loader.load(); + var ctrl = loader.getController(); + ((com.example.myapp.controller.ForgotPasswordController)ctrl).setEmail(email); + primaryStage.setScene(new Scene(root)); + primaryStage.setTitle("忘记密码"); + } + public static void main(String[] args){ + launch(args); + } +} -- 2.34.1 From 59f86d258b4e97b4b204e7c4c8ac371762da3359 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:37:33 +0800 Subject: [PATCH 02/44] ADD file via upload --- .../com/example/myapp/util/PasswordUtil.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/example/myapp/util/PasswordUtil.java diff --git a/src/main/java/com/example/myapp/util/PasswordUtil.java b/src/main/java/com/example/myapp/util/PasswordUtil.java new file mode 100644 index 0000000..39c7163 --- /dev/null +++ b/src/main/java/com/example/myapp/util/PasswordUtil.java @@ -0,0 +1,21 @@ +package com.example.myapp.util; + +import org.mindrot.jbcrypt.BCrypt; +import java.util.regex.Pattern; + +public class PasswordUtil { + private static final Pattern validPattern = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$"); + + public static boolean validatePasswordRules(String pwd){ + if(pwd==null) return false; + return validPattern.matcher(pwd).matches(); + } + + public static String hash(String plain){ + return BCrypt.hashpw(plain, BCrypt.gensalt(12)); + } + + public static boolean check(String plain, String hash){ + return BCrypt.checkpw(plain, hash); + } +} -- 2.34.1 From 0ab3df6e53a1662912f58459a068058b01c5b950 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:39:07 +0800 Subject: [PATCH 03/44] ADD file via upload --- .../myapp/service/AbstractQuestionSeting.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/AbstractQuestionSeting.java diff --git a/src/main/java/com/example/myapp/service/AbstractQuestionSeting.java b/src/main/java/com/example/myapp/service/AbstractQuestionSeting.java new file mode 100644 index 0000000..e14fa96 --- /dev/null +++ b/src/main/java/com/example/myapp/service/AbstractQuestionSeting.java @@ -0,0 +1,80 @@ +// AbstractQuestionSeting.java +package com.example.myapp.service; + +import com.example.myapp.model.Expression; +import java.util.Random; + +public abstract class AbstractQuestionSeting implements QuestionSeting { + protected Random rand = new Random(); + + // 只生成整数(1-50) + protected String getRandomNumber() { + int num = 1 + rand.nextInt(50); + return String.valueOf(num); + } + + // 生成完全平方数(用于开方运算) + protected String getPerfectSquare() { + int base = 1 + rand.nextInt(12); // 1-12的平方 + int square = base * base; + return String.valueOf(square); + } + + // 生成适合开方的数(完全平方数) + protected String getNumberForSqrt() { + // 80%概率生成完全平方数,20%概率生成普通数 + //if (rand.nextDouble() < 1) { + return getPerfectSquare(); +// } else { +// return getRandomNumber(); +// } + } + + protected String getRandomOperator() { + String[] ops = {"+", "-", "*", "/"}; + return ops[rand.nextInt(ops.length)]; + } + + protected int getPriority(String op) { + if (op == null) return 3; + switch (op) { + case "+": + case "-": + return 1; + case "*": + case "/": + return 2; + case "²": + case "√": + case "sin": + case "cos": + case "tan": + return 3; + default: + return 0; + } + } + + // 解析数字字符串为double + protected double parseNumber(String numStr) { + return Double.parseDouble(numStr); + } + + // 格式化结果为字符串,如果是整数显示整数,小数显示小数 + protected String formatResult(double value) { + if (value == (int) value) { + return String.valueOf((int) value); + } else { + // 保留2位小数,但去除末尾的0 + String formatted = String.format("%.2f", value); + if (formatted.endsWith(".00")) { + return formatted.substring(0, formatted.length() - 3); + } else if (formatted.endsWith("0")) { + return formatted.substring(0, formatted.length() - 1); + } + return formatted; + } + } + + public abstract String addParenthesesIfNeeded(Expression child, String parentOp, boolean isRightChild); +} \ No newline at end of file -- 2.34.1 From bc5ccc87a3c2c8542ac8982f2c929997622b1d13 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:39:17 +0800 Subject: [PATCH 04/44] ADD file via upload --- .../example/myapp/service/EmailService.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/EmailService.java diff --git a/src/main/java/com/example/myapp/service/EmailService.java b/src/main/java/com/example/myapp/service/EmailService.java new file mode 100644 index 0000000..67f32cc --- /dev/null +++ b/src/main/java/com/example/myapp/service/EmailService.java @@ -0,0 +1,46 @@ +package com.example.myapp.service; + +import jakarta.mail.*; +import jakarta.mail.internet.*; +import java.io.InputStream; +import java.util.Properties; + +public class EmailService { + private final Properties props = new Properties(); + private final String username; + private final String password; + private final String from; + + public EmailService() throws Exception{ + try(InputStream is = getClass().getResourceAsStream("/app.properties")){ + Properties p = new Properties(); + p.load(is); + String host = p.getProperty("smtp.host"); + String port = p.getProperty("smtp.port"); + username = p.getProperty("smtp.username"); + password = p.getProperty("smtp.password"); + from = p.getProperty("smtp.from"); + + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.host", host); + props.put("mail.smtp.port", port); + } + } + + public void sendRegistrationCode(String toEmail, String code) throws MessagingException { + Session session = Session.getInstance(props, new Authenticator(){ + protected PasswordAuthentication getPasswordAuthentication(){ + return new PasswordAuthentication(username, password); + } + }); + + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(from)); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); + message.setSubject("你的注册码"); + message.setText("你的注册码是: " + code + "\n\n请输入此注册码完成注册。"); + + Transport.send(message); + } +} -- 2.34.1 From 4e46dd158d14138efa26bebcdf7018b647c3e224 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:39:34 +0800 Subject: [PATCH 05/44] ADD file via upload --- .../example/myapp/service/ExamService.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/ExamService.java diff --git a/src/main/java/com/example/myapp/service/ExamService.java b/src/main/java/com/example/myapp/service/ExamService.java new file mode 100644 index 0000000..e1bf209 --- /dev/null +++ b/src/main/java/com/example/myapp/service/ExamService.java @@ -0,0 +1,90 @@ +// ExamService.java +package com.example.myapp.service; + +import com.example.myapp.model.Exam; +import com.example.myapp.model.Question; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; + +public class ExamService { + private Map userExams = new ConcurrentHashMap<>(); + private static final ExamService instance = new ExamService(); + + public static ExamService getInstance() { + return instance; + } + + public boolean generateExam(String gradeLevel, int questionCount, String email) { + try { + List questions = MathQuestionGenerator.generateQuestions(gradeLevel, questionCount); + Exam exam = new Exam(gradeLevel, questionCount, questions); + userExams.put(email, exam); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public Exam getCurrentExam(String email) { + return userExams.get(email); + } + + public boolean submitAnswer(String email, int questionIndex, String selectedAnswer) { + Exam exam = userExams.get(email); + if (exam != null && questionIndex >= 0 && questionIndex < exam.getQuestions().size()) { + exam.getQuestions().get(questionIndex).setUserAnswer(selectedAnswer); + return true; + } + return false; + } + + // 在 ExamService.java 的 calculateScore 方法中修改答案比较逻辑 + public int calculateScore(String email) { + Exam exam = userExams.get(email); + if (exam == null) return 0; + + int correctCount = 0; + for (Question question : exam.getQuestions()) { + String userAnswer = question.getUserAnswer(); + String correctAnswer = question.getCorrectAnswer(); + + // 使用容差比较答案(针对小数) + if (isAnswerCorrect(userAnswer, correctAnswer)) { + correctCount++; + } + } + + int score = (int) ((correctCount * 100.0) / exam.getQuestions().size()); + exam.setScore(score); + return score; + } + + // 新增答案比较方法,支持小数容差 + private boolean isAnswerCorrect(String userAnswer, String correctAnswer) { + if (userAnswer == null || correctAnswer == null) { + return false; + } + + try { + // 去除选项标签(如"A. ") + String cleanUserAnswer = userAnswer.replaceAll("^[A-D]\\.\\s*", ""); + String cleanCorrectAnswer = correctAnswer.replaceAll("^[A-D]\\.\\s*", ""); + + // 尝试解析为数字进行比较 + double userValue = Double.parseDouble(cleanUserAnswer); + double correctValue = Double.parseDouble(cleanCorrectAnswer); + + // 使用容差比较 + return Math.abs(userValue - correctValue) < 0.01; + } catch (NumberFormatException e) { + // 如果无法解析为数字,进行字符串比较 + return userAnswer.trim().equals(correctAnswer.trim()); + } + } + + public void clearExam(String email) { + userExams.remove(email); + } +} \ No newline at end of file -- 2.34.1 From e85f3275c2aa24b01818903686093227c13e7cf7 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:39:45 +0800 Subject: [PATCH 06/44] ADD file via upload --- .../myapp/service/FileStorageService.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/FileStorageService.java diff --git a/src/main/java/com/example/myapp/service/FileStorageService.java b/src/main/java/com/example/myapp/service/FileStorageService.java new file mode 100644 index 0000000..5ea2277 --- /dev/null +++ b/src/main/java/com/example/myapp/service/FileStorageService.java @@ -0,0 +1,104 @@ +package com.example.myapp.service; + +import com.example.myapp.model.User; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class FileStorageService { + private static final String DATA_DIR = "data"; + private static final String USERS_FILE = DATA_DIR + "/users.json"; + private static final String REGISTRATION_CODES_FILE = DATA_DIR + "/registration_codes.json"; + private static final String RESET_PASSWORD_CODES_FILE = DATA_DIR + "/reset_password_codes.json"; + + private final ObjectMapper objectMapper; + + public FileStorageService() { + this.objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + try { + Files.createDirectories(Paths.get(DATA_DIR)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 读取用户数据 + public Map readUsersData() { + try { + File file = new File(USERS_FILE); + if (!file.exists()) { + return new ConcurrentHashMap<>(); + } + return objectMapper.readValue(file, new TypeReference>() {}); + } catch (Exception e) { + e.printStackTrace(); + return new ConcurrentHashMap<>(); + } + } + + // 读取注册码数据 + public Map readRegistrationCodesData() { + try { + File file = new File(REGISTRATION_CODES_FILE); + if (!file.exists()) { + return new ConcurrentHashMap<>(); + } + return objectMapper.readValue(file, new TypeReference>() {}); + } catch (Exception e) { + e.printStackTrace(); + return new ConcurrentHashMap<>(); + } + } + + // 新增:读取重置密码码数据 + public Map readResetPasswordCodesData() { + try { + File file = new File(RESET_PASSWORD_CODES_FILE); + if (!file.exists()) { + return new ConcurrentHashMap<>(); + } + return objectMapper.readValue(file, new TypeReference>() {}); + } catch (Exception e) { + e.printStackTrace(); + return new ConcurrentHashMap<>(); + } + } + + // 写入用户数据 + public void writeUsersData(Map data) { + try { + objectMapper.writeValue(new File(USERS_FILE), data); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 写入注册码数据 + public void writeRegistrationCodesData(Map data) { + try { + objectMapper.writeValue(new File(REGISTRATION_CODES_FILE), data); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 新增:写入重置密码码数据 + public void writeResetPasswordCodesData(Map data) { + try { + objectMapper.writeValue(new File(RESET_PASSWORD_CODES_FILE), data); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file -- 2.34.1 From f10ebc1b3caa1f441e59f1cb04568db3bf144f65 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:39:54 +0800 Subject: [PATCH 07/44] ADD file via upload --- .../example/myapp/service/HighQueSeting.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/HighQueSeting.java diff --git a/src/main/java/com/example/myapp/service/HighQueSeting.java b/src/main/java/com/example/myapp/service/HighQueSeting.java new file mode 100644 index 0000000..a8b9a9e --- /dev/null +++ b/src/main/java/com/example/myapp/service/HighQueSeting.java @@ -0,0 +1,139 @@ +// HighQueSeting.java +package com.example.myapp.service; + +import com.example.myapp.model.Expression; + +public class HighQueSeting extends AbstractQuestionSeting { + + @Override + public String addParenthesesIfNeeded(Expression child, String parentOp, boolean isRightChild) { + if (child.mainOperator == null + || child.mainOperator.equals("²") || child.mainOperator.equals("√") + || child.mainOperator.equals("sin") || child.mainOperator.equals("cos") + || child.mainOperator.equals("tan")) { + return child.expression; + } + + int parentPriority = getPriority(parentOp); + int childPriority = getPriority(child.mainOperator); + + if (childPriority < parentPriority) { + return "(" + child.expression + ")"; + } + + if (isRightChild && (parentOp.equals("-") || parentOp.equals("/"))) { + if (parentPriority == childPriority) { + return "(" + child.expression + ")"; + } + } + + return child.expression; + } + + public Expression applyUnary(Expression child, String op) { + switch (op) { + case "²": + if (child.mainOperator == null) { + return new Expression(child.expression + "²", child.value * child.value, "²"); + } + return new Expression("(" + child.expression + ")²", child.value * child.value, "²"); + case "√": + String numStr = getNumberForSqrt(); + child = new Expression(numStr, parseNumber(numStr), null); + + double sqrtValue = Math.sqrt(child.value); + if (child.mainOperator == null) { + return new Expression("√" + child.expression, sqrtValue, "√"); + } + return new Expression("√(" + child.expression + ")", sqrtValue, "√"); + case "sin": + int[] sinAngles = {0, 30, 45, 60, 90, 120, 135, 150, 180}; + int sinAngle = sinAngles[rand.nextInt(sinAngles.length)]; + child = new Expression(String.valueOf(sinAngle), sinAngle, null); + return new Expression("sin(" + child.expression + "°)", + Math.sin(Math.toRadians(child.value)), "sin"); + case "cos": + int[] cosAngles = {0, 30, 45, 60, 90, 120, 135, 150, 180}; + int cosAngle = cosAngles[rand.nextInt(cosAngles.length)]; + child = new Expression(String.valueOf(cosAngle), cosAngle, null); + return new Expression("cos(" + child.expression + "°)", + Math.cos(Math.toRadians(child.value)), "cos"); + case "tan": + int[] tanAngles = {0, 30, 45, 60, 120, 135, 150, 180}; + int tanAngle = tanAngles[rand.nextInt(tanAngles.length)]; + child = new Expression(String.valueOf(tanAngle), tanAngle, null); + double tanValue = Math.tan(Math.toRadians(child.value)); + if (Math.abs(tanValue) > 1000) { + tanValue = tanValue > 0 ? 1000 : -1000; + } + return new Expression("tan(" + child.expression + "°)", tanValue, "tan"); + default: + return child; + } + } + + @Override + public Expression setQuestion(int count) { + Expression result = firstSetQuestion(count); + // 确保高中题目包含至少一个三角函数 + int attempts = 0; + while (!result.expression.contains("sin") && !result.expression.contains("cos") + && !result.expression.contains("tan") && attempts < 10) { + result = firstSetQuestion(count); + attempts++; + } + return result; + } + + public Expression probability(Expression result) { + // 高中阶段:40%概率添加一元运算 + if (rand.nextDouble() < 0.4) { + String[] unaryOps = {"²", "√", "sin", "cos", "tan"}; + String unaryOp = unaryOps[rand.nextInt(unaryOps.length)]; + result = applyUnary(result, unaryOp); + result.mainOperator = unaryOp; + } + return result; + } + + public Expression firstSetQuestion(int count) { + if (count == 1) { + String numStr = getRandomNumber(); + Expression expr = new Expression(numStr, parseNumber(numStr), null); + expr = probability(expr); + return expr; + } + int leftCount = 1 + rand.nextInt(count - 1); + int rightCount = count - leftCount; + + Expression left = firstSetQuestion(leftCount); + Expression right = firstSetQuestion(rightCount); + + String op = getRandomOperator(); + double value = 0; + switch (op) { + case "+": + value = left.value + right.value; + break; + case "-": + value = left.value - right.value; + break; + case "*": + value = left.value * right.value; + break; + case "/": + if (Math.abs(right.value) < 1e-10) { + return firstSetQuestion(count); + } + value = left.value / right.value; + break; + } + + String leftExpr = addParenthesesIfNeeded(left, op, false); + String rightExpr = addParenthesesIfNeeded(right, op, true); + + Expression result = new Expression(leftExpr + " " + op + " " + rightExpr, value, op); + result = probability(result); + return result; + } +} \ No newline at end of file -- 2.34.1 From 07b40f16d6757f6ce810f308f172441c98c2ab59 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:40:05 +0800 Subject: [PATCH 08/44] ADD file via upload --- .../myapp/service/MathQuestionGenerator.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/MathQuestionGenerator.java diff --git a/src/main/java/com/example/myapp/service/MathQuestionGenerator.java b/src/main/java/com/example/myapp/service/MathQuestionGenerator.java new file mode 100644 index 0000000..9e14ef1 --- /dev/null +++ b/src/main/java/com/example/myapp/service/MathQuestionGenerator.java @@ -0,0 +1,139 @@ +// MathQuestionGenerator.java +package com.example.myapp.service; + +import com.example.myapp.model.Question; +import com.example.myapp.model.Expression; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class MathQuestionGenerator { + private static final Random random = new Random(); + + public static List generateQuestions(String gradeLevel, int count) { + List questions = new ArrayList<>(); + QueSetingFactory factory = new QueSetingFactory(); + QuestionSeting questionSeting = factory.getQueSeting(gradeLevel); + Set generatedExpressions = new HashSet<>(); + int maxAttempts = count * 3; // 最大尝试次数,防止无限循环 + for (int i = 0; i < count; i++) { + boolean questionGenerated = false; + int attempts = 0; + while (!questionGenerated && attempts < maxAttempts) { + try { + int nodeCount = 2 + random.nextInt(3); + Expression expression = questionSeting.setQuestion(nodeCount); + if (generatedExpressions.contains(expression.expression)) { + attempts++; + continue; // 如果重复,重新生成 + } + String correctAnswer = formatValue(expression.value); + String questionText = "计算: " + expression.expression + " = ?"; + List options = generateOptions(expression.value); + Question question = new Question( + questionText, options.get(0), // A + options.get(1), // B + options.get(2), // C + options.get(3), // D + getCorrectAnswerLabel(options, correctAnswer) // 正确答案标签 + ); + questions.add(question); + generatedExpressions.add(expression.expression); // 记录已生成的题目 + questionGenerated = true; + } catch (Exception e) { + System.err.println("生成题目失败: " + e.getMessage()); + attempts++; + } + } + if (!questionGenerated) { + questions.add(createFallbackQuestion(i, generatedExpressions)); + } + } + return questions; + } + + // 格式化数值,整数显示整数,小数显示小数(去除末尾的0) + private static String formatValue(double value) { + if (value == (int) value) { + return String.valueOf((int) value); + } else { + // 保留2位小数,但去除末尾的0 + String formatted = String.format("%.2f", value); + if (formatted.endsWith(".00")) { + return formatted.substring(0, formatted.length() - 3); + } else if (formatted.endsWith("0")) { + return formatted.substring(0, formatted.length() - 1); + } + return formatted; + } + } + + private static List generateOptions(double correctValue) { + List options = new ArrayList<>(); + String correctAnswerStr = formatValue(correctValue); + options.add(correctAnswerStr); // 正确答案 + + // 生成3个错误答案 + while (options.size() < 4) { + double deviation; + // 根据正确答案的大小调整偏差 + if (Math.abs(correctValue) < 10) { + // 小数值使用较小的偏差 + deviation = 0.5 + random.nextDouble() * 3.0; + } else { + // 大数值使用较大的偏差 + deviation = 1 + random.nextInt(5); + } + + boolean positive = random.nextBoolean(); + double wrongValue = positive ? correctValue + deviation : correctValue - deviation; + + // 确保错误答案合理且与正确答案不同 + String wrongValueStr = formatValue(wrongValue); + if (!options.contains(wrongValueStr)) { + options.add(wrongValueStr); + } + } + + Collections.shuffle(options); + return options; + } + + private static String getCorrectAnswerLabel(List options, String correctAnswer) { + for (int i = 0; i < options.size(); i++) { + if (options.get(i).equals(correctAnswer)) { + return String.valueOf((char)('A' + i)); + } + } + return "D"; + } + + // 修改备用题目生成方法,也避免重复 + private static Question createFallbackQuestion(int index, Set generatedExpressions) { + int baseNum = index + 1; + String expression; + + // 尝试生成不重复的简单表达式 + if (!generatedExpressions.contains(baseNum + " + " + (baseNum + 1))) { + expression = baseNum + " + " + (baseNum + 1); + } else if (!generatedExpressions.contains(baseNum + " × " + (baseNum + 2))) { + expression = baseNum + " × " + (baseNum + 2); + } else { + expression = (baseNum + 5) + " - " + baseNum; + } + + generatedExpressions.add(expression); + + return new Question( + "计算: " + expression + " = ?", + "A. " + (2 * baseNum + 1), + "B. " + (2 * baseNum + 2), + "C. " + (2 * baseNum + 3), + "D. " + (2 * baseNum + 4), + "C" + ); + } +} \ No newline at end of file -- 2.34.1 From 54425f1e15761a804711481208cd4e6df1d1ec65 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:40:16 +0800 Subject: [PATCH 09/44] ADD file via upload --- .../myapp/service/MiddleQueSeting.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/MiddleQueSeting.java diff --git a/src/main/java/com/example/myapp/service/MiddleQueSeting.java b/src/main/java/com/example/myapp/service/MiddleQueSeting.java new file mode 100644 index 0000000..3b8b346 --- /dev/null +++ b/src/main/java/com/example/myapp/service/MiddleQueSeting.java @@ -0,0 +1,113 @@ +// MiddleQueSeting.java +package com.example.myapp.service; + +import com.example.myapp.model.Expression; + +public class MiddleQueSeting extends AbstractQuestionSeting { + + public Expression applyUnary(Expression child, String op) { + switch (op) { + case "²": + if (child.mainOperator == null) { + return new Expression(child.expression + "²", child.value * child.value, "²"); + } + return new Expression("(" + child.expression + ")²", child.value * child.value, "²"); + case "√": + // 使用适合开方的数(主要是完全平方数) + String numStr = getNumberForSqrt(); + child = new Expression(numStr, parseNumber(numStr), null); + + double sqrtValue = Math.sqrt(child.value); + if (child.mainOperator == null) { + return new Expression("√" + child.expression, sqrtValue, "√"); + } + return new Expression("√(" + child.expression + ")", sqrtValue, "√"); + default: + return child; + } + } + + @Override + public String addParenthesesIfNeeded(Expression child, String parentOp, boolean isRightChild) { + if (child.mainOperator == null || child.mainOperator.equals("²") || child.mainOperator.equals("√")) { + return child.expression; + } + + int parentPriority = getPriority(parentOp); + int childPriority = getPriority(child.mainOperator); + + if (childPriority < parentPriority) { + return "(" + child.expression + ")"; + } + + if (isRightChild && (parentOp.equals("-") || parentOp.equals("/"))) { + if (parentPriority == childPriority) { + return "(" + child.expression + ")"; + } + } + + return child.expression; + } + + @Override + public Expression setQuestion(int count) { + Expression result = firstSetQuestion(count); + // 确保初中题目包含平方或开方 + while (!result.expression.contains("²") && !result.expression.contains("√")) { + result = firstSetQuestion(count); + } + return result; + } + + public Expression firstSetQuestion(int count) { + if (count == 1) { + String numStr = getRandomNumber(); + Expression expr = new Expression(numStr, parseNumber(numStr), null); + return probability(expr); + } + int leftCount = 1 + rand.nextInt(count - 1); + int rightCount = count - leftCount; + Expression left = firstSetQuestion(leftCount); + Expression right = firstSetQuestion(rightCount); + String op = getRandomOperator(); + double value = 0; + switch (op) { + case "+": + value = left.value + right.value; + break; + case "-": + value = left.value - right.value; + if (value < 0) { + Expression temp = left; + left = right; + right = temp; + value = left.value - right.value; + } + break; + case "*": + value = left.value * right.value; + break; + case "/": + if (Math.abs(right.value) < 1e-10) { + right = firstSetQuestion(rightCount); + } + value = left.value / right.value; + break; + } + String leftExpr = addParenthesesIfNeeded(left, op, false); + String rightExpr = addParenthesesIfNeeded(right, op, true); + Expression result = new Expression(leftExpr + " " + op + " " + rightExpr, value, op); + result = probability(result); + return result; + } + + public Expression probability(Expression result) { + if (rand.nextDouble() < 0.3) { + String[] unaryOps = {"²", "√"}; + String unaryOp = unaryOps[rand.nextInt(unaryOps.length)]; + result = applyUnary(result, unaryOp); + result.mainOperator = unaryOp; + } + return result; + } +} \ No newline at end of file -- 2.34.1 From 39a594adff568e502878c7052f20c9e66257c7e4 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:40:28 +0800 Subject: [PATCH 10/44] ADD file via upload --- .../myapp/service/PrimaryQueSeting.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/PrimaryQueSeting.java diff --git a/src/main/java/com/example/myapp/service/PrimaryQueSeting.java b/src/main/java/com/example/myapp/service/PrimaryQueSeting.java new file mode 100644 index 0000000..f856499 --- /dev/null +++ b/src/main/java/com/example/myapp/service/PrimaryQueSeting.java @@ -0,0 +1,69 @@ +// PrimaryQueSeting.java +package com.example.myapp.service; + +import com.example.myapp.model.Expression; + +public class PrimaryQueSeting extends AbstractQuestionSeting { + + @Override + public Expression setQuestion(int count) { + if (count == 1) { + String expr = getRandomNumber(); + return new Expression(expr, parseNumber(expr), null); + } + + int leftCount = 1 + rand.nextInt(count - 1); + int rightCount = count - leftCount; + Expression left = setQuestion(leftCount); + Expression right = setQuestion(rightCount); + + String op = getRandomOperator(); + + // 处理除数为0的情况 + while (op.equals("/") && Math.abs(right.value) < 1e-10) { + right = setQuestion(rightCount); + } + + // 确保减法结果不为负数 + if (op.equals("-") && left.value < right.value) { + Expression temp = left; + left = right; + right = temp; + } + + String leftExpr = addParenthesesIfNeeded(left, op, false); + String rightExpr = addParenthesesIfNeeded(right, op, true); + + double value = switch (op) { + case "+" -> left.value + right.value; + case "-" -> left.value - right.value; + case "*" -> left.value * right.value; + case "/" -> left.value / right.value; + default -> 0; + }; + + return new Expression(leftExpr + " " + op + " " + rightExpr, value, op); + } + + @Override + public String addParenthesesIfNeeded(Expression child, String parentOp, boolean isRightChild) { + if (child.mainOperator == null) { + return child.expression; + } + + int parentPriority = getPriority(parentOp); + int childPriority = getPriority(child.mainOperator); + + if (childPriority < parentPriority) { + return "(" + child.expression + ")"; + } + + if (isRightChild && (parentOp.equals("-") || parentOp.equals("/"))) { + if (parentPriority == childPriority) { + return "(" + child.expression + ")"; + } + } + + return child.expression; + } +} \ No newline at end of file -- 2.34.1 From 5cf7ec1dfffda8130247d356f05bb4066981004c Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:40:39 +0800 Subject: [PATCH 11/44] ADD file via upload --- .../myapp/service/QueSetingFactory.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/QueSetingFactory.java diff --git a/src/main/java/com/example/myapp/service/QueSetingFactory.java b/src/main/java/com/example/myapp/service/QueSetingFactory.java new file mode 100644 index 0000000..0a687e8 --- /dev/null +++ b/src/main/java/com/example/myapp/service/QueSetingFactory.java @@ -0,0 +1,18 @@ +// QueSetingFactory.java +package com.example.myapp.service; + +public class QueSetingFactory { + public QuestionSeting getQueSeting(String type) { + switch (type) { + case "小学": + return new PrimaryQueSeting(); + case "初中": + return new MiddleQueSeting(); + case "高中": + return new HighQueSeting(); + default: + System.out.println("类型错误"); + return null; + } + } +} \ No newline at end of file -- 2.34.1 From 803ff633a623245c5a737261466ab86d0d5111a1 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:40:49 +0800 Subject: [PATCH 12/44] ADD file via upload --- QuestionSeting.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 QuestionSeting.java diff --git a/QuestionSeting.java b/QuestionSeting.java new file mode 100644 index 0000000..3d16bcd --- /dev/null +++ b/QuestionSeting.java @@ -0,0 +1,8 @@ +// QuestionSeting.java +package com.example.myapp.service; + +import com.example.myapp.model.Expression; + +public interface QuestionSeting { + Expression setQuestion(int count); +} \ No newline at end of file -- 2.34.1 From 077f2f0447afef63044b38a6a468d5b412d9113b Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:40:57 +0800 Subject: [PATCH 13/44] Delete 'QuestionSeting.java' --- QuestionSeting.java | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 QuestionSeting.java diff --git a/QuestionSeting.java b/QuestionSeting.java deleted file mode 100644 index 3d16bcd..0000000 --- a/QuestionSeting.java +++ /dev/null @@ -1,8 +0,0 @@ -// QuestionSeting.java -package com.example.myapp.service; - -import com.example.myapp.model.Expression; - -public interface QuestionSeting { - Expression setQuestion(int count); -} \ No newline at end of file -- 2.34.1 From c0e9c07b46b060a3cc6d0b3c6421895242288c71 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:41:09 +0800 Subject: [PATCH 14/44] ADD file via upload --- .../java/com/example/myapp/service/QuestionSeting.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/QuestionSeting.java diff --git a/src/main/java/com/example/myapp/service/QuestionSeting.java b/src/main/java/com/example/myapp/service/QuestionSeting.java new file mode 100644 index 0000000..3d16bcd --- /dev/null +++ b/src/main/java/com/example/myapp/service/QuestionSeting.java @@ -0,0 +1,8 @@ +// QuestionSeting.java +package com.example.myapp.service; + +import com.example.myapp.model.Expression; + +public interface QuestionSeting { + Expression setQuestion(int count); +} \ No newline at end of file -- 2.34.1 From 8d8c743d1e218672382e6fa57fa99f327827fa59 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:41:21 +0800 Subject: [PATCH 15/44] ADD file via upload --- .../example/myapp/service/UserService.java | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 src/main/java/com/example/myapp/service/UserService.java diff --git a/src/main/java/com/example/myapp/service/UserService.java b/src/main/java/com/example/myapp/service/UserService.java new file mode 100644 index 0000000..7e343df --- /dev/null +++ b/src/main/java/com/example/myapp/service/UserService.java @@ -0,0 +1,181 @@ +package com.example.myapp.service; + +import com.example.myapp.model.User; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class UserService { + private Map users; + private Map usernameToEmail; + private Map registrationCodes; + private Map resetPasswordCodes; + + private final FileStorageService fileStorageService; + private static final UserService instance = new UserService(); + + public static UserService getInstance() { + return instance; + } + + private UserService() { + this.fileStorageService = new FileStorageService(); + loadData(); + } + + private void loadData() { + users = fileStorageService.readUsersData(); + registrationCodes = fileStorageService.readRegistrationCodesData(); + resetPasswordCodes = fileStorageService.readResetPasswordCodesData(); + + usernameToEmail = new ConcurrentHashMap<>(); + if (users != null) { + for (User user : users.values()) { + if (user.getUsername() != null && !user.getUsername().trim().isEmpty()) { + usernameToEmail.put(user.getUsername().toLowerCase(), user.getEmail()); + } + } + } + + if (users == null) users = new ConcurrentHashMap<>(); + if (registrationCodes == null) registrationCodes = new ConcurrentHashMap<>(); + if (resetPasswordCodes == null) resetPasswordCodes = new ConcurrentHashMap<>(); + if (usernameToEmail == null) usernameToEmail = new ConcurrentHashMap<>(); + } + + private void saveUsers() { + fileStorageService.writeUsersData(users); + } + + private void saveRegistrationCodes() { + fileStorageService.writeRegistrationCodesData(registrationCodes); + } + + private void saveResetPasswordCodes() { + fileStorageService.writeResetPasswordCodesData(resetPasswordCodes); + } + + public boolean emailExists(String email) { + return users.containsKey(email); + } + + public boolean usernameExists(String username) { + return usernameToEmail.containsKey(username.toLowerCase()); + } + + public User getUserByIdentifier(String identifier) { + if (identifier.contains("@")) { + return users.get(identifier); + } else { + String email = usernameToEmail.get(identifier.toLowerCase()); + return email != null ? users.get(email) : null; + } + } + + public boolean userExists(String identifier) { + if (identifier.contains("@")) { + return emailExists(identifier); + } else { + return usernameExists(identifier); + } + } + + public boolean isUserRegistered(String email) { + User user = users.get(email); + return user != null && user.getPasswordHash() != null && !user.getPasswordHash().isEmpty(); + } + + public void createPendingUser(String email, String code) { + if (users.containsKey(email)) { + User existingUser = users.get(email); + if (existingUser.getPasswordHash() == null || existingUser.getPasswordHash().isEmpty()) { + registrationCodes.put(email, code); + saveRegistrationCodes(); + return; + } + } + + users.putIfAbsent(email, new User(email, null)); + registrationCodes.put(email, code); + saveUsers(); + saveRegistrationCodes(); + } + + public boolean setUsername(String email, String username) { + if (username == null || username.trim().isEmpty()) { + return false; + } + + String usernameLower = username.toLowerCase(); + if (usernameToEmail.containsKey(usernameLower)) { + return false; + } + + User user = users.get(email); + if (user != null) { + if (user.getUsername() != null) { + usernameToEmail.remove(user.getUsername().toLowerCase()); + } + + user.setUsername(username); + usernameToEmail.put(usernameLower, email); + saveUsers(); + return true; + } + return false; + } + + public boolean verifyCode(String email, String code) { + String storedCode = registrationCodes.get(email); + if (storedCode != null && storedCode.equals(code)) { + User u = users.get(email); + if (u != null) { + u.setVerified(true); + registrationCodes.remove(email); + saveUsers(); + saveRegistrationCodes(); + return true; + } + } + return false; + } + + // 忘记密码相关方法 + public void createResetPasswordCode(String email, String code) { + resetPasswordCodes.put(email, code); + saveResetPasswordCodes(); + } + + public boolean verifyResetPasswordCode(String email, String code) { + String storedCode = resetPasswordCodes.get(email); + if (storedCode != null && storedCode.equals(code)) { + resetPasswordCodes.remove(email); + saveResetPasswordCodes(); + return true; + } + return false; + } + + public void setPassword(String email, String passwordHash) { + User u = users.get(email); + if (u != null) { + u.setPasswordHash(passwordHash); + u.setVerified(true); + saveUsers(); + } + } + + public boolean checkPassword(String identifier, String plain) { + User user = getUserByIdentifier(identifier); + if (user == null || user.getPasswordHash() == null) { + return false; + } + + return com.example.myapp.util.PasswordUtil.check(plain, user.getPasswordHash()); + } + + public String getDisplayName(String identifier) { + User user = getUserByIdentifier(identifier); + if (user == null) return identifier; + return user.getUsername() != null ? user.getUsername() : user.getEmail(); + } +} \ No newline at end of file -- 2.34.1 From 590c8cd364069cbcd01c3d321971e6ad6f730ecc Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:42:09 +0800 Subject: [PATCH 16/44] ADD file via upload --- .../java/com/example/myapp/model/Exam.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/com/example/myapp/model/Exam.java diff --git a/src/main/java/com/example/myapp/model/Exam.java b/src/main/java/com/example/myapp/model/Exam.java new file mode 100644 index 0000000..a217d67 --- /dev/null +++ b/src/main/java/com/example/myapp/model/Exam.java @@ -0,0 +1,40 @@ +package com.example.myapp.model; + +import java.util.List; +import java.time.LocalDateTime; + +public class Exam { + private String gradeLevel; + private int questionCount; + private List questions; + private int score; + private LocalDateTime startTime; + private LocalDateTime endTime; + + public Exam(String gradeLevel, int questionCount, List questions) { + this.gradeLevel = gradeLevel; + this.questionCount = questionCount; + this.questions = questions; + this.startTime = LocalDateTime.now(); + this.score = -1; // -1表示未评分 + } + + // Getters and Setters + public String getGradeLevel() { return gradeLevel; } + public void setGradeLevel(String gradeLevel) { this.gradeLevel = gradeLevel; } + + public int getQuestionCount() { return questionCount; } + public void setQuestionCount(int questionCount) { this.questionCount = questionCount; } + + public List getQuestions() { return questions; } + public void setQuestions(List questions) { this.questions = questions; } + + public int getScore() { return score; } + public void setScore(int score) { this.score = score; } + + public LocalDateTime getStartTime() { return startTime; } + public void setStartTime(LocalDateTime startTime) { this.startTime = startTime; } + + public LocalDateTime getEndTime() { return endTime; } + public void setEndTime(LocalDateTime endTime) { this.endTime = endTime; } +} \ No newline at end of file -- 2.34.1 From eaeac8d87dda64e21b60c78009f77e3a2f345bbb Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:42:18 +0800 Subject: [PATCH 17/44] ADD file via upload --- .../java/com/example/myapp/model/Expression.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/example/myapp/model/Expression.java diff --git a/src/main/java/com/example/myapp/model/Expression.java b/src/main/java/com/example/myapp/model/Expression.java new file mode 100644 index 0000000..e69d229 --- /dev/null +++ b/src/main/java/com/example/myapp/model/Expression.java @@ -0,0 +1,14 @@ +// Expression.java +package com.example.myapp.model; + +public class Expression { + public String expression; + public double value; // 改为 double 类型支持小数答案 + public String mainOperator; + + public Expression(String expression, double value, String mainOperator) { + this.expression = expression; + this.value = value; + this.mainOperator = mainOperator; + } +} \ No newline at end of file -- 2.34.1 From be28dc9276ee3779a83fd00df28908a2d0981d47 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:42:29 +0800 Subject: [PATCH 18/44] ADD file via upload --- .../com/example/myapp/model/Question.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/com/example/myapp/model/Question.java diff --git a/src/main/java/com/example/myapp/model/Question.java b/src/main/java/com/example/myapp/model/Question.java new file mode 100644 index 0000000..9a69108 --- /dev/null +++ b/src/main/java/com/example/myapp/model/Question.java @@ -0,0 +1,52 @@ +package com.example.myapp.model; + +public class Question { + private String questionText; + private String optionA; + private String optionB; + private String optionC; + private String optionD; + private String correctAnswer; + private String userAnswer; + +// public Question(String questionText, String optionA, String optionB, +// String optionC, String optionD, String correctAnswer) { +// this.questionText = questionText; +// this.optionA = optionA; +// this.optionB = optionB; +// this.optionC = optionC; +// this.optionD = optionD; +// this.correctAnswer = correctAnswer; +// } + + // Getters and Setters + public Question(String questionText, String optionA, String optionB, + String optionC, String optionD, String correctAnswer) { + this.questionText = questionText; + this.optionA = optionA; + this.optionB = optionB; + this.optionC = optionC; + this.optionD = optionD; + this.correctAnswer = correctAnswer; + } + public String getQuestionText() { return questionText; } + public void setQuestionText(String questionText) { this.questionText = questionText; } + + public String getOptionA() { return optionA; } + public void setOptionA(String optionA) { this.optionA = optionA; } + + public String getOptionB() { return optionB; } + public void setOptionB(String optionB) { this.optionB = optionB; } + + public String getOptionC() { return optionC; } + public void setOptionC(String optionC) { this.optionC = optionC; } + + public String getOptionD() { return optionD; } + public void setOptionD(String optionD) { this.optionD = optionD; } + + 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; } +} \ No newline at end of file -- 2.34.1 From a9d055b92dfc058230debc23faea58f17d6abf9f Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:42:42 +0800 Subject: [PATCH 19/44] ADD file via upload --- .../java/com/example/myapp/model/User.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/main/java/com/example/myapp/model/User.java diff --git a/src/main/java/com/example/myapp/model/User.java b/src/main/java/com/example/myapp/model/User.java new file mode 100644 index 0000000..f4a24d8 --- /dev/null +++ b/src/main/java/com/example/myapp/model/User.java @@ -0,0 +1,62 @@ +package com.example.myapp.model; + +import java.io.Serializable; +import java.time.LocalDateTime; + +public class User implements Serializable { + private String email; + private String username; // 新增用户名字段 + private String passwordHash; + private boolean verified; + private LocalDateTime createdAt; + private LocalDateTime lastModified; + + public User() { + this.createdAt = LocalDateTime.now(); + this.lastModified = LocalDateTime.now(); + } + + public User(String email, String username) { + this.email = email; + this.username = username; + this.verified = false; + this.createdAt = LocalDateTime.now(); + this.lastModified = LocalDateTime.now(); + } + + // Getters and Setters + public String getEmail() { return email; } + public void setEmail(String email) { + this.email = email; + this.lastModified = LocalDateTime.now(); + } + + public String getUsername() { return username; } + public void setUsername(String username) { + this.username = username; + this.lastModified = LocalDateTime.now(); + } + + public String getPasswordHash() { return passwordHash; } + public void setPasswordHash(String passwordHash) { + this.passwordHash = passwordHash; + this.lastModified = LocalDateTime.now(); + } + + public boolean isVerified() { return verified; } + public void setVerified(boolean verified) { + this.verified = verified; + this.lastModified = LocalDateTime.now(); + } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + + public LocalDateTime getLastModified() { return lastModified; } + public void setLastModified(LocalDateTime lastModified) { this.lastModified = lastModified; } + + @Override + public String toString() { + return "User{email='" + email + "', username='" + username + "', verified=" + verified + "}"; + } +} \ No newline at end of file -- 2.34.1 From f08d2c25975bdcf2efcb7fd0421f6f1d60b45701 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:43:58 +0800 Subject: [PATCH 20/44] ADD file via upload --- .../controller/ChangePasswordController.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/ChangePasswordController.java diff --git a/src/main/java/com/example/myapp/controller/ChangePasswordController.java b/src/main/java/com/example/myapp/controller/ChangePasswordController.java new file mode 100644 index 0000000..8e5de44 --- /dev/null +++ b/src/main/java/com/example/myapp/controller/ChangePasswordController.java @@ -0,0 +1,82 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.UserService; +import com.example.myapp.util.PasswordUtil; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +public class ChangePasswordController { + @FXML private Label userLabel; + @FXML private PasswordField oldPwd; + @FXML private PasswordField newPwd; + @FXML private PasswordField newPwdConfirm; + @FXML private Button changeBtn; + @FXML private Button backBtn; + @FXML private Button clearBtn; + @FXML private Label statusLabel; + + private String email; + private final UserService userService = UserService.getInstance(); + + public void initUser(String email) { + this.email = email; + userLabel.setText("修改密码 - " + email); + clearFields(); + } + + @FXML + public void onChangePassword() { + String oldp = oldPwd.getText(); + String np = newPwd.getText(); + String np2 = newPwdConfirm.getText(); + + if (oldp.isEmpty() || np.isEmpty() || np2.isEmpty()) { + statusLabel.setText("请填写所有密码字段"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if (!userService.checkPassword(email, oldp)) { + statusLabel.setText("原密码不正确"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + if (!np.equals(np2)) { + statusLabel.setText("两次新密码不一致"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + if (!PasswordUtil.validatePasswordRules(np)) { + statusLabel.setText("新密码不符合规则(6-10位,含大小写和数字)"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + userService.setPassword(email, PasswordUtil.hash(np)); + statusLabel.setText("密码修改成功!"); + statusLabel.setStyle("-fx-text-fill: green;"); + clearFields(); + } + + @FXML + public void onBack() { + try { + Main.showDashboard(email); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @FXML + public void onClear() { + clearFields(); + statusLabel.setText(""); + } + + private void clearFields() { + oldPwd.clear(); + newPwd.clear(); + newPwdConfirm.clear(); + } +} \ No newline at end of file -- 2.34.1 From a99d0749b21729c4948ca8df72a56c1e018b1b4a Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:44:07 +0800 Subject: [PATCH 21/44] ADD file via upload --- .../myapp/controller/DashboardController.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/DashboardController.java diff --git a/src/main/java/com/example/myapp/controller/DashboardController.java b/src/main/java/com/example/myapp/controller/DashboardController.java new file mode 100644 index 0000000..376f00f --- /dev/null +++ b/src/main/java/com/example/myapp/controller/DashboardController.java @@ -0,0 +1,49 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.UserService; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +public class DashboardController { + @FXML private Label welcomeLabel; + @FXML private Button changePasswordBtn; + @FXML private Button studyBtn; + @FXML private Button logoutBtn; + + private String email; + private final UserService userService = UserService.getInstance(); + + public void initUser(String email){ + this.email = email; + String displayName = userService.getDisplayName(email); + welcomeLabel.setText("欢迎: " + displayName); + } + + @FXML + public void onChangePassword() { + try { + Main.showChangePassword(email); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @FXML + public void onStudy() { + try { + Main.showGradeSelection(email); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @FXML + public void onLogout() { + try { + Main.showLogin(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file -- 2.34.1 From b10ab098488953bf1c57a91c72458ba2b9dac129 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:44:15 +0800 Subject: [PATCH 22/44] ADD file via upload --- .../myapp/controller/ExamController.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/ExamController.java diff --git a/src/main/java/com/example/myapp/controller/ExamController.java b/src/main/java/com/example/myapp/controller/ExamController.java new file mode 100644 index 0000000..9ce9027 --- /dev/null +++ b/src/main/java/com/example/myapp/controller/ExamController.java @@ -0,0 +1,161 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.model.Exam; +import com.example.myapp.model.Question; +import com.example.myapp.service.ExamService; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +public class ExamController { + @FXML private Label questionNumberLabel; + @FXML private Label questionTextLabel; + @FXML private RadioButton optionARadio; + @FXML private RadioButton optionBRadio; + @FXML private RadioButton optionCRadio; + @FXML private RadioButton optionDRadio; + @FXML private Label optionALabel; + @FXML private Label optionBLabel; + @FXML private Label optionCLabel; + @FXML private Label optionDLabel; + @FXML private Button previousBtn; + @FXML private Button nextBtn; + @FXML private Button submitBtn; + @FXML private ProgressIndicator progressIndicator; + + private String email; + private ExamService examService = ExamService.getInstance(); + private int currentQuestionIndex = 0; + private ToggleGroup optionsToggleGroup; + + // 添加初始化方法,使用 @FXML 注解确保在FXML加载后立即执行 + @FXML + public void initialize() { + // 在这里初始化 ToggleGroup,确保在FXML加载完成后立即执行 + this.optionsToggleGroup = new ToggleGroup(); + + optionARadio.setToggleGroup(optionsToggleGroup); + optionBRadio.setToggleGroup(optionsToggleGroup); + optionCRadio.setToggleGroup(optionsToggleGroup); + optionDRadio.setToggleGroup(optionsToggleGroup); + + // 设置默认禁用状态 + previousBtn.setDisable(true); + submitBtn.setDisable(true); + } + + public void initExam(String email) { + this.email = email; + + // 确保有考试数据 + Exam exam = examService.getCurrentExam(email); + if (exam == null || exam.getQuestions().isEmpty()) { + showAlert("错误", "没有找到考试数据,请重新选择学段"); + try { + Main.showGradeSelection(email); + } catch (Exception e) { + e.printStackTrace(); + } + return; + } + + loadQuestion(0); + } + + @FXML + public void onPreviousQuestion() { + saveCurrentAnswer(); + if (currentQuestionIndex > 0) { + loadQuestion(currentQuestionIndex - 1); + } + } + + @FXML + public void onNextQuestion() { + saveCurrentAnswer(); + Exam exam = examService.getCurrentExam(email); + if (currentQuestionIndex < exam.getQuestions().size() - 1) { + loadQuestion(currentQuestionIndex + 1); + } + } + + @FXML + public void onSubmitExam() { + saveCurrentAnswer(); + + // 计算分数 + int score = examService.calculateScore(email); + + // 显示分数界面 + try { + Main.showScorePage(email, score); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void loadQuestion(int index) { + Exam exam = examService.getCurrentExam(email); + if (exam == null || index < 0 || index >= exam.getQuestions().size()) { + return; + } + + currentQuestionIndex = index; + Question question = exam.getQuestions().get(index); + + // 更新界面 + questionNumberLabel.setText("第 " + (index + 1) + " 题 / 共 " + exam.getQuestions().size() + " 题"); + questionTextLabel.setText(question.getQuestionText()); + optionALabel.setText("A. " + question.getOptionA()); + optionBLabel.setText("B. " + question.getOptionB()); + optionCLabel.setText("C. " + question.getOptionC()); + optionDLabel.setText("D. " + question.getOptionD()); + + // 清除选择 + optionsToggleGroup.selectToggle(null); + + // 恢复用户之前的选择 + if (question.getUserAnswer() != null) { + switch (question.getUserAnswer()) { + case "A": optionARadio.setSelected(true); break; + case "B": optionBRadio.setSelected(true); break; + case "C": optionCRadio.setSelected(true); break; + case "D": optionDRadio.setSelected(true); break; + } + } + + // 更新按钮状态 + previousBtn.setDisable(index == 0); + nextBtn.setDisable(index == exam.getQuestions().size() - 1); + submitBtn.setDisable(index != exam.getQuestions().size() - 1); + + // 更新进度 + progressIndicator.setProgress((double) (index + 1) / exam.getQuestions().size()); + } + + private void saveCurrentAnswer() { + // 添加空值检查 + if (optionsToggleGroup == null) { + return; + } + + RadioButton selectedRadio = (RadioButton) optionsToggleGroup.getSelectedToggle(); + if (selectedRadio != null) { + String answer = ""; + if (selectedRadio == optionARadio) answer = "A"; + else if (selectedRadio == optionBRadio) answer = "B"; + else if (selectedRadio == optionCRadio) answer = "C"; + else if (selectedRadio == optionDRadio) answer = "D"; + + examService.submitAnswer(email, currentQuestionIndex, answer); + } + } + + private void showAlert(String title, String message) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } +} \ No newline at end of file -- 2.34.1 From 8347af64e1d87afbed9d8771c94aa6479402c752 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:44:24 +0800 Subject: [PATCH 23/44] ADD file via upload --- .../controller/ForgotPasswordController.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/ForgotPasswordController.java diff --git a/src/main/java/com/example/myapp/controller/ForgotPasswordController.java b/src/main/java/com/example/myapp/controller/ForgotPasswordController.java new file mode 100644 index 0000000..823160b --- /dev/null +++ b/src/main/java/com/example/myapp/controller/ForgotPasswordController.java @@ -0,0 +1,105 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.EmailService; +import com.example.myapp.service.UserService; +import com.example.myapp.util.PasswordUtil; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import java.util.Random; + +public class ForgotPasswordController { + @FXML private Label emailLabel; + @FXML private Button sendCodeBtn; + @FXML private TextField codeField; + @FXML private PasswordField newPasswordField; + @FXML private PasswordField confirmPasswordField; + @FXML private Button resetPasswordBtn; + @FXML private Button backToLoginBtn; + @FXML private Label statusLabel; + + private String email; + private final UserService userService = UserService.getInstance(); + private EmailService emailService; + + public ForgotPasswordController() { + try { + emailService = new EmailService(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void setEmail(String email) { + this.email = email; + emailLabel.setText("重置密码 - " + email); + } + + @FXML + public void onSendCode() { + String code = generateCode(); + userService.createResetPasswordCode(email, code); + try { + emailService.sendRegistrationCode(email, code); + statusLabel.setText("验证码已发送到您的邮箱,请查收"); + statusLabel.setStyle("-fx-text-fill: green;"); + } catch (Exception e) { + e.printStackTrace(); + statusLabel.setText("发送失败: " + e.getMessage()); + statusLabel.setStyle("-fx-text-fill: red;"); + } + } + + @FXML + public void onResetPassword() { + String code = codeField.getText().trim(); + String newPassword = newPasswordField.getText(); + String confirmPassword = confirmPasswordField.getText(); + + if (!userService.verifyResetPasswordCode(email, code)) { + statusLabel.setText("验证码错误或已过期"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if (!newPassword.equals(confirmPassword)) { + statusLabel.setText("两次输入的密码不一致"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if (!PasswordUtil.validatePasswordRules(newPassword)) { + statusLabel.setText("密码不符合规则(6-10位,须含大写、小写和数字)"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + String hash = PasswordUtil.hash(newPassword); + userService.setPassword(email, hash); + + statusLabel.setText("密码重置成功,请重新登录"); + statusLabel.setStyle("-fx-text-fill: green;"); + + try { + Thread.sleep(2000); + Main.showLogin(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @FXML + public void onBackToLogin() { + try { + Main.showLogin(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private String generateCode() { + Random r = new Random(); + int v = 100000 + r.nextInt(900000); + return String.valueOf(v); + } +} \ No newline at end of file -- 2.34.1 From bda4edbdd7a3c249a9d2fef186f645cd7db19edb Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:44:35 +0800 Subject: [PATCH 24/44] ADD file via upload --- .../controller/GradeSelectionController.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/GradeSelectionController.java diff --git a/src/main/java/com/example/myapp/controller/GradeSelectionController.java b/src/main/java/com/example/myapp/controller/GradeSelectionController.java new file mode 100644 index 0000000..79d4fcb --- /dev/null +++ b/src/main/java/com/example/myapp/controller/GradeSelectionController.java @@ -0,0 +1,92 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.ExamService; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +public class GradeSelectionController { + @FXML private Label welcomeLabel; + + private String email; + private final ExamService examService = ExamService.getInstance(); + + public void initUser(String email) { + this.email = email; + welcomeLabel.setText("欢迎 " + email + ",请选择学段"); + } + + @FXML + public void onPrimarySchoolSelected() { + showQuestionCountDialog("小学"); + } + + @FXML + public void onMiddleSchoolSelected() { + showQuestionCountDialog("初中"); + } + + @FXML + public void onHighSchoolSelected() { + showQuestionCountDialog("高中"); + } + + @FXML + public void onBackToLogin() { + try { + Main.showLogin(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @FXML + public void onBackToDashboard() { + try { + Main.showDashboard(email); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void showQuestionCountDialog(String gradeLevel) { + TextInputDialog dialog = new TextInputDialog("10"); + dialog.setTitle("题目数量"); + dialog.setHeaderText("选择" + gradeLevel + "题目"); + dialog.setContentText("请输入题目数量(1-100):"); + + dialog.showAndWait().ifPresent(countStr -> { + try { + int questionCount = Integer.parseInt(countStr); + if (questionCount < 1 || questionCount > 100) { + showAlert("题目数量必须在1-100之间"); + return; + } + + boolean success = examService.generateExam(gradeLevel, questionCount, email); + if (success) { + // 修复:添加异常处理 + try { + Main.showExamPage(email); + } catch (Exception e) { + e.printStackTrace(); + showAlert("无法进入考试页面"); + } + } else { + showAlert("生成试卷失败,请重试"); + } + + } catch (NumberFormatException e) { + showAlert("请输入有效的数字"); + } + }); + } + + private void showAlert(String message) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("错误"); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } +} \ No newline at end of file -- 2.34.1 From bd77d69c6a4f9df1934da0ba0a7904aff4f3e6a9 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:44:44 +0800 Subject: [PATCH 25/44] ADD file via upload --- .../myapp/controller/LoginController.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/LoginController.java diff --git a/src/main/java/com/example/myapp/controller/LoginController.java b/src/main/java/com/example/myapp/controller/LoginController.java new file mode 100644 index 0000000..d38dd8b --- /dev/null +++ b/src/main/java/com/example/myapp/controller/LoginController.java @@ -0,0 +1,121 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.UserService; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +public class LoginController { + @FXML private TextField identifierField; // 修改:改为identifier + @FXML private PasswordField passwordField; + @FXML private Button loginBtn; + @FXML private Button registerBtn; + @FXML private Button forgotPasswordBtn; + @FXML private Label statusLabel; + + private final UserService userService = UserService.getInstance(); + + @FXML + public void onLogin(){ + String identifier = identifierField.getText().trim(); + String pwd = passwordField.getText(); + + if(identifier.isEmpty() || pwd.isEmpty()){ + statusLabel.setText("请输入用户名/邮箱和密码"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if(!userService.userExists(identifier)){ + statusLabel.setText("账号不存在,请先注册"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + // 获取用户邮箱(用于后续操作) + String email = identifier; + if (!identifier.contains("@")) { + // 如果是用户名登录,需要获取对应的邮箱 + var user = userService.getUserByIdentifier(identifier); + if (user != null) { + email = user.getEmail(); + } + } + + if(!userService.isUserRegistered(email)){ + statusLabel.setText("该账号未完成注册,请完成注册流程"); + statusLabel.setStyle("-fx-text-fill: orange;"); + return; + } + + boolean ok = userService.checkPassword(identifier, pwd); + if(ok){ + statusLabel.setText("登录成功"); + statusLabel.setStyle("-fx-text-fill: green;"); + try { + Main.showDashboard(email); + } catch(Exception e){ + e.printStackTrace(); + } + } else { + statusLabel.setText("密码错误"); + statusLabel.setStyle("-fx-text-fill: red;"); + } + } + + @FXML + public void onRegister(){ + try { + Main.showRegister(); + } catch(Exception e){ + e.printStackTrace(); + } + } + + @FXML + public void onForgotPassword() { + String identifier = identifierField.getText().trim(); + + if (identifier.isEmpty()) { + statusLabel.setText("请输入用户名/邮箱"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if (!userService.userExists(identifier)) { + statusLabel.setText("该账号未注册,请先注册"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + // 获取用户邮箱 + String email = identifier; + if (!identifier.contains("@")) { + var user = userService.getUserByIdentifier(identifier); + if (user != null) { + email = user.getEmail(); + } + } + + if (!userService.isUserRegistered(email)) { + statusLabel.setText("该账号未完成注册,请先完成注册"); + statusLabel.setStyle("-fx-text-fill: orange;"); + return; + } + + try { + Main.showForgotPassword(email); + } catch (Exception e) { + e.printStackTrace(); + statusLabel.setText("系统错误: " + e.getMessage()); + statusLabel.setStyle("-fx-text-fill: red;"); + } + } + + @FXML + public void onClear() { + identifierField.clear(); + passwordField.clear(); + statusLabel.setText(""); + } +} \ No newline at end of file -- 2.34.1 From 37047295fa9ada51250ab8359a64fb744aa92c24 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:44:52 +0800 Subject: [PATCH 26/44] ADD file via upload --- .../myapp/controller/RegisterController.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/RegisterController.java diff --git a/src/main/java/com/example/myapp/controller/RegisterController.java b/src/main/java/com/example/myapp/controller/RegisterController.java new file mode 100644 index 0000000..ed34d6f --- /dev/null +++ b/src/main/java/com/example/myapp/controller/RegisterController.java @@ -0,0 +1,142 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.EmailService; +import com.example.myapp.service.UserService; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +import java.util.Random; + +public class RegisterController { + @FXML private TextField emailField; + @FXML private Button sendCodeBtn; + @FXML private TextField codeField; + @FXML private Button verifyBtn; + @FXML private Label statusLabel; + + private final UserService userService = UserService.getInstance(); + private EmailService emailService; + + public RegisterController(){ + try { + emailService = new EmailService(); + } catch(Exception e){ + e.printStackTrace(); + } + } + + @FXML + public void onSendCode(){ + String email = emailField.getText().trim(); + + // 验证邮箱格式 + if(email.isEmpty()){ + statusLabel.setText("请输入邮箱地址"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if(!isValidEmail(email)){ + statusLabel.setText("请输入有效的邮箱地址"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + // 检查邮箱是否已被注册 + if(userService.emailExists(email)){ + // 检查用户是否已完成注册(设置了密码) + if(userService.isUserRegistered(email)){ + statusLabel.setText("该邮箱已被注册,请直接登录"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } else { + // 用户已存在但未完成注册,可以重新发送验证码 + statusLabel.setText("该邮箱正在注册流程中,重新发送验证码"); + statusLabel.setStyle("-fx-text-fill: orange;"); + } + } + + String code = generateCode(); + userService.createPendingUser(email, code); + try{ + emailService.sendRegistrationCode(email, code); + statusLabel.setText("注册码已发送到您的邮箱,请查收"); + statusLabel.setStyle("-fx-text-fill: green;"); + + // 禁用发送按钮一段时间,防止重复发送 + disableSendButtonTemporarily(); + + }catch(Exception e){ + e.printStackTrace(); + statusLabel.setText("发送失败: " + e.getMessage()); + statusLabel.setStyle("-fx-text-fill: red;"); + } + } + + private String generateCode(){ + Random r = new Random(); + int v = 100000 + r.nextInt(900000); + return String.valueOf(v); + } + + @FXML + public void onVerify(){ + String email = emailField.getText().trim(); + String code = codeField.getText().trim(); + + if(email.isEmpty() || code.isEmpty()){ + statusLabel.setText("请输入邮箱和验证码"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if(userService.verifyCode(email, code)){ + statusLabel.setText("验证通过,请设置密码"); + statusLabel.setStyle("-fx-text-fill: green;"); + try { + Main.showSetPassword(email); + } catch(Exception e){ + e.printStackTrace(); + } + } else { + statusLabel.setText("注册码错误或已过期"); + statusLabel.setStyle("-fx-text-fill: red;"); + } + } + @FXML + public void onClear() { + emailField.clear(); + codeField.clear(); + statusLabel.setText(""); + sendCodeBtn.setDisable(false); + } + @FXML + public void onBackToLogin() { + try { + Main.showLogin(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private boolean isValidEmail(String email) { + String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; + return email.matches(emailRegex); + } + + private void disableSendButtonTemporarily() { + sendCodeBtn.setDisable(true); + new java.util.Timer().schedule( + new java.util.TimerTask() { + @Override + public void run() { + javafx.application.Platform.runLater(() -> { + sendCodeBtn.setDisable(false); + }); + } + }, + 60000 // 60秒后才能重新发送 + ); + } +} \ No newline at end of file -- 2.34.1 From 576bf8c29e11e380e59148d9b49f2995c9794ff5 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:45:02 +0800 Subject: [PATCH 27/44] ADD file via upload --- .../myapp/controller/ScoreController.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/ScoreController.java diff --git a/src/main/java/com/example/myapp/controller/ScoreController.java b/src/main/java/com/example/myapp/controller/ScoreController.java new file mode 100644 index 0000000..f23f3a9 --- /dev/null +++ b/src/main/java/com/example/myapp/controller/ScoreController.java @@ -0,0 +1,65 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.ExamService; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +public class ScoreController { + @FXML private Label scoreLabel; + @FXML private Label commentLabel; + @FXML private Button continueBtn; + @FXML private Button exitBtn; + + private String email; + private int score; + private ExamService examService = ExamService.getInstance(); + + public void initScore(String email, int score) { + this.email = email; + this.score = score; + + scoreLabel.setText("得分: " + score + " 分"); + + // 根据分数给出评价 + if (score >= 90) { + commentLabel.setText("优秀!表现非常出色!"); + commentLabel.setStyle("-fx-text-fill: #27ae60;"); + } else if (score >= 80) { + commentLabel.setText("良好!继续努力!"); + commentLabel.setStyle("-fx-text-fill: #2980b9;"); + } else if (score >= 60) { + commentLabel.setText("及格!还有提升空间!"); + commentLabel.setStyle("-fx-text-fill: #f39c12;"); + } else { + commentLabel.setText("不及格!需要加强学习!"); + commentLabel.setStyle("-fx-text-fill: #e74c3c;"); + } + } + + @FXML + public void onContinue() { + // 清除当前考试数据 + examService.clearExam(email); + + // 返回选择界面 + try { + Main.showGradeSelection(email); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @FXML + public void onExit() { + // 清除考试数据 + examService.clearExam(email); + + // 返回登录界面 + try { + Main.showLogin(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file -- 2.34.1 From 51a467b08d03e76acb62fbb3511faf782bd41970 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:45:38 +0800 Subject: [PATCH 28/44] ADD file via upload --- .../controller/SetPasswordController.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/main/java/com/example/myapp/controller/SetPasswordController.java diff --git a/src/main/java/com/example/myapp/controller/SetPasswordController.java b/src/main/java/com/example/myapp/controller/SetPasswordController.java new file mode 100644 index 0000000..31b6870 --- /dev/null +++ b/src/main/java/com/example/myapp/controller/SetPasswordController.java @@ -0,0 +1,83 @@ +package com.example.myapp.controller; + +import com.example.myapp.Main; +import com.example.myapp.service.UserService; +import com.example.myapp.util.PasswordUtil; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +public class SetPasswordController { + @FXML private Label emailLabel; + @FXML private TextField usernameField; + @FXML private PasswordField pwdField; + @FXML private PasswordField pwdConfirmField; + @FXML private Button setBtn; + @FXML private Label statusLabel; + + private String email; + private final UserService userService = UserService.getInstance(); + + public void setEmail(String email){ + this.email = email; + emailLabel.setText("设置账号信息 - " + email); + } + + @FXML + public void onSetPassword(){ + String username = usernameField.getText().trim(); + String p1 = pwdField.getText(); + String p2 = pwdConfirmField.getText(); + + // 验证用户名 + if (username.isEmpty()) { + statusLabel.setText("请输入用户名"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if (username.length() < 2 || username.length() > 20) { + statusLabel.setText("用户名长度应为2-20个字符"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + // 检查用户名是否已存在 + if (userService.usernameExists(username)) { + statusLabel.setText("用户名已存在,请选择其他用户名"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + // 验证密码 + if(!p1.equals(p2)){ + statusLabel.setText("两次密码不一致"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + if(!PasswordUtil.validatePasswordRules(p1)){ + statusLabel.setText("密码不符合规则(6-10位,须含大写、小写和数字)"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + // 设置用户名和密码 + boolean usernameSet = userService.setUsername(email, username); + if (!usernameSet) { + statusLabel.setText("设置用户名失败"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + String hash = PasswordUtil.hash(p1); + userService.setPassword(email, hash); + statusLabel.setText("注册成功!即将进入用户中心"); + statusLabel.setStyle("-fx-text-fill: green;"); + + try { + Main.showDashboard(email); + } catch(Exception e){ + e.printStackTrace(); + } + } +} \ No newline at end of file -- 2.34.1 From 6084431bc572fa8ca1ac0fb866399ff018614047 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:46:18 +0800 Subject: [PATCH 29/44] Delete 'README.md' --- README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 94b350a..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# partner_program - -- 2.34.1 From 6348a65450eea5aa1e3c1db8213966bb84d68f13 Mon Sep 17 00:00:00 2001 From: hnu202326010302 <1677625723@qq.com> Date: Sun, 12 Oct 2025 15:57:58 +0800 Subject: [PATCH 30/44] ADD file via upload --- doc/数学在线考试系统说明文档.md | 152 ++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 doc/数学在线考试系统说明文档.md diff --git a/doc/数学在线考试系统说明文档.md b/doc/数学在线考试系统说明文档.md new file mode 100644 index 0000000..2fc37c0 --- /dev/null +++ b/doc/数学在线考试系统说明文档.md @@ -0,0 +1,152 @@ +# 数学在线考试系统 + +## 项目简介 + +一个基于JavaFX开发的数学在线考试系统,支持用户注册、登录、密码管理和多学段数学题目生成。系统提供小学、初中、高中三个学段的数学题目,支持自动组卷、在线答题和自动评分功能。 + +## 功能特性 + +### 用户管理 + +- 用户注册(邮箱验证) +- 用户登录(支持邮箱/用户名登录) +- 密码重置 +- 密码修改 +- 用户信息持久化存储 + +### 考试功能 + +- **多学段支持**:小学、初中、高中 +- **智能题目生成**:根据学段生成相应难度题目 +- **题目去重**:同一试卷中不会出现重复题目 +- **实时答题**:在线答题界面,支持题目导航 +- **自动评分**:答题完成后自动计算得分 +- **进度显示**:答题进度可视化 + +### 题目特点 + +- **小学**:基础四则运算,确保结果为正数 +- **初中**:包含平方、开方等运算 +- **高中**:包含三角函数等高级运算 +- **选项生成**:智能生成干扰项,避免重复 + +## 技术架构 + +### 后端技术栈 + +- **JavaFX** - 桌面应用框架 +- **Jackson** - JSON数据序列化 +- **Jakarta Mail** - 邮件发送服务 +- **BCrypt** - 密码加密 +- **JUnit** - 单元测试 + +### 项目结构 + +``` +src/main/java/com/example/myapp/ +├── controller/ # 控制器层 +│ ├── LoginController.java +│ ├── RegisterController.java +│ ├── DashboardController.java +│ ├── ExamController.java +│ └── ... +├── model/ # 数据模型 +│ ├── User.java +│ ├── Exam.java +│ ├── Question.java +│ └── Expression.java +├── service/ # 业务服务层 +│ ├── UserService.java +│ ├── ExamService.java +│ ├── EmailService.java +│ └── MathQuestionGenerator.java +├── util/ # 工具类 +│ └── PasswordUtil.java +└── Main.java # 应用入口 +``` + + + +## 核心功能模块 + +### 1. 用户认证系统 + +java + +复制下载 + +``` +// 密码规则:6-10位,必须包含大小写字母和数字 +public static boolean validatePasswordRules(String pwd) { + Pattern validPattern = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$"); + return validPattern.matcher(pwd).matches(); +} +``` + + + +### 2. 题目生成系统 + +采用工厂模式 + 抽象类实现多学段题目生成: + +java + +复制下载 + +``` +// 题目生成工厂 +public class QueSetingFactory { + public QuestionSeting getQueSeting(String type) { + switch (type) { + case "小学": return new PrimaryQueSeting(); + case "初中": return new MiddleQueSeting(); + case "高中": return new HighQueSeting(); + default: return null; + } + } +} +``` + + + +### 3. 数据存储系统 + +使用JSON文件进行数据持久化: + +- `data/users.json` - 用户数据 +- `data/registration_codes.json` - 注册验证码 +- `data/reset_password_codes.json` - 密码重置码 + +## 安装与运行 + +### 环境要求 + +- Java 17 或更高版本 +- Maven 3.6+ +- 可用的SMTP服务器(用于邮件发送) + + + +## 使用指南 + +### 用户注册流程 + +1. 输入邮箱地址 +2. 接收验证码邮件 +3. 输入验证码验证 +4. 设置用户名和密码 +5. 注册完成,进入用户中心 + +### 考试流程 + +1. 登录系统 +2. 选择学段(小学/初中/高中) +3. 输入题目数量(1-100题) +4. 进入答题界面 +5. 逐题作答,支持前后导航 +6. 提交试卷,查看成绩 + +### 密码管理 + +- **修改密码**:登录后可在用户中心修改 +- **重置密码**:登录页点击"忘记密码",通过邮箱验证重置 \ No newline at end of file -- 2.34.1 From 00c65406219b7d9f4711948f3c36d9a374be11f5 Mon Sep 17 00:00:00 2001 From: hnu202304060319 <3040369688@qq.com> Date: Sun, 12 Oct 2025 17:05:18 +0800 Subject: [PATCH 31/44] ADD file via upload --- src/main/resources/fxml/change_password.fxml | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/resources/fxml/change_password.fxml diff --git a/src/main/resources/fxml/change_password.fxml b/src/main/resources/fxml/change_password.fxml new file mode 100644 index 0000000..db6fd1c --- /dev/null +++ b/src/main/resources/fxml/change_password.fxml @@ -0,0 +1,45 @@ + + + + + + + +