develop to main #9

Merged
psoyzuart merged 49 commits from develop into main 4 months ago

@ -0,0 +1,8 @@
import ui.MainApplication;
public class Main {
public static void main(String[] args) {
// R1: 启动图形化界面应用 (JavaFX)
MainApplication.launch(MainApplication.class, args);
}
}

@ -0,0 +1,206 @@
package auth;
import util.ModifyUtils;
import util.EmailService;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;
public class AuthService {
private static final String USER_FILE_PATH = "user.txt";
private final EmailService emailService = new EmailService();
public AuthService() {
// 检查用户文件是否存在
File userFile = new File(USER_FILE_PATH);
if (!userFile.exists()) {
System.err.println("用户文件不存在: " + USER_FILE_PATH);
try {
// 尝试创建文件
if (userFile.createNewFile()) {
System.out.println("已创建用户文件: " + USER_FILE_PATH);
}
} catch (IOException e) {
System.err.println("创建用户文件失败: " + e.getMessage());
}
}
}
// --- 校验和辅助方法 ---
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$");
// R3: 密码校验: 6-10 位, 必须含大小写字母和数字
public static boolean isPasswordValid(String password) {
if (password == null || password.length() < 6 ||
password.length() > 10) {
return false;
}
Pattern pattern =
Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$");
return pattern.matcher(password).matches();
}
public boolean validateEmail(String email) {
if (email == null){
return false;
}
return EMAIL_PATTERN.matcher(email).matches();
}
// --- 用户加载和保存 ---
private User parseLine(String line, int lineNumber) {
line = line.trim();
if (line.isEmpty()) {
return null;
}
String[] parts = line.split("\\s+");
if (parts.length != 4) {
System.out.println("警告: 第" + lineNumber +
"行数据格式不正确需要4个数据。");
return null;
}
return new User(parts[0], parts[1], parts[2], parts[3]);
}
public List<User> loadUsers() {
List<User> users = new ArrayList<>();
int lineNumber = 1;
try (BufferedReader br = new BufferedReader(new FileReader(USER_FILE_PATH))) {
String line;
while ((line = br.readLine()) != null) {
User user = parseLine(line, lineNumber);
if (user != null) {
users.add(user);
}
lineNumber++;
}
} catch (IOException e) {
System.out.println("读取文件时发生错误: " + e.getMessage());
}
return users;
}
// --- 认证和注册 ---
public User login(String username, String password) {
List<User> users = loadUsers();
for (User user : users) {
if ((user.getUsername().equals(username) || user.getEmail().equals(username)) &&
user.getPassword().equals(password)) {
return user;
}
}
return null;
}
public boolean isUsernameOrEmailTaken(String username, String email) {
List<User> users = loadUsers();
for (User user : users) {
if ((username != null && user.getUsername().equals(username)) ||
(email != null && user.getEmail().equals(email))) {
return true;
}
}
return false;
}
// R2: 实际发送验证邮件
public String sendVerificationCode(String email) {
if (!validateEmail(email) || isUsernameOrEmailTaken(null, email)) {
return null;
}
String code = emailService.generateVerificationCode();
if (emailService.sendVerificationEmail(email, code)) {
return code;
}
return null;
}
private void writeUserData(String userData) throws IOException {
try (BufferedWriter writer =
new BufferedWriter(new FileWriter(USER_FILE_PATH, true))) {
writer.write(userData);
writer.newLine();
}
}
// R2: 注册用户
public boolean registerUser(String username, String password, String email) {
if (isUsernameOrEmailTaken(username, email)) {
return false;
}
// 格式: username password type email
String userData = username + " " + password + " 小学 " + email;
try {
writeUserData(userData);
return true;
} catch (IOException e) {
System.out.println("写入文件时发生错误: " + e.getMessage());
return false;
}
}
// --- 用户修改 ---
public boolean updatePassword(String username, String newPassword) {
// 索引 1 是密码
int result = ModifyUtils.modifyFileField(USER_FILE_PATH, username, 1, newPassword);
return result != -1;
}
public boolean updateDifficulty(String username, String newDifficulty) {
List<String> lines = new ArrayList<>();
boolean found = false;
try (BufferedReader br = new BufferedReader(new FileReader(USER_FILE_PATH))) {
String line;
while ((line = br.readLine()) != null) {
String[] parts = line.trim().split("\\s+");
if (parts.length >= 4 && parts[0].equals(username)) {
// 找到目标用户,更新难度
parts[2] = newDifficulty;
line = String.join(" ", parts);
found = true;
}
lines.add(line);
}
} catch (IOException e) {
System.err.println("读取用户文件错误: " + e.getMessage());
return false;
}
if (!found) {
System.err.println("未找到用户: " + username);
return false;
}
// 写回文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter(USER_FILE_PATH))) {
for (String line : lines) {
bw.write(line);
bw.newLine();
}
return true;
} catch (IOException e) {
System.err.println("写入用户文件错误: " + e.getMessage());
return false;
}
}
}

@ -0,0 +1,20 @@
package auth;
public class User {
private String username;
private String password;
private String type;
private String email;
public User(String username, String password, String type, String email) {
this.username = username;
this.password = password;
this.type = type;
this.email = email;
}
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getType() { return type; }
public String getEmail() { return email; }
}

