CQRS架构下Web API生成QR Code并存储返回的实现方案咨询
解决方案:CQRS架构下单请求完成QR Code生成、存储与返回
核心思路
严格遵循CQRS"命令改状态、查询读状态"的原则,通过同一请求内先执行命令、再执行查询的方式完成需求,无需前端发起两次请求。命令负责生成QR Code并将加密数据存入数据库,查询负责读取刚生成的Base64结果,通过唯一ID关联两者。
步骤1:定义命令与命令处理器
命令负责生成QR Code、加密数据并写入数据库,不返回结果(符合CQRS命令规范)。
命令类
public class GenerateAndSaveQrCodeCommand : IRequest { // 前端传入的原始数据 public string ReceiverData { get; set; } // 预先生成的唯一标识,用于后续查询关联 public Guid QrCodeId { get; set; } = Guid.NewGuid(); }
命令处理器
internal class GenerateAndSaveQrCodeCommandHandler : IRequestHandler<GenerateAndSaveQrCodeCommand> { private readonly IQrCodeClient<string> _qrCodeGenerator; private readonly AppDbContext _dbContext; public GenerateAndSaveQrCodeCommandHandler(IQrCodeClient<string> qrCodeGenerator, AppDbContext dbContext) { _qrCodeGenerator = qrCodeGenerator; _dbContext = dbContext; } public async Task Handle(GenerateAndSaveQrCodeCommand request, CancellationToken cancellationToken) { // 1. 调用外部服务生成QR Code的Base64字符串 var qrBase64 = await _qrCodeGenerator.GetQrCode(request.ReceiverData, cancellationToken); // 2. 对原始数据执行加密(替换为你的实际加密逻辑) var encryptedData = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(request.ReceiverData)); // 3. 将加密数据与Base64存入数据库 var qrRecord = new QrCodeRecord { Id = request.QrCodeId, EncryptedData = encryptedData, QrBase64 = qrBase64, CreatedAt = DateTime.UtcNow }; await _dbContext.QrCodeRecords.AddAsync(qrRecord, cancellationToken); await _dbContext.SaveChangesAsync(cancellationToken); } }
步骤2:定义查询与查询处理器
查询负责根据唯一ID读取刚生成的QR Code Base64结果。
查询类
public class GetQrCodeByIdQuery : IRequest<string> { public Guid QrCodeId { get; set; } }
查询处理器
internal class GetQrCodeByIdQueryHandler : IRequestHandler<GetQrCodeByIdQuery, string> { private readonly AppDbContext _dbContext; public GetQrCodeByIdQueryHandler(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<string> Handle(GetQrCodeByIdQuery request, CancellationToken cancellationToken) { var qrRecord = await _dbContext.QrCodeRecords .FirstOrDefaultAsync(r => r.Id == request.QrCodeId, cancellationToken); return qrRecord?.QrBase64 ?? throw new KeyNotFoundException("QR Code记录不存在"); } }
步骤3:修改API端点
在同一请求中顺序执行命令与查询,返回结果给前端。注意用POST方法(生成操作属于状态变更,不符合GET语义)。
app.MapPost("/generateQrCode", async ([FromBody] string receiverData, [FromServices] IMediator mediator) => { var qrCodeId = Guid.NewGuid(); // 执行命令:生成并保存QR Code await mediator.Send(new GenerateAndSaveQrCodeCommand { ReceiverData = receiverData, QrCodeId = qrCodeId }); // 执行查询:获取刚生成的QR Code Base64 var qrBase64 = await mediator.Send(new GetQrCodeByIdQuery { QrCodeId = qrCodeId }); return Results.Ok(new { QrCodeBase64 = qrBase64 }); });
关键说明
- 避免查询修改状态:你的原代码将数据库写入逻辑放在查询处理器中,违反了CQRS"查询只读"的原则,必须拆分到命令处理器。
- 命令与查询的关联:通过预先生成的
QrCodeId确保查询能精准获取刚生成的记录,避免并发场景下的数据混乱。 - 一致性保障:命令执行完成后才触发查询,依赖数据库的事务提交保证数据已持久化;单实例场景下无需额外处理,分布式场景可引入缓存(如Redis)临时存储生成结果,提升查询可靠性。
内容的提问来源于stack exchange,提问作者philomelka
相关产品推荐
相关产品推荐

