23 changed files with 2424 additions and 1 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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客户端特有的方法
|
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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>
|
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue