Compare commits

...

2 Commits

Author SHA1 Message Date
fangqiongMa 30b592507c v1.0
4 months ago
fangqiongMa 35cda96a76 v1.0
4 months ago

@ -0,0 +1,111 @@
# 中小学数学题生成系统 README
## 一、项目介绍
本系统是一款面向中小学场景的数学题自动生成工具,支持按学段(小学/初中/高中)生成对应难度题目,集成登录验证、学段切换、重复题目检测、本地保存等功能,满足教学练习中的题目生成需求。
## 二、运行环境要求
| 依赖项 | 版本要求 | 说明 |
|--------------|-------------------------|--------------------------|
| JDK | JDK 1.8 及以上 | 需配置 `JAVA_HOME` 环境变量 |
| 运行载体 | 可执行 JAR / IntelliJ IDEA | 推荐直接运行 JAR 包 |
| 操作系统 | Windows 10/11、macOS | 跨平台兼容 |
## 三、核心功能说明
### 1. 登录验证
- **预设测试账户**(按学段分类):
- 小学:`张三1/123`、`张三2/123`、`张三3/123`
- 初中:`李四1/123`、`李四2/123`、`李四3/123`
- 高中:`王五1/123`、`王五2/123`、`王五3/123`
- **输入校验**:支持用户名密码格式检测,错误时提示“输入格式错误”或“用户名/密码错误”。
### 2. 题目生成(按学段区分)
- **小学题**:无负数、可整除的加减乘除运算(支持括号,如 `(4 + 6) × 3`
- **初中题**:小学题基础上新增平方(`^2`)、根号(`√`),如 `5^2 - √16`
- **高中题**:小学题基础上新增三角函数(`sin`/`cos`/`tan`),如 `tan(45) + 8`
- **数量限制**:每次生成 10-30 道题,超出范围会提示调整。
### 3. 学段切换
- **操作逻辑**登录后选择“1-切换学段”或“0-保持当前学段”,支持小学/初中/高中三选一。
- **错误处理**输入非“0/1”或非学段名称如“大学”提示重新输入直至合法。
### 4. 重复题目检测(查重)
- **检测范围**:自动扫描当前用户文件夹下所有历史 `.txt` 题目文件。
- **匹配规则**:排除题号干扰,仅精确对比题目内容(如 `2 + 3` 不会与 `12 + 3` 误判重复)。
- **处理方式**:生成重复题目时自动重新生成,确保输出题目无重复。
### 5. 题目保存
- **保存路径**:项目根目录自动创建“用户名”文件夹(如 `张三1`),题目保存在该文件夹内。
- **文件名格式**`yyyy-MM-dd-HH-mm-ss.txt`(时间戳命名,避免文件覆盖)。
- **内容格式**(带题号+空行分隔):
```
1. 3 + 7 × 2
2. sin(30) + √9
```
## 四、使用步骤
### 方式1直接运行 JAR 包(推荐)
1. **获取 JAR 包**:从项目 `out/artifacts/` 目录复制 `MyProject.jar` 到目标文件夹(如 `D:\MathTool`)。
2. **打开命令行**
- Windows`Win + R` 输入 `cmd`,进入 JAR 所在目录(示例:`cd D:\MathTool`)。
- 解决中文乱码:执行 `chcp 65001`(切换 cmd 编码为 UTF-8
3. **启动系统**:执行 `java -jar MyProject.jar`
4. **操作流程**
① 输入预设账户(如 `张三1 123`)登录;
② 选择“0-保持学段”或“1-切换学段”;
③ 输入题目数量10-30等待生成保存
④ 生成后可选择“-2 继续出题”“-1 返回主页”“-3 退出系统”。
### 方式2IntelliJ IDEA 中运行
1. **导入项目**:打开 IDEA → 选择“Open” → 导入项目文件夹(`MyProject`)。
2. **配置 JDK**`File → Project Structure → Project SDK` 选择 JDK 1.8 及以上。
3. **启动主类**:找到 `src/Main.java` → 右键“Run Main.main()”。
4. **后续操作**同“方式1”步骤4。
## 五、项目目录结构
```
MyProject/
├─ src/ # 源码目录
│ ├─ account/ # 登录相关类
│ │ ├─ Account.java # 账户实体(用户名/密码/学段)
│ │ ├─ AccountService.java # 登录接口
│ │ └─ SimpleAccountService.java # 登录实现(预设账户)
│ ├─ persistence/ # 持久化相关类
│ │ ├─ FileSaver.java # 题目保存类
│ │ └─ QuestionChecker.java # 查重类
│ ├─ question/ # 题目生成相关类
│ │ ├─ Question.java # 题目实体(内容/答案)
│ │ ├─ QuestionGenerator.java # 题目生成接口
│ │ ├─ PrimaryQuestionGenerator.java # 小学题生成
│ │ ├─ MiddleQuestionGenerator.java # 初中题生成
│ │ └─ HighQuestionGenerator.java # 高中题生成
│ └─ Main.java # 程序入口(主类)
├─ out/ # 编译输出目录
│ └─ artifacts/ # JAR 包输出目录MyProject.jar
└─ README.md # 项目说明文档
```
## 六、常见问题FAQ
### Q1运行 JAR 提示“找不到或无法加载主类”?
A1重新通过 IDEA 导出 JAR`File → Project Structure → Artifacts → Build`),确保导出路径正确。
### Q2输入中文用户名/密码后显示乱码?
A2在 cmd 中先执行 `chcp 65001` 切换编码,再运行 JAR。
### Q3账户正确却提示“登录失败”
A3确认输入时用 **半角空格**(英文空格)分隔用户名和密码,避免全角空格(中文空格)。
### Q4生成题目后找不到文件
A4文件保存在“项目根目录/用户名/时间戳.txt”示例`MyProject/张三1/2024-05-20-15-40-00.txt`。
## 七、代码规范说明
1. **设计原则**:采用“接口先行”模式(如 `AccountService`、`QuestionGenerator`),降低模块耦合。
2. **方法行数**:所有方法行数 ≤ 40 行,通过拆分逻辑(如 `generateAndSaveQuestions`)提升可读性。
3. **容错处理**:包含输入校验、异常捕获(如 `NumberFormatException`)、空值判断,避免程序崩溃。
4. **命名规范**:遵循 Java 标准(类名 PascalCase、方法名 camelCase语义化命名`switchType`、`questionLoop`)。

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: Main

@ -0,0 +1,162 @@
import account.Account;
import account.AccountService;
import account.SimpleAccountService;
import persistence.FileSaver;
import persistence.QuestionChecker;
import question.Question;
import question.QuestionGenerator;
import question.PrimaryQuestionGenerator;
import question.MiddleQuestionGenerator;
import question.HighQuestionGenerator;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
private static final AccountService accountService = new SimpleAccountService();
private static final Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
while (true) {
System.out.println("欢迎来到题目生成系统!");
System.out.println("请输入用户名与密码(空格分隔):");
String[] parts = scanner.nextLine().split(" ");
if (parts.length != 2) {
System.out.println("输入格式错误!请重新输入!");
continue;
}
Account account = accountService.login(parts[0], parts[1]);
if (account == null) {
System.out.println("登录失败!用户名或密码错误!");
continue;
}
generator(account.getUserType(), account.getUserName());//登录成功 进入出题循环系统
}
}
/**
*
* @param type
* @param userName
*/
private static void generator(String type, String userName) {
while (true) {
System.out.println("当前类型为 " + type + " 请选择是否切换类型1-切换0-保持):");
String switchInput = scanner.nextLine().trim();
if (switchInput.equals("1")) {
String newType = switchType(type);//出题类型切换器
System.out.println("当前选择为 " + newType + " 出题");
//切换新类型出题
if (!questionLoop(newType, userName)) {
return;
}
} else if (switchInput.equals("0")) {
System.out.println("当前选择为 " + type + " 出题");
//使用原类型出题
if (!questionLoop(type, userName)) {
return;
}
} else {
System.out.println("输入错误请输入0或1");
}
}
}
/**
*
* @param userType
* @return
*/
private static String switchType(String userType) {
while (true){
System.out.println("请输入新的类型(小学/初中/高中):");
String newType = scanner.nextLine().trim();
if (!newType.equals("小学") && !newType.equals("初中") && !newType.equals("高中")) {
System.out.println("输入错误!请重新输入!");
continue;
}
return newType;
}
}
/**
*
* @param userType
* @param username
* @return
*/
private static boolean questionLoop(String userType, String username) {
while (true) {
System.out.println("请输入生成题目数量数量限制10-30-1返回主页【重新登录】-2继续出题-3退出系统");
String input = scanner.nextLine().trim();
if (input.equals("-1")) {
return false;
} else if (input.equals("-2")) {
return true;
} else if (input.equals("-3")) {
System.exit(0);
}
try {
int count = Integer.parseInt(input);
if (count < 10 || count > 30) {
System.out.println("题目数量必须在10-30之间");
continue;
}
generateAndSaveQuestions(userType, username, count);
} catch (NumberFormatException e) {
System.out.println("请输入有效数字!");//防止用户生成非数字时程序崩溃
}
}
}
/**
*
* @param userType
* @param username
* @param count
*/
private static void generateAndSaveQuestions(String userType, String username, int count) {
QuestionGenerator generator = getGenerator(userType);//匹配生成器类型
List<Question> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
Question q = generator.generateQuestion();//进行相应类型出题
if (!QuestionChecker.isDuplicate(username, q.getQuestion())) {
questions.add(q);//进行查重处理 未重复则加入到题目队列中
} else {
i--;
}
}
try {
FileSaver.saveQuestions(username, questions);//保存题目到文件夹
} catch (Exception e) {
System.out.println("题目保存失败!");
}
}
/**
*
* @param userType
* @return
*/
private static QuestionGenerator getGenerator(String userType) {
switch (userType) {
case "小学": return new PrimaryQuestionGenerator();
case "初中": return new MiddleQuestionGenerator();
case "高中": return new HighQuestionGenerator();
default: return new PrimaryQuestionGenerator();
}
}
}

