extends Node # ============================================================================ # NetworkManager.gd - 网络请求管理器 # ============================================================================ # 全局单例管理器,统一处理所有HTTP请求 # # 核心职责: # - 统一的HTTP请求接口 (GET, POST, PUT, DELETE, PATCH) # - 认证相关API封装 (登录、注册、验证码等) # - 请求状态管理和错误处理 # - 支持API v1.1.1规范的响应处理 # # 使用方式: # NetworkManager.login("user@example.com", "password", callback) # var request_id = NetworkManager.get_request("/api/data", callback) # # 注意事项: # - 作为自动加载单例,全局可访问 # - 所有请求都是异步的,通过回调函数或信号处理结果 # - 支持请求超时和取消功能 # - 自动处理JSON序列化和反序列化 # ============================================================================ # ============ 信号定义 ============ # 请求完成信号 # 参数: # request_id: String - 请求唯一标识符 # success: bool - 请求是否成功 # data: Dictionary - 响应数据 signal request_completed(request_id: String, success: bool, data: Dictionary) # 请求失败信号 # 参数: # request_id: String - 请求唯一标识符 # error_type: String - 错误类型名称 # message: String - 错误消息 signal request_failed(request_id: String, error_type: String, message: String) # 公告列表接收信号 signal notices_received(data: Array) # ============ 常量定义 ============ # API基础URL - 所有请求的根地址 # [Remote] 正式环境地址 (实际正式项目用此地址) # const API_BASE_URL = "https://whaletownend.xinghangee.icu" # [Local] 本地调试地址 (本地调试用此地址) const API_BASE_URL = "http://localhost:3000" # 默认请求超时时间(秒) const DEFAULT_TIMEOUT = 30.0 # ============ 枚举定义 ============ # HTTP请求方法枚举 enum RequestType { GET, # 获取数据 POST, # 创建数据 PUT, # 更新数据 DELETE, # 删除数据 PATCH # 部分更新数据 } # 错误类型枚举 # 用于分类不同类型的网络错误 enum ErrorType { NETWORK_ERROR, # 网络连接错误 - 无法连接到服务器 TIMEOUT_ERROR, # 请求超时 - 服务器响应时间过长 PARSE_ERROR, # JSON解析错误 - 服务器返回格式错误 HTTP_ERROR, # HTTP状态码错误 - 4xx, 5xx状态码 BUSINESS_ERROR # 业务逻辑错误 - API返回的业务错误 } # ============ 请求信息类 ============ # 请求信息封装类 # 存储单个HTTP请求的所有相关信息 class RequestInfo: var id: String # 请求唯一标识符 var url: String # 完整的请求URL var method: RequestType # HTTP请求方法 var headers: PackedStringArray # 请求头数组 var body: String # 请求体内容 var timeout: float # 超时时间(秒) var start_time: float # 请求开始时间戳 var http_request: HTTPRequest # Godot HTTPRequest节点引用 var callback: Callable # 完成时的回调函数 # 构造函数 # # 参数: # request_id: String - 请求唯一标识符 # request_url: String - 请求URL # request_method: RequestType - HTTP方法 # request_headers: PackedStringArray - 请求头(可选) # request_body: String - 请求体(可选) # request_timeout: float - 超时时间(可选,默认使用DEFAULT_TIMEOUT) func _init(request_id: String, request_url: String, request_method: RequestType, request_headers: PackedStringArray = [], request_body: String = "", request_timeout: float = DEFAULT_TIMEOUT): id = request_id url = request_url method = request_method headers = request_headers body = request_body timeout = request_timeout # 记录请求开始时间(简化版时间戳) start_time = Time.get_time_dict_from_system().hour * 3600 + Time.get_time_dict_from_system().minute * 60 + Time.get_time_dict_from_system().second # ============ 成员变量 ============ # 活动请求管理 var active_requests: Dictionary = {} # 存储所有活动请求 {request_id: RequestInfo} var request_counter: int = 0 # 请求计数器,用于生成唯一ID # ============ 生命周期方法 ============ # 初始化网络管理器 # 在节点准备就绪时调用 func _ready(): process_mode = Node.PROCESS_MODE_ALWAYS print("NetworkManager 已初始化") # ============ 公共API接口 ============ # 发送GET请求 # # 参数: # endpoint: String - API端点路径(如: "/api/users") # callback: Callable - 完成时的回调函数(可选) # timeout: float - 超时时间(可选,默认30秒) # # 返回值: # String - 请求ID,可用于取消请求或跟踪状态 # # 使用示例: # var request_id = NetworkManager.get_request("/api/users", my_callback) func get_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: return send_request(endpoint, RequestType.GET, [], "", callback, timeout) # 发送POST请求 # # 参数: # endpoint: String - API端点路径 # data: Dictionary - 要发送的数据(将自动转换为JSON) # callback: Callable - 完成时的回调函数(可选) # timeout: float - 超时时间(可选) # # 返回值: # String - 请求ID # # 使用示例: # var data = {"name": "张三", "age": 25} # var request_id = NetworkManager.post_request("/api/users", data, my_callback) func post_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: var body = JSON.stringify(data) var headers = ["Content-Type: application/json"] return send_request(endpoint, RequestType.POST, headers, body, callback, timeout) # 发送PUT请求 # # 参数: # endpoint: String - API端点路径 # data: Dictionary - 要更新的数据 # callback: Callable - 完成时的回调函数(可选) # timeout: float - 超时时间(可选) # # 返回值: # String - 请求ID func put_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: var body = JSON.stringify(data) var headers = ["Content-Type: application/json"] return send_request(endpoint, RequestType.PUT, headers, body, callback, timeout) # 发送DELETE请求 # # 参数: # endpoint: String - API端点路径 # callback: Callable - 完成时的回调函数(可选) # timeout: float - 超时时间(可选) # # 返回值: # String - 请求ID func delete_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: return send_request(endpoint, RequestType.DELETE, [], "", callback, timeout) # ============ 认证相关API ============ # 用户登录 # # 参数: # identifier: String - 用户标识符(邮箱或手机号) # password: String - 用户密码 # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 回调函数签名: # func callback(success: bool, data: Dictionary, error_info: Dictionary) # # 使用示例: # NetworkManager.login("user@example.com", "password123", func(success, data, error): # if success: # print("登录成功: ", data) # else: # print("登录失败: ", error.message) # ) func login(identifier: String, password: String, callback: Callable = Callable()) -> String: var data = { "identifier": identifier, "password": password } return post_request("/auth/login", data, callback) # 验证码登录 # # 参数: # identifier: String - 用户标识符(邮箱或手机号) # verification_code: String - 验证码 # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 使用场景: # - 用户忘记密码时的替代登录方式 # - 提供更安全的登录选项 func verification_code_login(identifier: String, verification_code: String, callback: Callable = Callable()) -> String: var data = { "identifier": identifier, "verification_code": verification_code } return post_request("/auth/verification-code-login", data, callback) # 发送登录验证码 # # 参数: # identifier: String - 用户标识符(邮箱或手机号) # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 向已注册用户发送登录验证码 # - 支持邮箱和手机号 # - 有频率限制保护 func send_login_verification_code(identifier: String, callback: Callable = Callable()) -> String: var data = {"identifier": identifier} return post_request("/auth/send-login-verification-code", data, callback) # 用户注册 # # 参数: # username: String - 用户名 # password: String - 密码 # nickname: String - 昵称 # email: String - 邮箱地址(可选) # email_verification_code: String - 邮箱验证码(可选) # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 注意事项: # - 如果提供邮箱,建议同时提供验证码 # - 用户名和邮箱必须唯一 # - 密码需要符合安全要求 func register(username: String, password: String, nickname: String, email: String = "", email_verification_code: String = "", callback: Callable = Callable()) -> String: var data = { "username": username, "password": password, "nickname": nickname } # 可选参数处理 if email != "": data["email"] = email if email_verification_code != "": data["email_verification_code"] = email_verification_code return post_request("/auth/register", data, callback) # 发送邮箱验证码 # # 参数: # email: String - 邮箱地址 # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 向指定邮箱发送验证码 # - 用于注册时的邮箱验证 # - 支持测试模式(开发环境) func send_email_verification(email: String, callback: Callable = Callable()) -> String: var data = {"email": email} return post_request("/auth/send-email-verification", data, callback) # 验证邮箱 # # 参数: # email: String - 邮箱地址 # verification_code: String - 验证码 # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 验证邮箱验证码的有效性 # - 通常在注册流程中使用 func verify_email(email: String, verification_code: String, callback: Callable = Callable()) -> String: var data = { "email": email, "verification_code": verification_code } return post_request("/auth/verify-email", data, callback) # 获取应用状态 # # 参数: # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 检查API服务器状态 # - 获取应用基本信息 # - 用于网络连接测试 func get_app_status(callback: Callable = Callable()) -> String: return get_request("/", callback) # 重新发送邮箱验证码 # # 参数: # email: String - 邮箱地址 # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 使用场景: # - 用户未收到验证码时重新发送 # - 验证码过期后重新获取 func resend_email_verification(email: String, callback: Callable = Callable()) -> String: var data = {"email": email} return post_request("/auth/resend-email-verification", data, callback) # 忘记密码 - 发送重置验证码 # # 参数: # identifier: String - 用户标识符(邮箱或手机号) # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 向用户发送密码重置验证码 # - 用于密码找回流程的第一步 func forgot_password(identifier: String, callback: Callable = Callable()) -> String: var data = {"identifier": identifier} return post_request("/auth/forgot-password", data, callback) # 重置密码 # # 参数: # identifier: String - 用户标识符 # verification_code: String - 重置验证码 # new_password: String - 新密码 # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 使用验证码重置用户密码 # - 密码找回流程的第二步 func reset_password(identifier: String, verification_code: String, new_password: String, callback: Callable = Callable()) -> String: var data = { "identifier": identifier, "verification_code": verification_code, "new_password": new_password } return post_request("/auth/reset-password", data, callback) # 修改密码 # # 参数: # user_id: String - 用户ID # old_password: String - 旧密码 # new_password: String - 新密码 # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 已登录用户修改密码 # - 需要验证旧密码 func change_password(user_id: String, old_password: String, new_password: String, callback: Callable = Callable()) -> String: var data = { "user_id": user_id, "old_password": old_password, "new_password": new_password } return put_request("/auth/change-password", data, callback) # GitHub OAuth登录 # # 参数: # github_id: String - GitHub用户ID # username: String - GitHub用户名 # nickname: String - 显示昵称 # email: String - GitHub邮箱 # avatar_url: String - 头像URL(可选) # callback: Callable - 完成时的回调函数(可选) # # 返回值: # String - 请求ID # # 功能: # - 通过GitHub账号登录或注册 # - 支持第三方OAuth认证 func github_login(github_id: String, username: String, nickname: String, email: String, avatar_url: String = "", callback: Callable = Callable()) -> String: var data = { "github_id": github_id, "username": username, "nickname": nickname, "email": email } # 可选头像URL if avatar_url != "": data["avatar_url"] = avatar_url return post_request("/auth/github", data, callback) # TODO: 获取公告列表 func request_notices(): # 发送 GET 请求到 /notices 接口 get_request("/notices", _on_notices_response) func _on_notices_response(success: bool, data: Dictionary, error_info: Dictionary): if success and data.has("data"): notices_received.emit(data["data"]) else: # 失败或无数据时发送空数组 notices_received.emit([]) # ============ 核心请求处理 ============ # 发送请求的核心方法 func send_request(endpoint: String, method: RequestType, headers: PackedStringArray, body: String, callback: Callable, timeout: float) -> String: # 生成请求ID request_counter += 1 var request_id = "req_" + str(request_counter) # 构建完整URL var full_url = API_BASE_URL + endpoint # 创建HTTPRequest节点 var http_request = HTTPRequest.new() add_child(http_request) # 设置超时 http_request.timeout = timeout # 创建请求信息 var request_info = RequestInfo.new(request_id, full_url, method, headers, body, timeout) request_info.http_request = http_request request_info.callback = callback # 存储请求信息 active_requests[request_id] = request_info # 连接信号 http_request.request_completed.connect(func(result: int, response_code: int, response_headers: PackedStringArray, response_body: PackedByteArray): _on_request_completed(request_id, result, response_code, response_headers, response_body) ) # 发送请求 var godot_method = _convert_to_godot_method(method) var error = http_request.request(full_url, headers, godot_method, body) print("=== 发送网络请求 ===") print("请求ID: ", request_id) print("URL: ", full_url) print("方法: ", RequestType.keys()[method]) print("Headers: ", headers) print("Body: ", body if body.length() < 200 else body.substr(0, 200) + "...") print("发送结果: ", error) if error != OK: print("请求发送失败,错误代码: ", error) _handle_request_error(request_id, ErrorType.NETWORK_ERROR, "网络请求发送失败: " + str(error)) return "" return request_id # 请求完成回调 func _on_request_completed(request_id: String, result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray): print("=== 网络请求完成 ===") print("请求ID: ", request_id) print("结果: ", result) print("状态码: ", response_code) print("响应头: ", headers) # 获取请求信息 if not active_requests.has(request_id): print("警告: 未找到请求ID ", request_id) return var _request_info = active_requests[request_id] var response_text = body.get_string_from_utf8() print("响应体长度: ", body.size(), " 字节") print("响应内容: ", response_text if response_text.length() < 500 else response_text.substr(0, 500) + "...") # 处理网络连接失败 if response_code == 0: _handle_request_error(request_id, ErrorType.NETWORK_ERROR, "网络连接失败,请检查网络连接") return # 解析JSON响应 var json = JSON.new() var parse_result = json.parse(response_text) if parse_result != OK: _handle_request_error(request_id, ErrorType.PARSE_ERROR, "服务器响应格式错误") return var response_data = json.data # 处理响应 _handle_response(request_id, response_code, response_data) # 处理响应 - 支持API v1.1.1的状态码 func _handle_response(request_id: String, response_code: int, data: Dictionary): var request_info = active_requests[request_id] # 检查业务成功标志 var success = data.get("success", true) # 默认true保持向后兼容 var error_code = data.get("error_code", "") var message = data.get("message", "") # 判断请求是否成功 var is_success = false # HTTP成功状态码且业务成功 if (response_code >= 200 and response_code < 300) and success: is_success = true # 特殊情况:206测试模式 - 根据API文档,这是成功的测试模式响应 elif response_code == 206 and error_code == "TEST_MODE_ONLY": is_success = true print("🧪 测试模式响应: ", message) # 201创建成功 elif response_code == 201: is_success = true if is_success: print("✅ 请求成功: ", request_id) # 发送成功信号 request_completed.emit(request_id, true, data) # 调用回调函数 if request_info.callback.is_valid(): request_info.callback.call(true, data, {}) else: print("❌ 请求失败: ", request_id, " - HTTP:", response_code, " 错误码:", error_code, " 消息:", message) # 确定错误类型 var error_type = _determine_error_type(response_code, error_code) # 发送失败信号 request_failed.emit(request_id, ErrorType.keys()[error_type], message) # 调用回调函数 if request_info.callback.is_valid(): var error_info = { "response_code": response_code, "error_code": error_code, "message": message, "error_type": error_type } request_info.callback.call(false, data, error_info) # 清理请求 _cleanup_request(request_id) # 处理请求错误 func _handle_request_error(request_id: String, error_type: ErrorType, message: String): print("❌ 请求错误: ", request_id, " - ", message) # 发送错误信号 request_failed.emit(request_id, ErrorType.keys()[error_type], message) # 调用回调函数 if active_requests.has(request_id): var request_info = active_requests[request_id] if request_info.callback.is_valid(): var error_info = { "error_type": error_type, "message": message } request_info.callback.call(false, {}, error_info) # 清理请求 _cleanup_request(request_id) # 确定错误类型 - 支持更多状态码 func _determine_error_type(response_code: int, error_code: String) -> ErrorType: # 根据错误码判断 match error_code: "SERVICE_UNAVAILABLE": return ErrorType.BUSINESS_ERROR "TOO_MANY_REQUESTS": return ErrorType.BUSINESS_ERROR "TEST_MODE_ONLY": return ErrorType.BUSINESS_ERROR "SEND_EMAIL_VERIFICATION_FAILED", "REGISTER_FAILED": # 这些可能是409冲突或其他业务错误 return ErrorType.BUSINESS_ERROR _: # 根据HTTP状态码判断 match response_code: 409: # 资源冲突 return ErrorType.BUSINESS_ERROR 206: # 测试模式 return ErrorType.BUSINESS_ERROR 429: # 频率限制 return ErrorType.BUSINESS_ERROR _: if response_code >= 400 and response_code < 500: return ErrorType.HTTP_ERROR elif response_code >= 500: return ErrorType.HTTP_ERROR else: return ErrorType.BUSINESS_ERROR # 清理请求资源 func _cleanup_request(request_id: String): if active_requests.has(request_id): var request_info = active_requests[request_id] # 移除HTTPRequest节点 if is_instance_valid(request_info.http_request): request_info.http_request.queue_free() # 从活动请求中移除 active_requests.erase(request_id) print("🧹 清理请求: ", request_id) # 转换请求方法 func _convert_to_godot_method(method: RequestType) -> HTTPClient.Method: match method: RequestType.GET: return HTTPClient.METHOD_GET RequestType.POST: return HTTPClient.METHOD_POST RequestType.PUT: return HTTPClient.METHOD_PUT RequestType.DELETE: return HTTPClient.METHOD_DELETE RequestType.PATCH: return HTTPClient.METHOD_PATCH _: return HTTPClient.METHOD_GET # ============ 工具方法 ============ # 取消请求 func cancel_request(request_id: String) -> bool: if active_requests.has(request_id): print("🚫 取消请求: ", request_id) _cleanup_request(request_id) return true return false # 取消所有请求 func cancel_all_requests(): print("🚫 取消所有请求") var request_ids = active_requests.keys() for request_id in request_ids: cancel_request(request_id) # 获取活动请求数量 func get_active_request_count() -> int: return active_requests.size() # 检查请求是否活动 func is_request_active(request_id: String) -> bool: return active_requests.has(request_id) # 获取请求信息 func get_request_info(request_id: String) -> Dictionary: if active_requests.has(request_id): var info = active_requests[request_id] return { "id": info.id, "url": info.url, "method": RequestType.keys()[info.method], "start_time": info.start_time, "timeout": info.timeout } return {} func _notification(what): if what == NOTIFICATION_WM_CLOSE_REQUEST: # 应用关闭时取消所有请求 cancel_all_requests()