稳定版 #1

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

29
.gitignore vendored

@ -0,0 +1,29 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21_PREVIEW" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/PaperGenerator.iml" filepath="$PROJECT_DIR$/PaperGenerator.iml" />
</modules>
</component>
</project>

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,257 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ArtifactsWorkspaceSettings">
<artifacts-to-build>
<artifact name="PaperGenerator:jar" />
</artifacts-to-build>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="3ea5e8cf-3c22-456e-ab12-5e2c19960d33" name="更改" comment="最新版">
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/uiDesigner.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/PaperGenerator.iml" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
<option value="Interface" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{
&quot;isMigrated&quot;: true
}</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="CurrentFile" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 1
}</component>
<component name="ProjectId" id="336AvVappG9b2cdW3j8XdhTSBTR" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;develop&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/ASUS/OneDrive/Desktop/paper_generator/src&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;project.structure.last.edited&quot;: &quot;工件&quot;,
&quot;project.structure.proportion&quot;: &quot;0.15&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;configurable.group.tools&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;,
&quot;应用程序.MathExamGenerator.executor&quot;: &quot;Run&quot;
}
}</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\ASUS\OneDrive\Desktop\paper_generator\doc" />
</key>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-28b599e66164-intellij.indexing.shared.core-IU-242.23726.103" />
<option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-IU-242.23726.103" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="3ea5e8cf-3c22-456e-ab12-5e2c19960d33" name="更改" comment="" />
<created>1758627387353</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1758627387353</updated>
<workItem from="1758627388441" duration="3671000" />
<workItem from="1758695711331" duration="7931000" />
<workItem from="1758704191437" duration="2037000" />
<workItem from="1758707319403" duration="929000" />
<workItem from="1758709126267" duration="1554000" />
<workItem from="1758711076220" duration="3702000" />
<workItem from="1758718107470" duration="357000" />
<workItem from="1758719070914" duration="2009000" />
<workItem from="1758885312219" duration="2559000" />
<workItem from="1758970249477" duration="8908000" />
<workItem from="1759029330275" duration="6000" />
<workItem from="1759044498862" duration="922000" />
<workItem from="1759045464856" duration="714000" />
<workItem from="1759046309686" duration="961000" />
<workItem from="1759047453581" duration="4264000" />
<workItem from="1759054544656" duration="1341000" />
</task>
<task id="LOCAL-00001" summary="第一版">
<option name="closed" value="true" />
<created>1758718169046</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1758718169046</updated>
</task>
<task id="LOCAL-00002" summary="第三版">
<option name="closed" value="true" />
<created>1758721030985</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1758721030986</updated>
</task>
<task id="LOCAL-00003" summary="第四版">
<option name="closed" value="true" />
<created>1758976924192</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1758976924192</updated>
</task>
<task id="LOCAL-00004" summary="第四版">
<option name="closed" value="true" />
<created>1758979600757</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1758979600757</updated>
</task>
<task id="LOCAL-00005" summary="最新版">
<option name="closed" value="true" />
<created>1759046417724</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1759046417724</updated>
</task>
<task id="LOCAL-00006" summary="最新版">
<option name="closed" value="true" />
<created>1759046442793</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1759046442793</updated>
</task>
<task id="LOCAL-00007" summary="最新版">
<option name="closed" value="true" />
<created>1759055214485</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1759055214485</updated>
</task>
<task id="LOCAL-00008" summary="最新版">
<option name="closed" value="true" />
<created>1759055292192</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1759055292192</updated>
</task>
<task id="LOCAL-00009" summary="最新版">
<option name="closed" value="true" />
<created>1759055353424</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1759055353424</updated>
</task>
<task id="LOCAL-00010" summary="最新版">
<option name="closed" value="true" />
<created>1759055878739</created>
<option name="number" value="00010" />
<option name="presentableId" value="LOCAL-00010" />
<option name="project" value="LOCAL" />
<updated>1759055878739</updated>
</task>
<option name="localTasksCounter" value="11" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="OPEN_GENERIC_TABS">
<map>
<entry key="3b51dc8d-22b0-4955-aa8d-40598d45841a" value="TOOL_WINDOW" />
</map>
</option>
<option name="TAB_STATES">
<map>
<entry key="3b51dc8d-22b0-4955-aa8d-40598d45841a">
<value>
<State>
<option name="CUSTOM_BOOLEAN_PROPERTIES">
<map>
<entry key="Show.Git.Branches" value="true" />
</map>
</option>
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="develop" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
<entry key="MAIN">
<value>
<State>
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="origin/develop" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="第一版" />
<MESSAGE value="第二版" />
<MESSAGE value="第三版" />
<MESSAGE value="第四版" />
<MESSAGE value="最新版" />
<option name="LAST_COMMIT_MESSAGE" value="最新版" />
</component>
</project>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -1,2 +0,0 @@
# paper_generator

