1.0.2 #8

Merged
hnu202326010319 merged 2 commits from LiangJunYaoBranch into develop 4 months ago

@ -9,20 +9,17 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="ad9e49ad-e421-455c-8a89-0f35a7a15146" name="Changes" comment="partial ui implication">
<change afterPath="$PROJECT_DIR$/.idea/artifacts/MathQuizApp_jar.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/META-INF/MANIFEST.MF" afterDir="false" />
<list default="true" id="ad9e49ad-e421-455c-8a89-0f35a7a15146" name="Changes" comment="发行版1.01">
<change afterPath="$PROJECT_DIR$/src/main/java/com/util/AppDataDirectory.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/data/users.json" beforeDir="false" afterPath="$PROJECT_DIR$/data/users.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/pom.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/Test.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/Test.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/model/Grade.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/model/Grade.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/model/QuizResult.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/model/QuizResult.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/service/FileIOService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/service/FileIOService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/service/QuizService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/service/QuizService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/service/UserService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/service/UserService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/ui/MainWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/ui/MainWindow.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/ui/QuizPage.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/ui/QuizPage.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/ui/ResultPage.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/ui/ResultPage.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/java/TestMain.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/util/FileUtils.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/util/FileUtils.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/util/PasswordValidator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/util/PasswordValidator.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -33,8 +30,8 @@
<option name="RECENT_TEMPLATES">
<list>
<option value="Kotlin Class" />
<option value="Class" />
<option value="JavaFXApplication" />
<option value="Class" />
</list>
</option>
</component>
@ -140,7 +137,9 @@
<workItem from="1759588522412" duration="122000" />
<workItem from="1759644297457" duration="32827000" />
<workItem from="1759720493898" duration="12215000" />
<workItem from="1759745765663" duration="2792000" />
<workItem from="1759745765663" duration="4945000" />
<workItem from="1759752200157" duration="6627000" />
<workItem from="1759758921108" duration="6306000" />
</task>
<task id="LOCAL-00001" summary="ui design v1">
<option name="closed" value="true" />
@ -158,7 +157,23 @@
<option name="project" value="LOCAL" />
<updated>1759684484435</updated>
</task>
<option name="localTasksCounter" value="3" />
<task id="LOCAL-00003" summary="version one ,do not package">
<option name="closed" value="true" />
<created>1759749578708</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1759749578708</updated>
</task>
<task id="LOCAL-00004" summary="发行版1.01">
<option name="closed" value="true" />
<created>1759758970278</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1759758970278</updated>
</task>
<option name="localTasksCounter" value="5" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -190,6 +205,8 @@
<component name="VcsManagerConfiguration">
<MESSAGE value="ui design v1" />
<MESSAGE value="partial ui implication" />
<option name="LAST_COMMIT_MESSAGE" value="partial ui implication" />
<MESSAGE value="version one ,do not package" />
<MESSAGE value="发行版1.01" />
<option name="LAST_COMMIT_MESSAGE" value="发行版1.01" />
</component>
</project>

@ -0,0 +1,10 @@
{
"userId": "75fd5144-97f1-44eb-bf34-d339a0cffd1c",
"username": "1111",
"password": "b6e4e03a35a862ae38516e7b6d0e0ae8c0a6444bd3d33713e7d212e8ecd5531a",
"email": "111@123.com",
"grade": "ELEMENTARY",
"totalQuizzes": 0,
"averageScore": 0.0,
"registrationDate": "Oct 6, 2025, 1:35:39PM"
}

@ -0,0 +1,66 @@
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mathquiz</groupId>
<artifactId>MathQuizApp</artifactId>
<name>Math Quiz Application</name>
<version>1.0.0</version>
<description>小初高数学学习软件 - JavaFX版本</description>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>com.Test</mainClass>
</transformer>
<transformer />
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<finalName>MathQuizApp</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>21.0.2</javafx.version>
</properties>
</project>