@ -0,0 +1,30 @@
package account;
public class Account {
private String userName;
private String password;
private String userType;
public Account(String userName, String password,String userType) {
this.userName = userName;
this.password = password;
this.userType = userType;
}
public String getUserType() {
return userType;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
public void setUserType(String userType) {
this.userType = userType;
}
}

@ -0,0 +1,5 @@
package account;
public interface AccountService {
Account login(String username, String password);
}

@ -0,0 +1,30 @@
package account;
import java.util.HashMap;
import java.util.Map;
public class SimpleAccountService implements AccountService {
private static final Map<String, Account> accounts = new HashMap<>();
static {
accounts.put("张三1", new Account("张三1", "123", "小学"));
accounts.put("张三2", new Account("张三2", "123", "小学"));
accounts.put("张三3", new Account("张三3", "123", "小学"));
accounts.put("李四1", new Account("李四1", "123", "初中"));
accounts.put("李四2", new Account("李四2", "123", "初中"));
accounts.put("李四3", new Account("李四3", "123", "初中"));
accounts.put("王五1", new Account("王五1", "123", "高中"));
accounts.put("王五2", new Account("王五2", "123", "高中"));
accounts.put("王五3", new Account("王五3", "123", "高中"));
}
@Override
public Account login(String username, String password) {
Account account = accounts.get(username);
if (account != null && account.getPassword().equals(password)) {
return account;
}
return null;
}
}

@ -0,0 +1,29 @@
package persistence;
import question.Question;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
public class FileSaver {
public static void saveQuestions(String username, List<Question> questions) {
File dir = new File(username);
if (!dir.exists()) dir.mkdirs();
String filename = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
try (FileWriter writer = new FileWriter(new File(dir, filename))) {
for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i).getQuestion() + "\n\n");
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("题目已生成并保存!");
}
}

