三层包模式是 Bitzsoft.Integrations 最核心的架构约定。它把”领域契约”和”厂商实现”严格分离,让消费者按需安装、按需注册,切换厂商只改配置不改代码。
以 Payment 域为例:
Bitzsoft.Integrations.Payment ← 抽象层├── Bitzsoft.Integrations.Payment.Alipay ← 实现层:支付宝├── Bitzsoft.Integrations.Payment.WeChatPay ← 实现层:微信支付├── Bitzsoft.Integrations.Payment.Stripe ← 实现层:Stripe└── Bitzsoft.Integrations.Payment.All ← 聚合层第一层:抽象包
包名:Bitzsoft.Integrations.{域}(如 Bitzsoft.Integrations.Payment)
定义领域契约,不含任何厂商逻辑。包含五类成员:
领域接口
public interface IPaymentProvider{ string Code { get; }
Task<PaymentResult<PaymentOrderResult>> CreateOrderAsync(PaymentOrderRequest request, CancellationToken ct = default); Task<PaymentResult<PaymentStatusResult>> QueryStatusAsync(string outTradeNo, CancellationToken ct = default); Task<PaymentResult> CloseOrderAsync(string outTradeNo, CancellationToken ct = default); Task<PaymentResult<RefundResult>> RefundAsync(RefundRequest request, CancellationToken ct = default); Task<PaymentResult<CallbackVerificationResult>> VerifyCallbackAsync(CallbackPayload payload, CancellationToken ct = default);}所有厂商实现这一个接口。消费者注入 IPaymentProvider,不用关心后面是哪家厂商。
共享 DTO / 模型 / 枚举
跨厂商统一的数据结构。如 PaymentOrderRequest(下单请求)、PaymentStatusResult(状态查询结果)、PaymentScene 枚举(App / Web / H5 / Code)。
结果包装
public sealed class PaymentResult<T>{ public bool IsSuccess { get; init; } public T? Data { get; init; } public string? ErrorCode { get; init; } public string? ErrorMessage { get; init; }
public static PaymentResult<T> Success(T data) => new() { IsSuccess = true, Data = data }; public static PaymentResult<T> Fail(string errorCode, string errorMessage) => new() { IsSuccess = false, ErrorCode = errorCode, ErrorMessage = errorMessage };}领域异常
public class PaymentException : Exception{ public string ProviderName { get; } public string? ErrorCode { get; } public string? ProviderMessage { get; } // 消息格式: "[{providerName}] {errorCode}: {providerMessage}"}供应商标识常量
public static class PaymentProviderCode{ public const string Alipay = "Alipay"; public const string WeChatPay = "WeChatPay"; public const string Stripe = "Stripe";}抽象包通过 InternalsVisibleTo 向自己的厂商包和聚合包开放 internal 成员:
<!-- src/Bitzsoft.Integrations.Payment/Bitzsoft.Integrations.Payment.csproj --><ItemGroup> <InternalsVisibleTo Include="Bitzsoft.Integrations.Payment.Alipay" /> <InternalsVisibleTo Include="Bitzsoft.Integrations.Payment.WeChatPay" /> <InternalsVisibleTo Include="Bitzsoft.Integrations.Payment.Stripe" /> <InternalsVisibleTo Include="Bitzsoft.Integrations.Payment.All" /></ItemGroup>第二层:厂商实现包
包名:Bitzsoft.Integrations.{域}.{厂商}(如 Bitzsoft.Integrations.Payment.Alipay)
每家厂商一个独立包。包含:
Bitzsoft.Integrations.Payment.Alipay/├── AlipayOptions.cs ← 厂商 Options(public sealed)├── AlipayPaymentProvider.cs ← Provider 实现(public sealed,构造函数 internal)├── AlipayProviderDescriptor.cs ← Provider 描述(internal static Instance)├── ServiceCollectionExtensions.cs ← DI 扩展(public static AddBitzsoftAlipayPayment)├── GlobalUsings.cs└── Internal/ ├── AlipayHttpClient.cs ← HTTP 封装(internal sealed) └── AlipaySigner.cs ← RSA 签名器(internal sealed, IDisposable)厂商 Options
public sealed class AlipayOptions{ public string AppId { get; set; } = string.Empty; public string AppPrivateKey { get; set; } = string.Empty; public string AlipayPublicKey { get; set; } = string.Empty; public string Gateway { get; set; } = "https://openapi.alipay.com/gateway.do"; public string NotifyUrl { get; set; } = string.Empty; public string SignType { get; set; } = "RSA2"; public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
// ① HttpClientName 默认 nameof(Provider类名),命名 HttpClient 的关键约定 public string HttpClientName { get; set; } = nameof(AlipayPaymentProvider);}字符串字段统一以 = string.Empty 兜底,避免 null。
Provider 实现
public sealed 类,构造函数 internal(禁止手动 new,只走 DI)。每个方法按 catch (DomainException) → catch (HttpRequestException) 分类型处理,转成 PaymentResult。
DI 扩展方法
namespace Microsoft.Extensions.DependencyInjection;
public static class AlipayServiceCollectionExtensions{ public static IServiceCollection AddBitzsoftAlipayPayment( this IServiceCollection services, Action<AlipayOptions> configure) { ... }
public static IServiceCollection AddBitzsoftAlipayPayment( this IServiceCollection services, IConfiguration configuration, string sectionKey = "Alipay") { ... }}命名约定 AddBitzsoft{厂商}{域}(),扩展类放 Microsoft.Extensions.DependencyInjection 命名空间。
第三层:聚合包
包名:Bitzsoft.Integrations.{域}.All(如 Bitzsoft.Integrations.Payment.All)
自身几乎不产 IL(IncludeBuildOutput=false),只靠 ProjectReference 把所有厂商实现拉进来。唯一编译产物是一个聚合 DI 扩展方法:
public static class PaymentServiceCollectionExtensions{ public static IServiceCollection AddBitzsoftPaymentAll( this IServiceCollection services, IConfiguration configuration, string sectionKey = "Payment") { var section = configuration.GetSection(sectionKey);
// ① 按配置节存在性自动注册——不存在的厂商不会被注册 if (section.GetSection("Alipay").Exists()) services.AddBitzsoftAlipayPayment(configuration, $"{sectionKey}:Alipay"); if (section.GetSection("WeChatPay").Exists()) services.AddBitzsoftWeChatPayPayment(configuration, $"{sectionKey}:WeChatPay"); if (section.GetSection("Stripe").Exists()) services.AddBitzsoftStripePayment(configuration, $"{sectionKey}:Stripe");
return services; }}消费者只装一个 .All 包、写一段配置、调一个方法,就能按配置节的存在性自动注册对应厂商。
聚合包的 csproj 有两个特殊属性:
<IncludeBuildOutput>false</IncludeBuildOutput><IncludeSymbols>false</IncludeSymbols>并用 build/NuGet/_._ 占位文件放进各 TFM 的 lib 目录,避免 NuGet “空 lib” 警告。