合并 #1

Merged
hnu202326010130 merged 1 commits from develop into main 4 months ago

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

@ -0,0 +1,148 @@
# 数学试卷生成系统
## 项目简介
这是一个智能数学试卷生成系统,能够根据不同年级(小学、初中、高中)自动生成相应难度的数学题目,并保存为试卷文件。
## 系统特性
### 🎯 核心功能
- **多年级支持**:支持小学、初中、高中三个年级的题目生成
- **智能题目生成**:根据年级自动调整题目难度和类型
- **重复检测**:防止生成重复题目,确保试卷质量
- **文件保存**:自动保存试卷到指定目录,支持时间戳命名
### 🚀 性能优化
- **高效缓存机制**智能缓存用户题目支持LRU和时间过期策略
- **优化文件I/O**使用NIO和缓冲区提升文件操作性能
- **内存管理**:自动清理过期缓存,防止内存泄漏
- **异常处理**:完善的错误处理和降级机制
### 🎨 用户体验
- **美观界面**:现代化的控制台界面设计
- **进度显示**:题目生成过程中显示实时进度条
- **即时预览**:生成完成后立即预览题目内容
- **友好提示**:清晰的操作指引和状态反馈
## 题目类型
### 小学数学
- 基础四则运算(加减乘除)
- 简单括号表达式
- 数值范围1-100
### 初中数学
- 复杂四则运算
- 多层括号嵌套
- 平方运算
- 数值范围1-1000
### 高中数学
- 三角函数sin, cos, tan
- 根式运算(√)
- 幂运算
- 复合函数表达式
- 数值范围1-10000
## 系统架构
```
src/com/mathpaper/
├── Main.java # 主程序入口
├── model/
│ └── User.java # 用户模型
├── generator/
│ ├── QuestionGenerator.java # 题目生成器接口
│ ├── PrimaryGenerator.java # 小学题目生成器
│ ├── JuniorGenerator.java # 初中题目生成器
│ └── SeniorGenerator.java # 高中题目生成器
└── util/
├── FileUtil.java # 文件操作工具
└── DuplicateChecker.java # 重复检测工具
```
## 使用方法
### 启动程序
```bash
# Windows PowerShell
.\start.ps1
# 或直接运行
java com.mathpaper.Main
```
### 操作流程
1. **登录**输入用户名3-20位字母、数字或下划线
2. **选择年级**:选择小学、初中或高中
3. **生成试卷**输入题目数量1-50道
4. **查看结果**:系统自动保存并预览生成的试卷
### 文件结构
```
doc/paper/用户名/时间戳.txt
```
例如:`doc/paper/test123/2025-09-28-22-50-30.txt`
## 技术特点
### 代码质量
- **模块化设计**:清晰的分层架构,职责分离
- **设计模式**:使用工厂模式、策略模式等
- **异常处理**:完善的错误处理和用户友好的提示
- **代码规范**:统一的编码风格和命名规范
### 性能优化
- **缓存策略**
- LRU缓存淘汰算法
- 时间过期自动清理
- 最大缓存用户数限制
- **文件I/O优化**
- NIO文件操作
- 8KB缓冲区
- UTF-8编码支持
- **内存管理**
- 自动垃圾回收
- 资源及时释放
- 内存泄漏防护
### 用户体验
- **响应式界面**:实时进度反馈
- **智能提示**:操作指引和错误提示
- **数据验证**:输入参数严格验证
- **容错处理**:优雅的错误恢复机制
## 开发环境
- **Java版本**Java 8+
- **编码格式**UTF-8
- **操作系统**Windows/Linux/macOS
- **依赖**无外部依赖纯Java实现
## 版本历史
### v2.1 (当前版本)
- ✅ 完成性能优化和代码重构
- ✅ 优化用户界面和交互体验
- ✅ 增强缓存机制和内存管理
- ✅ 改进文件I/O操作效率
- ✅ 完善异常处理和错误恢复
### v2.0
- ✅ 重构代码架构,提升可维护性
- ✅ 优化题目生成算法
- ✅ 增加重复检测功能
- ✅ 改进用户界面设计
### v1.0
- ✅ 基础功能实现
- ✅ 多年级题目生成
- ✅ 文件保存功能
## 贡献指南
欢迎提交Issue和Pull Request来改进这个项目
## 许可证
本项目采用MIT许可证。

@ -0,0 +1,15 @@
@echo off
chcp 65001 >nul
echo 启动数学试卷生成系统...
echo.
echo === 可用用户账号 ===
echo 小学用户: 张三1, 张三2, 张三3, zhangsan1, zhangsan2, zhangsan3
echo 初中用户: 李四1, 李四2, 李四3, lisi1, lisi2, lisi3
echo 高中用户: 王五1, 王五2, 王五3, wangwu1, wangwu2, wangwu3
echo 测试账号: test123, 测试
echo ===============================
echo.
echo 正在启动程序...
cd /d "%~dp0..\.."
java -cp src com.mathpaper.Main
pause

@ -0,0 +1,28 @@
# Math Paper Generation System Startup Script
# Automatically set UTF-8 encoding and start the program
Write-Host "启动数学试卷生成系统..." -ForegroundColor Green
# Set console encoding to UTF-8
Write-Host "设置字符编码为UTF-8..." -ForegroundColor Yellow
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
Write-Host "编码设置完成!" -ForegroundColor Green
Write-Host ""
# Display available user accounts
Write-Host "=== 可用用户账号 ===" -ForegroundColor Cyan
Write-Host "小学用户: 张三1, 张三2, 张三3, zhangsan1, zhangsan2, zhangsan3" -ForegroundColor White
Write-Host "初中用户: 李四1, 李四2, 李四3, lisi1, lisi2, lisi3" -ForegroundColor White
Write-Host "高中用户: 王五1, 王五2, 王五3, wangwu1, wangwu2, wangwu3" -ForegroundColor White
Write-Host "测试账号: test123, 测试" -ForegroundColor White
Write-Host "===============================" -ForegroundColor Cyan
Write-Host ""
# Change to project root directory
Set-Location (Join-Path $PSScriptRoot "..\..")
# Start Java program
Write-Host "正在启动程序..." -ForegroundColor Green
java -cp src com.mathpaper.Main

@ -0,0 +1,5 @@
1. 82 + 16 + 23 * (35 + 94)
2. 56
3. 23 + 90 - 15 * (76 + 26)

@ -0,0 +1,5 @@
1. 42 - 60
2. (19 * 60 + 9) + 64 + 89
3. 79

@ -0,0 +1,5 @@
1. 69 + 54 * 22
2. 6
3. 32 / 24 / 47

@ -0,0 +1,5 @@
1. 31 / (37 / 66)
2. 61 / 34 + (91 * 94) + 78
3. 93 + 77 * 2 + 54

@ -0,0 +1,5 @@
1. 47 * 35
2. (25 + 39 / 16) * 2 - 93
3. 28 + 60