@ -0,0 +1,170 @@
# 中小学数学试卷生成系统
一个基于Java的命令行应用程序能够根据不同年级自动生成相应难度的数学试卷。
## 项目简介
这是一个专为中小学教师、学生和家长设计的数学试卷自动生成工具。系统支持小学、初中、高中三个教育阶段,能够生成符合各阶段教学要求的数学题目,并自动保存为试卷文件。
## 功能特性
### 🎯 用户管理
- 支持9个预置用户账号张三1-3、李四1-3、王五1-3
- 自动识别用户对应的教育级别(小学/初中/高中)
- 安全的用户名密码验证机制
### 📚 题目生成
- **小学阶段**:四则运算、混合运算、带括号运算
- **初中阶段**:混合运算、分数运算、方程求解、幂运算和开方
- **高中阶段**:二次方程、函数计算、三角函数、概率、数列、导数、复数运算
### 📄 试卷管理
- 支持生成10-30道题目
- 智能题目去重机制,确保题目不重复
- 按用户分类自动保存试卷文件
- 时间戳命名,便于管理
### 💻 交互体验
- 友好的命令行交互界面
- 支持切换年级级别
- 用户登出和重新登录功能
## 技术架构
### 开发环境
- **编程语言**Java 21
- **开发工具**IntelliJ IDEA
- **项目类型**纯Java控制台应用程序
### 项目结构
```
src/com/bx/
├── generator_main/ # 主程序入口
├── manager/ # 业务管理层
├── question/ # 题目相关
│ └── generator/ # 题目生成器
```
### 设计模式
- **工厂模式**`QuestionFactory`根据难度级别分发题目生成
- **策略模式**:不同年级采用不同的题目生成策略
- **分层架构**:清晰的职责分离,便于维护和扩展
## 快速开始
### 环境要求
- Java 21或更高版本
- 支持UTF-8编码
### 运行步骤
1. 克隆项目到本地
2. 使用IntelliJ IDEA打开项目
3. 运行 `src/com/bx/generator_main/MathExamGenerator.java`
4. 或者使用 `java -jar PaperGenerator.jar` 指令运行提供的jar包
4. 使用预置账号登录:
- 小学用户张三1/123、李四1/123、王五1/123
- 初中用户张三2/123、李四2/123、王五2/123
- 高中用户张三3/123、李四3/123、王五3/123
### 使用流程
1. 输入用户名和密码登录系统
2. 选择要生成的题目数量10-30道
3. 系统自动生成相应难度的数学试卷
4. 试卷自动保存到 `user_papers/{用户名}/` 目录下
## 核心功能详解
### 题目类型
#### 小学题目
- 基础四则运算:`1 + 2 =`、`5 × 3 =`
- 混合运算:`2 + 3 × 4 =`、`(1 + 2) × 3 =`
- 带括号运算:`(4 + 5) × 2 =`、`8 ÷ (2 + 2) =`
#### 初中题目
- 分数运算:`1/2 + 1/3 =`、`2/3 × 3/4 =`
- 方程求解:`2x + 5 = 13`、`x² - 4 = 0`
- 幂运算:`2³ =`、`√16 =`
#### 高中题目
- 二次方程:`x² + 5x + 6 = 0`
- 三角函数:`sin(30°) =`、`cos(45°) =`
- 函数计算:`f(x) = x² + 2x + 1, f(2) =`
- 概率统计:基础概率计算题
- 数列计算:等差数列、等比数列
- 导数计算:基础函数求导
- 复数运算:`(2 + 3i) + (1 - 2i) =`
### 特点功能
#### 题目去重机制
系统采用哈希算法确保生成的题目不重复,每份试卷都是独一无二的。
#### 难度自适应
根据用户级别自动调整题目复杂度,确保题目符合相应年级的教学要求。
#### 文件管理
- 按用户分类存储试卷
- 时间戳命名便于查找
- 支持查看历史生成的试卷
## 扩展开发
### 添加新题目类型
1. 在相应的生成器类中添加新的题目生成方法
2. 遵循现有的命名规范和代码风格
3. 确保题目符合对应年级的难度要求
### 添加新用户
1. 修改 `UserManager.java` 中的用户列表
2. 遵循用户名密码的格式规范
3. 指定用户的教育级别
### 自定义题目数量
系统支持10-30道题目生成可通过修改 `ExamManager.java` 中的参数进行调整。
## 项目优势
### 教育价值
- 减轻教师出题负担
- 提供个性化练习题目
- 支持不同学习阶段的需求
### 技术特点
- **零依赖**纯Java实现无需额外库
- **跨平台**支持所有Java运行环境
- **易扩展**:清晰的架构便于功能扩展
- **高可靠**:完善的错误处理机制
### 用户体验
- 简洁的命令行界面
- 直观的操作流程
- 即时的反馈提示
## 注意事项
1. **编码要求**确保系统使用UTF-8编码避免中文乱码
2. **文件权限**:确保程序有权限在 `user_papers` 目录下创建文件
- 文件夹会在程序首次运行时自动创建
- 生成的题目文件保存在此文件夹中
3. **用户安全**:预置密码为演示用途,实际使用请修改
## 更新日志
### 第四版 (当前版本)
- 优化题目生成算法
- 修复终端执行jar包显示乱码问题
### 第三版
- 添加高中题目类型
- 改进文件管理系统
### 第二版
- 扩展初中题目类型
- 优化用户界面
### 第一版
- 基础功能实现
- 小学题目生成

@ -0,0 +1,107 @@
# 数学试卷生成器 - 终端运行说明
## 项目简介
这是一个中小学数学试卷自动生成程序,支持小学、初中、高中三个级别的数学题目生成。
## 编码说明
程序已智能适配不同操作系统的编码:
- **Windows系统**自动使用GBK编码避免CMD/PowerShell中文乱码
- **Linux/Mac系统**自动使用UTF-8编码
## 运行前准备
### 1. 编译项目
```bash
# 在项目根目录执行
javac -encoding UTF-8 -d out/production/PaperGenerator -cp src src/com/bx/generator_main/MathExamGenerator.java src/com/bx/manager/*.java src/com/bx/question/generator/*.java src/com/bx/question/QuestionFactory.java
```
### 2. 运行程序
```bash
# 在项目根目录执行
java -cp out/production/PaperGenerator com.bx.generator_main.MathExamGenerator
```
## 各终端运行方法
### Windows CMD
```cmd
# 直接运行程序会自动检测为Windows系统使用GBK编码
java -cp out/production/PaperGenerator com.bx.generator_main.MathExamGenerator
```
### Windows PowerShell
```powershell
# 直接运行程序会自动检测为Windows系统使用GBK编码
java -cp out/production/PaperGenerator com.bx.generator_main.MathExamGenerator
```
### Linux/Mac 终端
```bash
# 直接运行程序会自动检测为非Windows系统使用UTF-8编码
java -cp out/production/PaperGenerator com.bx.generator_main.MathExamGenerator
```
## 使用方法
### 登录流程
1. 程序启动后会显示:
```
系统默认编码: GBK
=== 中小学数学卷子自动生成程序 ===
请输入用户名和密码(空格分隔):
```
2. 输入用户名和密码(空格分隔),例如:
```
admin 123456
```
3. 登录成功后会显示当前选择的年级级别
### 功能操作
- **生成题目**输入题目数量10-30道
- **切换年级**:输入`切换为小学`、`切换为初中`或`切换为高中`
- **重新登录**:输入`-1`
### 示例操作流程
```
系统默认编码: GBK
=== 中小学数学卷子自动生成程序 ===
请输入用户名和密码空格分隔admin 123456
当前选择为小学出题
准备生成 小学 数学题目,请输入生成题目数量(输入-1将退出当前用户重新登录若要切换则输入切换为xx10
成功生成小学数学试卷到当前文件夹!
```
## 注意事项
1. **编码兼容性**:程序已自动适配不同操作系统,无需手动设置编码
2. **中文输入**:确保终端支持中文输入
3. **文件保存**:生成的试卷文件会保存在程序运行的当前目录
4. **用户管理**:用户账号信息存储在程序内置的数据结构中
## 常见问题
### Q: 运行后中文显示乱码?
A: 程序会自动检测操作系统并选择合适的编码,一般不会出现乱码。如果仍有乱码问题,请检查:
- 终端是否支持中文显示
- Java版本是否过旧建议Java 8及以上
### Q: 无法输入中文?
A: 确保你的终端输入法正常工作,程序已正确处理中文输入流。
### Q: 登录失败?
A: 请检查:
- 用户名和密码是否正确
- 用户名和密码之间是否有空格分隔
- 是否有多余的空格或特殊字符
## 技术实现
程序通过以下方式实现编码兼容:
1. 检测操作系统类型(`System.getProperty("os.name")`
2. Windows系统使用GBK编码其他系统使用UTF-8
3. 设置控制台输出流和错误流的编码
4. 使用对应编码创建Scanner对象处理输入
5. 设置相关系统属性确保Java运行时编码一致

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.bx.generator_main.MathExamGenerator

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.bx.generator_main.MathExamGenerator

@ -0,0 +1,143 @@
package com.bx.generator_main;
import com.bx.manager.ExamManager;
import com.bx.manager.UserManager;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Scanner;
// 数学试卷生成器主程序
public class MathExamGenerator {
protected static UserManager userManager = new UserManager();
protected static Scanner scanner;
static {
try {
// 检测系统默认编码
String defaultEncoding = System.getProperty("file.encoding");
System.out.println("系统默认编码: " + defaultEncoding);
// 检测操作系统
String osName = System.getProperty("os.name").toLowerCase();
String encoding = "UTF-8";
// Windows系统使用GBK编码以避免乱码
if (osName.contains("windows")) {
encoding = "GBK";
System.out.println("检测到Windows系统使用GBK编码");
} else {
System.out.println("检测到非Windows系统使用UTF-8编码");
}
// 设置控制台编码
System.setOut(new PrintStream(System.out, true, encoding));
System.setErr(new PrintStream(System.err, true, encoding));
// 设置系统属性
System.setProperty("file.encoding", encoding);
System.setProperty("sun.stdout.encoding", encoding);
System.setProperty("sun.stderr.encoding", encoding);
// 使用对应编码创建Scanner
scanner = new Scanner(System.in, encoding);
} catch (UnsupportedEncodingException e) {
// 如果指定编码不支持,回退到默认编码
System.err.println("指定编码不支持,使用默认编码");
scanner = new Scanner(System.in);
} catch (Exception e) {
// 其他异常也回退到默认
System.err.println("编码设置失败,使用默认编码: " + e.getMessage());
scanner = new Scanner(System.in);
}
}
public static void main(String[] args) {
System.out.println("=== 中小学数学卷子自动生成程序 ===");
// 登录验证
while (!performLogin()) {
// 继续尝试登录
}
// 登录成功后进入主菜单循环
runMainMenu();
}
//执行用户登录
public static boolean performLogin() {
System.out.print("请输入用户名和密码(空格分隔):");
String input = scanner.nextLine().trim();
String[] parts = input.split("\\s+");
if (parts.length == 2) {
String username = parts[0];
String password = parts[1];
//和存储在map容器中的账号密码比对验证
if (userManager.login(username, password)) {
System.out.println("当前选择为" + userManager.getCurrentLevel()+"出题");
return true;
}
}
System.out.println("请输入正确的用户名、密码,重新输入用户名、密码:");
return false;
}
//主菜单
private static void runMainMenu() {
while (true) {
System.out.print("准备生成 " + userManager.getCurrentLevel() + " 数学题目,请输入生成题目数量(输入-1将退出当前用户重新登录若要切换则输入切换为xx");
String input = scanner.nextLine().trim();
if ("-1".equals(input)) {
userManager.logout();
main(new String[0]);
return;
}
// 检查切换命令
if (handleSwitchCommand(input)) {
continue;
}
// 处理题目数量输入
handleQuestionCountInput(input);
}
}
//切换命令
private static boolean handleSwitchCommand(String input) {
if (input.startsWith("切换为")) {
String newLevel = input.substring(3).trim();
if ("小学".equals(newLevel) || "初中".equals(newLevel) || "高中".equals(newLevel)) {
userManager.setCurrentLevel(newLevel);
System.out.println("系统提示:准备生成 " + userManager.getCurrentLevel() + " 数学题目,请输入生成题目数量");
return true;
} else {
System.out.println("请输入小学、初中和高中三个选项中的一个");
return true;
}
}
return false;
}
//处理题目数量输入
private static void handleQuestionCountInput(String input) {
try {
int count = Integer.parseInt(input);
if (count >= 10 && count <= 30) {
ExamManager.generateExam(count, userManager.getCurrentLevel(), userManager.getCurrentUser());
} else {
System.out.println("题目数量必须在10-30之间");
}
} catch (NumberFormatException e) {
System.out.println("请输入有效的数字");
}
}
}

@ -0,0 +1,62 @@
package com.bx.manager;
import com.bx.question.QuestionFactory;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
//考试管理类 - 负责生成试卷和文件保存
public class ExamManager {
private static final Set<String> generatedQuestions = new HashSet<>();
//生成试卷 count为数量、level为难度、username为用户名
public static void generateExam(int count, String level, String username) {
List<String> questions = new ArrayList<>();
Set<String> currentExamQuestions = new HashSet<>();
for (int i = 1; i <= count; i++) {
String question = QuestionFactory.generateQuestion(level);
// 确保题目不重复
while (currentExamQuestions.contains(question) || generatedQuestions.contains(question)) {
question = QuestionFactory.generateQuestion(level);
}
currentExamQuestions.add(question);
generatedQuestions.add(question);
questions.add(i + ". " + question);
}
saveToFile(questions, username);
}
//将题目保存到用户专属文件夹
private static void saveToFile(List<String> questions, String username) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
String fileName = sdf.format(new Date()) + ".txt";
// 构建文件路径
String filePath;
if (username != null && !username.trim().isEmpty()) {
// 使用相对路径,创建用户专属文件夹
File userDir = new File("user_papers/" + username);
if (!userDir.exists()) {
userDir.mkdirs(); // 如果文件夹不存在则创建
}
filePath = "user_papers/" + username + "/" + fileName;
} else {
// 如果没有用户名,保存在当前目录
filePath = fileName;
}
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath), true)) {
for (String question : questions) {
writer.println(question);
writer.println(); // 空一行
}
System.out.println("题目已生成并保存到文件:" + filePath);
} catch (IOException e) {
System.err.println("保存文件时出错:" + e.getMessage());
}
}
}

