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)