.NET Core中结合依赖注入使用工厂模式

本篇是学习总结的整理,源代码在我的码云上,地址是https://gitee.com/lvyukang/payment-service-factory.git

之前在开发中遇到一个类似工厂模式的场景,所以通过腾讯元宝询问AI得到了这个代码,所以开源出来,也是学习总结的一部分。感慨一声,现在真是AI的时代,作为一个工具,使用好的话,真的是事半功倍,而且总有你想不到的地方给你补充。

整个项目弄下来,还真是体会到那句话,设计模式是为了代码行书方便,而不是炫技,可能是本人能力不足,体会不到代码的精妙之处,只是觉得繁琐复杂。每个人的悲欢并不相同,我只觉得吵闹。

一,项目结构

PaymentServiceFactory/
├── Controllers/
│   └── PaymentController.cs
├── Services/
│   ├── Interfaces/
│   │   ├── IPaymentService.cs
│   │   └── IOrderDetrInfoRepository.cs      # 仓储接口
│   ├── PaymentServiceFactorySimple.cs    # 工厂类
│   ├── Implementations/
│   │   ├── CreditCardPayment.cs
│   │   └── PayPalPayment.cs                 # 
│   ├── PaymentProcessor.cs                  # 业务逻辑层
│   └── Repositories/                        # 仓储实现
│       └── OrderDetrInfoRepository.cs
├── Data/                                 
│   └── AppDbContext.cs                      # 数据库上下文
└── Program.cs

  注意依赖注入的生命周期一致性,这个在数据库上下文时会有影响,图片中的PaymentServiceFactory.cs对应项目结构中的PaymentServiceFactorySimple.cs,vs2022里项目解决方案和类名重复了,所以把类名添加了一个后缀

 

二,代码

appsetting.json配置,因为是本地测试,所以配置都是虚假的

{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "PayPal": {
        "BaseUrl": "https://api-m.sandbox.paypal.com/",
        "ClientId": "你的PayPal客户端ID",
        "ClientSecret": "你的PayPal客户端密钥"
    },
    "ConnectionStrings": {
        "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=PaymentDemo;Trusted_Connection=True;"
    }
}

  

program.cs

var builder = WebApplication.CreateBuilder(args);

// 添加数据库上下文 (设置为 Scoped 生命周期)
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")),
    ServiceLifetime.Scoped);
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
    // 记录长时间运行的查询
    options.EnableSensitiveDataLogging();
    options.LogTo(Console.WriteLine, LogLevel.Warning, DbContextLoggerOptions.SingleLine);
});

// 添加仓储 (Scoped 生命周期)
builder.Services.AddScoped<IOrderDetrInfoRepository, OrderDetrInfoRepository>();

// 添加HttpClient
builder.Services.AddHttpClient("PayPal", client =>
{
    var config = builder.Configuration.GetSection("PayPal");
    client.BaseAddress = new Uri(config["BaseUrl"]);
});

// 注册支付服务 (Transient)
builder.Services.AddTransient<CreditCardPayment>();
builder.Services.AddTransient<PayPalPayment>();

// 注册工厂和处理器 (Scoped)
builder.Services.AddScoped<PaymentServiceFactorySimple>(); // 修改为 Scoped
builder.Services.AddScoped<PaymentProcessor>();

