合并到主分支 #1

Merged
hnu202326010210 merged 1 commits from develop into main 1 week ago

38
.gitignore vendored

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

@ -0,0 +1,60 @@
### **项目概述:中小学数学卷子自动生成程序**
这是一个面向中小学数学老师的命令行应用程序。其核心目标是根据教师的账户类型(小学、初中、高中),自动生成符合特定难度要求的数学练习题,并将其保存为文本文件,同时提供用户管理、难度切换和题目查重等辅助功能。
本项目的设计思路均由本人完成ai对于ui方面技术提供了意见支持。
#### **一、 大概功能 **
1. **用户认证系统 **
* 通过命令行输入用户名和密码进行登录。
* 系统预设了小学、初中、高中三类账户。
* 登录成功后,系统会根据账户类型自动设定默认的出题难度。
2. **分级题目生成**
* 用户可指定生成题目的数量10-30题
* 程序会根据当前选择的难度级别(小学、初中、高中)生成题目:
* **小学:** 包含 `+`, `-`, `*`, `/` 以及括号 `()` 的基本四则运算。
* **初中:** 在小学基础上,题目中至少包含一个**平方**或**开根号**运算。
* **高中:** 在初中基础上,题目中至少包含一个**三角函数**`sin`, `cos`, `tan`)。
3. **题目查重机制 **
* 为确保练习的新鲜度,系统会检查新生成的题目。
* 确保同一个老师账户下,新生成的卷子中的题目不与该老师历史上所有已生成文件中的题目重复。
4. **动态难度切换**
* 登录后,用户可以通过特定命令(如 `切换为 高中`)临时改变出题的难度级别,而无需重新登录。
5. **文件保存与管理**
* 生成的题目集将以 `年-月-日-时-分-秒.txt` 的格式命名,确保文件名唯一。
* 每个用户拥有一个独立的文件夹,用于存放其生成的所有题目文件,便于管理和查重。
#### **二、 Java 相关技术栈 **
1. **输入与输出 :**
* **`java.util.Scanner`:** 用于接收和解析来自命令行的用户输入(如用户名、密码、题目数量、切换指令等)。
* **`java.io` 或 `java.nio` API:** 用于文件操作。
* `File`: 创建用户目录、读写文件。
* `FileWriter` / `BufferedWriter`: 将生成的题目高效地写入 `.txt` 文件。
* `FileReader` / `BufferedReader`: 在执行查重功能时,读取历史题目文件。
2. **数据结构与集合 **
* **`java.util.Map` (如 `HashMap`):** 非常适合用于存储和快速查找预设的用户账户信息(用户名 -> 密码,用户名 -> 账户类型)。
* **`java.util.Set` (如 `HashSet`):** 查重功能的核心。在生成新题目之前,将该用户历史文件中的所有题目加载到一个 `Set` 中,可以利用其 `O(1)` 的平均时间复杂度快速判断新题目是否已存在。
* **`java.util.List` (如 `ArrayList`):** 用于临时存储一轮新生成的题目,便于后续处理和写入文件。
3. **数学与随机数 **
* **`java.util.Random`:** 用于生成随机数(作为题目中的操作数)和随机选择运算符,是题目多样性的关键。
* **`java.lang.Math`:** 提供 `Math.sqrt()` (开根号), `Math.pow()` (平方), 以及 `Math.sin()`, `Math.cos()`, `Math.tan()` 等三角函数运算。
4. **日期与时间 (Date & Time):**
* **`java.time.LocalDateTime`:** 获取当前的精确年、月、日、时、分、秒。
* **`java.time.format.DateTimeFormatter`:** 将 `LocalDateTime` 对象格式化为需求中指定的文件名字符串(`"yyyy-MM-dd-HH-mm-ss"`)。

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.scapeSharing</groupId>
<artifactId>Quiz_Generator_personal</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,32 @@
package com.quizgenerator.common;
import lombok.Getter;
/** 三种习题等级. */
@Getter
public enum Level {
PRIMARY("小学"),
JUNIOR_HIGH("初中"),
SENIOR_HIGH("高中");
private final String displayName;
Level(String displayName) {
this.displayName = displayName;
}
/**
* .
*
* @param text s
* @return Level
*/
public static Level fromString(String text) {
for (Level b : Level.values()) {
if (b.displayName.equalsIgnoreCase(text)) {
return b;
}
}
return null;
}
}

@ -0,0 +1,49 @@
package com.quizgenerator.common;
import java.util.Random;
/** 一个用于题目生成的辅助工具类. 包含所有生成器可能共享的静态方法。 */
public final class QuestionUtils {
private static final Random RANDOM = new Random();
private QuestionUtils() {}
/**
* .
*
* @param str
* @return true, false
*/
public static boolean isNotNumeric(String str) {
if (str == null) {
return true; // null 不是数字,所以返回 true
}
try {
Integer.parseInt(str);
return false; // 能成功转换,说明是数字,所以返回 false
} catch (NumberFormatException e) {
return true; // 转换失败,说明不是数字,所以返回 true
}
}
/**
* (+, -, *, /).
*
* @return
*/
public static char getRandomOperator() {
char[] operators = {'+', '-', '*', '/'};
return operators[RANDOM.nextInt(operators.length)];
}
/**
* 1100.
*
* @return
*/
public static int getRandomOperand() {
return RANDOM.nextInt(100) + 1;
}
}

@ -0,0 +1,36 @@
package com.quizgenerator.dao;
import com.quizgenerator.common.Level;
import com.quizgenerator.entity.User;
import java.util.HashMap;
import java.util.Map;
/** 由于数据较少,且不好提交数据库,所以选择写死在代码里面. */
public class UserDataBase {
private static final Map<String, User> userDatabase = new HashMap<>();
static {
// 小学
userDatabase.put("张三1", new User("张三1", "123", Level.PRIMARY));
userDatabase.put("张三2", new User("张三2", "123", Level.PRIMARY));
userDatabase.put("张三3", new User("张三3", "123", Level.PRIMARY));
// 初中
userDatabase.put("李四1", new User("李四1", "123", Level.JUNIOR_HIGH));
userDatabase.put("李四2", new User("李四2", "123", Level.JUNIOR_HIGH));
userDatabase.put("李四3", new User("李四3", "123", Level.JUNIOR_HIGH));
// 高中
userDatabase.put("王五1", new User("王五1", "123", Level.SENIOR_HIGH));
userDatabase.put("王五2", new User("王五2", "123", Level.SENIOR_HIGH));
userDatabase.put("王五3", new User("王五3", "123", Level.SENIOR_HIGH));
}
/**
* .
*
* @param username
* @return Usernull
*/
public static User findByUsername(String username) {
return userDatabase.get(username);
}
}

@ -0,0 +1,30 @@
package com.quizgenerator.entity;
import com.quizgenerator.common.Level;
import lombok.Data;
/** User类. */
@Data
public class User {
private final String username;
private final String password;
private final Level level;
/*public User(String username, String password, Level level) {
this.username = username;
this.password = password;
this.level = level;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public Level getLevel() {
return level;
}*/
}

@ -0,0 +1,12 @@
package com.quizgenerator.generator;
/** 题目生成接口. */
public interface QuestionGenerator {
/**
* .
*
* @return
*/
String generate();
}

@ -0,0 +1,32 @@
package com.quizgenerator.generator.impl;
import com.quizgenerator.common.QuestionUtils;
import com.quizgenerator.generator.QuestionGenerator;
import java.util.Random;
/** 初中题目生成. */
public class JuniorHighGenerator implements QuestionGenerator {
private final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimarySchoolGenerator();
@Override
public String generate() {
// 1.复用primary逻辑
// 可以优化,抽出复用逻辑,不应该让同层的类互相依赖
String baseQuestion = primaryGenerator.generate();
String[] parts = baseQuestion.split(" ");
int indexToModify;
do {
indexToModify = random.nextInt(parts.length);
} while (QuestionUtils.isNotNumeric(parts[indexToModify]));
if (random.nextBoolean()) {
parts[indexToModify] = parts[indexToModify] + "^2";
} else {
parts[indexToModify] = "sqrt(" + parts[indexToModify] + ")";
}
return String.join(" ", parts);
}
}

