znlgis 博客

GIS开发与技术分享

第十三章:远程请求与HTTP客户端

一、HttpClient 基础

1.1 .NET HttpClient 概述

在现代微服务和分布式架构中,服务间的 HTTP 通信是必不可少的。.NET 提供了 HttpClient 类用于发送 HTTP 请求,而 Furion 在此基础上进行了大量增强,提供了更简洁、更强大的远程请求方式。

1.2 原生 HttpClient 的问题

问题 说明
套接字耗尽 频繁创建/销毁 HttpClient 可能导致端口耗尽
DNS 更新 单例 HttpClient 不会更新 DNS 解析结果
配置繁琐 每次请求需手动设置请求头、序列化等
缺少拦截器 不支持统一的请求/响应拦截
重试困难 没有内置的重试机制

1.3 IHttpClientFactory

.NET 推荐使用 IHttpClientFactory 管理 HttpClient 实例:

// 注册 HttpClient
builder.Services.AddHttpClient();

// 使用命名客户端
builder.Services.AddHttpClient("GitHubApi", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.DefaultRequestHeaders.Add("User-Agent", "FurionApp");
});

二、Furion 远程请求增强

2.1 注册远程请求服务

var builder = WebApplication.CreateBuilder(args);

// 注册 Furion 远程请求服务
builder.Services.AddRemoteRequest();

var app = builder.Build();
app.Run();

2.2 基本使用方式

Furion 提供了多种远程请求方式:

方式 说明 适用场景
字符串扩展方法 直接在 URL 字符串上调用 快速简单请求
IHttpDispatchProxy 接口代理方式 微服务间通信
HttpClient 增强 增强版 HttpClient 需要完全控制
// 方式1:字符串扩展方法(最简洁)
var result = await "https://api.example.com/users".GetAsAsync<List<UserDto>>();

// 方式2:POST 请求
var user = await "https://api.example.com/users".SetBody(new { Name = "张三", Age = 25 })
    .PostAsAsync<UserDto>();

// 方式3:带请求头
var data = await "https://api.example.com/data"
    .SetHeaders(new Dictionary<string, object>
    {
        { "Authorization", "Bearer xxx" },
        { "X-Request-Id", Guid.NewGuid().ToString() }
    })
    .GetAsAsync<DataDto>();

三、IHttpDispatchProxy 接口代理

3.1 声明式 HTTP 请求

IHttpDispatchProxy 允许通过定义接口来声明 HTTP 请求,类似于 Java 中的 Feign:

/// <summary>
/// 用户服务 HTTP 代理接口
/// </summary>
public interface IUserServiceApi : IHttpDispatchProxy
{
    /// <summary>
    /// 获取用户列表
    /// </summary>
    [Get("https://api.userservice.com/api/users")]
    Task<List<UserDto>> GetUsersAsync();

    /// <summary>
    /// 根据Id获取用户
    /// </summary>
    [Get("https://api.userservice.com/api/users/{id}")]
    Task<UserDto> GetUserByIdAsync([RouteParam] long id);

    /// <summary>
    /// 创建用户
    /// </summary>
    [Post("https://api.userservice.com/api/users")]
    Task<UserDto> CreateUserAsync([Body] CreateUserInput input);

    /// <summary>
    /// 更新用户
    /// </summary>
    [Put("https://api.userservice.com/api/users/{id}")]
    Task<UserDto> UpdateUserAsync([RouteParam] long id, [Body] UpdateUserInput input);

    /// <summary>
    /// 删除用户
    /// </summary>
    [Delete("https://api.userservice.com/api/users/{id}")]
    Task DeleteUserAsync([RouteParam] long id);
}

3.2 使用接口代理

[ApiDescriptionSettings("用户管理")]
public class UserAppService : IDynamicApiController
{
    private readonly IUserServiceApi _userServiceApi;

    public UserAppService(IUserServiceApi userServiceApi)
    {
        _userServiceApi = userServiceApi;
    }

    /// <summary>
    /// 获取所有用户
    /// </summary>
    public async Task<List<UserDto>> GetUsers()
    {
        return await _userServiceApi.GetUsersAsync();
    }

    /// <summary>
    /// 获取用户详情
    /// </summary>
    public async Task<UserDto> GetUser(long id)
    {
        return await _userServiceApi.GetUserByIdAsync(id);
    }

