Files
whale-town-front/scripts/managers/NetworkService.gd
lzdFeiFei d623c705b6 feat:实现登录系统和用户认证功能
- 添加登录场景(login_scene.tscn)和主菜单场景(main_menu_scene.tscn)
- 实现认证管理器(AuthManager)用于用户登录和会话管理
- 添加核心服务:加密服务、存储服务、网络服务
- 配置项目主场景为登录场景
- 添加自动加载服务到项目配置
- 添加开发环境配置(VSCode、Claude)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 01:15:33 +08:00

283 lines
8.3 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Node
## 网络服务
## 封装 HTTP 请求,提供统一的网络通信接口
# API 基础 URL
const API_BASE_URL: String = "https://whaletownui.angforever.top"
# 请求超时时间(秒)
const REQUEST_TIMEOUT: float = 30.0
# 最大重试次数
const MAX_RETRY: int = 3
# 是否开启调试日志(显示请求和响应详情)
const DEBUG_MODE: bool = true
## 发送 POST 请求
func post(endpoint: String, data: Dictionary, headers: Array = []) -> Dictionary:
var url = API_BASE_URL + endpoint
return await _request(url, HTTPClient.METHOD_POST, data, headers)
## 发送 GET 请求
func sendGet(endpoint: String, headers: Array = []) -> Dictionary:
var url = API_BASE_URL + endpoint
return await _request(url, HTTPClient.METHOD_GET, {}, headers)
## 发送带 Token 的 POST 请求
func authenticatedPost(endpoint: String, data: Dictionary) -> Dictionary:
var tokenData = StorageService.loadToken()
var token = tokenData.get("token", "")
if token.is_empty():
return {
"success": false,
"error_code": ErrorCode.TOKEN_INVALID,
"message": ErrorCode.getMessage(ErrorCode.TOKEN_INVALID)
}
var headers = [
"Authorization: Bearer " + token
]
return await post(endpoint, data, headers)
## 核心请求方法
func _request(url: String, method: HTTPClient.Method, data: Dictionary, customHeaders: Array) -> Dictionary:
# 创建 HTTPRequest 节点
var httpRequest = HTTPRequest.new()
add_child(httpRequest)
# 设置超时
httpRequest.timeout = REQUEST_TIMEOUT
# 构建请求头
var headers = [
"Content-Type: application/json",
"Accept: application/json"
]
headers.append_array(customHeaders)
# 准备请求体
var body = ""
if method == HTTPClient.METHOD_POST or method == HTTPClient.METHOD_PUT:
body = JSON.stringify(data)
# 调试日志:请求信息
if DEBUG_MODE:
_logRequest(url, method, headers, body)
# 发送请求
var error = httpRequest.request(url, headers, method, body)
if error != OK:
httpRequest.queue_free()
return _createErrorResponse(ErrorCode.NETWORK_ERROR, "发送请求失败")
# 等待响应
var result = await httpRequest.request_completed
# 调试日志:响应信息
if DEBUG_MODE:
_logResponse(result)
# 清理
httpRequest.queue_free()
# 解析响应
return _parseResponse(result)
## 解析 HTTP 响应
func _parseResponse(result: Array) -> Dictionary:
var httpResult: int = result[0]
var responseCode: int = result[1]
var _headers: PackedStringArray = result[2]
var body: PackedByteArray = result[3]
# 检查 HTTP 请求错误
if httpResult != HTTPRequest.RESULT_SUCCESS:
return _handleHttpError(httpResult)
# 检查 HTTP 状态码
if responseCode < 200 or responseCode >= 300:
return _handleStatusCodeError(responseCode, body)
# 解析 JSON 响应体
var bodyString = body.get_string_from_utf8()
if bodyString.is_empty():
return _createErrorResponse(ErrorCode.SERVER_ERROR, "服务器返回空响应")
var json = JSON.new()
var parseError = json.parse(bodyString)
if parseError != OK:
push_error("JSON 解析失败: " + bodyString)
return _createErrorResponse(ErrorCode.SERVER_ERROR, "响应数据格式错误")
var responseData = json.data
# 根据后端API格式解析通用格式后续可能需要调整
if typeof(responseData) == TYPE_DICTIONARY:
# 检查是否有 success 字段(新版 API 格式)
if responseData.has("success"):
var success = responseData.get("success", false)
if success:
return {
"success": true,
"data": responseData.get("data", {}),
"message": responseData.get("message", "操作成功")
}
else:
# 业务错误
return {
"success": false,
"error_code": responseData.get("error_code", ErrorCode.SERVER_ERROR),
"message": responseData.get("message", "操作失败")
}
# 检查是否有 code 字段(旧版 API 格式)
elif responseData.has("code"):
var code = responseData.get("code", -1)
if code == 0 or code == 200: # 成功
return {
"success": true,
"data": responseData.get("data", {}),
"message": responseData.get("message", "操作成功")
}
else: # 业务错误
return {
"success": false,
"error_code": code,
"message": responseData.get("message", "操作失败")
}
else:
# 假设整个响应就是数据
return {
"success": true,
"data": responseData,
"message": "操作成功"
}
return _createErrorResponse(ErrorCode.SERVER_ERROR, "响应格式不正确")
## 处理 HTTP 请求错误
func _handleHttpError(httpResult: int) -> Dictionary:
match httpResult:
HTTPRequest.RESULT_TIMEOUT:
return _createErrorResponse(ErrorCode.TIMEOUT, "请求超时")
HTTPRequest.RESULT_CONNECTION_ERROR:
return _createErrorResponse(ErrorCode.NETWORK_ERROR, "网络连接失败")
HTTPRequest.RESULT_CANT_CONNECT:
return _createErrorResponse(ErrorCode.NETWORK_ERROR, "无法连接到服务器")
_:
return _createErrorResponse(ErrorCode.NETWORK_ERROR, "网络请求失败: " + str(httpResult))
## 处理 HTTP 状态码错误
func _handleStatusCodeError(statusCode: int, body: PackedByteArray) -> Dictionary:
var bodyString = body.get_string_from_utf8()
# 尝试解析错误信息
var json = JSON.new()
if json.parse(bodyString) == OK:
var data = json.data
if typeof(data) == TYPE_DICTIONARY and data.has("message"):
var message = data.get("message", "")
# 确保 message 是字符串类型
if typeof(message) == TYPE_ARRAY:
message = ", ".join(message)
elif typeof(message) != TYPE_STRING:
message = str(message)
return _createErrorResponse(statusCode, message)
# 通用状态码错误处理
match statusCode:
400:
return _createErrorResponse(statusCode, "请求参数错误")
401:
return _createErrorResponse(statusCode, "未授权,请重新登录")
403:
return _createErrorResponse(statusCode, "没有权限访问")
404:
return _createErrorResponse(statusCode, "请求的资源不存在")
500:
return _createErrorResponse(statusCode, "服务器内部错误")
502:
return _createErrorResponse(statusCode, "网关错误")
503:
return _createErrorResponse(statusCode, "服务暂时不可用")
_:
return _createErrorResponse(statusCode, "HTTP 错误: " + str(statusCode))
## 创建错误响应
func _createErrorResponse(errorCode: int, message: String) -> Dictionary:
return {
"success": false,
"error_code": errorCode,
"message": message
}
## 记录请求日志
func _logRequest(url: String, method: HTTPClient.Method, headers: Array, body: String) -> void:
print("\n========== HTTP 请求 ==========")
print("URL: ", url)
print("方法: ", _getMethodName(method))
print("请求头:")
for header in headers:
print(" ", header)
if not body.is_empty():
print("请求体:")
# 尝试格式化 JSON
var json = JSON.new()
if json.parse(body) == OK:
print(" ", JSON.stringify(json.data, " "))
else:
print(" ", body)
print("==============================\n")
## 记录响应日志
func _logResponse(result: Array) -> void:
var httpResult: int = result[0]
var responseCode: int = result[1]
var responseHeaders: PackedStringArray = result[2]
var body: PackedByteArray = result[3]
print("\n========== HTTP 响应 ==========")
print("请求结果: ", _getHttpResultName(httpResult))
print("状态码: ", responseCode)
print("响应头:")
for header in responseHeaders:
print(" ", header)
var bodyString = body.get_string_from_utf8()
if not bodyString.is_empty():
print("响应体:")
# 尝试格式化 JSON
var json = JSON.new()
if json.parse(bodyString) == OK:
print(" ", JSON.stringify(json.data, " "))
else:
print(" ", bodyString)
else:
print("响应体: (空)")
print("==============================\n")
## 获取 HTTP 方法名称
func _getMethodName(method: HTTPClient.Method) -> String:
match method:
HTTPClient.METHOD_GET: return "GET"
HTTPClient.METHOD_POST: return "POST"
HTTPClient.METHOD_PUT: return "PUT"
HTTPClient.METHOD_DELETE: return "DELETE"
HTTPClient.METHOD_PATCH: return "PATCH"
_: return "UNKNOWN"
## 获取 HTTP 请求结果名称
func _getHttpResultName(result: int) -> String:
match result:
HTTPRequest.RESULT_SUCCESS: return "成功"
HTTPRequest.RESULT_TIMEOUT: return "超时"
HTTPRequest.RESULT_CONNECTION_ERROR: return "连接错误"
HTTPRequest.RESULT_CANT_CONNECT: return "无法连接"
HTTPRequest.RESULT_NO_RESPONSE: return "无响应"
_: return "错误 (" + str(result) + ")"