20261008-ide1.0发布 #1
@ -297,6 +297,29 @@ public partial class SalesProductService
|
||||
return rulesSnapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步态规则快照:分区不 Ensure,其余与发布组装一致。
|
||||
/// </summary>
|
||||
private async Task<SalesProductRulesPublishSnapshotDto> BuildRulesPublishSnapshotForSyncAsync(int salesProductId)
|
||||
{
|
||||
var product = await GetProductOrThrowAsync(salesProductId);
|
||||
var rulesSnapshot = new SalesProductRulesPublishSnapshot
|
||||
{
|
||||
Product = MapProductToRulesSaveInput(product),
|
||||
Providers = (await GetAllocationCoreAsync(salesProductId)).Providers
|
||||
.Select(PublishSnapshotSlimmer.SlimAllocationProvider)
|
||||
.ToList()
|
||||
};
|
||||
await RulesSnapshotHelper.ApplyRulesToSnapshotForSyncAsync(
|
||||
rulesSnapshot,
|
||||
product,
|
||||
WeightRuleRepository,
|
||||
WeightRoundingRepository,
|
||||
KpiAreaPackService);
|
||||
await AllocationProviderSnapshotEnricher.EnrichAsync(rulesSnapshot.Providers);
|
||||
return rulesSnapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组装价格发布快照(不落库);价区钉分区包 Refs。
|
||||
/// </summary>
|
||||
|
||||
@ -410,7 +410,7 @@ public partial class SalesProductService : ApplicationService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布同步态(轻量):对比待发布与最新生效快照,不回传正文。
|
||||
/// 发布同步态:当前配置 vs 最新生效发布 Content。前端只展示 pending,不算差异。
|
||||
/// </summary>
|
||||
[HttpGet("/channel/sales-product/publish/sync-status")]
|
||||
public async Task<SalesProductPublishSyncStatusOutput> GetPublishSyncStatusAsync(
|
||||
@ -420,48 +420,45 @@ public partial class SalesProductService : ApplicationService
|
||||
var normalized = NormalizeOperatorType(operatorType);
|
||||
await GetProductOrThrowAsync(salesProductId);
|
||||
|
||||
// 待发布:同步态不 Ensure 分区包;对比时忽略 PackVersion/AreaName,只比价格项 + 分区 ContentHash
|
||||
object pendingSnapshot = normalized == PriceOperatorType
|
||||
// 当前 = 与发布同源的快照组装(分区只解析哈希,不 Ensure 写库)
|
||||
object currentSnapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotForSyncAsync(salesProductId)
|
||||
: await BuildRulesPublishSnapshotAsync(salesProductId);
|
||||
var pendingCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.HashSalesProductPriceSnapshotForSync(pendingSnapshot)
|
||||
: OmsDocHelper.HashSalesProductRulesSnapshotForSync(pendingSnapshot);
|
||||
var pendingHash = ComputeContentHash(pendingCanon);
|
||||
: await BuildRulesPublishSnapshotForSyncAsync(salesProductId);
|
||||
var currentHash = ComputeContentHash(
|
||||
normalized == PriceOperatorType
|
||||
? OmsDocHelper.HashSalesProductPriceSnapshotForSync(currentSnapshot)
|
||||
: OmsDocHelper.HashSalesProductRulesSnapshotForSync(currentSnapshot));
|
||||
|
||||
var headers = await SalesProductRepository.GetAllPublishRecordHeadersAsync(salesProductId, normalized);
|
||||
var onlineHeader = headers
|
||||
.Where(x => x.Status == Domain.Shared.Enums.EsSnapshotStatus.Active)
|
||||
.OrderByDescending(x => x.PublishTime)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (onlineHeader == null)
|
||||
// 生效 = 最新 Active 发布记录 Content
|
||||
var onlineRecord = await SalesProductRepository.GetEffectivePublishRecordAsync(
|
||||
salesProductId,
|
||||
normalized,
|
||||
DateTime.Now);
|
||||
if (onlineRecord == null || string.IsNullOrWhiteSpace(onlineRecord.Content))
|
||||
{
|
||||
return new SalesProductPublishSyncStatusOutput
|
||||
{
|
||||
Pending = true,
|
||||
OnlineVersion = null,
|
||||
OnlinePublishTime = null,
|
||||
PendingContentHash = pendingHash,
|
||||
PendingContentHash = currentHash,
|
||||
OnlineContentHash = null
|
||||
};
|
||||
}
|
||||
|
||||
var onlineRecord = await SalesProductRepository.GetPublishRecordByIdAsync(onlineHeader.Id);
|
||||
var onlineJson = onlineRecord?.Content ?? string.Empty;
|
||||
var onlineCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.HashSalesProductPriceSnapshotForSync(
|
||||
OmsDocHelper.DeserializePriceSnapshot(onlineJson))
|
||||
: OmsDocHelper.HashSalesProductRulesSnapshotForSync(
|
||||
OmsDocHelper.DeserializeRulesSnapshot(onlineJson));
|
||||
var onlineHash = ComputeContentHash(onlineCanon);
|
||||
var onlineHash = ComputeContentHash(
|
||||
normalized == PriceOperatorType
|
||||
? OmsDocHelper.HashSalesProductPriceSnapshotForSync(
|
||||
OmsDocHelper.DeserializePriceSnapshot(onlineRecord.Content))
|
||||
: OmsDocHelper.HashSalesProductRulesSnapshotForSync(
|
||||
OmsDocHelper.DeserializeRulesSnapshot(onlineRecord.Content)));
|
||||
|
||||
return new SalesProductPublishSyncStatusOutput
|
||||
{
|
||||
Pending = !string.Equals(pendingHash, onlineHash, StringComparison.Ordinal),
|
||||
OnlineVersion = onlineHeader.Version,
|
||||
OnlinePublishTime = onlineHeader.PublishTime,
|
||||
PendingContentHash = pendingHash,
|
||||
Pending = !string.Equals(currentHash, onlineHash, StringComparison.Ordinal),
|
||||
OnlineVersion = onlineRecord.Version,
|
||||
OnlinePublishTime = onlineRecord.PublishTime,
|
||||
PendingContentHash = currentHash,
|
||||
OnlineContentHash = onlineHash
|
||||
};
|
||||
}
|
||||
|
||||
@ -325,6 +325,46 @@ public partial class ServiceChannelService
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渠道规则同步态:偏远分区不 Ensure。
|
||||
/// </summary>
|
||||
private async Task<ServiceChannelRulesPublishSnapshotDto> BuildRulesPublishSnapshotForSyncAsync(int serviceChannelId)
|
||||
{
|
||||
var entity = await GetEntityOrThrowAsync(serviceChannelId);
|
||||
ServiceChannelDownstreamSnapshotDto? downstream = null;
|
||||
if (entity.GroupId is > 0)
|
||||
{
|
||||
var group = await ServiceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == entity.GroupId.Value);
|
||||
if (group != null)
|
||||
{
|
||||
downstream = await AllocationProviderSnapshotEnricher.BuildDownstreamFromGroupAsync(group);
|
||||
}
|
||||
}
|
||||
|
||||
var snapshot = new ServiceChannelRulesPublishSnapshotDto
|
||||
{
|
||||
Channel = MapChannelToSaveInput(entity),
|
||||
Downstream = downstream,
|
||||
RemoteAreaInfos = []
|
||||
};
|
||||
|
||||
if (entity.RemoteAreaId is > 0)
|
||||
{
|
||||
var packRef = await KpiAreaPackService.ResolvePackRefForSyncAsync(entity.RemoteAreaId.Value);
|
||||
snapshot.RemoteAreaId = entity.RemoteAreaId;
|
||||
snapshot.RemoteAreaName = packRef.AreaName;
|
||||
snapshot.RemoteAreaRef = packRef;
|
||||
}
|
||||
else
|
||||
{
|
||||
snapshot.RemoteAreaId = null;
|
||||
snapshot.RemoteAreaName = null;
|
||||
snapshot.RemoteAreaRef = null;
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private string ResolvePublishUser() =>
|
||||
string.IsNullOrWhiteSpace(CurrentUser.UserName)
|
||||
? CurrentUser.GetId<int>().ToString()
|
||||
|
||||
@ -403,7 +403,7 @@ public partial class ServiceChannelService : ApplicationService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布同步态(轻量):对比待发布与最新生效快照,不回传正文。与产品卖价对称。
|
||||
/// 发布同步态:当前配置 vs 最新生效发布 Content。前端只展示 pending。
|
||||
/// </summary>
|
||||
[HttpGet("/channel/service-channel/publish/sync-status")]
|
||||
public async Task<ServiceChannelPublishSyncStatusOutput> GetPublishSyncStatusAsync(
|
||||
@ -413,49 +413,70 @@ public partial class ServiceChannelService : ApplicationService
|
||||
var normalized = NormalizeOperatorType(operatorType);
|
||||
await GetEntityOrThrowAsync(serviceChannelId);
|
||||
|
||||
object pendingSnapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotForSyncAsync(serviceChannelId)
|
||||
: await BuildRulesPublishSnapshotAsync(serviceChannelId);
|
||||
var pendingCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.HashChannelPriceSnapshotForSync(pendingSnapshot)
|
||||
: OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty;
|
||||
var pendingHash = ComputeContentHash(pendingCanon);
|
||||
string currentCanon;
|
||||
if (normalized == PriceOperatorType)
|
||||
{
|
||||
var current = await BuildPricePublishSnapshotForSyncAsync(serviceChannelId);
|
||||
currentCanon = OmsDocHelper.HashChannelPriceSnapshotForSync(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
var current = await BuildRulesPublishSnapshotForSyncAsync(serviceChannelId);
|
||||
currentCanon = HashChannelRulesSnapshotForSync(current);
|
||||
}
|
||||
|
||||
var currentHash = ComputeContentHash(currentCanon);
|
||||
|
||||
var onlineRecord = await ServiceChannelRepository.GetEffectivePublishRecordAsync(
|
||||
serviceChannelId,
|
||||
normalized,
|
||||
DateTime.Now);
|
||||
if (onlineRecord == null)
|
||||
if (onlineRecord == null || string.IsNullOrWhiteSpace(onlineRecord.Content))
|
||||
{
|
||||
return new ServiceChannelPublishSyncStatusOutput
|
||||
{
|
||||
Pending = true,
|
||||
OnlineVersion = null,
|
||||
OnlinePublishTime = null,
|
||||
PendingContentHash = pendingHash,
|
||||
PendingContentHash = currentHash,
|
||||
OnlineContentHash = null
|
||||
};
|
||||
}
|
||||
|
||||
var onlineJson = onlineRecord.Content ?? string.Empty;
|
||||
var onlineCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.HashChannelPriceSnapshotForSync(
|
||||
OmsDocHelper.DeserializeChannelPriceSnapshot(onlineJson))
|
||||
: OmsDocHelper.SerializeSnapshotJson(
|
||||
OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(onlineJson))
|
||||
?? string.Empty;
|
||||
OmsDocHelper.DeserializeChannelPriceSnapshot(onlineRecord.Content))
|
||||
: HashChannelRulesSnapshotForSync(
|
||||
OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(onlineRecord.Content));
|
||||
var onlineHash = ComputeContentHash(onlineCanon);
|
||||
|
||||
return new ServiceChannelPublishSyncStatusOutput
|
||||
{
|
||||
Pending = !string.Equals(pendingHash, onlineHash, StringComparison.Ordinal),
|
||||
Pending = !string.Equals(currentHash, onlineHash, StringComparison.Ordinal),
|
||||
OnlineVersion = onlineRecord.Version,
|
||||
OnlinePublishTime = onlineRecord.PublishTime,
|
||||
PendingContentHash = pendingHash,
|
||||
PendingContentHash = currentHash,
|
||||
OnlineContentHash = onlineHash
|
||||
};
|
||||
}
|
||||
|
||||
private static string HashChannelRulesSnapshotForSync(ServiceChannelRulesPublishSnapshotDto? snapshot)
|
||||
{
|
||||
if (snapshot == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
snapshot.RemoteAreaName = null;
|
||||
if (snapshot.RemoteAreaRef != null)
|
||||
{
|
||||
snapshot.RemoteAreaRef.PackVersion = string.Empty;
|
||||
snapshot.RemoteAreaRef.AreaName = null;
|
||||
}
|
||||
|
||||
return OmsDocHelper.SerializeSnapshotJson(snapshot) ?? string.Empty;
|
||||
}
|
||||
|
||||
private static string ComputeContentHash(string content)
|
||||
{
|
||||
var hash = global::System.Security.Cryptography.SHA256.HashData(
|
||||
|
||||
@ -28,6 +28,7 @@ public static class RulesSnapshotHelper
|
||||
KpiAreaPackService.AreaTypeBlacklist,
|
||||
packService,
|
||||
publishUser,
|
||||
ensurePack: true,
|
||||
(s, areaId, areaName, packRef) =>
|
||||
{
|
||||
s.BlacklistAreaId = areaId;
|
||||
@ -43,6 +44,54 @@ public static class RulesSnapshotHelper
|
||||
KpiAreaPackService.AreaTypeRemote,
|
||||
packService,
|
||||
publishUser,
|
||||
ensurePack: true,
|
||||
(s, areaId, areaName, packRef) =>
|
||||
{
|
||||
s.RemoteAreaId = areaId;
|
||||
s.RemoteAreaName = areaName;
|
||||
s.RemoteAreaRef = packRef;
|
||||
s.RemoteAreaInfos = [];
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
await ApplyWeightRuleToSnapshotAsync(snapshot, product, weightRuleRepository, cancellationToken);
|
||||
await ApplyWeightRoundingToSnapshotAsync(snapshot, product, weightRoundingRepository, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步态:黑名单/偏远只解析哈希/已有包,不 Ensure 写库。
|
||||
/// </summary>
|
||||
public static async Task ApplyRulesToSnapshotForSyncAsync(
|
||||
SalesProductRulesPublishSnapshotDto snapshot,
|
||||
SalesProduct product,
|
||||
IWeightRuleRepository weightRuleRepository,
|
||||
IWeightRoundingRepository weightRoundingRepository,
|
||||
KpiAreaPackService packService,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ApplyKpiAreaPackRefAsync(
|
||||
snapshot,
|
||||
product.BlacklistAreaId,
|
||||
KpiAreaPackService.AreaTypeBlacklist,
|
||||
packService,
|
||||
publishUser: null,
|
||||
ensurePack: false,
|
||||
(s, areaId, areaName, packRef) =>
|
||||
{
|
||||
s.BlacklistAreaId = areaId;
|
||||
s.BlacklistAreaName = areaName;
|
||||
s.BlacklistAreaRef = packRef;
|
||||
s.BlacklistAreaInfos = [];
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
await ApplyKpiAreaPackRefAsync(
|
||||
snapshot,
|
||||
product.RemoteAreaId,
|
||||
KpiAreaPackService.AreaTypeRemote,
|
||||
packService,
|
||||
publishUser: null,
|
||||
ensurePack: false,
|
||||
(s, areaId, areaName, packRef) =>
|
||||
{
|
||||
s.RemoteAreaId = areaId;
|
||||
@ -107,6 +156,7 @@ public static class RulesSnapshotHelper
|
||||
string areaType,
|
||||
KpiAreaPackService packService,
|
||||
string? publishUser,
|
||||
bool ensurePack,
|
||||
Action<SalesProductRulesPublishSnapshotDto, int?, string?, KpiAreaPackRefDto?> assign,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@ -116,12 +166,14 @@ public static class RulesSnapshotHelper
|
||||
return;
|
||||
}
|
||||
|
||||
var packRef = await packService.EnsurePackAsync(
|
||||
areaId.Value,
|
||||
areaType,
|
||||
publishUser,
|
||||
description: "sales-product-rules-publish",
|
||||
cancellationToken);
|
||||
var packRef = ensurePack
|
||||
? await packService.EnsurePackAsync(
|
||||
areaId.Value,
|
||||
areaType,
|
||||
publishUser,
|
||||
description: "sales-product-rules-publish",
|
||||
cancellationToken)
|
||||
: await packService.ResolvePackRefForSyncAsync(areaId.Value, cancellationToken);
|
||||
assign(snapshot, areaId, packRef.AreaName, packRef);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user