diff --git a/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveJobRunner.cs b/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveJobRunner.cs index 515d53f..b7e10a4 100644 --- a/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveJobRunner.cs +++ b/src/Api/TakeoutSaaS.TenantApi/Services/ProductSkuSaveJobRunner.cs @@ -1,3 +1,4 @@ +using System.Buffers.Binary; using System.Text.Json; using Hangfire; using Microsoft.EntityFrameworkCore; @@ -77,8 +78,9 @@ public sealed class ProductSkuSaveJobRunner( await dbContext.SaveChangesAsync(); // 以租户+商品维度申请事务级咨询锁,保证同商品串行落库。 + var advisoryLockKey = BuildAdvisoryLockKey(job.TenantId, job.ProductId); await dbContext.Database.ExecuteSqlInterpolatedAsync( - $"SELECT pg_advisory_xact_lock({job.TenantId}, {job.ProductId});"); + $"SELECT pg_advisory_xact_lock({advisoryLockKey});"); var payload = DeserializePayload(job.PayloadJson); if (payload.Skus.Count == 0) @@ -194,5 +196,23 @@ public sealed class ProductSkuSaveJobRunner( return string.Join(Environment.NewLine, lines); } + private static long BuildAdvisoryLockKey(long tenantId, long productId) + { + Span raw = stackalloc byte[16]; + BinaryPrimitives.WriteInt64LittleEndian(raw, tenantId); + BinaryPrimitives.WriteInt64LittleEndian(raw[8..], productId); + + const ulong fnvOffsetBasis = 14695981039346656037UL; + const ulong fnvPrime = 1099511628211UL; + var hash = fnvOffsetBasis; + foreach (var b in raw) + { + hash ^= b; + hash *= fnvPrime; + } + + return unchecked((long)hash); + } + private sealed record JobMeta(long Id, long TenantId); }