@ -0,0 +1,31 @@
package com.quizgenerator.generator.impl;
import com.quizgenerator.common.QuestionUtils;
import com.quizgenerator.generator.QuestionGenerator;
import java.util.Random;
/** 小学题目生成. */
public class PrimarySchoolGenerator implements QuestionGenerator {
private final Random random = new Random();
@Override
public String generate() {
int operandCount = random.nextInt(4) + 2;
StringBuilder question = new StringBuilder();
question.append(QuestionUtils.getRandomOperand());
for (int i = 0; i < operandCount - 1; i++) {
question.append(" ").append(QuestionUtils.getRandomOperator()).append(" ");
if (random.nextBoolean() && i < operandCount - 2) {
question.append("( ").append(QuestionUtils.getRandomOperand()).append(" ");
question.append(QuestionUtils.getRandomOperator()).append(" ");
question.append(QuestionUtils.getRandomOperand()).append(" )");
i++;
} else {
question.append(QuestionUtils.getRandomOperand());
}
}
return question.toString();
}
}

@ -0,0 +1,32 @@
package com.quizgenerator.generator.impl;
import com.quizgenerator.common.QuestionUtils;
import com.quizgenerator.generator.QuestionGenerator;
import java.util.Random;
/** 高中题目生成 */
public class SeniorHighGenerator implements QuestionGenerator {
private final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimarySchoolGenerator();
@Override
public String generate() {
String baseQuestion = primaryGenerator.generate();
String[] parts = baseQuestion.split(" ");
int indexToModify;
do {
indexToModify = random.nextInt(parts.length);
} while (QuestionUtils.isNotNumeric(parts[indexToModify]));
String[] trigFuncs = {"sin", "cos", "tan"};
String func = trigFuncs[random.nextInt(trigFuncs.length)];
int[] angles = {30, 45, 60, 90};
int angle = angles[random.nextInt(angles.length)];
parts[indexToModify] = func + "(" + angle + ")";
return String.join(" ", parts);
}
}

@ -0,0 +1,121 @@
package com.quizgenerator.service;
import com.quizgenerator.common.Level;
import com.quizgenerator.generator.QuestionGenerator;
import com.quizgenerator.generator.impl.JuniorHighGenerator;
import com.quizgenerator.generator.impl.PrimarySchoolGenerator;
import com.quizgenerator.generator.impl.SeniorHighGenerator;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
/** 问题核心处理逻辑. */
public class QuestionService {
private final Map<Level, QuestionGenerator> generators = new HashMap<>();
/** 构造函数,初始化题目生成器. */
public QuestionService() {
generators.put(Level.PRIMARY, new PrimarySchoolGenerator());
generators.put(Level.JUNIOR_HIGH, new JuniorHighGenerator());
generators.put(Level.SENIOR_HIGH, new SeniorHighGenerator());
}
/**
* .
*
* @param level
* @param count
* @param username
* @return
*/
public List<String> generateUniqueQuiz(Level level, int count, String username) {
QuestionGenerator generator = generators.get(level);
if (generator == null) {
throw new IllegalArgumentException("无效的等级代号");
}
Set<String> existingQuestions = loadExistingQuestions(username);
List<String> newQuestions = new ArrayList<>();
while (newQuestions.size() < count) {
String question = generator.generate();
if (!existingQuestions.contains(question)) {
newQuestions.add(question);
existingQuestions.add(question);
}
}
return newQuestions;
}
private Set<String> loadExistingQuestions(String username) {
Set<String> questions = new HashSet<>();
File userDir = new File(username);
if (!userDir.exists() || !userDir.isDirectory()) {
return questions;
}
try (Stream<Path> paths = Files.walk(Paths.get(username))) {
paths
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(
path -> {
try {
Files.lines(path)
.forEach(
line -> {
// Extract question text, assuming format "1. question..."
if (line.matches("^\\d+\\.\\s.*")) {
questions.add(line.substring(line.indexOf('.') + 2));
}
});
} catch (IOException e) {
System.err.println("Error reading file: " + path);
}
});
} catch (IOException e) {
System.err.println("Error walking directory: " + username);
}
return questions;
}
/**
* .
*
* @param questions
* @param username
*/
public void saveQuizToFile(List<String> questions, String username) {
File userDir = new File(username);
if (!userDir.exists()) {
userDir.mkdirs(); // Create directory if it doesn't exist
}
String timestamp =
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"));
String filename = username + "/" + timestamp + ".txt";
try (FileWriter writer = new FileWriter(filename)) {
for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i) + "\n");
writer.write("\n"); // Blank line between questions
}
System.out.println("试卷已成功生成并保存至: " + filename);
} catch (IOException e) {
System.err.println("错误:无法保存文件 " + filename);
e.printStackTrace();
}
}
}

