Files
whale-town-front-v2/_Core/managers/ApiClient.gd

131 lines
5.2 KiB
GDScript

extends Node
# ============================================================================
# ApiClient.gd - 后端 JSON API 客户端
# ============================================================================
# 统一处理 WhaleTown 后端 HTTP JSON 请求、认证头和响应信封解析。
# ============================================================================
signal request_failed(endpoint: String, message: String)
const NetworkConfig = preload("res://_Core/utils/NetworkConfig.gd")
const REQUEST_TIMEOUT: float = 12.0
var _activeRequests: Array[HTTPRequest] = []
func _exit_tree() -> void:
for request in _activeRequests:
if is_instance_valid(request):
request.cancel_request()
request.queue_free()
_activeRequests.clear()
func get_json(endpoint: String, callback: Callable, authenticated: bool = true) -> void:
request_json(endpoint, {}, callback, HTTPClient.METHOD_GET, authenticated)
func post_json(endpoint: String, payload: Dictionary, callback: Callable, authenticated: bool = true) -> void:
request_json(endpoint, payload, callback, HTTPClient.METHOD_POST, authenticated)
func patch_json(endpoint: String, payload: Dictionary, callback: Callable, authenticated: bool = true) -> void:
request_json(endpoint, payload, callback, HTTPClient.METHOD_PATCH, authenticated)
func put_json(endpoint: String, payload: Dictionary, callback: Callable, authenticated: bool = true) -> void:
request_json(endpoint, payload, callback, HTTPClient.METHOD_PUT, authenticated)
func request_json(endpoint: String, payload: Dictionary, callback: Callable, method: int = HTTPClient.METHOD_GET, authenticated: bool = true) -> void:
var request := HTTPRequest.new()
request.timeout = REQUEST_TIMEOUT
add_child(request)
_activeRequests.append(request)
request.request_completed.connect(func(result: int, responseCode: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
_activeRequests.erase(request)
request.queue_free()
_handle_response(endpoint, result, responseCode, body, callback)
)
var url := "%s%s" % [NetworkConfig.get_api_base_url(), endpoint]
var body := "" if method == HTTPClient.METHOD_GET else JSON.stringify(payload)
var err := request.request(url, _headers(authenticated), method, body)
if err != OK:
_activeRequests.erase(request)
request.queue_free()
var message := "网络请求发送失败: %s" % error_string(err)
request_failed.emit(endpoint, message)
callback.call(false, {}, {"message": message})
func _headers(authenticated: bool) -> PackedStringArray:
var headers := PackedStringArray(["Content-Type: application/json"])
if authenticated:
var authManager := get_node_or_null("/root/AuthManager")
var accessToken := str(authManager.call("get_access_token")).strip_edges() if authManager != null and authManager.has_method("get_access_token") else ""
if not accessToken.is_empty():
headers.append("Authorization: Bearer %s" % accessToken)
return headers
func _handle_response(endpoint: String, result: int, responseCode: int, body: PackedByteArray, callback: Callable) -> void:
if result != HTTPRequest.RESULT_SUCCESS:
var message := "网络请求失败: %s" % _http_result_to_string(result)
request_failed.emit(endpoint, message)
callback.call(false, {}, {"message": message})
return
var bodyText := body.get_string_from_utf8()
var json := JSON.new()
if json.parse(bodyText) != OK:
var parseMessage := "服务器响应解析失败"
request_failed.emit(endpoint, parseMessage)
callback.call(false, {}, {"message": parseMessage, "response_code": responseCode})
return
var payloadVariant: Variant = json.data
if not (payloadVariant is Dictionary):
var formatMessage := "服务器响应格式错误"
request_failed.emit(endpoint, formatMessage)
callback.call(false, {}, {"message": formatMessage, "response_code": responseCode})
return
var response: Dictionary = payloadVariant
var ok := responseCode >= 200 and responseCode < 300 and bool(response.get("success", true))
if ok:
callback.call(true, response, {})
return
var errorInfo := {
"message": str(response.get("message", "请求失败")),
"response_code": responseCode,
"error_code": str(response.get("error_code", ""))
}
request_failed.emit(endpoint, str(errorInfo.get("message", "请求失败")))
callback.call(false, response, errorInfo)
func _http_result_to_string(result: int) -> String:
match result:
HTTPRequest.RESULT_CHUNKED_BODY_SIZE_MISMATCH:
return "响应分块大小不匹配"
HTTPRequest.RESULT_CANT_CONNECT:
return "无法连接服务器"
HTTPRequest.RESULT_CANT_RESOLVE:
return "无法解析服务器地址"
HTTPRequest.RESULT_CONNECTION_ERROR:
return "连接中断"
HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR:
return "TLS握手失败"
HTTPRequest.RESULT_NO_RESPONSE:
return "服务器无响应"
HTTPRequest.RESULT_BODY_SIZE_LIMIT_EXCEEDED:
return "响应内容过大"
HTTPRequest.RESULT_REQUEST_FAILED:
return "请求失败"
HTTPRequest.RESULT_DOWNLOAD_FILE_CANT_OPEN:
return "下载文件无法打开"
HTTPRequest.RESULT_DOWNLOAD_FILE_WRITE_ERROR:
return "下载文件写入失败"
HTTPRequest.RESULT_REDIRECT_LIMIT_REACHED:
return "重定向次数过多"
HTTPRequest.RESULT_TIMEOUT:
return "请求超时"
_:
return "未知错误 %s" % result