@ -0,0 +1,71 @@
package com.bx.manager;
import java.util.*;
// 用户管理类 - 存储好了用户的账号密码在登录时调用login和getUserLevel方法验证登录是否成功以及查看等级
public class UserManager {
private static final Map<String, String> USERS = new HashMap<>();
private String currentUser = "";
private String currentLevel = "";
// 初始化用户数据
static {
USERS.put("张三1", "123");
USERS.put("张三2", "123");
USERS.put("张三3", "123");
USERS.put("李四1", "123");
USERS.put("李四2", "123");
USERS.put("李四3", "123");
USERS.put("王五1", "123");
USERS.put("王五2", "123");
USERS.put("王五3", "123");
}
// 用户登录验证
public boolean login(String username, String password) {
if (USERS.containsKey(username) && USERS.get(username).equals(password)) {
currentUser = username;
currentLevel = getUserLevel(username);
return true;
}
return false;
}
//根据用户名获取对应的教育级别
private String getUserLevel(String username) {
switch (username) {
case "张三1": return "小学";
case "张三2": return "小学";
case "张三3": return "小学";
case "李四1": return "初中";
case "李四2": return "初中";
case "李四3": return "初中";
case "王五1": return "高中";
case "王五2": return "高中";
case "王五3": return "高中";
default: return "小学";
}
}
public String getCurrentUser() {
return currentUser;
}
public String getCurrentLevel() {
return currentLevel;
}
public void setCurrentLevel(String level) {
this.currentLevel = level;
}
//登出
public void logout()
{ currentUser = "";
currentLevel = "";
}
}

