From fa964125bca0cc1fb2e38d88e76c0d958194bccc Mon Sep 17 00:00:00 2001 From: bx <2936213174@qq.com> Date: Fri, 3 Oct 2025 14:16:15 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 38 ++++++ .idea/.gitignore | 8 ++ .idea/encodings.xml | 7 ++ .idea/misc.xml | 14 +++ .idea/vcs.xml | 6 + pom.xml | 84 +++++++++++++ src/main/java/com/bx/MathQuiz/Main.java | 4 + .../java/com/bx/model/ChoiceQuestion.java | 117 ++++++++++++++++++ src/main/java/com/bx/model/Grade.java | 49 ++++++++ src/main/java/com/bx/model/User.java | 95 ++++++++++++++ src/main/java/com/bx/util/RandomUtils.java | 83 +++++++++++++ 11 files changed, 505 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/bx/MathQuiz/Main.java create mode 100644 src/main/java/com/bx/model/ChoiceQuestion.java create mode 100644 src/main/java/com/bx/model/Grade.java create mode 100644 src/main/java/com/bx/model/User.java create mode 100644 src/main/java/com/bx/util/RandomUtils.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fdc35ea --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..dc0b01f --- /dev/null +++ b/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + com.mathquiz + MathQuizApp + 1.0.0 + jar + + Math Quiz Application + 小初高数学学习软件 - Swing版本 + + + UTF-8 + 21 + 21 + + + + + + com.google.code.gson + gson + 2.10.1 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + UTF-8 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + com.mathquiz.Main + true + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + com.mathquiz.Main + + + MathQuizApp + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/bx/MathQuiz/Main.java b/src/main/java/com/bx/MathQuiz/Main.java new file mode 100644 index 0000000..17c5761 --- /dev/null +++ b/src/main/java/com/bx/MathQuiz/Main.java @@ -0,0 +1,4 @@ +package com.bx.MathQuiz; + +public class Main { +} diff --git a/src/main/java/com/bx/model/ChoiceQuestion.java b/src/main/java/com/bx/model/ChoiceQuestion.java new file mode 100644 index 0000000..bc52c92 --- /dev/null +++ b/src/main/java/com/bx/model/ChoiceQuestion.java @@ -0,0 +1,117 @@ +package com.bx.model; + +import java.util.List; + +/** + * 选择题数据模型 + * 包含题目内容、正确答案、四个选项 + */ +public class ChoiceQuestion { + private String questionText; // 题目文本,如 "3 + 5" + private double correctAnswer; // 正确答案 + private List options; // 四个选项(包含正确答案) + private Grade grade; // 所属学段 + + private int correctOptionIndex; // 正确答案在选项中的索引(0-3) + + /** + * 无参构造函数 + */ + public ChoiceQuestion() { + } + + /** + * 完整构造函数 + */ + public ChoiceQuestion(String questionText, double correctAnswer, List options, Grade grade) { + this.questionText = questionText; + this.correctAnswer = correctAnswer; + this.options = options; + this.grade = grade; + + // 自动找到正确答案的索引 + this.correctOptionIndex = findCorrectOptionIndex(); + } + + /** + * 查找正确答案在选项中的位置 + */ + private int findCorrectOptionIndex() { + for (int i = 0; i < options.size(); i++) { + if (Math.abs(options.get(i) - correctAnswer) < 0.01) { + return i; + } + } + return -1; // 理论上不会发生 + } + + /** + * 检查用户选择的选项是否正确 + * @param optionIndex 用户选择的选项索引(0-3) + * @return true表示正确 + */ + public boolean checkAnswer(int optionIndex) { + return optionIndex == correctOptionIndex; + } + + /** + * 检查用户输入的答案值是否正确 + * @param answer 用户输入的答案值 + * @return true表示正确 + */ + public boolean checkAnswerValue(double answer) { + return Math.abs(answer - correctAnswer) < 0.01; + } + + // ========== Getter和Setter方法 ========== + + public String getQuestionText() { + return questionText; + } + + public void setQuestionText(String questionText) { + this.questionText = questionText; + } + + public double getCorrectAnswer() { + return correctAnswer; + } + + public void setCorrectAnswer(double correctAnswer) { + this.correctAnswer = correctAnswer; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + this.correctOptionIndex = findCorrectOptionIndex(); + } + + public Grade getGrade() { + return grade; + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public int getCorrectOptionIndex() { + return correctOptionIndex; + } + + public void setCorrectOptionIndex(int correctOptionIndex) { + this.correctOptionIndex = correctOptionIndex; + } + + @Override + public String toString() { + return "ChoiceQuestion{" + + "questionText='" + questionText + '\'' + + ", correctAnswer=" + correctAnswer + + ", grade=" + grade + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/bx/model/Grade.java b/src/main/java/com/bx/model/Grade.java new file mode 100644 index 0000000..8f878b2 --- /dev/null +++ b/src/main/java/com/bx/model/Grade.java @@ -0,0 +1,49 @@ +package com.bx.model; + +/** + * 学段枚举类 + * 定义三个学段:小学、初中、高中 + */ +public enum Grade { + ELEMENTARY("小学"), + MIDDLE("初中"), + HIGH("高中"); + + private final String displayName; + + Grade(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + //根据显示名称查找对应的枚举值 + public static Grade fromDisplayName(String displayName) { + for (Grade grade : values()) { + if (grade.displayName.equals(displayName)) { + return grade; + } + } + throw new IllegalArgumentException("未知学段: " + displayName); + } + + /** + * 获取所有学段的显示名称数组 + * @return ["小学", "初中", "高中"] + */ + public static String[] getDisplayNames() { + Grade[] grades = values(); + String[] names = new String[grades.length]; + for (int i = 0; i < grades.length; i++) { + names[i] = grades[i].displayName; + } + return names; + } + + @Override + public String toString() { + return displayName; + } +} \ No newline at end of file diff --git a/src/main/java/com/bx/model/User.java b/src/main/java/com/bx/model/User.java new file mode 100644 index 0000000..1557f2f --- /dev/null +++ b/src/main/java/com/bx/model/User.java @@ -0,0 +1,95 @@ +package com.bx.model; + +public class User { + private String username; // 用户名,格式:小学-张三 + private String password; // 密码(加密后) + private Grade grade; // 学段 + private String email; // 邮箱 + private String registrationCode; // 注册码(明文保存,用于用户查看) + private long createdTime; // 创建时间戳 + private long lastLoginTime; // 最后登录时间戳 + + /** + * 无参构造函数(Gson反序列化需要) + */ + public User() { + } + + /** + * 完整构造函数 + */ + public User(String username, String password, Grade grade, String email) { + this.username = username; + this.password = password; + this.grade = grade; + this.email = email; + this.createdTime = System.currentTimeMillis(); + } + + // ========== Getter和Setter方法 ========== + + 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 Grade getGrade() { + return grade; + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRegistrationCode() { + return registrationCode; + } + + public void setRegistrationCode(String registrationCode) { + this.registrationCode = registrationCode; + } + + public long getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(long createdTime) { + this.createdTime = createdTime; + } + + public long getLastLoginTime() { + return lastLoginTime; + } + + public void setLastLoginTime(long lastLoginTime) { + this.lastLoginTime = lastLoginTime; + } + + @Override + public String toString() { + return "User{" + + "username='" + username + '\'' + + ", grade=" + grade + + ", email='" + email + '\'' + + '}'; + } +} diff --git a/src/main/java/com/bx/util/RandomUtils.java b/src/main/java/com/bx/util/RandomUtils.java new file mode 100644 index 0000000..8735980 --- /dev/null +++ b/src/main/java/com/bx/util/RandomUtils.java @@ -0,0 +1,83 @@ +package com.bx.util; + +import java.util.Collections; +import java.util.List; +import java.util.Random; + +//各种随机数生成 +public class RandomUtils { + private static final Random random = new Random(); + + + //生成[min, max]范围内的随机整数(包含边界) + public static int nextInt(int min, int max) { + if (min > max) { + throw new IllegalArgumentException("min不能大于max"); + } + return min + random.nextInt(max - min + 1); + } + + + //从数组中随机选择一个元素(模板类) + public static T randomChoice(T[] array) { + if (array == null || array.length == 0) { + throw new IllegalArgumentException("数组不能为空"); + } + return array[random.nextInt(array.length)]; + } + + /** + * 从列表中随机选择一个元素 + * @param list 列表 + * @return 随机选中的元素 + */ + public static T randomChoice(List list) { + if (list == null || list.isEmpty()) { + throw new IllegalArgumentException("列表不能为空"); + } + return list.get(random.nextInt(list.size())); + } + + /** + * 打乱列表顺序 + * @param list 要打乱的列表 + */ + public static void shuffle(List list) { + Collections.shuffle(list, random); + } + + /** + * 生成指定范围内的随机双精度浮点数 + * @param min 最小值 + * @param max 最大值 + * @return 随机浮点数 + */ + public static double nextDouble(double min, double max) { + if (min > max) { + throw new IllegalArgumentException("min不能大于max"); + } + return min + (max - min) * random.nextDouble(); + } + + /** + * 生成随机布尔值 + * @return true或false + */ + public static boolean nextBoolean() { + return random.nextBoolean(); + } + + /** + * 按概率返回true + * @param probability 概率值(0.0 - 1.0) + * @return true或false + * + * 示例:probability(0.7) 有70%概率返回true + */ + public static boolean probability(double probability) { + if (probability < 0.0 || probability > 1.0) { + throw new IllegalArgumentException("概率必须在0.0-1.0之间"); + } + return random.nextDouble() < probability; + } +} \ No newline at end of file -- 2.34.1 From 06a4612ad6066a0a457209013cf4b0d2a3f8661b Mon Sep 17 00:00:00 2001 From: bx <2936213174@qq.com> Date: Fri, 3 Oct 2025 14:54:13 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/MathQuiz/Main.java | 4 + src/main/java/com/bx/MathQuiz/Main.java | 4 - .../com/{bx => }/model/ChoiceQuestion.java | 2 +- src/main/java/com/{bx => }/model/Grade.java | 2 +- src/main/java/com/{bx => }/model/User.java | 2 +- src/main/java/com/util/EmailUtil.java | 109 +++++++++++++++++ src/main/java/com/util/FileUtils.java | 111 ++++++++++++++++++ src/main/java/com/util/PasswordValidator.java | 102 ++++++++++++++++ .../java/com/{bx => }/util/RandomUtils.java | 32 ++--- src/test/java/TestMain.java | 2 + 10 files changed, 340 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/MathQuiz/Main.java delete mode 100644 src/main/java/com/bx/MathQuiz/Main.java rename src/main/java/com/{bx => }/model/ChoiceQuestion.java (99%) rename src/main/java/com/{bx => }/model/Grade.java (98%) rename src/main/java/com/{bx => }/model/User.java (99%) create mode 100644 src/main/java/com/util/EmailUtil.java create mode 100644 src/main/java/com/util/FileUtils.java create mode 100644 src/main/java/com/util/PasswordValidator.java rename src/main/java/com/{bx => }/util/RandomUtils.java (75%) create mode 100644 src/test/java/TestMain.java diff --git a/src/main/java/com/MathQuiz/Main.java b/src/main/java/com/MathQuiz/Main.java new file mode 100644 index 0000000..49ac2f4 --- /dev/null +++ b/src/main/java/com/MathQuiz/Main.java @@ -0,0 +1,4 @@ +package com.MathQuiz; + +public class Main { +} diff --git a/src/main/java/com/bx/MathQuiz/Main.java b/src/main/java/com/bx/MathQuiz/Main.java deleted file mode 100644 index 17c5761..0000000 --- a/src/main/java/com/bx/MathQuiz/Main.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.bx.MathQuiz; - -public class Main { -} diff --git a/src/main/java/com/bx/model/ChoiceQuestion.java b/src/main/java/com/model/ChoiceQuestion.java similarity index 99% rename from src/main/java/com/bx/model/ChoiceQuestion.java rename to src/main/java/com/model/ChoiceQuestion.java index bc52c92..b71c59a 100644 --- a/src/main/java/com/bx/model/ChoiceQuestion.java +++ b/src/main/java/com/model/ChoiceQuestion.java @@ -1,4 +1,4 @@ -package com.bx.model; +package com.model; import java.util.List; diff --git a/src/main/java/com/bx/model/Grade.java b/src/main/java/com/model/Grade.java similarity index 98% rename from src/main/java/com/bx/model/Grade.java rename to src/main/java/com/model/Grade.java index 8f878b2..7de066d 100644 --- a/src/main/java/com/bx/model/Grade.java +++ b/src/main/java/com/model/Grade.java @@ -1,4 +1,4 @@ -package com.bx.model; +package com.model; /** * 学段枚举类 diff --git a/src/main/java/com/bx/model/User.java b/src/main/java/com/model/User.java similarity index 99% rename from src/main/java/com/bx/model/User.java rename to src/main/java/com/model/User.java index 1557f2f..eadfd17 100644 --- a/src/main/java/com/bx/model/User.java +++ b/src/main/java/com/model/User.java @@ -1,4 +1,4 @@ -package com.bx.model; +package com.model; public class User { private String username; // 用户名,格式:小学-张三 diff --git a/src/main/java/com/util/EmailUtil.java b/src/main/java/com/util/EmailUtil.java new file mode 100644 index 0000000..ff915ac --- /dev/null +++ b/src/main/java/com/util/EmailUtil.java @@ -0,0 +1,109 @@ +package com.util; + +/** + * 邮件工具类 + * 用于发送注册码邮件(可选功能) + * 注意:当前项目需求中,注册码是直接显示在界面上的, + * 不需要发送邮件,所以这个类暂时保留为空实现。 + * 如果将来需要发送邮件,可以使用JavaMail库实现。 + */ +public class EmailUtil { + + /** + * 发送注册码邮件(预留接口) + * @param toEmail 收件人邮箱 + * @param registrationCode 注册码 + * @return true表示发送成功 + */ + public static boolean sendRegistrationCode(String toEmail, String registrationCode) { + // TODO: 暂不实现邮件发送功能 + // 原因:项目需求是直接在界面显示注册码,不需要发邮件 + + System.out.println("【模拟】发送注册码邮件"); + System.out.println("收件人: " + toEmail); + System.out.println("注册码: " + registrationCode); + + return true; + } + + /** + * 发送密码重置邮件(预留接口) + * @param toEmail 收件人邮箱 + * @param newPassword 新密码 + * @return true表示发送成功 + */ + public static boolean sendPasswordReset(String toEmail, String newPassword) { + // TODO: 将来如果需要"找回密码"功能,可以在这里实现 + + System.out.println("【模拟】发送密码重置邮件"); + System.out.println("收件人: " + toEmail); + System.out.println("新密码: " + newPassword); + + return true; + } + + /** + * 验证邮箱格式 + * @param email 邮箱地址 + * @return true表示格式正确 + */ + 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); + } +} + +/* + * 如果将来需要真正实现邮件发送,可以参考以下代码: + * + * 1. 在pom.xml添加依赖: + * + * javax.mail + * javax.mail-api + * 1.6.2 + * + * + * com.sun.mail + * javax.mail + * 1.6.2 + * + * + * 2. 实现代码示例: + * + * import javax.mail.*; + * import javax.mail.internet.*; + * import java.util.Properties; + * + * public static boolean sendEmail(String toEmail, String subject, String content) { + * try { + * Properties props = new Properties(); + * props.put("mail.smtp.auth", "true"); + * props.put("mail.smtp.starttls.enable", "true"); + * props.put("mail.smtp.host", "smtp.qq.com"); + * props.put("mail.smtp.port", "587"); + * + * Session session = Session.getInstance(props, new Authenticator() { + * protected PasswordAuthentication getPasswordAuthentication() { + * return new PasswordAuthentication("your-email@qq.com", "your-password"); + * } + * }); + * + * Message message = new MimeMessage(session); + * message.setFrom(new InternetAddress("your-email@qq.com")); + * message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); + * message.setSubject(subject); + * message.setText(content); + * + * Transport.send(message); + * return true; + * } catch (Exception e) { + * e.printStackTrace(); + * return false; + * } + * } + */ \ No newline at end of file diff --git a/src/main/java/com/util/FileUtils.java b/src/main/java/com/util/FileUtils.java new file mode 100644 index 0000000..d4daf9e --- /dev/null +++ b/src/main/java/com/util/FileUtils.java @@ -0,0 +1,111 @@ +package com.util; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; + +/** + * 文件操作工具类 + * 提供文件读写、目录创建等常用操作 + */ +public class FileUtils { + + /** + * 读取文件内容为字符串 + * @param filePath 文件路径 + * @return 文件内容 + * @throws IOException 读取失败时抛出 + */ + public static String readFileToString(String filePath) throws IOException { + return Files.readString(Paths.get(filePath), StandardCharsets.UTF_8); + } + + /** + * 写入字符串到文件 + * @param filePath 文件路径 + * @param content 要写入的内容 + * @throws IOException 写入失败时抛出 + */ + public static void writeStringToFile(String filePath, String content) throws IOException { + Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8); + } + + /** + * 创建目录(如果不存在) + * @param dirPath 目录路径 + * @throws IOException 创建失败时抛出 + */ + public static void createDirectoryIfNotExists(String dirPath) throws IOException { + Path path = Paths.get(dirPath); + if (!Files.exists(path)) { + Files.createDirectories(path); + } + } + + /** + * 检查文件是否存在 + * @param filePath 文件路径 + * @return true表示存在 + */ + public static boolean exists(String filePath) { + return Files.exists(Paths.get(filePath)); + } + + /** + * 删除文件 + * @param filePath 文件路径 + * @return true表示删除成功 + */ + public static boolean deleteFile(String filePath) { + try { + return Files.deleteIfExists(Paths.get(filePath)); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 获取目录下所有文件 + * @param dirPath 目录路径 + * @return 文件数组 + */ + public static File[] listFiles(String dirPath) { + File dir = new File(dirPath); + if (dir.exists() && dir.isDirectory()) { + return dir.listFiles(); + } + return new File[0]; + } + + /** + * 追加内容到文件末尾 + * @param filePath 文件路径 + * @param content 要追加的内容 + * @throws IOException 追加失败时抛出 + */ + public static void appendToFile(String filePath, String content) throws IOException { + Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8, + StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } + + /** + * 复制文件 + * @param sourcePath 源文件路径 + * @param targetPath 目标文件路径 + * @throws IOException 复制失败时抛出 + */ + public static void copyFile(String sourcePath, String targetPath) throws IOException { + Files.copy(Paths.get(sourcePath), Paths.get(targetPath), StandardCopyOption.REPLACE_EXISTING); + } + + /** + * 获取文件大小(字节) + * @param filePath 文件路径 + * @return 文件大小 + * @throws IOException 获取失败时抛出 + */ + public static long getFileSize(String filePath) throws IOException { + return Files.size(Paths.get(filePath)); + } +} \ No newline at end of file diff --git a/src/main/java/com/util/PasswordValidator.java b/src/main/java/com/util/PasswordValidator.java new file mode 100644 index 0000000..40a26b5 --- /dev/null +++ b/src/main/java/com/util/PasswordValidator.java @@ -0,0 +1,102 @@ +package com.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * 密码验证和加密工具类 + * 提供密码格式验证、加密、匹配等功能 + */ +public class PasswordValidator { + + /** + * 验证密码格式:6-10位,包含字母和数字 + * @param password 待验证的密码 + * @return true表示格式正确 + */ + public static boolean isValid(String password) { + if (password == null || password.length() < 6 || password.length() > 10) { + return false; + } + + boolean hasLetter = password.matches("^(?=.*[a-z])(?=.*[A-Z]).*$"); + boolean hasDigit = password.matches(".*\\d.*"); + + return hasLetter && hasDigit; + } + + /** + * 使用SHA-256加密密码 + * @param password 明文密码 + * @return 加密后的密码(16进制字符串) + */ + public static String encrypt(String password) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(password.getBytes()); + StringBuilder hexString = new StringBuilder(); + + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256算法不可用", e); + } + } + + /** + * 验证密码是否匹配 + * @param plainPassword 明文密码 + * @param encryptedPassword 加密后的密码 + * @return true表示匹配 + */ + public static boolean matches(String plainPassword, String encryptedPassword) { + return encrypt(plainPassword).equals(encryptedPassword); + } + + /** + * 生成6-10位随机注册码(包含字母和数字) + * @return 注册码 + */ + public static String generateRegistrationCode() { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + int length = 6 + (int) (Math.random() * 5); // 6-10位 + + StringBuilder code = new StringBuilder(); + + // 确保至少有一个字母 + code.append(chars.charAt((int) (Math.random() * 52))); + // 确保至少有一个数字 + code.append(chars.charAt(52 + (int) (Math.random() * 10))); + + // 填充剩余字符 + for (int i = 2; i < length; i++) { + code.append(chars.charAt((int) (Math.random() * chars.length()))); + } + + // 打乱字符顺序 + return shuffleString(code.toString()); + } + + /** + * 打乱字符串 + * @param str 原字符串 + * @return 打乱后的字符串 + */ + private static String shuffleString(String str) { + char[] chars = str.toCharArray(); + for (int i = chars.length - 1; i > 0; i--) { + int j = (int) (Math.random() * (i + 1)); + char temp = chars[i]; + chars[i] = chars[j]; + chars[j] = temp; + } + return new String(chars); + } +} \ No newline at end of file diff --git a/src/main/java/com/bx/util/RandomUtils.java b/src/main/java/com/util/RandomUtils.java similarity index 75% rename from src/main/java/com/bx/util/RandomUtils.java rename to src/main/java/com/util/RandomUtils.java index 8735980..f06a7ed 100644 --- a/src/main/java/com/bx/util/RandomUtils.java +++ b/src/main/java/com/util/RandomUtils.java @@ -1,4 +1,4 @@ -package com.bx.util; +package com.util; import java.util.Collections; import java.util.List; @@ -26,11 +26,8 @@ public class RandomUtils { return array[random.nextInt(array.length)]; } - /** - * 从列表中随机选择一个元素 - * @param list 列表 - * @return 随机选中的元素 - */ + + //从列表中随机选择一个元素 public static T randomChoice(List list) { if (list == null || list.isEmpty()) { throw new IllegalArgumentException("列表不能为空"); @@ -46,12 +43,8 @@ public class RandomUtils { Collections.shuffle(list, random); } - /** - * 生成指定范围内的随机双精度浮点数 - * @param min 最小值 - * @param max 最大值 - * @return 随机浮点数 - */ + + //生成指定范围内的随机双精度浮点数 public static double nextDouble(double min, double max) { if (min > max) { throw new IllegalArgumentException("min不能大于max"); @@ -59,21 +52,14 @@ public class RandomUtils { return min + (max - min) * random.nextDouble(); } - /** - * 生成随机布尔值 - * @return true或false - */ + + //生成随机布尔值 public static boolean nextBoolean() { return random.nextBoolean(); } - /** - * 按概率返回true - * @param probability 概率值(0.0 - 1.0) - * @return true或false - * - * 示例:probability(0.7) 有70%概率返回true - */ + //按概率返回true(题目生成概率) + //示例:probability(0.7) 有70%概率返回true public static boolean probability(double probability) { if (probability < 0.0 || probability > 1.0) { throw new IllegalArgumentException("概率必须在0.0-1.0之间"); diff --git a/src/test/java/TestMain.java b/src/test/java/TestMain.java new file mode 100644 index 0000000..d4c7da5 --- /dev/null +++ b/src/test/java/TestMain.java @@ -0,0 +1,2 @@ +public class TestMain { +} -- 2.34.1 From c50774c43216d847be71728c1eecf5644be1f838 Mon Sep 17 00:00:00 2001 From: bx <2936213174@qq.com> Date: Fri, 3 Oct 2025 20:06:47 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/uiDesigner.xml | 124 +++++ src/main/java/com/model/ChoiceQuestion.java | 81 +-- src/main/java/com/model/Grade.java | 47 +- src/main/java/com/model/QuizHistory.java | 82 +++ src/main/java/com/model/QuizResult.java | 66 +++ src/main/java/com/model/User.java | 89 +-- src/main/java/com/service/FileIOService.java | 242 +++++++++ src/main/java/com/service/QuizService.java | 432 +++++++++++++++ src/main/java/com/service/UserService.java | 321 +++++++++++ .../QuestionFactoryManager.java | 97 ++++ .../factory/ElementaryQuestionFactory.java | 41 ++ .../factory/HighQuestionFactory.java | 41 ++ .../factory/MiddleQuestionFactory.java | 41 ++ .../factory/QuestionFactory.java | 17 + .../strategy/AbstractQuestionStrategy.java | 138 +++++ .../strategy/QuestionStrategy.java | 12 + .../strategy/elementary/AdditionStrategy.java | 37 ++ .../strategy/elementary/DivisionStrategy.java | 39 ++ .../elementary/MultiplicationStrategy.java | 37 ++ .../elementary/ParenthesesAddStrategy.java | 40 ++ .../ParenthesesMultiplyStrategy.java | 41 ++ .../elementary/SubtractionStrategy.java | 40 ++ .../strategy/high/CosStrategy.java | 57 ++ .../strategy/high/SinStrategy.java | 56 ++ .../strategy/high/TanStrategy.java | 56 ++ .../strategy/high/TrigIdentityStrategy.java | 41 ++ .../middle/MixedSquareSqrtStrategy.java | 40 ++ .../strategy/middle/SqrtAddStrategy.java | 39 ++ .../strategy/middle/SqrtStrategy.java | 39 ++ .../strategy/middle/SquareAddStrategy.java | 37 ++ .../strategy/middle/SquareStrategy.java | 39 ++ src/main/java/com/util/FileUtils.java | 75 ++- src/main/java/com/util/PasswordValidator.java | 261 ++++++++- src/test/java/TestMain.java | 507 +++++++++++++++++- 34 files changed, 3195 insertions(+), 157 deletions(-) create mode 100644 .idea/uiDesigner.xml create mode 100644 src/main/java/com/model/QuizHistory.java create mode 100644 src/main/java/com/model/QuizResult.java create mode 100644 src/main/java/com/service/FileIOService.java create mode 100644 src/main/java/com/service/QuizService.java create mode 100644 src/main/java/com/service/UserService.java create mode 100644 src/main/java/com/service/question_generator/QuestionFactoryManager.java create mode 100644 src/main/java/com/service/question_generator/factory/ElementaryQuestionFactory.java create mode 100644 src/main/java/com/service/question_generator/factory/HighQuestionFactory.java create mode 100644 src/main/java/com/service/question_generator/factory/MiddleQuestionFactory.java create mode 100644 src/main/java/com/service/question_generator/factory/QuestionFactory.java create mode 100644 src/main/java/com/service/question_generator/strategy/AbstractQuestionStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/QuestionStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/elementary/AdditionStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/elementary/DivisionStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/elementary/MultiplicationStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/elementary/ParenthesesAddStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/elementary/ParenthesesMultiplyStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/elementary/SubtractionStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/high/CosStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/high/SinStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/high/TanStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/high/TrigIdentityStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/middle/MixedSquareSqrtStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/middle/SqrtAddStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/middle/SqrtStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/middle/SquareAddStrategy.java create mode 100644 src/main/java/com/service/question_generator/strategy/middle/SquareStrategy.java diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/model/ChoiceQuestion.java b/src/main/java/com/model/ChoiceQuestion.java index b71c59a..897059e 100644 --- a/src/main/java/com/model/ChoiceQuestion.java +++ b/src/main/java/com/model/ChoiceQuestion.java @@ -3,67 +3,34 @@ package com.model; import java.util.List; /** - * 选择题数据模型 - * 包含题目内容、正确答案、四个选项 + * 选择题模型(纯数据) */ public class ChoiceQuestion { - private String questionText; // 题目文本,如 "3 + 5" - private double correctAnswer; // 正确答案 - private List options; // 四个选项(包含正确答案) - private Grade grade; // 所属学段 - private int correctOptionIndex; // 正确答案在选项中的索引(0-3) + private String questionText; // 题目文本 + private Object correctAnswer; // 正确答案 + private List options; // 选项列表 + private Grade grade; // 所属学段 - /** - * 无参构造函数 - */ - public ChoiceQuestion() { - } + // ==================== 构造方法 ==================== - /** - * 完整构造函数 - */ - public ChoiceQuestion(String questionText, double correctAnswer, List options, Grade grade) { + public ChoiceQuestion(String questionText, double correctAnswer, + List options, Grade grade) { this.questionText = questionText; this.correctAnswer = correctAnswer; this.options = options; this.grade = grade; - - // 自动找到正确答案的索引 - this.correctOptionIndex = findCorrectOptionIndex(); - } - - /** - * 查找正确答案在选项中的位置 - */ - private int findCorrectOptionIndex() { - for (int i = 0; i < options.size(); i++) { - if (Math.abs(options.get(i) - correctAnswer) < 0.01) { - return i; - } - } - return -1; // 理论上不会发生 } - /** - * 检查用户选择的选项是否正确 - * @param optionIndex 用户选择的选项索引(0-3) - * @return true表示正确 - */ - public boolean checkAnswer(int optionIndex) { - return optionIndex == correctOptionIndex; - } - - /** - * 检查用户输入的答案值是否正确 - * @param answer 用户输入的答案值 - * @return true表示正确 - */ - public boolean checkAnswerValue(double answer) { - return Math.abs(answer - correctAnswer) < 0.01; + public ChoiceQuestion(String questionText, String correctAnswer, + List options, Grade grade) { + this.questionText = questionText; + this.correctAnswer = correctAnswer; + this.options = options; + this.grade = grade; } - // ========== Getter和Setter方法 ========== + // ==================== Getters & Setters ==================== public String getQuestionText() { return questionText; @@ -73,21 +40,20 @@ public class ChoiceQuestion { this.questionText = questionText; } - public double getCorrectAnswer() { + public Object getCorrectAnswer() { return correctAnswer; } - public void setCorrectAnswer(double correctAnswer) { + public void setCorrectAnswer(Object correctAnswer) { this.correctAnswer = correctAnswer; } - public List getOptions() { + public List getOptions() { return options; } - public void setOptions(List options) { + public void setOptions(List options) { this.options = options; - this.correctOptionIndex = findCorrectOptionIndex(); } public Grade getGrade() { @@ -98,19 +64,12 @@ public class ChoiceQuestion { this.grade = grade; } - public int getCorrectOptionIndex() { - return correctOptionIndex; - } - - public void setCorrectOptionIndex(int correctOptionIndex) { - this.correctOptionIndex = correctOptionIndex; - } - @Override public String toString() { return "ChoiceQuestion{" + "questionText='" + questionText + '\'' + ", correctAnswer=" + correctAnswer + + ", options=" + options + ", grade=" + grade + '}'; } diff --git a/src/main/java/com/model/Grade.java b/src/main/java/com/model/Grade.java index 7de066d..1d2729f 100644 --- a/src/main/java/com/model/Grade.java +++ b/src/main/java/com/model/Grade.java @@ -1,49 +1,10 @@ package com.model; /** - * 学段枚举类 - * 定义三个学段:小学、初中、高中 + * 学段枚举 */ public enum Grade { - ELEMENTARY("小学"), - MIDDLE("初中"), - HIGH("高中"); - - private final String displayName; - - Grade(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return displayName; - } - - //根据显示名称查找对应的枚举值 - public static Grade fromDisplayName(String displayName) { - for (Grade grade : values()) { - if (grade.displayName.equals(displayName)) { - return grade; - } - } - throw new IllegalArgumentException("未知学段: " + displayName); - } - - /** - * 获取所有学段的显示名称数组 - * @return ["小学", "初中", "高中"] - */ - public static String[] getDisplayNames() { - Grade[] grades = values(); - String[] names = new String[grades.length]; - for (int i = 0; i < grades.length; i++) { - names[i] = grades[i].displayName; - } - return names; - } - - @Override - public String toString() { - return displayName; - } + ELEMENTARY, // 小学 + MIDDLE, // 初中 + HIGH // 高中 } \ No newline at end of file diff --git a/src/main/java/com/model/QuizHistory.java b/src/main/java/com/model/QuizHistory.java new file mode 100644 index 0000000..1d60028 --- /dev/null +++ b/src/main/java/com/model/QuizHistory.java @@ -0,0 +1,82 @@ +package com.model; + + +import java.util.Date; +import java.util.List; + +/** + * 答题历史记录模型(纯数据) + */ +public class QuizHistory { + + private String username; // 用户名 + private Date timestamp; // 答题时间 + private List questions; // 题目列表 + private List userAnswers; // 用户答案列表 + private int score; // 得分 + + // ==================== 构造方法 ==================== + + public QuizHistory(String username, Date timestamp, + List questions, + List userAnswers, + int score) { + this.username = username; + this.timestamp = timestamp; + this.questions = questions; + this.userAnswers = userAnswers; + this.score = score; + } + + // ==================== Getters & Setters ==================== + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + public List getUserAnswers() { + return userAnswers; + } + + public void setUserAnswers(List userAnswers) { + this.userAnswers = userAnswers; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + @Override + public String toString() { + return "QuizHistory{" + + "username='" + username + '\'' + + ", timestamp=" + timestamp + + ", questions=" + (questions != null ? questions.size() : 0) + + ", score=" + score + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/model/QuizResult.java b/src/main/java/com/model/QuizResult.java new file mode 100644 index 0000000..7ce4e50 --- /dev/null +++ b/src/main/java/com/model/QuizResult.java @@ -0,0 +1,66 @@ +package com.model; + + +/** + * 答题结果模型(纯数据) + */ +public class QuizResult { + + private int totalQuestions; // 总题数 + private int correctCount; // 正确题数 + private int wrongCount; // 错误题数 + private int score; // 得分 + + // ==================== 构造方法 ==================== + + public QuizResult(int totalQuestions, int correctCount, int wrongCount, int score) { + this.totalQuestions = totalQuestions; + this.correctCount = correctCount; + this.wrongCount = wrongCount; + this.score = score; + } + + // ==================== Getters & Setters ==================== + + public int getTotalQuestions() { + return totalQuestions; + } + + public void setTotalQuestions(int totalQuestions) { + this.totalQuestions = totalQuestions; + } + + public int getCorrectCount() { + return correctCount; + } + + public void setCorrectCount(int correctCount) { + this.correctCount = correctCount; + } + + public int getWrongCount() { + return wrongCount; + } + + public void setWrongCount(int wrongCount) { + this.wrongCount = wrongCount; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + @Override + public String toString() { + return "QuizResult{" + + "totalQuestions=" + totalQuestions + + ", correctCount=" + correctCount + + ", wrongCount=" + wrongCount + + ", score=" + score + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/model/User.java b/src/main/java/com/model/User.java index eadfd17..288ce9b 100644 --- a/src/main/java/com/model/User.java +++ b/src/main/java/com/model/User.java @@ -1,32 +1,50 @@ package com.model; +import java.util.Date; + +/** + * 用户模型(纯数据) + */ public class User { - private String username; // 用户名,格式:小学-张三 + + private String username; // 用户名 private String password; // 密码(加密后) - private Grade grade; // 学段 private String email; // 邮箱 - private String registrationCode; // 注册码(明文保存,用于用户查看) - private long createdTime; // 创建时间戳 - private long lastLoginTime; // 最后登录时间戳 + private Grade grade; // 学段 + private int totalQuizzes; // 总答题次数 + private double averageScore; // 平均分 + private Date registrationDate; // 注册时间 + + // ==================== 构造方法 ==================== /** - * 无参构造函数(Gson反序列化需要) + * 完整构造方法(用于从文件加载) */ - public User() { + public User(String username, String password, String email, Grade grade, + int totalQuizzes, double averageScore, Date registrationDate) { + this.username = username; + this.password = password; + this.email = email; + this.grade = grade; + this.totalQuizzes = totalQuizzes; + this.averageScore = averageScore; + this.registrationDate = registrationDate; } /** - * 完整构造函数 + * 简化构造方法(用于新用户注册) */ - public User(String username, String password, Grade grade, String email) { + public User(String username, String password, String email, Grade grade) { this.username = username; this.password = password; - this.grade = grade; this.email = email; - this.createdTime = System.currentTimeMillis(); + this.grade = grade; + this.totalQuizzes = 0; + this.averageScore = 0.0; + this.registrationDate = new Date(); } - // ========== Getter和Setter方法 ========== + // ==================== Getters & Setters ==================== public String getUsername() { return username; @@ -44,14 +62,6 @@ public class User { this.password = password; } - public Grade getGrade() { - return grade; - } - - public void setGrade(Grade grade) { - this.grade = grade; - } - public String getEmail() { return email; } @@ -60,36 +70,47 @@ public class User { this.email = email; } - public String getRegistrationCode() { - return registrationCode; + public Grade getGrade() { + return grade; + } + + public void setGrade(Grade grade) { + this.grade = grade; } - public void setRegistrationCode(String registrationCode) { - this.registrationCode = registrationCode; + public int getTotalQuizzes() { + return totalQuizzes; } - public long getCreatedTime() { - return createdTime; + public void setTotalQuizzes(int totalQuizzes) { + this.totalQuizzes = totalQuizzes; } - public void setCreatedTime(long createdTime) { - this.createdTime = createdTime; + public double getAverageScore() { + return averageScore; } - public long getLastLoginTime() { - return lastLoginTime; + public void setAverageScore(double averageScore) { + this.averageScore = averageScore; } - public void setLastLoginTime(long lastLoginTime) { - this.lastLoginTime = lastLoginTime; + public Date getRegistrationDate() { + return registrationDate; + } + + public void setRegistrationDate(Date registrationDate) { + this.registrationDate = registrationDate; } @Override public String toString() { return "User{" + "username='" + username + '\'' + - ", grade=" + grade + ", email='" + email + '\'' + + ", grade=" + grade + + ", totalQuizzes=" + totalQuizzes + + ", averageScore=" + String.format("%.1f", averageScore) + + ", registrationDate=" + registrationDate + '}'; } -} +} \ No newline at end of file diff --git a/src/main/java/com/service/FileIOService.java b/src/main/java/com/service/FileIOService.java new file mode 100644 index 0000000..4bfa68e --- /dev/null +++ b/src/main/java/com/service/FileIOService.java @@ -0,0 +1,242 @@ +package com.service; + + +import com.model.*; +import com.util.FileUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * 文件IO服务 + * 负责所有数据的读写操作 + */ +public class FileIOService { + + private static final String DATA_DIR = "data"; + private static final String USERS_DIR = DATA_DIR + "/users"; + private static final String HISTORY_DIR = DATA_DIR + "/history"; + + private static final String USERS_FILE = DATA_DIR + "/users.json"; + private static final String CURRENT_USER_FILE = DATA_DIR + "/current_user.json"; + + private static final Gson gson = new GsonBuilder() + .setPrettyPrinting() + .setDateFormat("yyyy-MM-dd HH:mm:ss") + .create(); + + // ==================== 初始化 ==================== + + public void initDataDirectory() throws IOException { + FileUtils.createDirectoryIfNotExists(DATA_DIR); + FileUtils.createDirectoryIfNotExists(USERS_DIR); + FileUtils.createDirectoryIfNotExists(HISTORY_DIR); + + if (!FileUtils.exists(USERS_FILE)) { + Map> data = new HashMap<>(); + data.put("users", new ArrayList<>()); + FileUtils.saveAsJson(data, USERS_FILE); + } + + System.out.println("✓ 数据目录初始化完成"); + } + + // ==================== 用户操作 ==================== + + public void saveUser(User user) throws IOException { + Type type = new TypeToken>>(){}.getType(); + Map> data = FileUtils.readJsonToObject(USERS_FILE, type); + + List users = data.get("users"); + + boolean found = false; + for (int i = 0; i < users.size(); i++) { + if (users.get(i).getUsername().equals(user.getUsername())) { + users.set(i, user); + found = true; + break; + } + } + + if (!found) { + users.add(user); + } + + FileUtils.saveAsJson(data, USERS_FILE); + } + + public List loadAllUsers() throws IOException { + if (!FileUtils.exists(USERS_FILE)) { + return new ArrayList<>(); + } + + Type type = new TypeToken>>(){}.getType(); + Map> data = FileUtils.readJsonToObject(USERS_FILE, type); + + return data.get("users"); + } + + public User findUserByUsername(String username) throws IOException { + List users = loadAllUsers(); + + for (User user : users) { + if (user.getUsername().equals(username)) { + return user; + } + } + + return null; + } + + public boolean isUsernameExists(String username) throws IOException { + return findUserByUsername(username) != null; + } + + public void saveCurrentUser(User user) throws IOException { + FileUtils.saveAsJson(user, CURRENT_USER_FILE); + } + + public User loadCurrentUser() throws IOException { + if (!FileUtils.exists(CURRENT_USER_FILE)) { + return null; + } + return FileUtils.readJsonToObject(CURRENT_USER_FILE, User.class); + } + + public void clearCurrentUser() { + FileUtils.deleteFile(CURRENT_USER_FILE); + } + + // ==================== 答题历史操作 ==================== + + public void saveQuizHistory(QuizHistory history) throws IOException { + String filename = HISTORY_DIR + "/" + + sanitizeFilename(history.getUsername()) + "_" + + System.currentTimeMillis() + ".txt"; + + StringBuilder content = new StringBuilder(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + content.append("========== 答题记录 ==========\n"); + content.append("用户:").append(history.getUsername()).append("\n"); + content.append("时间:").append(dateFormat.format(history.getTimestamp())).append("\n"); + content.append("总分:").append(history.getScore()).append(" 分\n"); + + // 调用 QuizService 的业务方法计算正确数和错误数 + int correctCount = calculateCorrectCount(history); + int wrongCount = history.getQuestions().size() - correctCount; + + content.append("正确:").append(correctCount).append(" 题 "); + content.append("错误:").append(wrongCount).append(" 题\n"); + content.append("=============================\n\n"); + + List questions = history.getQuestions(); + List userAnswers = history.getUserAnswers(); + + for (int i = 0; i < questions.size(); i++) { + ChoiceQuestion q = questions.get(i); + Integer userAnswer = userAnswers.get(i); + + content.append("【题目 ").append(i + 1).append("】\n"); + content.append(q.getQuestionText()).append("\n"); + + List options = q.getOptions(); + for (int j = 0; j < options.size(); j++) { + content.append((char)('A' + j)).append(". ") + .append(options.get(j)).append(" "); + } + content.append("\n"); + + int correctIndex = getCorrectAnswerIndex(q); + content.append("正确答案:").append((char)('A' + correctIndex)).append("\n"); + + content.append("用户答案:"); + if (userAnswer != null) { + content.append((char)('A' + userAnswer)); + } else { + content.append("未作答"); + } + content.append("\n"); + + boolean isCorrect = (userAnswer != null && userAnswer == correctIndex); + content.append("结果:").append(isCorrect ? "✓ 正确" : "✗ 错误").append("\n\n"); + } + + FileUtils.writeStringToFile(filename, content.toString()); + } + + public List getHistoryQuestions() throws IOException { + List historyQuestions = new ArrayList<>(); + File[] files = FileUtils.listFiles(HISTORY_DIR); + + Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified())); + + int count = 0; + for (File file : files) { + if (count++ >= 20) break; + + try { + String content = FileUtils.readFileToString(file.getAbsolutePath()); + String[] lines = content.split("\n"); + + for (int i = 0; i < lines.length; i++) { + if (lines[i].startsWith("【题目")) { + if (i + 1 < lines.length) { + String questionText = lines[i + 1].trim(); + if (!questionText.isEmpty()) { + historyQuestions.add(questionText); + } + } + } + } + } catch (IOException e) { + System.err.println("读取历史文件失败: " + file.getName()); + } + } + + return historyQuestions; + } + + // ==================== 业务逻辑方法(从 Model 移过来)==================== + + /** + * 计算答题历史的正确数 + */ + private int calculateCorrectCount(QuizHistory history) { + int count = 0; + List questions = history.getQuestions(); + List userAnswers = history.getUserAnswers(); + + for (int i = 0; i < questions.size(); i++) { + ChoiceQuestion question = questions.get(i); + Integer userAnswer = userAnswers.get(i); + + if (userAnswer != null && userAnswer == getCorrectAnswerIndex(question)) { + count++; + } + } + return count; + } + + /** + * 获取题目的正确答案索引 + */ + private int getCorrectAnswerIndex(ChoiceQuestion question) { + return question.getOptions().indexOf(question.getCorrectAnswer()); + } + + // ==================== 工具方法 ==================== + + private String sanitizeFilename(String filename) { + if (filename == null) { + return "unknown"; + } + return filename.replaceAll("[\\\\/:*?\"<>|]", "_"); + } +} \ No newline at end of file diff --git a/src/main/java/com/service/QuizService.java b/src/main/java/com/service/QuizService.java new file mode 100644 index 0000000..1ce5243 --- /dev/null +++ b/src/main/java/com/service/QuizService.java @@ -0,0 +1,432 @@ +package com.service; + +import com.model.*; +import com.service.question_generator.QuestionFactoryManager; +import java.io.IOException; +import java.util.*; + +/** + * 答题服务(包含所有答题相关业务逻辑) + */ +public class QuizService { + + private final FileIOService fileIOService; + private final UserService userService; + + private List currentQuestions; + private List userAnswers; + private int currentQuestionIndex; + + // ==================== 构造方法 ==================== + + public QuizService() { + this.fileIOService = new FileIOService(); + this.userService = new UserService(fileIOService); + this.currentQuestions = new ArrayList<>(); + this.userAnswers = new ArrayList<>(); + this.currentQuestionIndex = 0; + } + + public QuizService(FileIOService fileIOService, UserService userService) { + this.fileIOService = fileIOService; + this.userService = userService; + this.currentQuestions = new ArrayList<>(); + this.userAnswers = new ArrayList<>(); + this.currentQuestionIndex = 0; + } + + // ==================== 答题会话管理 ==================== + + public void startNewQuiz(User user, int questionCount) throws IOException { + currentQuestions.clear(); + userAnswers.clear(); + currentQuestionIndex = 0; + + Set historyQuestions = getRecentHistoryQuestions(); + + Grade grade = user.getGrade(); + currentQuestions = QuestionFactoryManager.generateQuestions( + grade, questionCount, historyQuestions + ); + + for (int i = 0; i < currentQuestions.size(); i++) { + userAnswers.add(null); + } + + System.out.println("✓ 已生成 " + currentQuestions.size() + " 道 " + grade + " 题目"); + } + + private Set getRecentHistoryQuestions() throws IOException { + List historyList = fileIOService.getHistoryQuestions(); + return new HashSet<>(historyList); + } + + // ==================== 题目访问 ==================== + + public ChoiceQuestion getCurrentQuestion() { + if (currentQuestionIndex >= 0 && currentQuestionIndex < currentQuestions.size()) { + return currentQuestions.get(currentQuestionIndex); + } + return null; + } + + public ChoiceQuestion getQuestion(int index) { + if (index >= 0 && index < currentQuestions.size()) { + return currentQuestions.get(index); + } + return null; + } + + public List getAllQuestions() { + return new ArrayList<>(currentQuestions); + } + + public boolean nextQuestion() { + if (currentQuestionIndex < currentQuestions.size() - 1) { + currentQuestionIndex++; + return true; + } + return false; + } + + public boolean previousQuestion() { + if (currentQuestionIndex > 0) { + currentQuestionIndex--; + return true; + } + return false; + } + + public boolean goToQuestion(int index) { + if (index >= 0 && index < currentQuestions.size()) { + currentQuestionIndex = index; + return true; + } + return false; + } + + public int getCurrentQuestionIndex() { + return currentQuestionIndex; + } + + public int getTotalQuestions() { + return currentQuestions.size(); + } + + public boolean isFirstQuestion() { + return currentQuestionIndex == 0; + } + + public boolean isLastQuestion() { + return currentQuestionIndex == currentQuestions.size() - 1; + } + + // ==================== 答题操作 ==================== + + public boolean submitAnswer(int questionIndex, int optionIndex) { + if (questionIndex < 0 || questionIndex >= currentQuestions.size()) { + throw new IllegalArgumentException("题目索引无效: " + questionIndex); + } + + ChoiceQuestion question = currentQuestions.get(questionIndex); + + if (optionIndex < 0 || optionIndex >= question.getOptions().size()) { + throw new IllegalArgumentException("选项索引无效: " + optionIndex); + } + + userAnswers.set(questionIndex, optionIndex); + + return checkAnswer(question, optionIndex); + } + + public boolean submitCurrentAnswer(int optionIndex) { + return submitAnswer(currentQuestionIndex, optionIndex); + } + + public Integer getUserAnswer(int questionIndex) { + if (questionIndex >= 0 && questionIndex < userAnswers.size()) { + return userAnswers.get(questionIndex); + } + return null; + } + + public List getAllUserAnswers() { + return new ArrayList<>(userAnswers); + } + + public boolean isAllAnswered() { + for (Integer answer : userAnswers) { + if (answer == null) { + return false; + } + } + return true; + } + + public int getAnsweredCount() { + int count = 0; + for (Integer answer : userAnswers) { + if (answer != null) { + count++; + } + } + return count; + } + + // ==================== 成绩计算 ==================== + + public QuizResult calculateResult() { + int correctCount = 0; + int totalQuestions = currentQuestions.size(); + + for (int i = 0; i < totalQuestions; i++) { + ChoiceQuestion question = currentQuestions.get(i); + Integer userAnswer = userAnswers.get(i); + + if (userAnswer != null && checkAnswer(question, userAnswer)) { + correctCount++; + } + } + + int wrongCount = totalQuestions - correctCount; + int score = totalQuestions > 0 ? (correctCount * 100) / totalQuestions : 0; + + return new QuizResult(totalQuestions, correctCount, wrongCount, score); + } + + public List getCorrectQuestionIndices() { + List correctIndices = new ArrayList<>(); + + for (int i = 0; i < currentQuestions.size(); i++) { + ChoiceQuestion question = currentQuestions.get(i); + Integer userAnswer = userAnswers.get(i); + + if (userAnswer != null && checkAnswer(question, userAnswer)) { + correctIndices.add(i); + } + } + + return correctIndices; + } + + public List getWrongQuestionIndices() { + List wrongIndices = new ArrayList<>(); + + for (int i = 0; i < currentQuestions.size(); i++) { + ChoiceQuestion question = currentQuestions.get(i); + Integer userAnswer = userAnswers.get(i); + + if (userAnswer != null && !checkAnswer(question, userAnswer)) { + wrongIndices.add(i); + } + } + + return wrongIndices; + } + + public List getUnansweredQuestionIndices() { + List unansweredIndices = new ArrayList<>(); + + for (int i = 0; i < userAnswers.size(); i++) { + if (userAnswers.get(i) == null) { + unansweredIndices.add(i); + } + } + + return unansweredIndices; + } + + // ==================== 业务逻辑方法(从 Model 移过来)==================== + + /** + * 检查答案是否正确 + */ + public boolean checkAnswer(ChoiceQuestion question, int userAnswerIndex) { + if (userAnswerIndex < 0 || userAnswerIndex >= question.getOptions().size()) { + return false; + } + + Object userAnswer = question.getOptions().get(userAnswerIndex); + return question.getCorrectAnswer().equals(userAnswer); + } + + /** + * 获取题目的正确答案索引 + */ + public int getCorrectAnswerIndex(ChoiceQuestion question) { + return question.getOptions().indexOf(question.getCorrectAnswer()); + } + + /** + * 获取正确答案的字母形式 + */ + public String getCorrectAnswerLetter(ChoiceQuestion question) { + int index = getCorrectAnswerIndex(question); + if (index >= 0 && index < 4) { + return String.valueOf((char)('A' + index)); + } + return "未知"; + } + + /** + * 获取答题结果的正确率 + */ + public double getAccuracy(QuizResult result) { + if (result.getTotalQuestions() == 0) { + return 0.0; + } + return (result.getCorrectCount() * 100.0) / result.getTotalQuestions(); + } + + /** + * 判断是否及格 + */ + public boolean isPassed(QuizResult result) { + return result.getScore() >= 60; + } + + /** + * 获取评级 + */ + public String getGrade(QuizResult result) { + int score = result.getScore(); + if (score >= 90) return "优秀"; + if (score >= 80) return "良好"; + if (score >= 70) return "中等"; + if (score >= 60) return "及格"; + return "不及格"; + } + + /** + * 计算答题历史的正确数 + */ + public int getCorrectCount(QuizHistory history) { + int count = 0; + List questions = history.getQuestions(); + List userAnswers = history.getUserAnswers(); + + for (int i = 0; i < questions.size(); i++) { + ChoiceQuestion question = questions.get(i); + Integer userAnswer = userAnswers.get(i); + + if (userAnswer != null && checkAnswer(question, userAnswer)) { + count++; + } + } + return count; + } + + /** + * 计算答题历史的错误数 + */ + public int getWrongCount(QuizHistory history) { + return history.getQuestions().size() - getCorrectCount(history); + } + + // ==================== 格式化输出 ==================== + + public String formatQuestion(ChoiceQuestion question) { + StringBuilder sb = new StringBuilder(); + sb.append(question.getQuestionText()).append("\n"); + + List options = question.getOptions(); + for (int i = 0; i < options.size(); i++) { + sb.append((char)('A' + i)).append(". ").append(options.get(i)); + + if (i % 2 == 1) { + sb.append("\n"); + } else { + sb.append(" "); + } + } + + return sb.toString(); + } + + public String formatCurrentQuestion() { + ChoiceQuestion question = getCurrentQuestion(); + if (question == null) { + return "没有可用的题目"; + } + + StringBuilder sb = new StringBuilder(); + sb.append("第 ").append(currentQuestionIndex + 1) + .append(" / ").append(currentQuestions.size()).append(" 题\n"); + sb.append(formatQuestion(question)); + + return sb.toString(); + } + + public String formatQuestionWithAnswer(ChoiceQuestion question, Integer userAnswerIndex) { + StringBuilder sb = new StringBuilder(); + sb.append(question.getQuestionText()).append("\n"); + + List options = question.getOptions(); + int correctIndex = getCorrectAnswerIndex(question); + + for (int i = 0; i < options.size(); i++) { + sb.append((char)('A' + i)).append(". ").append(options.get(i)); + + if (i == correctIndex) { + sb.append(" ✓"); + } + + if (userAnswerIndex != null && i == userAnswerIndex) { + boolean isCorrect = checkAnswer(question, userAnswerIndex); + sb.append(isCorrect ? " [您的答案:正确]" : " [您的答案:错误]"); + } + + if (i % 2 == 1) { + sb.append("\n"); + } else { + sb.append(" "); + } + } + + return sb.toString(); + } + + public String formatResult(QuizResult result) { + StringBuilder sb = new StringBuilder(); + sb.append("\n========== 答题结束 ==========\n"); + sb.append("总题数:").append(result.getTotalQuestions()).append(" 题\n"); + sb.append("正确:").append(result.getCorrectCount()).append(" 题\n"); + sb.append("错误:").append(result.getWrongCount()).append(" 题\n"); + sb.append("得分:").append(result.getScore()).append(" 分\n"); + sb.append("正确率:").append(String.format("%.1f%%", getAccuracy(result))).append("\n"); + sb.append("评级:").append(getGrade(result)).append("\n"); + sb.append("===============================\n"); + + return sb.toString(); + } + + // ==================== 数据持久化 ==================== + + public void saveQuizHistory(User user) throws IOException { + QuizResult result = calculateResult(); + + QuizHistory history = new QuizHistory( + user.getUsername(), + new Date(), + currentQuestions, + userAnswers, + result.getScore() + ); + + fileIOService.saveQuizHistory(history); + + userService.updateUserStatistics(user, result.getScore()); + + System.out.println("✓ 答题记录已保存"); + } + + // ==================== Getters ==================== + + public List getCurrentQuestions() { + return new ArrayList<>(currentQuestions); + } + + public List getUserAnswers() { + return new ArrayList<>(userAnswers); + } +} \ No newline at end of file diff --git a/src/main/java/com/service/UserService.java b/src/main/java/com/service/UserService.java new file mode 100644 index 0000000..72c39c2 --- /dev/null +++ b/src/main/java/com/service/UserService.java @@ -0,0 +1,321 @@ +package com.service; + + +import com.model.Grade; +import com.model.User; +import com.util.PasswordValidator; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 用户服务(包含所有用户相关业务逻辑) + */ +public class UserService { + + private final FileIOService fileIOService; + private User currentUser; + + private static final Pattern USERNAME_PATTERN = Pattern.compile("^(小学|初中|高中)-([\\u4e00-\\u9fa5a-zA-Z]+)$"); + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"); + + // ==================== 构造方法 ==================== + + public UserService() { + this.fileIOService = new FileIOService(); + this.currentUser = null; + } + + public UserService(FileIOService fileIOService) { + this.fileIOService = fileIOService; + this.currentUser = null; + } + + // ==================== 用户注册 ==================== + + public User register(String username, String password, String email) throws IOException { + if (!validateUsername(username)) { + throw new IllegalArgumentException("用户名格式错误!正确格式:学段-姓名(如:小学-张三)"); + } + + if (fileIOService.isUsernameExists(username)) { + throw new IllegalArgumentException("用户名已存在!"); + } + + String passwordError = PasswordValidator.validatePassword(password); + if (passwordError != null) { + throw new IllegalArgumentException(passwordError); + } + + if (!validateEmail(email)) { + throw new IllegalArgumentException("邮箱格式错误!"); + } + + Grade grade = extractGradeFromUsername(username); + String hashedPassword = hashPassword(password); + + User user = new User(username, hashedPassword, email, grade); + fileIOService.saveUser(user); + + System.out.println("✓ 用户注册成功:" + username); + return user; + } + + // ==================== 用户登录 ==================== + + public User login(String username, String password) throws IOException { + User user = fileIOService.findUserByUsername(username); + + if (user == null) { + throw new IllegalArgumentException("用户名不存在!"); + } + + String hashedPassword = hashPassword(password); + if (!user.getPassword().equals(hashedPassword)) { + throw new IllegalArgumentException("密码错误!"); + } + + this.currentUser = user; + fileIOService.saveCurrentUser(user); + + System.out.println("✓ 登录成功,欢迎 " + getRealName(user) + "(" + getGradeDisplayName(user) + ")"); + return user; + } + + public User autoLogin() throws IOException { + User user = fileIOService.loadCurrentUser(); + + if (user != null) { + this.currentUser = user; + System.out.println("✓ 自动登录成功,欢迎回来 " + getRealName(user)); + } + + return user; + } + + public void logout() { + if (currentUser != null) { + System.out.println("✓ " + getRealName(currentUser) + " 已退出登录"); + this.currentUser = null; + fileIOService.clearCurrentUser(); + } + } + + public User getCurrentUser() { + return currentUser; + } + + public boolean isLoggedIn() { + return currentUser != null; + } + + // ==================== 密码管理 ==================== + + public boolean changePassword(User user, String oldPassword, String newPassword) throws IOException { + String hashedOldPassword = hashPassword(oldPassword); + if (!user.getPassword().equals(hashedOldPassword)) { + throw new IllegalArgumentException("旧密码错误!"); + } + + String passwordError = PasswordValidator.validatePassword(newPassword); + if (passwordError != null) { + throw new IllegalArgumentException(passwordError); + } + + if (oldPassword.equals(newPassword)) { + throw new IllegalArgumentException("新密码不能与旧密码相同!"); + } + + String hashedNewPassword = hashPassword(newPassword); + user.setPassword(hashedNewPassword); + + fileIOService.saveUser(user); + + if (currentUser != null && currentUser.getUsername().equals(user.getUsername())) { + this.currentUser = user; + fileIOService.saveCurrentUser(user); + } + + System.out.println("✓ 密码修改成功"); + return true; + } + + public boolean resetPassword(String username, String email, String newPassword) throws IOException { + User user = fileIOService.findUserByUsername(username); + + if (user == null) { + throw new IllegalArgumentException("用户名不存在!"); + } + + if (!user.getEmail().equals(email)) { + throw new IllegalArgumentException("邮箱验证失败!"); + } + + String passwordError = PasswordValidator.validatePassword(newPassword); + if (passwordError != null) { + throw new IllegalArgumentException(passwordError); + } + + String hashedNewPassword = hashPassword(newPassword); + user.setPassword(hashedNewPassword); + + fileIOService.saveUser(user); + + System.out.println("✓ 密码重置成功"); + return true; + } + + // ==================== 用户信息管理 ==================== + + public boolean updateEmail(User user, String newEmail) throws IOException { + if (!validateEmail(newEmail)) { + throw new IllegalArgumentException("邮箱格式错误!"); + } + + user.setEmail(newEmail); + fileIOService.saveUser(user); + + if (currentUser != null && currentUser.getUsername().equals(user.getUsername())) { + this.currentUser = user; + fileIOService.saveCurrentUser(user); + } + + System.out.println("✓ 邮箱更新成功"); + return true; + } + + public void updateUserStatistics(User user, int score) throws IOException { + int oldTotal = user.getTotalQuizzes(); + double oldAverage = user.getAverageScore(); + + int newTotal = oldTotal + 1; + double newAverage = (oldAverage * oldTotal + score) / newTotal; + + user.setTotalQuizzes(newTotal); + user.setAverageScore(newAverage); + + fileIOService.saveUser(user); + } + + public List getAllUsers() throws IOException { + return fileIOService.loadAllUsers(); + } + + public User findUser(String username) throws IOException { + return fileIOService.findUserByUsername(username); + } + + // ==================== 业务逻辑方法(从 Model 移过来)==================== + + /** + * 从用户名提取真实姓名 + */ + public String getRealName(User user) { + if (user == null || user.getUsername() == null) { + return ""; + } + + String username = user.getUsername(); + int dashIndex = username.indexOf('-'); + + if (dashIndex > 0 && dashIndex < username.length() - 1) { + return username.substring(dashIndex + 1); + } + + return username; + } + + /** + * 获取学段中文名称 + */ + public String getGradeDisplayName(User user) { + if (user == null || user.getGrade() == null) { + return "未知"; + } + + switch (user.getGrade()) { + case ELEMENTARY: + return "小学"; + case MIDDLE: + return "初中"; + case HIGH: + return "高中"; + default: + return "未知"; + } + } + + /** + * 获取用户统计信息 + */ + public String getUserStatistics(User user) { + StringBuilder sb = new StringBuilder(); + sb.append("========== 用户统计 ==========\n"); + sb.append("用户名:").append(user.getUsername()).append("\n"); + sb.append("真实姓名:").append(getRealName(user)).append("\n"); + sb.append("学段:").append(getGradeDisplayName(user)).append("\n"); + sb.append("邮箱:").append(user.getEmail()).append("\n"); + sb.append("总答题次数:").append(user.getTotalQuizzes()).append(" 次\n"); + sb.append("平均分:").append(String.format("%.1f", user.getAverageScore())).append(" 分\n"); + sb.append("注册时间:").append(user.getRegistrationDate()).append("\n"); + sb.append("=============================\n"); + + return sb.toString(); + } + + // ==================== 验证工具方法 ==================== + + private boolean validateUsername(String username) { + if (username == null || username.trim().isEmpty()) { + return false; + } + + Matcher matcher = USERNAME_PATTERN.matcher(username); + return matcher.matches(); + } + + private boolean validateEmail(String email) { + if (email == null || email.trim().isEmpty()) { + return false; + } + + Matcher matcher = EMAIL_PATTERN.matcher(email); + return matcher.matches(); + } + + private Grade extractGradeFromUsername(String username) { + if (username.startsWith("小学-")) { + return Grade.ELEMENTARY; + } else if (username.startsWith("初中-")) { + return Grade.MIDDLE; + } else if (username.startsWith("高中-")) { + return Grade.HIGH; + } + + throw new IllegalArgumentException("无法识别的学段"); + } + + private String hashPassword(String password) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(password.getBytes()); + + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("密码加密失败", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/QuestionFactoryManager.java b/src/main/java/com/service/question_generator/QuestionFactoryManager.java new file mode 100644 index 0000000..151c3ae --- /dev/null +++ b/src/main/java/com/service/question_generator/QuestionFactoryManager.java @@ -0,0 +1,97 @@ +package com.service.question_generator; + +import com.model.ChoiceQuestion; +import com.model.Grade; +import com.service.question_generator.factory.ElementaryQuestionFactory; +import com.service.question_generator.factory.HighQuestionFactory; +import com.service.question_generator.factory.MiddleQuestionFactory; +import com.service.question_generator.factory.QuestionFactory; + +import java.util.*; + +/** + * 题目工厂管理器 + */ +public class QuestionFactoryManager { + + private static final Map factories = new HashMap<>(); + + static { + factories.put(Grade.ELEMENTARY, new ElementaryQuestionFactory()); + factories.put(Grade.MIDDLE, new MiddleQuestionFactory()); + factories.put(Grade.HIGH, new HighQuestionFactory()); + } + + /** + * 生成单个题目 + * @param grade 学段 + * @return 题目对象 + */ + public static ChoiceQuestion generateQuestion(Grade grade) { + QuestionFactory factory = factories.get(grade); + if (factory == null) { + throw new IllegalArgumentException("不支持的学段: " + grade); + } + return factory.createQuestion(); + } + + /** + * 批量生成题目(不去重) + * + * @param grade 学段 + * @param count 题目数量 + * @param historyQuestions + * @return 题目列表 + */ + public static List generateQuestions(Grade grade, int count, Set historyQuestions) { + return generateUniqueQuestions(grade, count, new HashSet<>()); + } + + /** + * 批量生成题目(带历史去重) + * @param grade 学段 + * @param count 题目数量 + * @param historyQuestions 历史题目文本集合 + * @return 题目列表 + */ + public static List generateUniqueQuestions( + Grade grade, int count, Set historyQuestions) { + + List questions = new ArrayList<>(); + Set currentQuestions = new HashSet<>(historyQuestions); + + int maxAttempts = count * 10; + int attempts = 0; + + QuestionFactory factory = factories.get(grade); + if (factory == null) { + throw new IllegalArgumentException("不支持的学段: " + grade); + } + + while (questions.size() < count && attempts < maxAttempts) { + ChoiceQuestion question = factory.createQuestion(); + String questionText = question.getQuestionText(); + + if (!currentQuestions.contains(questionText)) { + questions.add(question); + currentQuestions.add(questionText); + } + + attempts++; + } + + if (questions.size() < count) { + System.out.println("警告:只生成了 " + questions.size() + + " 道题,未达到要求的 " + count + " 道"); + } + + return questions; + } + + /** + * 注册新的工厂(支持扩展) + */ + public static void registerFactory(Grade grade, QuestionFactory factory) { + factories.put(grade, factory); + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/factory/ElementaryQuestionFactory.java b/src/main/java/com/service/question_generator/factory/ElementaryQuestionFactory.java new file mode 100644 index 0000000..40c341f --- /dev/null +++ b/src/main/java/com/service/question_generator/factory/ElementaryQuestionFactory.java @@ -0,0 +1,41 @@ +package com.service.question_generator.factory; + +import com.model.ChoiceQuestion; +import com.model.Grade; +import com.service.question_generator.strategy.elementary.*; +import com.util.RandomUtils; +import com.service.question_generator.strategy.*; +import java.util.ArrayList; +import java.util.List; + +/** + * 小学题目工厂 + */ +public class ElementaryQuestionFactory implements QuestionFactory { + + private final List strategies; + + public ElementaryQuestionFactory() { + strategies = new ArrayList<>(); + // 注册所有小学题目生成策略 + strategies.add(new AdditionStrategy()); + strategies.add(new SubtractionStrategy()); + strategies.add(new MultiplicationStrategy()); + strategies.add(new DivisionStrategy()); + strategies.add(new ParenthesesAddStrategy()); + strategies.add(new ParenthesesMultiplyStrategy()); + } + + //重载接口方法 + @Override + public ChoiceQuestion createQuestion() { + // 从六个题型list中随机选择一个生成题目 + QuestionStrategy strategy = RandomUtils.randomChoice(strategies); + return strategy.generate(); + } + + @Override + public Grade getSupportedGrade() { + return Grade.ELEMENTARY; + } +} diff --git a/src/main/java/com/service/question_generator/factory/HighQuestionFactory.java b/src/main/java/com/service/question_generator/factory/HighQuestionFactory.java new file mode 100644 index 0000000..4357c0f --- /dev/null +++ b/src/main/java/com/service/question_generator/factory/HighQuestionFactory.java @@ -0,0 +1,41 @@ +package com.service.question_generator.factory; + +import com.model.ChoiceQuestion; +import com.model.Grade; +import com.service.question_generator.strategy.QuestionStrategy; +import com.service.question_generator.strategy.high.CosStrategy; +import com.service.question_generator.strategy.high.SinStrategy; +import com.service.question_generator.strategy.high.TanStrategy; +import com.service.question_generator.strategy.high.TrigIdentityStrategy; +import com.util.RandomUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 高中题目工厂 + */ +public class HighQuestionFactory implements QuestionFactory { + + private final List strategies; + + public HighQuestionFactory() { + strategies = new ArrayList<>(); + // 注册所有高中题目生成策略 + strategies.add(new SinStrategy()); + strategies.add(new CosStrategy()); + strategies.add(new TanStrategy()); + strategies.add(new TrigIdentityStrategy()); + } + + @Override + public ChoiceQuestion createQuestion() { + QuestionStrategy strategy = RandomUtils.randomChoice(strategies); + return strategy.generate(); + } + + @Override + public Grade getSupportedGrade() { + return Grade.HIGH; + } +} diff --git a/src/main/java/com/service/question_generator/factory/MiddleQuestionFactory.java b/src/main/java/com/service/question_generator/factory/MiddleQuestionFactory.java new file mode 100644 index 0000000..4352566 --- /dev/null +++ b/src/main/java/com/service/question_generator/factory/MiddleQuestionFactory.java @@ -0,0 +1,41 @@ +package com.service.question_generator.factory; + + + +import com.model.ChoiceQuestion; +import com.model.Grade; +import com.service.question_generator.strategy.middle.*; +import com.util.RandomUtils; +import com.service.question_generator.strategy.QuestionStrategy; + +import java.util.ArrayList; +import java.util.List; + +/** + * 初中题目工厂 + */ +public class MiddleQuestionFactory implements QuestionFactory { + + private final List strategies; + + public MiddleQuestionFactory() { + strategies = new ArrayList<>(); + // 注册所有初中题目生成策略 + strategies.add(new SquareStrategy()); + strategies.add(new SquareAddStrategy()); + strategies.add(new SqrtStrategy()); + strategies.add(new SqrtAddStrategy()); + strategies.add(new MixedSquareSqrtStrategy()); + } + + @Override + public ChoiceQuestion createQuestion() { + QuestionStrategy strategy = RandomUtils.randomChoice(strategies); + return strategy.generate(); + } + + @Override + public Grade getSupportedGrade() { + return Grade.MIDDLE; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/factory/QuestionFactory.java b/src/main/java/com/service/question_generator/factory/QuestionFactory.java new file mode 100644 index 0000000..4878dc6 --- /dev/null +++ b/src/main/java/com/service/question_generator/factory/QuestionFactory.java @@ -0,0 +1,17 @@ +package com.service.question_generator.factory; + +import com.model.ChoiceQuestion; +import com.model.Grade; + + +/** + * 题目工厂接口 + */ +public interface QuestionFactory { + + //创建题目 + ChoiceQuestion createQuestion(); + + //获取工厂支持的学段 + Grade getSupportedGrade(); +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/AbstractQuestionStrategy.java b/src/main/java/com/service/question_generator/strategy/AbstractQuestionStrategy.java new file mode 100644 index 0000000..d3dc40d --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/AbstractQuestionStrategy.java @@ -0,0 +1,138 @@ +package com.service.question_generator.strategy; + +import com.model.Grade; +import com.util.RandomUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 抽象题目生成策略基类 + * 提供通用的选项生成方法,减少重复代码 + */ +public abstract class AbstractQuestionStrategy implements QuestionStrategy { + + protected final Grade grade; + + /** + * 构造方法 + * @param grade 学段 + */ + protected AbstractQuestionStrategy(Grade grade) { + this.grade = grade; + } + + /** + * 生成数值型选项(包含正确答案和3个干扰项) + * @param correctAnswer 正确答案 + * @return 打乱顺序后的选项列表 + */ + protected List generateNumericOptions(double correctAnswer) { + List options = new ArrayList<>(); + options.add(correctAnswer); + + // 根据答案大小动态调整干扰项范围 + int range = (int) Math.max(10, Math.abs(correctAnswer) * 0.3); + + // 生成3个干扰项 + for (int i = 0; i < 3; i++) { + double distractor; + int attempts = 0; + + do { + int offset = RandomUtils.nextInt(-range, range); + if (offset == 0) offset = (i + 1) * 3; + distractor = correctAnswer + offset; + attempts++; + } while ((options.contains(distractor) || distractor < 0) && attempts < 20); + + if (attempts >= 20) { + distractor = correctAnswer + (i + 1) * 5; + } + + options.add(distractor); + } + + // 确保有4个选项 + while (options.size() < 4) { + options.add(correctAnswer + RandomUtils.nextInt(5, 15)); + } + + Collections.shuffle(options); + return options; + } + + /** + * 生成数值型选项(包含一个常见错误答案) + * @param correctAnswer 正确答案 + * @param commonError 常见错误答案 + * @return 打乱顺序后的选项列表 + */ + protected List generateNumericOptionsWithCommonError( + double correctAnswer, double commonError) { + + List options = new ArrayList<>(); + options.add(correctAnswer); + + // 添加常见错误项(如果有效) + if (commonError != correctAnswer && commonError > 0 && !Double.isNaN(commonError)) { + options.add(commonError); + } + + int range = (int) Math.max(10, Math.abs(correctAnswer) * 0.3); + + // 填充其他干扰项 + while (options.size() < 4) { + double distractor; + int attempts = 0; + + do { + int offset = RandomUtils.nextInt(-range, range); + if (offset == 0) offset = 5; + distractor = correctAnswer + offset; + attempts++; + } while ((options.contains(distractor) || distractor < 0) && attempts < 20); + + if (attempts < 20) { + options.add(distractor); + } else { + options.add(correctAnswer + options.size() * 3); + } + } + + Collections.shuffle(options); + return options; + } + + /** + * 生成字符串型选项(用于三角函数等) + * @param correctAnswer 正确答案 + * @param allPossibleValues 所有可能的值 + * @return 打乱顺序后的选项列表 + */ + protected List generateStringOptions( + String correctAnswer, List allPossibleValues) { + + List options = new ArrayList<>(); + options.add(correctAnswer); + + List availableValues = new ArrayList<>(allPossibleValues); + availableValues.remove(correctAnswer); + + // 随机选择3个干扰项 + while (options.size() < 4 && !availableValues.isEmpty()) { + int randomIndex = RandomUtils.nextInt(0, availableValues.size() - 1); + String distractor = availableValues.get(randomIndex); + options.add(distractor); + availableValues.remove(randomIndex); + } + + while (options.size() < 4) { + options.add("未知"); + } + + Collections.shuffle(options); + return options; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/QuestionStrategy.java b/src/main/java/com/service/question_generator/strategy/QuestionStrategy.java new file mode 100644 index 0000000..e9306d3 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/QuestionStrategy.java @@ -0,0 +1,12 @@ +package com.service.question_generator.strategy; +import com.model.ChoiceQuestion; + +//题目生成题型接口 +public interface QuestionStrategy { + + //生成题目 + ChoiceQuestion generate(); + + //题型 + String getStrategyName(); +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/elementary/AdditionStrategy.java b/src/main/java/com/service/question_generator/strategy/elementary/AdditionStrategy.java new file mode 100644 index 0000000..913e104 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/elementary/AdditionStrategy.java @@ -0,0 +1,37 @@ +package com.service.question_generator.strategy.elementary; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +/** + * 加法策略 + */ +public class AdditionStrategy extends AbstractQuestionStrategy { + + public AdditionStrategy() { + super(Grade.ELEMENTARY); + } + + @Override + public ChoiceQuestion generate() { + int num1 = RandomUtils.nextInt(1, 30); + int num2 = RandomUtils.nextInt(1, 30); + + String questionText = num1 + " + " + num2; + double answer = num1 + num2; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "加法"; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/elementary/DivisionStrategy.java b/src/main/java/com/service/question_generator/strategy/elementary/DivisionStrategy.java new file mode 100644 index 0000000..f702d43 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/elementary/DivisionStrategy.java @@ -0,0 +1,39 @@ +package com.service.question_generator.strategy.elementary; + + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +/** + * 除法策略(确保整除) + */ +public class DivisionStrategy extends AbstractQuestionStrategy { + + public DivisionStrategy() { + super(Grade.ELEMENTARY); + } + + @Override + public ChoiceQuestion generate() { + int divisor = RandomUtils.nextInt(2, 10); + int quotient = RandomUtils.nextInt(1, 10); + int dividend = divisor * quotient; + + String questionText = dividend + " ÷ " + divisor; + double answer = quotient; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "除法"; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/elementary/MultiplicationStrategy.java b/src/main/java/com/service/question_generator/strategy/elementary/MultiplicationStrategy.java new file mode 100644 index 0000000..10a9266 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/elementary/MultiplicationStrategy.java @@ -0,0 +1,37 @@ +package com.service.question_generator.strategy.elementary; + + +import com.model.ChoiceQuestion; +import com.model.Grade; +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +/** + * 乘法策略 + */ +public class MultiplicationStrategy extends AbstractQuestionStrategy { + + public MultiplicationStrategy() { + super(Grade.ELEMENTARY); + } + + @Override + public ChoiceQuestion generate() { + int factor1 = RandomUtils.nextInt(1, 12); + int factor2 = RandomUtils.nextInt(1, 12); + + String questionText = factor1 + " × " + factor2; + double answer = factor1 * factor2; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "乘法"; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/elementary/ParenthesesAddStrategy.java b/src/main/java/com/service/question_generator/strategy/elementary/ParenthesesAddStrategy.java new file mode 100644 index 0000000..8050541 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/elementary/ParenthesesAddStrategy.java @@ -0,0 +1,40 @@ +package com.service.question_generator.strategy.elementary; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + + +import java.util.List; + +/** + * 带括号的加法乘法混合运算策略 + * 例如:(a + b) × c + */ +public class ParenthesesAddStrategy extends AbstractQuestionStrategy { + + public ParenthesesAddStrategy() { + super(Grade.ELEMENTARY); + } + + @Override + public ChoiceQuestion generate() { + int num1 = RandomUtils.nextInt(1, 20); + int num2 = RandomUtils.nextInt(1, 20); + int num3 = RandomUtils.nextInt(2, 10); + + String questionText = "(" + num1 + " + " + num2 + ") × " + num3; + double answer = (num1 + num2) * num3; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "括号加法乘法"; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/elementary/ParenthesesMultiplyStrategy.java b/src/main/java/com/service/question_generator/strategy/elementary/ParenthesesMultiplyStrategy.java new file mode 100644 index 0000000..d5e8792 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/elementary/ParenthesesMultiplyStrategy.java @@ -0,0 +1,41 @@ +package com.service.question_generator.strategy.elementary; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; +/** + * 带括号的减法乘法混合运算策略 + * 例如:(a - b) × c + */ +public class ParenthesesMultiplyStrategy extends AbstractQuestionStrategy { + + public ParenthesesMultiplyStrategy() { + super(Grade.ELEMENTARY); + } + + @Override + public ChoiceQuestion generate() { + int num1 = RandomUtils.nextInt(1, 20); + int num2 = RandomUtils.nextInt(1, 20); + int num3 = RandomUtils.nextInt(2, 10); + + int larger = Math.max(num1, num2); + int smaller = Math.min(num1, num2); + + String questionText = "(" + larger + " - " + smaller + ") × " + num3; + double answer = (larger - smaller) * num3; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "括号减法乘法"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/elementary/SubtractionStrategy.java b/src/main/java/com/service/question_generator/strategy/elementary/SubtractionStrategy.java new file mode 100644 index 0000000..1170b06 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/elementary/SubtractionStrategy.java @@ -0,0 +1,40 @@ +package com.service.question_generator.strategy.elementary; + +import com.model.ChoiceQuestion; +import com.model.Grade; +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +/** + * 减法策略 + */ +public class SubtractionStrategy extends AbstractQuestionStrategy { + + public SubtractionStrategy() { + super(Grade.ELEMENTARY); + } + + @Override + public ChoiceQuestion generate() { + int num1 = RandomUtils.nextInt(1, 30); + int num2 = RandomUtils.nextInt(1, 30); + + // 确保结果为正数 + int larger = Math.max(num1, num2); + int smaller = Math.min(num1, num2); + + String questionText = larger + " - " + smaller; + double answer = larger - smaller; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "减法"; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/high/CosStrategy.java b/src/main/java/com/service/question_generator/strategy/high/CosStrategy.java new file mode 100644 index 0000000..6aba6ee --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/high/CosStrategy.java @@ -0,0 +1,57 @@ +package com.service.question_generator.strategy.high; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +import java.util.*; + +/** + * 余弦函数策略 + * 例如:cos(45°) + */ +public class CosStrategy extends AbstractQuestionStrategy { + + // 特殊角的余弦值表 + private static final Map COS_VALUES = new HashMap<>(); + + static { + COS_VALUES.put(0, "1"); + COS_VALUES.put(30, "√3/2"); + COS_VALUES.put(45, "√2/2"); + COS_VALUES.put(60, "1/2"); + COS_VALUES.put(90, "0"); + COS_VALUES.put(120, "-1/2"); + COS_VALUES.put(135, "-√2/2"); + COS_VALUES.put(150, "-√3/2"); + COS_VALUES.put(180, "-1"); + } + + public CosStrategy() { + super(Grade.HIGH); + } + + @Override + public ChoiceQuestion generate() { + List angles = new ArrayList<>(COS_VALUES.keySet()); + int randomIndex = RandomUtils.nextInt(0, angles.size() - 1); + int angle = angles.get(randomIndex); + + String questionText = "cos(" + angle + "°) = ?"; + String correctAnswer = COS_VALUES.get(angle); + + List allValues = new ArrayList<>(COS_VALUES.values()); + List options = generateStringOptions(correctAnswer, allValues); + + return new ChoiceQuestion(questionText, correctAnswer, options, grade); + } + + @Override + public String getStrategyName() { + return "余弦函数"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/high/SinStrategy.java b/src/main/java/com/service/question_generator/strategy/high/SinStrategy.java new file mode 100644 index 0000000..c0fec5f --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/high/SinStrategy.java @@ -0,0 +1,56 @@ +package com.service.question_generator.strategy.high; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; +import java.util.*; + +import java.util.List; + +/** + * 正弦函数策略 + * 例如:sin(30°) + */ +public class SinStrategy extends AbstractQuestionStrategy { + + // 特殊角的正弦值表 + private static final Map SIN_VALUES = new HashMap<>(); + + static { + SIN_VALUES.put(0, "0"); + SIN_VALUES.put(30, "1/2"); + SIN_VALUES.put(45, "√2/2"); + SIN_VALUES.put(60, "√3/2"); + SIN_VALUES.put(90, "1"); + SIN_VALUES.put(120, "√3/2"); + SIN_VALUES.put(135, "√2/2"); + SIN_VALUES.put(150, "1/2"); + SIN_VALUES.put(180, "0"); + } + + public SinStrategy() { + super(Grade.HIGH); + } + + @Override + public ChoiceQuestion generate() { + List angles = new ArrayList<>(SIN_VALUES.keySet()); + int randomIndex = RandomUtils.nextInt(0, angles.size() - 1); + int angle = angles.get(randomIndex); + + String questionText = "sin(" + angle + "°) = ?"; + String correctAnswer = SIN_VALUES.get(angle); + + List allValues = new ArrayList<>(SIN_VALUES.values()); + List options = generateStringOptions(correctAnswer, allValues); + + return new ChoiceQuestion(questionText, correctAnswer, options, grade); + } + + @Override + public String getStrategyName() { + return "正弦函数"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/high/TanStrategy.java b/src/main/java/com/service/question_generator/strategy/high/TanStrategy.java new file mode 100644 index 0000000..4e0a26a --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/high/TanStrategy.java @@ -0,0 +1,56 @@ +package com.service.question_generator.strategy.high; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +import java.util.*; + +/** + * 正切函数策略 + * 例如:tan(45°) + */ +public class TanStrategy extends AbstractQuestionStrategy { + + // 特殊角的正切值表 + private static final Map TAN_VALUES = new HashMap<>(); + + static { + TAN_VALUES.put(0, "0"); + TAN_VALUES.put(30, "√3/3"); + TAN_VALUES.put(45, "1"); + TAN_VALUES.put(60, "√3"); + TAN_VALUES.put(120, "-√3"); + TAN_VALUES.put(135, "-1"); + TAN_VALUES.put(150, "-√3/3"); + TAN_VALUES.put(180, "0"); + } + + public TanStrategy() { + super(Grade.HIGH); + } + + @Override + public ChoiceQuestion generate() { + List angles = new ArrayList<>(TAN_VALUES.keySet()); + int randomIndex = RandomUtils.nextInt(0, angles.size() - 1); + int angle = angles.get(randomIndex); + + String questionText = "tan(" + angle + "°) = ?"; + String correctAnswer = TAN_VALUES.get(angle); + + List allValues = new ArrayList<>(TAN_VALUES.values()); + List options = generateStringOptions(correctAnswer, allValues); + + return new ChoiceQuestion(questionText, correctAnswer, options, grade); + } + + @Override + public String getStrategyName() { + return "正切函数"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/high/TrigIdentityStrategy.java b/src/main/java/com/service/question_generator/strategy/high/TrigIdentityStrategy.java new file mode 100644 index 0000000..0af4b9e --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/high/TrigIdentityStrategy.java @@ -0,0 +1,41 @@ +package com.service.question_generator.strategy.high; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +import java.util.*; + +/** + * 三角恒等式策略 + * 例如:sin²(θ) + cos²(θ) = ? + */ +public class TrigIdentityStrategy extends AbstractQuestionStrategy { + + public TrigIdentityStrategy() { + super(Grade.HIGH); + } + + @Override + public ChoiceQuestion generate() { + int[] angles = {30, 45, 60, 90}; + int angle = angles[RandomUtils.nextInt(0, angles.length - 1)]; + + String questionText = "sin²(" + angle + "°) + cos²(" + angle + "°) = ?"; + String correctAnswer = "1"; // 三角恒等式,永远等于1 + + List allValues = Arrays.asList("1", "0", "2", "√2", "1/2"); + List options = generateStringOptions(correctAnswer, allValues); + + return new ChoiceQuestion(questionText, correctAnswer, options, grade); + } + + @Override + public String getStrategyName() { + return "三角恒等式"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/middle/MixedSquareSqrtStrategy.java b/src/main/java/com/service/question_generator/strategy/middle/MixedSquareSqrtStrategy.java new file mode 100644 index 0000000..739e2b3 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/middle/MixedSquareSqrtStrategy.java @@ -0,0 +1,40 @@ +package com.service.question_generator.strategy.middle; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +/** + * 平方开方混合运算策略 + * 例如:√49 + 3² + */ +public class MixedSquareSqrtStrategy extends AbstractQuestionStrategy { + + public MixedSquareSqrtStrategy() { + super(Grade.MIDDLE); + } + + @Override + public ChoiceQuestion generate() { + int sqrtRoot = RandomUtils.nextInt(2, 8); + int sqrtNum = sqrtRoot * sqrtRoot; + + int squareBase = RandomUtils.nextInt(2, 6); + + String questionText = "√" + sqrtNum + " + " + squareBase + "²"; + double answer = sqrtRoot + (squareBase * squareBase); + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "平方开方混合"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/middle/SqrtAddStrategy.java b/src/main/java/com/service/question_generator/strategy/middle/SqrtAddStrategy.java new file mode 100644 index 0000000..ed32748 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/middle/SqrtAddStrategy.java @@ -0,0 +1,39 @@ +package com.service.question_generator.strategy.middle; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +/** + * 开方加法混合策略 + * 例如:√49 + 5 + */ +public class SqrtAddStrategy extends AbstractQuestionStrategy { + + public SqrtAddStrategy() { + super(Grade.MIDDLE); + } + + @Override + public ChoiceQuestion generate() { + int root = RandomUtils.nextInt(2, 10); + int num = root * root; + int add = RandomUtils.nextInt(1, 20); + + String questionText = "√" + num + " + " + add; + double answer = root + add; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "开方加法"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/middle/SqrtStrategy.java b/src/main/java/com/service/question_generator/strategy/middle/SqrtStrategy.java new file mode 100644 index 0000000..8cc2eff --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/middle/SqrtStrategy.java @@ -0,0 +1,39 @@ +package com.service.question_generator.strategy.middle; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; +/** + * 开方运算策略(完全平方数) + * 例如:√49 + */ +public class SqrtStrategy extends AbstractQuestionStrategy { + + public SqrtStrategy() { + super(Grade.MIDDLE); + } + + @Override + public ChoiceQuestion generate() { + int root = RandomUtils.nextInt(2, 12); + int num = root * root; // 完全平方数 + + String questionText = "√" + num; + double answer = root; + + // 常见错误:把开方当成除以2 + double commonError = num / 2.0; + List options = generateNumericOptionsWithCommonError(answer, commonError); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "开方"; + } +} diff --git a/src/main/java/com/service/question_generator/strategy/middle/SquareAddStrategy.java b/src/main/java/com/service/question_generator/strategy/middle/SquareAddStrategy.java new file mode 100644 index 0000000..51e5516 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/middle/SquareAddStrategy.java @@ -0,0 +1,37 @@ +package com.service.question_generator.strategy.middle; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; +/** + * 平方加法混合策略 + * 例如:5² + 10 + */ +public class SquareAddStrategy extends AbstractQuestionStrategy { + + public SquareAddStrategy() { + super(Grade.MIDDLE); + } + + @Override + public ChoiceQuestion generate() { + int base = RandomUtils.nextInt(2, 10); + int add = RandomUtils.nextInt(1, 20); + + String questionText = base + "² + " + add; + double answer = base * base + add; + + List options = generateNumericOptions(answer); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "平方加法"; + } +} \ No newline at end of file diff --git a/src/main/java/com/service/question_generator/strategy/middle/SquareStrategy.java b/src/main/java/com/service/question_generator/strategy/middle/SquareStrategy.java new file mode 100644 index 0000000..8cf8b05 --- /dev/null +++ b/src/main/java/com/service/question_generator/strategy/middle/SquareStrategy.java @@ -0,0 +1,39 @@ +package com.service.question_generator.strategy.middle; + +import com.model.ChoiceQuestion; +import com.model.Grade; + +import com.service.question_generator.strategy.AbstractQuestionStrategy; +import com.util.RandomUtils; + +import java.util.List; + +/** + * 平方运算策略 + * 例如:5² + */ +public class SquareStrategy extends AbstractQuestionStrategy { + + public SquareStrategy() { + super(Grade.MIDDLE); + } + + @Override + public ChoiceQuestion generate() { + int num = RandomUtils.nextInt(1, 15); + + String questionText = num + "²"; + double answer = num * num; + + // 常见错误:把平方当成乘以2 + double commonError = num * 2; + List options = generateNumericOptionsWithCommonError(answer, commonError); + + return new ChoiceQuestion(questionText, answer, options, grade); + } + + @Override + public String getStrategyName() { + return "平方"; + } +} diff --git a/src/main/java/com/util/FileUtils.java b/src/main/java/com/util/FileUtils.java index d4daf9e..186a2cc 100644 --- a/src/main/java/com/util/FileUtils.java +++ b/src/main/java/com/util/FileUtils.java @@ -1,15 +1,22 @@ package com.util; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.*; +import java.lang.reflect.Type; /** * 文件操作工具类 - * 提供文件读写、目录创建等常用操作 + * 提供文件读写、目录创建、JSON序列化等常用操作 */ public class FileUtils { + private static final Gson gson = new GsonBuilder() + .setPrettyPrinting() // 格式化输出JSON + .create(); + /** * 读取文件内容为字符串 * @param filePath 文件路径 @@ -108,4 +115,70 @@ public class FileUtils { public static long getFileSize(String filePath) throws IOException { return Files.size(Paths.get(filePath)); } + + // ==================== JSON 操作方法 ==================== + + /** + * 将对象保存为JSON文件 + * @param data 要保存的对象 + * @param filePath 文件路径 + * @throws IOException 保存失败时抛出 + */ + public static void saveAsJson(Object data, String filePath) throws IOException { + String json = gson.toJson(data); + writeStringToFile(filePath, json); + } + + /** + * 从JSON文件读取对象 + * @param filePath 文件路径 + * @param classOfT 对象的Class类型 + * @return 反序列化后的对象 + * @throws IOException 读取失败时抛出 + */ + public static T readJsonToObject(String filePath, Class classOfT) throws IOException { + String json = readFileToString(filePath); + return gson.fromJson(json, classOfT); + } + + /** + * 从JSON文件读取对象(支持泛型) + * @param filePath 文件路径 + * @param typeOfT 对象的Type类型(用于泛型) + * @return 反序列化后的对象 + * @throws IOException 读取失败时抛出 + */ + public static T readJsonToObject(String filePath, Type typeOfT) throws IOException { + String json = readFileToString(filePath); + return gson.fromJson(json, typeOfT); + } + + /** + * 将对象转换为JSON字符串 + * @param data 要转换的对象 + * @return JSON字符串 + */ + public static String toJson(Object data) { + return gson.toJson(data); + } + + /** + * 将JSON字符串转换为对象 + * @param json JSON字符串 + * @param classOfT 对象的Class类型 + * @return 反序列化后的对象 + */ + public static T fromJson(String json, Class classOfT) { + return gson.fromJson(json, classOfT); + } + + /** + * 将JSON字符串转换为对象(支持泛型) + * @param json JSON字符串 + * @param typeOfT 对象的Type类型 + * @return 反序列化后的对象 + */ + public static T fromJson(String json, Type typeOfT) { + return gson.fromJson(json, typeOfT); + } } \ No newline at end of file diff --git a/src/main/java/com/util/PasswordValidator.java b/src/main/java/com/util/PasswordValidator.java index 40a26b5..1ab48e7 100644 --- a/src/main/java/com/util/PasswordValidator.java +++ b/src/main/java/com/util/PasswordValidator.java @@ -2,6 +2,7 @@ package com.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; /** * 密码验证和加密工具类 @@ -9,33 +10,123 @@ import java.security.NoSuchAlgorithmException; */ public class PasswordValidator { + // 密码长度限制 + private static final int MIN_LENGTH = 6; + private static final int MAX_LENGTH = 20; // 改为20位,更安全 + + // 用于生成随机注册码的字符集 + private static final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz"; + private static final String DIGITS = "0123456789"; + private static final String ALL_CHARS = UPPERCASE + LOWERCASE + DIGITS; + + // 使用 SecureRandom 替代 Math.random(),更安全 + private static final SecureRandom random = new SecureRandom(); + + // ==================== 密码验证 ==================== + /** - * 验证密码格式:6-10位,包含字母和数字 + * 验证密码格式(返回详细错误信息) + * + * @param password 待验证的密码 + * @return 如果密码有效返回 null,否则返回错误信息 + */ + public static String validatePassword(String password) { + if (password == null || password.isEmpty()) { + return "密码不能为空!"; + } + + if (password.length() < MIN_LENGTH) { + return "密码长度不能少于 " + MIN_LENGTH + " 位!"; + } + + if (password.length() > MAX_LENGTH) { + return "密码长度不能超过 " + MAX_LENGTH + " 位!"; + } + + if (password.contains(" ")) { + return "密码不能包含空格!"; + } + + // 检查是否包含字母 + boolean hasLetter = password.matches(".*[a-zA-Z].*"); + + // 检查是否包含数字 + boolean hasDigit = password.matches(".*\\d.*"); + + if (!hasLetter) { + return "密码必须包含字母!"; + } + + if (!hasDigit) { + return "密码必须包含数字!"; + } + + return null; // 验证通过 + } + + /** + * 验证密码格式(简单版本) + * * @param password 待验证的密码 * @return true表示格式正确 */ public static boolean isValid(String password) { - if (password == null || password.length() < 6 || password.length() > 10) { - return false; + return validatePassword(password) == null; + } + + /** + * 检查密码强度等级 + * + * @param password 密码 + * @return 强度等级:弱、中、强 + */ + public static String getPasswordStrength(String password) { + if (password == null || password.length() < MIN_LENGTH) { + return "弱"; } - boolean hasLetter = password.matches("^(?=.*[a-z])(?=.*[A-Z]).*$"); - boolean hasDigit = password.matches(".*\\d.*"); + int score = 0; + + // 长度加分 + if (password.length() >= 8) score++; + if (password.length() >= 12) score++; - return hasLetter && hasDigit; + // 包含小写字母 + if (password.matches(".*[a-z].*")) score++; + + // 包含大写字母 + if (password.matches(".*[A-Z].*")) score++; + + // 包含数字 + if (password.matches(".*\\d.*")) score++; + + // 包含特殊字符 + if (password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?].*")) score++; + + if (score <= 2) return "弱"; + if (score <= 4) return "中"; + return "强"; } + // ==================== 密码加密 ==================== + /** * 使用SHA-256加密密码 + * * @param password 明文密码 * @return 加密后的密码(16进制字符串) */ public static String encrypt(String password) { + if (password == null) { + throw new IllegalArgumentException("密码不能为null"); + } + try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(password.getBytes()); - StringBuilder hexString = new StringBuilder(); + StringBuilder hexString = new StringBuilder(); for (byte b : hash) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { @@ -52,32 +143,58 @@ public class PasswordValidator { /** * 验证密码是否匹配 + * * @param plainPassword 明文密码 * @param encryptedPassword 加密后的密码 * @return true表示匹配 */ public static boolean matches(String plainPassword, String encryptedPassword) { + if (plainPassword == null || encryptedPassword == null) { + return false; + } + return encrypt(plainPassword).equals(encryptedPassword); } + // ==================== 注册码生成 ==================== + /** * 生成6-10位随机注册码(包含字母和数字) + * * @return 注册码 */ public static String generateRegistrationCode() { - String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - int length = 6 + (int) (Math.random() * 5); // 6-10位 + return generateRegistrationCode(MIN_LENGTH, 10); + } - StringBuilder code = new StringBuilder(); + /** + * 生成指定长度范围的随机注册码 + * + * @param minLen 最小长度 + * @param maxLen 最大长度 + * @return 注册码 + */ + public static String generateRegistrationCode(int minLen, int maxLen) { + if (minLen < 4 || maxLen < minLen) { + throw new IllegalArgumentException("长度参数无效"); + } + + int length = minLen + random.nextInt(maxLen - minLen + 1); + + StringBuilder code = new StringBuilder(length); + + // 确保至少有一个大写字母 + code.append(UPPERCASE.charAt(random.nextInt(UPPERCASE.length()))); + + // 确保至少有一个小写字母 + code.append(LOWERCASE.charAt(random.nextInt(LOWERCASE.length()))); - // 确保至少有一个字母 - code.append(chars.charAt((int) (Math.random() * 52))); // 确保至少有一个数字 - code.append(chars.charAt(52 + (int) (Math.random() * 10))); + code.append(DIGITS.charAt(random.nextInt(DIGITS.length()))); // 填充剩余字符 - for (int i = 2; i < length; i++) { - code.append(chars.charAt((int) (Math.random() * chars.length()))); + for (int i = 3; i < length; i++) { + code.append(ALL_CHARS.charAt(random.nextInt(ALL_CHARS.length()))); } // 打乱字符顺序 @@ -85,18 +202,128 @@ public class PasswordValidator { } /** - * 打乱字符串 + * 生成固定长度的随机密码 + * + * @param length 密码长度 + * @param includeSpecialChars 是否包含特殊字符 + * @return 随机密码 + */ + public static String generateRandomPassword(int length, boolean includeSpecialChars) { + if (length < MIN_LENGTH) { + throw new IllegalArgumentException("密码长度不能少于 " + MIN_LENGTH); + } + + String chars = ALL_CHARS; + if (includeSpecialChars) { + chars += "!@#$%^&*()_+-=[]{}"; + } + + StringBuilder password = new StringBuilder(length); + + // 确保至少包含一个字母和一个数字 + password.append(UPPERCASE.charAt(random.nextInt(UPPERCASE.length()))); + password.append(DIGITS.charAt(random.nextInt(DIGITS.length()))); + + // 填充剩余字符 + for (int i = 2; i < length; i++) { + password.append(chars.charAt(random.nextInt(chars.length()))); + } + + return shuffleString(password.toString()); + } + + // ==================== 工具方法 ==================== + + /** + * 打乱字符串(使用 Fisher-Yates 算法) + * * @param str 原字符串 * @return 打乱后的字符串 */ private static String shuffleString(String str) { + if (str == null || str.length() <= 1) { + return str; + } + char[] chars = str.toCharArray(); + for (int i = chars.length - 1; i > 0; i--) { - int j = (int) (Math.random() * (i + 1)); + int j = random.nextInt(i + 1); + + // 交换 char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } + return new String(chars); } + + /** + * 检查密码是否包含常见弱密码 + * + * @param password 密码 + * @return true表示是弱密码 + */ + public static boolean isWeakPassword(String password) { + if (password == null) { + return true; + } + + String lowerPassword = password.toLowerCase(); + + // 常见弱密码列表 + String[] weakPasswords = { + "123456", "password", "123456789", "12345678", "12345", + "111111", "1234567", "sunshine", "qwerty", "iloveyou", + "princess", "admin", "welcome", "666666", "abc123", + "football", "123123", "monkey", "654321", "!@#$%^&*", + "charlie", "aa123456", "donald", "password1", "qwerty123" + }; + + for (String weak : weakPasswords) { + if (lowerPassword.equals(weak) || lowerPassword.contains(weak)) { + return true; + } + } + + // 检查是否是连续数字或字母 + if (password.matches("^(\\d)\\1+$") || // 全是相同数字 + password.matches("^(.)\\1+$") || // 全是相同字符 + password.matches("^(0123456789|123456789|987654321|abcdefghij|qwertyuiop).*")) { // 连续字符 + return true; + } + + return false; + } + + /** + * 生成密码建议 + * + * @param password 密码 + * @return 建议文本 + */ + public static String getPasswordSuggestion(String password) { + if (password == null || password.isEmpty()) { + return "请输入密码"; + } + + String error = validatePassword(password); + if (error != null) { + return error; + } + + String strength = getPasswordStrength(password); + + if ("弱".equals(strength)) { + return "密码强度较弱,建议:\n" + + "• 使用至少8位字符\n" + + "• 同时包含大小写字母、数字\n" + + "• 添加特殊字符"; + } else if ("中".equals(strength)) { + return "密码强度中等,可以考虑添加特殊字符提高安全性"; + } else { + return "密码强度良好!"; + } + } } \ No newline at end of file diff --git a/src/test/java/TestMain.java b/src/test/java/TestMain.java index d4c7da5..b002dde 100644 --- a/src/test/java/TestMain.java +++ b/src/test/java/TestMain.java @@ -1,2 +1,507 @@ + + +import com.model.*; +import com.service.*; +import com.service.question_generator.QuestionFactoryManager; +import com.util.PasswordValidator; + +import java.io.IOException; +import java.util.List; + +/** + * 完整测试类 + * 测试项目的各个功能模块 + */ public class TestMain { -} + + private static int testsPassed = 0; + private static int testsFailed = 0; + + public static void main(String[] args) { + System.out.println("========================================"); + System.out.println(" 数学答题系统 - 完整测试"); + System.out.println("========================================\n"); + + try { + // 1. 测试工具类 + testPasswordValidator(); + + // 2. 测试文件服务 + testFileIOService(); + + // 3. 测试用户服务 + testUserService(); + + // 4. 测试题目生成 + testQuestionGeneration(); + + // 5. 测试答题服务 + testQuizService(); + + // 6. 测试完整流程 + testCompleteWorkflow(); + + // 输出测试结果 + printTestSummary(); + + } catch (Exception e) { + System.err.println("测试过程中发生错误:" + e.getMessage()); + e.printStackTrace(); + } + } + + // ==================== 1. 测试密码验证工具 ==================== + + private static void testPasswordValidator() { + System.out.println("【测试1】密码验证工具"); + System.out.println("----------------------------------------"); + + // 测试1.1: 有效密码 + test("有效密码验证", + PasswordValidator.isValid("Abc123456"), + "密码 'Abc123456' 应该有效"); + + // 测试1.2: 密码太短 + test("密码太短检测", + !PasswordValidator.isValid("Abc12"), + "密码 'Abc12' 应该无效(太短)"); + + // 测试1.3: 缺少数字 + test("缺少数字检测", + !PasswordValidator.isValid("Abcdefgh"), + "密码 'Abcdefgh' 应该无效(缺少数字)"); + + // 测试1.4: 密码加密 + String encrypted1 = PasswordValidator.encrypt("test123"); + String encrypted2 = PasswordValidator.encrypt("test123"); + test("密码加密一致性", + encrypted1.equals(encrypted2), + "相同密码加密结果应该一致"); + + // 测试1.5: 密码匹配 + test("密码匹配验证", + PasswordValidator.matches("test123", encrypted1), + "密码匹配应该成功"); + + // 测试1.6: 生成注册码 + String code = PasswordValidator.generateRegistrationCode(); + test("注册码生成", + code.length() >= 6 && code.length() <= 10, + "注册码长度应该在6-10位之间,实际:" + code.length()); + + // 测试1.7: 密码强度检测 + String strength = PasswordValidator.getPasswordStrength("Abc123!@#"); + test("密码强度检测", + strength != null && !strength.isEmpty(), + "密码强度应该返回有效值,实际:" + strength); + + System.out.println(); + } + + // ==================== 2. 测试文件IO服务 ==================== + + private static void testFileIOService() throws IOException { + System.out.println("【测试2】文件IO服务"); + System.out.println("----------------------------------------"); + + FileIOService fileService = new FileIOService(); + + // 测试2.1: 初始化目录 + try { + fileService.initDataDirectory(); + test("初始化数据目录", true, "数据目录初始化成功"); + } catch (Exception e) { + test("初始化数据目录", false, "失败:" + e.getMessage()); + } + + // 测试2.2: 保存和加载用户 + User testUser = new User("小学-测试", "encrypted123", "test@test.com", Grade.ELEMENTARY); + try { + fileService.saveUser(testUser); + User loaded = fileService.findUserByUsername("小学-测试"); + test("保存和加载用户", + loaded != null && loaded.getUsername().equals("小学-测试"), + "用户数据应该正确保存和加载"); + } catch (Exception e) { + test("保存和加载用户", false, "失败:" + e.getMessage()); + } + + // 测试2.3: 检查用户名是否存在 + try { + boolean exists = fileService.isUsernameExists("小学-测试"); + test("检查用户名存在", exists, "用户名应该存在"); + } catch (Exception e) { + test("检查用户名存在", false, "失败:" + e.getMessage()); + } + + // 测试2.4: 查找不存在的用户 + try { + User notFound = fileService.findUserByUsername("不存在的用户"); + test("查找不存在的用户", + notFound == null, + "不存在的用户应该返回null"); + } catch (Exception e) { + test("查找不存在的用户", false, "失败:" + e.getMessage()); + } + + System.out.println(); + } + + // ==================== 3. 测试用户服务 ==================== + + private static void testUserService() throws IOException { + System.out.println("【测试3】用户服务"); + System.out.println("----------------------------------------"); + + UserService userService = new UserService(); + String testUsername = "小学-张三测试"; + + // 测试3.1: 用户注册 + try { + User user = userService.register(testUsername, "Test123456", "zhangsan@test.com"); + test("用户注册", + user != null && user.getUsername().equals(testUsername), + "用户注册应该成功"); + } catch (Exception e) { + test("用户注册", false, "失败:" + e.getMessage()); + } + + // 测试3.2: 重复注册检测 + try { + userService.register(testUsername, "Test123456", "test@test.com"); + test("重复注册检测", false, "应该抛出异常"); + } catch (IllegalArgumentException e) { + test("重复注册检测", + e.getMessage().contains("已存在"), + "应该检测到用户名已存在"); + } + + // 测试3.3: 用户登录 + try { + User user = userService.login(testUsername, "Test123456"); + test("用户登录", + user != null && userService.isLoggedIn(), + "用户登录应该成功"); + } catch (Exception e) { + test("用户登录", false, "失败:" + e.getMessage()); + } + + // 测试3.4: 错误密码登录 + try { + userService.logout(); // 先退出 + userService.login(testUsername, "WrongPassword"); + test("错误密码登录", false, "应该抛出异常"); + } catch (IllegalArgumentException e) { + test("错误密码登录", + e.getMessage().contains("密码错误"), + "应该检测到密码错误"); + } + + // 测试3.5: 获取当前用户 + try { + userService.login(testUsername, "Test123456"); + User current = userService.getCurrentUser(); + test("获取当前用户", + current != null && current.getUsername().equals(testUsername), + "应该返回当前登录用户"); + } catch (Exception e) { + test("获取当前用户", false, "失败:" + e.getMessage()); + } + + // 测试3.6: 提取真实姓名 + User user = userService.getCurrentUser(); + String realName = userService.getRealName(user); + test("提取真实姓名", + realName.equals("张三测试"), + "应该正确提取真实姓名,实际:" + realName); + + // 测试3.7: 获取学段显示名 + String gradeName = userService.getGradeDisplayName(user); + test("获取学段显示名", + gradeName.equals("小学"), + "应该返回'小学',实际:" + gradeName); + + // 测试3.8: 退出登录 + userService.logout(); + test("退出登录", + !userService.isLoggedIn(), + "退出后应该未登录状态"); + + System.out.println(); + } + + // ==================== 4. 测试题目生成 ==================== + + private static void testQuestionGeneration() { + System.out.println("【测试4】题目生成"); + System.out.println("----------------------------------------"); + + // 测试4.1: 生成小学题目 + try { + ChoiceQuestion q = QuestionFactoryManager.generateQuestion(Grade.ELEMENTARY); + test("生成小学题目", + q != null && q.getQuestionText() != null && q.getOptions().size() == 4, + "应该生成有效的小学题目"); + + // 显示示例题目 + System.out.println(" 示例题目:" + q.getQuestionText()); + System.out.println(" 选项数量:" + q.getOptions().size()); + } catch (Exception e) { + test("生成小学题目", false, "失败:" + e.getMessage()); + } + + // 测试4.2: 生成初中题目 + try { + ChoiceQuestion q = QuestionFactoryManager.generateQuestion(Grade.MIDDLE); + test("生成初中题目", + q != null && q.getQuestionText() != null, + "应该生成有效的初中题目"); + + System.out.println(" 示例题目:" + q.getQuestionText()); + } catch (Exception e) { + test("生成初中题目", false, "失败:" + e.getMessage()); + } + + // 测试4.3: 生成高中题目 + try { + ChoiceQuestion q = QuestionFactoryManager.generateQuestion(Grade.HIGH); + test("生成高中题目", + q != null && q.getQuestionText() != null, + "应该生成有效的高中题目"); + + System.out.println(" 示例题目:" + q.getQuestionText()); + } catch (Exception e) { + test("生成高中题目", false, "失败:" + e.getMessage()); + } + + // 测试4.4: 批量生成题目 + try { + List questions = QuestionFactoryManager.generateQuestions(Grade.ELEMENTARY, 10, historyQuestions); + test("批量生成题目", + questions.size() == 10, + "应该生成10道题目,实际:" + questions.size()); + } catch (Exception e) { + test("批量生成题目", false, "失败:" + e.getMessage()); + } + + // 测试4.5: 题目去重 + try { + ChoiceQuestion q1 = QuestionFactoryManager.generateQuestion(Grade.ELEMENTARY); + ChoiceQuestion q2 = QuestionFactoryManager.generateQuestion(Grade.ELEMENTARY); + + // 理论上不应该完全相同(概率极低) + boolean different = !q1.getQuestionText().equals(q2.getQuestionText()); + test("题目去重机制", + different, + "连续生成的题目应该不同(高概率)"); + } catch (Exception e) { + test("题目去重机制", false, "失败:" + e.getMessage()); + } + + System.out.println(); + } + + // ==================== 5. 测试答题服务 ==================== + + private static void testQuizService() throws IOException { + System.out.println("【测试5】答题服务"); + System.out.println("----------------------------------------"); + + FileIOService fileService = new FileIOService(); + UserService userService = new UserService(fileService); + QuizService quizService = new QuizService(fileService, userService); + + // 创建测试用户 + User testUser = new User("小学-李四", "encrypted", "lisi@test.com", Grade.ELEMENTARY); + fileService.saveUser(testUser); + + // 测试5.1: 开始答题 + try { + quizService.startNewQuiz(testUser, 5); + test("开始答题会话", + quizService.getTotalQuestions() == 5, + "应该生成5道题目"); + } catch (Exception e) { + test("开始答题会话", false, "失败:" + e.getMessage()); + } + + // 测试5.2: 获取当前题目 + ChoiceQuestion current = quizService.getCurrentQuestion(); + test("获取当前题目", + current != null, + "应该返回当前题目"); + + // 测试5.3: 提交答案 + try { + boolean correct = quizService.submitCurrentAnswer(0); + test("提交答案", + true, // 只要不抛异常就算通过 + "提交答案应该成功,结果:" + (correct ? "正确" : "错误")); + } catch (Exception e) { + test("提交答案", false, "失败:" + e.getMessage()); + } + + // 测试5.4: 题目导航 + boolean canNext = quizService.nextQuestion(); + test("下一题导航", + canNext, + "应该能够移动到下一题"); + + boolean canPrev = quizService.previousQuestion(); + test("上一题导航", + canPrev, + "应该能够移动到上一题"); + + // 测试5.5: 检查答案 + ChoiceQuestion question = quizService.getCurrentQuestion(); + int correctIndex = quizService.getCorrectAnswerIndex(question); + boolean isCorrect = quizService.checkAnswer(question, correctIndex); + test("检查正确答案", + isCorrect, + "正确答案应该通过验证"); + + // 测试5.6: 答题进度 + quizService.goToQuestion(0); + quizService.submitCurrentAnswer(0); + quizService.nextQuestion(); + quizService.submitCurrentAnswer(1); + + int answered = quizService.getAnsweredCount(); + test("答题进度统计", + answered == 2, + "应该有2道题已作答,实际:" + answered); + + // 测试5.7: 完成所有题目并计算成绩 + for (int i = 0; i < quizService.getTotalQuestions(); i++) { + quizService.goToQuestion(i); + quizService.submitCurrentAnswer(0); + } + + QuizResult result = quizService.calculateResult(); + test("计算成绩", + result.getTotalQuestions() == 5, + "成绩统计应该正确,总题数:" + result.getTotalQuestions()); + + System.out.println(" 得分:" + result.getScore()); + System.out.println(" 正确:" + result.getCorrectCount()); + System.out.println(" 错误:" + result.getWrongCount()); + + // 测试5.8: 格式化输出 + String formatted = quizService.formatResult(result); + test("格式化结果输出", + formatted != null && formatted.contains("答题结束"), + "应该返回格式化的结果文本"); + + System.out.println(); + } + + // ==================== 6. 测试完整流程 ==================== + + private static void testCompleteWorkflow() throws IOException { + System.out.println("【测试6】完整答题流程"); + System.out.println("----------------------------------------"); + + FileIOService fileService = new FileIOService(); + UserService userService = new UserService(fileService); + QuizService quizService = new QuizService(fileService, userService); + + try { + // 步骤1: 注册用户 + System.out.println("步骤1: 注册新用户..."); + User user = userService.register("初中-王五", "Test123456", "wangwu@test.com"); + test("完整流程-注册", user != null, "用户注册成功"); + + // 步骤2: 登录 + System.out.println("步骤2: 用户登录..."); + userService.login("初中-王五", "Test123456"); + test("完整流程-登录", userService.isLoggedIn(), "用户登录成功"); + + // 步骤3: 开始答题 + System.out.println("步骤3: 开始答题(10道题)..."); + quizService.startNewQuiz(user, 10); + test("完整流程-生成题目", + quizService.getTotalQuestions() == 10, + "题目生成成功"); + + // 步骤4: 答题(模拟全部答对) + System.out.println("步骤4: 模拟答题过程..."); + for (int i = 0; i < 10; i++) { + quizService.goToQuestion(i); + ChoiceQuestion q = quizService.getCurrentQuestion(); + int correctIndex = quizService.getCorrectAnswerIndex(q); + quizService.submitAnswer(i, correctIndex); + } + test("完整流程-答题", quizService.isAllAnswered(), "所有题目已作答"); + + // 步骤5: 计算成绩 + System.out.println("步骤5: 计算成绩..."); + QuizResult result = quizService.calculateResult(); + test("完整流程-计算成绩", + result.getScore() == 100, + "全部答对应该得100分,实际:" + result.getScore()); + + System.out.println(quizService.formatResult(result)); + + // 步骤6: 保存记录 + System.out.println("步骤6: 保存答题记录..."); + quizService.saveQuizHistory(user); + + // 验证用户统计是否更新 + User updatedUser = fileService.findUserByUsername("初中-王五"); + test("完整流程-保存记录", + updatedUser.getTotalQuizzes() == 1, + "用户答题次数应该增加,实际:" + updatedUser.getTotalQuizzes()); + + test("完整流程-平均分更新", + updatedUser.getAverageScore() == 100.0, + "平均分应该更新,实际:" + updatedUser.getAverageScore()); + + // 步骤7: 退出登录 + System.out.println("步骤7: 退出登录..."); + userService.logout(); + test("完整流程-退出", !userService.isLoggedIn(), "退出登录成功"); + + System.out.println("\n✓ 完整流程测试通过!"); + + } catch (Exception e) { + test("完整流程", false, "失败:" + e.getMessage()); + e.printStackTrace(); + } + + System.out.println(); + } + + // ==================== 测试工具方法 ==================== + + private static void test(String testName, boolean condition, String message) { + if (condition) { + System.out.println(" ✓ " + testName + ": 通过"); + if (message != null && !message.isEmpty()) { + System.out.println(" " + message); + } + testsPassed++; + } else { + System.out.println(" ✗ " + testName + ": 失败"); + if (message != null && !message.isEmpty()) { + System.out.println(" " + message); + } + testsFailed++; + } + } + + private static void printTestSummary() { + System.out.println("========================================"); + System.out.println(" 测试结果汇总"); + System.out.println("========================================"); + System.out.println("总测试数:" + (testsPassed + testsFailed)); + System.out.println("通过:" + testsPassed + " 项"); + System.out.println("失败:" + testsFailed + " 项"); + + if (testsFailed == 0) { + System.out.println("\n🎉 所有测试通过!项目功能正常,可以开始开发UI了!"); + } else { + System.out.println("\n⚠ 有 " + testsFailed + " 项测试失败,请检查并修复问题"); + } + System.out.println("========================================"); + } +} \ No newline at end of file -- 2.34.1 From ce09a0810a19e84392bb3c93eb7a8c5014020f5f Mon Sep 17 00:00:00 2001 From: bx <2936213174@qq.com> Date: Sat, 4 Oct 2025 10:47:50 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E7=AC=AC=E4=B8=89=E7=89=88=EF=BC=88?= =?UTF-8?q?=E5=B8=A6=E6=B5=8B=E8=AF=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/model/ChoiceQuestion.java | 8 +- src/main/java/com/model/Grade.java | 5 +- src/main/java/com/model/QuizHistory.java | 7 +- src/main/java/com/model/QuizResult.java | 6 +- src/main/java/com/model/User.java | 9 +- src/main/java/com/service/UserService.java | 217 +++++++++- .../QuestionFactoryManager.java | 47 +-- src/test/java/TestMain.java | 377 +++++++++++++++--- 8 files changed, 557 insertions(+), 119 deletions(-) diff --git a/src/main/java/com/model/ChoiceQuestion.java b/src/main/java/com/model/ChoiceQuestion.java index 897059e..e4b0b54 100644 --- a/src/main/java/com/model/ChoiceQuestion.java +++ b/src/main/java/com/model/ChoiceQuestion.java @@ -2,9 +2,7 @@ package com.model; import java.util.List; -/** - * 选择题模型(纯数据) - */ +//选择题 public class ChoiceQuestion { private String questionText; // 题目文本 @@ -12,7 +10,7 @@ public class ChoiceQuestion { private List options; // 选项列表 private Grade grade; // 所属学段 - // ==================== 构造方法 ==================== + public ChoiceQuestion(String questionText, double correctAnswer, List options, Grade grade) { @@ -30,7 +28,7 @@ public class ChoiceQuestion { this.grade = grade; } - // ==================== Getters & Setters ==================== + public String getQuestionText() { return questionText; diff --git a/src/main/java/com/model/Grade.java b/src/main/java/com/model/Grade.java index 1d2729f..afa5077 100644 --- a/src/main/java/com/model/Grade.java +++ b/src/main/java/com/model/Grade.java @@ -1,8 +1,7 @@ package com.model; -/** - * 学段枚举 - */ + +//学段 public enum Grade { ELEMENTARY, // 小学 MIDDLE, // 初中 diff --git a/src/main/java/com/model/QuizHistory.java b/src/main/java/com/model/QuizHistory.java index 1d60028..5bad9d0 100644 --- a/src/main/java/com/model/QuizHistory.java +++ b/src/main/java/com/model/QuizHistory.java @@ -4,9 +4,7 @@ package com.model; import java.util.Date; import java.util.List; -/** - * 答题历史记录模型(纯数据) - */ +//答题历史记录模型 public class QuizHistory { private String username; // 用户名 @@ -15,7 +13,6 @@ public class QuizHistory { private List userAnswers; // 用户答案列表 private int score; // 得分 - // ==================== 构造方法 ==================== public QuizHistory(String username, Date timestamp, List questions, @@ -28,7 +25,7 @@ public class QuizHistory { this.score = score; } - // ==================== Getters & Setters ==================== + public String getUsername() { return username; diff --git a/src/main/java/com/model/QuizResult.java b/src/main/java/com/model/QuizResult.java index 7ce4e50..eb70db5 100644 --- a/src/main/java/com/model/QuizResult.java +++ b/src/main/java/com/model/QuizResult.java @@ -1,9 +1,7 @@ package com.model; -/** - * 答题结果模型(纯数据) - */ +//答题结果 public class QuizResult { private int totalQuestions; // 总题数 @@ -11,7 +9,6 @@ public class QuizResult { private int wrongCount; // 错误题数 private int score; // 得分 - // ==================== 构造方法 ==================== public QuizResult(int totalQuestions, int correctCount, int wrongCount, int score) { this.totalQuestions = totalQuestions; @@ -20,7 +17,6 @@ public class QuizResult { this.score = score; } - // ==================== Getters & Setters ==================== public int getTotalQuestions() { return totalQuestions; diff --git a/src/main/java/com/model/User.java b/src/main/java/com/model/User.java index 288ce9b..78d530a 100644 --- a/src/main/java/com/model/User.java +++ b/src/main/java/com/model/User.java @@ -2,9 +2,8 @@ package com.model; import java.util.Date; -/** - * 用户模型(纯数据) - */ + +//用户 public class User { private String username; // 用户名 @@ -15,7 +14,7 @@ public class User { private double averageScore; // 平均分 private Date registrationDate; // 注册时间 - // ==================== 构造方法 ==================== + /** * 完整构造方法(用于从文件加载) @@ -44,7 +43,7 @@ public class User { this.registrationDate = new Date(); } - // ==================== Getters & Setters ==================== + public String getUsername() { return username; diff --git a/src/main/java/com/service/UserService.java b/src/main/java/com/service/UserService.java index 72c39c2..06c78bb 100644 --- a/src/main/java/com/service/UserService.java +++ b/src/main/java/com/service/UserService.java @@ -3,12 +3,17 @@ package com.service; import com.model.Grade; import com.model.User; +import com.util.FileUtils; import com.util.PasswordValidator; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -35,30 +40,236 @@ public class UserService { this.currentUser = null; } - // ==================== 用户注册 ==================== + // 注册码文件路径 + private static final String REGISTRATION_CODES_FILE = "data/registration_codes.txt"; - public User register(String username, String password, String email) throws IOException { + // 注册码有效期(毫秒) + private static final long CODE_EXPIRY_TIME = 10 * 60 * 1000; // 10分钟 + + // ==================== 注册码管理 ==================== + + /** + * 生成并保存注册码到文件 + * @param email 邮箱 + * @return 生成的注册码 + */ + public String generateRegistrationCode(String email) throws IOException { + if (!validateEmail(email)) { + throw new IllegalArgumentException("邮箱格式错误!"); + } + + // 生成6-10位注册码 + String code = PasswordValidator.generateRegistrationCode(); + long expiryTime = System.currentTimeMillis() + CODE_EXPIRY_TIME; + + // 保存到文件 + saveRegistrationCodeToFile(email, code, expiryTime); + + // 打印注册码(实际项目中可以发邮件) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println("========================================"); + System.out.println("【注册码】"); + System.out.println("邮箱: " + email); + System.out.println("注册码: " + code); + System.out.println("过期时间: " + sdf.format(new Date(expiryTime))); + System.out.println("========================================"); + + return code; + } + + /** + * 保存注册码到文件 + */ + private void saveRegistrationCodeToFile(String email, String code, long expiryTime) throws IOException { + // 读取现有的注册码 + Map codes = loadRegistrationCodesFromFile(); + + // 添加或更新 + codes.put(email, new RegistrationCode(code, expiryTime)); + + // 保存到文件 + StringBuilder content = new StringBuilder(); + content.append("# 注册码记录文件\n"); + content.append("# 格式: 邮箱|注册码|过期时间戳\n"); + content.append("# 过期时间格式: yyyy-MM-dd HH:mm:ss\n\n"); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + for (Map.Entry entry : codes.entrySet()) { + String emailKey = entry.getKey(); + RegistrationCode regCode = entry.getValue(); + + content.append(emailKey).append("|") + .append(regCode.code).append("|") + .append(regCode.expiryTime).append("|") + .append(sdf.format(new Date(regCode.expiryTime))) + .append("\n"); + } + + FileUtils.writeStringToFile(REGISTRATION_CODES_FILE, content.toString()); + } + + /** + * 从文件加载注册码 + */ + private Map loadRegistrationCodesFromFile() throws IOException { + Map codes = new HashMap<>(); + + if (!FileUtils.exists(REGISTRATION_CODES_FILE)) { + return codes; + } + + String content = FileUtils.readFileToString(REGISTRATION_CODES_FILE); + String[] lines = content.split("\n"); + + for (String line : lines) { + line = line.trim(); + + // 跳过注释和空行 + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + + String[] parts = line.split("\\|"); + if (parts.length >= 3) { + String email = parts[0].trim(); + String code = parts[1].trim(); + long expiryTime = Long.parseLong(parts[2].trim()); + + codes.put(email, new RegistrationCode(code, expiryTime)); + } + } + + return codes; + } + + /** + * 验证注册码 + * @param email 邮箱 + * @param code 用户输入的注册码 + * @return true表示验证通过 + */ + public boolean verifyRegistrationCode(String email, String code) throws IOException { + Map codes = loadRegistrationCodesFromFile(); + + RegistrationCode regCode = codes.get(email); + + if (regCode == null) { + throw new IllegalArgumentException("未找到该邮箱的注册码,请先获取注册码!"); + } + + // 检查是否过期 + if (System.currentTimeMillis() > regCode.expiryTime) { + // 删除过期的注册码 + codes.remove(email); + saveAllRegistrationCodes(codes); + throw new IllegalArgumentException("注册码已过期,请重新获取!"); + } + + // 验证注册码 + boolean isValid = regCode.code.equals(code); + + // 验证成功后删除注册码(一次性使用) + if (isValid) { + codes.remove(email); + saveAllRegistrationCodes(codes); + } + + return isValid; + } + + /** + * 保存所有注册码到文件 + */ + private void saveAllRegistrationCodes(Map codes) throws IOException { + StringBuilder content = new StringBuilder(); + content.append("# 注册码记录文件\n"); + content.append("# 格式: 邮箱|注册码|过期时间戳|过期时间\n\n"); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + for (Map.Entry entry : codes.entrySet()) { + content.append(entry.getKey()).append("|") + .append(entry.getValue().code).append("|") + .append(entry.getValue().expiryTime).append("|") + .append(sdf.format(new Date(entry.getValue().expiryTime))) + .append("\n"); + } + + FileUtils.writeStringToFile(REGISTRATION_CODES_FILE, content.toString()); + } + + /** + * 清理过期的注册码 + */ + public void cleanExpiredCodes() throws IOException { + Map codes = loadRegistrationCodesFromFile(); + long now = System.currentTimeMillis(); + + // 移除过期的 + codes.entrySet().removeIf(entry -> now > entry.getValue().expiryTime); + + // 保存回文件 + saveAllRegistrationCodes(codes); + + System.out.println("✓ 已清理过期的注册码"); + } + + // ==================== 注册码内部类 ==================== + + private static class RegistrationCode { + String code; + long expiryTime; + + RegistrationCode(String code, long expiryTime) { + this.code = code; + this.expiryTime = expiryTime; + } + } + + + // ==================== 用户注册==================== + + /** + * 用户注册(需要验证码) + */ + public User register(String username, String password, String email, String verificationCode) throws IOException { + // 1. 验证注册码 + if (!verifyRegistrationCode(email, verificationCode)) { + throw new IllegalArgumentException("注册码错误!"); + } + + // 2. 验证用户名格式 if (!validateUsername(username)) { throw new IllegalArgumentException("用户名格式错误!正确格式:学段-姓名(如:小学-张三)"); } + // 3. 验证用户名是否已存在 if (fileIOService.isUsernameExists(username)) { throw new IllegalArgumentException("用户名已存在!"); } + // 4. 验证密码强度 String passwordError = PasswordValidator.validatePassword(password); if (passwordError != null) { throw new IllegalArgumentException(passwordError); } + // 5. 验证邮箱格式 if (!validateEmail(email)) { throw new IllegalArgumentException("邮箱格式错误!"); } + // 6. 从用户名中提取学段 Grade grade = extractGradeFromUsername(username); + + // 7. 加密密码 String hashedPassword = hashPassword(password); + // 8. 创建用户对象 User user = new User(username, hashedPassword, email, grade); + + // 9. 保存到文件 fileIOService.saveUser(user); System.out.println("✓ 用户注册成功:" + username); @@ -209,7 +420,7 @@ public class UserService { return fileIOService.findUserByUsername(username); } - // ==================== 业务逻辑方法(从 Model 移过来)==================== + // ==================== 业务逻辑方法==================== /** * 从用户名提取真实姓名 diff --git a/src/main/java/com/service/question_generator/QuestionFactoryManager.java b/src/main/java/com/service/question_generator/QuestionFactoryManager.java index 151c3ae..1036792 100644 --- a/src/main/java/com/service/question_generator/QuestionFactoryManager.java +++ b/src/main/java/com/service/question_generator/QuestionFactoryManager.java @@ -23,42 +23,20 @@ public class QuestionFactoryManager { } /** - * 生成单个题目 - * @param grade 学段 - * @return 题目对象 - */ - public static ChoiceQuestion generateQuestion(Grade grade) { - QuestionFactory factory = factories.get(grade); - if (factory == null) { - throw new IllegalArgumentException("不支持的学段: " + grade); - } - return factory.createQuestion(); - } - - /** - * 批量生成题目(不去重) + * 生成题目(带去重功能) * - * @param grade 学段 - * @param count 题目数量 - * @param historyQuestions - * @return 题目列表 - */ - public static List generateQuestions(Grade grade, int count, Set historyQuestions) { - return generateUniqueQuestions(grade, count, new HashSet<>()); - } - - /** - * 批量生成题目(带历史去重) * @param grade 学段 * @param count 题目数量 - * @param historyQuestions 历史题目文本集合 + * @param historyQuestions 历史题目(用于去重,传 null 或空集合则不去重) * @return 题目列表 */ - public static List generateUniqueQuestions( + public static List generateQuestions( Grade grade, int count, Set historyQuestions) { List questions = new ArrayList<>(); - Set currentQuestions = new HashSet<>(historyQuestions); + Set usedQuestions = historyQuestions != null ? + new HashSet<>(historyQuestions) : + new HashSet<>(); int maxAttempts = count * 10; int attempts = 0; @@ -72,26 +50,19 @@ public class QuestionFactoryManager { ChoiceQuestion question = factory.createQuestion(); String questionText = question.getQuestionText(); - if (!currentQuestions.contains(questionText)) { + if (!usedQuestions.contains(questionText)) { questions.add(question); - currentQuestions.add(questionText); + usedQuestions.add(questionText); } attempts++; } if (questions.size() < count) { - System.out.println("警告:只生成了 " + questions.size() + + System.out.println("⚠ 警告:只生成了 " + questions.size() + " 道题,未达到要求的 " + count + " 道"); } return questions; } - - /** - * 注册新的工厂(支持扩展) - */ - public static void registerFactory(Grade grade, QuestionFactory factory) { - factories.put(grade, factory); - } } \ No newline at end of file diff --git a/src/test/java/TestMain.java b/src/test/java/TestMain.java index b002dde..245b50e 100644 --- a/src/test/java/TestMain.java +++ b/src/test/java/TestMain.java @@ -6,7 +6,9 @@ import com.service.question_generator.QuestionFactoryManager; import com.util.PasswordValidator; import java.io.IOException; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * 完整测试类 @@ -147,59 +149,155 @@ public class TestMain { System.out.println(); } - // ==================== 3. 测试用户服务 ==================== + // ==================== 3. 测试用户服务==================== private static void testUserService() throws IOException { - System.out.println("【测试3】用户服务"); + System.out.println("【测试3】用户服务(包含验证码)"); System.out.println("----------------------------------------"); UserService userService = new UserService(); + + // ========== 3.1 测试注册码生成和保存 ========== + + String testEmail1 = "test001@example.com"; + String registrationCode1 = null; + + try { + registrationCode1 = userService.generateRegistrationCode(testEmail1); + test("生成注册码", + registrationCode1 != null && registrationCode1.length() >= 6, + "注册码:" + registrationCode1); + } catch (Exception e) { + test("生成注册码", false, "失败:" + e.getMessage()); + } + + // ========== 3.2 测试注册码文件存储 ========== + + try { + boolean fileExists = com.util.FileUtils.exists("data/registration_codes.txt"); + test("注册码文件创建", + fileExists, + "注册码应该保存到文件"); + } catch (Exception e) { + test("注册码文件创建", false, "失败:" + e.getMessage()); + } + + // ========== 3.3 测试用户注册(带验证码)========== + String testUsername = "小学-张三测试"; + String testPassword = "Test123456"; - // 测试3.1: 用户注册 try { - User user = userService.register(testUsername, "Test123456", "zhangsan@test.com"); - test("用户注册", + User user = userService.register(testUsername, testPassword, testEmail1, registrationCode1); + test("用户注册(带验证码)", user != null && user.getUsername().equals(testUsername), - "用户注册应该成功"); + "用户注册成功"); + } catch (Exception e) { + test("用户注册(带验证码)", false, "失败:" + e.getMessage()); + } + + // ========== 3.4 测试注册码一次性使用 ========== + + try { + // 尝试用同一个注册码再次注册 + userService.register("小学-李四", "Test123456", testEmail1, registrationCode1); + test("注册码一次性使用", false, "应该抛出异常"); + } catch (IllegalArgumentException e) { + test("注册码一次性使用", + e.getMessage().contains("未找到"), + "注册码使用后应该被删除"); + } catch (Exception e) { + test("注册码一次性使用", false, "异常类型错误:" + e.getMessage()); + } + + // ========== 3.5 测试错误的注册码 ========== + + String testEmail2 = "test002@example.com"; + + try { + String code = userService.generateRegistrationCode(testEmail2); + // 故意使用错误的注册码 + userService.register("小学-王五", "Test123456", testEmail2, "wrongCode123"); + test("错误注册码检测", false, "应该抛出异常"); + } catch (IllegalArgumentException e) { + test("错误注册码检测", + e.getMessage().contains("注册码错误"), + "应该检测到错误的注册码"); + } catch (Exception e) { + test("错误注册码检测", false, "失败:" + e.getMessage()); + } + + // ========== 3.6 测试未获取注册码就注册 ========== + + try { + userService.register("小学-赵六", "Test123456", "nocode@test.com", "randomCode"); + test("未获取注册码检测", false, "应该抛出异常"); + } catch (IllegalArgumentException e) { + test("未获取注册码检测", + e.getMessage().contains("未找到"), + "应该检测到未获取注册码"); } catch (Exception e) { - test("用户注册", false, "失败:" + e.getMessage()); + test("未获取注册码检测", false, "失败:" + e.getMessage()); } - // 测试3.2: 重复注册检测 + // ========== 3.7 测试重复注册检测 ========== + + String testEmail3 = "test003@example.com"; + try { - userService.register(testUsername, "Test123456", "test@test.com"); + String code = userService.generateRegistrationCode(testEmail3); + userService.register(testUsername, "Test123456", testEmail3, code); test("重复注册检测", false, "应该抛出异常"); } catch (IllegalArgumentException e) { test("重复注册检测", e.getMessage().contains("已存在"), "应该检测到用户名已存在"); + } catch (Exception e) { + test("重复注册检测", false, "失败:" + e.getMessage()); } - // 测试3.3: 用户登录 + // ========== 3.8 测试用户登录 ========== + try { - User user = userService.login(testUsername, "Test123456"); + User user = userService.login(testUsername, testPassword); test("用户登录", user != null && userService.isLoggedIn(), - "用户登录应该成功"); + "用户登录成功"); } catch (Exception e) { test("用户登录", false, "失败:" + e.getMessage()); } - // 测试3.4: 错误密码登录 + // ========== 3.9 测试错误密码登录 ========== + try { userService.logout(); // 先退出 - userService.login(testUsername, "WrongPassword"); + userService.login(testUsername, "WrongPassword123"); test("错误密码登录", false, "应该抛出异常"); } catch (IllegalArgumentException e) { test("错误密码登录", e.getMessage().contains("密码错误"), "应该检测到密码错误"); + } catch (Exception e) { + test("错误密码登录", false, "失败:" + e.getMessage()); } - // 测试3.5: 获取当前用户 + // ========== 3.10 测试不存在的用户登录 ========== + try { - userService.login(testUsername, "Test123456"); + userService.login("小学-不存在", "Test123456"); + test("不存在用户登录", false, "应该抛出异常"); + } catch (IllegalArgumentException e) { + test("不存在用户登录", + e.getMessage().contains("不存在"), + "应该检测到用户名不存在"); + } catch (Exception e) { + test("不存在用户登录", false, "失败:" + e.getMessage()); + } + + // ========== 3.11 测试获取当前用户 ========== + + try { + userService.login(testUsername, testPassword); User current = userService.getCurrentUser(); test("获取当前用户", current != null && current.getUsername().equals(testUsername), @@ -208,25 +306,155 @@ public class TestMain { test("获取当前用户", false, "失败:" + e.getMessage()); } - // 测试3.6: 提取真实姓名 + // ========== 3.12 测试提取真实姓名 ========== + User user = userService.getCurrentUser(); String realName = userService.getRealName(user); test("提取真实姓名", realName.equals("张三测试"), "应该正确提取真实姓名,实际:" + realName); - // 测试3.7: 获取学段显示名 + // ========== 3.13 测试获取学段显示名 ========== + String gradeName = userService.getGradeDisplayName(user); test("获取学段显示名", gradeName.equals("小学"), "应该返回'小学',实际:" + gradeName); - // 测试3.8: 退出登录 + // ========== 3.14 测试退出登录 ========== + userService.logout(); test("退出登录", !userService.isLoggedIn(), "退出后应该未登录状态"); + // ========== 3.15 测试完整注册流程(不同学段)========== + + // 初中学生注册 + try { + String middleEmail = "middle@test.com"; + String middleCode = userService.generateRegistrationCode(middleEmail); + User middleUser = userService.register("初中-李明", "Middle123", middleEmail, middleCode); + + test("初中学生注册", + middleUser != null && middleUser.getGrade() == Grade.MIDDLE, + "初中学生注册成功"); + } catch (Exception e) { + test("初中学生注册", false, "失败:" + e.getMessage()); + } + + // 高中学生注册 + try { + String highEmail = "high@test.com"; + String highCode = userService.generateRegistrationCode(highEmail); + User highUser = userService.register("高中-王华", "High123456", highEmail, highCode); + + test("高中学生注册", + highUser != null && highUser.getGrade() == Grade.HIGH, + "高中学生注册成功"); + } catch (Exception e) { + test("高中学生注册", false, "失败:" + e.getMessage()); + } + + // ========== 3.16 测试密码强度验证 ========== + + try { + String weakEmail = "weak@test.com"; + String weakCode = userService.generateRegistrationCode(weakEmail); + userService.register("小学-弱密码", "123", weakEmail, weakCode); + test("密码强度验证", false, "应该拒绝弱密码"); + } catch (IllegalArgumentException e) { + test("密码强度验证", + e.getMessage().contains("密码"), + "应该检测到密码不符合要求"); + } catch (Exception e) { + test("密码强度验证", false, "失败:" + e.getMessage()); + } + + // ========== 3.17 测试邮箱格式验证 ========== + + try { + userService.generateRegistrationCode("invalid-email"); + test("邮箱格式验证", false, "应该拒绝无效邮箱"); + } catch (IllegalArgumentException e) { + test("邮箱格式验证", + e.getMessage().contains("邮箱"), + "应该检测到邮箱格式错误"); + } catch (Exception e) { + test("邮箱格式验证", false, "失败:" + e.getMessage()); + } + + // ========== 3.18 测试用户名格式验证 ========== + + try { + String invalidEmail = "invalid@test.com"; + String invalidCode = userService.generateRegistrationCode(invalidEmail); + userService.register("错误格式", "Test123456", invalidEmail, invalidCode); + test("用户名格式验证", false, "应该拒绝错误格式的用户名"); + } catch (IllegalArgumentException e) { + test("用户名格式验证", + e.getMessage().contains("格式"), + "应该检测到用户名格式错误"); + } catch (Exception e) { + test("用户名格式验证", false, "失败:" + e.getMessage()); + } + + // ========== 3.19 测试清理过期注册码 ========== + + try { + userService.cleanExpiredCodes(); + test("清理过期注册码", true, "清理操作成功"); + } catch (Exception e) { + test("清理过期注册码", false, "失败:" + e.getMessage()); + } + + // ========== 3.20 测试从文件重新加载注册码 ========== + + try { + String reloadEmail = "reload@test.com"; + String reloadCode = userService.generateRegistrationCode(reloadEmail); + + // 创建新的 UserService 实例(模拟重启) + UserService newUserService = new UserService(); + + // 使用之前保存的注册码 + User reloadUser = newUserService.register("小学-重载测试", "Reload123", reloadEmail, reloadCode); + + test("从文件重载注册码", + reloadUser != null, + "应该能从文件读取注册码"); + } catch (Exception e) { + test("从文件重载注册码", false, "失败:" + e.getMessage()); + } + + // ========== 3.21 查看注册码文件内容 ========== + + try { + if (com.util.FileUtils.exists("data/registration_codes.txt")) { + String fileContent = com.util.FileUtils.readFileToString( + "data/registration_codes.txt" + ); + + System.out.println("\n 【注册码文件内容预览】"); + String[] lines = fileContent.split("\n"); + int lineCount = 0; + for (String line : lines) { + if (lineCount++ < 10) { // 显示前10行 + System.out.println(" " + line); + } + } + if (lines.length > 10) { + System.out.println(" ... (共 " + lines.length + " 行)"); + } + + test("注册码文件格式", + fileContent.contains("#") && fileContent.contains("|"), + "文件格式正确"); + } + } catch (Exception e) { + test("查看文件内容", false, "失败:" + e.getMessage()); + } + System.out.println(); } @@ -236,47 +464,57 @@ public class TestMain { System.out.println("【测试4】题目生成"); System.out.println("----------------------------------------"); - // 测试4.1: 生成小学题目 + // 测试4.1: 生成小学题目(不去重) try { - ChoiceQuestion q = QuestionFactoryManager.generateQuestion(Grade.ELEMENTARY); + List questions = QuestionFactoryManager.generateQuestions( + Grade.ELEMENTARY, 1, null + ); + test("生成小学题目", - q != null && q.getQuestionText() != null && q.getOptions().size() == 4, - "应该生成有效的小学题目"); + questions.size() == 1 && questions.get(0).getQuestionText() != null, + "应该生成1道有效的小学题目"); - // 显示示例题目 - System.out.println(" 示例题目:" + q.getQuestionText()); - System.out.println(" 选项数量:" + q.getOptions().size()); + System.out.println(" 示例题目:" + questions.get(0).getQuestionText()); } catch (Exception e) { test("生成小学题目", false, "失败:" + e.getMessage()); } // 测试4.2: 生成初中题目 try { - ChoiceQuestion q = QuestionFactoryManager.generateQuestion(Grade.MIDDLE); + List questions = QuestionFactoryManager.generateQuestions( + Grade.MIDDLE, 1, null + ); + test("生成初中题目", - q != null && q.getQuestionText() != null, - "应该生成有效的初中题目"); + questions.size() == 1, + "应该生成1道有效的初中题目"); - System.out.println(" 示例题目:" + q.getQuestionText()); + System.out.println(" 示例题目:" + questions.get(0).getQuestionText()); } catch (Exception e) { test("生成初中题目", false, "失败:" + e.getMessage()); } // 测试4.3: 生成高中题目 try { - ChoiceQuestion q = QuestionFactoryManager.generateQuestion(Grade.HIGH); + List questions = QuestionFactoryManager.generateQuestions( + Grade.HIGH, 1, null + ); + test("生成高中题目", - q != null && q.getQuestionText() != null, - "应该生成有效的高中题目"); + questions.size() == 1, + "应该生成1道有效的高中题目"); - System.out.println(" 示例题目:" + q.getQuestionText()); + System.out.println(" 示例题目:" + questions.get(0).getQuestionText()); } catch (Exception e) { test("生成高中题目", false, "失败:" + e.getMessage()); } // 测试4.4: 批量生成题目 try { - List questions = QuestionFactoryManager.generateQuestions(Grade.ELEMENTARY, 10, historyQuestions); + List questions = QuestionFactoryManager.generateQuestions( + Grade.ELEMENTARY, 10, null + ); + test("批量生成题目", questions.size() == 10, "应该生成10道题目,实际:" + questions.size()); @@ -284,23 +522,42 @@ public class TestMain { test("批量生成题目", false, "失败:" + e.getMessage()); } - // 测试4.5: 题目去重 + // 测试4.5: 题目去重功能 try { - ChoiceQuestion q1 = QuestionFactoryManager.generateQuestion(Grade.ELEMENTARY); - ChoiceQuestion q2 = QuestionFactoryManager.generateQuestion(Grade.ELEMENTARY); + // 第一次生成 + List firstBatch = QuestionFactoryManager.generateQuestions( + Grade.ELEMENTARY, 5, null + ); + + // 收集已生成的题目文本 + Set historyQuestions = new HashSet<>(); + for (ChoiceQuestion q : firstBatch) { + historyQuestions.add(q.getQuestionText()); + } - // 理论上不应该完全相同(概率极低) - boolean different = !q1.getQuestionText().equals(q2.getQuestionText()); - test("题目去重机制", - different, - "连续生成的题目应该不同(高概率)"); + // 第二次生成(带去重) + List secondBatch = QuestionFactoryManager.generateQuestions( + Grade.ELEMENTARY, 5, historyQuestions + ); + + // 检查第二次生成的题目是否与第一次重复 + boolean noDuplicate = true; + for (ChoiceQuestion q : secondBatch) { + if (historyQuestions.contains(q.getQuestionText())) { + noDuplicate = false; + break; + } + } + + test("题目去重功能", + noDuplicate, + "第二次生成的题目不应与第一次重复"); } catch (Exception e) { - test("题目去重机制", false, "失败:" + e.getMessage()); + test("题目去重功能", false, "失败:" + e.getMessage()); } System.out.println(); } - // ==================== 5. 测试答题服务 ==================== private static void testQuizService() throws IOException { @@ -406,24 +663,34 @@ public class TestMain { QuizService quizService = new QuizService(fileService, userService); try { - // 步骤1: 注册用户 + // ========== 步骤1: 注册新用户 ========== System.out.println("步骤1: 注册新用户..."); - User user = userService.register("初中-王五", "Test123456", "wangwu@test.com"); + + String username = "初中-王五"; + String password = "Test123456"; + String email = "wangwu@test.com"; + + // 1.1 生成注册码 + String registrationCode = userService.generateRegistrationCode(email); + System.out.println(" 获取注册码:" + registrationCode); + + // 1.2 使用注册码注册 + User user = userService.register(username, password, email, registrationCode); test("完整流程-注册", user != null, "用户注册成功"); - // 步骤2: 登录 + // ========== 步骤2: 用户登录 ========== System.out.println("步骤2: 用户登录..."); - userService.login("初中-王五", "Test123456"); + userService.login(username, password); test("完整流程-登录", userService.isLoggedIn(), "用户登录成功"); - // 步骤3: 开始答题 + // ========== 步骤3: 开始答题 ========== System.out.println("步骤3: 开始答题(10道题)..."); quizService.startNewQuiz(user, 10); test("完整流程-生成题目", quizService.getTotalQuestions() == 10, "题目生成成功"); - // 步骤4: 答题(模拟全部答对) + // ========== 步骤4: 答题(模拟全部答对)========== System.out.println("步骤4: 模拟答题过程..."); for (int i = 0; i < 10; i++) { quizService.goToQuestion(i); @@ -433,7 +700,7 @@ public class TestMain { } test("完整流程-答题", quizService.isAllAnswered(), "所有题目已作答"); - // 步骤5: 计算成绩 + // ========== 步骤5: 计算成绩 ========== System.out.println("步骤5: 计算成绩..."); QuizResult result = quizService.calculateResult(); test("完整流程-计算成绩", @@ -442,12 +709,12 @@ public class TestMain { System.out.println(quizService.formatResult(result)); - // 步骤6: 保存记录 + // ========== 步骤6: 保存记录 ========== System.out.println("步骤6: 保存答题记录..."); quizService.saveQuizHistory(user); // 验证用户统计是否更新 - User updatedUser = fileService.findUserByUsername("初中-王五"); + User updatedUser = fileService.findUserByUsername(username); test("完整流程-保存记录", updatedUser.getTotalQuizzes() == 1, "用户答题次数应该增加,实际:" + updatedUser.getTotalQuizzes()); @@ -456,7 +723,7 @@ public class TestMain { updatedUser.getAverageScore() == 100.0, "平均分应该更新,实际:" + updatedUser.getAverageScore()); - // 步骤7: 退出登录 + // ========== 步骤7: 退出登录 ========== System.out.println("步骤7: 退出登录..."); userService.logout(); test("完整流程-退出", !userService.isLoggedIn(), "退出登录成功"); -- 2.34.1 From 05b231cafcaeb385469ca5380ee7269e20fe645b Mon Sep 17 00:00:00 2001 From: lsbp <2803234009@qq.com> Date: Sat, 4 Oct 2025 11:02:39 +0800 Subject: [PATCH 5/5] Remove .idea from repo --- .idea/.gitignore | 8 --- .idea/encodings.xml | 7 --- .idea/misc.xml | 14 ----- .idea/uiDesigner.xml | 124 ------------------------------------------- .idea/vcs.xml | 6 --- 5 files changed, 159 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/uiDesigner.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 35410ca..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index aa00ffa..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index fdc35ea..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 2b63946..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file -- 2.34.1