@ -0,0 +1,13 @@
package auth;
public class UserManager {
private final AuthService authService;
public UserManager() {
this.authService = new AuthService();
}
public User login(String username, String password) {
return authService.login(username, password);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

@ -0,0 +1,170 @@
/* 根容器样式 */
.root {
-fx-font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
-fx-background-image: url("1.jpg");
-fx-background-repeat: no-repeat;
-fx-background-size: cover;
}
/* 通用按钮样式 */
.button {
-fx-background-color: #4CAF50;
-fx-text-fill: white;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-padding: 10px 20px;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-cursor: hand;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 5, 0, 0, 2);
-fx-transition: all 0.3s;
}
.button:hover {
-fx-background-color: #45a049;
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 8, 0, 0, 3);
}
.button:pressed {
-fx-background-color: #3d8b40;
-fx-scale-x: 0.95;
-fx-scale-y: 0.95;
}
/* 次要按钮样式 */
.button-secondary {
-fx-background-color: #2196F3;
}
.button-secondary:hover {
-fx-background-color: #1976D2;
}
.button-secondary:pressed {
-fx-background-color: #0D47A1;
}
/* 警告按钮样式 */
.button-warning {
-fx-background-color: #ff9800;
}
.button-warning:hover {
-fx-background-color: #f57c00;
}
/* 危险按钮样式 */
.button-danger {
-fx-background-color: #f44336;
}
.button-danger:hover {
-fx-background-color: #d32f2f;
}
/* 标签样式 */
.label {
-fx-text-fill: #333333;
-fx-font-size: 14px;
}
.label-title {
-fx-font-size: 24px;
-fx-font-weight: bold;
-fx-text-fill: #2c3e50;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 3, 0, 1, 1);
}
.label-subtitle {
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-text-fill: #34495e;
}
.label-info {
-fx-text-fill: #7f8c8d;
-fx-font-size: 12px;
}
/* 输入框样式 */
.text-field, .password-field {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 10px 15px;
-fx-font-size: 14px;
-fx-effect: innershadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2);
}
.text-field:focused, .password-field:focused {
-fx-border-color: #3498db;
-fx-effect: dropshadow(three-pass-box, rgba(52, 152, 219, 0.3), 10, 0, 0, 3);
}
/* 下拉框样式 */
.combo-box {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 5px 15px;
}
.combo-box .arrow-button {
-fx-background-color: transparent;
}
.combo-box .list-cell {
-fx-background-color: white;
-fx-text-fill: #333333;
}
.combo-box .list-view {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 10px;
-fx-background-radius: 10px;
}
/* 单选按钮样式 */
.radio-button {
-fx-text-fill: #333333;
-fx-font-size: 14px;
-fx-padding: 5px;
}
.radio-button .radio {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 50%;
-fx-background-radius: 50%;
}
.radio-button:selected .radio {
-fx-background-color: #3498db;
-fx-border-color: #2980b9;
}
.radio-button .dot {
-fx-background-color: white;
-fx-background-radius: 50%;
}
/* 容器样式 */
.vbox {
-fx-background-color: rgba(255, 255, 255, 0.5);
-fx-background-radius: 16px;
}
.container {
-fx-background-color: white;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 15, 0, 0, 5);
-fx-padding: 30px;
}

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ExamController">
<children>
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="20.0" styleClass="vbox" stylesheets="@css/main.css">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<Label fx:id="titleLabel" style="-fx-font-size: 25; -fx-font-weight: bold;" text="难度" />
<HBox alignment="CENTER" prefHeight="65.0" prefWidth="241.0">
<children>
<ComboBox fx:id="difficultyCombo" prefHeight="37.0" prefWidth="99.0" promptText="难度" />
<Region prefHeight="65.0" prefWidth="31.0" />
<Button fx:id="switchDiffButton" onAction="#handleSwitchDifficulty" prefHeight="39.0" prefWidth="97.0" styleClass="button-warning" text="切换难度" />
</children>
</HBox>
<TextField fx:id="countField" alignment="CENTER" maxWidth="220.0" promptText="输入生成题目数量 (10-30)" />
<Button fx:id="startButton" onAction="#handleStartExam" styleClass="button-secondary" text="生成试卷" />
<Button fx:id="logoutButton" onAction="#handleLogout" styleClass="button-danger" text="退出登录" />
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</children>
</VBox>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" spacing="15" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ExamController">
<VBox alignment="CENTER_LEFT" maxHeight="300.0" maxWidth="270.0" prefHeight="300.0" styleClass="vbox">
<children>
<Label fx:id="problemText" style="-fx-font-size: 12;">
<VBox.margin>
<Insets left="8.0" />
</VBox.margin>
</Label>
<Region prefHeight="17.0" prefWidth="300.0" />
<VBox fx:id="optionsBox" alignment="CENTER_LEFT" maxHeight="150.0" maxWidth="300.0" spacing="5" />
<HBox alignment="CENTER">
<children>
<Button fx:id="nextButton" onAction="#handleNextProblem" text="下一题" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.LoginController">
<children>
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="20.0" styleClass="vbox">
<Label style="-fx-font-size: 25; -fx-font-weight: bold;" text="登 录" />
<TextField fx:id="usernameField" maxWidth="220.0" promptText="用户名/邮箱" />
<PasswordField fx:id="passwordField" maxWidth="220.0" promptText="密码" />
<HBox alignment="CENTER" prefHeight="23.0" prefWidth="143.0">
<children>
<Button fx:id="loginButton" onAction="#handleLogin" styleClass="button-secondary" text="登录" />
<Region prefHeight="23.0" prefWidth="30.0" />
<Button fx:id="registerButton" onAction="#handleRegister" text="注册" />
</children>
</HBox>
<Button fx:id="modifyPasswordButton" onAction="#handleModifyPassword" styleClass="button-warning" text="修改密码" />
<Label fx:id="messageLabel" style="-fx-text-fill: red;" textAlignment="CENTER">
<VBox.margin>
<Insets />
</VBox.margin>
</Label>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</children>
</VBox>

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" spacing="10" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ModifyPasswordController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="12.0" styleClass="vbox">
<children>
<Label style="-fx-font-size: 25; -fx-font-weight: bold;" text="修改密码">
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Label>
<TextField fx:id="usernameField" alignment="CENTER" maxWidth="220.0" promptText="用户名" />
<PasswordField fx:id="oldPasswordField" alignment="CENTER" maxWidth="220.0" promptText="旧密码" />
<PasswordField fx:id="newPasswordField" alignment="CENTER" maxWidth="220.0" promptText="新密码" />
<PasswordField fx:id="confirmPasswordField" alignment="CENTER" maxWidth="220.0" promptText="确认新密码" />
<HBox alignment="CENTER" prefHeight="66.0" prefWidth="560.0">
<children>
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回登录" />
<Region prefHeight="45.0" prefWidth="33.0" />
<Button fx:id="modifyButton" onAction="#handleModifyPassword" styleClass="button-secondary" text="确认修改" />
</children>
</HBox>
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.RegisterController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="30.0" styleClass="vbox">
<children>
<Label style="-fx-font-size: 24; -fx-font-weight: bold;" text="注册 (2/3) - 验证注册码">
<VBox.margin>
<Insets top="12.0" />
</VBox.margin>
</Label>
<Label fx:id="emailLabel" text="已发送到: " />
<TextField fx:id="codeField" alignment="CENTER" maxWidth="220.0" prefWidth="201.0" promptText="请输入收到的 6 位注册码" />
<Button fx:id="resendButton" onAction="#handleResendCode" prefHeight="39.0" prefWidth="96.0" text="重新发送" />
<HBox alignment="CENTER" prefHeight="58.0" prefWidth="560.0">
<children>
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回" />
<Region prefHeight="58.0" prefWidth="49.0" />
<Button fx:id="verifyButton" onAction="#handleVerifyCode" styleClass="button-secondary" text="验证" />
</children>
</HBox>
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.RegisterController">
<children>
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="30.0" styleClass="vbox" stylesheets="@css/main.css">
<Label style="-fx-font-size: 25; -fx-font-weight: bold;" text="注册 (1/3) - 邮箱验证" />
<TextField fx:id="emailField" alignment="CENTER" maxWidth="220.0" promptText="邮箱" />
<Button fx:id="sendCodeButton" onAction="#handleSendCode" prefHeight="39.0" prefWidth="110.0" styleClass="button-secondary" text="发送注册码" />
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回" />
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</children>
</VBox>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.RegisterController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="15.0" styleClass="vbox">
<children>
<Label fx:id="titleLabel" style="-fx-font-size: 25; -fx-font-weight: bold;" text="注册 (3/3) - 设置密码" />
<TextField fx:id="usernameField" alignment="CENTER" maxWidth="220.0" promptText="设置用户名" />
<PasswordField fx:id="passwordField" alignment="CENTER" maxWidth="220.0" promptText="设置密码" />
<PasswordField fx:id="confirmPasswordField" alignment="CENTER" maxWidth="220.0" promptText="确认密码" />
<Button fx:id="completeButton" onAction="#handleCompleteRegistration" prefWidth="96.0" styleClass="button-secondary" text="完成注册" />
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回" />
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ExamController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="20.0" styleClass="vbox">
<children>
<Label fx:id="scoreLabel" style="-fx-font-size: 25; -fx-font-weight: bold;" text="恭喜您完成答题!" />
<Label fx:id="finalScoreLabel" style="-fx-font-size: 15;" />
<ImageView fitHeight="70.0" fitWidth="130.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@css/2.gif" />
</image>
</ImageView>
<Button fx:id="continueButton" onAction="#handleContinue" prefHeight="39.0" prefWidth="218.0" styleClass="button-secondary" text="继续做题 (返回选择难度)" />
<HBox alignment="CENTER" prefHeight="0.0" prefWidth="270.0">
<children>
<Button fx:id="logoutButton" onAction="#handleLogout" styleClass="button-warning" text="退出登录" />
<Region prefHeight="39.0" prefWidth="28.0" />
<Button fx:id="exitButton" onAction="#handleExit" styleClass="button-danger" text="退出程序" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,28 @@
package generator;
import util.ExpressionUtils;
import java.util.*;
public class HighGenerator implements ProblemGenerator {
@Override
public List<Problem> generateProblems(int count) {
List<Problem> problems = new ArrayList<>();
int attempts = 0;
final int maxAttempts = count * 3; // 最多尝试3倍数量
while (problems.size() < count && attempts < maxAttempts) {
Problem problem = ExpressionUtils.generateHighExpr();
if (problem != null) {
problems.add(problem);
}
attempts++;
}
// 如果无法生成足够题目,用简单题目填充
while (problems.size() < count) {
problems.add(ExpressionUtils.createProblem("sin(30) + " + (problems.size() + 1)));
}
return problems;
}
}

