.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) })); });
浙公网安备 33010602011771号