设计权限定义和权限提供者

权限定义 每个微服务是自治的,权限定义也是每个微服务的职责,各自定义自己的权限,权限管理器会自动合并权限定义,权限定义上下文是微服务内的权限定义和全局权限定义的合集,权限提供者会根据权限定义上下文提供权限数据,权限分组是为了方便权限定义的管理,权限分组是权限定义的一部分,权限提供者自动注入到容器,提供权限查询端点。 架构设计 权限扩展方法 IServiceCollection AddPermissionDefinitions(this IServiceCollection services) EndpointRouteBuilder MapPermissionDefinitions(this IEndpointRouteBuilder endpoints, params string[] tags)

2026年3月10日

身份认证系统

ASP.NET Core Identity 体系结构 ASP.NET Core Identity 是一个成熟的身份认证系统,它提供了用户管理、角色管理、声明管理、密码管理、登录管理、外部登录管理、双重身份认证、电子邮件确认、电话号码确认、安全令牌管理、用户锁定、用户解锁、用户注销、用户删除、用户恢复等功能。 Identity 体系结构说明 实体类型 说明 User 表示用户。 Role 表示角色。 UserClaim 表示用户拥有的声明。 UserToken 表示用户的身份验证令牌。 UserLogin 将用户与登录名相关联。 RoleClaim 表示向角色内的所有用户授予的声明。 UserRole 关联用户和角色的联接实体。 使用 EfCore 存储 Identity 数据 定义实体类型 public class User : IdentityUser<int> { public DateTimeOffset CreationTime { get; set; } = DateTimeOffset.UtcNow; } public class Role : IdentityRole<int> { 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.CreationTime); } } 复用 Identity 数据库上下文 dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore public class IdentityServiceDbContext(DbContextOptions<IdentityServiceDbContext> options) : IdentityDbContext<User, Role, int>(options) { protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true); } } 使用 Identity API 终结点 builder.Services.AddIdentityApiEndpoints<User>(options => { options.Password.RequireDigit = false; options.Password.RequireLowercase = false; options.Password.RequireUppercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequiredLength = 5; options.SignIn.RequireConfirmedAccount = false; }).AddEntityFrameworkStores<IdentityServiceDbContext>(); app.MapGroup("identity").MapIdentityApi<User>(); 操作 Identity 数据 UserManager、RoleManager、SignInManager、PasswordValidator 等服务都可以用来操作 Identity 数据,没有特殊情况不建议使用 DbContext 直接操作 Identity 数据。 ...

2026年3月10日

实现 CQRS 中的 Query 模式

在 CQRS 模式的全程是 Command Query Responsibility Segregation,即命令查询职责分离。在 CQRS 模式中,命令和查询是分开实现的,命令负责写操作,查询负责读操作,这样可以更好地实现单一职责原则,同时也可以更好地实现性能优化。 https://learn.microsoft.com/zh-cn/azure/architecture/patterns/cqrs https://www.xcode.me/Training/UnitNote/576 定义订单查询接口 public interface IOrderQueries { Task<OrderDetails> GetOrderAsync(int id); } 实现订单查询接口 public class OrderQueries : IOrderQueries { private readonly OrderingContext _context; public OrderQueries(OrderingContext context) { _context = context; dbContext.Database.SetConnectionString("db2_connection_string"); } public async Task<OrderDetails> GetOrderAsync(int id) { // do something } } 注册订单查询接口 services.AddScoped<IOrderQueries, OrderQueries>(); 使用订单查询接口 public class OrderController : ControllerBase { private readonly IOrderQueries _orderQueries; public OrderController(IOrderQueries orderQueries) { _orderQueries = orderQueries; } [HttpGet("{id}")] public async Task<IActionResult> GetOrderAsync(int id) { var order = await _orderQueries.GetOrderAsync(id); return Ok(order); } }

2026年3月10日

实现产品管理微服务

