服务层第一版 #6

Merged
hnu202326010319 merged 6 commits from WuBaiXuan_Branch into develop 4 months ago

38
.gitignore vendored

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mathquiz</groupId>
<artifactId>MathQuizApp</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Math Quiz Application</name>
<description>小初高数学学习软件 - Swing版本</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- Gson for JSON -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- Maven Jar Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.mathquiz.Main</mainClass>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
<!-- Maven Shade Plugin - 打包为可执行JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.mathquiz.Main</mainClass>
</transformer>
</transformers>
<finalName>MathQuizApp</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,4 @@
package com.MathQuiz;
public class Main {
}

@ -0,0 +1,74 @@
package com.model;
import java.util.List;
//选择题
public class ChoiceQuestion {
private String questionText; // 题目文本
private Object correctAnswer; // 正确答案
private List<?> options; // 选项列表
private Grade grade; // 所属学段
public ChoiceQuestion(String questionText, double correctAnswer,
List<Double> options, Grade grade) {
this.questionText = questionText;
this.correctAnswer = correctAnswer;
this.options = options;
this.grade = grade;
}
public ChoiceQuestion(String questionText, String correctAnswer,
List<String> options, Grade grade) {
this.questionText = questionText;
this.correctAnswer = correctAnswer;
this.options = options;
this.grade = grade;
}
public String getQuestionText() {
return questionText;
}
public void setQuestionText(String questionText) {
this.questionText = questionText;
}
public Object getCorrectAnswer() {
return correctAnswer;
}
public void setCorrectAnswer(Object correctAnswer) {
this.correctAnswer = correctAnswer;
}
public List<?> getOptions() {
return options;
}
public void setOptions(List<?> options) {
this.options = options;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
@Override
public String toString() {
return "ChoiceQuestion{" +
"questionText='" + questionText + '\'' +
", correctAnswer=" + correctAnswer +
", options=" + options +
", grade=" + grade +
'}';
}
}

@ -0,0 +1,9 @@
package com.model;
//学段
public enum Grade {
ELEMENTARY, // 小学
MIDDLE, // 初中
HIGH // 高中
}

@ -0,0 +1,79 @@
package com.model;
import java.util.Date;
import java.util.List;
//答题历史记录模型
public class QuizHistory {
private String username; // 用户名
private Date timestamp; // 答题时间
private List<ChoiceQuestion> questions; // 题目列表
private List<Integer> userAnswers; // 用户答案列表
private int score; // 得分
public QuizHistory(String username, Date timestamp,
List<ChoiceQuestion> questions,
List<Integer> userAnswers,
int score) {
this.username = username;
this.timestamp = timestamp;
this.questions = questions;
this.userAnswers = userAnswers;
this.score = score;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public List<ChoiceQuestion> getQuestions() {
return questions;
}
public void setQuestions(List<ChoiceQuestion> questions) {
this.questions = questions;
}
public List<Integer> getUserAnswers() {
return userAnswers;
}
public void setUserAnswers(List<Integer> userAnswers) {
this.userAnswers = userAnswers;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "QuizHistory{" +
"username='" + username + '\'' +
", timestamp=" + timestamp +
", questions=" + (questions != null ? questions.size() : 0) +
", score=" + score +
'}';
}
}

@ -0,0 +1,62 @@
package com.model;
//答题结果
public class QuizResult {
private int totalQuestions; // 总题数
private int correctCount; // 正确题数
private int wrongCount; // 错误题数
private int score; // 得分
public QuizResult(int totalQuestions, int correctCount, int wrongCount, int score) {
this.totalQuestions = totalQuestions;
this.correctCount = correctCount;
this.wrongCount = wrongCount;
this.score = score;
}
public int getTotalQuestions() {
return totalQuestions;
}
public void setTotalQuestions(int totalQuestions) {
this.totalQuestions = totalQuestions;
}
public int getCorrectCount() {
return correctCount;
}
public void setCorrectCount(int correctCount) {
this.correctCount = correctCount;
}
public int getWrongCount() {
return wrongCount;
}
public void setWrongCount(int wrongCount) {
this.wrongCount = wrongCount;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "QuizResult{" +
"totalQuestions=" + totalQuestions +
", correctCount=" + correctCount +
", wrongCount=" + wrongCount +
", score=" + score +
'}';
}
}

@ -0,0 +1,115 @@
package com.model;
import java.util.Date;
//用户
public class User {
private String username; // 用户名
private String password; // 密码(加密后)
private String email; // 邮箱
private Grade grade; // 学段
private int totalQuizzes; // 总答题次数
private double averageScore; // 平均分
private Date registrationDate; // 注册时间
/**
*
*/
public User(String username, String password, String email, Grade grade,
int totalQuizzes, double averageScore, Date registrationDate) {
this.username = username;
this.password = password;
this.email = email;
this.grade = grade;
this.totalQuizzes = totalQuizzes;
this.averageScore = averageScore;
this.registrationDate = registrationDate;
}
/**
*
*/
public User(String username, String password, String email, Grade grade) {
this.username = username;
this.password = password;
this.email = email;
this.grade = grade;
this.totalQuizzes = 0;
this.averageScore = 0.0;
this.registrationDate = new Date();
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
public int getTotalQuizzes() {
return totalQuizzes;
}
public void setTotalQuizzes(int totalQuizzes) {
this.totalQuizzes = totalQuizzes;
}
public double getAverageScore() {
return averageScore;
}
public void setAverageScore(double averageScore) {
this.averageScore = averageScore;
}
public Date getRegistrationDate() {
return registrationDate;
}
public void setRegistrationDate(Date registrationDate) {
this.registrationDate = registrationDate;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", email='" + email + '\'' +
", grade=" + grade +
", totalQuizzes=" + totalQuizzes +
", averageScore=" + String.format("%.1f", averageScore) +
", registrationDate=" + registrationDate +
'}';
}
}

@ -0,0 +1,242 @@
package com.service;
import com.model.*;
import com.util.FileUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* IO
*
*/
public class FileIOService {
private static final String DATA_DIR = "data";
private static final String USERS_DIR = DATA_DIR + "/users";
private static final String HISTORY_DIR = DATA_DIR + "/history";
private static final String USERS_FILE = DATA_DIR + "/users.json";
private static final String CURRENT_USER_FILE = DATA_DIR + "/current_user.json";
private static final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
// ==================== 初始化 ====================
public void initDataDirectory() throws IOException {
FileUtils.createDirectoryIfNotExists(DATA_DIR);
FileUtils.createDirectoryIfNotExists(USERS_DIR);
FileUtils.createDirectoryIfNotExists(HISTORY_DIR);
if (!FileUtils.exists(USERS_FILE)) {
Map<String, List<User>> data = new HashMap<>();
data.put("users", new ArrayList<>());
FileUtils.saveAsJson(data, USERS_FILE);
}
System.out.println("✓ 数据目录初始化完成");
}
// ==================== 用户操作 ====================
public void saveUser(User user) throws IOException {
Type type = new TypeToken<Map<String, List<User>>>(){}.getType();
Map<String, List<User>> data = FileUtils.readJsonToObject(USERS_FILE, type);
List<User> users = data.get("users");
boolean found = false;
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getUsername().equals(user.getUsername())) {
users.set(i, user);
found = true;
break;
}
}
if (!found) {
users.add(user);
}
FileUtils.saveAsJson(data, USERS_FILE);
}
public List<User> loadAllUsers() throws IOException {
if (!FileUtils.exists(USERS_FILE)) {
return new ArrayList<>();
}
Type type = new TypeToken<Map<String, List<User>>>(){}.getType();
Map<String, List<User>> data = FileUtils.readJsonToObject(USERS_FILE, type);
return data.get("users");
}
public User findUserByUsername(String username) throws IOException {
List<User> users = loadAllUsers();
for (User user : users) {
if (user.getUsername().equals(username)) {
return user;
}
}
return null;
}
public boolean isUsernameExists(String username) throws IOException {
return findUserByUsername(username) != null;
}
public void saveCurrentUser(User user) throws IOException {
FileUtils.saveAsJson(user, CURRENT_USER_FILE);
}
public User loadCurrentUser() throws IOException {
if (!FileUtils.exists(CURRENT_USER_FILE)) {
return null;
}
return FileUtils.readJsonToObject(CURRENT_USER_FILE, User.class);
}
public void clearCurrentUser() {
FileUtils.deleteFile(CURRENT_USER_FILE);
}
// ==================== 答题历史操作 ====================
public void saveQuizHistory(QuizHistory history) throws IOException {
String filename = HISTORY_DIR + "/" +
sanitizeFilename(history.getUsername()) + "_" +
System.currentTimeMillis() + ".txt";
StringBuilder content = new StringBuilder();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
content.append("========== 答题记录 ==========\n");
content.append("用户:").append(history.getUsername()).append("\n");
content.append("时间:").append(dateFormat.format(history.getTimestamp())).append("\n");
content.append("总分:").append(history.getScore()).append(" 分\n");
// 调用 QuizService 的业务方法计算正确数和错误数
int correctCount = calculateCorrectCount(history);
int wrongCount = history.getQuestions().size() - correctCount;
content.append("正确:").append(correctCount).append(" 题 ");
content.append("错误:").append(wrongCount).append(" 题\n");
content.append("=============================\n\n");
List<ChoiceQuestion> questions = history.getQuestions();
List<Integer> userAnswers = history.getUserAnswers();
for (int i = 0; i < questions.size(); i++) {
ChoiceQuestion q = questions.get(i);
Integer userAnswer = userAnswers.get(i);
content.append("【题目 ").append(i + 1).append("】\n");
content.append(q.getQuestionText()).append("\n");
List<?> options = q.getOptions();
for (int j = 0; j < options.size(); j++) {
content.append((char)('A' + j)).append(". ")
.append(options.get(j)).append(" ");
}
content.append("\n");
int correctIndex = getCorrectAnswerIndex(q);
content.append("正确答案:").append((char)('A' + correctIndex)).append("\n");
content.append("用户答案:");
if (userAnswer != null) {
content.append((char)('A' + userAnswer));
} else {
content.append("未作答");
}
content.append("\n");
boolean isCorrect = (userAnswer != null && userAnswer == correctIndex);
content.append("结果:").append(isCorrect ? "✓ 正确" : "✗ 错误").append("\n\n");
}
FileUtils.writeStringToFile(filename, content.toString());
}
public List<String> getHistoryQuestions() throws IOException {
List<String> historyQuestions = new ArrayList<>();
File[] files = FileUtils.listFiles(HISTORY_DIR);
Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()));
int count = 0;
for (File file : files) {
if (count++ >= 20) break;
try {
String content = FileUtils.readFileToString(file.getAbsolutePath());
String[] lines = content.split("\n");
for (int i = 0; i < lines.length; i++) {
if (lines[i].startsWith("【题目")) {
if (i + 1 < lines.length) {
String questionText = lines[i + 1].trim();
if (!questionText.isEmpty()) {
historyQuestions.add(questionText);
}
}
}
}
} catch (IOException e) {
System.err.println("读取历史文件失败: " + file.getName());
}
}
return historyQuestions;
}
// ==================== 业务逻辑方法(从 Model 移过来)====================
/**
*
*/
private int calculateCorrectCount(QuizHistory history) {
int count = 0;
List<ChoiceQuestion> questions = history.getQuestions();
List<Integer> userAnswers = history.getUserAnswers();
for (int i = 0; i < questions.size(); i++) {
ChoiceQuestion question = questions.get(i);
Integer userAnswer = userAnswers.get(i);
if (userAnswer != null && userAnswer == getCorrectAnswerIndex(question)) {
count++;
}
}
return count;
}
/**
*
*/
private int getCorrectAnswerIndex(ChoiceQuestion question) {
return question.getOptions().indexOf(question.getCorrectAnswer());
}
// ==================== 工具方法 ====================
private String sanitizeFilename(String filename) {
if (filename == null) {
return "unknown";
}
return filename.replaceAll("[\\\\/:*?\"<>|]", "_");
}
}