    /// <summary>
    /// 创建用户
    /// </summary>
    public async Task<UserDto> CreateUser(CreateUserInput input)
    {
        return await _userServiceApi.CreateUserAsync(input);
    }
}

3.3 配置基础地址

通过 appsettings.json 配置服务地址,避免硬编码:

{
  "RemoteRequest": {
    "UserService": {
      "BaseUrl": "https://api.userservice.com",
      "Timeout": 30
    },
    "OrderService": {
      "BaseUrl": "https://api.orderservice.com",
      "Timeout": 60
    }
  }
}
/// <summary>
/// 使用配置的基础地址
/// </summary>
[Client("UserService")]
public interface IUserServiceApi : IHttpDispatchProxy
{
    [Get("/api/users")]
    Task<List<UserDto>> GetUsersAsync();

    [Get("/api/users/{id}")]
    Task<UserDto> GetUserByIdAsync([RouteParam] long id);

    [Post("/api/users")]
    Task<UserDto> CreateUserAsync([Body] CreateUserInput input);
}

四、RESTful 请求详解

4.1 GET 请求

// 简单 GET 请求
var users = await "https://api.example.com/users".GetAsAsync<List<UserDto>>();

// 带查询参数
var result = await "https://api.example.com/users"
    .SetQueries(new
    {
        page = 1,
        pageSize = 20,
        keyword = "张三",
        status = "active"
    })
    .GetAsAsync<PagedResult<UserDto>>();

// 带请求头的 GET
var data = await "https://api.example.com/protected/data"
    .SetHeaders(new Dictionary<string, object>
    {
        { "Authorization", $"Bearer {token}" }
    })
    .GetAsAsync<ProtectedData>();

// 获取原始字符串
var rawJson = await "https://api.example.com/users".GetAsStringAsync();

// 获取 HttpResponseMessage
var response = await "https://api.example.com/users".GetAsync();

4.2 POST 请求

// JSON Body
var newUser = await "https://api.example.com/users"
    .SetBody(new CreateUserInput
    {
        UserName = "张三",
        Email = "zhangsan@example.com",
        Password = "SecurePass123"
    })
    .PostAsAsync<UserDto>();

// Form 表单提交
var loginResult = await "https://api.example.com/auth/token"
    .SetBody(new Dictionary<string, string>
    {
        { "grant_type", "password" },
        { "username", "admin" },
        { "password", "123456" }
    }, "application/x-www-form-urlencoded")
    .PostAsAsync<TokenResult>();

// 带查询参数的 POST
var result = await "https://api.example.com/api/batch"
    .SetQueries(new { type = "import" })
    .SetBody(dataList)
    .PostAsAsync<BatchResult>();

4.3 PUT 请求

// 更新资源
var updatedUser = await "https://api.example.com/users/1"
    .SetBody(new UpdateUserInput
    {
        UserName = "张三(已更新)",
        Email = "zhangsan_new@example.com"
    })
    .PutAsAsync<UserDto>();

4.4 DELETE 请求

// 删除资源
await "https://api.example.com/users/1".DeleteAsync();

// 带条件删除
await "https://api.example.com/users"
    .SetQueries(new { ids = "1,2,3" })
    .DeleteAsync();

4.5 PATCH 请求

// 部分更新
var patchedUser = await "https://api.example.com/users/1"
    .SetBody(new { Email = "new_email@example.com" })
    .PatchAsAsync<UserDto>();

五、请求拦截器

5.1 请求拦截

/// <summary>
/// 请求拦截器:统一添加认证信息
/// </summary>
public class AuthorizationInterceptor : IHttpDispatchProxy
{
    // 在接口代理中使用拦截器
}

// 使用 OnRequesting 回调
var result = await "https://api.example.com/data"
    .OnRequesting((client, request) =>
    {
        // 添加认证头
        request.Headers.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", GetAccessToken());

        // 添加自定义头
        request.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString());
        request.Headers.Add("X-Client-Version", "1.0.0");
    })
    .GetAsAsync<DataResult>();

private static string GetAccessToken() => "your-access-token";

5.2 全局请求拦截