@ -0,0 +1,34 @@
package com.bx.question;
import com.bx.question.generator.HighQuestionGenerator;
import com.bx.question.generator.MiddleQuestionGenerator;
import com.bx.question.generator.PrimaryQuestionGenerator;
import com.bx.question.generator.QuestionGenerator;
//题目工厂类 - 根据难度级别生成对应的题目
public class QuestionFactory {
//根据难度级别获取对应的题目生成器
private static QuestionGenerator getGenerator(String level) {
switch (level) {
case "小学":
return new PrimaryQuestionGenerator();
case "初中":
return new MiddleQuestionGenerator();
case "高中":
return new HighQuestionGenerator();
default:
return new PrimaryQuestionGenerator();
}
}
//根据难度级别生成题目
public static String generateQuestion(String level) {
QuestionGenerator generator = getGenerator(level);
return generator.generateQuestion();
}
}

@ -0,0 +1,73 @@
package com.bx.question.generator;
import java.util.Random;
// 高中题目生成器 - 生成高等数学题目
public class HighQuestionGenerator implements QuestionGenerator{
private static final Random random = new Random();
@Override
public String generateQuestion() {
int type = random.nextInt(12);
return switch (type) {
case 0 -> generateQuadraticEquation();
case 1 -> generateFunctionQuestion();
case 2 -> generateTrigonometryQuestion();
case 3 -> generateProbabilityQuestion();
case 4 -> generateSequenceQuestion();
case 5 -> generateDerivativeQuestion();
case 6 -> generateComplexNumberQuestion();
default -> generateTrigonometryQuestion();
};
}
//解一元二次方程
private static String generateQuadraticEquation() {
int a = random.nextInt(5) + 1, b = random.nextInt(10) - 5, c = random.nextInt(10) - 5;
String bStr = b >= 0 ? "+" + b : String.valueOf(b);
String cStr = c >= 0 ? "+" + c : String.valueOf(c);
return "解方程:" + a + "x² " + bStr + "x " + cStr + " = 0";
}
//函数计算
private static String generateFunctionQuestion() {
int a = random.nextInt(9) + 1, b = random.nextInt(10) - 5, x = random.nextInt(10) + 1;
String bStr = b >= 0 ? "+" + b : String.valueOf(b);
return "已知f(x) = " + a + "x " + bStr + "求f(" + x + ")";
}
//三角函数
private static String generateTrigonometryQuestion() {
String[] functions = {"sin", "cos", "tan"};
String func = functions[random.nextInt(functions.length)];
int angle = random.nextInt(4) * 30; // 0, 30, 60, 90度
return "计算:" + func + "(" + angle + "°)";
}
//概率问题
private static String generateProbabilityQuestion() {
int total = random.nextInt(20) + 10, favorable = random.nextInt(total / 2) + 1;
return "从" + total + "个球中任取1个其中有" + favorable + "个红球,求取到红球的概率";
}
//数列
private static String generateSequenceQuestion() {
int first = random.nextInt(10) + 1, diff = random.nextInt(5) + 1, n = random.nextInt(10) + 5;
return "等差数列首项a₁=" + first + "公差d=" + diff + ",求第" + n + "项";
}
//导数
private static String generateDerivativeQuestion() {
int a = random.nextInt(9) + 1, n = random.nextInt(4) + 2;
return "求函数f(x) = " + a + "x^" + n + "的导数";
}
//复数运算
private static String generateComplexNumberQuestion() {
int a = random.nextInt(9) + 1, b = random.nextInt(9) + 1;
int c = random.nextInt(9) + 1, d = random.nextInt(9) + 1;
return "计算:(" + a + "+" + b + "i) + (" + c + "+" + d + "i)";
}
}

