最新版本 #3

Merged
hnu202326010318 merged 10 commits from develop into main 4 months ago

3
.gitignore vendored

@ -4,4 +4,5 @@ out/
bin/
questions/
generated_papers/
target/
target/
users.json

@ -1,120 +1,75 @@
# 个人项目:中小学数学卷子自动生成程序
**用户**:小学、初中和高中数学老师。
## 功能需求
1. **登录功能**
- 命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表)。
- 如果用户名和密码都正确,将根据账户类型显示:“当前选择为 XX出题”其中 XX 为“小学”、“初中”或“高中”中的一个。
- 否则提示:“请输入正确的用户名、密码”,并重新输入用户名和密码。
2. **出题准备与数量输入**
- 登录后,系统提示:“准备生成 XX 数学题目,请输入生成题目数量(输入 -1 将退出当前用户,重新登录):”,其中 XX 为账户对应的学段(小学、初中或高中)。
- 每道题目的操作数在 15 个之间,操作数取值范围为 1100。
3. **题目数量输入规则**
- 题目数量的有效输入范围是 1030包含 10 和 30或输入 -1 退出当前登录状态,返回登录界面。
- 程序根据输入的题目数量,生成符合对应学段难度要求的题目卷子(具体要求见附表)。
- 同一个老师的卷子中的题目不能与以前已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见第 5 条)。
4. **难度切换功能**
- 在登录状态下,用户可输入“切换为 XX”来更改出题类型其中 XX 为“小学”、“初中”或“高中”中的一个。
- 若输入不符合要求,程序提示:“请输入小学、初中和高中三个选项中的一个”。
- 输入正确后,显示:“准备生成 XX 数学题目,请输入生成题目数量”,用户输入题目数量后,系统按新设置的类型生成题目。
5. **题目保存规则**
- 生成的题目以“年-月-日-时-分-秒.txt”的格式命名并保存。
- 每个账号拥有独立的文件夹用于保存其生成的题目文件。
- 每道题目需带题号,每题之间空一行。
6. **提交要求**
- 2025年9月29日晚上10点前将个人项目代码提交至头歌分组作业-个人项目/src。
- 代码检查以 `master` 分支的最后版本为准。
- 提交作业时,作业描述中需填写学生姓名、学号和班级信息。
- “个人项目使用大模型后人工修改代码一览表”见附表4和个人项目可执行文件需以附件形式上传。
- **迟交扣分规则**
- 迟交2天及以内者每天扣20%
- 迟交2天及以上者成绩为0分。
7. **头歌代码托管平台提交规范**
- 项目命名为:`X班_姓名_个人项目`
- 项目下包含两个文件夹:`src` 和 `doc`
- 项目代码放入 `src` 目录下
- 文档说明md格式放入 `doc` 目录
- 建立 `master``develop` 分支
- 稳定版本放在 `master` 分支
- 最新版本放在 `develop` 分支
---
## 附表-1账户、密码
| 账户类型 | 账户 | 密码 | 备注 |
|----------|----------|------|------|
| 小学 | 张三1 | 123 | |
| 小学 | 张三2 | 123 | |
| 小学 | 张三3 | 123 | |
| 初中 | 李四1 | 123 | |
| 初中 | 李四2 | 123 | |
| 初中 | 李四3 | 123 | |
| 高中 | 王五1 | 123 | |
| 高中 | 王五2 | 123 | |
| 高中 | 王五3 | 123 | |
---
## 附表-2小学、初中、高中题目难度要求
| 学段 | 难度要求 | 备注 |
|------|----------|------|
| 小学 | `+`, `-`, `*`, `/` | 只能有 `+`, `-`, `*`, `/``()` |
| 初中 | 平方,开根号 | 题目中至少有一个平方或开根号的运算符 |
| 高中 | sin, cos, tan | 题目中至少有一个 sin, cos 或 tan 的运算符 |
---
## 附表-3功能评分细则
| 评分类别 | 扣分原因 | 扣分值100分制 |
|------|------------------------------------|------------|
| 登录 | 登陆没有文字提示 | 5 |
| 登录 | 其他 BUG | 5 |
| 出题 | 初中题目不满足要求 | 5 |
| 出题 | 生成的题目没有题号 | 5 |
| 出题 | 生成的题目没有换行 | 5 |
| 出题 | 切换为 XX 时,提示信息错误 | 5 |
| 出题 | 当切换为除小初高以外的选项,出现提示后,即使输入正确信息也无法再切换 | 5 |
| 出题 | 切换为高中时,生成的题目没有包含三角函数 | 5 |
| 出题 | 每次登录只能出题一次 | 5 |
| 出题 | 其他 BUG | 5 |
| 查重 | 没有查重功能 | 10 |
| 查重 | 其他 BUG | 5 |
| 切换 | 切换难度在输入错误后,再输错出现越界异常 | 5 |
| 切换 | 生成题目之后,无法退回主页 | 5 |
| 切换 | 切换难度不符合要求时未处理 | 5 |
| 切换 | 无切换功能 | 1 |
| 切换 | 其他 BUG | 5 |
| 保存 | 保存绝对路径 | 5 |
| 保存 | 文件名与需求不一致 | 5 |
| 保存 | 其他 BUG | 5 |
| 代码规范 | 参见相应语言的代码规范 | 5/处 |
| 设计合理 | 没有先定义接口类或抽象类,直接使用具体类 | 10 |
| 设计合理 | 类数量 = 1 | 10 |
| 设计合理 | 方法代码行数 > 40行 | 10 |
---
## 附表-4个人项目使用大模型后人工修改代码一览表
| 所在源代码文件名 | 人工修改代码(标红) | 修改原因 |
|------------------|:----------------------:|----------|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
> **注**:新增的语句,以及大模型生成的语句作了任何修改,都算人工修改代码。但表里面不要出现完全是大模型生成后原封不动的代码。
# 中小学数学卷子自动生成程序
本项目是一个基于 Java 开发的命令行应用CLI旨在为小学、初中和高中的数学老师提供一个便捷的工具用于自动生成符合教学要求的数学练习卷。
程序设计遵循了现代软件工程原则通过模块化和面向对象的设计实现了UI与逻辑分离、高内聚、低耦合以及良好的可扩展性。
## 主要功能
* **多学段支持**:可为 **小学**、**初中**、**高中** 三个不同学段生成题目。
* **分级与混合难度**
* 题目难度逐级递增,高年级题目会自动包含低年级知识点(例如,高中题可能包含平方、开根号等)。
* 生成的试卷会自动混合不同难度的题目(例如,一份高中试卷会包含一定比例的初中和小学基础题),更贴近真实考试。
* **历史题目查重**:确保为同一位老师生成的题目不会与历史试卷中的题目重复,保证每次练习都是全新的。
* **菜单式交互界面**:提供清晰、易用的数字菜单进行操作,无需记忆复杂命令。
* **自动保存与归档**:生成的试卷会自动以“年-月-日-时-分-秒.txt”的格式命名并保存在每位教师专属的文件夹下方便管理和追溯。
## 技术栈
* **开发语言**Java 23
* **构建工具**Apache Maven
## 如何启动
配置 **JDK 23 (或更高版本)****Apache Maven**
### 1\. 环境要求
* `JAVA_HOME` 环境变量已正确设置。
* `mvn` 命令可在您的终端或命令行中正常使用。
### 2\. 构建项目
首先,需要使用 Maven 将项目打包成一个可执行的 `.jar` 文件。
在项目的根目录(即 `pom.xml` 文件所在的目录)下,打开终端并执行以下命令:
```bash
mvn clean package
```
该命令会编译所有代码,并 T 在 `target/` 目录下生成一个名为 `MathGenerator-1.0.0.jar` 的文件。
### 3\. 运行程序
构建成功后,使用以下命令来启动程序:
```bash
java -jar target/MathGenerator-1.0.0.jar
```
如果一切顺利,您将在终端看到欢迎界面和用户登录提示。
## 项目结构
项目采用标准的 Maven 目录结构,核心逻辑清晰分离:
```
.
├── pom.xml # Maven 核心配置文件
└── src
└── main
└── java
└── com
└── mathgenerator
├── Application.java # 程序主入口
├── auth/ # 用户认证模块
├── model/ # 数据模型(枚举)
├── generator/ # 各级题目生成器
├── storage/ # 文件存储与查重
├── service/ # 核心业务服务与策略
│ ├── PaperService.java
│ └── strategy/
└── ui/ # 命令行UI界面处理
└── ConsoleUI.java
```

@ -1,103 +0,0 @@
# 中小学数学卷子自动生成程序 - 功能拆分文档
本项目是一个基于Java开发的命令行应用程序旨在为小学、初中和高中三个学段的教师自动生成符合特定难度要求的数学练习卷。项目采用模块化和面向对象的思想进行设计确保了代码的高内聚、低耦合以及良好的可扩展性。
## 一、核心功能拆分
项目整体功能被划分为六个核心模块每个模块由专门的Java包Package进行管理职责清晰。
### 1\. 用户认证模块 (`auth`包)
该模块专门负责用户的登录与身份验证。
* **`User` 类**: 一个数据类(建议使用 `Record`),用于封装用户的核心属性:用户名 (`username`)、密码 (`password`) 和所属学段 (`level`)。
* **`Authenticator` 类**:
* 内部维护一个包含所有预设用户信息的列表或哈希图。
* 提供核心的 `login(username, password)` 方法,用于验证用户凭据。
* 为了保证代码的健壮性,该方法返回一个 `Optional<User>` 对象,安全地处理登录成功(返回用户信息)或失败(返回空对象)的情况。
### 2\. 数据模型模块 (`model`包)
该模块定义了项目中使用的核心数据结构,确保数据的类型安全和一致性。
* **`Level` 枚举 (Enum)**:
* 定义了三个常量:`PRIMARY` (小学), `JUNIOR_HIGH` (初中), `SENIOR_HIGH` (高中)。
* 在整个程序中使用枚举代替纯字符串(如"小学"可以有效防止因拼写错误导致的bug使代码更易于维护。
### 3\. 题目生成模块 (`generator`包)
这是实现项目核心业务逻辑的模块,采用**接口驱动设计**,完美符合“先定义接口或抽象类”的优良设计要求。
* **`QuestionGenerator` 接口**:
* 定义了所有题目生成器必须遵守的统一规范(契约)。
* 包含一个核心抽象方法 `String generateSingleQuestion()`,强制所有实现类都必须提供生成单道题目的具体逻辑。
* **三个实现类**:
* `PrimarySchoolGenerator`: 实现接口,生成仅包含 `+`, `-`, `*`, `/` 和 `()` 的小学难度题目。
* `JuniorHighSchoolGenerator`: 实现接口,确保生成的题目中至少包含一个平方或开根号运算。
* `SeniorHighSchoolGenerator`: 实现接口,确保生成的题目中至少包含一个 `sin`, `cos``tan` 三角函数运算。
* 所有生成器都严格遵守“操作数在1-5个之间取值范围1-100”的规则。
### 4\. 文件存储模块 (`storage`包)
该模块封装了所有与文件系统交互的操作,将文件读写的细节与主逻辑分离。
* **`FileManager` 类**:
* **保存试卷**: 提供 `savePaper(username, paperContent)` 方法,该方法会根据当前系统时间生成“年-月-日-时-分-秒.txt”格式的文件名并将带题号的试卷内容保存到指定用户的专属文件夹下`generated_papers/张三1/`)。
* **读取历史题目 (查重)**: 提供 `loadExistingQuestions(username)` 方法,该方法会扫描特定用户文件夹下的所有 `.txt` 文件,读取并解析出全部历史题目,返回一个 `Set<String>` 集合,用于后续高效地进行查重。
### 5\. 业务服务模块 (`service`包)
该模块是业务逻辑的“协调者”或“指挥官”,它将题目生成和文件存储两个独立的功能串联起来,形成完整的业务流程。
* **`PaperService` 类**:
* 它负责处理“生成并保存一份完整试卷”的请求。
* 其工作流程为:根据用户信息创建对应的题目生成器 -\> 调用 `FileManager` 加载历史题目 -\> 循环生成新题目直至满足数量且无一重复 -\> 调用 `FileManager` 将最终的试卷保存到文件。
### 6\. 主程序与交互模块 (`Main.java`)
这是程序的唯一入口,也是直接与用户进行交互的界面层。
* **`Main` 类**:
* 包含 `public static void main(String[] args)` 方法。
* 内部维持一个主程序循环,负责显示登录提示、菜单选项。
* 使用 `Scanner` 对象接收用户的键盘输入。
* 根据用户输入(如登录、输入题目数量、输入“切换为 XX”、输入“-1”等调用 `Authenticator``PaperService` 中相应的方法来执行具体任务,并将结果反馈给用户。
## 二、项目结构
项目采用简化的纯Java结构不依赖任何构建工具。
```
X班_姓名_个人项目/
├── src/ # 源代码根目录
│ └── com/yourdomain/mathgenerator/ # Java包结构
│ ├── Main.java # 程序入口
│ ├── auth/ # 认证模块
│ ├── model/ # 数据模型模块
│ ├── generator/ # 题目生成模块
│ ├── storage/ # 文件存储模块
│ └── service/ # 业务服务模块
├── bin/ # 存放编译后的.class文件
└── generated_papers/ # 存放所有用户生成的试卷文件
```
## 三、如何编译与运行
1. **编译**: 在项目根目录下打开终端,执行以下命令将所有 `.java` 文件编译到 `bin` 目录。
```bash
# -d 指定输出目录,$(find...) 会查找src下的所有java文件
javac -d bin $(find src -name "*.java")
```
2. **运行**: 继续在根目录下执行以下命令来启动程序。
```bash
# -cp 指定类路径(classpath)让Java虚拟机能找到你的类
java -cp bin com.mathgenerator.Application
```

@ -0,0 +1,120 @@
# 功能拆分文档
本项目是一个基于Java开发的命令行应用程序旨在为小学、初-中和高中三个学段的教师自动生成符合特定难度要求的数学练习卷。项目采用模块化和面向对象的思想进行设计,并引入了策略模式等设计模式,确保了代码的高内聚、低耦合以及良好的可扩展性。
## 一、项目结构 (Maven)
项目采用标准的 Maven 目录结构,并遵循了职责分离的设计原则,将不同功能的模块划分到独立的包中,最终结构如下:
```
.
├── pom.xml # Maven 核心配置文件
├── users.json # 用户数据文件
└── src
└── main
└── java
└── com
└── mathgenerator
├── Application.java # 程序主入口:负责组装和启动
├── model/ # 数据模型包
│ ├── Level.java # 学段枚举
│ └── User.java # 用户数据模型
├── ui/ # 用户界面包
│ └── ConsoleUI.java # 负责所有命令行交互
├── storage/ # 存储包
│ └── FileManager.java # 负责所有文件的读写
├── generator/ # 题目生成器包
│ ├── QuestionGenerator.java
│ ├── PrimarySchoolGenerator.java
│ ├── JuniorHighSchoolGenerator.java
│ └── SeniorHighSchoolGenerator.java
└── service/ # 核心业务服务包
├── UserService.java # 负责用户注册、登录和持久化
├── PaperService.java # 负责试卷的创建流程
└── strategy/ # 试卷组合策略子包
├── PaperStrategy.java
└── MixedDifficultyStrategy.java
```
---
## 二、核心功能拆分
项目整体功能被划分为多个核心模块每个模块由专门的Java包Package进行管理职责清晰。
### 1\. 数据模型模块 (`model`包)
该模块定义了项目中使用的核心数据结构,确保数据的类型安全和一致性。
* **`Level` 枚举 (Enum)**:
* 定义了三个常量:`PRIMARY` (小学), `JUNIOR_HIGH` (初中), `SENIOR_HIGH` (高中)。
* 使用枚举代替纯字符串有效防止了因拼写错误导致的bug。
* **`User` 类**: 一个数据类(采用 `Record` 实现),用于封装用户的核心属性:用户名 (`username`)、密码 (`password`) 和所属学段 (`level`)。
### 2\. 题目生成模块 (`generator`包)
这是实现项目核心业务逻辑的模块,采用接口驱动和继承链设计。
* **`QuestionGenerator` 接口**: 定义了所有题目生成器必须遵守的统一规范,包含一个核心方法 `String generateSingleQuestion()`
* **三个实现类**:
* `PrimarySchoolGenerator`: 生成包含 `+`, `-`, `*`, `/` 和 `()` 的小学难度题目。
* `JuniorHighSchoolGenerator`: **继承自 `PrimarySchoolGenerator`**,在小学题目基础上,确保至少包含一个平方或开根号运算。
* `SeniorHighSchoolGenerator`: **继承自 `JuniorHighSchoolGenerator`**,在初中题目基础上,再确保至少包含一个 `sin`, `cos``tan` 三角函数运算。
* 所有生成器都遵守“操作数在1-5个之间取值范围1-100”的规则。
### 3\. 文件存储模块 (`storage`包)
该模块封装了所有与**试卷文件**系统交互的操作。
* **`FileManager` 类**:
* **保存试卷**: 提供 `savePaper` 方法,以“年-月-日-时-分-秒.txt”的格式保存试卷到用户专属的文件夹。
* **读取历史题目 (查重)**: 提供 `loadExistingQuestions` 方法,扫描用户文件夹下的所有历史题目,返回一个 `Set<String>` 集合用于查重。
### 4\. 业务服务模块 (`service`包)
该模块是业务逻辑的协调者,包含了用户管理和试卷生成两大核心服务。
* **`UserService` 类**:
* 是用户管理的**核心**。
* 负责用户的**注册**、**登录**以及**持久化存储**。
* 程序启动时从 `users.json` 文件加载用户数据,在用户注册时将新数据写回文件。
* **`PaperService` 类**:
* 负责处理“生成并保存一份完整试卷”的**流程**。
* 它持有一个 `PaperStrategy` 对象的引用,将具体的题目难度选择**委托**给策略对象来执行。
* **`service.strategy` 子包**:
* **`PaperStrategy` 接口**: 定义了“试卷组合策略”的抽象规范。
* **`MixedDifficultyStrategy` 类**: `PaperStrategy` 的一个具体实现,负责生成包含不同难度梯度的混合试卷。
### 5\. 主程序与UI模块 (`Application.java` 和 `ui`包)
为遵循单一职责原则程序的启动和UI交互被拆分到独立的单元中。
* **`Application.java`**: 程序的唯一入口。其 `main` 方法非常简洁,仅负责创建和组装所有核心组件(如 `UserService`, `PaperService`, `ConsoleUI` 等),然后启动应用。
* **`ui.ConsoleUI` 类**: 一个专门负责所有控制台用户界面显示和交互的类。它实现了**菜单驱动**的用户界面,处理用户的**登录**、**注册**、**难度切换**和**题目生成**等所有交互操作。
## 三、如何使用 Maven 构建与运行
项目采用 Maven 进行自动化构建和打包。
1. **构建 (打包)**: 在项目根目录(`pom.xml` 所在位置)打开终端,执行以下命令:
```bash
mvn clean package
```
该命令会自动完成编译、测试和打包,并在 `target/` 目录下生成一个可执行的 `MathGenerator-1.0.0.jar` 文件。
2. **运行**: 继续在终端中执行以下命令来启动程序。
```bash
java -jar target/MathGenerator-1.0.0.jar
```

@ -0,0 +1,48 @@
# 设计模式
本项目在开发过程中,为了实现代码的高内聚、低耦合以及未来的高可扩展性,采纳了多种业界标准的设计模式和原则。本文档将详细解析其中最核心的几种设计模式。
## 1. 策略模式 (Strategy Pattern)
策略模式是本项目架构中最为核心和体现拓展性的设计模式。
* **意图**:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。此模式让算法的变化独立于使用算法的客户。
* **在项目中的体现**
* **`PaperStrategy` 接口**:这是策略的抽象。它定义了一个 `selectGenerator(Level mainLevel)` 方法,所有具体的试卷组合算法都必须实现这个接口。
* **`MixedDifficultyStrategy` 类**这是一个具体的策略实现。它封装了我们设计的“混合难度”算法例如高中试卷包含60%高中题、30%初中题、10%小学题)。
* **`PaperService` 类**这是使用策略的上下文Context。`PaperService` 不再关心试卷的具体组合逻辑,它只持有一个 `PaperStrategy` 对象的引用,并在生成试卷时调用其 `selectGenerator` 方法。
* **带来的好处**
* **极高的可扩展性**:如果我们想新增一种“只出难题”的试卷模式,只需新增一个 `HardModeStrategy` 类实现 `PaperStrategy` 接口,然后在程序入口处注入这个新策略即可,**完全不需要修改 `PaperService` 的代码**。这完美遵循了“开闭原则”。
* **职责分离**`PaperService` 的职责被简化为控制试卷生成的整体流程,而具体的题目组合算法则被分离到各个策略类中,使得代码结构更清晰。
## 2. 工厂方法模式 (Factory Method Pattern) 的简化应用
虽然没有严格地实现一个完整的工厂方法模式,但其核心思想在项目中得到了应用。
* **意图**:定义一个用于创建对象的接口,让子类决定实例化哪一个类。
* **在项目中的体现**
* 我们的 `MixedDifficultyStrategy` 内部的 `selectGenerator` 方法实际上扮演了一个**简单工厂Simple Factory**的角色。它根据输入的 `Level` 和内部的随机逻辑,负责“生产”出具体的 `QuestionGenerator` 实例(`PrimarySchoolGenerator`, `JuniorHighSchoolGenerator` 等)。
* **带来的好处**
* **集中创建逻辑**:所有关于“如何根据难度创建对应题目生成器”的逻辑都被集中在一个地方,而不是散落在代码各处。当需要修改创建逻辑时,我们只需要改动这个“工厂”即可。
## 3. 模板方法模式 (Template Method Pattern)
此模式在我们的题目生成器继承链中得到了巧妙的运用。
* **意图**:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
* **在项目中的体现**
* **`PrimarySchoolGenerator`** 提供了最基础的题目生成逻辑。
* **`JuniorHighSchoolGenerator`** 继承前者,它的 `generateSingleQuestion` 方法首先通过 `super.generateSingleQuestion()` 调用父类的“模板”来获得一个基础题目,然后在此基础上增加自己的特定步骤(添加平方或开根号)。
* **`SeniorHighSchoolGenerator`** 同理,它调用父类(初中生成器)的方法,在这个“半成品”上增加自己的步骤(添加三角函数)。
* **带来的好处**
* **代码复用**:避免了在每个生成器中都重复编写基础表达式的生成逻辑。
* **结构清晰**:清晰地定义了不同难度题目之间的层级递进关系。
通过综合运用这些设计模式和原则,我们的项目不仅实现了所有功能需求,更拥有了一个专业、健壮且易于扩展的软件架构。