/// <summary>
/// 全局 HTTP 请求拦截处理器
/// </summary>
public class GlobalHttpMessageHandler : DelegatingHandler
{
    private readonly ILogger<GlobalHttpMessageHandler> _logger;

    public GlobalHttpMessageHandler(ILogger<GlobalHttpMessageHandler> logger)
    {
        _logger = logger;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 请求前处理
        var requestId = Guid.NewGuid().ToString("N");
        request.Headers.Add("X-Request-Id", requestId);

        _logger.LogInformation("[{RequestId}] {Method} {Url}",
            requestId, request.Method, request.RequestUri);

        var stopwatch = System.Diagnostics.Stopwatch.StartNew();

        // 发送请求
        var response = await base.SendAsync(request, cancellationToken);

        stopwatch.Stop();

        // 响应后处理
        _logger.LogInformation("[{RequestId}] 响应状态:{StatusCode},耗时:{Elapsed}ms",
            requestId, (int)response.StatusCode, stopwatch.ElapsedMilliseconds);

        return response;
    }
}

// 注册全局拦截器
builder.Services.AddTransient<GlobalHttpMessageHandler>();
builder.Services.AddHttpClient("Default")
    .AddHttpMessageHandler<GlobalHttpMessageHandler>();

六、响应拦截器

6.1 响应处理

// 响应拦截
var result = await "https://api.example.com/data"
    .OnResponsing((client, response) =>
    {
        // 检查响应状态
        if (!response.IsSuccessStatusCode)
        {
            // 记录错误日志
            Console.WriteLine($"请求失败:{response.StatusCode}");
        }

        // 检查特定响应头
        if (response.Headers.Contains("X-RateLimit-Remaining"))
        {
            var remaining = response.Headers.GetValues("X-RateLimit-Remaining").First();
            Console.WriteLine($"API 剩余调用次数:{remaining}");
        }
    })
    .GetAsAsync<DataResult>();

6.2 统一响应处理

/// <summary>
/// 远程请求帮助类
/// </summary>
public static class RemoteRequestHelper
{
    /// <summary>
    /// 带统一错误处理的 GET 请求
    /// </summary>
    public static async Task<T> SafeGetAsync<T>(string url, Dictionary<string, object> headers = null)
    {
        try
        {
            var request = url;
            if (headers != null)
            {
                request = url;
            }

            var response = await url
                .SetHeaders(headers ?? new Dictionary<string, object>())
                .OnResponsing((client, resp) =>
                {
                    if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                    {
                        throw new UnauthorizedAccessException("远程服务认证失败");
                    }

                    if (resp.StatusCode == System.Net.HttpStatusCode.NotFound)
                    {
                        throw new InvalidOperationException($"远程资源不存在:{url}");
                    }
                })
                .GetAsAsync<ApiResponse<T>>();

            if (response.Code != 200)
            {
                throw new Exception($"远程请求业务异常:{response.Message}");
            }

            return response.Data;
        }
        catch (HttpRequestException ex)
        {
            throw new Exception($"远程请求网络异常:{ex.Message}", ex);
        }
        catch (TaskCanceledException)
        {
            throw new TimeoutException($"远程请求超时:{url}");
        }
    }
}

/// <summary>
/// 统一 API 响应模型
/// </summary>
public class ApiResponse<T>
{
    public int Code { get; set; }
    public string Message { get; set; }
    public T Data { get; set; }
    public bool Success => Code == 200;
}

七、文件上传与下载

7.1 文件上传

/// <summary>
/// 文件上传服务
/// </summary>
public class FileUploadService
{
    /// <summary>
    /// 单文件上传
    /// </summary>
    public async Task<FileUploadResult> UploadFileAsync(Stream fileStream, string fileName)
    {
        var content = new MultipartFormDataContent();
        var streamContent = new StreamContent(fileStream);
        content.Add(streamContent, "file", fileName);

        var result = await "https://api.example.com/files/upload"
            .SetBody(content)
            .PostAsAsync<FileUploadResult>();

        return result;
    }

    /// <summary>
    /// 多文件上传
    /// </summary>
    public async Task<List<FileUploadResult>> UploadFilesAsync(List<(Stream Stream, string FileName)> files)
    {
        var content = new MultipartFormDataContent();

        foreach (var (stream, fileName) in files)
        {
            var streamContent = new StreamContent(stream);
            content.Add(streamContent, "files", fileName);
        }

        // 添加额外表单字段
        content.Add(new StringContent("images"), "category");

        var result = await "https://api.example.com/files/batch-upload"
            .SetBody(content)
            .PostAsAsync<List<FileUploadResult>>();

        return result;
    }
}