@ -7,11 +7,11 @@
<groupId>com.mathquiz</groupId>
<artifactId>MathQuizApp</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
<packaging>jar</packaging>
<name>Math Quiz Application</name>
<description>小初高数学学习软件 - JavaFX版本</description>
<description>å°<EFBFBD>åˆ<EFBFBD>高数学学习软ä»?- JavaFX版本</description>
<!-- 新增 JitPack 仓库配置 -->
<repositories>
@ -80,31 +80,44 @@
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.mathquiz.MathQuizApp/com.Test</mainClass>
<native-bundling>
<winConsole>false</winConsole>
<appName>${project.name}</appName>
<vendor>com.mathquiz</vendor>
<appVersion>${project.version}</appVersion>
<winShortcut>true</winShortcut>
<winMenu>true</winMenu>
</native-bundling>
</configuration>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>default-cli</id>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>jpackage</goal>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dependencies</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<!-- 新增:支æŒ?mvn javafx:run å<>¯åŠ¨çš„æ<E2809E>ä»¶é…<C3A9>ç½?-->
<!-- 支æŒ<C3A6> mvn javafx:run å<>¯åŠ¨çš„æ<E2809E>ä»¶é…<C3A9>ç½?-->
<!-- 支æŒ<C3A6> mvn javafx:run å<>¯åŠ¨çš„æ<E2809E>ä»¶é…<C3A9>ç½?-->
<!-- 用通用çš?exec-maven-plugin å<>¯åЍ JavaFX ç¨åº<C3A5> -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<!-- 指定你的å<E2809E>¯åŠ¨ç±»å…¨è·¯å¾„ï¼ˆå¿…é¡»ä¸Ž com.Test 一致) -->
<mainClass>com.Test</mainClass>
<!-- ä¼ é€?JVM å<>数:指定需è¦<C3A8>çš„ JavaFX 模å<C2A1>-->
<arguments>
<argument>--add-modules</argument>
<argument>javafx.controls,javafx.fxml,javafx.graphics,javafx.base</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
</project>

