fix(api): 修复商品保存事务重试冲突并兼容kind缺省
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 46s
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 46s
This commit is contained in:
@@ -80,7 +80,7 @@ public sealed class SaveProductRequest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商品类型(single/combo)。
|
/// 商品类型(single/combo)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Kind { get; set; } = "single";
|
public string? Kind { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商品名称。
|
/// 商品名称。
|
||||||
|
|||||||
@@ -126,12 +126,10 @@ public sealed class ProductController(
|
|||||||
await EnsureStoreAccessibleAsync(storeId, cancellationToken);
|
await EnsureStoreAccessibleAsync(storeId, cancellationToken);
|
||||||
|
|
||||||
var categoryId = StoreApiHelpers.ParseRequiredSnowflake(request.CategoryId, nameof(request.CategoryId));
|
var categoryId = StoreApiHelpers.ParseRequiredSnowflake(request.CategoryId, nameof(request.CategoryId));
|
||||||
var kind = ParseKind(request.Kind);
|
|
||||||
var productId = StoreApiHelpers.ParseSnowflakeOrNull(request.Id);
|
var productId = StoreApiHelpers.ParseSnowflakeOrNull(request.Id);
|
||||||
var timedOnShelfAt = ParseTimedOnShelfAt(request.ShelfMode, request.TimedOnShelfAt);
|
var timedOnShelfAt = ParseTimedOnShelfAt(request.ShelfMode, request.TimedOnShelfAt);
|
||||||
var normalizedStatus = ResolveStatusByShelfMode(request.ShelfMode, request.Status);
|
var normalizedStatus = ResolveStatusByShelfMode(request.ShelfMode, request.Status);
|
||||||
var imageUrls = NormalizeImageUrls(request.ImageUrls);
|
var imageUrls = NormalizeImageUrls(request.ImageUrls);
|
||||||
var comboGroups = NormalizeComboGroups(request.ComboGroups, kind);
|
|
||||||
|
|
||||||
ProductDto? existing = null;
|
ProductDto? existing = null;
|
||||||
if (productId.HasValue)
|
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 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 shouldReplaceLabels = existing is null || request.LabelIds is not null;
|
||||||
var shouldReplaceSkus = existing is null || request.Skus 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;
|
int? remainStock = soldoutMode.HasValue ? Math.Max(0, existing?.RemainStock ?? request.Stock) : null;
|
||||||
var soldoutReason = soldoutMode.HasValue ? existing?.SoldoutReason : null;
|
var soldoutReason = soldoutMode.HasValue ? existing?.SoldoutReason : null;
|
||||||
|
|
||||||
ProductDto? saved;
|
ProductDto? saved = null;
|
||||||
await using (var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken))
|
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)
|
if (existing is null)
|
||||||
{
|
{
|
||||||
saved = await mediator.Send(new CreateProductCommand
|
saved = await mediator.Send(new CreateProductCommand
|
||||||
@@ -261,7 +269,8 @@ public sealed class ProductController(
|
|||||||
|
|
||||||
if (saved is null)
|
if (saved is null)
|
||||||
{
|
{
|
||||||
return ApiResponse<ProductDetailResponse>.Error(ErrorCodes.NotFound, "商品不存在");
|
notFoundInTransaction = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldReplaceSpecAddon)
|
if (shouldReplaceSpecAddon)
|
||||||
@@ -281,9 +290,14 @@ public sealed class ProductController(
|
|||||||
|
|
||||||
await ReplaceComboGroupsAsync(saved.Id, storeId, kind, comboGroups, cancellationToken);
|
await ReplaceComboGroupsAsync(saved.Id, storeId, kind, comboGroups, cancellationToken);
|
||||||
await transaction.CommitAsync(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 detailComboGroups = await BuildComboGroupResponsesAsync(savedProduct.Id, storeId, cancellationToken);
|
||||||
var categoryNameLookup = await BuildCategoryNameLookupAsync(storeId, cancellationToken);
|
var categoryNameLookup = await BuildCategoryNameLookupAsync(storeId, cancellationToken);
|
||||||
var savedRelationState = await LoadProductRelationStateAsync(savedProduct.Id, 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)
|
private static ProductKind? ParseKindOrNull(string? kind)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(kind))
|
if (string.IsNullOrWhiteSpace(kind))
|
||||||
|
|||||||
Reference in New Issue
Block a user