@ -14,6 +14,13 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version> </dependency>
</dependencies>
<build>
<plugins>
<plugin>

@ -1,25 +1,25 @@
package com.mathgenerator;
import com.mathgenerator.auth.Authenticator;
import com.mathgenerator.service.PaperService;
import com.mathgenerator.service.UserService;
import com.mathgenerator.service.strategy.MixedDifficultyStrategy; // 导入具体策略
import com.mathgenerator.service.strategy.PaperStrategy; // 导入策略接口
import com.mathgenerator.storage.FileManager;
import com.mathgenerator.ui.ConsoleUI; // 导入新的UI类
import com.mathgenerator.ui.ConsoleUI;
/**
*
*
*/
public class Application {
public static void main(String[] args) {
// 1. 创建所有核心服务组件
Authenticator authenticator = new Authenticator();
UserService userService = new UserService();
FileManager fileManager = new FileManager();
PaperService paperService = new PaperService(fileManager);
// 2. 创建UI组件并将服务注入其中
ConsoleUI consoleUI = new ConsoleUI(authenticator, paperService);
// 1. 创建一个具体的策略实例
PaperStrategy strategy = new MixedDifficultyStrategy();
// 2. 将策略实例注入到 PaperService 中
PaperService paperService = new PaperService(fileManager, strategy);
ConsoleUI consoleUI = new ConsoleUI(userService, paperService);
// 3. 运行UI
consoleUI.run();
}
}