@ -0,0 +1,5 @@
1. 20 * 32
2. 74 + 35
3. 95 * 14

@ -0,0 +1,23 @@
1. 100 - 55 + 32
2. 80
3. 92
4. 76 * 100 / 2 / 73 * 6
5. 37 * 52 + 90
6. 36 - 73
7. 26 * (95 * 90) + 60
8. 100
9. 82 - 2
10. 67
11. 27 * 22 / 30 * 52
12. 98 - 41 / (95 / 83)

@ -0,0 +1,9 @@
1. 77
2. 10 - (98 * 46)
3. (63 + 61 * 45)
4. 10 + 98
5. (51 - 48 + 2)

@ -0,0 +1,39 @@
1. sin(3) * tan(3)
2. tan(√(5+2)) + tan(5) - 8 / 10 + 4
3. sin(6) / sin(2) + 6 * 4^2 + √7
4. tan(7) - 7^2 - (3 * 1 * tan(3))
5. cos(5) - √2 / 4^2
6. tan(√(8+4)) - 1
7. sin(√(8+2)) + √7 * 8
8. tan(√(4+4)) - tan(9) + √3 + √3
9. cos(3) - 5
10. sin(2) / tan(2) / 7
11. cos(3) * 4 * 5
12. sin(6) / sin(4) - 6^2 + 7
13. tan(√(8+3)) - tan(9) / 10 / 7
14. cos(6) - √9 - 9^2 + 2^2 * 2^2
15. tan(10) - 7 (* tan(6) + 9)
16. tan(√(10+2)) + cos(3)
17. cos(√(4+1)) * 7
18. tan(10) - √2 / tan(√(10+3)) / 4 / 7^2
19. sin(6) - sin(2) - 1 / √8 - 8
20. tan(3) / cos(5)

@ -0,0 +1,5 @@
1. 66 / 95 + 87
2. (27 / 36 / 19) * 68
3. 1 / 2

@ -0,0 +1,5 @@
1. 33
2. 27 + 99 - (24 + 51 / 26)
3. (25 + 26) + 71

@ -0,0 +1,31 @@
1. 39
2. 76 - 14 / 8
3. 49
4. 11 - 76 * (58 / 38 - 73)
5. 16 - 61 * 69
6. (47 / 82 / 65)
7. (62 / 70) / 10
8. 94 * 27 + 50 - 10
9. 44 - (2 + 76 + 58) - 2
10. 27
11. 57 / 60 + 23
12. 90 * 63
13. 72 + 81 + 54 * 1
14. 46 / 92
15. 58 + 12
16. (49 * 19 * 69 * 9)

@ -0,0 +1,23 @@
1. 54
2. 100 / 73 + 69 * 11 / 60
3. 8 + 4 * 40 * 60
4. 91
5. 88 / 58 - 77
6. 2
7. 89 * 87 - 99
8. 89 - 36 + 12
9. 90 / 21
10. 61 - 16 / 93 * 57 * 68
11. 33 * (96 / 45)
12. 14 + (63 + 26)

@ -0,0 +1,25 @@
1. tan(2) / 2 + 6 - √7 - tan(√(2+1))
2. sin(6) * 6^2 / tan(4)
3. sin(1) * cos(10) / √5 + 1
4. tan(√(10+5)) + cos(6) / 10^2 * √6 + 7
5. tan(8) / (4 / 9 + 1)
6. (tan(8) / 9^2 - 6^2 /) 2
7. tan(1) * √6 - sin(7) * 5 * √5
8. cos(8) / cos(8) / √5 - √2 / 10
9. cos(√(1+2)) * 10 / tan(2) / 5 + 3
10. cos(√(1+1)) * 5 + 6 + 6
11. sin(5) * 9^2 - tan(7) / 4 * 10
12. sin(10) / √8 + 8^2 * 6^2 - 5
13. tan(4) * 10

@ -0,0 +1,25 @@
1. √23 * 31
2. 18^2 - 1 + √23 / 18
3. 23^2 - (33 / 25)
4. 27^2 - 9^2
5. √10 - 22^2 * 7
6. 40^2 * √25 * 12 - 16
7. √25 / √1 * 18 * 8 + 32
8. 7^2 / √24 / (50 * 35)
9. 5^2 (* 21 + 35) / 9 * 33
10. √25 - 48^2 + 9
11. 20^2 * 48 + 39
12. √25 / 38 - 21 * 28 * 32
13. 4^2 / √2 * 15

@ -0,0 +1,29 @@
1. 65 / 18
2. 28 * 78
3. 22 + 93 * 63 * 98
4. 25 - 33 - (35 + 18 / 82)
5. 59
6. 98 + 52 * 94
7. 100 * (87 + 51)
8. (15 / 44 / 55)
9. 37 - 48 * 84
10. 10 * 38
11. 76
12. 67 / (23 * 85)
13. 38 - 100 * 77
14. 94 * 33
15. 38

@ -0,0 +1,27 @@
1. 10^2 + 42 - 20
2. √5 * 20
3. (√20 - √25 + 11) / 26 + 29
4. √23 * 6 / 32
5. 31^2 * (35 - √25)
6. √25 + 41 * 11 * 8
7. 48^2 / 7
8. √25 / 17
9. 14^2 / 3
10. √25 / 1 + √3 * 21
11. √25 * (17 / 32^2)
12. 26^2 / 49 - 15^2
13. 43^2 + (49 - 18^2)
14. 49^2 / 27 - 28^2 / 13 + 5

@ -0,0 +1,25 @@
1. cos(1) * 4 * tan(√(6+5)) * 2 / √6
2. cos(10) * sin(√(10+2)) / √1
3. sin(√(3+3)) + sin(√(5+2)) + 6 / 10 + 9
4. cos(√(9+2)) / √1 * 9^2
5. sin(9) + tan(√(2+5))
6. cos(8) - 1 - sin(√(10+2)) + 8
7. tan(2) - 5 + tan(9) / 1
8. tan(3) / 10 * tan(4) + 7 + 7^2
9. tan(√(9+4)) - tan(√(7+4))
10. tan(1) / sin(√(3+2)) / 4
11. tan(10) / 3 * 3 - cos(√(2+5)) * sin(10)
12. sin(3) + √3 / 5
13. tan(5) / cos(7) / sin(√(6+3)) - tan(3) + 2

Binary file not shown.