/// <summary>
/// 文件上传结果
/// </summary>
public class FileUploadResult
{
    public string FileId { get; set; }
    public string FileName { get; set; }
    public string Url { get; set; }
    public long Size { get; set; }
}

7.2 文件下载

/// <summary>
/// 文件下载服务
/// </summary>
public class FileDownloadService
{
    /// <summary>
    /// 下载文件到本地
    /// </summary>
    public async Task DownloadFileAsync(string fileUrl, string savePath)
    {
        var response = await fileUrl.GetAsync();
        response.EnsureSuccessStatusCode();

        await using var fileStream = File.Create(savePath);
        await response.Content.CopyToAsync(fileStream);
    }

    /// <summary>
    /// 下载文件为字节数组
    /// </summary>
    public async Task<byte[]> DownloadFileBytesAsync(string fileUrl)
    {
        var response = await fileUrl.GetAsync();
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsByteArrayAsync();
    }

    /// <summary>
    /// 下载大文件(流式处理)
    /// </summary>
    public async Task DownloadLargeFileAsync(string fileUrl, string savePath, IProgress<long> progress = null)
    {
        using var httpClient = new HttpClient();
        using var response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead);
        response.EnsureSuccessStatusCode();

        var totalBytes = response.Content.Headers.ContentLength ?? -1L;

        await using var contentStream = await response.Content.ReadAsStreamAsync();
        await using var fileStream = File.Create(savePath);

        var buffer = new byte[8192];
        var totalBytesRead = 0L;
        int bytesRead;

        while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0)
        {
            await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
            totalBytesRead += bytesRead;
            progress?.Report(totalBytesRead);
        }
    }
}

八、超时与重试

8.1 超时配置

// 设置请求超时
var result = await "https://api.example.com/slow-api"
    .SetHttpClientTimeout(TimeSpan.FromSeconds(60))
    .GetAsAsync<SlowResult>();

// 在命名客户端中配置超时
builder.Services.AddHttpClient("SlowService", client =>
{
    client.BaseAddress = new Uri("https://api.slowservice.com");
    client.Timeout = TimeSpan.FromMinutes(2);
});

8.2 重试策略

使用 Polly 实现重试策略:

// 安装 Microsoft.Extensions.Http.Polly 包
builder.Services.AddHttpClient("ResilientService")
    .AddTransientHttpErrorPolicy(policy =>
        policy.WaitAndRetryAsync(3, retryAttempt =>
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
    .AddTransientHttpErrorPolicy(policy =>
        policy.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

8.3 自定义重试逻辑

/// <summary>
/// 带重试的 HTTP 请求帮助类
/// </summary>
public static class ResilientHttpHelper
{
    /// <summary>
    /// 带重试的 GET 请求
    /// </summary>
    public static async Task<T> GetWithRetryAsync<T>(
        string url,
        int maxRetries = 3,
        int baseDelayMs = 1000)
    {
        Exception lastException = null;

        for (int i = 0; i <= maxRetries; i++)
        {
            try
            {
                return await url.GetAsAsync<T>();
            }
            catch (HttpRequestException ex) when (i < maxRetries)
            {
                lastException = ex;
                var delay = baseDelayMs * (int)Math.Pow(2, i);
                await Task.Delay(delay);
            }
            catch (TaskCanceledException ex) when (i < maxRetries)
            {
                lastException = ex;
                var delay = baseDelayMs * (int)Math.Pow(2, i);
                await Task.Delay(delay);
            }
        }

        throw new Exception($"请求 {url} 失败,已重试 {maxRetries} 次", lastException);
    }
}

九、请求日志

9.1 日志中间件

/// <summary>
/// HTTP 请求日志处理器
/// </summary>
public class HttpLoggingHandler : DelegatingHandler
{
    private readonly ILogger<HttpLoggingHandler> _logger;

    public HttpLoggingHandler(ILogger<HttpLoggingHandler> logger) => _logger = logger;

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var requestId = Guid.NewGuid().ToString("N")[..8];
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();

        // 记录请求信息
        _logger.LogInformation(
            "[{RequestId}] >>> {Method} {Url}",
            requestId, request.Method, request.RequestUri);

        if (request.Content != null)
        {
            var body = await request.Content.ReadAsStringAsync(cancellationToken);
            if (body.Length <= 1000) // 限制日志长度
            {
                _logger.LogDebug("[{RequestId}] 请求体:{Body}", requestId, body);
            }
        }

        // 发送请求
        var response = await base.SendAsync(request, cancellationToken);

        stopwatch.Stop();

        // 记录响应信息
        var level = response.IsSuccessStatusCode ? LogLevel.Information : LogLevel.Warning;
        _logger.Log(level,
            "[{RequestId}] <<< {StatusCode} {ReasonPhrase} ({Elapsed}ms)",
            requestId, (int)response.StatusCode, response.ReasonPhrase,
            stopwatch.ElapsedMilliseconds);

        return response;
    }
}

9.2 注册日志处理器

builder.Services.AddTransient<HttpLoggingHandler>();

builder.Services.AddHttpClient("LoggedClient")
    .AddHttpMessageHandler<HttpLoggingHandler>();

十、微服务间通信

10.1 服务发现与请求

/// <summary>
/// 微服务 HTTP 代理集合
/// </summary>
[Client("OrderService")]
public interface IOrderServiceApi : IHttpDispatchProxy
{
    [Get("/api/orders")]
    Task<PagedResult<OrderDto>> GetOrdersAsync(
        [QueryString] int page = 1,
        [QueryString] int pageSize = 20);

    [Get("/api/orders/{orderId}")]
    Task<OrderDto> GetOrderAsync([RouteParam] long orderId);

    [Post("/api/orders")]
    Task<OrderDto> CreateOrderAsync([Body] CreateOrderInput input);

    [Put("/api/orders/{orderId}/status")]
    Task UpdateOrderStatusAsync(
        [RouteParam] long orderId,
        [Body] UpdateStatusInput input);
}

[Client("InventoryService")]
public interface IInventoryServiceApi : IHttpDispatchProxy
{
    [Get("/api/inventory/{productId}")]
    Task<InventoryDto> GetInventoryAsync([RouteParam] long productId);

    [Post("/api/inventory/deduct")]
    Task<bool> DeductInventoryAsync([Body] DeductInventoryInput input);

    [Post("/api/inventory/restore")]
    Task<bool> RestoreInventoryAsync([Body] RestoreInventoryInput input);
}

10.2 编排多个服务调用

/// <summary>
/// 订单编排服务
/// </summary>
[ApiDescriptionSettings("订单编排")]
public class OrderOrchestrationService : IDynamicApiController
{
    private readonly IOrderServiceApi _orderApi;
    private readonly IInventoryServiceApi _inventoryApi;
    private readonly ILogger<OrderOrchestrationService> _logger;

    public OrderOrchestrationService(
        IOrderServiceApi orderApi,
        IInventoryServiceApi inventoryApi,
        ILogger<OrderOrchestrationService> logger)
    {
        _orderApi = orderApi;
        _inventoryApi = inventoryApi;
        _logger = logger;
    }

    /// <summary>
    /// 创建订单(编排多个服务)
    /// </summary>
    public async Task<OrderDto> CreateOrder(CreateOrderInput input)
    {
        // 1. 检查库存
        var inventory = await _inventoryApi.GetInventoryAsync(input.ProductId);
        if (inventory.Available < input.Quantity)
        {
            throw new Exception("库存不足");
        }

        // 2. 扣减库存
        var deducted = await _inventoryApi.DeductInventoryAsync(new DeductInventoryInput
        {
            ProductId = input.ProductId,
            Quantity = input.Quantity
        });

        if (!deducted)
        {
            throw new Exception("扣减库存失败");
        }

        try
        {
            // 3. 创建订单
            var order = await _orderApi.CreateOrderAsync(input);
            return order;
        }
        catch (Exception ex)
        {
            // 4. 补偿:恢复库存
            _logger.LogError(ex, "创建订单失败,开始恢复库存");
            await _inventoryApi.RestoreInventoryAsync(new RestoreInventoryInput
            {
                ProductId = input.ProductId,
                Quantity = input.Quantity
            });

            throw;
        }
    }
}

十一、请求转发

11.1 网关模式转发

/// <summary>
/// API 网关转发控制器
/// </summary>
[ApiDescriptionSettings("网关")]
public class GatewayAppService : IDynamicApiController
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _configuration;

    public GatewayAppService(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration)
    {
        _httpClientFactory = httpClientFactory;
        _configuration = configuration;
    }

    /// <summary>
    /// 转发请求到目标服务
    /// </summary>
    [HttpGet("{serviceName}/{**path}")]
    public async Task<IActionResult> ForwardRequest(
        string serviceName, string path,
        [FromServices] HttpContext httpContext)
    {
        var serviceUrl = _configuration[$"Services:{serviceName}:BaseUrl"];
        if (string.IsNullOrEmpty(serviceUrl))
        {
            return new NotFoundObjectResult($"服务 {serviceName} 未配置");
        }

        var targetUrl = $"{serviceUrl}/{path}{httpContext.Request.QueryString}";
        var client = _httpClientFactory.CreateClient();

        // 复制请求头
        foreach (var header in httpContext.Request.Headers)
        {
            if (!header.Key.StartsWith("Host", StringComparison.OrdinalIgnoreCase))
            {
                client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value.ToArray());
            }
        }

        var response = await client.GetAsync(targetUrl);
        var content = await response.Content.ReadAsStringAsync();

        return new ContentResult
        {
            Content = content,
            ContentType = response.Content.Headers.ContentType?.ToString(),
            StatusCode = (int)response.StatusCode
        };
    }
}

