Browse Source

添加X1.DynamicClientCore动态客户端核心模块 - 新增动态HTTP客户端功能 - 更新解决方案文件

feature/x1-web-request
hyh 5 months ago
parent
commit
47b9bfe898
  1. 10
      X1.sln
  2. 234
      src/X1.DynamicClientCore/Core/DynamicHttpClient.Core.cs
  3. 49
      src/X1.DynamicClientCore/Core/DynamicHttpClient.Sync.cs
  4. 225
      src/X1.DynamicClientCore/Core/DynamicHttpClient.SyncCore.cs
  5. 59
      src/X1.DynamicClientCore/Core/DynamicHttpClient.cs
  6. 200
      src/X1.DynamicClientCore/Extensions/ServiceCollectionExtensions.cs
  7. 101
      src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileDownload.cs
  8. 31
      src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileInfo.cs
  9. 314
      src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileUpload.cs
  10. 308
      src/X1.DynamicClientCore/Infrastructure/CircuitBreakerManager.cs
  11. 121
      src/X1.DynamicClientCore/Infrastructure/ServiceEndpointManager.cs
  12. 57
      src/X1.DynamicClientCore/Interfaces/IAsyncHttpClient.cs
  13. 54
      src/X1.DynamicClientCore/Interfaces/ICircuitBreakerManager.cs
  14. 23
      src/X1.DynamicClientCore/Interfaces/IDynamicHttpClient.cs
  15. 162
      src/X1.DynamicClientCore/Interfaces/IFileHttpClient.cs
  16. 18
      src/X1.DynamicClientCore/Interfaces/IHttpClientBase.cs
  17. 54
      src/X1.DynamicClientCore/Interfaces/IServiceEndpointManager.cs
  18. 53
      src/X1.DynamicClientCore/Interfaces/ISyncHttpClient.cs
  19. 76
      src/X1.DynamicClientCore/Models/CircuitBreakerOptions.cs
  20. 136
      src/X1.DynamicClientCore/Models/DynamicHttpClientException.cs
  21. 43
      src/X1.DynamicClientCore/Models/HttpRequestOptions.cs
  22. 77
      src/X1.DynamicClientCore/Models/ServiceEndpoint.cs
  23. 20
      src/X1.DynamicClientCore/X1.DynamicClientCore.csproj

10
X1.sln

@ -1,4 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
@ -16,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X1.Infrastructure", "src\X1
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X1.WebSocket", "src\X1.WebSocket\X1.WebSocket.csproj", "{155D6C93-9A46-45CD-B21F-0F60719515D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X1.DynamicClientCore", "src\X1.DynamicClientCore\X1.DynamicClientCore.csproj", "{6266232F-62BA-47E8-9046-7EA01E3DFD01}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -46,6 +49,10 @@ Global
{155D6C93-9A46-45CD-B21F-0F60719515D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{155D6C93-9A46-45CD-B21F-0F60719515D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{155D6C93-9A46-45CD-B21F-0F60719515D0}.Release|Any CPU.Build.0 = Release|Any CPU
{6266232F-62BA-47E8-9046-7EA01E3DFD01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6266232F-62BA-47E8-9046-7EA01E3DFD01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6266232F-62BA-47E8-9046-7EA01E3DFD01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6266232F-62BA-47E8-9046-7EA01E3DFD01}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -57,5 +64,6 @@ Global
{FE169440-2554-4810-97D6-CC44808AAA56} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
{6CF192C7-060B-4328-B9F4-B05BD7401232} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
{155D6C93-9A46-45CD-B21F-0F60719515D0} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
{6266232F-62BA-47E8-9046-7EA01E3DFD01} = {C0426B52-8F01-41DE-966C-ADC8DD078638}
EndGlobalSection
EndGlobal

234
src/X1.DynamicClientCore/Core/DynamicHttpClient.Core.cs

@ -0,0 +1,234 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Core
{
/// <summary>
/// DynamicHttpClient 核心执行部分
/// 包含核心的HTTP请求执行逻辑
/// </summary>
public partial class DynamicHttpClient
{
/// <summary>
/// 执行HTTP请求的统一方法
/// </summary>
private async Task<T?> ExecuteRequestAsync<T>(string serviceName, string endpoint, HttpMethod method,
object? data, RequestOptions? options, CancellationToken cancellationToken = default)
{
_logger.LogDebug("开始执行异步请求: {ServiceName}:{Endpoint}, 方法: {Method}", serviceName, endpoint, method);
int requestTimeout = 0; // 声明超时变量,供异常处理使用
try
{
// 1. 获取服务端点
_logger.LogDebug("正在获取服务端点: {ServiceName}", serviceName);
var serviceEndpoint = _endpointManager.GetEndpoint(serviceName);
if (serviceEndpoint == null)
{
_logger.LogWarning("服务端点未找到: {ServiceName}", serviceName);
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 未找到",
DynamicHttpClientExceptionType.ServiceNotFound,
serviceName,
endpoint);
}
if (!serviceEndpoint.Enabled)
{
_logger.LogWarning("服务端点已禁用: {ServiceName}", serviceName);
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 已禁用",
DynamicHttpClientExceptionType.ServiceDisabled,
serviceName,
endpoint);
}
_logger.LogDebug("服务端点获取成功: {ServiceName}, URL: {FullUrl}, 超时: {Timeout}s",
serviceName, serviceEndpoint.GetFullUrl(), serviceEndpoint.Timeout);
// 2. 创建HTTP客户端
_logger.LogDebug("正在创建HTTP客户端");
var httpClient = _httpClientFactory.CreateClient();
requestTimeout = options?.Timeout ?? serviceEndpoint.Timeout;
httpClient.Timeout = TimeSpan.FromSeconds(requestTimeout);
_logger.LogDebug("HTTP客户端创建完成,超时设置: {Timeout}s", requestTimeout);
// 3. 构建请求URL
var url = $"{serviceEndpoint.GetFullUrl()}/{endpoint.TrimStart('/')}";
_logger.LogDebug("构建请求URL: {Url}", url);
var request = new HttpRequestMessage(method, url);
// 4. 添加请求头
if (options?.Headers != null)
{
_logger.LogDebug("添加自定义请求头,数量: {HeaderCount}", options.Headers.Count);
foreach (var header in options.Headers)
{
request.Headers.Add(header.Key, header.Value);
_logger.LogDebug("添加请求头: {Key} = {Value}", header.Key, header.Value);
}
}
else
{
// 添加默认的JSON请求头
request.Headers.Add("Accept", "application/json");
_logger.LogDebug("添加默认JSON请求头: Accept = application/json");
}
// 5. 添加请求体
if (data != null && (method == HttpMethod.Post || method == HttpMethod.Put))
{
_logger.LogDebug("准备序列化请求数据,数据类型: {DataType}", data.GetType().Name);
try
{
var json = JsonConvert.SerializeObject(data);
_logger.LogDebug("请求数据序列化成功,长度: {JsonLength} 字符", json.Length);
request.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
_logger.LogDebug("请求体设置完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "请求数据序列化失败: {ServiceName}:{Endpoint}", serviceName, endpoint);
throw new DynamicHttpClientException(
"请求数据序列化失败",
DynamicHttpClientExceptionType.SerializationError,
serviceName,
endpoint,
innerException: ex);
}
}
else
{
_logger.LogDebug("无请求体数据");
}
// 6. 获取熔断器并执行请求
HttpResponseMessage response;
if (options?.EnableCircuitBreaker == true)
{
_logger.LogDebug("启用熔断器保护");
var circuitBreaker = _circuitBreakerManager.GetOrCreateCircuitBreaker(
serviceEndpoint.Name, options?.CircuitBreaker);
_logger.LogDebug("熔断器获取成功: {ServiceName}", serviceEndpoint.Name);
_logger.LogInformation("发送请求(带熔断器): {Method} {Url}", method, url);
response = await circuitBreaker.ExecuteAsync(async () => await httpClient.SendAsync(request, cancellationToken));
}
else
{
_logger.LogDebug("跳过熔断器保护");
_logger.LogInformation("发送请求(无熔断器): {Method} {Url}", method, url);
response = await httpClient.SendAsync(request, cancellationToken);
}
_logger.LogDebug("收到响应: {StatusCode} {ReasonPhrase}", response.StatusCode, response.ReasonPhrase);
if (response.IsSuccessStatusCode)
{
_logger.LogDebug("开始读取响应内容");
var content = await response.Content.ReadAsStringAsync(cancellationToken);
_logger.LogDebug("响应内容读取完成,长度: {ContentLength} 字符", content.Length);
_logger.LogInformation("请求成功: {StatusCode}", response.StatusCode);
if (typeof(T) == typeof(string))
{
_logger.LogDebug("返回字符串类型响应");
return (T)(object)content;
}
_logger.LogDebug("开始反序列化响应数据,目标类型: {TargetType}", typeof(T).Name);
try
{
var result = JsonConvert.DeserializeObject<T>(content);
_logger.LogDebug("响应数据反序列化成功");
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "响应数据反序列化失败: {ServiceName}:{Endpoint}, 目标类型: {TargetType}",
serviceName, endpoint, typeof(T).Name);
throw new DynamicHttpClientException(
"响应数据反序列化失败",
DynamicHttpClientExceptionType.SerializationError,
serviceName,
endpoint,
(int)response.StatusCode,
ex);
}
}
else
{
_logger.LogError("请求失败: {StatusCode} {ReasonPhrase}, URL: {Url}",
response.StatusCode, response.ReasonPhrase, url);
throw new DynamicHttpClientException(
$"HTTP请求失败: {response.StatusCode} {response.ReasonPhrase}",
DynamicHttpClientExceptionType.HttpRequestFailed,
serviceName,
endpoint,
(int)response.StatusCode);
}
}
catch (DynamicHttpClientException ex)
{
// 记录自定义异常,然后重新抛出
_logger.LogError(ex, "动态HTTP客户端异步异常: {ServiceName}:{Endpoint}, 类型: {ExceptionType}, 消息: {Message}",
serviceName, endpoint, ex.ExceptionType, ex.Message);
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
// 超时异常
_logger.LogWarning("异步请求超时: {ServiceName}:{Endpoint}, 超时时间: {Timeout}s",
serviceName, endpoint, requestTimeout);
throw new DynamicHttpClientException(
"请求超时",
DynamicHttpClientExceptionType.Timeout,
serviceName,
endpoint,
innerException: ex);
}
catch (OperationCanceledException ex)
{
// 请求被取消
_logger.LogInformation("异步请求被取消: {ServiceName}:{Endpoint}", serviceName, endpoint);
throw new DynamicHttpClientException(
"请求被取消",
DynamicHttpClientExceptionType.RequestCanceled,
serviceName,
endpoint,
innerException: ex);
}
catch (HttpRequestException ex)
{
// HTTP请求异常
_logger.LogError(ex, "HTTP异步请求异常: {ServiceName}:{Endpoint}, 消息: {Message}",
serviceName, endpoint, ex.Message);
throw new DynamicHttpClientException(
"网络请求失败",
DynamicHttpClientExceptionType.NetworkError,
serviceName,
endpoint,
innerException: ex);
}
catch (Exception ex)
{
// 其他未知异常
_logger.LogError(ex, "异步请求未知异常: {ServiceName}:{Endpoint}, 异常类型: {ExceptionType}, 消息: {Message}",
serviceName, endpoint, ex.GetType().Name, ex.Message);
throw new DynamicHttpClientException(
"请求执行失败",
DynamicHttpClientExceptionType.Unknown,
serviceName,
endpoint,
innerException: ex);
}
finally
{
_logger.LogDebug("异步请求执行完成: {ServiceName}:{Endpoint}", serviceName, endpoint);
}
}
}
}

49
src/X1.DynamicClientCore/Core/DynamicHttpClient.Sync.cs

@ -0,0 +1,49 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Core
{
/// <summary>
/// DynamicHttpClient 同步方法部分
/// 提供同步HTTP请求功能
/// </summary>
public partial class DynamicHttpClient
{
#region 同步HTTP方法
/// <summary>
/// 发送同步GET请求
/// </summary>
public T? Get<T>(string serviceName, string endpoint, RequestOptions? options = null)
{
return ExecuteRequest<T>(serviceName, endpoint, HttpMethod.Get, null, options);
}
/// <summary>
/// 发送同步POST请求
/// </summary>
public T? Post<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null)
{
return ExecuteRequest<T>(serviceName, endpoint, HttpMethod.Post, data, options);
}
/// <summary>
/// 发送同步PUT请求
/// </summary>
public T? Put<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null)
{
return ExecuteRequest<T>(serviceName, endpoint, HttpMethod.Put, data, options);
}
/// <summary>
/// 发送同步DELETE请求
/// </summary>
public T? Delete<T>(string serviceName, string endpoint, RequestOptions? options = null)
{
return ExecuteRequest<T>(serviceName, endpoint, HttpMethod.Delete, null, options);
}
#endregion
}
}

