killapp/Assets/Scripts/Network/HttpRequestManager.cs
“虞渠成” 3abf31324c 0610
2026-06-10 15:04:14 +08:00

779 lines
29 KiB
C#
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.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using Debug = UnityEngine.Debug;
// 添加 JSON 解析支持
using Newtonsoft.Json;
namespace Kill.Network
{
/// <summary>
/// HTTP请求管理器 - 统一管理所有HTTP请求
/// </summary>
public class HttpRequestManager : MonoBehaviour
{
public static HttpRequestManager Instance { get; private set; }
/// <summary>
/// 基础URL
/// </summary>
public string BaseUrl { get; set; } = "";
/// <summary>
/// 全局请求头
/// </summary>
private Dictionary<string, string> _globalHeaders = new Dictionary<string, string>();
/// <summary>
/// 请求拦截器
/// </summary>
public event Func<NetworkRequestTask, Task> OnRequestIntercept;
/// <summary>
/// 响应拦截器
/// </summary>
public event Func<string, Task<string>> OnResponseIntercept;
/// <summary>
/// 默认请求配置
/// </summary>
private HttpRequestConfig _defaultConfig = new HttpRequestConfig();
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
#region
/// <summary>
/// 设置基础URL
/// </summary>
public void SetBaseUrl(string baseUrl)
{
BaseUrl = baseUrl.TrimEnd('/');
}
/// <summary>
/// 添加全局请求头
/// </summary>
public void AddGlobalHeader(string key, string value)
{
_globalHeaders[key] = value;
}
/// <summary>
/// 移除全局请求头
/// </summary>
public void RemoveGlobalHeader(string key)
{
if (_globalHeaders.ContainsKey(key))
{
_globalHeaders.Remove(key);
}
}
/// <summary>
/// 设置默认配置
/// </summary>
public void SetDefaultConfig(HttpRequestConfig config)
{
_defaultConfig = config;
}
/// <summary>
/// 清除所有全局请求头
/// </summary>
public void ClearGlobalHeaders()
{
_globalHeaders.Clear();
}
#endregion
#region GET请求
/// <summary>
/// 发送GET请求
/// </summary>
public async Task<HttpResponse<T>> Get<T>(string url, Dictionary<string, string> queryParams = null, HttpRequestConfig config = null)
{
string fullUrl = BuildUrl(url, queryParams);
return await SendRequest<T>(fullUrl, HttpMethod.GET, null, config);
}
/// <summary>
/// 发送GET请求(返回原始字符串)
/// </summary>
public async Task<HttpResponse<string>> Get(string url, Dictionary<string, string> queryParams = null, HttpRequestConfig config = null)
{
string fullUrl = BuildUrl(url, queryParams);
return await SendRequest<string>(fullUrl, HttpMethod.GET, null, config);
}
#endregion
#region POST请求
/// <summary>
/// 发送POST请求(JSON数据)
/// </summary>
public async Task<HttpResponse<T>> Post<T>(string url, object data, HttpRequestConfig config = null)
{
string jsonData = data != null ? JsonConvert.SerializeObject(data) : "";
Debug.Log("发送数据:" + jsonData);
return await SendRequest<T>(url, HttpMethod.POST, jsonData, config, "application/json");
}
/// <summary>
/// 发送POST请求(表单数据)
/// </summary>
public async Task<HttpResponse<T>> PostForm<T>(string url, WWWForm formData, HttpRequestConfig config = null)
{
return await SendRequestWithForm<T>(url, HttpMethod.POST, formData, config);
}
/// <summary>
/// 发送POST请求(IMultipartFormSection)
/// </summary>
public async Task<HttpResponse<T>> PostFormData<T>(string url, List<IMultipartFormSection> formData, HttpRequestConfig config = null)
{
return await SendRequestWithFormData<T>(url, HttpMethod.POST, formData, config);
}
/// <summary>
/// 发送POST请求(原始字符串)
/// </summary>
public async Task<HttpResponse<string>> Post(string url, object data, HttpRequestConfig config = null)
{
return await Post<string>(url, data, config);
}
#endregion
#region PUT请求
/// <summary>
/// 发送PUT请求(JSON数据)
/// </summary>
public async Task<HttpResponse<T>> Put<T>(string url, object data, HttpRequestConfig config = null)
{
string jsonData = data != null ? JsonUtility.ToJson(data) : "";
Debug.Log(jsonData);
return await SendRequest<T>(url, HttpMethod.PUT, jsonData, config, "application/json");
}
/// <summary>
/// 发送PUT请求(表单数据)
/// </summary>
public async Task<HttpResponse<T>> PutForm<T>(string url, WWWForm form, HttpRequestConfig config = null)
{
return await SendRequestWithForm<T>(url, HttpMethod.PUT, form, config);
}
/// <summary>
/// 发送PUT请求(原始字符串)
/// </summary>
public async Task<HttpResponse<string>> Put(string url, object data, HttpRequestConfig config = null)
{
return await Put<string>(url, data, config);
}
#endregion
#region DELETE请求
/// <summary>
/// 发送DELETE请求
/// </summary>
public async Task<HttpResponse<T>> Delete<T>(string url, HttpRequestConfig config = null)
{
return await SendRequest<T>(url, HttpMethod.DELETE, null, config);
}
/// <summary>
/// 发送DELETE请求(带body)
/// </summary>
public async Task<HttpResponse<T>> Delete<T>(string url, object data, HttpRequestConfig config = null)
{
string jsonData = data != null ? JsonUtility.ToJson(data) : "";
return await SendRequest<T>(url, HttpMethod.DELETE, jsonData, config, "application/json");
}
#endregion
#region
/// <summary>
/// 下载文件
/// </summary>
public async Task<HttpResponse<string>> DownloadFile(string url, string savePath, Action<DownloadProgress> onProgress = null, HttpRequestConfig config = null)
{
var mergedConfig = MergeConfig(config);
var stopwatch = Stopwatch.StartNew();
try
{
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
request.timeout = mergedConfig.Timeout;
// 添加请求头
AddHeadersToRequest(request, mergedConfig.Headers);
// 发送请求
var operation = request.SendWebRequest();
// 监控进度
while (!operation.isDone)
{
long totalBytes = 0;
string contentLength = request.GetResponseHeader("Content-Length");
if (!string.IsNullOrEmpty(contentLength) && long.TryParse(contentLength, out long parsedLength))
{
totalBytes = parsedLength;
}
onProgress?.Invoke(new DownloadProgress
{
DownloadedBytes = (long)(request.downloadedBytes),
TotalBytes = totalBytes
});
await Task.Yield();
}
stopwatch.Stop();
if (request.result == UnityWebRequest.Result.Success)
{
// 确保目录存在
string directory = Path.GetDirectoryName(savePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 保存文件
File.WriteAllBytes(savePath, request.downloadHandler.data);
return new HttpResponse<string>
{
IsSuccess = true,
StatusCode = request.responseCode,
Data = savePath,
ElapsedMilliseconds = stopwatch.ElapsedMilliseconds
};
}
else
{
return HttpResponse<string>.Failure(request.error, request.responseCode);
}
}
}
catch (Exception ex)
{
stopwatch.Stop();
return HttpResponse<string>.Failure($"下载异常: {ex.Message}");
}
}
#endregion
#region
/// <summary>
/// 发送HTTP请求(核心方法)
/// </summary>
private async Task<HttpResponse<T>> SendRequest<T>(string url, HttpMethod method, string data, HttpRequestConfig config, string contentType = null)
{
var mergedConfig = MergeConfig(config);
string fullUrl = GetFullUrl(url);
// 检查网络状态
if (NetworkStateManager.Instance != null && !NetworkStateManager.Instance.IsConnected)
{
return HttpResponse<T>.Failure("网络未连接");
}
int retryCount = 0;
while (retryCount <= mergedConfig.MaxRetries)
{
var stopwatch = Stopwatch.StartNew();
try
{
using (UnityWebRequest request = CreateWebRequest(fullUrl, method, data, contentType))
{
request.timeout = mergedConfig.Timeout;
// 添加请求头
AddHeadersToRequest(request, mergedConfig.Headers);
// 发送请求
var operation = request.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
stopwatch.Stop();
Debug.Log(request.uri.ToString());
// 处理响应
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler.text;
Debug.Log("response:" + responseText);
// 响应拦截器
if (OnResponseIntercept != null)
{
responseText = await OnResponseIntercept.Invoke(responseText);
}
T resultData = default;
if (!string.IsNullOrEmpty(responseText) && typeof(T) != typeof(string))
{
try
{
// 使用 Newtonsoft.Json 解析,支持嵌套对象
resultData = JsonConvert.DeserializeObject<T>(responseText);
}
catch (Exception ex)
{
Debug.LogWarning($"Newtonsoft JSON解析失败: {ex.Message},尝试使用 Unity 的 JsonUtility");
try
{
// 降级使用 Unity 的 JsonUtility
resultData = JsonUtility.FromJson<T>(responseText);
}
catch (Exception ex2)
{
Debug.LogWarning($"JsonUtility 解析也失败: {ex2.Message}");
resultData = (T)(object)responseText;
}
}
}
else if (typeof(T) == typeof(string))
{
resultData = (T)(object)responseText;
}
return new HttpResponse<T>
{
IsSuccess = true,
StatusCode = request.responseCode,
Data = resultData,
RawResponse = responseText,
Headers = GetResponseHeaders(request),
ElapsedMilliseconds = stopwatch.ElapsedMilliseconds
};
}
else
{
// 请求失败,检查是否需要重试
if (retryCount < mergedConfig.MaxRetries && ShouldRetry(request.responseCode))
{
retryCount++;
int delay = mergedConfig.UseExponentialBackoff
? mergedConfig.RetryDelayMs * (int)Math.Pow(2, retryCount - 1)
: mergedConfig.RetryDelayMs;
Debug.Log($"请求失败,{delay}ms后重试({retryCount}/{mergedConfig.MaxRetries}): {fullUrl}");
await Task.Delay(delay);
continue;
}
return HttpResponse<T>.Failure(request.error, request.responseCode);
}
}
}
catch (Exception ex)
{
stopwatch.Stop();
if (retryCount < mergedConfig.MaxRetries)
{
retryCount++;
int delay = mergedConfig.UseExponentialBackoff
? mergedConfig.RetryDelayMs * (int)Math.Pow(2, retryCount - 1)
: mergedConfig.RetryDelayMs;
Debug.Log($"请求异常,{delay}ms后重试({retryCount}/{mergedConfig.MaxRetries}): {ex.Message}");
await Task.Delay(delay);
continue;
}
return HttpResponse<T>.Failure($"请求异常: {ex.Message}");
}
}
return HttpResponse<T>.Failure("请求失败,已重试所有次数");
}
/// <summary>
/// 发送带表单的请求
/// </summary>
private async Task<HttpResponse<T>> SendRequestWithForm<T>(string url, HttpMethod method, WWWForm form, HttpRequestConfig config)
{
var mergedConfig = MergeConfig(config);
string fullUrl = GetFullUrl(url);
int retryCount = 0;
while (retryCount <= mergedConfig.MaxRetries)
{
var stopwatch = Stopwatch.StartNew();
try
{
using (UnityWebRequest request = UnityWebRequest.Post(fullUrl, form))
{
if (method == HttpMethod.PUT)
{
request.method = "PUT";
}
request.timeout = mergedConfig.Timeout;
// 添加请求头(不要手动设置 Content-TypeWWWForm 会自动设置)
AddHeadersToRequest(request, mergedConfig.Headers);
var operation = request.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
stopwatch.Stop();
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler.text;
T resultData = default;
if (!string.IsNullOrEmpty(responseText) && typeof(T) != typeof(string))
{
try
{
resultData = JsonUtility.FromJson<T>(responseText);
}
catch
{
resultData = (T)(object)responseText;
}
}
else if (typeof(T) == typeof(string))
{
resultData = (T)(object)responseText;
}
return new HttpResponse<T>
{
IsSuccess = true,
StatusCode = request.responseCode,
Data = resultData,
RawResponse = responseText,
ElapsedMilliseconds = stopwatch.ElapsedMilliseconds
};
}
else
{
if (retryCount < mergedConfig.MaxRetries && ShouldRetry(request.responseCode))
{
retryCount++;
int delay = mergedConfig.UseExponentialBackoff
? mergedConfig.RetryDelayMs * (int)Math.Pow(2, retryCount - 1)
: mergedConfig.RetryDelayMs;
await Task.Delay(delay);
continue;
}
return HttpResponse<T>.Failure(request.error, request.responseCode);
}
}
}
catch (Exception ex)
{
stopwatch.Stop();
if (retryCount < mergedConfig.MaxRetries)
{
retryCount++;
int delay = mergedConfig.UseExponentialBackoff
? mergedConfig.RetryDelayMs * (int)Math.Pow(2, retryCount - 1)
: mergedConfig.RetryDelayMs;
await Task.Delay(delay);
continue;
}
return HttpResponse<T>.Failure($"请求异常: {ex.Message}");
}
}
return HttpResponse<T>.Failure("请求失败,已重试所有次数");
}
/// <summary>
/// 发送带表单的请求
/// </summary>
private async Task<HttpResponse<T>> SendRequestWithFormData<T>(string url, HttpMethod method, List<IMultipartFormSection> formData, HttpRequestConfig config)
{
var mergedConfig = MergeConfig(config);
string fullUrl = GetFullUrl(url);
int retryCount = 0;
while (retryCount <= mergedConfig.MaxRetries)
{
var stopwatch = Stopwatch.StartNew();
try
{
using (UnityWebRequest request = UnityWebRequest.Post(fullUrl, formData))
{
if (method == HttpMethod.PUT)
{
request.method = "PUT";
}
request.timeout = mergedConfig.Timeout;
// 添加请求头(不要手动设置 Content-TypeWWWForm 会自动设置)
AddHeadersToRequest(request, mergedConfig.Headers);
var operation = request.SendWebRequest();
while (!operation.isDone)
{
await Task.Yield();
}
stopwatch.Stop();
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler.text;
T resultData = default;
if (!string.IsNullOrEmpty(responseText) && typeof(T) != typeof(string))
{
try
{
resultData = JsonUtility.FromJson<T>(responseText);
}
catch
{
resultData = (T)(object)responseText;
}
}
else if (typeof(T) == typeof(string))
{
resultData = (T)(object)responseText;
}
return new HttpResponse<T>
{
IsSuccess = true,
StatusCode = request.responseCode,
Data = resultData,
RawResponse = responseText,
ElapsedMilliseconds = stopwatch.ElapsedMilliseconds
};
}
else
{
if (retryCount < mergedConfig.MaxRetries && ShouldRetry(request.responseCode))
{
retryCount++;
int delay = mergedConfig.UseExponentialBackoff
? mergedConfig.RetryDelayMs * (int)Math.Pow(2, retryCount - 1)
: mergedConfig.RetryDelayMs;
await Task.Delay(delay);
continue;
}
return HttpResponse<T>.Failure(request.error, request.responseCode);
}
}
}
catch (Exception ex)
{
stopwatch.Stop();
if (retryCount < mergedConfig.MaxRetries)
{
retryCount++;
int delay = mergedConfig.UseExponentialBackoff
? mergedConfig.RetryDelayMs * (int)Math.Pow(2, retryCount - 1)
: mergedConfig.RetryDelayMs;
await Task.Delay(delay);
continue;
}
return HttpResponse<T>.Failure($"请求异常: {ex.Message}");
}
}
return HttpResponse<T>.Failure("请求失败,已重试所有次数");
}
#endregion
#region
/// <summary>
/// 创建WebRequest
/// </summary>
private UnityWebRequest CreateWebRequest(string url, HttpMethod method, string data, string contentType)
{
UnityWebRequest request;
switch (method)
{
case HttpMethod.GET:
request = UnityWebRequest.Get(url);
break;
case HttpMethod.POST:
request = UnityWebRequest.Post(url, data, contentType ?? "application/json");
break;
case HttpMethod.PUT:
byte[] bodyRaw = Encoding.UTF8.GetBytes(data ?? "");
request = UnityWebRequest.Put(url, bodyRaw);
request.SetRequestHeader("Content-Type", contentType ?? "application/json");
break;
case HttpMethod.DELETE:
request = UnityWebRequest.Delete(url);
if (!string.IsNullOrEmpty(data))
{
byte[] deleteBody = Encoding.UTF8.GetBytes(data);
request.uploadHandler = new UploadHandlerRaw(deleteBody);
request.SetRequestHeader("Content-Type", contentType ?? "application/json");
}
break;
default:
request = UnityWebRequest.Get(url);
break;
}
// 设置下载处理器
if (request.downloadHandler == null)
{
request.downloadHandler = new DownloadHandlerBuffer();
}
return request;
}
/// <summary>
/// 添加请求头
/// </summary>
private void AddHeadersToRequest(UnityWebRequest request, Dictionary<string, string> customHeaders)
{
// 添加全局请求头
foreach (var header in _globalHeaders)
{
request.SetRequestHeader(header.Key, header.Value);
}
// 添加自定义请求头
if (customHeaders != null)
{
foreach (var header in customHeaders)
{
request.SetRequestHeader(header.Key, header.Value);
}
}
}
/// <summary>
/// 获取响应头
/// </summary>
private Dictionary<string, string> GetResponseHeaders(UnityWebRequest request)
{
var headers = new Dictionary<string, string>();
if (request.GetResponseHeaders() != null)
{
foreach (var header in request.GetResponseHeaders())
{
headers[header.Key] = header.Value;
}
}
return headers;
}
/// <summary>
/// 合并配置
/// </summary>
private HttpRequestConfig MergeConfig(HttpRequestConfig customConfig)
{
if (customConfig == null) return _defaultConfig;
return new HttpRequestConfig
{
Timeout = customConfig.Timeout > 0 ? customConfig.Timeout : _defaultConfig.Timeout,
MaxRetries = customConfig.MaxRetries >= 0 ? customConfig.MaxRetries : _defaultConfig.MaxRetries,
RetryDelayMs = customConfig.RetryDelayMs > 0 ? customConfig.RetryDelayMs : _defaultConfig.RetryDelayMs,
UseExponentialBackoff = customConfig.UseExponentialBackoff,
Headers = customConfig.Headers ?? _defaultConfig.Headers,
Priority = customConfig.Priority,
EnableCache = customConfig.EnableCache,
CacheDuration = customConfig.CacheDuration > 0 ? customConfig.CacheDuration : _defaultConfig.CacheDuration
};
}
/// <summary>
/// 构建完整URL
/// </summary>
private string GetFullUrl(string url)
{
if (string.IsNullOrEmpty(BaseUrl) || url.StartsWith("http://") || url.StartsWith("https://"))
{
return url;
}
return $"{BaseUrl}/{url.TrimStart('/')}";
}
/// <summary>
/// 构建带查询参数的URL
/// </summary>
private string BuildUrl(string url, Dictionary<string, string> queryParams)
{
string fullUrl = GetFullUrl(url);
if (queryParams == null || queryParams.Count == 0)
{
return fullUrl;
}
StringBuilder sb = new StringBuilder(fullUrl);
sb.Append(fullUrl.Contains("?") ? "&" : "?");
bool first = true;
foreach (var param in queryParams)
{
if (!first) sb.Append("&");
sb.Append($"{UnityWebRequest.EscapeURL(param.Key)}={UnityWebRequest.EscapeURL(param.Value)}");
first = false;
}
return sb.ToString();
}
/// <summary>
/// 判断是否应该重试
/// </summary>
private bool ShouldRetry(long statusCode)
{
// 5xx 服务器错误或 0(网络错误) 时重试
return statusCode >= 500 || statusCode == 0 || statusCode == 408;
}
#endregion
}
}