diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule.cs
index 8359f712..87b0d880 100644
--- a/Framework/YLErp.Core/DBModels/glms_risk_rule.cs
+++ b/Framework/YLErp.Core/DBModels/glms_risk_rule.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace YLErp.DBModels
@@ -19,8 +20,9 @@ namespace YLErp.DBModels
/// 规则说明
public string Description { get; set; }
/// 规则状态
- public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active;
+ public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Disabled;
/// 版本号(乐观锁)
+ [ConcurrencyCheck]
public int Version { get; set; } = 1;
/// 最后更新人工号
public int? UpdateOptId { get; set; }
diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs
index 89da2889..81f0de94 100644
--- a/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs
+++ b/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace YLErp.DBModels
@@ -13,7 +14,7 @@ namespace YLErp.DBModels
/// 应用说明
public string Description { get; set; }
/// 应用配置状态
- public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active;
+ public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Disabled;
/// 风控策略(预警/阻断)
public RiskControlStrategy ControlStrategy { get; set; }
/// 触发点列表(JSON 数组)
@@ -29,6 +30,7 @@ namespace YLErp.DBModels
/// 是否全局生效(0:否 1:是)
public int ScopeIsGlobal { get; set; }
/// 版本号(乐观锁)
+ [ConcurrencyCheck]
public int Version { get; set; } = 1;
/// 最后更新人工号
public int? UpdateOptId { get; set; }
diff --git a/Framework/YLErp.Core/DBModels/glms_risk_variable.cs b/Framework/YLErp.Core/DBModels/glms_risk_variable.cs
index 2a856819..5dad1ea6 100644
--- a/Framework/YLErp.Core/DBModels/glms_risk_variable.cs
+++ b/Framework/YLErp.Core/DBModels/glms_risk_variable.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace YLErp.DBModels
@@ -23,6 +24,7 @@ namespace YLErp.DBModels
/// 取值表达式(C# 表达式,用于计算变量值)
public string VariableExpr { get; set; }
/// 版本号(乐观锁)
+ [ConcurrencyCheck]
public int Version { get; set; } = 1;
/// 排序序号
public int SortOrder { get; set; }
diff --git a/UnitTestProject/Modules/RiskEngine/RiskApplicationQueryContractTest.cs b/UnitTestProject/Modules/RiskEngine/RiskApplicationQueryContractTest.cs
new file mode 100644
index 00000000..419eb075
--- /dev/null
+++ b/UnitTestProject/Modules/RiskEngine/RiskApplicationQueryContractTest.cs
@@ -0,0 +1,36 @@
+using YLErp.Modules.RiskEngine.Dto;
+
+namespace YLErp.Modules.RiskEngine
+{
+ [TestClass]
+ public class RiskApplicationQueryContractTest
+ {
+ [TestMethod]
+ public void QueryRequest_UsesFrontendParameterNames()
+ {
+ var request = new QueryRiskApplicationReq
+ {
+ page = 1,
+ rows = 20,
+ Keyword = "本金",
+ ControlStrategy = (RiskControlStrategy)2,
+ Status = (RiskRuleStatus)1,
+ ScopeAssetBookIds = "2000041",
+ ScopeClientIds = "1000017",
+ ScopeUnderlyingTypes = "深交所债券ETF",
+ ScopeTradeTypes = "收益互换"
+ };
+
+ Assert.AreEqual("本金", request.Keyword);
+ Assert.AreEqual(RiskControlStrategy.Approval, request.ControlStrategy);
+ Assert.AreEqual(RiskRuleStatus.Active, request.Status);
+ Assert.AreEqual("2000041", request.ScopeAssetBookIds);
+ Assert.AreEqual("1000017", request.ScopeClientIds);
+ Assert.AreEqual("深交所债券ETF", request.ScopeUnderlyingTypes);
+ Assert.AreEqual("收益互换", request.ScopeTradeTypes);
+
+ Assert.IsNull(typeof(QueryRiskApplicationReq).GetProperty("RuleName"));
+ Assert.IsNull(typeof(QueryRiskApplicationReq).GetProperty("Strategy"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTestProject/Modules/RiskEngine/RiskConcurrencyTokenTest.cs b/UnitTestProject/Modules/RiskEngine/RiskConcurrencyTokenTest.cs
new file mode 100644
index 00000000..62ef4ab6
--- /dev/null
+++ b/UnitTestProject/Modules/RiskEngine/RiskConcurrencyTokenTest.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel.DataAnnotations;
+using System.Reflection;
+
+namespace YLErp.Modules.RiskEngine
+{
+ [TestClass]
+ public class RiskConcurrencyTokenTest
+ {
+ [DataTestMethod]
+ [DataRow(typeof(glms_risk_rule))]
+ [DataRow(typeof(glms_risk_rule_application))]
+ [DataRow(typeof(glms_risk_variable))]
+ public void Version_IsConfiguredAsConcurrencyToken(Type entityType)
+ {
+ var versionProperty = entityType.GetProperty("Version", BindingFlags.Instance | BindingFlags.Public);
+
+ Assert.IsNotNull(versionProperty, $"{entityType.Name} 缺少 Version 属性");
+ Assert.IsNotNull(
+ versionProperty.GetCustomAttribute(),
+ $"{entityType.Name}.Version 未配置为 EF concurrency token");
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTestProject/Modules/RiskEngine/RiskRuleDefaultStatusTest.cs b/UnitTestProject/Modules/RiskEngine/RiskRuleDefaultStatusTest.cs
new file mode 100644
index 00000000..4b3c8453
--- /dev/null
+++ b/UnitTestProject/Modules/RiskEngine/RiskRuleDefaultStatusTest.cs
@@ -0,0 +1,22 @@
+namespace YLErp.Modules.RiskEngine
+{
+ [TestClass]
+ public class RiskRuleDefaultStatusTest
+ {
+ [TestMethod]
+ public void NewRiskRule_DefaultStatus_IsDisabled()
+ {
+ var rule = new glms_risk_rule();
+
+ Assert.AreEqual(RiskRuleStatus.Disabled, rule.Status);
+ }
+
+ [TestMethod]
+ public void NewRiskRuleApplication_DefaultStatus_IsDisabled()
+ {
+ var application = new glms_risk_rule_application();
+
+ Assert.AreEqual(RiskRuleStatus.Disabled, application.Status);
+ }
+ }
+}
\ No newline at end of file
diff --git a/YLErpDAL/Modules/RiskEngine/Dto/BatchDisableRulesResult.cs b/YLErpDAL/Modules/RiskEngine/Dto/BatchDisableRulesResult.cs
deleted file mode 100644
index cb2e2fcd..00000000
--- a/YLErpDAL/Modules/RiskEngine/Dto/BatchDisableRulesResult.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-
-namespace YLErp.Modules.RiskEngine.Dto
-{
- ///
- /// 批量停用规则结果
- ///
- public class BatchDisableRulesResult
- {
- /// 是否全部成功
- public bool Success { get; set; }
- /// 总数量
- public int TotalCount { get; set; }
- /// 成功数量
- public int SuccessCount { get; set; }
- /// 错误信息
- public string ErrorMessage { get; set; }
- /// 级联停用的应用 ID 列表
- public List CascadedApplicationIds { get; set; } = new List();
- }
-}
diff --git a/YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs b/YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs
index c6c7aafd..0ae0cdcb 100644
--- a/YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs
+++ b/YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs
@@ -1,32 +1,48 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
namespace YLErp.Modules.RiskEngine.Dto
{
- ///
- /// 批量操作结果
- ///
- public class BatchOperationResult
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum BatchOperationOutcome
{
- /// 是否全部成功
- public bool Success { get; set; }
- /// 总数量
- public int TotalCount { get; set; }
- /// 成功数量
- public int SuccessCount { get; set; }
- /// 错误信息
- public string ErrorMessage { get; set; }
+ [EnumMember(Value = "AllSucceeded")]
+ AllSucceeded,
+ [EnumMember(Value = "PartiallySucceeded")]
+ PartiallySucceeded,
+ [EnumMember(Value = "AllFailed")]
+ AllFailed
}
- ///
- /// 批量删除变量结果
- ///
- public class BatchDeleteVariablesResult : BatchOperationResult
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum BatchOperationItemStatus
{
- /// 成功删除的变量 ID
- public List DeletedIds { get; set; }
- /// 不存在的变量 ID
- public List MissingIds { get; set; }
- /// 因被生效规则引用而跳过的变量 ID
- public List BlockedIds { get; set; }
- /// 被跳过变量的原因,Key 为变量 ID
- public Dictionary BlockedReasons { get; set; }
+ [EnumMember(Value = "Succeeded")]
+ Succeeded,
+ [EnumMember(Value = "Unchanged")]
+ Unchanged,
+ [EnumMember(Value = "Failed")]
+ Failed
+ }
+
+ public class BatchOperationItemResult
+ {
+ public long Id { get; set; }
+ public BatchOperationItemStatus Status { get; set; }
+ public string Code { get; set; }
+ public string Message { get; set; }
+ public List CascadedApplicationIds { get; set; } = new List();
+ }
+
+ public class BatchOperationResult
+ {
+ public BatchOperationOutcome Outcome { get; set; }
+ public int TotalCount { get; set; }
+ public int SucceededCount { get; set; }
+ public int UnchangedCount { get; set; }
+ public int FailedCount { get; set; }
+ public bool HasChanges { get; set; }
+ public List Items { get; set; } = new List();
+ public List CascadedApplicationIds { get; set; } = new List();
}
}
diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs
index 0a501867..36916100 100644
--- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs
+++ b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs
@@ -8,12 +8,12 @@ namespace YLErp.Modules.RiskEngine.Dto
///
public class QueryRiskApplicationReq : BaseSearchReq
{
- /// 规则名称
- public string RuleName { get; set; }
+ /// 规则名称关键词
+ public string Keyword { get; set; }
/// 应用配置状态
public RiskRuleStatus? Status { get; set; }
/// 风控策略
- public RiskControlStrategy? Strategy { get; set; }
+ public RiskControlStrategy? ControlStrategy { get; set; }
/// 触发点
public string TriggerPoint { get; set; }
/// 适用范围-客户 ID 列表
diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs
index 737aa940..d0abff89 100644
--- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs
+++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs
@@ -1,4 +1,5 @@
-using BaseOUDAL;
+using BaseOUDAL;
+using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Globalization;
@@ -261,6 +262,23 @@ namespace YLErp.Modules.RiskEngine
#region Private Helpers
+ ///
+ /// 保存变更,并将 EF 乐观并发冲突转换为可识别的业务异常。
+ /// Version 仍由业务代码手工递增;并发令牌只负责将原 Version 加入 UPDATE/DELETE 条件。
+ ///
+ private void SaveChangesWithConcurrencyCheck()
+ {
+ try
+ {
+ DbContext.SaveChanges();
+ }
+ catch (DbUpdateConcurrencyException ex)
+ {
+ _logger.Error("风控配置发生并发修改冲突", ex);
+ throw new ServiceException("数据已被其他用户修改,请重新加载后再操作", ex);
+ }
+ }
+
private static string GetAuditOperationTypeName(string operationType)
{
if (string.IsNullOrWhiteSpace(operationType))
@@ -780,6 +798,44 @@ namespace YLErp.Modules.RiskEngine
return string.Join(", ", translated);
}
+ private static List DistinctIds(List ids)
+ {
+ return ids.Distinct().ToList();
+ }
+
+ private static BatchOperationItemResult BatchItem(long id, BatchOperationItemStatus status,
+ string code, string message, IEnumerable cascadedApplicationIds = null)
+ {
+ return new BatchOperationItemResult
+ {
+ Id = id,
+ Status = status,
+ Code = code,
+ Message = message,
+ CascadedApplicationIds = cascadedApplicationIds?.Distinct().ToList() ?? new List()
+ };
+ }
+
+ private static BatchOperationResult BuildBatchResult(List items)
+ {
+ var result = new BatchOperationResult
+ {
+ Items = items,
+ TotalCount = items.Count,
+ SucceededCount = items.Count(i => i.Status == BatchOperationItemStatus.Succeeded),
+ UnchangedCount = items.Count(i => i.Status == BatchOperationItemStatus.Unchanged),
+ FailedCount = items.Count(i => i.Status == BatchOperationItemStatus.Failed),
+ CascadedApplicationIds = items.SelectMany(i => i.CascadedApplicationIds).Distinct().ToList()
+ };
+ result.HasChanges = result.SucceededCount > 0;
+ result.Outcome = result.FailedCount == 0
+ ? BatchOperationOutcome.AllSucceeded
+ : result.FailedCount == result.TotalCount
+ ? BatchOperationOutcome.AllFailed
+ : BatchOperationOutcome.PartiallySucceeded;
+ return result;
+ }
+
#endregion
#region Rule Management
@@ -894,7 +950,7 @@ namespace YLErp.Modules.RiskEngine
ConditionJson = req.ConditionJson,
RuleExpr = req.RuleExpr,
Description = req.Description,
- Status = RiskRuleStatus.Active,
+ Status = RiskRuleStatus.Disabled,
Version = 1
};
@@ -908,11 +964,11 @@ namespace YLErp.Modules.RiskEngine
try
{
DbContext.glms_risk_rule.Add(entity);
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
WriteAuditLog("RULE_CREATE", "RULE", entity.id, req.RuleName, "创建规则",
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
transaction.Commit();
}
@@ -955,7 +1011,7 @@ namespace YLErp.Modules.RiskEngine
rule.UpdateDate = DateTime.Now;
WriteAuditLog("RULE_UPDATE", "RULE", ruleId, req.RuleName, "修改规则");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
@@ -983,7 +1039,7 @@ namespace YLErp.Modules.RiskEngine
rule.UpdateDate = DateTime.Now;
WriteAuditLog("RULE_DELETE", "RULE", ruleId, rule.RuleName, "删除规则");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
}
@@ -1002,12 +1058,14 @@ namespace YLErp.Modules.RiskEngine
throw new ServiceException($"规则表达式编译失败:{compileResult.ErrorMessage}");
rule.Status = RiskRuleStatus.Active;
+
+ rule.Version = rule.Version + 1;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
WriteAuditLog("RULE_ENABLE", "RULE", ruleId, rule.RuleName, "启用规则");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
}
@@ -1022,6 +1080,8 @@ namespace YLErp.Modules.RiskEngine
throw new ServiceException("仅已启用的规则可以停用");
rule.Status = RiskRuleStatus.Disabled;
+
+ rule.Version = rule.Version + 1;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
@@ -1038,6 +1098,7 @@ namespace YLErp.Modules.RiskEngine
foreach (var app in activeApps)
{
app.Status = RiskRuleStatus.Disabled;
+ app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
@@ -1052,7 +1113,7 @@ namespace YLErp.Modules.RiskEngine
_logger.Info($"[停用规则] 规则ID: {ruleId}, 级联停用应用: {string.Join(",", cascadedIds)}");
}
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
if (cascadedIds.Any())
@@ -1109,79 +1170,48 @@ namespace YLErp.Modules.RiskEngine
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)
+ var ids = DistinctIds(ruleIds);
+ var rules = DbContext.glms_risk_rule.Where(r => ids.Contains(r.id)).ToDictionary(r => (long)r.id);
+ var activeApps = DbContext.glms_risk_rule_application
+ .Where(a => a.Status == RiskRuleStatus.Active)
.ToList();
+ var items = new List();
+ var changedIds = new List();
- _logger.Info($"[批量删除] DB查询完成 - 匹配到 {rules.Count} 条规则");
-
- var foundIds = rules.Select(r => (long)r.id).ToList();
- var missingIds = ruleIds.Except(foundIds).ToList();
- if (missingIds.Any())
+ foreach (var id in ids)
{
- return new BatchOperationResult
+ if (!rules.TryGetValue(id, out var rule) || rule.Status == RiskRuleStatus.Deleted)
{
- Success = false,
- TotalCount = ruleIds.Count,
- SuccessCount = 0,
- ErrorMessage = $"以下规则不存在或已删除:{string.Join(", ", missingIds)}"
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "规则不存在或已删除"));
+ continue;
+ }
- var blockedRules = new List();
- foreach (var rule in rules)
- {
- _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)
- blockedRules.Add($"规则 '{rule.RuleName}'(ID:{rule.id}) 仍有 {activeCount} 个生效中的应用配置(IDs: {string.Join(",", activeApps)})");
- }
- if (blockedRules.Any())
- {
- return new BatchOperationResult
+ var referenceCount = activeApps.Count(a => ParseRuleIds(a.RuleIds).Contains(id));
+ if (referenceCount > 0)
{
- Success = false,
- TotalCount = ruleIds.Count,
- SuccessCount = 0,
- ErrorMessage = string.Join(";", blockedRules)
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "ACTIVE_APPLICATION_REFERENCE",
+ $"该规则仍有 {referenceCount} 个生效中的应用配置,请先停用或删除相关应用"));
+ continue;
+ }
- foreach (var rule in rules)
- {
- _logger.Info($"[批量删除] 写入状态 - 规则ID: {rule.id}");
rule.Status = RiskRuleStatus.Deleted;
- rule.Version = rule.Version + 1;
+ rule.Version++;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
-
WriteAuditLog("RULE_BATCH_DELETE", "RULE", rule.id, rule.RuleName, "批量删除规则");
+ changedIds.Add(id);
+ items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DELETED", "规则已删除"));
}
- _logger.Info("[批量删除] 保存数据库...");
- DbContext.SaveChanges();
- _logger.Info("[批量删除] 刷新规则缓存...");
- foreach (var ruleId in ruleIds)
+ if (changedIds.Any())
{
- RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
+ SaveChangesWithConcurrencyCheck();
+ foreach (var id in changedIds)
+ RiskEngineService.GetInstance().RefreshOneRuleCache(id);
}
- _logger.Info("[批量删除] 完成");
- return new BatchOperationResult
- {
- Success = true,
- TotalCount = ruleIds.Count,
- SuccessCount = ruleIds.Count
- };
+ return BuildBatchResult(items);
}
///
@@ -1192,200 +1222,115 @@ namespace YLErp.Modules.RiskEngine
if (ruleIds == null || ruleIds.Count == 0)
throw new ServiceException("规则ID列表不能为空");
- _logger.Info($"[批量启用规则] 开始 - 传入规则数: {ruleIds.Count}, IDs: {string.Join(",", ruleIds)}");
+ var ids = DistinctIds(ruleIds);
+ var rules = DbContext.glms_risk_rule.Where(r => ids.Contains(r.id)).ToDictionary(r => (long)r.id);
+ var items = new List();
+ var changedIds = new List();
- var rules = DbContext.glms_risk_rule
- .Where(r => ruleIds.Contains(r.id) && r.Status != RiskRuleStatus.Deleted)
- .ToList();
-
- _logger.Info($"[批量启用规则] DB查询完成 - 匹配到 {rules.Count} 条规则");
-
- var foundIds = rules.Select(r => (long)r.id).ToList();
- var missingIds = ruleIds.Except(foundIds).ToList();
- if (missingIds.Any())
+ foreach (var id in ids)
{
- return new BatchOperationResult
+ if (!rules.TryGetValue(id, out var rule) || rule.Status == RiskRuleStatus.Deleted)
{
- Success = false,
- TotalCount = ruleIds.Count,
- SuccessCount = 0,
- ErrorMessage = $"以下规则不存在或已删除:{string.Join(", ", missingIds)}"
- };
- }
-
- // 跳过已启用的规则
- var skippedIds = rules.Where(r => r.Status == RiskRuleStatus.Active).Select(r => (long)r.id).ToList();
- if (skippedIds.Any())
- _logger.Info($"[批量启用规则] 跳过已启用规则 - IDs: {string.Join(",", skippedIds)}");
-
- rules = rules.Where(r => r.Status != RiskRuleStatus.Active).ToList();
- if (!rules.Any())
- {
- _logger.Info("[批量启用规则] 所有规则均已启用,无需操作");
- return new BatchOperationResult
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "规则不存在或已删除"));
+ continue;
+ }
+ if (rule.Status == RiskRuleStatus.Active)
{
- Success = true,
- TotalCount = ruleIds.Count,
- SuccessCount = ruleIds.Count
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Unchanged, "ALREADY_ENABLED", "规则已启用,无需重复操作"));
+ continue;
+ }
- // 逐条编译校验,任一失败则整体拒绝
- var errors = new List();
- foreach (var rule in rules)
- {
var compileResult = RuleCompiler.ValidateAndCompileFormula(rule.id, rule.RuleExpr);
if (!compileResult.Success)
- errors.Add($"规则'{rule.RuleName}'(ID:{rule.id}):{compileResult.ErrorMessage}");
- }
- if (errors.Any())
- {
- return new BatchOperationResult
{
- Success = false,
- TotalCount = ruleIds.Count,
- SuccessCount = 0,
- ErrorMessage = $"规则表达式编译失败:{string.Join(";", errors)}"
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "COMPILE_FAILED",
+ $"规则表达式编译失败:{compileResult.ErrorMessage}"));
+ continue;
+ }
- foreach (var rule in rules)
- {
- _logger.Info($"[批量启用规则] 写入状态 - 规则ID: {rule.id}");
rule.Status = RiskRuleStatus.Active;
+ rule.Version++;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
+ WriteAuditLog("RULE_BATCH_ENABLE", "RULE", rule.id, rule.RuleName, "批量启用规则");
+ changedIds.Add(id);
+ items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "ENABLED", "规则已启用"));
}
- var enabledIds = rules.Select(r => r.id).ToList();
- WriteAuditLog("RULE_BATCH_ENABLE", "RULE", 0, "",
- $"批量启用规则(IDs: {string.Join(",", enabledIds)})");
-
- _logger.Info("[批量启用规则] 保存数据库...");
- DbContext.SaveChanges();
- _logger.Info("[批量启用规则] 刷新规则缓存...");
- foreach (var rule in rules)
+ if (changedIds.Any())
{
- RiskEngineService.GetInstance().RefreshOneRuleCache(rule.id);
+ SaveChangesWithConcurrencyCheck();
+ foreach (var id in changedIds)
+ RiskEngineService.GetInstance().RefreshOneRuleCache(id);
}
- _logger.Info("[批量启用规则] 完成");
- return new BatchOperationResult
- {
- Success = true,
- TotalCount = ruleIds.Count,
- SuccessCount = ruleIds.Count
- };
+ return BuildBatchResult(items);
}
///
/// 批量停用规则(仅 Active → Disabled,级联停用关联应用)
///
- public BatchDisableRulesResult BatchDisableRules(List ruleIds)
+ public BatchOperationResult BatchDisableRules(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)
+ var ids = DistinctIds(ruleIds);
+ var rules = DbContext.glms_risk_rule.Where(r => ids.Contains(r.id)).ToDictionary(r => (long)r.id);
+ var activeRules = ids.Where(id => rules.TryGetValue(id, out var rule) && rule.Status == RiskRuleStatus.Active).ToList();
+ var activeApps = DbContext.glms_risk_rule_application
+ .Where(a => a.Status == RiskRuleStatus.Active)
.ToList();
+ var cascadesByRule = activeRules.ToDictionary(
+ id => id,
+ id => activeApps.Where(a => ParseRuleIds(a.RuleIds).Contains(id)).Select(a => (long)a.id).Distinct().ToList());
+ var items = new List();
- _logger.Info($"[批量停用规则] DB查询完成 - 匹配到 {rules.Count} 条规则");
-
- var foundIds = rules.Select(r => (long)r.id).ToList();
- var missingIds = ruleIds.Except(foundIds).ToList();
- if (missingIds.Any())
+ foreach (var id in ids)
{
- return new BatchDisableRulesResult
+ if (!rules.TryGetValue(id, out var rule) || rule.Status == RiskRuleStatus.Deleted)
{
- Success = false,
- TotalCount = ruleIds.Count,
- SuccessCount = 0,
- ErrorMessage = $"以下规则不存在或已删除:{string.Join(", ", missingIds)}"
- };
- }
-
- // 跳过已停用的规则
- var skippedIds = rules.Where(r => r.Status == RiskRuleStatus.Disabled).Select(r => (long)r.id).ToList();
- if (skippedIds.Any())
- _logger.Info($"[批量停用规则] 跳过已停用规则 - IDs: {string.Join(",", skippedIds)}");
-
- rules = rules.Where(r => r.Status != RiskRuleStatus.Disabled).ToList();
- if (!rules.Any())
- {
- _logger.Info("[批量停用规则] 所有规则均已停用,无需操作");
- return new BatchDisableRulesResult
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "规则不存在或已删除"));
+ continue;
+ }
+ if (rule.Status == RiskRuleStatus.Disabled)
{
- Success = true,
- TotalCount = ruleIds.Count,
- SuccessCount = ruleIds.Count
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Unchanged, "ALREADY_DISABLED", "规则已停用,无需重复操作"));
+ continue;
+ }
- var allCascadedAppIds = new List();
- foreach (var rule in rules)
- {
- _logger.Info($"[批量停用规则] 写入状态 - 规则ID: {rule.id}");
rule.Status = RiskRuleStatus.Disabled;
+ rule.Version++;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
+ WriteAuditLog("RULE_BATCH_DISABLE", "RULE", rule.id, rule.RuleName, "批量停用规则");
+ items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DISABLED", "规则已停用", cascadesByRule[id]));
+ }
- // 级联停用关联的 Active 应用配置
- var activeApps = DbContext.glms_risk_rule_application
- .Where(a => a.Status == RiskRuleStatus.Active)
- .Where(RuleIdsMatchExpr(rule.id))
- .ToList();
-
- var cascadedIds = new List();
- foreach (var app in activeApps)
- {
- // 同一应用可能同时关联多条待停用规则,只对实际仍为 Active 的应用处理一次。
- if (app.Status != RiskRuleStatus.Active)
- continue;
-
- app.Status = RiskRuleStatus.Disabled;
- app.UpdateOptId = UserId;
- app.UpdateOptName = UserName;
- app.UpdateDate = DateTime.Now;
- cascadedIds.Add(app.id);
-
- WriteAuditLog("RULE_CASCADE_DISABLE_APP", "APPLICATION", app.id, $"应用{app.id}",
- $"因规则'{rule.RuleName}'(ID:{rule.id})停用,应用配置被级联停用");
- }
+ var cascadedIds = items.SelectMany(i => i.CascadedApplicationIds).Distinct().ToHashSet();
+ foreach (var app in activeApps.Where(a => cascadedIds.Contains(a.id)))
+ {
+ app.Status = RiskRuleStatus.Disabled;
+ app.Version++;
+ app.UpdateOptId = UserId;
+ app.UpdateOptName = UserName;
+ app.UpdateDate = DateTime.Now;
+ WriteAuditLog("RULE_CASCADE_DISABLE_APP", "APPLICATION", app.id, $"应用{app.id}",
+ $"因规则批量停用,应用配置被级联停用(规则IDs:{string.Join(",", activeRules.Where(id => cascadesByRule[id].Contains(app.id)))})");
+ }
+ if (items.Any(i => i.Status == BatchOperationItemStatus.Succeeded))
+ {
+ SaveChangesWithConcurrencyCheck();
+ foreach (var id in activeRules)
+ RiskEngineService.GetInstance().RefreshOneRuleCache(id);
if (cascadedIds.Any())
- {
- _logger.Info($"[批量停用规则] 规则ID: {rule.id}, 级联停用应用: {string.Join(",", cascadedIds)}");
- allCascadedAppIds.AddRange(cascadedIds);
- }
+ RiskEngineService.GetInstance().RefreshApplication();
}
- var disabledIds = rules.Select(r => r.id).ToList();
- WriteAuditLog("RULE_BATCH_DISABLE", "RULE", 0, "",
- $"批量停用规则(IDs: {string.Join(",", disabledIds)})");
-
- _logger.Info("[批量停用规则] 保存数据库...");
- DbContext.SaveChanges();
- _logger.Info("[批量停用规则] 刷新规则缓存...");
- foreach (var rule in rules)
- {
- RiskEngineService.GetInstance().RefreshOneRuleCache(rule.id);
- }
- if (allCascadedAppIds.Any())
- RiskEngineService.GetInstance().RefreshApplication();
- _logger.Info("[批量停用规则] 完成");
-
- return new BatchDisableRulesResult
- {
- Success = true,
- TotalCount = ruleIds.Count,
- SuccessCount = ruleIds.Count,
- CascadedApplicationIds = allCascadedAppIds
- };
+ return BuildBatchResult(items);
}
///
@@ -1462,9 +1407,9 @@ namespace YLErp.Modules.RiskEngine
query = query.Where(a => a.Status == req.Status.Value);
}
- if (req.Strategy.HasValue)
+ if (req.ControlStrategy.HasValue)
{
- query = query.Where(a => a.ControlStrategy == req.Strategy.Value);
+ query = query.Where(a => a.ControlStrategy == req.ControlStrategy.Value);
}
if (!string.IsNullOrWhiteSpace(req.TriggerPoint))
@@ -1472,11 +1417,11 @@ namespace YLErp.Modules.RiskEngine
query = query.Where(a => a.TriggerPoints.Contains(req.TriggerPoint));
}
- if (!string.IsNullOrWhiteSpace(req.RuleName))
+ if (!string.IsNullOrWhiteSpace(req.Keyword))
{
query = query.Where(a => DbContext.glms_risk_rule
.Any(r => r.Status != RiskRuleStatus.Deleted
- && r.RuleName.Contains(req.RuleName)
+ && r.RuleName.Contains(req.Keyword)
&& a.RuleIds != null
&& (a.RuleIds == r.id.ToString()
|| a.RuleIds.StartsWith(r.id.ToString() + ",")
@@ -1568,7 +1513,7 @@ namespace YLErp.Modules.RiskEngine
{
RuleIds = normalizedRuleIds,
Description = req.Description,
- Status = RiskRuleStatus.Active,
+ Status = RiskRuleStatus.Disabled,
ControlStrategy = req.ControlStrategy,
TriggerPoints = req.TriggerPoints,
ScopeAssetBookIds = req.ScopeAssetBookIds,
@@ -1589,12 +1534,12 @@ namespace YLErp.Modules.RiskEngine
try
{
DbContext.glms_risk_rule_application.Add(entity);
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
WriteAuditLog("APP_CREATE", "APPLICATION", entity.id, ResolveRuleNames(entity.RuleIds),
$"创建应用配置:策略={GetStrategyName(req.ControlStrategy)}, 触发时点={MapTriggerPointsToChinese(req.TriggerPoints)}",
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
transaction.Commit();
}
@@ -1657,7 +1602,7 @@ namespace YLErp.Modules.RiskEngine
WriteAuditLog("APP_UPDATE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds),
$"修改应用配置:策略={GetStrategyName(req.ControlStrategy)}, 触发时点={MapTriggerPointsToChinese(req.TriggerPoints)}");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshApplication();
@@ -1678,7 +1623,7 @@ namespace YLErp.Modules.RiskEngine
app.UpdateDate = DateTime.Now;
WriteAuditLog("APP_DELETE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "删除应用配置");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshApplication();
}
@@ -1691,53 +1636,32 @@ namespace YLErp.Modules.RiskEngine
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.Deleted)
- .ToList();
-
- _logger.Info($"[批量删除应用] DB查询完成 - 匹配到 {apps.Count} 条应用");
-
- var foundIds = apps.Select(a => (long)a.id).ToList();
- var missingIds = applicationIds.Except(foundIds).ToList();
- if (missingIds.Any())
+ var ids = DistinctIds(applicationIds);
+ var apps = DbContext.glms_risk_rule_application.Where(a => ids.Contains(a.id)).ToDictionary(a => (long)a.id);
+ var items = new List();
+ foreach (var id in ids)
{
- return new BatchOperationResult
+ if (!apps.TryGetValue(id, out var app) || app.Status == RiskRuleStatus.Deleted)
{
- Success = false,
- TotalCount = applicationIds.Count,
- SuccessCount = 0,
- ErrorMessage = $"以下应用配置不存在或已删除:{string.Join(", ", missingIds)}"
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "应用配置不存在或已删除"));
+ continue;
+ }
- foreach (var app in apps)
- {
- _logger.Info($"[批量删除应用] 写入状态 - 应用ID: {app.id}");
app.Status = RiskRuleStatus.Deleted;
- app.Version = app.Version + 1;
+ app.Version++;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
+ WriteAuditLog("APP_BATCH_DELETE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量删除应用配置");
+ items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DELETED", "应用配置已删除"));
}
- var deletedIds = apps.Select(a => a.id).ToList();
- WriteAuditLog("APP_BATCH_DELETE", "APPLICATION", 0, "",
- $"批量删除应用配置(IDs: {string.Join(",", deletedIds)})");
-
- _logger.Info("[批量删除应用] 保存数据库...");
- DbContext.SaveChanges();
- _logger.Info("[批量删除应用] 刷新应用缓存...");
- RiskEngineService.GetInstance().RefreshApplication();
- _logger.Info("[批量删除应用] 完成");
-
- return new BatchOperationResult
+ if (items.Any(i => i.Status == BatchOperationItemStatus.Succeeded))
{
- Success = true,
- TotalCount = applicationIds.Count,
- SuccessCount = applicationIds.Count
- };
+ SaveChangesWithConcurrencyCheck();
+ RiskEngineService.GetInstance().RefreshApplication();
+ }
+ return BuildBatchResult(items);
}
///
@@ -1754,12 +1678,14 @@ namespace YLErp.Modules.RiskEngine
throw new ServiceException($"以下关联规则不可用:{string.Join(";", errors)}");
app.Status = RiskRuleStatus.Active;
+
+ app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APP_ENABLE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "启用应用配置");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshApplication();
}
@@ -1774,12 +1700,14 @@ namespace YLErp.Modules.RiskEngine
throw new ServiceException("仅已启用的应用配置可以停用");
app.Status = RiskRuleStatus.Disabled;
+
+ app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APP_DISABLE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "停用应用配置");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
RiskEngineService.GetInstance().RefreshApplication();
}
@@ -1792,88 +1720,50 @@ namespace YLErp.Modules.RiskEngine
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))
- .ToList();
-
- _logger.Info($"[批量启用] DB查询完成 - 匹配到 {apps.Count} 条应用");
-
- var foundIds = apps.Select(a => (long)a.id).ToList();
- var missingIds = applicationIds.Except(foundIds).ToList();
- if (missingIds.Any())
+ var ids = DistinctIds(applicationIds);
+ var apps = DbContext.glms_risk_rule_application.Where(a => ids.Contains(a.id)).ToDictionary(a => (long)a.id);
+ var items = new List();
+ foreach (var id in ids)
{
- return new BatchOperationResult
+ if (!apps.TryGetValue(id, out var app))
{
- 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
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "应用配置不存在"));
+ continue;
+ }
+ if (app.Status == RiskRuleStatus.Deleted)
{
- Success = true,
- TotalCount = applicationIds.Count,
- SuccessCount = applicationIds.Count
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "DELETED_ENTITY", "已删除应用不能启用"));
+ continue;
+ }
+ if (app.Status == RiskRuleStatus.Active)
+ {
+ items.Add(BatchItem(id, BatchOperationItemStatus.Unchanged, "ALREADY_ENABLED", "应用配置已启用,无需重复操作"));
+ continue;
+ }
- var allErrors = new List();
- foreach (var app in apps)
- {
- _logger.Info($"[批量启用] 校验规则 - 应用ID: {app.id}, RuleIds: {app.RuleIds}");
var errors = ValidateAndEnsureRuleCache(app.RuleIds);
- _logger.Info($"[批量启用] 规则校验结果 - 应用ID: {app.id}, Errors: {(errors.Any() ? string.Join(";", errors) : "无")}");
if (errors.Any())
- allErrors.Add($"应用 '{app.id}':{string.Join(";", errors)}");
- }
-
- if (allErrors.Any())
- {
- return new BatchOperationResult
{
- Success = false,
- TotalCount = applicationIds.Count,
- SuccessCount = 0,
- ErrorMessage = string.Join(";", allErrors)
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "RELATED_RULE_UNAVAILABLE",
+ $"关联规则不可用:{string.Join(";", errors)}"));
+ continue;
+ }
- foreach (var app in apps)
- {
- _logger.Info($"[批量启用] 写入状态 - 应用ID: {app.id}");
app.Status = RiskRuleStatus.Active;
+ app.Version++;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
-
WriteAuditLog("APP_BATCH_ENABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量启用应用配置");
+ items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "ENABLED", "应用配置已启用"));
}
- _logger.Info("[批量启用] 保存数据库...");
- DbContext.SaveChanges();
- _logger.Info("[批量启用] 刷新应用缓存...");
- RiskEngineService.GetInstance().RefreshApplication();
- _logger.Info("[批量启用] 完成");
-
- return new BatchOperationResult
+ if (items.Any(i => i.Status == BatchOperationItemStatus.Succeeded))
{
- Success = true,
- TotalCount = applicationIds.Count,
- SuccessCount = applicationIds.Count
- };
+ SaveChangesWithConcurrencyCheck();
+ RiskEngineService.GetInstance().RefreshApplication();
+ }
+ return BuildBatchResult(items);
}
///
@@ -1884,50 +1774,42 @@ namespace YLErp.Modules.RiskEngine
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)
- .ToList();
-
- _logger.Info($"[批量停用] DB查询完成 - 匹配到 {apps.Count} 条已启用应用");
-
- var foundIds = apps.Select(a => (long)a.id).ToList();
- var missingIds = applicationIds.Except(foundIds).ToList();
- if (missingIds.Any())
+ var ids = DistinctIds(applicationIds);
+ var apps = DbContext.glms_risk_rule_application.Where(a => ids.Contains(a.id)).ToDictionary(a => (long)a.id);
+ var items = new List();
+ foreach (var id in ids)
{
- return new BatchOperationResult
+ if (!apps.TryGetValue(id, out var app))
{
- Success = false,
- TotalCount = applicationIds.Count,
- SuccessCount = apps.Count,
- ErrorMessage = $"以下应用配置不存在或当前状态不允许停用:{string.Join(", ", missingIds)}"
- };
- }
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "应用配置不存在"));
+ continue;
+ }
+ if (app.Status == RiskRuleStatus.Deleted)
+ {
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "DELETED_ENTITY", "已删除应用不能停用"));
+ continue;
+ }
+ if (app.Status == RiskRuleStatus.Disabled)
+ {
+ items.Add(BatchItem(id, BatchOperationItemStatus.Unchanged, "ALREADY_DISABLED", "应用配置已停用,无需重复操作"));
+ continue;
+ }
- foreach (var app in apps)
- {
- _logger.Info($"[批量停用] 写入状态 - 应用ID: {app.id}");
app.Status = RiskRuleStatus.Disabled;
+ app.Version++;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
-
WriteAuditLog("APP_BATCH_DISABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量停用应用配置");
+ items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DISABLED", "应用配置已停用"));
}
- _logger.Info("[批量停用] 保存数据库...");
- DbContext.SaveChanges();
- _logger.Info("[批量停用] 刷新应用缓存...");
- RiskEngineService.GetInstance().RefreshApplication();
- _logger.Info("[批量停用] 完成");
-
- return new BatchOperationResult
+ if (items.Any(i => i.Status == BatchOperationItemStatus.Succeeded))
{
- Success = true,
- TotalCount = applicationIds.Count,
- SuccessCount = applicationIds.Count
- };
+ SaveChangesWithConcurrencyCheck();
+ RiskEngineService.GetInstance().RefreshApplication();
+ }
+ return BuildBatchResult(items);
}
#endregion
@@ -2237,11 +2119,11 @@ namespace YLErp.Modules.RiskEngine
try
{
DbContext.glms_risk_variable.Add(entity);
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
WriteAuditLog("VARIABLE_CREATE", "VARIABLE", entity.id, req.VariableName, $"创建变量:{req.VariableName}",
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
transaction.Commit();
}
@@ -2333,11 +2215,11 @@ namespace YLErp.Modules.RiskEngine
$"因变量'{variable.VariableName}'(ID:{variableId})表达式变更,规则表达式被级联更新");
}
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
WriteAuditLog("VAR_UPDATE", "VARIABLE", variableId, req.VariableName, $"修改变量:{variable.VariableName}");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
transaction.Commit();
}
catch
@@ -2356,7 +2238,7 @@ namespace YLErp.Modules.RiskEngine
else
{
WriteAuditLog("VAR_UPDATE", "VARIABLE", variableId, req.VariableName, $"修改变量:{variable.VariableName}");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
InvalidateVariableCache();
}
@@ -2378,7 +2260,7 @@ namespace YLErp.Modules.RiskEngine
DbContext.glms_risk_variable.Remove(variable);
WriteAuditLog("VAR_DELETE", "VARIABLE", variableId, variable.VariableName, $"删除变量:{variable.VariableName}");
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
InvalidateVariableCache();
}
@@ -2386,53 +2268,47 @@ namespace YLErp.Modules.RiskEngine
///
/// 批量删除变量(不存在或被生效规则引用的变量将被跳过)
///
- public BatchDeleteVariablesResult BatchDeleteVariables(List variableIds)
+ public BatchOperationResult BatchDeleteVariables(List variableIds)
{
if (variableIds == null || variableIds.Count == 0)
throw new ServiceException("变量ID列表不能为空");
- var distinctIds = variableIds.Distinct().ToList();
- var variables = DbContext.glms_risk_variable
- .Where(v => distinctIds.Contains(v.id))
+ var ids = DistinctIds(variableIds);
+ var variables = DbContext.glms_risk_variable.Where(v => ids.Contains(v.id)).ToDictionary(v => (long)v.id);
+ var activeRules = DbContext.glms_risk_rule
+ .Where(r => r.Status == RiskRuleStatus.Active && r.ConditionJson != null)
.ToList();
- var foundIds = variables.Select(v => (long)v.id).ToList();
- var missingIds = distinctIds.Except(foundIds).ToList();
- var blockedIds = new List();
- var blockedReasons = new Dictionary();
- var deletedIds = new List();
+ var items = new List();
- foreach (var variable in variables)
+ foreach (var id in ids)
{
- var activeRefCount = GetRulesByVariableId(variable.id)
- .Count(r => r.Status == RiskRuleStatus.Active);
+ if (!variables.TryGetValue(id, out var variable))
+ {
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "NOT_FOUND", "变量不存在"));
+ continue;
+ }
+
+ var idPattern = $@"""VariableId""\s*:\s*{id}\b";
+ var thresholdPattern = $@"""ThresholdVariableId""\s*:\s*{id}\b";
+ var activeRefCount = activeRules.Count(r => Regex.IsMatch(r.ConditionJson, idPattern) || Regex.IsMatch(r.ConditionJson, thresholdPattern));
if (activeRefCount > 0)
{
- blockedIds.Add(variable.id);
- blockedReasons[variable.id] = $"该变量被 {activeRefCount} 个生效中的规则引用,无法删除";
+ items.Add(BatchItem(id, BatchOperationItemStatus.Failed, "ACTIVE_RULE_REFERENCE",
+ $"该变量被 {activeRefCount} 个生效中的规则引用,无法删除"));
continue;
}
DbContext.glms_risk_variable.Remove(variable);
WriteAuditLog("VAR_BATCH_DELETE", "VARIABLE", variable.id, variable.VariableName, $"批量删除变量:{variable.VariableName}");
- deletedIds.Add(variable.id);
+ items.Add(BatchItem(id, BatchOperationItemStatus.Succeeded, "DELETED", "变量已删除"));
}
- if (deletedIds.Count > 0)
+ if (items.Any(i => i.Status == BatchOperationItemStatus.Succeeded))
{
- DbContext.SaveChanges();
+ SaveChangesWithConcurrencyCheck();
InvalidateVariableCache();
}
-
- return new BatchDeleteVariablesResult
- {
- Success = true,
- TotalCount = distinctIds.Count,
- SuccessCount = deletedIds.Count,
- DeletedIds = deletedIds,
- MissingIds = missingIds,
- BlockedIds = blockedIds,
- BlockedReasons = blockedReasons
- };
+ return BuildBatchResult(items);
}
///
diff --git a/YLErpWeb/Controllers/RiskRuleController.cs b/YLErpWeb/Controllers/RiskRuleController.cs
index e47aa4f7..e90a2087 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;
@@ -157,8 +157,6 @@ namespace YLErp.Web.Controllers
{
var service = GetRiskRuleService();
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)
@@ -230,8 +228,6 @@ namespace YLErp.Web.Controllers
{
var service = GetRiskRuleService();
var result = await Task.Run(() => service.BatchEnableRules(req?.Ids));
- if (!result.Success)
- return Json(new { success = false, message = result.ErrorMessage });
return Json(new { success = true, data = result });
}
catch (ServiceException ex)
@@ -254,15 +250,7 @@ namespace YLErp.Web.Controllers
{
var service = GetRiskRuleService();
var result = await Task.Run(() => service.BatchDisableRules(req?.Ids));
- if (!result.Success)
- return Json(new { success = false, message = result.ErrorMessage });
- return Json(new
- {
- success = true,
- data = result,
- cascadedAppIds = result.CascadedApplicationIds,
- cascadedAppCount = result.CascadedApplicationIds.Count
- });
+ return Json(new { success = true, data = result });
}
catch (ServiceException ex)
{
@@ -486,8 +474,6 @@ namespace YLErp.Web.Controllers
{
var service = GetRiskRuleService();
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)
@@ -510,8 +496,6 @@ namespace YLErp.Web.Controllers
{
var service = GetRiskRuleService();
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)
@@ -534,8 +518,6 @@ namespace YLErp.Web.Controllers
{
var service = GetRiskRuleService();
var result = await Task.Run(() => service.BatchDeleteApplications(req?.Ids));
- if (!result.Success)
- return Json(new { success = false, message = result.ErrorMessage });
return Json(new { success = true, data = result });
}
catch (ServiceException ex)