225
src/X1.DynamicClientCore/Core/DynamicHttpClient.SyncCore.cs

@ -0,0 +1,225 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Polly;
using Polly.CircuitBreaker;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Core
{
/// <summary>
/// DynamicHttpClient 同步核心执行部分
/// 包含同步HTTP请求的核心执行逻辑
/// </summary>
public partial class DynamicHttpClient
{
/// <summary>
/// 执行同步HTTP请求的统一方法
/// 避免使用 GetAwaiter().GetResult() 防止死锁
/// </summary>
private T? ExecuteRequest<T>(string serviceName, string endpoint, HttpMethod method, object? data, RequestOptions? options)
{
_logger.LogDebug("开始执行同步请求: {ServiceName}:{Endpoint}, 方法: {Method}", serviceName, endpoint, method);
int requestTimeout = 0; // 声明超时变量,供异常处理使用
try
{
// 1. 获取服务端点
_logger.LogDebug("正在获取服务端点: {ServiceName}", serviceName);
var serviceEndpoint = _endpointManager.GetEndpoint(serviceName);
if (serviceEndpoint == null)
{
_logger.LogWarning("服务端点未找到: {ServiceName}", serviceName);
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 未找到",
DynamicHttpClientExceptionType.ServiceNotFound,
serviceName,
endpoint);
}
if (!serviceEndpoint.Enabled)
{
_logger.LogWarning("服务端点已禁用: {ServiceName}", serviceName);
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 已禁用",
DynamicHttpClientExceptionType.ServiceDisabled,
serviceName,
endpoint);
}
_logger.LogDebug("服务端点获取成功: {ServiceName}, URL: {FullUrl}, 超时: {Timeout}s",
serviceName, serviceEndpoint.GetFullUrl(), serviceEndpoint.Timeout);
// 2. 创建HTTP客户端
_logger.LogDebug("正在创建HTTP客户端");
var httpClient = _httpClientFactory.CreateClient();
requestTimeout = options?.Timeout ?? serviceEndpoint.Timeout;
httpClient.Timeout = TimeSpan.FromSeconds(requestTimeout);
_logger.LogDebug("HTTP客户端创建完成,超时设置: {Timeout}s", requestTimeout);
// 3. 构建请求URL
var url = $"{serviceEndpoint.GetFullUrl()}/{endpoint.TrimStart('/')}";
_logger.LogDebug("构建请求URL: {Url}", url);
var request = new HttpRequestMessage(method, url);
// 4. 添加请求头
if (options?.Headers != null)
{
_logger.LogDebug("添加自定义请求头,数量: {HeaderCount}", options.Headers.Count);
foreach (var header in options.Headers)
{
request.Headers.Add(header.Key, header.Value);
_logger.LogDebug("添加请求头: {Key} = {Value}", header.Key, header.Value);
}
}
else
{
// 添加默认的JSON请求头
request.Headers.Add("Accept", "application/json");
_logger.LogDebug("添加默认JSON请求头: Accept = application/json");
}
// 5. 添加请求体
if (data != null && (method == HttpMethod.Post || method == HttpMethod.Put))
{
_logger.LogDebug("准备序列化请求数据,数据类型: {DataType}", data.GetType().Name);
try
{
var json = JsonConvert.SerializeObject(data);
_logger.LogDebug("请求数据序列化成功,长度: {JsonLength} 字符", json.Length);
request.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
_logger.LogDebug("请求体设置完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "请求数据序列化失败: {ServiceName}:{Endpoint}", serviceName, endpoint);
throw new DynamicHttpClientException(
"请求数据序列化失败",
DynamicHttpClientExceptionType.SerializationError,
serviceName,
endpoint,
innerException: ex);
}
}
else
{
_logger.LogDebug("无请求体数据");
}
// 6. 获取熔断器并执行同步请求
HttpResponseMessage response;
if (options?.EnableCircuitBreaker == true)
{
_logger.LogDebug("启用同步熔断器保护");
var circuitBreaker = _circuitBreakerManager.GetOrCreateSyncCircuitBreaker(
serviceEndpoint.Name, options?.CircuitBreaker);
_logger.LogDebug("同步熔断器获取成功: {ServiceName}", serviceEndpoint.Name);
_logger.LogInformation("发送同步请求(带熔断器): {Method} {Url}", method, url);
response = circuitBreaker.Execute(() => httpClient.Send(request));
}
else
{
_logger.LogDebug("跳过同步熔断器保护");
_logger.LogInformation("发送同步请求(无熔断器): {Method} {Url}", method, url);
response = httpClient.Send(request);
}
_logger.LogDebug("收到响应: {StatusCode} {ReasonPhrase}", response.StatusCode, response.ReasonPhrase);
if (response.IsSuccessStatusCode)
{
_logger.LogDebug("开始读取响应内容");
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
_logger.LogDebug("响应内容读取完成,长度: {ContentLength} 字符", content.Length);
_logger.LogInformation("同步请求成功: {StatusCode}", response.StatusCode);
if (typeof(T) == typeof(string))
{
_logger.LogDebug("返回字符串类型响应");
return (T)(object)content;
}
_logger.LogDebug("开始反序列化响应数据,目标类型: {TargetType}", typeof(T).Name);
try
{
var result = JsonConvert.DeserializeObject<T>(content);
_logger.LogDebug("响应数据反序列化成功");
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "响应数据反序列化失败: {ServiceName}:{Endpoint}, 目标类型: {TargetType}",
serviceName, endpoint, typeof(T).Name);
throw new DynamicHttpClientException(
"响应数据反序列化失败",
DynamicHttpClientExceptionType.SerializationError,
serviceName,
endpoint,
(int)response.StatusCode,
ex);
}
}
else
{
_logger.LogError("同步请求失败: {StatusCode} {ReasonPhrase}, URL: {Url}",
response.StatusCode, response.ReasonPhrase, url);
throw new DynamicHttpClientException(
$"HTTP请求失败: {response.StatusCode} {response.ReasonPhrase}",
DynamicHttpClientExceptionType.HttpRequestFailed,
serviceName,
endpoint,
(int)response.StatusCode);
}
}
catch (DynamicHttpClientException ex)
{
// 记录自定义异常,然后重新抛出
_logger.LogError(ex, "动态HTTP客户端同步异常: {ServiceName}:{Endpoint}, 类型: {ExceptionType}, 消息: {Message}",
serviceName, endpoint, ex.ExceptionType, ex.Message);
throw;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
// 超时异常
_logger.LogWarning("同步请求超时: {ServiceName}:{Endpoint}, 超时时间: {Timeout}s",
serviceName, endpoint, requestTimeout);
throw new DynamicHttpClientException(
"请求超时",
DynamicHttpClientExceptionType.Timeout,
serviceName,
endpoint,
innerException: ex);
}
catch (HttpRequestException ex)
{
// HTTP请求异常
_logger.LogError(ex, "HTTP同步请求异常: {ServiceName}:{Endpoint}, 消息: {Message}",
serviceName, endpoint, ex.Message);
throw new DynamicHttpClientException(
"网络请求失败",
DynamicHttpClientExceptionType.NetworkError,
serviceName,
endpoint,
innerException: ex);
}
catch (Exception ex)
{
// 其他未知异常
_logger.LogError(ex, "同步请求未知异常: {ServiceName}:{Endpoint}, 异常类型: {ExceptionType}, 消息: {Message}",
serviceName, endpoint, ex.GetType().Name, ex.Message);
throw new DynamicHttpClientException(
"请求执行失败",
DynamicHttpClientExceptionType.Unknown,
serviceName,
endpoint,
innerException: ex);
}
finally
{
_logger.LogDebug("同步请求执行完成: {ServiceName}:{Endpoint}", serviceName, endpoint);
}
}
}
}

