增加
自动上报违规商品 上货自动检测违规商品 自动下架违规商品
This commit is contained in:
1 parent
a319af0e91
commit
d5e38dd518
9 files changed
+308
-19
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user