@ -0,0 +1,15 @@
package generator;
import util.ExpressionUtils;
import java.util.*;
public class MiddleGenerator implements ProblemGenerator {
@Override
public List<Problem> generateProblems(int count) {
List<Problem> problems = new ArrayList<>();
for (int i = 0; i < count; i++) {
problems.add(ExpressionUtils.generateMiddleExpr());
}
return problems;
}
}

@ -0,0 +1,15 @@
package generator;
import util.ExpressionUtils;
import java.util.*;
public class PrimaryGenerator implements ProblemGenerator {
@Override
public List<Problem> generateProblems(int count) {
List<Problem> problems = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
problems.add( ExpressionUtils.generatePrimaryExpr());
}
return problems;
}
}

@ -0,0 +1,61 @@
package generator;
import java.util.List;
import java.util.Collections;
public class Problem {
private final String expression;
private final double result;
private final List<String> options;
private final String correctAnswerOption;
public Problem(String expression, double result, List<String> options,
String correctAnswerOption) {
this.expression = expression;
this.result = result;
this.options = options;
this.correctAnswerOption = correctAnswerOption;
}
public String getQuestionText() {
return "计算: " + expression + " = ?";
}
public String getExpression() {
return expression;
}
public List<String> getOptions() {
return Collections.unmodifiableList(options);
}
public String getCorrectAnswerOption() {
return formatResult(result);
}
private String formatResult(double result) {
if (Double.isNaN(result)) {
return "Error";
}
if (Math.abs(result - Math.round(result)) < 0.0001) {
return String.valueOf((int) Math.round(result));
}
return String.format("%.2f", result);
}
public double getResult() {
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getQuestionText()).append("\n");
char optionChar = 'A';
for (String option : options) {
sb.append(optionChar++).append(". ").append(option).append(" ");
}
sb.append("\n正确答案: ").append(correctAnswerOption);
return sb.toString();
}
}

@ -0,0 +1,7 @@
package generator;
import java.util.List;
public interface ProblemGenerator {
List<Problem> generateProblems(int count);
}

@ -0,0 +1,3 @@
javafx.version=17.0.16
javafx.runtime.version=17.0.16+2
javafx.runtime.build=2

@ -0,0 +1,78 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
:: 设置路径
set JAVA_FX_PATH=javafx-sdk-17.0.16\lib
set MAIL_JAR=referenced-lib\activation-1.1.1.jar
set ACTIVATION_JAR=referenced-lib\javax.mail-1.6.2.jar
:: 检查必要的 JAR 文件
if not exist "%MAIL_JAR%" (
echo 错误: 找不到 %MAIL_JAR%
echo 请确保 JavaMail JAR 文件存在于 lib 文件夹中
pause
exit /b 1
)
if not exist "%ACTIVATION_JAR%" (
echo 警告: 找不到 %ACTIVATION_JAR%,尝试继续编译...
)
:: 清理并创建输出目录
if exist out rmdir /s /q out
mkdir out
:: 构建类路径
set CLASSPATH=%MAIL_JAR%
if exist "%ACTIVATION_JAR%" set CLASSPATH=%CLASSPATH%;%ACTIVATION_JAR%
echo 类路径: %CLASSPATH%
echo.
:: 编译项目
echo 正在编译项目...
javac -d out --module-path "%JAVA_FX_PATH%" --add-modules javafx.controls,javafx.fxml ^
-cp "referenced-lib\activation-1.1.1.jar;referenced-lib\javax.mail-1.6.2.jar" ^
auth\AuthService.java ^
auth\User.java ^
auth\UserManager.java ^
generator\HighGenerator.java ^
generator\MiddleGenerator.java ^
generator\PrimaryGenerator.java ^
generator\Problem.java ^
generator\ProblemGenerator.java ^
service\ExamService.java ^
ui\ExamController.java ^
ui\LoginController.java ^
ui\MainApplication.java ^
ui\ModifyPasswordController.java ^
ui\RegisterController.java ^
util\EmailService.java ^
util\ExpressionUtils.java ^
util\FileUtils.java ^
util\ModifyUtils.java ^
Main.java
if !errorlevel! neq 0 (
echo.
echo 编译失败!
echo 请检查:
echo 1. JavaFX 路径是否正确: %JAVA_FX_PATH%
echo 2. JAR 文件是否存在: %CLASSPATH%
pause
exit /b 1
)
:: 复制资源文件
echo 正在复制资源文件...
xcopy fxml out\fxml /E /I /Y >nul 2>&1
copy user.txt out\user.txt >nul 2>&1
:: 运行项目
echo 正在启动应用程序...
java --module-path "%JAVA_FX_PATH%" ^
--add-modules javafx.controls,javafx.fxml ^
-cp "out;%CLASSPATH%" ^
-Dfile.encoding=UTF-8 ^
Main

