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 { Id = x.Id, 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> GetReasonIdsByGoodsAsync(long[] ids) { var rows = await Select.Where(x => ids.Contains(x.GoodsId)).ToListAsync(x => new { x.GoodsId, x.ReasonId }); return rows.ToLookup(x => x.GoodsId, x => checked((int)x.ReasonId)); } 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 dictionary = 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 }); dictionary.Add(name, reason); } var ids = items.Keys.ToArray(); var existing = (await repository.Select.Where(x => ids.Contains(x.GoodsId)) .ToListAsync()).ToLookup(x => x.GoodsId); var now = DateTime.Now; var recordedIds = new List(); foreach (var (goodsId, reasons) in items) { // 判定要求名称完整匹配。 var accepted = reasons.Where(name => dictionary[name].IsViolation && dictionary[name].Name == name) .Select(name => dictionary[name].Id).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 CountReasonsAsync(uint[] ids) => Orm.Select() .Where(x => ids.Contains(x.Id)).CountAsync(); public async Task InsertLogsAsync(GoodsViolationLogEntity[] logs) { // 即使 ORM 拆分 INSERT,各批次也在同一事务内提交。 using var unit = Orm.CreateUnitOfWork(); await unit.GetRepository().InsertAsync(logs); unit.Commit(); } // 游标按自增主键倒序翻页,同一时间的事件也有确定顺序。 public Task> GetLogsAsync(string[] shopIds, long? goodsId, long beforeId, int size) => Orm.Select().Where(x => shopIds.Contains(x.ShopId!)) .WhereIf(goodsId.HasValue, x => x.GoodsId == goodsId) .WhereIf(beforeId > 0, x => x.Id < beforeId).OrderByDescending(x => x.Id).Limit(size).ToListAsync(); public Task MarkDeletedAsync(long[] ids) => Orm.Update() .Set(x => x.IsIndexDeleted, true) .Set(x => x.UpdatedTime, DateTime.Now) .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); }