@ -0,0 +1,432 @@
package com.service;
import com.model.*;
import com.service.question_generator.QuestionFactoryManager;
import java.io.IOException;
import java.util.*;
/**
*
*/
public class QuizService {
private final FileIOService fileIOService;
private final UserService userService;
private List<ChoiceQuestion> currentQuestions;
private List<Integer> userAnswers;
private int currentQuestionIndex;
// ==================== 构造方法 ====================
public QuizService() {
this.fileIOService = new FileIOService();
this.userService = new UserService(fileIOService);
this.currentQuestions = new ArrayList<>();
this.userAnswers = new ArrayList<>();
this.currentQuestionIndex = 0;
}
public QuizService(FileIOService fileIOService, UserService userService) {
this.fileIOService = fileIOService;
this.userService = userService;
this.currentQuestions = new ArrayList<>();
this.userAnswers = new ArrayList<>();
this.currentQuestionIndex = 0;
}
// ==================== 答题会话管理 ====================
public void startNewQuiz(User user, int questionCount) throws IOException {
currentQuestions.clear();
userAnswers.clear();
currentQuestionIndex = 0;
Set<String> historyQuestions = getRecentHistoryQuestions();
Grade grade = user.getGrade();
currentQuestions = QuestionFactoryManager.generateQuestions(
grade, questionCount, historyQuestions
);
for (int i = 0; i < currentQuestions.size(); i++) {
userAnswers.add(null);
}
System.out.println("✓ 已生成 " + currentQuestions.size() + " 道 " + grade + " 题目");
}
private Set<String> getRecentHistoryQuestions() throws IOException {
List<String> historyList = fileIOService.getHistoryQuestions();
return new HashSet<>(historyList);
}
// ==================== 题目访问 ====================
public ChoiceQuestion getCurrentQuestion() {
if (currentQuestionIndex >= 0 && currentQuestionIndex < currentQuestions.size()) {
return currentQuestions.get(currentQuestionIndex);
}
return null;
}
public ChoiceQuestion getQuestion(int index) {
if (index >= 0 && index < currentQuestions.size()) {
return currentQuestions.get(index);
}
return null;
}
public List<ChoiceQuestion> getAllQuestions() {
return new ArrayList<>(currentQuestions);
}
public boolean nextQuestion() {
if (currentQuestionIndex < currentQuestions.size() - 1) {
currentQuestionIndex++;
return true;
}
return false;
}
public boolean previousQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
return true;
}
return false;
}
public boolean goToQuestion(int index) {
if (index >= 0 && index < currentQuestions.size()) {
currentQuestionIndex = index;
return true;
}
return false;
}
public int getCurrentQuestionIndex() {
return currentQuestionIndex;
}
public int getTotalQuestions() {
return currentQuestions.size();
}
public boolean isFirstQuestion() {
return currentQuestionIndex == 0;
}
public boolean isLastQuestion() {
return currentQuestionIndex == currentQuestions.size() - 1;
}
// ==================== 答题操作 ====================
public boolean submitAnswer(int questionIndex, int optionIndex) {
if (questionIndex < 0 || questionIndex >= currentQuestions.size()) {
throw new IllegalArgumentException("题目索引无效: " + questionIndex);
}
ChoiceQuestion question = currentQuestions.get(questionIndex);
if (optionIndex < 0 || optionIndex >= question.getOptions().size()) {
throw new IllegalArgumentException("选项索引无效: " + optionIndex);
}
userAnswers.set(questionIndex, optionIndex);
return checkAnswer(question, optionIndex);
}
public boolean submitCurrentAnswer(int optionIndex) {
return submitAnswer(currentQuestionIndex, optionIndex);
}
public Integer getUserAnswer(int questionIndex) {
if (questionIndex >= 0 && questionIndex < userAnswers.size()) {
return userAnswers.get(questionIndex);
}
return null;
}
public List<Integer> getAllUserAnswers() {
return new ArrayList<>(userAnswers);
}
public boolean isAllAnswered() {
for (Integer answer : userAnswers) {
if (answer == null) {
return false;
}
}
return true;
}
public int getAnsweredCount() {
int count = 0;
for (Integer answer : userAnswers) {
if (answer != null) {
count++;
}
}
return count;
}
// ==================== 成绩计算 ====================
public QuizResult calculateResult() {
int correctCount = 0;
int totalQuestions = currentQuestions.size();
for (int i = 0; i < totalQuestions; i++) {
ChoiceQuestion question = currentQuestions.get(i);
Integer userAnswer = userAnswers.get(i);
if (userAnswer != null && checkAnswer(question, userAnswer)) {
correctCount++;
}
}
int wrongCount = totalQuestions - correctCount;
int score = totalQuestions > 0 ? (correctCount * 100) / totalQuestions : 0;
return new QuizResult(totalQuestions, correctCount, wrongCount, score);
}
public List<Integer> getCorrectQuestionIndices() {
List<Integer> correctIndices = new ArrayList<>();
for (int i = 0; i < currentQuestions.size(); i++) {
ChoiceQuestion question = currentQuestions.get(i);
Integer userAnswer = userAnswers.get(i);
if (userAnswer != null && checkAnswer(question, userAnswer)) {
correctIndices.add(i);
}
}
return correctIndices;
}
public List<Integer> getWrongQuestionIndices() {
List<Integer> wrongIndices = new ArrayList<>();
for (int i = 0; i < currentQuestions.size(); i++) {
ChoiceQuestion question = currentQuestions.get(i);
Integer userAnswer = userAnswers.get(i);
if (userAnswer != null && !checkAnswer(question, userAnswer)) {
wrongIndices.add(i);
}
}
return wrongIndices;
}
public List<Integer> getUnansweredQuestionIndices() {
List<Integer> unansweredIndices = new ArrayList<>();
for (int i = 0; i < userAnswers.size(); i++) {
if (userAnswers.get(i) == null) {
unansweredIndices.add(i);
}
}
return unansweredIndices;
}
// ==================== 业务逻辑方法(从 Model 移过来)====================
/**
*
*/
public boolean checkAnswer(ChoiceQuestion question, int userAnswerIndex) {
if (userAnswerIndex < 0 || userAnswerIndex >= question.getOptions().size()) {
return false;
}
Object userAnswer = question.getOptions().get(userAnswerIndex);
return question.getCorrectAnswer().equals(userAnswer);
}
/**
*
*/
public int getCorrectAnswerIndex(ChoiceQuestion question) {
return question.getOptions().indexOf(question.getCorrectAnswer());
}
/**
*
*/
public String getCorrectAnswerLetter(ChoiceQuestion question) {
int index = getCorrectAnswerIndex(question);
if (index >= 0 && index < 4) {
return String.valueOf((char)('A' + index));
}
return "未知";
}
/**
*
*/
public double getAccuracy(QuizResult result) {
if (result.getTotalQuestions() == 0) {
return 0.0;
}
return (result.getCorrectCount() * 100.0) / result.getTotalQuestions();
}
/**
*
*/
public boolean isPassed(QuizResult result) {
return result.getScore() >= 60;
}
/**
*
*/
public String getGrade(QuizResult result) {
int score = result.getScore();
if (score >= 90) return "优秀";
if (score >= 80) return "良好";
if (score >= 70) return "中等";
if (score >= 60) return "及格";
return "不及格";
}
/**
*
*/
public int getCorrectCount(QuizHistory history) {
int count = 0;
List<ChoiceQuestion> questions = history.getQuestions();
List<Integer> userAnswers = history.getUserAnswers();
for (int i = 0; i < questions.size(); i++) {
ChoiceQuestion question = questions.get(i);
Integer userAnswer = userAnswers.get(i);
if (userAnswer != null && checkAnswer(question, userAnswer)) {
count++;
}
}
return count;
}
/**
*
*/
public int getWrongCount(QuizHistory history) {
return history.getQuestions().size() - getCorrectCount(history);
}
// ==================== 格式化输出 ====================
public String formatQuestion(ChoiceQuestion question) {
StringBuilder sb = new StringBuilder();
sb.append(question.getQuestionText()).append("\n");
List<?> options = question.getOptions();
for (int i = 0; i < options.size(); i++) {
sb.append((char)('A' + i)).append(". ").append(options.get(i));
if (i % 2 == 1) {
sb.append("\n");
} else {
sb.append(" ");
}
}
return sb.toString();
}
public String formatCurrentQuestion() {
ChoiceQuestion question = getCurrentQuestion();
if (question == null) {
return "没有可用的题目";
}
StringBuilder sb = new StringBuilder();
sb.append("第 ").append(currentQuestionIndex + 1)
.append(" / ").append(currentQuestions.size()).append(" 题\n");
sb.append(formatQuestion(question));
return sb.toString();
}
public String formatQuestionWithAnswer(ChoiceQuestion question, Integer userAnswerIndex) {
StringBuilder sb = new StringBuilder();
sb.append(question.getQuestionText()).append("\n");
List<?> options = question.getOptions();
int correctIndex = getCorrectAnswerIndex(question);
for (int i = 0; i < options.size(); i++) {
sb.append((char)('A' + i)).append(". ").append(options.get(i));
if (i == correctIndex) {
sb.append(" ✓");
}
if (userAnswerIndex != null && i == userAnswerIndex) {
boolean isCorrect = checkAnswer(question, userAnswerIndex);
sb.append(isCorrect ? " [您的答案:正确]" : " [您的答案:错误]");
}
if (i % 2 == 1) {
sb.append("\n");
} else {
sb.append(" ");
}
}
return sb.toString();
}
public String formatResult(QuizResult result) {
StringBuilder sb = new StringBuilder();
sb.append("\n========== 答题结束 ==========\n");
sb.append("总题数:").append(result.getTotalQuestions()).append(" 题\n");
sb.append("正确:").append(result.getCorrectCount()).append(" 题\n");
sb.append("错误:").append(result.getWrongCount()).append(" 题\n");
sb.append("得分:").append(result.getScore()).append(" 分\n");
sb.append("正确率:").append(String.format("%.1f%%", getAccuracy(result))).append("\n");
sb.append("评级:").append(getGrade(result)).append("\n");
sb.append("===============================\n");
return sb.toString();
}
// ==================== 数据持久化 ====================
public void saveQuizHistory(User user) throws IOException {
QuizResult result = calculateResult();
QuizHistory history = new QuizHistory(
user.getUsername(),
new Date(),
currentQuestions,
userAnswers,
result.getScore()
);
fileIOService.saveQuizHistory(history);
userService.updateUserStatistics(user, result.getScore());
System.out.println("✓ 答题记录已保存");
}
// ==================== Getters ====================
public List<ChoiceQuestion> getCurrentQuestions() {
return new ArrayList<>(currentQuestions);
}
public List<Integer> getUserAnswers() {
return new ArrayList<>(userAnswers);
}
}

