使用 Dapr 实现分布式事件总线

抽象分布式总线 使用设计模式,将分布式总线的使用方式进行抽象,定义了总线的基本功能。 基于事件总线的组件 使用组件化的方式实现分布式事件总线,将总线的实现细节封装在组件中。 基于 RabbbitMQ 实现:https://github.com/dotnet/eShop/tree/main/src/EventBusRabbitMQ 事件总线的 Dapr 实现 Dapr 是一个开源的分布式应用程序运行时,发布和订阅模块使微服务能够使用事件驱动架构的消息相互通信,Dapr 提供了一个事件总线的实现,使用 Dapr 发布事件,提供 Dapr 订阅终结点,将消息路由到不同的事件处理程序。

2026年3月10日

使用 Dpar 发布订阅配置分布式事件

Dapr 技术架构 https://docs.dapr.io/concepts/overview 安装 Dapr CLI 脚手架工具 https://docs.dapr.io/zh-hans/getting-started/install-dapr-cli 基于容器初始化 Dapr 运行时 https://docs.dapr.io/zh-hans/getting-started/install-dapr-selfhost dapr init 不用容器离线初始化 Dapr 运行时 dapr init -s 使用离线包初始化 Dapr 运行时 https://docs.dapr.io/zh-hans/operations/hosting/self-hosted/self-hosted-airgap/ dapr init --from-dir C:\daprbundle 打开默认初始化目录 explorer "$env:USERPROFILE\.dapr" explorer "%USERPROFILE%\.dapr" 使用 Dashboard 查看 Dapr 运行状态 dapr dashboard 发布订阅配置分布式事件 /components/redis-pubsub.yaml 基于 Redis 发布订阅配置 apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub spec: type: pubsub.redis version: v1 metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" 基于 RabbitMQ 发布订阅配置 /components/rabbitmq-pubsub.yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub spec: type: pubsub.rabbitmq version: v1 metadata: - name: host value: "amqp://guest:guestpwd@localhost:5672" - name: durable value: "false" - name: deletedWhenUnused value: "false" - name: autoAck value: "false" - name: reconnectWait value: "0" - name: concurrency value: parallel 使用 Dapr 命令启动 Sidecar 和应用程序 dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001 --app-port 5000 --log-level debug --config ./configuration/config.yaml --components-path ./components dotnet run 使用 Aspire 命令启动 Sidecar 和应用程序 dotnet add package Aspire.Hosting.Dapr var pubsub = builder.AddDaprPubSub("pubsub", new DaprComponentOptions { LocalPath = "./DaprComponents/" }); var productService = builder.AddProject<Projects.HelloWorld_ProductService>("productservice") .WithReference(identityService) .WithDaprSidecar() .WithReference(pubsub); 使用 RabbitMQ 发布订阅配置 var username = builder.AddParameter("username", secret: true); var password = builder.AddParameter("password", secret: true); var messaging = builder.AddRabbitMQ("messaging", username, password); // Service consumption builder.AddProject<Projects.ExampleProject>() .WithReference(messaging);

2026年3月10日

使用 Github Copilot 自动编码

什么是 Github Copilot Github Copilot 是 Github 与 OpenAI 合作推出的一款基于人工智能的代码自动补全工具。它可以根据上下文提示,自动生成代码片段,帮助开发者提高编码效率。 如何使用 Github Copilot Github Copilot 作为插件的在 Visual Studio Code 和 Visual Studio 中使用。安装插件后,即可在编码过程中使用。 Github Copilot 是收费的吗 Github Copilot 是收费的,但是在 2021 年 7 月 1 日至 2022 年 6 月 30 日期间,Github Copilot 将免费提供给所有用户使用。

2026年3月10日

使用 gRPC 实现购物车服务