59
src/X1.DynamicClientCore/Core/DynamicHttpClient.cs

@ -0,0 +1,59 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using X1.DynamicClientCore.Interfaces;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Core
{
/// <summary>
/// 动态HTTP客户端实现
/// 支持同步/异步HTTP请求和文件操作
/// 使用partial类拆分,便于维护和扩展
/// </summary>
public partial class DynamicHttpClient : IDynamicHttpClient
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServiceEndpointManager _endpointManager;
private readonly ICircuitBreakerManager _circuitBreakerManager;
private readonly ILogger<DynamicHttpClient> _logger;
/// <summary>
/// 初始化动态HTTP客户端
/// </summary>
public DynamicHttpClient(
IHttpClientFactory httpClientFactory,
IServiceEndpointManager endpointManager,
ICircuitBreakerManager circuitBreakerManager,
ILogger<DynamicHttpClient> logger)
{
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
_endpointManager = endpointManager ?? throw new ArgumentNullException(nameof(endpointManager));
_circuitBreakerManager = circuitBreakerManager ?? throw new ArgumentNullException(nameof(circuitBreakerManager));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
#region 异步HTTP方法
public async Task<T?> GetAsync<T>(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
return await ExecuteRequestAsync<T>(serviceName, endpoint, HttpMethod.Get, null, options, cancellationToken);
}
public async Task<T?> PostAsync<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
return await ExecuteRequestAsync<T>(serviceName, endpoint, HttpMethod.Post, data, options, cancellationToken);
}
public async Task<T?> PutAsync<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
return await ExecuteRequestAsync<T>(serviceName, endpoint, HttpMethod.Put, data, options, cancellationToken);
}
public async Task<T?> DeleteAsync<T>(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
return await ExecuteRequestAsync<T>(serviceName, endpoint, HttpMethod.Delete, null, options, cancellationToken);
}
#endregion
}
}

200
src/X1.DynamicClientCore/Extensions/ServiceCollectionExtensions.cs

@ -0,0 +1,200 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using X1.DynamicClientCore.Interfaces;
using X1.DynamicClientCore.Models;
using X1.DynamicClientCore.Core;
using X1.DynamicClientCore.Infrastructure;
namespace X1.DynamicClientCore.Extensions
{
/// <summary>
/// 服务集合扩展类
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// 添加动态服务客户端
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="configure">配置委托</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddDynamicServiceClient(
this IServiceCollection services,
Action<DynamicServiceClientOptions>? configure = null)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
// 注册HttpClient工厂
services.AddHttpClient();
// 配置选项
var options = new DynamicServiceClientOptions();
configure?.Invoke(options);
// 创建并配置服务端点管理器
var endpointManager = CreateAndConfigureEndpointManager(options);
// 注册核心服务(单例)
services.AddSingleton<IServiceEndpointManager>(endpointManager);
services.AddSingleton<ICircuitBreakerManager, CircuitBreakerManager>();
// 注册动态HTTP客户端(单例)
services.AddSingleton<IDynamicHttpClient, DynamicHttpClient>();
services.AddSingleton<IHttpClientBase, DynamicHttpClient>();
services.AddSingleton<IAsyncHttpClient, DynamicHttpClient>();
services.AddSingleton<ISyncHttpClient, DynamicHttpClient>();
return services;
}
/// <summary>
/// 创建并配置服务端点管理器
/// </summary>
/// <param name="options">配置选项</param>
/// <returns>配置好的服务端点管理器</returns>
private static ServiceEndpointManager CreateAndConfigureEndpointManager(DynamicServiceClientOptions options)
{
var endpointManager = new ServiceEndpointManager();
if (options.PreConfiguredEndpoints?.Any() == true)
{
var validEndpoints = options.PreConfiguredEndpoints
.Where(endpoint => endpoint != null)
.ToList();
// 验证端点配置
if (options.ValidateEndpoints)
{
var invalidEndpoints = validEndpoints
.Where(endpoint => !endpoint.IsValid())
.ToList();
if (invalidEndpoints.Any())
{
var invalidNames = string.Join(", ", invalidEndpoints.Select(e => e.Name));
System.Diagnostics.Debug.WriteLine($"Warning: Found {invalidEndpoints.Count} invalid endpoints: {invalidNames}");
}
validEndpoints = validEndpoints.Where(endpoint => endpoint.IsValid()).ToList();
}
// 添加有效端点
foreach (var endpoint in validEndpoints)
{
try
{
endpointManager.AddOrUpdateEndpoint(endpoint);
System.Diagnostics.Debug.WriteLine($"Successfully added endpoint: {endpoint.Name} -> {endpoint.GetFullUrl()}");
}
catch (Exception ex)
{
// 记录错误但继续处理其他端点
System.Diagnostics.Debug.WriteLine($"Failed to add endpoint {endpoint?.Name}: {ex.Message}");
}
}
// 连接性验证(可选)
if (options.ValidateEndpointConnectivity && validEndpoints.Any())
{
ValidateEndpointConnectivity(endpointManager, options.ConnectivityValidationTimeout);
}
}
return endpointManager;
}
/// <summary>
/// 验证服务端点连接性
/// </summary>
/// <param name="endpointManager">服务端点管理器</param>
/// <param name="timeoutSeconds">超时时间(秒)</param>
private static void ValidateEndpointConnectivity(ServiceEndpointManager endpointManager, int timeoutSeconds)
{
var endpoints = endpointManager.GetAllEndpoints().ToList();
foreach (var endpoint in endpoints)
{
try
{
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
var url = endpoint.GetFullUrl();
var response = httpClient.GetAsync(url).Result;
System.Diagnostics.Debug.WriteLine($"Connectivity check passed for {endpoint.Name}: {url}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Connectivity check failed for {endpoint.Name}: {ex.Message}");
}
}
}
/// <summary>
/// 添加动态服务客户端(带预配置端点)
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="endpoints">预配置的服务端点</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddDynamicServiceClient(
this IServiceCollection services,
IEnumerable<ServiceEndpoint> endpoints)
{
return services.AddDynamicServiceClient(options =>
{
options.PreConfiguredEndpoints = endpoints.ToList();
});
}
/// <summary>
/// 添加动态服务客户端(带单个预配置端点)
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="endpoint">预配置的服务端点</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddDynamicServiceClient(
this IServiceCollection services,
ServiceEndpoint endpoint)
{
return services.AddDynamicServiceClient(new[] { endpoint });
}
}
/// <summary>
/// 动态服务客户端配置选项
/// </summary>
public class DynamicServiceClientOptions
{
/// <summary>
/// 预配置的服务端点
/// </summary>
public List<ServiceEndpoint>? PreConfiguredEndpoints { get; set; }
/// <summary>
/// 默认请求选项
/// </summary>
public RequestOptions? DefaultRequestOptions { get; set; }
/// <summary>
/// 是否启用默认日志记录
/// </summary>
public bool EnableDefaultLogging { get; set; } = true;
/// <summary>
/// 是否验证服务端点配置
/// </summary>
public bool ValidateEndpoints { get; set; } = true;
/// <summary>
/// 是否在启动时验证服务端点可达性
/// </summary>
public bool ValidateEndpointConnectivity { get; set; } = false;
/// <summary>
/// 连接性验证超时时间(秒)
/// </summary>
public int ConnectivityValidationTimeout { get; set; } = 5;
}
}