@ -0,0 +1,532 @@
package com.service;
import com.model.Grade;
import com.model.User;
import com.util.FileUtils;
import com.util.PasswordValidator;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*/
public class UserService {
private final FileIOService fileIOService;
private User currentUser;
private static final Pattern USERNAME_PATTERN = Pattern.compile("^(小学|初中|高中)-([\\u4e00-\\u9fa5a-zA-Z]+)$");
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
// ==================== 构造方法 ====================
public UserService() {
this.fileIOService = new FileIOService();
this.currentUser = null;
}
public UserService(FileIOService fileIOService) {
this.fileIOService = fileIOService;
this.currentUser = null;
}
// 注册码文件路径
private static final String REGISTRATION_CODES_FILE = "data/registration_codes.txt";
// 注册码有效期(毫秒)
private static final long CODE_EXPIRY_TIME = 10 * 60 * 1000; // 10分钟
// ==================== 注册码管理 ====================
/**
*
* @param email
* @return
*/
public String generateRegistrationCode(String email) throws IOException {
if (!validateEmail(email)) {
throw new IllegalArgumentException("邮箱格式错误!");
}
// 生成6-10位注册码
String code = PasswordValidator.generateRegistrationCode();
long expiryTime = System.currentTimeMillis() + CODE_EXPIRY_TIME;
// 保存到文件
saveRegistrationCodeToFile(email, code, expiryTime);
// 打印注册码(实际项目中可以发邮件)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("========================================");
System.out.println("【注册码】");
System.out.println("邮箱: " + email);
System.out.println("注册码: " + code);
System.out.println("过期时间: " + sdf.format(new Date(expiryTime)));
System.out.println("========================================");
return code;
}
/**
*
*/
private void saveRegistrationCodeToFile(String email, String code, long expiryTime) throws IOException {
// 读取现有的注册码
Map<String, RegistrationCode> codes = loadRegistrationCodesFromFile();
// 添加或更新
codes.put(email, new RegistrationCode(code, expiryTime));
// 保存到文件
StringBuilder content = new StringBuilder();
content.append("# 注册码记录文件\n");
content.append("# 格式: 邮箱|注册码|过期时间戳\n");
content.append("# 过期时间格式: yyyy-MM-dd HH:mm:ss\n\n");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (Map.Entry<String, RegistrationCode> entry : codes.entrySet()) {
String emailKey = entry.getKey();
RegistrationCode regCode = entry.getValue();
content.append(emailKey).append("|")
.append(regCode.code).append("|")
.append(regCode.expiryTime).append("|")
.append(sdf.format(new Date(regCode.expiryTime)))
.append("\n");
}
FileUtils.writeStringToFile(REGISTRATION_CODES_FILE, content.toString());
}
/**
*
*/
private Map<String, RegistrationCode> loadRegistrationCodesFromFile() throws IOException {
Map<String, RegistrationCode> codes = new HashMap<>();
if (!FileUtils.exists(REGISTRATION_CODES_FILE)) {
return codes;
}
String content = FileUtils.readFileToString(REGISTRATION_CODES_FILE);
String[] lines = content.split("\n");
for (String line : lines) {
line = line.trim();
// 跳过注释和空行
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
String[] parts = line.split("\\|");
if (parts.length >= 3) {
String email = parts[0].trim();
String code = parts[1].trim();
long expiryTime = Long.parseLong(parts[2].trim());
codes.put(email, new RegistrationCode(code, expiryTime));
}
}
return codes;
}
/**
*
* @param email
* @param code
* @return true
*/
public boolean verifyRegistrationCode(String email, String code) throws IOException {
Map<String, RegistrationCode> codes = loadRegistrationCodesFromFile();
RegistrationCode regCode = codes.get(email);
if (regCode == null) {
throw new IllegalArgumentException("未找到该邮箱的注册码,请先获取注册码!");
}
// 检查是否过期
if (System.currentTimeMillis() > regCode.expiryTime) {
// 删除过期的注册码
codes.remove(email);
saveAllRegistrationCodes(codes);
throw new IllegalArgumentException("注册码已过期,请重新获取!");
}
// 验证注册码
boolean isValid = regCode.code.equals(code);
// 验证成功后删除注册码(一次性使用)
if (isValid) {
codes.remove(email);
saveAllRegistrationCodes(codes);
}
return isValid;
}
/**
*
*/
private void saveAllRegistrationCodes(Map<String, RegistrationCode> codes) throws IOException {
StringBuilder content = new StringBuilder();
content.append("# 注册码记录文件\n");
content.append("# 格式: 邮箱|注册码|过期时间戳|过期时间\n\n");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (Map.Entry<String, RegistrationCode> entry : codes.entrySet()) {
content.append(entry.getKey()).append("|")
.append(entry.getValue().code).append("|")
.append(entry.getValue().expiryTime).append("|")
.append(sdf.format(new Date(entry.getValue().expiryTime)))
.append("\n");
}
FileUtils.writeStringToFile(REGISTRATION_CODES_FILE, content.toString());
}
/**
*
*/
public void cleanExpiredCodes() throws IOException {
Map<String, RegistrationCode> codes = loadRegistrationCodesFromFile();
long now = System.currentTimeMillis();
// 移除过期的
codes.entrySet().removeIf(entry -> now > entry.getValue().expiryTime);
// 保存回文件
saveAllRegistrationCodes(codes);
System.out.println("✓ 已清理过期的注册码");
}
// ==================== 注册码内部类 ====================
private static class RegistrationCode {
String code;
long expiryTime;
RegistrationCode(String code, long expiryTime) {
this.code = code;
this.expiryTime = expiryTime;
}
}
// ==================== 用户注册====================
/**
*
*/
public User register(String username, String password, String email, String verificationCode) throws IOException {
// 1. 验证注册码
if (!verifyRegistrationCode(email, verificationCode)) {
throw new IllegalArgumentException("注册码错误!");
}
// 2. 验证用户名格式
if (!validateUsername(username)) {
throw new IllegalArgumentException("用户名格式错误!正确格式:学段-姓名(如:小学-张三)");
}
// 3. 验证用户名是否已存在
if (fileIOService.isUsernameExists(username)) {
throw new IllegalArgumentException("用户名已存在!");
}
// 4. 验证密码强度
String passwordError = PasswordValidator.validatePassword(password);
if (passwordError != null) {
throw new IllegalArgumentException(passwordError);
}
// 5. 验证邮箱格式
if (!validateEmail(email)) {
throw new IllegalArgumentException("邮箱格式错误!");
}
// 6. 从用户名中提取学段
Grade grade = extractGradeFromUsername(username);
// 7. 加密密码
String hashedPassword = hashPassword(password);
// 8. 创建用户对象
User user = new User(username, hashedPassword, email, grade);
// 9. 保存到文件
fileIOService.saveUser(user);
System.out.println("✓ 用户注册成功:" + username);
return user;
}
// ==================== 用户登录 ====================
public User login(String username, String password) throws IOException {
User user = fileIOService.findUserByUsername(username);
if (user == null) {
throw new IllegalArgumentException("用户名不存在!");
}
String hashedPassword = hashPassword(password);
if (!user.getPassword().equals(hashedPassword)) {
throw new IllegalArgumentException("密码错误!");
}
this.currentUser = user;
fileIOService.saveCurrentUser(user);
System.out.println("✓ 登录成功,欢迎 " + getRealName(user) + "" + getGradeDisplayName(user) + "");
return user;
}
public User autoLogin() throws IOException {
User user = fileIOService.loadCurrentUser();
if (user != null) {
this.currentUser = user;
System.out.println("✓ 自动登录成功,欢迎回来 " + getRealName(user));
}
return user;
}
public void logout() {
if (currentUser != null) {
System.out.println("✓ " + getRealName(currentUser) + " 已退出登录");
this.currentUser = null;
fileIOService.clearCurrentUser();
}
}
public User getCurrentUser() {
return currentUser;
}
public boolean isLoggedIn() {
return currentUser != null;
}
// ==================== 密码管理 ====================
public boolean changePassword(User user, String oldPassword, String newPassword) throws IOException {
String hashedOldPassword = hashPassword(oldPassword);
if (!user.getPassword().equals(hashedOldPassword)) {
throw new IllegalArgumentException("旧密码错误!");
}
String passwordError = PasswordValidator.validatePassword(newPassword);
if (passwordError != null) {
throw new IllegalArgumentException(passwordError);
}
if (oldPassword.equals(newPassword)) {
throw new IllegalArgumentException("新密码不能与旧密码相同!");
}
String hashedNewPassword = hashPassword(newPassword);
user.setPassword(hashedNewPassword);
fileIOService.saveUser(user);
if (currentUser != null && currentUser.getUsername().equals(user.getUsername())) {
this.currentUser = user;
fileIOService.saveCurrentUser(user);
}
System.out.println("✓ 密码修改成功");
return true;
}
public boolean resetPassword(String username, String email, String newPassword) throws IOException {
User user = fileIOService.findUserByUsername(username);
if (user == null) {
throw new IllegalArgumentException("用户名不存在!");
}
if (!user.getEmail().equals(email)) {
throw new IllegalArgumentException("邮箱验证失败!");
}
String passwordError = PasswordValidator.validatePassword(newPassword);
if (passwordError != null) {
throw new IllegalArgumentException(passwordError);
}
String hashedNewPassword = hashPassword(newPassword);
user.setPassword(hashedNewPassword);
fileIOService.saveUser(user);
System.out.println("✓ 密码重置成功");
return true;
}
// ==================== 用户信息管理 ====================
public boolean updateEmail(User user, String newEmail) throws IOException {
if (!validateEmail(newEmail)) {
throw new IllegalArgumentException("邮箱格式错误!");
}
user.setEmail(newEmail);
fileIOService.saveUser(user);
if (currentUser != null && currentUser.getUsername().equals(user.getUsername())) {
this.currentUser = user;
fileIOService.saveCurrentUser(user);
}
System.out.println("✓ 邮箱更新成功");
return true;
}
public void updateUserStatistics(User user, int score) throws IOException {
int oldTotal = user.getTotalQuizzes();
double oldAverage = user.getAverageScore();
int newTotal = oldTotal + 1;
double newAverage = (oldAverage * oldTotal + score) / newTotal;
user.setTotalQuizzes(newTotal);
user.setAverageScore(newAverage);
fileIOService.saveUser(user);
}
public List<User> getAllUsers() throws IOException {
return fileIOService.loadAllUsers();
}
public User findUser(String username) throws IOException {
return fileIOService.findUserByUsername(username);
}
// ==================== 业务逻辑方法====================
/**
*
*/
public String getRealName(User user) {
if (user == null || user.getUsername() == null) {
return "";
}
String username = user.getUsername();
int dashIndex = username.indexOf('-');
if (dashIndex > 0 && dashIndex < username.length() - 1) {
return username.substring(dashIndex + 1);
}
return username;
}
/**
*
*/
public String getGradeDisplayName(User user) {
if (user == null || user.getGrade() == null) {
return "未知";
}
switch (user.getGrade()) {
case ELEMENTARY:
return "小学";
case MIDDLE:
return "初中";
case HIGH:
return "高中";
default:
return "未知";
}
}
/**
*
*/
public String getUserStatistics(User user) {
StringBuilder sb = new StringBuilder();
sb.append("========== 用户统计 ==========\n");
sb.append("用户名:").append(user.getUsername()).append("\n");
sb.append("真实姓名:").append(getRealName(user)).append("\n");
sb.append("学段:").append(getGradeDisplayName(user)).append("\n");
sb.append("邮箱:").append(user.getEmail()).append("\n");
sb.append("总答题次数:").append(user.getTotalQuizzes()).append(" 次\n");
sb.append("平均分:").append(String.format("%.1f", user.getAverageScore())).append(" 分\n");
sb.append("注册时间:").append(user.getRegistrationDate()).append("\n");
sb.append("=============================\n");
return sb.toString();
}
// ==================== 验证工具方法 ====================
private boolean validateUsername(String username) {
if (username == null || username.trim().isEmpty()) {
return false;
}
Matcher matcher = USERNAME_PATTERN.matcher(username);
return matcher.matches();
}
private boolean validateEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
Matcher matcher = EMAIL_PATTERN.matcher(email);
return matcher.matches();
}
private Grade extractGradeFromUsername(String username) {
if (username.startsWith("小学-")) {
return Grade.ELEMENTARY;
} else if (username.startsWith("初中-")) {
return Grade.MIDDLE;
} else if (username.startsWith("高中-")) {
return Grade.HIGH;
}
throw new IllegalArgumentException("无法识别的学段");
}
private String hashPassword(String password) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("密码加密失败", e);
}
}
}