@ -0,0 +1,109 @@
package service;
import auth.User;
import generator.Problem;
import generator.ProblemGenerator;
import generator.PrimaryGenerator;
import generator.MiddleGenerator;
import generator.HighGenerator;
import util.FileUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ExamService {
public ProblemGenerator getGenerator(String type) {
switch (type) {
case "小学": return new PrimaryGenerator();
case "初中": return new MiddleGenerator();
case "高中": return new HighGenerator();
default: return null;
}
}
private Problem generateUniqueProblem(ProblemGenerator generator,
Set<String> historyExpressions) {
int attempts = 0;
final int maxAttemptsPerProblem = 1000;
while (attempts < maxAttemptsPerProblem) {
Problem problem = generator.generateProblems(1).get(0);
// 查重基于题干 (表达式字符串)
if (!historyExpressions.contains(problem.getExpression())) {
historyExpressions.add(problem.getExpression());
return problem;
}
attempts++;
}
return null;
}
// R5: 生成一张唯一试卷,并将其保存到用户的历史记录中。
public List<Problem> generateExam(User currentUser, int count) {
if (count < 10 || count > 30) {
throw new IllegalArgumentException("题目数量必须在 10 到 30 之间");
}
ProblemGenerator generator = getGenerator(currentUser.getType());
if (generator == null) {
System.err.println("错误: 无法获取题目生成器,用户类型: " + currentUser.getType());
return new ArrayList<>();
}
Set<String> historyExpressions = FileUtils.loadHistory(currentUser.getUsername());
List<Problem> problems = new ArrayList<>();
for(int i = 0; i < count; i++) {
Problem problem = generateUniqueProblem(generator, historyExpressions);
if (problem != null) {
problems.add(problem);
} else {
System.err.println("警告: 无法生成足够数量的唯一题目。已生成: " + problems.size() + " 题");
break;
}
}
if (!problems.isEmpty()) {
FileUtils.saveProblems(currentUser.getUsername(), problems);
} else {
System.err.println("错误: 未能生成任何题目");
}
return problems;
}
// 将这个私有方法改为公共方法,以便在其他类中调用
public boolean isAnswerCorrect(Problem problem, String userAnswer) {
if (userAnswer == null || problem.getCorrectAnswerOption() == null) {
return false;
}
boolean isCorrect = userAnswer.trim().equalsIgnoreCase(problem.getCorrectAnswerOption().trim());
return isCorrect;
}
// R6: 根据答对的百分比计算分数。
public int calculateScore(List<Problem> problems, List<String> answers) {
if (problems == null || answers == null ||
problems.size() != answers.size() ||
problems.isEmpty()) {
System.err.println("评分错误: 题目和答案数量不匹配");
return 0;
}
int correctCount = 0;
for (int i = 0; i < problems.size(); i++) {
Problem problem = problems.get(i);
String userAnswer = answers.get(i);
if (isAnswerCorrect(problem, userAnswer)) {
correctCount++;
}
}
double score = ((double) correctCount / problems.size()) * 100;
int finalScore = (int) Math.round(score);
return finalScore;
}
}