101
src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileDownload.cs

@ -0,0 +1,101 @@
using Microsoft.Extensions.Logging;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Core
{
/// <summary>
/// DynamicHttpClient 文件下载部分
/// 提供文件下载功能
/// </summary>
public partial class DynamicHttpClient
{
#region 文件下载方法
/// <summary>
/// 异步下载文件到指定路径
/// </summary>
public async Task<bool> DownloadFileAsync(string serviceName, string endpoint, string localFilePath, RequestOptions? options = null)
{
try
{
var fileBytes = await DownloadFileAsBytesAsync(serviceName, endpoint, options);
if (fileBytes != null)
{
await File.WriteAllBytesAsync(localFilePath, fileBytes);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "文件下载失败: {ServiceName}:{Endpoint} -> {LocalPath}", serviceName, endpoint, localFilePath);
return false;
}
}
/// <summary>
/// 异步下载文件到流
/// </summary>
public async Task<bool> DownloadFileToStreamAsync(string serviceName, string endpoint, Stream outputStream, RequestOptions? options = null)
{
try
{
var fileBytes = await DownloadFileAsBytesAsync(serviceName, endpoint, options);
if (fileBytes != null)
{
await outputStream.WriteAsync(fileBytes);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "文件下载到流失败: {ServiceName}:{Endpoint}", serviceName, endpoint);
return false;
}
}
/// <summary>
/// 异步下载文件为字节数组
/// </summary>
public async Task<byte[]?> DownloadFileAsBytesAsync(string serviceName, string endpoint, RequestOptions? options = null)
{
try
{
var response = await ExecuteRequestAsync<byte[]>(serviceName, endpoint, HttpMethod.Get, null, options);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "文件下载为字节数组失败: {ServiceName}:{Endpoint}", serviceName, endpoint);
return null;
}
}
/// <summary>
/// 同步下载文件到指定路径
/// </summary>
public bool DownloadFile(string serviceName, string endpoint, string localFilePath, RequestOptions? options = null)
{
return DownloadFileAsync(serviceName, endpoint, localFilePath, options).GetAwaiter().GetResult();
}
/// <summary>
/// 同步下载文件到流
/// </summary>
public bool DownloadFileToStream(string serviceName, string endpoint, Stream outputStream, RequestOptions? options = null)
{
return DownloadFileToStreamAsync(serviceName, endpoint, outputStream, options).GetAwaiter().GetResult();
}
/// <summary>
/// 同步下载文件为字节数组
/// </summary>
public byte[]? DownloadFileAsBytes(string serviceName, string endpoint, RequestOptions? options = null)
{
return DownloadFileAsBytesAsync(serviceName, endpoint, options).GetAwaiter().GetResult();
}
#endregion
}
}

31
src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileInfo.cs

@ -0,0 +1,31 @@
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Core
{
/// <summary>
/// DynamicHttpClient 文件信息部分
/// 提供文件信息获取功能
/// </summary>
public partial class DynamicHttpClient
{
#region 文件信息方法
/// <summary>
/// 异步获取文件信息
/// </summary>
public async Task<T?> GetFileInfoAsync<T>(string serviceName, string endpoint, RequestOptions? options = null)
{
return await GetAsync<T>(serviceName, endpoint, options);
}
/// <summary>
/// 同步获取文件信息
/// </summary>
public T? GetFileInfo<T>(string serviceName, string endpoint, RequestOptions? options = null)
{
return GetFileInfoAsync<T>(serviceName, endpoint, options).GetAwaiter().GetResult();
}
#endregion
}
}

314
src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileUpload.cs

@ -0,0 +1,314 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Core
{
/// <summary>
/// DynamicHttpClient 文件上传部分
/// 提供文件上传功能
/// </summary>
public partial class DynamicHttpClient
{
#region 文件上传方法
/// <summary>
/// 异步上传单个文件
/// </summary>
public async Task<T?> UploadFileAsync<T>(string serviceName, string endpoint, string filePath, string formFieldName = "file", RequestOptions? options = null)
{
if (!File.Exists(filePath))
{
throw new DynamicHttpClientException(
$"文件不存在: {filePath}",
DynamicHttpClientExceptionType.ConfigurationError,
serviceName,
endpoint);
}
using var fileStream = File.OpenRead(filePath);
var fileName = Path.GetFileName(filePath);
return await UploadFileStreamAsync<T>(serviceName, endpoint, fileStream, fileName, formFieldName, options);
}
/// <summary>
/// 异步上传多个文件
/// </summary>
public async Task<T?> UploadFilesAsync<T>(string serviceName, string endpoint, IEnumerable<string> filePaths, string formFieldName = "files", RequestOptions? options = null)
{
var files = filePaths.ToList();
if (!files.Any())
{
throw new DynamicHttpClientException(
"文件列表为空",
DynamicHttpClientExceptionType.ConfigurationError,
serviceName,
endpoint);
}
return await ExecuteFileUploadRequestAsync<T>(serviceName, endpoint, files, formFieldName, options);
}
/// <summary>
/// 异步上传文件流
/// </summary>
public async Task<T?> UploadFileStreamAsync<T>(string serviceName, string endpoint, Stream fileStream, string fileName, string formFieldName = "file", RequestOptions? options = null)
{
var files = new List<(Stream Stream, string FileName)> { (fileStream, fileName) };
return await ExecuteFileUploadRequestAsync<T>(serviceName, endpoint, files, formFieldName, options);
}
/// <summary>
/// 同步上传单个文件
/// </summary>
public T? UploadFile<T>(string serviceName, string endpoint, string filePath, string formFieldName = "file", RequestOptions? options = null)
{
return UploadFileAsync<T>(serviceName, endpoint, filePath, formFieldName, options).GetAwaiter().GetResult();
}
/// <summary>
/// 同步上传多个文件
/// </summary>
public T? UploadFiles<T>(string serviceName, string endpoint, IEnumerable<string> filePaths, string formFieldName = "files", RequestOptions? options = null)
{
return UploadFilesAsync<T>(serviceName, endpoint, filePaths, formFieldName, options).GetAwaiter().GetResult();
}
#endregion
/// <summary>
/// 执行文件上传请求
/// </summary>
private async Task<T?> ExecuteFileUploadRequestAsync<T>(string serviceName, string endpoint, List<string> filePaths, string formFieldName, RequestOptions? options)
{
try
{
// 1. 获取服务端点
var serviceEndpoint = _endpointManager.GetEndpoint(serviceName);
if (serviceEndpoint == null)
{
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 未找到",
DynamicHttpClientExceptionType.ServiceNotFound,
serviceName,
endpoint);
}
if (!serviceEndpoint.Enabled)
{
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 已禁用",
DynamicHttpClientExceptionType.ServiceDisabled,
serviceName,
endpoint);
}
// 2. 创建HTTP客户端
var httpClient = _httpClientFactory.CreateClient();
var timeout = options?.Timeout ?? serviceEndpoint.Timeout;
httpClient.Timeout = TimeSpan.FromSeconds(timeout);
// 3. 构建请求URL
var url = $"{serviceEndpoint.GetFullUrl()}/{endpoint.TrimStart('/')}";
var request = new HttpRequestMessage(HttpMethod.Post, url);
// 4. 创建MultipartFormDataContent
var content = new MultipartFormDataContent();
foreach (var filePath in filePaths)
{
if (File.Exists(filePath))
{
var fileStream = File.OpenRead(filePath);
var fileName = Path.GetFileName(filePath);
var streamContent = new StreamContent(fileStream);
content.Add(streamContent, formFieldName, fileName);
}
}
request.Content = content;
// 5. 添加请求头
if (options?.Headers != null)
{
foreach (var header in options.Headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
// 6. 获取熔断器并执行请求
var circuitBreaker = _circuitBreakerManager.GetOrCreateCircuitBreaker(
serviceEndpoint.Name, options?.CircuitBreaker);
_logger.LogInformation("上传文件: {Method} {Url}, 文件数量: {FileCount}", HttpMethod.Post, url, filePaths.Count);
var response = await circuitBreaker.ExecuteAsync(async () => await httpClient.SendAsync(request));
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogInformation("文件上传成功: {StatusCode}", response.StatusCode);
if (typeof(T) == typeof(string))
{
return (T)(object)responseContent;
}
try
{
return JsonConvert.DeserializeObject<T>(responseContent);
}
catch (Exception ex)
{
throw new DynamicHttpClientException(
"响应数据反序列化失败",
DynamicHttpClientExceptionType.SerializationError,
serviceName,
endpoint,
(int)response.StatusCode,
ex);
}
}
else
{
_logger.LogError("文件上传失败: {StatusCode} {ReasonPhrase}", response.StatusCode, response.ReasonPhrase);
throw new DynamicHttpClientException(
$"文件上传失败: {response.StatusCode} {response.ReasonPhrase}",
DynamicHttpClientExceptionType.HttpRequestFailed,
serviceName,
endpoint,
(int)response.StatusCode);
}
}
catch (DynamicHttpClientException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "文件上传异常: {ServiceName}:{Endpoint}", serviceName, endpoint);
throw new DynamicHttpClientException(
"文件上传失败",
DynamicHttpClientExceptionType.Unknown,
serviceName,
endpoint,
innerException: ex);
}
}
/// <summary>
/// 执行文件流上传请求
/// </summary>
private async Task<T?> ExecuteFileUploadRequestAsync<T>(string serviceName, string endpoint, List<(Stream Stream, string FileName)> files, string formFieldName, RequestOptions? options)
{
try
{
// 1. 获取服务端点
var serviceEndpoint = _endpointManager.GetEndpoint(serviceName);
if (serviceEndpoint == null)
{
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 未找到",
DynamicHttpClientExceptionType.ServiceNotFound,
serviceName,
endpoint);
}
if (!serviceEndpoint.Enabled)
{
throw new DynamicHttpClientException(
$"服务 '{serviceName}' 已禁用",
DynamicHttpClientExceptionType.ServiceDisabled,
serviceName,
endpoint);
}
// 2. 创建HTTP客户端
var httpClient = _httpClientFactory.CreateClient();
var timeout = options?.Timeout ?? serviceEndpoint.Timeout;
httpClient.Timeout = TimeSpan.FromSeconds(timeout);
// 3. 构建请求URL
var url = $"{serviceEndpoint.GetFullUrl()}/{endpoint.TrimStart('/')}";
var request = new HttpRequestMessage(HttpMethod.Post, url);
// 4. 创建MultipartFormDataContent
var content = new MultipartFormDataContent();
foreach (var (stream, fileName) in files)
{
var streamContent = new StreamContent(stream);
content.Add(streamContent, formFieldName, fileName);
}
request.Content = content;
// 5. 添加请求头
if (options?.Headers != null)
{
foreach (var header in options.Headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
// 6. 获取熔断器并执行请求
var circuitBreaker = _circuitBreakerManager.GetOrCreateCircuitBreaker(
serviceEndpoint.Name, options?.CircuitBreaker);
_logger.LogInformation("上传文件流: {Method} {Url}, 文件数量: {FileCount}", HttpMethod.Post, url, files.Count);
var response = await circuitBreaker.ExecuteAsync(async () => await httpClient.SendAsync(request));
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogInformation("文件流上传成功: {StatusCode}", response.StatusCode);
if (typeof(T) == typeof(string))
{
return (T)(object)responseContent;
}
try
{
return JsonConvert.DeserializeObject<T>(responseContent);
}
catch (Exception ex)
{
throw new DynamicHttpClientException(
"响应数据反序列化失败",
DynamicHttpClientExceptionType.SerializationError,
serviceName,
endpoint,
(int)response.StatusCode,
ex);
}
}
else
{
_logger.LogError("文件流上传失败: {StatusCode} {ReasonPhrase}", response.StatusCode, response.ReasonPhrase);
throw new DynamicHttpClientException(
$"文件流上传失败: {response.StatusCode} {response.ReasonPhrase}",
DynamicHttpClientExceptionType.HttpRequestFailed,
serviceName,
endpoint,
(int)response.StatusCode);
}
}
catch (DynamicHttpClientException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "文件流上传异常: {ServiceName}:{Endpoint}", serviceName, endpoint);
throw new DynamicHttpClientException(
"文件流上传失败",
DynamicHttpClientExceptionType.Unknown,
serviceName,
endpoint,
innerException: ex);
}
}
}
}