@ -0,0 +1,68 @@
package com.service.question_generator;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.factory.ElementaryQuestionFactory;
import com.service.question_generator.factory.HighQuestionFactory;
import com.service.question_generator.factory.MiddleQuestionFactory;
import com.service.question_generator.factory.QuestionFactory;
import java.util.*;
/**
*
*/
public class QuestionFactoryManager {
private static final Map<Grade, QuestionFactory> factories = new HashMap<>();
static {
factories.put(Grade.ELEMENTARY, new ElementaryQuestionFactory());
factories.put(Grade.MIDDLE, new MiddleQuestionFactory());
factories.put(Grade.HIGH, new HighQuestionFactory());
}
/**
*
*
* @param grade
* @param count
* @param historyQuestions null
* @return
*/
public static List<ChoiceQuestion> generateQuestions(
Grade grade, int count, Set<String> historyQuestions) {
List<ChoiceQuestion> questions = new ArrayList<>();
Set<String> usedQuestions = historyQuestions != null ?
new HashSet<>(historyQuestions) :
new HashSet<>();
int maxAttempts = count * 10;
int attempts = 0;
QuestionFactory factory = factories.get(grade);
if (factory == null) {
throw new IllegalArgumentException("不支持的学段: " + grade);
}
while (questions.size() < count && attempts < maxAttempts) {
ChoiceQuestion question = factory.createQuestion();
String questionText = question.getQuestionText();
if (!usedQuestions.contains(questionText)) {
questions.add(question);
usedQuestions.add(questionText);
}
attempts++;
}
if (questions.size() < count) {
System.out.println("⚠ 警告:只生成了 " + questions.size() +
" 道题,未达到要求的 " + count + " 道");
}
return questions;
}
}

@ -0,0 +1,41 @@
package com.service.question_generator.factory;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.elementary.*;
import com.util.RandomUtils;
import com.service.question_generator.strategy.*;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class ElementaryQuestionFactory implements QuestionFactory {
private final List<QuestionStrategy> strategies;
public ElementaryQuestionFactory() {
strategies = new ArrayList<>();
// 注册所有小学题目生成策略
strategies.add(new AdditionStrategy());
strategies.add(new SubtractionStrategy());
strategies.add(new MultiplicationStrategy());
strategies.add(new DivisionStrategy());
strategies.add(new ParenthesesAddStrategy());
strategies.add(new ParenthesesMultiplyStrategy());
}
//重载接口方法
@Override
public ChoiceQuestion createQuestion() {
// 从六个题型list中随机选择一个生成题目
QuestionStrategy strategy = RandomUtils.randomChoice(strategies);
return strategy.generate();
}
@Override
public Grade getSupportedGrade() {
return Grade.ELEMENTARY;
}
}

@ -0,0 +1,41 @@
package com.service.question_generator.factory;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.QuestionStrategy;
import com.service.question_generator.strategy.high.CosStrategy;
import com.service.question_generator.strategy.high.SinStrategy;
import com.service.question_generator.strategy.high.TanStrategy;
import com.service.question_generator.strategy.high.TrigIdentityStrategy;
import com.util.RandomUtils;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class HighQuestionFactory implements QuestionFactory {
private final List<QuestionStrategy> strategies;
public HighQuestionFactory() {
strategies = new ArrayList<>();
// 注册所有高中题目生成策略
strategies.add(new SinStrategy());
strategies.add(new CosStrategy());
strategies.add(new TanStrategy());
strategies.add(new TrigIdentityStrategy());
}
@Override
public ChoiceQuestion createQuestion() {
QuestionStrategy strategy = RandomUtils.randomChoice(strategies);
return strategy.generate();
}
@Override
public Grade getSupportedGrade() {
return Grade.HIGH;
}
}

@ -0,0 +1,41 @@
package com.service.question_generator.factory;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.middle.*;
import com.util.RandomUtils;
import com.service.question_generator.strategy.QuestionStrategy;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class MiddleQuestionFactory implements QuestionFactory {
private final List<QuestionStrategy> strategies;
public MiddleQuestionFactory() {
strategies = new ArrayList<>();
// 注册所有初中题目生成策略
strategies.add(new SquareStrategy());
strategies.add(new SquareAddStrategy());
strategies.add(new SqrtStrategy());
strategies.add(new SqrtAddStrategy());
strategies.add(new MixedSquareSqrtStrategy());
}
@Override
public ChoiceQuestion createQuestion() {
QuestionStrategy strategy = RandomUtils.randomChoice(strategies);
return strategy.generate();
}
@Override
public Grade getSupportedGrade() {
return Grade.MIDDLE;
}
}

@ -0,0 +1,17 @@
package com.service.question_generator.factory;
import com.model.ChoiceQuestion;
import com.model.Grade;
/**
*
*/
public interface QuestionFactory {
//创建题目
ChoiceQuestion createQuestion();
//获取工厂支持的学段
Grade getSupportedGrade();
}

@ -0,0 +1,138 @@
package com.service.question_generator.strategy;
import com.model.Grade;
import com.util.RandomUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
*
*
*/
public abstract class AbstractQuestionStrategy implements QuestionStrategy {
protected final Grade grade;
/**
*
* @param grade
*/
protected AbstractQuestionStrategy(Grade grade) {
this.grade = grade;
}
/**
* 3
* @param correctAnswer
* @return
*/
protected List<Double> generateNumericOptions(double correctAnswer) {
List<Double> options = new ArrayList<>();
options.add(correctAnswer);
// 根据答案大小动态调整干扰项范围
int range = (int) Math.max(10, Math.abs(correctAnswer) * 0.3);
// 生成3个干扰项
for (int i = 0; i < 3; i++) {
double distractor;
int attempts = 0;
do {
int offset = RandomUtils.nextInt(-range, range);
if (offset == 0) offset = (i + 1) * 3;
distractor = correctAnswer + offset;
attempts++;
} while ((options.contains(distractor) || distractor < 0) && attempts < 20);
if (attempts >= 20) {
distractor = correctAnswer + (i + 1) * 5;
}
options.add(distractor);
}
// 确保有4个选项
while (options.size() < 4) {
options.add(correctAnswer + RandomUtils.nextInt(5, 15));
}
Collections.shuffle(options);
return options;
}
/**
*
* @param correctAnswer
* @param commonError
* @return
*/
protected List<Double> generateNumericOptionsWithCommonError(
double correctAnswer, double commonError) {
List<Double> options = new ArrayList<>();
options.add(correctAnswer);
// 添加常见错误项(如果有效)
if (commonError != correctAnswer && commonError > 0 && !Double.isNaN(commonError)) {
options.add(commonError);
}
int range = (int) Math.max(10, Math.abs(correctAnswer) * 0.3);
// 填充其他干扰项
while (options.size() < 4) {
double distractor;
int attempts = 0;
do {
int offset = RandomUtils.nextInt(-range, range);
if (offset == 0) offset = 5;
distractor = correctAnswer + offset;
attempts++;
} while ((options.contains(distractor) || distractor < 0) && attempts < 20);
if (attempts < 20) {
options.add(distractor);
} else {
options.add(correctAnswer + options.size() * 3);
}
}
Collections.shuffle(options);
return options;
}
/**
*
* @param correctAnswer
* @param allPossibleValues
* @return
*/
protected List<String> generateStringOptions(
String correctAnswer, List<String> allPossibleValues) {
List<String> options = new ArrayList<>();
options.add(correctAnswer);
List<String> availableValues = new ArrayList<>(allPossibleValues);
availableValues.remove(correctAnswer);
// 随机选择3个干扰项
while (options.size() < 4 && !availableValues.isEmpty()) {
int randomIndex = RandomUtils.nextInt(0, availableValues.size() - 1);
String distractor = availableValues.get(randomIndex);
options.add(distractor);
availableValues.remove(randomIndex);
}
while (options.size() < 4) {
options.add("未知");
}
Collections.shuffle(options);
return options;
}
}

@ -0,0 +1,12 @@
package com.service.question_generator.strategy;
import com.model.ChoiceQuestion;
//题目生成题型接口
public interface QuestionStrategy {
//生成题目
ChoiceQuestion generate();
//题型
String getStrategyName();
}

@ -0,0 +1,37 @@
package com.service.question_generator.strategy.elementary;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
*/
public class AdditionStrategy extends AbstractQuestionStrategy {
public AdditionStrategy() {
super(Grade.ELEMENTARY);
}
@Override
public ChoiceQuestion generate() {
int num1 = RandomUtils.nextInt(1, 30);
int num2 = RandomUtils.nextInt(1, 30);
String questionText = num1 + " + " + num2;
double answer = num1 + num2;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "加法";
}
}

@ -0,0 +1,39 @@
package com.service.question_generator.strategy.elementary;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
*/
public class DivisionStrategy extends AbstractQuestionStrategy {
public DivisionStrategy() {
super(Grade.ELEMENTARY);
}
@Override
public ChoiceQuestion generate() {
int divisor = RandomUtils.nextInt(2, 10);
int quotient = RandomUtils.nextInt(1, 10);
int dividend = divisor * quotient;
String questionText = dividend + " ÷ " + divisor;
double answer = quotient;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "除法";
}
}

@ -0,0 +1,37 @@
package com.service.question_generator.strategy.elementary;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
*/
public class MultiplicationStrategy extends AbstractQuestionStrategy {
public MultiplicationStrategy() {
super(Grade.ELEMENTARY);
}
@Override
public ChoiceQuestion generate() {
int factor1 = RandomUtils.nextInt(1, 12);
int factor2 = RandomUtils.nextInt(1, 12);
String questionText = factor1 + " × " + factor2;
double answer = factor1 * factor2;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "乘法";
}
}

@ -0,0 +1,40 @@
package com.service.question_generator.strategy.elementary;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
* (a + b) × c
*/
public class ParenthesesAddStrategy extends AbstractQuestionStrategy {
public ParenthesesAddStrategy() {
super(Grade.ELEMENTARY);
}
@Override
public ChoiceQuestion generate() {
int num1 = RandomUtils.nextInt(1, 20);
int num2 = RandomUtils.nextInt(1, 20);
int num3 = RandomUtils.nextInt(2, 10);
String questionText = "(" + num1 + " + " + num2 + ") × " + num3;
double answer = (num1 + num2) * num3;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "括号加法乘法";
}
}

@ -0,0 +1,41 @@
package com.service.question_generator.strategy.elementary;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
* (a - b) × c
*/
public class ParenthesesMultiplyStrategy extends AbstractQuestionStrategy {
public ParenthesesMultiplyStrategy() {
super(Grade.ELEMENTARY);
}
@Override
public ChoiceQuestion generate() {
int num1 = RandomUtils.nextInt(1, 20);
int num2 = RandomUtils.nextInt(1, 20);
int num3 = RandomUtils.nextInt(2, 10);
int larger = Math.max(num1, num2);
int smaller = Math.min(num1, num2);
String questionText = "(" + larger + " - " + smaller + ") × " + num3;
double answer = (larger - smaller) * num3;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "括号减法乘法";
}
}

