自动上报违规商品
上货自动检测违规商品
自动下架违规商品
This commit is contained in:
chfei committed 2026-10-30 14:14:14 +08:00
1 parent a319af0e91
commit d5e38dd518
9 files changed
+308 -19

No files matched your search

+10 -1
View File
@@ -133,5 +133,14 @@ namespace AllBase.Helper
/// 微信使用软件设置的默认上货地址
/// </summary>
public bool IsWxUseSoftwareDefaultAddress { get; set; } = true;
}
/// <summary>自动上报违规商品</summary>
public bool AutoReportViolationProducts { get; set; } = true;
/// <summary>上货自动剔除违规商品</summary>
public bool AutoExcludeViolationProductsOnUpload { get; set; } = true;
/// <summary>自动下架违规商品</summary>
public bool AutoDelistViolationProducts { get; set; } = true;
}
}
+3
View File
@@ -44,6 +44,9 @@
<Compile Update="InitForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="KJDForms\SystemForms\SystemViolationDetectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="PDDForms\AdminForms\RandomComProductForm.cs">
<SubType>Form</SubType>
</Compile>
+5 -2
View File
@@ -29,8 +29,9 @@
private void InitializeComponent()
{
System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("解除限制");
System.Windows.Forms.TreeNode violationNode = new System.Windows.Forms.TreeNode("违规检测");
System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("系统设置", new System.Windows.Forms.TreeNode[] {
treeNode1});
treeNode1, violationNode});
this.panel1 = new System.Windows.Forms.Panel();
this.panel3 = new System.Windows.Forms.Panel();
this.panel2 = new System.Windows.Forms.Panel();
@@ -103,6 +104,8 @@
this.tv.Name = "tv";
treeNode1.Name = "解除限制";
treeNode1.Text = "解除限制";
violationNode.Name = "违规检测";
violationNode.Text = "违规检测";
treeNode2.Name = "系统设置";
treeNode2.Text = "系统设置";
this.tv.Nodes.AddRange(new System.Windows.Forms.TreeNode[] {
@@ -162,4 +165,4 @@
private UserComponent.UcTreeView tv;
private System.Windows.Forms.SplitContainer splitContainer1;
}
}
}
@@ -15,6 +15,7 @@ namespace TemuKJD.KJDForms.SystemForms
public partial class SystemForm : BaseForm
{
public Lazy<SystemRemoveRestrictionsForm> systemRemoveRestrictionsForm = new Lazy<SystemRemoveRestrictionsForm>();
public Lazy<SystemViolationDetectionForm> systemViolationDetectionForm = new Lazy<SystemViolationDetectionForm>();
public SystemForm()
{
InitializeComponent();
@@ -46,6 +47,9 @@ namespace TemuKJD.KJDForms.SystemForms
case "解除限制":
SetForm(systemRemoveRestrictionsForm.Value);
break;
case "违规检测":
SetForm(systemViolationDetectionForm.Value);
break;
//case "商品管理":
// SetForm(shopProductForm.Value);
// break;
@@ -0,0 +1,61 @@
namespace TemuKJD.KJDForms.SystemForms
{
partial class SystemViolationDetectionForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.chkAutoReport = new TemuKJD.UserComponent.UcCheckBox(this.components);
this.chkAutoExcludeOnUpload = new TemuKJD.UserComponent.UcCheckBox(this.components);
this.chkAutoDelist = new TemuKJD.UserComponent.UcCheckBox(this.components);
this.SuspendLayout();
// chkAutoReport
this.chkAutoReport.AutoSize = true;
this.chkAutoReport.Location = new System.Drawing.Point(24, 24);
this.chkAutoReport.Name = "chkAutoReport";
this.chkAutoReport.TabIndex = 0;
this.chkAutoReport.Text = "自动上报违规商品";
this.chkAutoReport.UseVisualStyleBackColor = true;
// chkAutoExcludeOnUpload
this.chkAutoExcludeOnUpload.AutoSize = true;
this.chkAutoExcludeOnUpload.Location = new System.Drawing.Point(24, 64);
this.chkAutoExcludeOnUpload.Name = "chkAutoExcludeOnUpload";
this.chkAutoExcludeOnUpload.TabIndex = 1;
this.chkAutoExcludeOnUpload.Text = "上货自动剔除违规商品";
this.chkAutoExcludeOnUpload.UseVisualStyleBackColor = true;
// chkAutoDelist
this.chkAutoDelist.AutoSize = true;
this.chkAutoDelist.Location = new System.Drawing.Point(24, 104);
this.chkAutoDelist.Name = "chkAutoDelist";
this.chkAutoDelist.TabIndex = 2;
this.chkAutoDelist.Text = "自动下架违规商品";
this.chkAutoDelist.UseVisualStyleBackColor = true;
// SystemViolationDetectionForm
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.AutoScroll = true;
this.BackColor = System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.chkAutoReport);
this.Controls.Add(this.chkAutoExcludeOnUpload);
this.Controls.Add(this.chkAutoDelist);
this.Font = new System.Drawing.Font("微软雅黑", 9F);
this.Name = "SystemViolationDetectionForm";
this.Text = "违规检测";
this.ResumeLayout(false);
this.PerformLayout();
}
private UserComponent.UcCheckBox chkAutoReport;
private UserComponent.UcCheckBox chkAutoExcludeOnUpload;
private UserComponent.UcCheckBox chkAutoDelist;
}
}
@@ -0,0 +1,32 @@
using System;
using AllBase.Helper;
using TemuKJD.BaseForms;
namespace TemuKJD.KJDForms.SystemForms
{
public partial class SystemViolationDetectionForm : BaseForm
{
public SystemViolationDetectionForm()
{
InitializeComponent();
var settings = GlobalConfig.GlobalSettings;
chkAutoReport.Checked = settings.AutoReportViolationProducts;
chkAutoExcludeOnUpload.Checked = settings.AutoExcludeViolationProductsOnUpload;
chkAutoDelist.Checked = settings.AutoDelistViolationProducts;
chkAutoReport.CheckedChanged += Settings_CheckedChanged;
chkAutoExcludeOnUpload.CheckedChanged += Settings_CheckedChanged;
chkAutoDelist.CheckedChanged += Settings_CheckedChanged;
}
private void Settings_CheckedChanged(object sender, EventArgs e)
{
var settings = GlobalConfig.GlobalSettings;
settings.AutoReportViolationProducts = chkAutoReport.Checked;
settings.AutoExcludeViolationProductsOnUpload = chkAutoExcludeOnUpload.Checked;
settings.AutoDelistViolationProducts = chkAutoDelist.Checked;
StartUp.SaveGlobalSettings();
}
}
}
@@ -34,6 +34,10 @@ namespace TemuKJD.PDDForms.ProductForms
if (shop.LoginStatus != "登录成功")
continue;
// 开启自动下架时,后台检测当前店铺在售商品并下架违规商品。
if (GlobalConfig.GlobalSettings.AutoDelistViolationProducts)
CloudGoodsService.CheckAndOffShelfViolations(shop);
Thread.Sleep(Randomx.GetId(500, 1500));
WXProdViolationGetInfo info = new WXProdViolationGetInfo() { shopId = shop.ShopId, updateDay = DateTime.Now.ToString("yyMMdd") };
@@ -52,7 +56,9 @@ namespace TemuKJD.PDDForms.ProductForms
info.failReason = "";
var result = getShopiolationProd(shop);
CloudGoodsService.ReportViolationsFromJson(result);
// 开启自动上报时,后台解析店铺处罚记录并上报违规商品。
if (GlobalConfig.GlobalSettings.AutoReportViolationProducts)
CloudGoodsService.ReportViolationsFromJson(result, shop);
Thread.Sleep(Randomx.GetId(1000, 2000));
if (!result.Contains("\"code\":0"))
@@ -675,7 +675,7 @@ namespace TemuKJD.WXForms.ProductForms
item.Cells["colSelect"].Value = wxUploadRule.SelectedShopIds.Contains(shopId);
}
}
isInit = true;
}
@@ -1715,6 +1715,36 @@ namespace TemuKJD.WXForms.ProductForms
}));
}
// 开启上货自动剔除时,上传前检测违规状态;违规或检测失败均禁止上传。
if (GlobalConfig.GlobalSettings.AutoExcludeViolationProductsOnUpload)
{
try
{
if (!long.TryParse(dbProd.spu_id, out var goodsId) || goodsId <= 0)
throw new Exception("商品ID无效");
var statuses = CloudGoodsService.GetViolationStatus(new List<long> { goodsId }).GetAwaiter().GetResult();
var status = statuses.FirstOrDefault(item => item.GoodsId == goodsId);
if (status == null)
throw new Exception("未返回当前商品的违规状态");
if (status.IsViolation)
return "{\"success\":false,\"draftId\":0,\"status\":\"未上传\",\"errMsg\":\"当前商品违规,禁止上传\"}";
}
catch (Exception ex)
{
if (cell != null)
Invoke(new Action(() => cell.Value = "商品违规检测失败,禁止上传"));
return JsonConvert.SerializeObject(new
{
success = false,
draftId = 0,
status = "未上传",
errMsg = "商品违规检测失败,禁止上传:" + ex.Message
});
}
}
#region 裂变标题
if (BaseClass.UserAuthority.Contains("裂变调试") && chkFissionGoodsName.Checked)
{
+155 -14
View File
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Work.Api.Wx;
namespace Work.Services
{
@@ -48,28 +49,31 @@ namespace Work.Services
}
/// <summary>
/// 在后台解析微信处罚 JSON 并上报;无商品或处理失败时返回空列表,异常在内部记录。
/// 在后台解析微信处罚 JSON、匹配店铺商品编码并上报,异常在内部记录。
/// </summary>
public static void ReportViolationsFromJson(string json)
public static void ReportViolationsFromJson(string json, ShopLoginInfo shop)
{
try
{
Task.Run(() => ReportViolationsFromJsonCore(json)).ConfigureAwait(false);
}
catch (Exception ex)
_ = Task.Run(async () =>
{
try
{
LogSave.AddFile("解析并上报违规商品失败:" + ex.Message);
await ReportViolationsFromJsonCore(json, shop).ConfigureAwait(false);
}
catch
catch (Exception ex)
{
// 日志失败也不能影响调用方。
try
{
LogSave.AddFile("解析并上报违规商品失败:" + ex.Message);
}
catch
{
// 日志失败也不能影响调用方。
}
}
}
});
}
private static async Task<List<ReportGoodsViolationResult>> ReportViolationsFromJsonCore(string json)
private static async Task<List<ReportGoodsViolationResult>> ReportViolationsFromJsonCore(string json, ShopLoginInfo shop)
{
if (string.IsNullOrWhiteSpace(json))
{
@@ -96,6 +100,8 @@ namespace Work.Services
throw new ArgumentException("处罚数据缺少 record.items 数组。", nameof(json));
}
if (records.Count == 0)
return new List<ReportGoodsViolationResult>();
var items = new List<ReportGoodsViolationItem>();
foreach (var token in records)
{
@@ -138,12 +144,147 @@ namespace Work.Services
}
}
var reports = items.GroupBy(item => item.GoodsId).Select(group => new ReportGoodsViolationItem
if (items.Count == 0)
{
return new List<ReportGoodsViolationResult>();
}
var productCodes = GetWxProductCodes(shop, items.Select(item => item.GoodsId));
var mappedItems = new List<ReportGoodsViolationItem>();
foreach (var item in items)
{
if (!productCodes.TryGetValue(item.GoodsId, out var code) || string.IsNullOrWhiteSpace(code))
{
LogSave.AddFile($"跳过违规商品 {item.GoodsId}:未找到对应的 spuCode。");
continue;
}
if (!TryGetGoodsId(code, out var goodsId))
{
LogSave.AddFile($"跳过违规商品 {item.GoodsId}:spuCode 无法转换为有效商品编号。");
continue;
}
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();
return reports.Count == 0 ? new List<ReportGoodsViolationResult>() : await ReportViolations(reports);
return reports.Count == 0
? new List<ReportGoodsViolationResult>() : await ReportViolations(reports);
}
private static bool TryGetGoodsId(string code, out long goodsId)
{
code = code?.Trim() ?? string.Empty;
if (code.StartsWith("P1-", StringComparison.Ordinal))
{
code = code.Substring(3);
}
else if (code.StartsWith("EC1", StringComparison.Ordinal) && code.EndsWith("A", StringComparison.Ordinal))
{
code = code.Substring(3, code.Length - 4);
}
return long.TryParse(code, out goodsId) && goodsId > 0;
}
/// <summary>
/// 后台检查当前店铺所有在售商品,云端确认违规后立即下架;异常仅记录日志。
/// </summary>
public static void CheckAndOffShelfViolations(ShopLoginInfo shop)
{
_ = Task.Run(async () =>
{
try
{
await CheckAndOffShelfViolationsCore(shop).ConfigureAwait(false);
}
catch (Exception ex)
{
try
{
LogSave.AddFile($"店铺 {shop?.ShopId} 在售商品违规检查失败:{ex.Message}");
}
catch { }
}
});
}
private static async Task CheckAndOffShelfViolationsCore(ShopLoginInfo shop)
{
// 先读取完整列表,避免下架改变分页位置而漏查后面的商品。
var products = GetWxProductCodes(shop, status: 5);
var mappings = new Dictionary<long, long>();
foreach (var product in products)
{
if (TryGetGoodsId(product.Value, out var goodsId))
{
mappings.Add(product.Key, goodsId);
}
else
{
LogSave.AddFile($"店铺 {shop.ShopId} 跳过商品 {product.Key}:spuCode 无法转换为有效商品编号。");
}
}
var goodsIds = mappings.Values.Distinct().ToList();
for (var offset = 0; offset < goodsIds.Count; offset += 50)
{
var batch = goodsIds.Skip(offset).Take(50).ToList();
var statuses = await GetViolationStatus(batch).ConfigureAwait(false);
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();
// 同一云商品可能对应多个微信商品,每次下架最多 50 个。
for (var index = 0; index < productIds.Count; index += 50)
{
var ids = string.Join(",", productIds.Skip(index).Take(50));
var response = JObject.Parse(WxProductApi.DownProd(shop, ids));
if (response["code"]?.ToString() != "0")
{
throw new Exception($"微信商品 {ids} 下架失败:{response}");
}
LogSave.AddFile($"店铺 {shop.ShopId} 云端检测违规,已下架微信商品:{ids}");
}
}
}
private static Dictionary<long, string> GetWxProductCodes(ShopLoginInfo shop, IEnumerable<long> productIds = null, int status = -1)
{
if (shop == null)
{
throw new ArgumentNullException(nameof(shop));
}
var codes = new Dictionary<long, string>();
var prodIds = productIds == null ? null : string.Join(",", productIds.Distinct());
long received = 0;
for (var page = 1; ; page++)
{
var response = JObject.Parse(WxProductApi.SearchProduct(shop, page, pageSize: 50, prodIds: prodIds, status: status));
if (response["ret"]?.ToString() != "0" || response["success"]?.Type != JTokenType.Boolean
|| !(bool)response["success"] || !(response["productList"] is JArray products)
|| !long.TryParse(response["totalNum"]?.ToString(), out var total) || total < 0)
{
throw new Exception($"获取微信商品列表第 {page} 页失败。");
}
var previousCount = codes.Count;
foreach (var product in products)
{
if (!(product is JObject item) || !long.TryParse(item["productId"]?.ToString(), out var id) || id <= 0)
{
throw new Exception("微信商品列表包含无效的 productId。");
}
codes[id] = item["spuCode"]?.Type == JTokenType.String ? (string)item["spuCode"] : null;
}
received += products.Count;
if (received >= total)
{
return codes;
}
if (codes.Count == previousCount)
{
throw new Exception("微信商品列表分页未返回新商品,无法获取全部搜索结果。");
}
}
}
/// <summary>