@ -0,0 +1,114 @@
package com.bx.question.generator;
import java.util.Random;
//初中题目生成器(混合运算、分数运算、方程、开方、开根号)
public class MiddleQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
@Override
public String generateQuestion() {
int type = random.nextInt(7);
int operandCount = random.nextInt(4) + 2; // 2-5个操作数
return switch (type) {
case 0 -> generateMixedArithmetic(operandCount);
case 1 -> generateFractionQuestion();
case 2 -> generateEquationQuestion();
case 3 -> generatePowerAndRootQuestion();
default -> generatePowerAndRootQuestion();
};
}
//生成混合运算题目
private static String generateMixedArithmetic(int count) {
StringBuilder question = new StringBuilder();
String[] operators = {"+", "-", "×", "÷"};
int num = random.nextInt(100) + 1;
question.append(num);
for (int i = 1; i < count; i++) {
String op = operators[random.nextInt(operators.length)];
int nextNum = random.nextInt(20) + 1; //第二个数1-20
question.append(" ").append(op).append(" ").append(nextNum);
}
question.append(" = ?");
return question.toString();
}
//生成分数运算题目
private static String generateFractionQuestion() {
int num1 = random.nextInt(9) + 1, den1 = random.nextInt(9) + 2;
int num2 = random.nextInt(9) + 1, den2 = random.nextInt(9) + 2;
String[] ops = {"+", "-", "×", "÷"};
String op = ops[random.nextInt(ops.length)];
return num1 + "/" + den1 + " " + op + " " + num2 + "/" + den2 + " = ?";
}
//方程
private static String generateEquationQuestion() {
int type = random.nextInt(4);
switch (type) {
case 0: {
// 一元一次方程ax + b = c
int a = random.nextInt(9) + 1;
int b = random.nextInt(20) + 1;
int c = random.nextInt(30) + 1;
return "解方程:" + a + "x + " + b + " = " + c;
}
case 1: {
// 含分母的方程:(x + a)/b = c
int a = random.nextInt(10) + 1;
int b = random.nextInt(5) + 2;
int c = random.nextInt(8) + 1;
return "解方程:(x + " + a + ")/" + b + " = " + c;
}
case 2: {
// 两边都含xax + b = cx + d
int a = random.nextInt(9) + 2;
int b = random.nextInt(20) + 1;
int c = random.nextInt(5) + 1;
int d = random.nextInt(15) + 1;
return "解方程:" + a + "x + " + b + " = " + c + "x + " + d;
}
default: {
// 含括号的方程a(x + b) = c
int a = random.nextInt(5) + 2;
int b = random.nextInt(10) + 1;
int c = random.nextInt(30) + 10;
return "解方程:" + a + "(x + " + b + ") = " + c;
}
}
}
//平方、开方、立方、幂运算
private static String generatePowerAndRootQuestion() {
int type = random.nextInt(4);
switch (type) {
case 0: {
// 平方运算
int base = random.nextInt(15) + 1;
return "计算:" + base + "² = ?";
}
case 1: {
// 开平方运算√a
int[] perfectSquares = {4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225};
int square = perfectSquares[random.nextInt(perfectSquares.length)];
return "计算:√" + square + " = ?";
}
case 2: {
// 立方运算
int base = random.nextInt(8) + 1;
return "计算:" + base + "³ = ?";
}
default: {
// 幂运算a^n
int base = random.nextInt(10) + 2;
int exponent = random.nextInt(3) + 2; // 指数2-4
return "计算:" + base + "^" + exponent + " = ?";
}
}
}
}