@ -0,0 +1,40 @@
package com.service.question_generator.strategy.elementary;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
*/
public class SubtractionStrategy extends AbstractQuestionStrategy {
public SubtractionStrategy() {
super(Grade.ELEMENTARY);
}
@Override
public ChoiceQuestion generate() {
int num1 = RandomUtils.nextInt(1, 30);
int num2 = RandomUtils.nextInt(1, 30);
// 确保结果为正数
int larger = Math.max(num1, num2);
int smaller = Math.min(num1, num2);
String questionText = larger + " - " + smaller;
double answer = larger - smaller;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "减法";
}
}

@ -0,0 +1,57 @@
package com.service.question_generator.strategy.high;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
import java.util.*;
/**
*
* cos(45°)
*/
public class CosStrategy extends AbstractQuestionStrategy {
// 特殊角的余弦值表
private static final Map<Integer, String> COS_VALUES = new HashMap<>();
static {
COS_VALUES.put(0, "1");
COS_VALUES.put(30, "√3/2");
COS_VALUES.put(45, "√2/2");
COS_VALUES.put(60, "1/2");
COS_VALUES.put(90, "0");
COS_VALUES.put(120, "-1/2");
COS_VALUES.put(135, "-√2/2");
COS_VALUES.put(150, "-√3/2");
COS_VALUES.put(180, "-1");
}
public CosStrategy() {
super(Grade.HIGH);
}
@Override
public ChoiceQuestion generate() {
List<Integer> angles = new ArrayList<>(COS_VALUES.keySet());
int randomIndex = RandomUtils.nextInt(0, angles.size() - 1);
int angle = angles.get(randomIndex);
String questionText = "cos(" + angle + "°) = ?";
String correctAnswer = COS_VALUES.get(angle);
List<String> allValues = new ArrayList<>(COS_VALUES.values());
List<String> options = generateStringOptions(correctAnswer, allValues);
return new ChoiceQuestion(questionText, correctAnswer, options, grade);
}
@Override
public String getStrategyName() {
return "余弦函数";
}
}

@ -0,0 +1,56 @@
package com.service.question_generator.strategy.high;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.*;
import java.util.List;
/**
*
* sin(30°)
*/
public class SinStrategy extends AbstractQuestionStrategy {
// 特殊角的正弦值表
private static final Map<Integer, String> SIN_VALUES = new HashMap<>();
static {
SIN_VALUES.put(0, "0");
SIN_VALUES.put(30, "1/2");
SIN_VALUES.put(45, "√2/2");
SIN_VALUES.put(60, "√3/2");
SIN_VALUES.put(90, "1");
SIN_VALUES.put(120, "√3/2");
SIN_VALUES.put(135, "√2/2");
SIN_VALUES.put(150, "1/2");
SIN_VALUES.put(180, "0");
}
public SinStrategy() {
super(Grade.HIGH);
}
@Override
public ChoiceQuestion generate() {
List<Integer> angles = new ArrayList<>(SIN_VALUES.keySet());
int randomIndex = RandomUtils.nextInt(0, angles.size() - 1);
int angle = angles.get(randomIndex);
String questionText = "sin(" + angle + "°) = ?";
String correctAnswer = SIN_VALUES.get(angle);
List<String> allValues = new ArrayList<>(SIN_VALUES.values());
List<String> options = generateStringOptions(correctAnswer, allValues);
return new ChoiceQuestion(questionText, correctAnswer, options, grade);
}
@Override
public String getStrategyName() {
return "正弦函数";
}
}

@ -0,0 +1,56 @@
package com.service.question_generator.strategy.high;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
import java.util.*;
/**
*
* tan(45°)
*/
public class TanStrategy extends AbstractQuestionStrategy {
// 特殊角的正切值表
private static final Map<Integer, String> TAN_VALUES = new HashMap<>();
static {
TAN_VALUES.put(0, "0");
TAN_VALUES.put(30, "√3/3");
TAN_VALUES.put(45, "1");
TAN_VALUES.put(60, "√3");
TAN_VALUES.put(120, "-√3");
TAN_VALUES.put(135, "-1");
TAN_VALUES.put(150, "-√3/3");
TAN_VALUES.put(180, "0");
}
public TanStrategy() {
super(Grade.HIGH);
}
@Override
public ChoiceQuestion generate() {
List<Integer> angles = new ArrayList<>(TAN_VALUES.keySet());
int randomIndex = RandomUtils.nextInt(0, angles.size() - 1);
int angle = angles.get(randomIndex);
String questionText = "tan(" + angle + "°) = ?";
String correctAnswer = TAN_VALUES.get(angle);
List<String> allValues = new ArrayList<>(TAN_VALUES.values());
List<String> options = generateStringOptions(correctAnswer, allValues);
return new ChoiceQuestion(questionText, correctAnswer, options, grade);
}
@Override
public String getStrategyName() {
return "正切函数";
}
}

@ -0,0 +1,41 @@
package com.service.question_generator.strategy.high;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
import java.util.*;
/**
*
* sin²(θ) + cos²(θ) = ?
*/
public class TrigIdentityStrategy extends AbstractQuestionStrategy {
public TrigIdentityStrategy() {
super(Grade.HIGH);
}
@Override
public ChoiceQuestion generate() {
int[] angles = {30, 45, 60, 90};
int angle = angles[RandomUtils.nextInt(0, angles.length - 1)];
String questionText = "sin²(" + angle + "°) + cos²(" + angle + "°) = ?";
String correctAnswer = "1"; // 三角恒等式永远等于1
List<String> allValues = Arrays.asList("1", "0", "2", "√2", "1/2");
List<String> options = generateStringOptions(correctAnswer, allValues);
return new ChoiceQuestion(questionText, correctAnswer, options, grade);
}
@Override
public String getStrategyName() {
return "三角恒等式";
}
}

@ -0,0 +1,40 @@
package com.service.question_generator.strategy.middle;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
* 49 + 3²
*/
public class MixedSquareSqrtStrategy extends AbstractQuestionStrategy {
public MixedSquareSqrtStrategy() {
super(Grade.MIDDLE);
}
@Override
public ChoiceQuestion generate() {
int sqrtRoot = RandomUtils.nextInt(2, 8);
int sqrtNum = sqrtRoot * sqrtRoot;
int squareBase = RandomUtils.nextInt(2, 6);
String questionText = "√" + sqrtNum + " + " + squareBase + "²";
double answer = sqrtRoot + (squareBase * squareBase);
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "平方开方混合";
}
}

@ -0,0 +1,39 @@
package com.service.question_generator.strategy.middle;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
* 49 + 5
*/
public class SqrtAddStrategy extends AbstractQuestionStrategy {
public SqrtAddStrategy() {
super(Grade.MIDDLE);
}
@Override
public ChoiceQuestion generate() {
int root = RandomUtils.nextInt(2, 10);
int num = root * root;
int add = RandomUtils.nextInt(1, 20);
String questionText = "√" + num + " + " + add;
double answer = root + add;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "开方加法";
}
}

@ -0,0 +1,39 @@
package com.service.question_generator.strategy.middle;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
* 49
*/
public class SqrtStrategy extends AbstractQuestionStrategy {
public SqrtStrategy() {
super(Grade.MIDDLE);
}
@Override
public ChoiceQuestion generate() {
int root = RandomUtils.nextInt(2, 12);
int num = root * root; // 完全平方数
String questionText = "√" + num;
double answer = root;
// 常见错误把开方当成除以2
double commonError = num / 2.0;
List<Double> options = generateNumericOptionsWithCommonError(answer, commonError);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "开方";
}
}

@ -0,0 +1,37 @@
package com.service.question_generator.strategy.middle;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
* 5² + 10
*/
public class SquareAddStrategy extends AbstractQuestionStrategy {
public SquareAddStrategy() {
super(Grade.MIDDLE);
}
@Override
public ChoiceQuestion generate() {
int base = RandomUtils.nextInt(2, 10);
int add = RandomUtils.nextInt(1, 20);
String questionText = base + "² + " + add;
double answer = base * base + add;
List<Double> options = generateNumericOptions(answer);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "平方加法";
}
}

@ -0,0 +1,39 @@
package com.service.question_generator.strategy.middle;
import com.model.ChoiceQuestion;
import com.model.Grade;
import com.service.question_generator.strategy.AbstractQuestionStrategy;
import com.util.RandomUtils;
import java.util.List;
/**
*
* 5²
*/
public class SquareStrategy extends AbstractQuestionStrategy {
public SquareStrategy() {
super(Grade.MIDDLE);
}
@Override
public ChoiceQuestion generate() {
int num = RandomUtils.nextInt(1, 15);
String questionText = num + "²";
double answer = num * num;
// 常见错误把平方当成乘以2
double commonError = num * 2;
List<Double> options = generateNumericOptionsWithCommonError(answer, commonError);
return new ChoiceQuestion(questionText, answer, options, grade);
}
@Override
public String getStrategyName() {
return "平方";
}
}

@ -0,0 +1,109 @@
package com.util;
/**
*
*
*
*
* 使JavaMail
*/
public class EmailUtil {
/**
*
* @param toEmail
* @param registrationCode
* @return true
*/
public static boolean sendRegistrationCode(String toEmail, String registrationCode) {
// TODO: 暂不实现邮件发送功能
// 原因:项目需求是直接在界面显示注册码,不需要发邮件
System.out.println("【模拟】发送注册码邮件");
System.out.println("收件人: " + toEmail);
System.out.println("注册码: " + registrationCode);
return true;
}
/**
*
* @param toEmail
* @param newPassword
* @return true
*/
public static boolean sendPasswordReset(String toEmail, String newPassword) {
// TODO: 将来如果需要"找回密码"功能,可以在这里实现
System.out.println("【模拟】发送密码重置邮件");
System.out.println("收件人: " + toEmail);
System.out.println("新密码: " + newPassword);
return true;
}
/**
*
* @param email
* @return true
*/
public static boolean isValidEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
// 简单的邮箱格式验证
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
return email.matches(emailRegex);
}
}
/*
*
*
* 1. pom.xml
* <dependency>
* <groupId>javax.mail</groupId>
* <artifactId>javax.mail-api</artifactId>
* <version>1.6.2</version>
* </dependency>
* <dependency>
* <groupId>com.sun.mail</groupId>
* <artifactId>javax.mail</artifactId>
* <version>1.6.2</version>
* </dependency>
*
* 2.
*
* import javax.mail.*;
* import javax.mail.internet.*;
* import java.util.Properties;
*
* public static boolean sendEmail(String toEmail, String subject, String content) {
* try {
* Properties props = new Properties();
* props.put("mail.smtp.auth", "true");
* props.put("mail.smtp.starttls.enable", "true");
* props.put("mail.smtp.host", "smtp.qq.com");
* props.put("mail.smtp.port", "587");
*
* Session session = Session.getInstance(props, new Authenticator() {
* protected PasswordAuthentication getPasswordAuthentication() {
* return new PasswordAuthentication("your-email@qq.com", "your-password");
* }
* });
*
* Message message = new MimeMessage(session);
* message.setFrom(new InternetAddress("your-email@qq.com"));
* message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
* message.setSubject(subject);
* message.setText(content);
*
* Transport.send(message);
* return true;
* } catch (Exception e) {
* e.printStackTrace();
* return false;
* }
* }
*/

