119 lines
4.0 KiB
C#
119 lines
4.0 KiB
C#
|
|
using Newtonsoft.Json;
|
|||
|
|
using System.Diagnostics;
|
|||
|
|
using System.Net.Http;
|
|||
|
|
using System.Text;
|
|||
|
|
|
|||
|
|
namespace SHH.CameraDashboard
|
|||
|
|
{
|
|||
|
|
public static class HttpService
|
|||
|
|
{
|
|||
|
|
// 单例 HttpClient,避免端口耗尽
|
|||
|
|
private static readonly HttpClient _client;
|
|||
|
|
|
|||
|
|
// 【关键】日志事件,UI层订阅这个事件来显示日志
|
|||
|
|
public static event Action<ApiLogEntry> OnApiLog;
|
|||
|
|
|
|||
|
|
static HttpService()
|
|||
|
|
{
|
|||
|
|
_client = new HttpClient();
|
|||
|
|
// 设置一个合理的超时,避免界面卡死
|
|||
|
|
_client.Timeout = TimeSpan.FromSeconds(5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 泛型 GET 方法
|
|||
|
|
/// </summary>
|
|||
|
|
public static async Task<T> GetAsync<T>(string url)
|
|||
|
|
{
|
|||
|
|
return await ExecuteRequestAsync<T>(new HttpRequestMessage(HttpMethod.Get, url));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 泛型 POST 方法
|
|||
|
|
/// </summary>
|
|||
|
|
public static async Task<T> PostAsync<T>(string url, object data)
|
|||
|
|
{
|
|||
|
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
|||
|
|
string json = JsonConvert.SerializeObject(data);
|
|||
|
|
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
|||
|
|
return await ExecuteRequestAsync<T>(request, json);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 专门用于【连通性测试】的方法 (不关心返回值内容,只关心通不通)
|
|||
|
|
/// </summary>
|
|||
|
|
public static async Task<bool> TestConnectionAsync(string url)
|
|||
|
|
{
|
|||
|
|
// 复用核心逻辑,但泛型传 string (忽略结果) 或 object
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
await ExecuteRequestAsync<string>(new HttpRequestMessage(HttpMethod.Get, url));
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 核心执行逻辑 ---
|
|||
|
|
private static async Task<T> ExecuteRequestAsync<T>(HttpRequestMessage request, string requestBody = "")
|
|||
|
|
{
|
|||
|
|
// 1. 准备日志对象
|
|||
|
|
var log = new ApiLogEntry
|
|||
|
|
{
|
|||
|
|
Method = request.Method.ToString(),
|
|||
|
|
Url = request.RequestUri.ToString(),
|
|||
|
|
RequestBody = requestBody
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var sw = Stopwatch.StartNew(); // 开始计时
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 2. 发起网络请求
|
|||
|
|
var response = await _client.SendAsync(request);
|
|||
|
|
|
|||
|
|
sw.Stop(); // 停止计时
|
|||
|
|
log.DurationMs = sw.ElapsedMilliseconds;
|
|||
|
|
log.StatusCode = (int)response.StatusCode;
|
|||
|
|
|
|||
|
|
// 3. 读取响应内容
|
|||
|
|
string content = await response.Content.ReadAsStringAsync();
|
|||
|
|
log.ResponseBody = content;
|
|||
|
|
|
|||
|
|
if (response.IsSuccessStatusCode)
|
|||
|
|
{
|
|||
|
|
// 如果 T 是 string,直接返回内容,不反序列化
|
|||
|
|
if (typeof(T) == typeof(string))
|
|||
|
|
return (T)(object)content;
|
|||
|
|
|
|||
|
|
// 反序列化 JSON
|
|||
|
|
return JsonConvert.DeserializeObject<T>(content);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
log.ErrorMessage = $"HTTP {response.StatusCode}: {response.ReasonPhrase}";
|
|||
|
|
throw new HttpRequestException(log.ErrorMessage);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
sw.Stop();
|
|||
|
|
if (log.DurationMs == 0) log.DurationMs = sw.ElapsedMilliseconds;
|
|||
|
|
|
|||
|
|
log.StatusCode = 0; // 0 代表网络层面的失败(如断网)
|
|||
|
|
log.ErrorMessage = ex.Message;
|
|||
|
|
log.ResponseBody = ex.ToString(); // 记录堆栈以便排查
|
|||
|
|
|
|||
|
|
throw; // 抛出异常供调用方 UI 处理
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
// 4. 【广播日志】无论成功失败,都触发事件
|
|||
|
|
// 使用 Invoke 确保 UI 订阅者能收到
|
|||
|
|
OnApiLog?.Invoke(log);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|