From f4913b4fc29fda9cd08ff612593fe13fa42e295a Mon Sep 17 00:00:00 2001 From: chfei Date: Tue, 10 Nov 2026 09:54:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9Bbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/MAX.Framework/appsettings.Serilog.json | 1 + Plugins/CloudProduct/CloudProductPlugin.cs | 5 ----- .../CloudProduct/Controllers/GoodsController.cs | 16 ++++++++++++++++ .../Repositorys/GoodsViolationRepository.cs | 17 +++++++++++++++-- Plugins/CloudProduct/appsettings.json | 1 - Store.API/appsettings.json | 2 +- Tests/CloudProduct.Checks/IntegrationChecks.cs | 6 +++++- 7 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Core/MAX.Framework/appsettings.Serilog.json b/Core/MAX.Framework/appsettings.Serilog.json index 996c119..f735ee1 100644 --- a/Core/MAX.Framework/appsettings.Serilog.json +++ b/Core/MAX.Framework/appsettings.Serilog.json @@ -8,6 +8,7 @@ "MinimumLevel": { "Default": "Debug", "Override": { + "MAX.Framework.Security.TokenAuthenticationHandler": "Information", "Microsoft": "Information", "Microsoft.AspNetCore": "Warning", "System": "Warning", diff --git a/Plugins/CloudProduct/CloudProductPlugin.cs b/Plugins/CloudProduct/CloudProductPlugin.cs index 70b0be2..639afd8 100644 --- a/Plugins/CloudProduct/CloudProductPlugin.cs +++ b/Plugins/CloudProduct/CloudProductPlugin.cs @@ -36,11 +36,6 @@ namespace Store.Modules.CloudProduct // 服务自行创建客户端,单例复用连接池并由容器负责释放。 services.AddSingleton(); - // 图片识别沿用专用缓存库,所有请求共享并发上限。 - var inspectionConnection = RequireConfiguration("ProductInspection:ConnectionString"); - services.AddKeyedSingleton("ProductInspectionConnection", (_, _) => - new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql, inspectionConnection) - .UseAdoConnectionPool(true).Build()); var maximum = Context.Configuration.GetValue("ProductInspection:MaxConcurrentAnalysis") ?? 5; if (maximum is < 1 or > 100) throw new InvalidOperationException("识别并发数必须在 1 至 100 之间。"); services.AddKeyedSingleton("ProductInspectionAnalysisGate", (_, _) => new SemaphoreSlim(maximum, maximum)); diff --git a/Plugins/CloudProduct/Controllers/GoodsController.cs b/Plugins/CloudProduct/Controllers/GoodsController.cs index c3442fa..d9ce091 100644 --- a/Plugins/CloudProduct/Controllers/GoodsController.cs +++ b/Plugins/CloudProduct/Controllers/GoodsController.cs @@ -86,6 +86,22 @@ namespace Store.Modules.CloudProduct.Controllers { await _goodsIndexJob.ProcessPendingAsync(); } + + /// 循环处理数据库中待分词的正常商品,成功写入索引后标记 is_segmented=1。 + [HttpGet] + [EndpointSummary("重建所有待处理商品索引")] + public async Task RebuildPendingIndex() + { + long count = 0; + int batchCount; + do + { + batchCount = await _goodsService.IndexPendingGoodsAsync(1000); + count += batchCount; + } while (batchCount > 0); + + return count; + } /// /// 分页搜索商品,可按标题、价格、类目、销量和平台筛选。 /// diff --git a/Plugins/CloudProduct/Repositorys/GoodsViolationRepository.cs b/Plugins/CloudProduct/Repositorys/GoodsViolationRepository.cs index ed13392..47f73f3 100644 --- a/Plugins/CloudProduct/Repositorys/GoodsViolationRepository.cs +++ b/Plugins/CloudProduct/Repositorys/GoodsViolationRepository.cs @@ -112,9 +112,22 @@ public class GoodsViolationRepository( public async Task InsertLogsAsync(GoodsViolationLogEntity[] logs) { - // 即使 ORM 拆分 INSERT,各批次也在同一事务内提交。 using var unit = Orm.CreateUnitOfWork(); - await unit.GetRepository().InsertAsync(logs); + unit.GetOrBeginTransaction(); + foreach (var log in logs) + { + try + { + await unit.Orm.Insert(log).ExecuteAffrowsAsync(); + } + catch (Exception ex) when (ex.GetBaseException() is MySql.Data.MySqlClient.MySqlException { Number: 1062 } duplicate + && (duplicate.Message.EndsWith(".ux_violation_log_event'", StringComparison.Ordinal) + || duplicate.Message.EndsWith("'ux_violation_log_event'", StringComparison.Ordinal))) + { + // 事件唯一键已存在即视为上报成功,保留首次记录,继续写入本批其他事件。 + continue; + } + } unit.Commit(); } diff --git a/Plugins/CloudProduct/appsettings.json b/Plugins/CloudProduct/appsettings.json index 9d7eca8..f9dcc9a 100644 --- a/Plugins/CloudProduct/appsettings.json +++ b/Plugins/CloudProduct/appsettings.json @@ -25,6 +25,5 @@ "Model": "qwen3.7-flash", "ApiKey": "sk-fa7fbaa3bb4047928f30bf05ff52c03a", "MaxConcurrentAnalysis": 5, - "ConnectionString": "Server=192.168.0.200;Port=3306;Database=product_inspection;Uid=root;Pwd=123456;Allow User Variables=true" } } diff --git a/Store.API/appsettings.json b/Store.API/appsettings.json index 91d99b9..316351b 100644 --- a/Store.API/appsettings.json +++ b/Store.API/appsettings.json @@ -6,7 +6,7 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;", + "DefaultConnection": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;" }, // 登录认证:请求头使用 Authorization: Bearer 。 "TokenAuth": { diff --git a/Tests/CloudProduct.Checks/IntegrationChecks.cs b/Tests/CloudProduct.Checks/IntegrationChecks.cs index da2ac6d..d80f7a0 100644 --- a/Tests/CloudProduct.Checks/IntegrationChecks.cs +++ b/Tests/CloudProduct.Checks/IntegrationChecks.cs @@ -124,12 +124,16 @@ internal static class IntegrationChecks Check(!await db.Select().Where(x => x.GoodsId == first).AnyAsync(), "reporting alone creates no log"); await violations.RecordLogsAsync([ new() { ShopId = "shop-a", GoodsId = first, ReasonId = (int)existingReasonId, Action = ViolationLogAction.Report }, + ]); + await violations.RecordLogsAsync([ + new() { ShopId = "shop-a", GoodsId = first, ReasonId = (int)existingReasonId, Action = ViolationLogAction.Report }, + new() { ShopId = "shop-c", GoodsId = first, ReasonId = (int)ignoredReasonId, Action = ViolationLogAction.Report }, new() { ShopId = "shop-c", GoodsId = first, ReasonId = (int)ignoredReasonId, Action = ViolationLogAction.Report } ]); var reportLogs = await violations.GetLogsAsync(["shop-a", "shop-c"], first, 0, 50); Check(reportLogs.Count == 2 && reportLogs.All(x => x.Action == ViolationLogAction.Report) && reportLogs.Any(x => x.ShopId == "shop-a" && x.ReasonId == existingReasonId) - && reportLogs.Any(x => x.ShopId == "shop-c" && x.ReasonId == ignoredReasonId), "keep report source and ignored reasons"); + && reportLogs.Any(x => x.ShopId == "shop-c" && x.ReasonId == ignoredReasonId), "skip existing and in-batch duplicate events while keeping new events and their sources"); foreach (var action in new[] { ViolationLogAction.AutoListingExcluded, ViolationLogAction.AutoOffShelf }) await violations.RecordLogsAsync([new() { ShopId = "shop-b", GoodsId = first, ReasonId = (int)existingReasonId, Action = action }]); var latest = (await violations.GetLogsAsync(["shop-b"], first, 0, 1)).Single();