@ -0,0 +1,43 @@
package persistence;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class QuestionChecker {
public static boolean isDuplicate(String username, String question) {
File dir = new File(username);
if (!dir.exists()) return false;
File[] files = dir.listFiles();
if (files == null) return false;
// 待查题目去掉空格,方便精确匹配
String target = question.trim();
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".txt")) {
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.isEmpty()) continue;// 跳过空行
// 去掉题号部分(如 "1. "
int dotIndex = line.indexOf('.');
if (dotIndex != -1) {
String storedQuestion = line.substring(dotIndex + 1).trim();
// 精确匹配
if (storedQuestion.equals(target)) {
return true;
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
return false;
}
}

@ -0,0 +1,26 @@
package question;
import java.util.Random;
public class HighQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
@Override
public Question generateQuestion() {
Question q = primaryGenerator.generateQuestion();
String[] funcs = {"sin", "cos", "tan"};
String func = funcs[random.nextInt(funcs.length)];
return new Question(func + "(" + q.getQuestion() + ")", evaluate(func, q.getQuestion()));
}
private String evaluate(String func, String expr) {
try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval("Math." + func + "(Math.toRadians(" + expr + "))"));
} catch (Exception e) {
return "?";
}
}
}

@ -0,0 +1,28 @@
package question;
import java.util.Random;
public class MiddleQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
private final QuestionGenerator primaryGenerator = new PrimaryQuestionGenerator();
@Override
public Question generateQuestion() {
Question q = primaryGenerator.generateQuestion();
if (random.nextBoolean()) {
return new Question(q.getQuestion() + " ^ 2", evaluate(q.getQuestion() + "^2"));
} else {
return new Question("√" + q.getQuestion(), evaluate("Math.sqrt(" + q.getQuestion() + ")"));
}
}
private String evaluate(String expr) {
try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval(expr.replace("^", "**")));
} catch (Exception e) {
return "?";
}
}
}

