Merge branch 'origin/feature/p132_74-risk-engine' into glms/feature/1.4.2 (规则批量启用/应用审批优化)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量停用规则结果
|
||||
/// </summary>
|
||||
public class BatchDisableRulesResult
|
||||
{
|
||||
/// <summary>是否全部成功</summary>
|
||||
public bool Success { get; set; }
|
||||
/// <summary>总数量</summary>
|
||||
public int TotalCount { get; set; }
|
||||
/// <summary>成功数量</summary>
|
||||
public int SuccessCount { get; set; }
|
||||
/// <summary>错误信息</summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
/// <summary>级联停用的应用 ID 列表</summary>
|
||||
public List<long> CascadedApplicationIds { get; set; } = new List<long>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量操作请求(兼容前端 {ids: [...]} 格式)
|
||||
/// </summary>
|
||||
public class BatchIdsReq
|
||||
{
|
||||
public List<long> Ids { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 停用规则结果
|
||||
/// </summary>
|
||||
public class DisableRuleResult
|
||||
{
|
||||
/// <summary>级联停用的应用 ID 列表</summary>
|
||||
public List<long> CascadedApplicationIds { get; set; } = new List<long>();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
/// <summary>
|
||||
@@ -11,5 +13,7 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string RuleName { get; set; }
|
||||
/// <summary>规则描述文本</summary>
|
||||
public string RuleText { get; set; }
|
||||
/// <summary>规则状态</summary>
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Qdp.Foundation.Utilities;
|
||||
using System.Reflection;
|
||||
using YLErp.BLL;
|
||||
@@ -918,6 +918,17 @@ namespace YLErp.Modules.RiskEngine
|
||||
return true;
|
||||
}
|
||||
|
||||
var hasAnyScope =
|
||||
!IsScopeEmpty(application.ScopeAssetBookIds) ||
|
||||
!IsScopeEmpty(application.ScopeClientIds) ||
|
||||
!IsScopeEmpty(application.ScopeUnderlyingTypes) ||
|
||||
!IsScopeEmpty(application.ScopeTradeTypes);
|
||||
if (!hasAnyScope)
|
||||
{
|
||||
_logger.Error($"[风控引擎] 非全局应用未配置任何适用范围,拒绝匹配 - ApplicationId: {application.Id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trade == null)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using BaseOUDAL;
|
||||
using BaseOUDAL;
|
||||
using Newtonsoft.Json;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using YLErp.BLL;
|
||||
using YLErp.Commons;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.RiskEngine.Dto;
|
||||
|
||||
@@ -76,9 +77,11 @@ using YLErp.Modules.RiskEngine.Dto;
|
||||
UpdateRule(long, UpdateRiskRuleReq) 修改规则(乐观锁校验,Version+1)
|
||||
DeleteRule(long) 删除规则(强保护:有 Active 应用时拒绝)
|
||||
EnableRule(long) 启用规则(仅 Disabled → Active)
|
||||
DisableRule(long) 停用规则(仅 Active → Disabled)
|
||||
DisableRule(long) 停用规则(仅 Active → Disabled,级联停用关联应用)
|
||||
GetRuleVersions(long) 获取规则版本历史(从审计日志 SnapshotData 提取版本号)
|
||||
BatchDeleteRules(List<long>) 批量删除规则(逐条保护检查,单事务)
|
||||
BatchEnableRules(List<long>) 批量启用规则(逐条编译校验)
|
||||
BatchDisableRules(List<long>) 批量停用规则(级联停用关联应用)
|
||||
GetRuleApplications(long) 获取规则关联的应用配置列表
|
||||
GetAllRuleList() 获取所有启用规则(轻量字段,供应用配置下拉)
|
||||
|
||||
@@ -88,6 +91,7 @@ using YLErp.Modules.RiskEngine.Dto;
|
||||
CreateApplication(CreateRiskApplicationReq) 新建应用(关联规则 Active 校验,触发时点/Scope 校验)
|
||||
UpdateApplication(long, UpdateRiskApplicationReq) 修改应用(乐观锁校验)
|
||||
DeleteApplication(long) 删除应用(软删除)
|
||||
BatchDeleteApplications(List<long>) 批量删除应用(软删除)
|
||||
EnableApplication(long) 启用应用(校验关联规则状态)
|
||||
DisableApplication(long) 停用应用
|
||||
BatchEnableApplications(List<long>) 批量启用(逐条校验关联规则)
|
||||
@@ -207,6 +211,39 @@ namespace YLErp.Modules.RiskEngine
|
||||
{ RiskVariableDataType.Boolean, new HashSet<string> { "是", "否" } }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> AuditOperationTypeNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "RULE_CREATE", "新建规则" },
|
||||
{ "RULE_UPDATE", "修改规则" },
|
||||
{ "RULE_DELETE", "删除规则" },
|
||||
{ "RULE_ENABLE", "启用规则" },
|
||||
{ "RULE_DISABLE", "停用规则" },
|
||||
{ "RULE_BATCH_DELETE", "批量删除规则" },
|
||||
{ "RULE_BATCH_ENABLE", "批量启用规则" },
|
||||
{ "RULE_BATCH_DISABLE", "批量停用规则" },
|
||||
{ "APP_CREATE", "新建应用" },
|
||||
{ "APP_UPDATE", "修改应用" },
|
||||
{ "APP_DELETE", "删除应用" },
|
||||
{ "APP_ENABLE", "启用应用" },
|
||||
{ "APP_DISABLE", "停用应用" },
|
||||
{ "APP_BATCH_ENABLE", "批量启用应用" },
|
||||
{ "APP_BATCH_DISABLE", "批量停用应用" },
|
||||
{ "APP_BATCH_DELETE", "批量删除应用" },
|
||||
{ "RULE_CASCADE_DISABLE_APP", "规则级联停用应用" },
|
||||
{ "VAR_CREATE", "新增变量" },
|
||||
{ "VARIABLE_CREATE", "新增变量" },
|
||||
{ "VAR_UPDATE", "修改变量" },
|
||||
{ "VAR_DELETE", "删除变量" },
|
||||
{ "VAR_CASCADE_UPDATE_RULE", "变量级联更新规则" }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> AuditTargetTypeNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "RULE", "规则" },
|
||||
{ "APPLICATION", "应用" },
|
||||
{ "VARIABLE", "变量" }
|
||||
};
|
||||
|
||||
public RiskRuleService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
@@ -221,6 +258,39 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
private static string GetAuditOperationTypeName(string operationType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(operationType))
|
||||
return string.Empty;
|
||||
|
||||
return AuditOperationTypeNames.TryGetValue(operationType.Trim(), out var name)
|
||||
? name
|
||||
: operationType;
|
||||
}
|
||||
|
||||
private static string GetAuditTargetTypeName(string targetType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(targetType))
|
||||
return string.Empty;
|
||||
|
||||
return AuditTargetTypeNames.TryGetValue(targetType.Trim(), out var name)
|
||||
? name
|
||||
: targetType;
|
||||
}
|
||||
|
||||
private static string FormatAuditId(object value)
|
||||
{
|
||||
return value == null ? string.Empty : value.ToString();
|
||||
}
|
||||
|
||||
private static string SanitizeFileNamePart(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return string.Empty;
|
||||
|
||||
var sanitized = Regex.Replace(value.Trim(), @"[\\/:*?""<>|\r\n]+", "_");
|
||||
return sanitized.Length <= 40 ? sanitized : sanitized.Substring(0, 40);
|
||||
}
|
||||
/// <summary>
|
||||
/// 写入审计日志
|
||||
/// </summary>
|
||||
@@ -398,24 +468,32 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
private void ValidateScopeFields(CreateRiskApplicationReq req)
|
||||
{
|
||||
var hasAnyScope =
|
||||
!string.IsNullOrWhiteSpace(req.ScopeAssetBookIds) ||
|
||||
!string.IsNullOrWhiteSpace(req.ScopeClientIds) ||
|
||||
!string.IsNullOrWhiteSpace(req.ScopeUnderlyingTypes) ||
|
||||
!string.IsNullOrWhiteSpace(req.ScopeTradeTypes);
|
||||
|
||||
if (req.ScopeIsGlobal == 1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(req.ScopeAssetBookIds) ||
|
||||
!string.IsNullOrEmpty(req.ScopeClientIds) ||
|
||||
!string.IsNullOrEmpty(req.ScopeUnderlyingTypes) ||
|
||||
!string.IsNullOrEmpty(req.ScopeTradeTypes))
|
||||
if (hasAnyScope)
|
||||
{
|
||||
throw new ServiceException("全局适用时,不应指定其他适用范围维度");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(req.ScopeAssetBookIds))
|
||||
if (!hasAnyScope)
|
||||
{
|
||||
throw new ServiceException("非全局应用至少需要指定一个适用范围");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeAssetBookIds))
|
||||
{
|
||||
ValidateIdList(req.ScopeAssetBookIds, "适用账户");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(req.ScopeClientIds))
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeClientIds))
|
||||
{
|
||||
ValidateIdList(req.ScopeClientIds, "适用对手方");
|
||||
}
|
||||
@@ -540,6 +618,27 @@ namespace YLErp.Modules.RiskEngine
|
||||
return ids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规范化逗号分隔的规则 ID:去除空格和重复项,统一为无空格格式。
|
||||
/// </summary>
|
||||
private string NormalizeRuleIds(string ruleIds)
|
||||
{
|
||||
return string.Join(",", ParseRuleIds(ruleIds).Distinct());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EF Core 可翻译的逗号分隔字段精确匹配表达式
|
||||
/// </summary>
|
||||
private static Expression<Func<glms_risk_rule_application, bool>> 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 + ","));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验规则状态可用性并确保编译缓存就绪。
|
||||
/// 返回不可用规则信息列表,列表为空表示全部通过。
|
||||
@@ -722,7 +821,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 +839,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 +962,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} 个生效中的应用配置,请先停用或删除相关应用");
|
||||
|
||||
@@ -903,7 +1008,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// <summary>
|
||||
/// 停用规则(仅 Active → Disabled)
|
||||
/// </summary>
|
||||
public void DisableRule(long ruleId)
|
||||
public DisableRuleResult DisableRule(long ruleId)
|
||||
{
|
||||
var rule = DbContext.glms_risk_rule.FirstOrDefault(r => r.id == ruleId && r.Status == RiskRuleStatus.Active);
|
||||
if (rule == null)
|
||||
@@ -915,9 +1020,38 @@ namespace YLErp.Modules.RiskEngine
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("RULE_DISABLE", "RULE", ruleId, rule.RuleName, "停用规则");
|
||||
|
||||
// 级联停用关联的 Active 应用配置
|
||||
var activeApps = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status == RiskRuleStatus.Active)
|
||||
.Where(RuleIdsMatchExpr(ruleId))
|
||||
.ToList();
|
||||
|
||||
var cascadedIds = new List<long>();
|
||||
foreach (var app in activeApps)
|
||||
{
|
||||
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:{ruleId})停用,应用配置被级联停用");
|
||||
}
|
||||
|
||||
if (cascadedIds.Any())
|
||||
{
|
||||
_logger.Info($"[停用规则] 规则ID: {ruleId}, 级联停用应用: {string.Join(",", cascadedIds)}");
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
|
||||
RiskEngineService.GetInstance().RefreshOneRuleCache(ruleId);
|
||||
if (cascadedIds.Any())
|
||||
RiskEngineService.GetInstance().RefreshApplication();
|
||||
|
||||
return new DisableRuleResult { CascadedApplicationIds = cascadedIds };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -965,24 +1099,58 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchDeleteRules(List<long> 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<string>();
|
||||
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 +1160,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
|
||||
{
|
||||
@@ -1006,6 +1177,210 @@ namespace YLErp.Modules.RiskEngine
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量启用规则(仅 Disabled → Active,逐条编译校验)
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchEnableRules(List<long> 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();
|
||||
|
||||
_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 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
|
||||
{
|
||||
Success = true,
|
||||
TotalCount = ruleIds.Count,
|
||||
SuccessCount = ruleIds.Count
|
||||
};
|
||||
}
|
||||
|
||||
// 逐条编译校验,任一失败则整体拒绝
|
||||
var errors = new List<string>();
|
||||
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)}"
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
_logger.Info($"[批量启用规则] 写入状态 - 规则ID: {rule.id}");
|
||||
rule.Status = RiskRuleStatus.Active;
|
||||
rule.UpdateOptId = UserId;
|
||||
rule.UpdateOptName = UserName;
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
RiskEngineService.GetInstance().RefreshOneRuleCache(rule.id);
|
||||
}
|
||||
_logger.Info("[批量启用规则] 完成");
|
||||
|
||||
return new BatchOperationResult
|
||||
{
|
||||
Success = true,
|
||||
TotalCount = ruleIds.Count,
|
||||
SuccessCount = ruleIds.Count
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量停用规则(仅 Active → Disabled,级联停用关联应用)
|
||||
/// </summary>
|
||||
public BatchDisableRulesResult BatchDisableRules(List<long> 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();
|
||||
|
||||
_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 BatchDisableRulesResult
|
||||
{
|
||||
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
|
||||
{
|
||||
Success = true,
|
||||
TotalCount = ruleIds.Count,
|
||||
SuccessCount = ruleIds.Count
|
||||
};
|
||||
}
|
||||
|
||||
var allCascadedAppIds = new List<long>();
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
_logger.Info($"[批量停用规则] 写入状态 - 规则ID: {rule.id}");
|
||||
rule.Status = RiskRuleStatus.Disabled;
|
||||
rule.UpdateOptId = UserId;
|
||||
rule.UpdateOptName = UserName;
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
|
||||
// 级联停用关联的 Active 应用配置
|
||||
var activeApps = DbContext.glms_risk_rule_application
|
||||
.Where(a => a.Status == RiskRuleStatus.Active)
|
||||
.Where(RuleIdsMatchExpr(rule.id))
|
||||
.ToList();
|
||||
|
||||
var cascadedIds = new List<long>();
|
||||
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})停用,应用配置被级联停用");
|
||||
}
|
||||
|
||||
if (cascadedIds.Any())
|
||||
{
|
||||
_logger.Info($"[批量停用规则] 规则ID: {rule.id}, 级联停用应用: {string.Join(",", cascadedIds)}");
|
||||
allCascadedAppIds.AddRange(cascadedIds);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取规则关联的应用配置列表
|
||||
/// </summary>
|
||||
@@ -1013,9 +1388,9 @@ 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(a => a.Status != RiskRuleStatus.Deleted)
|
||||
.Where(RuleIdsMatchExpr(ruleId))
|
||||
.OrderByDescending(a => a.UpdateDate)
|
||||
.ToList();
|
||||
|
||||
@@ -1046,18 +1421,19 @@ namespace YLErp.Modules.RiskEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有启用规则列表(轻量字段,供下拉选择)
|
||||
/// 获取所有未删除规则列表(轻量字段,供下拉选择)
|
||||
/// </summary>
|
||||
public List<RiskRuleSimpleItem> GetAllRuleList()
|
||||
{
|
||||
return DbContext.glms_risk_rule
|
||||
.Where(r => r.Status == RiskRuleStatus.Active)
|
||||
.Where(r => r.Status != RiskRuleStatus.Deleted)
|
||||
.OrderBy(r => r.id)
|
||||
.Select(r => new RiskRuleSimpleItem
|
||||
{
|
||||
Id = r.id,
|
||||
RuleName = r.RuleName,
|
||||
RuleText = r.RuleText
|
||||
RuleText = r.RuleText,
|
||||
Status = r.Status
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
@@ -1093,8 +1469,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))
|
||||
@@ -1170,7 +1550,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
public RiskApplicationDetail CreateApplication(CreateRiskApplicationReq req)
|
||||
{
|
||||
var errors = ValidateAndEnsureRuleCache(req.RuleIds);
|
||||
var normalizedRuleIds = NormalizeRuleIds(req.RuleIds);
|
||||
var errors = ValidateAndEnsureRuleCache(normalizedRuleIds);
|
||||
if (errors.Any())
|
||||
throw new ServiceException($"以下关联规则不可用:{string.Join(";", errors)}");
|
||||
ValidateTriggerPoints(req.TriggerPoints);
|
||||
@@ -1178,7 +1559,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
var entity = new glms_risk_rule_application
|
||||
{
|
||||
RuleIds = req.RuleIds,
|
||||
RuleIds = normalizedRuleIds,
|
||||
Description = req.Description,
|
||||
Status = RiskRuleStatus.Active,
|
||||
ControlStrategy = req.ControlStrategy,
|
||||
@@ -1203,7 +1584,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
DbContext.glms_risk_rule_application.Add(entity);
|
||||
DbContext.SaveChanges();
|
||||
|
||||
WriteAuditLog("APP_CREATE", "APPLICATION", entity.id, ResolveRuleNames(req.RuleIds),
|
||||
WriteAuditLog("APP_CREATE", "APPLICATION", entity.id, ResolveRuleNames(entity.RuleIds),
|
||||
$"创建应用配置:策略={GetStrategyName(req.ControlStrategy)}, 触发时点={MapTriggerPointsToChinese(req.TriggerPoints)}",
|
||||
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
|
||||
DbContext.SaveChanges();
|
||||
@@ -1232,9 +1613,11 @@ namespace YLErp.Modules.RiskEngine
|
||||
if (app.Version != req.Version)
|
||||
throw new ServiceException("应用配置已被其他用户修改,请重新加载后再编辑");
|
||||
|
||||
string normalizedRuleIds = null;
|
||||
if (!string.IsNullOrWhiteSpace(req.RuleIds))
|
||||
{
|
||||
var errors = ValidateAndEnsureRuleCache(req.RuleIds);
|
||||
normalizedRuleIds = NormalizeRuleIds(req.RuleIds);
|
||||
var errors = ValidateAndEnsureRuleCache(normalizedRuleIds);
|
||||
if (errors.Any())
|
||||
throw new ServiceException($"以下关联规则不可用:{string.Join(";", errors)}");
|
||||
}
|
||||
@@ -1250,8 +1633,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
};
|
||||
ValidateScopeFields(scopeReq);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.RuleIds))
|
||||
app.RuleIds = req.RuleIds;
|
||||
if (normalizedRuleIds != null)
|
||||
app.RuleIds = normalizedRuleIds;
|
||||
app.Description = req.Description;
|
||||
app.ControlStrategy = req.ControlStrategy;
|
||||
app.TriggerPoints = req.TriggerPoints;
|
||||
@@ -1293,6 +1676,63 @@ namespace YLErp.Modules.RiskEngine
|
||||
RiskEngineService.GetInstance().RefreshApplication();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除应用配置(软删除)
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchDeleteApplications(List<long> applicationIds)
|
||||
{
|
||||
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())
|
||||
{
|
||||
return new BatchOperationResult
|
||||
{
|
||||
Success = false,
|
||||
TotalCount = applicationIds.Count,
|
||||
SuccessCount = 0,
|
||||
ErrorMessage = $"以下应用配置不存在或已删除:{string.Join(", ", missingIds)}"
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var app in apps)
|
||||
{
|
||||
_logger.Info($"[批量删除应用] 写入状态 - 应用ID: {app.id}");
|
||||
app.Status = RiskRuleStatus.Deleted;
|
||||
app.Version = app.Version + 1;
|
||||
app.UpdateOptId = UserId;
|
||||
app.UpdateOptName = UserName;
|
||||
app.UpdateDate = DateTime.Now;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
Success = true,
|
||||
TotalCount = applicationIds.Count,
|
||||
SuccessCount = applicationIds.Count
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用应用配置(校验关联规则状态)
|
||||
/// </summary>
|
||||
@@ -1342,16 +1782,46 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchEnableApplications(List<long> applicationIds)
|
||||
{
|
||||
_logger.Info($"[批量启用] 开始 - 传入应用数: {applicationIds?.Count ?? 0}, IDs: {string.Join(",", applicationIds ?? new List<long>())}");
|
||||
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<string>();
|
||||
foreach (var app in apps)
|
||||
@@ -1360,7 +1830,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
var errors = ValidateAndEnsureRuleCache(app.RuleIds);
|
||||
_logger.Info($"[批量启用] 规则校验结果 - 应用ID: {app.id}, Errors: {(errors.Any() ? string.Join(";", errors) : "无")}");
|
||||
if (errors.Any())
|
||||
allErrors.Add($"应用 '{app.Description}':{string.Join(";", errors)}");
|
||||
allErrors.Add($"应用 '{app.id}':{string.Join(";", errors)}");
|
||||
}
|
||||
|
||||
if (allErrors.Any())
|
||||
@@ -1404,7 +1874,10 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
public BatchOperationResult BatchDisableApplications(List<long> applicationIds)
|
||||
{
|
||||
_logger.Info($"[批量停用] 开始 - 传入应用数: {applicationIds?.Count ?? 0}, IDs: {string.Join(",", applicationIds ?? new List<long>())}");
|
||||
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 +1885,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)
|
||||
{
|
||||
@@ -1660,16 +2143,15 @@ namespace YLErp.Modules.RiskEngine
|
||||
rule.UpdateOptId = UserId;
|
||||
rule.UpdateOptName = UserName;
|
||||
rule.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("VAR_CASCADE_UPDATE_RULE", "RULE", rule.id, rule.RuleName,
|
||||
$"因变量'{variable.VariableName}'(ID:{variableId})表达式变更,规则表达式被级联更新");
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
|
||||
WriteAuditLog("VAR_UPDATE", "VARIABLE", variableId, req.VariableName, $"修改变量:{variable.VariableName}");
|
||||
|
||||
var cascadedRuleIds = affectedRules.Select(r => r.id.ToString()).ToList();
|
||||
WriteAuditLog("VAR_UPDATE_CASCADE", "VARIABLE", variableId, req.VariableName,
|
||||
$"变量 [{variable.VariableName}] VariableExpr 变更,级联更新规则 {string.Join("、", cascadedRuleIds)}");
|
||||
|
||||
DbContext.SaveChanges();
|
||||
transaction.Commit();
|
||||
}
|
||||
@@ -1746,6 +2228,11 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// 查询审计日志列表(分页)
|
||||
/// </summary>
|
||||
public SearchListResult<RiskAuditLogListItem> QueryAuditLogs(QueryRiskAuditLogReq req)
|
||||
{
|
||||
return BuildAuditLogQuery(req).ToSearchList(req);
|
||||
}
|
||||
|
||||
private IQueryable<RiskAuditLogListItem> BuildAuditLogQuery(QueryRiskAuditLogReq req)
|
||||
{
|
||||
var query = DbContext.glms_risk_rule_audit_log.AsQueryable();
|
||||
|
||||
@@ -1806,7 +2293,55 @@ namespace YLErp.Modules.RiskEngine
|
||||
OptDate = l.OptDate
|
||||
});
|
||||
|
||||
return result.ToSearchList(req);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据查询条件生成审计日志导出文件名
|
||||
/// </summary>
|
||||
public string BuildAuditLogExportFileName(QueryRiskAuditLogReq req)
|
||||
{
|
||||
req ??= new QueryRiskAuditLogReq();
|
||||
var conditions = new List<string>();
|
||||
|
||||
var operationTypes = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(req.OperationType))
|
||||
operationTypes.Add(req.OperationType.Trim());
|
||||
if (!string.IsNullOrWhiteSpace(req.OperationTypes))
|
||||
{
|
||||
operationTypes.AddRange(req.OperationTypes.Split(',',
|
||||
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
}
|
||||
var operationTypeNames = operationTypes
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Select(GetAuditOperationTypeName)
|
||||
.ToList();
|
||||
if (operationTypeNames.Any())
|
||||
conditions.Add($"操作类型-{string.Join("+", operationTypeNames)}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.TargetType))
|
||||
conditions.Add($"目标类型-{GetAuditTargetTypeName(req.TargetType)}");
|
||||
if (!string.IsNullOrWhiteSpace(req.TargetName))
|
||||
conditions.Add($"目标名称-{SanitizeFileNamePart(req.TargetName)}");
|
||||
if (!string.IsNullOrWhiteSpace(req.OptName))
|
||||
conditions.Add($"操作人-{SanitizeFileNamePart(req.OptName)}");
|
||||
|
||||
if (req.StartDate.HasValue && req.EndDate.HasValue)
|
||||
conditions.Add($"日期-{req.StartDate.Value:yyyyMMdd}至{req.EndDate.Value:yyyyMMdd}");
|
||||
else if (req.StartDate.HasValue)
|
||||
conditions.Add($"开始日期-{req.StartDate.Value:yyyyMMdd}");
|
||||
else if (req.EndDate.HasValue)
|
||||
conditions.Add($"结束日期-{req.EndDate.Value:yyyyMMdd}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.Keyword))
|
||||
conditions.Add($"关键词-{SanitizeFileNamePart(req.Keyword)}");
|
||||
|
||||
var suffix = conditions.Any() ? string.Join("_", conditions) : "全部";
|
||||
var fileNameWithoutExtension = $"风控操作日志_{suffix}";
|
||||
if (fileNameWithoutExtension.Length > 180)
|
||||
fileNameWithoutExtension = fileNameWithoutExtension.Substring(0, 180);
|
||||
|
||||
return $"{fileNameWithoutExtension}.xlsx";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1814,7 +2349,28 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// </summary>
|
||||
public byte[] ExportAuditLogs(QueryRiskAuditLogReq req)
|
||||
{
|
||||
throw new ServiceException("导出功能暂未实现");
|
||||
var logs = BuildAuditLogQuery(req).ToList();
|
||||
var columns = new List<ExcelHelper.DataColumnModel>
|
||||
{
|
||||
new ExcelHelper.DataColumnModel("日志ID", "Id", typeof(string),
|
||||
(value, _) => FormatAuditId(value)),
|
||||
new ExcelHelper.DataColumnModel("操作类型", "OperationType", typeof(string),
|
||||
(value, _) => GetAuditOperationTypeName(value?.ToString())),
|
||||
new ExcelHelper.DataColumnModel("目标类型", "TargetType", typeof(string),
|
||||
(value, _) => GetAuditTargetTypeName(value?.ToString())),
|
||||
new ExcelHelper.DataColumnModel("目标ID", "TargetId", typeof(string),
|
||||
(value, _) => FormatAuditId(value)),
|
||||
new ExcelHelper.DataColumnModel("目标名称", "TargetName", typeof(string)),
|
||||
new ExcelHelper.DataColumnModel("操作详情", "OperationDetail", typeof(string)),
|
||||
new ExcelHelper.DataColumnModel("操作结果", "Result", typeof(string)),
|
||||
new ExcelHelper.DataColumnModel("操作人", "OptName", typeof(string)),
|
||||
new ExcelHelper.DataColumnModel("操作时间", "OptDate", (value, _) =>
|
||||
value == null ? string.Empty : ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"))
|
||||
};
|
||||
|
||||
new ExcelHelper().ListToExcel<RiskAuditLogListItem>(
|
||||
columns.ToArray(), logs, "风控操作日志", true, out var buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -151,12 +151,14 @@ namespace YLErp.Web.Controllers
|
||||
[HttpDelete("risk-rules/batch")]
|
||||
[MyAuthorize("风险控制-风控规则删除")]
|
||||
/// <summary>批量删除风控规则</summary>
|
||||
public async Task<JsonResult> BatchDeleteRiskRules([FromBody] List<long> ids)
|
||||
public async Task<JsonResult> 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)
|
||||
@@ -200,8 +202,13 @@ namespace YLErp.Web.Controllers
|
||||
try
|
||||
{
|
||||
var service = GetRiskRuleService();
|
||||
await Task.Run(() => service.DisableRule(id));
|
||||
return Json(new { success = true });
|
||||
var result = await Task.Run(() => service.DisableRule(id));
|
||||
return Json(new
|
||||
{
|
||||
success = true,
|
||||
cascadedAppIds = result.CascadedApplicationIds,
|
||||
cascadedAppCount = result.CascadedApplicationIds.Count
|
||||
});
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
@@ -214,6 +221,60 @@ namespace YLErp.Web.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-rules/batch-enable")]
|
||||
[MyAuthorize("风险控制-风控规则启停")]
|
||||
/// <summary>批量启用风控规则</summary>
|
||||
public async Task<JsonResult> BatchEnableRiskRules([FromBody] BatchIdsReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量启用风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("risk-rules/batch-disable")]
|
||||
[MyAuthorize("风险控制-风控规则启停")]
|
||||
/// <summary>批量停用风控规则</summary>
|
||||
public async Task<JsonResult> BatchDisableRiskRules([FromBody] BatchIdsReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量停用风控规则");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("risk-rules/{id}/applications")]
|
||||
[MyAuthorize("风险控制-风控规则查看")]
|
||||
/// <summary>获取规则关联的应用配置列表</summary>
|
||||
@@ -238,7 +299,7 @@ namespace YLErp.Web.Controllers
|
||||
|
||||
[HttpGet("risk-rules/list")]
|
||||
[MyAuthorize("风险控制-风控规则查看")]
|
||||
/// <summary>获取所有启用规则列表(供下拉选择)</summary>
|
||||
/// <summary>获取所有未删除规则列表(供下拉选择)</summary>
|
||||
public async Task<JsonResult> GetAllRiskRules()
|
||||
{
|
||||
try
|
||||
@@ -419,12 +480,14 @@ namespace YLErp.Web.Controllers
|
||||
[HttpPost("risk-applications/batch-enable")]
|
||||
[MyAuthorize("风险控制-风控应用启停")]
|
||||
/// <summary>批量启用应用配置</summary>
|
||||
public async Task<JsonResult> BatchEnableRiskApplications([FromBody] List<long> ids)
|
||||
public async Task<JsonResult> 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 +504,14 @@ namespace YLErp.Web.Controllers
|
||||
[HttpPost("risk-applications/batch-disable")]
|
||||
[MyAuthorize("风险控制-风控应用启停")]
|
||||
/// <summary>批量停用应用配置</summary>
|
||||
public async Task<JsonResult> BatchDisableRiskApplications([FromBody] List<long> ids)
|
||||
public async Task<JsonResult> 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)
|
||||
@@ -460,6 +525,30 @@ namespace YLErp.Web.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("risk-applications/batch")]
|
||||
[MyAuthorize("风险控制-风控应用删除")]
|
||||
/// <summary>批量删除应用配置</summary>
|
||||
public async Task<JsonResult> BatchDeleteRiskApplications([FromBody] BatchIdsReq req)
|
||||
{
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量删除应用配置");
|
||||
return Json(new { success = false, message = "系统异常,请联系管理员" });
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Variable Management
|
||||
@@ -683,7 +772,8 @@ namespace YLErp.Web.Controllers
|
||||
{
|
||||
var service = GetRiskRuleService();
|
||||
var bytes = await Task.Run(() => service.ExportAuditLogs(req));
|
||||
return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "风控操作日志.xlsx");
|
||||
var fileName = service.BuildAuditLogExportFileName(req);
|
||||
return File(bytes, xlsxMimeType, fileName);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user