@ -0,0 +1,442 @@
package ui;
import auth.User;
import generator.Problem;
import service.ExamService;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import auth.AuthService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class ExamController {
@FXML private Label titleLabel;
@FXML private ComboBox<String> difficultyCombo;
@FXML private TextField countField;
@FXML private Label problemText;
@FXML private VBox optionsBox;
@FXML private Button nextButton;
@FXML private Label scoreLabel;
@FXML private Label finalScoreLabel;
private Stage primaryStage;
private User currentUser;
private final ExamService examService = new ExamService();
private List<Problem> currentExam;
private List<String> userAnswers;
private int currentProblemIndex = 0;
private Map<Integer, Map<String, String>> optionMappings = new HashMap<>();
// 添加setter方法用于在界面间传递数据
public void setCurrentExam(List<Problem> currentExam) {
this.currentExam = currentExam;
}
public void setUserAnswers(List<String> userAnswers) {
this.userAnswers = userAnswers;
}
@FXML
public void initialize() {
if (currentUser != null) {
System.out.println("当前用户: " + currentUser.getUsername() + ", 类型: " + currentUser.getType());
}
}
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
public void setCurrentUser(User user) {
this.currentUser = user;
initializeDifficultySelection();
}
private void initializeDifficultySelection() {
if (titleLabel != null) {
titleLabel.setText("难度(当前: " + currentUser.getType() + ")");
}
if (difficultyCombo != null) {
// 清空并重新添加选项
difficultyCombo.getItems().clear();
difficultyCombo.getItems().addAll("小学", "初中", "高中");
// 设置当前用户类型为默认值
difficultyCombo.setValue(currentUser.getType());
}
}
@FXML
private void handleSwitchDifficulty() {
if (difficultyCombo == null) {
showAlert("错误", "界面初始化失败,请重新登录");
return;
}
String newDifficulty = difficultyCombo.getValue();
if (newDifficulty == null) {
showAlert("错误", "请先选择难度");
return;
}
if (currentUser == null) {
System.err.println("错误: currentUser 为 null");
showAlert("错误", "用户信息丢失,请重新登录");
return;
}
if (newDifficulty.equals(currentUser.getType())) {
showAlert("提示", "已经是" + newDifficulty + "难度");
return;
}
try {
// 创建 AuthService 实例
AuthService authService = new AuthService();
// 更新数据库中的难度
boolean success = authService.updateDifficulty(currentUser.getUsername(), newDifficulty);
if (success) {
// 验证更新是否成功 - 重新加载用户数据
User updatedUser = authService.login(currentUser.getUsername(), currentUser.getPassword());
if (updatedUser != null) {
currentUser = updatedUser;
showAlert("成功", "难度已成功切换为: " + newDifficulty);
// 更新界面显示
if (titleLabel != null) {
titleLabel.setText("难度选择 (当前: " + currentUser.getType() + ")");
}
// 重新初始化下拉框
initializeDifficultySelection();
} else {
System.err.println("错误: 重新加载用户信息失败");
showAlert("错误", "用户信息验证失败");
}
} else {
System.err.println("错误: 文件更新失败");
showAlert("错误", "难度切换失败,请检查文件权限或联系管理员");
}
} catch (Exception e) {
System.err.println("难度切换异常: " + e.getMessage());
e.printStackTrace();
showAlert("错误", "难度切换异常: " + e.getMessage());
}
}
@FXML
private void handleStartExam() {
try {
int count = Integer.parseInt(countField.getText());
if (count < 10 || count > 30) {
throw new NumberFormatException();
}
generateAndShowExam(count);
} catch (NumberFormatException ex) {
showAlert("错误", "请输入有效的题目数量 (10-30)。");
} catch (Exception ex) {
showAlert("错误", "生成试卷失败: " + ex.getMessage());
}
}
@FXML
private void handleNextProblem() {
saveCurrentAnswer();
currentProblemIndex++;
if (currentProblemIndex < currentExam.size()) {
showProblem(currentProblemIndex);
} else {
showScoreScreen();
}
}
@FXML
private void handleContinue() {
showDifficultySelection();
}
@FXML
private void handleExit() {
primaryStage.close();
}
@FXML
private void handleLogout() {
showLoginScreen();
}
private void generateAndShowExam(int count) throws Exception {
currentExam = examService.generateExam(currentUser, count);
// 检查试卷是否成功生成
if (currentExam == null || currentExam.isEmpty()) {
throw new Exception("无法生成试卷,请稍后重试");
}
// 初始化所有数据结构
userAnswers = new ArrayList<>(currentExam.size());
optionMappings = new HashMap<>();
for (int i = 0; i < currentExam.size(); i++) {
userAnswers.add(null);
}
currentProblemIndex = 0;
showProblem(currentProblemIndex);
}
private void showProblem(int index) {
try {
// 检查索引是否有效
if (currentExam == null || index < 0 || index >= currentExam.size()) {
showAlert("错误", "题目索引无效,无法显示题目");
return;
}
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/exam-problem.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(currentUser);
controller.setCurrentExam(currentExam);
controller.setUserAnswers(userAnswers);
controller.setOptionMappings(optionMappings); // 传递选项映射
controller.setCurrentProblemIndex(index); // 设置当前题目索引
controller.displayProblem(currentExam.get(index), index);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载题目界面失败: " + e.getMessage());
}
}
public void setOptionMappings(Map<Integer, Map<String, String>> optionMappings) {
this.optionMappings = optionMappings;
}
public void setCurrentProblemIndex(int currentProblemIndex) {
this.currentProblemIndex = currentProblemIndex;
}
// 修改 displayProblem 方法,确保每次都正确初始化
public void displayProblem(Problem problem, int index) {
// 确保当前索引正确设置
this.currentProblemIndex = index;
// 确保 optionMappings 不为 null
if (optionMappings == null) {
optionMappings = new HashMap<>();
}
if (problemText != null) {
problemText.setText((index + 1) + ". " + problem.getQuestionText());
}
if (optionsBox != null) {
optionsBox.getChildren().clear();
ToggleGroup group = new ToggleGroup();
List<String> options = problem.getOptions();
char optionChar = 'A';
// 创建或更新当前题目的选项映射
Map<String, String> currentOptionMap = new HashMap<>();
optionMappings.put(index, currentOptionMap);
for (int i = 0; i < options.size(); i++) {
String optionLabel = String.valueOf(optionChar);
String optionValue = options.get(i);
currentOptionMap.put(optionLabel, optionValue);
RadioButton rb = new RadioButton(optionLabel + ". " + optionValue);
rb.setUserData(optionLabel); // 存储选项字母 A, B, C, D
rb.setToggleGroup(group);
rb.setStyle("-fx-font-size: 12px; -fx-font-weight: bold;");
optionsBox.getChildren().add(rb);
// 恢复选择 - 使用选项字母进行比较
if (userAnswers != null && index < userAnswers.size() &&
userAnswers.get(index) != null &&
userAnswers.get(index).equals(optionLabel)) {
rb.setSelected(true);
}
optionChar++;
}
}
if (nextButton != null) {
nextButton.setText(index < currentExam.size() - 1 ? "下一题" : "提交并评分");
}
}
private void saveCurrentAnswer() {
if (optionsBox == null || userAnswers == null || currentProblemIndex >= userAnswers.size()) {
return;
}
// 查找选中的单选按钮
for (javafx.scene.Node node : optionsBox.getChildren()) {
if (node instanceof RadioButton) {
RadioButton rb = (RadioButton) node;
if (rb.isSelected()) {
String selectedOption = (String) rb.getUserData();
userAnswers.set(currentProblemIndex, selectedOption);
return;
}
}
}
// 如果没有选择任何选项设置为null
userAnswers.set(currentProblemIndex, null);
}
private void showScoreScreen() {
try {
// 保存最后一题的答案
saveCurrentAnswer();
// 计算分数 - 使用新的评分方法
int score = calculateScore();
int correctCount = getCorrectCount();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/score-screen.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(currentUser);
controller.displayScore(score, currentExam.size(), correctCount);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载评分界面失败: " + e.getMessage());
}
}
// 添加计算正确数量的方法
private int getCorrectCount() {
int correctCount = 0;
for (int i = 0; i < currentExam.size(); i++) {
Problem problem = currentExam.get(i);
String userAnswerLetter = userAnswers.get(i);
if (userAnswerLetter != null && optionMappings.containsKey(i)) {
String userSelectedValue = optionMappings.get(i).get(userAnswerLetter);
String correctAnswerValue = problem.getCorrectAnswerOption();
if (userSelectedValue != null && userSelectedValue.equals(correctAnswerValue)) {
correctCount++;
}
}
}
return correctCount;
}
// 修改 displayScore 方法显示更详细的信息
public void displayScore(int score, int totalQuestions, int correctCount) {
if (finalScoreLabel != null) {
finalScoreLabel.setText(String.format("最终得分: %d / 100\n正确题数: %d / %d",
score, correctCount, totalQuestions));
}
}
private void showDifficultySelection() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/difficulty-selection.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(currentUser);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载难度选择界面失败: " + e.getMessage());
}
}
private void showLoginScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
// 在 ExamController 中添加评分方法
private int calculateScore() {
if (currentExam == null || userAnswers == null ||
currentExam.size() != userAnswers.size()) {
System.err.println("评分错误: 数据不完整");
return 0;
}
int correctCount = 0;
for (int i = 0; i < currentExam.size(); i++) {
Problem problem = currentExam.get(i);
String userAnswerLetter = userAnswers.get(i);
if (userAnswerLetter != null && optionMappings != null && optionMappings.containsKey(i)) {
// 获取用户选择的选项对应的数值
String userSelectedValue = optionMappings.get(i).get(userAnswerLetter);
// 获取正确答案
String correctAnswerValue = problem.getCorrectAnswerOption();
boolean isCorrect = userSelectedValue != null &&
userSelectedValue.equals(correctAnswerValue);
if (isCorrect) {
correctCount++;
}
}
}
double score = ((double) correctCount / currentExam.size()) * 100;
int finalScore = (int) Math.round(score);
return finalScore;
}
}