@ -0,0 +1,65 @@
package question;
import java.util.Random;
public class PrimaryQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
@Override
public Question generateQuestion() {
int numOperands = random.nextInt(5) + 1;
StringBuilder sb = new StringBuilder();
int currentValue = random.nextInt(100) + 1;
sb.append(currentValue);
for (int i = 1; i < numOperands; i++) {
String[] ops = {"+", "-", "*", "/"};
String op;
int nextVal;
do {
op = ops[random.nextInt(ops.length)];
nextVal = random.nextInt(100) + 1;
} while ((op.equals("-") && currentValue < nextVal) ||
(op.equals("/") && (nextVal == 0 || currentValue % nextVal != 0)));
sb.append(" ").append(op).append(" ").append(nextVal);
switch (op) {
case "+": currentValue += nextVal; break;
case "-": currentValue -= nextVal; break;
case "*": currentValue *= nextVal; break;
case "/": currentValue /= nextVal; break;
}
}
String expr = addParentheses(sb.toString());
return new Question(expr, evaluate(expr));
}
private String addParentheses(String expr) {
String[] tokens = expr.split(" ");
if (tokens.length < 3) return expr;
int start = random.nextInt(tokens.length / 2) * 2;
int end = start + 2 + random.nextInt((tokens.length - start) / 2) * 2;
end = Math.min(end, tokens.length - 1);
StringBuilder newExpr = new StringBuilder();
for (int i = 0; i < tokens.length; i++) {
if (i == start) newExpr.append("(");
newExpr.append(tokens[i]);
if (i == end) newExpr.append(")");
if (i != tokens.length - 1) newExpr.append(" ");
}
return newExpr.toString();
}
private String evaluate(String expr) {
try {
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
return String.valueOf(engine.eval(expr));
} catch (Exception e) {
return "?";
}
}
}

@ -0,0 +1,20 @@
package question;
public class Question {
private String question;
private String answer;
public Question(String question, String answer) {
this.question = question;
this.answer = answer;
}
public String getQuestion() {
return question;
}
public String getAnswer() {
return answer;
}
}

@ -0,0 +1,5 @@
package question;
public interface QuestionGenerator {
Question generateQuestion();
}
Loading…
Cancel
Save