前言
基础设施层(Ordering.Infrastructure)
1.基础设施层职责
| 职责类型 | 说明 |
|---|---|
| 1️⃣ 持久化实现(Repository 实现) | 实现领域层定义的仓储接口(IOrderRepository、IBuyerRepository等),与数据库交互(通常使用 ORM,如 EF Core、Dapper)。 |
| 2️⃣ 外部通信支持 | 提供与外部系统(HTTP、消息队列、文件、缓存、第三方服务等)的交互实现。 |
| 3️⃣ 跨领域通用服务 | 实现如日志、邮件发送、文件存储、加密、身份验证等通用技术功能。 |
| 4️⃣ 基础设施封装 | 封装底层框架和外部依赖,使上层(应用/领域层)不直接依赖框架实现。 |
| 5️⃣ 依赖注入(IoC)配置 | 负责将具体实现注册到依赖注入容器中。 |
2.基础设施层设计原则
| 原则 | 说明 |
|---|---|
| 1️⃣ 技术与业务分离 | 基础设施层只处理“技术性细节”,不包含任何业务逻辑。 |
| 2️⃣ 依赖倒置原则(DIP) | 领域层和应用层定义接口(如 IOrderRepository),基础设施层实现这些接口;这样高层不依赖低层。 |
| 3️⃣ 低耦合、高内聚 | 实现类内部封装对数据库、文件系统、外部 API 的操作,不泄露具体技术细节。 |
| 4️⃣ 可替换性 | 可随时替换底层技术(如 EF → Dapper → MongoDB),不影响上层业务逻辑。 |
| 5️⃣ 对外提供能力,对内隐藏实现 | 外部层通过接口访问基础设施能力,不直接依赖实现类。 |
| 6️⃣ 最小依赖原则 | 尽量减少对第三方库的直接依赖;如果使用外部 SDK,最好通过适配器封装。 |
| 7️⃣ 稳定性优先 | 基础设施层应尽可能稳定,不频繁修改;它是整个系统的技术“地基”。 |
3.基础设施层组成架构
| 组成模块 | 作用说明 | 示例实现 |
|---|---|---|
| 1️⃣ 持久化(Persistence) | 提供数据持久化支持,实现领域层定义的仓储接口(Repository),与数据库交互(ORM / SQL)。 | OrderRepository、AppDbContext、OrderEntityTypeConfiguration |
| 2️⃣ 仓储实现(Repository Implementations) | 实现 IRepository<T> 接口,封装数据库访问逻辑,避免领域层直接依赖 ORM。 |
BuyerRepository、OrderRepository |
| 3️⃣ 数据访问组件(Data Access Utilities) | 封装通用查询、分页、事务(UnitOfWork)等基础数据操作。 | EfUnitOfWork、SpecificationEvaluator |
| 4️⃣ 消息总线(EventBus / Messaging) | 实现领域事件与集成事件的消息发布、订阅机制,支撑异步通信。 | RabbitMqEventBus、IntegrationEventService |
| 5️⃣ 外部系统适配(Integration / Adapter) | 对接外部服务或第三方系统,提供 HTTP、RPC、支付、短信等调用封装。 | PaymentServiceAdapter、EmailSenderAdapter |
| 6️⃣ 缓存服务(Caching) | 封装分布式或本地缓存技术,提升读性能,支持缓存一致性策略。 | RedisCacheService、MemoryCacheProvider |
| 7️⃣ 文件与对象存储(File Storage) | 封装文件系统或云存储(如 OSS、S3)操作接口。 | LocalFileStorage、AliyunOssStorageService |
| 8️⃣ 身份与权限(Identity / Auth) | 提供统一认证授权支持,封装外部身份服务调用。 | IdentityServiceAdapter、JwtTokenProvider |
| 9️⃣ 日志与审计(Logging / Auditing) | 统一日志接口和实现,记录系统操作与异常信息,支持分布式追踪。 | SerilogLogger、AuditTrailService |
| 🔟 配置与密钥管理(Configuration / Secrets) | 统一管理系统配置与敏感信息访问。 | AppSettingsProvider、KeyVaultProvider |
| 11️⃣ 后台任务与调度(Background Jobs / Scheduler) | 封装定时任务、后台处理机制。 | QuartzJobScheduler、OutboxDispatcher |
| 12️⃣ 健康与监控(Health / Telemetry) | 提供健康检查、日志追踪、指标采集等运维支持。 | HealthCheckService、OpenTelemetryTracer |
| 13️⃣ 工具与基础组件(Infrastructure Utilities) | 提供通用的技术工具,如时间、加密、GUID、文件工具类。 | SystemClock、EncryptionHelper |
| 14️⃣ IoC 注册与模块加载(Dependency Injection Setup) | 注册基础设施层所有依赖与模块,供应用层使用。 | InfrastructureModule、ServiceCollectionExtensions |
4.数据持久化
4.1订单数据库上下文(OrderingContext)
OrderingContext 是订单微服务的 EF Core 工作单元与领域事件协调中心,负责管理订单相关实体的持久化、事务控制、及领域事件分发,实现业务数据一致性与集成事件日志支持。

4.2EFCore模型配置(Model Configuration)
EF Core 模型配置:使用 Fluent API + IEntityTypeConfiguration<T> 实现实体到数据库表的映射关系,
通过 OnModelCreating 统一注册,支持 Schema 隔离与集成事件日志(Outbox)扩展。
OnModelCreating配置了:
- 默认 Schema 为
ordering - 配置请求表
- 配置支付方式表
- 配置订单表
- 配置订单明细表
- 配置信用卡类型表
- 配置买家表
- 启用集成事件日志表

Schema 隔离 就是通过为每个上下文指定独立的数据库命名空间(Schema),
使得不同领域或微服务的表逻辑上隔离,避免冲突并提升可维护性。

4.3数据库迁移
数据库迁移 是指在应用程序开发过程中,随着领域模型或数据库结构的变化,自动或半自动地管理数据库模式(Schema)版本的过程。
它的核心目标是:
- 同步数据库结构与代码模型(比如 EF Core 的实体类)
- 版本化数据库变更,保证团队协作和生产环境安全
- 简化数据库升级和回滚,减少手工修改 SQL 的风险
EFCore执行数据库迁移命令的时候也是在基础设施层执行
dotnet ef migrations add --startup-project Ordering.API --context OrderingContext [migration-name]

eshop对迁移和种子数据进行了封装不需要手动执行命令

4.3总结
数据库上下文(DbContext)管理
- 统一管理所有实体集合(DbSet)和数据库连接。
- 提供事务边界和工作单元(UnitOfWork)支持。
实体映射(Entity Mapping)
- 使用 ORM(如 EF Core)将领域模型映射到数据库表。
- 配置表结构、字段类型、索引、关系、默认 Schema。
数据库迁移
- 同步代码模型和数据库结构
数据持久化模块是基础设施层的核心,负责将领域模型可靠、安全地存储到数据库,提供事务、一致性、映射、查询和扩展功能,是领域层与数据库之间的桥梁。
5.仓储实现
5.1买家仓储(BuyerRepository)
BuyerRepository 是基础设施层对 Buyer 聚合根的仓储实现,继承了领域层买家仓储接口(IBuyerRepository),封装 EF Core 对买家及其支付方式的增删改查操作,并通过工作单元提供事务管理,保证聚合一致性与领域隔离。