308
src/X1.DynamicClientCore/Infrastructure/CircuitBreakerManager.cs

@ -0,0 +1,308 @@
using Microsoft.Extensions.Logging;
using Polly;
using Polly.CircuitBreaker;
using Polly.Extensions.Http;
using X1.DynamicClientCore.Interfaces;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Infrastructure
{
/// <summary>
/// 熔断器管理器 - 负责管理每个服务的熔断器实例
/// </summary>
/// <remarks>
/// 为每个服务端点维护独立的熔断器,支持动态配置和状态监控
/// </remarks>
public class CircuitBreakerManager : ICircuitBreakerManager
{
private readonly Dictionary<string, AsyncCircuitBreakerPolicy> _circuitBreakers = new();
private readonly Dictionary<string, CircuitBreakerPolicy> _syncCircuitBreakers = new();
private readonly Dictionary<string, CircuitBreakerOptions> _options = new();
private readonly ILogger<CircuitBreakerManager> _logger;
private readonly object _lock = new();
/// <summary>
/// 初始化熔断器管理器
/// </summary>
/// <param name="logger">日志记录器</param>
public CircuitBreakerManager(ILogger<CircuitBreakerManager> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// 获取或创建服务的异步熔断器策略
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="options">熔断器配置</param>
/// <returns>异步熔断器策略</returns>
public AsyncCircuitBreakerPolicy GetOrCreateCircuitBreaker(string serviceName, CircuitBreakerOptions? options = null)
{
lock (_lock)
{
if (_circuitBreakers.TryGetValue(serviceName, out var existingPolicy))
{
// 如果配置发生变化,更新熔断器
if (options != null && HasOptionsChanged(serviceName, options))
{
_logger.LogInformation("更新服务 {ServiceName} 的异步熔断器配置", serviceName);
_circuitBreakers.Remove(serviceName);
_options.Remove(serviceName);
}
else
{
return existingPolicy;
}
}
// 创建新的异步熔断器策略
var policy = CreateCircuitBreakerPolicy(serviceName, options);
_circuitBreakers[serviceName] = policy;
_options[serviceName] = options ?? new CircuitBreakerOptions();
_logger.LogInformation("为服务 {ServiceName} 创建异步熔断器策略", serviceName);
return policy;
}
}
/// <summary>
/// 获取或创建服务的同步熔断器策略
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="options">熔断器配置</param>
/// <returns>同步熔断器策略</returns>
public CircuitBreakerPolicy GetOrCreateSyncCircuitBreaker(string serviceName, CircuitBreakerOptions? options = null)
{
lock (_lock)
{
if (_syncCircuitBreakers.TryGetValue(serviceName, out var existingPolicy))
{
// 如果配置发生变化,更新熔断器
if (options != null && HasOptionsChanged(serviceName, options))
{
_logger.LogInformation("更新服务 {ServiceName} 的同步熔断器配置", serviceName);
_syncCircuitBreakers.Remove(serviceName);
_options.Remove(serviceName);
}
else
{
return existingPolicy;
}
}
// 创建新的同步熔断器策略
var policy = CreateSyncCircuitBreakerPolicy(serviceName, options);
_syncCircuitBreakers[serviceName] = policy;
_options[serviceName] = options ?? new CircuitBreakerOptions();
_logger.LogInformation("为服务 {ServiceName} 创建同步熔断器策略", serviceName);
return policy;
}
}
/// <summary>
/// 获取熔断器状态
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>熔断器状态</returns>
public CircuitState? GetCircuitState(string serviceName)
{
lock (_lock)
{
// 优先返回异步熔断器状态,如果没有则返回同步熔断器状态
if (_circuitBreakers.TryGetValue(serviceName, out var asyncPolicy))
{
return asyncPolicy.CircuitState;
}
if (_syncCircuitBreakers.TryGetValue(serviceName, out var syncPolicy))
{
return syncPolicy.CircuitState;
}
return null;
}
}
/// <summary>
/// 手动重置熔断器
/// </summary>
/// <param name="serviceName">服务名称</param>
public void ResetCircuitBreaker(string serviceName)
{
lock (_lock)
{
bool reset = false;
if (_circuitBreakers.TryGetValue(serviceName, out var asyncPolicy))
{
asyncPolicy.Reset();
reset = true;
}
if (_syncCircuitBreakers.TryGetValue(serviceName, out var syncPolicy))
{
syncPolicy.Reset();
reset = true;
}
if (reset)
{
_logger.LogInformation("手动重置服务 {ServiceName} 的熔断器", serviceName);
}
}
}
/// <summary>
/// 移除服务的熔断器
/// </summary>
/// <param name="serviceName">服务名称</param>
public void RemoveCircuitBreaker(string serviceName)
{
lock (_lock)
{
bool removed = false;
if (_circuitBreakers.Remove(serviceName))
{
removed = true;
}
if (_syncCircuitBreakers.Remove(serviceName))
{
removed = true;
}
if (removed)
{
_options.Remove(serviceName);
_logger.LogInformation("移除服务 {ServiceName} 的熔断器", serviceName);
}
}
}
/// <summary>
/// 获取所有熔断器状态
/// </summary>
/// <returns>所有熔断器状态</returns>
public Dictionary<string, CircuitState> GetAllCircuitStates()
{
lock (_lock)
{
var states = new Dictionary<string, CircuitState>();
// 添加异步熔断器状态
foreach (var kvp in _circuitBreakers)
{
states[kvp.Key] = kvp.Value.CircuitState;
}
// 添加同步熔断器状态(如果异步熔断器不存在)
foreach (var kvp in _syncCircuitBreakers)
{
if (!states.ContainsKey(kvp.Key))
{
states[kvp.Key] = kvp.Value.CircuitState;
}
}
return states;
}
}
/// <summary>
/// 创建异步熔断器策略
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="options">熔断器配置</param>
/// <returns>异步熔断器策略</returns>
private AsyncCircuitBreakerPolicy CreateCircuitBreakerPolicy(string serviceName, CircuitBreakerOptions? options)
{
options ??= new CircuitBreakerOptions();
if (!options.Enabled)
{
// 如果禁用熔断器,返回一个永远不会熔断的策略
return Policy.Handle<Exception>().CircuitBreakerAsync(1000, TimeSpan.FromDays(365));
}
return Policy
.Handle<HttpRequestException>()
.Or<TimeoutException>()
.Or<OperationCanceledException>()
.AdvancedCircuitBreakerAsync(
failureThreshold: options.FailureRateThreshold,
samplingDuration: TimeSpan.FromSeconds(options.SamplingDuration),
minimumThroughput: options.MinimumThroughput,
durationOfBreak: TimeSpan.FromSeconds(options.DurationOfBreak),
onBreak: (exception, duration) =>
{
_logger.LogWarning("服务 {ServiceName} 异步熔断器打开,持续时间: {Duration}秒",
serviceName, duration.TotalSeconds);
},
onReset: () =>
{
_logger.LogInformation("服务 {ServiceName} 异步熔断器重置", serviceName);
},
onHalfOpen: () =>
{
_logger.LogInformation("服务 {ServiceName} 异步熔断器进入半开状态", serviceName);
});
}
/// <summary>
/// 创建同步熔断器策略
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="options">熔断器配置</param>
/// <returns>同步熔断器策略</returns>
private CircuitBreakerPolicy CreateSyncCircuitBreakerPolicy(string serviceName, CircuitBreakerOptions? options)
{
options ??= new CircuitBreakerOptions();
if (!options.Enabled)
{
// 如果禁用熔断器,返回一个永远不会熔断的策略
return Policy.Handle<Exception>().CircuitBreaker(1000, TimeSpan.FromDays(365));
}
return Policy
.Handle<HttpRequestException>()
.Or<TimeoutException>()
.Or<OperationCanceledException>()
.AdvancedCircuitBreaker(
failureThreshold: options.FailureRateThreshold,
samplingDuration: TimeSpan.FromSeconds(options.SamplingDuration),
minimumThroughput: options.MinimumThroughput,
durationOfBreak: TimeSpan.FromSeconds(options.DurationOfBreak),
onBreak: (exception, duration) =>
{
_logger.LogWarning("服务 {ServiceName} 同步熔断器打开,持续时间: {Duration}秒",
serviceName, duration.TotalSeconds);
},
onReset: () =>
{
_logger.LogInformation("服务 {ServiceName} 同步熔断器重置", serviceName);
},
onHalfOpen: () =>
{
_logger.LogInformation("服务 {ServiceName} 同步熔断器进入半开状态", serviceName);
});
}
/// <summary>
/// 检查配置是否发生变化
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="newOptions">新配置</param>
/// <returns>是否发生变化</returns>
private bool HasOptionsChanged(string serviceName, CircuitBreakerOptions newOptions)
{
if (!_options.TryGetValue(serviceName, out var existingOptions))
{
return true;
}
return existingOptions.Enabled != newOptions.Enabled
|| existingOptions.FailureThreshold != newOptions.FailureThreshold
|| existingOptions.DurationOfBreak != newOptions.DurationOfBreak
|| existingOptions.SamplingDuration != newOptions.SamplingDuration
|| existingOptions.MinimumThroughput != newOptions.MinimumThroughput
|| Math.Abs(existingOptions.FailureRateThreshold - newOptions.FailureRateThreshold) > 0.001
|| existingOptions.EnableHalfOpen != newOptions.EnableHalfOpen
|| existingOptions.HandledEventsAllowedBeforeBreaking != newOptions.HandledEventsAllowedBeforeBreaking;
}
}
}

121
src/X1.DynamicClientCore/Infrastructure/ServiceEndpointManager.cs

@ -0,0 +1,121 @@
using X1.DynamicClientCore.Interfaces;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Infrastructure
{
/// <summary>
/// 服务端点管理器 - 负责管理所有服务端点的配置信息
/// </summary>
/// <remarks>
/// 提供线程安全的服务端点存储和管理功能,支持动态添加、更新、删除和查询操作
/// </remarks>
public class ServiceEndpointManager : IServiceEndpointManager
{
/// <summary>
/// 服务端点存储字典
/// </summary>
private readonly Dictionary<string, ServiceEndpoint> _endpoints = new();
/// <summary>
/// 线程同步锁
/// </summary>
private readonly object _lock = new();
/// <summary>
/// 获取所有服务端点
/// </summary>
/// <returns>所有服务端点</returns>
public IEnumerable<ServiceEndpoint> GetAllEndpoints()
{
lock (_lock)
{
return _endpoints.Values.ToList();
}
}
/// <summary>
/// 获取服务端点
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>服务端点</returns>
public ServiceEndpoint? GetEndpoint(string serviceName)
{
lock (_lock)
{
return _endpoints.TryGetValue(serviceName, out var endpoint) ? endpoint : null;
}
}
/// <summary>
/// 添加或更新服务端点
/// </summary>
/// <param name="endpoint">服务端点</param>
public void AddOrUpdateEndpoint(ServiceEndpoint endpoint)
{
if (endpoint == null)
throw new ArgumentNullException(nameof(endpoint));
if (!endpoint.IsValid())
throw new ArgumentException("服务端点配置无效", nameof(endpoint));
lock (_lock)
{
_endpoints[endpoint.Name] = endpoint;
}
}
/// <summary>
/// 移除服务端点
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>是否成功移除</returns>
public bool RemoveEndpoint(string serviceName)
{
if (string.IsNullOrEmpty(serviceName))
throw new ArgumentNullException(nameof(serviceName));
lock (_lock)
{
return _endpoints.Remove(serviceName);
}
}
/// <summary>
/// 检查服务端点是否存在
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>是否存在</returns>
public bool Exists(string serviceName)
{
lock (_lock)
{
return _endpoints.ContainsKey(serviceName);
}
}
/// <summary>
/// 清空所有服务端点
/// </summary>
public void Clear()
{
lock (_lock)
{
_endpoints.Clear();
}
}
/// <summary>
/// 获取服务端点数量
/// </summary>
public int Count
{
get
{
lock (_lock)
{
return _endpoints.Count;
}
}
}
}
}

57
src/X1.DynamicClientCore/Interfaces/IAsyncHttpClient.cs

@ -0,0 +1,57 @@
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Interfaces
{
/// <summary>
/// 异步HTTP客户端接口
/// 提供异步的HTTP请求方法
/// </summary>
public interface IAsyncHttpClient
{
/// <summary>
/// 发送异步GET请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>响应数据</returns>
Task<T?> GetAsync<T>(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default);
/// <summary>
/// 发送异步POST请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="data">请求数据</param>
/// <param name="options">请求选项</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>响应数据</returns>
Task<T?> PostAsync<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default);
/// <summary>
/// 发送异步PUT请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="data">请求数据</param>
/// <param name="options">请求选项</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>响应数据</returns>
Task<T?> PutAsync<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default);
/// <summary>
/// 发送异步DELETE请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>响应数据</returns>
Task<T?> DeleteAsync<T>(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default);
}
}