@ -0,0 +1,161 @@
package com.bx.question.generator;
import java.util.Random;
//小学题目生成器
public class PrimaryQuestionGenerator implements QuestionGenerator {
private static final Random random = new Random();
// 操作数范围
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 100;
// 运算符数组
private static final String[] OPERATORS = {"+", "-", "x", "÷"};
@Override
public String generateQuestion() {
// 50%概率生成单一运算符50%概率生成复合运算符
if (random.nextBoolean()) {
return generateSingleOperatorQuestion();
} else {
return generateMixedOperatorQuestion();
}
}
//生成符合范围的操作数
private int generateRandomNumber() {
return random.nextInt(MAX_NUMBER) + MIN_NUMBER;
}
//单一运算符
private String generateSingleOperatorQuestion() {
String operator = OPERATORS[random.nextInt(4)]; // 随机选择一个运算符
int operandCount = random.nextInt(4) + 2; // 2-5个操作数
StringBuilder question = new StringBuilder();
// 生成第一个数
question.append(generateRandomNumber());
// 生成后续的数和运算符
for (int i = 1; i < operandCount; i++) {
question.append(" ").append(operator).append(" ").append(generateRandomNumber());
}
return question.append(" = ?").toString();
}
//复合运算符
private String generateMixedOperatorQuestion() {
int choice = random.nextInt(3);
if (choice == 0) {
return generateMixedWithoutBrackets();
} else if (choice == 1) {
return generateMixedWithBrackets3();
} else
return generateMixedWithBrackets4();
}
//生成无括号的混合运算
private String generateMixedWithoutBrackets() {
StringBuilder question = new StringBuilder();
int count = random.nextInt(4) + 2;
String[] operators = {"+", "-", "×", "÷"};
int num = random.nextInt(100) + 1;
question.append(num);
for (int i = 1; i < count; i++) {
String op = operators[random.nextInt(operators.length)];
int nextNum = random.nextInt(20) + 1; //下一个数1-20
question.append(" ").append(op).append(" ").append(nextNum);
}
question.append(" = ?");
return question.toString();
}
//生成带括号的混合运算(三个操作数)
private String generateMixedWithBrackets3() {
StringBuilder question = new StringBuilder();
// 括号内的运算
int innerNum1 = generateRandomNumber();
int innerNum2 = generateRandomNumber();
String innerOp = OPERATORS[random.nextInt(4)];
// 括号外的数字和运算符
int outerNum = generateRandomNumber();
String outerOp = OPERATORS[random.nextInt(4)];
// 随机决定括号在前还是在后
if (random.nextBoolean()) {
// 括号在前:(a op b) op c
question.append("(").append(innerNum1).append(" ").append(innerOp)
.append(" ").append(innerNum2).append(") ").append(outerOp)
.append(" ").append(outerNum);
} else {
// 括号在后a op (b op c)
question.append(outerNum).append(" ").append(outerOp).append(" (")
.append(innerNum1).append(" ").append(innerOp).append(" ")
.append(innerNum2).append(")");
}
return question.append(" = ?").toString();
}
//生成带括号的混合运算(四个操作数)
private String generateMixedWithBrackets4() {
StringBuilder question = new StringBuilder();
int choice = random.nextInt(3);
if (choice == 0) {
// 格式1: (a op b) op (c op d)
int num1 = generateRandomNumber();
int num2 = generateRandomNumber();
int num3 = generateRandomNumber();
int num4 = generateRandomNumber();
String op1 = OPERATORS[random.nextInt(4)];
String op2 = OPERATORS[random.nextInt(4)];
String op3 = OPERATORS[random.nextInt(4)];
question.append("(").append(num1).append(" ").append(op1).append(" ")
.append(num2).append(") ").append(op2).append(" (")
.append(num3).append(" ").append(op3).append(" ").append(num4).append(")");
} else if (choice == 1) {
// 格式2: (a op b op c) op d
int num1 = generateRandomNumber();
int num2 = generateRandomNumber();
int num3 = generateRandomNumber();
int num4 = generateRandomNumber();
String op1 = OPERATORS[random.nextInt(4)];
String op2 = OPERATORS[random.nextInt(4)];
String op3 = OPERATORS[random.nextInt(4)];
question.append("(").append(num1).append(" ").append(op1).append(" ")
.append(num2).append(" ").append(op2).append(" ").append(num3)
.append(") ").append(op3).append(" ").append(num4);
} else {
// 格式3: a op (b op c op d)
int num1 = generateRandomNumber();
int num2 = generateRandomNumber();
int num3 = generateRandomNumber();
int num4 = generateRandomNumber();
String op1 = OPERATORS[random.nextInt(4)];
String op2 = OPERATORS[random.nextInt(4)];
String op3 = OPERATORS[random.nextInt(4)];
question.append(num1).append(" ").append(op1).append(" (")
.append(num2).append(" ").append(op2).append(" ").append(num3)
.append(" ").append(op3).append(" ").append(num4).append(")");
}
return question.append(" = ?").toString();
}
}

@ -0,0 +1,5 @@
package com.bx.question.generator;
public interface QuestionGenerator {
String generateQuestion();
}
Loading…
Cancel
Save