@ -1,46 +0,0 @@
package com.mathgenerator.auth;
import com.mathgenerator.model.Level;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.stream.Collectors;
/**
*
*/
public class Authenticator {
private static final Map<String, User> USER_DATABASE = initializeUsers();
/**
*
*/
private static Map<String, User> initializeUsers() {
return Stream.of(
new User("张三1", "123", Level.PRIMARY),
new User("张三2", "123", Level.PRIMARY),
new User("张三3", "123", Level.PRIMARY),
new User("李四1", "123", Level.JUNIOR_HIGH),
new User("李四2", "123", Level.JUNIOR_HIGH),
new User("李四3", "123", Level.JUNIOR_HIGH),
new User("王五1", "123", Level.SENIOR_HIGH),
new User("王五2", "123", Level.SENIOR_HIGH),
new User("王五3", "123", Level.SENIOR_HIGH)
).collect(Collectors.toMap(User::username, user -> user));
}
/**
*
* @param username
* @param password
* @return UserOptionalOptional
*/
public Optional<User> login(String username, String password) {
User user = USER_DATABASE.get(username);
if (user != null && user.password().equals(password)) {
return Optional.of(user);
}
return Optional.empty();
}
}

@ -11,6 +11,15 @@ import java.util.regex.Pattern;
* ()
*/
public class JuniorHighSchoolGenerator extends PrimarySchoolGenerator {
/**
*
* <p>
* PrimarySchoolGenerator
*
* x²x
*
* @return
*/
@Override
public String generateSingleQuestion() {
// 1. 先生成一个完整的小学难度题目(包含括号等)

@ -12,7 +12,15 @@ import java.util.concurrent.ThreadLocalRandom;
public class PrimarySchoolGenerator implements QuestionGenerator {
private static final String[] OPERATORS = {"+", "-", "*", "/"};
/**
*
* <p>
* 251-100
* "+", "-", "*", "/"
*
*
* @return
*/
@Override
public String generateSingleQuestion() {
ThreadLocalRandom random = ThreadLocalRandom.current();

@ -8,14 +8,26 @@ import java.util.regex.Pattern;
/**
*
* () sin, cos, tan
* <p>
* JuniorHighSchoolGenerator
* sin, cos, tan
*/
public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator { // <-- 关键改动:继承自初中生成器
public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator {
private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"};
/**
*
* <p>
* JuniorHighSchoolGenerator
* /
*
* sin(), cos(), tan() (°)
*
* @return
*/
@Override
public String generateSingleQuestion() {
// 1. 先生成一个完整的初中难度题目(已包含小学内容和平方/开根号)
// 1. 先生成一个完整的初中难度题目
String juniorHighQuestion = super.generateSingleQuestion();
ThreadLocalRandom random = ThreadLocalRandom.current();
@ -30,11 +42,11 @@ public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator { // <-
if (numbers.isEmpty()) {
return juniorHighQuestion;
}
// 3. 随机选择一个数字,用三角函数包裹
String numberToModify = numbers.get(random.nextInt(numbers.size()));
String function = TRIG_FUNCTIONS[random.nextInt(TRIG_FUNCTIONS.length)];
String modifiedNumber = function + "(" + numberToModify + ")";
String modifiedNumber = function + "(" + numberToModify + "°)";
// 4. 将修改后的部分替换回原题目
return juniorHighQuestion.replaceFirst(Pattern.quote(numberToModify), modifiedNumber);

@ -9,11 +9,19 @@ public enum Level {
SENIOR_HIGH("高中");
private final String chineseName;
/**
*
*
* @param chineseName
*/
Level(String chineseName) {
this.chineseName = chineseName;
}
/**
*
*
* @return
*/
public String getChineseName() {
return chineseName;
}

@ -1,6 +1,4 @@
package com.mathgenerator.auth;
import com.mathgenerator.model.Level;
package com.mathgenerator.model;
/**
* (Record)

@ -1,11 +1,8 @@
package com.mathgenerator.service;
import com.mathgenerator.generator.JuniorHighSchoolGenerator;
import com.mathgenerator.generator.PrimarySchoolGenerator;
import com.mathgenerator.generator.QuestionGenerator;
import com.mathgenerator.generator.SeniorHighSchoolGenerator;
import com.mathgenerator.model.User;
import com.mathgenerator.model.Level;
import com.mathgenerator.auth.User;
import com.mathgenerator.service.strategy.PaperStrategy; // 导入策略接口
import com.mathgenerator.storage.FileManager;
import java.io.IOException;
import java.util.ArrayList;
@ -14,30 +11,48 @@ import java.util.List;
import java.util.Set;
/**
*
* PaperStrategy
*/
public class PaperService {
private final FileManager fileManager;
private final PaperStrategy paperStrategy; // 持有策略接口的引用
public PaperService(FileManager fileManager) {
/**
* PaperService
* <p>
*
* DI
*
* @param fileManager FileManager
* @param paperStrategy PaperStrategy
*/
public PaperService(FileManager fileManager, PaperStrategy paperStrategy) {
this.fileManager = fileManager;
this.paperStrategy = paperStrategy;
}
/**
*
* @param user
* @param count
* @param currentLevel
* <p>
* orchestrates
* 1.
* 2. {@link PaperStrategy}
* 3.
* 4. {@link FileManager}
*
* @param user
* @param count
* @param currentLevel
*/
public void createAndSavePaper(User user, int count, Level currentLevel) {
QuestionGenerator generator = createGenerator(currentLevel);
Set<String> existingQuestions = fileManager.loadExistingQuestions(user.username());
List<String> newPaper = new ArrayList<>();
Set<String> generatedInSession = new HashSet<>();
System.out.println("正在生成题目,请稍候...");
System.out.println("正在根据策略生成题目,请稍候...");
while (newPaper.size() < count) {
String question = generator.generateSingleQuestion();
// 将选择生成器的逻辑委托给策略对象
String question = paperStrategy.selectGenerator(currentLevel).generateSingleQuestion();
if (!existingQuestions.contains(question) && !generatedInSession.contains(question)) {
newPaper.add(question);
generatedInSession.add(question);
@ -46,18 +61,10 @@ public class PaperService {
try {
String filePath = fileManager.savePaper(user.username(), newPaper);
System.out.println("成功!" + count + "道" + currentLevel.getChineseName() + "数学题目已生成。");
System.out.println("成功!" + count + "道数学题目已生成。");
System.out.println("文件已保存至: " + filePath);
} catch (IOException e) {
System.err.println("错误:保存文件失败 - " + e.getMessage());
}
}
private QuestionGenerator createGenerator(Level level) {
return switch (level) {
case PRIMARY -> new PrimarySchoolGenerator();
case JUNIOR_HIGH -> new JuniorHighSchoolGenerator();
case SENIOR_HIGH -> new SeniorHighSchoolGenerator();
};
}
}

@ -0,0 +1,109 @@
package com.mathgenerator.service;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.mathgenerator.model.User;
import com.mathgenerator.model.Level;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* <p>
* Authenticator
* JSON
*/
public class UserService {
//private static final Path USER_FILE_PATH = Paths.get("./src/main/java/com/mathgenerator/auth/users.json");
private static final Path USER_FILE_PATH = Paths.get("users.json");
private final Map<String, User> userDatabase;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
/**
* UserService
* users.json
*/
public UserService() {
this.userDatabase = loadUsers();
}
/**
* users.json
*
* @return Map
*/
private Map<String, User> loadUsers() {
if (!Files.exists(USER_FILE_PATH)) {
return new ConcurrentHashMap<>(); // 如果文件不存在返回一个空的Map
}
try (FileReader reader = new FileReader(USER_FILE_PATH.toFile())) {
Type type = new TypeToken<Map<String, User>>() {}.getType();
Map<String, User> loadedUsers = gson.fromJson(reader, type);
return new ConcurrentHashMap<>(loadedUsers); // 确保线程安全
} catch (IOException e) {
System.err.println("错误:加载用户文件失败 - " + e.getMessage());
return new ConcurrentHashMap<>();
}
}
/**
* users.json
*/
private void saveUsers() {
try (FileWriter writer = new FileWriter(USER_FILE_PATH.toFile())) {
gson.toJson(this.userDatabase, writer);
} catch (IOException e) {
System.err.println("错误:保存用户文件失败 - " + e.getMessage());
}
}
/**
*
*
* @param username
* @return UserOptional
*/
public Optional<User> findUserByUsername(String username) {
return Optional.ofNullable(this.userDatabase.get(username));
}
/**
*
*
* @param username
* @param password
* @return UserOptional
*/
public Optional<User> login(String username, String password) {
return findUserByUsername(username)
.filter(user -> user.password().equals(password));
}
/**
*
*
* @param username
* @param password
* @param level
* @return truefalse
*/
public boolean register(String username, String password, Level level) {
if (findUserByUsername(username).isPresent()) {
return false; // 用户已存在,注册失败
}
User newUser = new User(username, password, level);
this.userDatabase.put(username, newUser);
saveUsers(); // 每次注册后立即保存
return true;
}
}

@ -0,0 +1,37 @@
package com.mathgenerator.service.strategy;
import com.mathgenerator.generator.*;
import com.mathgenerator.model.Level;
import java.util.concurrent.ThreadLocalRandom;
/**
*
* <p>
*
*/
public class MixedDifficultyStrategy implements PaperStrategy {
// 策略内部持有所有可能的生成器
private final QuestionGenerator primaryGenerator = new PrimarySchoolGenerator();
private final QuestionGenerator juniorHighGenerator = new JuniorHighSchoolGenerator();
private final QuestionGenerator seniorHighGenerator = new SeniorHighSchoolGenerator();
@Override
public QuestionGenerator selectGenerator(Level mainLevel) {
double randomValue = ThreadLocalRandom.current().nextDouble();
return switch (mainLevel) {
case PRIMARY -> primaryGenerator;
case JUNIOR_HIGH -> {
// 初中试卷70%初中难度30%小学难度
if (randomValue < 0.7) yield juniorHighGenerator;
else yield primaryGenerator;
}
case SENIOR_HIGH -> {
// 高中试卷60%高中难度30%初中难度10%小学难度
if (randomValue < 0.6) yield seniorHighGenerator;
else if (randomValue < 0.9) yield juniorHighGenerator;
else yield primaryGenerator;
}
};
}
}

@ -0,0 +1,18 @@
package com.mathgenerator.service.strategy;
import com.mathgenerator.generator.QuestionGenerator;
import com.mathgenerator.model.Level;
/**
*
*
*/
public interface PaperStrategy {
/**
*
*
* @param mainLevel
* @return QuestionGenerator
*/
QuestionGenerator selectGenerator(Level mainLevel);
}

@ -1,7 +1,6 @@
package com.mathgenerator.storage;
import java.io.IOException;
//TODO:这里不能导入所有类
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -21,7 +20,7 @@ import java.util.stream.Stream;
public class FileManager {
private static final Path BASE_PATH = Paths.get("generated_papers");
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
private static final Pattern QUESTION_PATTERN = Pattern.compile("^\\d+\\.\\s+(.*)");
private static final Pattern QUESTION_PATTERN = Pattern.compile("^\\d+\\.\\s+(.*?)\\s*=\\s*$");
/**
*
@ -39,7 +38,7 @@ public class FileManager {
StringBuilder formattedContent = new StringBuilder();
for (int i = 0; i < paperContent.size(); i++) {
formattedContent.append(i + 1).append(". ").append(paperContent.get(i));
formattedContent.append(i + 1).append(". ").append(paperContent.get(i)).append(" = ");
if (i < paperContent.size() - 1) {
formattedContent.append(System.lineSeparator()).append(System.lineSeparator());
}
@ -70,7 +69,10 @@ public class FileManager {
return new HashSet<>();
}
}
/**
* @param file (Path)
* @return (Stream) I/O
*/
private Stream<String> readQuestionsFromFile(Path file) {
try {
//TODO:这里需要修改警告:(72, 26) 在没有 'try-with-resources' 语句的情况下使用 'Stream<String>'

@ -1,10 +1,9 @@
package com.mathgenerator.ui;
import com.mathgenerator.auth.Authenticator;
import com.mathgenerator.auth.User;
import com.mathgenerator.model.User;
import com.mathgenerator.model.Level;
import com.mathgenerator.service.PaperService;
import com.mathgenerator.service.UserService;
import java.util.Optional;
import java.util.Scanner;
@ -13,12 +12,19 @@ import java.util.Scanner;
*/
public class ConsoleUI {
private final Scanner scanner = new Scanner(System.in);
private final Authenticator authenticator;
private final UserService userService;
private final PaperService paperService;
// 通过构造函数接收依赖的服务,这是一种常见的设计模式(依赖注入)
public ConsoleUI(Authenticator authenticator, PaperService paperService) {
this.authenticator = authenticator;
/**
* ConsoleUI
*
* 使UI使
*
* @param userService
* @param paperService
*/
public ConsoleUI(UserService userService, PaperService paperService) {
this.userService = userService;
this.paperService = paperService;
}
@ -26,36 +32,107 @@ public class ConsoleUI {
*
*/
public void run() {
//TODO警告:(29, 9) 'while' 语句不能在未抛出异常的情况下完成
while (true) {
Optional<User> userOptional = handleLogin();
userOptional.ifPresent(this::showUserMenu);
// 在主循环中加入注册选项
printHeader("欢迎使用中小学数学卷子自动生成程序");
System.out.println("1. 用户登录");
System.out.println("2. 新用户注册");
System.out.println("3. 退出程序");
printSeparator();
System.out.print("请选择操作 (1-3): ");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1" -> handleLogin().ifPresent(this::showUserMenu);
case "2" -> handleRegistration();
case "3" -> {
System.out.println("感谢使用,程序已退出。");
return; // 退出run()方法,结束程序
}
default -> System.out.println("无效输入,请输入 1-3 之间的数字。");
}
}
}
/**
*
*
* @return OptionalOptional
*/
private Optional<User> handleLogin() {
printHeader("用户登录"); // 统一标题格式
System.out.print("请输入用户名和密码 (用空格隔开): (输入 exit 退出程序)\n> ");
String line = scanner.nextLine();
if ("exit".equalsIgnoreCase(line.trim())) {
System.out.println("感谢使用,程序已退出。");
System.exit(0);
}
printHeader("用户登录");
System.out.print("请输入用户名和密码 (用空格隔开): \n> ");
String[] credentials = scanner.nextLine().split("\\s+");
String[] credentials = line.split("\\s+");
if (credentials.length != 2) {
System.out.println("输入格式错误,请重新输入。");
System.out.println("输入格式错误。");
return Optional.empty();
}
Optional<User> userOptional = authenticator.login(credentials[0], credentials[1]);
// 调用新的UserService进行登录
Optional<User> userOptional = userService.login(credentials[0], credentials[1]);
if (userOptional.isEmpty()) {
System.out.println("用户名或密码错误,请重新输入。");
System.out.println("用户名或密码错误。");
}
return userOptional;
}
/**
*
*/
private void handleRegistration() {
printHeader("新用户注册");
System.out.print("请输入新用户名: ");
String username = scanner.nextLine().trim();
if (userService.findUserByUsername(username).isPresent()) {
System.out.println("注册失败:该用户名已被占用。");
return;
}
System.out.print("请输入密码: ");
String password = scanner.nextLine().trim();
Level level = selectLevelForRegistration();
if (level == null) {
System.out.println("操作已取消。");
return;
}
if (userService.register(username, password, level)) {
System.out.println("注册成功!现在您可以使用新账户登录了。");
} else {
// 理论上,这里的失败分支不会被触发,因为我们已经提前检查了用户名
System.out.println("注册失败,发生未知错误。");
}
}
/**
*
*/
private Level selectLevelForRegistration() {
while (true) {
printHeader("请为新用户选择默认的学段");
System.out.println("1. 小学");
System.out.println("2. 初中");
System.out.println("3. 高中");
System.out.println("0. 取消注册");
printSeparator();
System.out.print("请选择 (0-3): ");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1": return Level.PRIMARY;
case "2": return Level.JUNIOR_HIGH;
case "3": return Level.SENIOR_HIGH;
case "0": return null; // 用户取消
default: System.out.println("无效输入,请重新选择。");
}
}
}
/**
*
*
* @param user
*/
private void showUserMenu(User user) {
Level currentLevel = user.level();
System.out.println("\n登录成功! 欢迎 " + user.username());
@ -88,7 +165,10 @@ public class ConsoleUI {
}
/**
*
*
*
* @param currentLevel
* @return
*/
private Level handleLevelSwitchMenu(Level currentLevel) {
while (true) { // <-- 增加循环
@ -100,7 +180,6 @@ public class ConsoleUI {
printSeparator();
System.out.print("请选择 (0-3): ");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1":
System.out.println("难度已成功切换为 " + Level.PRIMARY.getChineseName() + "。");
@ -115,37 +194,51 @@ public class ConsoleUI {
System.out.println("已返回主菜单。");
return currentLevel; // 返回当前难度,退出循环
default:
// 如果输入无效,打印提示信息,循环将继续,要求用户重新输入
System.out.println("无效输入,请输入 0-3 之间的数字。");
}
}
}
/**
*
*
* @param user
* @param currentLevel
*/
private void handleGeneration(User user, Level currentLevel) {
printHeader("生成 " + currentLevel.getChineseName() + " 题目");
System.out.print("请输入生成题目数量 (10-30输入 0 返回主菜单): ");
try {
int count = Integer.parseInt(scanner.nextLine().trim());
if (count == 0) {
System.out.println("已取消生成,返回主菜单。");
return;
}
if (count >= 10 && count <= 30) {
paperService.createAndSavePaper(user, count, currentLevel);
} else {
System.out.println("输入无效,题目数量必须在 10 到 30 之间。");
while (true) {
printHeader("生成 " + currentLevel.getChineseName() + " 题目");
System.out.print("请输入生成题目数量 (10-30输入 0 返回主菜单): ");
String input = scanner.nextLine().trim();
try {
int count = Integer.parseInt(input);
if (count == 0) {
System.out.println("已取消生成,返回主菜单。");
break; // 退出循环,返回主菜单
}
if (count >= 10 && count <= 30) {
paperService.createAndSavePaper(user, count, currentLevel);
break; // 成功生成,退出循环,返回主菜单
} else {
System.out.println("无效输入,题目数量必须在 10 到 30 之间。请重新输入。");
}
} catch (NumberFormatException e) {
System.out.println("无效输入,请输入一个有效的数字。请重新输入。");
}
} catch (NumberFormatException e) {
System.out.println("输入无效,请输入一个有效的数字。");
}
}
/**
*
*
* @param title
*/
private void printHeader(String title) {
printSeparator();
System.out.println(title);
printSeparator();
}
/**
* 线
*/
private void printSeparator() {
System.out.println("======================================================");
}

Loading…
Cancel
Save