@ -0,0 +1,125 @@
package ui;
import auth.AuthService;
import auth.User;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class LoginController {
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
@FXML private Label messageLabel;
private final AuthService authService = new AuthService();
private Stage primaryStage;
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
@FXML
private void handleLogin() {
String username = usernameField.getText();
String password = passwordField.getText();
User user = authService.login(username, password);
if (user != null) {
messageLabel.setText("登录成功!");
showExamSelectScreen(user);
} else {
messageLabel.setText("登录失败,用户名或密码错误。");
}
}
@FXML
private void handleRegister() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-email.fxml"));
Parent root = loader.load();
RegisterController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载注册界面失败: " + e.getMessage());
}
}
@FXML
private void handleModifyPassword() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/modify-password.fxml"));
Parent root = loader.load();
ModifyPasswordController controller = loader.getController();
// 确保primaryStage不为null
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
// 备选方案从当前场景获取stage
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载修改密码界面失败: " + e.getMessage());
}
}
private void showExamSelectScreen(User user) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/difficulty-selection.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(user);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
controller.setCurrentUser(user);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载考试选择界面失败: " + e.getMessage());
}
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
}

@ -0,0 +1,35 @@
package ui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.image.Image;
public class MainApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
// 获取控制器并设置primaryStage
LoginController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
// 设置窗口图标
try {
Image icon = new Image(getClass().getResourceAsStream("/fxml/css/3.png"));
primaryStage.getIcons().add(icon);
} catch (Exception e) {
System.err.println("无法加载图标文件: " + e.getMessage());
}
primaryStage.setTitle("小初高数学学习软件");
primaryStage.setScene(scene);
primaryStage.show();
}
}

@ -0,0 +1,109 @@
package ui;
import auth.AuthService;
import auth.User;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class ModifyPasswordController {
@FXML private TextField usernameField;
@FXML private PasswordField oldPasswordField;
@FXML private PasswordField newPasswordField;
@FXML private PasswordField confirmPasswordField;
@FXML private Label messageLabel;
private final AuthService authService = new AuthService();
private Stage primaryStage;
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
@FXML
private void handleModifyPassword() {
String username = usernameField.getText().trim();
String oldPassword = oldPasswordField.getText();
String newPassword = newPasswordField.getText();
String confirmPassword = confirmPasswordField.getText();
User user = authService.login(username, oldPassword);
if (user == null) {
messageLabel.setText("错误:用户名或旧密码不正确。");
return;
}
if (!newPassword.equals(confirmPassword)) {
messageLabel.setText("错误:两次输入的新密码不一致。");
return;
}
if (!AuthService.isPasswordValid(newPassword)) {
messageLabel.setText("错误:新密码不符合要求 (6-10位, 需含大小写字母和数字)。");
return;
}
if (authService.updatePassword(username, newPassword)) {
showAlert("成功", "密码修改成功,请重新登录。");
showLoginScreen();
} else {
messageLabel.setText("错误:密码修改失败,请稍后重试。");
}
}
@FXML
private void handleBack() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
private void showLoginScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
}

@ -0,0 +1,206 @@
package ui;
import auth.AuthService;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class RegisterController {
@FXML private TextField emailField;
@FXML private TextField codeField;
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
@FXML private PasswordField confirmPasswordField;
@FXML private Label messageLabel;
@FXML private Label emailLabel;
@FXML private Label titleLabel;
private final AuthService authService = new AuthService();
private final Map<String, String> pendingRegistrations = new HashMap<>();
private Stage primaryStage;
private String currentEmail;
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
@FXML
private void handleSendCode() {
String email = emailField.getText().trim();
if (!authService.validateEmail(email)) {
messageLabel.setText("错误:邮箱格式不正确。");
return;
}
if (authService.isUsernameOrEmailTaken(null, email)) {
messageLabel.setText("错误:该邮箱已被注册。");
return;
}
String code = authService.sendVerificationCode(email);
if (code != null) {
pendingRegistrations.put(email, code);
currentEmail = email;
showAlert("成功", "验证码已发送到您的邮箱,请查收!");
showRegisterCodeScreen();
} else {
messageLabel.setText("错误:验证码发送失败,请检查邮箱或服务器设置。");
}
}
@FXML
private void handleVerifyCode() {
String inputCode = codeField.getText().trim();
String storedCode = pendingRegistrations.get(currentEmail);
if (inputCode.equals(storedCode)) {
messageLabel.setText("验证成功!");
showRegisterPasswordScreen();
} else {
messageLabel.setText("错误:验证码不正确。");
}
}
@FXML
private void handleResendCode() {
String newCode = authService.sendVerificationCode(currentEmail);
if (newCode != null) {
pendingRegistrations.put(currentEmail, newCode);
messageLabel.setText("新的验证码已发送。");
} else {
messageLabel.setText("错误:重发失败。");
}
}
@FXML
private void handleCompleteRegistration() {
String username = usernameField.getText().trim();
String password = passwordField.getText();
String confirmPassword = confirmPasswordField.getText();
if (!validatePasswordSetup(username, password, confirmPassword)) {
return;
}
if (authService.registerUser(username, password, currentEmail)) {
pendingRegistrations.remove(currentEmail);
showAlert("成功", "注册成功,请登录。");
showLoginScreen();
} else {
messageLabel.setText("致命错误:注册失败,请联系管理员。");
}
}
@FXML
private void handleBack() {
showLoginScreen();
}
private boolean validatePasswordSetup(String username, String password, String confirmPassword) {
if (authService.isUsernameOrEmailTaken(username, null)) {
messageLabel.setText("错误:该用户名已被占用。");
return false;
}
if (!password.equals(confirmPassword)) {
messageLabel.setText("错误:两次密码输入不一致。");
return false;
}
if (!AuthService.isPasswordValid(password)) {
messageLabel.setText("错误:密码不符合要求 (6-10位, 需含大小写字母和数字)。");
return false;
}
return true;
}
private void showRegisterCodeScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-code.fxml"));
Parent root = loader.load();
RegisterController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentEmail(currentEmail);
controller.setPendingRegistrations(pendingRegistrations);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载验证码界面失败: " + e.getMessage());
}
}
private void showRegisterPasswordScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-password.fxml"));
Parent root = loader.load();
RegisterController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentEmail(currentEmail);
controller.setPendingRegistrations(pendingRegistrations);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载密码设置界面失败: " + e.getMessage());
}
}
private void showLoginScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) emailField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
// Setters for sharing data between different registration screens
public void setCurrentEmail(String email) {
this.currentEmail = email;
if (emailLabel != null) {
emailLabel.setText("已发送到: " + email);
}
if (titleLabel != null) {
titleLabel.setText("注册 (3/3) - 设置密码 (邮箱: " + email + ")");
}
}
public void setPendingRegistrations(Map<String, String> pendingRegistrations) {
this.pendingRegistrations.clear();
this.pendingRegistrations.putAll(pendingRegistrations);
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
}