@ -0,0 +1,469 @@
package com.mathpaper;
import com.mathpaper.model.User;
import com.mathpaper.generator.QuestionGenerator;
import com.mathpaper.generator.PrimaryGenerator;
import com.mathpaper.generator.JuniorGenerator;
import com.mathpaper.generator.SeniorGenerator;
import com.mathpaper.util.FileUtil;
import com.mathpaper.util.DuplicateChecker;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**
*
*
*
* @author
* @version 2.0
*/
public class Main {
// 常量定义
private static final int MIN_QUESTIONS = 1;
private static final int MAX_QUESTIONS = 100;
private static final int PREVIEW_COUNT = 5;
private static final int MAX_ATTEMPTS_MULTIPLIER = 10;
private static final String GRADE_SWITCH_PREFIX = "切换为";
private static final String EXIT_COMMAND = "-1";
// 当前年级(全局变量)
private static String currentGrade = "小学";
// 界面美化常量
private static final String SEPARATOR = "=" + "=".repeat(50);
private static final String MENU_SEPARATOR = "=" + "=".repeat(50);
private static final int PROGRESS_BAR_LENGTH = 30;
// 预设用户账号
private static final Map<String, User> USERS = new HashMap<>();
static {
initializeUsers();
}
/**
*
*/
private static void initializeUsers() {
// 小学用户(中文和英文用户名)
addUser("张三1", "123", "小学");
addUser("张三2", "123", "小学");
addUser("张三3", "123", "小学");
addUser("zhangsan1", "123", "小学");
addUser("zhangsan2", "123", "小学");
addUser("zhangsan3", "123", "小学");
addUser("primary1", "123", "小学");
addUser("primary2", "123", "小学");
// 初中用户(中文和英文用户名)
addUser("李四1", "123", "初中");
addUser("李四2", "123", "初中");
addUser("李四3", "123", "初中");
addUser("lisi1", "123", "初中");
addUser("lisi2", "123", "初中");
addUser("lisi3", "123", "初中");
addUser("junior1", "123", "初中");
addUser("junior2", "123", "初中");
// 高中用户(中文和英文用户名)
addUser("王五1", "123", "高中");
addUser("王五2", "123", "高中");
addUser("王五3", "123", "高中");
addUser("wangwu1", "123", "高中");
addUser("wangwu2", "123", "高中");
addUser("wangwu3", "123", "高中");
addUser("senior1", "123", "高中");
addUser("senior2", "123", "高中");
// 测试账号
addUser("test123", "123", "小学");
addUser("测试", "123", "小学");
}
/**
*
*/
private static void addUser(String username, String password, String grade) {
// 由于User类已简化这里只存储用户名
try {
USERS.put(username, new User(username));
} catch (IllegalArgumentException e) {
System.err.println("添加用户失败: " + username + " - " + e.getMessage());
}
}
/**
*
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
// 定期清理过期缓存
DuplicateChecker.cleanupExpiredCache();
// 用户登录
User user = performLogin(scanner);
if (user == null) {
System.out.println("登录失败,程序退出");
return;
}
// 主交互循环
runMainLoop(scanner, user);
} catch (Exception e) {
System.err.println("程序运行出现异常: " + e.getMessage());
} finally {
scanner.close();
}
}
/**
*
*/
private static User performLogin(Scanner scanner) {
System.out.println("=== 用户登录 ===");
System.out.println("请输入用户名和密码进行登录");
System.out.println();
while (true) {
System.out.print("用户名: ");
String username = scanner.nextLine().trim();
// 验证用户名格式
String validationError = User.getValidationError(username);
if (validationError != null) {
System.out.println("❌ " + validationError);
System.out.println("请重新输入:");
continue;
}
// 检查用户是否存在
if (!USERS.containsKey(username)) {
System.out.println("❌ 用户名不存在,请检查输入或使用以下用户名:");
System.out.println("中文用户张三1, 张三2, 张三3, 李四1, 李四2, 李四3, 王五1, 王五2, 王五3, 测试");
System.out.println("英文用户zhangsan1, zhangsan2, zhangsan3, lisi1, lisi2, lisi3, wangwu1, wangwu2, wangwu3, test123");
System.out.println("请重新输入:");
continue;
}
System.out.print("密码: ");
String password = scanner.nextLine().trim();
// 验证密码所有用户密码都是123
if (!"123".equals(password)) {
System.out.println("❌ 密码错误!所有用户的密码都是 123");
System.out.println("请重新输入:");
continue;
}
try {
User user = new User(username);
System.out.println("✅ 登录成功!欢迎," + username + "");
System.out.println();
return user;
} catch (IllegalArgumentException e) {
System.out.println("❌ " + e.getMessage());
System.out.println("请重新输入:");
}
}
}
/**
*
*/
private static User validateLogin(String input) {
String[] parts = input.split("\\s+");
if (parts.length != 2) {
return null;
}
String username = parts[0].trim();
String password = parts[1].trim();
// 简单验证:检查用户是否存在
User user = USERS.get(username);
if (user != null) {
return user;
}
return null;
}
/**
*
*/
private static void runMainLoop(Scanner scanner, User user) {
QuestionGenerator generator = getGenerator(currentGrade);
while (true) {
displayMainMenu(user);
String choice = scanner.nextLine().trim();
switch (choice) {
case "1":
handleGradeSwitch(scanner, user);
generator = getGenerator(currentGrade);
break;
case "2":
handleQuestionGeneration(scanner, user, generator);
break;
case "3":
displayExitMessage();
return;
default:
System.out.println("❌ 无效选择,请重新输入!");
}
}
}
/**
*
*/
private static void displayWelcomeScreen(User user) {
System.out.println(SEPARATOR);
System.out.println("🎓 数学题目生成系统");
System.out.println("👤 用户:" + user.getUsername());
System.out.println("📚 当前年级:" + currentGrade);
System.out.println(SEPARATOR);
}
/**
*
*/
private static void displayMainMenu(User user) {
System.out.println(MENU_SEPARATOR);
System.out.println("📚 当前年级:" + currentGrade);
System.out.println("👤 当前用户:" + user.getUsername());
System.out.println();
System.out.println("1. 切换年级");
System.out.println("2. 生成试卷");
System.out.println("3. 退出系统");
System.out.println(MENU_SEPARATOR);
System.out.print("\n🔹 请选择操作(当前:" + currentGrade + "");
}
/**
* 线
*/
private static void displaySeparator() {
System.out.println("\n" + "-".repeat(30) + "\n");
}
/**
* 退
*/
private static void displayExitMessage() {
System.out.println("\n" + SEPARATOR);
System.out.println("👋 感谢使用数学题目生成系统!");
System.out.println("🎉 祝您学习愉快,再见!");
System.out.println(SEPARATOR);
}
/**
*
*/
private static QuestionGenerator getGenerator(String grade) {
switch (grade) {
case "小学":
return new PrimaryGenerator();
case "初中":
return new JuniorGenerator();
case "高中":
return new SeniorGenerator();
default:
System.err.println("未知年级: " + grade + ",使用小学模式");
return new PrimaryGenerator();
}
}
/**
*
*/
private static boolean isValidGrade(String grade) {
return "小学".equals(grade) || "初中".equals(grade) || "高中".equals(grade);
}
/**
*
*/
private static void generateAndSavePaper(QuestionGenerator generator, int count, User user) {
try {
List<String> questions = generateQuestionsWithProgress(generator, count, user.getUsername());
if (questions.isEmpty()) {
System.out.println("❌ 生成失败:无法生成题目,请稍后重试");
return;
}
if (questions.size() < count) {
System.out.println("⚠️ 注意:由于查重限制,实际生成了 " + questions.size() + " 道题目");
}
System.out.print("💾 正在保存试卷...");
String path = FileUtil.getSavePath(user.getUsername());
FileUtil.savePaper(path, questions);
System.out.println(" 完成!");
System.out.println("✅ 试卷已保存至:" + path);
displayQuestionPreview(questions);
} catch (Exception e) {
System.err.println("❌ 生成试卷时出现错误: " + e.getMessage());
}
}
/**
*
*/
private static List<String> generateQuestionsWithProgress(QuestionGenerator generator, int count, String username) {
List<String> questions = new ArrayList<>();
generator.clearCurrentQuestions();
int attempts = 0;
int maxAttempts = count * MAX_ATTEMPTS_MULTIPLIER;
System.out.println("📊 生成进度:");
while (questions.size() < count && attempts < maxAttempts) {
try {
String question = generator.generateUniqueQuestion();
if (question != null && !question.trim().isEmpty()) {
// 检查是否与历史题目重复
if (!DuplicateChecker.isDuplicate(question, username)) {
questions.add(question);
// 显示进度
displayProgress(questions.size(), count);
}
}
} catch (Exception e) {
System.err.println("⚠️ 生成题目时出现错误: " + e.getMessage());
}
attempts++;
}
if (questions.size() < count && attempts >= maxAttempts) {
System.out.println("\n⚠ 提示:已达到最大尝试次数,可能存在题目重复过多的情况");
} else if (questions.size() == count) {
System.out.println("\n🎉 题目生成完成!");
}
return questions;
}
/**
*
*/
private static void displayProgress(int current, int total) {
int percentage = (int) ((double) current / total * 100);
int progressLength = (int) ((double) current / total * PROGRESS_BAR_LENGTH);
StringBuilder progressBar = new StringBuilder();
progressBar.append("[");
for (int i = 0; i < PROGRESS_BAR_LENGTH; i++) {
if (i < progressLength) {
progressBar.append("█");
} else {
progressBar.append("░");
}
}
progressBar.append("]");
System.out.print("\r" + progressBar.toString() + " " + current + "/" + total + " (" + percentage + "%)");
if (current == total) {
System.out.println(); // 换行
}
}
/**
*
*/
private static void displayQuestionPreview(List<String> questions) {
System.out.println("\n📝 题目预览");
System.out.println("─".repeat(40));
int displayCount = Math.min(PREVIEW_COUNT, questions.size());
for (int i = 0; i < displayCount; i++) {
System.out.println((i + 1) + ". " + questions.get(i));
}
if (questions.size() > PREVIEW_COUNT) {
System.out.println(" ... 还有 " + (questions.size() - PREVIEW_COUNT) + " 道题目");
}
System.out.println("─".repeat(40));
System.out.println("📊 总计:" + questions.size() + " 道题目");
}
/**
*
*/
private static void handleGradeSwitch(Scanner scanner, User user) {
System.out.println("\n📚 请选择年级:");
System.out.println("1. 小学");
System.out.println("2. 初中");
System.out.println("3. 高中");
System.out.print("请输入选择1-3");
String choice = scanner.nextLine().trim();
String newGrade = null;
switch (choice) {
case "1":
newGrade = "小学";
break;
case "2":
newGrade = "初中";
break;
case "3":
newGrade = "高中";
break;
default:
System.out.println("❌ 无效选择!");
return;
}
if (!newGrade.equals(currentGrade)) {
currentGrade = newGrade;
System.out.println("✅ 已切换到" + currentGrade + "模式");
} else {
System.out.println(" 当前已是" + currentGrade + "模式");
}
}
/**
*
*/
private static void handleQuestionGeneration(Scanner scanner, User user, QuestionGenerator generator) {
System.out.print("\n📝 请输入要生成的题目数量1-50");
String input = scanner.nextLine().trim();
try {
int count = Integer.parseInt(input);
if (count < 1 || count > 50) {
System.out.println("❌ 题目数量必须在1-50之间");
return;
}
System.out.println("\n🔄 开始生成" + count + "道" + currentGrade + "数学题目...");
generateAndSavePaper(generator, count, user);
} catch (NumberFormatException e) {
System.out.println("❌ 请输入有效的数字!");
}
}
}

