fix(api): 修复商品保存事务重试冲突并兼容kind缺省
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 46s

This commit is contained in:
2026-02-24 16:51:45 +08:00
parent 2c7981b6d0
commit c2a6cf7b1e
2 changed files with 31 additions and 7 deletions

View File

@@ -80,7 +80,7 @@ public sealed class SaveProductRequest
/// <summary>
/// 商品类型single/combo
/// </summary>
public string Kind { get; set; } = "single";
public string? Kind { get; set; }
/// <summary>
/// 商品名称。

View File

@@ -126,12 +126,10 @@ public sealed class ProductController(
await EnsureStoreAccessibleAsync(storeId, cancellationToken);
var categoryId = StoreApiHelpers.ParseRequiredSnowflake(request.CategoryId, nameof(request.CategoryId));
var kind = ParseKind(request.Kind);
var productId = StoreApiHelpers.ParseSnowflakeOrNull(request.Id);
var timedOnShelfAt = ParseTimedOnShelfAt(request.ShelfMode, request.TimedOnShelfAt);
var normalizedStatus = ResolveStatusByShelfMode(request.ShelfMode, request.Status);
var imageUrls = NormalizeImageUrls(request.ImageUrls);
var comboGroups = NormalizeComboGroups(request.ComboGroups, kind);
ProductDto? existing = null;
if (productId.HasValue)
@@ -147,6 +145,9 @@ public sealed class ProductController(
}
}
var kind = ResolveKindForSave(request.Kind, existing);
var comboGroups = NormalizeComboGroups(request.ComboGroups, kind);
var shouldReplaceSpecAddon = existing is null || request.SpecTemplateIds is not null || request.AddonGroupIds is not null;
var shouldReplaceLabels = existing is null || request.LabelIds is not null;
var shouldReplaceSkus = existing is null || request.Skus is not null;
@@ -187,9 +188,16 @@ public sealed class ProductController(
int? remainStock = soldoutMode.HasValue ? Math.Max(0, existing?.RemainStock ?? request.Stock) : null;
var soldoutReason = soldoutMode.HasValue ? existing?.SoldoutReason : null;
ProductDto? saved;
await using (var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken))
ProductDto? saved = null;
var notFoundInTransaction = false;
var executionStrategy = dbContext.Database.CreateExecutionStrategy();
await executionStrategy.ExecuteAsync(async () =>
{
saved = null;
notFoundInTransaction = false;
dbContext.ChangeTracker.Clear();
await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
if (existing is null)
{
saved = await mediator.Send(new CreateProductCommand
@@ -261,7 +269,8 @@ public sealed class ProductController(
if (saved is null)
{
return ApiResponse<ProductDetailResponse>.Error(ErrorCodes.NotFound, "商品不存在");
notFoundInTransaction = true;
return;
}
if (shouldReplaceSpecAddon)
@@ -281,9 +290,14 @@ public sealed class ProductController(
await ReplaceComboGroupsAsync(saved.Id, storeId, kind, comboGroups, cancellationToken);
await transaction.CommitAsync(cancellationToken);
});
if (notFoundInTransaction || saved is null)
{
return ApiResponse<ProductDetailResponse>.Error(ErrorCodes.NotFound, "商品不存在");
}
var savedProduct = saved!;
var savedProduct = saved;
var detailComboGroups = await BuildComboGroupResponsesAsync(savedProduct.Id, storeId, cancellationToken);
var categoryNameLookup = await BuildCategoryNameLookupAsync(storeId, cancellationToken);
var savedRelationState = await LoadProductRelationStateAsync(savedProduct.Id, storeId, cancellationToken);
@@ -727,6 +741,16 @@ public sealed class ProductController(
};
}
private static ProductKind ResolveKindForSave(string? kind, ProductDto? existing)
{
if (string.IsNullOrWhiteSpace(kind))
{
return existing?.Kind ?? ProductKind.Single;
}
return ParseKind(kind);
}
private static ProductKind? ParseKindOrNull(string? kind)
{
if (string.IsNullOrWhiteSpace(kind))