当你需要同时接入两家以上厂商,或者要按租户、地区、灰度策略动态切换,就走「多厂商路由」路线。核心区别:不再直接注入能力接口,而是注入 IIntegrationProviderResolver<T>,按稳定的 Provider ID 解析出具体实现。
这篇指南用 Payment 域(支付宝 + 微信支付) 做完整可运行示例,模式适用于任何多厂商场景。
单厂商 vs 多厂商
业务代码(CreateOrderAsync、处理 Result)两种模式完全一样,区别只在「怎么拿到那个 Provider」。
多厂商路由五步
1. 安装聚合包
装一个 .All 包,它会通过 ProjectReference 把该域全部厂商实现拉进来。
dotnet new web -n MyMultiPaycd MyMultiPay
# ① 装聚合包,一次性带齐支付宝 + 微信 + Stripedotnet add package Bitzsoft.Integrations.Payment.All2. 配置多个厂商节
各厂商独立配置节,节名就是 Provider ID(如 Alipay、WeChatPay)。聚合注册方法只注册配置中存在的厂商——没配的厂商不会被注册,也不会报错。
{ "Payment": { "Alipay": { "AppId": "2021000...", "AppPrivateKey": "MIIEvQIBADANB...", "AlipayPublicKey": "MIIBIjANBg...", "NotifyUrl": "https://your-domain.com/callback/alipay" }, "WeChatPay": { "AppId": "wx...", "MchId": "1900...", "MchSerialNo": "...", "APIv3Key": "...", "PrivateKey": "-----BEGIN PRIVATE KEY-----...", "WeChatPayPublicKey": "-----BEGIN PUBLIC KEY-----..." } }}生产环境敏感字段用环境变量覆盖,例如 Payment__Alipay__AppPrivateKey。键名用双下划线分隔层级。
3. 注册聚合服务
调用 AddBitzsoft{域}All(),它会扫描配置节,只注册存在的厂商。
using Bitzsoft.Integrations.Payment;using Bitzsoft.Integrations.Payment.Models;
var builder = WebApplication.CreateBuilder(args);
// ① 一行注册全部——按配置节存在性自动注册builder.Services.AddBitzsoftPaymentAll(builder.Configuration);
var app = builder.Build();注册内部按节存在性逐个调用厂商注册方法:
// AddBitzsoftPaymentAll 内部逻辑(已实现,无需你写)var section = configuration.GetSection("Payment");if (section.GetSection("Alipay").Exists()) services.AddBitzsoftAlipayPayment(configuration, "Payment:Alipay");if (section.GetSection("WeChatPay").Exists()) services.AddBitzsoftWeChatPayPayment(configuration, "Payment:WeChatPay");if (section.GetSection("Stripe").Exists()) services.AddBitzsoftStripePayment(configuration, "Payment:Stripe");4. 注入 Resolver 按 ID 路由
多厂商场景下,必须用 IIntegrationProviderResolver<T> 按稳定的 Provider ID 解析,不要直接注入能力接口。
// Program.cs —— 按 URL 里的 provider 参数路由app.MapPost("/api/pay/{provider}", async ( string provider, IIntegrationProviderResolver<IPaymentProvider> providers, // ① 注入 Resolver PaymentOrderRequest request) =>{ // ② 按稳定 ID 解析;provider 传 "Alipay" 或 "WeChatPay"(大小写不敏感) IPaymentProvider payment; try { payment = providers.GetRequired(provider); } catch (IntegrationProviderResolutionException) { return Results.NotFound(new { error = $"未配置的支付厂商: {provider}" }); }
// ③ 拿到 Provider 后,业务代码和单厂商完全一样 var result = await payment.CreateOrderAsync(request); return result.IsSuccess ? Results.Ok(result.Data) : Results.BadRequest(new { result.ErrorCode, result.ErrorMessage });});为什么不能直接注入 IPaymentProvider
多厂商时直接注入 IPaymentProvider 拿到的是注册顺序的第一个,无法指定。Resolver 通过 ProviderKey(格式 {Domain}:{ProviderId})去重存储,按 ID 精确返回。
5. 用 Catalog 发现可用 Provider
IIntegrationProviderCatalog 枚举当前容器里所有已注册的 Provider,适合做动态展示、健康检查、配置校验。
// 列出当前已注册的全部支付厂商app.MapGet("/api/pay/providers", (IIntegrationProviderCatalog catalog) =>{ return catalog.Providers.Select(d => new { id = d.ProviderId, // "Alipay" / "WeChatPay" name = d.DisplayName, // "支付宝" / "微信支付" domain = d.Domain, // "Payment" stability = d.Stability, // Preview / Stable capabilities = d.CapabilityTypes.Select(t => t.Name), regions = d.Regions, });});也可以按精确 key 查询某个 Provider 是否存在:
// 校验目标 Provider 是否可用if (catalog.TryGet("Payment", "Alipay", out var descriptor)){ Console.WriteLine($"可用: {descriptor.DisplayName},稳定性 {descriptor.Stability}");}| API | 用途 |
|---|---|
IIntegrationProviderCatalog.Providers | 枚举全部已注册 Provider 的 Descriptor |
catalog.TryGet(domain, providerId, out var) | 按域 + ID 查询单个 Descriptor |
resolver.TryResolve(providerId, out var) | 尝试解析能力,不抛异常 |
resolver.GetRequired(providerId) | 解析能力,不存在抛 IntegrationProviderResolutionException |
resolver.Providers | 枚举全部能力实例 |
完整可运行示例
下面这个最小应用同时支持支付宝和微信支付,按 URL 选择。
dotnet new web -n MyMultiPay && cd MyMultiPaydotnet add package Bitzsoft.Integrations.Payment.Allusing Bitzsoft.Integrations.Payment;using Bitzsoft.Integrations.Payment.Models;
var builder = WebApplication.CreateBuilder(args);builder.Services.AddBitzsoftPaymentAll(builder.Configuration); // ① 注册全部var app = builder.Build();
// ② 路由:用支付宝下单app.MapPost("/api/pay/alipay", async ( IIntegrationProviderResolver<IPaymentProvider> providers, PaymentOrderRequest req) =>{ var payment = providers.GetRequired(PaymentProviderCode.Alipay); // ③ 用常量而非硬编码字符串 var result = await payment.CreateOrderAsync(req); return result.IsSuccess ? Results.Ok(result.Data) : Results.BadRequest(result.ErrorMessage);});
// ④ 路由:用微信支付下单app.MapPost("/api/pay/wechat", async ( IIntegrationProviderResolver<IPaymentProvider> providers, PaymentOrderRequest req) =>{ var payment = providers.GetRequired(PaymentProviderCode.WeChatPay); var result = await payment.CreateOrderAsync(req); return result.IsSuccess ? Results.Ok(result.Data) : Results.BadRequest(result.ErrorMessage);});
// ⑤ 动态路由:URL 里带 providerapp.MapPost("/api/pay/{provider}", async ( string provider, IIntegrationProviderResolver<IPaymentProvider> providers, PaymentOrderRequest req) =>{ if (!providers.TryResolve(provider, out var payment)) return Results.NotFound($"未配置的厂商: {provider}");
var result = await payment.CreateOrderAsync(req); return result.IsSuccess ? Results.Ok(result.Data) : Results.BadRequest(result.ErrorMessage);});
// ⑥ 列出可用厂商app.MapGet("/api/pay/providers", (IIntegrationProviderCatalog catalog) => catalog.Providers.Select(d => new { d.ProviderId, d.DisplayName, d.Stability }));
app.Run();路由策略示例
Provider ID 是稳定配置值,适合做各种路由策略:
// 按租户配置路由public sealed class TenantPaymentRouter( IIntegrationProviderResolver<IPaymentProvider> providers, ITenantStore tenants){ public async Task<PaymentResult<PaymentOrderResult>> PayAsync( string tenantId, PaymentOrderRequest req) { var providerId = await tenants.GetPaymentProviderAsync(tenantId); // "Alipay" 或 "WeChatPay" var payment = providers.GetRequired(providerId); // ① 按租户配置路由 return await payment.CreateOrderAsync(req); }}
// 按地区路由:大陆用支付宝/微信,海外用 Stripepublic sealed class RegionalPaymentRouter( IIntegrationProviderResolver<IPaymentProvider> providers){ public IPaymentProvider Resolve(string region) => region switch { "CN" => providers.GetRequired(PaymentProviderCode.Alipay), // ② 大陆默认支付宝 "TW" or "HK" => providers.GetRequired(PaymentProviderCode.WeChatPay), _ => providers.GetRequired(PaymentProviderCode.Stripe), // ③ 海外用 Stripe };}常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
IntegrationProviderResolutionException | Provider ID 不存在或拼写错误 | 检查 provider 参数大小写,确认配置节已写入 |
注入 IPaymentProvider 拿到错误的厂商 | 多厂商时直接注入只拿第一个 | 改用 IIntegrationProviderResolver<T> 按解析 |
| 某厂商没被注册 | 配置节缺失 | 确认 Payment:{厂商} 节存在且非空 |
IEnumerable<T> 数量对不上 | 注册顺序 + TryAdd 丢弃后续实现 | 不要用 IEnumerable,用 Resolver |
何时回到单厂商模式
如果你的应用确定只用一家(比如内部系统只用支付宝,没有多租户、多地区需求),回到单厂商消费更简单——少装一个聚合包、少注入一层 Resolver。两种模式的业务代码可平滑互转。
下一步
- 单厂商消费指南:只用一家时的更简方案。
- Provider 目录与解析:Descriptor、Catalog、Resolver 的设计原理。
- ADR-0005:Provider Catalog 与 Resolver:跨 TFM 路由的决策依据。