创建实体 HelloWorld.ProductService.Entities.Products Product & CatalogBrand EfCore 使用 PostgreSQL 数据库 dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL 创建 DbContext 上下文 namespace HelloWorld.ProductService.EntityFrameworks { public class ProductServiceDbContext(DbContextOptions<ProductServiceDbContext> options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } } } 创建实体配置类 namespace HelloWorld.ProductService.EntityFrameworks.EntityConfigurations.Products { public class ProductEntityTypeConfiguration : IEntityTypeConfiguration<Product> { public void Configure(EntityTypeBuilder<Product> builder) { builder.ToTable("Products"); builder.Property(x => x.Name).HasMaxLength(32); builder.HasOne(x => x.Brand).WithMany(); } } } 数据库连接字符串 { "ConnectionStrings": { "ProductDatabase": "Host=localhost;Port=5432;Database=ProductService;Username=postgres;Password=postgres" } } 注册数据库上下文 builder.Services.AddDbContext<ProductServiceDbContext>(options => { options.UseNpgsql(builder.Configuration.GetConnectionString(DbConstants.ConnectionStringName)); }); 迁移数据库 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 创建模型 namespace HelloWorld.ProductService.Models.Products { public class ProductCreateRequest { public required string Name { get; init; } public string? Description { get; init; } public decimal Price { get; init; } public int BrandId { get; init; } public string? ImageUrl { get; init; } } } 创建自动映射 namespace HelloWorld.ProductService.AutoMapper { public class ProductsMapConfiguration : Profile { public ProductsMapConfiguration() { CreateMap<ProductCreateRequest, Product>(); CreateMap<ProductUpdateRequest, Product>(); CreateMap<Product, ProductListItem>().AfterMap((src, dest) => dest.BrandName = src.Brand.Name); CreateMap<Product, ProductDetailsResponse>(); CreateMap<BrandCreateRequest, Brand>(); CreateMap<BrandUpdateRequest, Brand>(); CreateMap<Brand, BrandDetailsResponse>(); CreateMap<Brand, BrandListItem>(); } } } 创建验证器 namespace HelloWorld.ProductService.Validations.Products { public class BrandUpdateRequestValidator : AbstractValidator<BrandUpdateRequest> { public BrandUpdateRequestValidator() { RuleFor(x => x.Id).GreaterThan(0); RuleFor(x => x.Name).NotNull().NotEmpty().Length(8, 32); } } } 创建控制器 namespace HelloWorld.ProductService.Controllers { [Route("api/[controller]")] [ApiController] public class ProductsController : ControllerBase } 定义权限 namespace HelloWorld.ProductService.PermissionProviders { public static class CatalogPermissions public class CatalogPermissionDefiitionProvider : IPermissionDefinitionProvider } 权限本地化 CatalogPermissionDefinitionProvider.en-US.resx CatalogPermissionDefinitionProvider.zh-CN.resx 实体属性本地化 namespace HelloWorld.ProductService.Entities.Products { public class Product { [Display(Name = "ProductName")] public string Name { get; set; } } } 重复性工作 重复性工作可以通过代码生成工具来减少,例如 Visual Studio 插件, T4 模板, Roslyn 等。

2026年3月10日

实现权限访问控制列表