5.2订单仓储(OrderRepository)
OrderRepository 是基础设施层对 Order 聚合根的仓储实现,继承了领域层订单仓储接口(IOrderRepository),封装 EF Core 对订单及其明细的增删改查操作,通过工作单元管理事务,并使用显式加载保证聚合完整性与一致性。

5.3总结
聚合根访问
- 仓储只对聚合根(Aggregate Root)提供入口,不暴露内部子实体。
- 通过聚合根的方法保持聚合内部的一致性和完整性。
CRUD 封装
- 提供基础增删改查接口:
Add、Update、Delete、GetById、FindAsync。 - 可以封装软删除、瞬态判断、批量操作等策略。
查询封装
- 封装 LINQ 查询、Specification 模式或原生 SQL 查询。
- 提供分页、排序、Include 导航属性等通用查询功能。
事务支持
- 仓储通过工作单元(UnitOfWork)提供事务边界,保证跨多个仓储操作的原子性。
- 示例:仓储接口
IRepository<T>通常暴露UnitOfWork属性。
隔离领域层与基础设施
- 领域层只依赖仓储接口,不直接操作 ORM 或数据库。
- 可以在不修改领域层的情况下替换底层实现(EF Core、Dapper、MongoDB 等)。
仓储是领域层与持久化层之间的抽象接口,封装聚合根访问、CRUD、查询和事务操作,保证领域模型独立于具体数据访问实现,同时提供统一的数据操作入口。
6.数据访问组件
在 DDD 架构中,数据访问组件通常属于 基础设施层(Infrastructure Layer),主要职责是:
- 封装通用的数据操作逻辑,如增删改查(CRUD)。
- 提供事务控制(Unit of Work),保证多个操作在同一事务中提交或回滚。
- 支持查询策略(Specifications)和分页,避免业务层直接操作 ORM。
- 解耦领域层和 ORM 框架,确保领域层不依赖具体数据库实现。
核心目的是:让应用层或领域层只关注业务逻辑,不关心具体数据访问实现。
6.1事务管理
正常的事务管理由IUnitOfWork接口提供,但是eshop的IUnitOfWork没有封装事务管理

订单数据库上下文OrderingContext实现了事务的相关方法:
BeginTransactionAsync:开始事务CommitTransactionAsync:提交事务RollbackTransaction:回滚事务

6.2通用仓储和工作单元(EfRepository)
eshop中没有封装统一的仓储,既然学到这了,我们就顺手封装一个,顺便加深基础设施层的理解
6.2.1仓储接口(IEfRepository<T>)
首先仓储接口限制了泛型只能是聚合根,然后大致看一下我们需要实现的四部分:
- 基本的CRUD
- 批量增删改
Specification查询:Specification(规范)是一个封装业务规则或查询条件的对象,用于在仓储或查询中复用和解耦业务逻辑。- 原生
sql查询

6.2.2基础CRUD
注入EfRepository需要传入2个参数:
TContext:DbContext类型。每个服务的DbContext不一样,比如:商品服务的是CatalogContext,订单服务的是OrderingContextT:具体的实体类型。

CRUD这部分就是调用了EFCore的一些方法:
FindAsync:条件查询ToListAsync:将IQueryable或IEnumerable查询结果转换成List<T>。AddAsync:添加实体Update:更新实体Delete:删除实体

6.2.3批量操作(增/删)
批量操作(增/删)也是调用EFCore的方法:
AddRangeAsync:批量添加一组实体到 DbContext 中。UpdateRange:批量更新一组实体。DeleteRange:遍历传入的实体集合,依次调用Delete(entity)方法。