54
src/X1.DynamicClientCore/Interfaces/ICircuitBreakerManager.cs

@ -0,0 +1,54 @@
using Polly;
using Polly.CircuitBreaker;
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Interfaces
{
/// <summary>
/// 熔断器管理器接口
/// 负责管理每个服务的熔断器实例
/// </summary>
public interface ICircuitBreakerManager
{
/// <summary>
/// 获取或创建服务的异步熔断器策略
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="options">熔断器配置</param>
/// <returns>异步熔断器策略</returns>
AsyncCircuitBreakerPolicy GetOrCreateCircuitBreaker(string serviceName, CircuitBreakerOptions? options = null);
/// <summary>
/// 获取或创建服务的同步熔断器策略
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="options">熔断器配置</param>
/// <returns>同步熔断器策略</returns>
CircuitBreakerPolicy GetOrCreateSyncCircuitBreaker(string serviceName, CircuitBreakerOptions? options = null);
/// <summary>
/// 获取熔断器状态
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>熔断器状态</returns>
CircuitState? GetCircuitState(string serviceName);
/// <summary>
/// 手动重置熔断器
/// </summary>
/// <param name="serviceName">服务名称</param>
void ResetCircuitBreaker(string serviceName);
/// <summary>
/// 移除服务的熔断器
/// </summary>
/// <param name="serviceName">服务名称</param>
void RemoveCircuitBreaker(string serviceName);
/// <summary>
/// 获取所有熔断器状态
/// </summary>
/// <returns>所有熔断器状态</returns>
Dictionary<string, CircuitState> GetAllCircuitStates();
}
}