@ -0,0 +1,151 @@
package com.mathpaper.generator;
import java.util.ArrayList;
import java.util.List;
/**
*
* 2-51^2
*
* @author
* @version 2.0
*/
public class JuniorGenerator extends QuestionGenerator {
// 常量定义
private static final int MIN_OPERANDS = 2;
private static final int MAX_OPERANDS = 5;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 50; // 避免数字过大
private static final String[] OPERATORS = {"+", "-", "*", "/"};
private static final double ADVANCED_OP_PROBABILITY = 0.3; // 30%概率添加高级运算
private static final double BRACKET_PROBABILITY = 0.4; // 40%概率添加括号
/**
*
*/
public JuniorGenerator() {
super("初中");
}
@Override
public String generateQuestion() {
int numCount = randomInt(MIN_OPERANDS, MAX_OPERANDS);
List<Integer> nums = generateNumbers(numCount);
List<String> ops = generateOperators(numCount - 1);
return addAdvancedOperations(nums, ops);
}
/**
* 1-50
* @param count
* @return
*/
private List<Integer> generateNumbers(int count) {
List<Integer> nums = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
nums.add(randomInt(MIN_NUMBER, MAX_NUMBER));
}
return nums;
}
/**
*
* @param count
* @return
*/
private List<String> generateOperators(int count) {
List<String> ops = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ops.add(randomChoice(OPERATORS));
}
return ops;
}
/**
*
* @param nums
* @param ops
* @return
*/
private String addAdvancedOperations(List<Integer> nums, List<String> ops) {
StringBuilder sb = new StringBuilder(nums.size() * 12); // 预估容量
// 确保至少有一个平方或开根号
boolean hasAdvanced = false;
int advancedCount = 0;
for (int i = 0; i < nums.size(); i++) {
int num = nums.get(i);
// 决定是否添加高级运算
boolean shouldAddAdvanced = !hasAdvanced ||
(advancedCount < 2 && random.nextDouble() < ADVANCED_OP_PROBABILITY);
if (shouldAddAdvanced) {
if (random.nextBoolean()) {
// 添加平方
sb.append(num).append("^2");
} else {
// 添加开根号(选择较小的数避免复杂计算)
int sqrtNum = Math.min(num, 25); // 限制开根号的数不超过25
sb.append("√").append(sqrtNum);
}
hasAdvanced = true;
advancedCount++;
} else {
sb.append(num);
}
if (i < ops.size()) {
sb.append(" ").append(ops.get(i)).append(" ");
}
}
// 添加嵌套括号
return addNestedBrackets(sb.toString());
}
/**
*
* @param expression
* @return
*/
private String addNestedBrackets(String expression) {
if (random.nextDouble() >= BRACKET_PROBABILITY) {
return expression;
}
String[] parts = expression.split(" ");
if (parts.length < 3) {
return expression;
}
// 选择括号范围(确保至少包含一个完整的运算)
int maxStart = parts.length - 3; // 至少需要 数字 运算符 数字
int start = randomInt(0, maxStart);
int minEnd = start + 2; // 至少包含3个部分
int maxEnd = Math.min(start + 6, parts.length - 1); // 最多包含7个部分
int end = randomInt(minEnd, maxEnd);
StringBuilder result = new StringBuilder(expression.length() + 4);
for (int i = 0; i < parts.length; i++) {
if (i == start) {
result.append("(");
}
result.append(parts[i]);
if (i == end) {
result.append(")");
}
if (i < parts.length - 1) {
result.append(" ");
}
}
return result.toString();
}
}

