Files
zszq-trs/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs
T
尹峰 a259b745ce feat(风控引擎): RiskRuleService/RiskRuleController 权限控制及需求对齐
【权限控制】
  - FunctionRight.xml 注册 16 个风控引擎权限标识(规则/应用/变量/日志 四类 Tab+操作权限)
  - RiskRuleController 28 个 Action 均添加 [MyAuthorize] 校验
  - 权限管理员角色自动获得全部风控权限(UserInfo.cs)

【需求对齐】对照 v1.15 需求文档修复差异:
  - 审计日志操作类型命名对齐:APPLICATION_* → APP_*,VARIABLE_* → VAR_*
  - 规则列表新增按变量编码筛选(QueryRiskRuleReq.VariableCode → FormulaJson.Contains)
  - 变量列表新增分页支持(QueryRiskVariableReq 继承 BaseSearchReq,QueryVariableList → SearchListResult)
  - 需求文档同步至 v1.16
2026-06-23 16:01:31 +08:00

1415 lines
62 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BaseOUDAL;
using Newtonsoft.Json;
using Qdp.Foundation.Utilities;
using YLErp.BLL;
using YLErp.DBModels;
using YLErp.Model;
using YLErp.Modules.RiskEngine.Dto;
/*
================================================================================
风控规则管理服务 — RiskRuleService 技术方案说明
================================================================================
【职责定位】
RiskRuleService 是风控引擎模块的 CRUD 管理服务层,负责规则(Rule)、应用配置
Application)、变量(Variable)和操作审计日志(AuditLog)的全生命周期管理。
与 RiskEngineService(引擎执行层)的关系:
- 本服务:配置管理(增删改查 + 审计日志)
- RiskEngineService:运行时执行(缓存 + 公式解析 + 风控判定)
- 交互方式:本服务在每次写操作后调用 RiskEngineService.RefreshCache() 通知引擎刷新
【设计文档】
详细设计文档:.trae/documents/RiskRuleService详细设计.mdv1.1
需求来源文档:.trae/documents/风控引擎需求详细设计.mdv1.14)
核心概念:
- Rule(规则):定义"什么情况下触发",由变量 + 操作符 + 阈值组成的条件列表(FormulaJson
- Application(应用配置):定义"何时、对谁、怎么处理",关联规则 + 策略/时点/范围(1个规则可配N个应用)
- Variable(变量):变量池中的可选项,用于构建规则条件(含实现脚本 ImplementationScript
- AuditLog(审计日志):记录所有 CRUD 操作,支持版本追溯(SnapshotData 存储版本号快照)
【架构位置】
┌─────────────────────────────────────────────────────────────────┐
│ API 层 │
│ RiskRuleController27 个端点) │
│ - /api/risk-rules/* 规则 CRUD10 个端点) │
│ - /api/risk-applications/* 应用配置 CRUD9 个端点) │
│ - /api/risk-variables/* 变量池 CRUD6 个端点) │
│ - /api/risk-audit-logs/* 审计日志(3 个端点) │
└──────────────────────────┬──────────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────────┐
│ 服务层(本文件所在层) │
│ │
│ RiskRuleService(本文件) │
│ - 规则 / 应用 / 变量 / 审计日志的 CRUD │
│ - FormulaJson 完整校验(变量存在性、操作符、类型匹配、阈值引用) │
│ - 乐观锁并发控制(Version 字段) │
│ - 删除保护(有 Active 引用时拒绝删除) │
│ - CRUD 后触发 RiskEngineService.RefreshCache() │
│ │
│ RiskEngineService(另一团队开发,单例) │
│ - 内存缓存管理 + 公式执行 + 风控判定 │
│ - RefreshCache():从数据库重新加载 Active 规则/应用/变量 │
└──────────────────────────┬──────────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────────┐
│ 数据访问层(EF Core DbContext 直接访问,无独立 Repository
│ - glms_risk_rule 规则定义表 │
│ - glms_risk_rule_application 规则应用配置表(RuleId 外键) │
│ - glms_risk_variable 变量池定义表 │
│ - glms_risk_rule_audit_log 操作审计日志表 │
└─────────────────────────────────────────────────────────────────┘
【方法清单】(共 27 个公开方法 + 10 个私有辅助方法)
── 规则管理(10 个) ──────────────────────────────────────────────
QueryRuleList(QueryRiskRuleReq) 查询规则列表(分页,含 ApplicationCount 子查询)
GetRuleDetail(long) 获取规则详情(含关联应用摘要)
CreateRule(CreateRiskRuleReq) 新建规则(RuleCode 自动生成,FormulaJson 完整校验)
UpdateRule(long, UpdateRiskRuleReq) 修改规则(乐观锁校验,Version+1)
DeleteRule(long) 删除规则(强保护:有 Active 应用时拒绝)
EnableRule(long) 启用规则(仅 Disabled → Active
DisableRule(long) 停用规则(仅 Active → Disabled
GetRuleVersions(long) 获取规则版本历史(从审计日志 SnapshotData 提取版本号)
BatchDeleteRules(List<long>) 批量删除规则(逐条保护检查,单事务)
GetRuleApplications(long) 获取规则关联的应用配置列表
── 应用配置管理(9 个) ───────────────────────────────────────────
QueryApplicationList(QueryRiskApplicationReq) 查询应用列表(Join 规则表,多条件筛选)
GetApplicationDetail(long) 获取应用详情(含关联规则信息)
CreateApplication(CreateRiskApplicationReq) 新建应用(关联规则 Active 校验,触发时点/Scope 校验)
UpdateApplication(long, UpdateRiskApplicationReq) 修改应用(乐观锁校验)
DeleteApplication(long) 删除应用(软删除)
EnableApplication(long) 启用应用(校验关联规则状态)
DisableApplication(long) 停用应用
BatchEnableApplications(List<long>) 批量启用(逐条校验关联规则)
BatchDisableApplications(List<long>) 批量停用
── 变量管理(6 个) ───────────────────────────────────────────────
QueryVariableList(QueryRiskVariableReq) 查询变量列表(全量返回,Category/Keyword 筛选)
GetVariableDetail(long) 获取变量详情(含 ReferenceCount 引用统计)
CreateVariable(CreateRiskVariableReq) 新建变量(编码唯一性校验,脚本长度校验 ≤10000)
UpdateVariable(long, UpdateRiskVariableReq) 修改变量(乐观锁,DataType 变更保护)
DeleteVariable(long) 删除变量(引用保护:有 Active 规则引用时拒绝,硬删除)
GetAllVariableList() 获取所有已实现变量(轻量字段,供规则编辑器下拉)
── 审计日志(3 个) ───────────────────────────────────────────────
QueryAuditLogs(QueryRiskAuditLogReq) 查询审计日志(多条件筛选,分页)
ExportAuditLogs(QueryRiskAuditLogReq) 导出审计日志(占位实现,后续迭代 Excel 导出)
GetAuditLogDetail(long) 获取审计日志详情(含完整 SnapshotData
── 私有辅助方法 ───────────────────────────────────────────────────
WriteAuditLog(...) 写入审计日志(含 snapshotData 可选参数)
GenerateCode(prefix, existingCodes) 编码生成(RISK/APP-YYYYMMDD-NNNN,每日上限 9999
GetVariableCodeSet() 获取变量编码集合(5 分钟本地缓存)
InvalidateVariableCache() 清除变量缓存
ValidateFormulaJson(formulaJson) FormulaJson 完整校验
ValidateTriggerPoints(triggerPoints) 触发时点校验(5 个合法时点)
ValidateScopeFields(req) Scope 字段校验(全局互斥 + ID 格式校验)
ValidateIdList(idList, fieldName) ID 列表格式校验
TryRefreshCache() 安全调用 RefreshCache(失败仅记日志)
GetRuleOrThrow / GetApplicationOrThrow / GetVariableOrThrow 实体获取快捷方法
【关键设计决策】
1. 数据访问:直接使用 EF Core DbContext(无独立 Repository,与项目风格一致)
2. 事务策略:Create* 方法使用 BeginTransaction 包裹双 SaveChanges(实体 + 审计日志同事务)
3. 并发控制:乐观锁(Version 字段),修改时版本不匹配抛 ServiceException
4. 删除保护:强保护模式,有 Active 引用时拒绝删除规则/变量
5. RuleCode/ApplicationCodeService 层自动生成,格式 RISK/APP-YYYYMMDD-NNNN
6. FormulaJson 校验:变量存在性 + 操作符合法性(按 DataType 映射)+ 类型匹配 + 阈值变量引用
7. FormulaText:前端生成,后端仅校验非空并存储
8. Scope 字段:逗号分隔字符串存储,全局适用时不允许指定其他维度
9. 缓存刷新:写操作后调用 TryRefreshCache(),失败仅记日志不影响操作结果
10. 批量操作:单事务批量,全部成功或全部失败
11. 变量缓存:5 分钟本地 HashSet<string> 缓存,CRUD 后 InvalidateVariableCache()
12. 审计日志:16 种操作类型,CRUD + 审计日志同一事务,SnapshotData 存储版本快照
【数据库表】(MySQL,迁移脚本位于 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql
glms_risk_rule 规则定义表
- 唯一索引:RuleCode
- 普通索引:Status, OptDate
- 关键字段:FormulaJson(公式定义), FormulaText(可读文本), Version(乐观锁)
glms_risk_rule_application 规则应用配置表
- 唯一索引:ApplicationCode
- 普通索引:RuleId, Status, OptDate
- 外键:RuleId → glms_risk_rule.id
- 关键字段:ControlStrategy(禁止/审批/提示), TriggerPoints(触发时点), Scope*(适用范围)
glms_risk_variable 变量池定义表
- 唯一索引:VariableCode
- 关键字段:ImplementationScript(实现脚本), DataType(数值/日期/布尔), IsImplemented
glms_risk_rule_audit_log 操作审计日志表
- 普通索引:TargetType+TargetId, OperationType, OptDate
- 关键字段:SnapshotDataJSON 快照,存储版本号等)
【关键文件清单】
本服务:
- RiskRuleService.cs(本文件):规则/应用/变量/日志 CRUD 管理
- Dto/ 目录:请求/响应 DTO 模型
关联服务:
- RiskEngineService.cs:引擎执行层(单例,本服务通过 GetInstance() 获取引用)
- RiskRuleController.csYLErpWeb/Controllers/):API 控制器(27 个端点)
实体模型:
- Framework/YLErp.Core/DBModels/glms_risk_rule.cs
- Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs
- Framework/YLErp.Core/DBModels/glms_risk_variable.cs
- Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs
枚举定义:
- Framework/YLErp.Core/DBModels/Enums/RiskRuleStatus.csActive/Disabled/Deleted
- Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.csBlock/Approve/Warn
- Framework/YLErp.Core/DBModels/Enums/RiskVariableCategory.cs
- Framework/YLErp.Core/DBModels/Enums/RiskVariableDataType.csNumeric/Date/Boolean
【命名空间】
YLErp.Modules.RiskEngine
【注意事项】
1. RiskEngineService 由另一个团队开发实现,本服务通过 GetInstance() 获取单例引用
2. 所有写操作后必须调用 TryRefreshCache(),确保引擎缓存与数据库一致
3. 变量删除为硬删除(物理删除),规则/应用删除为软删除(Status=Deleted
4. ExportAuditLogs 当前为占位实现,Excel 导出功能待后续迭代
5. ImplementationScript 当前仅做基础长度校验(≤10000),完整编译校验待引入 Roslyn 库
6. ValidateScopeFields 的 ID 存在性校验已跳过(前端下拉选择器保证有效性)
================================================================================
*/
namespace YLErp.Modules.RiskEngine
{
public class RiskRuleService : YLBaseService
{
private readonly IYcLogger _logger = LogFactory.GetLogger("RiskRuleService");
private readonly RiskEngineService _riskEngineService;
private HashSet<string> _variableCodeCache;
private DateTime _variableCacheUpdateTime;
private static readonly HashSet<string> ValidTriggerPoints = new HashSet<string>
{
"BOOK_CONFIRM",
"CLOSE_REVIEW",
"UPLOAD_CONFIRMATION",
"EVENT_TRIGGER",
"FUND_PAYMENT"
};
private static readonly Dictionary<RiskVariableDataType, HashSet<string>> ValidOperatorsByType = new Dictionary<RiskVariableDataType, HashSet<string>>
{
{ RiskVariableDataType.Numeric, new HashSet<string> { ">", "<", ">=", "<=", "=", "≠", "介于", "不介于" } },
{ RiskVariableDataType.Date, new HashSet<string> { "<", ">", "=", ">=", "<=", "介于", "不介于" } },
{ RiskVariableDataType.Boolean, new HashSet<string> { "是", "否" } }
};
public RiskRuleService(OptUserInfo userInfo) : base(userInfo)
{
_riskEngineService = RiskEngineService.GetInstance();
}
public RiskRuleService(YLBaseService baseService) : base(baseService)
{
_riskEngineService = RiskEngineService.GetInstance();
}
public RiskRuleService(OptUserInfo optUser, YLContext dbContext) : base(optUser, dbContext)
{
_riskEngineService = RiskEngineService.GetInstance();
}
#region Private Helpers
private void WriteAuditLog(string operationType, string targetType, long targetId,
string targetName, string targetCode, string operationDetail, string result = "成功", string snapshotData = null)
{
var log = new glms_risk_rule_audit_log
{
OperationType = operationType,
TargetType = targetType,
TargetId = targetId,
TargetName = targetName,
TargetCode = targetCode,
OperationDetail = operationDetail,
Result = result,
SnapshotData = snapshotData,
OptId = UserId,
OptName = UserName,
OptDate = DateTime.Now
};
DbContext.glms_risk_rule_audit_log.Add(log);
}
private string GenerateCode(string prefix, IQueryable<string> existingCodes)
{
var today = DateTime.Now.ToString("yyyyMMdd");
var pattern = $"{prefix}-{today}-";
var maxCode = existingCodes
.Where(c => c.StartsWith(pattern))
.OrderByDescending(c => c)
.FirstOrDefault();
int sequence;
if (string.IsNullOrEmpty(maxCode))
{
sequence = 1;
}
else
{
var lastPart = maxCode.Substring(pattern.Length);
if (!int.TryParse(lastPart, out int lastSequence))
throw new ServiceException("编码格式异常");
sequence = lastSequence + 1;
}
if (sequence > 9999)
throw new ServiceException("今日编码已达上限,请明日再试");
return $"{pattern}{sequence:D4}";
}
private HashSet<string> GetVariableCodeSet()
{
if (_variableCodeCache == null ||
DateTime.Now - _variableCacheUpdateTime > TimeSpan.FromMinutes(5))
{
_variableCodeCache = DbContext.glms_risk_variable
.Where(v => v.IsImplemented)
.Select(v => v.VariableCode)
.ToHashSet();
_variableCacheUpdateTime = DateTime.Now;
}
return _variableCodeCache;
}
private void InvalidateVariableCache()
{
_variableCodeCache = null;
}
private void ValidateFormulaJson(string formulaJson)
{
FormulaDefinition formula;
try
{
formula = JsonConvert.DeserializeObject<FormulaDefinition>(formulaJson);
}
catch
{
throw new ServiceException("公式表达式 JSON 格式不合法");
}
if (formula.Conditions == null || formula.Conditions.Count == 0)
throw new ServiceException("公式条件列表不能为空");
if (formula.LogicOperator != "AND")
throw new ServiceException("当前仅支持 AND 逻辑运算符");
var allVariableCodes = GetVariableCodeSet();
for (int i = 0; i < formula.Conditions.Count; i++)
{
var cond = formula.Conditions[i];
var condLabel = $"条件{i + 1}";
if (!allVariableCodes.Contains(cond.VariableCode))
throw new ServiceException($"{condLabel}:变量编码 '{cond.VariableCode}' 不存在或未实现");
var variableDef = DbContext.glms_risk_variable
.FirstOrDefault(v => v.VariableCode == cond.VariableCode);
var validOperators = ValidOperatorsByType[variableDef.DataType];
if (!validOperators.Contains(cond.Operator))
throw new ServiceException($"{condLabel}:操作符 '{cond.Operator}' 不适用于{GetDataTypeName(variableDef.DataType)}类型变量");
if (cond.ThresholdType == "fixed")
{
if (cond.Value == null)
throw new ServiceException($"{condLabel}:固定阈值不能为空");
if (variableDef.DataType == RiskVariableDataType.Numeric)
{
if (!decimal.TryParse(cond.Value.ToString(), out _))
throw new ServiceException($"{condLabel}:数值型变量的阈值必须为数字");
}
if (variableDef.DataType == RiskVariableDataType.Date)
{
if (!DateTime.TryParse(cond.Value.ToString(), out _))
throw new ServiceException($"{condLabel}:日期型变量的阈值必须为合法日期");
}
}
else if (cond.ThresholdType == "variable")
{
if (string.IsNullOrEmpty(cond.ThresholdVariableCode))
throw new ServiceException($"{condLabel}:变量阈值引用的变量编码不能为空");
if (!allVariableCodes.Contains(cond.ThresholdVariableCode))
throw new ServiceException($"{condLabel}:阈值变量编码 '{cond.ThresholdVariableCode}' 不存在或未实现");
var thresholdVarDef = DbContext.glms_risk_variable
.FirstOrDefault(v => v.VariableCode == cond.ThresholdVariableCode);
if (thresholdVarDef.DataType != variableDef.DataType)
throw new ServiceException($"{condLabel}:阈值变量与条件变量的数据类型不一致");
}
else
{
throw new ServiceException($"{condLabel}:阈值类型 '{cond.ThresholdType}' 不合法,仅支持 fixed/variable");
}
}
}
private void ValidateTriggerPoints(string triggerPoints)
{
if (string.IsNullOrWhiteSpace(triggerPoints))
throw new ServiceException("触发时点不能为空");
var points = triggerPoints.Split(',');
foreach (var point in points)
{
var trimmed = point.Trim();
if (!ValidTriggerPoints.Contains(trimmed))
throw new ServiceException($"触发时点 '{trimmed}' 不合法");
}
if (points.All(p => string.IsNullOrWhiteSpace(p)))
throw new ServiceException("至少需要指定一个触发时点");
}
private void ValidateScopeFields(CreateRiskApplicationReq req)
{
if (req.ScopeIsGlobal)
{
if (!string.IsNullOrEmpty(req.ScopeAssetBookIds) ||
!string.IsNullOrEmpty(req.ScopeClientIds) ||
!string.IsNullOrEmpty(req.ScopeUnderlyingTypes) ||
!string.IsNullOrEmpty(req.ScopeTradeTypes))
{
throw new ServiceException("全局适用时,不应指定其他适用范围维度");
}
return;
}
if (!string.IsNullOrEmpty(req.ScopeAssetBookIds))
{
ValidateIdList(req.ScopeAssetBookIds, "适用账户");
}
if (!string.IsNullOrEmpty(req.ScopeClientIds))
{
ValidateIdList(req.ScopeClientIds, "适用对手方");
}
}
private void ValidateIdList(string idList, string fieldName)
{
var parts = idList.Split(',');
foreach (var part in parts)
{
if (!int.TryParse(part.Trim(), out _))
throw new ServiceException($"{fieldName}列表格式不合法,应为逗号分隔的数字 ID");
}
}
private void TryRefreshCache()
{
try
{
_riskEngineService.RefreshCache();
}
catch (Exception ex)
{
_logger.Error($"缓存刷新失败:{ex.Message}", ex);
}
}
private string GetDataTypeName(RiskVariableDataType dataType)
{
return dataType switch
{
RiskVariableDataType.Numeric => "数值",
RiskVariableDataType.Date => "日期",
RiskVariableDataType.Boolean => "布尔",
_ => "未知"
};
}
private glms_risk_rule GetRuleOrThrow(long ruleId)
{
var rule = DbContext.glms_risk_rule.FirstOrDefault(r => r.id == ruleId && r.Status != RiskRuleStatus.Deleted);
if (rule == null)
throw new ServiceException("规则不存在或已删除");
return rule;
}
private glms_risk_rule_application GetApplicationOrThrow(long applicationId)
{
var app = DbContext.glms_risk_rule_application.FirstOrDefault(a => a.id == applicationId && a.Status != RiskRuleStatus.Deleted);
if (app == null)
throw new ServiceException("应用配置不存在或已删除");
return app;
}
private glms_risk_variable GetVariableOrThrow(long variableId)
{
var variable = DbContext.glms_risk_variable.FirstOrDefault(v => v.id == variableId);
if (variable == null)
throw new ServiceException("变量不存在");
return variable;
}
private void ValidateRuleParams(string ruleName, string formulaJson, string formulaText)
{
if (string.IsNullOrWhiteSpace(ruleName))
throw new ServiceException("规则名称不能为空");
if (ruleName.Length > 200)
throw new ServiceException("规则名称长度不能超过200字符");
if (string.IsNullOrWhiteSpace(formulaJson))
throw new ServiceException("公式表达式不能为空");
if (string.IsNullOrWhiteSpace(formulaText))
throw new ServiceException("公式可读文本不能为空");
ValidateFormulaJson(formulaJson);
}
#endregion
#region Rule Management
public SearchListResult<RiskRuleListItem> QueryRuleList(QueryRiskRuleReq req)
{
var query = DbContext.glms_risk_rule
.Where(r => r.Status != RiskRuleStatus.Deleted);
if (!string.IsNullOrWhiteSpace(req.Keyword))
{
query = query.Where(r => r.RuleCode.Contains(req.Keyword)
|| r.RuleName.Contains(req.Keyword)
|| r.RuleDescription.Contains(req.Keyword));
}
if (req.Status.HasValue)
{
query = query.Where(r => r.Status == req.Status.Value);
}
if (!string.IsNullOrWhiteSpace(req.VariableCode))
{
query = query.Where(r => r.FormulaJson.Contains(req.VariableCode));
}
var result = query.OrderByDescending(r => r.UpdateDate)
.Select(r => new RiskRuleListItem
{
Id = r.id,
RuleCode = r.RuleCode,
RuleName = r.RuleName,
RuleDescription = r.RuleDescription,
FormulaText = r.FormulaText,
Status = r.Status,
Version = r.Version,
OptName = r.OptName,
OptDate = r.OptDate.GetValueOrDefault(),
UpdateOptName = r.UpdateOptName,
UpdateDate = r.UpdateDate ?? r.OptDate.GetValueOrDefault(),
ApplicationCount = DbContext.glms_risk_rule_application.Count(a => a.RuleId == r.id && a.Status != RiskRuleStatus.Deleted)
});
return result.ToSearchList(req);
}
public RiskRuleDetail GetRuleDetail(long ruleId)
{
var rule = GetRuleOrThrow(ruleId);
var applications = DbContext.glms_risk_rule_application
.Where(a => a.RuleId == ruleId && a.Status != RiskRuleStatus.Deleted)
.Select(a => new RiskRuleApplicationSummary
{
Id = a.id,
ApplicationCode = a.ApplicationCode,
Status = a.Status,
ControlStrategy = a.ControlStrategy,
TriggerPoints = a.TriggerPoints
})
.ToList();
return new RiskRuleDetail
{
Id = rule.id,
RuleCode = rule.RuleCode,
RuleName = rule.RuleName,
RuleDescription = rule.RuleDescription,
FormulaJson = rule.FormulaJson,
FormulaText = rule.FormulaText,
Status = rule.Status,
Version = rule.Version,
OptName = rule.OptName,
OptDate = rule.OptDate.GetValueOrDefault(),
UpdateOptName = rule.UpdateOptName,
UpdateDate = rule.UpdateDate ?? rule.OptDate.GetValueOrDefault(),
Applications = applications
};
}
public RiskRuleDetail CreateRule(CreateRiskRuleReq req)
{
ValidateRuleParams(req.RuleName, req.FormulaJson, req.FormulaText);
var ruleCode = GenerateCode("RISK", DbContext.glms_risk_rule.Select(r => r.RuleCode));
var entity = new glms_risk_rule
{
RuleCode = ruleCode,
RuleName = req.RuleName,
RuleDescription = req.RuleDescription,
FormulaJson = req.FormulaJson,
FormulaText = req.FormulaText,
Status = RiskRuleStatus.Active,
Version = 1
};
SetDBModelOpt(entity);
entity.UpdateOptId = UserId;
entity.UpdateOptName = UserName;
entity.UpdateDate = DateTime.Now;
using (var transaction = DbContext.Database.BeginTransaction())
{
try
{
DbContext.glms_risk_rule.Add(entity);
DbContext.SaveChanges();
WriteAuditLog("RULE_CREATE", "RULE", entity.id, req.RuleName, entity.RuleCode, "创建规则",
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
DbContext.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
TryRefreshCache();
return GetRuleDetail(entity.id);
}
public RiskRuleDetail UpdateRule(long ruleId, UpdateRiskRuleReq req)
{
var rule = GetRuleOrThrow(ruleId);
if (rule.Version != req.ExpectedVersion)
throw new ServiceException("规则已被其他用户修改,请重新加载后再编辑");
ValidateRuleParams(req.RuleName, req.FormulaJson, req.FormulaText);
rule.RuleName = req.RuleName;
rule.RuleDescription = req.RuleDescription;
rule.FormulaJson = req.FormulaJson;
rule.FormulaText = req.FormulaText;
rule.Version = rule.Version + 1;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
WriteAuditLog("RULE_UPDATE", "RULE", ruleId, req.RuleName, rule.RuleCode, "修改规则");
DbContext.SaveChanges();
TryRefreshCache();
return GetRuleDetail(ruleId);
}
public void DeleteRule(long ruleId)
{
var rule = GetRuleOrThrow(ruleId);
var activeCount = DbContext.glms_risk_rule_application
.Count(a => a.RuleId == ruleId && a.Status == RiskRuleStatus.Active);
if (activeCount > 0)
throw new ServiceException($"该规则仍有 {activeCount} 个生效中的应用配置,请先停用或删除相关应用");
rule.Status = RiskRuleStatus.Deleted;
rule.Version = rule.Version + 1;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
WriteAuditLog("RULE_DELETE", "RULE", ruleId, rule.RuleName, rule.RuleCode, "删除规则");
DbContext.SaveChanges();
TryRefreshCache();
}
public void EnableRule(long ruleId)
{
var rule = DbContext.glms_risk_rule.FirstOrDefault(r => r.id == ruleId && r.Status == RiskRuleStatus.Disabled);
if (rule == null)
throw new ServiceException("仅已停用的规则可以启用");
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, rule.RuleCode, "启用规则",
snapshotData: JsonConvert.SerializeObject(new { Version = rule.Version }));
DbContext.SaveChanges();
TryRefreshCache();
}
public void DisableRule(long ruleId)
{
var rule = DbContext.glms_risk_rule.FirstOrDefault(r => r.id == ruleId && r.Status == RiskRuleStatus.Active);
if (rule == null)
throw new ServiceException("仅已生效的规则可以停用");
rule.Status = RiskRuleStatus.Disabled;
rule.Version = rule.Version + 1;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
WriteAuditLog("RULE_DISABLE", "RULE", ruleId, rule.RuleName, rule.RuleCode, "停用规则");
DbContext.SaveChanges();
TryRefreshCache();
}
public List<RiskRuleVersionItem> GetRuleVersions(long ruleId)
{
GetRuleOrThrow(ruleId);
var logs = DbContext.glms_risk_rule_audit_log
.Where(l => l.TargetType == "RULE" && l.TargetId == ruleId)
.OrderByDescending(l => l.OptDate)
.ToList();
var result = new List<RiskRuleVersionItem>();
foreach (var l in logs)
{
int version = 0;
if (!string.IsNullOrEmpty(l.SnapshotData))
{
try
{
var snapshot = JsonConvert.DeserializeAnonymousType(l.SnapshotData, new { Version = 0 });
if (snapshot != null)
version = snapshot.Version;
}
catch { }
}
result.Add(new RiskRuleVersionItem
{
Version = version,
OperationType = l.OperationType,
OperationDetail = l.OperationDetail,
OptName = l.OptName,
OptDate = l.OptDate
});
}
return result;
}
public BatchOperationResult BatchDeleteRules(List<long> 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("部分规则不存在或已删除");
foreach (var rule in rules)
{
var activeCount = DbContext.glms_risk_rule_application
.Count(a => a.RuleId == rule.id && a.Status == RiskRuleStatus.Active);
if (activeCount > 0)
throw new ServiceException($"规则 '{rule.RuleName}' 仍有 {activeCount} 个生效中的应用配置,请先停用或删除相关应用");
}
foreach (var rule in rules)
{
rule.Status = RiskRuleStatus.Deleted;
rule.Version = rule.Version + 1;
rule.UpdateOptId = UserId;
rule.UpdateOptName = UserName;
rule.UpdateDate = DateTime.Now;
WriteAuditLog("RULE_BATCH_DELETE", "RULE", rule.id, rule.RuleName, rule.RuleCode, "批量删除规则");
}
DbContext.SaveChanges();
TryRefreshCache();
return new BatchOperationResult
{
Success = true,
TotalCount = ruleIds.Count,
SuccessCount = ruleIds.Count
};
}
public List<RiskApplicationListItem> GetRuleApplications(long ruleId)
{
GetRuleOrThrow(ruleId);
var result = from app in DbContext.glms_risk_rule_application
join rule in DbContext.glms_risk_rule on app.RuleId equals rule.id
where app.RuleId == ruleId && app.Status != RiskRuleStatus.Deleted
orderby app.UpdateDate descending
select new RiskApplicationListItem
{
Id = app.id,
ApplicationCode = app.ApplicationCode,
RuleId = app.RuleId,
RuleCode = rule.RuleCode,
RuleName = rule.RuleName,
FormulaText = rule.FormulaText,
Status = app.Status,
ControlStrategy = app.ControlStrategy,
TriggerPoints = app.TriggerPoints,
ScopeAssetBookIds = app.ScopeAssetBookIds,
ScopeClientIds = app.ScopeClientIds,
ScopeUnderlyingTypes = app.ScopeUnderlyingTypes,
ScopeTradeTypes = app.ScopeTradeTypes,
ScopeIsGlobal = app.ScopeIsGlobal,
Version = app.Version,
OptName = app.OptName,
OptDate = app.OptDate.GetValueOrDefault(),
UpdateOptName = app.UpdateOptName,
UpdateDate = app.UpdateDate ?? app.OptDate.GetValueOrDefault()
};
return result.ToList();
}
#endregion
#region Application Management
public SearchListResult<RiskApplicationListItem> QueryApplicationList(QueryRiskApplicationReq req)
{
var query = from app in DbContext.glms_risk_rule_application
join rule in DbContext.glms_risk_rule on app.RuleId equals rule.id
where app.Status != RiskRuleStatus.Deleted && rule.Status != RiskRuleStatus.Deleted
select new { app, rule };
if (!string.IsNullOrWhiteSpace(req.Keyword))
{
query = query.Where(x => x.app.ApplicationCode.Contains(req.Keyword)
|| x.rule.RuleName.Contains(req.Keyword));
}
if (req.Status.HasValue)
{
query = query.Where(x => x.app.Status == req.Status.Value);
}
if (req.Strategy.HasValue)
{
query = query.Where(x => x.app.ControlStrategy == req.Strategy.Value);
}
if (!string.IsNullOrWhiteSpace(req.TriggerPoint))
{
query = query.Where(x => x.app.TriggerPoints.Contains(req.TriggerPoint));
}
var result = query.OrderByDescending(x => x.app.UpdateDate)
.Select(x => new RiskApplicationListItem
{
Id = x.app.id,
ApplicationCode = x.app.ApplicationCode,
RuleId = x.app.RuleId,
RuleCode = x.rule.RuleCode,
RuleName = x.rule.RuleName,
FormulaText = x.rule.FormulaText,
Status = x.app.Status,
ControlStrategy = x.app.ControlStrategy,
TriggerPoints = x.app.TriggerPoints,
ScopeAssetBookIds = x.app.ScopeAssetBookIds,
ScopeClientIds = x.app.ScopeClientIds,
ScopeUnderlyingTypes = x.app.ScopeUnderlyingTypes,
ScopeTradeTypes = x.app.ScopeTradeTypes,
ScopeIsGlobal = x.app.ScopeIsGlobal,
Version = x.app.Version,
OptName = x.app.OptName,
OptDate = x.app.OptDate.GetValueOrDefault(),
UpdateOptName = x.app.UpdateOptName,
UpdateDate = x.app.UpdateDate ?? x.app.OptDate.GetValueOrDefault()
});
return result.ToSearchList(req);
}
public RiskApplicationDetail GetApplicationDetail(long applicationId)
{
var detail = (from app in DbContext.glms_risk_rule_application
join rule in DbContext.glms_risk_rule on app.RuleId equals rule.id
where app.id == applicationId && app.Status != RiskRuleStatus.Deleted
select new RiskApplicationDetail
{
Id = app.id,
ApplicationCode = app.ApplicationCode,
RuleId = app.RuleId,
RuleCode = rule.RuleCode,
RuleName = rule.RuleName,
FormulaJson = rule.FormulaJson,
FormulaText = rule.FormulaText,
Status = app.Status,
ControlStrategy = app.ControlStrategy,
TriggerPoints = app.TriggerPoints,
ScopeAssetBookIds = app.ScopeAssetBookIds,
ScopeClientIds = app.ScopeClientIds,
ScopeUnderlyingTypes = app.ScopeUnderlyingTypes,
ScopeTradeTypes = app.ScopeTradeTypes,
ScopeIsGlobal = app.ScopeIsGlobal,
Version = app.Version,
OptName = app.OptName,
OptDate = app.OptDate.GetValueOrDefault(),
UpdateOptName = app.UpdateOptName,
UpdateDate = app.UpdateDate ?? app.OptDate.GetValueOrDefault()
}).FirstOrDefault();
if (detail == null)
throw new ServiceException("应用配置不存在或已删除");
return detail;
}
public RiskApplicationDetail CreateApplication(CreateRiskApplicationReq req)
{
if (req.RuleId <= 0)
throw new ServiceException("关联规则 ID 不合法");
var rule = DbContext.glms_risk_rule.FirstOrDefault(r => r.id == req.RuleId && r.Status != RiskRuleStatus.Deleted);
if (rule == null)
throw new ServiceException("关联规则不存在或已删除");
if (rule.Status == RiskRuleStatus.Disabled)
throw new ServiceException("关联规则已停用,无法创建应用配置");
ValidateTriggerPoints(req.TriggerPoints);
ValidateScopeFields(req);
var applicationCode = GenerateCode("APP", DbContext.glms_risk_rule_application.Select(a => a.ApplicationCode));
var entity = new glms_risk_rule_application
{
ApplicationCode = applicationCode,
RuleId = req.RuleId,
Status = RiskRuleStatus.Active,
ControlStrategy = req.ControlStrategy,
TriggerPoints = req.TriggerPoints,
ScopeAssetBookIds = req.ScopeAssetBookIds,
ScopeClientIds = req.ScopeClientIds,
ScopeUnderlyingTypes = req.ScopeUnderlyingTypes,
ScopeTradeTypes = req.ScopeTradeTypes,
ScopeIsGlobal = req.ScopeIsGlobal,
Version = 1
};
SetDBModelOpt(entity);
entity.UpdateOptId = UserId;
entity.UpdateOptName = UserName;
entity.UpdateDate = DateTime.Now;
using (var transaction = DbContext.Database.BeginTransaction())
{
try
{
DbContext.glms_risk_rule_application.Add(entity);
DbContext.SaveChanges();
WriteAuditLog("APP_CREATE", "APPLICATION", entity.id, entity.ApplicationCode, entity.ApplicationCode,
$"创建应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}",
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
DbContext.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
TryRefreshCache();
return GetApplicationDetail(entity.id);
}
public RiskApplicationDetail UpdateApplication(long applicationId, UpdateRiskApplicationReq req)
{
var app = GetApplicationOrThrow(applicationId);
if (app.Version != req.ExpectedVersion)
throw new ServiceException("应用配置已被其他用户修改,请重新加载后再编辑");
ValidateTriggerPoints(req.TriggerPoints);
var scopeReq = new CreateRiskApplicationReq
{
ScopeAssetBookIds = req.ScopeAssetBookIds,
ScopeClientIds = req.ScopeClientIds,
ScopeUnderlyingTypes = req.ScopeUnderlyingTypes,
ScopeTradeTypes = req.ScopeTradeTypes,
ScopeIsGlobal = req.ScopeIsGlobal
};
ValidateScopeFields(scopeReq);
app.ControlStrategy = req.ControlStrategy;
app.TriggerPoints = req.TriggerPoints;
app.ScopeAssetBookIds = req.ScopeAssetBookIds;
app.ScopeClientIds = req.ScopeClientIds;
app.ScopeUnderlyingTypes = req.ScopeUnderlyingTypes;
app.ScopeTradeTypes = req.ScopeTradeTypes;
app.ScopeIsGlobal = req.ScopeIsGlobal;
app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APPLICATION_UPDATE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode,
$"修改应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}");
DbContext.SaveChanges();
TryRefreshCache();
return GetApplicationDetail(applicationId);
}
public void DeleteApplication(long applicationId)
{
var app = GetApplicationOrThrow(applicationId);
app.Status = RiskRuleStatus.Deleted;
app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APPLICATION_DELETE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode, "删除应用配置");
DbContext.SaveChanges();
TryRefreshCache();
}
public void EnableApplication(long applicationId)
{
var app = DbContext.glms_risk_rule_application.FirstOrDefault(a => a.id == applicationId && a.Status == RiskRuleStatus.Disabled);
if (app == null)
throw new ServiceException("仅已停用的应用配置可以启用");
var rule = DbContext.glms_risk_rule.FirstOrDefault(r => r.id == app.RuleId && r.Status != RiskRuleStatus.Deleted);
if (rule == null)
throw new ServiceException("关联规则不存在或已删除,无法启用应用配置");
if (rule.Status == RiskRuleStatus.Disabled)
throw new ServiceException("关联规则已停用,无法启用应用配置");
app.Status = RiskRuleStatus.Active;
app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APP_ENABLE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode, "启用应用配置");
DbContext.SaveChanges();
TryRefreshCache();
}
public void DisableApplication(long applicationId)
{
var app = DbContext.glms_risk_rule_application.FirstOrDefault(a => a.id == applicationId && a.Status == RiskRuleStatus.Active);
if (app == null)
throw new ServiceException("仅已生效的应用配置可以停用");
app.Status = RiskRuleStatus.Disabled;
app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APPLICATION_DISABLE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode, "停用应用配置");
DbContext.SaveChanges();
TryRefreshCache();
}
public BatchOperationResult BatchEnableApplications(List<long> applicationIds)
{
var apps = DbContext.glms_risk_rule_application
.Where(a => applicationIds.Contains(a.id) && a.Status == RiskRuleStatus.Disabled)
.ToList();
if (apps.Count != applicationIds.Count)
throw new ServiceException("部分应用配置不存在或当前状态不允许启用");
foreach (var app in apps)
{
var rule = DbContext.glms_risk_rule.FirstOrDefault(r => r.id == app.RuleId && r.Status != RiskRuleStatus.Deleted);
if (rule == null)
throw new ServiceException($"应用配置 '{app.ApplicationCode}' 的关联规则不存在或已删除");
if (rule.Status != RiskRuleStatus.Active)
throw new ServiceException($"应用配置 '{app.ApplicationCode}' 的关联规则未生效,无法启用");
}
foreach (var app in apps)
{
app.Status = RiskRuleStatus.Active;
app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APPLICATION_BATCH_ENABLE", "APPLICATION", app.id, app.ApplicationCode, app.ApplicationCode, "批量启用应用配置");
}
DbContext.SaveChanges();
TryRefreshCache();
return new BatchOperationResult
{
Success = true,
TotalCount = applicationIds.Count,
SuccessCount = applicationIds.Count
};
}
public BatchOperationResult BatchDisableApplications(List<long> applicationIds)
{
var apps = DbContext.glms_risk_rule_application
.Where(a => applicationIds.Contains(a.id) && a.Status == RiskRuleStatus.Active)
.ToList();
if (apps.Count != applicationIds.Count)
throw new ServiceException("部分应用配置不存在或当前状态不允许停用");
foreach (var app in apps)
{
app.Status = RiskRuleStatus.Disabled;
app.Version = app.Version + 1;
app.UpdateOptId = UserId;
app.UpdateOptName = UserName;
app.UpdateDate = DateTime.Now;
WriteAuditLog("APPLICATION_BATCH_DISABLE", "APPLICATION", app.id, app.ApplicationCode, app.ApplicationCode, "批量停用应用配置");
}
DbContext.SaveChanges();
TryRefreshCache();
return new BatchOperationResult
{
Success = true,
TotalCount = applicationIds.Count,
SuccessCount = applicationIds.Count
};
}
#endregion
#region Variable Management
public SearchListResult<RiskVariableListItem> QueryVariableList(QueryRiskVariableReq req)
{
var query = DbContext.glms_risk_variable.AsQueryable();
if (req.Category.HasValue)
{
query = query.Where(v => v.Category == req.Category.Value);
}
if (!string.IsNullOrWhiteSpace(req.Keyword))
{
query = query.Where(v => v.VariableCode.Contains(req.Keyword)
|| v.VariableName.Contains(req.Keyword));
}
var result = query.OrderBy(v => v.SortOrder).ThenBy(v => v.VariableCode)
.Select(v => new RiskVariableListItem
{
Id = v.id,
VariableCode = v.VariableCode,
VariableName = v.VariableName,
Category = v.Category,
DataType = v.DataType,
Unit = v.Unit,
ValueDomain = v.ValueDomain,
Description = v.Description,
IsImplemented = v.IsImplemented,
Version = v.Version,
SortOrder = v.SortOrder
});
return result.ToSearchList(req);
}
public RiskVariableDetail GetVariableDetail(long variableId)
{
var variable = GetVariableOrThrow(variableId);
var referenceCount = DbContext.glms_risk_rule
.Count(r => r.Status == RiskRuleStatus.Active && r.FormulaJson.Contains(variable.VariableCode));
return new RiskVariableDetail
{
Id = variable.id,
VariableCode = variable.VariableCode,
VariableName = variable.VariableName,
Category = variable.Category,
DataType = variable.DataType,
Unit = variable.Unit,
ValueDomain = variable.ValueDomain,
Description = variable.Description,
ImplementationScript = variable.ImplementationScript,
SourceExpression = variable.SourceExpression,
IsImplemented = variable.IsImplemented,
Version = variable.Version,
SortOrder = variable.SortOrder,
OptName = variable.OptName,
OptDate = variable.OptDate.GetValueOrDefault(),
UpdateOptName = variable.UpdateOptName,
UpdateDate = variable.UpdateDate ?? variable.OptDate.GetValueOrDefault(),
ReferenceCount = referenceCount
};
}
public RiskVariableDetail CreateVariable(CreateRiskVariableReq req)
{
if (string.IsNullOrWhiteSpace(req.VariableCode))
throw new ServiceException("变量编码不能为空");
if (string.IsNullOrWhiteSpace(req.VariableName))
throw new ServiceException("变量名称不能为空");
if (string.IsNullOrWhiteSpace(req.ImplementationScript))
throw new ServiceException("实现脚本不能为空");
if (req.ImplementationScript.Length > 10000)
throw new ServiceException("实现脚本长度不能超过 10000 字符");
var exists = DbContext.glms_risk_variable.Any(v => v.VariableCode == req.VariableCode);
if (exists)
throw new ServiceException($"变量编码 '{req.VariableCode}' 已存在");
var entity = new glms_risk_variable
{
VariableCode = req.VariableCode,
VariableName = req.VariableName,
Category = req.Category,
DataType = req.DataType,
Unit = req.Unit,
ValueDomain = req.ValueDomain,
Description = req.Description,
ImplementationScript = req.ImplementationScript,
SourceExpression = req.SourceExpression,
IsImplemented = req.IsImplemented,
SortOrder = req.SortOrder,
Version = 1
};
SetDBModelOpt(entity);
entity.UpdateOptId = UserId;
entity.UpdateOptName = UserName;
entity.UpdateDate = DateTime.Now;
using (var transaction = DbContext.Database.BeginTransaction())
{
try
{
DbContext.glms_risk_variable.Add(entity);
DbContext.SaveChanges();
WriteAuditLog("VARIABLE_CREATE", "VARIABLE", entity.id, req.VariableName, req.VariableCode, $"创建变量:{req.VariableCode}",
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
DbContext.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
InvalidateVariableCache();
TryRefreshCache();
return GetVariableDetail(entity.id);
}
public RiskVariableDetail UpdateVariable(long variableId, UpdateRiskVariableReq req)
{
var variable = GetVariableOrThrow(variableId);
if (variable.Version != req.ExpectedVersion)
throw new ServiceException("变量已被其他用户修改,请重新加载后再编辑");
if (string.IsNullOrWhiteSpace(req.VariableName))
throw new ServiceException("变量名称不能为空");
if (string.IsNullOrWhiteSpace(req.ImplementationScript))
throw new ServiceException("实现脚本不能为空");
if (req.DataType != variable.DataType)
{
var activeRefCount = DbContext.glms_risk_rule
.Count(r => r.Status == RiskRuleStatus.Active && r.FormulaJson.Contains(variable.VariableCode));
if (activeRefCount > 0)
throw new ServiceException($"该变量被 {activeRefCount} 个生效中的规则引用,无法修改数据类型");
}
variable.VariableName = req.VariableName;
variable.Category = req.Category;
variable.DataType = req.DataType;
variable.Unit = req.Unit;
variable.ValueDomain = req.ValueDomain;
variable.Description = req.Description;
variable.ImplementationScript = req.ImplementationScript;
variable.SourceExpression = req.SourceExpression;
variable.IsImplemented = req.IsImplemented;
variable.SortOrder = req.SortOrder;
variable.Version = variable.Version + 1;
variable.UpdateOptId = UserId;
variable.UpdateOptName = UserName;
variable.UpdateDate = DateTime.Now;
WriteAuditLog("VARIABLE_UPDATE", "VARIABLE", variableId, req.VariableName, variable.VariableCode, $"修改变量:{variable.VariableCode}");
DbContext.SaveChanges();
InvalidateVariableCache();
TryRefreshCache();
return GetVariableDetail(variableId);
}
public void DeleteVariable(long variableId)
{
var variable = GetVariableOrThrow(variableId);
var activeRefCount = DbContext.glms_risk_rule
.Count(r => r.Status == RiskRuleStatus.Active && r.FormulaJson.Contains(variable.VariableCode));
if (activeRefCount > 0)
throw new ServiceException($"该变量被 {activeRefCount} 个生效中的规则引用,无法删除");
DbContext.glms_risk_variable.Remove(variable);
WriteAuditLog("VAR_DELETE", "VARIABLE", variableId, variable.VariableName, variable.VariableCode, $"删除变量:{variable.VariableCode}");
DbContext.SaveChanges();
InvalidateVariableCache();
TryRefreshCache();
}
public List<RiskVariableSimpleItem> GetAllVariableList()
{
var result = DbContext.glms_risk_variable
.Where(v => v.IsImplemented)
.OrderBy(v => v.SortOrder)
.Select(v => new RiskVariableSimpleItem
{
Id = v.id,
VariableCode = v.VariableCode,
VariableName = v.VariableName,
Category = v.Category,
DataType = v.DataType,
Unit = v.Unit
})
.ToList();
return result;
}
#endregion
#region Audit Log
public SearchListResult<RiskAuditLogListItem> QueryAuditLogs(QueryRiskAuditLogReq req)
{
var query = DbContext.glms_risk_rule_audit_log.AsQueryable();
if (!string.IsNullOrWhiteSpace(req.OperationType))
{
query = query.Where(l => l.OperationType == req.OperationType);
}
if (!string.IsNullOrWhiteSpace(req.TargetType))
{
query = query.Where(l => l.TargetType == req.TargetType);
}
if (req.StartDate.HasValue)
{
query = query.Where(l => l.OptDate >= req.StartDate.Value);
}
if (req.EndDate.HasValue)
{
query = query.Where(l => l.OptDate <= req.EndDate.Value);
}
if (!string.IsNullOrWhiteSpace(req.Keyword))
{
query = query.Where(l => l.TargetName.Contains(req.Keyword)
|| l.TargetCode.Contains(req.Keyword)
|| l.OperationDetail.Contains(req.Keyword));
}
var result = query.OrderByDescending(l => l.OptDate)
.Select(l => new RiskAuditLogListItem
{
Id = l.id,
OperationType = l.OperationType,
TargetType = l.TargetType,
TargetId = l.TargetId,
TargetName = l.TargetName,
TargetCode = l.TargetCode,
OperationDetail = l.OperationDetail,
Result = l.Result,
OptName = l.OptName,
OptDate = l.OptDate
});
return result.ToSearchList(req);
}
public byte[] ExportAuditLogs(QueryRiskAuditLogReq req)
{
throw new ServiceException("导出功能暂未实现");
}
public RiskAuditLogDetail GetAuditLogDetail(long logId)
{
var log = DbContext.glms_risk_rule_audit_log.FirstOrDefault(l => l.id == logId);
if (log == null)
throw new ServiceException("审计日志不存在");
return new RiskAuditLogDetail
{
Id = log.id,
OperationType = log.OperationType,
TargetType = log.TargetType,
TargetId = log.TargetId,
TargetName = log.TargetName,
TargetCode = log.TargetCode,
OperationDetail = log.OperationDetail,
Result = log.Result,
SnapshotData = log.SnapshotData,
OptId = log.OptId,
OptName = log.OptName,
OptDate = log.OptDate
};
}
#endregion
}
}