@ -6,10 +6,12 @@ import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class Test extends Application {
@Override
public void start(Stage primaryStage) {
public void start(Stage primaryStage) throws IOException {
MainWindow mainWindow = new MainWindow(primaryStage);
Scene scene = new Scene(mainWindow, 1366, 786);
primaryStage.setTitle("中小学数学答题系统");

@ -2,6 +2,7 @@ package com.service;
import com.model.*;
import com.util.AppDataDirectory;
import com.util.FileUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -19,12 +20,13 @@ import java.util.*;
*/
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 DATA_DIR = AppDataDirectory.getFullPath("data");
private static final String USERS_DIR = AppDataDirectory.getFullPath("data/users");
private static final String HISTORY_DIR = AppDataDirectory.getFullPath("data/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 String REGISTRATION_CODES_FILE = AppDataDirectory.getFullPath("data/registration_codes.json");
private static final String USERS_FILE = AppDataDirectory.getFullPath("data/users.json");
private static final String CURRENT_USER_FILE = AppDataDirectory.getFullPath("data/current_user.json");
private static final Gson gson = new GsonBuilder()
.setPrettyPrinting()
@ -33,10 +35,15 @@ public class FileIOService {
// ==================== 初始化 ====================
public FileIOService() throws IOException {
initDataDirectory();
}
public void initDataDirectory() throws IOException {
FileUtils.createDirectoryIfNotExists(DATA_DIR);
FileUtils.createDirectoryIfNotExists(USERS_DIR);
FileUtils.createDirectoryIfNotExists(HISTORY_DIR);
FileUtils.ensureFileExists(REGISTRATION_CODES_FILE);
if (!FileUtils.exists(USERS_FILE)) {
Map<String, List<User>> data = new HashMap<>();
@ -255,4 +262,9 @@ public class FileIOService {
}
return filename.replaceAll("[\\\\/:*?\"<>|]", "_");
}
public String getRegistrationCodesFilePath() {
return REGISTRATION_CODES_FILE;
}
}

@ -20,7 +20,7 @@ public class QuizService {
// ==================== 构造方法 ====================
public QuizService() {
public QuizService() throws IOException {
this.fileIOService = new FileIOService();
this.userService = new UserService(fileIOService);
this.currentQuestions = new ArrayList<>();

@ -3,9 +3,11 @@ package com.service;
import com.model.Grade;
import com.model.User;
import com.ui.NavigablePanel;
import com.util.FileUtils;
import com.util.PasswordValidator;
import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@ -33,7 +35,7 @@ public class UserService {
// ==================== 构造方法 ====================
public UserService() {
public UserService() throws IOException {
this.fileIOService = new FileIOService();
this.currentUser = null;
}
@ -43,8 +45,6 @@ public class UserService {
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分钟
@ -66,6 +66,7 @@ public class UserService {
long expiryTime = System.currentTimeMillis() + CODE_EXPIRY_TIME;
// 保存到文件
System.out.println(expiryTime);
saveRegistrationCodeToFile(email, code, expiryTime);
// 打印注册码(实际项目中可以发邮件)
@ -109,7 +110,33 @@ public class UserService {
.append("\n");
}
FileUtils.writeStringToFile(REGISTRATION_CODES_FILE, content.toString());
FileUtils.writeStringToFile(fileIOService.getRegistrationCodesFilePath(), content.toString());
}
/**
*
*/
private void initializeDataDirectory() {
try {
File dataDir = new File("data");
if (!dataDir.exists()) {
dataDir.mkdirs();
System.out.println("✓ 已创建 data 目录");
}
// 确保注册码文件存在(即使是空的也创建)
File codesFile = new File(fileIOService.getRegistrationCodesFilePath());
if (!codesFile.exists()) {
StringBuilder initialContent = new StringBuilder();
initialContent.append("# 注册码记录文件\n");
initialContent.append("# 格式: 邮箱|注册码|过期时间戳|过期时间\n\n");
FileUtils.writeStringToFile(fileIOService.getRegistrationCodesFilePath(), initialContent.toString());
System.out.println("✓ 已创建注册码文件");
}
} catch (IOException e) {
System.err.println(" 初始化数据目录失败: " + e.getMessage());
}
}
/**
@ -117,12 +144,12 @@ public class UserService {
*/
private Map<String, RegistrationCode> loadRegistrationCodesFromFile() throws IOException {
Map<String, RegistrationCode> codes = new HashMap<>();
if (!FileUtils.exists(REGISTRATION_CODES_FILE)) {
return codes;
System.out.println(fileIOService.getRegistrationCodesFilePath());
if (!FileUtils.exists(fileIOService.getRegistrationCodesFilePath())) {
throw new IOException("目录不存在");
}
String content = FileUtils.readFileToString(REGISTRATION_CODES_FILE);
String content = FileUtils.readFileToString(fileIOService.getRegistrationCodesFilePath());
String[] lines = content.split("\n");
for (String line : lines) {
@ -199,7 +226,7 @@ public class UserService {
.append("\n");
}
FileUtils.writeStringToFile(REGISTRATION_CODES_FILE, content.toString());
FileUtils.writeStringToFile(fileIOService.getRegistrationCodesFilePath(), content.toString());
}
/**
@ -242,10 +269,9 @@ public class UserService {
throw new IllegalArgumentException("注册码错误!");
}
// // 2. 验证用户名格式
// if (!validateUsername(username)) {
// throw new IllegalArgumentException("用户名格式错误!正确格式:学段-姓名(如:小学-张三)");
// }
if (!validateEmail(email)) {
throw new IllegalArgumentException("邮箱格式错误!");
}
// 3. 验证用户名是否已存在
if (fileIOService.isEmailExists(email)) {
@ -258,9 +284,11 @@ public class UserService {
// throw new IllegalArgumentException(passwordError);
// }
// 5. 验证邮箱格式
if (!validateEmail(email)) {
throw new IllegalArgumentException("邮箱格式错误!");
// 5. 验证密码格式
try {
PasswordValidator.validatePassword(password);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("密码格式错误!");
}
// 6. 从用户名中提取学段
@ -342,17 +370,10 @@ public class UserService {
throw new IllegalArgumentException("旧密码错误!");
}
String passwordError = PasswordValidator.validatePassword(newPassword);
if (passwordError != null) {
throw new IllegalArgumentException(passwordError);
}
if (oldPassword.equals(newPassword)) {
throw new IllegalArgumentException("新密码不能与旧密码相同!");
}
if (!oldPassword.equals(confirmPassword)) {
throw new IllegalArgumentException("两次新密码不同!");
try {
PasswordValidator.validatePassword(newPassword);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage());
}
String hashedNewPassword = PasswordValidator.encrypt(newPassword);
@ -380,9 +401,10 @@ public class UserService {
throw new IllegalArgumentException("邮箱验证失败!");
}
String passwordError = PasswordValidator.validatePassword(newPassword);
if (passwordError != null) {
throw new IllegalArgumentException(passwordError);
try {
PasswordValidator.validatePassword(newPassword);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage());
}
String hashedNewPassword = PasswordValidator.encrypt(newPassword);

@ -12,7 +12,6 @@ import javafx.scene.control.Toggle;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.List;
public class MainWindow extends BorderPane {
private final UserService userService = new UserService();
@ -22,7 +21,7 @@ public class MainWindow extends BorderPane {
private final Stage primaryStage;
private Panel currentPanel;
public MainWindow(Stage primaryStage) {
public MainWindow(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
showStartPage();
}
@ -276,7 +275,7 @@ public class MainWindow extends BorderPane {
return currentPanel;
}
public static void start(Stage stage) {
public static void start(Stage stage) throws IOException {
MainWindow mainWindow = new MainWindow(stage);
Scene scene = new Scene(mainWindow, 800, 600);
stage.setScene(scene);

@ -0,0 +1,45 @@
package com.util;
import java.io.File;
/**
*
*
*/
public class AppDataDirectory {
private static final String APP_NAME = "Math-Quiz-App"; // 替换为你的应用名
/**
*
*/
public static String getApplicationDataDirectory() {
String os = System.getProperty("os.name").toLowerCase();
String basePath;
if (os.contains("win")) {
// Windows
String appData = System.getenv("APPDATA");
basePath = (appData != null) ? appData : System.getProperty("user.home") + "/AppData/Roaming";
} else if (os.contains("mac")) {
// macOS
basePath = System.getProperty("user.home") + "/Library/Application Support";
} else {
// Linux/Unix
String xdgDataHome = System.getenv("XDG_DATA_HOME");
if (xdgDataHome == null) {
xdgDataHome = System.getProperty("user.home") + "/.local/share";
}
basePath = xdgDataHome;
}
return basePath + "/" + APP_NAME;
}
/**
*
*/
public static String getFullPath(String relativePath) {
String appDataDir = getApplicationDataDirectory();
return appDataDir + "/" + relativePath;
}
}

@ -34,9 +34,22 @@ public class FileUtils {
* @throws IOException
*/
public static void writeStringToFile(String filePath, String content) throws IOException {
Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8);
File file = new File(filePath);
// 确保父目录存在
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
// 写入文件
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
writer.write(content);
}
}
/**
*
* @param dirPath
@ -45,7 +58,20 @@ public class FileUtils {
public static void createDirectoryIfNotExists(String dirPath) throws IOException {
Path path = Paths.get(dirPath);
if (!Files.exists(path)) {
System.out.println(1);
Files.createDirectories(path);
System.out.println(2);
}
}
public static void ensureFileExists(String filePath) throws IOException {
if (!FileUtils.exists(filePath)) {
// 自动创建父目录
String parentDir = Paths.get(filePath).getParent().toString();
FileUtils.createDirectoryIfNotExists(parentDir);
// 创建空文件
FileUtils.writeStringToFile(filePath, "");
}
}

@ -29,53 +29,42 @@ public class PasswordValidator {
* @param password
* @return null
*/
public static String validatePassword(String password) {
public static void validatePassword(String password) {
if (password == null || password.isEmpty()) {
return "密码不能为空!";
throw new IllegalArgumentException("密码不能为空!");
}
if (password.length() < MIN_LENGTH) {
return "密码长度不能少于 " + MIN_LENGTH + " 位!";
throw new IllegalArgumentException("密码长度不能少于 " + MIN_LENGTH + " 位!");
}
if (password.length() > MAX_LENGTH) {
return "密码长度不能超过 " + MAX_LENGTH + " 位!";
throw new IllegalArgumentException ("密码长度不能超过 " + MAX_LENGTH + " 位!");
}
if (password.contains(" ")) {
return "密码不能包含空格!";
throw new IllegalArgumentException ("密码不能包含空格!");
}
// 检查是否包含小写字母
boolean hasLowerLetter = password.matches(".*[a-z].*");
if (!hasLowerLetter) {
return "必须包含小写字母!";
throw new IllegalArgumentException ("必须包含小写字母!");
}
// 检查是否包含大写字母
boolean hasUpperLetter = password.matches(".*[A-Z].*");
if (!hasUpperLetter) {
return "必须包含大写字母!";
throw new IllegalArgumentException ("必须包含大写字母!");
}
// 检查是否包含数字
boolean hasDigit = password.matches(".*\\d.*");
if (!hasDigit) {
return "密码必须包含数字!";
throw new IllegalArgumentException ("密码必须包含数字!");
}
return null; // 验证通过
}
/**
*
*
* @param password
* @return true
*/
public static boolean isValid(String password) {
return validatePassword(password) == null;
}
// /**
// * 检查密码强度等级

Loading…
Cancel
Save