diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..c58f279 --- /dev/null +++ b/src/Main.java @@ -0,0 +1,8 @@ +import ui.MainApplication; + +public class Main { + public static void main(String[] args) { + // R1: 启动图形化界面应用 (JavaFX) + MainApplication.launch(MainApplication.class, args); + } +} \ No newline at end of file diff --git a/src/auth/AuthService.java b/src/auth/AuthService.java new file mode 100644 index 0000000..8a06ce2 --- /dev/null +++ b/src/auth/AuthService.java @@ -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 loadUsers() { + List 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 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 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 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; + } + } + +} \ No newline at end of file diff --git a/src/auth/User.java b/src/auth/User.java new file mode 100644 index 0000000..835b8ac --- /dev/null +++ b/src/auth/User.java @@ -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; } +} diff --git a/src/auth/UserManager.java b/src/auth/UserManager.java new file mode 100644 index 0000000..c12a1c8 --- /dev/null +++ b/src/auth/UserManager.java @@ -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); + } +} diff --git a/src/fxml/css/1.jpg b/src/fxml/css/1.jpg new file mode 100644 index 0000000..2672684 Binary files /dev/null and b/src/fxml/css/1.jpg differ diff --git a/src/fxml/css/2.gif b/src/fxml/css/2.gif new file mode 100644 index 0000000..6383347 Binary files /dev/null and b/src/fxml/css/2.gif differ diff --git a/src/fxml/css/3.png b/src/fxml/css/3.png new file mode 100644 index 0000000..22c53bf Binary files /dev/null and b/src/fxml/css/3.png differ diff --git a/src/fxml/css/main.css b/src/fxml/css/main.css new file mode 100644 index 0000000..31935f0 --- /dev/null +++ b/src/fxml/css/main.css @@ -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; +} + diff --git a/src/fxml/difficulty-selection.fxml b/src/fxml/difficulty-selection.fxml new file mode 100644 index 0000000..2ad8916 --- /dev/null +++ b/src/fxml/difficulty-selection.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + +