增加违规记录页面
This commit is contained in:
1 parent
25d5b5d61c
commit
6252901a94
8 files changed
+884
-18
No files matched your search
@@ -164,14 +164,46 @@ namespace Work.Services
|
||||
}
|
||||
mappedItems.Add(new ReportGoodsViolationItem { GoodsId = goodsId, Reasons = item.Reasons });
|
||||
}
|
||||
var reports = mappedItems.GroupBy(item => item.GoodsId).Select(group => new ReportGoodsViolationItem
|
||||
{
|
||||
GoodsId = group.Key,
|
||||
Reasons = string.Join(";", group.Select(item => item.Reasons).Distinct())
|
||||
}).ToList();
|
||||
var reports = mappedItems.GroupBy(item => new { item.GoodsId, item.Reasons }).Select(group => group.First()).ToList();
|
||||
|
||||
return reports.Count == 0
|
||||
? new List<ReportGoodsViolationResult>() : await ReportViolations(reports);
|
||||
var results = new List<ReportGoodsViolationResult>();
|
||||
for (int offset = 0; offset < reports.Count && GlobalConfig.GlobalSettings.AutoReportViolationProducts; offset += 1000)
|
||||
{
|
||||
var batch = reports.Skip(offset).Take(1000).ToList();
|
||||
results.AddRange(await ReportViolations(batch));
|
||||
await RecordReportLogs(shop.ShopId, batch);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static async Task RecordReportLogs(string shopId, List<ReportGoodsViolationItem> batch)
|
||||
{
|
||||
if (!GlobalConfig.GlobalSettings.AutoReportViolationProducts) return;
|
||||
Dictionary<string, int> reasonIds;
|
||||
try
|
||||
{
|
||||
// 上报成功后读取字典,才能取得本批新增违规原因的编号。
|
||||
reasonIds = (await GetViolationReasons()).ToDictionary(reason => reason.Name, reason => reason.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogSave.AddFile($"店铺 {shopId} 已上报违规商品,读取日志原因字典失败:{ex.Message}");
|
||||
return;
|
||||
}
|
||||
var logs = new List<RecordViolationLogRequest>(batch.Count);
|
||||
foreach (var item in batch)
|
||||
{
|
||||
if (!reasonIds.TryGetValue(item.Reasons, out var reasonId))
|
||||
{
|
||||
LogSave.AddFile($"店铺 {shopId} 商品 {item.GoodsId} 找不到违规原因编号:{item.Reasons}");
|
||||
continue;
|
||||
}
|
||||
logs.Add(new RecordViolationLogRequest
|
||||
{
|
||||
ShopId = shopId, GoodsId = item.GoodsId, ReasonId = reasonId, Action = ViolationLogAction.Report
|
||||
});
|
||||
}
|
||||
await TryRecordLogs(logs);
|
||||
}
|
||||
|
||||
private static bool TryGetGoodsId(string code, out long goodsId)
|
||||
@@ -231,19 +263,24 @@ namespace Work.Services
|
||||
{
|
||||
var batch = goodsIds.Skip(offset).Take(50).ToList();
|
||||
var statuses = GetViolationStatus(batch);
|
||||
var violations = new HashSet<long>(statuses.Where(item => item != null && item.IsViolation && batch.Contains(item.GoodsId))
|
||||
.Select(item => item.GoodsId));
|
||||
var productIds = mappings.Where(item => violations.Contains(item.Value)).Select(item => item.Key).ToList();
|
||||
var violations = statuses.Where(item => item != null && item.IsViolation && batch.Contains(item.GoodsId))
|
||||
.ToDictionary(item => item.GoodsId);
|
||||
var productIds = mappings.Where(item => violations.ContainsKey(item.Value)).Select(item => item.Key).ToList();
|
||||
// 同一云商品可能对应多个微信商品,每次下架最多 50 个。
|
||||
for (var index = 0; index < productIds.Count; index += 50)
|
||||
{
|
||||
var ids = string.Join(",", productIds.Skip(index).Take(50));
|
||||
if (!GlobalConfig.GlobalSettings.AutoDelistViolationProducts)
|
||||
return;
|
||||
var page = productIds.Skip(index).Take(50).ToArray();
|
||||
var ids = string.Join(",", page);
|
||||
var response = JObject.Parse(WxProductApi.DownProd(shop, ids));
|
||||
if (response["code"]?.ToString() != "0")
|
||||
{
|
||||
throw new Exception($"微信商品 {ids} 下架失败:{response}");
|
||||
}
|
||||
LogSave.AddFile($"店铺 {shop.ShopId} 云端检测违规,已下架微信商品:{ids}");
|
||||
var downed = page.Select(id => mappings[id]).Distinct().Select(id => violations[id]);
|
||||
await RecordShopActionLogs(shop.ShopId, downed, ViolationLogAction.AutoOffShelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,6 +374,97 @@ namespace Work.Services
|
||||
return ret.Data ?? throw new Exception("批量查询商品违规状态返回数据为空。");
|
||||
}
|
||||
|
||||
/// <summary>逐原因记录店铺动作。日志请求失败不能改变剔除决定或中断后续下架。</summary>
|
||||
public static Task RecordShopActionLogs(string shopId, GoodsViolationStatus status, ViolationLogAction action)
|
||||
=> RecordShopActionLogs(shopId, new[] { status }, action);
|
||||
|
||||
private static async Task RecordShopActionLogs(string shopId, IEnumerable<GoodsViolationStatus> statuses, ViolationLogAction action)
|
||||
{
|
||||
if (!(action == ViolationLogAction.AutoListingExcluded && GlobalConfig.GlobalSettings.AutoExcludeViolationProductsOnUpload
|
||||
|| action == ViolationLogAction.AutoOffShelf && GlobalConfig.GlobalSettings.AutoDelistViolationProducts))
|
||||
return;
|
||||
var logs = new List<RecordViolationLogRequest>();
|
||||
foreach (var status in statuses)
|
||||
{
|
||||
if (status.ReasonIds == null || status.ReasonIds.Length == 0)
|
||||
{
|
||||
LogSave.AddFile($"店铺 {shopId} 商品 {status.GoodsId} 缺少违规原因编号,无法上传动作 {action} 日志。");
|
||||
continue;
|
||||
}
|
||||
logs.AddRange(status.ReasonIds.Select(id => new RecordViolationLogRequest
|
||||
{
|
||||
ShopId = shopId, GoodsId = status.GoodsId, ReasonId = id, Action = action
|
||||
}));
|
||||
}
|
||||
await TryRecordLogs(logs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task TryRecordLogs(List<RecordViolationLogRequest> logs)
|
||||
{
|
||||
if (logs.Count == 0) return;
|
||||
try
|
||||
{
|
||||
await RecordViolationLogs(logs).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogSave.AddFile($"店铺 {logs[0].ShopId} 动作 {logs[0].Action} 的 {logs.Count} 条日志上传失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>记录店铺已完成的违规上报、上货剔除或下架动作。</summary>
|
||||
public static Task RecordViolationLog(RecordViolationLogRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
return RecordViolationLogs(new[] { request });
|
||||
}
|
||||
|
||||
public static async Task RecordViolationLogs(IReadOnlyList<RecordViolationLogRequest> requests)
|
||||
{
|
||||
if (requests == null || requests.Count == 0 || requests.Any(item => item == null))
|
||||
throw new ArgumentException("请提供有效的违规日志。", nameof(requests));
|
||||
var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/RecordViolationLog", requests, timeOut: 5);
|
||||
var ret = JsonConvert.DeserializeObject<Result>(html) ?? throw new Exception("记录违规日志返回为空。");
|
||||
if (ret.Code != 200)
|
||||
throw new Exception(ret.Msg);
|
||||
}
|
||||
|
||||
/// <summary>读取日志筛选和显示使用的违规原因字典。</summary>
|
||||
public static async Task<List<ViolationReason>> GetViolationReasons()
|
||||
{
|
||||
using (var web = new System.Net.WebClient { Encoding = Encoding.UTF8 })
|
||||
{
|
||||
var html = await web.DownloadStringTaskAsync(ServerUrl + "/api/CloudGoods/GetViolationReasons");
|
||||
var ret = JsonConvert.DeserializeObject<Result<List<ViolationReason>>>(html);
|
||||
if (ret?.Code != 200 || ret.Data == null)
|
||||
throw new Exception(ret?.Msg ?? "查询违规类型返回为空。");
|
||||
return ret.Data;
|
||||
}
|
||||
}
|
||||
|
||||
public class ViolationReason
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool IsViolation { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>按日志编号倒序查询,下一页传入上一页最后一条日志的 Id。</summary>
|
||||
public static async Task<List<GoodsViolationLogEntity>> GetViolationLogs(string[] shopIds, long? goodsId = null,
|
||||
long beforeId = 0, int size = 50)
|
||||
{
|
||||
if (shopIds == null || shopIds.Length == 0 || shopIds.Any(string.IsNullOrWhiteSpace))
|
||||
throw new ArgumentException("请提供有效的店铺编号。", nameof(shopIds));
|
||||
var request = new GetViolationLogsRequest { ShopIds = shopIds, GoodsId = goodsId, BeforeId = beforeId, Size = size };
|
||||
var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/GetViolationLogs", request, timeOut: 5);
|
||||
var ret = JsonConvert.DeserializeObject<Result<List<GoodsViolationLogEntity>>>(html)
|
||||
?? throw new Exception("查询违规日志返回为空。");
|
||||
if (ret.Code != 200)
|
||||
throw new Exception(ret.Msg);
|
||||
return ret.Data ?? throw new Exception("查询违规日志返回数据为空。");
|
||||
}
|
||||
|
||||
#region user
|
||||
/// <summary>
|
||||
/// 搜索收藏
|
||||
@@ -408,6 +536,39 @@ namespace Work.Services
|
||||
return string.IsNullOrEmpty(BaseClass.ParentUserName) ? BaseClass.UserName : BaseClass.ParentUserName;
|
||||
}
|
||||
}
|
||||
public enum ViolationLogAction
|
||||
{
|
||||
Report = 1,
|
||||
AutoListingExcluded = 2,
|
||||
AutoOffShelf = 3
|
||||
}
|
||||
|
||||
public sealed class RecordViolationLogRequest
|
||||
{
|
||||
public string ShopId { get; set; } = string.Empty;
|
||||
public long GoodsId { get; set; }
|
||||
public int ReasonId { get; set; }
|
||||
public ViolationLogAction Action { get; set; }
|
||||
}
|
||||
|
||||
public sealed class GetViolationLogsRequest
|
||||
{
|
||||
public string[] ShopIds { get; set; } = Array.Empty<string>();
|
||||
public long? GoodsId { get; set; }
|
||||
public long BeforeId { get; set; }
|
||||
public int Size { get; set; } = 50;
|
||||
}
|
||||
|
||||
public class GoodsViolationLogEntity
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string? ShopId { get; set; }
|
||||
public long GoodsId { get; set; }
|
||||
public int ReasonId { get; set; }
|
||||
public ViolationLogAction Action { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 违规商品上报项。
|
||||
/// </summary>
|
||||
@@ -437,6 +598,9 @@ namespace Work.Services
|
||||
/// </summary>
|
||||
public class GoodsViolationStatus
|
||||
{
|
||||
[JsonProperty("reasonIds")]
|
||||
public int[] ReasonIds { get; set; }
|
||||
|
||||
[JsonProperty("goodsId")]
|
||||
public long GoodsId { get; set; }
|
||||
|
||||
|
||||
Reference in new issue
Block a user