// 控制器
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// 创建数据库(仅用于演示,生产环境应使用迁移)
using (var scope = app.Services.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    dbContext.Database.EnsureCreated();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();

设置数据库上下文那块不知道如何合并,也懒得合并,后续如果继续问AI应该也是可以的,不过现在也可以用,就懒得管了。

依赖注入特别要注意生命周期,数据库上下文、repository和service之间不停的调用,如果周期协调不好,程序反手就是一个报错.......

data/AppDbContext.cs

就是一个EFCore的数据库上下文,注意要引入Microsoft.EntityFrameworkCore和Microsoft.EntityFrameworkCore.salserve的包

PaymentController.cs

    [ApiController]
    [Route("api/[controller]")]
    public class PaymentController : ControllerBase
    {
        private readonly PaymentProcessor _paymentProcessor;
        private readonly AppDbContext _context;

        public PaymentController(PaymentProcessor paymentProcessor, AppDbContext context)
        {
            _paymentProcessor = paymentProcessor;
            _context = context;
        }

        [HttpPost("{orderId}")]
        public async Task<IActionResult> ProcessPayment(
    string orderId,
        [FromQuery] string type,
        [FromBody] decimal amount)
        {
            try
            {
                var result = await _paymentProcessor.ProcessPaymentAsync(type, orderId, amount);
                return Ok(new { OrderId = orderId, PaymentType = type, Result = result });
            }
            catch (KeyNotFoundException ex)
            {
                return BadRequest(ex.Message);
            }
        }
    }

 

PaymentProcessor.cs,业务逻辑层,在此我觉得是抽象了工厂模式的处理逻辑

/// <summary>
/// 业务逻辑层
/// </summary> 
public class PaymentProcessor
{
    private readonly PaymentServiceFactorySimple _factory;
    private readonly ILogger<PaymentProcessor> _logger;

    public PaymentProcessor(PaymentServiceFactorySimple factory, ILogger<PaymentProcessor> logger)
    {
        _factory = factory;
        _logger = logger;
    }

    public async Task<string> ProcessPaymentAsync(string paymentType, string orderId, decimal amount)
    {
        try
        {
            var paymentService = _factory.Create(paymentType);
            return await paymentService.ProcessPayment(orderId, amount);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "支付处理失败: {PaymentType}, 订单: {OrderId}", paymentType, orderId);
            return $"支付处理失败: {ex.Message}";
        }
    }
}

PaymentServiceFactorySimple.cs就是工厂类了,在此进行工厂生成,后续再扩充service也要在这里添加

/// <summary>
/// 工厂类
/// </summary>
public class PaymentServiceFactorySimple
{
    private readonly IServiceProvider _serviceProvider;

    public PaymentServiceFactorySimple(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IPaymentService Create(string paymentType)
    {
        return paymentType switch
        {
            "CreditCard" => ActivatorUtilities.CreateInstance<CreditCardPayment>(_serviceProvider),
            "PayPal" => ActivatorUtilities.CreateInstance<PayPalPayment>(_serviceProvider),
            _ => throw new KeyNotFoundException($"未找到支付类型: {paymentType}")
        };
    }
}

IPaymentService.cs

public interface IPaymentService
{
    // 添加 orderId 作为方法参数
    Task<string> ProcessPayment(string orderId, decimal amount);
}

 

CreditCardPayment.cs,空的实现方法,后续新增的一个工厂都从这里开始第一步。

public class CreditCardPayment : IPaymentService
{
    // 添加 orderId 作为方法参数
    public Task<string> ProcessPayment(string orderId, decimal amount)
    {
        // 模拟异步操作
        return Task.FromResult($"信用卡支付处理成功! 订单: {orderId}, 金额: ${amount}");
    }
}

Repository仓储

IOrderDetrInfoRepository.cs仓储的接口类

public interface IOrderDetrInfoRepository
{
    Task<OrderDetail> GetOrderDetailAsync(string orderId);
    Task UpdateOrderStatusAsync(string orderId, string status);
}

OrderDetrInfoRepository.cs,仓储实现类。

public class OrderDetrInfoRepository : IOrderDetrInfoRepository
{
    private readonly AppDbContext _context;