什么是 gRPC 通信框架 gRPC 是一个高性能、开源和通用的 RPC 框架,由 Google 开发,基于 HTTP/2 协议,支持多种语言(如 Go、Java、Python、C++、Node.js、Ruby、C#、Objective-C、PHP 和 Dart)。 gRPC 使用 Protocol Buffers 作为接口定义语言(IDL),Protocol Buffers 是一种轻便高效的结构化数据序列化方法,类似于 XML 或 JSON,但更小、更快、更简单。 ASP.NET Core 中使用 gRPC 实现服务 定义一个 gRPC 服务,需要创建一个 gRPC 服务定义文件(.proto 文件),然后使用 gRPC 工具生成服务端和客户端代码。 带有 OpenAPI 的 gRPC 服务 dotnet add package Microsoft.AspNetCore.Grpc.Swagger <IncludeHttpRuleProtos>true</IncludeHttpRuleProtos> var builder = WebApplication.CreateBuilder(args); builder.Services.AddGrpc().AddJsonTranscoding(); builder.Services.AddGrpcSwagger(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "gRPC transcoding", Version = "v1" }); }); var app = builder.Build(); app.UseSwagger(); if (app.Environment.IsDevelopment()) app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); } app.MapGrpcService<GreeterService>(); app.Run(); // My amazing greeter service. service Greeter { // Sends a greeting. rpc SayHello (HelloRequest) returns (HelloReply) { option (google.api.http) = { get: "/v1/greeter/{name}" }; } } message HelloRequest { // Name to say hello to. string name = 1; } message HelloReply { // Hello reply message. string message = 1; } 单元测试 gRPC 服务 dotnet add package moq public class GreeterServiceTests { [Fact] public async Task SayHello_ReturnsHello() { // Arrange var mockLogger = new Mock<ILogger<GreeterService>>(); var service = new GreeterService(mockLogger.Object); var request = new HelloRequest { Name = "Unit Test" }; var context = new ServerCallContext(); // Act var reply = await service.SayHello(request, context); // Assert Assert.Equal("Hello Unit Test", reply.Message); } } 集成测试 gRPC 服务 dotnet add package Microsoft.AspNetCore.TestHost public class GreeterServiceIntegrationTests { private readonly TestServer _server; private readonly Greeter.GreeterClient _client; public GreeterServiceIntegrationTests() { _server = new TestServer(new WebHostBuilder() .ConfigureServices(services => { services.AddGrpc(); }) .Configure(app => { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<GreeterService>(); }); })); _client = new Greeter.GreeterClient(_server.CreateGrpcChannel()); } [Fact] public async Task SayHello_ReturnsHello() { // Arrange var request = new HelloRequest { Name = "Integration Test" }; // Act var reply = await _client.SayHelloAsync(request); // Assert Assert.Equal("Hello Integration Test", reply.Message); } }

2026年3月10日

使用 HybridCache 混合缓存库

混合缓存可以同时使用内存缓存和分布式缓存,它可以提高缓存的命中率,减少对分布式缓存的访问,从而提高性能。 当从缓存中获取数据时,它首先会从内存缓存中获取数据,如果内存缓存中没有数据,它会从分布式缓存中获取数据。 当向缓存中写入数据时,它会同时向内存缓存和分布式缓存中写入数据。 Service 1 ----> HybridCache ----> MemoryCache ----> DistributedCache https://learn.microsoft.com/zh-cn/azure/architecture/best-practices/caching 安装程序包 dotnet add package Microsoft.Extensions.Caching.Hybrid 配置服务 builder.Services.AddHybridCache(); 使用服务 public class SomeService(HybridCache cache) { private HybridCache _cache = cache; public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default) { return await _cache.GetOrCreateAsync( $"{name}-{id}",async cancel => await GetDataFromTheSourceAsync(name, id, cancel),cancellationToken: token ); } public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token) { string someInfo = $"someinfo-{name}-{id}"; return someInfo; } } 按标记移除缓存条目 public class SomeService(HybridCache cache) { private HybridCache _cache = cache; public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default) { var tags = new List<string> { "tag1", "tag2", "tag3" }; var entryOptions = new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(1), LocalCacheExpiration = TimeSpan.FromMinutes(1) }; return await _cache.GetOrCreateAsync( $"{name}-{id}", // Unique key to the cache entry async cancel => await GetDataFromTheSourceAsync(name, id, cancel), entryOptions, tags, cancellationToken: token ); } public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token) { string someInfo = $"someinfo-{name}-{id}"; return someInfo; } public async Task RemoveByTagAsync(string tag) { await _cache.RemoveByTagAsync(tag); } }

2026年3月10日

使用 JwtBearer 令牌进行身份验证

介绍 JwtBearer 令牌身份验证是一种基于 JSON Web 令牌的身份验证方法, 用于验证用户的身份, 它是一种无状态的身份验证方法, 适用于 Web API 和 Web 应用程序。 安装 NuGet 包 dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer 自建 Identity Api 终结点 builder.Services.AddIdentity<User, Role>(options => { options.SignIn.RequireConfirmedAccount = false; options.Password.RequireDigit = false; options.Password.RequireLowercase = false; options.Password.RequireUppercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequiredLength = 5; }).AddEntityFrameworkStores<IdentityServiceDbContext>(); 使用 JwtBearer 验证令牌 builder.Services.AddAuthentication(options => { options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = CustomJwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.TokenValidationParameters.ValidateIssuer = false; options.TokenValidationParameters.ValidateAudience = false; options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(issuerSigningKey)); }) 自定义身份认证处理程序 public class CustomJwtBearerDefaults public class CustomJwtBearerOptions public class CustomJwtBearerHandler public class CustomJwtBearerExtensions 配置令牌生成 builder.Services.AddAuthentication().AddJwtBearer().AddCustomJwtBearer(options => { options.IssuerSigningKey = issuerSigningKey; options.SecurityAlgorithm = SecurityAlgorithms.HmacSha256; }); 在容器中运行 pgAdmin 管理工具 docker pull dpage/pgadmin4 docker run --name pgadmin -e PGADMIN_DEFAULT_EMAIL=test@test.com -e PGADMIN_DEFAULT_PASSWORD=test -e TZ=Asia/Shanghai -d -p 5050:80 dpage/pgadmin4

