diff --git a/doc/设计文档/设计文档第三版.md b/doc/设计文档/设计文档第三版.md new file mode 100644 index 0000000..33e458f --- /dev/null +++ b/doc/设计文档/设计文档第三版.md @@ -0,0 +1,219 @@ +# 数学题库生成系统详细设计文档 + +## 项目概述 + +### 项目目标 +为小初高学生提供一个本地的桌面端图像化应用,支持用户注册和登录,根据学生年级按规则随机生成数学选择题,完成答题后自动计算分数 + +### 技术栈 + +| 类型 | 技术 | 说明 | +| -------- | -------------------------- | ---------------------------------------- | +| 开发语言 | Java 21+ | 保证跨平台兼容性,支持 JavaFX | +| GUI框架 | JavaFX 17+ | 实现桌面图形化界面,提供组件化开发能力 | +| 依赖管理 | Maven | 管理 JavaFX、JSON 解析等依赖 | +| JSON解析 | Gson 2.10+ | 序列化 / 反序列化用户数据、题目数据 | +| 邮件发送 | JavaMail API + 第三方 SMTP | 发送注册码(如 QQ 邮箱 / 网易邮箱 SMTP) | +| 数据加密 | BCrypt 0.9.0+ | 加密存储用户密码,避免明文泄露 | +| 构建打包 | Maven Shade Plugin | 打包成可执行 JAR,支持直接运行 | + +### 总体设计 + +#### 项目目录结构 + +MathQuizApp/ +├── src/ +│ └── main/ +│ └── java/ +│ └── com/ +│ └── mathquiz/ +│ ├── Main.java # 程序入口 +│ │ +│ ├── model/ # 数据模型 +│ │ ├── User.java +│ │ ├── Grade.java +│ │ └── ChoiceQuestion.java +│ │ +│ ├── service/ # 后端逻辑(无GUI) +│ │ ├── UserService.java +│ │ ├── QuestionGenerator.java +│ │ ├── FileIOService.java +│ │ └── QuizService.java +│ │ +│ ├── ui/ # 前端 GUI +│ │ ├── MainWindow.java +│ │ ├── RegisterPanel.java +│ │ ├── LoginPanel.java +| | ├── PasswordModifyPanel.java +│ │ ├── GradeSelectPanel.java +│ │ ├── QuizPanel.java +│ │ └── ResultPanel.java +│ │ +│ └── util/ # 工具类 +│ ├── PasswordValidator.java +| ├── EmailUtil.java +│ ├── RandomUtils.java +│ └── FileUtils.java +│ +├── data/ # 运行时生成(不提交) +│ ├── users/ # 用户信息 JSON +│ └── temp_codes/ # 临时注册码文件 +│ +├── pom.xml # Maven 依赖(含 JavaFX) +└── README.md + +## 详细模块设计 + +### 模型层设计 + +#### User类 +- String name // 用户ID +- String email // 用户邮箱 +- String encryptedPwd // 加密后的用户密码 +- Grade grade //学段 + +#### ChoiceQuestion +String questionId; // 题目唯一ID(UUID生成) +Grade grade; // 所属学段 +String questionContent; // 题干 +List options; // 选项列表(固定4个,顺序随机) +String correctAnswer; // 正确答案(如"A"/"B"/"C"/"D") + +#### Grade 枚举 +PRIMARY("小学", 1) +JUNIOR("初中", 2) +SENIOR("高中", 3) + +### 工具层设计 + +#### PasswordValidator + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| -------------------------- | ------------------------------------------------ | --------- | ------------------------------------------------------------ | +| isValid | String password | boolean | 校验密码:**6–10位**,**必须同时包含大写字母、小写字母和数字** | +| encrypt | String password | String | 使用 **SHA-256** 对密码哈希,返回 64 位十六进制字符串 | +| matches | String plainPassword, String encryptedPassword | boolean | 对明文密码加密后与存储的密文比对,判断是否匹配 | +| generateRegistrationCode | — | String | 生成 **6–10位** 随机字符串,**保证至少含1字母+1数字**,并打乱顺序 | +| shuffleString | String str | String | (私有)对字符串字符顺序进行 Fisher-Yates 打乱 | + +#### EmailUtil + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ---------------------- | ----------------------------------------- | --------- | ------------------------------------------------------------ | +| sendRegistrationCode | String toEmail, String registrationCode | boolean | **模拟发送注册码邮件**(实际不发邮件),打印收件人和注册码到控制台,始终返回 true | +| sendPasswordReset | String toEmail, String newPassword | boolean | **预留接口**:模拟发送密码重置邮件,打印信息到控制台,始终返回 true | +| isValidEmail | String email | boolean | 使用正则表达式 ^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ 验证邮箱格式是否合法 | + +#### FileUtils + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ---------------------------- | -------------------------------------- | --------- | ------------------------------------------------------------ | +| readFileToString | String filePath | String | 以 UTF-8 编码读取文件内容为字符串,失败抛出 IOException | +| writeStringToFile | String filePath, String content | void | 以 UTF-8 编码将字符串写入文件(覆盖),失败抛出 IOException | +| createDirectoryIfNotExists | String dirPath | void | 若目录不存在,则递归创建目录,失败抛出 IOException | +| exists | String filePath | boolean | 判断文件或目录是否存在 | +| deleteFile | String filePath | boolean | 删除文件,若文件不存在也返回 true,异常时返回 false 并打印堆栈 | +| listFiles | String dirPath | File[] | 返回目录下所有文件(不含子目录),若路径无效返回空数组 | +| appendToFile | String filePath, String content | void | 以 UTF-8 编码追加内容到文件末尾,自动创建文件(若不存在) | +| copyFile | String sourcePath, String targetPath | void | 复制文件,目标文件存在则覆盖,失败抛出 IOException | +| getFileSize | String filePath | long | 返回文件大小(字节),失败抛出 IOException | + +#### RandomUtils + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| -------------- | ------------------------ | --------- | ---------------------------------------------------------- | +| nextInt | int min, int max | int | 生成 [min, max] 范围内的随机整数(含边界) | +| randomChoice | T[] array | T | 从非空数组中随机返回一个元素 | +| randomChoice | List list | T | 从非空列表中随机返回一个元素 | +| shuffle | List list | void | 原地打乱列表顺序(使用 Collections.shuffle) | +| nextDouble | double min, double max | double | 生成 [min, max) 范围内的随机双精度浮点数 | +| nextBoolean | — | boolean | 返回 true 或 false(各 50% 概率) | +| probability | double probability | boolean | 按指定概率返回 true(如 0.7 表示 70% 概率返回 true) | + +### 服务层设计 + +#### UserService + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| -------------------- | ----------------------------------------------------------- | ------- | ------------------------------------------------------------ | +| sendRegistrationCode | String email | boolean | 生成 6 位数字注册码,调用 EmailUtil.sendTextEmail()发送(开发阶段可模拟),并将注册码写入 data/temp_codes/{email}.txt | +| verifyCode | String email, String code | boolean | 读取 data/temp_codes/{email}.txt,校验注册码是否匹配 | +| setPassword | String email, String pwd1, String pwd2 | boolean | 校验两次密码一致且符合规则(6–10位,含大小写+数字),使用 BCrypt加密后保存用户到 data/users/{hash}.json,成功后删除临时注册码文件 | +| login | String email, String password | User | 读取用户文件,用 BCrypt.matches() 验证密码,返回 User 对象或 null | +| changePassword | String email, String oldPwd, String newPwd1, String newPwd2 | boolean | 先验证原密码正确,再校验新密码格式,更新加密密码并保存 | + +#### QuestionGenerator (抽象类) + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ------------- | ------------- | ------------------- | ------------------------------------------------------------ | +| create | Grade grade | QuestionGenerator | 静态工厂方法,根据年级返回对应子类实例(PrimaryGenerator / MiddleGenerator / SeniorGenerator) | +| generateOne | — | ChoiceQuestion | 抽象方法,由子类实现,生成一道符合年级要求的选择题(含题干、4选项、正确答案) | + +#### FileIOService + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ----------------------- | --------------------------- | ------------- | ------------------------------------------------------------ | +| saveUser | User user | void | 将用户序列化为 JSON,保存到 data/users/{BCrypt.hash(email).substring(0,16)}.json | +| loadUserByEmail | String email | User | 遍历 data/users/ 下所有 JSON 文件,反序列化后匹配邮箱,返回 User 或 null | +| saveCode | String email, String code | void | 写入 data/temp_codes/{email}.txt | +| loadCode | String email | String | 读取 data/temp_codes/{email}.txt,返回注册码或 null | +| loadUsedQuestionStems | String email | Set | 遍历 data/users/{email}/papers/(若存在)下所有 .txt 文件,提取题干(每行以“1.”、“2.”开头的内容),用于查重 | + +#### QuizService + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ------------------- | ---------------------------------------------------------- | ---------------------- | ------------------------------------------------------------ | +| generateQuestions | String email, int count | List | 调用 QuestionGenerator.generateOne() 循环生成,确保题干不重复(使用 loadUsedQuestionStems + 当前卷子 Set) | +| calculateScore | List questions, List userAnswers | int | 比对每题 correctAnswer 与用户答案,计算百分比得分(四舍五入) | +| savePaper | String email, List questions | void | 生成时间戳文件名(yyyy-MM-dd-HH-mm-ss.txt),保存题干到 data/users/{email}/papers/(目录自动创建) | + +### UI层接口设计 + +#### MainWindow + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ---------------------- | --------------------------------------------------------- | ------ | ------------------------------------------------- | +| showPanel | Pane panel | void | 设置 BorderPane.center 为指定面板,实现页面切换 | +| showLoginPanel | — | void | 创建并显示 LoginPanel | +| showRegisterPanel | — | void | 创建并显示 RegisterPanel | +| showGradeSelectPanel | — | void | 创建并显示 GradeSelectPanel | +| showQuizPanel | List questions, QuizService quizService | void | 创建并显示 QuizPanel | +| showResultPanel | int score, Runnable onContinue | void | 创建并显示 ResultPanel | + +#### RegisterPanel + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ---------------- | ----------------------- | ------ | ------------------------------------------------------------ | +| 构造函数 | MainWindow mainWindow | — | 初始化邮箱、注册码、密码输入框和按钮,绑定事件 | +| sendCodeAction | — | void | 调用 mainWindow.getUserService().sendRegistrationCode(),提示“注册码已发送(模拟)” | +| registerAction | — | void | 调用 setPassword(),成功则跳转到年级选择页 | + +#### LoginPanel + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ------------- | ----------------------- | ------ | ----------------------------------------------------------- | +| 构造函数 | MainWindow mainWindow | — | 初始化登录表单,绑定“登录”按钮 | +| loginAction | — | void | 调用 login(),成功则设置 currentUser 并跳转到年级选择页 | + +#### GradeSelectPanel + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ----------- | ----------------------- | ------ | ------------------------------------------------------------ | +| 构造函数 | MainWindow mainWindow | — | 创建三个年级按钮 | +| startQuiz | Grade grade | void | 弹出数量输入对话框(10–30),调用 QuizService.generateQuestions(),跳转到答题页 | + +#### QuizPanel + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| -------------- | ------------------------------------------------------------ | ------ | ------------------------------------------------------------ | +| 构造函数 | MainWindow mainWindow, List questions, QuizService quizService | — | 显示第1题 | +| showQuestion | int index | void | 渲染当前题干和4个选项(RadioButton),绑定“提交”按钮 | +| submitAction | — | void | 记录答案,若非最后一题则显示下一题,否则计算分数并跳转结果页 | + +#### ResultPanel + +| 方法名 | 参数列表 | 返回值 | 逻辑描述 | +| ---------------- | ------------------------------------------------------- | ------ | ----------------------------------------------------- | +| 构造函数 | MainWindow mainWindow, int score, Runnable onContinue | — | 显示得分,绑定“退出”和“继续做题”按钮 | +| exitAction | — | void | 跳转到登录页 | +| continueAction | — | void | 执行 onContinue.run()(保存试卷),跳转到年级选择页 | \ No newline at end of file diff --git a/src/main/java/com/mathquiz/model/ChoiceQuestion.java b/src/main/java/com/mathquiz/model/ChoiceQuestion.java index e69de29..e3dcb16 100644 --- a/src/main/java/com/mathquiz/model/ChoiceQuestion.java +++ b/src/main/java/com/mathquiz/model/ChoiceQuestion.java @@ -0,0 +1,141 @@ +package com.mathquiz.model; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * 选择题模型类 + * 表示一道完整的数学选择题,包含题干、选项、正确答案等信息 + */ +public class ChoiceQuestion { + + /** + * 题目唯一ID(UUID生成) + */ + private String questionId; + + /** + * 所属学段(小学 / 初中 / 高中) + */ + private Grade grade; + + /** + * 题干内容,例如:"2 + 3 × (4 - 1) = ?" + */ + private String questionContent; + + /** + * 选项列表,固定4个选项,顺序已随机打乱 + * 格式示例:["11", "10", "13", "9"] + */ + private List options; + + /** + * 正确答案标识,取值为 "A"、"B"、"C" 或 "D" + */ + private String correctAnswer; + + // ---------------- 构造函数 ---------------- + + /** + * 默认构造函数(用于 JSON 反序列化) + */ + public ChoiceQuestion() {} + + /** + * 全参构造函数 + */ + public ChoiceQuestion(String questionId, Grade grade, String questionContent, + List options, String correctAnswer) { + this.questionId = questionId; + this.grade = grade; + this.questionContent = questionContent; + this.options = options; + this.correctAnswer = correctAnswer; + } + + // ---------------- 静态工厂方法 ---------------- + + /** + * 创建一道新题目,自动分配 UUID 作为 questionId + * + * @param grade 年级 + * @param questionContent 题干 + * @param options 4个选项(顺序应已打乱) + * @param correctAnswer 正确答案("A"-"D") + * @return 新的 ChoiceQuestion 实例 + */ + public static ChoiceQuestion of(Grade grade, String questionContent, + List options, String correctAnswer) { + return new ChoiceQuestion(UUID.randomUUID().toString(), grade, questionContent, options, correctAnswer); + } + + // ---------------- Getter & Setter ---------------- + + public String getQuestionId() { + return questionId; + } + + public void setQuestionId(String questionId) { + this.questionId = questionId; + } + + public Grade getGrade() { + return grade; + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public String getQuestionContent() { + return questionContent; + } + + public void setQuestionContent(String questionContent) { + this.questionContent = questionContent; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public void setCorrectAnswer(String correctAnswer) { + this.correctAnswer = correctAnswer; + } + + // ---------------- Object 方法 ---------------- + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ChoiceQuestion that = (ChoiceQuestion) o; + return Objects.equals(questionId, that.questionId); + } + + @Override + public int hashCode() { + return Objects.hash(questionId); + } + + @Override + public String toString() { + return "ChoiceQuestion{" + + "questionId='" + questionId + '\'' + + ", grade=" + grade + + ", questionContent='" + questionContent + '\'' + + ", options=" + options + + ", correctAnswer='" + correctAnswer + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/mathquiz/model/Grade.java b/src/main/java/com/mathquiz/model/Grade.java index e69de29..ede720f 100644 --- a/src/main/java/com/mathquiz/model/Grade.java +++ b/src/main/java/com/mathquiz/model/Grade.java @@ -0,0 +1,27 @@ +package com.mathquiz.model; + +/** + * 年级枚举,表示小学、初中、高中三个学段 + */ + +public enum Grade { + PRIMARY("小学", 1), + JUNIOR("初中", 2), + SENIOR("高中", 3); + + private final String displayName; + private final int level; + + Grade(String displayName, int level) { + this.displayName = displayName; + this.level = level; + } + + public String getDisplayName() { + return displayName; + } + + public int getLevel() { + return level; + } +} \ No newline at end of file diff --git a/src/main/java/com/mathquiz/model/User.java b/src/main/java/com/mathquiz/model/User.java index e69de29..d519167 100644 --- a/src/main/java/com/mathquiz/model/User.java +++ b/src/main/java/com/mathquiz/model/User.java @@ -0,0 +1,52 @@ +package com.mathquiz.model; + +/** + * 用户类 + */ + +public class User { + private String name; + private String email; + private String encryptedPwd; + private Grade grade; + + public User() {} + + public User(String email, String encryptedPwd) { + this.email = email; + this.encryptedPwd = encryptedPwd; + } + + // Getter & Setter + public java.lang.String getName() { + return name; + } + + public void setName(java.lang.String name) { + this.name = name; + } + + public java.lang.String getEmail() { + return email; + } + + public void setEmail(java.lang.String email) { + this.email = email; + } + + public java.lang.String getEncryptedPwd() { + return encryptedPwd; + } + + public void setEncryptedPwd(java.lang.String encryptedPwd) { + this.encryptedPwd = encryptedPwd; + } + + public Grade getGrade() { + return grade; + } + + public void setGrade(Grade grade) { + this.grade = grade; + } +} \ No newline at end of file diff --git a/src/main/java/com/mathquiz/util/EmailUtil.java b/src/main/java/com/mathquiz/util/EmailUtil.java new file mode 100644 index 0000000..ff915ac --- /dev/null +++ b/src/main/java/com/mathquiz/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/mathquiz/util/FileUtils.java b/src/main/java/com/mathquiz/util/FileUtils.java new file mode 100644 index 0000000..d4daf9e --- /dev/null +++ b/src/main/java/com/mathquiz/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/mathquiz/util/PasswordValidator.java b/src/main/java/com/mathquiz/util/PasswordValidator.java new file mode 100644 index 0000000..40a26b5 --- /dev/null +++ b/src/main/java/com/mathquiz/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/mathquiz/util/RandomUtils.java b/src/main/java/com/mathquiz/util/RandomUtils.java new file mode 100644 index 0000000..c7351b9 --- /dev/null +++ b/src/main/java/com/mathquiz/util/RandomUtils.java @@ -0,0 +1,69 @@ +package com.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)]; + } + + + //从列表中随机选择一个元素 + 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); + } + + + //生成指定范围内的随机双精度浮点数 + public static double nextDouble(double min, double max) { + if (min > max) { + throw new IllegalArgumentException("min不能大于max"); + } + return min + (max - min) * random.nextDouble(); + } + + + //生成随机布尔值 + public static boolean nextBoolean() { + return random.nextBoolean(); + } + + //按概率返回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之间"); + } + return random.nextDouble() < probability; + } +} \ No newline at end of file