@ -0,0 +1,184 @@
package com.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.lang.reflect.Type;
/**
*
* JSON
*/
public class FileUtils {
private static final Gson gson = new GsonBuilder()
.setPrettyPrinting() // 格式化输出JSON
.create();
/**
*
* @param filePath
* @return
* @throws IOException
*/
public static String readFileToString(String filePath) throws IOException {
return Files.readString(Paths.get(filePath), StandardCharsets.UTF_8);
}
/**
*
* @param filePath
* @param content
* @throws IOException
*/
public static void writeStringToFile(String filePath, String content) throws IOException {
Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8);
}
/**
*
* @param dirPath
* @throws IOException
*/
public static void createDirectoryIfNotExists(String dirPath) throws IOException {
Path path = Paths.get(dirPath);
if (!Files.exists(path)) {
Files.createDirectories(path);
}
}
/**
*
* @param filePath
* @return true
*/
public static boolean exists(String filePath) {
return Files.exists(Paths.get(filePath));
}
/**
*
* @param filePath
* @return true
*/
public static boolean deleteFile(String filePath) {
try {
return Files.deleteIfExists(Paths.get(filePath));
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
*
* @param dirPath
* @return
*/
public static File[] listFiles(String dirPath) {
File dir = new File(dirPath);
if (dir.exists() && dir.isDirectory()) {
return dir.listFiles();
}
return new File[0];
}
/**
*
* @param filePath
* @param content
* @throws IOException
*/
public static void appendToFile(String filePath, String content) throws IOException {
Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
/**
*
* @param sourcePath
* @param targetPath
* @throws IOException
*/
public static void copyFile(String sourcePath, String targetPath) throws IOException {
Files.copy(Paths.get(sourcePath), Paths.get(targetPath), StandardCopyOption.REPLACE_EXISTING);
}
/**
*
* @param filePath
* @return
* @throws IOException
*/
public static long getFileSize(String filePath) throws IOException {
return Files.size(Paths.get(filePath));
}
// ==================== JSON 操作方法 ====================
/**
* JSON
* @param data
* @param filePath
* @throws IOException
*/
public static void saveAsJson(Object data, String filePath) throws IOException {
String json = gson.toJson(data);
writeStringToFile(filePath, json);
}
/**
* JSON
* @param filePath
* @param classOfT Class
* @return
* @throws IOException
*/
public static <T> T readJsonToObject(String filePath, Class<T> classOfT) throws IOException {
String json = readFileToString(filePath);
return gson.fromJson(json, classOfT);
}
/**
* JSON
* @param filePath
* @param typeOfT Type
* @return
* @throws IOException
*/
public static <T> T readJsonToObject(String filePath, Type typeOfT) throws IOException {
String json = readFileToString(filePath);
return gson.fromJson(json, typeOfT);
}
/**
* JSON
* @param data
* @return JSON
*/
public static String toJson(Object data) {
return gson.toJson(data);
}
/**
* JSON
* @param json JSON
* @param classOfT Class
* @return
*/
public static <T> T fromJson(String json, Class<T> classOfT) {
return gson.fromJson(json, classOfT);
}
/**
* JSON
* @param json JSON
* @param typeOfT Type
* @return
*/
public static <T> T fromJson(String json, Type typeOfT) {
return gson.fromJson(json, typeOfT);
}
}

@ -0,0 +1,329 @@
package com.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
*
*
*/
public class PasswordValidator {
// 密码长度限制
private static final int MIN_LENGTH = 6;
private static final int MAX_LENGTH = 20; // 改为20位更安全
// 用于生成随机注册码的字符集
private static final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
private static final String DIGITS = "0123456789";
private static final String ALL_CHARS = UPPERCASE + LOWERCASE + DIGITS;
// 使用 SecureRandom 替代 Math.random(),更安全
private static final SecureRandom random = new SecureRandom();
// ==================== 密码验证 ====================
/**
*
*
* @param password
* @return null
*/
public static String validatePassword(String password) {
if (password == null || password.isEmpty()) {
return "密码不能为空!";
}
if (password.length() < MIN_LENGTH) {
return "密码长度不能少于 " + MIN_LENGTH + " 位!";
}
if (password.length() > MAX_LENGTH) {
return "密码长度不能超过 " + MAX_LENGTH + " 位!";
}
if (password.contains(" ")) {
return "密码不能包含空格!";
}
// 检查是否包含字母
boolean hasLetter = password.matches(".*[a-zA-Z].*");
// 检查是否包含数字
boolean hasDigit = password.matches(".*\\d.*");
if (!hasLetter) {
return "密码必须包含字母!";
}
if (!hasDigit) {
return "密码必须包含数字!";
}
return null; // 验证通过
}
/**
*
*
* @param password
* @return true
*/
public static boolean isValid(String password) {
return validatePassword(password) == null;
}
/**
*
*
* @param password
* @return
*/
public static String getPasswordStrength(String password) {
if (password == null || password.length() < MIN_LENGTH) {
return "弱";
}
int score = 0;
// 长度加分
if (password.length() >= 8) score++;
if (password.length() >= 12) score++;
// 包含小写字母
if (password.matches(".*[a-z].*")) score++;
// 包含大写字母
if (password.matches(".*[A-Z].*")) score++;
// 包含数字
if (password.matches(".*\\d.*")) score++;
// 包含特殊字符
if (password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?].*")) score++;
if (score <= 2) return "弱";
if (score <= 4) return "中";
return "强";
}
// ==================== 密码加密 ====================
/**
* 使SHA-256
*
* @param password
* @return 16
*/
public static String encrypt(String password) {
if (password == null) {
throw new IllegalArgumentException("密码不能为null");
}
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256算法不可用", e);
}
}
/**
*
*
* @param plainPassword
* @param encryptedPassword
* @return true
*/
public static boolean matches(String plainPassword, String encryptedPassword) {
if (plainPassword == null || encryptedPassword == null) {
return false;
}
return encrypt(plainPassword).equals(encryptedPassword);
}
// ==================== 注册码生成 ====================
/**
* 6-10
*
* @return
*/
public static String generateRegistrationCode() {
return generateRegistrationCode(MIN_LENGTH, 10);
}
/**
*
*
* @param minLen
* @param maxLen
* @return
*/
public static String generateRegistrationCode(int minLen, int maxLen) {
if (minLen < 4 || maxLen < minLen) {
throw new IllegalArgumentException("长度参数无效");
}
int length = minLen + random.nextInt(maxLen - minLen + 1);
StringBuilder code = new StringBuilder(length);
// 确保至少有一个大写字母
code.append(UPPERCASE.charAt(random.nextInt(UPPERCASE.length())));
// 确保至少有一个小写字母
code.append(LOWERCASE.charAt(random.nextInt(LOWERCASE.length())));
// 确保至少有一个数字
code.append(DIGITS.charAt(random.nextInt(DIGITS.length())));
// 填充剩余字符
for (int i = 3; i < length; i++) {
code.append(ALL_CHARS.charAt(random.nextInt(ALL_CHARS.length())));
}
// 打乱字符顺序
return shuffleString(code.toString());
}
/**
*
*
* @param length
* @param includeSpecialChars
* @return
*/
public static String generateRandomPassword(int length, boolean includeSpecialChars) {
if (length < MIN_LENGTH) {
throw new IllegalArgumentException("密码长度不能少于 " + MIN_LENGTH);
}
String chars = ALL_CHARS;
if (includeSpecialChars) {
chars += "!@#$%^&*()_+-=[]{}";
}
StringBuilder password = new StringBuilder(length);
// 确保至少包含一个字母和一个数字
password.append(UPPERCASE.charAt(random.nextInt(UPPERCASE.length())));
password.append(DIGITS.charAt(random.nextInt(DIGITS.length())));
// 填充剩余字符
for (int i = 2; i < length; i++) {
password.append(chars.charAt(random.nextInt(chars.length())));
}
return shuffleString(password.toString());
}
// ==================== 工具方法 ====================
/**
* 使 Fisher-Yates
*
* @param str
* @return
*/
private static String shuffleString(String str) {
if (str == null || str.length() <= 1) {
return str;
}
char[] chars = str.toCharArray();
for (int i = chars.length - 1; i > 0; i--) {
int j = random.nextInt(i + 1);
// 交换
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
return new String(chars);
}
/**
*
*
* @param password
* @return true
*/
public static boolean isWeakPassword(String password) {
if (password == null) {
return true;
}
String lowerPassword = password.toLowerCase();
// 常见弱密码列表
String[] weakPasswords = {
"123456", "password", "123456789", "12345678", "12345",
"111111", "1234567", "sunshine", "qwerty", "iloveyou",
"princess", "admin", "welcome", "666666", "abc123",
"football", "123123", "monkey", "654321", "!@#$%^&*",
"charlie", "aa123456", "donald", "password1", "qwerty123"
};
for (String weak : weakPasswords) {
if (lowerPassword.equals(weak) || lowerPassword.contains(weak)) {
return true;
}
}
// 检查是否是连续数字或字母
if (password.matches("^(\\d)\\1+$") || // 全是相同数字
password.matches("^(.)\\1+$") || // 全是相同字符
password.matches("^(0123456789|123456789|987654321|abcdefghij|qwertyuiop).*")) { // 连续字符
return true;
}
return false;
}
/**
*
*
* @param password
* @return
*/
public static String getPasswordSuggestion(String password) {
if (password == null || password.isEmpty()) {
return "请输入密码";
}
String error = validatePassword(password);
if (error != null) {
return error;
}
String strength = getPasswordStrength(password);
if ("弱".equals(strength)) {
return "密码强度较弱,建议:\n" +
"• 使用至少8位字符\n" +
"• 同时包含大小写字母、数字\n" +
"• 添加特殊字符";
} else if ("中".equals(strength)) {
return "密码强度中等,可以考虑添加特殊字符提高安全性";
} else {
return "密码强度良好!";
}
}
}

@ -0,0 +1,69 @@
package com.util;
import java.util.Collections;
import java.util.List;
import java.util.Random;
//各种随机数生成
public class RandomUtils {
private static final Random random = new Random();
//生成[min, max]范围内的随机整数(包含边界)
public static int nextInt(int min, int max) {
if (min > max) {
throw new IllegalArgumentException("min不能大于max");
}
return min + random.nextInt(max - min + 1);
}
//从数组中随机选择一个元素(模板类)
public static <T> T randomChoice(T[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("数组不能为空");
}
return array[random.nextInt(array.length)];
}
//从列表中随机选择一个元素
public static <T> T randomChoice(List<T> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException("列表不能为空");
}
return list.get(random.nextInt(list.size()));
}
/**
*
* @param list
*/
public static <T> void shuffle(List<T> list) {
Collections.shuffle(list, random);
}
//生成指定范围内的随机双精度浮点数
public static double nextDouble(double min, double max) {
if (min > max) {
throw new IllegalArgumentException("min不能大于max");
}
return min + (max - min) * random.nextDouble();
}
//生成随机布尔值
public static boolean nextBoolean() {
return random.nextBoolean();
}
//按概率返回true题目生成概率
//示例probability(0.7) 有70%概率返回true
public static boolean probability(double probability) {
if (probability < 0.0 || probability > 1.0) {
throw new IllegalArgumentException("概率必须在0.0-1.0之间");
}
return random.nextDouble() < probability;
}
}

@ -0,0 +1,774 @@
import com.model.*;
import com.service.*;
import com.service.question_generator.QuestionFactoryManager;
import com.util.PasswordValidator;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*
*
*/
public class TestMain {
private static int testsPassed = 0;
private static int testsFailed = 0;
public static void main(String[] args) {
System.out.println("========================================");
System.out.println(" 数学答题系统 - 完整测试");
System.out.println("========================================\n");
try {
// 1. 测试工具类
testPasswordValidator();
// 2. 测试文件服务
testFileIOService();
// 3. 测试用户服务
testUserService();
// 4. 测试题目生成
testQuestionGeneration();
// 5. 测试答题服务
testQuizService();
// 6. 测试完整流程
testCompleteWorkflow();
// 输出测试结果
printTestSummary();
} catch (Exception e) {
System.err.println("测试过程中发生错误:" + e.getMessage());
e.printStackTrace();
}
}
// ==================== 1. 测试密码验证工具 ====================
private static void testPasswordValidator() {
System.out.println("【测试1】密码验证工具");
System.out.println("----------------------------------------");
// 测试1.1: 有效密码
test("有效密码验证",
PasswordValidator.isValid("Abc123456"),
"密码 'Abc123456' 应该有效");
// 测试1.2: 密码太短
test("密码太短检测",
!PasswordValidator.isValid("Abc12"),
"密码 'Abc12' 应该无效(太短)");
// 测试1.3: 缺少数字
test("缺少数字检测",
!PasswordValidator.isValid("Abcdefgh"),
"密码 'Abcdefgh' 应该无效(缺少数字)");
// 测试1.4: 密码加密
String encrypted1 = PasswordValidator.encrypt("test123");
String encrypted2 = PasswordValidator.encrypt("test123");
test("密码加密一致性",
encrypted1.equals(encrypted2),
"相同密码加密结果应该一致");
// 测试1.5: 密码匹配
test("密码匹配验证",
PasswordValidator.matches("test123", encrypted1),
"密码匹配应该成功");
// 测试1.6: 生成注册码
String code = PasswordValidator.generateRegistrationCode();
test("注册码生成",
code.length() >= 6 && code.length() <= 10,
"注册码长度应该在6-10位之间实际" + code.length());
// 测试1.7: 密码强度检测
String strength = PasswordValidator.getPasswordStrength("Abc123!@#");
test("密码强度检测",
strength != null && !strength.isEmpty(),
"密码强度应该返回有效值,实际:" + strength);
System.out.println();
}
// ==================== 2. 测试文件IO服务 ====================
private static void testFileIOService() throws IOException {
System.out.println("【测试2】文件IO服务");
System.out.println("----------------------------------------");
FileIOService fileService = new FileIOService();
// 测试2.1: 初始化目录
try {
fileService.initDataDirectory();
test("初始化数据目录", true, "数据目录初始化成功");
} catch (Exception e) {
test("初始化数据目录", false, "失败:" + e.getMessage());
}
// 测试2.2: 保存和加载用户
User testUser = new User("小学-测试", "encrypted123", "test@test.com", Grade.ELEMENTARY);
try {
fileService.saveUser(testUser);
User loaded = fileService.findUserByUsername("小学-测试");
test("保存和加载用户",
loaded != null && loaded.getUsername().equals("小学-测试"),
"用户数据应该正确保存和加载");
} catch (Exception e) {
test("保存和加载用户", false, "失败:" + e.getMessage());
}
// 测试2.3: 检查用户名是否存在
try {
boolean exists = fileService.isUsernameExists("小学-测试");
test("检查用户名存在", exists, "用户名应该存在");
} catch (Exception e) {
test("检查用户名存在", false, "失败:" + e.getMessage());
}
// 测试2.4: 查找不存在的用户
try {
User notFound = fileService.findUserByUsername("不存在的用户");
test("查找不存在的用户",
notFound == null,
"不存在的用户应该返回null");
} catch (Exception e) {
test("查找不存在的用户", false, "失败:" + e.getMessage());
}
System.out.println();
}
// ==================== 3. 测试用户服务====================
private static void testUserService() throws IOException {
System.out.println("【测试3】用户服务包含验证码");
System.out.println("----------------------------------------");
UserService userService = new UserService();
// ========== 3.1 测试注册码生成和保存 ==========
String testEmail1 = "test001@example.com";
String registrationCode1 = null;
try {
registrationCode1 = userService.generateRegistrationCode(testEmail1);
test("生成注册码",
registrationCode1 != null && registrationCode1.length() >= 6,
"注册码:" + registrationCode1);
} catch (Exception e) {
test("生成注册码", false, "失败:" + e.getMessage());
}
// ========== 3.2 测试注册码文件存储 ==========
try {
boolean fileExists = com.util.FileUtils.exists("data/registration_codes.txt");
test("注册码文件创建",
fileExists,
"注册码应该保存到文件");
} catch (Exception e) {
test("注册码文件创建", false, "失败:" + e.getMessage());
}
// ========== 3.3 测试用户注册(带验证码)==========
String testUsername = "小学-张三测试";
String testPassword = "Test123456";
try {
User user = userService.register(testUsername, testPassword, testEmail1, registrationCode1);
test("用户注册(带验证码)",
user != null && user.getUsername().equals(testUsername),
"用户注册成功");
} catch (Exception e) {
test("用户注册(带验证码)", false, "失败:" + e.getMessage());
}
// ========== 3.4 测试注册码一次性使用 ==========
try {
// 尝试用同一个注册码再次注册
userService.register("小学-李四", "Test123456", testEmail1, registrationCode1);
test("注册码一次性使用", false, "应该抛出异常");
} catch (IllegalArgumentException e) {
test("注册码一次性使用",
e.getMessage().contains("未找到"),
"注册码使用后应该被删除");
} catch (Exception e) {
test("注册码一次性使用", false, "异常类型错误:" + e.getMessage());
}
// ========== 3.5 测试错误的注册码 ==========
String testEmail2 = "test002@example.com";
try {
String code = userService.generateRegistrationCode(testEmail2);
// 故意使用错误的注册码
userService.register("小学-王五", "Test123456", testEmail2, "wrongCode123");
test("错误注册码检测", false, "应该抛出异常");
} catch (IllegalArgumentException e) {
test("错误注册码检测",
e.getMessage().contains("注册码错误"),
"应该检测到错误的注册码");
} catch (Exception e) {
test("错误注册码检测", false, "失败:" + e.getMessage());
}
// ========== 3.6 测试未获取注册码就注册 ==========
try {
userService.register("小学-赵六", "Test123456", "nocode@test.com", "randomCode");
test("未获取注册码检测", false, "应该抛出异常");
} catch (IllegalArgumentException e) {
test("未获取注册码检测",
e.getMessage().contains("未找到"),
"应该检测到未获取注册码");
} catch (Exception e) {
test("未获取注册码检测", false, "失败:" + e.getMessage());
}
// ========== 3.7 测试重复注册检测 ==========
String testEmail3 = "test003@example.com";
try {
String code = userService.generateRegistrationCode(testEmail3);
userService.register(testUsername, "Test123456", testEmail3, code);
test("重复注册检测", false, "应该抛出异常");
} catch (IllegalArgumentException e) {
test("重复注册检测",
e.getMessage().contains("已存在"),
"应该检测到用户名已存在");
} catch (Exception e) {
test("重复注册检测", false, "失败:" + e.getMessage());
}
// ========== 3.8 测试用户登录 ==========
try {
User user = userService.login(testUsername, testPassword);
test("用户登录",
user != null && userService.isLoggedIn(),
"用户登录成功");
} catch (Exception e) {
test("用户登录", false, "失败:" + e.getMessage());
}
// ========== 3.9 测试错误密码登录 ==========
try {
userService.logout(); // 先退出
userService.login(testUsername, "WrongPassword123");
test("错误密码登录", false, "应该抛出异常");
} catch (IllegalArgumentException e) {
test("错误密码登录",
e.getMessage().contains("密码错误"),
"应该检测到密码错误");
} catch (Exception e) {
test("错误密码登录", false, "失败:" + e.getMessage());
}
// ========== 3.10 测试不存在的用户登录 ==========
try {
userService.login("小学-不存在", "Test123456");
test("不存在用户登录", false, "应该抛出异常");
} catch (IllegalArgumentException e) {
test("不存在用户登录",
e.getMessage().contains("不存在"),
"应该检测到用户名不存在");
} catch (Exception e) {
test("不存在用户登录", false, "失败:" + e.getMessage());
}
// ========== 3.11 测试获取当前用户 ==========
try {
userService.login(testUsername, testPassword);
User current = userService.getCurrentUser();
test("获取当前用户",
current != null && current.getUsername().equals(testUsername),
"应该返回当前登录用户");
} catch (Exception e) {
test("获取当前用户", false, "失败:" + e.getMessage());
}
// ========== 3.12 测试提取真实姓名 ==========
User user = userService.getCurrentUser();
String realName = userService.getRealName(user);
test("提取真实姓名",
realName.equals("张三测试"),
"应该正确提取真实姓名,实际:" + realName);
// ========== 3.13 测试获取学段显示名 ==========
String gradeName = userService.getGradeDisplayName(user);
test("获取学段显示名",
gradeName.equals("小学"),
"应该返回'小学',实际:" + gradeName);
// ========== 3.14 测试退出登录 ==========
userService.logout();
test("退出登录",
!userService.isLoggedIn(),
"退出后应该未登录状态");
// ========== 3.15 测试完整注册流程(不同学段)==========
// 初中学生注册
try {
String middleEmail = "middle@test.com";
String middleCode = userService.generateRegistrationCode(middleEmail);
User middleUser = userService.register("初中-李明", "Middle123", middleEmail, middleCode);
test("初中学生注册",
middleUser != null && middleUser.getGrade() == Grade.MIDDLE,
"初中学生注册成功");
} catch (Exception e) {
test("初中学生注册", false, "失败:" + e.getMessage());
}
// 高中学生注册
try {
String highEmail = "high@test.com";
String highCode = userService.generateRegistrationCode(highEmail);
User highUser = userService.register("高中-王华", "High123456", highEmail, highCode);
test("高中学生注册",
highUser != null && highUser.getGrade() == Grade.HIGH,
"高中学生注册成功");
} catch (Exception e) {
test("高中学生注册", false, "失败:" + e.getMessage());
}
// ========== 3.16 测试密码强度验证 ==========
try {
String weakEmail = "weak@test.com";
String weakCode = userService.generateRegistrationCode(weakEmail);
userService.register("小学-弱密码", "123", weakEmail, weakCode);
test("密码强度验证", false, "应该拒绝弱密码");
} catch (IllegalArgumentException e) {
test("密码强度验证",
e.getMessage().contains("密码"),
"应该检测到密码不符合要求");
} catch (Exception e) {
test("密码强度验证", false, "失败:" + e.getMessage());
}
// ========== 3.17 测试邮箱格式验证 ==========
try {
userService.generateRegistrationCode("invalid-email");
test("邮箱格式验证", false, "应该拒绝无效邮箱");
} catch (IllegalArgumentException e) {
test("邮箱格式验证",
e.getMessage().contains("邮箱"),
"应该检测到邮箱格式错误");
} catch (Exception e) {
test("邮箱格式验证", false, "失败:" + e.getMessage());
}
// ========== 3.18 测试用户名格式验证 ==========
try {
String invalidEmail = "invalid@test.com";
String invalidCode = userService.generateRegistrationCode(invalidEmail);
userService.register("错误格式", "Test123456", invalidEmail, invalidCode);
test("用户名格式验证", false, "应该拒绝错误格式的用户名");
} catch (IllegalArgumentException e) {
test("用户名格式验证",
e.getMessage().contains("格式"),
"应该检测到用户名格式错误");
} catch (Exception e) {
test("用户名格式验证", false, "失败:" + e.getMessage());
}
// ========== 3.19 测试清理过期注册码 ==========
try {
userService.cleanExpiredCodes();
test("清理过期注册码", true, "清理操作成功");
} catch (Exception e) {
test("清理过期注册码", false, "失败:" + e.getMessage());
}
// ========== 3.20 测试从文件重新加载注册码 ==========
try {
String reloadEmail = "reload@test.com";
String reloadCode = userService.generateRegistrationCode(reloadEmail);
// 创建新的 UserService 实例(模拟重启)
UserService newUserService = new UserService();
// 使用之前保存的注册码
User reloadUser = newUserService.register("小学-重载测试", "Reload123", reloadEmail, reloadCode);
test("从文件重载注册码",
reloadUser != null,
"应该能从文件读取注册码");
} catch (Exception e) {
test("从文件重载注册码", false, "失败:" + e.getMessage());
}
// ========== 3.21 查看注册码文件内容 ==========
try {
if (com.util.FileUtils.exists("data/registration_codes.txt")) {
String fileContent = com.util.FileUtils.readFileToString(
"data/registration_codes.txt"
);
System.out.println("\n 【注册码文件内容预览】");
String[] lines = fileContent.split("\n");
int lineCount = 0;
for (String line : lines) {
if (lineCount++ < 10) { // 显示前10行
System.out.println(" " + line);
}
}
if (lines.length > 10) {
System.out.println(" ... (共 " + lines.length + " 行)");
}
test("注册码文件格式",
fileContent.contains("#") && fileContent.contains("|"),
"文件格式正确");
}
} catch (Exception e) {
test("查看文件内容", false, "失败:" + e.getMessage());
}
System.out.println();
}
// ==================== 4. 测试题目生成 ====================
private static void testQuestionGeneration() {
System.out.println("【测试4】题目生成");
System.out.println("----------------------------------------");
// 测试4.1: 生成小学题目(不去重)
try {
List<ChoiceQuestion> questions = QuestionFactoryManager.generateQuestions(
Grade.ELEMENTARY, 1, null
);
test("生成小学题目",
questions.size() == 1 && questions.get(0).getQuestionText() != null,
"应该生成1道有效的小学题目");
System.out.println(" 示例题目:" + questions.get(0).getQuestionText());
} catch (Exception e) {
test("生成小学题目", false, "失败:" + e.getMessage());
}
// 测试4.2: 生成初中题目
try {
List<ChoiceQuestion> questions = QuestionFactoryManager.generateQuestions(
Grade.MIDDLE, 1, null
);
test("生成初中题目",
questions.size() == 1,
"应该生成1道有效的初中题目");
System.out.println(" 示例题目:" + questions.get(0).getQuestionText());
} catch (Exception e) {
test("生成初中题目", false, "失败:" + e.getMessage());
}
// 测试4.3: 生成高中题目
try {
List<ChoiceQuestion> questions = QuestionFactoryManager.generateQuestions(
Grade.HIGH, 1, null
);
test("生成高中题目",
questions.size() == 1,
"应该生成1道有效的高中题目");
System.out.println(" 示例题目:" + questions.get(0).getQuestionText());
} catch (Exception e) {
test("生成高中题目", false, "失败:" + e.getMessage());
}
// 测试4.4: 批量生成题目
try {
List<ChoiceQuestion> questions = QuestionFactoryManager.generateQuestions(
Grade.ELEMENTARY, 10, null
);
test("批量生成题目",
questions.size() == 10,
"应该生成10道题目实际" + questions.size());
} catch (Exception e) {
test("批量生成题目", false, "失败:" + e.getMessage());
}
// 测试4.5: 题目去重功能
try {
// 第一次生成
List<ChoiceQuestion> firstBatch = QuestionFactoryManager.generateQuestions(
Grade.ELEMENTARY, 5, null
);
// 收集已生成的题目文本
Set<String> historyQuestions = new HashSet<>();
for (ChoiceQuestion q : firstBatch) {
historyQuestions.add(q.getQuestionText());
}
// 第二次生成(带去重)
List<ChoiceQuestion> secondBatch = QuestionFactoryManager.generateQuestions(
Grade.ELEMENTARY, 5, historyQuestions
);
// 检查第二次生成的题目是否与第一次重复
boolean noDuplicate = true;
for (ChoiceQuestion q : secondBatch) {
if (historyQuestions.contains(q.getQuestionText())) {
noDuplicate = false;
break;
}
}
test("题目去重功能",
noDuplicate,
"第二次生成的题目不应与第一次重复");
} catch (Exception e) {
test("题目去重功能", false, "失败:" + e.getMessage());
}
System.out.println();
}
// ==================== 5. 测试答题服务 ====================
private static void testQuizService() throws IOException {
System.out.println("【测试5】答题服务");
System.out.println("----------------------------------------");
FileIOService fileService = new FileIOService();
UserService userService = new UserService(fileService);
QuizService quizService = new QuizService(fileService, userService);
// 创建测试用户
User testUser = new User("小学-李四", "encrypted", "lisi@test.com", Grade.ELEMENTARY);
fileService.saveUser(testUser);
// 测试5.1: 开始答题
try {
quizService.startNewQuiz(testUser, 5);
test("开始答题会话",
quizService.getTotalQuestions() == 5,
"应该生成5道题目");
} catch (Exception e) {
test("开始答题会话", false, "失败:" + e.getMessage());
}
// 测试5.2: 获取当前题目
ChoiceQuestion current = quizService.getCurrentQuestion();
test("获取当前题目",
current != null,
"应该返回当前题目");
// 测试5.3: 提交答案
try {
boolean correct = quizService.submitCurrentAnswer(0);
test("提交答案",
true, // 只要不抛异常就算通过
"提交答案应该成功,结果:" + (correct ? "正确" : "错误"));
} catch (Exception e) {
test("提交答案", false, "失败:" + e.getMessage());
}
// 测试5.4: 题目导航
boolean canNext = quizService.nextQuestion();
test("下一题导航",
canNext,
"应该能够移动到下一题");
boolean canPrev = quizService.previousQuestion();
test("上一题导航",
canPrev,
"应该能够移动到上一题");
// 测试5.5: 检查答案
ChoiceQuestion question = quizService.getCurrentQuestion();
int correctIndex = quizService.getCorrectAnswerIndex(question);
boolean isCorrect = quizService.checkAnswer(question, correctIndex);
test("检查正确答案",
isCorrect,
"正确答案应该通过验证");
// 测试5.6: 答题进度
quizService.goToQuestion(0);
quizService.submitCurrentAnswer(0);
quizService.nextQuestion();
quizService.submitCurrentAnswer(1);
int answered = quizService.getAnsweredCount();
test("答题进度统计",
answered == 2,
"应该有2道题已作答实际" + answered);
// 测试5.7: 完成所有题目并计算成绩
for (int i = 0; i < quizService.getTotalQuestions(); i++) {
quizService.goToQuestion(i);
quizService.submitCurrentAnswer(0);
}
QuizResult result = quizService.calculateResult();
test("计算成绩",
result.getTotalQuestions() == 5,
"成绩统计应该正确,总题数:" + result.getTotalQuestions());
System.out.println(" 得分:" + result.getScore());
System.out.println(" 正确:" + result.getCorrectCount());
System.out.println(" 错误:" + result.getWrongCount());
// 测试5.8: 格式化输出
String formatted = quizService.formatResult(result);
test("格式化结果输出",
formatted != null && formatted.contains("答题结束"),
"应该返回格式化的结果文本");
System.out.println();
}
// ==================== 6. 测试完整流程 ====================
private static void testCompleteWorkflow() throws IOException {
System.out.println("【测试6】完整答题流程");
System.out.println("----------------------------------------");
FileIOService fileService = new FileIOService();
UserService userService = new UserService(fileService);
QuizService quizService = new QuizService(fileService, userService);
try {
// ========== 步骤1: 注册新用户 ==========
System.out.println("步骤1: 注册新用户...");
String username = "初中-王五";
String password = "Test123456";
String email = "wangwu@test.com";
// 1.1 生成注册码
String registrationCode = userService.generateRegistrationCode(email);
System.out.println(" 获取注册码:" + registrationCode);
// 1.2 使用注册码注册
User user = userService.register(username, password, email, registrationCode);
test("完整流程-注册", user != null, "用户注册成功");
// ========== 步骤2: 用户登录 ==========
System.out.println("步骤2: 用户登录...");
userService.login(username, password);
test("完整流程-登录", userService.isLoggedIn(), "用户登录成功");
// ========== 步骤3: 开始答题 ==========
System.out.println("步骤3: 开始答题10道题...");
quizService.startNewQuiz(user, 10);
test("完整流程-生成题目",
quizService.getTotalQuestions() == 10,
"题目生成成功");
// ========== 步骤4: 答题(模拟全部答对)==========
System.out.println("步骤4: 模拟答题过程...");
for (int i = 0; i < 10; i++) {
quizService.goToQuestion(i);
ChoiceQuestion q = quizService.getCurrentQuestion();
int correctIndex = quizService.getCorrectAnswerIndex(q);
quizService.submitAnswer(i, correctIndex);
}
test("完整流程-答题", quizService.isAllAnswered(), "所有题目已作答");
// ========== 步骤5: 计算成绩 ==========
System.out.println("步骤5: 计算成绩...");
QuizResult result = quizService.calculateResult();
test("完整流程-计算成绩",
result.getScore() == 100,
"全部答对应该得100分实际" + result.getScore());
System.out.println(quizService.formatResult(result));
// ========== 步骤6: 保存记录 ==========
System.out.println("步骤6: 保存答题记录...");
quizService.saveQuizHistory(user);
// 验证用户统计是否更新
User updatedUser = fileService.findUserByUsername(username);
test("完整流程-保存记录",
updatedUser.getTotalQuizzes() == 1,
"用户答题次数应该增加,实际:" + updatedUser.getTotalQuizzes());
test("完整流程-平均分更新",
updatedUser.getAverageScore() == 100.0,
"平均分应该更新,实际:" + updatedUser.getAverageScore());
// ========== 步骤7: 退出登录 ==========
System.out.println("步骤7: 退出登录...");
userService.logout();
test("完整流程-退出", !userService.isLoggedIn(), "退出登录成功");
System.out.println("\n✓ 完整流程测试通过!");
} catch (Exception e) {
test("完整流程", false, "失败:" + e.getMessage());
e.printStackTrace();
}
System.out.println();
}
// ==================== 测试工具方法 ====================
private static void test(String testName, boolean condition, String message) {
if (condition) {
System.out.println(" ✓ " + testName + ": 通过");
if (message != null && !message.isEmpty()) {
System.out.println(" " + message);
}
testsPassed++;
} else {
System.out.println(" ✗ " + testName + ": 失败");
if (message != null && !message.isEmpty()) {
System.out.println(" " + message);
}
testsFailed++;
}
}
private static void printTestSummary() {
System.out.println("========================================");
System.out.println(" 测试结果汇总");
System.out.println("========================================");
System.out.println("总测试数:" + (testsPassed + testsFailed));
System.out.println("通过:" + testsPassed + " 项");
System.out.println("失败:" + testsFailed + " 项");
if (testsFailed == 0) {
System.out.println("\n🎉 所有测试通过项目功能正常可以开始开发UI了");
} else {
System.out.println("\n⚠ 有 " + testsFailed + " 项测试失败,请检查并修复问题");
}
System.out.println("========================================");
}
}
Loading…
Cancel
Save