diff --git a/X1.sln b/X1.sln
index 185d4e4..fd311c4 100644
--- a/X1.sln
+++ b/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
diff --git a/src/X1.DynamicClientCore/Core/DynamicHttpClient.Core.cs b/src/X1.DynamicClientCore/Core/DynamicHttpClient.Core.cs
new file mode 100644
index 0000000..35aaac4
--- /dev/null
+++ b/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
+{
+ ///
+ /// DynamicHttpClient 核心执行部分
+ /// 包含核心的HTTP请求执行逻辑
+ ///
+ public partial class DynamicHttpClient
+ {
+ ///
+ /// 执行HTTP请求的统一方法
+ ///
+ private async Task ExecuteRequestAsync(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(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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Core/DynamicHttpClient.Sync.cs b/src/X1.DynamicClientCore/Core/DynamicHttpClient.Sync.cs
new file mode 100644
index 0000000..60335af
--- /dev/null
+++ b/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
+{
+ ///
+ /// DynamicHttpClient 同步方法部分
+ /// 提供同步HTTP请求功能
+ ///
+ public partial class DynamicHttpClient
+ {
+ #region 同步HTTP方法
+
+ ///
+ /// 发送同步GET请求
+ ///
+ public T? Get(string serviceName, string endpoint, RequestOptions? options = null)
+ {
+ return ExecuteRequest(serviceName, endpoint, HttpMethod.Get, null, options);
+ }
+
+ ///
+ /// 发送同步POST请求
+ ///
+ public T? Post(string serviceName, string endpoint, object? data = null, RequestOptions? options = null)
+ {
+ return ExecuteRequest(serviceName, endpoint, HttpMethod.Post, data, options);
+ }
+
+ ///
+ /// 发送同步PUT请求
+ ///
+ public T? Put(string serviceName, string endpoint, object? data = null, RequestOptions? options = null)
+ {
+ return ExecuteRequest(serviceName, endpoint, HttpMethod.Put, data, options);
+ }
+
+ ///
+ /// 发送同步DELETE请求
+ ///
+ public T? Delete(string serviceName, string endpoint, RequestOptions? options = null)
+ {
+ return ExecuteRequest(serviceName, endpoint, HttpMethod.Delete, null, options);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Core/DynamicHttpClient.SyncCore.cs b/src/X1.DynamicClientCore/Core/DynamicHttpClient.SyncCore.cs
new file mode 100644
index 0000000..85097e4
--- /dev/null
+++ b/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
+{
+ ///
+ /// DynamicHttpClient 同步核心执行部分
+ /// 包含同步HTTP请求的核心执行逻辑
+ ///
+ public partial class DynamicHttpClient
+ {
+ ///
+ /// 执行同步HTTP请求的统一方法
+ /// 避免使用 GetAwaiter().GetResult() 防止死锁
+ ///
+ private T? ExecuteRequest(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(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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Core/DynamicHttpClient.cs b/src/X1.DynamicClientCore/Core/DynamicHttpClient.cs
new file mode 100644
index 0000000..af8d02f
--- /dev/null
+++ b/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
+{
+ ///
+ /// 动态HTTP客户端实现
+ /// 支持同步/异步HTTP请求和文件操作
+ /// 使用partial类拆分,便于维护和扩展
+ ///
+ public partial class DynamicHttpClient : IDynamicHttpClient
+ {
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly IServiceEndpointManager _endpointManager;
+ private readonly ICircuitBreakerManager _circuitBreakerManager;
+ private readonly ILogger _logger;
+
+ ///
+ /// 初始化动态HTTP客户端
+ ///
+ public DynamicHttpClient(
+ IHttpClientFactory httpClientFactory,
+ IServiceEndpointManager endpointManager,
+ ICircuitBreakerManager circuitBreakerManager,
+ ILogger 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 GetAsync(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ return await ExecuteRequestAsync(serviceName, endpoint, HttpMethod.Get, null, options, cancellationToken);
+ }
+
+ public async Task PostAsync(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ return await ExecuteRequestAsync(serviceName, endpoint, HttpMethod.Post, data, options, cancellationToken);
+ }
+
+ public async Task PutAsync(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ return await ExecuteRequestAsync(serviceName, endpoint, HttpMethod.Put, data, options, cancellationToken);
+ }
+
+ public async Task DeleteAsync(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ return await ExecuteRequestAsync(serviceName, endpoint, HttpMethod.Delete, null, options, cancellationToken);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Extensions/ServiceCollectionExtensions.cs b/src/X1.DynamicClientCore/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..953182f
--- /dev/null
+++ b/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
+{
+ ///
+ /// 服务集合扩展类
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// 添加动态服务客户端
+ ///
+ /// 服务集合
+ /// 配置委托
+ /// 服务集合
+ public static IServiceCollection AddDynamicServiceClient(
+ this IServiceCollection services,
+ Action? 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(endpointManager);
+ services.AddSingleton();
+
+ // 注册动态HTTP客户端(单例)
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ return services;
+ }
+
+ ///
+ /// 创建并配置服务端点管理器
+ ///
+ /// 配置选项
+ /// 配置好的服务端点管理器
+ 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;
+ }
+
+ ///
+ /// 验证服务端点连接性
+ ///
+ /// 服务端点管理器
+ /// 超时时间(秒)
+ 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}");
+ }
+ }
+ }
+
+ ///
+ /// 添加动态服务客户端(带预配置端点)
+ ///
+ /// 服务集合
+ /// 预配置的服务端点
+ /// 服务集合
+ public static IServiceCollection AddDynamicServiceClient(
+ this IServiceCollection services,
+ IEnumerable endpoints)
+ {
+ return services.AddDynamicServiceClient(options =>
+ {
+ options.PreConfiguredEndpoints = endpoints.ToList();
+ });
+ }
+
+ ///
+ /// 添加动态服务客户端(带单个预配置端点)
+ ///
+ /// 服务集合
+ /// 预配置的服务端点
+ /// 服务集合
+ public static IServiceCollection AddDynamicServiceClient(
+ this IServiceCollection services,
+ ServiceEndpoint endpoint)
+ {
+ return services.AddDynamicServiceClient(new[] { endpoint });
+ }
+ }
+
+ ///
+ /// 动态服务客户端配置选项
+ ///
+ public class DynamicServiceClientOptions
+ {
+ ///
+ /// 预配置的服务端点
+ ///
+ public List? PreConfiguredEndpoints { get; set; }
+
+ ///
+ /// 默认请求选项
+ ///
+ public RequestOptions? DefaultRequestOptions { get; set; }
+
+ ///
+ /// 是否启用默认日志记录
+ ///
+ public bool EnableDefaultLogging { get; set; } = true;
+
+ ///
+ /// 是否验证服务端点配置
+ ///
+ public bool ValidateEndpoints { get; set; } = true;
+
+ ///
+ /// 是否在启动时验证服务端点可达性
+ ///
+ public bool ValidateEndpointConnectivity { get; set; } = false;
+
+ ///
+ /// 连接性验证超时时间(秒)
+ ///
+ public int ConnectivityValidationTimeout { get; set; } = 5;
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileDownload.cs b/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileDownload.cs
new file mode 100644
index 0000000..7837821
--- /dev/null
+++ b/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileDownload.cs
@@ -0,0 +1,101 @@
+using Microsoft.Extensions.Logging;
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Core
+{
+ ///
+ /// DynamicHttpClient 文件下载部分
+ /// 提供文件下载功能
+ ///
+ public partial class DynamicHttpClient
+ {
+ #region 文件下载方法
+
+ ///
+ /// 异步下载文件到指定路径
+ ///
+ public async Task 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;
+ }
+ }
+
+ ///
+ /// 异步下载文件到流
+ ///
+ public async Task 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;
+ }
+ }
+
+ ///
+ /// 异步下载文件为字节数组
+ ///
+ public async Task DownloadFileAsBytesAsync(string serviceName, string endpoint, RequestOptions? options = null)
+ {
+ try
+ {
+ var response = await ExecuteRequestAsync(serviceName, endpoint, HttpMethod.Get, null, options);
+ return response;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "文件下载为字节数组失败: {ServiceName}:{Endpoint}", serviceName, endpoint);
+ return null;
+ }
+ }
+
+ ///
+ /// 同步下载文件到指定路径
+ ///
+ public bool DownloadFile(string serviceName, string endpoint, string localFilePath, RequestOptions? options = null)
+ {
+ return DownloadFileAsync(serviceName, endpoint, localFilePath, options).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// 同步下载文件到流
+ ///
+ public bool DownloadFileToStream(string serviceName, string endpoint, Stream outputStream, RequestOptions? options = null)
+ {
+ return DownloadFileToStreamAsync(serviceName, endpoint, outputStream, options).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// 同步下载文件为字节数组
+ ///
+ public byte[]? DownloadFileAsBytes(string serviceName, string endpoint, RequestOptions? options = null)
+ {
+ return DownloadFileAsBytesAsync(serviceName, endpoint, options).GetAwaiter().GetResult();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileInfo.cs b/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileInfo.cs
new file mode 100644
index 0000000..f56f1b5
--- /dev/null
+++ b/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileInfo.cs
@@ -0,0 +1,31 @@
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Core
+{
+ ///
+ /// DynamicHttpClient 文件信息部分
+ /// 提供文件信息获取功能
+ ///
+ public partial class DynamicHttpClient
+ {
+ #region 文件信息方法
+
+ ///
+ /// 异步获取文件信息
+ ///
+ public async Task GetFileInfoAsync(string serviceName, string endpoint, RequestOptions? options = null)
+ {
+ return await GetAsync(serviceName, endpoint, options);
+ }
+
+ ///
+ /// 同步获取文件信息
+ ///
+ public T? GetFileInfo(string serviceName, string endpoint, RequestOptions? options = null)
+ {
+ return GetFileInfoAsync(serviceName, endpoint, options).GetAwaiter().GetResult();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileUpload.cs b/src/X1.DynamicClientCore/FileOperations/DynamicHttpClient.FileUpload.cs
new file mode 100644
index 0000000..070ce6a
--- /dev/null
+++ b/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
+{
+ ///
+ /// DynamicHttpClient 文件上传部分
+ /// 提供文件上传功能
+ ///
+ public partial class DynamicHttpClient
+ {
+ #region 文件上传方法
+
+ ///
+ /// 异步上传单个文件
+ ///
+ public async Task UploadFileAsync(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(serviceName, endpoint, fileStream, fileName, formFieldName, options);
+ }
+
+ ///
+ /// 异步上传多个文件
+ ///
+ public async Task UploadFilesAsync(string serviceName, string endpoint, IEnumerable filePaths, string formFieldName = "files", RequestOptions? options = null)
+ {
+ var files = filePaths.ToList();
+ if (!files.Any())
+ {
+ throw new DynamicHttpClientException(
+ "文件列表为空",
+ DynamicHttpClientExceptionType.ConfigurationError,
+ serviceName,
+ endpoint);
+ }
+
+ return await ExecuteFileUploadRequestAsync(serviceName, endpoint, files, formFieldName, options);
+ }
+
+ ///
+ /// 异步上传文件流
+ ///
+ public async Task UploadFileStreamAsync(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(serviceName, endpoint, files, formFieldName, options);
+ }
+
+ ///
+ /// 同步上传单个文件
+ ///
+ public T? UploadFile(string serviceName, string endpoint, string filePath, string formFieldName = "file", RequestOptions? options = null)
+ {
+ return UploadFileAsync(serviceName, endpoint, filePath, formFieldName, options).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// 同步上传多个文件
+ ///
+ public T? UploadFiles(string serviceName, string endpoint, IEnumerable filePaths, string formFieldName = "files", RequestOptions? options = null)
+ {
+ return UploadFilesAsync(serviceName, endpoint, filePaths, formFieldName, options).GetAwaiter().GetResult();
+ }
+
+ #endregion
+
+ ///
+ /// 执行文件上传请求
+ ///
+ private async Task ExecuteFileUploadRequestAsync(string serviceName, string endpoint, List 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(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);
+ }
+ }
+
+ ///
+ /// 执行文件流上传请求
+ ///
+ private async Task ExecuteFileUploadRequestAsync(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(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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Infrastructure/CircuitBreakerManager.cs b/src/X1.DynamicClientCore/Infrastructure/CircuitBreakerManager.cs
new file mode 100644
index 0000000..5884a1e
--- /dev/null
+++ b/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
+{
+ ///
+ /// 熔断器管理器 - 负责管理每个服务的熔断器实例
+ ///
+ ///
+ /// 为每个服务端点维护独立的熔断器,支持动态配置和状态监控
+ ///
+ public class CircuitBreakerManager : ICircuitBreakerManager
+ {
+ private readonly Dictionary _circuitBreakers = new();
+ private readonly Dictionary _syncCircuitBreakers = new();
+ private readonly Dictionary _options = new();
+ private readonly ILogger _logger;
+ private readonly object _lock = new();
+
+ ///
+ /// 初始化熔断器管理器
+ ///
+ /// 日志记录器
+ public CircuitBreakerManager(ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// 获取或创建服务的异步熔断器策略
+ ///
+ /// 服务名称
+ /// 熔断器配置
+ /// 异步熔断器策略
+ 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;
+ }
+ }
+
+ ///
+ /// 获取或创建服务的同步熔断器策略
+ ///
+ /// 服务名称
+ /// 熔断器配置
+ /// 同步熔断器策略
+ 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;
+ }
+ }
+
+ ///
+ /// 获取熔断器状态
+ ///
+ /// 服务名称
+ /// 熔断器状态
+ 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;
+ }
+ }
+
+ ///
+ /// 手动重置熔断器
+ ///
+ /// 服务名称
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// 移除服务的熔断器
+ ///
+ /// 服务名称
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// 获取所有熔断器状态
+ ///
+ /// 所有熔断器状态
+ public Dictionary GetAllCircuitStates()
+ {
+ lock (_lock)
+ {
+ var states = new Dictionary();
+
+ // 添加异步熔断器状态
+ 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;
+ }
+ }
+
+ ///
+ /// 创建异步熔断器策略
+ ///
+ /// 服务名称
+ /// 熔断器配置
+ /// 异步熔断器策略
+ private AsyncCircuitBreakerPolicy CreateCircuitBreakerPolicy(string serviceName, CircuitBreakerOptions? options)
+ {
+ options ??= new CircuitBreakerOptions();
+
+ if (!options.Enabled)
+ {
+ // 如果禁用熔断器,返回一个永远不会熔断的策略
+ return Policy.Handle().CircuitBreakerAsync(1000, TimeSpan.FromDays(365));
+ }
+
+ return Policy
+ .Handle()
+ .Or()
+ .Or()
+ .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);
+ });
+ }
+
+ ///
+ /// 创建同步熔断器策略
+ ///
+ /// 服务名称
+ /// 熔断器配置
+ /// 同步熔断器策略
+ private CircuitBreakerPolicy CreateSyncCircuitBreakerPolicy(string serviceName, CircuitBreakerOptions? options)
+ {
+ options ??= new CircuitBreakerOptions();
+
+ if (!options.Enabled)
+ {
+ // 如果禁用熔断器,返回一个永远不会熔断的策略
+ return Policy.Handle().CircuitBreaker(1000, TimeSpan.FromDays(365));
+ }
+
+ return Policy
+ .Handle()
+ .Or()
+ .Or()
+ .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);
+ });
+ }
+
+ ///
+ /// 检查配置是否发生变化
+ ///
+ /// 服务名称
+ /// 新配置
+ /// 是否发生变化
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Infrastructure/ServiceEndpointManager.cs b/src/X1.DynamicClientCore/Infrastructure/ServiceEndpointManager.cs
new file mode 100644
index 0000000..118be03
--- /dev/null
+++ b/src/X1.DynamicClientCore/Infrastructure/ServiceEndpointManager.cs
@@ -0,0 +1,121 @@
+using X1.DynamicClientCore.Interfaces;
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Infrastructure
+{
+ ///
+ /// 服务端点管理器 - 负责管理所有服务端点的配置信息
+ ///
+ ///
+ /// 提供线程安全的服务端点存储和管理功能,支持动态添加、更新、删除和查询操作
+ ///
+ public class ServiceEndpointManager : IServiceEndpointManager
+ {
+ ///
+ /// 服务端点存储字典
+ ///
+ private readonly Dictionary _endpoints = new();
+
+ ///
+ /// 线程同步锁
+ ///
+ private readonly object _lock = new();
+
+ ///
+ /// 获取所有服务端点
+ ///
+ /// 所有服务端点
+ public IEnumerable GetAllEndpoints()
+ {
+ lock (_lock)
+ {
+ return _endpoints.Values.ToList();
+ }
+ }
+
+ ///
+ /// 获取服务端点
+ ///
+ /// 服务名称
+ /// 服务端点
+ public ServiceEndpoint? GetEndpoint(string serviceName)
+ {
+ lock (_lock)
+ {
+ return _endpoints.TryGetValue(serviceName, out var endpoint) ? endpoint : null;
+ }
+ }
+
+ ///
+ /// 添加或更新服务端点
+ ///
+ /// 服务端点
+ 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;
+ }
+ }
+
+ ///
+ /// 移除服务端点
+ ///
+ /// 服务名称
+ /// 是否成功移除
+ public bool RemoveEndpoint(string serviceName)
+ {
+ if (string.IsNullOrEmpty(serviceName))
+ throw new ArgumentNullException(nameof(serviceName));
+
+ lock (_lock)
+ {
+ return _endpoints.Remove(serviceName);
+ }
+ }
+
+ ///
+ /// 检查服务端点是否存在
+ ///
+ /// 服务名称
+ /// 是否存在
+ public bool Exists(string serviceName)
+ {
+ lock (_lock)
+ {
+ return _endpoints.ContainsKey(serviceName);
+ }
+ }
+
+ ///
+ /// 清空所有服务端点
+ ///
+ public void Clear()
+ {
+ lock (_lock)
+ {
+ _endpoints.Clear();
+ }
+ }
+
+ ///
+ /// 获取服务端点数量
+ ///
+ public int Count
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _endpoints.Count;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Interfaces/IAsyncHttpClient.cs b/src/X1.DynamicClientCore/Interfaces/IAsyncHttpClient.cs
new file mode 100644
index 0000000..8cd8777
--- /dev/null
+++ b/src/X1.DynamicClientCore/Interfaces/IAsyncHttpClient.cs
@@ -0,0 +1,57 @@
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Interfaces
+{
+ ///
+ /// 异步HTTP客户端接口
+ /// 提供异步的HTTP请求方法
+ ///
+ public interface IAsyncHttpClient
+ {
+ ///
+ /// 发送异步GET请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 取消令牌
+ /// 响应数据
+ Task GetAsync(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default);
+
+ ///
+ /// 发送异步POST请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求数据
+ /// 请求选项
+ /// 取消令牌
+ /// 响应数据
+ Task PostAsync(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default);
+
+ ///
+ /// 发送异步PUT请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求数据
+ /// 请求选项
+ /// 取消令牌
+ /// 响应数据
+ Task PutAsync(string serviceName, string endpoint, object? data = null, RequestOptions? options = null, CancellationToken cancellationToken = default);
+
+ ///
+ /// 发送异步DELETE请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 取消令牌
+ /// 响应数据
+ Task DeleteAsync(string serviceName, string endpoint, RequestOptions? options = null, CancellationToken cancellationToken = default);
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Interfaces/ICircuitBreakerManager.cs b/src/X1.DynamicClientCore/Interfaces/ICircuitBreakerManager.cs
new file mode 100644
index 0000000..db83fa4
--- /dev/null
+++ b/src/X1.DynamicClientCore/Interfaces/ICircuitBreakerManager.cs
@@ -0,0 +1,54 @@
+using Polly;
+using Polly.CircuitBreaker;
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Interfaces
+{
+ ///
+ /// 熔断器管理器接口
+ /// 负责管理每个服务的熔断器实例
+ ///
+ public interface ICircuitBreakerManager
+ {
+ ///
+ /// 获取或创建服务的异步熔断器策略
+ ///
+ /// 服务名称
+ /// 熔断器配置
+ /// 异步熔断器策略
+ AsyncCircuitBreakerPolicy GetOrCreateCircuitBreaker(string serviceName, CircuitBreakerOptions? options = null);
+
+ ///
+ /// 获取或创建服务的同步熔断器策略
+ ///
+ /// 服务名称
+ /// 熔断器配置
+ /// 同步熔断器策略
+ CircuitBreakerPolicy GetOrCreateSyncCircuitBreaker(string serviceName, CircuitBreakerOptions? options = null);
+
+ ///
+ /// 获取熔断器状态
+ ///
+ /// 服务名称
+ /// 熔断器状态
+ CircuitState? GetCircuitState(string serviceName);
+
+ ///
+ /// 手动重置熔断器
+ ///
+ /// 服务名称
+ void ResetCircuitBreaker(string serviceName);
+
+ ///
+ /// 移除服务的熔断器
+ ///
+ /// 服务名称
+ void RemoveCircuitBreaker(string serviceName);
+
+ ///
+ /// 获取所有熔断器状态
+ ///
+ /// 所有熔断器状态
+ Dictionary GetAllCircuitStates();
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Interfaces/IDynamicHttpClient.cs b/src/X1.DynamicClientCore/Interfaces/IDynamicHttpClient.cs
new file mode 100644
index 0000000..8c4720f
--- /dev/null
+++ b/src/X1.DynamicClientCore/Interfaces/IDynamicHttpClient.cs
@@ -0,0 +1,23 @@
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Interfaces
+{
+ ///
+ /// 动态HTTP客户端接口
+ /// 提供统一的HTTP请求服务,支持动态服务配置和增强功能
+ /// 使用多继承方式组合基础HTTP功能和文件操作功能
+ ///
+ public interface IDynamicHttpClient : IHttpClientBase, IFileHttpClient
+ {
+ // 继承自 IHttpClientBase 的方法:
+ // - 异步方法:GetAsync, PostAsync, PutAsync, DeleteAsync
+ // - 同步方法:Get, Post, Put, Delete
+
+ // 继承自 IFileHttpClient 的方法:
+ // - 文件上传:UploadFileAsync, UploadFilesAsync, UploadFileStreamAsync
+ // - 文件下载:DownloadFileAsync, DownloadFileToStreamAsync, DownloadFileAsBytesAsync
+ // - 文件信息:GetFileInfoAsync
+
+ // 可以在这里添加动态HTTP客户端特有的方法
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Interfaces/IFileHttpClient.cs b/src/X1.DynamicClientCore/Interfaces/IFileHttpClient.cs
new file mode 100644
index 0000000..baa6a99
--- /dev/null
+++ b/src/X1.DynamicClientCore/Interfaces/IFileHttpClient.cs
@@ -0,0 +1,162 @@
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Interfaces
+{
+ ///
+ /// 文件HTTP客户端接口
+ /// 专门处理文件上传和下载操作
+ ///
+ public interface IFileHttpClient
+ {
+ #region 文件上传
+
+ ///
+ /// 异步上传单个文件
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 文件路径
+ /// 表单字段名称
+ /// 请求选项
+ /// 响应数据
+ Task UploadFileAsync(string serviceName, string endpoint, string filePath, string formFieldName = "file", RequestOptions? options = null);
+
+ ///
+ /// 异步上传多个文件
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 文件路径列表
+ /// 表单字段名称
+ /// 请求选项
+ /// 响应数据
+ Task UploadFilesAsync(string serviceName, string endpoint, IEnumerable filePaths, string formFieldName = "files", RequestOptions? options = null);
+
+ ///
+ /// 异步上传文件流
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 文件流
+ /// 文件名
+ /// 表单字段名称
+ /// 请求选项
+ /// 响应数据
+ Task UploadFileStreamAsync(string serviceName, string endpoint, Stream fileStream, string fileName, string formFieldName = "file", RequestOptions? options = null);
+
+ ///
+ /// 同步上传单个文件
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 文件路径
+ /// 表单字段名称
+ /// 请求选项
+ /// 响应数据
+ T? UploadFile(string serviceName, string endpoint, string filePath, string formFieldName = "file", RequestOptions? options = null);
+
+ ///
+ /// 同步上传多个文件
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 文件路径列表
+ /// 表单字段名称
+ /// 请求选项
+ /// 响应数据
+ T? UploadFiles(string serviceName, string endpoint, IEnumerable filePaths, string formFieldName = "files", RequestOptions? options = null);
+
+ #endregion
+
+ #region 文件下载
+
+ ///
+ /// 异步下载文件到指定路径
+ ///
+ /// 服务名称
+ /// API端点
+ /// 本地文件保存路径
+ /// 请求选项
+ /// 下载结果
+ Task DownloadFileAsync(string serviceName, string endpoint, string localFilePath, RequestOptions? options = null);
+
+ ///
+ /// 异步下载文件到流
+ ///
+ /// 服务名称
+ /// API端点
+ /// 输出流
+ /// 请求选项
+ /// 下载结果
+ Task DownloadFileToStreamAsync(string serviceName, string endpoint, Stream outputStream, RequestOptions? options = null);
+
+ ///
+ /// 异步下载文件为字节数组
+ ///
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 文件字节数组
+ Task DownloadFileAsBytesAsync(string serviceName, string endpoint, RequestOptions? options = null);
+
+ ///
+ /// 同步下载文件到指定路径
+ ///
+ /// 服务名称
+ /// API端点
+ /// 本地文件保存路径
+ /// 请求选项
+ /// 下载结果
+ bool DownloadFile(string serviceName, string endpoint, string localFilePath, RequestOptions? options = null);
+
+ ///
+ /// 同步下载文件到流
+ ///
+ /// 服务名称
+ /// API端点
+ /// 输出流
+ /// 请求选项
+ /// 下载结果
+ bool DownloadFileToStream(string serviceName, string endpoint, Stream outputStream, RequestOptions? options = null);
+
+ ///
+ /// 同步下载文件为字节数组
+ ///
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 文件字节数组
+ byte[]? DownloadFileAsBytes(string serviceName, string endpoint, RequestOptions? options = null);
+
+ #endregion
+
+ #region 文件信息
+
+ ///
+ /// 异步获取文件信息
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 文件信息
+ Task GetFileInfoAsync(string serviceName, string endpoint, RequestOptions? options = null);
+
+ ///
+ /// 同步获取文件信息
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 文件信息
+ T? GetFileInfo(string serviceName, string endpoint, RequestOptions? options = null);
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Interfaces/IHttpClientBase.cs b/src/X1.DynamicClientCore/Interfaces/IHttpClientBase.cs
new file mode 100644
index 0000000..225de2e
--- /dev/null
+++ b/src/X1.DynamicClientCore/Interfaces/IHttpClientBase.cs
@@ -0,0 +1,18 @@
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Interfaces
+{
+ ///
+ /// 基础HTTP客户端接口
+ /// 组合同步和异步的HTTP请求方法
+ /// 实现接口隔离原则,支持按需使用
+ ///
+ public interface IHttpClientBase : IAsyncHttpClient, ISyncHttpClient
+ {
+ // 继承自 IAsyncHttpClient 的方法:
+ // - GetAsync, PostAsync, PutAsync, DeleteAsync
+
+ // 继承自 ISyncHttpClient 的方法:
+ // - Get, Post, Put, Delete
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Interfaces/IServiceEndpointManager.cs b/src/X1.DynamicClientCore/Interfaces/IServiceEndpointManager.cs
new file mode 100644
index 0000000..895a312
--- /dev/null
+++ b/src/X1.DynamicClientCore/Interfaces/IServiceEndpointManager.cs
@@ -0,0 +1,54 @@
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Interfaces
+{
+ ///
+ /// 服务端点管理器接口
+ /// 负责管理所有服务端点的配置信息
+ ///
+ public interface IServiceEndpointManager
+ {
+ ///
+ /// 获取所有服务端点
+ ///
+ /// 所有服务端点
+ IEnumerable GetAllEndpoints();
+
+ ///
+ /// 获取服务端点
+ ///
+ /// 服务名称
+ /// 服务端点
+ ServiceEndpoint? GetEndpoint(string serviceName);
+
+ ///
+ /// 添加或更新服务端点
+ ///
+ /// 服务端点
+ void AddOrUpdateEndpoint(ServiceEndpoint endpoint);
+
+ ///
+ /// 移除服务端点
+ ///
+ /// 服务名称
+ /// 是否成功移除
+ bool RemoveEndpoint(string serviceName);
+
+ ///
+ /// 检查服务端点是否存在
+ ///
+ /// 服务名称
+ /// 是否存在
+ bool Exists(string serviceName);
+
+ ///
+ /// 清空所有服务端点
+ ///
+ void Clear();
+
+ ///
+ /// 获取服务端点数量
+ ///
+ int Count { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Interfaces/ISyncHttpClient.cs b/src/X1.DynamicClientCore/Interfaces/ISyncHttpClient.cs
new file mode 100644
index 0000000..cdd862a
--- /dev/null
+++ b/src/X1.DynamicClientCore/Interfaces/ISyncHttpClient.cs
@@ -0,0 +1,53 @@
+using X1.DynamicClientCore.Models;
+
+namespace X1.DynamicClientCore.Interfaces
+{
+ ///
+ /// 同步HTTP客户端接口
+ /// 提供同步的HTTP请求方法
+ ///
+ public interface ISyncHttpClient
+ {
+ ///
+ /// 发送同步GET请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 响应数据
+ T? Get(string serviceName, string endpoint, RequestOptions? options = null);
+
+ ///
+ /// 发送同步POST请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求数据
+ /// 请求选项
+ /// 响应数据
+ T? Post(string serviceName, string endpoint, object? data = null, RequestOptions? options = null);
+
+ ///
+ /// 发送同步PUT请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求数据
+ /// 请求选项
+ /// 响应数据
+ T? Put(string serviceName, string endpoint, object? data = null, RequestOptions? options = null);
+
+ ///
+ /// 发送同步DELETE请求
+ ///
+ /// 响应数据类型
+ /// 服务名称
+ /// API端点
+ /// 请求选项
+ /// 响应数据
+ T? Delete(string serviceName, string endpoint, RequestOptions? options = null);
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Models/CircuitBreakerOptions.cs b/src/X1.DynamicClientCore/Models/CircuitBreakerOptions.cs
new file mode 100644
index 0000000..d2d19f3
--- /dev/null
+++ b/src/X1.DynamicClientCore/Models/CircuitBreakerOptions.cs
@@ -0,0 +1,76 @@
+using System.Text.Json.Serialization;
+
+namespace X1.DynamicClientCore.Models
+{
+ ///
+ /// 熔断器配置选项
+ ///
+ ///
+ /// 定义熔断器的行为参数,包括失败阈值、恢复时间等配置
+ ///
+ public class CircuitBreakerOptions
+ {
+ ///
+ /// 是否启用熔断器
+ ///
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = true;
+
+ ///
+ /// 失败阈值 - 连续失败多少次后触发熔断
+ ///
+ [JsonPropertyName("failureThreshold")]
+ public int FailureThreshold { get; set; } = 5;
+
+ ///
+ /// 熔断持续时间(秒)- 熔断后多长时间尝试恢复
+ ///
+ [JsonPropertyName("durationOfBreak")]
+ public int DurationOfBreak { get; set; } = 30;
+
+ ///
+ /// 采样持续时间(秒)- 统计失败次数的时间窗口
+ ///
+ [JsonPropertyName("samplingDuration")]
+ public int SamplingDuration { get; set; } = 60;
+
+ ///
+ /// 最小吞吐量 - 在采样期间内最少需要多少请求才进行熔断判断
+ ///
+ [JsonPropertyName("minimumThroughput")]
+ public int MinimumThroughput { get; set; } = 10;
+
+ ///
+ /// 失败率阈值 - 失败率超过多少百分比时触发熔断(0.0-1.0)
+ ///
+ [JsonPropertyName("failureRateThreshold")]
+ public double FailureRateThreshold { get; set; } = 0.5;
+
+ ///
+ /// 是否启用半开状态 - 熔断器在恢复期间是否允许部分请求通过
+ ///
+ [JsonPropertyName("enableHalfOpen")]
+ public bool EnableHalfOpen { get; set; } = true;
+
+ ///
+ /// 半开状态下的请求数量 - 允许通过的请求数量
+ ///
+ [JsonPropertyName("handledEventsAllowedBeforeBreaking")]
+ public int HandledEventsAllowedBeforeBreaking { get; set; } = 2;
+
+ ///
+ /// 验证配置是否有效
+ ///
+ /// 验证结果
+ public bool IsValid()
+ {
+ return FailureThreshold > 0
+ && DurationOfBreak > 0
+ && SamplingDuration > 0
+ && MinimumThroughput > 0
+ && FailureRateThreshold > 0.0
+ && FailureRateThreshold <= 1.0
+ && HandledEventsAllowedBeforeBreaking > 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Models/DynamicHttpClientException.cs b/src/X1.DynamicClientCore/Models/DynamicHttpClientException.cs
new file mode 100644
index 0000000..f20d999
--- /dev/null
+++ b/src/X1.DynamicClientCore/Models/DynamicHttpClientException.cs
@@ -0,0 +1,136 @@
+using System.Runtime.Serialization;
+
+namespace X1.DynamicClientCore.Models
+{
+ ///
+ /// 动态HTTP客户端异常
+ /// 用于生产环境的安全异常处理
+ ///
+ [Serializable]
+ public class DynamicHttpClientException : Exception
+ {
+ ///
+ /// 异常类型
+ ///
+ public DynamicHttpClientExceptionType ExceptionType { get; }
+
+ ///
+ /// 服务名称
+ ///
+ public string? ServiceName { get; }
+
+ ///
+ /// 端点
+ ///
+ public string? Endpoint { get; }
+
+ ///
+ /// HTTP状态码(如果有)
+ ///
+ public int? StatusCode { get; }
+
+ ///
+ /// 初始化动态HTTP客户端异常
+ ///
+ /// 异常消息
+ /// 异常类型
+ /// 服务名称
+ /// 端点
+ /// HTTP状态码
+ /// 内部异常
+ 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;
+ }
+
+ ///
+ /// 序列化构造函数
+ ///
+ 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));
+ }
+
+ ///
+ /// 获取序列化信息
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 动态HTTP客户端异常类型
+ ///
+ public enum DynamicHttpClientExceptionType
+ {
+ ///
+ /// 未知异常
+ ///
+ Unknown,
+
+ ///
+ /// 服务端点未找到
+ ///
+ ServiceNotFound,
+
+ ///
+ /// 服务端点已禁用
+ ///
+ ServiceDisabled,
+
+ ///
+ /// HTTP请求失败
+ ///
+ HttpRequestFailed,
+
+ ///
+ /// 超时异常
+ ///
+ Timeout,
+
+ ///
+ /// 网络连接异常
+ ///
+ NetworkError,
+
+ ///
+ /// 序列化异常
+ ///
+ SerializationError,
+
+ ///
+ /// 熔断器异常
+ ///
+ CircuitBreakerOpen,
+
+ ///
+ /// 配置异常
+ ///
+ ConfigurationError,
+
+ ///
+ /// 请求被取消
+ ///
+ RequestCanceled
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Models/HttpRequestOptions.cs b/src/X1.DynamicClientCore/Models/HttpRequestOptions.cs
new file mode 100644
index 0000000..8616207
--- /dev/null
+++ b/src/X1.DynamicClientCore/Models/HttpRequestOptions.cs
@@ -0,0 +1,43 @@
+using System.Text.Json.Serialization;
+
+namespace X1.DynamicClientCore.Models
+{
+ ///
+ /// HTTP请求选项 - 定义HTTP请求的配置参数
+ ///
+ ///
+ /// 包含超时、请求头、日志、熔断器等配置选项
+ ///
+ public class RequestOptions
+ {
+ ///
+ /// 超时时间(秒)
+ ///
+ [JsonPropertyName("timeout")]
+ public int Timeout { get; set; } = 10;
+
+ ///
+ /// 请求头
+ ///
+ [JsonPropertyName("headers")]
+ public Dictionary Headers { get; set; } = new();
+
+ ///
+ /// 是否记录日志
+ ///
+ [JsonPropertyName("enableLogging")]
+ public bool EnableLogging { get; set; } = true;
+
+ ///
+ /// 是否启用熔断器
+ ///
+ [JsonPropertyName("enableCircuitBreaker")]
+ public bool EnableCircuitBreaker { get; set; } = true;
+
+ ///
+ /// 熔断器配置
+ ///
+ [JsonPropertyName("circuitBreaker")]
+ public CircuitBreakerOptions? CircuitBreaker { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/Models/ServiceEndpoint.cs b/src/X1.DynamicClientCore/Models/ServiceEndpoint.cs
new file mode 100644
index 0000000..94e66ce
--- /dev/null
+++ b/src/X1.DynamicClientCore/Models/ServiceEndpoint.cs
@@ -0,0 +1,77 @@
+using System.Text.Json.Serialization;
+
+namespace X1.DynamicClientCore.Models
+{
+ ///
+ /// 服务端点配置模型 - 定义单个服务的连接配置信息
+ ///
+ ///
+ /// 包含服务的网络地址、协议、超时等配置,提供URL生成和配置验证功能
+ ///
+ public class ServiceEndpoint
+ {
+ ///
+ /// 服务名称(如 "B1", "B2", "B3")
+ ///
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 服务IP地址
+ ///
+ [JsonPropertyName("ip")]
+ public string Ip { get; set; } = string.Empty;
+
+ ///
+ /// 服务端口
+ ///
+ [JsonPropertyName("port")]
+ public int Port { get; set; }
+
+ ///
+ /// 协议类型(http/https)
+ ///
+ [JsonPropertyName("protocol")]
+ public string Protocol { get; set; } = "http";
+
+ ///
+ /// 超时时间(秒)
+ ///
+ [JsonPropertyName("timeout")]
+ public int Timeout { get; set; } = 10;
+
+ ///
+ /// 是否启用
+ ///
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; } = true;
+
+ ///
+ /// 基础路径
+ ///
+ [JsonPropertyName("basePath")]
+ public string BasePath { get; set; } = string.Empty;
+
+ ///
+ /// 获取完整的服务URL
+ ///
+ /// 完整的服务URL
+ public string GetFullUrl()
+ {
+ var baseUrl = $"{Protocol}://{Ip}:{Port}";
+ return string.IsNullOrEmpty(BasePath) ? baseUrl : $"{baseUrl}/{BasePath.TrimStart('/')}";
+ }
+
+ ///
+ /// 验证配置是否有效
+ ///
+ /// 验证结果
+ public bool IsValid()
+ {
+ return !string.IsNullOrEmpty(Name)
+ && !string.IsNullOrEmpty(Ip)
+ && Port > 0
+ && Port <= 65535;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/X1.DynamicClientCore/X1.DynamicClientCore.csproj b/src/X1.DynamicClientCore/X1.DynamicClientCore.csproj
new file mode 100644
index 0000000..0b6d07c
--- /dev/null
+++ b/src/X1.DynamicClientCore/X1.DynamicClientCore.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file