diff --git a/YLErpDAL/Modules/RiskEngine/Dto/BatchIdsReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/BatchIdsReq.cs new file mode 100644 index 00000000..58650c8f --- /dev/null +++ b/YLErpDAL/Modules/RiskEngine/Dto/BatchIdsReq.cs @@ -0,0 +1,10 @@ +namespace YLErp.Modules.RiskEngine.Dto +{ + /// + /// 批量操作请求(兼容前端 {ids: [...]} 格式) + /// + public class BatchIdsReq + { + public List Ids { get; set; } + } +} diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs index be59ce93..b927712d 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs @@ -540,6 +540,19 @@ namespace YLErp.Modules.RiskEngine return ids; } + /// + /// EF Core 可翻译的逗号分隔字段精确匹配表达式 + /// + private static Expression> RuleIdsMatchExpr(long ruleId) + { + var idStr = ruleId.ToString(); + return a => a.RuleIds != null && + (a.RuleIds == idStr || + a.RuleIds.StartsWith(idStr + ",") || + a.RuleIds.EndsWith("," + idStr) || + a.RuleIds.Contains("," + idStr + ",")); + } + /// /// 校验规则状态可用性并确保编译缓存就绪。 /// 返回不可用规则信息列表,列表为空表示全部通过。 @@ -722,7 +735,12 @@ namespace YLErp.Modules.RiskEngine OptDate = r.OptDate.GetValueOrDefault(), UpdateOptName = r.UpdateOptName, UpdateDate = r.UpdateDate ?? r.OptDate.GetValueOrDefault(), - ApplicationCount = DbContext.glms_risk_rule_application.Count(a => a.RuleIds.Contains(r.id.ToString()) && a.Status != RiskRuleStatus.Deleted) + ApplicationCount = DbContext.glms_risk_rule_application + .Count(a => a.RuleIds != null && + (a.RuleIds == r.id.ToString() || + a.RuleIds.StartsWith(r.id.ToString() + ",") || + a.RuleIds.EndsWith("," + r.id.ToString()) || + a.RuleIds.Contains("," + r.id.ToString() + ","))) }); return result.ToSearchList(req); @@ -735,9 +753,9 @@ namespace YLErp.Modules.RiskEngine { var rule = GetRuleOrThrow(ruleId); - var ruleIdStr = ruleId.ToString(); + var applications = DbContext.glms_risk_rule_application - .Where(a => a.RuleIds.Contains(ruleIdStr) && a.Status != RiskRuleStatus.Deleted) + .Where(RuleIdsMatchExpr(ruleId)) .Select(a => new RiskRuleApplicationSummary { Id = a.id, @@ -858,9 +876,10 @@ namespace YLErp.Modules.RiskEngine { var rule = GetRuleOrThrow(ruleId); - var ruleIdStr = ruleId.ToString(); + var activeCount = DbContext.glms_risk_rule_application - .Count(a => a.RuleIds.Contains(ruleIdStr) && a.Status == RiskRuleStatus.Active); + .Where(a => a.Status == RiskRuleStatus.Active) + .Count(RuleIdsMatchExpr(ruleId)); if (activeCount > 0) throw new ServiceException($"该规则仍有 {activeCount} 个生效中的应用配置,请先停用或删除相关应用"); @@ -965,24 +984,58 @@ namespace YLErp.Modules.RiskEngine /// public BatchOperationResult BatchDeleteRules(List ruleIds) { + if (ruleIds == null || ruleIds.Count == 0) + throw new ServiceException("规则ID列表不能为空"); + + _logger.Info($"[批量删除] 开始 - 传入规则数: {ruleIds.Count}, IDs: {string.Join(",", ruleIds)}"); + var rules = DbContext.glms_risk_rule .Where(r => ruleIds.Contains(r.id) && r.Status != RiskRuleStatus.Deleted) .ToList(); - if (rules.Count != ruleIds.Count) - throw new ServiceException("部分规则不存在或已删除"); + _logger.Info($"[批量删除] DB查询完成 - 匹配到 {rules.Count} 条规则"); + var foundIds = rules.Select(r => (long)r.id).ToList(); + var missingIds = ruleIds.Except(foundIds).ToList(); + if (missingIds.Any()) + { + return new BatchOperationResult + { + Success = false, + TotalCount = ruleIds.Count, + SuccessCount = 0, + ErrorMessage = $"以下规则不存在或已删除:{string.Join(", ", missingIds)}" + }; + } + + var blockedRules = new List(); foreach (var rule in rules) { - var ruleIdStr = rule.id.ToString(); - var activeCount = DbContext.glms_risk_rule_application - .Count(a => a.RuleIds.Contains(ruleIdStr) && a.Status == RiskRuleStatus.Active); + _logger.Info($"[批量删除] 检查关联应用 - 规则ID: {rule.id}"); + var activeApps = DbContext.glms_risk_rule_application + .Where(a => a.Status == RiskRuleStatus.Active) + .Where(RuleIdsMatchExpr(rule.id)) + .Select(a => (long)a.id) + .ToList(); + var activeCount = activeApps.Count; + _logger.Info($"[批量删除] 关联应用检查结果 - 规则ID: {rule.id}, ActiveCount: {activeCount}, AppIds: [{string.Join(",", activeApps)}]"); if (activeCount > 0) - throw new ServiceException($"规则 '{rule.RuleName}' 仍有 {activeCount} 个生效中的应用配置,请先停用或删除相关应用"); + blockedRules.Add($"规则 '{rule.RuleName}'(ID:{rule.id}) 仍有 {activeCount} 个生效中的应用配置(IDs: {string.Join(",", activeApps)})"); + } + if (blockedRules.Any()) + { + return new BatchOperationResult + { + Success = false, + TotalCount = ruleIds.Count, + SuccessCount = 0, + ErrorMessage = string.Join(";", blockedRules) + }; } foreach (var rule in rules) { + _logger.Info($"[批量删除] 写入状态 - 规则ID: {rule.id}"); rule.Status = RiskRuleStatus.Deleted; rule.Version = rule.Version + 1; rule.UpdateOptId = UserId; @@ -992,11 +1045,14 @@ namespace YLErp.Modules.RiskEngine WriteAuditLog("RULE_BATCH_DELETE", "RULE", rule.id, rule.RuleName, "批量删除规则"); } + _logger.Info("[批量删除] 保存数据库..."); DbContext.SaveChanges(); + _logger.Info("[批量删除] 刷新规则缓存..."); foreach (var ruleId in ruleIds) { RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId); } + _logger.Info("[批量删除] 完成"); return new BatchOperationResult { @@ -1013,9 +1069,8 @@ namespace YLErp.Modules.RiskEngine { GetRuleOrThrow(ruleId); - var ruleIdStr = ruleId.ToString(); var apps = DbContext.glms_risk_rule_application - .Where(a => a.RuleIds.Contains(ruleIdStr) && a.Status != RiskRuleStatus.Deleted) + .Where(RuleIdsMatchExpr(ruleId)) .OrderByDescending(a => a.UpdateDate) .ToList(); @@ -1093,8 +1148,12 @@ namespace YLErp.Modules.RiskEngine { query = query.Where(a => DbContext.glms_risk_rule .Any(r => r.Status != RiskRuleStatus.Deleted - && a.RuleIds.Contains(r.id.ToString()) - && r.RuleName.Contains(req.RuleName))); + && r.RuleName.Contains(req.RuleName) + && a.RuleIds != null + && (a.RuleIds == r.id.ToString() + || a.RuleIds.StartsWith(r.id.ToString() + ",") + || a.RuleIds.EndsWith("," + r.id.ToString()) + || a.RuleIds.Contains("," + r.id.ToString() + ",")))); } if (!string.IsNullOrWhiteSpace(req.ScopeClientIds)) @@ -1342,16 +1401,46 @@ namespace YLErp.Modules.RiskEngine /// public BatchOperationResult BatchEnableApplications(List applicationIds) { - _logger.Info($"[批量启用] 开始 - 传入应用数: {applicationIds?.Count ?? 0}, IDs: {string.Join(",", applicationIds ?? new List())}"); + if (applicationIds == null || applicationIds.Count == 0) + throw new ServiceException("应用配置ID列表不能为空"); + + _logger.Info($"[批量启用] 开始 - 传入应用数: {applicationIds.Count}, IDs: {string.Join(",", applicationIds)}"); var apps = DbContext.glms_risk_rule_application - .Where(a => applicationIds.Contains(a.id) && a.Status == RiskRuleStatus.Disabled) + .Where(a => applicationIds.Contains(a.id)) .ToList(); - _logger.Info($"[批量启用] DB查询完成 - 匹配到 {apps.Count} 条已停用应用"); + _logger.Info($"[批量启用] DB查询完成 - 匹配到 {apps.Count} 条应用"); - if (apps.Count != applicationIds.Count) - throw new ServiceException("部分应用配置不存在或当前状态不允许启用"); + var foundIds = apps.Select(a => (long)a.id).ToList(); + var missingIds = applicationIds.Except(foundIds).ToList(); + if (missingIds.Any()) + { + return new BatchOperationResult + { + Success = false, + TotalCount = applicationIds.Count, + SuccessCount = 0, + ErrorMessage = $"以下应用配置不存在:{string.Join(", ", missingIds)}" + }; + } + + // 跳过已启用的应用,只处理已停用的 + var skippedIds = apps.Where(a => a.Status == RiskRuleStatus.Active).Select(a => (long)a.id).ToList(); + if (skippedIds.Any()) + _logger.Info($"[批量启用] 跳过已启用应用 - IDs: {string.Join(",", skippedIds)}"); + + apps = apps.Where(a => a.Status != RiskRuleStatus.Active).ToList(); + if (!apps.Any()) + { + _logger.Info("[批量启用] 所有应用均已启用,无需操作"); + return new BatchOperationResult + { + Success = true, + TotalCount = applicationIds.Count, + SuccessCount = applicationIds.Count + }; + } var allErrors = new List(); foreach (var app in apps) @@ -1404,7 +1493,10 @@ namespace YLErp.Modules.RiskEngine /// public BatchOperationResult BatchDisableApplications(List applicationIds) { - _logger.Info($"[批量停用] 开始 - 传入应用数: {applicationIds?.Count ?? 0}, IDs: {string.Join(",", applicationIds ?? new List())}"); + if (applicationIds == null || applicationIds.Count == 0) + throw new ServiceException("应用配置ID列表不能为空"); + + _logger.Info($"[批量停用] 开始 - 传入应用数: {applicationIds.Count}, IDs: {string.Join(",", applicationIds)}"); var apps = DbContext.glms_risk_rule_application .Where(a => applicationIds.Contains(a.id) && a.Status == RiskRuleStatus.Active) @@ -1412,8 +1504,18 @@ namespace YLErp.Modules.RiskEngine _logger.Info($"[批量停用] DB查询完成 - 匹配到 {apps.Count} 条已启用应用"); - if (apps.Count != applicationIds.Count) - throw new ServiceException("部分应用配置不存在或当前状态不允许停用"); + var foundIds = apps.Select(a => (long)a.id).ToList(); + var missingIds = applicationIds.Except(foundIds).ToList(); + if (missingIds.Any()) + { + return new BatchOperationResult + { + Success = false, + TotalCount = applicationIds.Count, + SuccessCount = apps.Count, + ErrorMessage = $"以下应用配置不存在或当前状态不允许停用:{string.Join(", ", missingIds)}" + }; + } foreach (var app in apps) { diff --git a/YLErpWeb/Controllers/RiskRuleController.cs b/YLErpWeb/Controllers/RiskRuleController.cs index b30a905d..f46ed8b7 100644 --- a/YLErpWeb/Controllers/RiskRuleController.cs +++ b/YLErpWeb/Controllers/RiskRuleController.cs @@ -1,4 +1,4 @@ -using Qdp.Foundation.Utilities; +using Qdp.Foundation.Utilities; using System.Threading.Tasks; using YLErp.DBModels; using YLErp.Modules.RiskEngine; @@ -151,12 +151,14 @@ namespace YLErp.Web.Controllers [HttpDelete("risk-rules/batch")] [MyAuthorize("风险控制-风控规则删除")] /// 批量删除风控规则 - public async Task BatchDeleteRiskRules([FromBody] List ids) + public async Task BatchDeleteRiskRules([FromBody] BatchIdsReq req) { try { var service = GetRiskRuleService(); - var result = await Task.Run(() => service.BatchDeleteRules(ids)); + var result = await Task.Run(() => service.BatchDeleteRules(req?.Ids)); + if (!result.Success) + return Json(new { success = false, message = result.ErrorMessage }); return Json(new { success = true, data = result }); } catch (ServiceException ex) @@ -419,12 +421,14 @@ namespace YLErp.Web.Controllers [HttpPost("risk-applications/batch-enable")] [MyAuthorize("风险控制-风控应用启停")] /// 批量启用应用配置 - public async Task BatchEnableRiskApplications([FromBody] List ids) + public async Task BatchEnableRiskApplications([FromBody] BatchIdsReq req) { try { var service = GetRiskRuleService(); - var result = await Task.Run(() => service.BatchEnableApplications(ids)); + var result = await Task.Run(() => service.BatchEnableApplications(req?.Ids)); + if (!result.Success) + return Json(new { success = false, message = result.ErrorMessage }); return Json(new { success = true, data = result }); } catch (ServiceException ex) @@ -441,12 +445,14 @@ namespace YLErp.Web.Controllers [HttpPost("risk-applications/batch-disable")] [MyAuthorize("风险控制-风控应用启停")] /// 批量停用应用配置 - public async Task BatchDisableRiskApplications([FromBody] List ids) + public async Task BatchDisableRiskApplications([FromBody] BatchIdsReq req) { try { var service = GetRiskRuleService(); - var result = await Task.Run(() => service.BatchDisableApplications(ids)); + var result = await Task.Run(() => service.BatchDisableApplications(req?.Ids)); + if (!result.Success) + return Json(new { success = false, message = result.ErrorMessage }); return Json(new { success = true, data = result }); } catch (ServiceException ex)