23
src/X1.DynamicClientCore/Interfaces/IDynamicHttpClient.cs

@ -0,0 +1,23 @@
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Interfaces
{
/// <summary>
/// 动态HTTP客户端接口
/// 提供统一的HTTP请求服务,支持动态服务配置和增强功能
/// 使用多继承方式组合基础HTTP功能和文件操作功能
/// </summary>
public interface IDynamicHttpClient : IHttpClientBase, IFileHttpClient
{
// 继承自 IHttpClientBase 的方法:
// - 异步方法:GetAsync<T>, PostAsync<T>, PutAsync<T>, DeleteAsync<T>
// - 同步方法:Get<T>, Post<T>, Put<T>, Delete<T>
// 继承自 IFileHttpClient 的方法:
// - 文件上传:UploadFileAsync<T>, UploadFilesAsync<T>, UploadFileStreamAsync<T>
// - 文件下载:DownloadFileAsync, DownloadFileToStreamAsync, DownloadFileAsBytesAsync
// - 文件信息:GetFileInfoAsync<T>
// 可以在这里添加动态HTTP客户端特有的方法
}
}

162
src/X1.DynamicClientCore/Interfaces/IFileHttpClient.cs

@ -0,0 +1,162 @@
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Interfaces
{
/// <summary>
/// 文件HTTP客户端接口
/// 专门处理文件上传和下载操作
/// </summary>
public interface IFileHttpClient
{
#region 文件上传
/// <summary>
/// 异步上传单个文件
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="filePath">文件路径</param>
/// <param name="formFieldName">表单字段名称</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
Task<T?> UploadFileAsync<T>(string serviceName, string endpoint, string filePath, string formFieldName = "file", RequestOptions? options = null);
/// <summary>
/// 异步上传多个文件
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="filePaths">文件路径列表</param>
/// <param name="formFieldName">表单字段名称</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
Task<T?> UploadFilesAsync<T>(string serviceName, string endpoint, IEnumerable<string> filePaths, string formFieldName = "files", RequestOptions? options = null);
/// <summary>
/// 异步上传文件流
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="fileStream">文件流</param>
/// <param name="fileName">文件名</param>
/// <param name="formFieldName">表单字段名称</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
Task<T?> UploadFileStreamAsync<T>(string serviceName, string endpoint, Stream fileStream, string fileName, string formFieldName = "file", RequestOptions? options = null);
/// <summary>
/// 同步上传单个文件
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="filePath">文件路径</param>
/// <param name="formFieldName">表单字段名称</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
T? UploadFile<T>(string serviceName, string endpoint, string filePath, string formFieldName = "file", RequestOptions? options = null);
/// <summary>
/// 同步上传多个文件
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="filePaths">文件路径列表</param>
/// <param name="formFieldName">表单字段名称</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
T? UploadFiles<T>(string serviceName, string endpoint, IEnumerable<string> filePaths, string formFieldName = "files", RequestOptions? options = null);
#endregion
#region 文件下载
/// <summary>
/// 异步下载文件到指定路径
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="localFilePath">本地文件保存路径</param>
/// <param name="options">请求选项</param>
/// <returns>下载结果</returns>
Task<bool> DownloadFileAsync(string serviceName, string endpoint, string localFilePath, RequestOptions? options = null);
/// <summary>
/// 异步下载文件到流
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="outputStream">输出流</param>
/// <param name="options">请求选项</param>
/// <returns>下载结果</returns>
Task<bool> DownloadFileToStreamAsync(string serviceName, string endpoint, Stream outputStream, RequestOptions? options = null);
/// <summary>
/// 异步下载文件为字节数组
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <returns>文件字节数组</returns>
Task<byte[]?> DownloadFileAsBytesAsync(string serviceName, string endpoint, RequestOptions? options = null);
/// <summary>
/// 同步下载文件到指定路径
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="localFilePath">本地文件保存路径</param>
/// <param name="options">请求选项</param>
/// <returns>下载结果</returns>
bool DownloadFile(string serviceName, string endpoint, string localFilePath, RequestOptions? options = null);
/// <summary>
/// 同步下载文件到流
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="outputStream">输出流</param>
/// <param name="options">请求选项</param>
/// <returns>下载结果</returns>
bool DownloadFileToStream(string serviceName, string endpoint, Stream outputStream, RequestOptions? options = null);
/// <summary>
/// 同步下载文件为字节数组
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <returns>文件字节数组</returns>
byte[]? DownloadFileAsBytes(string serviceName, string endpoint, RequestOptions? options = null);
#endregion
#region 文件信息
/// <summary>
/// 异步获取文件信息
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <returns>文件信息</returns>
Task<T?> GetFileInfoAsync<T>(string serviceName, string endpoint, RequestOptions? options = null);
/// <summary>
/// 同步获取文件信息
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <returns>文件信息</returns>
T? GetFileInfo<T>(string serviceName, string endpoint, RequestOptions? options = null);
#endregion
}
}

18
src/X1.DynamicClientCore/Interfaces/IHttpClientBase.cs

@ -0,0 +1,18 @@
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Interfaces
{
/// <summary>
/// 基础HTTP客户端接口
/// 组合同步和异步的HTTP请求方法
/// 实现接口隔离原则,支持按需使用
/// </summary>
public interface IHttpClientBase : IAsyncHttpClient, ISyncHttpClient
{
// 继承自 IAsyncHttpClient 的方法:
// - GetAsync<T>, PostAsync<T>, PutAsync<T>, DeleteAsync<T>
// 继承自 ISyncHttpClient 的方法:
// - Get<T>, Post<T>, Put<T>, Delete<T>
}
}

54
src/X1.DynamicClientCore/Interfaces/IServiceEndpointManager.cs

@ -0,0 +1,54 @@
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Interfaces
{
/// <summary>
/// 服务端点管理器接口
/// 负责管理所有服务端点的配置信息
/// </summary>
public interface IServiceEndpointManager
{
/// <summary>
/// 获取所有服务端点
/// </summary>
/// <returns>所有服务端点</returns>
IEnumerable<ServiceEndpoint> GetAllEndpoints();
/// <summary>
/// 获取服务端点
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>服务端点</returns>
ServiceEndpoint? GetEndpoint(string serviceName);
/// <summary>
/// 添加或更新服务端点
/// </summary>
/// <param name="endpoint">服务端点</param>
void AddOrUpdateEndpoint(ServiceEndpoint endpoint);
/// <summary>
/// 移除服务端点
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>是否成功移除</returns>
bool RemoveEndpoint(string serviceName);
/// <summary>
/// 检查服务端点是否存在
/// </summary>
/// <param name="serviceName">服务名称</param>
/// <returns>是否存在</returns>
bool Exists(string serviceName);
/// <summary>
/// 清空所有服务端点
/// </summary>
void Clear();
/// <summary>
/// 获取服务端点数量
/// </summary>
int Count { get; }
}
}

53
src/X1.DynamicClientCore/Interfaces/ISyncHttpClient.cs

@ -0,0 +1,53 @@
using X1.DynamicClientCore.Models;
namespace X1.DynamicClientCore.Interfaces
{
/// <summary>
/// 同步HTTP客户端接口
/// 提供同步的HTTP请求方法
/// </summary>
public interface ISyncHttpClient
{
/// <summary>
/// 发送同步GET请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
T? Get<T>(string serviceName, string endpoint, RequestOptions? options = null);
/// <summary>
/// 发送同步POST请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="data">请求数据</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
T? Post<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null);
/// <summary>
/// 发送同步PUT请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="data">请求数据</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
T? Put<T>(string serviceName, string endpoint, object? data = null, RequestOptions? options = null);
/// <summary>
/// 发送同步DELETE请求
/// </summary>
/// <typeparam name="T">响应数据类型</typeparam>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">API端点</param>
/// <param name="options">请求选项</param>
/// <returns>响应数据</returns>
T? Delete<T>(string serviceName, string endpoint, RequestOptions? options = null);
}
}

76
src/X1.DynamicClientCore/Models/CircuitBreakerOptions.cs