    public OrderDetrInfoRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task<OrderDetail> GetOrderDetailAsync(string orderId)
    {
// 使用单次查询更新(减少上下文跟踪开销)
return await _context.OrderDetails .AsNoTracking() // 不需要更改跟踪时使用 .FirstOrDefaultAsync(o => o.OrderId == orderId) ?? throw new KeyNotFoundException($"订单 {orderId} 未找到"); } public async Task UpdateOrderStatusAsync(string orderId, string status) { var order = await GetOrderDetailAsync(orderId); order.PaymentStatus = status; _context.Update(order); await _context.SaveChangesAsync(); } }

UpdateOrderStatusAsync还有一种写法也值得记录一下

public async Task UpdateOrderStatusAsync(string orderId, string status)
{
    // 使用单次查询更新(减少上下文跟踪开销)
    var result = await _context.OrderDetails
        .Where(o => o.OrderId == orderId)
        .ExecuteUpdateAsync(setters => 
            setters.SetProperty(o => o.PaymentStatus, status));
    
    if (result == 0)
        throw new KeyNotFoundException($"订单 {orderId} 未找到");
}

PayPalPayment.cs实现

public class PayPalPayment : IPaymentService
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly ILogger<PayPalPayment> _logger;
    private readonly IOrderDetrInfoRepository _orderRepository;

    public PayPalPayment(
        IHttpClientFactory httpClientFactory,
        ILogger<PayPalPayment> logger,
        IOrderDetrInfoRepository orderRepository)
    {
        _httpClientFactory = httpClientFactory;
        _logger = logger;
        _orderRepository = orderRepository;
    }

    public async Task<string> ProcessPayment(string orderId, decimal amount)
    {
        OrderDetail order = null;
        try
        {
            // 确保在相同的作用域内完成所有数据库操作
            order = await _orderRepository.GetOrderDetailAsync(orderId);

            if (order.Amount != amount)
            {
                throw new ApplicationException($"金额不匹配: 请求金额 ${amount}, 订单金额 ${order.Amount}");
            }

            var response = await ProcessPayPalPaymentAsync(amount, order.Currency);

            await _orderRepository.UpdateOrderStatusAsync(orderId, "Completed");

            return $"PayPal支付成功! 订单: {orderId}, 交易ID: {response.TransactionId}";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "PayPal支付处理失败");

            if (order != null)
            {
                try
                {
                    await _orderRepository.UpdateOrderStatusAsync(orderId, "Failed");
                }
                catch (Exception updateEx)
                {
                    _logger.LogError(updateEx, "更新订单状态失败");
                }
            }

            throw; // 将异常抛出给上层处理
        }
    }

    private async Task<PayPalResponse> ProcessPayPalPaymentAsync(decimal amount, string currency)
    {
        using var client = _httpClientFactory.CreateClient("PayPal");
        var request = new PayPalRequest(amount, currency);

        var response = await client.PostAsJsonAsync("/v1/payments", request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<PayPalResponse>() ??
            throw new InvalidOperationException("无法解析PayPal响应");
    }

    private record PayPalRequest(decimal Amount, string Currency);
    private record PayPalResponse(string TransactionId);
}

 

三,后续

后续CreditCardPayment.cs和新增另一个接口的推送注册,gitee代码里就不添加了,仅作记录。

为了为信用卡支付添加新的HTTP推送地址,需要调整依赖注入配置和信用卡支付服务实现。

1. 修改 Program.cs 中的 HTTP 客户端配置

var builder = WebApplication.CreateBuilder(args);

// 添加 PayPal 支付网关
builder.Services.AddHttpClient("PayPal", client =>
{
    var config = builder.Configuration.GetSection("PayPal");
    client.BaseAddress = new Uri(config["BaseUrl"]);
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(30);
});

// 新增信用卡支付网关 
builder.Services.AddHttpClient("CreditCard", client =>
{
    var config = builder.Configuration.GetSection("CreditCard");
    client.BaseAddress = new Uri(config["BaseUrl"]);
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
        "Bearer", 
        config["ApiKey"]
    );
    client.Timeout = TimeSpan.FromSeconds(15);
});

// 其他配置保持不变...

2. 更新 appsettings.json 配置

{
  "PayPal": {
    "BaseUrl": "https://api.paypal.com/v1/",
    "ApiKey": "your_paypal_key"
  },
  "CreditCard": {
    "BaseUrl": "https://api.creditcard.com/payment/v2/",
    "ApiKey": "your_cc_api_key",
    "MerchantId": "YOUR_MERCHANT_ID"
  }
}

3. 修改信用卡支付服务实现
​​Services/Implementations/CreditCardPayment.cs​

using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

public class CreditCardPayment : IPaymentService
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _configuration;
    // ... 其他依赖