@ -0,0 +1,69 @@
package util;
import java.util.Properties;
import java.util.Random;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public class EmailService {
// 邮件配置:请修改此处或设置环境变量
private static final String SENDER_EMAIL = "2810672597@qq.com";
private static final String SENDER_PASSWORD =("acyhhpwgcqkqdgff");
private static final String SMTP_HOST = "smtp.qq.com"; // 例如: smtp.gmail.com
private static final String SMTP_PORT = "587"; // 或 465 (SSL)
private static final String SUBJECT = "数学学习软件注册验证码";
public String generateVerificationCode() {
Random rand = new Random();
return String.format("%06d", rand.nextInt(1000000));
}
private Properties getMailProperties() {
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.port", SMTP_PORT);
return props;
}
private Message createMessage(Session session, String recipientEmail,
String code) throws MessagingException {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(SENDER_EMAIL));
message.setRecipients(
Message.RecipientType.TO,
InternetAddress.parse(recipientEmail)
);
message.setSubject(SUBJECT);
message.setText("您的注册验证码是: " + code +
"\n\n请在应用中输入此代码完成注册。");
return message;
}
// R2: 实际发送验证邮件
public boolean sendVerificationEmail(String recipientEmail, String code) {
if (SENDER_EMAIL == null || SENDER_PASSWORD == null) {
System.err.println("错误: 邮件发送凭证未设置。");
return false;
}
Session session = Session.getInstance(getMailProperties(), new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(SENDER_EMAIL, SENDER_PASSWORD);
}
});
try {
Message message = createMessage(session, recipientEmail, code);
Transport.send(message);
return true;
} catch (MessagingException e) {
System.err.println("邮件发送失败: " + e.getMessage());
return false;
}
}
}

@ -0,0 +1,358 @@
package util;
import generator.Problem;
import java.util.*;
public class ExpressionUtils {
private static Random rand = new Random();
private static final String[] OPS_PRIMARY = {"+", "-", "*", "/"};
private static final String[] OPS_MIDDLE = {"+", "-", "*", "/"};
//private static final String[] OPS_HIGH = {"+", "-", "*", "/", "^2", "sqrt"};
public static String randomNumber() {
return String.valueOf(rand.nextInt(100) + 1);
}
// --- 表达式求解和选项生成辅助方法 ---
public static double solveExpression(String expr) {
// 预处理表达式,确保格式正确
String parsableExpr = expr
.replaceAll("(\\d+|\\([^)]+\\))\\^2", "pow($1, 2)") // 处理平方
.replaceAll("sqrt\\s+(\\d+)", "sqrt($1)") // 处理缺少括号的sqrt
.replaceAll("\\s+", " "); // 标准化空格
try {
return new Object() {
int pos = -1, ch;
void nextChar() {
ch = (++pos < parsableExpr.length()) ? parsableExpr.charAt(pos) : -1;
}
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < parsableExpr.length()) throw new RuntimeException("Unexpected: " + (char) ch);
return x;
}
double parseExpression() {
double x = parseTerm();
for (; ; ) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseTerm() {
double x = parseFactor();
for (; ; ) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) { // division
double divisor = parseFactor();
if (divisor == 0) throw new ArithmeticException("Division by zero");
x /= divisor;
} else return x;
}
}
double parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(parsableExpr.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = parsableExpr.substring(startPos, this.pos);
x = parseFactor();
switch (func) {
case "sqrt":
x = Math.sqrt(x);
break;
case "sin":
x = Math.sin(Math.toRadians(x));
break;
case "cos":
x = Math.cos(Math.toRadians(x));
break;
case "tan":
x = Math.tan(Math.toRadians(x));
break;
case "pow":
eat(',');
double y = parseExpression();
x = Math.pow(x, y);
eat(')');
break;
default:
throw new RuntimeException("Unknown function: " + func);
}
} else {
throw new RuntimeException("Unexpected: " + (char) ch);
}
return x;
}
}.parse();
} catch (Exception e) {
System.err.println("Error parsing expression: '" + expr + "'. Parsable version: '" + parsableExpr + "'. Error: " + e.getMessage());
return Double.NaN;
}
}
private static String formatResult(double result) {
if (Double.isNaN(result)) {
return "Error";
}
if (Math.abs(result - Math.round(result)) < 0.0001) {
return String.valueOf((int) Math.round(result));
}
return String.format("%.2f", result);
}
/**
* MODIFICATION:
*/
private static String generateDistractor(double correctResult, List<String> existingOptions) {
if (Double.isNaN(correctResult)) {
return String.valueOf(rand.nextInt(100));
}
// 检查正确答案是否为整数
boolean isIntegerResult = Math.abs(correctResult - Math.round(correctResult)) < 0.0001;
double distractor;
String distractorStr;
do {
if (isIntegerResult) {
// 生成一个整数干扰项
int offset = rand.nextInt(20) + 1; // 扩大随机范围以避免重复
distractor = rand.nextBoolean() ? Math.round(correctResult) + offset : Math.round(correctResult) - offset;
// 确保干扰项不为负数
if (distractor < 0) {
distractor = Math.round(correctResult) + offset;
}
} else {
// 答案是小数,则生成小数干扰项
distractor = correctResult +
(rand.nextInt(10) + 1) * (rand.nextBoolean() ? 1 : -1) +
(rand.nextDouble() * 0.9);
}
distractorStr = formatResult(distractor);
} while (existingOptions.contains(distractorStr)); // 确保选项不重复
return distractorStr;
}
public static List<String> generateOptions(double correctResult) {
List<String> options = new ArrayList<>();
options.add(formatResult(correctResult));
for (int i = 0; i < 3; i++) {
options.add(generateDistractor(correctResult, options));
}
Collections.shuffle(options);
return options;
}
public static Problem createProblem(String expression) {
double result = solveExpression(expression);
// 如果解析失败返回null让调用者重新生成
if (Double.isNaN(result)) {
return null;
}
String correctAnswerOption = formatResult(result);
List<String> options = generateOptions(result);
return new Problem(expression, result, options, correctAnswerOption);
}
// --- 题目生成方法 ---
private static int getDivisor(int dividend) {
List<Integer> divisors = new ArrayList<>();
for (int j = 1; j <= dividend; j++) {
if (dividend % j == 0) {
divisors.add(j);
}
}
return divisors.get(rand.nextInt(divisors.size()));
}
/**
* MODIFICATION:
*/
public static Problem generatePrimaryExpr() {
Problem problem;
double result;
do {
// 生成 2 到 4 个操作数
int operands = rand.nextInt(3) + 2;
List<String> parts = new ArrayList<>();
for (int i = 0; i < operands; i++) {
if (i > 0) {
parts.add(OPS_PRIMARY[rand.nextInt(OPS_PRIMARY.length)]);
}
// 仅当操作数 >= 3 时,才有可能生成括号
if (operands >= 3 && rand.nextBoolean() && i < operands - 1) {
int num1 = rand.nextInt(50) + 1;
String innerOp = OPS_PRIMARY[rand.nextInt(OPS_PRIMARY.length)];
int num2;
if (innerOp.equals("/")) {
num2 = getDivisor(num1);
} else {
num2 = rand.nextInt(50) + 1;
}
parts.add("(" + num1 + " " + innerOp + " " + num2 + ")");
i++; // 括号表达式计为2个操作数
} else {
int num;
if (i > 0 && parts.get(parts.size() - 1).equals("/")) {
String dividendStr = parts.get(parts.size() - 2);
if (dividendStr.startsWith("(")) {
// 为避免 (a+b)/c 这种复杂情况无法保证整数结果,直接替换运算符
parts.set(parts.size() - 1, OPS_PRIMARY[rand.nextInt(3)]); // +, -, *
num = rand.nextInt(100) + 1;
} else {
int dividend = Integer.parseInt(dividendStr);
num = getDivisor(dividend);
}
} else {
num = rand.nextInt(100) + 1;
}
parts.add(String.valueOf(num));
}
}
String expression = String.join(" ", parts);
problem = createProblem(expression);
result = problem.getResult();
// 循环直到答案为非负整数
} while (result < 0 || Double.isNaN(result) || Math.abs(result - Math.round(result)) > 0.0001);
return problem;
}
public static Problem generateMiddleExpr() {
int operands = rand.nextInt(5) + 1;
StringBuilder expr = new StringBuilder();
boolean hasSquareOrSqrt = false;
for (int i = 0; i < operands; i++) {
if (i > 0) {
expr.append(" ").append(OPS_MIDDLE[rand.nextInt(OPS_MIDDLE.length)]).append(" ");
}
int num = rand.nextInt(100) + 1;
if (!hasSquareOrSqrt && rand.nextBoolean()) {
expr.append(rand.nextBoolean() ? "sqrt(" + num + ")" : num + "^2");
hasSquareOrSqrt = true;
} else {
expr.append(num);
}
}
if (!hasSquareOrSqrt) {
expr.append(" + ").append(rand.nextInt(50) + 1).append("^2");
}
return createProblem(expr.toString());
}
public static Problem generateHighExpr() {
int attempts = 0;
final int maxAttempts = 10;
while (attempts < maxAttempts) {
int operands = rand.nextInt(3) + 2; // 2-4个操作数
StringBuilder expr = new StringBuilder();
boolean hasTrig = false;
for (int i = 0; i < operands; i++) {
if (i > 0) {
String[] validOps = {"+", "-", "*", "/"};
String op = validOps[rand.nextInt(validOps.length)];
expr.append(" ").append(op).append(" ");
}
// 强制至少有一个操作数是三角函数
if (!hasTrig && (i == operands - 1 || rand.nextBoolean())) {
String[] funcs = {"sin", "cos", "tan"};
String func = funcs[rand.nextInt(funcs.length)];
int angle = rand.nextInt(90) + 1; // 1-90度
expr.append(func).append("(").append(angle).append(")");
hasTrig = true;
} else {
// 其他操作数可以是普通数字、平方或开方
int num = rand.nextInt(100) + 1;
if (rand.nextBoolean() && hasTrig) { // 确保已经有三角函数后再添加其他函数
if (rand.nextBoolean()) {
expr.append(num).append("^2");
} else {
expr.append("sqrt(").append(num).append(")");
}
} else {
expr.append(num);
}
}
}
// 如果没有三角函数,强制添加一个
if (!hasTrig) {
String[] funcs = {"sin", "cos", "tan"};
String func = funcs[rand.nextInt(funcs.length)];
int angle = rand.nextInt(90) + 1;
if (rand.nextBoolean()) {
// 在开头添加
expr.insert(0, func + "(" + angle + ") + ");
} else {
// 在结尾添加
expr.append(" + ").append(func).append("(").append(angle).append(")");
}
hasTrig = true;
}
String expression = expr.toString();
// 验证表达式
Problem problem = createProblem(expression);
if (problem != null) {
return problem;
}
attempts++;
System.err.println("生成高中题目失败,尝试次数: " + attempts + ", 表达式: " + expression);
}
return createProblem("sin(30) + cos(60)");
}
}