2026年3月10日

使用 OpenAPI 规范生成 API 文档

Swagger 是一个与语言无关的规范,用于描述 REST API。 它使计算机和用户无需直接访问源代码即可了解 REST API 的功能,Swagger 项目已于捐赠给 OpenAPI 计划,自此它被称为 OpenAPI 规范。 Swashbuckle 是一个用于 .NET Core 的开源项目,它是 OpenAPI 工具的集合,用于生成 OpenAPI 规范的文档。 它可以自动生成 API 文档,包括 API 的描述、请求和响应的格式、参数的描述等。 安装 Swashbuckle.AspNetCore 在 .NET Core 项目中,可以通过 NuGet 安装 Swashbuckle.AspNetCore 包。 dotnet add package Swashbuckle.AspNetCore 配置 Swashbuckle 在 Startup.cs 文件中,可以通过以下方式配置 Swagger: builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }

2026年3月10日

使用 PostgreSQL 数据库存储数据

在 Docker 中 启动 PostgreSQL 数据库 https://www.postgresql.org docker pull postgres docker run --name postgres -e POSTGRES_PASSWORD=postgres -e TZ=Asia/Shanghai -d -p 5432:5432 postgres 使用 PgAdmin 连接 PostgreSQL 数据库 https://www.pgadmin.org SHOW timezone; EfCore 使用 PostgreSQL 数据库 dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL 定义实体类型 public class User { public int Id { get; set; } public string UserName { get; set; } public string PasswordHash { get; set; } public DateTimeOffset CreationTime { get; set; } = DateTimeOffset.UtcNow; } 配置实体类型 public class UserEntityTypeConfiguration : IEntityTypeConfiguration<User> { public void Configure(EntityTypeBuilder<User> builder) { builder.ToTable("Users"); builder.HasKey(x => x.Id); builder.Property(x => x.UserName).IsRequired().HasMaxLength(50); builder.Property(x => x.PasswordHash).IsRequired().HasMaxLength(50); builder.Property(x => x.CreationTime); } } 创建 DbContext 上下文 public class IdentityServiceDbContext : DbContext { public IdentityServiceDbContext(DbContextOptions<IdentityServiceDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true); } } 数据库连接字符串 { "ConnectionStrings": { "IdentityDatabase": "Host=localhost;Port=5432;Database=IdentityService;Username=postgres;Password=postgres" } } 注册数据库上下文 builder.Services.AddDbContext<IdentityServiceDbContext>(options => { options.UseNpgsql(builder.Configuration.GetConnectionString("IdentityDatabase")); }); 迁移数据库 dotnet tool install --global dotnet-ef dotnet add package Microsoft.EntityFrameworkCore.Design dotnet ef migrations add InitialCreate --output-dir EntityFrameworks/Migrations dotnet ef database update PostgreSQL 数据库命名约定 https://github.com/efcore/EFCore.NamingConventions ...

