class_name AuthManager # ============================================================================ # AuthManager.gd - 认证管理器 # ============================================================================ # 认证系统的业务逻辑管理器,负责处理所有认证相关的业务逻辑 # # 核心职责: # - 用户登录业务逻辑(密码登录 + 验证码登录) # - 用户注册业务逻辑 # - 表单验证逻辑 # - 验证码管理逻辑 # - 网络请求管理 # - 响应处理和状态管理 # # 使用方式: # var auth_manager = AuthManager.new() # auth_manager.login_success.connect(_on_login_success) # auth_manager.execute_password_login(username, password) # # 注意事项: # - 这是业务逻辑层,不包含任何UI相关代码 # - 通过信号与UI层通信 # - 所有验证逻辑都在这里实现 # ============================================================================ extends RefCounted # ============ 信号定义 ============ # 登录成功信号 signal login_success(username: String) # 登录失败信号 signal login_failed(message: String) # 注册成功信号 signal register_success(message: String) # 注册失败信号 signal register_failed(message: String) # 验证码发送成功信号 signal verification_code_sent(message: String) # 验证码发送失败信号 signal verification_code_failed(message: String) # 表单验证失败信号 signal form_validation_failed(field: String, message: String) # 网络状态变化信号 signal network_status_changed(is_connected: bool, message: String) # 按钮状态变化信号 signal button_state_changed(button_name: String, is_loading: bool, text: String) # Toast消息信号 signal show_toast_message(message: String, is_success: bool) # ============ 枚举定义 ============ # 登录模式枚举 enum LoginMode { PASSWORD, # 密码登录模式 VERIFICATION # 验证码登录模式 } # ============ 成员变量 ============ # 登录状态 var current_login_mode: LoginMode = LoginMode.PASSWORD var is_processing: bool = false # 验证码管理 var verification_codes_sent: Dictionary = {} var code_cooldown: float = 60.0 var current_email: String = "" # 网络请求管理 var active_request_ids: Array = [] # ============ Token 管理 ============ # 本地存储路径常量 const AUTH_CONFIG_PATH: String = "user://auth.cfg" # Token 存储(内存中,用于快速访问) var _access_token: String = "" # JWT访问令牌(短期,用于API和WebSocket) var _refresh_token: String = "" # JWT刷新令牌(长期,用于获取新access_token) var _user_info: Dictionary = {} # 用户信息 var _token_expiry: float = 0.0 # access_token过期时间(Unix时间戳) # 游戏 token(兼容旧代码,保留但标记为废弃) var _game_token: String = "" # @deprecated 使用 _access_token 替代 # ============ 生命周期方法 ============ # 初始化管理器 func _init() -> void: print("AuthManager 初始化完成") _load_auth_data() # 清理资源 func cleanup() -> void: # 取消所有活动的网络请求 for request_id in active_request_ids: NetworkManager.cancel_request(request_id) active_request_ids.clear() # ============ Token 管理 ============ # 保存 Token 到内存 # # 参数: # data: Dictionary - 登录响应数据 # # 功能: # - 从登录响应中提取 access_token 和 refresh_token # - 保存到内存变量中 # - 保存用户信息 func _save_tokens_to_memory(data: Dictionary) -> void: if not data.has("data"): print("⚠️ 登录响应中没有 data 字段") return var token_data: Dictionary = data.data _access_token = token_data.get("access_token", "") _refresh_token = token_data.get("refresh_token", "") _user_info = token_data.get("user", {}) _token_expiry = Time.get_unix_time_from_system() + float(token_data.get("expires_in", 0)) # 保持兼容性:设置 _game_token _game_token = _access_token print("✅ Token已保存到内存") print(" Access Token: ", _access_token.substr(0, 20) + "...") print(" 用户: ", _user_info.get("username", "未知")) # 保存 Token 到本地(ConfigFile) # # 参数: # data: Dictionary - 登录响应数据 # # 功能: # - 将 refresh_token 和用户信息保存到 ConfigFile # - access_token 不保存到本地,仅保存在内存中 func _save_tokens_to_local(data: Dictionary) -> void: if not data.has("data"): return var token_data: Dictionary = data.data var auth_data: Dictionary = { "refresh_token": token_data.get("refresh_token", ""), "user_id": token_data.get("user", {}).get("id", ""), "username": token_data.get("user", {}).get("username", ""), "saved_at": Time.get_unix_time_from_system() } var config: ConfigFile = ConfigFile.new() config.load(AUTH_CONFIG_PATH) config.set_value("auth", "refresh_token", auth_data["refresh_token"]) config.set_value("auth", "user_id", auth_data["user_id"]) config.set_value("auth", "username", auth_data["username"]) config.set_value("auth", "saved_at", auth_data["saved_at"]) var error: Error = config.save(AUTH_CONFIG_PATH) if error == OK: print("✅ Token已保存到本地: ", AUTH_CONFIG_PATH) else: print("❌ 保存Token到本地失败,错误码: ", error) # 从本地加载 Token(游戏启动时调用) # # 功能: # - 从 ConfigFile 加载 refresh_token 和用户信息 # - access_token 需要通过 refresh_token 刷新获取 func _load_auth_data() -> void: if not FileAccess.file_exists(AUTH_CONFIG_PATH): print("ℹ️ 本地不存在认证数据") return var config: ConfigFile = ConfigFile.new() var error: Error = config.load(AUTH_CONFIG_PATH) if error != OK: print("❌ 加载本地认证数据失败,错误码: ", error) return _refresh_token = config.get_value("auth", "refresh_token", "") var user_id: String = config.get_value("auth", "user_id", "") var username: String = config.get_value("auth", "username", "") if not _refresh_token.is_empty(): _user_info = { "id": user_id, "username": username } print("✅ 已从本地加载认证数据") print(" 用户: ", username) else: print("⚠️ 本地认证数据无效(没有 refresh_token)") # 清除本地认证数据(登出时调用) # # 功能: # - 清除内存中的 Token # - 删除本地 ConfigFile func _clear_auth_data() -> void: _access_token = "" _refresh_token = "" _user_info = {} _token_expiry = 0.0 _game_token = "" if FileAccess.file_exists(AUTH_CONFIG_PATH): DirAccess.remove_absolute(AUTH_CONFIG_PATH) print("✅ 已清除本地认证数据") # ============ Token 访问方法 ============ # 设置游戏 token(兼容旧代码,推荐使用 _save_tokens_to_memory) # # 参数: # token: String - 游戏认证 token # # 使用场景: # - 登录成功后设置 token # - 从服务器响应中获取 token func set_game_token(token: String) -> void: _game_token = token _access_token = token # 同步更新 access_token print("AuthManager: 游戏 token 已设置") # 获取游戏 token # # 返回值: # String - access_token(如果未设置则返回空字符串) # # 使用场景: # - ChatManager 连接 WebSocket 时需要 token # - 其他需要游戏认证的场景 func get_game_token() -> String: return _access_token # 获取 access token # # 返回值: # String - JWT访问令牌 # # 使用场景: # - API请求认证 # - WebSocket聊天认证 func get_access_token() -> String: return _access_token # 获取 refresh token # # 返回值: # String - JWT刷新令牌 # # 使用场景: # - 刷新过期的 access token func get_refresh_token() -> String: return _refresh_token # 获取用户信息 # # 返回值: # Dictionary - 用户信息字典 func get_user_info() -> Dictionary: return _user_info # ============ 登录相关方法 ============ # 执行密码登录 # # 参数: # username: String - 用户名/邮箱 # password: String - 密码 # # 功能: # - 验证输入参数 # - 发送登录请求 # - 处理响应结果 func execute_password_login(username: String, password: String): if is_processing: show_toast_message.emit("请等待当前操作完成", false) return # 验证输入 var validation_result = validate_login_inputs(username, password) if not validation_result.valid: form_validation_failed.emit(validation_result.field, validation_result.message) return # 设置处理状态 is_processing = true button_state_changed.emit("main_btn", true, "登录中...") show_toast_message.emit("正在验证登录信息...", true) # 发送网络请求 var request_id = NetworkManager.login(username, password, _on_login_response) if request_id != "": active_request_ids.append(request_id) else: _reset_login_state() show_toast_message.emit("网络请求失败", false) # 执行验证码登录 # # 参数: # identifier: String - 用户标识符 # verification_code: String - 验证码 func execute_verification_login(identifier: String, verification_code: String): if is_processing: show_toast_message.emit("请等待当前操作完成", false) return # 验证输入 if identifier.is_empty(): form_validation_failed.emit("username", "请输入用户名/手机/邮箱") return if verification_code.is_empty(): form_validation_failed.emit("verification", "请输入验证码") return # 设置处理状态 is_processing = true button_state_changed.emit("main_btn", true, "登录中...") show_toast_message.emit("正在验证验证码...", true) # 发送网络请求 var request_id = NetworkManager.verification_code_login(identifier, verification_code, _on_verification_login_response) if request_id != "": active_request_ids.append(request_id) else: _reset_login_state() show_toast_message.emit("网络请求失败", false) # 切换登录模式 func toggle_login_mode(): if current_login_mode == LoginMode.PASSWORD: current_login_mode = LoginMode.VERIFICATION else: current_login_mode = LoginMode.PASSWORD # 获取当前登录模式 func get_current_login_mode() -> LoginMode: return current_login_mode # ============ 注册相关方法 ============ # 执行用户注册 # # 参数: # username: String - 用户名 # email: String - 邮箱 # password: String - 密码 # confirm_password: String - 确认密码 # verification_code: String - 邮箱验证码 func execute_register(username: String, email: String, password: String, confirm_password: String, verification_code: String): if is_processing: show_toast_message.emit("请等待当前操作完成", false) return # 验证注册表单 var validation_result = validate_register_form(username, email, password, confirm_password, verification_code) if not validation_result.valid: form_validation_failed.emit(validation_result.field, validation_result.message) return # 设置处理状态 is_processing = true button_state_changed.emit("register_btn", true, "注册中...") show_toast_message.emit("正在创建账户...", true) # 发送注册请求 var request_id = NetworkManager.register(username, password, username, email, verification_code, _on_register_response) if request_id != "": active_request_ids.append(request_id) else: _reset_register_state() show_toast_message.emit("网络请求失败", false) # ============ 验证码相关方法 ============ # 发送邮箱验证码 # # 参数: # email: String - 邮箱地址 func send_email_verification_code(email: String): # 验证邮箱格式 var email_validation = validate_email(email) if not email_validation.valid: form_validation_failed.emit("email", email_validation.message) return # 检查冷却时间 if not _can_send_verification_code(email): var remaining = get_remaining_cooldown_time(email) show_toast_message.emit("该邮箱请等待 %d 秒后再次发送" % remaining, false) return # 记录发送状态 _record_verification_code_sent(email) # 发送请求 var request_id = NetworkManager.send_email_verification(email, _on_send_code_response) if request_id != "": active_request_ids.append(request_id) else: _reset_verification_code_state(email) show_toast_message.emit("网络请求失败", false) # 发送登录验证码 # # 参数: # identifier: String - 用户标识符 func send_login_verification_code(identifier: String): if identifier.is_empty(): form_validation_failed.emit("username", "请先输入用户名/手机/邮箱") return button_state_changed.emit("get_code_btn", true, "发送中...") show_toast_message.emit("正在发送登录验证码...", true) var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response) if request_id != "": active_request_ids.append(request_id) else: button_state_changed.emit("get_code_btn", false, "获取验证码") show_toast_message.emit("网络请求失败", false) # 发送密码重置验证码 # # 参数: # identifier: String - 用户标识符 func send_password_reset_code(identifier: String): if identifier.is_empty(): show_toast_message.emit("请先输入邮箱或手机号", false) return if not _is_valid_identifier(identifier): show_toast_message.emit("请输入有效的邮箱或手机号", false) return button_state_changed.emit("forgot_password_btn", true, "发送中...") show_toast_message.emit("正在发送密码重置验证码...", true) var request_id = NetworkManager.forgot_password(identifier, _on_forgot_password_response) if request_id != "": active_request_ids.append(request_id) else: button_state_changed.emit("forgot_password_btn", false, "忘记密码") show_toast_message.emit("网络请求失败", false) # ============ 验证方法 ============ # 验证登录输入 func validate_login_inputs(username: String, password: String) -> Dictionary: var result = {"valid": false, "field": "", "message": ""} if username.is_empty(): result.field = "username" result.message = "用户名不能为空" return result if password.is_empty(): result.field = "password" result.message = "密码不能为空" return result result.valid = true return result # 验证注册表单 func validate_register_form(username: String, email: String, password: String, confirm_password: String, verification_code: String) -> Dictionary: var result = {"valid": false, "field": "", "message": ""} # 验证用户名 var username_validation = validate_username(username) if not username_validation.valid: result.field = "username" result.message = username_validation.message return result # 验证邮箱 var email_validation = validate_email(email) if not email_validation.valid: result.field = "email" result.message = email_validation.message return result # 验证密码 var password_validation = validate_password(password) if not password_validation.valid: result.field = "password" result.message = password_validation.message return result # 验证确认密码 var confirm_validation = validate_confirm_password(password, confirm_password) if not confirm_validation.valid: result.field = "confirm" result.message = confirm_validation.message return result # 验证验证码 var code_validation = validate_verification_code(verification_code) if not code_validation.valid: result.field = "verification" result.message = code_validation.message return result # 检查是否已发送验证码 if not _has_sent_verification_code(email): result.field = "verification" result.message = "请先获取邮箱验证码" return result result.valid = true return result # 验证用户名 func validate_username(username: String) -> Dictionary: var result = {"valid": false, "message": ""} if username.is_empty(): result.message = "用户名不能为空" return result if not StringUtils.is_valid_username(username): if username.length() > 50: result.message = "用户名长度不能超过50字符" else: result.message = "用户名只能包含字母、数字和下划线" return result result.valid = true return result # 验证邮箱 func validate_email(email: String) -> Dictionary: var result = {"valid": false, "message": ""} if email.is_empty(): result.message = "邮箱不能为空" return result if not StringUtils.is_valid_email(email): result.message = "请输入有效的邮箱地址" return result result.valid = true return result # 验证密码 func validate_password(password: String) -> Dictionary: return StringUtils.validate_password_strength(password) # 验证确认密码 func validate_confirm_password(password: String, confirm: String) -> Dictionary: var result = {"valid": false, "message": ""} if confirm.is_empty(): result.message = "确认密码不能为空" return result if password != confirm: result.message = "两次输入的密码不一致" return result result.valid = true return result # 验证验证码 func validate_verification_code(code: String) -> Dictionary: var result = {"valid": false, "message": ""} if code.is_empty(): result.message = "验证码不能为空" return result if code.length() != 6: result.message = "验证码必须是6位数字" return result for i in range(code.length()): var character = code[i] if not (character >= '0' and character <= '9'): result.message = "验证码必须是6位数字" return result result.valid = true return result # ============ 网络响应处理 ============ # 处理登录响应 func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary) -> void: _reset_login_state() var result = ResponseHandler.handle_login_response(success, data, error_info) if result.should_show_toast: show_toast_message.emit(result.message, result.success) if result.success: # 保存 Token 到内存和本地 _save_tokens_to_memory(data) _save_tokens_to_local(data) var username: String = _user_info.get("username", "") # 延迟发送登录成功信号 await Engine.get_main_loop().create_timer(1.0).timeout login_success.emit(username) else: login_failed.emit(result.message) # 处理验证码登录响应 func _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary) -> void: _reset_login_state() var result = ResponseHandler.handle_verification_code_login_response(success, data, error_info) if result.should_show_toast: show_toast_message.emit(result.message, result.success) if result.success: # 保存 Token 到内存和本地 _save_tokens_to_memory(data) _save_tokens_to_local(data) var username: String = _user_info.get("username", "") await Engine.get_main_loop().create_timer(1.0).timeout login_success.emit(username) else: login_failed.emit(result.message) # 处理注册响应 func _on_register_response(success: bool, data: Dictionary, error_info: Dictionary): _reset_register_state() var result = ResponseHandler.handle_register_response(success, data, error_info) if result.should_show_toast: show_toast_message.emit(result.message, result.success) if result.success: register_success.emit(result.message) else: register_failed.emit(result.message) # 处理发送验证码响应 func _on_send_code_response(success: bool, data: Dictionary, error_info: Dictionary): var result = ResponseHandler.handle_send_verification_code_response(success, data, error_info) if result.should_show_toast: show_toast_message.emit(result.message, result.success) if result.success: verification_code_sent.emit(result.message) else: verification_code_failed.emit(result.message) _reset_verification_code_state(current_email) # 处理发送登录验证码响应 func _on_send_login_code_response(success: bool, data: Dictionary, error_info: Dictionary): button_state_changed.emit("get_code_btn", false, "获取验证码") var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) if result.should_show_toast: show_toast_message.emit(result.message, result.success) # 处理忘记密码响应 func _on_forgot_password_response(success: bool, data: Dictionary, error_info: Dictionary): button_state_changed.emit("forgot_password_btn", false, "忘记密码") var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) if result.should_show_toast: show_toast_message.emit(result.message, result.success) # ============ 网络测试 ============ # 测试网络连接 func test_network_connection(): var request_id = NetworkManager.get_app_status(_on_network_test_response) if request_id != "": active_request_ids.append(request_id) # 处理网络测试响应 func _on_network_test_response(success: bool, data: Dictionary, error_info: Dictionary): var result = ResponseHandler.handle_network_test_response(success, data, error_info) network_status_changed.emit(result.success, result.message) # ============ 私有辅助方法 ============ # 重置登录状态 func _reset_login_state(): is_processing = false button_state_changed.emit("main_btn", false, "进入小镇") # 重置注册状态 func _reset_register_state(): is_processing = false button_state_changed.emit("register_btn", false, "注册") # 检查是否可以发送验证码 func _can_send_verification_code(email: String) -> bool: if not verification_codes_sent.has(email): return true var email_data = verification_codes_sent[email] if not email_data.sent: return true var current_time = Time.get_time_dict_from_system() var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second return (current_timestamp - email_data.time) >= code_cooldown # 获取剩余冷却时间 func get_remaining_cooldown_time(email: String) -> int: if not verification_codes_sent.has(email): return 0 var email_data = verification_codes_sent[email] var current_time = Time.get_time_dict_from_system() var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second return int(code_cooldown - (current_timestamp - email_data.time)) # 记录验证码发送状态 func _record_verification_code_sent(email: String): var current_time = Time.get_time_dict_from_system() var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second if not verification_codes_sent.has(email): verification_codes_sent[email] = {} verification_codes_sent[email].sent = true verification_codes_sent[email].time = current_timestamp current_email = email # 重置验证码状态 func _reset_verification_code_state(email: String): if verification_codes_sent.has(email): verification_codes_sent[email].sent = false # 检查是否已发送验证码 func _has_sent_verification_code(email: String) -> bool: if not verification_codes_sent.has(email): return false return verification_codes_sent[email].get("sent", false) # 验证标识符格式 func _is_valid_identifier(identifier: String) -> bool: return StringUtils.is_valid_email(identifier) or _is_valid_phone(identifier) # 验证手机号格式 func _is_valid_phone(phone: String) -> bool: var regex = RegEx.new() regex.compile("^\\+?[1-9]\\d{1,14}$") return regex.search(phone) != null