    public CreditCardPayment(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration,
        // ... 其他依赖)
    {
        _httpClientFactory = httpClientFactory;
        _configuration = configuration.GetSection("CreditCard");
        // ... 其他初始化
    }

    public async Task<string> ProcessPayment(string orderId, decimal amount)
    {
        OrderDetail order = await _orderRepository.GetOrderDetailAsync(orderId);
        
        // 创建支付请求
        var requestData = new CreditCardRequest(
            Amount: amount,
            Currency: order.Currency,
            OrderId: orderId,
            MerchantId: _configuration["MerchantId"]
        );

        try
        {
            // 使用命名客户端发送请求
            var client = _httpClientFactory.CreateClient("CreditCard");
            var response = await client.PostAsJsonAsync("process", requestData);
            response.EnsureSuccessStatusCode();
            
            var responseObj = await response.Content
                .ReadFromJsonAsync<CreditCardResponse>();
            
            await _orderRepository.UpdateOrderStatusAsync(orderId, "Completed");
            
            return $"信用卡支付成功! 授权码: {responseObj.AuthCode}";
        }
        catch (HttpRequestException ex)
        {
            // 处理特定的HTTP错误
            if (ex.StatusCode.HasValue && 
                (int)ex.StatusCode.Value >= 500)
            {
                _logger.LogWarning("信用卡网关服务器错误: {StatusCode}", ex.StatusCode);
            }
            // ... 其他错误处理
        }
    }

    // 信用卡请求DTO
    private record CreditCardRequest(
        decimal Amount, 
        string Currency, 
        string OrderId, 
        string MerchantId);
    
    // 信用卡响应DTO
    private record CreditCardResponse(string AuthCode, string TransactionId);
}

4. 优化HTTP请求处理(可选但推荐)
在 CreditCardPayment 类中添加重试策略:

private async Task<CreditCardResponse> ProcessCreditCardPayment(CreditCardRequest request)
{
    var policy = Policy<CreditCardResponse>
        .Handle<HttpRequestException>(ex => 
            ex.StatusCode != HttpStatusCode.BadRequest) // 除400外都重试
        .OrResult(r => r?.AuthCode == "TEMPORARY_FAILURE")
        .WaitAndRetryAsync(3, retryAttempt => 
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
    
    return await policy.ExecuteAsync(async () => {
        using var client = _httpClientFactory.CreateClient("CreditCard");
        var response = await client.PostAsJsonAsync("process", request);
        
        // 处理429 Too Many Requests
        if (response.StatusCode == HttpStatusCode.TooManyRequests)
        {
            var retryAfter = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(5);
            await Task.Delay(retryAfter);
            throw new HttpRequestException("Rate limited", null, HttpStatusCode.TooManyRequests);
        }
        
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<CreditCardResponse>();
    });
}

5. 测试配置的HTTP客户端
在单元测试中添加验证:

[Fact]
public void CreditCardClient_HasCorrectConfiguration()
{
    var factory = new TestHttpClientFactory();
    var config = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .Build();
    
    var service = new CreditCardPayment(
        factory, 
        config, 
        Mock.Of<IOrderDetrInfoRepository>(),
        Mock.Of<ILogger<CreditCardPayment>>());

    var client = factory.CreateClient("CreditCard");
    
    // 验证基地址
    client.BaseAddress.Should().Be(new Uri("https://api.creditcard.com/payment/v2/"));
    
    // 验证认证头
    client.DefaultRequestHeaders.Authorization.Parameter
        .Should().Be("your_cc_api_key");
    
    // 验证超时设置
    client.Timeout.Should().Be(TimeSpan.FromSeconds(15));
}

注意事项
​​敏感数据处理​​:
永远不要将API密钥硬编码在代码中(个人觉得没用)
使用Azure Key Vault或HashiCorp Vault管理密钥

// 安全获取密钥
var apiKey = await _secretClient.GetSecretAsync("CreditCardApiKey");

​​配置验证​​:
在应用启动时验证关键配置

app.MapGet("/config-check", (IConfiguration config) => 
{
    var ccConfig = config.GetSection("CreditCard");
    return string.IsNullOrEmpty(ccConfig["BaseUrl"]) 
        ? Results.Problem("Missing CreditCard base URL") 
        : Results.Ok("Config valid");
});

​​性能优化​​:

// 启动时预热连接
app.Services.GetService<StartupInitializer>().Initialize();

流量管理​​:

// 在CreditCard客户端添加速率限制器
builder.Services.AddRateLimiter(options => {
    options.AddPolicy<string>("CreditCardPolicy", context => 
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: "CreditCard",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                AutoReplenishment = true,
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }));
});

 

posted on 2025-07-08 10:09  御行所  阅读(27)  评论(0)    收藏  举报

导航