6.2.4Specification查询
根据传入的 Specification 对象构造 IQueryable 查询:先应用主条件(Criteria)和额外条件(AdditionalCriteria),再按指定的排序(OrderBy/OrderByDescending)排列,同时自动 Include 导航属性和过滤软删除数据,但不执行分页操作,保证查询逻辑集中、复用性高且避免重复分页导致数据库错误。
后面我们会进行单元测试,这里先了解一下,后面结合用法在加深影响
private IQueryable<T> ApplySpecification(ISpecification<T> spec){IQueryable<T> query = _dbSet.AsQueryable();if (spec == null) return query;// 过滤条件if (spec.Criteria != null)query = query.Where(spec.Criteria);// 额外的 AND 条件(多个 Criteria 组合)if (spec.AdditionalCriteria != null)foreach (var additional in spec.AdditionalCriteria)if (additional != null)query = query.Where(additional);// 排序:优先支持通过委托传入排序函数if (spec.OrderBy != null) query = spec.OrderBy(query);if (spec.OrderByDescending != null) query = spec.OrderByDescending(query);// Include 导航属性(支持表达式集合)if (spec.Includes != null){foreach (var include in spec.Includes){if (include != null)query = query.Include(include);}}// 软删除过滤:当实体实现 ISoftDelete 时自动过滤已删除数据if (typeof(ISoftDelete).IsAssignableFrom(typeof(T))){// 使用 LINQ 转换做软删除过滤,避免直接转换为 SQL 常量导致问题query = query.Where(e => !((ISoftDelete)e).IsDeleted);}// 注意:不要在这里做 Skip/Take(分页),由仓储分页入口统一负责,避免重复分页产生负 OFFSET。return query;}

6.2.5原生sql查询
平时开发可能原生sql用的还是比较多,因为关联表太多了,原生sql查询还是方便些,这里就加上了这部分功能:
ListBySqlAsync:原生SQL条件查询ListBySqlPagedAsync:原生SQL条件分页查询ExecuteSqlAsync:执行原生 (非查询语句),返回受影响行数

6.2.6单元测试
上面那些基本的查询都是平时常用的,所以我这边基本看了一下,实现了这个仓储层,然后单元测试几个复杂的接口(平时单元测试用的并不多,所以也记录一下)
首先了解几个包:
| 包名 | 作用 |
|---|---|
xunit |
核心测试框架,定义和执行单元测试 |
xunit.runner.visualstudio |
运行器,集成到 VS 测试资源管理器和命令行 |
NSubstitute |
Mock 框架,用于创建接口或虚方法的替身对象 |
NSubstitute.Analyzers.CSharp |
分析器,静态检查 NSubstitute 使用问题,提供即时提示 |

在单元测试类的构造函数中注入:
- 数据库连接
OrderingContext - 工作单元
EfUnitOfWork - 订单仓储
EfRepository<OrderingContext, Order> - 买家仓储
EfRepository<OrderingContext, Buyer>

创建支付方式、买家、订单
创建买家、支付方式、订单,封装完整的EfRepository,就不用每个服务在单独封装仓储了
// 测试:添加订单项
// 测试:完整添加订单(包括订单项、买家、支付方式)
[Fact]
public async Task CanAddFullOrderWithBuyerAndPayment()
{using var scope = _serviceProvider.CreateScope();var orderRepository = scope.ServiceProvider.GetRequiredService<IEfRepository<Order>>();var buyerRepository = scope.ServiceProvider.GetRequiredService<IEfRepository<Buyer>>();var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();// 1️.创建 Buyer 并初始化支付方式var expiration = DateTime.UtcNow.AddYears(3);var identity = Guid.NewGuid().ToString();var buyer = BuyerFactory.CreateBuyerWithPayment(identity: identity,name: "张三",cardTypeId: 1,alias: "招商银行信用卡",cardNumber: "6225888888888888",securityNumber: "321",cardHolderName: "Zhang San",expiration: expiration,orderId: 0);await buyerRepository.AddAsync(buyer);await unitOfWork.SaveChangesAsync();// 2️.创建订单var order = OrderFactory.CreateOrder(userId: buyer.IdentityGuid,userName: buyer.Name,address: new Address(street: "朝阳区建国路88号 SOHO大厦A座18层",city: "北京",state: "北京",country: "中国",zipcode: "100022"),cardTypeId: 1,cardNumber: "6225888888888888",cardSecurityNumber: "321",cardHolderName: "Zhang San",cardExpiration: expiration);order.SetPaymentMethodVerified(buyer.Id, buyer.PaymentMethods.First().Id);// 3. 添加订单项order.AddOrderItem(productId: 101,productName: "Laptop",unitPrice: 1000,discount: 50,pictureUrl: "http://example.com/laptop.jpg",units: 2);// 4.保存到仓储await orderRepository.AddAsync(order);await unitOfWork.SaveChangesAsync();// 5.验证保存成功var savedOrder = await orderRepository.GetByIdAsync(order.Id);Assert.NotNull(savedOrder);Assert.Equal(OrderStatus.Submitted, savedOrder.OrderStatus);// 验证 BuyerAssert.NotNull(savedOrder.Buyer);Assert.Equal(identity, savedOrder.Buyer.IdentityGuid);// 验证支付方式Assert.Single(savedOrder.Buyer.PaymentMethods);var payment = savedOrder.Buyer.PaymentMethods.First();Assert.Equal(buyer.PaymentMethods.First().Id, order.PaymentId);// 验证订单项Assert.Single(savedOrder.OrderItems);var item = savedOrder.OrderItems.First();Assert.Equal(101, item.ProductId);
}

不放心就去数据库看看

复杂条件分页查询
涉及到多条件、排序、导航属性、分页的复杂查询
public class OrdersByUserSpecification : ISpecification<Order>{public OrdersByUserSpecification(OrderStatus orderStatus,int? pageIndex = null,int? pageSize = null,bool orderByDateDesc = true){// 主过滤条件Criteria = o => o.OrderStatus == orderStatus;// 额外过滤条件AdditionalCriteria = new System.Collections.Generic.List<Expression<Func<Order, bool>>> {o => !string.IsNullOrEmpty(o.Description),o => o.OrderStatus != OrderStatus.AwaitingValidation,};// Include导航属性:加载订单明细和买家信息Includes = new System.Collections.Generic.List<Expression<Func<Order, object>>> {o => o.OrderItems,o => o.Buyer};// 排序if (orderByDateDesc){OrderByDescending = q => q.OrderByDescending(o => o.OrderDate);}else{OrderBy = q => q.OrderBy(o => o.OrderDate);}// 分页if (pageIndex.HasValue && pageSize.HasValue){IsPagingEnabled = true;Skip = (pageIndex.Value - 1) * pageSize.Value;Take = pageSize.Value;}else{IsPagingEnabled = false;}}public Expression<Func<Order, bool>> Criteria { get; }public System.Collections.Generic.List<Expression<Func<Order, bool>>> AdditionalCriteria { get; }public Func<IQueryable<Order>, IOrderedQueryable<Order>> OrderBy { get; private set; }public Func<IQueryable<Order>, IOrderedQueryable<Order>> OrderByDescending { get; private set; }public bool IsPagingEnabled { get; private set; }public int Skip { get; private set; }public int Take { get; private set; }public System.Collections.Generic.List<Expression<Func<Order, object>>> Includes { get; }}

原生sql分页查询
原生sql分页查询相对来说更灵活

6.3总结
通用 CRUD 封装
- 提供增删改查的基础方法,供各个仓储调用。
- 可以封装软删除、批量操作、瞬态判断等逻辑。
分页与排序支持
- 统一实现分页查询逻辑,避免各仓储重复实现。
- 支持分页、Skip/Take、排序规则(升序/降序)。
Specification 查询
- 支持领域规范(Specification Pattern)查询,动态组合条件、Include、排序等。
- 仓储只调用
ApplySpecification即可完成复杂查询逻辑,而无需关心 LINQ 细节。
事务管理(UnitOfWork)
- 与
IUnitOfWork配合,实现跨多个仓储的事务边界。 - 统一处理
SaveChanges和领域事件分发(如EfUnitOfWork)。
原生 SQL / DTO 支持
- 支持原生 SQL 查询(
FromSqlRaw)或映射到无主键 DTO。 - 支持分页、参数化 SQL,保证灵活性和安全性。
复用与解耦
- 仓储实现不直接处理 LINQ 或 EF Core 查询细节,只调用数据访问组件。
- 提高代码复用,减少重复查询逻辑。
数据访问组件是基础设施层的通用工具类,统一封装 CRUD、分页、Specification 查询、事务和原生 SQL 支持,解耦仓储与具体 ORM,实现跨实体和仓储的可复用数据访问逻辑。
7.集成事件
之前详细学过集成事件的代码,这次从设计的角度学习一下
eshop中事件总线总线封装了三个类库:
EventBus:抽象层 + 框架级逻辑(发布/订阅、事件类型到处理器的映射、订阅注册表、消息反序列化与分发)。不依赖具体中间件,实现总线的公共行为和契约。EventBusRabbitMQ:EventBus的 RabbitMQ 具体实现,负责与 RabbitMQ 的连接/通道管理、交换机/队列/绑定、消息投递与消费者回调(ack/nack、重连、QoS等)。IntegrationEventLogEF:Outbox/事件日志实现(基于 EF Core)。用于在业务事务中持久化“要发布的事件”,并由后台发布器读取并交给EventBus投递,从而实现可靠投递与事务一致性。
这三者常见配合流程:业务代码 -> 保存业务数据 + 保存事件日志(同一事务) -> 事务提交 -> 后台任务读取数据库中未发布事件 -> 调用 EventBus.Publish(...)(由 EventBusRabbitMQ 实际发出)。

7.1事件总线抽象层(EventBus)
7.1.1事件总线订阅信息(EventBusSubscriptionInfo)
EventBusSubscriptionInfo 是 eShop.EventBus.Abstractions 中的一个辅助类,它主要负责两件事:
- 管理事件名称与 CLR 类型的映射关系(即订阅表):确定每个事件消息名对应哪个 .NET 类型,方便反序列化。
- 提供序列化/反序列化的配置选项(
JsonSerializerOptions):保证事件在不同运行环境(含 AOT / trimming 发布)下能正确序列化与反序列化。
EventTypes:存储服务订阅的事件类型,键是服务名称,值是服务类型
JsonSerializerOptions:根据环境返回不同的JsonSerializerOptions,用于支持AOT/trimming环境

7.1.2事件总线接口(IEventBus)
IEventBus 是整个 eShop 事件驱动架构的“抽象总线入口”,它定义了微服务之间发布事件的唯一契约,使得服务间通信与中间件实现彻底解耦。
简单理解:抽象事件总线接口,这里只定义了发布事件的方法

7.1.3事件总线构建器(IEventBusBuilder)
IEventBusBuilder 是事件总线注册体系中的“构建器接口”,它承载 DI 容器上下文,使得框架和扩展包可以用统一、流式的方式注册事件总线及其依赖组件(如 RabbitMQ、订阅管理器等)。
简单理解:Programe在调用事件总线或者依赖组件时可以流式调用

7.1.4事件处理器接口(IIntegrationEventHandler)
这组接口用一个弱类型统一入口(IIntegrationEventHandler)配合强类型泛型实现(IIntegrationEventHandler<T>)的桥接方式,既满足事件总线运行时的动态调用需求,又让业务实现保持类型安全和易读性。
-
IIntegrationEventHandler:- 所有事件处理器的 统一入口接口(弱类型)。
- 定义了通用的事件处理方法
Task Handle(IntegrationEvent @event)。 - 事件总线(EventBus)在接收到任意事件消息后,统一调用此接口,而不关心事件的具体类型。
- 实际的强类型事件处理器(
IIntegrationEventHandler<T>)通过显式接口实现该方法,自动将事件从基类IntegrationEvent转换为具体的类型TIntegrationEvent。
-
IIntegrationEventHandler<T>:-
强类型事件处理接口,用于处理特定类型的集成事件。
-
继承自
IIntegrationEventHandler,并对其Handle方法进行了显式实现:Task IIntegrationEventHandler.Handle(IntegrationEvent @event)=> Handle((TIntegrationEvent)@event); -
这样事件总线只需调用统一的
Handle(IntegrationEvent),框架内部自动完成类型转换并调用具体的强类型处理逻辑Handle(TIntegrationEvent @event)。 -
开发者只需专注于实现 业务逻辑部分,例如:
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent> {public async Task Handle(OrderStartedIntegrationEvent @event){// 处理订单创建事件的业务逻辑} }
-
流程
EventBus 收到消息│▼
IIntegrationEventHandler.Handle(IntegrationEvent)│(显式实现)▼
IIntegrationEventHandler<T>.Handle(TIntegrationEvent)│▼
具体事件处理器中的业务逻辑执行,如OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent>
总结
IIntegrationEventHandler提供了统一的事件调用入口IIntegrationEventHandler<T>提供了类型安全的业务逻辑实现

7.1.5集成事件基类(IntegrationEvent)
IntegrationEvent是所有跨微服务事件(集成事件)的通用基类:
Id:事件唯一标识符。每个事件实例在创建时自动分配一个Guid。用于幂等控制、防止重复消费。CreationDate:事件创建的 UTC 时间,用于事件日志、监控、消息时序分析等。
并通过 [JsonInclude] 与 record 支持可靠的序列化、可追踪性和不可变性。
[JsonInclude]的作用
System.Text.Json默认只序列化 public 属性带有 getter 的成员。而在record或 AOT 环境下(如 Minimal API、Native AOT 发布),反序列化可能不调用构造函数。加上[JsonInclude]后,无论构造函数是否被调用,JSON 序列化器都会:
- 在序列化时包含这些字段
- 在反序列化时正确填充它们
为何定义为
record
record是值语义类型(以数据为中心),非常适合表示“消息”或“事件”这种不可变数据载体- 默认支持结构性相等(两个事件的字段值相同则相等)
- 支持简洁的模式匹配与
with表达式;- 序列化/反序列化更加自然。

7.1.6事件总线构建器扩展类(EventBusBuilderExtensions)
这是一个 静态类,用于对 IEventBusBuilder 提供扩展方法。使用 链式调用(Fluent API) 配置事件总线,便于在启动时统一注册事件与处理器。
作用:
- 配置事件总线使用的 JSON 序列化选项。
- 注册事件订阅及其处理器到依赖注入容器。
ConfigureJsonOptions:EventBusSubscriptionInfo 会根据运行环境生成适配的 JsonSerializerOptions(支持 AOT/Trim),并将其配置到事件总线构建器中。

AddSubscription:注册事件订阅及对应处理器
- 注册处理器到 DI 容器:
- 使用 Keyed Service 模式 ,为每种事件类型绑定一个 Key(这里是
typeof(T))。 - 允许同一个事件类型有多个处理器。
- 在事件总线运行时,可以通过事件类型快速找到对应的处理器集合。
- 使用 Keyed Service 模式 ,为每种事件类型绑定一个 Key(这里是
- 存储事件类型映射:
- EventBus 内部使用事件名(通常是
T.Name)发送/订阅消息。 - 通过映射表:
- 消费者可以直接找到事件对应的 CLR Type
- 避免在运行时通过反射
Type.GetType查找类型,提高性能
- 也方便 EventBus 在初始化时订阅消息队列对应的事件。
- EventBus 内部使用事件名(通常是

EventBusBuilderExtensions 提供了 事件总线配置和订阅注册的核心工具,通过链式 API 配置 JSON 序列化、注册事件处理器,并维护事件类型映射表,使 EventBus 能高效、安全、类型化地发布和处理事件。
7.1.7反射扩展工具类(GenericTypeExtensions)
GenericTypeExtensions提供了获取泛型类型名称和根据属性名动态获取对象属性值的方法,并兼顾 AOT/Trim 场景下的反射安全性
这2个方法都是获取类型名称

根据传入的属性名,从对象实例中获取对应属性的值,并返回该值的字符串表示
这个类是我在优化幂等性命令处理器抽象类的时候加的,为了优化里面的switch case,获取实际命令的名称

7.1.8统一using 管理(GlobalUsings)
这两行 global using 在 eShop.EventBus 类库中全局引入 JSON 序列化和事件类型命名空间,使所有文件无需重复 using,方便事件序列化与处理。

7.2事件总线实现层(EventBusRabbitMQ)
7.2.1Activity扩展类(ActivityExtensions)
ActivityExtensions是一个 Activity 扩展类,用于在 分布式追踪/OpenTelemetry 中将异常信息记录到 Activity(跟踪/Span)上。
AddTag:异常信息标签,遵循 OpenTelemetry 的 [Semantic Conventions]:
| 标签名 | 说明 |
|---|---|
exception.message |
异常消息文本 |
exception.stacktrace |
异常堆栈信息(完整 ToString()) |
exception.type |
异常类型全名(Namespace + 类名) |
这些信息会被追踪系统(如 Jaeger、Zipkin、OpenTelemetry Collector)收集,便于排查问题。
SetStatus(ActivityStatusCode.Error):标记 Activity 状态为错误,让监控系统知道这个 Span/Activity 表示一次失败操作。
ActivityStatusCode 是 OpenTelemetry 中的标准状态码:
Ok:正常Error:异常/失败Unset:未设置状态

7.2.2事件总线配置选项类(EventBusOptions)
EventBusOptions 是一个典型的 POCO(Plain Old CLR Object,普通的 CLR 对象,纯数据载体,可用于配置或数据传输)配置类,主要用于:
- 将 appsettings.json 中
"EventBus"配置节绑定到强类型对象。 - 通过 依赖注入(
IOptions<EventBusOptions>或IOptionsMonitor<EventBusOptions>)在程序中使用配置。 - 提供默认值(如
RetryCount = 10)并允许外部覆盖。

以订单服务(Ordering.API)的EventBusOptions配置为例

7.2.3统一using 管理(GlobalUsings)
GlobalUsings 的作用是:在整个项目范围内统一引入事件总线、RabbitMQ、JSON、日志、网络和弹性策略相关命名空间,避免每个文件重复写 using。

7.2.4RabbitMQ事件总线依赖注入扩展(RabbitMqDependencyInjectionExtensions)
RabbitMqDependencyInjectionExtensions是 RabbitMQ 事件总线的依赖注入扩展类,用于在 .NET 7+ 的 Generic Host 或 WebApplicationBuilder 环境中,方便地注册 RabbitMQ 事件总线及相关服务。
简单的总结一下AddRabbitMqEventBus:
- 1.参数验证
- 2.注册
RabbitMQ客户端 - 3.集成
OpenTelemetry追踪 - 4.注册
EventBusOptions选项配置 - 5.注册
RabbitMQ分布式追踪与上下文传播(RabbitMQTelemetry) - 6.注册事件总线接口和实现(
IEventBus, RabbitMQEventBus) - 7.注册消费者(
IHostedService, RabbitMQEventBus)

内部类实现IEventBusBuilder,封装 IServiceCollection 并提供链式调用能力,同时隐藏具体实现,接口本身无法直接实例化且不便于存储状态,方便在调用 AddRabbitMqEventBus 后继续调用扩展方法

7.2.5RabbitMQ事件总线实现类(RabbitMQEventBus)
RabbitMQEventBus 是一个 支持 RabbitMQ 消息的事件总线实现,集成了 OpenTelemetry 分布式追踪、弹性重试策略,并作为后台服务处理异步消息消费。
RabbitMQEventBus 是事件总线核心实现类,看到这了就再过一遍吧,应该是第三遍了吧
7.2.5.1发布事件(PublishAsync)
PublishAsync逻辑:
-
1.获取事件类型名作为 routingKey。
-
2.创建临时发布通道(
_rabbitMQConnection.CreateModel())。 -
3.声明 Exchange(Direct 类型,幂等)。
-
4.序列化(支持
AOT/Trim)事件为字节数组。 -
5.创建 OpenTelemetry Activity
- 注入 trace context 到消息头:保证跨进程链路追踪。
- 下游可以从消息头获取
PropagationContext
-
6.使用
_pipeline弹性重试策略执行BasicPublish:BasicPublish:发布消息mandatory = true:如果routingKey未匹配队列会触发BasicReturn。BasicReturn:用于通知生产者消息未能路由到任何队列的机制。建议在该回调中处理未成功发布的消息,以防消息丢失。
-
7.异常处理:
- 调用
activity.SetExceptionTags(ex)把异常写入 span。 - 抛出异常让重试策略处理。
- 调用

7.2.5.2初始化RabbitMQ(StartAsync)
StartAsync逻辑:
- 1.开启后台线程(
TaskCreationOptions.LongRunning),初始化RabbitMQ时都在后台线程执行,异步执行,避免阻塞宿主启动。 - 2.获取
RabbitMQ连接(_rabbitMQConnection) - 3.创建消费者通道(
_consumerChannel) - 4.订阅消费者通道连接异常回调(
_consumerChannel.CallbackException) - 5.声明直接类型(
direct)交换机(ExchangeDeclare) - 6.声明队列(
QueueDeclare) - 7.通过消费者通道(
_consumerChannel)创建异步事件消费者 - 8.注册异步事件消费者接收消息回调(
OnMessageReceived) - 9.监听队列(
BasicConsume) - 10.遍历订阅事件字典(
EventTypes:存储具体的订阅的事件名和事件类型),绑定队列和交换机时把每个事件名作为路由一起绑定

7.2.5.3接收消息(OnMessageReceived)
OnMessageReceived:
- 1.提取 Trace Context
- 从
IBasicProperties.Headers中解析PropagationContext信息。 - 设置
Baggage.Current,以便后续追踪。 - 上游生产者会将 trace context 存放在消息的 Header 中。
- 从
- 2.创建receive Activity
- 为该消息创建分布式追踪的接收 Activity。
- 设置相关标签(Tags)以记录元数据信息。
- 3.反序列化消息:将消息字节流反序列化为具体的事件对象类型。
- 4.调用
ProcessEvent对反序列化后的消息进行处理。 - 5.如处理过程中发生异常,通过
SetExceptionTags记录异常信息到 Activity 标签中。 - 6.消息确认(Ack):
- 当前实现无论是否异常都会
BasicAck。 - 真实生产环境建议使用 DLX / Nack 策略防止消息丢失。
- 当前实现无论是否异常都会

7.2.5.4处理消息(ProcessEvent)
ProcessEvent:
- 1.创建局部的
Scope,确保每个消息独立的Scope,用来获取订阅事件的所有事件处理器 - 2.根据事件名称获取事件类型
- 3.反序列化消息为事件
- 4.根据事件类型获取所有的事件处理器,先调用弱类型接口
IIntegrationEventHandler的Handle方法,然后通过强类型的泛型接口IIntegrationEventHandler<T>调用具体的事件处理器

7.2.5.5序列换/反序列化(SerializeMessage/DeserializeMessage)
通过订阅信息类 (EventBusSubscriptionInfo) 提供的运行时配置,根据不同的运行环境(例如 AOT 编译或程序集裁剪 Trim 场景)动态返回适配的 JsonSerializerOptions,确保在序列化和反序列化 IntegrationEvent 时既保持类型信息完整,又避免因裁剪或 AOT 限制导致的反射问题。

7.2.5.6弹性重试管道(CreateResiliencePipeline)
CreateResiliencePipeline 用于创建一个简单的 弹性重试策略,使用 Polly 风格的 ResiliencePipeline。主要目的是在调用 RabbitMQ 或其他可能出现网络异常的操作时(使用弹性管道(重试)执行发布操作),遇到特定异常可以自动重试,并采用 指数退避(Exponential Backoff)来避免瞬时重试导致压力放大。

7.2.5.7总结
第三次过这个代码了,这次学起来更游刃有余了
总结
| 方法 | 作用/说明 |
|---|---|
PublishAsync(IntegrationEvent @event) |
发布事件到 RabbitMQ:序列化消息、创建 exchange、注入 trace context、执行弹性重试策略发布 |
OnMessageReceived(object sender, BasicDeliverEventArgs eventArgs) |
消费者接收消息的回调:提取 trace、创建 receive activity、反序列化消息、调用对应 handler、手动 Ack |
ProcessEvent(string eventName, string message) |
根据事件名找到对应事件类型,反序列化消息并执行注册的 handler 集合 |
SerializeMessage(IntegrationEvent @event) |
将事件对象序列化为 UTF8 字节数组 |
DeserializeMessage(string message, Type eventType) |
将消息字符串反序列化为 IntegrationEvent 对象,支持 AOT/Trim |
StartAsync(CancellationToken cancellationToken) |
启动 IHostedService:创建 RabbitMQ 消费通道、声明 exchange/queue、绑定 routingKey 并开始 BasicConsume |
CreateResiliencePipeline(int retryCount) |
创建弹性重试管道(ResiliencePipeline),支持指定异常类型重试和指数退避延迟 |
7.2.6OpenTelemetry消息追踪与上下文传播(RabbitMQTelemetry)
封装 RabbitMQ 消息总线的 OpenTelemetry 支持,通过 ActivitySource 创建追踪片段并使用 TextMapPropagator 注入或提取跨进程的 trace 上下文和 Baggage,实现分布式链路追踪。
| 成员 | 类型 | 作用 |
|---|---|---|
ActivitySourceName |
string |
静态名称,唯一标识 RabbitMQ EventBus 的 trace 源 |
ActivitySource |
ActivitySource |
生成 Activity(span),记录消息发送/接收的 trace |
Propagator |
TextMapPropagator |
跨进程/跨服务传播 trace context 和 baggage |

7.3集成事件日志(IntegrationEventLogEF)
7.3.1集成事件日志服务接口(IIntegrationEventLogService)
IIntegrationEventLogService 提供了微服务 Outbox 模式的事件存储和状态管理接口,保证本地事务与消息发布的可靠性、幂等性和最终一致性。
RetrieveEventLogsPendingToPublishAsync:根据事务 Id 获取待发布的事件日志SaveEventAsync:使用外部事务保存事件日志MarkEventAsPublishedAsync/MarkEventAsInProgressAsync/MarkEventAsFailedAsync:标记事件日志状态为已发布/发布中/发布失败。

7.3.2集成事件日志服务(IntegrationEventLogService)
IntegrationEventLogService<TContext> 是 Outbox 模式 的核心服务,继承了IIntegrationEventLogService,提供了微服务 Outbox 模式的事件日志存储、状态更新和反序列化功能,保证业务数据与事件消息的一致性、可靠性和幂等性。
| 方法名 | 功能描述 |
|---|---|
RetrieveEventLogsPendingToPublishAsync(Guid transactionId) |
根据事务 ID 查询待发布的事件日志,返回状态为 NotPublished 的事件集合,并按创建时间排序,反序列化 JSON 为事件对象。 |
SaveEventAsync(IntegrationEvent @event, IDbContextTransaction transaction) |
将事件保存到数据库事件日志表中,绑定同一事务,保证业务数据和事件日志的原子性。 |
MarkEventAsPublishedAsync(Guid eventId) |
将指定事件标记为已发布,避免重复投递。 |
MarkEventAsInProgressAsync(Guid eventId) |
将指定事件标记为“发布中”,用于并发控制,避免重复发送。 |
MarkEventAsFailedAsync(Guid eventId) |
将指定事件标记为发布失败,方便后台任务或重试机制重新处理。 |
UpdateEventStatus(Guid eventId, EventStateEnum status) |
私有方法,用于统一更新事件状态,内部被 MarkEventAsPublished/InProgress/Failed 调用。 |

7.3.3EF Core事务重试封装类(ResilientTransaction)
ResilientTransaction 是一个 EF Core 事务封装器,用于在 数据库执行策略(ExecutionStrategy)重试机制 下安全执行事务操作。
它的核心目的是:保证当 EF Core 遇到瞬时故障需要重试时,整个事务逻辑(BeginTransaction + 业务操作 + Commit)都能被重试,避免事务被中断或提交不完整。
ResilientTransaction 封装了 EF Core 显式事务的重试逻辑,确保在瞬时故障重试策略下,事务操作(BeginTransaction + 业务逻辑 + Commit)具有原子性和可重试性。

7.3.4集成事件日志状态(EventStateEnum)
EventStateEnum 是 集成事件日志状态枚举,用于跟踪事件在 Outbox 模式中的生命周期,确保事件发送的可靠性和一致性。通过不同状态,系统可以明确区分哪些事件需要发布、正在发布、已发布或发送失败,从而支持重试机制和幂等投递。
| 枚举成员 | 值 | 含义 | 适用场景 / 说明 |
|---|---|---|---|
NotPublished |
0 | 未发布 | 事件刚创建或刚写入事件日志表时的初始状态,还没有发送到消息中间件。用于在 Outbox 模式中标记待发布事件。 |
InProgress |
1 | 发布中 | 事件已经开始发送到消息中间件,但尚未完成或确认发送成功。在高并发环境下,可防止多个发布者同时发送同一事件。 |
Published |
2 | 已发布 | 事件已成功发送到消息中间件并被确认。此状态意味着可以安全地认为事件已经被外部系统接收。 |
PublishedFailed |
3 | 发布失败 | 事件在发送过程中出现异常或失败,需要重新尝试发送。通常与后台重试机制或死信队列结合使用。 |

7.3.5统一using 管理(GlobalUsings)
global using 配置的作用是:
- 简化代码:在整个项目中不用每个文件都写重复的
using。 - 统一依赖:对 EF Core、数据库操作、JSON 序列化、事件总线等常用功能进行统一导入。
- 项目可维护性更高:新增命名空间只需在一处添加即可生效。

7.3.6集成事件日志实体类(IntegrationEventLogEntry)
IntegrationEventLogEntry 是 Outbox 模式下用于持久化集成事件及其状态的实体类,支持 JSON 序列化存储、状态跟踪和反序列化恢复事件对象。

7.3.7集成事件日志扩展类(IntegrationLogExtensions)
IntegrationLogExtensions是一个为 EF Core 提供的扩展配置类,用于统一定义 IntegrationEventLogEntry 实体的表名和主键映射,简化 Outbox 模式中事件日志表的注册。

7.4总结
总览
- EventBus(抽象层):定义事件总线的统一契约、事件/订阅元模型与 DI 构建器,解耦业务与消息中间件实现。
- EventBusRabbitMQ(实现层):基于 RabbitMQ 的事件总线实现,负责消息的发布/消费、OpenTelemetry 链路传播、重试策略与消费者生命周期管理。
- IntegrationEventLogEF(Outbox 层):基于 EF Core 的事件日志(Outbox)实现,负责事件持久化、状态管理和与业务事务的原子性保证。
本来完了,下面ChatGPT总结的挺好的,我保存一下,方便后面查阅
5.7.4.1EventBus(抽象层)
定位与职责
- 为上层业务提供统一的事件发布/订阅抽象,屏蔽中间件差异。
- 定义基础类型(
IntegrationEvent)、处理器接口(IIntegrationEventHandler、IIntegrationEventHandler<T>)、总线接口(IEventBus)、订阅元数据类(EventBusSubscriptionInfo)及 DI 构建器(IEventBusBuilder)。
关键类型
IntegrationEvent:事件基类,含Id、CreationDate等元数据(record,带[JsonInclude])。IEventBus:核心发布接口(Task PublishAsync(IntegrationEvent))。IIntegrationEventHandler/IIntegrationEventHandler<T>:弱/强类型处理器组合(显式接口实现桥接弱类型调用到强类型处理)。EventBusSubscriptionInfo:维护EventTypes映射与JsonSerializerOptions(支持 AOT/trim 的 TypeInfoResolver 策略)。IEventBusBuilder+EventBusBuilderExtensions:用于链式注册(ConfigureJsonOptions、AddSubscription<T,TH>等),会将事件类型映射写入EventBusSubscriptionInfo并把处理器以 keyed service 注册进 DI。
运行/设计要点
- 解耦:业务仅依赖抽象层,便于替换中间件实现。
- 通过
EventTypes映射避免运行时频繁Type.GetType(string)的反射查找,提高性能并兼容裁剪/ AOT。 - 注重 AOT/trim 场景(
JsonSerializerOptions、DynamicallyAccessedMembers注解等)。
7.4.2EventBusRabbitMQ(RabbitMQ 实现)
定位与职责
IEventBus的 RabbitMQ 实现:RabbitMQEventBus。- 负责:事件发布(publish)、消费(consume)、消息 headers 注入/提取 trace context、OpenTelemetry 集成、弹性重试(ResiliencePipeline/Polly 风格)、作为
IHostedService启动消费者通道。
关键类型 / 成员
RabbitMQEventBus(实现IEventBus,IHostedService,IDisposable):_pipeline:弹性重试管道(指数退避)。_propagator/_activitySource(来自RabbitMQTelemetry):用于 trace 注入/提取与创建 Activity。_subscriptionInfo:事件名→Type 映射与JsonSerializerOptions。PublishAsync(IntegrationEvent):序列化、声明 exchange、创建 Activity、注入 trace header、通过 pipeline 执行BasicPublish。OnMessageReceived:消费回调,提取 trace context、创建 receive Activity、反序列化、按 keyed service 解析并依次执行 handler、BasicAck。ProcessEvent:查映射、反序列化为具体事件类型、从 scope 获取所有 keyed handlers 并调用。StartAsync/StopAsync:consumer channel 初始化、queue/exchange 声明、队列绑定、注册AsyncEventingBasicConsumer。SerializeMessage/DeserializeMessage:使用JsonSerializer(带UnconditionalSuppressMessage以兼容 Trim/AOT)。
RabbitMQTelemetry:提供ActivitySource名称与TextMapPropagator(默认Propagators.DefaultTextMapPropagator)。
运行/设计要点
- 链路追踪:发送端注入 propagate header(TraceContext + Baggage),消费端提取并创建 child Activity,使异步消息链路可追踪。
- 重试策略:发布操作包裹在
_pipeline.Execute(...),遇到BrokerUnreachableException/SocketException等按指数退避重试。 - Keyed services:事件处理器按事件类型 key 注册,支持一个事件多个处理器。
- 独立 scope:每条消息处理都创建
IServiceScope(async scope),保证Scoped服务(如 DbContext)安全。 - 注意点:
- 当前实现
OnMessageReceived捕获异常但仍BasicAck—— 生产要考虑 DLX/Nack/重试,避免消息丢失。 ActivitySource实例化策略:建议单例化RabbitMQTelemetry,避免大量重复 ActivitySource。- AOT/Trim:
JsonSerializer使用的JsonSerializerOptions.TypeInfoResolver与 suppress 特性需谨慎配置以保证反序列化成功。 StartAsync使用TaskCreationOptions.LongRunning启动后台线程来创建 consumer channel(因为 RabbitMQ 客户端调用是阻塞的)。
- 当前实现
7.4.3IntegrationEventLogEF(Outbox / 事件日志)
定位与职责
- 实现 Outbox 模式:在本地事务内持久化事件到
IntegrationEventLog表;后端/总线读取并发布这些事件,保证“业务数据写入 + 事件发布”的一致性与可靠性。
关键类型 / 成员
IntegrationEventLogEntry(实体):- 字段:
EventId、EventTypeName、Content(JSON)、CreationTime、State(EventStateEnum)、TimesSent、TransactionId。 - 方法:
DeserializeJsonContent(Type)用于将Content反序列化为事件对象。
- 字段:
EventStateEnum:NotPublished/InProgress/Published/PublishedFailed。IIntegrationEventLogService:声明RetrieveEventLogsPendingToPublishAsync、SaveEventAsync、MarkEventAsPublished/InProgress/Failed。IntegrationEventLogService<TContext>(实现):- 保存事件时:
SaveEventAsync把事件写入IntegrationEventLogEntry并与外部事务绑定(_context.Database.UseTransaction(transaction.GetDbTransaction()))。 - 查询待发布事件:
RetrieveEventLogsPendingToPublishAsync(Guid),按CreationTime排序并DeserializeJsonContent。 - 更新状态:
UpdateEventStatus,TimesSent++(InProgress)等。
- 保存事件时:
IntegrationLogExtensions:ModelBuilder扩展,映射表名与主键(IntegrationEventLog)。
运行/设计要点
- 事务内写事件:业务写入与事件写入绑定同一 DB 事务(Outbox),确保原子性。
- 状态驱动发布:事件状态从
NotPublished→InProgress→Published/PublishedFailed,配合发布器实现幂等与重试。 - 与 EventBus 的结合:发布流程常见实现:
- 在业务事务中写入
IntegrationEventLogEntry(SaveEventAsync); - 事务提交后,后台发射器/发布器(或在同一请求中)调用
RetrieveEventLogsPendingToPublishAsync->MarkEventAsInProgressAsync-> 尝试发布(调用IEventBus.PublishAsync) -> 成功后MarkEventAsPublishedAsync,异常时MarkEventAsFailedAsync。
- 在业务事务中写入
- ResilientTransaction:辅助类
ResilientTransaction用于在 EF Core 的 ExecutionStrategy(重试策略)保护下正确地在回调内部创建并提交显式事务(Begin/Commit 包裹在策略回调内),避免重试场景下事务不一致问题。
8.统一微服务基础配置与启动模板(eShop.ServiceDefaults)
8.1认证和授权扩展类(AuthenticationExtensions)
注册 JWT 认证与授权(AddDefaultAuthentication):
- 1.读取
appsettings.json中Identity配置 - 2.取消
Claim映射:不再把 JWT 中的"sub"自动映射为.NET ClaimTypes.NameIdentifier,保持 claim 原样,方便你直接读取"sub"字段作为用户 ID。 - 3.注册 JWT Bearer 认证
- 设置
Authority(认证服务器地址) - 强制必须使用 HTTPS(生产要求:
RequireHttpsMetadata = true) - 设置
Audience(接收者/订阅人) - 配置
ValidIssuers(允许的签发者) - 配置 Audience 验证
- 设置
- 4.注册
Authorization授权系统:AddAuthorization()不会创建任何自定义策略,但提供一个默认策略→RequireAuthenticatedUser,只要[Authorize]就会要求“必须登录”。

8.2声明(Claims)扩展类(ClaimsPrincipalExtensions)
这个扩展类提供两个工具方法:从 JWT Claims 中获取当前用户的 ID(sub)和用户名(Name),用于控制器或服务里快速取登录用户信息。

8.3配置扩展类(ConfigurationExtensions)
ConfigurationExtensions 扩展类通过 GetRequiredValue为关键配置项提供强制校验机制,缺失时立即抛出带完整路径的错误,确保应用在配置完整前不会运行。
三元表达式:
- 如果
configuration是IConfigurationSection: 返回s.Path + ":" + name - 否则 : 返回
name
configuration is IConfigurationSection s ? s.Path + ":" + name : name

8.4默认配置扩展类(Extensions:服务注册/OpenTelemetry/健康检查/终结点映射)
Extensions通过一系列扩展方法,统一为微服务提供了 默认基础设施配置,包括:
- 健康检查(HealthChecks)
- 可观测性(OpenTelemetry:日志、指标、链路追踪)
- HTTP 客户端默认行为(弹性处理、服务发现)
- 默认终结点映射(健康检查、Prometheus 指标)
8.4.1可观察性框架(ConfigureOpenTelemetry)
OpenTelemetry是个监测程序的工具,先了解配置,后面看看怎么使用
OpenTelemetry(简称 OTEL) 是一个开源的 可观察性(Observability)框架,用来统一收集、处理和导出应用程序的 追踪(Tracing)、指标(Metrics)、日志(Logging) 数据:
- 配置
OpenTelemetry日志- 支持结构化日志
- 保留
Scope,便于追踪单次请求
- 配置
OpenTelemetry指标采集(Metrics)ASP.NET Core请求指标HttpClient调用指标CLR运行时指标- 自定义
Meter(如 AI 组件)
- 配置
OpenTelemetry链路追踪(Tracing)- HTTP 请求、Grpc 调用、HttpClient 请求自动追踪
- 自定义或第三方库 Activity 需要
.AddSource(...)才能被采集。 - 开发环境开启
AlwaysOnSampler(全部采样:开发环境下,我们希望看到全部的调用链路)
- 配置
OpenTelemetry数据导出器(exporters)- 根据配置
OTEL_EXPORTER_OTLP_ENDPOINT启用 OTLP 导出 - 日志、指标、追踪可统一发送到可视化平台
- 根据配置

默认没有OTEL_EXPORTER_OTLP_ENDPOINT配置,就是不开启OTLP,暂时也没有这个环境,后面一起看看怎么用的
配置OpenTelemetry 数据导出器(AddOpenTelemetryExporters):
- 检测是否配置 OTLP 导出端点
- 如果配置了,注册:
- 日志导出器:AddOtlpExporter
- 指标导出器:metrics.AddOtlpExporter
- 链路追踪导出器:tracing.AddOtlpExporter
目的是将所有可观测数据统一发送到外部系统(如 Jaeger、Prometheus、Grafana、OpenTelemetry Collector)。

8.4.2健康检测(AddDefaultHealthChecks)
注册默认健康检查服务:
- 添加一个
"self"健康检查,带标签"live" - 健康检查用于容器编排系统(如 Kubernetes)判断服务健康状态
AddCheck参数说明:
name:健康检查的名字。可以在监控或导出时看到这个名字,便于区分不同检查项。check: 委托(Func<HealthCheckResult>),用来返回健康状态。HealthCheckResult.Healthy():健康,还可以返回HealthCheckResult.Degraded():有些问题,但系统还能工作HealthCheckResult.Unhealthy():不健康
tags: Tag(标签),用于给健康检查打标签。"live":存活检查(liveness probe)"ready":就绪检查(readiness probe)

8.4.3映射默认断点(MapDefaultEndpoints)
注册默认的 Web 终结点:
-
健康检查端点
-
/health→ Readiness Probe(依赖服务健康) -
/alive→ Liveness Probe(进程存活) -
仅在
Development环境下启用,避免生产安全风险
-
-
Prometheus 指标端点
-
可选,需要安装
OpenTelemetry.Exporter.Prometheus.AspNetCore -
暴露
/metrics给 Prometheus 拉取
-

8.4.4HttpClient
配置HttpClient默认行为:
- 默认开启 弹性策略(Resilience Policy):可能包括 重试(Retry)、熔断(Circuit Breaker)、超时(Timeout),目的是让微服务调用 HttpClient 时更可靠,不轻易因为网络抖动失败
HttpClient服务发现:- 自动集成服务发现逻辑
- HttpClient 可以使用 服务名 而非固定 URL 发起请求
- 与上面的
AddServiceDiscovery()配合,保证调用时可解析服务地址

8.4.5服务注册和发现(AddServiceDiscovery)
服务注册与发现:
- 服务注册(Service Registration):微服务启动时向注册中心(如 Nacos、Consul、Eureka)注册自己的地址、端口、服务名等信息。
- 服务发现(Service Discovery):其他微服务通过服务名动态查找目标微服务的实际地址,实现调用而无需硬编码 IP/端口。
这里的服务注册和发现(builder.Services.AddServiceDiscovery()):并没有直接依赖具体注册中心(Consul、Nacos、Kubernetes DNS)只是提供 服务发现能力的接口,让后续 HttpClient 或微服务调用可以使用服务名,实际生效依赖具体运行时平台或注册中心组件。

8.4.5服务调用
AddBasicServiceDefaults:提供基础服务能力(健康检查 + 可观测性):
- 购物车服务(
Basket.API) - 订单处理服务(
OrderProcessor)
AddServiceDefaults:在基础能力上增加微服务特有的注册、发现和 HttpClient 默认配置
这样设计让功能分层清晰、可复用、解耦:
-
商品服务(
Catalog.API) -
认证服务(
Identity.API) -
订单服务(
Ordering.API) -
支付处理服务(
PaymentProcessor) -
前端
Web应用(WebApp) -
Webhook客户端(WebhookClient) -
Webhook服务(Webhooks.API)

8.5HttpClient扩展方法类扩展类(HttpClientExtensions)
为 HttpClient 添加自动注入 Bearer Token 的能力,内部会注册 IHttpContextAccessor 和自定义的 HttpClientAuthorizationDelegatingHandler,并把 handler 加入 HttpClient 的请求管道,返回原始 builder 支持链式调用。

继承自 DelegatingHandler,重写SendAsync方法,作用是拦截 HttpClient 请求,从当前 HttpContext 获取用户的 access token,并将其添加到HttpClient 请求头的 Authorization 字段,然后调用下一个 handler 或最终发送请求。

8.6OpenApi/Scalar扩展类(partial Extensions)
这是Extensions的扩展类,把OpenApi/Scalar单独封装出来
partial class(部分类)
- 一个类可以拆成多个文件写,每个文件用
partial class声明。 - 编译时会自动把它们 合并成一个完整类。

UseDefaultOpenApi :
- 1.读取
appsetting.json中OpenApi节点的配置 - 2.映射 OpenAPI 路由
- 3.如果是开发环境:
- 启用 Scalar 文档
- 根路径重定向到 Scalar 文档首页(
/scalar/v1)

AddDefaultOpenApi:
- 1.读取
appsetting.json中OpenApi和Identity节点的配置 - 2.使用
API Versioning进行多版本 OpenAPI 配置 - 3.配置2个版本的
OpenAPI文档- ApplyApiVersionInfo:设置文档标题和描述
- ApplyAuthorizationChecks:根据 Identity Scopes 添加接口权限检查
- ApplySecuritySchemeDefinitions:定义安全方案(Bearer、OAuth2 等)
- ApplyOperationDeprecatedStatus:标记过期接口
- ApplyApiVersionDescription:为文档添加版本说明
- ApplySchemaNullableFalse:所有 Schema 的 Nullable 属性设置为 false
- AddDocumentTransformer:清空默认服务器列表,方便动态端口
- ApplyDefaultRequestIdHeaderExample:为接口请求添加默认 requestId 示例
具体的我们之前都详细看过了,这里简单回忆下

8.7扩展类(OpenApiOptionsExtensions)
OpenApiOptionsExtensions 提供了一组针对 OpenAPI/Scalar 的扩展,负责把 API 版本信息、描述、deprecated 标注、OAuth2 安全定义、api-version 参数说明、schema nullable 策略、以及 requestId header 示例等“文档元信息”注入到生成的 OpenAPI 文档中,从而统一项目的 API 文档风格与安全说明。

Extensions中AddDefaultOpenApi方法配置OpenAPI的具体实现

9.身份服务(IdentityService)
最后发现Ordering.API还有基础设置的封装,我就接着写了
身份服务接口(IIdentityService)定义2个方法
GetUserIdentity:获取当前用户的唯一标识GetUserName:获取当前用户的用户名

身份服务实现:从请求上下文中获取用户的唯一标识(sub)和用户名
用户的唯一标识(sub):我们在配置认证授权的时候取消了AspNetCore对sub的自动映射(ClaimTypes.NameIdentifier),所以这里直接从请求上下文获取sub
IdentityService 是通过请求上下文(HttpContext)获取当前用户信息的业务服务,而ClaimsPrincipalExtensions 则是对 ClaimsPrincipal 提供便捷读取用户信息的扩展方法。

10.其他服务
eshop只是个示例项目,可能还有很多微服务组件没用上,我这里就不具体一个给加上了,但是还是给个解决方案,老张有个开源的项目Blog.Core,里面需要封装的组件基本都有,如果需要可以自己去学习
地址:http://apk.neters.club/.doc/
比如这里就有:网关、服务注册和发现(Nacos)、t4模版、Appllo远程配置、认证授权、DTO映射(AutoMapper)、后台服务(HostedService)、分布式缓存(Redis)

下面还有Serilog、Serilog.Es、定时服务(QuartzNet)

📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!
💬 加入交流群(QQ群):576434538
