Merge branch 'master' of http://git.ts.e57.cn/chfei/KJD
This commit is contained in:
commit
f9a1af6073
8 files changed
+884
-18
No files matched your search
@@ -10,7 +10,7 @@
|
||||
<ErrorReportUrlHistory />
|
||||
<FallbackCulture>zh-CN</FallbackCulture>
|
||||
<VerifyUploadedFiles>false</VerifyUploadedFiles>
|
||||
<ActiveDebugProfile>Production</ActiveDebugProfile>
|
||||
<ActiveDebugProfile>Development</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
@@ -182,6 +182,9 @@
|
||||
<Compile Update="WXForms\ProductForms\WXFinderSquareForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="WXForms\ProductForms\WXViolationLogsForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="WXForms\ShopForms\WXOemPointsTopUpListForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
||||
@@ -10,18 +10,29 @@ namespace TemuKJD.KJDForms.SystemForms
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var settings = GlobalConfig.GlobalSettings;
|
||||
chkAutoReport.Checked = settings.AutoReportViolationProducts;
|
||||
chkAutoExcludeOnUpload.Checked = settings.AutoExcludeViolationProductsOnUpload;
|
||||
chkAutoDelist.Checked = settings.AutoDelistViolationProducts;
|
||||
|
||||
VisibleChanged += (s, e) => { if (Visible) LoadSettings(); };
|
||||
LoadSettings();
|
||||
chkAutoReport.CheckedChanged += Settings_CheckedChanged;
|
||||
chkAutoExcludeOnUpload.CheckedChanged += Settings_CheckedChanged;
|
||||
chkAutoDelist.CheckedChanged += Settings_CheckedChanged;
|
||||
}
|
||||
|
||||
private bool loading;
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
loading = true;
|
||||
var settings = GlobalConfig.GlobalSettings;
|
||||
chkAutoReport.Checked = settings.AutoReportViolationProducts;
|
||||
chkAutoExcludeOnUpload.Checked = settings.AutoExcludeViolationProductsOnUpload;
|
||||
chkAutoDelist.Checked = settings.AutoDelistViolationProducts;
|
||||
|
||||
loading = false;
|
||||
}
|
||||
|
||||
private void Settings_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (loading) return;
|
||||
var settings = GlobalConfig.GlobalSettings;
|
||||
settings.AutoReportViolationProducts = chkAutoReport.Checked;
|
||||
settings.AutoExcludeViolationProductsOnUpload = chkAutoExcludeOnUpload.Checked;
|
||||
|
||||
@@ -1718,7 +1718,8 @@ namespace TemuKJD.WXForms.ProductForms
|
||||
// 开启上货自动剔除时,上传前检测违规状态;仅服务端明确返回违规时禁止上传。
|
||||
if (GlobalConfig.GlobalSettings.AutoExcludeViolationProductsOnUpload)
|
||||
{
|
||||
Invoke(new Action(() => cell.Value = "商品违规检测..."));
|
||||
if (cell != null)
|
||||
Invoke(new Action(() => cell.Value = "商品违规检测..."));
|
||||
try
|
||||
{
|
||||
long goodsId = 0;
|
||||
@@ -1731,7 +1732,21 @@ namespace TemuKJD.WXForms.ProductForms
|
||||
if (status == null)
|
||||
throw new Exception("未返回当前商品的违规状态");
|
||||
if (status.IsViolation)
|
||||
{
|
||||
var shopId = shop.ShopId;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await CloudGoodsService.RecordShopActionLogs(shopId, status, ViolationLogAction.AutoListingExcluded);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("剔除违规商品日志记录失败:" + ex.Message);
|
||||
}
|
||||
});
|
||||
return "{\"success\":false,\"draftId\":0,\"status\":\"未上传\",\"errMsg\":\"当前商品违规,禁止上传\"}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -6329,6 +6344,37 @@ namespace TemuKJD.WXForms.ProductForms
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图片宽高均达到指定尺寸时返回 true;尺寸不足或无效输入返回 false。
|
||||
/// </summary>
|
||||
public bool IsBase64ImageSizeValid(string base64Image, int width, int height)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(base64Image) || width <= 0 || height <= 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(base64Image)))
|
||||
using (var image = System.Drawing.Image.FromStream(stream))
|
||||
{
|
||||
return image.Width >= width && image.Height >= height;
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (OutOfMemoryException)
|
||||
{
|
||||
// GDI+ 对不支持或损坏的图片也可能抛出此异常。
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string UploadImg(ShopLoginInfo shop, string img_url, DataGridViewCell cell = null, bool randomBlock = false)
|
||||
{
|
||||
string getImgError;
|
||||
|
||||
@@ -35,9 +35,11 @@ namespace TemuKJD.WXForms.ProductForms
|
||||
public Lazy<WXProdNoCheckEditAddressForm> prodNoCheckEditForm = new Lazy<WXProdNoCheckEditAddressForm>();
|
||||
public Lazy<CloudGoodsForm> cloudgoodsForm = new Lazy<CloudGoodsForm>();
|
||||
public Lazy<CloudGoodsFavoriteForm> cloudgoodsFavoriteForm = new Lazy<CloudGoodsFavoriteForm>();
|
||||
private readonly Lazy<WXViolationLogsForm> violationLogsForm = new Lazy<WXViolationLogsForm>();
|
||||
public WXProductForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
tv.Nodes[0].Nodes.Insert(1, "违规记录", "违规记录");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -102,6 +104,9 @@ namespace TemuKJD.WXForms.ProductForms
|
||||
case "云端数据":
|
||||
SetForm(cloudgoodsForm.Value);
|
||||
break;
|
||||
case "违规记录":
|
||||
SetForm(violationLogsForm.Value);
|
||||
break;
|
||||
case "我的商品库":
|
||||
SetForm(userCollectAuthProdListForm.Value);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using DevComponents.DotNetBar;
|
||||
using TemuKJD.UserComponent;
|
||||
|
||||
namespace TemuKJD.WXForms.ProductForms
|
||||
{
|
||||
partial class WXViolationLogsForm
|
||||
{
|
||||
private TableLayoutPanel layout;
|
||||
private FlowLayoutPanel settings;
|
||||
private FlowLayoutPanel filters;
|
||||
private CheckBox report;
|
||||
private CheckBox exclude;
|
||||
private CheckBox delist;
|
||||
private PanelEx btnSave;
|
||||
private Label shopLabel;
|
||||
private ComboBox shop;
|
||||
private Label reasonLabel;
|
||||
private ComboBox reason;
|
||||
private Label actionLabel;
|
||||
private ComboBox action;
|
||||
private PanelEx btnQuery;
|
||||
private UserControls.UcSplitPage pagination;
|
||||
private Label message;
|
||||
private DataGridViewEx grid;
|
||||
private DataGridViewTextBoxColumn colAction;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
this.layout = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.settings = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.report = new System.Windows.Forms.CheckBox();
|
||||
this.exclude = new System.Windows.Forms.CheckBox();
|
||||
this.delist = new System.Windows.Forms.CheckBox();
|
||||
this.btnSave = new DevComponents.DotNetBar.PanelEx();
|
||||
this.filters = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.shopLabel = new System.Windows.Forms.Label();
|
||||
this.shop = new System.Windows.Forms.ComboBox();
|
||||
this.reasonLabel = new System.Windows.Forms.Label();
|
||||
this.reason = new System.Windows.Forms.ComboBox();
|
||||
this.actionLabel = new System.Windows.Forms.Label();
|
||||
this.action = new System.Windows.Forms.ComboBox();
|
||||
this.btnQuery = new DevComponents.DotNetBar.PanelEx();
|
||||
this.pagination = new UserControls.UcSplitPage();
|
||||
this.message = new System.Windows.Forms.Label();
|
||||
this.grid = new TemuKJD.UserComponent.DataGridViewEx();
|
||||
this.ShopId = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.GoodsId = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.ReasonId = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.colAction = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.CreatedTime = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.layout.SuspendLayout();
|
||||
this.settings.SuspendLayout();
|
||||
this.filters.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.grid)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// layout
|
||||
//
|
||||
this.layout.ColumnCount = 1;
|
||||
this.layout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.layout.Controls.Add(this.settings, 0, 0);
|
||||
this.layout.Controls.Add(this.filters, 0, 1);
|
||||
this.layout.Controls.Add(this.grid, 0, 2);
|
||||
this.layout.Controls.Add(this.pagination, 0, 3);
|
||||
this.layout.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.layout.Location = new System.Drawing.Point(0, 0);
|
||||
this.layout.Name = "layout";
|
||||
this.layout.RowCount = 4;
|
||||
this.layout.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.layout.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.layout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
this.layout.Size = new System.Drawing.Size(1100, 530);
|
||||
this.layout.TabIndex = 0;
|
||||
//
|
||||
// settings
|
||||
//
|
||||
this.settings.AutoSize = true;
|
||||
this.settings.Controls.Add(this.report);
|
||||
this.settings.Controls.Add(this.exclude);
|
||||
this.settings.Controls.Add(this.delist);
|
||||
this.settings.Controls.Add(this.btnSave);
|
||||
this.settings.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.settings.Location = new System.Drawing.Point(3, 3);
|
||||
this.settings.Name = "settings";
|
||||
this.settings.Padding = new System.Windows.Forms.Padding(6, 4, 6, 4);
|
||||
this.settings.Size = new System.Drawing.Size(1094, 39);
|
||||
this.settings.TabIndex = 0;
|
||||
//
|
||||
// report
|
||||
//
|
||||
this.report.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.report.AutoSize = true;
|
||||
this.report.Location = new System.Drawing.Point(9, 9);
|
||||
this.report.Name = "report";
|
||||
this.report.Size = new System.Drawing.Size(123, 21);
|
||||
this.report.TabIndex = 0;
|
||||
this.report.Text = "自动上报违规商品";
|
||||
//
|
||||
// exclude
|
||||
//
|
||||
this.exclude.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.exclude.AutoSize = true;
|
||||
this.exclude.Location = new System.Drawing.Point(138, 9);
|
||||
this.exclude.Name = "exclude";
|
||||
this.exclude.Size = new System.Drawing.Size(147, 21);
|
||||
this.exclude.TabIndex = 1;
|
||||
this.exclude.Text = "上货自动剔除违规商品";
|
||||
//
|
||||
// delist
|
||||
//
|
||||
this.delist.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.delist.AutoSize = true;
|
||||
this.delist.Location = new System.Drawing.Point(291, 9);
|
||||
this.delist.Name = "delist";
|
||||
this.delist.Size = new System.Drawing.Size(123, 21);
|
||||
this.delist.TabIndex = 2;
|
||||
this.delist.Text = "自动下架违规商品";
|
||||
//
|
||||
// btnSave
|
||||
//
|
||||
this.btnSave.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;
|
||||
this.btnSave.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;
|
||||
this.btnSave.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.btnSave.DisabledBackColor = System.Drawing.Color.Empty;
|
||||
this.btnSave.Location = new System.Drawing.Point(420, 7);
|
||||
this.btnSave.Name = "btnSave";
|
||||
this.btnSave.Size = new System.Drawing.Size(80, 25);
|
||||
this.btnSave.Style.Alignment = System.Drawing.StringAlignment.Center;
|
||||
this.btnSave.Style.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
|
||||
this.btnSave.Style.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
|
||||
this.btnSave.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.None;
|
||||
this.btnSave.Style.ForeColor.Color = System.Drawing.Color.White;
|
||||
this.btnSave.StyleMouseOver.Alignment = System.Drawing.StringAlignment.Center;
|
||||
this.btnSave.StyleMouseOver.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255)))));
|
||||
this.btnSave.StyleMouseOver.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255)))));
|
||||
this.btnSave.TabIndex = 4;
|
||||
this.btnSave.Text = "保存配置";
|
||||
this.btnSave.Click += new System.EventHandler(this.Save_Click);
|
||||
//
|
||||
// filters
|
||||
//
|
||||
this.filters.AutoSize = true;
|
||||
this.filters.Controls.Add(this.shopLabel);
|
||||
this.filters.Controls.Add(this.shop);
|
||||
this.filters.Controls.Add(this.reasonLabel);
|
||||
this.filters.Controls.Add(this.reason);
|
||||
this.filters.Controls.Add(this.actionLabel);
|
||||
this.filters.Controls.Add(this.action);
|
||||
this.filters.Controls.Add(this.btnQuery);
|
||||
|
||||
this.filters.Controls.Add(this.message);
|
||||
this.filters.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.filters.Location = new System.Drawing.Point(3, 48);
|
||||
this.filters.Name = "filters";
|
||||
this.filters.Padding = new System.Windows.Forms.Padding(6, 4, 6, 4);
|
||||
this.filters.Size = new System.Drawing.Size(1094, 39);
|
||||
this.filters.TabIndex = 1;
|
||||
//
|
||||
// shopLabel
|
||||
//
|
||||
this.shopLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.shopLabel.AutoSize = true;
|
||||
this.shopLabel.Location = new System.Drawing.Point(9, 11);
|
||||
this.shopLabel.Name = "shopLabel";
|
||||
this.shopLabel.Size = new System.Drawing.Size(32, 17);
|
||||
this.shopLabel.TabIndex = 0;
|
||||
this.shopLabel.Text = "店铺";
|
||||
//
|
||||
// shop
|
||||
//
|
||||
this.shop.DisplayMember = "Value";
|
||||
this.shop.SelectionChangeCommitted += this.Query_Click;
|
||||
this.shop.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.shop.Location = new System.Drawing.Point(47, 7);
|
||||
this.shop.Name = "shop";
|
||||
this.shop.Size = new System.Drawing.Size(150, 25);
|
||||
this.shop.TabIndex = 1;
|
||||
this.shop.ValueMember = "Key";
|
||||
//
|
||||
// reasonLabel
|
||||
//
|
||||
this.reasonLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.reasonLabel.AutoSize = true;
|
||||
this.reasonLabel.Location = new System.Drawing.Point(203, 11);
|
||||
this.reasonLabel.Name = "reasonLabel";
|
||||
this.reasonLabel.Size = new System.Drawing.Size(56, 17);
|
||||
this.reasonLabel.TabIndex = 2;
|
||||
this.reasonLabel.Text = "违规类型";
|
||||
//
|
||||
// reason
|
||||
//
|
||||
this.reason.DisplayMember = "Value";
|
||||
this.reason.SelectionChangeCommitted += this.Query_Click;
|
||||
this.reason.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.reason.Location = new System.Drawing.Point(265, 7);
|
||||
this.reason.Name = "reason";
|
||||
this.reason.Size = new System.Drawing.Size(150, 25);
|
||||
this.reason.TabIndex = 3;
|
||||
this.reason.ValueMember = "Key";
|
||||
//
|
||||
// actionLabel
|
||||
//
|
||||
this.actionLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.actionLabel.AutoSize = true;
|
||||
this.actionLabel.Location = new System.Drawing.Point(421, 11);
|
||||
this.actionLabel.Name = "actionLabel";
|
||||
this.actionLabel.Size = new System.Drawing.Size(56, 17);
|
||||
this.actionLabel.TabIndex = 4;
|
||||
this.actionLabel.Text = "日志动作";
|
||||
//
|
||||
// action
|
||||
//
|
||||
this.action.SelectionChangeCommitted += this.Query_Click;
|
||||
this.action.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.action.Items.AddRange(new object[] {
|
||||
"全部动作",
|
||||
"违规上报",
|
||||
"上货剔除",
|
||||
"违规下架"});
|
||||
this.action.Location = new System.Drawing.Point(483, 7);
|
||||
this.action.Name = "action";
|
||||
this.action.Size = new System.Drawing.Size(150, 25);
|
||||
this.action.TabIndex = 5;
|
||||
//
|
||||
// btnQuery
|
||||
//
|
||||
this.btnQuery.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;
|
||||
this.btnQuery.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;
|
||||
this.btnQuery.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.btnQuery.DisabledBackColor = System.Drawing.Color.Empty;
|
||||
this.btnQuery.Location = new System.Drawing.Point(639, 7);
|
||||
this.btnQuery.Name = "btnQuery";
|
||||
this.btnQuery.Size = new System.Drawing.Size(58, 25);
|
||||
this.btnQuery.Style.Alignment = System.Drawing.StringAlignment.Center;
|
||||
this.btnQuery.Style.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
|
||||
this.btnQuery.Style.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
|
||||
this.btnQuery.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.None;
|
||||
this.btnQuery.Style.ForeColor.Color = System.Drawing.Color.White;
|
||||
this.btnQuery.StyleMouseOver.Alignment = System.Drawing.StringAlignment.Center;
|
||||
this.btnQuery.StyleMouseOver.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255)))));
|
||||
this.btnQuery.StyleMouseOver.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255)))));
|
||||
this.btnQuery.TabIndex = 6;
|
||||
this.btnQuery.Text = "查询";
|
||||
this.btnQuery.Click += new System.EventHandler(this.Query_Click);
|
||||
//
|
||||
// pagination
|
||||
//
|
||||
this.pagination.Dock = DockStyle.Right;
|
||||
this.pagination.Name = "pagination";
|
||||
this.pagination.Size = new Size(462, 25);
|
||||
this.pagination.PageSize = 50;
|
||||
this.pagination.IsShowPageSize = false;
|
||||
this.pagination.IsShowTotalCount = false;
|
||||
this.pagination.PageChange += this.Pagination_PageChange;
|
||||
//
|
||||
// message
|
||||
//
|
||||
this.message.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.message.AutoSize = true;
|
||||
this.message.Location = new System.Drawing.Point(789, 11);
|
||||
this.message.Name = "message";
|
||||
this.message.Size = new System.Drawing.Size(0, 17);
|
||||
this.message.TabIndex = 14;
|
||||
//
|
||||
// grid
|
||||
//
|
||||
this.grid.AllowUserToAddRows = false;
|
||||
this.grid.AllowUserToDeleteRows = false;
|
||||
dataGridViewCellStyle1.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.grid.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
|
||||
this.grid.AutoRowNumber = true;
|
||||
this.grid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
|
||||
this.grid.BackgroundColor = System.Drawing.Color.White;
|
||||
this.grid.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
|
||||
this.grid.Columns.AddRange(new DataGridViewColumn[] { this.ShopId, this.GoodsId, this.ReasonId, this.colAction, this.CreatedTime });
|
||||
this.grid.CellFormatting += new DataGridViewCellFormattingEventHandler(this.Grid_CellFormatting);
|
||||
this.grid.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.grid.Location = new System.Drawing.Point(3, 93);
|
||||
this.grid.MultiSelectCheckBox = true;
|
||||
this.grid.Name = "grid";
|
||||
this.grid.ReadOnly = true;
|
||||
this.grid.RowHeadersVisible = true;
|
||||
this.grid.Size = new System.Drawing.Size(1094, 434);
|
||||
this.grid.TabIndex = 2;
|
||||
//
|
||||
//
|
||||
// ShopId
|
||||
//
|
||||
this.ShopId.DataPropertyName = "ShopId";
|
||||
this.ShopId.HeaderText = "店铺";
|
||||
this.ShopId.Name = "ShopId";
|
||||
this.ShopId.Width = 38;
|
||||
//
|
||||
// GoodsId
|
||||
//
|
||||
this.GoodsId.DataPropertyName = "GoodsId";
|
||||
this.GoodsId.HeaderText = "商品编号";
|
||||
this.GoodsId.Name = "GoodsId";
|
||||
this.GoodsId.Width = 48;
|
||||
//
|
||||
// ReasonId
|
||||
//
|
||||
this.ReasonId.DataPropertyName = "ReasonId";
|
||||
this.ReasonId.HeaderText = "违规类型";
|
||||
this.ReasonId.Name = "ReasonId";
|
||||
this.ReasonId.Width = 64;
|
||||
//
|
||||
// colAction
|
||||
//
|
||||
this.colAction.DataPropertyName = "Action";
|
||||
this.colAction.HeaderText = "日志动作";
|
||||
this.colAction.Name = "colAction";
|
||||
this.colAction.Width = 96;
|
||||
//
|
||||
// CreatedTime
|
||||
//
|
||||
this.CreatedTime.DataPropertyName = "CreatedTime";
|
||||
dataGridViewCellStyle2.Format = "yyyy-MM-dd HH:mm:ss";
|
||||
this.CreatedTime.DefaultCellStyle = dataGridViewCellStyle2;
|
||||
this.CreatedTime.HeaderText = "记录时间";
|
||||
this.CreatedTime.Name = "CreatedTime";
|
||||
this.CreatedTime.Width = 191;
|
||||
//
|
||||
// WXViolationLogsForm
|
||||
//
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
this.BackColor = System.Drawing.Color.White;
|
||||
this.ClientSize = new System.Drawing.Size(1100, 530);
|
||||
this.Controls.Add(this.layout);
|
||||
this.Font = new System.Drawing.Font("微软雅黑", 9F);
|
||||
this.KeyPreview = true;
|
||||
this.Name = "WXViolationLogsForm";
|
||||
this.Text = "违规日志";
|
||||
this.Shown += new System.EventHandler(this.Query_Click);
|
||||
this.VisibleChanged += new System.EventHandler(this.Form_VisibleChanged);
|
||||
this.layout.ResumeLayout(false);
|
||||
this.layout.PerformLayout();
|
||||
this.settings.ResumeLayout(false);
|
||||
this.settings.PerformLayout();
|
||||
this.filters.ResumeLayout(false);
|
||||
this.filters.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.grid)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
private DataGridViewTextBoxColumn ShopId;
|
||||
private DataGridViewTextBoxColumn GoodsId;
|
||||
private DataGridViewTextBoxColumn ReasonId;
|
||||
private DataGridViewTextBoxColumn CreatedTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using AllBase;
|
||||
using AllBase.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using TemuKJD.BaseForms;
|
||||
using Work.Services;
|
||||
|
||||
namespace TemuKJD.WXForms.ProductForms
|
||||
{
|
||||
public partial class WXViolationLogsForm : BaseForm
|
||||
{
|
||||
private readonly BindingList<GoodsViolationLogEntity> rows = new BindingList<GoodsViolationLogEntity>();
|
||||
private Dictionary<int, string> reasons;
|
||||
private Dictionary<string, string> shops;
|
||||
private const int PageSize = 50;
|
||||
private readonly List<long> pageCursors = new List<long> { 0 };
|
||||
private int currentPage;
|
||||
|
||||
public WXViolationLogsForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
action.SelectedIndex = 0;
|
||||
grid.AutoGenerateColumns = false;
|
||||
grid.DataSource = rows;
|
||||
}
|
||||
|
||||
private void Save_Click(object sender, EventArgs e)
|
||||
{
|
||||
var config = GlobalConfig.GlobalSettings;
|
||||
config.AutoReportViolationProducts = report.Checked;
|
||||
config.AutoExcludeViolationProductsOnUpload = exclude.Checked;
|
||||
config.AutoDelistViolationProducts = delist.Checked;
|
||||
|
||||
StartUp.SaveGlobalSettings();
|
||||
message.Text = "配置已保存";
|
||||
}
|
||||
|
||||
private void Form_VisibleChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!Visible) return;
|
||||
var config = GlobalConfig.GlobalSettings;
|
||||
report.Checked = config.AutoReportViolationProducts;
|
||||
exclude.Checked = config.AutoExcludeViolationProductsOnUpload;
|
||||
delist.Checked = config.AutoDelistViolationProducts;
|
||||
var selected = shop.SelectedValue as string;
|
||||
shops = GlobalConfig.ShopLoginList.Where(x => !string.IsNullOrWhiteSpace(x.ShopId))
|
||||
.GroupBy(x => x.ShopId).ToDictionary(g => g.Key, g => string.IsNullOrWhiteSpace(g.First().ShopAccount)
|
||||
? "未知店铺" : g.First().ShopAccount);
|
||||
shop.DataSource = new[] { new KeyValuePair<string, string>("", "全部店铺") }.Concat(shops).ToList();
|
||||
if (selected != null && shops.ContainsKey(selected)) shop.SelectedValue = selected;
|
||||
}
|
||||
|
||||
private async void Query_Click(object sender, EventArgs e) => await QueryAsync(0, reset: true);
|
||||
|
||||
private async void Pagination_PageChange(object sender)
|
||||
{
|
||||
int targetPage = pagination.CurrentPage - 1;
|
||||
if (targetPage != currentPage) await QueryAsync(targetPage);
|
||||
}
|
||||
|
||||
private void Grid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
||||
{
|
||||
if (e.Value == null) return;
|
||||
switch (grid.Columns[e.ColumnIndex].DataPropertyName)
|
||||
{
|
||||
case nameof(GoodsViolationLogEntity.ShopId):
|
||||
e.Value = shops.TryGetValue((string)e.Value, out var name) ? name : "未知店铺";
|
||||
e.FormattingApplied = true;
|
||||
break;
|
||||
case nameof(GoodsViolationLogEntity.ReasonId):
|
||||
e.Value = reasons.TryGetValue((int)e.Value, out var label) ? label : "未知违规类型";
|
||||
e.FormattingApplied = true;
|
||||
break;
|
||||
case nameof(GoodsViolationLogEntity.Action):
|
||||
var index = (int)(ViolationLogAction)e.Value;
|
||||
e.Value = index > 0 && index < action.Items.Count ? action.Items[index] : "未知日志动作";
|
||||
e.FormattingApplied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task QueryAsync(int targetPage, bool reset = false)
|
||||
{
|
||||
if (!filters.Enabled) return;
|
||||
filters.Enabled = false;
|
||||
pagination.Enabled = false;
|
||||
try
|
||||
{
|
||||
if (reset)
|
||||
{
|
||||
currentPage = 0;
|
||||
pageCursors.Clear();
|
||||
pageCursors.Add(0);
|
||||
rows.Clear();
|
||||
|
||||
}
|
||||
message.Text = "正在查询…";
|
||||
if (reasons == null)
|
||||
{
|
||||
var types = await CloudGoodsService.GetViolationReasons();
|
||||
if (IsDisposed) return;
|
||||
reasons = types.ToDictionary(type => type.Id, type => type.Name);
|
||||
reason.DataSource = new[] { new KeyValuePair<int, string>(0, "全部类型") }
|
||||
.Concat(types.Where(type => type.IsViolation).Select(type => new KeyValuePair<int, string>(type.Id, type.Name))).ToList();
|
||||
}
|
||||
var selected = shop.SelectedValue as string;
|
||||
var ids = string.IsNullOrEmpty(selected) ? shops.Keys.ToArray() : new[] { selected };
|
||||
if (ids.Length == 0) { message.Text = "暂无可查询的店铺"; return; }
|
||||
var reasonId = (int)reason.SelectedValue;
|
||||
var actionId = action.SelectedIndex;
|
||||
var cursor = pageCursors[targetPage];
|
||||
var matches = new List<GoodsViolationLogEntity>();
|
||||
while (matches.Count <= PageSize)
|
||||
{
|
||||
var batch = await CloudGoodsService.GetViolationLogs(ids, beforeId: cursor, size: PageSize);
|
||||
if (IsDisposed) return;
|
||||
foreach (var log in batch)
|
||||
{
|
||||
if (cursor != 0 && log.Id >= cursor) throw new InvalidOperationException("违规日志分页顺序异常。");
|
||||
cursor = log.Id;
|
||||
if ((reasonId == 0 || reasonId == log.ReasonId) && (actionId == 0 || actionId == (int)log.Action))
|
||||
matches.Add(log);
|
||||
if (matches.Count > PageSize) break;
|
||||
}
|
||||
if (batch.Count < PageSize) break;
|
||||
}
|
||||
|
||||
if (matches.Count <= PageSize || pageCursors.ElementAtOrDefault(targetPage + 1) != matches[PageSize - 1].Id)
|
||||
{
|
||||
pageCursors.RemoveRange(targetPage + 1, pageCursors.Count - targetPage - 1);
|
||||
if (matches.Count > PageSize) pageCursors.Add(matches[PageSize - 1].Id);
|
||||
}
|
||||
currentPage = targetPage;
|
||||
rows.Clear();
|
||||
foreach (var log in matches.Take(PageSize)) rows.Add(log);
|
||||
|
||||
message.Text = rows.Count == 0 ? "无匹配日志" : $"本页 {rows.Count} 条";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
message.Text = "查询失败:" + ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
pagination.TotalPage = pageCursors.Count;
|
||||
if (pagination.CurrentPage != currentPage + 1) pagination.CurrentPage = currentPage + 1;
|
||||
filters.Enabled = true;
|
||||
pagination.Enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -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