@ -0,0 +1,79 @@
package util;
import generator.Problem;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class FileUtils {
private static final String FOLDER_PATH = "exams/";
public static String getUserFolder(String username) {
String folder = FOLDER_PATH + username;
File dir = new File(folder);
if (!dir.exists()) {
dir.mkdirs();
}
return folder;
}
private static void processHistoryFile(File file, Set<String> history) {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
if (!line.trim().isEmpty() && line.matches("^\\d+\\..*")) {
// 仅提取题干 (表达式字符串) 用于查重
history.add(line.substring(line.indexOf('.') + 1).trim());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static Set<String> loadHistory(String username) {
Set<String> history = new HashSet<>();
File dir = new File(getUserFolder(username));
File[] files = dir.listFiles((d, name) -> name.endsWith(".txt"));
if (files != null) {
for (File file : files) {
processHistoryFile(file, history);
}
}
return history;
}
private static void writeProblem(BufferedWriter bw, int index, Problem problem) throws IOException {
bw.write(index + "." + problem.getExpression());
bw.newLine();
char optionChar = 'A';
StringBuilder optionsLine = new StringBuilder();
for (String option : problem.getOptions()) {
optionsLine.append(optionChar++).append(". ").append(option).append(" ");
}
bw.write(optionsLine.toString().trim());
bw.newLine();
bw.write("答案: " + problem.getCorrectAnswerOption());
bw.newLine();
bw.newLine();
}
public static void saveProblems(String username, List<Problem> problems) {
String folder = getUserFolder(username);
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
String filename = folder + "/" + timestamp + ".txt";
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename))) {
int index = 1;
for (Problem problem : problems) {
writeProblem(bw, index++, problem);
}
System.out.println("卷子已保存到: " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,76 @@
package util;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class to modify specific fields in user.txt.
* ModifyClass.java ModifyPassword.java
*/
public class ModifyUtils {
private static List<String> readAllLines(String filePath, List<String> lines) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
private static void writeAllLines(String filePath, List<String> lines) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
for (String l : lines) {
writer.write(l + System.lineSeparator());
}
}
}
private static int findTargetLine(List<String> lines, String targetName) {
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.isEmpty()) continue;
String[] parts = line.split("\\s+");
if (parts.length > 0 && parts[0].equals(targetName)) {
return i; // 0-based index
}
}
return -1;
}
// 核心逻辑:修改文件中特定用户的某个字段 (0-based)
public static int modifyFileField(String filePath, String targetName,
int fieldIndex, String newValue) {
List<String> lines = new ArrayList<>();
try {
readAllLines(filePath, lines);
} catch (IOException e) {
System.err.println("读取文件错误: " + e.getMessage());
return -1;
}
int idx = findTargetLine(lines, targetName);
if (idx == -1) {
return -1;
}
String[] parts = lines.get(idx).trim().split("\\s+");
if (parts.length > fieldIndex) {
parts[fieldIndex] = newValue;
lines.set(idx, String.join(" ", parts));
try {
writeAllLines(filePath, lines);
return idx + 1; // 返回 1-based 行号
} catch (IOException e) {
System.err.println("写入文件错误: " + e.getMessage());
return -1;
}
} else {
System.out.println("目标行元素不足。");
return -1;
}
}
}
Loading…
Cancel
Save