@ -0,0 +1,130 @@
package com.mathpaper.generator;
import java.util.ArrayList;
import java.util.List;
/**
*
* 1-51
*
* @author
* @version 2.0
*/
public class PrimaryGenerator extends QuestionGenerator {
// 常量定义
private static final int MIN_OPERANDS = 1;
private static final int MAX_OPERANDS = 5;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 100;
private static final String[] OPERATORS = {"+", "-", "*", "/"};
private static final double BRACKET_PROBABILITY = 0.5; // 50%概率添加括号
/**
*
*/
public PrimaryGenerator() {
super("小学");
}
@Override
public String generateQuestion() {
int numCount = randomInt(MIN_OPERANDS, MAX_OPERANDS);
List<Integer> nums = generateNumbers(numCount);
List<String> ops = generateOperators(numCount - 1);
return addBrackets(nums, ops);
}
/**
* 1-100
* @param count
* @return
*/
private List<Integer> generateNumbers(int count) {
List<Integer> nums = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
nums.add(randomInt(MIN_NUMBER, MAX_NUMBER));
}
return nums;
}
/**
*
* @param count
* @return
*/
private List<String> generateOperators(int count) {
List<String> ops = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ops.add(randomChoice(OPERATORS));
}
return ops;
}
/**
* 1
* @param nums
* @param ops
* @return
*/
private String addBrackets(List<Integer> nums, List<String> ops) {
// 随机决定是否添加括号需要至少3个操作数
if (nums.size() >= 3 && random.nextDouble() < BRACKET_PROBABILITY) {
return addSimpleBrackets(nums, ops);
}
return buildBasicExpression(nums, ops);
}
/**
*
* @param nums
* @param ops
* @return
*/
private String buildBasicExpression(List<Integer> nums, List<String> ops) {
StringBuilder sb = new StringBuilder(nums.size() * 8); // 预估容量
for (int i = 0; i < nums.size(); i++) {
sb.append(nums.get(i));
if (i < ops.size()) {
sb.append(" ").append(ops.get(i)).append(" ");
}
}
return sb.toString();
}
/**
*
* @param nums
* @param ops
* @return
*/
private String addSimpleBrackets(List<Integer> nums, List<String> ops) {
StringBuilder sb = new StringBuilder(nums.size() * 10); // 预估容量(包含括号)
// 随机选择括号位置确保至少包含2个操作数
int maxStart = nums.size() - 2;
int bracketStart = randomInt(0, maxStart);
int bracketEnd = randomInt(bracketStart + 1, nums.size() - 1);
for (int i = 0; i < nums.size(); i++) {
if (i == bracketStart) {
sb.append("(");
}
sb.append(nums.get(i));
if (i == bracketEnd) {
sb.append(")");
}
if (i < ops.size()) {
sb.append(" ").append(ops.get(i)).append(" ");
}
}
return sb.toString();
}
}

@ -0,0 +1,194 @@
package com.mathpaper.generator;
import java.util.HashSet;
import java.util.Set;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
*
*
*
* @author
* @version 2.0
*/
public abstract class QuestionGenerator {
// 常量定义
protected static final int MAX_GENERATION_ATTEMPTS = 50;
protected static final int INITIAL_SET_CAPACITY = 64;
protected final String grade;
protected final Set<String> currentQuestions; // 本次生成的题目(防重复)
protected final Random random;
// 性能统计
private int totalAttempts = 0;
private int successfulGenerations = 0;
/**
*
* @param grade
*/
public QuestionGenerator(String grade) {
if (grade == null || grade.trim().isEmpty()) {
throw new IllegalArgumentException("年级不能为空");
}
this.grade = grade.trim();
this.currentQuestions = new HashSet<>(INITIAL_SET_CAPACITY);
this.random = ThreadLocalRandom.current(); // 使用线程安全的随机数生成器
}
/**
*
*
* @return
*/
public abstract String generateQuestion();
/**
*
* @return null
*/
public String generateUniqueQuestion() {
String question = null;
int attempts = 0;
while (attempts < MAX_GENERATION_ATTEMPTS) {
try {
question = generateQuestion();
totalAttempts++;
if (question != null && !question.trim().isEmpty()) {
String normalizedQuestion = normalizeQuestion(question);
if (!currentQuestions.contains(normalizedQuestion)) {
currentQuestions.add(normalizedQuestion);
successfulGenerations++;
return question;
}
}
} catch (Exception e) {
System.err.println("生成题目时发生错误: " + e.getMessage());
}
attempts++;
}
// 如果达到最大尝试次数仍未成功,记录警告
if (attempts >= MAX_GENERATION_ATTEMPTS) {
System.err.println("警告:" + grade + "题目生成器达到最大尝试次数(" + MAX_GENERATION_ATTEMPTS + ")");
}
return question; // 返回最后一次生成的题目,即使可能重复
}
/**
*
* @param question
* @return
*/
protected String normalizeQuestion(String question) {
if (question == null) {
return "";
}
return question.trim()
.replaceAll("\\s+", " ") // 统一空格
.toLowerCase(); // 转小写(如果需要)
}
/**
*
*/
public void clearCurrentQuestions() {
currentQuestions.clear();
// 重置统计信息
totalAttempts = 0;
successfulGenerations = 0;
}
/**
*
* @return
*/
public String getGrade() {
return grade;
}
/**
*
* @return
*/
public int getCurrentQuestionCount() {
return currentQuestions.size();
}
/**
*
* @param question
* @return true
*/
public boolean isQuestionExists(String question) {
if (question == null || question.trim().isEmpty()) {
return false;
}
String normalized = normalizeQuestion(question);
return currentQuestions.contains(normalized);
}
/**
*
* @return 0.0-1.0
*/
public double getGenerationEfficiency() {
if (totalAttempts == 0) {
return 0.0;
}
return (double) successfulGenerations / totalAttempts;
}
/**
*
* @return
*/
public String getStatistics() {
return String.format("生成器统计[%s]: 总尝试=%d, 成功=%d, 效率=%.2f%%",
grade, totalAttempts, successfulGenerations,
getGenerationEfficiency() * 100);
}
/**
*
* @param min
* @param max
* @return
*/
protected int randomInt(int min, int max) {
if (min > max) {
throw new IllegalArgumentException("最小值不能大于最大值");
}
if (min == max) {
return min;
}
return random.nextInt(max - min + 1) + min;
}
/**
*
* @param array
* @return
*/
protected <T> T randomChoice(T[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("数组不能为空");
}
return array[random.nextInt(array.length)];
}
}

