using FreeSql; using MAX.Core.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Store.Modules.CloudProduct.Entities; using Store.Modules.CloudProduct.Dtos; namespace Store.Modules.CloudProduct.Repositorys; public class GoodsViolationRepository( [FromKeyedServices("CloudProductConnection")] IFreeSql fsql) : BaseRepository(fsql), IScopedDependency { public Task> GetReasonsAsync() => Orm.Select() .OrderBy(x => x.Id).ToListAsync(x => new ViolationReasonDto { Name = x.Name, IsViolation = x.IsViolation }); public Task> GetViolationIdsAsync(long[] ids) => Select .Where(x => ids.Contains(x.GoodsId)).Distinct().ToListAsync(x => x.GoodsId); public async Task> GetOffShelfGoodsIdsAsync(long[] ids) { var records = await Select.Where(x => ids.Contains(x.GoodsId)).ToListAsync(); return records.GroupBy(x => x.GoodsId).Where(x => x.All(row => row.IsIndexDeleted)) .Select(x => x.Key).ToList(); } // 解除违规和设置待同步必须一起提交,进程中断后后台索引任务仍可恢复。 public async Task RemoveAndScheduleRestoreAsync(long[] ids) { using var unit = Orm.CreateUnitOfWork(); var violations = unit.GetRepository(); var goods = unit.GetRepository(); var removedIds = await violations.Select.Where(x => ids.Contains(x.GoodsId)).Distinct().ToListAsync(x => x.GoodsId); await violations.DeleteAsync(x => ids.Contains(x.GoodsId)); await goods.UpdateDiy.Set(x => x.IsSegmented, (byte)0) .Where(x => ids.Contains(x.GoodsId) && x.Status == 0).ExecuteAffrowsAsync(); unit.Commit(); return removedIds.Count; } public Task> GetRestorableGoodsAsync(long[] ids) => Orm.Select() .Where(x => ids.Contains(x.GoodsId) && x.Status == 0).ToListAsync(); public Task MarkRestoredAsync(long[] ids) => Orm.Update() .Set(x => x.IsSegmented, (byte)1) .Where(x => ids.Contains(x.GoodsId) && x.Status == 0).ExecuteAffrowsAsync(); // 调用方持有 GoodsIndexLock,事务内合并原因,避免并发覆盖。 public async Task MergeAsync(Dictionary items) { using var unit = Orm.CreateUnitOfWork(); var repository = unit.GetRepository(); var reasonRepository = unit.GetRepository(); var reasonIds = new Dictionary(StringComparer.Ordinal); foreach (var name in items.Values.SelectMany(x => x).Distinct(StringComparer.Ordinal)) { // 让数据库按字典表的排序规则匹配名称,与唯一索引保持一致。 var reason = await reasonRepository.Select.Where(x => x.Name == name).FirstAsync(); // 新类型只收录到字典,绝不因首次上报自动启用商品封禁。 reason ??= await reasonRepository.InsertAsync(new ViolationReasonEntity { Name = name, IsViolation = false }); // 数据库排序规则可能忽略部分字符差异;判定时仍要求名称完整相等。 if (reason.IsViolation && string.Equals(reason.Name, name, StringComparison.Ordinal)) reasonIds.Add(name, reason.Id); } var ids = items.Keys.ToArray(); var existing = (await repository.Select.Where(x => ids.Contains(x.GoodsId)) .ToListAsync()).ToLookup(x => x.GoodsId); var now = DateTime.UtcNow; var recordedIds = new List(); foreach (var (goodsId, reasons) in items) { var accepted = reasons.Where(reasonIds.ContainsKey).Select(x => reasonIds[x]).Distinct().ToArray(); // 忽略原因仅记录字典,不新增或改变该商品已有的违规状态。 if (accepted.Length == 0) continue; var rows = existing[goodsId].ToArray(); var previous = rows.Select(x => x.ReasonId).ToHashSet(); var added = accepted.Where(x => !previous.Contains(x)).ToArray(); if (added.Length == 0 && rows.All(x => x.IsIndexDeleted)) continue; // 下架是商品级操作:新增原因或存在失败记录时,整件商品重新标记待下架。 foreach (var row in rows) { row.IsIndexDeleted = false; row.UpdatedTime = now; await repository.UpdateAsync(row); } foreach (var reasonId in added) { await repository.InsertAsync(new GoodsViolationEntity { GoodsId = goodsId, ReasonId = reasonId, IsIndexDeleted = false, CreatedTime = now, UpdatedTime = now }); } recordedIds.Add(goodsId); } unit.Commit(); return recordedIds.ToArray(); } public Task MarkDeletedAsync(long[] ids) => Orm.Update() .Set(x => x.IsIndexDeleted, true) .Set(x => x.UpdatedTime, DateTime.UtcNow) .Where(x => ids.Contains(x.GoodsId)).ExecuteAffrowsAsync(); public Task> GetBatchAsync(long afterId, int size, bool pendingOnly) => Select .Where(x => x.GoodsId > afterId) .WhereIf(pendingOnly, x => !x.IsIndexDeleted) .Distinct().OrderBy(x => x.GoodsId).Limit(size).ToListAsync(x => x.GoodsId); public Task> GetByReasonAsync(uint reasonId, long afterId, int size) => Select .Where(x => x.ReasonId == reasonId) .Where(x => x.GoodsId > afterId).OrderBy(x => x.GoodsId) .Limit(size).ToListAsync(x => x.GoodsId); }