工具层与模型层初版 #2

Merged
hnu202326010319 merged 4 commits from LiangJunYaoBranch into develop 4 months ago

@ -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; // 题目唯一IDUUID生成
Grade grade; // 所属学段
String questionContent; // 题干
List<String> options; // 选项列表固定4个顺序随机
String correctAnswer; // 正确答案(如"A"/"B"/"C"/"D"
#### Grade 枚举
PRIMARY("小学", 1)
JUNIOR("初中", 2)
SENIOR("高中", 3)
### 工具层设计
#### PasswordValidator
| 方法名 | 参数列表 | 返回值 | 逻辑描述 |
| -------------------------- | ------------------------------------------------ | --------- | ------------------------------------------------------------ |
| isValid | String password | boolean | 校验密码:**610位****必须同时包含大写字母、小写字母和数字** |
| encrypt | String password | String | 使用 **SHA-256** 对密码哈希,返回 64 位十六进制字符串 |
| matches | String plainPassword, String encryptedPassword | boolean | 对明文密码加密后与存储的密文比对,判断是否匹配 |
| generateRegistrationCode | — | String | 生成 **610位** 随机字符串,**保证至少含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<T> list | T | 从非空列表中随机返回一个元素 |
| shuffle | List<T> 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 | 校验两次密码一致且符合规则610位含大小写+数字),使用 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<String> | 遍历 data/users/{email}/papers/(若存在)下所有 .txt 文件提取题干每行以“1.”、“2.”开头的内容),用于查重 |
#### QuizService
| 方法名 | 参数列表 | 返回值 | 逻辑描述 |
| ------------------- | ---------------------------------------------------------- | ---------------------- | ------------------------------------------------------------ |
| generateQuestions | String email, int count | List<ChoiceQuestion> | 调用 QuestionGenerator.generateOne() 循环生成,确保题干不重复(使用 loadUsedQuestionStems + 当前卷子 Set |
| calculateScore | List<ChoiceQuestion> questions, List<String> userAnswers | int | 比对每题 correctAnswer 与用户答案,计算百分比得分(四舍五入) |
| savePaper | String email, List<ChoiceQuestion> 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<ChoiceQuestion> 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 | 弹出数量输入对话框1030调用 QuizService.generateQuestions(),跳转到答题页 |
#### QuizPanel
| 方法名 | 参数列表 | 返回值 | 逻辑描述 |
| -------------- | ------------------------------------------------------------ | ------ | ------------------------------------------------------------ |
| 构造函数 | MainWindow mainWindow, List<ChoiceQuestion> 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()(保存试卷),跳转到年级选择页 |

@ -0,0 +1,141 @@
package com.mathquiz.model;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
*
*
*/
public class ChoiceQuestion {
/**
* IDUUID
*/
private String questionId;
/**
* / /
*/
private Grade grade;
/**
* "2 + 3 × (4 - 1) = ?"
*/
private String questionContent;
/**
* 4
* ["11", "10", "13", "9"]
*/
private List<String> options;
/**
* "A""B""C" "D"
*/
private String correctAnswer;
// ---------------- 构造函数 ----------------
/**
* JSON
*/
public ChoiceQuestion() {}
/**
*
*/
public ChoiceQuestion(String questionId, Grade grade, String questionContent,
List<String> 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<String> 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<String> getOptions() {
return options;
}
public void setOptions(List<String> 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 + '\'' +
'}';
}
}

@ -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;
}
}

@ -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;
}
}

@ -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
* <dependency>
* <groupId>javax.mail</groupId>
* <artifactId>javax.mail-api</artifactId>
* <version>1.6.2</version>
* </dependency>
* <dependency>
* <groupId>com.sun.mail</groupId>
* <artifactId>javax.mail</artifactId>
* <version>1.6.2</version>
* </dependency>
*
* 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;
* }
* }
*/

@ -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));
}
}

@ -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);
}
}

@ -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> T randomChoice(T[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("数组不能为空");
}
return array[random.nextInt(array.length)];
}
//从列表中随机选择一个元素
public static <T> T randomChoice(List<T> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException("列表不能为空");
}
return list.get(random.nextInt(list.size()));
}
/**
*
* @param list
*/
public static <T> void shuffle(List<T> 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;
}
}
Loading…
Cancel
Save