@ -0,0 +1,216 @@
package com.mathpaper.generator;
import java.util.ArrayList;
import java.util.List;
/**
*
* 2-51sin/cos/tan
*
* @author
* @version 2.0
*/
public class SeniorGenerator extends QuestionGenerator {
// 常量定义
private static final int MIN_OPERANDS = 2;
private static final int MAX_OPERANDS = 5;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 10; // 避免过大的弧度值
private static final String[] OPERATORS = {"+", "-", "*", "/"};
private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"};
private static final double TRIG_PROBABILITY = 0.4; // 40%概率添加三角函数
private static final double NESTED_PROBABILITY = 0.3; // 30%概率函数嵌套
private static final double ADVANCED_OP_PROBABILITY = 0.3; // 30%概率添加高级运算
private static final double BRACKET_PROBABILITY = 0.3; // 30%概率添加括号
/**
*
*/
public SeniorGenerator() {
super("高中");
}
@Override
public String generateQuestion() {
int numCount = randomInt(MIN_OPERANDS, MAX_OPERANDS);
List<Integer> nums = generateNumbers(numCount);
List<String> ops = generateOperators(numCount - 1);
return addTrigonometricFunctions(nums, ops);
}
/**
* 1-10
* @param count
* @return
*/
private List<Integer> generateNumbers(int count) {
List<Integer> nums = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
nums.add(randomInt(MIN_NUMBER, MAX_NUMBER));
}
return nums;
}
/**
*
* @param count
* @return
*/
private List<String> generateOperators(int count) {
List<String> ops = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ops.add(randomChoice(OPERATORS));
}
return ops;
}
/**
*
* @param nums
* @param ops
* @return
*/
private String addTrigonometricFunctions(List<Integer> nums, List<String> ops) {
StringBuilder sb = new StringBuilder(nums.size() * 15); // 预估容量
// 确保至少有一个三角函数
boolean hasTrig = false;
int trigCount = 0;
for (int i = 0; i < nums.size(); i++) {
int num = nums.get(i);
// 决定是否添加三角函数
boolean shouldAddTrig = !hasTrig ||
(trigCount < 2 && random.nextDouble() < TRIG_PROBABILITY);
if (shouldAddTrig) {
String func = randomChoice(TRIG_FUNCTIONS);
// 决定是否嵌套函数
if (random.nextDouble() < NESTED_PROBABILITY) {
// 函数嵌套,如 sin(√(5+3))
int innerNum = randomInt(1, 5);
sb.append(func).append("(√(").append(num)
.append("+").append(innerNum).append("))");
} else {
// 简单函数,如 sin(3)
sb.append(func).append("(").append(num).append(")");
}
hasTrig = true;
trigCount++;
} else {
// 添加其他高级运算或普通数字
if (random.nextDouble() < ADVANCED_OP_PROBABILITY) {
if (random.nextBoolean()) {
sb.append(num).append("^2");
} else {
// 限制开根号的数值避免过于复杂
int sqrtNum = Math.min(num, 9);
sb.append("√").append(sqrtNum);
}
} else {
sb.append(num);
}
}
if (i < ops.size()) {
sb.append(" ").append(ops.get(i)).append(" ");
}
}
// 添加复杂括号
return addComplexBrackets(sb.toString());
}
/**
*
* @param expression
* @return
*/
private String addComplexBrackets(String expression) {
// 如果表达式中已有函数调用的括号,减少额外括号的概率
boolean hasFunctionBrackets = expression.contains("sin(") ||
expression.contains("cos(") ||
expression.contains("tan(");
double actualBracketProb = hasFunctionBrackets ?
BRACKET_PROBABILITY * 0.5 : BRACKET_PROBABILITY;
if (random.nextDouble() >= actualBracketProb) {
return expression;
}
String[] parts = expression.split(" ");
if (parts.length < 5) {
return expression;
}
// 选择括号范围,避免破坏函数调用结构
int maxStart = parts.length - 4;
int start = randomInt(0, maxStart);
int minEnd = start + 3;
int maxEnd = Math.min(start + 5, parts.length - 1);
int end = randomInt(minEnd, maxEnd);
// 检查是否会破坏函数调用结构
StringBuilder testBuilder = new StringBuilder();
for (int i = start; i <= end; i++) {
testBuilder.append(parts[i]);
if (i < end) testBuilder.append(" ");
}
String testSection = testBuilder.toString();
// 如果选中的部分包含不完整的函数调用,跳过添加括号
if (containsIncompleteFunction(testSection)) {
return expression;
}
StringBuilder result = new StringBuilder(expression.length() + 4);
for (int i = 0; i < parts.length; i++) {
if (i == start) {
result.append("(");
}
result.append(parts[i]);
if (i == end) {
result.append(")");
}
if (i < parts.length - 1) {
result.append(" ");
}
}
return result.toString();
}
/**
*
* @param text
* @return true
*/
private boolean containsIncompleteFunction(String text) {
for (String func : TRIG_FUNCTIONS) {
if (text.contains(func + "(") && !text.contains(")")) {
return true;
}
if (text.contains(")") && !text.contains(func + "(")) {
// 检查是否有孤立的右括号
int leftCount = 0;
int rightCount = 0;
for (char c : text.toCharArray()) {
if (c == '(') leftCount++;
if (c == ')') rightCount++;
}
if (rightCount > leftCount) {
return true;
}
}
}
return false;
}
}

@ -0,0 +1,118 @@
package com.mathpaper.model;
import java.util.regex.Pattern;
/**
*
*
*
* @author
* @version 2.0
*/
public class User {
// 用户名验证正则表达式允许字母、数字、下划线和中文字符长度2-20
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_\\u4e00-\\u9fa5]{2,20}$");
// 用户名长度限制
private static final int MIN_USERNAME_LENGTH = 2;
private static final int MAX_USERNAME_LENGTH = 20;
private String username;
/**
*
* @param username
* @throws IllegalArgumentException
*/
public User(String username) {
setUsername(username);
}
/**
*
* @return
*/
public String getUsername() {
return username;
}
/**
*
* @param username
* @throws IllegalArgumentException
*/
public void setUsername(String username) {
if (!isValidUsername(username)) {
throw new IllegalArgumentException("用户名无效必须是2-20位字母、数字、下划线或中文字符");
}
this.username = username.trim();
}
/**
*
* @param username
* @return truefalse
*/
public static boolean isValidUsername(String username) {
if (username == null || username.trim().isEmpty()) {
return false;
}
String trimmed = username.trim();
// 检查长度
if (trimmed.length() < MIN_USERNAME_LENGTH || trimmed.length() > MAX_USERNAME_LENGTH) {
return false;
}
// 检查格式
return USERNAME_PATTERN.matcher(trimmed).matches();
}
/**
*
* @param username
* @return null
*/
public static String getValidationError(String username) {
if (username == null || username.trim().isEmpty()) {
return "用户名不能为空";
}
String trimmed = username.trim();
if (trimmed.length() < MIN_USERNAME_LENGTH) {
return "用户名长度不能少于" + MIN_USERNAME_LENGTH + "位";
}
if (trimmed.length() > MAX_USERNAME_LENGTH) {
return "用户名长度不能超过" + MAX_USERNAME_LENGTH + "位";
}
if (!USERNAME_PATTERN.matcher(trimmed).matches()) {
return "用户名只能包含字母、数字、下划线和中文字符";
}
return null; // 验证通过
}
@Override
public String toString() {
return "User{username='" + username + "'}";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return username != null ? username.equals(user.username) : user.username == null;
}
@Override
public int hashCode() {
return username != null ? username.hashCode() : 0;
}
}