授权三要素 主体、资源、操作。 例如:用户(主体)对文件(资源)的读写(操作)。主体、资源、操作三者之间的关系称为授权关系,基于这种关系,我们可以将授权关系抽象为一个三元组(主体,资源,操作),这个三元组就是授权的基本单位。 基于角色的访问控制 基于角色的访问控制(Role-Based Access Control,简称 RBAC)是一种访问控制模型,通过角色来控制用户对资源的访问权限。角色是一组权限的集合,用户通过分配角色来获取相应的权限。基于角色的访问控制模型简单易用。还有其它的访问控制模型,如基于属性的访问控制(Attribute-Based Access Control,简称 ABAC)。 权限访问控制列表 权限访问控制列表(Access Control List,简称 ACL)是一种权限控制模型,用于控制用户对资源的访问权限。ACL 通过为每个资源定义一个访问控制列表,来控制用户对资源的操作权限。 ACL 存储设计 public class PermissionGranted { public int Id { get; set; } public int RoleId { get; set; } public required string PermissionName { get; set; } public string? ResourceType { get; set; } public string? ResourceId { get; set; } } 在 DbContext 中配置 ACL 实体 public void Configure(EntityTypeBuilder<PermissionGranted> builder) { builder.ToTable("PermissionGranted"); builder.Property(x => x.Id); builder.Property(x => x.PermissionName).HasMaxLength(64); builder.Property(x => x.ResourceType).HasMaxLength(16); builder.Property(x => x.ResourceId).HasMaxLength(32); builder.HasOne<Role>().WithMany().HasForeignKey(x => x.RoleId).IsRequired(); builder.HasIndex(x => new { x.RoleId, x.PermissionName, x.ResourceType, x.ResourceId }).IsUnique(); } 权限 ACL 存储设计 public class PermissionGranted { public int Id { get; set; } public int RoleId { get; set; } public required string PermissionName { get; set; } public string? ResourceType { get; set; } public string? ResourceId { get; set; } } 在 DbContext 中配置 ACL 实体 public void Configure(EntityTypeBuilder<PermissionGranted> builder) { builder.ToTable("PermissionGranted"); builder.Property(x => x.Id); builder.Property(x => x.PermissionName).HasMaxLength(64); builder.Property(x => x.ResourceType).HasMaxLength(16); builder.Property(x => x.ResourceId).HasMaxLength(32); builder.HasOne<Role>().WithMany().HasForeignKey(x => x.RoleId).IsRequired(); builder.HasIndex(x => new { x.RoleId, x.PermissionName, x.ResourceType, x.ResourceId }).IsUnique(); } 设计一个权限检查器 public interface IPermissionChecker { Task<bool> IsGrantedAsync(string name, string? resourceType = null, string? resourceId = null); Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name, string? resourceType = null, string? resourceId = null); } 实现抽象的权限检查器 遍历每个角色的权限,如果有一个角色拥有该权限,则返回 true,表示授权通过。 ...

2026年3月10日

使用 Aspire 启动分布式微服务

准备数据库 docker run --name postgres -e POSTGRES_PASSWORD=postgres -e TZ=Asia/Shanghai -d -p 5432:5432 postgres 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 初始化 Dapr 运行时 dapr init -s --from-dir C:\dapr 延长等待 RabbitMQ 启动时间 public static IResourceBuilder<IDaprSidecarResource> WithReference(this IResourceBuilder<IDaprSidecarResource> builder, IResourceBuilder<IResourceWithConnectionString> resourceBuilder, int waitInSeconds = 10) 修改 MediatR 依赖注入 之前使用 options.AddBehavior 方法注册行为,现在使用 options.AddOpenBehavior 方法注册行为。 builder.Services.AddMediatR(options => { options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()); options.AddOpenBehavior(typeof(LoggingBehavior<,>)); options.AddOpenBehavior(typeof(ValidatorBehavior<,>)); options.AddOpenBehavior(typeof(TransactionBehavior<,>)); }); 使用分布式事件的微服务需要依赖注入上下文 builder.Services.AddHttpContextAccessor(); Azure Data Studio 删除数据库 -- 终止所有连接到目标数据库的会话 SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'my_database' AND pid <> pg_backend_pid(); -- 删除数据库 DROP DATABASE my_database; IdentityService 演示数据生成前自动创建数据库 await serviceProvider.GetRequiredService<IdentityServiceDbContext>().Database.EnsureCreatedAsync(cancellationToken); 统一使用 DateTimeOffset 类型而不是 DateTime 类型 使用 UTC 时间的优点是可以在不同的时区之间进行转换,而不会丢失时间信息,让程序更好的处理时间,有利于跨时区的应用程序国际化本地化。 ...

