云收藏 增加AI检测图片

This commit is contained in:
chfei committed 2026-11-05 18:50:13 +08:00
1 parent b28940c02d
commit b54a5f5310
36 files changed
+1978 -523

No files matched your search

+10
View File
@@ -127,8 +127,18 @@ namespace AllBase.Helper
Alibaba = 6,
}
public sealed class ComplianceOptions
{
public int? MainCount { get; set; }
public int? DetailCount { get; set; } = 0;
public bool IncludeSku { get; set; }
public string Keywords { get; set; }
}
public class GlobalSettings
{
public ComplianceOptions GoodsCompliance { get; set; } = new ComplianceOptions();
/// <summary>
/// 微信使用软件设置的默认上货地址
/// </summary>
+172 -156
View File
@@ -8,6 +8,7 @@ using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace CsharpHttpHelper
@@ -52,6 +53,16 @@ namespace CsharpHttpHelper
});
}
public static Task<HttpResult> GetAsync(string Url)
{
return RequestAsync(new HttpItem { URL = Url, Method = MethodType.Get });
}
public static Task<HttpResult> PostAsync(string Url, string PostData)
{
return RequestAsync(new HttpItem { URL = Url, Method = MethodType.Post, Postdata = PostData });
}
/// <summary>
/// 下载文件
/// </summary>
@@ -77,17 +88,13 @@ namespace CsharpHttpHelper
public static HttpResult Request(HttpItem item)
{
HttpResult result = new HttpHelper().GetHtml(item);
if (!string.IsNullOrEmpty(result.Html))
{
if (result.Html[0] == 65279)
{
result.Html = result.Html.Remove(0, 1);
}
}
return result;
return new HttpHelper().GetHtml(item);
}
public static Task<HttpResult> RequestAsync(HttpItem item)
{
return new HttpHelper().GetHtmlCoreAsync(item);
}
#region Cookie
/// <summary>
@@ -137,8 +144,6 @@ namespace CsharpHttpHelper
private Encoding postencoding = Encoding.Default;
//HttpWebRequest对象用来发起请求
private HttpWebRequest request = null;
//获取影响流的数据对象
private HttpWebResponse response = null;
/// <summary>
/// 根据相传入的数据,得到相应页面数据
/// </summary>
@@ -146,112 +151,49 @@ namespace CsharpHttpHelper
/// <returns>返回HttpResult类型</returns>
public HttpResult GetHtml(HttpItem item)
{
//返回参数
HttpResult result = new HttpResult();
result.item = item;
try
{
//准备参数
SetRequest(item);
if (item.Method == MethodType.Post || item.Method == MethodType.Put)
{
byte[] body;
if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata))
{
using var reader = new StreamReader(item.Postdata, postencoding);
body = postencoding.GetBytes(reader.ReadToEnd());
}
else body = GetPostBytes(item);
if (body != null)
{
request.ContentLength = body.Length;
using var stream = request.GetRequestStream();
stream.Write(body, 0, body.Length);
}
}
}
catch (Exception ex)
{
result = new HttpResult();
result.Cookie = string.Empty;
result.Header = null;
result.Html = ex.Message;
result.StatusDescription = "配置参数时出错:" + ex.Message;
return result;
return ConfigurationError(ex);
}
var result = new HttpResult { item = item };
try
{
#region 得到请求的response
using (response = (HttpWebResponse)request.GetResponse())
{
result.StatusCode = response.StatusCode;
result.StatusDescription = response.StatusDescription;
result.Header = response.Headers;
if (response.Cookies != null) result.CookieCollection = response.Cookies;
if (response.Headers["set-cookie"] != null) result.Cookie = response.Headers["set-cookie"];
MemoryStream _stream = new MemoryStream();
//GZIIP处理
if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase))
{
//开始读取流并设置编码方式
//new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240);
//.net4.0以下写法
_stream = GetMemoryStream(new GZipStream(response.GetResponseStream(), CompressionMode.Decompress));
}
else
{
//开始读取流并设置编码方式
//response.GetResponseStream().CopyTo(_stream, 10240);
//.net4.0以下写法
_stream = GetMemoryStream(response.GetResponseStream());
}
//获取Byte
byte[] ResponseByte = _stream.ToArray();
_stream.Close();
if (ResponseByte != null & ResponseByte.Length > 0)
{
//是否返回Byte类型数据
if (item.ResultType == ResultType.Byte) result.ResultByte = ResponseByte;
//从这里开始我们要无视编码了
if (encoding == null)
{
Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), "<meta([^<]*)charset=([^<]*)[\"']", RegexOptions.IgnoreCase);
string c = (meta.Groups.Count > 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty;
if (c.Length > 2)
{
try
{
if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" "));
encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim());
}
catch
{
if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8;
else encoding = Encoding.GetEncoding(response.CharacterSet);
}
}
else
{
if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8;
else encoding = Encoding.GetEncoding(response.CharacterSet);
}
}
//得到返回的HTML
result.Html = encoding.GetString(ResponseByte);
}
else
{
//得到返回的HTML
result.Html = "本次请求并未返回任何数据";
}
}
#endregion
using var response = (HttpWebResponse)request.GetResponse();
SetResponseHeaders(result, response);
if (response.Cookies != null) result.CookieCollection = response.Cookies;
result.Cookie = response.Headers["set-cookie"];
using var source = OpenResponseStream(response);
using var buffer = new MemoryStream();
source.CopyTo(buffer);
DecodeBody(item, result, response, buffer.ToArray());
}
catch (WebException ex)
catch (WebException ex) when (ex.Response is HttpWebResponse)
{
//这里是在发生异常时返回的错误信息
response = (HttpWebResponse)ex.Response;
result.Html = ex.Message;
if (response != null)
{
using (response)
{
result.StatusCode = response.StatusCode;
result.StatusDescription = response.StatusDescription;
result.Header = response.Headers;
// 保留错误响应正文,供微信调用方识别业务错误和登录失效。
using (var source = response.GetResponseStream())
using (var decoded = string.Equals(response.ContentEncoding, "gzip", StringComparison.OrdinalIgnoreCase)
? (Stream)new GZipStream(source, CompressionMode.Decompress) : source)
using (var reader = new StreamReader(decoded, item.Encoding ?? Encoding.UTF8))
result.Html = reader.ReadToEnd();
}
}
using var response = (HttpWebResponse)ex.Response;
SetResponseHeaders(result, response);
using var reader = new StreamReader(OpenResponseStream(response), item.Encoding ?? Encoding.UTF8);
result.Html = reader.ReadToEnd();
}
catch (Exception ex)
{
@@ -260,22 +202,127 @@ namespace CsharpHttpHelper
if (item.IsToLower) result.Html = result.Html.ToLower();
return result;
}
/// <summary>
/// 4.0以下.net版本取数据使用
/// </summary>
/// <param name="streamResponse">流</param>
private MemoryStream GetMemoryStream(Stream streamResponse)
public Task<HttpResult> GetHtmlAsync(HttpItem item)
{
MemoryStream _stream = new MemoryStream();
int Length = 256;
Byte[] buffer = new Byte[Length];
int bytesRead = streamResponse.Read(buffer, 0, Length);
while (bytesRead > 0)
// 每次异步调用独立保存请求和编码,允许同一实例并发使用。
return new HttpHelper().GetHtmlCoreAsync(item);
}
private async Task<HttpResult> GetHtmlCoreAsync(HttpItem item)
{
using var timeout = new CancellationTokenSource();
using var abort = timeout.Token.Register(() => request.Abort());
try
{
_stream.Write(buffer, 0, bytesRead);
bytesRead = streamResponse.Read(buffer, 0, Length);
SetRequest(item);
timeout.CancelAfter(request.Timeout);
if (item.Method == MethodType.Post || item.Method == MethodType.Put)
{
byte[] body;
if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata))
{
using var reader = new StreamReader(item.Postdata, postencoding);
body = postencoding.GetBytes(await reader.ReadToEndAsync().ConfigureAwait(false));
}
else body = GetPostBytes(item);
if (body != null)
{
request.ContentLength = body.Length;
using var stream = await request.GetRequestStreamAsync().ConfigureAwait(false);
await stream.WriteAsync(body, 0, body.Length).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
return ConfigurationError(ex);
}
var result = new HttpResult { item = item };
try
{
using var response = (HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false);
SetResponseHeaders(result, response);
if (response.Cookies != null) result.CookieCollection = response.Cookies;
result.Cookie = response.Headers["set-cookie"];
using var source = OpenResponseStream(response);
using var buffer = new MemoryStream();
await source.CopyToAsync(buffer).ConfigureAwait(false);
DecodeBody(item, result, response, buffer.ToArray());
}
catch (WebException ex) when (ex.Response is HttpWebResponse)
{
using var response = (HttpWebResponse)ex.Response;
SetResponseHeaders(result, response);
using var reader = new StreamReader(OpenResponseStream(response), item.Encoding ?? Encoding.UTF8);
result.Html = await reader.ReadToEndAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
result.Html = ex.Message;
}
if (item.IsToLower) result.Html = result.Html.ToLower();
return result;
}
private static HttpResult ConfigurationError(Exception ex)
{
return new HttpResult { Cookie = string.Empty, Html = ex.Message, StatusDescription = "配置参数时出错:" + ex.Message };
}
private static void SetResponseHeaders(HttpResult result, HttpWebResponse response)
{
result.StatusCode = response.StatusCode;
result.StatusDescription = response.StatusDescription;
result.Header = response.Headers;
}
private static Stream OpenResponseStream(HttpWebResponse response)
{
var stream = response.GetResponseStream();
return string.Equals(response.ContentEncoding, "gzip", StringComparison.OrdinalIgnoreCase)
? new GZipStream(stream, CompressionMode.Decompress) : stream;
}
private void DecodeBody(HttpItem item, HttpResult result, HttpWebResponse response, byte[] bytes)
{
if (bytes.Length > 0)
{
//是否返回Byte类型数据
if (item.ResultType == ResultType.Byte) result.ResultByte = bytes;
//从这里开始我们要无视编码了
if (encoding == null)
{
Match meta = Regex.Match(Encoding.Default.GetString(bytes), "<meta([^<]*)charset=([^<]*)[\"']", RegexOptions.IgnoreCase);
string c = (meta.Groups.Count > 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty;
if (c.Length > 2)
{
try
{
if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" "));
encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim());
}
catch
{
if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8;
else encoding = Encoding.GetEncoding(response.CharacterSet);
}
}
else
{
if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8;
else encoding = Encoding.GetEncoding(response.CharacterSet);
}
}
//得到返回的HTML
result.Html = encoding.GetString(bytes);
}
else
{
//得到返回的HTML
result.Html = "本次请求并未返回任何数据";
}
return _stream;
}
/// <summary>
/// 为请求准备参数
@@ -308,6 +355,7 @@ namespace CsharpHttpHelper
request.UserAgent = item.UserAgent;
// 编码
encoding = item.Encoding;
if (item.Method != MethodType.Get && item.PostEncoding != null) postencoding = item.PostEncoding;
//设置安全凭证
request.Credentials = item.ICredentials;
//设置Cookie
@@ -316,8 +364,6 @@ namespace CsharpHttpHelper
request.Referer = item.Referer;
//是否执行跳转功能
request.AllowAutoRedirect = item.Allowautoredirect;
//设置Post数据
SetPostData(item);
//设置最大连接
if (item.Connectionlimit > 0) request.ServicePoint.ConnectionLimit = item.Connectionlimit;
}
@@ -373,43 +419,13 @@ namespace CsharpHttpHelper
request.CookieContainer.Add(item.CookieCollection);
}
}
/// <summary>
/// 设置Post数据
/// </summary>
/// <param name="item">Http参数</param>
private void SetPostData(HttpItem item)
private byte[] GetPostBytes(HttpItem item)
{
//验证在得到结果时是否有传入数据
if (request.Method.Trim().ToLower().Contains("post") || request.Method.Trim().ToLower().Contains("put"))
{
if (item.PostEncoding != null)
{
postencoding = item.PostEncoding;
}
byte[] buffer = null;
//写入Byte类型
if (item.PostDataType == PostDataType.Byte && item.PostdataByte != null && item.PostdataByte.Length > 0)
{
//验证在得到结果时是否有传入数据
buffer = item.PostdataByte;
}//写入文件
else if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata))
{
StreamReader r = new StreamReader(item.Postdata, postencoding);
buffer = postencoding.GetBytes(r.ReadToEnd());
r.Close();
} //写入字符串
else if (!string.IsNullOrEmpty(item.Postdata))
{
buffer = postencoding.GetBytes(item.Postdata);
}
if (buffer != null)
{
request.ContentLength = buffer.Length;
request.GetRequestStream().Write(buffer, 0, buffer.Length);
}
}
if (item.PostDataType == PostDataType.Byte && item.PostdataByte?.Length > 0)
return item.PostdataByte;
return string.IsNullOrEmpty(item.Postdata) ? null : postencoding.GetBytes(item.Postdata);
}
/// <summary>
/// 设置代理
/// </summary>
+3 -3
View File
@@ -8,8 +8,8 @@ namespace AllBase.Model.Result
{
public enum PlatformType
{
None,
PDD,
WX,
None = 0,
PDD = 1,
WX = 2,
}
}
+2
View File
@@ -20,6 +20,8 @@ namespace AllBase.Model.Result
public int Code { get; set; }
public string Msg { get; set; }
public bool? Success { get; set; }
public string TraceId { get; set; }
}
public class Result<TData> : Result
{
@@ -12,7 +12,7 @@ namespace TemuKJD.BLL.ImportConverts
{
public class ConverterToPddFactory
{
private static List<IGoodsConverter> _converters = new List<IGoodsConverter>
private static List<IGoodsConverter> CreateConverters() => new List<IGoodsConverter>
{
new ConverterToPddV5(),
new ConverterWXToPddV1(),
@@ -24,7 +24,7 @@ namespace TemuKJD.BLL.ImportConverts
public static PDDFrontProductInfo5 Convert(string filePath)
{
foreach (IGoodsConverter item in _converters)
foreach (IGoodsConverter item in CreateConverters())
{
try
{
@@ -47,7 +47,7 @@ namespace TemuKJD.BLL.ImportConverts
public static PDDFrontProductInfo5 SetContentConvert(string content)
{
foreach (IGoodsConverter item in _converters)
foreach (IGoodsConverter item in CreateConverters())
{
try
{
@@ -8,7 +8,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
namespace TemuKJD.BLL.ImportConverters
{
+5 -2
View File
@@ -68,10 +68,10 @@
<ProjectReference Include="..\Work\Work.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="PDDForms\ProductSelectForms\CloudGoodsFavoriteForm.cs">
<Compile Update="WXForms\ProductForms\CloudGoodsFavoriteForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="PDDForms\ProductSelectForms\CloudGoodsForm.cs">
<Compile Update="WXForms\ProductForms\CloudGoodsForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Properties\Resources.Designer.cs">
@@ -86,4 +86,7 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Remove="BLL\PDDProdFormConverter.cs;BLL\ImportConverters\*.cs" />
</ItemGroup>
</Project>
+6
View File
@@ -44,6 +44,9 @@
<Compile Update="InitForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="KJDForms\SystemForms\SystemGoodsComplianceForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="KJDForms\SystemForms\SystemViolationDetectionForm.cs">
<SubType>Form</SubType>
</Compile>
@@ -65,6 +68,9 @@
<Compile Update="PDDForms\ProductSelectForms\PDDDBProdGroupManageFrom.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="UserComponent\UcNumericRange.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Update="WXForms\AdminForms\WXAdminOemWithdrawal.cs">
<SubType>Form</SubType>
</Compile>
+3 -1
View File
@@ -30,8 +30,9 @@
{
System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("解除限制");
System.Windows.Forms.TreeNode violationNode = new System.Windows.Forms.TreeNode("违规检测");
System.Windows.Forms.TreeNode complianceNode = new System.Windows.Forms.TreeNode("商品 AI 检测");
System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("系统设置", new System.Windows.Forms.TreeNode[] {
treeNode1, violationNode});
treeNode1, violationNode, complianceNode});
this.panel1 = new System.Windows.Forms.Panel();
this.panel3 = new System.Windows.Forms.Panel();
this.panel2 = new System.Windows.Forms.Panel();
@@ -104,6 +105,7 @@
this.tv.Name = "tv";
treeNode1.Name = "解除限制";
treeNode1.Text = "解除限制";
complianceNode.Name = "商品 AI 检测";
violationNode.Name = "违规检测";
violationNode.Text = "违规检测";
treeNode2.Name = "系统设置";
@@ -16,6 +16,8 @@ namespace TemuKJD.KJDForms.SystemForms
{
public Lazy<SystemRemoveRestrictionsForm> systemRemoveRestrictionsForm = new Lazy<SystemRemoveRestrictionsForm>();
public Lazy<SystemViolationDetectionForm> systemViolationDetectionForm = new Lazy<SystemViolationDetectionForm>();
private readonly Lazy<SystemGoodsComplianceForm> goodsComplianceForm = new Lazy<SystemGoodsComplianceForm>();
public SystemForm()
{
InitializeComponent();
@@ -47,6 +49,9 @@ namespace TemuKJD.KJDForms.SystemForms
case "解除限制":
SetForm(systemRemoveRestrictionsForm.Value);
break;
case "商品 AI 检测":
SetForm(goodsComplianceForm.Value);
break;
case "违规检测":
SetForm(systemViolationDetectionForm.Value);
break;
@@ -0,0 +1,186 @@
using System.Drawing;
using System.Windows.Forms;
namespace TemuKJD.KJDForms.SystemForms
{
partial class SystemGoodsComplianceForm
{
private GroupBox imagesGroup;
private Label mainLabel;
private ComboBox main;
private NumericUpDown mainCount;
private Label mainUnit;
private Label detailLabel;
private ComboBox detail;
private NumericUpDown detailCount;
private Label detailUnit;
private CheckBox sku;
private GroupBox keywordsGroup;
private TextBox keywords;
private Label keywordsHint;
private Button btnSaveDetection;
private void InitializeComponent()
{
this.imagesGroup = new GroupBox();
this.mainLabel = new Label();
this.main = new ComboBox();
this.mainCount = new NumericUpDown();
this.mainUnit = new Label();
this.detailLabel = new Label();
this.detail = new ComboBox();
this.detailCount = new NumericUpDown();
this.detailUnit = new Label();
this.sku = new CheckBox();
this.keywordsGroup = new GroupBox();
this.keywords = new TextBox();
this.keywordsHint = new Label();
this.btnSaveDetection = new Button();
this.imagesGroup.SuspendLayout();
this.keywordsGroup.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.mainCount)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.detailCount)).BeginInit();
this.SuspendLayout();
this.imagesGroup.Name = "imagesGroup";
this.imagesGroup.Location = new Point(20, 16);
this.imagesGroup.TabIndex = 0;
this.imagesGroup.Size = new Size(760, 153);
this.imagesGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
this.imagesGroup.Text = "检测图片";
this.imagesGroup.Controls.Add(this.mainLabel);
this.imagesGroup.Controls.Add(this.main);
this.imagesGroup.Controls.Add(this.mainCount);
this.imagesGroup.Controls.Add(this.mainUnit);
this.imagesGroup.Controls.Add(this.detailLabel);
this.imagesGroup.Controls.Add(this.detail);
this.imagesGroup.Controls.Add(this.detailCount);
this.imagesGroup.Controls.Add(this.detailUnit);
this.imagesGroup.Controls.Add(this.sku);
this.mainLabel.Name = "mainLabel";
this.mainLabel.Location = new Point(16, 34);
this.mainLabel.TabIndex = 0;
this.mainLabel.AutoSize = true;
this.mainLabel.Text = "商品主图";
this.main.Name = "main";
this.main.Location = new Point(106, 30);
this.main.TabIndex = 1;
this.main.Width = 110;
this.main.DropDownStyle = ComboBoxStyle.DropDownList;
this.main.Items.AddRange(new object[] { "全部", "前几张" });
this.main.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged);
this.mainCount.Name = "mainCount";
this.mainCount.Location = new Point(228, 30);
this.mainCount.TabIndex = 2;
this.mainCount.Enabled = false;
this.mainCount.Minimum = 1M;
this.mainCount.Maximum = 999M;
this.mainCount.Value = 1M;
this.mainCount.Width = 60;
this.mainCount.AccessibleName = "商品主图检测张数";
this.mainUnit.Name = "mainUnit";
this.mainUnit.Location = new Point(294, 34);
this.mainUnit.TabIndex = 3;
this.mainUnit.AutoSize = true;
this.mainUnit.Text = "张";
this.detailLabel.Name = "detailLabel";
this.detailLabel.Location = new Point(16, 72);
this.detailLabel.TabIndex = 4;
this.detailLabel.AutoSize = true;
this.detailLabel.Text = "详情图";
this.detail.Name = "detail";
this.detail.Location = new Point(106, 68);
this.detail.TabIndex = 5;
this.detail.Width = 110;
this.detail.DropDownStyle = ComboBoxStyle.DropDownList;
this.detail.Items.AddRange(new object[] { "不检测", "前几张", "全部" });
this.detail.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged);
this.detailCount.Name = "detailCount";
this.detailCount.Location = new Point(228, 68);
this.detailCount.TabIndex = 6;
this.detailCount.Enabled = false;
this.detailCount.Minimum = 1M;
this.detailCount.Maximum = 999M;
this.detailCount.Value = 1M;
this.detailCount.Width = 60;
this.detailCount.AccessibleName = "详情图检测张数";
this.detailUnit.Name = "detailUnit";
this.detailUnit.Location = new Point(294, 72);
this.detailUnit.TabIndex = 7;
this.detailUnit.AutoSize = true;
this.detailUnit.Text = "张";
this.sku.Name = "sku";
this.sku.Location = new Point(106, 112);
this.sku.TabIndex = 8;
this.sku.AutoSize = true;
this.sku.Text = "检测 SKU 图片";
this.keywordsGroup.Name = "keywordsGroup";
this.keywordsGroup.Location = new Point(20, 183);
this.keywordsGroup.TabIndex = 1;
this.keywordsGroup.Size = new Size(760, 143);
this.keywordsGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
this.keywordsGroup.Text = "检测关键词";
this.keywordsGroup.Controls.Add(this.keywords);
this.keywordsGroup.Controls.Add(this.keywordsHint);
this.keywords.Name = "keywords";
this.keywords.Location = new Point(16, 27);
this.keywords.TabIndex = 0;
this.keywords.Size = new Size(728, 68);
this.keywords.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
this.keywords.Multiline = true;
this.keywords.AcceptsReturn = true;
this.keywords.ScrollBars = ScrollBars.Vertical;
this.keywords.AccessibleName = "检测关键词";
this.keywordsHint.Name = "keywordsHint";
this.keywordsHint.Location = new Point(16, 105);
this.keywordsHint.TabIndex = 1;
this.keywordsHint.AutoSize = true;
this.keywordsHint.ForeColor = SystemColors.GrayText;
this.keywordsHint.Text = "多个关键词可用逗号、分号、空格或换行分隔。";
this.btnSaveDetection.Name = "btnSaveDetection";
this.btnSaveDetection.Location = new Point(20, 341);
this.btnSaveDetection.TabIndex = 2;
this.btnSaveDetection.Size = new Size(96, 32);
this.btnSaveDetection.Text = "保存条件";
this.btnSaveDetection.BackColor = Color.FromArgb(63, 126, 255);
this.btnSaveDetection.ForeColor = Color.White;
this.btnSaveDetection.FlatStyle = FlatStyle.Flat;
this.btnSaveDetection.UseVisualStyleBackColor = false;
this.btnSaveDetection.Cursor = Cursors.Hand;
this.btnSaveDetection.FlatAppearance.BorderSize = 0;
this.btnSaveDetection.Click += new System.EventHandler(this.SaveDetectionClick);
this.AutoScaleMode = AutoScaleMode.None;
this.AutoScroll = true;
this.AutoScrollMinSize = new Size(365, 390);
this.BackColor = Color.White;
this.ClientSize = new Size(800, 450);
this.Font = new Font("微软雅黑", 9F);
this.Controls.Add(this.imagesGroup);
this.Controls.Add(this.keywordsGroup);
this.Controls.Add(this.btnSaveDetection);
this.Name = "SystemGoodsComplianceForm";
this.Text = "商品 AI 检测";
this.imagesGroup.ResumeLayout(false);
this.imagesGroup.PerformLayout();
this.keywordsGroup.ResumeLayout(false);
this.keywordsGroup.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.mainCount)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.detailCount)).EndInit();
this.ResumeLayout(false);
}
}
}
@@ -0,0 +1,64 @@
using System;
using System.Windows.Forms;
using Work.Services;
using AllBase.Helper;
using TemuKJD.BaseForms;
namespace TemuKJD.KJDForms.SystemForms
{
public partial class SystemGoodsComplianceForm : BaseForm
{
public SystemGoodsComplianceForm()
{
InitializeComponent();
VisibleChanged += (sender, e) => { if (Visible) LoadSettings(); };
LoadSettings();
}
private void LoadSettings()
{
var options = GlobalConfig.GlobalSettings.GoodsCompliance ?? new ComplianceOptions();
main.SelectedIndex = options.MainCount.HasValue ? 1 : 0;
detail.SelectedIndex = options.DetailCount == null ? 2 : options.DetailCount == 0 ? 0 : 1;
mainCount.Value = Math.Min(mainCount.Maximum, Math.Max(1, options.MainCount ?? 1));
detailCount.Value = Math.Min(detailCount.Maximum, Math.Max(1, options.DetailCount ?? 1));
sku.Checked = options.IncludeSku;
keywords.Text = options.Keywords;
}
private void ImageRangeChanged(object sender, EventArgs e)
{
mainCount.Enabled = main.SelectedIndex == 1;
detailCount.Enabled = detail.SelectedIndex == 1;
}
private void SaveDetectionClick(object sender, EventArgs e)
{
if (GoodsComplianceService.Keywords(keywords.Text).Length == 0)
{
MessageBox.Show(this, "请输入检测关键词。");
keywords.Focus();
return;
}
var settings = GlobalConfig.GlobalSettings;
var previous = settings.GoodsCompliance;
settings.GoodsCompliance = new ComplianceOptions
{
MainCount = main.SelectedIndex == 0 ? (int?)null : (int)mainCount.Value,
DetailCount = detail.SelectedIndex == 2 ? (int?)null : detail.SelectedIndex == 0 ? 0 : (int)detailCount.Value,
IncludeSku = sku.Checked,
Keywords = keywords.Text.Trim()
};
try { StartUp.SaveGlobalSettings(); }
catch (Exception ex)
{
settings.GoodsCompliance = previous;
MessageBox.Show(this, "保存检测条件失败:" + ex.Message);
return;
}
MessageBox.Show(this, "已保存 AI 检测条件。");
}
}
}
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using TemuKJD.BaseForms;
using TemuKJD.WXForms.ProductForms;
using AllBase;
using TemuKJD.PDDForms.ProductSelectForms;
using TemuKJD.PDDForms.ProductForms.CouponForms;
+159
View File
@@ -0,0 +1,159 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace TemuKJD.UserComponent
{
[DefaultEvent(nameof(ValueChanged))]
public class UcNumericRange : UserControl
{
private const decimal DefaultMaximum = 1000000m;
private const int NormalWidth = 220;
private const int CompactWidth = 112;
private readonly Label separator = new Label { Text = "至", AutoSize = true, Anchor = AnchorStyles.None };
private readonly NumericUpDown lower = CreateEditor("起始值");
private readonly NumericUpDown upper = CreateEditor("结束值");
private bool updating;
public UcNumericRange()
{
upper.Value = DefaultMaximum;
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 3, RowCount = 1 };
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
layout.Controls.Add(CreateCell(lower), 0, 0);
layout.Controls.Add(separator, 1, 0);
layout.Controls.Add(CreateCell(upper), 2, 0);
Controls.Add(layout);
Size = new Size(NormalWidth, lower.PreferredHeight + 2);
lower.ValueChanged += (s, e) => UpdateRange(lower, upper);
upper.ValueChanged += (s, e) => UpdateRange(upper, lower);
}
[Description("两个数值之间显示的文字。"), DefaultValue("至")]
public string SeparatorText
{
get => separator.Text;
set => separator.Text = value;
}
[Description("使用窄布局并隐藏微调按钮,仍可用方向键调整数值。"), DefaultValue(false)]
public bool Compact
{
get => separator.Margin == Padding.Empty;
set
{
if (value == Compact) return;
separator.Margin = value ? Padding.Empty : new Padding(3, 0, 3, 0);
SetCompactBorder(lower, value);
SetCompactBorder(upper, value);
if (Dock == DockStyle.None && Width == (value ? NormalWidth : CompactWidth))
Width = value ? CompactWidth : NormalWidth;
}
}
private static void SetCompactBorder(NumericUpDown editor, bool compact)
{
var cell = (Panel)editor.Parent;
editor.BorderStyle = compact ? BorderStyle.None : BorderStyle.Fixed3D;
cell.BorderStyle = compact ? BorderStyle.FixedSingle : BorderStyle.None;
cell.BackColor = compact ? editor.BackColor : Color.Empty;
cell.PerformLayout();
}
private Panel CreateCell(NumericUpDown editor)
{
var cell = new Panel { Dock = DockStyle.Fill, Margin = Padding.Empty };
cell.Controls.Add(editor);
var buttons = editor.Controls[0] is TextBox ? editor.Controls[1] : editor.Controls[0];
// 紧凑模式将右侧按钮移出可见区域,保留原生数字输入和键盘操作。
cell.Layout += (s, e) =>
{
int clippedWidth = Compact ? editor.Width - buttons.Left : 0;
editor.Width = cell.ClientSize.Width + clippedWidth;
editor.Top = Compact ? Math.Max(0, (cell.ClientSize.Height - editor.Height) / 2) : 0;
};
return cell;
}
private static NumericUpDown CreateEditor(string name)
{
return new NumericUpDown { AccessibleName = name, Margin = Padding.Empty, Maximum = DefaultMaximum };
}
[Description("允许输入的最小值。"), DefaultValue(typeof(decimal), "0")]
public decimal Minimum
{
get => lower.Minimum;
set => SetLimits(value, Math.Max(value, Maximum));
}
[Description("允许输入的最大值。"), DefaultValue(typeof(decimal), "1000000")]
public decimal Maximum
{
get => lower.Maximum;
set => SetLimits(Math.Min(value, Minimum), value);
}
[Description("选中范围的起始值。"), DefaultValue(typeof(decimal), "0")]
public decimal LowerValue
{
get => lower.Value;
set => lower.Value = value;
}
[Description("选中范围的结束值。"), DefaultValue(typeof(decimal), "1000000")]
public decimal UpperValue
{
get => upper.Value;
set => upper.Value = value;
}
[Description("数字输入框显示的小数位数。"), DefaultValue(0)]
public int DecimalPlaces
{
get => lower.DecimalPlaces;
set
{
if (value < 0 || value > 28) throw new ArgumentOutOfRangeException(nameof(value));
lower.DecimalPlaces = upper.DecimalPlaces = value;
decimal increment = 1m;
for (int i = 0; i < value; i++)
increment /= 10m;
lower.Increment = upper.Increment = increment;
}
}
public event EventHandler ValueChanged;
private void SetLimits(decimal minimum, decimal maximum)
{
decimal oldLower = LowerValue, oldUpper = UpperValue;
updating = true;
try
{
lower.Minimum = upper.Minimum = minimum;
lower.Maximum = upper.Maximum = maximum;
}
finally { updating = false; }
if (LowerValue != oldLower || UpperValue != oldUpper)
ValueChanged?.Invoke(this, EventArgs.Empty);
}
private void UpdateRange(NumericUpDown edited, NumericUpDown other)
{
if (updating) return;
updating = true;
try
{
if (LowerValue > UpperValue)
other.Value = edited.Value;
}
finally { updating = false; }
ValueChanged?.Invoke(this, EventArgs.Empty);
}
}
}
+22 -22
View File
@@ -288,28 +288,28 @@ namespace TemuKJD.UserForm
_ = Task.Run(() =>
{
try
{
StoreApiClient.Instance.Auth.Login(new LoginRequest
{
UserName = BaseClass.UserName,
Password = BaseClass.PassWord
});
}
catch (Exception ex)
{
try
{
LogSave.AddFile("Store API 登录失败:" + ex.Message);
}
catch
{
// 日志失败也不能影响主程序。
}
}
});
//_ = Task.Run(() =>
//{
// try
// {
// StoreApiClient.Instance.Auth.Login(new LoginRequest
// {
// UserName = BaseClass.UserName,
// Password = BaseClass.PassWord
// });
// }
// catch (Exception ex)
// {
// try
// {
// LogSave.AddFile("Store API 登录失败:" + ex.Message);
// }
// catch
// {
// // 日志失败也不能影响主程序。
// }
// }
//});
Hide();
mf.Hide();
@@ -1,5 +1,5 @@
namespace TemuKJD.PDDForms.ProductSelectForms
namespace TemuKJD.WXForms.ProductForms
{
partial class CloudGoodsFavoriteForm
{
@@ -29,25 +29,25 @@ namespace TemuKJD.PDDForms.ProductSelectForms
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle9 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle10 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.cb_safe = new System.Windows.Forms.ComboBox();
this.label4 = new System.Windows.Forms.Label();
this.num_price = new TemuKJD.UserComponent.UcNumericRange();
this.num_sold = new TemuKJD.UserComponent.UcNumericRange();
this.panel1 = new System.Windows.Forms.Panel();
this.btnSafeDetection = new DevComponents.DotNetBar.PanelEx();
this.btn_removefavorite = new DevComponents.DotNetBar.PanelEx();
this.bt_importUploadList = new DevComponents.DotNetBar.PanelEx();
this.label5r = new System.Windows.Forms.Label();
this.cb_Platform = new System.Windows.Forms.ComboBox();
this.cb_Platformr = new System.Windows.Forms.ComboBox();
this.cmbCatSelect2 = new System.Windows.Forms.ComboBox();
this.cmbCatSelect1 = new System.Windows.Forms.ComboBox();
this.cmbCatSelect4 = new System.Windows.Forms.ComboBox();
this.cmbCatSelect3 = new System.Windows.Forms.ComboBox();
this.label4 = new System.Windows.Forms.Label();
this.num_maxprice = new System.Windows.Forms.NumericUpDown();
this.num_minprice = new System.Windows.Forms.NumericUpDown();
this.label7 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.num_maxsold = new System.Windows.Forms.NumericUpDown();
this.num_minsold = new System.Windows.Forms.NumericUpDown();
this.txt_title = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
@@ -57,6 +57,9 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.splitContainer2 = new System.Windows.Forms.SplitContainer();
this.pbLoading = new System.Windows.Forms.PictureBox();
this.dgvData = new TemuKJD.UserComponent.DataGridViewEx();
this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage();
this.statusBar = new TemuKJD.UserControls.StatusBarUserControl();
this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
this.colSelect = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.c_Platform = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column1 = new System.Windows.Forms.DataGridViewImageColumn();
@@ -64,23 +67,19 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column3 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column4 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage();
this.statusBar = new TemuKJD.UserControls.StatusBarUserControl();
this.colDetectionResult = new System.Windows.Forms.DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.num_maxprice)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.num_minprice)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.num_maxsold)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.num_minsold)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit();
this.splitContainer2.Panel1.SuspendLayout();
this.splitContainer2.Panel2.SuspendLayout();
this.splitContainer2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.pbLoading)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.dgvData)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
this.SuspendLayout();
//
// splitContainer1
@@ -94,20 +93,19 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add(this.cb_safe);
this.splitContainer1.Panel1.Controls.Add(this.label4);
this.splitContainer1.Panel1.Controls.Add(this.num_price);
this.splitContainer1.Panel1.Controls.Add(this.num_sold);
this.splitContainer1.Panel1.Controls.Add(this.panel1);
this.splitContainer1.Panel1.Controls.Add(this.label5r);
this.splitContainer1.Panel1.Controls.Add(this.cb_Platform);
this.splitContainer1.Panel1.Controls.Add(this.cb_Platformr);
this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect2);
this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect1);
this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect4);
this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect3);
this.splitContainer1.Panel1.Controls.Add(this.label4);
this.splitContainer1.Panel1.Controls.Add(this.num_maxprice);
this.splitContainer1.Panel1.Controls.Add(this.num_minprice);
this.splitContainer1.Panel1.Controls.Add(this.label7);
this.splitContainer1.Panel1.Controls.Add(this.label3);
this.splitContainer1.Panel1.Controls.Add(this.num_maxsold);
this.splitContainer1.Panel1.Controls.Add(this.num_minsold);
this.splitContainer1.Panel1.Controls.Add(this.txt_title);
this.splitContainer1.Panel1.Controls.Add(this.label1);
this.splitContainer1.Panel1.Controls.Add(this.label6);
@@ -119,26 +117,133 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
this.splitContainer1.Panel2.Controls.Add(this.splitContainer2);
this.splitContainer1.Panel2.Margin = new System.Windows.Forms.Padding(4);
this.splitContainer1.Size = new System.Drawing.Size(1643, 530);
this.splitContainer1.Size = new System.Drawing.Size(1719, 530);
this.splitContainer1.TabIndex = 35;
//
// cb_safe
//
this.cb_safe.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cb_safe.DropDownWidth = 150;
this.cb_safe.FormattingEnabled = true;
this.cb_safe.Items.AddRange(new object[] {
"全部",
"安全",
"违规",
"未检测"});
this.cb_safe.Location = new System.Drawing.Point(1141, 13);
this.cb_safe.Name = "cb_safe";
this.cb_safe.Size = new System.Drawing.Size(97, 25);
this.cb_safe.TabIndex = 930;
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(1104, 17);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(44, 17);
this.label4.TabIndex = 929;
this.label4.Text = "检测:";
//
// num_price
//
this.num_price.Compact = true;
this.num_price.Location = new System.Drawing.Point(391, 15);
this.num_price.LowerValue = new decimal(new int[] {
1,
0,
0,
0});
this.num_price.Maximum = new decimal(new int[] {
9999999,
0,
0,
0});
this.num_price.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.num_price.Name = "num_price";
this.num_price.SeparatorText = "";
this.num_price.Size = new System.Drawing.Size(108, 23);
this.num_price.TabIndex = 922;
this.num_price.UpperValue = new decimal(new int[] {
9999999,
0,
0,
0});
//
// num_sold
//
this.num_sold.Compact = true;
this.num_sold.Location = new System.Drawing.Point(218, 15);
this.num_sold.LowerValue = new decimal(new int[] {
1,
0,
0,
0});
this.num_sold.Maximum = new decimal(new int[] {
9999999,
0,
0,
0});
this.num_sold.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.num_sold.Name = "num_sold";
this.num_sold.SeparatorText = "";
this.num_sold.Size = new System.Drawing.Size(108, 23);
this.num_sold.TabIndex = 921;
this.num_sold.UpperValue = new decimal(new int[] {
9999999,
0,
0,
0});
//
// panel1
//
this.panel1.Controls.Add(this.btnSafeDetection);
this.panel1.Controls.Add(this.btn_removefavorite);
this.panel1.Controls.Add(this.bt_importUploadList);
this.panel1.Dock = System.Windows.Forms.DockStyle.Right;
this.panel1.Location = new System.Drawing.Point(1507, 0);
this.panel1.Location = new System.Drawing.Point(1509, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(136, 50);
this.panel1.Size = new System.Drawing.Size(210, 50);
this.panel1.TabIndex = 914;
//
// btnSafeDetection
//
this.btnSafeDetection.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;
this.btnSafeDetection.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnSafeDetection.DisabledBackColor = System.Drawing.Color.Empty;
this.btnSafeDetection.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnSafeDetection.Location = new System.Drawing.Point(132, 14);
this.btnSafeDetection.Name = "btnSafeDetection";
this.btnSafeDetection.Size = new System.Drawing.Size(58, 23);
this.btnSafeDetection.Style.Alignment = System.Drawing.StringAlignment.Center;
this.btnSafeDetection.Style.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
this.btnSafeDetection.Style.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
this.btnSafeDetection.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;
this.btnSafeDetection.Style.BorderDashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
this.btnSafeDetection.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.None;
this.btnSafeDetection.Style.ForeColor.Color = System.Drawing.Color.White;
this.btnSafeDetection.Style.GradientAngle = 90;
this.btnSafeDetection.StyleMouseOver.Alignment = System.Drawing.StringAlignment.Center;
this.btnSafeDetection.StyleMouseOver.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255)))));
this.btnSafeDetection.StyleMouseOver.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255)))));
this.btnSafeDetection.TabIndex = 451;
this.btnSafeDetection.Text = "AI检测";
this.btnSafeDetection.Click += new System.EventHandler(this.btnSafeDetection_Click);
//
// btn_removefavorite
//
this.btn_removefavorite.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;
this.btn_removefavorite.Cursor = System.Windows.Forms.Cursors.Hand;
this.btn_removefavorite.DisabledBackColor = System.Drawing.Color.Empty;
this.btn_removefavorite.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btn_removefavorite.Location = new System.Drawing.Point(67, 14);
this.btn_removefavorite.Location = new System.Drawing.Point(68, 14);
this.btn_removefavorite.Name = "btn_removefavorite";
this.btn_removefavorite.Size = new System.Drawing.Size(58, 23);
this.btn_removefavorite.Style.Alignment = System.Drawing.StringAlignment.Center;
@@ -183,31 +288,31 @@ namespace TemuKJD.PDDForms.ProductSelectForms
// label5r
//
this.label5r.AutoSize = true;
this.label5r.Location = new System.Drawing.Point(1048, 16);
this.label5r.Location = new System.Drawing.Point(942, 16);
this.label5r.Name = "label5r";
this.label5r.Size = new System.Drawing.Size(44, 17);
this.label5r.TabIndex = 907;
this.label5r.Text = "来源:";
//
// cb_Platform
// cb_Platformr
//
this.cb_Platform.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cb_Platform.DropDownWidth = 150;
this.cb_Platform.FormattingEnabled = true;
this.cb_Platform.Items.AddRange(new object[] {
this.cb_Platformr.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cb_Platformr.DropDownWidth = 150;
this.cb_Platformr.FormattingEnabled = true;
this.cb_Platformr.Items.AddRange(new object[] {
"全部",
"多多",
"微信"});
this.cb_Platform.Location = new System.Drawing.Point(1098, 13);
this.cb_Platform.Name = "cb_Platform";
this.cb_Platform.Size = new System.Drawing.Size(97, 25);
this.cb_Platform.TabIndex = 906;
this.cb_Platformr.Location = new System.Drawing.Point(992, 13);
this.cb_Platformr.Name = "cb_Platformr";
this.cb_Platformr.Size = new System.Drawing.Size(97, 25);
this.cb_Platformr.TabIndex = 906;
//
// cmbCatSelect2
//
this.cmbCatSelect2.DropDownWidth = 150;
this.cmbCatSelect2.FormattingEnabled = true;
this.cmbCatSelect2.Location = new System.Drawing.Point(745, 12);
this.cmbCatSelect2.Location = new System.Drawing.Point(639, 12);
this.cmbCatSelect2.Name = "cmbCatSelect2";
this.cmbCatSelect2.Size = new System.Drawing.Size(97, 25);
this.cmbCatSelect2.TabIndex = 897;
@@ -216,7 +321,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
this.cmbCatSelect1.DropDownWidth = 150;
this.cmbCatSelect1.FormattingEnabled = true;
this.cmbCatSelect1.Location = new System.Drawing.Point(648, 12);
this.cmbCatSelect1.Location = new System.Drawing.Point(542, 12);
this.cmbCatSelect1.Name = "cmbCatSelect1";
this.cmbCatSelect1.Size = new System.Drawing.Size(97, 25);
this.cmbCatSelect1.TabIndex = 896;
@@ -225,7 +330,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
this.cmbCatSelect4.DropDownWidth = 150;
this.cmbCatSelect4.FormattingEnabled = true;
this.cmbCatSelect4.Location = new System.Drawing.Point(939, 12);
this.cmbCatSelect4.Location = new System.Drawing.Point(833, 12);
this.cmbCatSelect4.Name = "cmbCatSelect4";
this.cmbCatSelect4.Size = new System.Drawing.Size(97, 25);
this.cmbCatSelect4.TabIndex = 899;
@@ -234,50 +339,15 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
this.cmbCatSelect3.DropDownWidth = 150;
this.cmbCatSelect3.FormattingEnabled = true;
this.cmbCatSelect3.Location = new System.Drawing.Point(842, 12);
this.cmbCatSelect3.Location = new System.Drawing.Point(736, 12);
this.cmbCatSelect3.Name = "cmbCatSelect3";
this.cmbCatSelect3.Size = new System.Drawing.Size(97, 25);
this.cmbCatSelect3.TabIndex = 898;
//
// label4
//
this.label4.AutoSize = true;
this.label4.ForeColor = System.Drawing.Color.Gray;
this.label4.Location = new System.Drawing.Point(518, 17);
this.label4.Margin = new System.Windows.Forms.Padding(0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(13, 17);
this.label4.TabIndex = 488;
this.label4.Text = "-";
//
// num_maxprice
//
this.num_maxprice.Location = new System.Drawing.Point(534, 14);
this.num_maxprice.Maximum = new decimal(new int[] {
99999,
0,
0,
0});
this.num_maxprice.Name = "num_maxprice";
this.num_maxprice.Size = new System.Drawing.Size(70, 23);
this.num_maxprice.TabIndex = 487;
//
// num_minprice
//
this.num_minprice.Location = new System.Drawing.Point(447, 14);
this.num_minprice.Maximum = new decimal(new int[] {
99999,
0,
0,
0});
this.num_minprice.Name = "num_minprice";
this.num_minprice.Size = new System.Drawing.Size(70, 23);
this.num_minprice.TabIndex = 486;
//
// label7
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(388, 17);
this.label7.Location = new System.Drawing.Point(332, 17);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(64, 17);
this.label7.TabIndex = 485;
@@ -294,30 +364,6 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.label3.TabIndex = 484;
this.label3.Text = "-";
//
// num_maxsold
//
this.num_maxsold.Location = new System.Drawing.Point(310, 14);
this.num_maxsold.Maximum = new decimal(new int[] {
9999999,
0,
0,
0});
this.num_maxsold.Name = "num_maxsold";
this.num_maxsold.Size = new System.Drawing.Size(70, 23);
this.num_maxsold.TabIndex = 483;
//
// num_minsold
//
this.num_minsold.Location = new System.Drawing.Point(223, 14);
this.num_minsold.Maximum = new decimal(new int[] {
9999999,
0,
0,
0});
this.num_minsold.Name = "num_minsold";
this.num_minsold.Size = new System.Drawing.Size(70, 23);
this.num_minsold.TabIndex = 482;
//
// txt_title
//
this.txt_title.Location = new System.Drawing.Point(60, 14);
@@ -337,7 +383,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
// label6
//
this.label6.AutoSize = true;
this.label6.Location = new System.Drawing.Point(610, 17);
this.label6.Location = new System.Drawing.Point(504, 17);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(44, 17);
this.label6.TabIndex = 440;
@@ -349,7 +395,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.btnSearch.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnSearch.DisabledBackColor = System.Drawing.Color.Empty;
this.btnSearch.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnSearch.Location = new System.Drawing.Point(1208, 14);
this.btnSearch.Location = new System.Drawing.Point(1257, 14);
this.btnSearch.Name = "btnSearch";
this.btnSearch.Size = new System.Drawing.Size(58, 23);
this.btnSearch.Style.Alignment = System.Drawing.StringAlignment.Center;
@@ -372,7 +418,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.pnlEmpty.CanvasColor = System.Drawing.SystemColors.Control;
this.pnlEmpty.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;
this.pnlEmpty.DisabledBackColor = System.Drawing.Color.Empty;
this.pnlEmpty.Location = new System.Drawing.Point(1272, 14);
this.pnlEmpty.Location = new System.Drawing.Point(1321, 15);
this.pnlEmpty.Name = "pnlEmpty";
this.pnlEmpty.Size = new System.Drawing.Size(58, 23);
this.pnlEmpty.Style.Alignment = System.Drawing.StringAlignment.Center;
@@ -420,7 +466,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
this.splitContainer2.Panel2.Controls.Add(this.ucSplitPage1);
this.splitContainer2.Panel2.Controls.Add(this.statusBar);
this.splitContainer2.Size = new System.Drawing.Size(1643, 476);
this.splitContainer2.Size = new System.Drawing.Size(1719, 476);
this.splitContainer2.SplitterDistance = 447;
this.splitContainer2.TabIndex = 0;
//
@@ -428,7 +474,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
this.pbLoading.Anchor = System.Windows.Forms.AnchorStyles.None;
this.pbLoading.Image = global::TemuKJD.Properties.Resources.loading_blue;
this.pbLoading.Location = new System.Drawing.Point(796, 198);
this.pbLoading.Location = new System.Drawing.Point(834, 198);
this.pbLoading.Name = "pbLoading";
this.pbLoading.Size = new System.Drawing.Size(50, 50);
this.pbLoading.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
@@ -439,20 +485,20 @@ namespace TemuKJD.PDDForms.ProductSelectForms
//
this.dgvData.AllowUserToAddRows = false;
this.dgvData.AllowUserToDeleteRows = false;
dataGridViewCellStyle9.BackColor = System.Drawing.Color.WhiteSmoke;
this.dgvData.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle9;
dataGridViewCellStyle1.BackColor = System.Drawing.Color.WhiteSmoke;
this.dgvData.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
this.dgvData.AutoRowNumber = true;
this.dgvData.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dgvData.BackgroundColor = System.Drawing.Color.White;
this.dgvData.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255)))));
dataGridViewCellStyle10.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle10.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle10.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
dataGridViewCellStyle10.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle10.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle10.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle10.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dgvData.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle10;
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle2.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dgvData.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
this.dgvData.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvData.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colSelect,
@@ -461,7 +507,8 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.c_goodsid,
this.Column2,
this.Column3,
this.Column4});
this.Column4,
this.colDetectionResult});
this.dgvData.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvData.Location = new System.Drawing.Point(4, 0);
this.dgvData.MultiSelectCheckBox = true;
@@ -469,9 +516,44 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.dgvData.ReadOnly = true;
this.dgvData.RowTemplate.Height = 60;
this.dgvData.ShowCellErrors = false;
this.dgvData.Size = new System.Drawing.Size(1635, 447);
this.dgvData.Size = new System.Drawing.Size(1711, 447);
this.dgvData.TabIndex = 5;
//
// ucSplitPage1
//
this.ucSplitPage1.CurrentPage = 1;
this.ucSplitPage1.Dock = System.Windows.Forms.DockStyle.Right;
this.ucSplitPage1.IsShowPageSize = true;
this.ucSplitPage1.IsShowTotalCount = true;
this.ucSplitPage1.Location = new System.Drawing.Point(1257, 0);
this.ucSplitPage1.Margin = new System.Windows.Forms.Padding(0);
this.ucSplitPage1.MaxPageSizeValue = 2000;
this.ucSplitPage1.Name = "ucSplitPage1";
this.ucSplitPage1.PageSize = 50;
this.ucSplitPage1.Size = new System.Drawing.Size(462, 25);
this.ucSplitPage1.TabIndex = 4;
this.ucSplitPage1.TotalCount = ((long)(0));
this.ucSplitPage1.TotalPage = 1;
//
// statusBar
//
this.statusBar.BackColor = System.Drawing.Color.White;
this.statusBar.Dock = System.Windows.Forms.DockStyle.Fill;
this.statusBar.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.statusBar.Location = new System.Drawing.Point(0, 0);
this.statusBar.Margin = new System.Windows.Forms.Padding(0);
this.statusBar.Name = "statusBar";
this.statusBar.Size = new System.Drawing.Size(1719, 25);
this.statusBar.TabIndex = 1;
this.statusBar.Visible = false;
//
// numericUpDown1
//
this.numericUpDown1.Location = new System.Drawing.Point(918, 274);
this.numericUpDown1.Name = "numericUpDown1";
this.numericUpDown1.Size = new System.Drawing.Size(120, 21);
this.numericUpDown1.TabIndex = 7;
//
// colSelect
//
this.colSelect.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None;
@@ -537,38 +619,20 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.Column4.ReadOnly = true;
this.Column4.Width = 120;
//
// ucSplitPage1
// colDetectionResult
//
this.ucSplitPage1.CurrentPage = 1;
this.ucSplitPage1.Dock = System.Windows.Forms.DockStyle.Right;
this.ucSplitPage1.IsShowPageSize = true;
this.ucSplitPage1.IsShowTotalCount = true;
this.ucSplitPage1.Location = new System.Drawing.Point(1181, 0);
this.ucSplitPage1.Margin = new System.Windows.Forms.Padding(0);
this.ucSplitPage1.MaxPageSizeValue = 2000;
this.ucSplitPage1.Name = "ucSplitPage1";
this.ucSplitPage1.PageSize = 50;
this.ucSplitPage1.Size = new System.Drawing.Size(462, 25);
this.ucSplitPage1.TabIndex = 4;
this.ucSplitPage1.TotalCount = ((long)(0));
this.ucSplitPage1.TotalPage = 1;
//
// statusBar
//
this.statusBar.BackColor = System.Drawing.Color.White;
this.statusBar.Dock = System.Windows.Forms.DockStyle.Fill;
this.statusBar.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.statusBar.Location = new System.Drawing.Point(0, 0);
this.statusBar.Margin = new System.Windows.Forms.Padding(0);
this.statusBar.Name = "statusBar";
this.statusBar.Size = new System.Drawing.Size(1643, 25);
this.statusBar.TabIndex = 1;
this.statusBar.Visible = false;
this.colDetectionResult.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None;
this.colDetectionResult.HeaderText = "AI检测结果";
this.colDetectionResult.MinimumWidth = 50;
this.colDetectionResult.Name = "colDetectionResult";
this.colDetectionResult.ReadOnly = true;
this.colDetectionResult.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.colDetectionResult.Width = 160;
//
// CloudGoodsFavoriteForm
//
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.ClientSize = new System.Drawing.Size(1643, 530);
this.ClientSize = new System.Drawing.Size(1719, 530);
this.Controls.Add(this.splitContainer1);
this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.KeyPreview = true;
@@ -581,16 +645,13 @@ namespace TemuKJD.PDDForms.ProductSelectForms
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
this.splitContainer1.ResumeLayout(false);
this.panel1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.num_maxprice)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.num_minprice)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.num_maxsold)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.num_minsold)).EndInit();
this.splitContainer2.Panel1.ResumeLayout(false);
this.splitContainer2.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit();
this.splitContainer2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.pbLoading)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.dgvData)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
this.ResumeLayout(false);
}
@@ -609,11 +670,6 @@ namespace TemuKJD.PDDForms.ProductSelectForms
private System.Windows.Forms.Label label6;
private System.Windows.Forms.TextBox txt_title;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.NumericUpDown num_maxsold;
private System.Windows.Forms.NumericUpDown num_minsold;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.NumericUpDown num_maxprice;
private System.Windows.Forms.NumericUpDown num_minprice;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.ComboBox cmbCatSelect2;
@@ -621,7 +677,16 @@ namespace TemuKJD.PDDForms.ProductSelectForms
private System.Windows.Forms.ComboBox cmbCatSelect4;
private System.Windows.Forms.ComboBox cmbCatSelect3;
private System.Windows.Forms.Label label5r;
private System.Windows.Forms.ComboBox cb_Platform;
private System.Windows.Forms.ComboBox cb_Platformr;
private System.Windows.Forms.Panel panel1;
private DevComponents.DotNetBar.PanelEx btn_removefavorite;
private DevComponents.DotNetBar.PanelEx bt_importUploadList;
private DevComponents.DotNetBar.PanelEx btnSafeDetection;
private System.Windows.Forms.NumericUpDown numericUpDown1;
private UserComponent.UcNumericRange num_sold;
private UserComponent.UcNumericRange num_price;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.ComboBox cb_safe;
private System.Windows.Forms.DataGridViewCheckBoxColumn colSelect;
private System.Windows.Forms.DataGridViewTextBoxColumn c_Platform;
private System.Windows.Forms.DataGridViewImageColumn Column1;
@@ -629,8 +694,6 @@ namespace TemuKJD.PDDForms.ProductSelectForms
private System.Windows.Forms.DataGridViewTextBoxColumn Column2;
private System.Windows.Forms.DataGridViewTextBoxColumn Column3;
private System.Windows.Forms.DataGridViewTextBoxColumn Column4;
private System.Windows.Forms.Panel panel1;
private DevComponents.DotNetBar.PanelEx btn_removefavorite;
private DevComponents.DotNetBar.PanelEx bt_importUploadList;
private System.Windows.Forms.DataGridViewTextBoxColumn colDetectionResult;
}
}
}
@@ -1,3 +1,4 @@
using TemuKJD.BLL;
using AllBase;
using AllBase.Helper;
using AllBase.Model.Result;
@@ -19,28 +20,30 @@ using System.Net;
using System.Net.Http;
using System.Security.Policy;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using TemuKJD.BaseForms;
using TemuKJD.Extension;
using TemuKJD.ToolForms;
using TemuKJD.UserControls;
using TemuKJD.WXForms.ProductForms;
using Work;
using Work.GoodDetail;
using Work.Services;
namespace TemuKJD.PDDForms.ProductSelectForms
namespace TemuKJD.WXForms.ProductForms
{
public partial class CloudGoodsFavoriteForm : BaseForm
{
private GoodsSearchRequest search = new GoodsSearchRequest();
private List<ComboBox> catBoxList = new List<ComboBox>();
private BindingList<CloudGoods> dataList = new BindingList<CloudGoods>();
private CancellationTokenSource detection;
CheckBox chkSelectAll = null;
public CloudGoodsFavoriteForm()
{
InitializeComponent();
Disposed += (sender, e) => detection?.Cancel();
}
private void AdminCardCodeForm_Load(object sender, EventArgs e)
@@ -50,13 +53,14 @@ namespace TemuKJD.PDDForms.ProductSelectForms
InitCategory();
InitBindingDataGridView();
num_minsold.Value = 0;
num_maxsold.Value = 9999999;
num_minprice.Value = 0;
num_maxprice.Value = 999;
num_sold.LowerValue = 1;
num_sold.UpperValue = 9999999;
num_price.LowerValue = 1;
num_price.UpperValue = 9999999;
cb_Platformr.SelectedIndex = 0;
cb_safe.SelectedIndex = 0;
cb_Platform.SelectedIndex = 0;
ucSplitPage1.TotalCount = 0;
@@ -79,6 +83,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
this.Column3.DataPropertyName = nameof(CloudGoods.Price);
this.Column4.DataPropertyName = nameof(CloudGoods.SoldQuantity);
this.c_goodsid.DataPropertyName = nameof(CloudGoods.GoodsId);
this.colDetectionResult.DataPropertyName = nameof(CloudGoods.DetectionResult);
this.c_Platform.DataPropertyName = nameof(CloudGoods.Platform);
// 2. 设置列样式
// 时间列格式化
@@ -311,6 +316,8 @@ namespace TemuKJD.PDDForms.ProductSelectForms
}
private async void btnSearch_Click(object sender, EventArgs e)
{
if (detection != null) return;
activeSearches++;
try
{
this.pbLoading.Visible = true;
@@ -318,12 +325,13 @@ namespace TemuKJD.PDDForms.ProductSelectForms
UserFavoritesGoodsSearchRequest search = new UserFavoritesGoodsSearchRequest();
search.Platform = cb_Platform.SelectedIndex == 1 ? PlatformType.PDD : cb_Platform.SelectedIndex == 2 ? PlatformType.WX : PlatformType.None;
search.Platform = cb_Platformr.SelectedIndex == 1 ? PlatformType.PDD : cb_Platformr.SelectedIndex == 2 ? PlatformType.WX : PlatformType.None;
search.SafeStatus = cb_safe.SelectedIndex;
search.Title = txt_title.Text;
search.MinSoldQuantity = (int)num_minsold.Value;
search.MaxSoldQuantity = (int)num_maxsold.Value;
search.MinPrice = (long)num_minprice.Value * 100;
search.MaxPrice = (long)num_maxprice.Value * 100;
search.MinSoldQuantity = (int)num_sold.LowerValue;
search.MaxSoldQuantity = (int)num_sold.UpperValue;
search.MinPrice = (long)(num_price.LowerValue * 100);
search.MaxPrice = (long)(num_price.UpperValue * 100);
if (cmbCatSelect1.SelectedItem is PddCatInfo cat1)
{
search.CatId1 = (int)(cat1.cat_id);
@@ -349,7 +357,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
finally
{
this.pbLoading.Visible = false;
btnSearch.Enabled = true;
btnSearch.Enabled = --activeSearches == 0;
}
@@ -357,26 +365,36 @@ namespace TemuKJD.PDDForms.ProductSelectForms
private void pnlEmpty_Click(object sender, EventArgs e)
{
txt_title.Text = "";
num_minsold.Value = 0;
num_maxsold.Value = 99999;
num_minprice.Value = 0;
num_maxprice.Value = 999;
num_sold.LowerValue = 1;
num_sold.UpperValue = 9999999;
num_price.LowerValue = 1;
num_price.UpperValue = 9999999;
cmbCatSelect1.SelectedIndex = 0;
cb_Platform.SelectedIndex = 0;
cb_Platformr.SelectedIndex = 0;
cb_safe.SelectedIndex = 0;
ucSplitPage1.PageChange -= UcSplitPage1_PageChange;
ucSplitPage1.CurrentPage = 1;
ucSplitPage1.PageChange += UcSplitPage1_PageChange;
}
private int latestGoodsQuery;
private int activeSearches;
public async Task GetGoods(UserFavoritesGoodsSearchRequest request)
{
dataList.Clear();
var query = ++latestGoodsQuery;
PageData<CloudGoods> ret = await CloudGoodsService.SearchFavorites(request);
Invoke(new Action(() =>
{
ret.List.ForEach(a => dataList.Add(a));
if (query != latestGoodsQuery) return;
dataList.Clear();
foreach (var goods in ret.List)
{
dataList.Add(goods);
}
ucSplitPage1.TotalPage = Math.Max(1, ret.TotalPages);
ucSplitPage1.TotalCount = ret.Total;
this.pbLoading.Visible = false;
@@ -424,7 +442,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms
product_name = item.Title,
cat_name = catName,
product_get_type_text = item.Platform == 1 ? ("多多") : item.Platform == 2 ? ("微信") : ("未知"),
db_type = "云端数据",
db_type = "云端数据",
});
succ++;
}
@@ -444,13 +462,13 @@ namespace TemuKJD.PDDForms.ProductSelectForms
private async void btn_removefavorite_Click(object sender, EventArgs e)
{
int succ = 0;
int err = 0;
int err = 0;
try
{
this.pbLoading.Visible = true;
btn_removefavorite.Enabled = false;
var goodsIds = dataList.Where(a => a.IsSelected).Select(a => a.GoodsId).ToList();
if(goodsIds.Count==0)
if (goodsIds.Count == 0)
{
BaseMsgForm.ShowInfo($"请勾选商品");
return;
@@ -473,6 +491,69 @@ namespace TemuKJD.PDDForms.ProductSelectForms
btn_removefavorite.Enabled = true;
}
}
/// <summary>
/// 批量AI检测
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnSafeDetection_Click(object sender, EventArgs e)
{
if (detection != null)
{
detection.Cancel();
btnSafeDetection.Text = "正在停止";
btnSafeDetection.Enabled = false;
return;
}
if (!btnSearch.Enabled || !btn_removefavorite.Enabled)
{
BaseMsgForm.ShowInfo("请等待当前操作完成后再检测。");
return;
}
var selected = dataList.Where(x => x.IsSelected)
.Select(x => new ComplianceGoods
{
id = x.GoodsId.ToString(),
name = x.Title,
image = x.ThumbUrl,
type = ComplianceGoods.Cloud,
platform = x.Platform
}).ToList();
var locked = splitContainer1.Panel1.Controls.Cast<Control>().Where(c => c != panel1)
.Concat(panel1.Controls.Cast<Control>().Where(c => c != btnSafeDetection))
.Concat(new Control[] { splitContainer2 }).Where(c => c.Enabled).ToList();
var originalText = btnSafeDetection.Text;
detection = new CancellationTokenSource();
try
{
foreach (var control in locked) control.Enabled = false;
btnSafeDetection.Text = "停止检测";
var result = await GoodsComplianceService.DetectSelectedAsync(selected, ComplianceProductReader.Read, (id, status) =>
{
if (IsDisposed) return;
foreach (var goods in dataList.Where(x => x.GoodsId.ToString() == id))
goods.DetectionResult = status;
}, detection.Token);
if (!IsDisposed)
{
var title = result.Unfinished > 0 ? "AI 检测已停止" : "AI 检测完成";
BaseMsgForm.ShowInfo($"{title}:安全 {result.Safe},有风险 {result.Risk},失败 {result.Failed},未完成 {result.Unfinished}。");
}
}
catch (Exception ex) { if (!IsDisposed) MessageBox.Show(this, ex.Message); }
finally
{
detection.Dispose();
detection = null;
if (!IsDisposed)
{
foreach (var control in locked) control.Enabled = true;
btnSafeDetection.Text = originalText;
btnSafeDetection.Enabled = true;
}
}
}
}
@@ -1,5 +1,5 @@
namespace TemuKJD.PDDForms.ProductSelectForms
namespace TemuKJD.WXForms.ProductForms
{
partial class CloudGoodsForm
{
@@ -25,12 +25,11 @@ using TemuKJD.BaseForms;
using TemuKJD.Extension;
using TemuKJD.ToolForms;
using TemuKJD.UserControls;
using TemuKJD.WXForms.ProductForms;
using Work;
using Work.GoodDetail;
using Work.Services;
namespace TemuKJD.PDDForms.ProductSelectForms
namespace TemuKJD.WXForms.ProductForms
{
public partial class CloudGoodsForm : BaseForm
{
@@ -1540,54 +1540,7 @@ namespace TemuKJD.WXForms.ProductForms
/// <param name="noBanWx">校验微信禁售商品</param>
/// <returns></returns>
public static KJDDBProduct GetProductDetail(string prodDbId, out string errMsg, bool noViolationProduct = false, bool noBanWx = false)
{
errMsg = "";
if (string.IsNullOrEmpty(prodDbId))
{
errMsg = "产品编号为空";
return new KJDDBProduct();
}
List<KJDDBProduct> list = new List<KJDDBProduct>();
var url = BaseClass.BuildObjString("product/get_pdd_product_detail_list");
var data = UserLogin.GetSignaturex() + "&search_db_ids=" + prodDbId;
if (noViolationProduct)
data += "&wx_safe_status=安全商品,未校验";
if (noBanWx)
data += "&ban_wx=0";
if (BaseClass.SaLevel > 0 || BaseClass.UserAuthority.Contains("爆款数据") || BaseClass.UserAuthority.Contains("学员数据") || BaseClass.ParentAuthority.Contains("学员数据"))
data += "&special_prod_type=普通商品,爆款数据";
var html = GetHtml.PostUtf8(url, data);
if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200"))
html = GetHtml.PostUtf8(url, data);
if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200"))
html = GetHtml.PostUtf8(url, data);
if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200"))
{
return null;
}
var jArr = JObject.Parse(html)["data"];
if (jArr.Count() == 0)
return null;
var sProd = JsonHelper.SerializeObject(jArr);
try
{
list = JsonHelper.DeserializeJsonToList<KJDDBProduct>(sProd);
var item = list.FirstOrDefault();
if (item == null)
item = new KJDDBProduct();
return item;
}
catch (Exception ex)
{
return null;
}
}
=> ComplianceProductReader.GetProductDetail(prodDbId, out errMsg, noViolationProduct, noBanWx);
public static KJDDBProduct GetCloudProductDetail(TemuDBProductSelect selectProd, out string errMsg, bool noViolationProduct = false, bool noBanWx = false)
{
+23 -1
View File
@@ -418,7 +418,13 @@ namespace Work.Services
public static async Task<PageData<CloudGoods>> SearchFavorites(UserFavoritesGoodsSearchRequest request)
{
request.UserName = GetUserName();
return await StoreApiClient.Instance.CloudGoods.SearchFavorites(request);
var page = await StoreApiClient.Instance.CloudGoods.SearchFavorites(request);
foreach (var item in page.List)
{
item.DetectionResult = item.IsSafe == null ? "未检测" : item.IsSafe.Value ? "安全" : "违规";
}
return page;
}
/// <summary>
@@ -576,6 +582,8 @@ namespace Work.Services
/// 平台类型
/// </summary>
public PlatformType Platform { get; set; }
/// <summary>安全状态:0 全部(默认),1 安全,2 违规,3 未检测。</summary>
public int SafeStatus { get; set; }
/// <summary>
/// 页码(默认1)
@@ -629,6 +637,7 @@ namespace Work.Services
}
public class CloudGoods : BindableBase
{
public bool IsSelected
{
get => GetValue<bool>();
@@ -719,5 +728,18 @@ namespace Work.Services
/// 索引同步状态:0未同步 1已同步
/// </summary>
public byte IndexStatus { get; set; }
/// <summary>
/// 安全检测
/// </summary>
public bool? IsSafe { get; set; }
[JsonIgnore]
public string? DetectionResult
{
get => GetValue<string>();
set => SetValue(value);
}
}
}
+143
View File
@@ -0,0 +1,143 @@
using AllBase.Model;
using AllBase.Model.Result;
using AllBase.Model.Result.PDDResult;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Linq;
using TemuKJD.BLL.ImportConverts;
using AllBase;
using System.Collections.Generic;
using TemuKJD.BLL;
namespace Work.Services;
public static class ComplianceProductReader
{
public static Product Read(ComplianceGoods item)
{
KJDDBProduct db;
if (item.type == ComplianceGoods.Cloud)
db = new KJDDBProduct { product_json = CloudGoodsService.GetGoodsDetail(long.Parse(item.id)) };
else if (item.type == ComplianceGoods.Library)
{
db = GetProductDetail(item.id, out var message);
if (!string.IsNullOrEmpty(message)) throw new InvalidDataException(message);
}
else throw new InvalidDataException("未知商品来源:" + item.type);
if (string.IsNullOrWhiteSpace(db?.product_json)) throw new InvalidDataException("未取得商品详情。");
var raw = db.product_json;
var root = JObject.Parse(raw);
PDDProductInfo info;
string error = null;
var converter = new PDDProdFormConverter();
if (root.SelectToken("detail.galleries") is JArray)
info = root.ToObject<PDDProductInfo>();
else if (db.product_get_type_text == "厂商直采") info = converter.JstPddProdToPddConvert(out error, db);
else if (db.product_get_type_text == "微信店采") info = converter.WXProdToPddConvert(out error, db);
else if (db.product_get_type_text == "微信小店") info = converter.WXClientToPddConvert(out error, db);
else if (db.product_get_type_text == "1688厂家") info = converter._1688ClientToPddConvert(out error, db);
else if (db.product_get_type_text == "1688外部数据") info = converter._1688OutToPddConvert(out error, db);
else
{
var front = ConverterToPddFactory.SetContentConvert(raw);
if (front != null)
{
var originalId = front.GoodsId > 0 ? front.GoodsId.ToString() : db.spu_id;
return new Product
{
ProductId = item.type == ComplianceGoods.Cloud ? item.id : originalId,
Platform = item.platform.HasValue ? (PlatformType)item.platform.Value : front.Platform,
Title = front.Title ?? item.name, Description = front.Description,
MainImages = front.MainImages, DetailImages = front.DetailImages,
Skus = front.Skus?.Select(x => new ProductSku { SkuId = x.SkuId, ImageUrl = x.SpecImage }).ToList()
};
}
string converted;
if (raw.Contains("\"spec_keys\"") && raw.Contains("\"spec_values\""))
converted = converter.PddFrontToPddBackConvert(raw, isSaveDb: false);
else if (raw.Contains("\"goods_id\"") && raw.Contains("\"gallery\""))
converted = converter.PddFrontToPddBackConvert2(raw, isSaveDb: false);
else if (raw.Contains("\"topGallery\"") && raw.Contains("\"detailGallery\""))
converted = converter.PddFrontToPddBackConvert3(raw, isSaveDb: false);
else if (raw.Contains("\"商品编号\"") && raw.Contains("\"商品链接\""))
converted = converter.PddFrontToPddBackConvertV4(raw, isSaveDb: false);
else throw new InvalidDataException("不支持的商品详情格式。");
if (converted == null || !converted.StartsWith("success", StringComparison.Ordinal))
throw new InvalidDataException(converted ?? "商品详情转换失败。");
info = JObject.Parse(converted.Substring(7)).ToObject<PDDProductInfo>();
}
if (!string.IsNullOrEmpty(error) || info?.detail == null)
throw new InvalidDataException(error ?? "商品详情转换失败。");
var detail = info.detail;
var platform = db.product_get_type_text switch
{
"微信店采" or "微信小店" or "微信外部数据" or "微信" => PlatformType.WX,
"厂商直采" or "1688厂家" or "1688外部数据" => PlatformType.None,
_ => PlatformType.PDD
};
var productId = db.product_get_type_text == "厂商直采"
? (string)root.SelectToken("pddData.itemSpuId") ?? db.spu_id
: detail.goods_id > 0 ? detail.goods_id.ToString() : db.spu_id;
return new Product
{
Platform = item.platform.HasValue ? (PlatformType)item.platform.Value : platform,
ProductId = item.type == ComplianceGoods.Cloud ? item.id : productId,
Title = detail.goods_name ?? item.name, Description = detail.goods_desc,
MainImages = detail.galleries?.Where(x => x.type == 1).Select(x => x.url).ToList(),
DetailImages = detail.galleries?.Where(x => x.type == 2).Select(x => x.url).ToList(),
Skus = detail.sku?.Select(x => new ProductSku { SkuId = x.sku_id.ToString(), ImageUrl = x.thumb_url }).ToList()
};
}
public static KJDDBProduct GetProductDetail(string prodDbId, out string errMsg, bool noViolationProduct = false, bool noBanWx = false)
{
errMsg = "";
if (string.IsNullOrEmpty(prodDbId))
{
errMsg = "产品编号为空";
return new KJDDBProduct();
}
List<KJDDBProduct> list = new List<KJDDBProduct>();
var url = BaseClass.BuildObjString("product/get_pdd_product_detail_list");
var data = UserLogin.GetSignaturex() + "&search_db_ids=" + prodDbId;
if (noViolationProduct)
data += "&wx_safe_status=安全商品,未校验";
if (noBanWx)
data += "&ban_wx=0";
if (BaseClass.SaLevel > 0 || BaseClass.UserAuthority.Contains("爆款数据") || BaseClass.UserAuthority.Contains("学员数据") || BaseClass.ParentAuthority.Contains("学员数据"))
data += "&special_prod_type=普通商品,爆款数据";
var html = GetHtml.PostUtf8(url, data);
if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200"))
html = GetHtml.PostUtf8(url, data);
if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200"))
html = GetHtml.PostUtf8(url, data);
if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200"))
{
return null;
}
var jArr = JObject.Parse(html)["data"];
if (jArr.Count() == 0)
return null;
var sProd = JsonHelper.SerializeObject(jArr);
try
{
list = JsonHelper.DeserializeJsonToList<KJDDBProduct>(sProd);
var item = list.FirstOrDefault();
if (item == null)
item = new KJDDBProduct();
return item;
}
catch (Exception ex)
{
return null;
}
}
}
+265
View File
@@ -0,0 +1,265 @@
using AllBase.Helper;
using AllBase;
using AllBase.Model;
using Work.Store.Api;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Net.Http;
using System.Linq;
using System.Text;
namespace Work.Services;
public sealed class ComplianceGoods
{
public const string Cloud = "云端收藏";
public const string Library = "我的商品库";
public string id { get; set; }
public string name { get; set; }
public string image { get; set; }
public string type { get; set; }
public byte? platform { get; set; }
}
public sealed class ComplianceResult
{
public string Text { get; internal set; }
public string[] Hits { get; internal set; } = Array.Empty<string>();
public int HitImageNumber { get; internal set; }
public string Error { get; internal set; }
public string Status => Error != null ? "失败,可重试" : Hits.Length == 0 ? "安全" : "有风险";
public string Summary => Error != null ? "失败:" + Error :
Hits.Length == 0 ? Status : $"有风险:第 {HitImageNumber} 张图片命中关键词:{string.Join(",", Hits)}";
}
public sealed class ComplianceBatchResult
{
public int Safe { get; internal set; }
public int Risk { get; internal set; }
public int Failed { get; internal set; }
public int Unfinished { get; internal set; }
}
public static class GoodsComplianceService
{
private static readonly HttpClient ImageClient = new HttpClient(new HttpClientHandler { UseCookies = false })
{
Timeout = TimeSpan.FromSeconds(30),
MaxResponseContentBufferSize = 32 * 1024 * 1024
};
private static readonly SemaphoreSlim BatchGate = new SemaphoreSlim(1, 1);
public static async Task<ComplianceBatchResult> DetectSelectedAsync(IReadOnlyList<ComplianceGoods> items,
Func<ComplianceGoods, Product> readProduct, Action<string, string> update, CancellationToken cancellationToken = default)
{
if (items == null || items.Count == 0) throw new ArgumentException("请选择商品。", nameof(items));
if (readProduct == null) throw new ArgumentNullException(nameof(readProduct));
if (update == null) throw new ArgumentNullException(nameof(update));
if (!BatchGate.Wait(0)) throw new InvalidOperationException("已有商品正在检测。");
try
{
var options = GlobalConfig.GlobalSettings.GoodsCompliance;
var words = ValidateOptions(options);
var path = QueuePath();
var pending = ReadPending(path);
if (items.Any(InvalidItem)) throw new ArgumentException("缺少商品 ID 或来源。", nameof(items));
var selected = items.GroupBy(x => Tuple.Create(x.type, x.id)).Select(g => g.First()).ToList();
var existing = new HashSet<Tuple<string, string>>(pending.Select(x => Tuple.Create(x.type, x.id)));
pending.AddRange(selected.Where(x => existing.Add(Tuple.Create(x.type, x.id))));
SavePending(path, pending);
var batch = new ComplianceBatchResult { Unfinished = selected.Count };
foreach (var item in selected)
{
if (cancellationToken.IsCancellationRequested) break;
update(item.id, "读取详情中");
ComplianceResult result;
try
{
var product = await Task.Run(() => readProduct(item), cancellationToken);
result = await DetectProductAsync(product, options, words,
number => update(item.id, $"正在检测第 {number} 张图片"), cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
update(item.id, "已停止");
break;
}
catch (Exception ex) { result = new ComplianceResult { Error = ex.Message }; }
if (result.Error == null)
{
var remaining = pending.Where(x => x.type != item.type || x.id != item.id).ToList();
try { SavePending(path, remaining); pending = remaining; }
catch (Exception ex) { result.Error = ex.Message; }
}
if (result.Error != null) batch.Failed++;
else if (result.Hits.Length > 0) batch.Risk++;
else batch.Safe++;
batch.Unfinished--;
update(item.id, result.Summary);
}
return batch;
}
finally { BatchGate.Release(); }
}
private static string QueuePath()
{
var username = BaseClass.UserName;
if (string.IsNullOrWhiteSpace(username)) throw new InvalidOperationException("请先登录。");
var user = Convert.ToBase64String(Encoding.UTF8.GetBytes(username)).Replace('/', '_').Replace('+', '-');
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data", "config", "goods_compliance_" + user + ".json");
}
private static bool InvalidItem(ComplianceGoods item) =>
item == null || string.IsNullOrWhiteSpace(item.id) || string.IsNullOrWhiteSpace(item.type);
private static List<ComplianceGoods> ReadPending(string path)
{
if (!File.Exists(path)) return new List<ComplianceGoods>();
var items = JsonConvert.DeserializeObject<List<ComplianceGoods>>(File.ReadAllText(path));
if (items == null || items.Any(InvalidItem))
throw new InvalidDataException("待检测配置无效,请检查 " + path);
return items;
}
private static void SavePending(string path, List<ComplianceGoods> items)
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
var temp = path + ".tmp";
File.WriteAllText(temp, JsonConvert.SerializeObject(items));
if (File.Exists(path)) File.Replace(temp, path, null);
else File.Move(temp, path);
}
public static async Task DetectAsync(IReadOnlyList<Product> products, ComplianceOptions options,
Action<Product, ComplianceResult> completed, Action<Product> started = null)
{
if (products == null) throw new ArgumentNullException(nameof(products));
if (completed == null) throw new ArgumentNullException(nameof(completed));
var words = ValidateOptions(options);
foreach (var product in products)
{
started?.Invoke(product);
completed(product, await DetectProductAsync(product, options, words));
}
}
private static string[] ValidateOptions(ComplianceOptions options)
{
var words = Keywords(options?.Keywords);
if (words.Length == 0) throw new ArgumentException("请先在系统设置的「商品 AI 检测」中保存 AI 检测关键词。");
if (options.MainCount <= 0 || options.DetailCount < 0)
throw new ArgumentException("请检查检测关键词和图片数量。");
return words;
}
private static async Task<ComplianceResult> DetectProductAsync(Product product, ComplianceOptions options,
string[] words, Action<int> imageStarted = null, CancellationToken cancellationToken = default)
{
var result = new ComplianceResult();
try
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(product?.ProductId)) throw new InvalidDataException("缺少商品 ID。");
var urls = SelectImages(product.MainImages, options.MainCount)
.Concat(SelectImages(product.DetailImages, options.DetailCount));
if (options.IncludeSku) urls = urls.Concat(SelectImages(product.Skus?.Select(s => s?.ImageUrl), null));
var texts = new List<string>();
int imageNumber = 0;
foreach (var url in urls.Distinct(StringComparer.Ordinal))
{
cancellationToken.ThrowIfCancellationRequested();
imageNumber++;
imageStarted?.Invoke(imageNumber);
try
{
var jpeg = await DownloadInspectionImageAsync(url);
cancellationToken.ThrowIfCancellationRequested();
var lines = await StoreApiClient.Instance.CloudGoods.RecognizeAsync(BaseClass.GetMd5(url), jpeg);
var imageText = string.Join(Environment.NewLine, lines);
texts.AddRange(lines);
result.Hits = Matches(imageText, words);
if (result.Hits.Length > 0)
{
result.HitImageNumber = imageNumber;
break;
}
}
catch (Exception ex) when (!cancellationToken.IsCancellationRequested)
{
throw new InvalidDataException($"第 {imageNumber} 张图片检测失败:{ex.Message}", ex);
}
}
cancellationToken.ThrowIfCancellationRequested();
if (imageNumber == 0) throw new InvalidDataException("所选检测范围内没有图片。");
result.Text = string.Join(Environment.NewLine, texts);
try
{
await StoreApiClient.Instance.CloudGoods.SaveProductSafety(new SaveProductSafetyRequest
{
UserName = GetUserName(),
ProductId = product.ProductId,
IsSafe = result.Hits.Length == 0
});
}
catch (Exception ex)
{
result.Error = $"检测结果为{result.Status},上报失败:{ex.Message}";
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }
catch (Exception ex) { result.Error = ex.Message; }
return result;
}
private static IEnumerable<string> SelectImages(IEnumerable<string> urls, int? count) =>
(urls ?? Enumerable.Empty<string>()).Where(url => !string.IsNullOrWhiteSpace(url)).Select(url => url.Trim())
.Distinct(StringComparer.Ordinal).Take(count ?? int.MaxValue);
private static async Task<byte[]> DownloadInspectionImageAsync(string url)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) ||
(uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
throw new InvalidDataException("图片链接必须为 HTTP 或 HTTPS 地址。");
var bytes = await ImageClient.GetByteArrayAsync(uri).ConfigureAwait(false);
const int maxSide = 600;
using (var input = new MemoryStream(bytes))
using (var source = Image.FromStream(input))
{
var scale = Math.Min(1d, (double)maxSide / Math.Max(source.Width, source.Height));
int width = Math.Max(1, (int)Math.Round(source.Width * scale));
int height = Math.Max(1, (int)Math.Round(source.Height * scale));
using var canvas = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using var output = new MemoryStream();
using (var g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(source, new Rectangle(0, 0, width, height),
0, 0, source.Width, source.Height, GraphicsUnit.Pixel);
}
canvas.Save(output, ImageFormat.Jpeg);
return output.ToArray();
}
}
public static string[] Keywords(string value) =>
System.Text.RegularExpressions.Regex.Split(value ?? "", @"[,,;;|\s]+")
.Where(x => x.Length > 0).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
public static string[] Matches(string recognized, string[] keywords) =>
keywords.Where(x => recognized.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0).ToArray();
public static string GetUserName()
{
return string.IsNullOrEmpty(BaseClass.ParentUserName) ? BaseClass.UserName : BaseClass.ParentUserName;
}
}
+3 -1
View File
@@ -8,10 +8,12 @@ public sealed class ApiException : HttpException
{
public int? Code { get; }
public JToken ResponseData { get; }
public string TraceId { get; }
public ApiException(string message, int? code, JToken responseData) : base(message)
public ApiException(string message, int? code, JToken responseData, string traceId = null) : base(message)
{
Code = code;
ResponseData = responseData;
TraceId = traceId;
}
}
+119 -62
View File
@@ -1,4 +1,5 @@
using CsharpHttpHelper;
using AllBase.Model.Result;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@@ -6,6 +7,8 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Work.Store.Api;
@@ -14,16 +17,14 @@ public sealed class ApiTransport
{
private readonly string _baseUrl;
private string _token;
private Func<string> _login;
internal object SessionGate { get; } = new object();
private LoginRequest _credentials;
internal SemaphoreSlim SessionGate { get; } = new SemaphoreSlim(1, 1);
internal void SetSession(string token, Func<string> login)
// 调用方持有 SessionGate。
internal void SetSession(string token, LoginRequest credentials)
{
lock (SessionGate)
{
_token = token;
_login = login;
}
_token = token;
_credentials = credentials;
}
public ApiTransport(string baseUrl)
@@ -39,29 +40,102 @@ public sealed class ApiTransport
/// <summary>仅存于内存;手动赋值会清除自动登录凭据,设为 null 退出本地会话。</summary>
public string Token
{
get { lock (SessionGate) return _token; }
get
{
SessionGate.Wait();
try { return _token; }
finally { SessionGate.Release(); }
}
set
{
if (value != null && (string.IsNullOrWhiteSpace(value) || value.IndexOfAny(new[] { '\r', '\n' }) >= 0))
throw new ArgumentException("Token 不能为空白或包含换行。", nameof(value));
SetSession(value, null);
SessionGate.Wait();
try { SetSession(value, null); }
finally { SessionGate.Release(); }
}
}
/// <summary>同步请求;无 data 时返回 default(T),界面调用需放在后台线程。</summary>
public T Send<T>(string route, MethodType method, object body = null,
IDictionary<string, string> query = null, bool authenticated = true,
int timeoutMilliseconds = 30000)
{
var item = CreateRequest(route, method, body, query, timeoutMilliseconds);
string token = null;
if (authenticated)
{
//SessionGate.Wait();
//try { token = _token ?? throw new HttpException("请先登录。") { IsOnline = false }; }
//finally { SessionGate.Release(); }
}
for (int attempt = 0; ; attempt++)
{
if (token != null) item.Header["Authorization"] = "Bearer " + token;
var response = HttpHelper.Request(item);
try { return ReadResult<T>(response); }
catch (HttpException ex) when (authenticated && !ex.IsOnline)
{
SessionGate.Wait();
try
{
if (_token == token)
{
_token = null;
if (attempt == 0 && _credentials != null)
_token = AuthApi.LoginCore(this, _credentials).Token;
}
if (attempt != 0 || _token == null) throw;
token = _token;
}
finally { SessionGate.Release(); }
}
}
}
public async Task<T> SendAsync<T>(string route, MethodType method, object body = null,
IDictionary<string, string> query = null, bool authenticated = true,
int timeoutMilliseconds = 30000)
{
var item = CreateRequest(route, method, body, query, timeoutMilliseconds);
string token = null;
if (authenticated)
{
//await SessionGate.WaitAsync().ConfigureAwait(false);
//try { token = _token ?? throw new HttpException("请先登录。") { IsOnline = false }; }
//finally { SessionGate.Release(); }
}
for (int attempt = 0; ; attempt++)
{
if (token != null) item.Header["Authorization"] = "Bearer " + token;
var response = await HttpHelper.RequestAsync(item).ConfigureAwait(false);
try { return ReadResult<T>(response); }
catch (HttpException ex) when (authenticated && !ex.IsOnline)
{
await SessionGate.WaitAsync().ConfigureAwait(false);
try
{
if (_token == token)
{
_token = null;
if (attempt == 0 && _credentials != null)
_token = (await AuthApi.LoginCoreAsync(this, _credentials).ConfigureAwait(false)).Token;
}
if (attempt != 0 || _token == null) throw;
token = _token;
}
finally { SessionGate.Release(); }
}
}
}
private HttpItem CreateRequest(string route, MethodType method, object body,
IDictionary<string, string> query, int timeoutMilliseconds)
{
if (string.IsNullOrWhiteSpace(route) ||
!Regex.IsMatch(route, @"\A[A-Za-z0-9_-]+(/[A-Za-z0-9_-]+)*\z"))
throw new ArgumentException("接口必须使用相对路由,例如 Auth/Login。", nameof(route));
if (body != null) Validator.ValidateObject(body, new ValidationContext(body), true);
if (timeoutMilliseconds <= 0) throw new ArgumentOutOfRangeException(nameof(timeoutMilliseconds));
string token = authenticated ? Token : null;
if (authenticated && token == null)
throw new HttpException("请先登录。") { IsOnline = false };
var url = new StringBuilder(_baseUrl).Append(route);
if (query != null)
{
@@ -74,7 +148,7 @@ public sealed class ApiTransport
first = false;
}
}
var item = new HttpItem
return new HttpItem
{
URL = url.ToString(),
Method = method,
@@ -87,54 +161,31 @@ public sealed class ApiTransport
ReadWriteTimeout = timeoutMilliseconds,
Allowautoredirect = false
};
for (int attempt = 0; ; attempt++)
{
if (token != null) item.Header["Authorization"] = "Bearer " + token;
var response = HttpHelper.Request(item);
int status = (int)response.StatusCode;
if (status == 0) throw new HttpException("无法连接服务器或请求超时。");
}
JObject result = null;
JsonException parseError = null;
try { result = JObject.Parse(response.Html ?? ""); }
catch (JsonException ex) { parseError = ex; }
private static T ReadResult<T>(HttpResult response)
{
int status = (int)response.StatusCode;
if (status == 0) throw new HttpException("无法连接服务器或请求超时。");
var code = result?["code"];
if (status == 401 || (code?.Type == JTokenType.Integer && code.ToString() == "401"))
{
if (authenticated)
{
lock (SessionGate)
{
// 并发旧请求复用已更新的 Token;同一请求最多重新发送一次。
if (_token == token)
{
_token = null;
if (attempt == 0 && _login != null)
_token = _login();
}
if (attempt == 0 && _token != null)
{
token = _token;
continue;
}
}
}
throw new HttpException(result?["msg"]?.ToString() ?? "登录已失效,请重新登录。") { IsOnline = false };
}
if (status < 200 || status >= 300)
throw new HttpException($"请求失败(HTTP {status}):{result?["msg"]}");
if (result == null)
throw new HttpException("服务器响应不是有效的 JSON 对象。", parseError);
// 先检查业务码,避免错误响应的 data 结构掩盖服务端错误。
if (code?.Type != JTokenType.Integer || code.ToString() != "200")
throw new ApiException("请求失败:" + (result["msg"]?.ToString() ?? "响应缺少成功状态码。"),
code?.Type == JTokenType.Integer && int.TryParse(code.ToString(), out var businessCode) ? businessCode : (int?)null,
result["data"]);
if (result["data"] == null || result["data"].Type == JTokenType.Null) return default;
try { return result["data"].ToObject<T>(); }
catch (JsonException ex) { throw new HttpException("服务器响应 data 格式无效。", ex); }
}
Result<JToken> result = null;
JsonException parseError = null;
try { result = JsonConvert.DeserializeObject<Result<JToken>>(response.Html ?? ""); }
catch (JsonException ex) { parseError = ex; }
if (status == 401 || (result?.Success == false && result.Code == 401))
throw new HttpException(result?.Msg ?? "登录已失效,请重新登录。") { IsOnline = false };
// 统一错误响应即使使用 HTTP 4xx/5xx,也保留业务码、数据和追踪 ID。
if (result?.Success == false)
throw new ApiException("请求失败:" + (result.Msg ?? $"业务码 {result.Code}。"),
result.Code, result.Data, result.TraceId);
if (status < 200 || status >= 300)
throw new HttpException($"请求失败(HTTP {status}):{result?.Msg}");
if (result?.Success == null)
throw new HttpException("服务器响应不是有效的 Result 格式。", parseError);
if (result.Data == null || result.Data.Type == JTokenType.Null) return default;
try { return result.Data.ToObject<T>(); }
catch (JsonException ex) { throw new HttpException("服务器响应 data 格式无效。", ex); }
}
public T GetRequired<T>(string route, MethodType method, object body = null,
@@ -142,5 +193,11 @@ public sealed class ApiTransport
int timeoutMilliseconds = 30000) where T : class
=> Send<T>(route, method, body, query, authenticated, timeoutMilliseconds)
?? throw new HttpException("服务器响应缺少 data。");
public async Task<T> GetRequiredAsync<T>(string route, MethodType method, object body = null,
IDictionary<string, string> query = null, bool authenticated = true,
int timeoutMilliseconds = 30000) where T : class
=> await SendAsync<T>(route, method, body, query, authenticated, timeoutMilliseconds).ConfigureAwait(false)
?? throw new HttpException("服务器响应缺少 data。");
}
+19 -5
View File
@@ -1,5 +1,6 @@
using CsharpHttpHelper;
using System;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
@@ -52,17 +53,30 @@ public sealed class AuthApi
DeviceId = _deviceId ?? request.DeviceId,
DeviceName = request.DeviceName
};
lock (_transport.SessionGate)
_transport.SessionGate.Wait();
try
{
var result = LoginCore(credentials);
_transport.SetSession(result.Token, () => LoginCore(credentials).Token);
var result = LoginCore(_transport, credentials);
_transport.SetSession(result.Token, credentials);
return result;
}
finally { _transport.SessionGate.Release(); }
}
private LoginResult LoginCore(LoginRequest request)
internal static LoginResult LoginCore(ApiTransport transport, LoginRequest request)
{
var result = transport.GetRequired<LoginResult>("Auth/Login", MethodType.Post, request, authenticated: false);
return ValidateToken(result);
}
internal static async Task<LoginResult> LoginCoreAsync(ApiTransport transport, LoginRequest request)
{
var result = await transport.GetRequiredAsync<LoginResult>("Auth/Login", MethodType.Post, request, authenticated: false).ConfigureAwait(false);
return ValidateToken(result);
}
private static LoginResult ValidateToken(LoginResult result)
{
var result = _transport.GetRequired<LoginResult>("Auth/Login", MethodType.Post, request, authenticated: false);
if (result == null || string.IsNullOrWhiteSpace(result.Token) ||
result.Token.IndexOfAny(new[] { '\r', '\n' }) >= 0 ||
!string.Equals(result.TokenType, "Bearer", StringComparison.OrdinalIgnoreCase))
+62 -31
View File
@@ -1,55 +1,86 @@
using AllBase.Model.Result;
using AllBase;
using AllBase.Helper;
using AllBase.Model.Result;
using CsharpHttpHelper;
using NaiBa.Util.Helper;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Work.Services;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
namespace Work.Store.Api;
/// <summary>云商品接口,沿用服务端不带版本号的路由。</summary>
public sealed class CloudGoodsApi
{
private readonly ApiTransport _transport;
private readonly ApiTransport _transport;
public CloudGoodsApi(ApiTransport transport)
=> _transport = transport ?? throw new ArgumentNullException(nameof(transport));
public CloudGoodsApi(ApiTransport transport)
=> _transport = transport ?? throw new ArgumentNullException(nameof(transport));
public Task<PageData<CloudGoods>> GetGoods(GoodsSearchRequest request)
=> Task.Run(() => _transport.GetRequired<PageData<CloudGoods>>("CloudGoods/SearchGoods", MethodType.Post, request));
public Task<PageData<CloudGoods>> GetGoods(GoodsSearchRequest request)
=> _transport.GetRequiredAsync<PageData<CloudGoods>>("CloudGoods/SearchGoods", MethodType.Post, request);
public string GetGoodsDetail(long goodsId)
=> _transport.GetRequired<string>("CloudGoods/GetGoodsRawData", MethodType.Post,
query: new Dictionary<string, string> { ["goodsId"] = goodsId.ToString(CultureInfo.InvariantCulture) });
public string GetGoodsDetail(long goodsId)
=> _transport.GetRequired<string>("CloudGoods/GetGoodsRawData", MethodType.Post,
query: new Dictionary<string, string> { ["goodsId"] = goodsId.ToString(CultureInfo.InvariantCulture) });
public Task<List<ReportGoodsViolationResult>> ReportViolations(List<ReportGoodsViolationItem> items)
=> Task.Run(() => _transport.GetRequired<List<ReportGoodsViolationResult>>("CloudGoods/ReportViolations", MethodType.Post, items,
timeoutMilliseconds: 5000));
public Task<List<ReportGoodsViolationResult>> ReportViolations(List<ReportGoodsViolationItem> items)
=> _transport.GetRequiredAsync<List<ReportGoodsViolationResult>>("CloudGoods/ReportViolations", MethodType.Post, items,
timeoutMilliseconds: 5000);
public List<GoodsViolationStatus> GetViolationStatus(List<long> goodsIds)
=> _transport.GetRequired<List<GoodsViolationStatus>>("CloudGoods/GetViolationStatus", MethodType.Post, goodsIds,
timeoutMilliseconds: 5000);
public List<GoodsViolationStatus> GetViolationStatus(List<long> goodsIds)
=> _transport.GetRequired<List<GoodsViolationStatus>>("CloudGoods/GetViolationStatus", MethodType.Post, goodsIds,
timeoutMilliseconds: 5000);
public Task RecordViolationLogs(IReadOnlyList<RecordViolationLogRequest> requests)
=> Task.Run(() => _transport.Send<object>("CloudGoods/RecordViolationLog", MethodType.Post, requests,
timeoutMilliseconds: 5000));
public Task RecordViolationLogs(IReadOnlyList<RecordViolationLogRequest> requests)
=> _transport.SendAsync<object>("CloudGoods/RecordViolationLog", MethodType.Post, requests,
timeoutMilliseconds: 5000);
public Task<List<CloudGoodsService.ViolationReason>> GetViolationReasons()
=> Task.Run(() => _transport.GetRequired<List<CloudGoodsService.ViolationReason>>("CloudGoods/GetViolationReasons", MethodType.Get));
public Task<List<CloudGoodsService.ViolationReason>> GetViolationReasons()
=> _transport.GetRequiredAsync<List<CloudGoodsService.ViolationReason>>("CloudGoods/GetViolationReasons", MethodType.Get);
public Task<List<GoodsViolationLogEntity>> GetViolationLogs(GetViolationLogsRequest request)
=> Task.Run(() => _transport.GetRequired<List<GoodsViolationLogEntity>>("CloudGoods/GetViolationLogs", MethodType.Post, request,
timeoutMilliseconds: 5000));
public Task<List<GoodsViolationLogEntity>> GetViolationLogs(GetViolationLogsRequest request)
=> _transport.GetRequiredAsync<List<GoodsViolationLogEntity>>("CloudGoods/GetViolationLogs", MethodType.Post, request,
timeoutMilliseconds: 5000);
public Task<PageData<CloudGoods>> SearchFavorites(UserFavoritesGoodsSearchRequest request)
=> Task.Run(() => _transport.GetRequired<PageData<CloudGoods>>("CloudGoods/SearchFavorites", MethodType.Post, request));
public Task<PageData<CloudGoods>> SearchFavorites(UserFavoritesGoodsSearchRequest request)
=> _transport.GetRequiredAsync<PageData<CloudGoods>>("CloudGoods/SearchFavorites", MethodType.Post, request);
public Task<int> Favorite(string userName, List<long> goodsIds)
=> Task.Run(() => _transport.Send<int>("CloudGoods/AddFavorite", MethodType.Post,
new { UserName = userName, GoodsIds = goodsIds }));
public Task<int> Favorite(string userName, List<long> goodsIds)
=> _transport.SendAsync<int>("CloudGoods/AddFavorite", MethodType.Post,
new { UserName = userName, GoodsIds = goodsIds });
public Task<int> RemoveFavorites(string userName, List<long> goodsIds)
=> Task.Run(() => _transport.Send<int>("CloudGoods/RemoveFavorites", MethodType.Post,
new { UserName = userName, GoodsIds = goodsIds }));
public Task<int> RemoveFavorites(string userName, List<long> goodsIds)
=> _transport.SendAsync<int>("CloudGoods/RemoveFavorites", MethodType.Post,
new { UserName = userName, GoodsIds = goodsIds });
/// <summary>识别上传图片中的文字和品牌 Logo。</summary>
public async Task<List<string>> RecognizeAsync(string urlMd5, byte[] jpeg)
{
int sleep = 0;
if (EnvironmentHelper.IsProduction)
{
sleep = RandomHelper.GetRandom(600, 1200);
}
var minimumDuration = Task.Delay(TimeSpan.FromMilliseconds(sleep));
var request = new { UserName = BaseClass.UserName, Md5 = urlMd5, Image = Convert.ToBase64String(jpeg) };
var recognitionTask = _transport.GetRequiredAsync<List<string>>("CloudGoods/InspectImage", MethodType.Post, request);
await Task.WhenAll(minimumDuration, recognitionTask).ConfigureAwait(false);
return await recognitionTask.ConfigureAwait(false);
}
public async Task SaveProductSafety(SaveProductSafetyRequest request)
{
var ret = await _transport.SendAsync<int>("CloudGoods/SaveProductSafety", MethodType.Post, new
{
UserName = request.UserName,
ProductId = request.ProductId,
IsSafe = request.IsSafe
});
}
}
+70
View File
@@ -0,0 +1,70 @@
using AllBase;
using AllBase.Helper;
using CsharpHttpHelper;
using NaiBa.Util.Helper;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
namespace Work.Store.Api;
public sealed class GoodsComplianceApi
{
private readonly ApiTransport transport;
internal GoodsComplianceApi(ApiTransport transport) => this.transport = transport;
///// <summary>上报平台商品的安全检测记录。</summary>
//public Task SaveProductSafety(SaveProductSafetyRequest request)
//{
// request.UserName = BaseClass.UserName;
// return transport.SendAsync<object>("ProductInspection/SaveProductSafety", MethodType.Post,
// request ?? throw new ArgumentNullException(nameof(request)));
//}
/// <summary>按平台和商品 ID 批量查询安全记录。</summary>
public Task<List<ProductSafetyDto>> QueryProductSafety(QueryProductSafetyRequest request)
{
request.UserName = BaseClass.UserName;
return transport.GetRequiredAsync<List<ProductSafetyDto>>("ProductInspection/QueryProductSafety", MethodType.Post,
request ?? throw new ArgumentNullException(nameof(request)));
}
/// <summary>识别上传图片中的文字和品牌 Logo。</summary>
public async Task<List<string>> RecognizeAsync(string urlMd5, byte[] jpeg)
{
int sleep = 0;
if (EnvironmentHelper.IsProduction)
{
sleep = RandomHelper.GetRandom(600, 1200);
}
var minimumDuration = Task.Delay(TimeSpan.FromMilliseconds(sleep));
var request = new { UserName = BaseClass.UserName, Md5 = urlMd5, Image = Convert.ToBase64String(jpeg) };
var recognitionTask = transport.GetRequiredAsync<List<string>>("ProductInspection/InspectImage", MethodType.Post, request);
await Task.WhenAll(minimumDuration, recognitionTask).ConfigureAwait(false);
return await recognitionTask.ConfigureAwait(false);
}
}
public sealed class SaveProductSafetyRequest
{
[Required] public string UserName { get; set; } = "";
[Required] public string ProductId { get; set; } = "";
[Required] public bool? IsSafe { get; set; }
}
public sealed class QueryProductSafetyRequest
{
[Required] public string UserName { get; set; } = "";
[Required] public string PlatformId { get; set; } = "";
[Required] public string[] ProductIds { get; set; } = [];
}
public sealed record ProductSafetyDto(string ProductId, bool IsSafe)
{
[Required] public string UserName { get; set; } = "";
public string ProductId { get; } = ProductId;
public bool IsSafe { get; } = IsSafe;
}
+5 -3
View File
@@ -9,15 +9,16 @@ namespace Work.Store.Api;
/// <summary>全局共用一个用户会话与请求出口,窗体通过 Instance 访问。</summary>
public sealed class StoreApiClient
{
private static readonly string BaseUrl = EnvironmentHelper.IsDevelopment
? "http://localhost:14312/api/"
: "http://103.90.155.78:9001/api/";
private static readonly string BaseUrl = EnvironmentHelper.IsProduction
? "http://103.90.155.78:9001/api/"
: "http://localhost:14312/api/";
public static StoreApiClient Instance { get; } = new StoreApiClient();
public ApiTransport Transport { get; }
public AuthApi Auth { get; }
public CloudGoodsApi CloudGoods { get; }
//public GoodsComplianceApi GoodsCompliance { get; }
public ImageCacheApi ImageCache { get; }
/// <summary>当前 Windows 安装的 SHA-256 设备指纹,登录时作为 DeviceId 发送。</summary>
public string DeviceId { get; }
@@ -28,6 +29,7 @@ public sealed class StoreApiClient
Transport = new ApiTransport(BaseUrl);
Auth = new AuthApi(Transport, DeviceId);
CloudGoods = new CloudGoodsApi(Transport);
//GoodsCompliance = new GoodsComplianceApi(Transport);
ImageCache = new ImageCacheApi(Transport);
}
+4
View File
@@ -78,4 +78,8 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\TEMUKJD\BLL\PDDProdFormConverter.cs" Link="ImportConverters\PDDProdFormConverter.cs" />
<Compile Include="..\TEMUKJD\BLL\ImportConverters\*.cs" Link="ImportConverters\%(Filename)%(Extension)" />
</ItemGroup>
</Project>
+112
View File
@@ -0,0 +1,112 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using CsharpHttpHelper;
using Work.Store.Api;
// Compile against Work.dll, AllBase.dll and Newtonsoft.Json.dll; run beside these assemblies.
internal static class ApiTransportSmoke
{
private static void Check(bool passed)
{
if (!passed) throw new Exception("ApiTransport assertion failed.");
}
private static async Task<T> Respond<T>(int status, string json, Func<ApiTransport, Task<T>> request)
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var server = Task.Run(() =>
{
using (var client = listener.AcceptTcpClient())
using (var stream = client.GetStream())
using (var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, true))
{
while (!string.IsNullOrEmpty(reader.ReadLine())) { }
var body = Encoding.UTF8.GetBytes(json);
var header = Encoding.ASCII.GetBytes("HTTP/1.1 " + status + " Response\r\nContent-Type: application/json\r\nContent-Length: " + body.Length + "\r\nConnection: close\r\n\r\n");
stream.Write(header, 0, header.Length);
stream.Write(body, 0, body.Length);
}
});
try
{
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
return await request(new ApiTransport("http://127.0.0.1:" + port));
}
finally
{
listener.Stop();
await server;
}
}
private static Task<string> Send(ApiTransport transport)
{
return transport.SendAsync<string>("Test", MethodType.Get, authenticated: false, timeoutMilliseconds: 5000);
}
public static async Task Main()
{
Check(await Respond(200, "{\"code\":0,\"success\":true,\"data\":\"ok\"}", Send) == "ok");
Check(await Respond(200, "{\"code\":0,\"success\":true,\"data\":null}", Send) == null);
Check(await Respond(200, "{\"success\":true,\"data\":null}", transport =>
transport.SendAsync<int>("Test", MethodType.Get, authenticated: false)) == 0);
Check((await Respond(200, "{\"success\":true,\"data\":[1,2]}", transport =>
transport.SendAsync<int[]>("Test", MethodType.Get, authenticated: false)))[1] == 2);
foreach (var status in new[] { 200, 500 })
{
await Respond(status, "{\"data\":null,\"code\":5000,\"msg\":\"semaphore limit\",\"traceId\":\"trace-1\",\"success\":false}", async transport =>
{
try { await Send(transport); throw new Exception("Expected ApiException."); }
catch (ApiException ex)
{
Check(ex.Code == 5000 && ex.TraceId == "trace-1" && ex.Message.Contains("semaphore limit"));
}
return true;
});
}
await Respond(200, "{\"code\":200,\"success\":false,\"data\":{}}", async transport =>
{
try { await Send(transport); throw new Exception("Expected ApiException."); }
catch (ApiException ex) { Check(ex.Code == 200 && ex.ResponseData is Newtonsoft.Json.Linq.JObject); }
return true;
});
foreach (var json in new[] { "not json", "[]", "null", "{\"code\":\"invalid\",\"success\":true}",
"{\"code\":200,\"data\":\"legacy\"}", "{\"code\":200,\"success\":null}", "{}" })
{
await Respond(200, json, async transport =>
{
try { await Send(transport); throw new Exception("Expected response format error."); }
catch (HttpException ex) { Check(ex.Message.Contains("Result")); }
return true;
});
}
await Respond(401, "{\"code\":401,\"success\":false,\"msg\":\"expired\"}", async transport =>
{
try { await Send(transport); throw new Exception("Expected authentication error."); }
catch (HttpException ex) { Check(!ex.IsOnline && ex.Message == "expired"); }
return true;
});
Check(await Respond(200, "{\"success\":true,\"data\":\"ok\"}", transport =>
Task.Run(() => transport.GetRequired<string>("Test", MethodType.Get, authenticated: false))) == "ok");
var stalled = new TcpListener(IPAddress.Loopback, 0);
stalled.Start();
try
{
var transport = new ApiTransport("http://" + stalled.LocalEndpoint);
try
{
await transport.SendAsync<string>("Test", MethodType.Get, authenticated: false, timeoutMilliseconds: 250);
throw new Exception("Expected timeout.");
}
catch (HttpException ex) { Check(ex.Message.Contains("超时")); }
}
finally { stalled.Stop(); }
Console.WriteLine("ApiTransport smoke checks passed.");
}
}
+70
View File
@@ -0,0 +1,70 @@
using AllBase.Helper;
using AllBase;
using AllBase.Model;
using System.Collections.Generic;
using System;
using System.Linq;
using Work.Services;
// Compile against the built Work.dll, AllBase.dll and Newtonsoft.Json.dll and run in an isolated directory.
internal static class GoodsComplianceSmoke
{
private static void Check(bool passed)
{
if (!passed) throw new Exception("商品合规自测失败");
}
public static void Main()
{
var user = Guid.NewGuid().ToString();
BaseClass.UserName = user;
GlobalConfig.GlobalSettings.GoodsCompliance = new ComplianceOptions { Keywords = "测试" };
var cloud = new ComplianceGoods { id = "123", type = ComplianceGoods.Cloud };
var library = new ComplianceGoods { id = "123", type = ComplianceGoods.Library };
var attempted = new List<ComplianceGoods>();
var failures = new List<string>();
var batch = GoodsComplianceService.DetectSelectedAsync(new[] { cloud, cloud, library }, item =>
{
attempted.Add(item);
throw new InvalidOperationException("读取失败");
}, (id, status) => { if (status == "失败:读取失败") failures.Add(id); }).GetAwaiter().GetResult();
Check(attempted.SequenceEqual(new[] { cloud, library }) && failures.Count == 2);
var file = System.IO.Directory.GetFiles(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"data", "config"), "goods_compliance_" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(user)) + ".json").Single();
Check(Newtonsoft.Json.JsonConvert.DeserializeObject<List<ComplianceGoods>>(System.IO.File.ReadAllText(file))
.Select(x => x.type).SequenceEqual(new[] { cloud.type, library.type }));
Check(batch.Safe == 0 && batch.Risk == 0 && batch.Failed == 2 && batch.Unfinished == 0);
using (var stop = new System.Threading.CancellationTokenSource())
{
attempted.Clear();
var stopped = GoodsComplianceService.DetectSelectedAsync(new[] { cloud, library }, item =>
{
attempted.Add(item);
stop.Cancel();
return new Product { ProductId = item.id };
}, (id, status) => { }, stop.Token).GetAwaiter().GetResult();
Check(attempted.SequenceEqual(new[] { cloud }) && stopped.Unfinished == 2 && stopped.Failed == 0);
Check(Newtonsoft.Json.JsonConvert.DeserializeObject<List<ComplianceGoods>>(System.IO.File.ReadAllText(file)).Count == 2);
}
var words = GoodsComplianceService.Keywords("广告,测试,abc 促销;ABC|示例");
Check(words.Length == 5);
Check(GoodsComplianceService.Matches("ABC广告", words).SequenceEqual(new[] { "广告", "abc" }));
Check(GoodsComplianceService.Matches("普通商品", words).Length == 0);
var invalidImage = new Product { ProductId = "bad-image", MainImages = new List<string> { "file:///image.jpg" } };
var statuses = new List<string>();
GoodsComplianceService.DetectSelectedAsync(new[] { cloud }, item => invalidImage,
(id, status) => statuses.Add(status)).GetAwaiter().GetResult();
Check(statuses.SequenceEqual(new[] { "读取详情中", "正在检测第 1 张图片", "失败:第 1 张图片检测失败:图片链接必须为 HTTP 或 HTTPS 地址。" }));
var products = new[] { new Product(), new Product { ProductId = "empty" }, invalidImage };
var callbacks = new List<Product>();
GoodsComplianceService.DetectAsync(products, new ComplianceOptions { Keywords = "测试" }, (product, result) =>
{
Check(result.Error != null);
if (product == invalidImage) Check(result.Error.Contains("第 1 张图片") && result.Error.Contains("HTTP"));
callbacks.Add(product);
}).GetAwaiter().GetResult();
Check(callbacks.SequenceEqual(products));
System.IO.File.Delete(file);
Console.WriteLine("商品合规队列与匹配自测通过");
}
}
+68
View File
@@ -0,0 +1,68 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using CsharpHttpHelper;
internal static class HttpHelperAsyncSmoke
{
public static async Task Main()
{
await CheckRequests(false);
await CheckRequests(true);
}
private static async Task CheckRequests(bool asynchronous)
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
try
{
string url = "http://" + listener.LocalEndpoint + "/";
var pending = asynchronous ? HttpHelper.PostAsync(url, "hello") : Task.Run(() => HttpHelper.Post(url, "hello"));
using (var client = await listener.AcceptTcpClientAsync())
using (var stream = client.GetStream())
using (var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, true))
{
if (!reader.ReadLine().StartsWith("POST ", StringComparison.OrdinalIgnoreCase))
throw new Exception("Expected POST");
while (!string.IsNullOrEmpty(reader.ReadLine())) { }
var reply = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
stream.Write(reply, 0, reply.Length);
var body = new char[5];
if (reader.ReadBlock(body, 0, body.Length) != 5 || new string(body) != "hello")
throw new Exception("POST body mismatch");
reply = Encoding.ASCII.GetBytes("HTTP/1.1 400 Bad Request\r\nContent-Length: 7\r\nConnection: close\r\n\r\ninvalid");
stream.Write(reply, 0, reply.Length);
}
var result = await pending;
if (result.StatusCode != HttpStatusCode.BadRequest || result.Html != "invalid")
throw new Exception("Missing error response");
var get = asynchronous ? HttpHelper.GetAsync(url) : Task.Run(() => HttpHelper.Get(url));
using (var client = await listener.AcceptTcpClientAsync())
using (var stream = client.GetStream())
using (var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, true))
{
if (!reader.ReadLine().StartsWith("GET ", StringComparison.OrdinalIgnoreCase))
throw new Exception("Expected GET");
while (!string.IsNullOrEmpty(reader.ReadLine())) { }
var reply = Encoding.UTF8.GetBytes("HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 5\r\nConnection: close\r\n\r\n\uFEFFok");
stream.Write(reply, 0, reply.Length);
}
if ((await get).Html != "\uFEFFok") throw new Exception("Expected GET response to retain its BOM");
var item = new HttpItem { URL = url, Timeout = 250 };
var stalled = asynchronous ? HttpHelper.RequestAsync(item) : Task.Run(() => HttpHelper.Request(item));
if (await Task.WhenAny(stalled, Task.Delay(5000)) != stalled || string.IsNullOrEmpty((await stalled).Html))
throw new Exception("Expected a timeout result");
}
finally
{
listener.Stop();
}
}
}
+45
View File
@@ -0,0 +1,45 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using TemuKJD.UserComponent;
internal static class NumericRangeSmoke
{
[STAThread]
public static void Main()
{
using (var range = new UcNumericRange())
{
int width = range.Width;
var layout = (TableLayoutPanel)range.Controls[0];
var separator = (Label)layout.GetControlFromPosition(1, 0);
var margin = separator.Margin;
var cell = layout.GetControlFromPosition(0, 0);
var editor = (NumericUpDown)cell.Controls[0];
var text = (TextBox)(editor.Controls[0] is TextBox ? editor.Controls[0] : editor.Controls[1]);
var buttons = editor.Controls[0] == text ? editor.Controls[1] : editor.Controls[0];
range.SeparatorText = "~";
range.Compact = true;
var rightCell = layout.GetControlFromPosition(2, 0);
using (var bitmap = new Bitmap(range.Width, range.Height))
{
range.DrawToBitmap(bitmap, range.ClientRectangle);
int y = rightCell.Top + rightCell.Height / 2;
int border = bitmap.GetPixel(rightCell.Right - 1, y).ToArgb();
if (border == editor.BackColor.ToArgb() || border != bitmap.GetPixel(rightCell.Left, y).ToArgb())
throw new Exception("结束值输入框右边框未绘制");
}
if (range.Width != 112 || buttons.Left < cell.Width - 2 || text.Right < cell.Width - 4)
throw new Exception("紧凑模式未充分利用输入区域");
if (separator.Text != "~" || range.Width >= width || separator.Margin != Padding.Empty)
throw new Exception("紧凑布局或分隔文字错误");
range.Compact = false;
if (buttons.Right > cell.Width || text.Right > buttons.Left) throw new Exception("微调按钮未恢复");
if (range.Width != width || separator.Margin != margin)
throw new Exception("普通布局未恢复");
range.Width = 190;
range.Compact = true;
if (range.Width != 190) throw new Exception("自定义宽度被覆盖");
}
}
}