killapp/Assets/Scripts/Network/HttpRequestManager.cs

689 lines
25 KiB
C#
Raw Normal View History

2026-04-16 14:57:19 +08:00
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 ? JsonUtility.ToJson(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, Dictionary<string, string> formData, HttpRequestConfig config = null)
{
WWWForm form = new WWWForm();
if (formData != null)
{
foreach (var pair in formData)
{
form.AddField(pair.Key, pair.Value);
}
}
return await SendRequestWithForm<T>(url, HttpMethod.POST, form, 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) : "";
return await SendRequest<T>(url, HttpMethod.PUT, jsonData, config, "application/json");
}
/// <summary>
/// 发送PUT请求(表单数据)
/// </summary>
public async Task<HttpResponse<T>> PutForm<T>(string url, Dictionary<string, string> formData, HttpRequestConfig config = null)
{
WWWForm form = new WWWForm();
if (formData != null)
{
foreach (var pair in formData)
{
form.AddField(pair.Key, pair.Value);
}
}
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();
// 处理响应
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler.text;
Debug.Log(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;
// 添加请求头
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
}
}