@ -0,0 +1,76 @@
using System.Text.Json.Serialization;
namespace X1.DynamicClientCore.Models
{
/// <summary>
/// 熔断器配置选项
/// </summary>
/// <remarks>
/// 定义熔断器的行为参数,包括失败阈值、恢复时间等配置
/// </remarks>
public class CircuitBreakerOptions
{
/// <summary>
/// 是否启用熔断器
/// </summary>
[JsonPropertyName("enabled")]
public bool Enabled { get; set; } = true;
/// <summary>
/// 失败阈值 - 连续失败多少次后触发熔断
/// </summary>
[JsonPropertyName("failureThreshold")]
public int FailureThreshold { get; set; } = 5;
/// <summary>
/// 熔断持续时间(秒)- 熔断后多长时间尝试恢复
/// </summary>
[JsonPropertyName("durationOfBreak")]
public int DurationOfBreak { get; set; } = 30;
/// <summary>
/// 采样持续时间(秒)- 统计失败次数的时间窗口
/// </summary>
[JsonPropertyName("samplingDuration")]
public int SamplingDuration { get; set; } = 60;
/// <summary>
/// 最小吞吐量 - 在采样期间内最少需要多少请求才进行熔断判断
/// </summary>
[JsonPropertyName("minimumThroughput")]
public int MinimumThroughput { get; set; } = 10;
/// <summary>
/// 失败率阈值 - 失败率超过多少百分比时触发熔断(0.0-1.0)
/// </summary>
[JsonPropertyName("failureRateThreshold")]
public double FailureRateThreshold { get; set; } = 0.5;
/// <summary>
/// 是否启用半开状态 - 熔断器在恢复期间是否允许部分请求通过
/// </summary>
[JsonPropertyName("enableHalfOpen")]
public bool EnableHalfOpen { get; set; } = true;
/// <summary>
/// 半开状态下的请求数量 - 允许通过的请求数量
/// </summary>
[JsonPropertyName("handledEventsAllowedBeforeBreaking")]
public int HandledEventsAllowedBeforeBreaking { get; set; } = 2;
/// <summary>
/// 验证配置是否有效
/// </summary>
/// <returns>验证结果</returns>
public bool IsValid()
{
return FailureThreshold > 0
&& DurationOfBreak > 0
&& SamplingDuration > 0
&& MinimumThroughput > 0
&& FailureRateThreshold > 0.0
&& FailureRateThreshold <= 1.0
&& HandledEventsAllowedBeforeBreaking > 0;
}
}
}

136
src/X1.DynamicClientCore/Models/DynamicHttpClientException.cs

@ -0,0 +1,136 @@
using System.Runtime.Serialization;
namespace X1.DynamicClientCore.Models
{
/// <summary>
/// 动态HTTP客户端异常
/// 用于生产环境的安全异常处理
/// </summary>
[Serializable]
public class DynamicHttpClientException : Exception
{
/// <summary>
/// 异常类型
/// </summary>
public DynamicHttpClientExceptionType ExceptionType { get; }
/// <summary>
/// 服务名称
/// </summary>
public string? ServiceName { get; }
/// <summary>
/// 端点
/// </summary>
public string? Endpoint { get; }
/// <summary>
/// HTTP状态码(如果有)
/// </summary>
public int? StatusCode { get; }
/// <summary>
/// 初始化动态HTTP客户端异常
/// </summary>
/// <param name="message">异常消息</param>
/// <param name="exceptionType">异常类型</param>
/// <param name="serviceName">服务名称</param>
/// <param name="endpoint">端点</param>
/// <param name="statusCode">HTTP状态码</param>
/// <param name="innerException">内部异常</param>
public DynamicHttpClientException(
string message,
DynamicHttpClientExceptionType exceptionType,
string? serviceName = null,
string? endpoint = null,
int? statusCode = null,
Exception? innerException = null)
: base(message, innerException)
{
ExceptionType = exceptionType;
ServiceName = serviceName;
Endpoint = endpoint;
StatusCode = statusCode;
}
/// <summary>
/// 序列化构造函数
/// </summary>
protected DynamicHttpClientException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
ExceptionType = (DynamicHttpClientExceptionType)info.GetValue(nameof(ExceptionType), typeof(DynamicHttpClientExceptionType));
ServiceName = info.GetString(nameof(ServiceName));
Endpoint = info.GetString(nameof(Endpoint));
StatusCode = info.GetInt32(nameof(StatusCode));
}
/// <summary>
/// 获取序列化信息
/// </summary>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(ExceptionType), ExceptionType);
info.AddValue(nameof(ServiceName), ServiceName);
info.AddValue(nameof(Endpoint), Endpoint);
info.AddValue(nameof(StatusCode), StatusCode ?? 0);
}
}
/// <summary>
/// 动态HTTP客户端异常类型
/// </summary>
public enum DynamicHttpClientExceptionType
{
/// <summary>
/// 未知异常
/// </summary>
Unknown,
/// <summary>
/// 服务端点未找到
/// </summary>
ServiceNotFound,
/// <summary>
/// 服务端点已禁用
/// </summary>
ServiceDisabled,
/// <summary>
/// HTTP请求失败
/// </summary>
HttpRequestFailed,
/// <summary>
/// 超时异常
/// </summary>
Timeout,
/// <summary>
/// 网络连接异常
/// </summary>
NetworkError,
/// <summary>
/// 序列化异常
/// </summary>
SerializationError,
/// <summary>
/// 熔断器异常
/// </summary>
CircuitBreakerOpen,
/// <summary>
/// 配置异常
/// </summary>
ConfigurationError,
/// <summary>
/// 请求被取消
/// </summary>
RequestCanceled
}
}

43
src/X1.DynamicClientCore/Models/HttpRequestOptions.cs

@ -0,0 +1,43 @@
using System.Text.Json.Serialization;
namespace X1.DynamicClientCore.Models
{
/// <summary>
/// HTTP请求选项 - 定义HTTP请求的配置参数
/// </summary>
/// <remarks>
/// 包含超时、请求头、日志、熔断器等配置选项
/// </remarks>
public class RequestOptions
{
/// <summary>
/// 超时时间(秒)
/// </summary>
[JsonPropertyName("timeout")]
public int Timeout { get; set; } = 10;
/// <summary>
/// 请求头
/// </summary>
[JsonPropertyName("headers")]
public Dictionary<string, string> Headers { get; set; } = new();
/// <summary>
/// 是否记录日志
/// </summary>
[JsonPropertyName("enableLogging")]
public bool EnableLogging { get; set; } = true;
/// <summary>
/// 是否启用熔断器
/// </summary>
[JsonPropertyName("enableCircuitBreaker")]
public bool EnableCircuitBreaker { get; set; } = true;
/// <summary>
/// 熔断器配置
/// </summary>
[JsonPropertyName("circuitBreaker")]
public CircuitBreakerOptions? CircuitBreaker { get; set; }
}
}

77
src/X1.DynamicClientCore/Models/ServiceEndpoint.cs

@ -0,0 +1,77 @@
using System.Text.Json.Serialization;
namespace X1.DynamicClientCore.Models
{
/// <summary>
/// 服务端点配置模型 - 定义单个服务的连接配置信息
/// </summary>
/// <remarks>
/// 包含服务的网络地址、协议、超时等配置,提供URL生成和配置验证功能
/// </remarks>
public class ServiceEndpoint
{
/// <summary>
/// 服务名称(如 "B1", "B2", "B3")
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// 服务IP地址
/// </summary>
[JsonPropertyName("ip")]
public string Ip { get; set; } = string.Empty;
/// <summary>
/// 服务端口
/// </summary>
[JsonPropertyName("port")]
public int Port { get; set; }
/// <summary>
/// 协议类型(http/https)
/// </summary>
[JsonPropertyName("protocol")]
public string Protocol { get; set; } = "http";
/// <summary>
/// 超时时间(秒)
/// </summary>
[JsonPropertyName("timeout")]
public int Timeout { get; set; } = 10;
/// <summary>
/// 是否启用
/// </summary>
[JsonPropertyName("enabled")]
public bool Enabled { get; set; } = true;
/// <summary>
/// 基础路径
/// </summary>
[JsonPropertyName("basePath")]
public string BasePath { get; set; } = string.Empty;
/// <summary>
/// 获取完整的服务URL
/// </summary>
/// <returns>完整的服务URL</returns>
public string GetFullUrl()
{
var baseUrl = $"{Protocol}://{Ip}:{Port}";
return string.IsNullOrEmpty(BasePath) ? baseUrl : $"{baseUrl}/{BasePath.TrimStart('/')}";
}
/// <summary>
/// 验证配置是否有效
/// </summary>
/// <returns>验证结果</returns>
public bool IsValid()
{
return !string.IsNullOrEmpty(Name)
&& !string.IsNullOrEmpty(Ip)
&& Port > 0
&& Port <= 65535;
}
}
}

20
src/X1.DynamicClientCore/X1.DynamicClientCore.csproj

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Polly" Version="8.2.0" />
<PackageReference Include="Polly.Extensions.Http" Version="3.0.0" />
</ItemGroup>
</Project>
Loading…
Cancel
Save