@ -0,0 +1,281 @@
package com.mathpaper.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
*
*
*
* @author
* @version 2.0
*/
public class DuplicateChecker {
// 缓存用户的历史题目,提高查重性能
private static final ConcurrentHashMap<String, Set<String>> userQuestionCache = new ConcurrentHashMap<>();
// 缓存的最大用户数,防止内存溢出
private static final int MAX_CACHED_USERS = 10;
// 缓存访问时间记录用于LRU清理
private static final ConcurrentHashMap<String, Long> cacheAccessTime = new ConcurrentHashMap<>();
// 缓存清理阈值(毫秒)
private static final long CACHE_EXPIRY_TIME = 30 * 60 * 1000; // 30分钟
/**
*
* @param question
* @param username
* @return truefalse
*/
public static boolean isDuplicate(String question, String username) {
if (question == null || question.trim().isEmpty()) {
return false;
}
if (username == null || username.trim().isEmpty()) {
return false;
}
try {
String cleanQuestion = cleanQuestionText(question);
Set<String> userQuestions = getUserQuestions(username);
return userQuestions.contains(cleanQuestion);
} catch (Exception e) {
System.err.println("查重检查时发生错误: " + e.getMessage());
return false; // 出错时默认不重复,避免阻塞题目生成
}
}
/**
*
*/
private static Set<String> getUserQuestions(String username) {
// 检查缓存
Set<String> cachedQuestions = userQuestionCache.get(username);
if (cachedQuestions != null) {
// 更新访问时间
cacheAccessTime.put(username, System.currentTimeMillis());
return cachedQuestions;
}
// 缓存未命中,从文件加载
Set<String> questions = loadUserQuestionsFromFiles(username);
// 缓存管理:如果缓存已满,清理最久未使用的条目
if (userQuestionCache.size() >= MAX_CACHED_USERS) {
cleanupOldestCache();
}
// 添加到缓存
userQuestionCache.put(username, questions);
cacheAccessTime.put(username, System.currentTimeMillis());
return questions;
}
/**
*
*/
private static Set<String> loadUserQuestionsFromFiles(String username) {
Set<String> questions = new HashSet<>();
try {
String userDir = FileUtil.getUserPaperDir(username);
Path userPath = Paths.get(userDir);
if (!Files.exists(userPath)) {
return questions;
}
// 使用NIO遍历用户目录下的所有文件
Files.walk(userPath)
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(path -> {
try {
Files.lines(path, StandardCharsets.UTF_8)
.map(String::trim)
.filter(line -> !line.isEmpty())
.map(DuplicateChecker::cleanQuestionText)
.forEach(questions::add);
} catch (IOException e) {
System.err.println("读取文件失败: " + path + " - " + e.getMessage());
}
});
} catch (Exception e) {
System.err.println("加载用户题目失败: " + e.getMessage());
}
return questions;
}
/**
* 使
*/
private static void cleanupOldestCache() {
if (cacheAccessTime.isEmpty()) {
return;
}
// 找到最久未访问的用户
String oldestUser = cacheAccessTime.entrySet().stream()
.min((e1, e2) -> Long.compare(e1.getValue(), e2.getValue()))
.map(entry -> entry.getKey())
.orElse(null);
if (oldestUser != null) {
userQuestionCache.remove(oldestUser);
cacheAccessTime.remove(oldestUser);
}
}
/**
*
*/
public static void cleanupExpiredCache() {
long currentTime = System.currentTimeMillis();
cacheAccessTime.entrySet().removeIf(entry -> {
if (currentTime - entry.getValue() > CACHE_EXPIRY_TIME) {
userQuestionCache.remove(entry.getKey());
return true;
}
return false;
});
}
/**
*
*/
private static void loadQuestionsFromFile(Path filePath, Set<String> questions) {
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
String cleanLine = cleanQuestionText(line);
if (!cleanLine.isEmpty()) {
questions.add(cleanLine);
}
}
} catch (IOException e) {
System.err.println("读取文件时发生错误: " + filePath + " - " + e.getMessage());
}
}
/**
*
* @param text
* @return
*/
private static String cleanQuestionText(String text) {
if (text == null || text.trim().isEmpty()) {
return "";
}
String cleaned = text.trim();
// 去除题号(如"1. "、"2) "等格式)
cleaned = cleaned.replaceFirst("^\\d+[.)、]\\s*", "");
// 去除多余的空白字符
cleaned = cleaned.replaceAll("\\s+", " ").trim();
return cleaned;
}
/**
*
* @param questions
* @param username
* @return
*/
public static int countDuplicates(String[] questions, String username) {
if (questions == null || questions.length == 0) {
return 0;
}
if (username == null || username.trim().isEmpty()) {
return 0;
}
int duplicateCount = 0;
try {
for (String question : questions) {
if (isDuplicate(question, username)) {
duplicateCount++;
}
}
} catch (Exception e) {
System.err.println("批量查重时发生错误: " + e.getMessage());
}
return duplicateCount;
}
/**
*
* @param username
* @return
*/
public static int getHistoryFileCount(String username) {
if (username == null || username.trim().isEmpty()) {
return 0;
}
try {
String userDir = FileUtil.getUserPaperDir(username);
Path dirPath = Paths.get(userDir);
if (!Files.exists(dirPath) || !Files.isDirectory(dirPath)) {
return 0;
}
return (int) Files.list(dirPath)
.filter(path -> Files.isRegularFile(path))
.filter(path -> path.toString().endsWith(".txt"))
.count();
} catch (IOException e) {
System.err.println("获取历史文件数量时发生错误: " + e.getMessage());
return 0;
}
}
/**
*
* @param username
*/
public static void clearUserCache(String username) {
if (username != null) {
userQuestionCache.remove(username);
}
}
/**
*
*/
public static void clearAllCache() {
userQuestionCache.clear();
}
/**
*
* @return
*/
public static int getCachedUserCount() {
return userQuestionCache.size();
}
}