@ -0,0 +1,30 @@
package com.quizgenerator.service;
import com.quizgenerator.dao.UserDataBase;
import com.quizgenerator.entity.User;
/** 用户服务. */
public class UserService {
/**
* .
*
* @param username
* @param password
* @return User null
*/
public User login(String username, String password) {
// 1. 调用修改后的 findByUsername。如果用户不存在这里会得到 null
User user = UserDataBase.findByUsername(username);
// 2. 检查用户是否存在user != null并且密码是否匹配
// TODO 使用md5加密密码
if (user != null && user.getPassword().equals(password)) {
// 登录成功,返回找到的 user 对象
return user;
} else {
// 用户不存在或密码错误,返回 null
return null;
}
}
}

@ -0,0 +1,94 @@
package com.quizgenerator.system;
import com.quizgenerator.common.Level;
import com.quizgenerator.entity.User;
import java.util.Scanner;
/** UI界面. */
public class ConsoleUi {
private final Scanner scanner;
/** 输入. */
public ConsoleUi() {
this.scanner = new Scanner(System.in);
}
/** 主界面展示. */
public void displayWelcomeMessage() {
System.out.println("==========================================");
System.out.println("欢迎使用中小学数学卷子自动生成程序");
System.out.println("==========================================");
}
/**
* Prompt.
*
* @return .
*/
public String[] promptForCredentials() {
System.out.print("请输入用户名和密码 (用空格隔开): ");
return scanner.nextLine().split(" ");
}
/** 错误信息. */
public void displayLoginError() {
System.out.println("用户名或密码错误,请重新输入。");
}
/** 格式错误. */
public void displayInvalidInputFormat() {
System.out.println("输入格式不正确,请输入 '用户名 密码'。");
}
/**
* .
*
* @param user .
*/
public void displayLoginSuccess(User user) {
System.out.println("\n登录成功! 当前选择为 " + user.getLevel().getDisplayName() + " 出题。");
}
/**
* .
*
* @param currentLevel .
* @return .
*/
public String promptForMainMenuAction(Level currentLevel) {
System.out.print(
"\n准备生成"
+ currentLevel.getDisplayName()
+ "数学题目, 请输入生成题目数量(10-30, 或输入-1退出登录, 或输入'切换为 XX'): ");
return scanner.nextLine().trim();
}
/** 退出展示. */
public void displayLogoutMessage() {
System.out.println("已退出当前用户,返回登录界面。");
}
/**
* .
*
* @param newLevel .
*/
public void displayLevelChangeSuccess(Level newLevel) {
System.out.println("切换成功! 当前难度已设置为: " + newLevel.getDisplayName());
}
/** 修改错误信息. */
public void displayInvalidLevelChangeCommand() {
System.out.println("无效的切换指令, 请输入'切换为 小学'、'切换为 初中'或'切换为 高中'。");
}
/** 显示无效数字范围的错误信息. */
public void displayInvalidNumberRange() {
System.out.println("输入无效, 题目数量必须在 10 到 30 之间。");
}
/** 显示通用无效输入错误信息. */
public void displayGenericInvalidInput() {
System.out.println("无效输入, 请输入数字、'-1'或切换指令。");
}
}

