Files

142 lines
6.9 KiB
C#

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<GoodsViolationEntity>(fsql), IScopedDependency
{
public Task<List<ViolationReasonDto>> GetReasonsAsync() => Orm.Select<ViolationReasonEntity>()
.OrderBy(x => x.Id).ToListAsync(x => new ViolationReasonDto
{
Id = x.Id, Name = x.Name,
IsViolation = x.IsViolation
});
public Task<List<long>> GetViolationIdsAsync(long[] ids) => Select
.Where(x => ids.Contains(x.GoodsId)).Distinct().ToListAsync(x => x.GoodsId);
public async Task<ILookup<long, int>> 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<List<long>> 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<int> RemoveAndScheduleRestoreAsync(long[] ids)
{
using var unit = Orm.CreateUnitOfWork();
var violations = unit.GetRepository<GoodsViolationEntity>();
var goods = unit.GetRepository<GoodsEntity>();
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<List<GoodsEntity>> GetRestorableGoodsAsync(long[] ids) => Orm.Select<GoodsEntity>()
.Where(x => ids.Contains(x.GoodsId) && x.Status == 0).ToListAsync();
public Task<int> MarkRestoredAsync(long[] ids) => Orm.Update<GoodsEntity>()
.Set(x => x.IsSegmented, (byte)1)
.Where(x => ids.Contains(x.GoodsId) && x.Status == 0).ExecuteAffrowsAsync();
// 调用方持有 GoodsIndexLock,事务内合并原因,避免并发覆盖。
public async Task<long[]> MergeAsync(Dictionary<long, string[]> items)
{
using var unit = Orm.CreateUnitOfWork();
var repository = unit.GetRepository<GoodsViolationEntity>();
var reasonRepository = unit.GetRepository<ViolationReasonEntity>();
var dictionary = new Dictionary<string, ViolationReasonEntity>(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<long>();
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<long> CountReasonsAsync(uint[] ids) => Orm.Select<ViolationReasonEntity>()
.Where(x => ids.Contains(x.Id)).CountAsync();
public async Task InsertLogsAsync(GoodsViolationLogEntity[] logs)
{
// 即使 ORM 拆分 INSERT,各批次也在同一事务内提交。
using var unit = Orm.CreateUnitOfWork();
await unit.GetRepository<GoodsViolationLogEntity>().InsertAsync(logs);
unit.Commit();
}
// 游标按自增主键倒序翻页,同一时间的事件也有确定顺序。
public Task<List<GoodsViolationLogEntity>> GetLogsAsync(string[] shopIds, long? goodsId, long beforeId, int size)
=> Orm.Select<GoodsViolationLogEntity>().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<int> MarkDeletedAsync(long[] ids) => Orm.Update<GoodsViolationEntity>()
.Set(x => x.IsIndexDeleted, true)
.Set(x => x.UpdatedTime, DateTime.Now)
.Where(x => ids.Contains(x.GoodsId)).ExecuteAffrowsAsync();
public Task<List<long>> 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<List<long>> 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);
}