fix: 修复商户中心查询 RowVersion 列不存在问题
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 38s

This commit is contained in:
2026-02-06 13:56:08 +08:00
parent 5f2064324d
commit b02950e465
6 changed files with 26 additions and 14 deletions

View File

@@ -44,7 +44,7 @@ public sealed record UpdateMerchantCommand : IRequest<UpdateMerchantResultDto?>
public string? ContactEmail { get; init; }
/// <summary>
/// 并发控制版本。
/// 并发控制版本(兼容字段,当前由数据库 xmin 托管)
/// </summary>
public byte[] RowVersion { get; init; } = Array.Empty<byte>();
public byte[]? RowVersion { get; init; }
}

View File

@@ -29,16 +29,11 @@ public sealed class UpdateMerchantCommandHandler(
/// <inheritdoc />
public async Task<UpdateMerchantResultDto?> Handle(UpdateMerchantCommand request, CancellationToken cancellationToken)
{
if (request.RowVersion == null || request.RowVersion.Length == 0)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空");
}
// 1. 获取操作者权限
var currentTenantId = tenantProvider.GetCurrentTenantId();
// 2. 读取商户信息
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken);
var merchant = await merchantRepository.GetForUpdateAsync(request.MerchantId, currentTenantId, cancellationToken);
if (merchant == null)
{
@@ -73,7 +68,6 @@ public sealed class UpdateMerchantCommandHandler(
merchant.Address = registeredAddress;
merchant.ContactPhone = contactPhone;
merchant.ContactEmail = contactEmail;
merchant.RowVersion = request.RowVersion;
var requiresReview = merchant.Status == MerchantStatus.Approved && criticalChanged;
if (requiresReview)

View File

@@ -21,6 +21,5 @@ public sealed class UpdateMerchantCommandValidator : AbstractValidator<UpdateMer
RuleFor(x => x.ContactPhone).NotEmpty().MaximumLength(32);
RuleFor(x => x.ContactEmail).EmailAddress().MaximumLength(128)
.When(x => !string.IsNullOrWhiteSpace(x.ContactEmail));
RuleFor(x => x.RowVersion).NotEmpty();
}
}

View File

@@ -18,6 +18,15 @@ public interface IMerchantRepository
/// <returns>商户实体或 null。</returns>
Task<Merchant?> FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 依据标识获取商户(用于更新,返回可跟踪实体)。
/// </summary>
/// <param name="merchantId">商户 ID。</param>
/// <param name="tenantId">租户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>商户实体或 null。</returns>
Task<Merchant?> GetForUpdateAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 按状态筛选商户列表。
/// </summary>

View File

@@ -536,10 +536,12 @@ public sealed class TakeoutAppDbContext(
builder.Property(x => x.IsFrozen).HasDefaultValue(false);
builder.Property(x => x.FrozenReason).HasMaxLength(500);
builder.Property(x => x.ClaimedByName).HasMaxLength(100);
builder.Property(x => x.RowVersion)
.IsRowVersion()
.IsConcurrencyToken()
.HasColumnType("bytea");
builder.Ignore(x => x.RowVersion);
builder.Property<uint>("xmin")
.HasColumnName("xmin")
.HasColumnType("xid")
.ValueGeneratedOnAddOrUpdate()
.IsConcurrencyToken();
builder.HasIndex(x => x.TenantId);
builder.HasIndex(x => new { x.TenantId, x.Status });
builder.HasIndex(x => x.ClaimedBy);

View File

@@ -25,6 +25,14 @@ public sealed class EfMerchantRepository(TakeoutAppDbContext context, TakeoutLog
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<Merchant?> GetForUpdateAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
return context.Merchants
.Where(x => x.TenantId == tenantId && x.Id == merchantId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Merchant>> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default)
{