@ -0,0 +1,42 @@
package com.quizgenerator.system;
import com.quizgenerator.entity.User;
import com.quizgenerator.service.UserService;
/** 登录逻辑. */
public class LoginHandler {
private final UserService userService;
private final ConsoleUi ui;
/**
* .
*
* @param userService .
* @param ui UI.
*/
public LoginHandler(UserService userService, ConsoleUi ui) {
this.userService = userService;
this.ui = ui;
}
/**
* .
*
* @return .
*/
public User performLogin() {
User currentUser = null;
while (currentUser == null) {
String[] credentials = ui.promptForCredentials();
if (credentials.length == 2) {
currentUser = userService.login(credentials[0], credentials[1]);
if (currentUser == null) {
ui.displayLoginError();
}
} else {
ui.displayInvalidInputFormat();
}
}
return currentUser;
}
}

@ -0,0 +1,88 @@
package com.quizgenerator.system;
import com.quizgenerator.common.Level;
import com.quizgenerator.entity.User;
import com.quizgenerator.service.QuestionService;
import java.util.List;
/** 主菜单处理器. */
public class MainMenuHandler {
private final QuestionService questionService;
private final ConsoleUi ui;
/**
* .
*
* @param questionService .
* @param ui UI.
*/
public MainMenuHandler(QuestionService questionService, ConsoleUi ui) {
this.questionService = questionService;
this.ui = ui;
}
/**
* .
*
* @param currentUser .
*/
public void handleMainMenu(User currentUser) {
Level currentLevel = currentUser.getLevel();
while (true) {
String input = ui.promptForMainMenuAction(currentLevel);
if ("-1".equals(input)) {
ui.displayLogoutMessage();
return; // 主菜单-1表示退出
}
if (input.startsWith("切换为")) {
currentLevel = handleChangeLevel(input, currentLevel);
continue;
}
handleQuizGeneration(input, currentLevel, currentUser.getUsername());
}
}
/**
* .
*
* @param input .
* @param defaultLevel .
* @return .
*/
private Level handleChangeLevel(String input, Level defaultLevel) {
String targetLevelStr = input.substring(3).trim();
Level newLevel = Level.fromString(targetLevelStr);
if (newLevel != null) {
ui.displayLevelChangeSuccess(newLevel);
return newLevel;
} else {
ui.displayInvalidLevelChangeCommand();
return defaultLevel;
}
}
/**
* .
*
* @param input .
* @param level .
* @param username .
*/
private void handleQuizGeneration(String input, Level level, String username) {
try {
int count = Integer.parseInt(input);
if (count >= 10 && count <= 30) {
List<String> quiz = questionService.generateUniqueQuiz(level, count, username);
questionService.saveQuizToFile(quiz, username);
} else {
ui.displayInvalidNumberRange();
}
} catch (NumberFormatException e) {
ui.displayGenericInvalidInput();
}
}
}

@ -0,0 +1,49 @@
package com.quizgenerator.system;
import com.quizgenerator.entity.User;
import com.quizgenerator.service.QuestionService;
import com.quizgenerator.service.UserService;
/** 应用程序的主入口类,负责启动和协调整个问答流程. */
public class QuizApplication {
private final LoginHandler loginHandler;
private final MainMenuHandler mainMenuHandler;
private final ConsoleUi consoleUi;
/** 构造 QuizApplication 实例. 负责初始化所有必要的服务和处理器. */
public QuizApplication() {
// 1. 初始化
UserService userService = new UserService();
QuestionService questionService = new QuestionService();
this.consoleUi = new ConsoleUi();
// 2. 初始化处理器
this.loginHandler = new LoginHandler(userService, consoleUi);
this.mainMenuHandler = new MainMenuHandler(questionService, consoleUi);
}
/** 启动并运行应用程序的主循环. 这个方法会持续运行,处理用户登录和主菜单交互. */
public void run() {
consoleUi.displayWelcomeMessage();
while (true) {
// 3. 处理登录流程
User currentUser = loginHandler.performLogin();
// 4. 进入主菜单循环
if (currentUser != null) {
consoleUi.displayLoginSuccess(currentUser);
mainMenuHandler.handleMainMenu(currentUser);
}
}
}
/**
* .
*
* @param args (使).
*/
public static void main(String[] args) {
QuizApplication app = new QuizApplication();
app.run();
}
}
Loading…
Cancel
Save