十二、最佳实践

12.1 远程请求设计原则

原则 说明
使用 IHttpClientFactory 避免直接 new HttpClient
设置合理超时 根据业务场景设置超时时间
添加重试策略 网络不稳定场景添加重试
断路器模式 避免雪崩效应
请求日志 记录请求和响应便于排查
接口代理 使用 IHttpDispatchProxy 提升可维护性
配置外置 服务地址放配置文件
异常处理 统一处理远程请求异常

12.2 完整的远程请求服务示例

/// <summary>
/// 第三方支付服务代理
/// </summary>
[Client("PaymentService")]
public interface IPaymentApi : IHttpDispatchProxy
{
    /// <summary>
    /// 创建支付订单
    /// </summary>
    [Post("/api/payment/create")]
    Task<PaymentResult> CreatePaymentAsync([Body] CreatePaymentInput input);

    /// <summary>
    /// 查询支付状态
    /// </summary>
    [Get("/api/payment/{paymentId}/status")]
    Task<PaymentStatusResult> GetPaymentStatusAsync([RouteParam] string paymentId);

    /// <summary>
    /// 退款
    /// </summary>
    [Post("/api/payment/{paymentId}/refund")]
    Task<RefundResult> RefundAsync([RouteParam] string paymentId, [Body] RefundInput input);
}

/// <summary>
/// 支付服务封装
/// </summary>
public class PaymentService
{
    private readonly IPaymentApi _paymentApi;
    private readonly ILogger<PaymentService> _logger;

    public PaymentService(IPaymentApi paymentApi, ILogger<PaymentService> logger)
    {
        _paymentApi = paymentApi;
        _logger = logger;
    }

    /// <summary>
    /// 发起支付(带重试和异常处理)
    /// </summary>
    public async Task<PaymentResult> CreatePaymentWithRetry(CreatePaymentInput input)
    {
        const int maxRetries = 3;

        for (int i = 0; i <= maxRetries; i++)
        {
            try
            {
                var result = await _paymentApi.CreatePaymentAsync(input);
                _logger.LogInformation("支付订单创建成功:{PaymentId}", result.PaymentId);
                return result;
            }
            catch (Exception ex) when (i < maxRetries)
            {
                _logger.LogWarning(ex, "支付请求失败,第 {Retry} 次重试", i + 1);
                await Task.Delay(1000 * (i + 1));
            }
        }

        throw new Exception("支付服务暂时不可用,请稍后重试");
    }
}

通过 Furion 的远程请求增强功能,可以极大简化微服务间的 HTTP 通信代码,提升代码的可读性和可维护性。结合拦截器、重试策略和日志记录,可以构建出健壮可靠的分布式系统。