2026年3月10日

使用 Azure Data Studio 管理数据库

Azure Data Studio 是一个轻量级的跨平台数据库工具,提供了一个现代化的用户界面,可以帮助用户更轻松地管理数据库。 全部代码开源 Azure Data Studio 是一个开源项目,全部代码都托管在 GitHub 上,用户可以自由查看源代码,也可以参与到项目中来。 https://github.com/Microsoft/azuredatastudio 可管理的数据库 Azure Data Studio 支持多种数据库,包括 SQL Server、PostgreSQL、MySQL 、TimescaleDB、Oracle 等,通过安装相应的扩展,可以支持更多的数据库。 带有智能感知的查询编辑器 丰富的 SQL 编辑器、IntelliSense、关键字完成、代码片段、代码导航和源代码管理集成 (Git))提供一种基于键盘的新式 SQL 编码体验。 智能 SQL 代码片段 Azure Data Studio 提供了大量的 SQL 代码片段,可以帮助用户快速编写 SQL 代码。 使用服务器组 借助服务器组,可以分类管理服务器,方便用户更轻松地管理多个服务器。 使用集成终端 用户界面中的“集成终端”窗口中使用常用的命令行工具,例如:sqlcmd 命令。 使用表设计器 Azure Data Studio 提供了一个表设计器,可以帮助用户更轻松地设计表。 备份和还原数据库 Azure Data Studio 提供了备份和还原数据库的功能,可以帮助用户更轻松地备份和还原数据库。 使用 Copilot 插件 Copilot 插件可以借助强大的 AI 帮助用户更轻松地编写 SQL 代码,提高编码效率。 可使用 Git 管理 SQL 脚本 Azure Data Studio 集成了 Git,可以帮助用户更轻松地管理 SQL 脚本。 ...

2026年3月10日

使用 CQRS 模式实现订单服务

CQRS 是命令和查询责任分离的英文缩写,它是一种将读取操作和更新操作分离的模式,查询返回结果,不改变系统的状态,没有副作用。命令执行更改系统状态,幂等性是指相同的命令多次执行,结果是一样的,不会有副作用。 为什么要使用 CQRS 模式 在传统的体系结构中,使用同一数据模型查询和更新数据库。 这十分简单,非常适用于基本的 CRUD 操作。 CQRS 将读取和写入分离到不同的模型,使用命令来更新数据,使用查询来读取数据。读取存储可以是写入存储的只读副本,或者读取和写入存储可以具有完全不同的结构。 使用多个只读副本可以提高查询性能,尤其是在只读副本靠近应用程序实例的分布式方案中。 使用 MediatR 实现 CQRS 中的 Command 模式 dotnet add package MediatR public class CreateOrderCommand : IRequest<bool> { public string Product { get; set; } public int Quantity { get; set; } } public class CreateOrderCommandHandler1 : IRequestHandler<CreateOrderCommand, bool> { public Task<bool> Handle(CreateOrderCommand request, CancellationToken cancellationToken) { return Task.FromResult(1); } } public class CreateOrderCommandHandler2 : IRequestHandler<CreateOrderCommand, bool> { public Task<bool> Handle(CreateOrderCommand request, CancellationToken cancellationToken) { return Task.FromResult(2); } } public class OrdersController : ControllerBase { private readonly IMediator _mediator; public OrdersController(IMediator mediator) { _mediator = mediator; } [HttpPost] public async Task<IActionResult> CreateOrder(CreateOrderCommand command) { var orderId = await _mediator.Send(command); return Ok(orderId); } } builder.Services.AddMediatR(options => options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); 使用 Mediator 请求管道处理 CQRS 中的命令 LoggingBehavior、ValidatorBehavior 和 TransactionBehavior ...

2026年3月10日

使用 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日