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>
This commit is contained in:
2025-12-23 01:15:33 +08:00
parent 8ed260b413
commit d623c705b6
28 changed files with 1455 additions and 0 deletions

View File

@@ -0,0 +1,282 @@
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) + ")"