@ -0,0 +1,290 @@
package com.mathpaper.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
*
*
*
* @author
* @version 2.1 -
*/
public class FileUtil {
// 基础目录常量
private static final String BASE_DIR = "doc";
private static final String PAPER_DIR = "paper";
private static final String FILE_EXTENSION = ".txt";
// 性能优化常量
private static final int BUFFER_SIZE = 8192; // 8KB缓冲区
private static final int MAX_FILENAME_LENGTH = 255;
// 时间戳格式
private static final SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
/**
* doc/paper//.txt
* @param username
* @return
* @throws IllegalArgumentException
*/
public static String getSavePath(String username) {
validateUsername(username);
try {
String sanitizedUsername = sanitizeFilename(username.trim());
Path userDir = Paths.get(BASE_DIR, PAPER_DIR, sanitizedUsername);
// 确保目录存在
Files.createDirectories(userDir);
String timestamp = TIMESTAMP_FORMAT.format(new Date());
Path filePath = userDir.resolve(timestamp + FILE_EXTENSION);
return filePath.toString();
} catch (IOException e) {
System.err.println("创建保存路径时发生错误: " + e.getMessage());
// 降级到传统方式
return getFallbackSavePath(username);
}
}
/**
*
*/
private static void validateUsername(String username) {
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (username.trim().length() > MAX_FILENAME_LENGTH) {
throw new IllegalArgumentException("用户名过长");
}
}
/**
*
*/
private static String getFallbackSavePath(String username) {
String sanitizedUsername = sanitizeFilename(username.trim());
String baseDir = BASE_DIR + File.separator + PAPER_DIR + File.separator + sanitizedUsername;
File dir = new File(baseDir);
if (!dir.exists() && !dir.mkdirs()) {
throw new RuntimeException("无法创建目录: " + baseDir);
}
String timestamp = TIMESTAMP_FORMAT.format(new Date());
return baseDir + File.separator + timestamp + FILE_EXTENSION;
}
/**
*
*/
private static String sanitizeFilename(String filename) {
if (filename == null) {
return "default";
}
// 替换Windows文件名中的非法字符
String sanitized = filename.replaceAll("[<>:\"/\\\\|?*]", "_")
.replaceAll("\\s+", "_")
.trim();
// 确保文件名不为空且不超过最大长度
if (sanitized.isEmpty()) {
sanitized = "default";
}
if (sanitized.length() > MAX_FILENAME_LENGTH) {
sanitized = sanitized.substring(0, MAX_FILENAME_LENGTH);
}
return sanitized;
}
/**
*
* @param path
* @param questions
* @throws IllegalArgumentException
* @throws RuntimeException
*/
public static void savePaper(String path, List<String> questions) {
validateSaveParameters(path, questions);
try {
Path filePath = Paths.get(path);
// 确保父目录存在
Path parentDir = filePath.getParent();
if (parentDir != null) {
Files.createDirectories(parentDir);
}
// 使用NIO写入文件指定UTF-8编码和缓冲区大小
try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
writeQuestionsToFile(writer, questions);
}
} catch (IOException e) {
System.err.println("保存文件时发生错误: " + e.getMessage());
// 尝试降级保存
savePaperFallback(path, questions);
} catch (Exception e) {
System.err.println("保存试卷时发生未知错误: " + e.getMessage());
throw new RuntimeException("保存试卷失败", e);
}
}
/**
*
*/
private static void validateSaveParameters(String path, List<String> questions) {
if (path == null || path.trim().isEmpty()) {
throw new IllegalArgumentException("文件路径不能为空");
}
if (questions == null || questions.isEmpty()) {
throw new IllegalArgumentException("题目列表不能为空");
}
}
/**
*
*/
private static void writeQuestionsToFile(BufferedWriter writer, List<String> questions) throws IOException {
for (int i = 0; i < questions.size(); i++) {
String question = questions.get(i);
if (question != null && !question.trim().isEmpty()) {
writer.write((i + 1) + ". " + question.trim());
// 题间空一行,但最后一题不加空行
if (i < questions.size() - 1) {
writer.newLine();
writer.newLine();
}
}
}
writer.flush();
}
/**
* 使IO
*/
private static void savePaperFallback(String path, List<String> questions) {
try (BufferedWriter bw = new BufferedWriter(
new java.io.FileWriter(path, StandardCharsets.UTF_8), BUFFER_SIZE)) {
writeQuestionsToFile(bw, questions);
} catch (IOException e) {
System.err.println("降级保存也失败: " + e.getMessage());
throw new RuntimeException("文件保存完全失败", e);
}
}
/**
*
* @param path
* @return
*/
public static boolean fileExists(String path) {
if (path == null || path.trim().isEmpty()) {
return false;
}
try {
Path filePath = Paths.get(path);
return Files.exists(filePath) && Files.isRegularFile(filePath);
} catch (Exception e) {
// 降级到传统方式
File file = new File(path);
return file.exists() && file.isFile();
}
}
/**
*
* @param dirPath
* @return
*/
public static boolean createDirectory(String dirPath) {
if (dirPath == null || dirPath.trim().isEmpty()) {
return false;
}
try {
Path path = Paths.get(dirPath);
Files.createDirectories(path);
return true;
} catch (IOException e) {
System.err.println("创建目录失败: " + e.getMessage());
// 降级到传统方式
File dir = new File(dirPath);
return dir.mkdirs() || dir.exists();
}
}
/**
*
* @param username
* @return
*/
public static String getUserPaperDir(String username) {
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
String sanitizedUsername = sanitizeFilename(username.trim());
return BASE_DIR + File.separator + PAPER_DIR + File.separator + sanitizedUsername;
}
/**
*
* @param path
* @return -1
*/
public static long getFileSize(String path) {
if (path == null || path.trim().isEmpty()) {
return -1;
}
try {
Path filePath = Paths.get(path);
return Files.exists(filePath) ? Files.size(filePath) : -1;
} catch (IOException e) {
System.err.println("获取文件大小失败: " + e.getMessage());
return -1;
}
}
/**
*
* @param path
* @return
*/
public static boolean deleteFile(String path) {
if (path == null || path.trim().isEmpty()) {
return false;
}
try {
Path filePath = Paths.get(path);
return Files.deleteIfExists(filePath);
} catch (IOException e) {
System.err.println("删除文件失败: " + e.getMessage());
return false;
}
}
}

@ -0,0 +1,6 @@
@echo off
REM Convenience startup script - redirects to the main startup script
REM 便捷启动脚本 - 重定向到主启动脚本
echo Redirecting to main startup script...
call "doc\bin\start.bat"

@ -0,0 +1,5 @@
# Convenience startup script - redirects to the main startup script
# 便捷启动脚本 - 重定向到主启动脚本
Write-Host "Redirecting to main startup script..." -ForegroundColor Yellow
& ".\doc\bin\start.ps1"
Loading…
Cancel
Save