2026年3月10日

使用 TimeProvider 获取时间

传统方式获取时间 我们通常使用 DateTime.Now 和 DateTimeOffset.Now 来获取当前时间,但是这样的代码很难测试,时间依赖于系统时间,很难控制和重现,只能修改系统时间。 var now1 = DateTime.Now; var nowUtc1 = DateTime.UtcNow; var now2 = DateTimeOffset.Now; var nowUtc2 = DateTimeOffset.UtcNow; 最新的 TimeProvider 类型 为了解决上述问题,在 .NET 8 中新增了 TimeProvider 类型,可以用来获取时间,并且可以在测试中轻松地控制时间。 var now = TimeProvider.System.GetLocalNow(); var nowUtc = TimeProvider.System.GetUtcNow(); 重写 TimeProvider 模拟时间 我们可以重写 TimeProvider 类型,模拟时间,方便测试。 public class MyTimeProvider : TimeProvider { public override DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow; } 依赖注入 TimeProvider 实现 默认实现 TimeProvider.System 使用系统时间。 builder.Services.AddSingleton(TimeProvider.System); 注入 MyTimeProvider 使用自定义时间。 builder.Services.AddSingleton<TimeProvider, MyTimeProvider>(); 使用 TimeProvider 的代码 public class MyService(TimeProvider timeProvider) { public void DoSomething() { var now = timeProvider.GetLocalNow(); var nowUtc = timeProvider.GetUtcNow(); } } 演示零度框架中使用 TimeProvider 代码。 关于时间的单元测试 Microsoft.Extensions.TimeProvider.Testing

2026年3月10日

使用 TimeProvider 类注入时间

System.TimeProvider 是一种时间抽象,它以 DateTimeOffset 类型的形式提供时间点。 通过使用 TimeProvider,可确保代码可测试且可预测。 TimeProvider 已在 .NET 8 中引入。 默认实现 默认情况下,TimeProvider 使用 DateTimeOffset.UtcNow 作为时间源。 Console.WriteLine($"Local: {TimeProvider.System.GetLocalNow()}"); Console.WriteLine($"Utc: {TimeProvider.System.GetUtcNow()}"); 自定义实现 public class CustomTimeProvider: TimeProvider { public override DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow.AddHours(1); } builder.Services.AddSingleton(TimeProvider.System); builder.Services.AddSingleton<TimeProvider, CustomTimeProvider>(); public class MyService { private readonly TimeProvider _timeProvider; public MyService(TimeProvider timeProvider) { _timeProvider = timeProvider; } public void DoSomething() { var now = _timeProvider.Now; Console.WriteLine(now); } } FakeTimeProvider 实现 dotnet add package Microsoft.Extensions.TimeProvider.Testing FakeTimeProvider fakeTimeProvider = new(); fakeTimeProvider.SetUtcNow(fakeTimeProvider.GetUtcNow().AddHours(1)); MyService service = new (timeProvider); service.DoSomething();

2026年3月10日